blob: 2f1ccdb8de83d02c31aac37e2d0e5d26587c11b7 [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 {
Jens Geyerdce22992020-05-16 23:02:27 +0200130 await Trans.WriteAsync(bytes, i, 1, cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100131 }
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
Jens Geyerdce22992020-05-16 23:02:27 +0200318 public override Task WriteFieldStopAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100319 {
Jens Geyerdce22992020-05-16 23:02:27 +0200320 cancellationToken.ThrowIfCancellationRequested();
321 return Task.CompletedTask;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100322 }
323
324 public override async Task WriteMapBeginAsync(TMap map, CancellationToken cancellationToken)
325 {
326 await WriteJsonArrayStartAsync(cancellationToken);
327 await WriteJsonStringAsync(TJSONProtocolHelper.GetTypeNameForTypeId(map.KeyType), cancellationToken);
328 await WriteJsonStringAsync(TJSONProtocolHelper.GetTypeNameForTypeId(map.ValueType), cancellationToken);
329 await WriteJsonIntegerAsync(map.Count, cancellationToken);
330 await WriteJsonObjectStartAsync(cancellationToken);
331 }
332
333 public override async Task WriteMapEndAsync(CancellationToken cancellationToken)
334 {
335 await WriteJsonObjectEndAsync(cancellationToken);
336 await WriteJsonArrayEndAsync(cancellationToken);
337 }
338
339 public override async Task WriteListBeginAsync(TList list, CancellationToken cancellationToken)
340 {
341 await WriteJsonArrayStartAsync(cancellationToken);
342 await WriteJsonStringAsync(TJSONProtocolHelper.GetTypeNameForTypeId(list.ElementType), cancellationToken);
343 await WriteJsonIntegerAsync(list.Count, cancellationToken);
344 }
345
346 public override async Task WriteListEndAsync(CancellationToken cancellationToken)
347 {
348 await WriteJsonArrayEndAsync(cancellationToken);
349 }
350
351 public override async Task WriteSetBeginAsync(TSet set, CancellationToken cancellationToken)
352 {
353 await WriteJsonArrayStartAsync(cancellationToken);
354 await WriteJsonStringAsync(TJSONProtocolHelper.GetTypeNameForTypeId(set.ElementType), cancellationToken);
355 await WriteJsonIntegerAsync(set.Count, cancellationToken);
356 }
357
358 public override async Task WriteSetEndAsync(CancellationToken cancellationToken)
359 {
360 await WriteJsonArrayEndAsync(cancellationToken);
361 }
362
363 public override async Task WriteBoolAsync(bool b, CancellationToken cancellationToken)
364 {
365 await WriteJsonIntegerAsync(b ? 1 : 0, cancellationToken);
366 }
367
368 public override async Task WriteByteAsync(sbyte b, CancellationToken cancellationToken)
369 {
370 await WriteJsonIntegerAsync(b, cancellationToken);
371 }
372
373 public override async Task WriteI16Async(short i16, CancellationToken cancellationToken)
374 {
375 await WriteJsonIntegerAsync(i16, cancellationToken);
376 }
377
378 public override async Task WriteI32Async(int i32, CancellationToken cancellationToken)
379 {
380 await WriteJsonIntegerAsync(i32, cancellationToken);
381 }
382
383 public override async Task WriteI64Async(long i64, CancellationToken cancellationToken)
384 {
385 await WriteJsonIntegerAsync(i64, cancellationToken);
386 }
387
388 public override async Task WriteDoubleAsync(double d, CancellationToken cancellationToken)
389 {
390 await WriteJsonDoubleAsync(d, cancellationToken);
391 }
392
393 public override async Task WriteStringAsync(string s, CancellationToken cancellationToken)
394 {
395 var b = Utf8Encoding.GetBytes(s);
396 await WriteJsonStringAsync(b, cancellationToken);
397 }
398
399 public override async Task WriteBinaryAsync(byte[] bytes, CancellationToken cancellationToken)
400 {
401 await WriteJsonBase64Async(bytes, cancellationToken);
402 }
403
404 /// <summary>
405 /// Read in a JSON string, unescaping as appropriate.. Skip Reading from the
406 /// context if skipContext is true.
407 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +0200408 private async ValueTask<byte[]> ReadJsonStringAsync(bool skipContext, CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100409 {
410 using (var buffer = new MemoryStream())
411 {
412 var codeunits = new List<char>();
413
414
415 if (!skipContext)
416 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200417 await Context.ReadConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100418 }
419
420 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Quote, cancellationToken);
421
422 while (true)
423 {
424 var ch = await Reader.ReadAsync(cancellationToken);
425 if (ch == TJSONProtocolConstants.Quote[0])
426 {
427 break;
428 }
429
430 // escaped?
431 if (ch != TJSONProtocolConstants.EscSequences[0])
432 {
433 await buffer.WriteAsync(new[] {ch}, 0, 1, cancellationToken);
434 continue;
435 }
436
437 // distinguish between \uXXXX and \?
438 ch = await Reader.ReadAsync(cancellationToken);
439 if (ch != TJSONProtocolConstants.EscSequences[1]) // control chars like \n
440 {
441 var off = Array.IndexOf(TJSONProtocolConstants.EscapeChars, (char) ch);
442 if (off == -1)
443 {
444 throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected control char");
445 }
446 ch = TJSONProtocolConstants.EscapeCharValues[off];
447 await buffer.WriteAsync(new[] {ch}, 0, 1, cancellationToken);
448 continue;
449 }
450
451 // it's \uXXXX
452 await Trans.ReadAllAsync(_tempBuffer, 0, 4, cancellationToken);
453
454 var wch = (short) ((TJSONProtocolHelper.ToHexVal(_tempBuffer[0]) << 12) +
455 (TJSONProtocolHelper.ToHexVal(_tempBuffer[1]) << 8) +
456 (TJSONProtocolHelper.ToHexVal(_tempBuffer[2]) << 4) +
457 TJSONProtocolHelper.ToHexVal(_tempBuffer[3]));
458
459 if (char.IsHighSurrogate((char) wch))
460 {
461 if (codeunits.Count > 0)
462 {
463 throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected low surrogate char");
464 }
465 codeunits.Add((char) wch);
466 }
467 else if (char.IsLowSurrogate((char) wch))
468 {
469 if (codeunits.Count == 0)
470 {
471 throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected high surrogate char");
472 }
473
474 codeunits.Add((char) wch);
475 var tmp = Utf8Encoding.GetBytes(codeunits.ToArray());
476 await buffer.WriteAsync(tmp, 0, tmp.Length, cancellationToken);
477 codeunits.Clear();
478 }
479 else
480 {
481 var tmp = Utf8Encoding.GetBytes(new[] {(char) wch});
482 await buffer.WriteAsync(tmp, 0, tmp.Length, cancellationToken);
483 }
484 }
485
486 if (codeunits.Count > 0)
487 {
488 throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected low surrogate char");
489 }
490
491 return buffer.ToArray();
492 }
493 }
494
495 /// <summary>
496 /// Read in a sequence of characters that are all valid in JSON numbers. Does
497 /// not do a complete regex check to validate that this is actually a number.
498 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +0200499 private async ValueTask<string> ReadJsonNumericCharsAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100500 {
501 var strbld = new StringBuilder();
502 while (true)
503 {
504 //TODO: workaround for primitive types with TJsonProtocol, think - how to rewrite into more easy form without exceptions
505 try
506 {
507 var ch = await Reader.PeekAsync(cancellationToken);
508 if (!TJSONProtocolHelper.IsJsonNumeric(ch))
509 {
510 break;
511 }
512 var c = (char)await Reader.ReadAsync(cancellationToken);
513 strbld.Append(c);
514 }
515 catch (TTransportException)
516 {
517 break;
518 }
519 }
520 return strbld.ToString();
521 }
522
523 /// <summary>
524 /// Read in a JSON number. If the context dictates, Read in enclosing quotes.
525 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +0200526 private async ValueTask<long> ReadJsonIntegerAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100527 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200528 await Context.ReadConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100529 if (Context.EscapeNumbers())
530 {
531 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Quote, cancellationToken);
532 }
533
534 var str = await ReadJsonNumericCharsAsync(cancellationToken);
535 if (Context.EscapeNumbers())
536 {
537 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Quote, cancellationToken);
538 }
539
540 try
541 {
542 return long.Parse(str);
543 }
544 catch (FormatException)
545 {
546 throw new TProtocolException(TProtocolException.INVALID_DATA, "Bad data encounted in numeric data");
547 }
548 }
549
550 /// <summary>
551 /// Read in a JSON double value. Throw if the value is not wrapped in quotes
552 /// when expected or if wrapped in quotes when not expected.
553 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +0200554 private async ValueTask<double> ReadJsonDoubleAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100555 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200556 await Context.ReadConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100557 if (await Reader.PeekAsync(cancellationToken) == TJSONProtocolConstants.Quote[0])
558 {
559 var arr = await ReadJsonStringAsync(true, cancellationToken);
560 var dub = double.Parse(Utf8Encoding.GetString(arr, 0, arr.Length), CultureInfo.InvariantCulture);
561
562 if (!Context.EscapeNumbers() && !double.IsNaN(dub) && !double.IsInfinity(dub))
563 {
564 // Throw exception -- we should not be in a string in this case
565 throw new TProtocolException(TProtocolException.INVALID_DATA, "Numeric data unexpectedly quoted");
566 }
567
568 return dub;
569 }
570
571 if (Context.EscapeNumbers())
572 {
573 // This will throw - we should have had a quote if escapeNum == true
574 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Quote, cancellationToken);
575 }
576
577 try
578 {
579 return double.Parse(await ReadJsonNumericCharsAsync(cancellationToken), CultureInfo.InvariantCulture);
580 }
581 catch (FormatException)
582 {
583 throw new TProtocolException(TProtocolException.INVALID_DATA, "Bad data encounted in numeric data");
584 }
585 }
586
587 /// <summary>
588 /// Read in a JSON string containing base-64 encoded data and decode it.
589 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +0200590 private async ValueTask<byte[]> ReadJsonBase64Async(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100591 {
592 var b = await ReadJsonStringAsync(false, cancellationToken);
593 var len = b.Length;
594 var off = 0;
595 var size = 0;
596
597 // reduce len to ignore fill bytes
598 while ((len > 0) && (b[len - 1] == '='))
599 {
600 --len;
601 }
602
603 // read & decode full byte triplets = 4 source bytes
604 while (len > 4)
605 {
606 // Decode 4 bytes at a time
607 TBase64Utils.Decode(b, off, 4, b, size); // NB: decoded in place
608 off += 4;
609 len -= 4;
610 size += 3;
611 }
612
613 // Don't decode if we hit the end or got a single leftover byte (invalid
614 // base64 but legal for skip of regular string exType)
615 if (len > 1)
616 {
617 // Decode remainder
618 TBase64Utils.Decode(b, off, len, b, size); // NB: decoded in place
619 size += len - 1;
620 }
621
622 // Sadly we must copy the byte[] (any way around this?)
623 var result = new byte[size];
624 Array.Copy(b, 0, result, 0, size);
625 return result;
626 }
627
628 private async Task ReadJsonObjectStartAsync(CancellationToken cancellationToken)
629 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200630 await Context.ReadConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100631 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.LeftBrace, cancellationToken);
632 PushContext(new JSONPairContext(this));
633 }
634
635 private async Task ReadJsonObjectEndAsync(CancellationToken cancellationToken)
636 {
637 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.RightBrace, cancellationToken);
638 PopContext();
639 }
640
641 private async Task ReadJsonArrayStartAsync(CancellationToken cancellationToken)
642 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200643 await Context.ReadConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100644 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.LeftBracket, cancellationToken);
645 PushContext(new JSONListContext(this));
646 }
647
648 private async Task ReadJsonArrayEndAsync(CancellationToken cancellationToken)
649 {
650 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.RightBracket, cancellationToken);
651 PopContext();
652 }
653
Jens Geyer5a17b132019-05-26 15:53:37 +0200654 public override async ValueTask<TMessage> ReadMessageBeginAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100655 {
656 var message = new TMessage();
Paulo Nevesf049ff32020-02-05 11:58:18 +0100657
658 resetContext();
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100659 await ReadJsonArrayStartAsync(cancellationToken);
660 if (await ReadJsonIntegerAsync(cancellationToken) != Version)
661 {
662 throw new TProtocolException(TProtocolException.BAD_VERSION, "Message contained bad version.");
663 }
664
665 var buf = await ReadJsonStringAsync(false, cancellationToken);
666 message.Name = Utf8Encoding.GetString(buf, 0, buf.Length);
667 message.Type = (TMessageType) await ReadJsonIntegerAsync(cancellationToken);
668 message.SeqID = (int) await ReadJsonIntegerAsync(cancellationToken);
669 return message;
670 }
671
672 public override async Task ReadMessageEndAsync(CancellationToken cancellationToken)
673 {
674 await ReadJsonArrayEndAsync(cancellationToken);
675 }
676
Jens Geyer5a17b132019-05-26 15:53:37 +0200677 public override async ValueTask<TStruct> ReadStructBeginAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100678 {
679 await ReadJsonObjectStartAsync(cancellationToken);
Jens Geyerdce22992020-05-16 23:02:27 +0200680
681 return AnonymousStruct;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100682 }
683
684 public override async Task ReadStructEndAsync(CancellationToken cancellationToken)
685 {
686 await ReadJsonObjectEndAsync(cancellationToken);
687 }
688
Jens Geyer5a17b132019-05-26 15:53:37 +0200689 public override async ValueTask<TField> ReadFieldBeginAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100690 {
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100691 var ch = await Reader.PeekAsync(cancellationToken);
692 if (ch == TJSONProtocolConstants.RightBrace[0])
693 {
Jens Geyerdce22992020-05-16 23:02:27 +0200694 return StopField;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100695 }
Jens Geyerdce22992020-05-16 23:02:27 +0200696
697 var field = new TField()
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100698 {
Jens Geyerdce22992020-05-16 23:02:27 +0200699 ID = (short)await ReadJsonIntegerAsync(cancellationToken)
700 };
701
702 await ReadJsonObjectStartAsync(cancellationToken);
703 field.Type = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken));
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100704 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 Geyerdce22992020-05-16 23:02:27 +0200848 public virtual Task WriteConditionalDelimiterAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100849 {
Jens Geyerdce22992020-05-16 23:02:27 +0200850 cancellationToken.ThrowIfCancellationRequested();
851 return Task.CompletedTask;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100852 }
853
Jens Geyerdce22992020-05-16 23:02:27 +0200854 public virtual Task ReadConditionalDelimiterAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100855 {
Jens Geyerdce22992020-05-16 23:02:27 +0200856 cancellationToken.ThrowIfCancellationRequested();
857 return Task.CompletedTask;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100858 }
859
860 public virtual bool EscapeNumbers()
861 {
862 return false;
863 }
864 }
865
866 /// <summary>
867 /// Context for JSON lists. Will insert/Read commas before each item except
868 /// for the first one
869 /// </summary>
870 protected class JSONListContext : JSONBaseContext
871 {
872 private bool _first = true;
873
874 public JSONListContext(TJsonProtocol protocol)
875 : base(protocol)
876 {
877 }
878
Jens Geyer2ff952b2019-04-13 19:46:54 +0200879 public override async Task WriteConditionalDelimiterAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100880 {
881 if (_first)
882 {
883 _first = false;
884 }
885 else
886 {
887 await Proto.Trans.WriteAsync(TJSONProtocolConstants.Comma, cancellationToken);
888 }
889 }
890
Jens Geyer2ff952b2019-04-13 19:46:54 +0200891 public override async Task ReadConditionalDelimiterAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100892 {
893 if (_first)
894 {
895 _first = false;
896 }
897 else
898 {
899 await Proto.ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Comma, cancellationToken);
900 }
901 }
902 }
903
904 /// <summary>
905 /// Context for JSON records. Will insert/Read colons before the value portion
906 /// of each record pair, and commas before each key except the first. In
907 /// addition, will indicate that numbers in the key position need to be
908 /// escaped in quotes (since JSON keys must be strings).
909 /// </summary>
910 // ReSharper disable once InconsistentNaming
911 protected class JSONPairContext : JSONBaseContext
912 {
913 private bool _colon = true;
914
915 private bool _first = true;
916
917 public JSONPairContext(TJsonProtocol proto)
918 : base(proto)
919 {
920 }
921
Jens Geyer2ff952b2019-04-13 19:46:54 +0200922 public override async Task WriteConditionalDelimiterAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100923 {
924 if (_first)
925 {
926 _first = false;
927 _colon = true;
928 }
929 else
930 {
931 await Proto.Trans.WriteAsync(_colon ? TJSONProtocolConstants.Colon : TJSONProtocolConstants.Comma, cancellationToken);
932 _colon = !_colon;
933 }
934 }
935
Jens Geyer2ff952b2019-04-13 19:46:54 +0200936 public override async Task ReadConditionalDelimiterAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100937 {
938 if (_first)
939 {
940 _first = false;
941 _colon = true;
942 }
943 else
944 {
945 await Proto.ReadJsonSyntaxCharAsync(_colon ? TJSONProtocolConstants.Colon : TJSONProtocolConstants.Comma, cancellationToken);
946 _colon = !_colon;
947 }
948 }
949
950 public override bool EscapeNumbers()
951 {
952 return _colon;
953 }
954 }
955
956 /// <summary>
957 /// Holds up to one byte from the transport
958 /// </summary>
959 protected class LookaheadReader
960 {
961 private readonly byte[] _data = new byte[1];
962
963 private bool _hasData;
964 protected TJsonProtocol Proto;
965
966 public LookaheadReader(TJsonProtocol proto)
967 {
968 Proto = proto;
969 }
970
971 /// <summary>
972 /// Return and consume the next byte to be Read, either taking it from the
973 /// data buffer if present or getting it from the transport otherwise.
974 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +0200975 public async ValueTask<byte> ReadAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100976 {
Jens Geyerdce22992020-05-16 23:02:27 +0200977 cancellationToken.ThrowIfCancellationRequested();
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100978
979 if (_hasData)
980 {
981 _hasData = false;
982 }
983 else
984 {
985 // find more easy way to avoid exception on reading primitive types
986 await Proto.Trans.ReadAllAsync(_data, 0, 1, cancellationToken);
987 }
988 return _data[0];
989 }
990
991 /// <summary>
992 /// Return the next byte to be Read without consuming, filling the data
993 /// buffer if it has not been filled alReady.
994 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +0200995 public async ValueTask<byte> PeekAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100996 {
Jens Geyerdce22992020-05-16 23:02:27 +0200997 cancellationToken.ThrowIfCancellationRequested();
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100998
999 if (!_hasData)
1000 {
1001 // find more easy way to avoid exception on reading primitive types
1002 await Proto.Trans.ReadAllAsync(_data, 0, 1, cancellationToken);
Jens Geyer5a17b132019-05-26 15:53:37 +02001003 _hasData = true;
Jens Geyeraa0c8b32019-01-28 23:27:45 +01001004 }
Jens Geyeraa0c8b32019-01-28 23:27:45 +01001005 return _data[0];
1006 }
1007 }
1008 }
1009}