blob: 02cf3ed4e7f7e5e9b93740209b58b20b54e340b8 [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 Geyer4115e952023-11-21 23:00:01 +010030#pragma warning disable IDE0079 // net20 - unneeded suppression
31#pragma warning disable IDE0290 // net8 - primary CTOR
Jens Geyere26b4a82024-11-12 23:53:04 +010032#pragma warning disable IDE0305 // net9 - collection init
33#pragma warning disable IDE0300 // net9 - collection init
Jens Geyer0d128322021-02-25 09:42:52 +010034
Jens Geyeraa0c8b32019-01-28 23:27:45 +010035namespace Thrift.Protocol
36{
37 /// <summary>
38 /// JSON protocol implementation for thrift.
39 /// This is a full-featured protocol supporting Write and Read.
40 /// Please see the C++ class header for a detailed description of the
41 /// protocol's wire format.
42 /// Adapted from the Java version.
43 /// </summary>
44 // ReSharper disable once InconsistentNaming
45 public class TJsonProtocol : TProtocol
46 {
47 private const long Version = 1;
48
49 // Temporary buffer used by several methods
50 private readonly byte[] _tempBuffer = new byte[4];
51
52 // Current context that we are in
53 protected JSONBaseContext Context;
54
55 // Stack of nested contexts that we may be in
56 protected Stack<JSONBaseContext> ContextStack = new Stack<JSONBaseContext>();
57
58 // Reader that manages a 1-byte buffer
59 protected LookaheadReader Reader;
60
61 // Default encoding
62 protected Encoding Utf8Encoding = Encoding.UTF8;
63
64 /// <summary>
65 /// TJsonProtocol Constructor
66 /// </summary>
67 public TJsonProtocol(TTransport trans)
68 : base(trans)
69 {
70 Context = new JSONBaseContext(this);
71 Reader = new LookaheadReader(this);
72 }
73
74 /// <summary>
75 /// Push a new JSON context onto the stack.
76 /// </summary>
77 protected void PushContext(JSONBaseContext c)
78 {
79 ContextStack.Push(Context);
80 Context = c;
81 }
82
83 /// <summary>
84 /// Pop the last JSON context off the stack
85 /// </summary>
86 protected void PopContext()
87 {
88 Context = ContextStack.Pop();
89 }
90
91 /// <summary>
Paulo Nevesf049ff32020-02-05 11:58:18 +010092 /// Resets the context stack to pristine state. Allows for reusal of the protocol
93 /// even in cases where the protocol instance was in an undefined state due to
94 /// dangling/stale/obsolete contexts
95 /// </summary>
Jens Geyer0d128322021-02-25 09:42:52 +010096 private void ResetContext()
Paulo Nevesf049ff32020-02-05 11:58:18 +010097 {
98 ContextStack.Clear();
99 Context = new JSONBaseContext(this);
100 }
101 /// <summary>
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100102 /// Read a byte that must match b[0]; otherwise an exception is thrown.
103 /// Marked protected to avoid synthetic accessor in JSONListContext.Read
104 /// and JSONPairContext.Read
105 /// </summary>
106 protected async Task ReadJsonSyntaxCharAsync(byte[] bytes, CancellationToken cancellationToken)
107 {
108 var ch = await Reader.ReadAsync(cancellationToken);
109 if (ch != bytes[0])
110 {
111 throw new TProtocolException(TProtocolException.INVALID_DATA, $"Unexpected character: {(char) ch}");
112 }
113 }
114
115 /// <summary>
116 /// Write the bytes in array buf as a JSON characters, escaping as needed
117 /// </summary>
118 private async Task WriteJsonStringAsync(byte[] bytes, CancellationToken cancellationToken)
119 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200120 await Context.WriteConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100121 await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken);
122
123 var len = bytes.Length;
124 for (var i = 0; i < len; i++)
125 {
126 if ((bytes[i] & 0x00FF) >= 0x30)
127 {
128 if (bytes[i] == TJSONProtocolConstants.Backslash[0])
129 {
130 await Trans.WriteAsync(TJSONProtocolConstants.Backslash, cancellationToken);
131 await Trans.WriteAsync(TJSONProtocolConstants.Backslash, cancellationToken);
132 }
133 else
134 {
Jens Geyerdce22992020-05-16 23:02:27 +0200135 await Trans.WriteAsync(bytes, i, 1, cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100136 }
137 }
138 else
139 {
140 _tempBuffer[0] = TJSONProtocolConstants.JsonCharTable[bytes[i]];
141 if (_tempBuffer[0] == 1)
142 {
143 await Trans.WriteAsync(bytes, i, 1, cancellationToken);
144 }
145 else if (_tempBuffer[0] > 1)
146 {
147 await Trans.WriteAsync(TJSONProtocolConstants.Backslash, cancellationToken);
148 await Trans.WriteAsync(_tempBuffer, 0, 1, cancellationToken);
149 }
150 else
151 {
152 await Trans.WriteAsync(TJSONProtocolConstants.EscSequences, cancellationToken);
153 _tempBuffer[0] = TJSONProtocolHelper.ToHexChar((byte) (bytes[i] >> 4));
154 _tempBuffer[1] = TJSONProtocolHelper.ToHexChar(bytes[i]);
155 await Trans.WriteAsync(_tempBuffer, 0, 2, cancellationToken);
156 }
157 }
158 }
159 await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken);
160 }
161
162 /// <summary>
163 /// Write out number as a JSON value. If the context dictates so, it will be
164 /// wrapped in quotes to output as a JSON string.
165 /// </summary>
166 private async Task WriteJsonIntegerAsync(long num, CancellationToken cancellationToken)
167 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200168 await Context.WriteConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100169 var str = num.ToString();
170
171 var escapeNum = Context.EscapeNumbers();
172 if (escapeNum)
173 {
174 await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken);
175 }
176
177 var bytes = Utf8Encoding.GetBytes(str);
178 await Trans.WriteAsync(bytes, cancellationToken);
179
180 if (escapeNum)
181 {
182 await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken);
183 }
184 }
185
186 /// <summary>
187 /// Write out a double as a JSON value. If it is NaN or infinity or if the
188 /// context dictates escaping, Write out as JSON string.
189 /// </summary>
190 private async Task WriteJsonDoubleAsync(double num, CancellationToken cancellationToken)
191 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200192 await Context.WriteConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100193 var str = num.ToString("G17", CultureInfo.InvariantCulture);
194 var special = false;
195
196 switch (str[0])
197 {
198 case 'N': // NaN
199 case 'I': // Infinity
200 special = true;
201 break;
202 case '-':
203 if (str[1] == 'I')
204 {
205 // -Infinity
206 special = true;
207 }
208 break;
209 }
210
211 var escapeNum = special || Context.EscapeNumbers();
212
213 if (escapeNum)
214 {
215 await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken);
216 }
217
218 await Trans.WriteAsync(Utf8Encoding.GetBytes(str), cancellationToken);
219
220 if (escapeNum)
221 {
222 await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken);
223 }
224 }
225
226 /// <summary>
227 /// Write out contents of byte array b as a JSON string with base-64 encoded
228 /// data
229 /// </summary>
230 private async Task WriteJsonBase64Async(byte[] bytes, CancellationToken cancellationToken)
231 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200232 await Context.WriteConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100233 await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken);
234
235 var len = bytes.Length;
236 var off = 0;
237
238 while (len >= 3)
239 {
240 // Encode 3 bytes at a time
241 TBase64Utils.Encode(bytes, off, 3, _tempBuffer, 0);
242 await Trans.WriteAsync(_tempBuffer, 0, 4, cancellationToken);
243 off += 3;
244 len -= 3;
245 }
246
247 if (len > 0)
248 {
249 // Encode remainder
250 TBase64Utils.Encode(bytes, off, len, _tempBuffer, 0);
251 await Trans.WriteAsync(_tempBuffer, 0, len + 1, cancellationToken);
252 }
253
254 await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken);
255 }
256
257 private async Task WriteJsonObjectStartAsync(CancellationToken cancellationToken)
258 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200259 await Context.WriteConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100260 await Trans.WriteAsync(TJSONProtocolConstants.LeftBrace, cancellationToken);
261 PushContext(new JSONPairContext(this));
262 }
263
264 private async Task WriteJsonObjectEndAsync(CancellationToken cancellationToken)
265 {
266 PopContext();
267 await Trans.WriteAsync(TJSONProtocolConstants.RightBrace, cancellationToken);
268 }
269
270 private async Task WriteJsonArrayStartAsync(CancellationToken cancellationToken)
271 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200272 await Context.WriteConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100273 await Trans.WriteAsync(TJSONProtocolConstants.LeftBracket, cancellationToken);
274 PushContext(new JSONListContext(this));
275 }
276
277 private async Task WriteJsonArrayEndAsync(CancellationToken cancellationToken)
278 {
279 PopContext();
280 await Trans.WriteAsync(TJSONProtocolConstants.RightBracket, cancellationToken);
281 }
282
283 public override async Task WriteMessageBeginAsync(TMessage message, CancellationToken cancellationToken)
284 {
Jens Geyer0d128322021-02-25 09:42:52 +0100285 ResetContext();
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100286 await WriteJsonArrayStartAsync(cancellationToken);
287 await WriteJsonIntegerAsync(Version, cancellationToken);
288
289 var b = Utf8Encoding.GetBytes(message.Name);
290 await WriteJsonStringAsync(b, cancellationToken);
291
292 await WriteJsonIntegerAsync((long) message.Type, cancellationToken);
293 await WriteJsonIntegerAsync(message.SeqID, cancellationToken);
294 }
295
296 public override async Task WriteMessageEndAsync(CancellationToken cancellationToken)
297 {
298 await WriteJsonArrayEndAsync(cancellationToken);
299 }
300
301 public override async Task WriteStructBeginAsync(TStruct @struct, CancellationToken cancellationToken)
302 {
303 await WriteJsonObjectStartAsync(cancellationToken);
304 }
305
306 public override async Task WriteStructEndAsync(CancellationToken cancellationToken)
307 {
308 await WriteJsonObjectEndAsync(cancellationToken);
309 }
310
311 public override async Task WriteFieldBeginAsync(TField field, CancellationToken cancellationToken)
312 {
313 await WriteJsonIntegerAsync(field.ID, cancellationToken);
314 await WriteJsonObjectStartAsync(cancellationToken);
315 await WriteJsonStringAsync(TJSONProtocolHelper.GetTypeNameForTypeId(field.Type), cancellationToken);
316 }
317
318 public override async Task WriteFieldEndAsync(CancellationToken cancellationToken)
319 {
320 await WriteJsonObjectEndAsync(cancellationToken);
321 }
322
Jens Geyerdce22992020-05-16 23:02:27 +0200323 public override Task WriteFieldStopAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100324 {
Jens Geyerdce22992020-05-16 23:02:27 +0200325 cancellationToken.ThrowIfCancellationRequested();
326 return Task.CompletedTask;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100327 }
328
329 public override async Task WriteMapBeginAsync(TMap map, CancellationToken cancellationToken)
330 {
331 await WriteJsonArrayStartAsync(cancellationToken);
332 await WriteJsonStringAsync(TJSONProtocolHelper.GetTypeNameForTypeId(map.KeyType), cancellationToken);
333 await WriteJsonStringAsync(TJSONProtocolHelper.GetTypeNameForTypeId(map.ValueType), cancellationToken);
334 await WriteJsonIntegerAsync(map.Count, cancellationToken);
335 await WriteJsonObjectStartAsync(cancellationToken);
336 }
337
338 public override async Task WriteMapEndAsync(CancellationToken cancellationToken)
339 {
340 await WriteJsonObjectEndAsync(cancellationToken);
341 await WriteJsonArrayEndAsync(cancellationToken);
342 }
343
344 public override async Task WriteListBeginAsync(TList list, CancellationToken cancellationToken)
345 {
346 await WriteJsonArrayStartAsync(cancellationToken);
347 await WriteJsonStringAsync(TJSONProtocolHelper.GetTypeNameForTypeId(list.ElementType), cancellationToken);
348 await WriteJsonIntegerAsync(list.Count, cancellationToken);
349 }
350
351 public override async Task WriteListEndAsync(CancellationToken cancellationToken)
352 {
353 await WriteJsonArrayEndAsync(cancellationToken);
354 }
355
356 public override async Task WriteSetBeginAsync(TSet set, CancellationToken cancellationToken)
357 {
358 await WriteJsonArrayStartAsync(cancellationToken);
359 await WriteJsonStringAsync(TJSONProtocolHelper.GetTypeNameForTypeId(set.ElementType), cancellationToken);
360 await WriteJsonIntegerAsync(set.Count, cancellationToken);
361 }
362
363 public override async Task WriteSetEndAsync(CancellationToken cancellationToken)
364 {
365 await WriteJsonArrayEndAsync(cancellationToken);
366 }
367
368 public override async Task WriteBoolAsync(bool b, CancellationToken cancellationToken)
369 {
370 await WriteJsonIntegerAsync(b ? 1 : 0, cancellationToken);
371 }
372
373 public override async Task WriteByteAsync(sbyte b, CancellationToken cancellationToken)
374 {
375 await WriteJsonIntegerAsync(b, cancellationToken);
376 }
377
378 public override async Task WriteI16Async(short i16, CancellationToken cancellationToken)
379 {
380 await WriteJsonIntegerAsync(i16, cancellationToken);
381 }
382
383 public override async Task WriteI32Async(int i32, CancellationToken cancellationToken)
384 {
385 await WriteJsonIntegerAsync(i32, cancellationToken);
386 }
387
388 public override async Task WriteI64Async(long i64, CancellationToken cancellationToken)
389 {
390 await WriteJsonIntegerAsync(i64, cancellationToken);
391 }
392
393 public override async Task WriteDoubleAsync(double d, CancellationToken cancellationToken)
394 {
395 await WriteJsonDoubleAsync(d, cancellationToken);
396 }
397
398 public override async Task WriteStringAsync(string s, CancellationToken cancellationToken)
399 {
400 var b = Utf8Encoding.GetBytes(s);
401 await WriteJsonStringAsync(b, cancellationToken);
402 }
403
404 public override async Task WriteBinaryAsync(byte[] bytes, CancellationToken cancellationToken)
405 {
406 await WriteJsonBase64Async(bytes, cancellationToken);
407 }
Jens Geyer62445c12022-06-29 00:00:00 +0200408 public override async Task WriteUuidAsync(Guid uuid, CancellationToken cancellationToken = default)
409 {
410 await WriteStringAsync(uuid.ToString("D"), cancellationToken); // no curly braces
411 }
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100412
413 /// <summary>
414 /// Read in a JSON string, unescaping as appropriate.. Skip Reading from the
415 /// context if skipContext is true.
416 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +0200417 private async ValueTask<byte[]> ReadJsonStringAsync(bool skipContext, CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100418 {
419 using (var buffer = new MemoryStream())
420 {
421 var codeunits = new List<char>();
422
423
424 if (!skipContext)
425 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200426 await Context.ReadConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100427 }
428
429 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Quote, cancellationToken);
430
431 while (true)
432 {
433 var ch = await Reader.ReadAsync(cancellationToken);
434 if (ch == TJSONProtocolConstants.Quote[0])
435 {
436 break;
437 }
438
439 // escaped?
440 if (ch != TJSONProtocolConstants.EscSequences[0])
441 {
Jens Geyer8e89abe2023-07-20 21:43:23 +0200442#if NET5_0_OR_GREATER
Jens Geyer0d128322021-02-25 09:42:52 +0100443 var wbuf = new[] { ch };
444 await buffer.WriteAsync(wbuf.AsMemory(0, 1), cancellationToken);
Jens Geyer8e89abe2023-07-20 21:43:23 +0200445#else
446 await buffer.WriteAsync(new[] { ch }, 0, 1, cancellationToken);
Jens Geyer0d128322021-02-25 09:42:52 +0100447#endif
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100448 continue;
449 }
450
451 // distinguish between \uXXXX and \?
452 ch = await Reader.ReadAsync(cancellationToken);
453 if (ch != TJSONProtocolConstants.EscSequences[1]) // control chars like \n
454 {
455 var off = Array.IndexOf(TJSONProtocolConstants.EscapeChars, (char) ch);
456 if (off == -1)
457 {
458 throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected control char");
459 }
460 ch = TJSONProtocolConstants.EscapeCharValues[off];
Jens Geyer8e89abe2023-07-20 21:43:23 +0200461#if NET5_0_OR_GREATER
Jens Geyer0d128322021-02-25 09:42:52 +0100462 var wbuf = new[] { ch };
463 await buffer.WriteAsync( wbuf.AsMemory(0, 1), cancellationToken);
Jens Geyer8e89abe2023-07-20 21:43:23 +0200464#else
465 await buffer.WriteAsync(new[] { ch }, 0, 1, cancellationToken);
Jens Geyer0d128322021-02-25 09:42:52 +0100466#endif
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100467 continue;
468 }
469
470 // it's \uXXXX
471 await Trans.ReadAllAsync(_tempBuffer, 0, 4, cancellationToken);
472
473 var wch = (short) ((TJSONProtocolHelper.ToHexVal(_tempBuffer[0]) << 12) +
474 (TJSONProtocolHelper.ToHexVal(_tempBuffer[1]) << 8) +
475 (TJSONProtocolHelper.ToHexVal(_tempBuffer[2]) << 4) +
476 TJSONProtocolHelper.ToHexVal(_tempBuffer[3]));
477
478 if (char.IsHighSurrogate((char) wch))
479 {
480 if (codeunits.Count > 0)
481 {
482 throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected low surrogate char");
483 }
484 codeunits.Add((char) wch);
485 }
486 else if (char.IsLowSurrogate((char) wch))
487 {
488 if (codeunits.Count == 0)
489 {
490 throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected high surrogate char");
491 }
492
493 codeunits.Add((char) wch);
494 var tmp = Utf8Encoding.GetBytes(codeunits.ToArray());
Jens Geyer8e89abe2023-07-20 21:43:23 +0200495#if NET5_0_OR_GREATER
Jens Geyer0d128322021-02-25 09:42:52 +0100496 await buffer.WriteAsync(tmp.AsMemory(0, tmp.Length), cancellationToken);
Jens Geyer8e89abe2023-07-20 21:43:23 +0200497#else
498 await buffer.WriteAsync(tmp, 0, tmp.Length, cancellationToken);
Jens Geyer0d128322021-02-25 09:42:52 +0100499#endif
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100500 codeunits.Clear();
501 }
502 else
503 {
Jens Geyer0d128322021-02-25 09:42:52 +0100504 var tmp = Utf8Encoding.GetBytes(new[] { (char)wch });
Jens Geyer8e89abe2023-07-20 21:43:23 +0200505#if NET5_0_OR_GREATER
Jens Geyer0d128322021-02-25 09:42:52 +0100506 await buffer.WriteAsync(tmp.AsMemory( 0, tmp.Length), cancellationToken);
Jens Geyer8e89abe2023-07-20 21:43:23 +0200507#else
508 await buffer.WriteAsync(tmp, 0, tmp.Length, cancellationToken);
Jens Geyer0d128322021-02-25 09:42:52 +0100509#endif
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100510 }
511 }
512
513 if (codeunits.Count > 0)
514 {
515 throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected low surrogate char");
516 }
517
518 return buffer.ToArray();
519 }
520 }
521
522 /// <summary>
523 /// Read in a sequence of characters that are all valid in JSON numbers. Does
524 /// not do a complete regex check to validate that this is actually a number.
525 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +0200526 private async ValueTask<string> ReadJsonNumericCharsAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100527 {
528 var strbld = new StringBuilder();
529 while (true)
530 {
531 //TODO: workaround for primitive types with TJsonProtocol, think - how to rewrite into more easy form without exceptions
532 try
533 {
534 var ch = await Reader.PeekAsync(cancellationToken);
535 if (!TJSONProtocolHelper.IsJsonNumeric(ch))
536 {
537 break;
538 }
539 var c = (char)await Reader.ReadAsync(cancellationToken);
540 strbld.Append(c);
541 }
542 catch (TTransportException)
543 {
544 break;
545 }
546 }
547 return strbld.ToString();
548 }
549
550 /// <summary>
551 /// Read in a JSON number. If the context dictates, Read in enclosing quotes.
552 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +0200553 private async ValueTask<long> ReadJsonIntegerAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100554 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200555 await Context.ReadConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100556 if (Context.EscapeNumbers())
557 {
558 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Quote, cancellationToken);
559 }
560
561 var str = await ReadJsonNumericCharsAsync(cancellationToken);
562 if (Context.EscapeNumbers())
563 {
564 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Quote, cancellationToken);
565 }
566
567 try
568 {
569 return long.Parse(str);
570 }
571 catch (FormatException)
572 {
573 throw new TProtocolException(TProtocolException.INVALID_DATA, "Bad data encounted in numeric data");
574 }
575 }
576
577 /// <summary>
578 /// Read in a JSON double value. Throw if the value is not wrapped in quotes
579 /// when expected or if wrapped in quotes when not expected.
580 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +0200581 private async ValueTask<double> ReadJsonDoubleAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100582 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200583 await Context.ReadConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100584 if (await Reader.PeekAsync(cancellationToken) == TJSONProtocolConstants.Quote[0])
585 {
586 var arr = await ReadJsonStringAsync(true, cancellationToken);
587 var dub = double.Parse(Utf8Encoding.GetString(arr, 0, arr.Length), CultureInfo.InvariantCulture);
588
589 if (!Context.EscapeNumbers() && !double.IsNaN(dub) && !double.IsInfinity(dub))
590 {
591 // Throw exception -- we should not be in a string in this case
592 throw new TProtocolException(TProtocolException.INVALID_DATA, "Numeric data unexpectedly quoted");
593 }
594
595 return dub;
596 }
597
598 if (Context.EscapeNumbers())
599 {
600 // This will throw - we should have had a quote if escapeNum == true
601 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Quote, cancellationToken);
602 }
603
604 try
605 {
606 return double.Parse(await ReadJsonNumericCharsAsync(cancellationToken), CultureInfo.InvariantCulture);
607 }
608 catch (FormatException)
609 {
610 throw new TProtocolException(TProtocolException.INVALID_DATA, "Bad data encounted in numeric data");
611 }
612 }
613
614 /// <summary>
615 /// Read in a JSON string containing base-64 encoded data and decode it.
616 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +0200617 private async ValueTask<byte[]> ReadJsonBase64Async(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100618 {
619 var b = await ReadJsonStringAsync(false, cancellationToken);
620 var len = b.Length;
621 var off = 0;
622 var size = 0;
623
624 // reduce len to ignore fill bytes
625 while ((len > 0) && (b[len - 1] == '='))
626 {
627 --len;
628 }
629
630 // read & decode full byte triplets = 4 source bytes
631 while (len > 4)
632 {
633 // Decode 4 bytes at a time
634 TBase64Utils.Decode(b, off, 4, b, size); // NB: decoded in place
635 off += 4;
636 len -= 4;
637 size += 3;
638 }
639
640 // Don't decode if we hit the end or got a single leftover byte (invalid
641 // base64 but legal for skip of regular string exType)
642 if (len > 1)
643 {
644 // Decode remainder
645 TBase64Utils.Decode(b, off, len, b, size); // NB: decoded in place
646 size += len - 1;
647 }
648
649 // Sadly we must copy the byte[] (any way around this?)
650 var result = new byte[size];
651 Array.Copy(b, 0, result, 0, size);
652 return result;
653 }
654
655 private async Task ReadJsonObjectStartAsync(CancellationToken cancellationToken)
656 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200657 await Context.ReadConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100658 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.LeftBrace, cancellationToken);
659 PushContext(new JSONPairContext(this));
660 }
661
662 private async Task ReadJsonObjectEndAsync(CancellationToken cancellationToken)
663 {
664 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.RightBrace, cancellationToken);
665 PopContext();
666 }
667
668 private async Task ReadJsonArrayStartAsync(CancellationToken cancellationToken)
669 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200670 await Context.ReadConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100671 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.LeftBracket, cancellationToken);
672 PushContext(new JSONListContext(this));
673 }
674
675 private async Task ReadJsonArrayEndAsync(CancellationToken cancellationToken)
676 {
677 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.RightBracket, cancellationToken);
678 PopContext();
679 }
680
Jens Geyer5a17b132019-05-26 15:53:37 +0200681 public override async ValueTask<TMessage> ReadMessageBeginAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100682 {
683 var message = new TMessage();
Paulo Nevesf049ff32020-02-05 11:58:18 +0100684
Jens Geyer0d128322021-02-25 09:42:52 +0100685 ResetContext();
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100686 await ReadJsonArrayStartAsync(cancellationToken);
687 if (await ReadJsonIntegerAsync(cancellationToken) != Version)
688 {
689 throw new TProtocolException(TProtocolException.BAD_VERSION, "Message contained bad version.");
690 }
691
692 var buf = await ReadJsonStringAsync(false, cancellationToken);
693 message.Name = Utf8Encoding.GetString(buf, 0, buf.Length);
694 message.Type = (TMessageType) await ReadJsonIntegerAsync(cancellationToken);
695 message.SeqID = (int) await ReadJsonIntegerAsync(cancellationToken);
696 return message;
697 }
698
699 public override async Task ReadMessageEndAsync(CancellationToken cancellationToken)
700 {
Philip Lee2d2790f2022-09-15 12:43:03 +0100701 cancellationToken.ThrowIfCancellationRequested();
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100702 await ReadJsonArrayEndAsync(cancellationToken);
Philip Lee2d2790f2022-09-15 12:43:03 +0100703 Transport.ResetConsumedMessageSize();
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100704 }
705
Jens Geyer5a17b132019-05-26 15:53:37 +0200706 public override async ValueTask<TStruct> ReadStructBeginAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100707 {
708 await ReadJsonObjectStartAsync(cancellationToken);
Jens Geyerdce22992020-05-16 23:02:27 +0200709
710 return AnonymousStruct;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100711 }
712
713 public override async Task ReadStructEndAsync(CancellationToken cancellationToken)
714 {
715 await ReadJsonObjectEndAsync(cancellationToken);
716 }
717
Jens Geyer5a17b132019-05-26 15:53:37 +0200718 public override async ValueTask<TField> ReadFieldBeginAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100719 {
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100720 var ch = await Reader.PeekAsync(cancellationToken);
721 if (ch == TJSONProtocolConstants.RightBrace[0])
722 {
Jens Geyerdce22992020-05-16 23:02:27 +0200723 return StopField;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100724 }
Jens Geyerdce22992020-05-16 23:02:27 +0200725
726 var field = new TField()
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100727 {
Jens Geyerdce22992020-05-16 23:02:27 +0200728 ID = (short)await ReadJsonIntegerAsync(cancellationToken)
729 };
730
731 await ReadJsonObjectStartAsync(cancellationToken);
732 field.Type = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken));
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100733 return field;
734 }
735
736 public override async Task ReadFieldEndAsync(CancellationToken cancellationToken)
737 {
738 await ReadJsonObjectEndAsync(cancellationToken);
739 }
740
Jens Geyer5a17b132019-05-26 15:53:37 +0200741 public override async ValueTask<TMap> ReadMapBeginAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100742 {
743 var map = new TMap();
744 await ReadJsonArrayStartAsync(cancellationToken);
745 map.KeyType = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken));
746 map.ValueType = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken));
747 map.Count = (int) await ReadJsonIntegerAsync(cancellationToken);
Jens Geyer50806452019-11-23 01:55:58 +0100748 CheckReadBytesAvailable(map);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100749 await ReadJsonObjectStartAsync(cancellationToken);
750 return map;
751 }
752
753 public override async Task ReadMapEndAsync(CancellationToken cancellationToken)
754 {
755 await ReadJsonObjectEndAsync(cancellationToken);
756 await ReadJsonArrayEndAsync(cancellationToken);
757 }
758
Jens Geyer5a17b132019-05-26 15:53:37 +0200759 public override async ValueTask<TList> ReadListBeginAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100760 {
761 var list = new TList();
762 await ReadJsonArrayStartAsync(cancellationToken);
763 list.ElementType = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken));
764 list.Count = (int) await ReadJsonIntegerAsync(cancellationToken);
Jens Geyer50806452019-11-23 01:55:58 +0100765 CheckReadBytesAvailable(list);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100766 return list;
767 }
768
769 public override async Task ReadListEndAsync(CancellationToken cancellationToken)
770 {
771 await ReadJsonArrayEndAsync(cancellationToken);
772 }
773
Jens Geyer5a17b132019-05-26 15:53:37 +0200774 public override async ValueTask<TSet> ReadSetBeginAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100775 {
776 var set = new TSet();
777 await ReadJsonArrayStartAsync(cancellationToken);
778 set.ElementType = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken));
779 set.Count = (int) await ReadJsonIntegerAsync(cancellationToken);
Jens Geyer50806452019-11-23 01:55:58 +0100780 CheckReadBytesAvailable(set);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100781 return set;
782 }
783
784 public override async Task ReadSetEndAsync(CancellationToken cancellationToken)
785 {
786 await ReadJsonArrayEndAsync(cancellationToken);
787 }
788
Jens Geyer5a17b132019-05-26 15:53:37 +0200789 public override async ValueTask<bool> ReadBoolAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100790 {
791 return await ReadJsonIntegerAsync(cancellationToken) != 0;
792 }
793
Jens Geyer5a17b132019-05-26 15:53:37 +0200794 public override async ValueTask<sbyte> ReadByteAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100795 {
796 return (sbyte) await ReadJsonIntegerAsync(cancellationToken);
797 }
798
Jens Geyer5a17b132019-05-26 15:53:37 +0200799 public override async ValueTask<short> ReadI16Async(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100800 {
801 return (short) await ReadJsonIntegerAsync(cancellationToken);
802 }
803
Jens Geyer5a17b132019-05-26 15:53:37 +0200804 public override async ValueTask<int> ReadI32Async(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100805 {
806 return (int) await ReadJsonIntegerAsync(cancellationToken);
807 }
808
Jens Geyer5a17b132019-05-26 15:53:37 +0200809 public override async ValueTask<long> ReadI64Async(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100810 {
811 return await ReadJsonIntegerAsync(cancellationToken);
812 }
813
Jens Geyer5a17b132019-05-26 15:53:37 +0200814 public override async ValueTask<double> ReadDoubleAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100815 {
816 return await ReadJsonDoubleAsync(cancellationToken);
817 }
818
Jens Geyer5a17b132019-05-26 15:53:37 +0200819 public override async ValueTask<string> ReadStringAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100820 {
821 var buf = await ReadJsonStringAsync(false, cancellationToken);
822 return Utf8Encoding.GetString(buf, 0, buf.Length);
823 }
824
Jens Geyer5a17b132019-05-26 15:53:37 +0200825 public override async ValueTask<byte[]> ReadBinaryAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100826 {
827 return await ReadJsonBase64Async(cancellationToken);
828 }
829
Jens Geyer62445c12022-06-29 00:00:00 +0200830 public override async ValueTask<Guid> ReadUuidAsync(CancellationToken cancellationToken = default)
831 {
832 return new Guid( await ReadStringAsync(cancellationToken));
833 }
834
Jens Geyer50806452019-11-23 01:55:58 +0100835 // Return the minimum number of bytes a type will consume on the wire
836 public override int GetMinSerializedSize(TType type)
837 {
838 switch (type)
839 {
840 case TType.Stop: return 0;
841 case TType.Void: return 0;
842 case TType.Bool: return 1; // written as int
843 case TType.Byte: return 1;
844 case TType.Double: return 1;
845 case TType.I16: return 1;
846 case TType.I32: return 1;
847 case TType.I64: return 1;
848 case TType.String: return 2; // empty string
849 case TType.Struct: return 2; // empty struct
850 case TType.Map: return 2; // empty map
851 case TType.Set: return 2; // empty set
852 case TType.List: return 2; // empty list
Jens Geyer62445c12022-06-29 00:00:00 +0200853 case TType.Uuid: return 36; // "E236974D-F0B0-4E05-8F29-0B455D41B1A1"
Jens Geyer0d128322021-02-25 09:42:52 +0100854 default: throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, "unrecognized type code");
Jens Geyer50806452019-11-23 01:55:58 +0100855 }
856 }
857
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100858 /// <summary>
859 /// Factory for JSON protocol objects
860 /// </summary>
Jens Geyer421444f2019-03-20 22:13:25 +0100861 public class Factory : TProtocolFactory
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100862 {
Jens Geyer421444f2019-03-20 22:13:25 +0100863 public override TProtocol GetProtocol(TTransport trans)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100864 {
865 return new TJsonProtocol(trans);
866 }
867 }
868
869 /// <summary>
870 /// Base class for tracking JSON contexts that may require
871 /// inserting/Reading additional JSON syntax characters
872 /// This base context does nothing.
873 /// </summary>
874 protected class JSONBaseContext
875 {
876 protected TJsonProtocol Proto;
877
878 public JSONBaseContext(TJsonProtocol proto)
879 {
880 Proto = proto;
881 }
882
Jens Geyerdce22992020-05-16 23:02:27 +0200883 public virtual Task WriteConditionalDelimiterAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100884 {
Jens Geyerdce22992020-05-16 23:02:27 +0200885 cancellationToken.ThrowIfCancellationRequested();
886 return Task.CompletedTask;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100887 }
888
Jens Geyerdce22992020-05-16 23:02:27 +0200889 public virtual Task ReadConditionalDelimiterAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100890 {
Jens Geyerdce22992020-05-16 23:02:27 +0200891 cancellationToken.ThrowIfCancellationRequested();
892 return Task.CompletedTask;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100893 }
894
895 public virtual bool EscapeNumbers()
896 {
897 return false;
898 }
899 }
900
901 /// <summary>
902 /// Context for JSON lists. Will insert/Read commas before each item except
903 /// for the first one
904 /// </summary>
905 protected class JSONListContext : JSONBaseContext
906 {
907 private bool _first = true;
908
909 public JSONListContext(TJsonProtocol protocol)
910 : base(protocol)
911 {
912 }
913
Jens Geyer2ff952b2019-04-13 19:46:54 +0200914 public override async Task WriteConditionalDelimiterAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100915 {
916 if (_first)
917 {
918 _first = false;
919 }
920 else
921 {
922 await Proto.Trans.WriteAsync(TJSONProtocolConstants.Comma, cancellationToken);
923 }
924 }
925
Jens Geyer2ff952b2019-04-13 19:46:54 +0200926 public override async Task ReadConditionalDelimiterAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100927 {
928 if (_first)
929 {
930 _first = false;
931 }
932 else
933 {
934 await Proto.ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Comma, cancellationToken);
935 }
936 }
937 }
938
939 /// <summary>
940 /// Context for JSON records. Will insert/Read colons before the value portion
941 /// of each record pair, and commas before each key except the first. In
942 /// addition, will indicate that numbers in the key position need to be
943 /// escaped in quotes (since JSON keys must be strings).
944 /// </summary>
945 // ReSharper disable once InconsistentNaming
946 protected class JSONPairContext : JSONBaseContext
947 {
948 private bool _colon = true;
949
950 private bool _first = true;
951
952 public JSONPairContext(TJsonProtocol proto)
953 : base(proto)
954 {
955 }
956
Jens Geyer2ff952b2019-04-13 19:46:54 +0200957 public override async Task WriteConditionalDelimiterAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100958 {
959 if (_first)
960 {
961 _first = false;
962 _colon = true;
963 }
964 else
965 {
966 await Proto.Trans.WriteAsync(_colon ? TJSONProtocolConstants.Colon : TJSONProtocolConstants.Comma, cancellationToken);
967 _colon = !_colon;
968 }
969 }
970
Jens Geyer2ff952b2019-04-13 19:46:54 +0200971 public override async Task ReadConditionalDelimiterAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100972 {
973 if (_first)
974 {
975 _first = false;
976 _colon = true;
977 }
978 else
979 {
980 await Proto.ReadJsonSyntaxCharAsync(_colon ? TJSONProtocolConstants.Colon : TJSONProtocolConstants.Comma, cancellationToken);
981 _colon = !_colon;
982 }
983 }
984
985 public override bool EscapeNumbers()
986 {
987 return _colon;
988 }
989 }
990
991 /// <summary>
992 /// Holds up to one byte from the transport
993 /// </summary>
994 protected class LookaheadReader
995 {
996 private readonly byte[] _data = new byte[1];
997
998 private bool _hasData;
999 protected TJsonProtocol Proto;
1000
1001 public LookaheadReader(TJsonProtocol proto)
1002 {
1003 Proto = proto;
1004 }
1005
1006 /// <summary>
1007 /// Return and consume the next byte to be Read, either taking it from the
1008 /// data buffer if present or getting it from the transport otherwise.
1009 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +02001010 public async ValueTask<byte> ReadAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +01001011 {
Jens Geyerdce22992020-05-16 23:02:27 +02001012 cancellationToken.ThrowIfCancellationRequested();
Jens Geyeraa0c8b32019-01-28 23:27:45 +01001013
1014 if (_hasData)
1015 {
1016 _hasData = false;
1017 }
1018 else
1019 {
1020 // find more easy way to avoid exception on reading primitive types
1021 await Proto.Trans.ReadAllAsync(_data, 0, 1, cancellationToken);
1022 }
1023 return _data[0];
1024 }
1025
1026 /// <summary>
1027 /// Return the next byte to be Read without consuming, filling the data
1028 /// buffer if it has not been filled alReady.
1029 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +02001030 public async ValueTask<byte> PeekAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +01001031 {
Jens Geyerdce22992020-05-16 23:02:27 +02001032 cancellationToken.ThrowIfCancellationRequested();
Jens Geyeraa0c8b32019-01-28 23:27:45 +01001033
1034 if (!_hasData)
1035 {
1036 // find more easy way to avoid exception on reading primitive types
1037 await Proto.Trans.ReadAllAsync(_data, 0, 1, cancellationToken);
Jens Geyer5a17b132019-05-26 15:53:37 +02001038 _hasData = true;
Jens Geyeraa0c8b32019-01-28 23:27:45 +01001039 }
Jens Geyeraa0c8b32019-01-28 23:27:45 +01001040 return _data[0];
1041 }
1042 }
1043 }
1044}