blob: bec9fae44604bd5a3d2b43df12ebafac1639ae56 [file] [log] [blame]
Jens Geyeraa0c8b32019-01-28 23:27:45 +01001// Licensed to the Apache Software Foundation(ASF) under one
2// or more contributor license agreements.See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License. You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied. See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18using System;
19using System.Collections.Generic;
Jens Geyeradde44b2019-02-05 01:00:02 +010020using System.Diagnostics;
Jens Geyeraa0c8b32019-01-28 23:27:45 +010021using System.IO;
22using System.Linq;
23using System.Security.Authentication;
24using System.Security.Cryptography.X509Certificates;
25using System.Text;
26using System.Threading;
27using System.Threading.Tasks;
28using Microsoft.Extensions.Logging;
29using Thrift;
30using Thrift.Collections;
31using Thrift.Processor;
32using Thrift.Protocol;
33using Thrift.Server;
34using Thrift.Transport;
35using Thrift.Transport.Server;
36
Jens Geyer828ffa82020-11-21 15:15:32 +010037#pragma warning disable IDE0063 // using can be simplified, we don't
38
Jens Geyeraa0c8b32019-01-28 23:27:45 +010039namespace ThriftTest
40{
Jens Geyeradde44b2019-02-05 01:00:02 +010041 internal enum ProtocolChoice
42 {
43 Binary,
44 Compact,
45 Json
46 }
47
Jens Geyeradde44b2019-02-05 01:00:02 +010048 internal enum TransportChoice
49 {
50 Socket,
51 TlsSocket,
52 NamedPipe
53 }
54
Kyle Smith7b94dd42019-03-23 17:26:56 +010055 internal enum BufferChoice
56 {
57 None,
58 Buffered,
59 Framed
60 }
61
Jens Geyeraa0c8b32019-01-28 23:27:45 +010062 internal class ServerParam
63 {
Kyle Smith7b94dd42019-03-23 17:26:56 +010064 internal BufferChoice buffering = BufferChoice.None;
Jens Geyeradde44b2019-02-05 01:00:02 +010065 internal ProtocolChoice protocol = ProtocolChoice.Binary;
66 internal TransportChoice transport = TransportChoice.Socket;
Jens Geyeraa0c8b32019-01-28 23:27:45 +010067 internal int port = 9090;
68 internal string pipe = null;
69
70 internal void Parse(List<string> args)
71 {
72 for (var i = 0; i < args.Count; i++)
73 {
74 if (args[i].StartsWith("--pipe="))
75 {
76 pipe = args[i].Substring(args[i].IndexOf("=") + 1);
Jens Geyeradde44b2019-02-05 01:00:02 +010077 transport = TransportChoice.NamedPipe;
Jens Geyeraa0c8b32019-01-28 23:27:45 +010078 }
79 else if (args[i].StartsWith("--port="))
80 {
81 port = int.Parse(args[i].Substring(args[i].IndexOf("=") + 1));
Jens Geyeradde44b2019-02-05 01:00:02 +010082 if(transport != TransportChoice.TlsSocket)
83 transport = TransportChoice.Socket;
Jens Geyeraa0c8b32019-01-28 23:27:45 +010084 }
85 else if (args[i] == "-b" || args[i] == "--buffered" || args[i] == "--transport=buffered")
86 {
Kyle Smith7b94dd42019-03-23 17:26:56 +010087 buffering = BufferChoice.Buffered;
Jens Geyeraa0c8b32019-01-28 23:27:45 +010088 }
89 else if (args[i] == "-f" || args[i] == "--framed" || args[i] == "--transport=framed")
90 {
Kyle Smith7b94dd42019-03-23 17:26:56 +010091 buffering = BufferChoice.Framed;
Jens Geyeraa0c8b32019-01-28 23:27:45 +010092 }
93 else if (args[i] == "--binary" || args[i] == "--protocol=binary")
94 {
Jens Geyeradde44b2019-02-05 01:00:02 +010095 protocol = ProtocolChoice.Binary;
Jens Geyeraa0c8b32019-01-28 23:27:45 +010096 }
97 else if (args[i] == "--compact" || args[i] == "--protocol=compact")
98 {
Jens Geyeradde44b2019-02-05 01:00:02 +010099 protocol = ProtocolChoice.Compact;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100100 }
101 else if (args[i] == "--json" || args[i] == "--protocol=json")
102 {
Jens Geyeradde44b2019-02-05 01:00:02 +0100103 protocol = ProtocolChoice.Json;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100104 }
105 else if (args[i] == "--threaded" || args[i] == "--server-type=threaded")
106 {
107 throw new NotImplementedException(args[i]);
108 }
109 else if (args[i] == "--threadpool" || args[i] == "--server-type=threadpool")
110 {
111 throw new NotImplementedException(args[i]);
112 }
113 else if (args[i] == "--prototype" || args[i] == "--processor=prototype")
114 {
115 throw new NotImplementedException(args[i]);
116 }
117 else if (args[i] == "--ssl")
118 {
Jens Geyeradde44b2019-02-05 01:00:02 +0100119 transport = TransportChoice.TlsSocket;
120 }
121 else if (args[i] == "--help")
122 {
123 PrintOptionsHelp();
124 return;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100125 }
126 else
127 {
Jens Geyeradde44b2019-02-05 01:00:02 +0100128 Console.WriteLine("Invalid argument: {0}", args[i]);
129 PrintOptionsHelp();
130 return;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100131 }
132 }
133
134 }
Jens Geyeradde44b2019-02-05 01:00:02 +0100135
136 internal static void PrintOptionsHelp()
137 {
138 Console.WriteLine("Server options:");
139 Console.WriteLine(" --pipe=<pipe name>");
140 Console.WriteLine(" --port=<port number>");
141 Console.WriteLine(" --transport=<transport name> one of buffered,framed (defaults to none)");
142 Console.WriteLine(" --protocol=<protocol name> one of compact,json (defaults to binary)");
143 Console.WriteLine(" --server-type=<type> one of threaded,threadpool (defaults to simple)");
144 Console.WriteLine(" --processor=<prototype>");
145 Console.WriteLine(" --ssl");
146 Console.WriteLine();
147 }
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100148 }
149
150 public class TestServer
151 {
Jens Geyeref0cb012021-04-02 12:18:15 +0200152 #pragma warning disable CA2211
153 public static int _clientID = -1; // use with Interlocked only!
154 #pragma warning restore CA2211
155
Jens Geyereacd1d42019-11-20 19:03:14 +0100156 private static readonly TConfiguration Configuration = null; // or new TConfiguration() if needed
157
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100158 public delegate void TestLogDelegate(string msg, params object[] values);
159
160 public class MyServerEventHandler : TServerEventHandler
161 {
162 public int callCount = 0;
163
164 public Task PreServeAsync(CancellationToken cancellationToken)
165 {
166 callCount++;
167 return Task.CompletedTask;
168 }
169
170 public Task<object> CreateContextAsync(TProtocol input, TProtocol output, CancellationToken cancellationToken)
171 {
172 callCount++;
173 return Task.FromResult<object>(null);
174 }
175
176 public Task DeleteContextAsync(object serverContext, TProtocol input, TProtocol output, CancellationToken cancellationToken)
177 {
178 callCount++;
179 return Task.CompletedTask;
180 }
181
182 public Task ProcessContextAsync(object serverContext, TTransport transport, CancellationToken cancellationToken)
183 {
184 callCount++;
185 return Task.CompletedTask;
186 }
187 }
188
189 public class TestHandlerAsync : ThriftTest.IAsync
190 {
Jens Geyer261cad32019-11-20 19:03:14 +0100191 public TServer Server { get; set; }
192 private readonly int handlerID;
193 private readonly StringBuilder sb = new StringBuilder();
194 private readonly TestLogDelegate logger;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100195
196 public TestHandlerAsync()
197 {
198 handlerID = Interlocked.Increment(ref _clientID);
Jens Geyer261cad32019-11-20 19:03:14 +0100199 logger += TestConsoleLogger;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100200 logger.Invoke("New TestHandler instance created");
201 }
202
Jens Geyer261cad32019-11-20 19:03:14 +0100203 public void TestConsoleLogger(string msg, params object[] values)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100204 {
205 sb.Clear();
206 sb.AppendFormat("handler{0:D3}:", handlerID);
207 sb.AppendFormat(msg, values);
208 sb.AppendLine();
209 Console.Write(sb.ToString());
210 }
211
212 public Task testVoidAsync(CancellationToken cancellationToken)
213 {
214 logger.Invoke("testVoid()");
215 return Task.CompletedTask;
216 }
217
218 public Task<string> testStringAsync(string thing, CancellationToken cancellationToken)
219 {
220 logger.Invoke("testString({0})", thing);
221 return Task.FromResult(thing);
222 }
223
224 public Task<bool> testBoolAsync(bool thing, CancellationToken cancellationToken)
225 {
226 logger.Invoke("testBool({0})", thing);
227 return Task.FromResult(thing);
228 }
229
230 public Task<sbyte> testByteAsync(sbyte thing, CancellationToken cancellationToken)
231 {
232 logger.Invoke("testByte({0})", thing);
233 return Task.FromResult(thing);
234 }
235
236 public Task<int> testI32Async(int thing, CancellationToken cancellationToken)
237 {
238 logger.Invoke("testI32({0})", thing);
239 return Task.FromResult(thing);
240 }
241
242 public Task<long> testI64Async(long thing, CancellationToken cancellationToken)
243 {
244 logger.Invoke("testI64({0})", thing);
245 return Task.FromResult(thing);
246 }
247
248 public Task<double> testDoubleAsync(double thing, CancellationToken cancellationToken)
249 {
250 logger.Invoke("testDouble({0})", thing);
251 return Task.FromResult(thing);
252 }
253
254 public Task<byte[]> testBinaryAsync(byte[] thing, CancellationToken cancellationToken)
255 {
Jens Geyerbd1a2732019-06-26 22:52:44 +0200256 logger.Invoke("testBinary({0} bytes)", thing.Length);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100257 return Task.FromResult(thing);
258 }
259
260 public Task<Xtruct> testStructAsync(Xtruct thing, CancellationToken cancellationToken)
261 {
Jens Geyerffb97e12019-12-06 23:43:08 +0100262 logger.Invoke("testStruct({{\"{0}\", {1}, {2}, {3}}})", thing.String_thing, thing.Byte_thing, thing.I32_thing, thing.I64_thing);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100263 return Task.FromResult(thing);
264 }
265
266 public Task<Xtruct2> testNestAsync(Xtruct2 nest, CancellationToken cancellationToken)
267 {
Jens Geyerffb97e12019-12-06 23:43:08 +0100268 var thing = nest.Struct_thing;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100269 logger.Invoke("testNest({{{0}, {{\"{1}\", {2}, {3}, {4}, {5}}}}})",
Jens Geyerffb97e12019-12-06 23:43:08 +0100270 nest.Byte_thing,
271 thing.String_thing,
272 thing.Byte_thing,
273 thing.I32_thing,
274 thing.I64_thing,
275 nest.I32_thing);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100276 return Task.FromResult(nest);
277 }
278
279 public Task<Dictionary<int, int>> testMapAsync(Dictionary<int, int> thing, CancellationToken cancellationToken)
280 {
281 sb.Clear();
282 sb.Append("testMap({{");
283 var first = true;
284 foreach (var key in thing.Keys)
285 {
286 if (first)
287 {
288 first = false;
289 }
290 else
291 {
292 sb.Append(", ");
293 }
294 sb.AppendFormat("{0} => {1}", key, thing[key]);
295 }
296 sb.Append("}})");
297 logger.Invoke(sb.ToString());
298 return Task.FromResult(thing);
299 }
300
301 public Task<Dictionary<string, string>> testStringMapAsync(Dictionary<string, string> thing, CancellationToken cancellationToken)
302 {
303 sb.Clear();
304 sb.Append("testStringMap({{");
305 var first = true;
306 foreach (var key in thing.Keys)
307 {
308 if (first)
309 {
310 first = false;
311 }
312 else
313 {
314 sb.Append(", ");
315 }
316 sb.AppendFormat("{0} => {1}", key, thing[key]);
317 }
318 sb.Append("}})");
319 logger.Invoke(sb.ToString());
320 return Task.FromResult(thing);
321 }
322
323 public Task<THashSet<int>> testSetAsync(THashSet<int> thing, CancellationToken cancellationToken)
324 {
325 sb.Clear();
326 sb.Append("testSet({{");
327 var first = true;
328 foreach (int elem in thing)
329 {
330 if (first)
331 {
332 first = false;
333 }
334 else
335 {
336 sb.Append(", ");
337 }
338 sb.AppendFormat("{0}", elem);
339 }
340 sb.Append("}})");
341 logger.Invoke(sb.ToString());
342 return Task.FromResult(thing);
343 }
344
345 public Task<List<int>> testListAsync(List<int> thing, CancellationToken cancellationToken)
346 {
347 sb.Clear();
348 sb.Append("testList({{");
349 var first = true;
350 foreach (var elem in thing)
351 {
352 if (first)
353 {
354 first = false;
355 }
356 else
357 {
358 sb.Append(", ");
359 }
360 sb.AppendFormat("{0}", elem);
361 }
362 sb.Append("}})");
363 logger.Invoke(sb.ToString());
364 return Task.FromResult(thing);
365 }
366
367 public Task<Numberz> testEnumAsync(Numberz thing, CancellationToken cancellationToken)
368 {
369 logger.Invoke("testEnum({0})", thing);
370 return Task.FromResult(thing);
371 }
372
373 public Task<long> testTypedefAsync(long thing, CancellationToken cancellationToken)
374 {
375 logger.Invoke("testTypedef({0})", thing);
376 return Task.FromResult(thing);
377 }
378
379 public Task<Dictionary<int, Dictionary<int, int>>> testMapMapAsync(int hello, CancellationToken cancellationToken)
380 {
381 logger.Invoke("testMapMap({0})", hello);
382 var mapmap = new Dictionary<int, Dictionary<int, int>>();
383
384 var pos = new Dictionary<int, int>();
385 var neg = new Dictionary<int, int>();
386 for (var i = 1; i < 5; i++)
387 {
388 pos[i] = i;
389 neg[-i] = -i;
390 }
391
392 mapmap[4] = pos;
393 mapmap[-4] = neg;
394
395 return Task.FromResult(mapmap);
396 }
397
398 public Task<Dictionary<long, Dictionary<Numberz, Insanity>>> testInsanityAsync(Insanity argument, CancellationToken cancellationToken)
399 {
400 logger.Invoke("testInsanity()");
401
402 /** from ThriftTest.thrift:
403 * So you think you've got this all worked, out eh?
404 *
405 * Creates a the returned map with these values and prints it out:
406 * { 1 => { 2 => argument,
407 * 3 => argument,
408 * },
409 * 2 => { 6 => <empty Insanity struct>, },
410 * }
411 * @return map<UserId, map<Numberz,Insanity>> - a map with the above values
412 */
413
414 var first_map = new Dictionary<Numberz, Insanity>();
415 var second_map = new Dictionary<Numberz, Insanity>(); ;
416
417 first_map[Numberz.TWO] = argument;
418 first_map[Numberz.THREE] = argument;
419
420 second_map[Numberz.SIX] = new Insanity();
421
422 var insane = new Dictionary<long, Dictionary<Numberz, Insanity>>
423 {
424 [1] = first_map,
425 [2] = second_map
426 };
427
428 return Task.FromResult(insane);
429 }
430
431 public Task<Xtruct> testMultiAsync(sbyte arg0, int arg1, long arg2, Dictionary<short, string> arg3, Numberz arg4, long arg5,
432 CancellationToken cancellationToken)
433 {
434 logger.Invoke("testMulti()");
435
436 var hello = new Xtruct(); ;
Jens Geyerffb97e12019-12-06 23:43:08 +0100437 hello.String_thing = "Hello2";
438 hello.Byte_thing = arg0;
439 hello.I32_thing = arg1;
440 hello.I64_thing = arg2;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100441 return Task.FromResult(hello);
442 }
443
444 public Task testExceptionAsync(string arg, CancellationToken cancellationToken)
445 {
446 logger.Invoke("testException({0})", arg);
447 if (arg == "Xception")
448 {
449 var x = new Xception
450 {
451 ErrorCode = 1001,
452 Message = arg
453 };
454 throw x;
455 }
456 if (arg == "TException")
457 {
458 throw new TException();
459 }
460 return Task.CompletedTask;
461 }
462
463 public Task<Xtruct> testMultiExceptionAsync(string arg0, string arg1, CancellationToken cancellationToken)
464 {
465 logger.Invoke("testMultiException({0}, {1})", arg0, arg1);
466 if (arg0 == "Xception")
467 {
468 var x = new Xception
469 {
470 ErrorCode = 1001,
471 Message = "This is an Xception"
472 };
473 throw x;
474 }
475
476 if (arg0 == "Xception2")
477 {
478 var x = new Xception2
479 {
480 ErrorCode = 2002,
Jens Geyerffb97e12019-12-06 23:43:08 +0100481 Struct_thing = new Xtruct { String_thing = "This is an Xception2" }
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100482 };
483 throw x;
484 }
485
Jens Geyerffb97e12019-12-06 23:43:08 +0100486 var result = new Xtruct { String_thing = arg1 };
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100487 return Task.FromResult(result);
488 }
489
490 public Task testOnewayAsync(int secondsToSleep, CancellationToken cancellationToken)
491 {
492 logger.Invoke("testOneway({0}), sleeping...", secondsToSleep);
493 Task.Delay(secondsToSleep * 1000, cancellationToken).GetAwaiter().GetResult();
494 logger.Invoke("testOneway finished");
495
496 return Task.CompletedTask;
497 }
498 }
499
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100500
501 private static X509Certificate2 GetServerCert()
502 {
503 var serverCertName = "server.p12";
504 var possiblePaths = new List<string>
505 {
506 "../../../keys/",
507 "../../keys/",
508 "../keys/",
509 "keys/",
510 };
511
512 string existingPath = null;
513 foreach (var possiblePath in possiblePaths)
514 {
515 var path = Path.GetFullPath(possiblePath + serverCertName);
516 if (File.Exists(path))
517 {
518 existingPath = path;
519 break;
520 }
521 }
522
523 if (string.IsNullOrEmpty(existingPath))
524 {
525 throw new FileNotFoundException($"Cannot find file: {serverCertName}");
526 }
527
528 var cert = new X509Certificate2(existingPath, "thrift");
529
530 return cert;
531 }
532
533 public static int Execute(List<string> args)
534 {
Jens Geyer261cad32019-11-20 19:03:14 +0100535 using (var loggerFactory = new LoggerFactory()) //.AddConsole().AddDebug();
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100536 {
Jens Geyer261cad32019-11-20 19:03:14 +0100537 var logger = loggerFactory.CreateLogger("Test");
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100538
539 try
540 {
Jens Geyer261cad32019-11-20 19:03:14 +0100541 var param = new ServerParam();
542
543 try
544 {
545 param.Parse(args);
546 }
547 catch (Exception ex)
548 {
549 Console.WriteLine("*** FAILED ***");
550 Console.WriteLine("Error while parsing arguments");
551 Console.WriteLine(ex.Message + " ST: " + ex.StackTrace);
552 return 1;
553 }
554
555
556 // Endpoint transport (mandatory)
557 TServerTransport trans;
558 switch (param.transport)
559 {
560 case TransportChoice.NamedPipe:
561 Debug.Assert(param.pipe != null);
Jens Geyeref0cb012021-04-02 12:18:15 +0200562 trans = new TNamedPipeServerTransport(param.pipe, Configuration, NamedPipeClientFlags.OnlyLocalClients);
Jens Geyer261cad32019-11-20 19:03:14 +0100563 break;
564
565
566 case TransportChoice.TlsSocket:
567 var cert = GetServerCert();
568 if (cert == null || !cert.HasPrivateKey)
569 {
570 cert?.Dispose();
571 throw new InvalidOperationException("Certificate doesn't contain private key");
572 }
573
Jens Geyereacd1d42019-11-20 19:03:14 +0100574 trans = new TTlsServerSocketTransport(param.port, Configuration,
575 cert,
Jens Geyer261cad32019-11-20 19:03:14 +0100576 (sender, certificate, chain, errors) => true,
577 null, SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12);
578 break;
579
580 case TransportChoice.Socket:
581 default:
Jens Geyereacd1d42019-11-20 19:03:14 +0100582 trans = new TServerSocketTransport(param.port, Configuration);
Jens Geyer261cad32019-11-20 19:03:14 +0100583 break;
584 }
585
586 // Layered transport (mandatory)
587 TTransportFactory transFactory = null;
588 switch (param.buffering)
589 {
590 case BufferChoice.Framed:
591 transFactory = new TFramedTransport.Factory();
592 break;
593 case BufferChoice.Buffered:
594 transFactory = new TBufferedTransport.Factory();
595 break;
596 default:
597 Debug.Assert(param.buffering == BufferChoice.None, "unhandled case");
598 transFactory = null; // no layered transprt
599 break;
600 }
601
Jens Geyer828ffa82020-11-21 15:15:32 +0100602 TProtocolFactory proto = param.protocol switch
Jens Geyer261cad32019-11-20 19:03:14 +0100603 {
Jens Geyer828ffa82020-11-21 15:15:32 +0100604 ProtocolChoice.Compact => new TCompactProtocol.Factory(),
605 ProtocolChoice.Json => new TJsonProtocol.Factory(),
606 ProtocolChoice.Binary => new TBinaryProtocol.Factory(),
607 _ => new TBinaryProtocol.Factory(),
608 };
Jens Geyer261cad32019-11-20 19:03:14 +0100609
610 // Processor
611 var testHandler = new TestHandlerAsync();
612 var testProcessor = new ThriftTest.AsyncProcessor(testHandler);
613 var processorFactory = new TSingletonProcessorFactory(testProcessor);
614
615 TServer serverEngine = new TSimpleAsyncServer(processorFactory, trans, transFactory, transFactory, proto, proto, logger);
616
617 //Server event handler
618 var serverEvents = new MyServerEventHandler();
619 serverEngine.SetEventHandler(serverEvents);
620
621 // Run it
622 var where = (!string.IsNullOrEmpty(param.pipe)) ? "on pipe " + param.pipe : "on port " + param.port;
623 Console.WriteLine("Starting the AsyncBaseServer " + where +
624 " with processor TPrototypeProcessorFactory prototype factory " +
625 (param.buffering == BufferChoice.Buffered ? " with buffered transport" : "") +
626 (param.buffering == BufferChoice.Framed ? " with framed transport" : "") +
627 (param.transport == TransportChoice.TlsSocket ? " with encryption" : "") +
628 (param.protocol == ProtocolChoice.Compact ? " with compact protocol" : "") +
629 (param.protocol == ProtocolChoice.Json ? " with json protocol" : "") +
630 "...");
631 serverEngine.ServeAsync(CancellationToken.None).GetAwaiter().GetResult();
632 Console.ReadLine();
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100633 }
Jens Geyer261cad32019-11-20 19:03:14 +0100634 catch (Exception x)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100635 {
Jens Geyer261cad32019-11-20 19:03:14 +0100636 Console.Error.Write(x);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100637 return 1;
638 }
639
Jens Geyer261cad32019-11-20 19:03:14 +0100640 Console.WriteLine("done.");
641 return 0;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100642 }
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100643 }
644 }
645
646}