| | 1 | | // Copyright (c) ZeroC, Inc. |
| | 2 | |
|
| | 3 | | using System.Buffers; |
| | 4 | | using System.Diagnostics; |
| | 5 | | using System.Runtime.CompilerServices; |
| | 6 | | using System.Runtime.InteropServices; |
| | 7 | | using System.Text; |
| | 8 | | using ZeroC.Slice.Internal; |
| | 9 | | using static ZeroC.Slice.Internal.Slice1Definitions; |
| | 10 | |
|
| | 11 | | namespace ZeroC.Slice; |
| | 12 | |
|
| | 13 | | /// <summary>Provides methods to decode data encoded with Slice.</summary> |
| | 14 | | public ref partial struct SliceDecoder |
| | 15 | | { |
| | 16 | | /// <summary>Gets the number of bytes decoded in the underlying buffer.</summary> |
| 288241 | 17 | | public readonly long Consumed => _reader.Consumed; |
| | 18 | |
|
| | 19 | | /// <summary>Gets the decoding context.</summary> |
| | 20 | | /// <remarks>The decoding context is a kind of cookie: the code that creates the decoder can store this context in |
| | 21 | | /// the decoder for later retrieval.</remarks> |
| 52 | 22 | | public object? DecodingContext { get; } |
| | 23 | |
|
| | 24 | | /// <summary>Gets the Slice encoding decoded by this decoder.</summary> |
| 182326 | 25 | | public SliceEncoding Encoding { get; } |
| | 26 | |
|
| | 27 | | /// <summary>Gets a value indicating whether this decoder has reached the end of its underlying buffer.</summary> |
| | 28 | | /// <value><see langword="true" /> when this decoder has reached the end of its underlying buffer; otherwise |
| | 29 | | /// <see langword="false" />.</value> |
| 22559 | 30 | | public readonly bool End => _reader.End; |
| | 31 | |
|
| | 32 | | /// <summary>Gets the number of bytes remaining in the underlying buffer.</summary> |
| | 33 | | /// <value>The number of bytes remaining in the underlying buffer.</value> |
| 101 | 34 | | public readonly long Remaining => _reader.Remaining; |
| | 35 | |
|
| | 36 | | private const string EndOfBufferMessage = "Attempting to decode past the end of the Slice decoder buffer."; |
| | 37 | |
|
| 8 | 38 | | private static readonly IActivator _defaultActivator = |
| 8 | 39 | | ActivatorFactory.Instance.Get(typeof(SliceDecoder).Assembly); |
| | 40 | |
|
| 8 | 41 | | private static readonly UTF8Encoding _utf8 = |
| 8 | 42 | | new(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); // no BOM |
| | 43 | |
|
| | 44 | | private readonly IActivator? _activator; |
| | 45 | |
|
| | 46 | | private ClassContext _classContext; |
| | 47 | |
|
| | 48 | | // The number of bytes already allocated for strings, dictionaries, and sequences. |
| | 49 | | private int _currentCollectionAllocation; |
| | 50 | |
|
| | 51 | | // The current depth when decoding a class recursively. |
| | 52 | | private int _currentDepth; |
| | 53 | |
|
| | 54 | | // The maximum number of bytes that can be allocated for strings, dictionaries, and sequences. |
| | 55 | | private readonly int _maxCollectionAllocation; |
| | 56 | |
|
| | 57 | | // The maximum depth when decoding a class recursively. |
| | 58 | | private readonly int _maxDepth; |
| | 59 | |
|
| | 60 | | // The sequence reader. |
| | 61 | | private SequenceReader<byte> _reader; |
| | 62 | |
|
| | 63 | | /// <summary>Constructs a new Slice decoder over a byte buffer.</summary> |
| | 64 | | /// <param name="buffer">The byte buffer.</param> |
| | 65 | | /// <param name="encoding">The Slice encoding version.</param> |
| | 66 | | /// <param name="decodingContext">The decoding context.</param> |
| | 67 | | /// <param name="maxCollectionAllocation">The maximum cumulative allocation in bytes when decoding strings, |
| | 68 | | /// sequences, and dictionaries from this buffer.<c>-1</c> (the default) is equivalent to 8 times the buffer |
| | 69 | | /// length.</param> |
| | 70 | | /// <param name="activator">The activator for decoding Slice1-encoded classes and exceptions.</param> |
| | 71 | | /// <param name="maxDepth">The maximum depth when decoding a class recursively. The default is <c>3</c>.</param> |
| | 72 | | public SliceDecoder( |
| | 73 | | ReadOnlySequence<byte> buffer, |
| | 74 | | SliceEncoding encoding, |
| | 75 | | object? decodingContext = null, |
| | 76 | | int maxCollectionAllocation = -1, |
| | 77 | | IActivator? activator = null, |
| | 78 | | int maxDepth = 3) |
| 56395 | 79 | | { |
| 56395 | 80 | | Encoding = encoding; |
| 56395 | 81 | | DecodingContext = decodingContext; |
| | 82 | |
|
| 56395 | 83 | | _currentCollectionAllocation = 0; |
| | 84 | |
|
| 56395 | 85 | | _maxCollectionAllocation = maxCollectionAllocation == -1 ? 8 * (int)buffer.Length : |
| 56395 | 86 | | (maxCollectionAllocation >= 0 ? maxCollectionAllocation : |
| 56395 | 87 | | throw new ArgumentException( |
| 56395 | 88 | | $"The {nameof(maxCollectionAllocation)} argument must be greater than or equal to -1.", |
| 56395 | 89 | | nameof(maxCollectionAllocation))); |
| | 90 | |
|
| 56395 | 91 | | _activator = activator; |
| 56395 | 92 | | _classContext = default; |
| 56395 | 93 | | _currentDepth = 0; |
| 56395 | 94 | | _maxDepth = maxDepth >= 1 ? maxDepth : |
| 56395 | 95 | | throw new ArgumentException($"The {nameof(maxDepth)} argument must be greater than 0.", nameof(maxDepth)); |
| | 96 | |
|
| 56395 | 97 | | _reader = new SequenceReader<byte>(buffer); |
| 56395 | 98 | | } |
| | 99 | |
|
| | 100 | | /// <summary>Constructs a new Slice decoder over a byte buffer.</summary> |
| | 101 | | /// <param name="buffer">The byte buffer.</param> |
| | 102 | | /// <param name="encoding">The Slice encoding version.</param> |
| | 103 | | /// <param name="decodingContext">The decoding context.</param> |
| | 104 | | /// <param name="maxCollectionAllocation">The maximum cumulative allocation in bytes when decoding strings, |
| | 105 | | /// sequences, and dictionaries from this buffer.<c>-1</c> (the default) is equivalent to 8 times the buffer |
| | 106 | | /// length.</param> |
| | 107 | | /// <param name="activator">The activator for decoding Slice1-encoded classes and exceptions.</param> |
| | 108 | | /// <param name="maxDepth">The maximum depth when decoding a class recursively. The default is <c>3</c>.</param> |
| | 109 | | public SliceDecoder( |
| | 110 | | ReadOnlyMemory<byte> buffer, |
| | 111 | | SliceEncoding encoding, |
| | 112 | | object? decodingContext = null, |
| | 113 | | int maxCollectionAllocation = -1, |
| | 114 | | IActivator? activator = null, |
| | 115 | | int maxDepth = 3) |
| 314 | 116 | | : this( |
| 314 | 117 | | new ReadOnlySequence<byte>(buffer), |
| 314 | 118 | | encoding, |
| 314 | 119 | | decodingContext, |
| 314 | 120 | | maxCollectionAllocation, |
| 314 | 121 | | activator, |
| 314 | 122 | | maxDepth) |
| 314 | 123 | | { |
| 314 | 124 | | } |
| | 125 | |
|
| | 126 | | // Decode methods for basic types |
| | 127 | |
|
| | 128 | | /// <summary>Checks if the in memory representation of the bool value is valid according to the Slice encoding.</sum |
| | 129 | | /// <param name="value">The value to check.</param> |
| | 130 | | /// <exception cref="InvalidDataException">If the value is out of the bool type accepted range.</exception> |
| | 131 | | public static void CheckBoolValue(bool value) |
| 12 | 132 | | { |
| 12 | 133 | | if (Unsafe.As<bool, byte>(ref value) > 1) |
| 2 | 134 | | { |
| 2 | 135 | | throw new InvalidDataException("The value is out of the bool type accepted range."); |
| | 136 | | } |
| 10 | 137 | | } |
| | 138 | |
|
| | 139 | | /// <summary>Decodes a slice bool into a bool.</summary> |
| | 140 | | /// <returns>The bool decoded by this decoder.</returns> |
| | 141 | | public bool DecodeBool() |
| 2217 | 142 | | { |
| 2217 | 143 | | if (_reader.TryRead(out byte value)) |
| 2216 | 144 | | { |
| 2216 | 145 | | return value switch |
| 2216 | 146 | | { |
| 1637 | 147 | | 0 => false, |
| 578 | 148 | | 1 => true, |
| 1 | 149 | | _ => throw new InvalidDataException("The value is out of the bool type accepted range.") |
| 2216 | 150 | | }; |
| | 151 | | } |
| | 152 | | else |
| 1 | 153 | | { |
| 1 | 154 | | throw new InvalidDataException(EndOfBufferMessage); |
| | 155 | | } |
| 2215 | 156 | | } |
| | 157 | |
|
| | 158 | | /// <summary>Decodes a Slice float32 into a float.</summary> |
| | 159 | | /// <returns>The float decoded by this decoder.</returns> |
| | 160 | | public float DecodeFloat32() => |
| 0 | 161 | | SequenceMarshal.TryRead(ref _reader, out float value) ? |
| 0 | 162 | | value : throw new InvalidDataException(EndOfBufferMessage); |
| | 163 | |
|
| | 164 | | /// <summary>Decodes a Slice float64 into a double.</summary> |
| | 165 | | /// <returns>The double decoded by this decoder.</returns> |
| | 166 | | public double DecodeFloat64() => |
| 0 | 167 | | SequenceMarshal.TryRead(ref _reader, out double value) ? |
| 0 | 168 | | value : throw new InvalidDataException(EndOfBufferMessage); |
| | 169 | |
|
| | 170 | | /// <summary>Decodes a Slice int8 into an sbyte.</summary> |
| | 171 | | /// <returns>The sbyte decoded by this decoder.</returns> |
| 2 | 172 | | public sbyte DecodeInt8() => (sbyte)DecodeUInt8(); |
| | 173 | |
|
| | 174 | | /// <summary>Decodes a Slice int16 into a short.</summary> |
| | 175 | | /// <returns>The short decoded by this decoder.</returns> |
| | 176 | | public short DecodeInt16() => |
| 1094 | 177 | | SequenceMarshal.TryRead(ref _reader, out short value) ? |
| 1094 | 178 | | value : throw new InvalidDataException(EndOfBufferMessage); |
| | 179 | |
|
| | 180 | | /// <summary>Decodes a Slice int32 into an int.</summary> |
| | 181 | | /// <returns>The int decoded by this decoder.</returns> |
| | 182 | | public int DecodeInt32() => |
| 156161 | 183 | | SequenceMarshal.TryRead(ref _reader, out int value) ? |
| 156161 | 184 | | value : throw new InvalidDataException(EndOfBufferMessage); |
| | 185 | |
|
| | 186 | | /// <summary>Decodes a Slice int64 into a long.</summary> |
| | 187 | | /// <returns>The long decoded by this decoder.</returns> |
| | 188 | | public long DecodeInt64() => |
| 113 | 189 | | SequenceMarshal.TryRead(ref _reader, out long value) ? |
| 113 | 190 | | value : throw new InvalidDataException(EndOfBufferMessage); |
| | 191 | |
|
| | 192 | | /// <summary>Decodes a size encoded on a variable number of bytes.</summary> |
| | 193 | | /// <returns>The size decoded by this decoder.</returns> |
| | 194 | | public int DecodeSize() |
| 179312 | 195 | | { |
| 179312 | 196 | | if (Encoding == SliceEncoding.Slice1) |
| 26527 | 197 | | { |
| 26527 | 198 | | byte firstByte = DecodeUInt8(); |
| 26527 | 199 | | if (firstByte < 255) |
| 26486 | 200 | | { |
| 26486 | 201 | | return firstByte; |
| | 202 | | } |
| | 203 | | else |
| 41 | 204 | | { |
| 41 | 205 | | int size = DecodeInt32(); |
| 41 | 206 | | if (size < 0) |
| 0 | 207 | | { |
| 0 | 208 | | throw new InvalidDataException($"Decoded invalid size: {size}."); |
| | 209 | | } |
| 41 | 210 | | return size; |
| | 211 | | } |
| | 212 | | } |
| | 213 | | else |
| 152785 | 214 | | { |
| | 215 | | try |
| 152785 | 216 | | { |
| 152785 | 217 | | return checked((int)DecodeVarUInt62()); |
| | 218 | | } |
| 2 | 219 | | catch (OverflowException exception) |
| 2 | 220 | | { |
| 2 | 221 | | throw new InvalidDataException("Cannot decode size larger than int.MaxValue.", exception); |
| | 222 | | } |
| | 223 | | } |
| 179306 | 224 | | } |
| | 225 | |
|
| | 226 | | /// <summary>Decodes a Slice string into a string.</summary> |
| | 227 | | /// <returns>The string decoded by this decoder.</returns> |
| | 228 | | public string DecodeString() |
| 153335 | 229 | | { |
| 153335 | 230 | | int size = DecodeSize(); |
| 153335 | 231 | | if (size == 0) |
| 10827 | 232 | | { |
| 10827 | 233 | | return ""; |
| | 234 | | } |
| | 235 | | else |
| 142508 | 236 | | { |
| | 237 | | string result; |
| 142508 | 238 | | if (_reader.UnreadSpan.Length >= size) |
| 142505 | 239 | | { |
| | 240 | | try |
| 142505 | 241 | | { |
| 142505 | 242 | | result = _utf8.GetString(_reader.UnreadSpan[0..size]); |
| 142504 | 243 | | } |
| 1 | 244 | | catch (Exception exception) when (exception is ArgumentException or DecoderFallbackException) |
| 1 | 245 | | { |
| | 246 | | // The two exceptions that can be thrown by GetString are ArgumentException and |
| | 247 | | // DecoderFallbackException. Both of which are a result of malformed data. As such, we can just |
| | 248 | | // throw an InvalidDataException. |
| 1 | 249 | | throw new InvalidDataException("Invalid UTF-8 string.", exception); |
| | 250 | | } |
| 142504 | 251 | | } |
| | 252 | | else |
| 3 | 253 | | { |
| 3 | 254 | | ReadOnlySequence<byte> bytes = _reader.UnreadSequence; |
| 3 | 255 | | if (size > bytes.Length) |
| 0 | 256 | | { |
| 0 | 257 | | throw new InvalidDataException(EndOfBufferMessage); |
| | 258 | | } |
| | 259 | | try |
| 3 | 260 | | { |
| 3 | 261 | | result = _utf8.GetString(bytes.Slice(0, size)); |
| 2 | 262 | | } |
| 1 | 263 | | catch (Exception exception) when (exception is ArgumentException or DecoderFallbackException) |
| 1 | 264 | | { |
| | 265 | | // The two exceptions that can be thrown by GetString are ArgumentException and |
| | 266 | | // DecoderFallbackException. Both of which are a result of malformed data. As such, we can just |
| | 267 | | // throw an InvalidDataException. |
| 1 | 268 | | throw new InvalidDataException("Invalid UTF-8 string.", exception); |
| | 269 | | } |
| 2 | 270 | | } |
| | 271 | |
|
| 142506 | 272 | | _reader.Advance(size); |
| | 273 | |
|
| | 274 | | // We can only compute the new allocation _after_ decoding the string. For dictionaries and sequences, |
| | 275 | | // we perform this check before the allocation. |
| 142506 | 276 | | IncreaseCollectionAllocation(result.Length * Unsafe.SizeOf<char>()); |
| 142506 | 277 | | return result; |
| | 278 | | } |
| 153333 | 279 | | } |
| | 280 | |
|
| | 281 | | /// <summary>Decodes a Slice uint8 into a byte.</summary> |
| | 282 | | /// <returns>The byte decoded by this decoder.</returns> |
| | 283 | | public byte DecodeUInt8() => |
| 84788 | 284 | | _reader.TryRead(out byte value) ? value : throw new InvalidDataException(EndOfBufferMessage); |
| | 285 | |
|
| | 286 | | /// <summary>Decodes a Slice uint16 into a ushort.</summary> |
| | 287 | | /// <returns>The ushort decoded by this decoder.</returns> |
| | 288 | | public ushort DecodeUInt16() => |
| 1 | 289 | | SequenceMarshal.TryRead(ref _reader, out ushort value) ? |
| 1 | 290 | | value : throw new InvalidDataException(EndOfBufferMessage); |
| | 291 | |
|
| | 292 | | /// <summary>Decodes a Slice uint32 into a uint.</summary> |
| | 293 | | /// <returns>The uint decoded by this decoder.</returns> |
| | 294 | | public uint DecodeUInt32() => |
| 15 | 295 | | SequenceMarshal.TryRead(ref _reader, out uint value) ? |
| 15 | 296 | | value : throw new InvalidDataException(EndOfBufferMessage); |
| | 297 | |
|
| | 298 | | /// <summary>Decodes a Slice uint64 into a ulong.</summary> |
| | 299 | | /// <returns>The ulong decoded by this decoder.</returns> |
| | 300 | | public ulong DecodeUInt64() => |
| 0 | 301 | | SequenceMarshal.TryRead(ref _reader, out ulong value) ? |
| 0 | 302 | | value : throw new InvalidDataException(EndOfBufferMessage); |
| | 303 | |
|
| | 304 | | /// <summary>Decodes a Slice varint32 into an int.</summary> |
| | 305 | | /// <returns>The int decoded by this decoder.</returns> |
| | 306 | | public int DecodeVarInt32() |
| 332 | 307 | | { |
| | 308 | | try |
| 332 | 309 | | { |
| 332 | 310 | | return checked((int)DecodeVarInt62()); |
| | 311 | | } |
| 2 | 312 | | catch (OverflowException exception) |
| 2 | 313 | | { |
| 2 | 314 | | throw new InvalidDataException("The value is out of the varint32 accepted range.", exception); |
| | 315 | | } |
| 330 | 316 | | } |
| | 317 | |
|
| | 318 | | /// <summary>Decodes a Slice varint62 into a long.</summary> |
| | 319 | | /// <returns>The long decoded by this decoder.</returns> |
| | 320 | | public long DecodeVarInt62() => |
| 352 | 321 | | (PeekByte() & 0x03) switch |
| 352 | 322 | | { |
| 331 | 323 | | 0 => (sbyte)DecodeUInt8() >> 2, |
| 2 | 324 | | 1 => DecodeInt16() >> 2, |
| 11 | 325 | | 2 => DecodeInt32() >> 2, |
| 8 | 326 | | _ => DecodeInt64() >> 2 |
| 352 | 327 | | }; |
| | 328 | |
|
| | 329 | | /// <summary>Decodes a Slice varuint32 into a uint.</summary> |
| | 330 | | /// <returns>The uint decoded by this decoder.</returns> |
| | 331 | | public uint DecodeVarUInt32() |
| 1 | 332 | | { |
| | 333 | | try |
| 1 | 334 | | { |
| 1 | 335 | | return checked((uint)DecodeVarUInt62()); |
| | 336 | | } |
| 1 | 337 | | catch (OverflowException exception) |
| 1 | 338 | | { |
| 1 | 339 | | throw new InvalidDataException("The value is out of the varuint32 accepted range.", exception); |
| | 340 | | } |
| 0 | 341 | | } |
| | 342 | |
|
| | 343 | | /// <summary>Decodes a Slice varuint62 into a ulong.</summary> |
| | 344 | | /// <returns>The ulong decoded by this decoder.</returns> |
| | 345 | | public ulong DecodeVarUInt62() => |
| 168427 | 346 | | TryDecodeVarUInt62(out ulong value) ? value : throw new InvalidDataException(EndOfBufferMessage); |
| | 347 | |
|
| | 348 | | /// <summary>Tries to decode a Slice uint8 into a byte.</summary> |
| | 349 | | /// <param name="value">When this method returns <see langword="true" />, this value is set to the decoded byte. |
| | 350 | | /// Otherwise, this value is set to its default value.</param> |
| | 351 | | /// <returns><see langword="true" /> if the decoder is not at the end of the buffer and the decode operation |
| | 352 | | /// succeeded; otherwise, <see langword="false" />.</returns> |
| 26049 | 353 | | public bool TryDecodeUInt8(out byte value) => _reader.TryRead(out value); |
| | 354 | |
|
| | 355 | | /// <summary>Tries to decode a Slice varuint62 into a ulong.</summary> |
| | 356 | | /// <param name="value">When this method returns <see langword="true" />, this value is set to the decoded ulong. |
| | 357 | | /// Otherwise, this value is set to its default value.</param> |
| | 358 | | /// <returns><see langword="true" /> if the decoder is not at the end of the buffer and the decode operation |
| | 359 | | /// succeeded; otherwise, <see langword="false" />.</returns> |
| | 360 | | public bool TryDecodeVarUInt62(out ulong value) |
| 223439 | 361 | | { |
| 223439 | 362 | | if (_reader.TryPeek(out byte b)) |
| 223431 | 363 | | { |
| 223431 | 364 | | switch (b & 0x03) |
| | 365 | | { |
| | 366 | | case 0: |
| 177340 | 367 | | { |
| 177340 | 368 | | if (_reader.TryRead(out byte byteValue)) |
| 177340 | 369 | | { |
| 177340 | 370 | | value = (uint)byteValue >> 2; |
| 177340 | 371 | | return true; |
| | 372 | | } |
| 0 | 373 | | break; |
| | 374 | | } |
| | 375 | | case 1: |
| 14211 | 376 | | { |
| 14211 | 377 | | if (SequenceMarshal.TryRead(ref _reader, out ushort ushortValue)) |
| 14208 | 378 | | { |
| 14208 | 379 | | value = (uint)ushortValue >> 2; |
| 14208 | 380 | | return true; |
| | 381 | | } |
| 3 | 382 | | break; |
| | 383 | | } |
| | 384 | | case 2: |
| 31866 | 385 | | { |
| 31866 | 386 | | if (SequenceMarshal.TryRead(ref _reader, out uint uintValue)) |
| 31864 | 387 | | { |
| 31864 | 388 | | value = uintValue >> 2; |
| 31864 | 389 | | return true; |
| | 390 | | } |
| 2 | 391 | | break; |
| | 392 | | } |
| | 393 | | default: |
| 14 | 394 | | { |
| 14 | 395 | | if (SequenceMarshal.TryRead(ref _reader, out ulong ulongValue)) |
| 10 | 396 | | { |
| 10 | 397 | | value = ulongValue >> 2; |
| 10 | 398 | | return true; |
| | 399 | | } |
| 4 | 400 | | break; |
| | 401 | | } |
| | 402 | | } |
| 9 | 403 | | } |
| 17 | 404 | | value = 0; |
| 17 | 405 | | return false; |
| 223439 | 406 | | } |
| | 407 | |
|
| | 408 | | // Other methods |
| | 409 | |
|
| | 410 | | /// <summary>Copy bytes from the underlying reader into the destination to fill completely destination. |
| | 411 | | /// </summary> |
| | 412 | | /// <param name="destination">The span to which bytes of this decoder will be copied.</param> |
| | 413 | | /// <remarks>This method also moves the reader's Consumed property.</remarks> |
| | 414 | | public void CopyTo(Span<byte> destination) |
| 5998 | 415 | | { |
| 5998 | 416 | | if (_reader.TryCopyTo(destination)) |
| 5998 | 417 | | { |
| 5998 | 418 | | _reader.Advance(destination.Length); |
| 5998 | 419 | | } |
| | 420 | | else |
| 0 | 421 | | { |
| 0 | 422 | | throw new InvalidDataException(EndOfBufferMessage); |
| | 423 | | } |
| 5998 | 424 | | } |
| | 425 | |
|
| | 426 | | /// <summary>Copy all bytes from the underlying reader into the destination buffer writer.</summary> |
| | 427 | | /// <param name="destination">The destination buffer writer.</param> |
| | 428 | | /// <remarks>This method also moves the reader's Consumed property.</remarks> |
| | 429 | | public void CopyTo(IBufferWriter<byte> destination) |
| 19 | 430 | | { |
| 19 | 431 | | destination.Write(_reader.UnreadSequence); |
| 19 | 432 | | _reader.AdvanceToEnd(); |
| 19 | 433 | | } |
| | 434 | |
|
| | 435 | | /// <summary>Decodes a Slice2-encoded tagged field.</summary> |
| | 436 | | /// <typeparam name="T">The type of the decoded value.</typeparam> |
| | 437 | | /// <param name="tag">The tag.</param> |
| | 438 | | /// <param name="decodeFunc">A decode function that decodes the value of this tagged field.</param> |
| | 439 | | /// <returns>The decoded value of the tagged field, or <see langword="null" /> if not found.</returns> |
| | 440 | | /// <remarks>We return a T? and not a T to avoid ambiguities in the generated code with nullable reference types |
| | 441 | | /// such as string?.</remarks> |
| | 442 | | public T? DecodeTagged<T>(int tag, DecodeFunc<T> decodeFunc) |
| 63 | 443 | | { |
| 63 | 444 | | if (Encoding == SliceEncoding.Slice1) |
| 0 | 445 | | { |
| 0 | 446 | | throw new InvalidOperationException("Slice1 encoded tags must be decoded with tag formats."); |
| | 447 | | } |
| | 448 | |
|
| 63 | 449 | | int requestedTag = tag; |
| | 450 | |
|
| 63 | 451 | | while (true) |
| 63 | 452 | | { |
| 63 | 453 | | long startPos = _reader.Consumed; |
| 63 | 454 | | tag = DecodeVarInt32(); |
| | 455 | |
|
| 63 | 456 | | if (tag == requestedTag) |
| 37 | 457 | | { |
| | 458 | | // Found requested tag, so skip size: |
| 37 | 459 | | SkipSize(); |
| 37 | 460 | | return decodeFunc(ref this); |
| | 461 | | } |
| 26 | 462 | | else if (tag == Slice2Definitions.TagEndMarker || tag > requestedTag) |
| 26 | 463 | | { |
| 26 | 464 | | _reader.Rewind(_reader.Consumed - startPos); // rewind |
| 26 | 465 | | break; // while |
| | 466 | | } |
| | 467 | | else |
| 0 | 468 | | { |
| 0 | 469 | | Skip(DecodeSize()); |
| | 470 | | // and continue while loop |
| 0 | 471 | | } |
| 0 | 472 | | } |
| 26 | 473 | | return default; |
| 63 | 474 | | } |
| | 475 | |
|
| | 476 | | /// <summary>Decodes a Slice1-encoded tagged field.</summary> |
| | 477 | | /// <typeparam name="T">The type of the decoded value.</typeparam> |
| | 478 | | /// <param name="tag">The tag.</param> |
| | 479 | | /// <param name="tagFormat">The expected tag format of this tag when found in the underlying buffer.</param> |
| | 480 | | /// <param name="decodeFunc">A decode function that decodes the value of this tag.</param> |
| | 481 | | /// <param name="useTagEndMarker">When <see langword="true" />, a tag end marker marks the end of the tagged fields. |
| | 482 | | /// When <see langword="false" />, the end of the buffer marks the end of the tagged fields.</param> |
| | 483 | | /// <returns>The decoded value of the tagged field, or <see langword="null" /> if not found.</returns> |
| | 484 | | /// <remarks>We return a T? and not a T to avoid ambiguities in the generated code with nullable reference types |
| | 485 | | /// such as string?.</remarks> |
| | 486 | | public T? DecodeTagged<T>(int tag, TagFormat tagFormat, DecodeFunc<T> decodeFunc, bool useTagEndMarker) |
| 102 | 487 | | { |
| 102 | 488 | | if (Encoding != SliceEncoding.Slice1) |
| 0 | 489 | | { |
| 0 | 490 | | throw new InvalidOperationException("Tag formats can only be used with the Slice1 encoding."); |
| | 491 | | } |
| | 492 | |
|
| 102 | 493 | | if (DecodeTagHeader(tag, tagFormat, useTagEndMarker)) |
| 56 | 494 | | { |
| 56 | 495 | | if (tagFormat == TagFormat.VSize) |
| 8 | 496 | | { |
| 8 | 497 | | SkipSize(); |
| 8 | 498 | | } |
| 48 | 499 | | else if (tagFormat == TagFormat.FSize) |
| 4 | 500 | | { |
| 4 | 501 | | Skip(4); |
| 4 | 502 | | } |
| 56 | 503 | | return decodeFunc(ref this); |
| | 504 | | } |
| | 505 | | else |
| 46 | 506 | | { |
| 46 | 507 | | return default!; // i.e. null |
| | 508 | | } |
| 102 | 509 | | } |
| | 510 | |
|
| | 511 | | /// <summary>Gets a bit sequence reader to read the underlying bit sequence later on.</summary> |
| | 512 | | /// <param name="bitSequenceSize">The minimum number of bits in the sequence.</param> |
| | 513 | | /// <returns>A bit sequence reader.</returns> |
| | 514 | | public BitSequenceReader GetBitSequenceReader(int bitSequenceSize) |
| 1094 | 515 | | { |
| 1094 | 516 | | if (Encoding == SliceEncoding.Slice1) |
| 0 | 517 | | { |
| 0 | 518 | | throw new InvalidOperationException("Cannot create a bit sequence reader using the Slice1 encoding."); |
| | 519 | | } |
| | 520 | |
|
| 1094 | 521 | | if (bitSequenceSize <= 0) |
| 0 | 522 | | { |
| 0 | 523 | | throw new ArgumentOutOfRangeException( |
| 0 | 524 | | nameof(bitSequenceSize), |
| 0 | 525 | | $"The {nameof(bitSequenceSize)} argument must be greater than 0."); |
| | 526 | | } |
| | 527 | |
|
| 1094 | 528 | | int size = SliceEncoder.GetBitSequenceByteCount(bitSequenceSize); |
| 1094 | 529 | | ReadOnlySequence<byte> bitSequence = _reader.UnreadSequence.Slice(0, size); |
| 1094 | 530 | | _reader.Advance(size); |
| 1094 | 531 | | Debug.Assert(bitSequence.Length == size); |
| 1094 | 532 | | return new BitSequenceReader(bitSequence); |
| 1094 | 533 | | } |
| | 534 | |
|
| | 535 | | /// <summary>Increases the number of bytes in the decoder's collection allocation.</summary> |
| | 536 | | /// <param name="byteCount">The number of bytes to add.</param> |
| | 537 | | /// <exception cref="InvalidDataException">Thrown when the total number of bytes exceeds the max collection |
| | 538 | | /// allocation.</exception> |
| | 539 | | /// <seealso cref="SliceDecoder(ReadOnlySequence{byte}, SliceEncoding, object?, int, IActivator?, int)" /> |
| | 540 | | public void IncreaseCollectionAllocation(int byteCount) |
| 149842 | 541 | | { |
| 149842 | 542 | | _currentCollectionAllocation += byteCount; |
| 149842 | 543 | | if (_currentCollectionAllocation > _maxCollectionAllocation) |
| 4 | 544 | | { |
| 4 | 545 | | throw new InvalidDataException( |
| 4 | 546 | | $"The decoding exceeds the max collection allocation of '{_maxCollectionAllocation}'."); |
| | 547 | | } |
| 149838 | 548 | | } |
| | 549 | |
|
| | 550 | | /// <summary>Skip the given number of bytes.</summary> |
| | 551 | | /// <param name="count">The number of bytes to skip.</param> |
| | 552 | | public void Skip(int count) |
| 169 | 553 | | { |
| 169 | 554 | | if (_reader.Remaining >= count) |
| 169 | 555 | | { |
| 169 | 556 | | _reader.Advance(count); |
| 169 | 557 | | } |
| | 558 | | else |
| 0 | 559 | | { |
| 0 | 560 | | throw new InvalidDataException(EndOfBufferMessage); |
| | 561 | | } |
| 169 | 562 | | } |
| | 563 | |
|
| | 564 | | /// <summary>Skips the remaining tagged fields.</summary> |
| | 565 | | /// <param name="useTagEndMarker">Whether or not the tagged fields use a tag end marker (Slice1 only).</param> |
| | 566 | | public void SkipTagged(bool useTagEndMarker = true) |
| 269 | 567 | | { |
| 269 | 568 | | if (Encoding == SliceEncoding.Slice1) |
| 78 | 569 | | { |
| 78 | 570 | | if (!useTagEndMarker && _classContext.Current.InstanceType != InstanceType.None) |
| 0 | 571 | | { |
| 0 | 572 | | throw new ArgumentException( |
| 0 | 573 | | $"The {nameof(useTagEndMarker)} argument must be true when decoding a class/exception fields.", |
| 0 | 574 | | nameof(useTagEndMarker)); |
| | 575 | | } |
| 78 | 576 | | else if (useTagEndMarker && _classContext.Current.InstanceType == InstanceType.None) |
| 0 | 577 | | { |
| 0 | 578 | | throw new ArgumentException( |
| 0 | 579 | | $"The {nameof(useTagEndMarker)} argument must be false when decoding parameters.", |
| 0 | 580 | | nameof(useTagEndMarker)); |
| | 581 | | } |
| | 582 | |
|
| 113 | 583 | | while (true) |
| 113 | 584 | | { |
| 113 | 585 | | if (!useTagEndMarker && _reader.End) |
| 50 | 586 | | { |
| | 587 | | // When we don't use an end marker, the end of the buffer indicates the end of the tagged fields. |
| 50 | 588 | | break; |
| | 589 | | } |
| | 590 | |
|
| 63 | 591 | | int v = DecodeUInt8(); |
| 63 | 592 | | if (useTagEndMarker && v == TagEndMarker) |
| 28 | 593 | | { |
| | 594 | | // When we use an end marker, the end marker (and only the end marker) indicates the end of the |
| | 595 | | // tagged fields. |
| 28 | 596 | | break; |
| | 597 | | } |
| | 598 | |
|
| 35 | 599 | | var format = (TagFormat)(v & 0x07); // Read first 3 bits. |
| 35 | 600 | | if ((v >> 3) == 30) |
| 4 | 601 | | { |
| 4 | 602 | | SkipSize(); |
| 4 | 603 | | } |
| 35 | 604 | | SkipTaggedValue(format); |
| 35 | 605 | | } |
| 78 | 606 | | } |
| | 607 | | else |
| 191 | 608 | | { |
| 202 | 609 | | while (true) |
| 202 | 610 | | { |
| 202 | 611 | | if (DecodeVarInt32() == Slice2Definitions.TagEndMarker) |
| 191 | 612 | | { |
| 191 | 613 | | break; // while |
| | 614 | | } |
| | 615 | |
|
| | 616 | | // Skip tagged value |
| 11 | 617 | | Skip(DecodeSize()); |
| 11 | 618 | | } |
| 191 | 619 | | } |
| 269 | 620 | | } |
| | 621 | |
|
| | 622 | | /// <summary>Skip Slice size.</summary> |
| | 623 | | public void SkipSize() |
| 59 | 624 | | { |
| 59 | 625 | | if (Encoding == SliceEncoding.Slice1) |
| 16 | 626 | | { |
| 16 | 627 | | byte b = DecodeUInt8(); |
| 16 | 628 | | if (b == 255) |
| 0 | 629 | | { |
| 0 | 630 | | Skip(4); |
| 0 | 631 | | } |
| 16 | 632 | | } |
| | 633 | | else |
| 43 | 634 | | { |
| 43 | 635 | | Skip(DecodeVarInt62Length(PeekByte())); |
| 43 | 636 | | } |
| 59 | 637 | | } |
| | 638 | |
|
| | 639 | | // Applies to all var type: varint62, varuint62 etc. |
| 43 | 640 | | internal static int DecodeVarInt62Length(byte from) => 1 << (from & 0x03); |
| | 641 | |
|
| | 642 | | private bool DecodeTagHeader(int tag, TagFormat expectedFormat, bool useTagEndMarker) |
| 102 | 643 | | { |
| 102 | 644 | | Debug.Assert(Encoding == SliceEncoding.Slice1); |
| | 645 | |
|
| 102 | 646 | | if (!useTagEndMarker && _classContext.Current.InstanceType != InstanceType.None) |
| 0 | 647 | | { |
| 0 | 648 | | throw new ArgumentException( |
| 0 | 649 | | $"The {nameof(useTagEndMarker)} argument must be true when decoding the fields of a class or exception." |
| 0 | 650 | | nameof(useTagEndMarker)); |
| | 651 | | } |
| 102 | 652 | | else if (useTagEndMarker && _classContext.Current.InstanceType == InstanceType.None) |
| 0 | 653 | | { |
| 0 | 654 | | throw new ArgumentException( |
| 0 | 655 | | $"The {nameof(useTagEndMarker)} argument must be false when decoding parameters.", |
| 0 | 656 | | nameof(useTagEndMarker)); |
| | 657 | | } |
| | 658 | |
|
| 102 | 659 | | if (_classContext.Current.InstanceType != InstanceType.None) |
| 67 | 660 | | { |
| | 661 | | // tagged fields of a class or exception |
| 67 | 662 | | if ((_classContext.Current.SliceFlags & SliceFlags.HasTaggedFields) == 0) |
| 20 | 663 | | { |
| | 664 | | // The current slice has no tagged field. |
| 20 | 665 | | return false; |
| | 666 | | } |
| 47 | 667 | | } |
| | 668 | |
|
| 82 | 669 | | int requestedTag = tag; |
| | 670 | |
|
| 82 | 671 | | while (true) |
| 82 | 672 | | { |
| 82 | 673 | | if (!useTagEndMarker && _reader.End) |
| 10 | 674 | | { |
| 10 | 675 | | return false; // End of buffer indicates end of tagged fields. |
| | 676 | | } |
| | 677 | |
|
| 72 | 678 | | long savedPos = _reader.Consumed; |
| | 679 | |
|
| 72 | 680 | | int v = DecodeUInt8(); |
| 72 | 681 | | if (useTagEndMarker && v == TagEndMarker) |
| 3 | 682 | | { |
| 3 | 683 | | _reader.Rewind(_reader.Consumed - savedPos); |
| 3 | 684 | | return false; |
| | 685 | | } |
| | 686 | |
|
| 69 | 687 | | var format = (TagFormat)(v & 0x07); // First 3 bits. |
| 69 | 688 | | tag = v >> 3; |
| 69 | 689 | | if (tag == 30) |
| 8 | 690 | | { |
| 8 | 691 | | tag = DecodeSize(); |
| 8 | 692 | | } |
| | 693 | |
|
| 69 | 694 | | if (tag > requestedTag) |
| 13 | 695 | | { |
| 13 | 696 | | _reader.Rewind(_reader.Consumed - savedPos); |
| 13 | 697 | | return false; // No tagged field with the requested tag. |
| | 698 | | } |
| 56 | 699 | | else if (tag < requestedTag) |
| 0 | 700 | | { |
| 0 | 701 | | SkipTaggedValue(format); |
| 0 | 702 | | } |
| | 703 | | else |
| 56 | 704 | | { |
| 56 | 705 | | if (expectedFormat == TagFormat.OptimizedVSize) |
| 14 | 706 | | { |
| 14 | 707 | | expectedFormat = TagFormat.VSize; // fix virtual tag format |
| 14 | 708 | | } |
| | 709 | |
|
| 56 | 710 | | if (format != expectedFormat) |
| 0 | 711 | | { |
| 0 | 712 | | throw new InvalidDataException($"Invalid tagged field '{tag}': unexpected format."); |
| | 713 | | } |
| 56 | 714 | | return true; |
| | 715 | | } |
| 0 | 716 | | } |
| 102 | 717 | | } |
| | 718 | |
|
| | 719 | | private readonly byte PeekByte() => |
| 395 | 720 | | _reader.TryPeek(out byte value) ? value : throw new InvalidDataException(EndOfBufferMessage); |
| | 721 | |
|
| | 722 | | private void SkipTaggedValue(TagFormat format) |
| 35 | 723 | | { |
| 35 | 724 | | Debug.Assert(Encoding == SliceEncoding.Slice1); |
| | 725 | |
|
| 35 | 726 | | switch (format) |
| | 727 | | { |
| | 728 | | case TagFormat.F1: |
| 4 | 729 | | Skip(1); |
| 4 | 730 | | break; |
| | 731 | | case TagFormat.F2: |
| 2 | 732 | | Skip(2); |
| 2 | 733 | | break; |
| | 734 | | case TagFormat.F4: |
| 5 | 735 | | Skip(4); |
| 5 | 736 | | break; |
| | 737 | | case TagFormat.F8: |
| 6 | 738 | | Skip(8); |
| 6 | 739 | | break; |
| | 740 | | case TagFormat.Size: |
| 4 | 741 | | SkipSize(); |
| 4 | 742 | | break; |
| | 743 | | case TagFormat.VSize: |
| 12 | 744 | | Skip(DecodeSize()); |
| 12 | 745 | | break; |
| | 746 | | case TagFormat.FSize: |
| 2 | 747 | | int size = DecodeInt32(); |
| 2 | 748 | | if (size < 0) |
| 0 | 749 | | { |
| 0 | 750 | | throw new InvalidDataException($"Decoded invalid size: {size}."); |
| | 751 | | } |
| 2 | 752 | | Skip(size); |
| 2 | 753 | | break; |
| | 754 | | default: |
| 0 | 755 | | throw new InvalidDataException($"Cannot skip tagged field with tag format '{format}'."); |
| | 756 | | } |
| 35 | 757 | | } |
| | 758 | | } |