blob: 081f42e96924d3606a05a90cc197824cd614ade5 [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
Jens Geyer0d128322021-02-25 09:42:52 +010030#pragma warning disable IDE0079 // unnecessary suppression
31#pragma warning disable IDE0063 // simplify using
32#pragma warning disable IDE0066 // use switch expression
33
Jens Geyeraa0c8b32019-01-28 23:27:45 +010034namespace Thrift.Protocol
35{
36 /// <summary>
37 /// JSON protocol implementation for thrift.
38 /// This is a full-featured protocol supporting Write and Read.
39 /// Please see the C++ class header for a detailed description of the
40 /// protocol's wire format.
41 /// Adapted from the Java version.
42 /// </summary>
43 // ReSharper disable once InconsistentNaming
44 public class TJsonProtocol : TProtocol
45 {
46 private const long Version = 1;
47
48 // Temporary buffer used by several methods
49 private readonly byte[] _tempBuffer = new byte[4];
50
51 // Current context that we are in
52 protected JSONBaseContext Context;
53
54 // Stack of nested contexts that we may be in
55 protected Stack<JSONBaseContext> ContextStack = new Stack<JSONBaseContext>();
56
57 // Reader that manages a 1-byte buffer
58 protected LookaheadReader Reader;
59
60 // Default encoding
61 protected Encoding Utf8Encoding = Encoding.UTF8;
62
63 /// <summary>
64 /// TJsonProtocol Constructor
65 /// </summary>
66 public TJsonProtocol(TTransport trans)
67 : base(trans)
68 {
69 Context = new JSONBaseContext(this);
70 Reader = new LookaheadReader(this);
71 }
72
73 /// <summary>
74 /// Push a new JSON context onto the stack.
75 /// </summary>
76 protected void PushContext(JSONBaseContext c)
77 {
78 ContextStack.Push(Context);
79 Context = c;
80 }
81
82 /// <summary>
83 /// Pop the last JSON context off the stack
84 /// </summary>
85 protected void PopContext()
86 {
87 Context = ContextStack.Pop();
88 }
89
90 /// <summary>
Paulo Nevesf049ff32020-02-05 11:58:18 +010091 /// Resets the context stack to pristine state. Allows for reusal of the protocol
92 /// even in cases where the protocol instance was in an undefined state due to
93 /// dangling/stale/obsolete contexts
94 /// </summary>
Jens Geyer0d128322021-02-25 09:42:52 +010095 private void ResetContext()
Paulo Nevesf049ff32020-02-05 11:58:18 +010096 {
97 ContextStack.Clear();
98 Context = new JSONBaseContext(this);
99 }
100 /// <summary>
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100101 /// Read a byte that must match b[0]; otherwise an exception is thrown.
102 /// Marked protected to avoid synthetic accessor in JSONListContext.Read
103 /// and JSONPairContext.Read
104 /// </summary>
105 protected async Task ReadJsonSyntaxCharAsync(byte[] bytes, CancellationToken cancellationToken)
106 {
107 var ch = await Reader.ReadAsync(cancellationToken);
108 if (ch != bytes[0])
109 {
110 throw new TProtocolException(TProtocolException.INVALID_DATA, $"Unexpected character: {(char) ch}");
111 }
112 }
113
114 /// <summary>
115 /// Write the bytes in array buf as a JSON characters, escaping as needed
116 /// </summary>
117 private async Task WriteJsonStringAsync(byte[] bytes, CancellationToken cancellationToken)
118 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200119 await Context.WriteConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100120 await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken);
121
122 var len = bytes.Length;
123 for (var i = 0; i < len; i++)
124 {
125 if ((bytes[i] & 0x00FF) >= 0x30)
126 {
127 if (bytes[i] == TJSONProtocolConstants.Backslash[0])
128 {
129 await Trans.WriteAsync(TJSONProtocolConstants.Backslash, cancellationToken);
130 await Trans.WriteAsync(TJSONProtocolConstants.Backslash, cancellationToken);
131 }
132 else
133 {
Jens Geyerdce22992020-05-16 23:02:27 +0200134 await Trans.WriteAsync(bytes, i, 1, cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100135 }
136 }
137 else
138 {
139 _tempBuffer[0] = TJSONProtocolConstants.JsonCharTable[bytes[i]];
140 if (_tempBuffer[0] == 1)
141 {
142 await Trans.WriteAsync(bytes, i, 1, cancellationToken);
143 }
144 else if (_tempBuffer[0] > 1)
145 {
146 await Trans.WriteAsync(TJSONProtocolConstants.Backslash, cancellationToken);
147 await Trans.WriteAsync(_tempBuffer, 0, 1, cancellationToken);
148 }
149 else
150 {
151 await Trans.WriteAsync(TJSONProtocolConstants.EscSequences, cancellationToken);
152 _tempBuffer[0] = TJSONProtocolHelper.ToHexChar((byte) (bytes[i] >> 4));
153 _tempBuffer[1] = TJSONProtocolHelper.ToHexChar(bytes[i]);
154 await Trans.WriteAsync(_tempBuffer, 0, 2, cancellationToken);
155 }
156 }
157 }
158 await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken);
159 }
160
161 /// <summary>
162 /// Write out number as a JSON value. If the context dictates so, it will be
163 /// wrapped in quotes to output as a JSON string.
164 /// </summary>
165 private async Task WriteJsonIntegerAsync(long num, CancellationToken cancellationToken)
166 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200167 await Context.WriteConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100168 var str = num.ToString();
169
170 var escapeNum = Context.EscapeNumbers();
171 if (escapeNum)
172 {
173 await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken);
174 }
175
176 var bytes = Utf8Encoding.GetBytes(str);
177 await Trans.WriteAsync(bytes, cancellationToken);
178
179 if (escapeNum)
180 {
181 await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken);
182 }
183 }
184
185 /// <summary>
186 /// Write out a double as a JSON value. If it is NaN or infinity or if the
187 /// context dictates escaping, Write out as JSON string.
188 /// </summary>
189 private async Task WriteJsonDoubleAsync(double num, CancellationToken cancellationToken)
190 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200191 await Context.WriteConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100192 var str = num.ToString("G17", CultureInfo.InvariantCulture);
193 var special = false;
194
195 switch (str[0])
196 {
197 case 'N': // NaN
198 case 'I': // Infinity
199 special = true;
200 break;
201 case '-':
202 if (str[1] == 'I')
203 {
204 // -Infinity
205 special = true;
206 }
207 break;
208 }
209
210 var escapeNum = special || Context.EscapeNumbers();
211
212 if (escapeNum)
213 {
214 await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken);
215 }
216
217 await Trans.WriteAsync(Utf8Encoding.GetBytes(str), cancellationToken);
218
219 if (escapeNum)
220 {
221 await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken);
222 }
223 }
224
225 /// <summary>
226 /// Write out contents of byte array b as a JSON string with base-64 encoded
227 /// data
228 /// </summary>
229 private async Task WriteJsonBase64Async(byte[] bytes, CancellationToken cancellationToken)
230 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200231 await Context.WriteConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100232 await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken);
233
234 var len = bytes.Length;
235 var off = 0;
236
237 while (len >= 3)
238 {
239 // Encode 3 bytes at a time
240 TBase64Utils.Encode(bytes, off, 3, _tempBuffer, 0);
241 await Trans.WriteAsync(_tempBuffer, 0, 4, cancellationToken);
242 off += 3;
243 len -= 3;
244 }
245
246 if (len > 0)
247 {
248 // Encode remainder
249 TBase64Utils.Encode(bytes, off, len, _tempBuffer, 0);
250 await Trans.WriteAsync(_tempBuffer, 0, len + 1, cancellationToken);
251 }
252
253 await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken);
254 }
255
256 private async Task WriteJsonObjectStartAsync(CancellationToken cancellationToken)
257 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200258 await Context.WriteConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100259 await Trans.WriteAsync(TJSONProtocolConstants.LeftBrace, cancellationToken);
260 PushContext(new JSONPairContext(this));
261 }
262
263 private async Task WriteJsonObjectEndAsync(CancellationToken cancellationToken)
264 {
265 PopContext();
266 await Trans.WriteAsync(TJSONProtocolConstants.RightBrace, cancellationToken);
267 }
268
269 private async Task WriteJsonArrayStartAsync(CancellationToken cancellationToken)
270 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200271 await Context.WriteConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100272 await Trans.WriteAsync(TJSONProtocolConstants.LeftBracket, cancellationToken);
273 PushContext(new JSONListContext(this));
274 }
275
276 private async Task WriteJsonArrayEndAsync(CancellationToken cancellationToken)
277 {
278 PopContext();
279 await Trans.WriteAsync(TJSONProtocolConstants.RightBracket, cancellationToken);
280 }
281
282 public override async Task WriteMessageBeginAsync(TMessage message, CancellationToken cancellationToken)
283 {
Jens Geyer0d128322021-02-25 09:42:52 +0100284 ResetContext();
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100285 await WriteJsonArrayStartAsync(cancellationToken);
286 await WriteJsonIntegerAsync(Version, cancellationToken);
287
288 var b = Utf8Encoding.GetBytes(message.Name);
289 await WriteJsonStringAsync(b, cancellationToken);
290
291 await WriteJsonIntegerAsync((long) message.Type, cancellationToken);
292 await WriteJsonIntegerAsync(message.SeqID, cancellationToken);
293 }
294
295 public override async Task WriteMessageEndAsync(CancellationToken cancellationToken)
296 {
297 await WriteJsonArrayEndAsync(cancellationToken);
298 }
299
300 public override async Task WriteStructBeginAsync(TStruct @struct, CancellationToken cancellationToken)
301 {
302 await WriteJsonObjectStartAsync(cancellationToken);
303 }
304
305 public override async Task WriteStructEndAsync(CancellationToken cancellationToken)
306 {
307 await WriteJsonObjectEndAsync(cancellationToken);
308 }
309
310 public override async Task WriteFieldBeginAsync(TField field, CancellationToken cancellationToken)
311 {
312 await WriteJsonIntegerAsync(field.ID, cancellationToken);
313 await WriteJsonObjectStartAsync(cancellationToken);
314 await WriteJsonStringAsync(TJSONProtocolHelper.GetTypeNameForTypeId(field.Type), cancellationToken);
315 }
316
317 public override async Task WriteFieldEndAsync(CancellationToken cancellationToken)
318 {
319 await WriteJsonObjectEndAsync(cancellationToken);
320 }
321
Jens Geyerdce22992020-05-16 23:02:27 +0200322 public override Task WriteFieldStopAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100323 {
Jens Geyerdce22992020-05-16 23:02:27 +0200324 cancellationToken.ThrowIfCancellationRequested();
325 return Task.CompletedTask;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100326 }
327
328 public override async Task WriteMapBeginAsync(TMap map, CancellationToken cancellationToken)
329 {
330 await WriteJsonArrayStartAsync(cancellationToken);
331 await WriteJsonStringAsync(TJSONProtocolHelper.GetTypeNameForTypeId(map.KeyType), cancellationToken);
332 await WriteJsonStringAsync(TJSONProtocolHelper.GetTypeNameForTypeId(map.ValueType), cancellationToken);
333 await WriteJsonIntegerAsync(map.Count, cancellationToken);
334 await WriteJsonObjectStartAsync(cancellationToken);
335 }
336
337 public override async Task WriteMapEndAsync(CancellationToken cancellationToken)
338 {
339 await WriteJsonObjectEndAsync(cancellationToken);
340 await WriteJsonArrayEndAsync(cancellationToken);
341 }
342
343 public override async Task WriteListBeginAsync(TList list, CancellationToken cancellationToken)
344 {
345 await WriteJsonArrayStartAsync(cancellationToken);
346 await WriteJsonStringAsync(TJSONProtocolHelper.GetTypeNameForTypeId(list.ElementType), cancellationToken);
347 await WriteJsonIntegerAsync(list.Count, cancellationToken);
348 }
349
350 public override async Task WriteListEndAsync(CancellationToken cancellationToken)
351 {
352 await WriteJsonArrayEndAsync(cancellationToken);
353 }
354
355 public override async Task WriteSetBeginAsync(TSet set, CancellationToken cancellationToken)
356 {
357 await WriteJsonArrayStartAsync(cancellationToken);
358 await WriteJsonStringAsync(TJSONProtocolHelper.GetTypeNameForTypeId(set.ElementType), cancellationToken);
359 await WriteJsonIntegerAsync(set.Count, cancellationToken);
360 }
361
362 public override async Task WriteSetEndAsync(CancellationToken cancellationToken)
363 {
364 await WriteJsonArrayEndAsync(cancellationToken);
365 }
366
367 public override async Task WriteBoolAsync(bool b, CancellationToken cancellationToken)
368 {
369 await WriteJsonIntegerAsync(b ? 1 : 0, cancellationToken);
370 }
371
372 public override async Task WriteByteAsync(sbyte b, CancellationToken cancellationToken)
373 {
374 await WriteJsonIntegerAsync(b, cancellationToken);
375 }
376
377 public override async Task WriteI16Async(short i16, CancellationToken cancellationToken)
378 {
379 await WriteJsonIntegerAsync(i16, cancellationToken);
380 }
381
382 public override async Task WriteI32Async(int i32, CancellationToken cancellationToken)
383 {
384 await WriteJsonIntegerAsync(i32, cancellationToken);
385 }
386
387 public override async Task WriteI64Async(long i64, CancellationToken cancellationToken)
388 {
389 await WriteJsonIntegerAsync(i64, cancellationToken);
390 }
391
392 public override async Task WriteDoubleAsync(double d, CancellationToken cancellationToken)
393 {
394 await WriteJsonDoubleAsync(d, cancellationToken);
395 }
396
397 public override async Task WriteStringAsync(string s, CancellationToken cancellationToken)
398 {
399 var b = Utf8Encoding.GetBytes(s);
400 await WriteJsonStringAsync(b, cancellationToken);
401 }
402
403 public override async Task WriteBinaryAsync(byte[] bytes, CancellationToken cancellationToken)
404 {
405 await WriteJsonBase64Async(bytes, cancellationToken);
406 }
407
408 /// <summary>
409 /// Read in a JSON string, unescaping as appropriate.. Skip Reading from the
410 /// context if skipContext is true.
411 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +0200412 private async ValueTask<byte[]> ReadJsonStringAsync(bool skipContext, CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100413 {
414 using (var buffer = new MemoryStream())
415 {
416 var codeunits = new List<char>();
417
418
419 if (!skipContext)
420 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200421 await Context.ReadConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100422 }
423
424 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Quote, cancellationToken);
425
426 while (true)
427 {
428 var ch = await Reader.ReadAsync(cancellationToken);
429 if (ch == TJSONProtocolConstants.Quote[0])
430 {
431 break;
432 }
433
434 // escaped?
435 if (ch != TJSONProtocolConstants.EscSequences[0])
436 {
Jens Geyer0d128322021-02-25 09:42:52 +0100437#if NETSTANDARD2_0
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100438 await buffer.WriteAsync(new[] {ch}, 0, 1, cancellationToken);
Jens Geyer0d128322021-02-25 09:42:52 +0100439#else
440 var wbuf = new[] { ch };
441 await buffer.WriteAsync(wbuf.AsMemory(0, 1), cancellationToken);
442#endif
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100443 continue;
444 }
445
446 // distinguish between \uXXXX and \?
447 ch = await Reader.ReadAsync(cancellationToken);
448 if (ch != TJSONProtocolConstants.EscSequences[1]) // control chars like \n
449 {
450 var off = Array.IndexOf(TJSONProtocolConstants.EscapeChars, (char) ch);
451 if (off == -1)
452 {
453 throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected control char");
454 }
455 ch = TJSONProtocolConstants.EscapeCharValues[off];
Jens Geyer0d128322021-02-25 09:42:52 +0100456#if NETSTANDARD2_0
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100457 await buffer.WriteAsync(new[] {ch}, 0, 1, cancellationToken);
Jens Geyer0d128322021-02-25 09:42:52 +0100458#else
459 var wbuf = new[] { ch };
460 await buffer.WriteAsync( wbuf.AsMemory(0, 1), cancellationToken);
461#endif
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100462 continue;
463 }
464
465 // it's \uXXXX
466 await Trans.ReadAllAsync(_tempBuffer, 0, 4, cancellationToken);
467
468 var wch = (short) ((TJSONProtocolHelper.ToHexVal(_tempBuffer[0]) << 12) +
469 (TJSONProtocolHelper.ToHexVal(_tempBuffer[1]) << 8) +
470 (TJSONProtocolHelper.ToHexVal(_tempBuffer[2]) << 4) +
471 TJSONProtocolHelper.ToHexVal(_tempBuffer[3]));
472
473 if (char.IsHighSurrogate((char) wch))
474 {
475 if (codeunits.Count > 0)
476 {
477 throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected low surrogate char");
478 }
479 codeunits.Add((char) wch);
480 }
481 else if (char.IsLowSurrogate((char) wch))
482 {
483 if (codeunits.Count == 0)
484 {
485 throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected high surrogate char");
486 }
487
488 codeunits.Add((char) wch);
489 var tmp = Utf8Encoding.GetBytes(codeunits.ToArray());
Jens Geyer0d128322021-02-25 09:42:52 +0100490#if NETSTANDARD2_0
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100491 await buffer.WriteAsync(tmp, 0, tmp.Length, cancellationToken);
Jens Geyer0d128322021-02-25 09:42:52 +0100492#else
493 await buffer.WriteAsync(tmp.AsMemory(0, tmp.Length), cancellationToken);
494#endif
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100495 codeunits.Clear();
496 }
497 else
498 {
Jens Geyer0d128322021-02-25 09:42:52 +0100499 var tmp = Utf8Encoding.GetBytes(new[] { (char)wch });
500#if NETSTANDARD2_0
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100501 await buffer.WriteAsync(tmp, 0, tmp.Length, cancellationToken);
Jens Geyer0d128322021-02-25 09:42:52 +0100502#else
503 await buffer.WriteAsync(tmp.AsMemory( 0, tmp.Length), cancellationToken);
504#endif
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100505 }
506 }
507
508 if (codeunits.Count > 0)
509 {
510 throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected low surrogate char");
511 }
512
513 return buffer.ToArray();
514 }
515 }
516
517 /// <summary>
518 /// Read in a sequence of characters that are all valid in JSON numbers. Does
519 /// not do a complete regex check to validate that this is actually a number.
520 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +0200521 private async ValueTask<string> ReadJsonNumericCharsAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100522 {
523 var strbld = new StringBuilder();
524 while (true)
525 {
526 //TODO: workaround for primitive types with TJsonProtocol, think - how to rewrite into more easy form without exceptions
527 try
528 {
529 var ch = await Reader.PeekAsync(cancellationToken);
530 if (!TJSONProtocolHelper.IsJsonNumeric(ch))
531 {
532 break;
533 }
534 var c = (char)await Reader.ReadAsync(cancellationToken);
535 strbld.Append(c);
536 }
537 catch (TTransportException)
538 {
539 break;
540 }
541 }
542 return strbld.ToString();
543 }
544
545 /// <summary>
546 /// Read in a JSON number. If the context dictates, Read in enclosing quotes.
547 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +0200548 private async ValueTask<long> ReadJsonIntegerAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100549 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200550 await Context.ReadConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100551 if (Context.EscapeNumbers())
552 {
553 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Quote, cancellationToken);
554 }
555
556 var str = await ReadJsonNumericCharsAsync(cancellationToken);
557 if (Context.EscapeNumbers())
558 {
559 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Quote, cancellationToken);
560 }
561
562 try
563 {
564 return long.Parse(str);
565 }
566 catch (FormatException)
567 {
568 throw new TProtocolException(TProtocolException.INVALID_DATA, "Bad data encounted in numeric data");
569 }
570 }
571
572 /// <summary>
573 /// Read in a JSON double value. Throw if the value is not wrapped in quotes
574 /// when expected or if wrapped in quotes when not expected.
575 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +0200576 private async ValueTask<double> ReadJsonDoubleAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100577 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200578 await Context.ReadConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100579 if (await Reader.PeekAsync(cancellationToken) == TJSONProtocolConstants.Quote[0])
580 {
581 var arr = await ReadJsonStringAsync(true, cancellationToken);
582 var dub = double.Parse(Utf8Encoding.GetString(arr, 0, arr.Length), CultureInfo.InvariantCulture);
583
584 if (!Context.EscapeNumbers() && !double.IsNaN(dub) && !double.IsInfinity(dub))
585 {
586 // Throw exception -- we should not be in a string in this case
587 throw new TProtocolException(TProtocolException.INVALID_DATA, "Numeric data unexpectedly quoted");
588 }
589
590 return dub;
591 }
592
593 if (Context.EscapeNumbers())
594 {
595 // This will throw - we should have had a quote if escapeNum == true
596 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Quote, cancellationToken);
597 }
598
599 try
600 {
601 return double.Parse(await ReadJsonNumericCharsAsync(cancellationToken), CultureInfo.InvariantCulture);
602 }
603 catch (FormatException)
604 {
605 throw new TProtocolException(TProtocolException.INVALID_DATA, "Bad data encounted in numeric data");
606 }
607 }
608
609 /// <summary>
610 /// Read in a JSON string containing base-64 encoded data and decode it.
611 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +0200612 private async ValueTask<byte[]> ReadJsonBase64Async(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100613 {
614 var b = await ReadJsonStringAsync(false, cancellationToken);
615 var len = b.Length;
616 var off = 0;
617 var size = 0;
618
619 // reduce len to ignore fill bytes
620 while ((len > 0) && (b[len - 1] == '='))
621 {
622 --len;
623 }
624
625 // read & decode full byte triplets = 4 source bytes
626 while (len > 4)
627 {
628 // Decode 4 bytes at a time
629 TBase64Utils.Decode(b, off, 4, b, size); // NB: decoded in place
630 off += 4;
631 len -= 4;
632 size += 3;
633 }
634
635 // Don't decode if we hit the end or got a single leftover byte (invalid
636 // base64 but legal for skip of regular string exType)
637 if (len > 1)
638 {
639 // Decode remainder
640 TBase64Utils.Decode(b, off, len, b, size); // NB: decoded in place
641 size += len - 1;
642 }
643
644 // Sadly we must copy the byte[] (any way around this?)
645 var result = new byte[size];
646 Array.Copy(b, 0, result, 0, size);
647 return result;
648 }
649
650 private async Task ReadJsonObjectStartAsync(CancellationToken cancellationToken)
651 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200652 await Context.ReadConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100653 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.LeftBrace, cancellationToken);
654 PushContext(new JSONPairContext(this));
655 }
656
657 private async Task ReadJsonObjectEndAsync(CancellationToken cancellationToken)
658 {
659 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.RightBrace, cancellationToken);
660 PopContext();
661 }
662
663 private async Task ReadJsonArrayStartAsync(CancellationToken cancellationToken)
664 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200665 await Context.ReadConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100666 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.LeftBracket, cancellationToken);
667 PushContext(new JSONListContext(this));
668 }
669
670 private async Task ReadJsonArrayEndAsync(CancellationToken cancellationToken)
671 {
672 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.RightBracket, cancellationToken);
673 PopContext();
674 }
675
Jens Geyer5a17b132019-05-26 15:53:37 +0200676 public override async ValueTask<TMessage> ReadMessageBeginAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100677 {
678 var message = new TMessage();
Paulo Nevesf049ff32020-02-05 11:58:18 +0100679
Jens Geyer0d128322021-02-25 09:42:52 +0100680 ResetContext();
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100681 await ReadJsonArrayStartAsync(cancellationToken);
682 if (await ReadJsonIntegerAsync(cancellationToken) != Version)
683 {
684 throw new TProtocolException(TProtocolException.BAD_VERSION, "Message contained bad version.");
685 }
686
687 var buf = await ReadJsonStringAsync(false, cancellationToken);
688 message.Name = Utf8Encoding.GetString(buf, 0, buf.Length);
689 message.Type = (TMessageType) await ReadJsonIntegerAsync(cancellationToken);
690 message.SeqID = (int) await ReadJsonIntegerAsync(cancellationToken);
691 return message;
692 }
693
694 public override async Task ReadMessageEndAsync(CancellationToken cancellationToken)
695 {
696 await ReadJsonArrayEndAsync(cancellationToken);
697 }
698
Jens Geyer5a17b132019-05-26 15:53:37 +0200699 public override async ValueTask<TStruct> ReadStructBeginAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100700 {
701 await ReadJsonObjectStartAsync(cancellationToken);
Jens Geyerdce22992020-05-16 23:02:27 +0200702
703 return AnonymousStruct;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100704 }
705
706 public override async Task ReadStructEndAsync(CancellationToken cancellationToken)
707 {
708 await ReadJsonObjectEndAsync(cancellationToken);
709 }
710
Jens Geyer5a17b132019-05-26 15:53:37 +0200711 public override async ValueTask<TField> ReadFieldBeginAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100712 {
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100713 var ch = await Reader.PeekAsync(cancellationToken);
714 if (ch == TJSONProtocolConstants.RightBrace[0])
715 {
Jens Geyerdce22992020-05-16 23:02:27 +0200716 return StopField;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100717 }
Jens Geyerdce22992020-05-16 23:02:27 +0200718
719 var field = new TField()
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100720 {
Jens Geyerdce22992020-05-16 23:02:27 +0200721 ID = (short)await ReadJsonIntegerAsync(cancellationToken)
722 };
723
724 await ReadJsonObjectStartAsync(cancellationToken);
725 field.Type = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken));
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100726 return field;
727 }
728
729 public override async Task ReadFieldEndAsync(CancellationToken cancellationToken)
730 {
731 await ReadJsonObjectEndAsync(cancellationToken);
732 }
733
Jens Geyer5a17b132019-05-26 15:53:37 +0200734 public override async ValueTask<TMap> ReadMapBeginAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100735 {
736 var map = new TMap();
737 await ReadJsonArrayStartAsync(cancellationToken);
738 map.KeyType = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken));
739 map.ValueType = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken));
740 map.Count = (int) await ReadJsonIntegerAsync(cancellationToken);
Jens Geyer50806452019-11-23 01:55:58 +0100741 CheckReadBytesAvailable(map);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100742 await ReadJsonObjectStartAsync(cancellationToken);
743 return map;
744 }
745
746 public override async Task ReadMapEndAsync(CancellationToken cancellationToken)
747 {
748 await ReadJsonObjectEndAsync(cancellationToken);
749 await ReadJsonArrayEndAsync(cancellationToken);
750 }
751
Jens Geyer5a17b132019-05-26 15:53:37 +0200752 public override async ValueTask<TList> ReadListBeginAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100753 {
754 var list = new TList();
755 await ReadJsonArrayStartAsync(cancellationToken);
756 list.ElementType = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken));
757 list.Count = (int) await ReadJsonIntegerAsync(cancellationToken);
Jens Geyer50806452019-11-23 01:55:58 +0100758 CheckReadBytesAvailable(list);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100759 return list;
760 }
761
762 public override async Task ReadListEndAsync(CancellationToken cancellationToken)
763 {
764 await ReadJsonArrayEndAsync(cancellationToken);
765 }
766
Jens Geyer5a17b132019-05-26 15:53:37 +0200767 public override async ValueTask<TSet> ReadSetBeginAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100768 {
769 var set = new TSet();
770 await ReadJsonArrayStartAsync(cancellationToken);
771 set.ElementType = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken));
772 set.Count = (int) await ReadJsonIntegerAsync(cancellationToken);
Jens Geyer50806452019-11-23 01:55:58 +0100773 CheckReadBytesAvailable(set);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100774 return set;
775 }
776
777 public override async Task ReadSetEndAsync(CancellationToken cancellationToken)
778 {
779 await ReadJsonArrayEndAsync(cancellationToken);
780 }
781
Jens Geyer5a17b132019-05-26 15:53:37 +0200782 public override async ValueTask<bool> ReadBoolAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100783 {
784 return await ReadJsonIntegerAsync(cancellationToken) != 0;
785 }
786
Jens Geyer5a17b132019-05-26 15:53:37 +0200787 public override async ValueTask<sbyte> ReadByteAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100788 {
789 return (sbyte) await ReadJsonIntegerAsync(cancellationToken);
790 }
791
Jens Geyer5a17b132019-05-26 15:53:37 +0200792 public override async ValueTask<short> ReadI16Async(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100793 {
794 return (short) await ReadJsonIntegerAsync(cancellationToken);
795 }
796
Jens Geyer5a17b132019-05-26 15:53:37 +0200797 public override async ValueTask<int> ReadI32Async(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100798 {
799 return (int) await ReadJsonIntegerAsync(cancellationToken);
800 }
801
Jens Geyer5a17b132019-05-26 15:53:37 +0200802 public override async ValueTask<long> ReadI64Async(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100803 {
804 return await ReadJsonIntegerAsync(cancellationToken);
805 }
806
Jens Geyer5a17b132019-05-26 15:53:37 +0200807 public override async ValueTask<double> ReadDoubleAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100808 {
809 return await ReadJsonDoubleAsync(cancellationToken);
810 }
811
Jens Geyer5a17b132019-05-26 15:53:37 +0200812 public override async ValueTask<string> ReadStringAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100813 {
814 var buf = await ReadJsonStringAsync(false, cancellationToken);
815 return Utf8Encoding.GetString(buf, 0, buf.Length);
816 }
817
Jens Geyer5a17b132019-05-26 15:53:37 +0200818 public override async ValueTask<byte[]> ReadBinaryAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100819 {
820 return await ReadJsonBase64Async(cancellationToken);
821 }
822
Jens Geyer50806452019-11-23 01:55:58 +0100823 // Return the minimum number of bytes a type will consume on the wire
824 public override int GetMinSerializedSize(TType type)
825 {
826 switch (type)
827 {
828 case TType.Stop: return 0;
829 case TType.Void: return 0;
830 case TType.Bool: return 1; // written as int
831 case TType.Byte: return 1;
832 case TType.Double: return 1;
833 case TType.I16: return 1;
834 case TType.I32: return 1;
835 case TType.I64: return 1;
836 case TType.String: return 2; // empty string
837 case TType.Struct: return 2; // empty struct
838 case TType.Map: return 2; // empty map
839 case TType.Set: return 2; // empty set
840 case TType.List: return 2; // empty list
Jens Geyer0d128322021-02-25 09:42:52 +0100841 default: throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, "unrecognized type code");
Jens Geyer50806452019-11-23 01:55:58 +0100842 }
843 }
844
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100845 /// <summary>
846 /// Factory for JSON protocol objects
847 /// </summary>
Jens Geyer421444f2019-03-20 22:13:25 +0100848 public class Factory : TProtocolFactory
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100849 {
Jens Geyer421444f2019-03-20 22:13:25 +0100850 public override TProtocol GetProtocol(TTransport trans)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100851 {
852 return new TJsonProtocol(trans);
853 }
854 }
855
856 /// <summary>
857 /// Base class for tracking JSON contexts that may require
858 /// inserting/Reading additional JSON syntax characters
859 /// This base context does nothing.
860 /// </summary>
861 protected class JSONBaseContext
862 {
863 protected TJsonProtocol Proto;
864
865 public JSONBaseContext(TJsonProtocol proto)
866 {
867 Proto = proto;
868 }
869
Jens Geyerdce22992020-05-16 23:02:27 +0200870 public virtual Task WriteConditionalDelimiterAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100871 {
Jens Geyerdce22992020-05-16 23:02:27 +0200872 cancellationToken.ThrowIfCancellationRequested();
873 return Task.CompletedTask;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100874 }
875
Jens Geyerdce22992020-05-16 23:02:27 +0200876 public virtual Task ReadConditionalDelimiterAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100877 {
Jens Geyerdce22992020-05-16 23:02:27 +0200878 cancellationToken.ThrowIfCancellationRequested();
879 return Task.CompletedTask;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100880 }
881
882 public virtual bool EscapeNumbers()
883 {
884 return false;
885 }
886 }
887
888 /// <summary>
889 /// Context for JSON lists. Will insert/Read commas before each item except
890 /// for the first one
891 /// </summary>
892 protected class JSONListContext : JSONBaseContext
893 {
894 private bool _first = true;
895
896 public JSONListContext(TJsonProtocol protocol)
897 : base(protocol)
898 {
899 }
900
Jens Geyer2ff952b2019-04-13 19:46:54 +0200901 public override async Task WriteConditionalDelimiterAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100902 {
903 if (_first)
904 {
905 _first = false;
906 }
907 else
908 {
909 await Proto.Trans.WriteAsync(TJSONProtocolConstants.Comma, cancellationToken);
910 }
911 }
912
Jens Geyer2ff952b2019-04-13 19:46:54 +0200913 public override async Task ReadConditionalDelimiterAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100914 {
915 if (_first)
916 {
917 _first = false;
918 }
919 else
920 {
921 await Proto.ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Comma, cancellationToken);
922 }
923 }
924 }
925
926 /// <summary>
927 /// Context for JSON records. Will insert/Read colons before the value portion
928 /// of each record pair, and commas before each key except the first. In
929 /// addition, will indicate that numbers in the key position need to be
930 /// escaped in quotes (since JSON keys must be strings).
931 /// </summary>
932 // ReSharper disable once InconsistentNaming
933 protected class JSONPairContext : JSONBaseContext
934 {
935 private bool _colon = true;
936
937 private bool _first = true;
938
939 public JSONPairContext(TJsonProtocol proto)
940 : base(proto)
941 {
942 }
943
Jens Geyer2ff952b2019-04-13 19:46:54 +0200944 public override async Task WriteConditionalDelimiterAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100945 {
946 if (_first)
947 {
948 _first = false;
949 _colon = true;
950 }
951 else
952 {
953 await Proto.Trans.WriteAsync(_colon ? TJSONProtocolConstants.Colon : TJSONProtocolConstants.Comma, cancellationToken);
954 _colon = !_colon;
955 }
956 }
957
Jens Geyer2ff952b2019-04-13 19:46:54 +0200958 public override async Task ReadConditionalDelimiterAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100959 {
960 if (_first)
961 {
962 _first = false;
963 _colon = true;
964 }
965 else
966 {
967 await Proto.ReadJsonSyntaxCharAsync(_colon ? TJSONProtocolConstants.Colon : TJSONProtocolConstants.Comma, cancellationToken);
968 _colon = !_colon;
969 }
970 }
971
972 public override bool EscapeNumbers()
973 {
974 return _colon;
975 }
976 }
977
978 /// <summary>
979 /// Holds up to one byte from the transport
980 /// </summary>
981 protected class LookaheadReader
982 {
983 private readonly byte[] _data = new byte[1];
984
985 private bool _hasData;
986 protected TJsonProtocol Proto;
987
988 public LookaheadReader(TJsonProtocol proto)
989 {
990 Proto = proto;
991 }
992
993 /// <summary>
994 /// Return and consume the next byte to be Read, either taking it from the
995 /// data buffer if present or getting it from the transport otherwise.
996 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +0200997 public async ValueTask<byte> ReadAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100998 {
Jens Geyerdce22992020-05-16 23:02:27 +0200999 cancellationToken.ThrowIfCancellationRequested();
Jens Geyeraa0c8b32019-01-28 23:27:45 +01001000
1001 if (_hasData)
1002 {
1003 _hasData = false;
1004 }
1005 else
1006 {
1007 // find more easy way to avoid exception on reading primitive types
1008 await Proto.Trans.ReadAllAsync(_data, 0, 1, cancellationToken);
1009 }
1010 return _data[0];
1011 }
1012
1013 /// <summary>
1014 /// Return the next byte to be Read without consuming, filling the data
1015 /// buffer if it has not been filled alReady.
1016 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +02001017 public async ValueTask<byte> PeekAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +01001018 {
Jens Geyerdce22992020-05-16 23:02:27 +02001019 cancellationToken.ThrowIfCancellationRequested();
Jens Geyeraa0c8b32019-01-28 23:27:45 +01001020
1021 if (!_hasData)
1022 {
1023 // find more easy way to avoid exception on reading primitive types
1024 await Proto.Trans.ReadAllAsync(_data, 0, 1, cancellationToken);
Jens Geyer5a17b132019-05-26 15:53:37 +02001025 _hasData = true;
Jens Geyeraa0c8b32019-01-28 23:27:45 +01001026 }
Jens Geyeraa0c8b32019-01-28 23:27:45 +01001027 return _data[0];
1028 }
1029 }
1030 }
1031}