blob: 493d89deeded795e2d7fed11eaebcceebdf66565 [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
37namespace ThriftTest
38{
Jens Geyeradde44b2019-02-05 01:00:02 +010039 internal enum ProtocolChoice
40 {
41 Binary,
42 Compact,
43 Json
44 }
45
Jens Geyeradde44b2019-02-05 01:00:02 +010046 internal enum TransportChoice
47 {
48 Socket,
49 TlsSocket,
50 NamedPipe
51 }
52
Kyle Smith7b94dd42019-03-23 17:26:56 +010053 internal enum BufferChoice
54 {
55 None,
56 Buffered,
57 Framed
58 }
59
Jens Geyeraa0c8b32019-01-28 23:27:45 +010060 internal class ServerParam
61 {
Kyle Smith7b94dd42019-03-23 17:26:56 +010062 internal BufferChoice buffering = BufferChoice.None;
Jens Geyeradde44b2019-02-05 01:00:02 +010063 internal ProtocolChoice protocol = ProtocolChoice.Binary;
64 internal TransportChoice transport = TransportChoice.Socket;
Jens Geyeraa0c8b32019-01-28 23:27:45 +010065 internal int port = 9090;
66 internal string pipe = null;
67
68 internal void Parse(List<string> args)
69 {
70 for (var i = 0; i < args.Count; i++)
71 {
72 if (args[i].StartsWith("--pipe="))
73 {
74 pipe = args[i].Substring(args[i].IndexOf("=") + 1);
Jens Geyeradde44b2019-02-05 01:00:02 +010075 transport = TransportChoice.NamedPipe;
Jens Geyeraa0c8b32019-01-28 23:27:45 +010076 }
77 else if (args[i].StartsWith("--port="))
78 {
79 port = int.Parse(args[i].Substring(args[i].IndexOf("=") + 1));
Jens Geyeradde44b2019-02-05 01:00:02 +010080 if(transport != TransportChoice.TlsSocket)
81 transport = TransportChoice.Socket;
Jens Geyeraa0c8b32019-01-28 23:27:45 +010082 }
83 else if (args[i] == "-b" || args[i] == "--buffered" || args[i] == "--transport=buffered")
84 {
Kyle Smith7b94dd42019-03-23 17:26:56 +010085 buffering = BufferChoice.Buffered;
Jens Geyeraa0c8b32019-01-28 23:27:45 +010086 }
87 else if (args[i] == "-f" || args[i] == "--framed" || args[i] == "--transport=framed")
88 {
Kyle Smith7b94dd42019-03-23 17:26:56 +010089 buffering = BufferChoice.Framed;
Jens Geyeraa0c8b32019-01-28 23:27:45 +010090 }
91 else if (args[i] == "--binary" || args[i] == "--protocol=binary")
92 {
Jens Geyeradde44b2019-02-05 01:00:02 +010093 protocol = ProtocolChoice.Binary;
Jens Geyeraa0c8b32019-01-28 23:27:45 +010094 }
95 else if (args[i] == "--compact" || args[i] == "--protocol=compact")
96 {
Jens Geyeradde44b2019-02-05 01:00:02 +010097 protocol = ProtocolChoice.Compact;
Jens Geyeraa0c8b32019-01-28 23:27:45 +010098 }
99 else if (args[i] == "--json" || args[i] == "--protocol=json")
100 {
Jens Geyeradde44b2019-02-05 01:00:02 +0100101 protocol = ProtocolChoice.Json;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100102 }
103 else if (args[i] == "--threaded" || args[i] == "--server-type=threaded")
104 {
105 throw new NotImplementedException(args[i]);
106 }
107 else if (args[i] == "--threadpool" || args[i] == "--server-type=threadpool")
108 {
109 throw new NotImplementedException(args[i]);
110 }
111 else if (args[i] == "--prototype" || args[i] == "--processor=prototype")
112 {
113 throw new NotImplementedException(args[i]);
114 }
115 else if (args[i] == "--ssl")
116 {
Jens Geyeradde44b2019-02-05 01:00:02 +0100117 transport = TransportChoice.TlsSocket;
118 }
119 else if (args[i] == "--help")
120 {
121 PrintOptionsHelp();
122 return;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100123 }
124 else
125 {
Jens Geyeradde44b2019-02-05 01:00:02 +0100126 Console.WriteLine("Invalid argument: {0}", args[i]);
127 PrintOptionsHelp();
128 return;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100129 }
130 }
131
132 }
Jens Geyeradde44b2019-02-05 01:00:02 +0100133
134 internal static void PrintOptionsHelp()
135 {
136 Console.WriteLine("Server options:");
137 Console.WriteLine(" --pipe=<pipe name>");
138 Console.WriteLine(" --port=<port number>");
139 Console.WriteLine(" --transport=<transport name> one of buffered,framed (defaults to none)");
140 Console.WriteLine(" --protocol=<protocol name> one of compact,json (defaults to binary)");
141 Console.WriteLine(" --server-type=<type> one of threaded,threadpool (defaults to simple)");
142 Console.WriteLine(" --processor=<prototype>");
143 Console.WriteLine(" --ssl");
144 Console.WriteLine();
145 }
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100146 }
147
148 public class TestServer
149 {
150 public static int _clientID = -1;
Jens Geyereacd1d42019-11-20 19:03:14 +0100151 private static readonly TConfiguration Configuration = null; // or new TConfiguration() if needed
152
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100153 public delegate void TestLogDelegate(string msg, params object[] values);
154
155 public class MyServerEventHandler : TServerEventHandler
156 {
157 public int callCount = 0;
158
159 public Task PreServeAsync(CancellationToken cancellationToken)
160 {
161 callCount++;
162 return Task.CompletedTask;
163 }
164
165 public Task<object> CreateContextAsync(TProtocol input, TProtocol output, CancellationToken cancellationToken)
166 {
167 callCount++;
168 return Task.FromResult<object>(null);
169 }
170
171 public Task DeleteContextAsync(object serverContext, TProtocol input, TProtocol output, CancellationToken cancellationToken)
172 {
173 callCount++;
174 return Task.CompletedTask;
175 }
176
177 public Task ProcessContextAsync(object serverContext, TTransport transport, CancellationToken cancellationToken)
178 {
179 callCount++;
180 return Task.CompletedTask;
181 }
182 }
183
184 public class TestHandlerAsync : ThriftTest.IAsync
185 {
Jens Geyer261cad32019-11-20 19:03:14 +0100186 public TServer Server { get; set; }
187 private readonly int handlerID;
188 private readonly StringBuilder sb = new StringBuilder();
189 private readonly TestLogDelegate logger;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100190
191 public TestHandlerAsync()
192 {
193 handlerID = Interlocked.Increment(ref _clientID);
Jens Geyer261cad32019-11-20 19:03:14 +0100194 logger += TestConsoleLogger;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100195 logger.Invoke("New TestHandler instance created");
196 }
197
Jens Geyer261cad32019-11-20 19:03:14 +0100198 public void TestConsoleLogger(string msg, params object[] values)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100199 {
200 sb.Clear();
201 sb.AppendFormat("handler{0:D3}:", handlerID);
202 sb.AppendFormat(msg, values);
203 sb.AppendLine();
204 Console.Write(sb.ToString());
205 }
206
207 public Task testVoidAsync(CancellationToken cancellationToken)
208 {
209 logger.Invoke("testVoid()");
210 return Task.CompletedTask;
211 }
212
213 public Task<string> testStringAsync(string thing, CancellationToken cancellationToken)
214 {
215 logger.Invoke("testString({0})", thing);
216 return Task.FromResult(thing);
217 }
218
219 public Task<bool> testBoolAsync(bool thing, CancellationToken cancellationToken)
220 {
221 logger.Invoke("testBool({0})", thing);
222 return Task.FromResult(thing);
223 }
224
225 public Task<sbyte> testByteAsync(sbyte thing, CancellationToken cancellationToken)
226 {
227 logger.Invoke("testByte({0})", thing);
228 return Task.FromResult(thing);
229 }
230
231 public Task<int> testI32Async(int thing, CancellationToken cancellationToken)
232 {
233 logger.Invoke("testI32({0})", thing);
234 return Task.FromResult(thing);
235 }
236
237 public Task<long> testI64Async(long thing, CancellationToken cancellationToken)
238 {
239 logger.Invoke("testI64({0})", thing);
240 return Task.FromResult(thing);
241 }
242
243 public Task<double> testDoubleAsync(double thing, CancellationToken cancellationToken)
244 {
245 logger.Invoke("testDouble({0})", thing);
246 return Task.FromResult(thing);
247 }
248
249 public Task<byte[]> testBinaryAsync(byte[] thing, CancellationToken cancellationToken)
250 {
Jens Geyerbd1a2732019-06-26 22:52:44 +0200251 logger.Invoke("testBinary({0} bytes)", thing.Length);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100252 return Task.FromResult(thing);
253 }
254
255 public Task<Xtruct> testStructAsync(Xtruct thing, CancellationToken cancellationToken)
256 {
Paulo Neves4266fae2019-11-24 22:46:37 +0100257 logger.Invoke("testStruct({{\"{0}\", {1}, {2}, {3}}})", thing.StringThing, thing.ByteThing, thing.I32Thing, thing.I64Thing);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100258 return Task.FromResult(thing);
259 }
260
261 public Task<Xtruct2> testNestAsync(Xtruct2 nest, CancellationToken cancellationToken)
262 {
Paulo Neves4266fae2019-11-24 22:46:37 +0100263 var thing = nest.StructThing;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100264 logger.Invoke("testNest({{{0}, {{\"{1}\", {2}, {3}, {4}, {5}}}}})",
Paulo Neves4266fae2019-11-24 22:46:37 +0100265 nest.ByteThing,
266 thing.StringThing,
267 thing.ByteThing,
268 thing.I32Thing,
269 thing.I64Thing,
270 nest.I32Thing);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100271 return Task.FromResult(nest);
272 }
273
274 public Task<Dictionary<int, int>> testMapAsync(Dictionary<int, int> thing, CancellationToken cancellationToken)
275 {
276 sb.Clear();
277 sb.Append("testMap({{");
278 var first = true;
279 foreach (var key in thing.Keys)
280 {
281 if (first)
282 {
283 first = false;
284 }
285 else
286 {
287 sb.Append(", ");
288 }
289 sb.AppendFormat("{0} => {1}", key, thing[key]);
290 }
291 sb.Append("}})");
292 logger.Invoke(sb.ToString());
293 return Task.FromResult(thing);
294 }
295
296 public Task<Dictionary<string, string>> testStringMapAsync(Dictionary<string, string> thing, CancellationToken cancellationToken)
297 {
298 sb.Clear();
299 sb.Append("testStringMap({{");
300 var first = true;
301 foreach (var key in thing.Keys)
302 {
303 if (first)
304 {
305 first = false;
306 }
307 else
308 {
309 sb.Append(", ");
310 }
311 sb.AppendFormat("{0} => {1}", key, thing[key]);
312 }
313 sb.Append("}})");
314 logger.Invoke(sb.ToString());
315 return Task.FromResult(thing);
316 }
317
318 public Task<THashSet<int>> testSetAsync(THashSet<int> thing, CancellationToken cancellationToken)
319 {
320 sb.Clear();
321 sb.Append("testSet({{");
322 var first = true;
323 foreach (int elem in thing)
324 {
325 if (first)
326 {
327 first = false;
328 }
329 else
330 {
331 sb.Append(", ");
332 }
333 sb.AppendFormat("{0}", elem);
334 }
335 sb.Append("}})");
336 logger.Invoke(sb.ToString());
337 return Task.FromResult(thing);
338 }
339
340 public Task<List<int>> testListAsync(List<int> thing, CancellationToken cancellationToken)
341 {
342 sb.Clear();
343 sb.Append("testList({{");
344 var first = true;
345 foreach (var elem in thing)
346 {
347 if (first)
348 {
349 first = false;
350 }
351 else
352 {
353 sb.Append(", ");
354 }
355 sb.AppendFormat("{0}", elem);
356 }
357 sb.Append("}})");
358 logger.Invoke(sb.ToString());
359 return Task.FromResult(thing);
360 }
361
362 public Task<Numberz> testEnumAsync(Numberz thing, CancellationToken cancellationToken)
363 {
364 logger.Invoke("testEnum({0})", thing);
365 return Task.FromResult(thing);
366 }
367
368 public Task<long> testTypedefAsync(long thing, CancellationToken cancellationToken)
369 {
370 logger.Invoke("testTypedef({0})", thing);
371 return Task.FromResult(thing);
372 }
373
374 public Task<Dictionary<int, Dictionary<int, int>>> testMapMapAsync(int hello, CancellationToken cancellationToken)
375 {
376 logger.Invoke("testMapMap({0})", hello);
377 var mapmap = new Dictionary<int, Dictionary<int, int>>();
378
379 var pos = new Dictionary<int, int>();
380 var neg = new Dictionary<int, int>();
381 for (var i = 1; i < 5; i++)
382 {
383 pos[i] = i;
384 neg[-i] = -i;
385 }
386
387 mapmap[4] = pos;
388 mapmap[-4] = neg;
389
390 return Task.FromResult(mapmap);
391 }
392
393 public Task<Dictionary<long, Dictionary<Numberz, Insanity>>> testInsanityAsync(Insanity argument, CancellationToken cancellationToken)
394 {
395 logger.Invoke("testInsanity()");
396
397 /** from ThriftTest.thrift:
398 * So you think you've got this all worked, out eh?
399 *
400 * Creates a the returned map with these values and prints it out:
401 * { 1 => { 2 => argument,
402 * 3 => argument,
403 * },
404 * 2 => { 6 => <empty Insanity struct>, },
405 * }
406 * @return map<UserId, map<Numberz,Insanity>> - a map with the above values
407 */
408
409 var first_map = new Dictionary<Numberz, Insanity>();
410 var second_map = new Dictionary<Numberz, Insanity>(); ;
411
412 first_map[Numberz.TWO] = argument;
413 first_map[Numberz.THREE] = argument;
414
415 second_map[Numberz.SIX] = new Insanity();
416
417 var insane = new Dictionary<long, Dictionary<Numberz, Insanity>>
418 {
419 [1] = first_map,
420 [2] = second_map
421 };
422
423 return Task.FromResult(insane);
424 }
425
426 public Task<Xtruct> testMultiAsync(sbyte arg0, int arg1, long arg2, Dictionary<short, string> arg3, Numberz arg4, long arg5,
427 CancellationToken cancellationToken)
428 {
429 logger.Invoke("testMulti()");
430
431 var hello = new Xtruct(); ;
Paulo Neves4266fae2019-11-24 22:46:37 +0100432 hello.StringThing = "Hello2";
433 hello.ByteThing = arg0;
434 hello.I32Thing = arg1;
435 hello.I64Thing = arg2;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100436 return Task.FromResult(hello);
437 }
438
439 public Task testExceptionAsync(string arg, CancellationToken cancellationToken)
440 {
441 logger.Invoke("testException({0})", arg);
442 if (arg == "Xception")
443 {
444 var x = new Xception
445 {
446 ErrorCode = 1001,
447 Message = arg
448 };
449 throw x;
450 }
451 if (arg == "TException")
452 {
453 throw new TException();
454 }
455 return Task.CompletedTask;
456 }
457
458 public Task<Xtruct> testMultiExceptionAsync(string arg0, string arg1, CancellationToken cancellationToken)
459 {
460 logger.Invoke("testMultiException({0}, {1})", arg0, arg1);
461 if (arg0 == "Xception")
462 {
463 var x = new Xception
464 {
465 ErrorCode = 1001,
466 Message = "This is an Xception"
467 };
468 throw x;
469 }
470
471 if (arg0 == "Xception2")
472 {
473 var x = new Xception2
474 {
475 ErrorCode = 2002,
Paulo Neves4266fae2019-11-24 22:46:37 +0100476 StructThing = new Xtruct { StringThing = "This is an Xception2" }
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100477 };
478 throw x;
479 }
480
Paulo Neves4266fae2019-11-24 22:46:37 +0100481 var result = new Xtruct { StringThing = arg1 };
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100482 return Task.FromResult(result);
483 }
484
485 public Task testOnewayAsync(int secondsToSleep, CancellationToken cancellationToken)
486 {
487 logger.Invoke("testOneway({0}), sleeping...", secondsToSleep);
488 Task.Delay(secondsToSleep * 1000, cancellationToken).GetAwaiter().GetResult();
489 logger.Invoke("testOneway finished");
490
491 return Task.CompletedTask;
492 }
493 }
494
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100495
496 private static X509Certificate2 GetServerCert()
497 {
498 var serverCertName = "server.p12";
499 var possiblePaths = new List<string>
500 {
501 "../../../keys/",
502 "../../keys/",
503 "../keys/",
504 "keys/",
505 };
506
507 string existingPath = null;
508 foreach (var possiblePath in possiblePaths)
509 {
510 var path = Path.GetFullPath(possiblePath + serverCertName);
511 if (File.Exists(path))
512 {
513 existingPath = path;
514 break;
515 }
516 }
517
518 if (string.IsNullOrEmpty(existingPath))
519 {
520 throw new FileNotFoundException($"Cannot find file: {serverCertName}");
521 }
522
523 var cert = new X509Certificate2(existingPath, "thrift");
524
525 return cert;
526 }
527
528 public static int Execute(List<string> args)
529 {
Jens Geyer261cad32019-11-20 19:03:14 +0100530 using (var loggerFactory = new LoggerFactory()) //.AddConsole().AddDebug();
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100531 {
Jens Geyer261cad32019-11-20 19:03:14 +0100532 var logger = loggerFactory.CreateLogger("Test");
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100533
534 try
535 {
Jens Geyer261cad32019-11-20 19:03:14 +0100536 var param = new ServerParam();
537
538 try
539 {
540 param.Parse(args);
541 }
542 catch (Exception ex)
543 {
544 Console.WriteLine("*** FAILED ***");
545 Console.WriteLine("Error while parsing arguments");
546 Console.WriteLine(ex.Message + " ST: " + ex.StackTrace);
547 return 1;
548 }
549
550
551 // Endpoint transport (mandatory)
552 TServerTransport trans;
553 switch (param.transport)
554 {
555 case TransportChoice.NamedPipe:
556 Debug.Assert(param.pipe != null);
Jens Geyereacd1d42019-11-20 19:03:14 +0100557 trans = new TNamedPipeServerTransport(param.pipe, Configuration);
Jens Geyer261cad32019-11-20 19:03:14 +0100558 break;
559
560
561 case TransportChoice.TlsSocket:
562 var cert = GetServerCert();
563 if (cert == null || !cert.HasPrivateKey)
564 {
565 cert?.Dispose();
566 throw new InvalidOperationException("Certificate doesn't contain private key");
567 }
568
Jens Geyereacd1d42019-11-20 19:03:14 +0100569 trans = new TTlsServerSocketTransport(param.port, Configuration,
570 cert,
Jens Geyer261cad32019-11-20 19:03:14 +0100571 (sender, certificate, chain, errors) => true,
572 null, SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12);
573 break;
574
575 case TransportChoice.Socket:
576 default:
Jens Geyereacd1d42019-11-20 19:03:14 +0100577 trans = new TServerSocketTransport(param.port, Configuration);
Jens Geyer261cad32019-11-20 19:03:14 +0100578 break;
579 }
580
581 // Layered transport (mandatory)
582 TTransportFactory transFactory = null;
583 switch (param.buffering)
584 {
585 case BufferChoice.Framed:
586 transFactory = new TFramedTransport.Factory();
587 break;
588 case BufferChoice.Buffered:
589 transFactory = new TBufferedTransport.Factory();
590 break;
591 default:
592 Debug.Assert(param.buffering == BufferChoice.None, "unhandled case");
593 transFactory = null; // no layered transprt
594 break;
595 }
596
597 // Protocol (mandatory)
598 TProtocolFactory proto;
599 switch (param.protocol)
600 {
601 case ProtocolChoice.Compact:
602 proto = new TCompactProtocol.Factory();
603 break;
604 case ProtocolChoice.Json:
605 proto = new TJsonProtocol.Factory();
606 break;
607 case ProtocolChoice.Binary:
608 default:
609 proto = new TBinaryProtocol.Factory();
610 break;
611 }
612
613 // Processor
614 var testHandler = new TestHandlerAsync();
615 var testProcessor = new ThriftTest.AsyncProcessor(testHandler);
616 var processorFactory = new TSingletonProcessorFactory(testProcessor);
617
618 TServer serverEngine = new TSimpleAsyncServer(processorFactory, trans, transFactory, transFactory, proto, proto, logger);
619
620 //Server event handler
621 var serverEvents = new MyServerEventHandler();
622 serverEngine.SetEventHandler(serverEvents);
623
624 // Run it
625 var where = (!string.IsNullOrEmpty(param.pipe)) ? "on pipe " + param.pipe : "on port " + param.port;
626 Console.WriteLine("Starting the AsyncBaseServer " + where +
627 " with processor TPrototypeProcessorFactory prototype factory " +
628 (param.buffering == BufferChoice.Buffered ? " with buffered transport" : "") +
629 (param.buffering == BufferChoice.Framed ? " with framed transport" : "") +
630 (param.transport == TransportChoice.TlsSocket ? " with encryption" : "") +
631 (param.protocol == ProtocolChoice.Compact ? " with compact protocol" : "") +
632 (param.protocol == ProtocolChoice.Json ? " with json protocol" : "") +
633 "...");
634 serverEngine.ServeAsync(CancellationToken.None).GetAwaiter().GetResult();
635 Console.ReadLine();
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100636 }
Jens Geyer261cad32019-11-20 19:03:14 +0100637 catch (Exception x)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100638 {
Jens Geyer261cad32019-11-20 19:03:14 +0100639 Console.Error.Write(x);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100640 return 1;
641 }
642
Jens Geyer261cad32019-11-20 19:03:14 +0100643 Console.WriteLine("done.");
644 return 0;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100645 }
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100646 }
647 }
648
649}