blob: 3b2ed06cbc98c5b4ffdb2d44fc9c009cd09d637f [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;
20using System.Globalization;
21using System.IO;
22using System.Linq;
23using System.Text;
24using System.Threading;
25using System.Threading.Tasks;
26using Thrift.Protocol.Entities;
27using Thrift.Protocol.Utilities;
28using Thrift.Transport;
29
30namespace Thrift.Protocol
31{
32 /// <summary>
33 /// JSON protocol implementation for thrift.
34 /// This is a full-featured protocol supporting Write and Read.
35 /// Please see the C++ class header for a detailed description of the
36 /// protocol's wire format.
37 /// Adapted from the Java version.
38 /// </summary>
39 // ReSharper disable once InconsistentNaming
40 public class TJsonProtocol : TProtocol
41 {
42 private const long Version = 1;
43
44 // Temporary buffer used by several methods
45 private readonly byte[] _tempBuffer = new byte[4];
46
47 // Current context that we are in
48 protected JSONBaseContext Context;
49
50 // Stack of nested contexts that we may be in
51 protected Stack<JSONBaseContext> ContextStack = new Stack<JSONBaseContext>();
52
53 // Reader that manages a 1-byte buffer
54 protected LookaheadReader Reader;
55
56 // Default encoding
57 protected Encoding Utf8Encoding = Encoding.UTF8;
58
59 /// <summary>
60 /// TJsonProtocol Constructor
61 /// </summary>
62 public TJsonProtocol(TTransport trans)
63 : base(trans)
64 {
65 Context = new JSONBaseContext(this);
66 Reader = new LookaheadReader(this);
67 }
68
69 /// <summary>
70 /// Push a new JSON context onto the stack.
71 /// </summary>
72 protected void PushContext(JSONBaseContext c)
73 {
74 ContextStack.Push(Context);
75 Context = c;
76 }
77
78 /// <summary>
79 /// Pop the last JSON context off the stack
80 /// </summary>
81 protected void PopContext()
82 {
83 Context = ContextStack.Pop();
84 }
85
86 /// <summary>
Paulo Nevesf049ff32020-02-05 11:58:18 +010087 /// Resets the context stack to pristine state. Allows for reusal of the protocol
88 /// even in cases where the protocol instance was in an undefined state due to
89 /// dangling/stale/obsolete contexts
90 /// </summary>
91 private void resetContext()
92 {
93 ContextStack.Clear();
94 Context = new JSONBaseContext(this);
95 }
96 /// <summary>
Jens Geyeraa0c8b32019-01-28 23:27:45 +010097 /// Read a byte that must match b[0]; otherwise an exception is thrown.
98 /// Marked protected to avoid synthetic accessor in JSONListContext.Read
99 /// and JSONPairContext.Read
100 /// </summary>
101 protected async Task ReadJsonSyntaxCharAsync(byte[] bytes, CancellationToken cancellationToken)
102 {
103 var ch = await Reader.ReadAsync(cancellationToken);
104 if (ch != bytes[0])
105 {
106 throw new TProtocolException(TProtocolException.INVALID_DATA, $"Unexpected character: {(char) ch}");
107 }
108 }
109
110 /// <summary>
111 /// Write the bytes in array buf as a JSON characters, escaping as needed
112 /// </summary>
113 private async Task WriteJsonStringAsync(byte[] bytes, CancellationToken cancellationToken)
114 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200115 await Context.WriteConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100116 await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken);
117
118 var len = bytes.Length;
119 for (var i = 0; i < len; i++)
120 {
121 if ((bytes[i] & 0x00FF) >= 0x30)
122 {
123 if (bytes[i] == TJSONProtocolConstants.Backslash[0])
124 {
125 await Trans.WriteAsync(TJSONProtocolConstants.Backslash, cancellationToken);
126 await Trans.WriteAsync(TJSONProtocolConstants.Backslash, cancellationToken);
127 }
128 else
129 {
130 await Trans.WriteAsync(bytes.ToArray(), i, 1, cancellationToken);
131 }
132 }
133 else
134 {
135 _tempBuffer[0] = TJSONProtocolConstants.JsonCharTable[bytes[i]];
136 if (_tempBuffer[0] == 1)
137 {
138 await Trans.WriteAsync(bytes, i, 1, cancellationToken);
139 }
140 else if (_tempBuffer[0] > 1)
141 {
142 await Trans.WriteAsync(TJSONProtocolConstants.Backslash, cancellationToken);
143 await Trans.WriteAsync(_tempBuffer, 0, 1, cancellationToken);
144 }
145 else
146 {
147 await Trans.WriteAsync(TJSONProtocolConstants.EscSequences, cancellationToken);
148 _tempBuffer[0] = TJSONProtocolHelper.ToHexChar((byte) (bytes[i] >> 4));
149 _tempBuffer[1] = TJSONProtocolHelper.ToHexChar(bytes[i]);
150 await Trans.WriteAsync(_tempBuffer, 0, 2, cancellationToken);
151 }
152 }
153 }
154 await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken);
155 }
156
157 /// <summary>
158 /// Write out number as a JSON value. If the context dictates so, it will be
159 /// wrapped in quotes to output as a JSON string.
160 /// </summary>
161 private async Task WriteJsonIntegerAsync(long num, CancellationToken cancellationToken)
162 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200163 await Context.WriteConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100164 var str = num.ToString();
165
166 var escapeNum = Context.EscapeNumbers();
167 if (escapeNum)
168 {
169 await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken);
170 }
171
172 var bytes = Utf8Encoding.GetBytes(str);
173 await Trans.WriteAsync(bytes, cancellationToken);
174
175 if (escapeNum)
176 {
177 await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken);
178 }
179 }
180
181 /// <summary>
182 /// Write out a double as a JSON value. If it is NaN or infinity or if the
183 /// context dictates escaping, Write out as JSON string.
184 /// </summary>
185 private async Task WriteJsonDoubleAsync(double num, CancellationToken cancellationToken)
186 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200187 await Context.WriteConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100188 var str = num.ToString("G17", CultureInfo.InvariantCulture);
189 var special = false;
190
191 switch (str[0])
192 {
193 case 'N': // NaN
194 case 'I': // Infinity
195 special = true;
196 break;
197 case '-':
198 if (str[1] == 'I')
199 {
200 // -Infinity
201 special = true;
202 }
203 break;
204 }
205
206 var escapeNum = special || Context.EscapeNumbers();
207
208 if (escapeNum)
209 {
210 await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken);
211 }
212
213 await Trans.WriteAsync(Utf8Encoding.GetBytes(str), cancellationToken);
214
215 if (escapeNum)
216 {
217 await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken);
218 }
219 }
220
221 /// <summary>
222 /// Write out contents of byte array b as a JSON string with base-64 encoded
223 /// data
224 /// </summary>
225 private async Task WriteJsonBase64Async(byte[] bytes, CancellationToken cancellationToken)
226 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200227 await Context.WriteConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100228 await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken);
229
230 var len = bytes.Length;
231 var off = 0;
232
233 while (len >= 3)
234 {
235 // Encode 3 bytes at a time
236 TBase64Utils.Encode(bytes, off, 3, _tempBuffer, 0);
237 await Trans.WriteAsync(_tempBuffer, 0, 4, cancellationToken);
238 off += 3;
239 len -= 3;
240 }
241
242 if (len > 0)
243 {
244 // Encode remainder
245 TBase64Utils.Encode(bytes, off, len, _tempBuffer, 0);
246 await Trans.WriteAsync(_tempBuffer, 0, len + 1, cancellationToken);
247 }
248
249 await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken);
250 }
251
252 private async Task WriteJsonObjectStartAsync(CancellationToken cancellationToken)
253 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200254 await Context.WriteConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100255 await Trans.WriteAsync(TJSONProtocolConstants.LeftBrace, cancellationToken);
256 PushContext(new JSONPairContext(this));
257 }
258
259 private async Task WriteJsonObjectEndAsync(CancellationToken cancellationToken)
260 {
261 PopContext();
262 await Trans.WriteAsync(TJSONProtocolConstants.RightBrace, cancellationToken);
263 }
264
265 private async Task WriteJsonArrayStartAsync(CancellationToken cancellationToken)
266 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200267 await Context.WriteConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100268 await Trans.WriteAsync(TJSONProtocolConstants.LeftBracket, cancellationToken);
269 PushContext(new JSONListContext(this));
270 }
271
272 private async Task WriteJsonArrayEndAsync(CancellationToken cancellationToken)
273 {
274 PopContext();
275 await Trans.WriteAsync(TJSONProtocolConstants.RightBracket, cancellationToken);
276 }
277
278 public override async Task WriteMessageBeginAsync(TMessage message, CancellationToken cancellationToken)
279 {
Paulo Nevesf049ff32020-02-05 11:58:18 +0100280 resetContext();
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100281 await WriteJsonArrayStartAsync(cancellationToken);
282 await WriteJsonIntegerAsync(Version, cancellationToken);
283
284 var b = Utf8Encoding.GetBytes(message.Name);
285 await WriteJsonStringAsync(b, cancellationToken);
286
287 await WriteJsonIntegerAsync((long) message.Type, cancellationToken);
288 await WriteJsonIntegerAsync(message.SeqID, cancellationToken);
289 }
290
291 public override async Task WriteMessageEndAsync(CancellationToken cancellationToken)
292 {
293 await WriteJsonArrayEndAsync(cancellationToken);
294 }
295
296 public override async Task WriteStructBeginAsync(TStruct @struct, CancellationToken cancellationToken)
297 {
298 await WriteJsonObjectStartAsync(cancellationToken);
299 }
300
301 public override async Task WriteStructEndAsync(CancellationToken cancellationToken)
302 {
303 await WriteJsonObjectEndAsync(cancellationToken);
304 }
305
306 public override async Task WriteFieldBeginAsync(TField field, CancellationToken cancellationToken)
307 {
308 await WriteJsonIntegerAsync(field.ID, cancellationToken);
309 await WriteJsonObjectStartAsync(cancellationToken);
310 await WriteJsonStringAsync(TJSONProtocolHelper.GetTypeNameForTypeId(field.Type), cancellationToken);
311 }
312
313 public override async Task WriteFieldEndAsync(CancellationToken cancellationToken)
314 {
315 await WriteJsonObjectEndAsync(cancellationToken);
316 }
317
318 public override async Task WriteFieldStopAsync(CancellationToken cancellationToken)
319 {
320 if (cancellationToken.IsCancellationRequested)
321 {
322 await Task.FromCanceled(cancellationToken);
323 }
324 }
325
326 public override async Task WriteMapBeginAsync(TMap map, CancellationToken cancellationToken)
327 {
328 await WriteJsonArrayStartAsync(cancellationToken);
329 await WriteJsonStringAsync(TJSONProtocolHelper.GetTypeNameForTypeId(map.KeyType), cancellationToken);
330 await WriteJsonStringAsync(TJSONProtocolHelper.GetTypeNameForTypeId(map.ValueType), cancellationToken);
331 await WriteJsonIntegerAsync(map.Count, cancellationToken);
332 await WriteJsonObjectStartAsync(cancellationToken);
333 }
334
335 public override async Task WriteMapEndAsync(CancellationToken cancellationToken)
336 {
337 await WriteJsonObjectEndAsync(cancellationToken);
338 await WriteJsonArrayEndAsync(cancellationToken);
339 }
340
341 public override async Task WriteListBeginAsync(TList list, CancellationToken cancellationToken)
342 {
343 await WriteJsonArrayStartAsync(cancellationToken);
344 await WriteJsonStringAsync(TJSONProtocolHelper.GetTypeNameForTypeId(list.ElementType), cancellationToken);
345 await WriteJsonIntegerAsync(list.Count, cancellationToken);
346 }
347
348 public override async Task WriteListEndAsync(CancellationToken cancellationToken)
349 {
350 await WriteJsonArrayEndAsync(cancellationToken);
351 }
352
353 public override async Task WriteSetBeginAsync(TSet set, CancellationToken cancellationToken)
354 {
355 await WriteJsonArrayStartAsync(cancellationToken);
356 await WriteJsonStringAsync(TJSONProtocolHelper.GetTypeNameForTypeId(set.ElementType), cancellationToken);
357 await WriteJsonIntegerAsync(set.Count, cancellationToken);
358 }
359
360 public override async Task WriteSetEndAsync(CancellationToken cancellationToken)
361 {
362 await WriteJsonArrayEndAsync(cancellationToken);
363 }
364
365 public override async Task WriteBoolAsync(bool b, CancellationToken cancellationToken)
366 {
367 await WriteJsonIntegerAsync(b ? 1 : 0, cancellationToken);
368 }
369
370 public override async Task WriteByteAsync(sbyte b, CancellationToken cancellationToken)
371 {
372 await WriteJsonIntegerAsync(b, cancellationToken);
373 }
374
375 public override async Task WriteI16Async(short i16, CancellationToken cancellationToken)
376 {
377 await WriteJsonIntegerAsync(i16, cancellationToken);
378 }
379
380 public override async Task WriteI32Async(int i32, CancellationToken cancellationToken)
381 {
382 await WriteJsonIntegerAsync(i32, cancellationToken);
383 }
384
385 public override async Task WriteI64Async(long i64, CancellationToken cancellationToken)
386 {
387 await WriteJsonIntegerAsync(i64, cancellationToken);
388 }
389
390 public override async Task WriteDoubleAsync(double d, CancellationToken cancellationToken)
391 {
392 await WriteJsonDoubleAsync(d, cancellationToken);
393 }
394
395 public override async Task WriteStringAsync(string s, CancellationToken cancellationToken)
396 {
397 var b = Utf8Encoding.GetBytes(s);
398 await WriteJsonStringAsync(b, cancellationToken);
399 }
400
401 public override async Task WriteBinaryAsync(byte[] bytes, CancellationToken cancellationToken)
402 {
403 await WriteJsonBase64Async(bytes, cancellationToken);
404 }
405
406 /// <summary>
407 /// Read in a JSON string, unescaping as appropriate.. Skip Reading from the
408 /// context if skipContext is true.
409 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +0200410 private async ValueTask<byte[]> ReadJsonStringAsync(bool skipContext, CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100411 {
412 using (var buffer = new MemoryStream())
413 {
414 var codeunits = new List<char>();
415
416
417 if (!skipContext)
418 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200419 await Context.ReadConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100420 }
421
422 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Quote, cancellationToken);
423
424 while (true)
425 {
426 var ch = await Reader.ReadAsync(cancellationToken);
427 if (ch == TJSONProtocolConstants.Quote[0])
428 {
429 break;
430 }
431
432 // escaped?
433 if (ch != TJSONProtocolConstants.EscSequences[0])
434 {
435 await buffer.WriteAsync(new[] {ch}, 0, 1, cancellationToken);
436 continue;
437 }
438
439 // distinguish between \uXXXX and \?
440 ch = await Reader.ReadAsync(cancellationToken);
441 if (ch != TJSONProtocolConstants.EscSequences[1]) // control chars like \n
442 {
443 var off = Array.IndexOf(TJSONProtocolConstants.EscapeChars, (char) ch);
444 if (off == -1)
445 {
446 throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected control char");
447 }
448 ch = TJSONProtocolConstants.EscapeCharValues[off];
449 await buffer.WriteAsync(new[] {ch}, 0, 1, cancellationToken);
450 continue;
451 }
452
453 // it's \uXXXX
454 await Trans.ReadAllAsync(_tempBuffer, 0, 4, cancellationToken);
455
456 var wch = (short) ((TJSONProtocolHelper.ToHexVal(_tempBuffer[0]) << 12) +
457 (TJSONProtocolHelper.ToHexVal(_tempBuffer[1]) << 8) +
458 (TJSONProtocolHelper.ToHexVal(_tempBuffer[2]) << 4) +
459 TJSONProtocolHelper.ToHexVal(_tempBuffer[3]));
460
461 if (char.IsHighSurrogate((char) wch))
462 {
463 if (codeunits.Count > 0)
464 {
465 throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected low surrogate char");
466 }
467 codeunits.Add((char) wch);
468 }
469 else if (char.IsLowSurrogate((char) wch))
470 {
471 if (codeunits.Count == 0)
472 {
473 throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected high surrogate char");
474 }
475
476 codeunits.Add((char) wch);
477 var tmp = Utf8Encoding.GetBytes(codeunits.ToArray());
478 await buffer.WriteAsync(tmp, 0, tmp.Length, cancellationToken);
479 codeunits.Clear();
480 }
481 else
482 {
483 var tmp = Utf8Encoding.GetBytes(new[] {(char) wch});
484 await buffer.WriteAsync(tmp, 0, tmp.Length, cancellationToken);
485 }
486 }
487
488 if (codeunits.Count > 0)
489 {
490 throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected low surrogate char");
491 }
492
493 return buffer.ToArray();
494 }
495 }
496
497 /// <summary>
498 /// Read in a sequence of characters that are all valid in JSON numbers. Does
499 /// not do a complete regex check to validate that this is actually a number.
500 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +0200501 private async ValueTask<string> ReadJsonNumericCharsAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100502 {
503 var strbld = new StringBuilder();
504 while (true)
505 {
506 //TODO: workaround for primitive types with TJsonProtocol, think - how to rewrite into more easy form without exceptions
507 try
508 {
509 var ch = await Reader.PeekAsync(cancellationToken);
510 if (!TJSONProtocolHelper.IsJsonNumeric(ch))
511 {
512 break;
513 }
514 var c = (char)await Reader.ReadAsync(cancellationToken);
515 strbld.Append(c);
516 }
517 catch (TTransportException)
518 {
519 break;
520 }
521 }
522 return strbld.ToString();
523 }
524
525 /// <summary>
526 /// Read in a JSON number. If the context dictates, Read in enclosing quotes.
527 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +0200528 private async ValueTask<long> ReadJsonIntegerAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100529 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200530 await Context.ReadConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100531 if (Context.EscapeNumbers())
532 {
533 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Quote, cancellationToken);
534 }
535
536 var str = await ReadJsonNumericCharsAsync(cancellationToken);
537 if (Context.EscapeNumbers())
538 {
539 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Quote, cancellationToken);
540 }
541
542 try
543 {
544 return long.Parse(str);
545 }
546 catch (FormatException)
547 {
548 throw new TProtocolException(TProtocolException.INVALID_DATA, "Bad data encounted in numeric data");
549 }
550 }
551
552 /// <summary>
553 /// Read in a JSON double value. Throw if the value is not wrapped in quotes
554 /// when expected or if wrapped in quotes when not expected.
555 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +0200556 private async ValueTask<double> ReadJsonDoubleAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100557 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200558 await Context.ReadConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100559 if (await Reader.PeekAsync(cancellationToken) == TJSONProtocolConstants.Quote[0])
560 {
561 var arr = await ReadJsonStringAsync(true, cancellationToken);
562 var dub = double.Parse(Utf8Encoding.GetString(arr, 0, arr.Length), CultureInfo.InvariantCulture);
563
564 if (!Context.EscapeNumbers() && !double.IsNaN(dub) && !double.IsInfinity(dub))
565 {
566 // Throw exception -- we should not be in a string in this case
567 throw new TProtocolException(TProtocolException.INVALID_DATA, "Numeric data unexpectedly quoted");
568 }
569
570 return dub;
571 }
572
573 if (Context.EscapeNumbers())
574 {
575 // This will throw - we should have had a quote if escapeNum == true
576 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Quote, cancellationToken);
577 }
578
579 try
580 {
581 return double.Parse(await ReadJsonNumericCharsAsync(cancellationToken), CultureInfo.InvariantCulture);
582 }
583 catch (FormatException)
584 {
585 throw new TProtocolException(TProtocolException.INVALID_DATA, "Bad data encounted in numeric data");
586 }
587 }
588
589 /// <summary>
590 /// Read in a JSON string containing base-64 encoded data and decode it.
591 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +0200592 private async ValueTask<byte[]> ReadJsonBase64Async(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100593 {
594 var b = await ReadJsonStringAsync(false, cancellationToken);
595 var len = b.Length;
596 var off = 0;
597 var size = 0;
598
599 // reduce len to ignore fill bytes
600 while ((len > 0) && (b[len - 1] == '='))
601 {
602 --len;
603 }
604
605 // read & decode full byte triplets = 4 source bytes
606 while (len > 4)
607 {
608 // Decode 4 bytes at a time
609 TBase64Utils.Decode(b, off, 4, b, size); // NB: decoded in place
610 off += 4;
611 len -= 4;
612 size += 3;
613 }
614
615 // Don't decode if we hit the end or got a single leftover byte (invalid
616 // base64 but legal for skip of regular string exType)
617 if (len > 1)
618 {
619 // Decode remainder
620 TBase64Utils.Decode(b, off, len, b, size); // NB: decoded in place
621 size += len - 1;
622 }
623
624 // Sadly we must copy the byte[] (any way around this?)
625 var result = new byte[size];
626 Array.Copy(b, 0, result, 0, size);
627 return result;
628 }
629
630 private async Task ReadJsonObjectStartAsync(CancellationToken cancellationToken)
631 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200632 await Context.ReadConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100633 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.LeftBrace, cancellationToken);
634 PushContext(new JSONPairContext(this));
635 }
636
637 private async Task ReadJsonObjectEndAsync(CancellationToken cancellationToken)
638 {
639 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.RightBrace, cancellationToken);
640 PopContext();
641 }
642
643 private async Task ReadJsonArrayStartAsync(CancellationToken cancellationToken)
644 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200645 await Context.ReadConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100646 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.LeftBracket, cancellationToken);
647 PushContext(new JSONListContext(this));
648 }
649
650 private async Task ReadJsonArrayEndAsync(CancellationToken cancellationToken)
651 {
652 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.RightBracket, cancellationToken);
653 PopContext();
654 }
655
Jens Geyer5a17b132019-05-26 15:53:37 +0200656 public override async ValueTask<TMessage> ReadMessageBeginAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100657 {
658 var message = new TMessage();
Paulo Nevesf049ff32020-02-05 11:58:18 +0100659
660 resetContext();
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100661 await ReadJsonArrayStartAsync(cancellationToken);
662 if (await ReadJsonIntegerAsync(cancellationToken) != Version)
663 {
664 throw new TProtocolException(TProtocolException.BAD_VERSION, "Message contained bad version.");
665 }
666
667 var buf = await ReadJsonStringAsync(false, cancellationToken);
668 message.Name = Utf8Encoding.GetString(buf, 0, buf.Length);
669 message.Type = (TMessageType) await ReadJsonIntegerAsync(cancellationToken);
670 message.SeqID = (int) await ReadJsonIntegerAsync(cancellationToken);
671 return message;
672 }
673
674 public override async Task ReadMessageEndAsync(CancellationToken cancellationToken)
675 {
676 await ReadJsonArrayEndAsync(cancellationToken);
677 }
678
Jens Geyer5a17b132019-05-26 15:53:37 +0200679 public override async ValueTask<TStruct> ReadStructBeginAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100680 {
681 await ReadJsonObjectStartAsync(cancellationToken);
682 return new TStruct();
683 }
684
685 public override async Task ReadStructEndAsync(CancellationToken cancellationToken)
686 {
687 await ReadJsonObjectEndAsync(cancellationToken);
688 }
689
Jens Geyer5a17b132019-05-26 15:53:37 +0200690 public override async ValueTask<TField> ReadFieldBeginAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100691 {
692 var field = new TField();
693 var ch = await Reader.PeekAsync(cancellationToken);
694 if (ch == TJSONProtocolConstants.RightBrace[0])
695 {
696 field.Type = TType.Stop;
697 }
698 else
699 {
700 field.ID = (short) await ReadJsonIntegerAsync(cancellationToken);
701 await ReadJsonObjectStartAsync(cancellationToken);
702 field.Type = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken));
703 }
704 return field;
705 }
706
707 public override async Task ReadFieldEndAsync(CancellationToken cancellationToken)
708 {
709 await ReadJsonObjectEndAsync(cancellationToken);
710 }
711
Jens Geyer5a17b132019-05-26 15:53:37 +0200712 public override async ValueTask<TMap> ReadMapBeginAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100713 {
714 var map = new TMap();
715 await ReadJsonArrayStartAsync(cancellationToken);
716 map.KeyType = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken));
717 map.ValueType = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken));
718 map.Count = (int) await ReadJsonIntegerAsync(cancellationToken);
Jens Geyer50806452019-11-23 01:55:58 +0100719 CheckReadBytesAvailable(map);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100720 await ReadJsonObjectStartAsync(cancellationToken);
721 return map;
722 }
723
724 public override async Task ReadMapEndAsync(CancellationToken cancellationToken)
725 {
726 await ReadJsonObjectEndAsync(cancellationToken);
727 await ReadJsonArrayEndAsync(cancellationToken);
728 }
729
Jens Geyer5a17b132019-05-26 15:53:37 +0200730 public override async ValueTask<TList> ReadListBeginAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100731 {
732 var list = new TList();
733 await ReadJsonArrayStartAsync(cancellationToken);
734 list.ElementType = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken));
735 list.Count = (int) await ReadJsonIntegerAsync(cancellationToken);
Jens Geyer50806452019-11-23 01:55:58 +0100736 CheckReadBytesAvailable(list);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100737 return list;
738 }
739
740 public override async Task ReadListEndAsync(CancellationToken cancellationToken)
741 {
742 await ReadJsonArrayEndAsync(cancellationToken);
743 }
744
Jens Geyer5a17b132019-05-26 15:53:37 +0200745 public override async ValueTask<TSet> ReadSetBeginAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100746 {
747 var set = new TSet();
748 await ReadJsonArrayStartAsync(cancellationToken);
749 set.ElementType = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken));
750 set.Count = (int) await ReadJsonIntegerAsync(cancellationToken);
Jens Geyer50806452019-11-23 01:55:58 +0100751 CheckReadBytesAvailable(set);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100752 return set;
753 }
754
755 public override async Task ReadSetEndAsync(CancellationToken cancellationToken)
756 {
757 await ReadJsonArrayEndAsync(cancellationToken);
758 }
759
Jens Geyer5a17b132019-05-26 15:53:37 +0200760 public override async ValueTask<bool> ReadBoolAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100761 {
762 return await ReadJsonIntegerAsync(cancellationToken) != 0;
763 }
764
Jens Geyer5a17b132019-05-26 15:53:37 +0200765 public override async ValueTask<sbyte> ReadByteAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100766 {
767 return (sbyte) await ReadJsonIntegerAsync(cancellationToken);
768 }
769
Jens Geyer5a17b132019-05-26 15:53:37 +0200770 public override async ValueTask<short> ReadI16Async(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100771 {
772 return (short) await ReadJsonIntegerAsync(cancellationToken);
773 }
774
Jens Geyer5a17b132019-05-26 15:53:37 +0200775 public override async ValueTask<int> ReadI32Async(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100776 {
777 return (int) await ReadJsonIntegerAsync(cancellationToken);
778 }
779
Jens Geyer5a17b132019-05-26 15:53:37 +0200780 public override async ValueTask<long> ReadI64Async(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100781 {
782 return await ReadJsonIntegerAsync(cancellationToken);
783 }
784
Jens Geyer5a17b132019-05-26 15:53:37 +0200785 public override async ValueTask<double> ReadDoubleAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100786 {
787 return await ReadJsonDoubleAsync(cancellationToken);
788 }
789
Jens Geyer5a17b132019-05-26 15:53:37 +0200790 public override async ValueTask<string> ReadStringAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100791 {
792 var buf = await ReadJsonStringAsync(false, cancellationToken);
793 return Utf8Encoding.GetString(buf, 0, buf.Length);
794 }
795
Jens Geyer5a17b132019-05-26 15:53:37 +0200796 public override async ValueTask<byte[]> ReadBinaryAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100797 {
798 return await ReadJsonBase64Async(cancellationToken);
799 }
800
Jens Geyer50806452019-11-23 01:55:58 +0100801 // Return the minimum number of bytes a type will consume on the wire
802 public override int GetMinSerializedSize(TType type)
803 {
804 switch (type)
805 {
806 case TType.Stop: return 0;
807 case TType.Void: return 0;
808 case TType.Bool: return 1; // written as int
809 case TType.Byte: return 1;
810 case TType.Double: return 1;
811 case TType.I16: return 1;
812 case TType.I32: return 1;
813 case TType.I64: return 1;
814 case TType.String: return 2; // empty string
815 case TType.Struct: return 2; // empty struct
816 case TType.Map: return 2; // empty map
817 case TType.Set: return 2; // empty set
818 case TType.List: return 2; // empty list
819 default: throw new TTransportException(TTransportException.ExceptionType.Unknown, "unrecognized type code");
820 }
821 }
822
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100823 /// <summary>
824 /// Factory for JSON protocol objects
825 /// </summary>
Jens Geyer421444f2019-03-20 22:13:25 +0100826 public class Factory : TProtocolFactory
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100827 {
Jens Geyer421444f2019-03-20 22:13:25 +0100828 public override TProtocol GetProtocol(TTransport trans)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100829 {
830 return new TJsonProtocol(trans);
831 }
832 }
833
834 /// <summary>
835 /// Base class for tracking JSON contexts that may require
836 /// inserting/Reading additional JSON syntax characters
837 /// This base context does nothing.
838 /// </summary>
839 protected class JSONBaseContext
840 {
841 protected TJsonProtocol Proto;
842
843 public JSONBaseContext(TJsonProtocol proto)
844 {
845 Proto = proto;
846 }
847
Jens Geyer2ff952b2019-04-13 19:46:54 +0200848 public virtual async Task WriteConditionalDelimiterAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100849 {
850 if (cancellationToken.IsCancellationRequested)
851 {
852 await Task.FromCanceled(cancellationToken);
853 }
854 }
855
Jens Geyer2ff952b2019-04-13 19:46:54 +0200856 public virtual async Task ReadConditionalDelimiterAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100857 {
858 if (cancellationToken.IsCancellationRequested)
859 {
860 await Task.FromCanceled(cancellationToken);
861 }
862 }
863
864 public virtual bool EscapeNumbers()
865 {
866 return false;
867 }
868 }
869
870 /// <summary>
871 /// Context for JSON lists. Will insert/Read commas before each item except
872 /// for the first one
873 /// </summary>
874 protected class JSONListContext : JSONBaseContext
875 {
876 private bool _first = true;
877
878 public JSONListContext(TJsonProtocol protocol)
879 : base(protocol)
880 {
881 }
882
Jens Geyer2ff952b2019-04-13 19:46:54 +0200883 public override async Task WriteConditionalDelimiterAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100884 {
885 if (_first)
886 {
887 _first = false;
888 }
889 else
890 {
891 await Proto.Trans.WriteAsync(TJSONProtocolConstants.Comma, cancellationToken);
892 }
893 }
894
Jens Geyer2ff952b2019-04-13 19:46:54 +0200895 public override async Task ReadConditionalDelimiterAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100896 {
897 if (_first)
898 {
899 _first = false;
900 }
901 else
902 {
903 await Proto.ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Comma, cancellationToken);
904 }
905 }
906 }
907
908 /// <summary>
909 /// Context for JSON records. Will insert/Read colons before the value portion
910 /// of each record pair, and commas before each key except the first. In
911 /// addition, will indicate that numbers in the key position need to be
912 /// escaped in quotes (since JSON keys must be strings).
913 /// </summary>
914 // ReSharper disable once InconsistentNaming
915 protected class JSONPairContext : JSONBaseContext
916 {
917 private bool _colon = true;
918
919 private bool _first = true;
920
921 public JSONPairContext(TJsonProtocol proto)
922 : base(proto)
923 {
924 }
925
Jens Geyer2ff952b2019-04-13 19:46:54 +0200926 public override async Task WriteConditionalDelimiterAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100927 {
928 if (_first)
929 {
930 _first = false;
931 _colon = true;
932 }
933 else
934 {
935 await Proto.Trans.WriteAsync(_colon ? TJSONProtocolConstants.Colon : TJSONProtocolConstants.Comma, cancellationToken);
936 _colon = !_colon;
937 }
938 }
939
Jens Geyer2ff952b2019-04-13 19:46:54 +0200940 public override async Task ReadConditionalDelimiterAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100941 {
942 if (_first)
943 {
944 _first = false;
945 _colon = true;
946 }
947 else
948 {
949 await Proto.ReadJsonSyntaxCharAsync(_colon ? TJSONProtocolConstants.Colon : TJSONProtocolConstants.Comma, cancellationToken);
950 _colon = !_colon;
951 }
952 }
953
954 public override bool EscapeNumbers()
955 {
956 return _colon;
957 }
958 }
959
960 /// <summary>
961 /// Holds up to one byte from the transport
962 /// </summary>
963 protected class LookaheadReader
964 {
965 private readonly byte[] _data = new byte[1];
966
967 private bool _hasData;
968 protected TJsonProtocol Proto;
969
970 public LookaheadReader(TJsonProtocol proto)
971 {
972 Proto = proto;
973 }
974
975 /// <summary>
976 /// Return and consume the next byte to be Read, either taking it from the
977 /// data buffer if present or getting it from the transport otherwise.
978 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +0200979 public async ValueTask<byte> ReadAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100980 {
981 if (cancellationToken.IsCancellationRequested)
982 {
983 return await Task.FromCanceled<byte>(cancellationToken);
984 }
985
986 if (_hasData)
987 {
988 _hasData = false;
989 }
990 else
991 {
992 // find more easy way to avoid exception on reading primitive types
993 await Proto.Trans.ReadAllAsync(_data, 0, 1, cancellationToken);
994 }
995 return _data[0];
996 }
997
998 /// <summary>
999 /// Return the next byte to be Read without consuming, filling the data
1000 /// buffer if it has not been filled alReady.
1001 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +02001002 public async ValueTask<byte> PeekAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +01001003 {
1004 if (cancellationToken.IsCancellationRequested)
1005 {
1006 return await Task.FromCanceled<byte>(cancellationToken);
1007 }
1008
1009 if (!_hasData)
1010 {
1011 // find more easy way to avoid exception on reading primitive types
1012 await Proto.Trans.ReadAllAsync(_data, 0, 1, cancellationToken);
Jens Geyer5a17b132019-05-26 15:53:37 +02001013 _hasData = true;
Jens Geyeraa0c8b32019-01-28 23:27:45 +01001014 }
Jens Geyeraa0c8b32019-01-28 23:27:45 +01001015 return _data[0];
1016 }
1017 }
1018 }
1019}