blob: 37424a74ffaf81877cdac2b90e12f22e9c8849c6 [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
Jens Geyer92e5b712025-06-04 23:05:40 +0200471 Trans.CheckReadBytesAvailable(4);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100472 await Trans.ReadAllAsync(_tempBuffer, 0, 4, cancellationToken);
473
474 var wch = (short) ((TJSONProtocolHelper.ToHexVal(_tempBuffer[0]) << 12) +
475 (TJSONProtocolHelper.ToHexVal(_tempBuffer[1]) << 8) +
476 (TJSONProtocolHelper.ToHexVal(_tempBuffer[2]) << 4) +
477 TJSONProtocolHelper.ToHexVal(_tempBuffer[3]));
478
479 if (char.IsHighSurrogate((char) wch))
480 {
481 if (codeunits.Count > 0)
482 {
483 throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected low surrogate char");
484 }
485 codeunits.Add((char) wch);
486 }
487 else if (char.IsLowSurrogate((char) wch))
488 {
489 if (codeunits.Count == 0)
490 {
491 throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected high surrogate char");
492 }
493
494 codeunits.Add((char) wch);
495 var tmp = Utf8Encoding.GetBytes(codeunits.ToArray());
Jens Geyer8e89abe2023-07-20 21:43:23 +0200496#if NET5_0_OR_GREATER
Jens Geyer0d128322021-02-25 09:42:52 +0100497 await buffer.WriteAsync(tmp.AsMemory(0, tmp.Length), cancellationToken);
Jens Geyer8e89abe2023-07-20 21:43:23 +0200498#else
499 await buffer.WriteAsync(tmp, 0, tmp.Length, cancellationToken);
Jens Geyer0d128322021-02-25 09:42:52 +0100500#endif
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100501 codeunits.Clear();
502 }
503 else
504 {
Jens Geyer0d128322021-02-25 09:42:52 +0100505 var tmp = Utf8Encoding.GetBytes(new[] { (char)wch });
Jens Geyer8e89abe2023-07-20 21:43:23 +0200506#if NET5_0_OR_GREATER
Jens Geyer0d128322021-02-25 09:42:52 +0100507 await buffer.WriteAsync(tmp.AsMemory( 0, tmp.Length), cancellationToken);
Jens Geyer8e89abe2023-07-20 21:43:23 +0200508#else
509 await buffer.WriteAsync(tmp, 0, tmp.Length, cancellationToken);
Jens Geyer0d128322021-02-25 09:42:52 +0100510#endif
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100511 }
512 }
513
514 if (codeunits.Count > 0)
515 {
516 throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected low surrogate char");
517 }
518
519 return buffer.ToArray();
520 }
521 }
522
523 /// <summary>
524 /// Read in a sequence of characters that are all valid in JSON numbers. Does
525 /// not do a complete regex check to validate that this is actually a number.
526 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +0200527 private async ValueTask<string> ReadJsonNumericCharsAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100528 {
529 var strbld = new StringBuilder();
530 while (true)
531 {
532 //TODO: workaround for primitive types with TJsonProtocol, think - how to rewrite into more easy form without exceptions
533 try
534 {
535 var ch = await Reader.PeekAsync(cancellationToken);
536 if (!TJSONProtocolHelper.IsJsonNumeric(ch))
537 {
538 break;
539 }
540 var c = (char)await Reader.ReadAsync(cancellationToken);
541 strbld.Append(c);
542 }
543 catch (TTransportException)
544 {
545 break;
546 }
547 }
548 return strbld.ToString();
549 }
550
551 /// <summary>
552 /// Read in a JSON number. If the context dictates, Read in enclosing quotes.
553 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +0200554 private async ValueTask<long> ReadJsonIntegerAsync(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 (Context.EscapeNumbers())
558 {
559 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Quote, cancellationToken);
560 }
561
562 var str = await ReadJsonNumericCharsAsync(cancellationToken);
563 if (Context.EscapeNumbers())
564 {
565 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Quote, cancellationToken);
566 }
567
568 try
569 {
570 return long.Parse(str);
571 }
572 catch (FormatException)
573 {
574 throw new TProtocolException(TProtocolException.INVALID_DATA, "Bad data encounted in numeric data");
575 }
576 }
577
578 /// <summary>
579 /// Read in a JSON double value. Throw if the value is not wrapped in quotes
580 /// when expected or if wrapped in quotes when not expected.
581 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +0200582 private async ValueTask<double> ReadJsonDoubleAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100583 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200584 await Context.ReadConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100585 if (await Reader.PeekAsync(cancellationToken) == TJSONProtocolConstants.Quote[0])
586 {
587 var arr = await ReadJsonStringAsync(true, cancellationToken);
588 var dub = double.Parse(Utf8Encoding.GetString(arr, 0, arr.Length), CultureInfo.InvariantCulture);
589
590 if (!Context.EscapeNumbers() && !double.IsNaN(dub) && !double.IsInfinity(dub))
591 {
592 // Throw exception -- we should not be in a string in this case
593 throw new TProtocolException(TProtocolException.INVALID_DATA, "Numeric data unexpectedly quoted");
594 }
595
596 return dub;
597 }
598
599 if (Context.EscapeNumbers())
600 {
601 // This will throw - we should have had a quote if escapeNum == true
602 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Quote, cancellationToken);
603 }
604
605 try
606 {
607 return double.Parse(await ReadJsonNumericCharsAsync(cancellationToken), CultureInfo.InvariantCulture);
608 }
609 catch (FormatException)
610 {
611 throw new TProtocolException(TProtocolException.INVALID_DATA, "Bad data encounted in numeric data");
612 }
613 }
614
615 /// <summary>
616 /// Read in a JSON string containing base-64 encoded data and decode it.
617 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +0200618 private async ValueTask<byte[]> ReadJsonBase64Async(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100619 {
620 var b = await ReadJsonStringAsync(false, cancellationToken);
621 var len = b.Length;
622 var off = 0;
623 var size = 0;
624
625 // reduce len to ignore fill bytes
626 while ((len > 0) && (b[len - 1] == '='))
627 {
628 --len;
629 }
630
631 // read & decode full byte triplets = 4 source bytes
632 while (len > 4)
633 {
634 // Decode 4 bytes at a time
635 TBase64Utils.Decode(b, off, 4, b, size); // NB: decoded in place
636 off += 4;
637 len -= 4;
638 size += 3;
639 }
640
641 // Don't decode if we hit the end or got a single leftover byte (invalid
642 // base64 but legal for skip of regular string exType)
643 if (len > 1)
644 {
645 // Decode remainder
646 TBase64Utils.Decode(b, off, len, b, size); // NB: decoded in place
647 size += len - 1;
648 }
649
650 // Sadly we must copy the byte[] (any way around this?)
651 var result = new byte[size];
652 Array.Copy(b, 0, result, 0, size);
653 return result;
654 }
655
656 private async Task ReadJsonObjectStartAsync(CancellationToken cancellationToken)
657 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200658 await Context.ReadConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100659 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.LeftBrace, cancellationToken);
660 PushContext(new JSONPairContext(this));
661 }
662
663 private async Task ReadJsonObjectEndAsync(CancellationToken cancellationToken)
664 {
665 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.RightBrace, cancellationToken);
666 PopContext();
667 }
668
669 private async Task ReadJsonArrayStartAsync(CancellationToken cancellationToken)
670 {
Jens Geyer2ff952b2019-04-13 19:46:54 +0200671 await Context.ReadConditionalDelimiterAsync(cancellationToken);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100672 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.LeftBracket, cancellationToken);
673 PushContext(new JSONListContext(this));
674 }
675
676 private async Task ReadJsonArrayEndAsync(CancellationToken cancellationToken)
677 {
678 await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.RightBracket, cancellationToken);
679 PopContext();
680 }
681
Jens Geyer5a17b132019-05-26 15:53:37 +0200682 public override async ValueTask<TMessage> ReadMessageBeginAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100683 {
684 var message = new TMessage();
Paulo Nevesf049ff32020-02-05 11:58:18 +0100685
Jens Geyer0d128322021-02-25 09:42:52 +0100686 ResetContext();
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100687 await ReadJsonArrayStartAsync(cancellationToken);
688 if (await ReadJsonIntegerAsync(cancellationToken) != Version)
689 {
690 throw new TProtocolException(TProtocolException.BAD_VERSION, "Message contained bad version.");
691 }
692
693 var buf = await ReadJsonStringAsync(false, cancellationToken);
694 message.Name = Utf8Encoding.GetString(buf, 0, buf.Length);
695 message.Type = (TMessageType) await ReadJsonIntegerAsync(cancellationToken);
696 message.SeqID = (int) await ReadJsonIntegerAsync(cancellationToken);
697 return message;
698 }
699
700 public override async Task ReadMessageEndAsync(CancellationToken cancellationToken)
701 {
Philip Lee2d2790f2022-09-15 12:43:03 +0100702 cancellationToken.ThrowIfCancellationRequested();
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100703 await ReadJsonArrayEndAsync(cancellationToken);
Jens Geyer960bf512025-02-05 23:47:54 +0100704 Transport.ResetMessageSizeAndConsumedBytes();
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100705 }
706
Jens Geyer5a17b132019-05-26 15:53:37 +0200707 public override async ValueTask<TStruct> ReadStructBeginAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100708 {
709 await ReadJsonObjectStartAsync(cancellationToken);
Jens Geyerdce22992020-05-16 23:02:27 +0200710
711 return AnonymousStruct;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100712 }
713
714 public override async Task ReadStructEndAsync(CancellationToken cancellationToken)
715 {
716 await ReadJsonObjectEndAsync(cancellationToken);
717 }
718
Jens Geyer5a17b132019-05-26 15:53:37 +0200719 public override async ValueTask<TField> ReadFieldBeginAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100720 {
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100721 var ch = await Reader.PeekAsync(cancellationToken);
722 if (ch == TJSONProtocolConstants.RightBrace[0])
723 {
Jens Geyerdce22992020-05-16 23:02:27 +0200724 return StopField;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100725 }
Jens Geyerdce22992020-05-16 23:02:27 +0200726
727 var field = new TField()
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100728 {
Jens Geyerdce22992020-05-16 23:02:27 +0200729 ID = (short)await ReadJsonIntegerAsync(cancellationToken)
730 };
731
732 await ReadJsonObjectStartAsync(cancellationToken);
733 field.Type = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken));
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100734 return field;
735 }
736
737 public override async Task ReadFieldEndAsync(CancellationToken cancellationToken)
738 {
739 await ReadJsonObjectEndAsync(cancellationToken);
740 }
741
Jens Geyer5a17b132019-05-26 15:53:37 +0200742 public override async ValueTask<TMap> ReadMapBeginAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100743 {
744 var map = new TMap();
745 await ReadJsonArrayStartAsync(cancellationToken);
746 map.KeyType = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken));
747 map.ValueType = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken));
748 map.Count = (int) await ReadJsonIntegerAsync(cancellationToken);
Jens Geyer50806452019-11-23 01:55:58 +0100749 CheckReadBytesAvailable(map);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100750 await ReadJsonObjectStartAsync(cancellationToken);
751 return map;
752 }
753
754 public override async Task ReadMapEndAsync(CancellationToken cancellationToken)
755 {
756 await ReadJsonObjectEndAsync(cancellationToken);
757 await ReadJsonArrayEndAsync(cancellationToken);
758 }
759
Jens Geyer5a17b132019-05-26 15:53:37 +0200760 public override async ValueTask<TList> ReadListBeginAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100761 {
762 var list = new TList();
763 await ReadJsonArrayStartAsync(cancellationToken);
764 list.ElementType = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken));
765 list.Count = (int) await ReadJsonIntegerAsync(cancellationToken);
Jens Geyer50806452019-11-23 01:55:58 +0100766 CheckReadBytesAvailable(list);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100767 return list;
768 }
769
770 public override async Task ReadListEndAsync(CancellationToken cancellationToken)
771 {
772 await ReadJsonArrayEndAsync(cancellationToken);
773 }
774
Jens Geyer5a17b132019-05-26 15:53:37 +0200775 public override async ValueTask<TSet> ReadSetBeginAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100776 {
777 var set = new TSet();
778 await ReadJsonArrayStartAsync(cancellationToken);
779 set.ElementType = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken));
780 set.Count = (int) await ReadJsonIntegerAsync(cancellationToken);
Jens Geyer50806452019-11-23 01:55:58 +0100781 CheckReadBytesAvailable(set);
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100782 return set;
783 }
784
785 public override async Task ReadSetEndAsync(CancellationToken cancellationToken)
786 {
787 await ReadJsonArrayEndAsync(cancellationToken);
788 }
789
Jens Geyer5a17b132019-05-26 15:53:37 +0200790 public override async ValueTask<bool> ReadBoolAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100791 {
792 return await ReadJsonIntegerAsync(cancellationToken) != 0;
793 }
794
Jens Geyer5a17b132019-05-26 15:53:37 +0200795 public override async ValueTask<sbyte> ReadByteAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100796 {
797 return (sbyte) await ReadJsonIntegerAsync(cancellationToken);
798 }
799
Jens Geyer5a17b132019-05-26 15:53:37 +0200800 public override async ValueTask<short> ReadI16Async(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100801 {
802 return (short) await ReadJsonIntegerAsync(cancellationToken);
803 }
804
Jens Geyer5a17b132019-05-26 15:53:37 +0200805 public override async ValueTask<int> ReadI32Async(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100806 {
807 return (int) await ReadJsonIntegerAsync(cancellationToken);
808 }
809
Jens Geyer5a17b132019-05-26 15:53:37 +0200810 public override async ValueTask<long> ReadI64Async(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100811 {
812 return await ReadJsonIntegerAsync(cancellationToken);
813 }
814
Jens Geyer5a17b132019-05-26 15:53:37 +0200815 public override async ValueTask<double> ReadDoubleAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100816 {
817 return await ReadJsonDoubleAsync(cancellationToken);
818 }
819
Jens Geyer5a17b132019-05-26 15:53:37 +0200820 public override async ValueTask<string> ReadStringAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100821 {
822 var buf = await ReadJsonStringAsync(false, cancellationToken);
823 return Utf8Encoding.GetString(buf, 0, buf.Length);
824 }
825
Jens Geyer5a17b132019-05-26 15:53:37 +0200826 public override async ValueTask<byte[]> ReadBinaryAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100827 {
828 return await ReadJsonBase64Async(cancellationToken);
829 }
830
Jens Geyer62445c12022-06-29 00:00:00 +0200831 public override async ValueTask<Guid> ReadUuidAsync(CancellationToken cancellationToken = default)
832 {
833 return new Guid( await ReadStringAsync(cancellationToken));
834 }
835
Jens Geyer50806452019-11-23 01:55:58 +0100836 // Return the minimum number of bytes a type will consume on the wire
837 public override int GetMinSerializedSize(TType type)
838 {
839 switch (type)
840 {
Hasnain Lakhani845a87a2025-05-27 22:31:42 -0700841 case TType.Stop: return 1; // T_STOP needs to count itself
842 case TType.Void: return 1; // T_VOID needs to count itself
Jens Geyer50806452019-11-23 01:55:58 +0100843 case TType.Bool: return 1; // written as int
844 case TType.Byte: return 1;
845 case TType.Double: return 1;
846 case TType.I16: return 1;
847 case TType.I32: return 1;
848 case TType.I64: return 1;
849 case TType.String: return 2; // empty string
850 case TType.Struct: return 2; // empty struct
851 case TType.Map: return 2; // empty map
852 case TType.Set: return 2; // empty set
853 case TType.List: return 2; // empty list
Jens Geyer62445c12022-06-29 00:00:00 +0200854 case TType.Uuid: return 36; // "E236974D-F0B0-4E05-8F29-0B455D41B1A1"
Jens Geyer0d128322021-02-25 09:42:52 +0100855 default: throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, "unrecognized type code");
Jens Geyer50806452019-11-23 01:55:58 +0100856 }
857 }
858
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100859 /// <summary>
860 /// Factory for JSON protocol objects
861 /// </summary>
Jens Geyer421444f2019-03-20 22:13:25 +0100862 public class Factory : TProtocolFactory
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100863 {
Jens Geyer421444f2019-03-20 22:13:25 +0100864 public override TProtocol GetProtocol(TTransport trans)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100865 {
866 return new TJsonProtocol(trans);
867 }
868 }
869
870 /// <summary>
871 /// Base class for tracking JSON contexts that may require
872 /// inserting/Reading additional JSON syntax characters
873 /// This base context does nothing.
874 /// </summary>
875 protected class JSONBaseContext
876 {
877 protected TJsonProtocol Proto;
878
879 public JSONBaseContext(TJsonProtocol proto)
880 {
881 Proto = proto;
882 }
883
Jens Geyerdce22992020-05-16 23:02:27 +0200884 public virtual Task WriteConditionalDelimiterAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100885 {
Jens Geyerdce22992020-05-16 23:02:27 +0200886 cancellationToken.ThrowIfCancellationRequested();
887 return Task.CompletedTask;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100888 }
889
Jens Geyerdce22992020-05-16 23:02:27 +0200890 public virtual Task ReadConditionalDelimiterAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100891 {
Jens Geyerdce22992020-05-16 23:02:27 +0200892 cancellationToken.ThrowIfCancellationRequested();
893 return Task.CompletedTask;
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100894 }
895
896 public virtual bool EscapeNumbers()
897 {
898 return false;
899 }
900 }
901
902 /// <summary>
903 /// Context for JSON lists. Will insert/Read commas before each item except
904 /// for the first one
905 /// </summary>
906 protected class JSONListContext : JSONBaseContext
907 {
908 private bool _first = true;
909
910 public JSONListContext(TJsonProtocol protocol)
911 : base(protocol)
912 {
913 }
914
Jens Geyer2ff952b2019-04-13 19:46:54 +0200915 public override async Task WriteConditionalDelimiterAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100916 {
917 if (_first)
918 {
919 _first = false;
920 }
921 else
922 {
923 await Proto.Trans.WriteAsync(TJSONProtocolConstants.Comma, cancellationToken);
924 }
925 }
926
Jens Geyer2ff952b2019-04-13 19:46:54 +0200927 public override async Task ReadConditionalDelimiterAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100928 {
929 if (_first)
930 {
931 _first = false;
932 }
933 else
934 {
935 await Proto.ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Comma, cancellationToken);
936 }
937 }
938 }
939
940 /// <summary>
941 /// Context for JSON records. Will insert/Read colons before the value portion
942 /// of each record pair, and commas before each key except the first. In
943 /// addition, will indicate that numbers in the key position need to be
944 /// escaped in quotes (since JSON keys must be strings).
945 /// </summary>
946 // ReSharper disable once InconsistentNaming
947 protected class JSONPairContext : JSONBaseContext
948 {
949 private bool _colon = true;
950
951 private bool _first = true;
952
953 public JSONPairContext(TJsonProtocol proto)
954 : base(proto)
955 {
956 }
957
Jens Geyer2ff952b2019-04-13 19:46:54 +0200958 public override async Task WriteConditionalDelimiterAsync(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.Trans.WriteAsync(_colon ? TJSONProtocolConstants.Colon : TJSONProtocolConstants.Comma, cancellationToken);
968 _colon = !_colon;
969 }
970 }
971
Jens Geyer2ff952b2019-04-13 19:46:54 +0200972 public override async Task ReadConditionalDelimiterAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +0100973 {
974 if (_first)
975 {
976 _first = false;
977 _colon = true;
978 }
979 else
980 {
981 await Proto.ReadJsonSyntaxCharAsync(_colon ? TJSONProtocolConstants.Colon : TJSONProtocolConstants.Comma, cancellationToken);
982 _colon = !_colon;
983 }
984 }
985
986 public override bool EscapeNumbers()
987 {
988 return _colon;
989 }
990 }
991
992 /// <summary>
993 /// Holds up to one byte from the transport
994 /// </summary>
995 protected class LookaheadReader
996 {
997 private readonly byte[] _data = new byte[1];
998
999 private bool _hasData;
1000 protected TJsonProtocol Proto;
1001
1002 public LookaheadReader(TJsonProtocol proto)
1003 {
1004 Proto = proto;
1005 }
1006
1007 /// <summary>
1008 /// Return and consume the next byte to be Read, either taking it from the
1009 /// data buffer if present or getting it from the transport otherwise.
1010 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +02001011 public async ValueTask<byte> ReadAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +01001012 {
Jens Geyerdce22992020-05-16 23:02:27 +02001013 cancellationToken.ThrowIfCancellationRequested();
Jens Geyeraa0c8b32019-01-28 23:27:45 +01001014
1015 if (_hasData)
1016 {
1017 _hasData = false;
1018 }
1019 else
1020 {
1021 // find more easy way to avoid exception on reading primitive types
Jens Geyer92e5b712025-06-04 23:05:40 +02001022 Proto.Trans.CheckReadBytesAvailable(1);
Jens Geyeraa0c8b32019-01-28 23:27:45 +01001023 await Proto.Trans.ReadAllAsync(_data, 0, 1, cancellationToken);
1024 }
1025 return _data[0];
1026 }
1027
1028 /// <summary>
1029 /// Return the next byte to be Read without consuming, filling the data
1030 /// buffer if it has not been filled alReady.
1031 /// </summary>
Jens Geyer5a17b132019-05-26 15:53:37 +02001032 public async ValueTask<byte> PeekAsync(CancellationToken cancellationToken)
Jens Geyeraa0c8b32019-01-28 23:27:45 +01001033 {
Jens Geyerdce22992020-05-16 23:02:27 +02001034 cancellationToken.ThrowIfCancellationRequested();
Jens Geyeraa0c8b32019-01-28 23:27:45 +01001035
1036 if (!_hasData)
1037 {
1038 // find more easy way to avoid exception on reading primitive types
Jens Geyer92e5b712025-06-04 23:05:40 +02001039 Proto.Trans.CheckReadBytesAvailable(1);
Jens Geyeraa0c8b32019-01-28 23:27:45 +01001040 await Proto.Trans.ReadAllAsync(_data, 0, 1, cancellationToken);
Jens Geyer5a17b132019-05-26 15:53:37 +02001041 _hasData = true;
Jens Geyeraa0c8b32019-01-28 23:27:45 +01001042 }
Jens Geyeraa0c8b32019-01-28 23:27:45 +01001043 return _data[0];
1044 }
1045 }
1046 }
1047}