< Summary

Information
Class: ZeroC.Slice.Codec.SliceDecoder
Assembly: ZeroC.Slice.Codec
File(s): /home/runner/work/icerpc-csharp/icerpc-csharp/src/ZeroC.Slice.Codec/SliceDecoder.cs
Tag: 1321_24790053727
Line coverage
89%
Covered lines: 209
Uncovered lines: 25
Coverable lines: 234
Total lines: 502
Line coverage: 89.3%
Branch coverage
75%
Covered branches: 59
Total branches: 78
Branch coverage: 75.6%
Method coverage
92%
Covered methods: 35
Fully covered methods: 28
Total methods: 38
Method coverage: 92.1%
Full method coverage: 73.6%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_Consumed()100%11100%
get_DecodingContext()100%11100%
get_End()100%11100%
get_Remaining()100%11100%
.cctor()100%11100%
.ctor(...)66.66%66100%
.ctor(...)100%11100%
CheckBoolValue(...)100%22100%
DecodeBool()100%66100%
DecodeFloat32()0%620%
DecodeFloat64()0%620%
DecodeInt8()100%11100%
DecodeInt16()50%22100%
DecodeInt32()50%22100%
DecodeInt64()50%22100%
DecodeSize()100%11100%
DecodeString()83.33%7675%
DecodeUInt8()100%22100%
DecodeUInt16()50%22100%
DecodeUInt32()50%22100%
DecodeUInt64()0%620%
DecodeVarInt32()100%1185.71%
DecodeVarInt62()100%44100%
DecodeVarUInt32()100%1185.71%
DecodeVarUInt62()100%22100%
TryDecodeUInt8(...)100%11100%
TryDecodeVarUInt62(...)100%141493.75%
CopyTo(...)50%2275%
CopyTo(...)100%11100%
DecodeTagged(...)83.33%6680%
GetBitSequenceReader(...)75%4473.33%
IncreaseCollectionAllocation(...)100%22100%
Skip(...)50%2275%
SkipTagged()100%22100%
SkipSize()100%11100%
DecodeVarInt62Length(...)100%11100%
DecreaseCollectionAllocation(...)100%11100%
PeekByte()50%22100%

File(s)

/home/runner/work/icerpc-csharp/icerpc-csharp/src/ZeroC.Slice.Codec/SliceDecoder.cs

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3using System.Buffers;
 4using System.Diagnostics;
 5using System.Runtime.CompilerServices;
 6using System.Runtime.InteropServices;
 7using System.Text;
 8using ZeroC.Slice.Codec.Internal;
 9
 10namespace ZeroC.Slice.Codec;
 11
 12/// <summary>Provides methods to decode data encoded with Slice.</summary>
 13public ref partial struct SliceDecoder
 14{
 15    /// <summary>Gets the number of bytes decoded in the underlying buffer.</summary>
 24028116    public readonly long Consumed => _reader.Consumed;
 17
 18    /// <summary>Gets the decoding context.</summary>
 19    /// <remarks>The decoding context is a kind of cookie: the code that creates the decoder can store this context in
 20    /// the decoder for later retrieval.</remarks>
 2221    public object? DecodingContext { get; }
 22
 23    /// <summary>Gets a value indicating whether this decoder has reached the end of its underlying buffer.</summary>
 24    /// <value><see langword="true" /> when this decoder has reached the end of its underlying buffer; otherwise
 25    /// <see langword="false" />.</value>
 734926    public readonly bool End => _reader.End;
 27
 28    /// <summary>Gets the number of bytes remaining in the underlying buffer.</summary>
 29    /// <value>The number of bytes remaining in the underlying buffer.</value>
 5230    public readonly long Remaining => _reader.Remaining;
 31
 32    private const string EndOfBufferMessage = "Attempting to decode past the end of the Slice decoder buffer.";
 33
 934    private static readonly UTF8Encoding _utf8 =
 935        new(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); // no BOM
 36
 37    // The number of bytes already allocated for strings, dictionaries, and sequences.
 38    private int _currentCollectionAllocation;
 39
 40    // The maximum number of bytes that can be allocated for strings, dictionaries, and sequences.
 41    private readonly int _maxCollectionAllocation;
 42
 43    // The sequence reader.
 44    private SequenceReader<byte> _reader;
 45
 46    /// <summary>Constructs a new Slice decoder over a byte buffer.</summary>
 47    /// <param name="buffer">The byte buffer.</param>
 48    /// <param name="decodingContext">The decoding context.</param>
 49    /// <param name="maxCollectionAllocation">The maximum cumulative allocation in bytes when decoding strings,
 50    /// sequences, and dictionaries from this buffer.<c>-1</c> (the default) is equivalent to 8 times the buffer
 51    /// length, clamped to <see cref="int.MaxValue" />.</param>
 52    public SliceDecoder(ReadOnlySequence<byte> buffer, object? decodingContext = null, int maxCollectionAllocation = -1)
 2356853    {
 2356854        DecodingContext = decodingContext;
 55
 2356856        _currentCollectionAllocation = 0;
 2356857        _maxCollectionAllocation = maxCollectionAllocation == -1 ?
 2356858            (buffer.Length > int.MaxValue / 8 ? int.MaxValue : (int)(8L * buffer.Length)) :
 2356859            (maxCollectionAllocation >= 0 ? maxCollectionAllocation :
 2356860                throw new ArgumentException(
 2356861                    $"The {nameof(maxCollectionAllocation)} argument must be greater than or equal to -1.",
 2356862                    nameof(maxCollectionAllocation)));
 63
 2356864        _reader = new SequenceReader<byte>(buffer);
 2356865    }
 66
 67    /// <summary>Constructs a new Slice decoder over a byte buffer.</summary>
 68    /// <param name="buffer">The byte buffer.</param>
 69    /// <param name="decodingContext">The decoding context.</param>
 70    /// <param name="maxCollectionAllocation">The maximum cumulative allocation in bytes when decoding strings,
 71    /// sequences, and dictionaries from this buffer.<c>-1</c> (the default) is equivalent to 8 times the buffer
 72    /// length, clamped to <see cref="int.MaxValue" />.</param>
 73    public SliceDecoder(ReadOnlyMemory<byte> buffer, object? decodingContext = null, int maxCollectionAllocation = -1)
 19574        : this(new ReadOnlySequence<byte>(buffer), decodingContext, maxCollectionAllocation)
 19575    {
 19576    }
 77
 78    // Decode methods for basic types
 79
 80    /// <summary>Checks if the in memory representation of the bool value is valid according to the Slice encoding.</sum
 81    /// <param name="value">The value to check.</param>
 82    /// <exception cref="InvalidDataException">If the value is out of the bool type accepted range.</exception>
 83    public static void CheckBoolValue(bool value)
 684    {
 685        if (Unsafe.As<bool, byte>(ref value) > 1)
 186        {
 187            throw new InvalidDataException("The value is out of the bool type accepted range.");
 88        }
 589    }
 90
 91    /// <summary>Decodes a slice bool into a bool.</summary>
 92    /// <returns>The bool decoded by this decoder.</returns>
 93    public bool DecodeBool()
 213894    {
 213895        if (_reader.TryRead(out byte value))
 213796        {
 213797            return value switch
 213798            {
 157299                0 => false,
 564100                1 => true,
 1101                _ => throw new InvalidDataException("The value is out of the bool type accepted range.")
 2137102            };
 103        }
 104        else
 1105        {
 1106            throw new InvalidDataException(EndOfBufferMessage);
 107        }
 2136108    }
 109
 110    /// <summary>Decodes a Slice float32 into a float.</summary>
 111    /// <returns>The float decoded by this decoder.</returns>
 112    public float DecodeFloat32() =>
 0113        SequenceMarshal.TryRead(ref _reader, out float value) ?
 0114            value : throw new InvalidDataException(EndOfBufferMessage);
 115
 116    /// <summary>Decodes a Slice float64 into a double.</summary>
 117    /// <returns>The double decoded by this decoder.</returns>
 118    public double DecodeFloat64() =>
 0119        SequenceMarshal.TryRead(ref _reader, out double value) ?
 0120            value : throw new InvalidDataException(EndOfBufferMessage);
 121
 122    /// <summary>Decodes a Slice int8 into an sbyte.</summary>
 123    /// <returns>The sbyte decoded by this decoder.</returns>
 2124    public sbyte DecodeInt8() => (sbyte)DecodeUInt8();
 125
 126    /// <summary>Decodes a Slice int16 into a short.</summary>
 127    /// <returns>The short decoded by this decoder.</returns>
 128    public short DecodeInt16() =>
 1067129        SequenceMarshal.TryRead(ref _reader, out short value) ?
 1067130            value : throw new InvalidDataException(EndOfBufferMessage);
 131
 132    /// <summary>Decodes a Slice int32 into an int.</summary>
 133    /// <returns>The int decoded by this decoder.</returns>
 134    public int DecodeInt32() =>
 138389135        SequenceMarshal.TryRead(ref _reader, out int value) ?
 138389136            value : throw new InvalidDataException(EndOfBufferMessage);
 137
 138    /// <summary>Decodes a Slice int64 into a long.</summary>
 139    /// <returns>The long decoded by this decoder.</returns>
 140    public long DecodeInt64() =>
 251141        SequenceMarshal.TryRead(ref _reader, out long value) ?
 251142            value : throw new InvalidDataException(EndOfBufferMessage);
 143
 144    /// <summary>Decodes a size encoded on a variable number of bytes.</summary>
 145    /// <returns>The size decoded by this decoder.</returns>
 146    public int DecodeSize()
 145270147    {
 148        try
 145270149        {
 145270150            return checked((int)DecodeVarUInt62());
 151        }
 1152        catch (OverflowException exception)
 1153        {
 1154            throw new InvalidDataException("Cannot decode size larger than int.MaxValue.", exception);
 155        }
 145267156    }
 157
 158    /// <summary>Decodes a Slice string into a string.</summary>
 159    /// <returns>The string decoded by this decoder.</returns>
 160    public string DecodeString()
 139283161    {
 139283162        int size = DecodeSize();
 139283163        if (size == 0)
 1377164        {
 1377165            return "";
 166        }
 167        else
 137906168        {
 169            // In the worst-case scenario, each byte becomes a new character. We'll adjust this allocation increase
 170            // after decoding the string.
 137906171            IncreaseCollectionAllocation(size, Unsafe.SizeOf<char>());
 172
 173            string result;
 137903174            if (_reader.UnreadSpan.Length >= size)
 137902175            {
 176                try
 137902177                {
 137902178                    result = _utf8.GetString(_reader.UnreadSpan[0..size]);
 137901179                }
 1180                catch (Exception exception) when (exception is ArgumentException or DecoderFallbackException)
 1181                {
 182                    // The two exceptions that can be thrown by GetString are ArgumentException and
 183                    // DecoderFallbackException. Both of which are a result of malformed data. As such, we can just
 184                    // throw an InvalidDataException.
 1185                    throw new InvalidDataException("Invalid UTF-8 string.", exception);
 186                }
 137901187            }
 188            else
 1189            {
 1190                ReadOnlySequence<byte> bytes = _reader.UnreadSequence;
 1191                if (size > bytes.Length)
 0192                {
 0193                    throw new InvalidDataException(EndOfBufferMessage);
 194                }
 195                try
 1196                {
 1197                    result = _utf8.GetString(bytes.Slice(0, size));
 1198                }
 0199                catch (Exception exception) when (exception is ArgumentException or DecoderFallbackException)
 0200                {
 201                    // The two exceptions that can be thrown by GetString are ArgumentException and
 202                    // DecoderFallbackException. Both of which are a result of malformed data. As such, we can just
 203                    // throw an InvalidDataException.
 0204                    throw new InvalidDataException("Invalid UTF-8 string.", exception);
 205                }
 1206            }
 207
 137902208            _reader.Advance(size);
 209
 210            // Make the adjustment. The overall increase in allocation is result.Length * SizeOf<char>().
 137902211            DecreaseCollectionAllocation(size - result.Length, Unsafe.SizeOf<char>());
 137902212            return result;
 213        }
 139279214    }
 215
 216    /// <summary>Decodes a Slice uint8 into a byte.</summary>
 217    /// <returns>The byte decoded by this decoder.</returns>
 218    public byte DecodeUInt8() =>
 364219        _reader.TryRead(out byte value) ? value : throw new InvalidDataException(EndOfBufferMessage);
 220
 221    /// <summary>Decodes a Slice uint16 into a ushort.</summary>
 222    /// <returns>The ushort decoded by this decoder.</returns>
 223    public ushort DecodeUInt16() =>
 1224        SequenceMarshal.TryRead(ref _reader, out ushort value) ?
 1225            value : throw new InvalidDataException(EndOfBufferMessage);
 226
 227    /// <summary>Decodes a Slice uint32 into a uint.</summary>
 228    /// <returns>The uint decoded by this decoder.</returns>
 229    public uint DecodeUInt32() =>
 21230        SequenceMarshal.TryRead(ref _reader, out uint value) ?
 21231            value : throw new InvalidDataException(EndOfBufferMessage);
 232
 233    /// <summary>Decodes a Slice uint64 into a ulong.</summary>
 234    /// <returns>The ulong decoded by this decoder.</returns>
 235    public ulong DecodeUInt64() =>
 0236        SequenceMarshal.TryRead(ref _reader, out ulong value) ?
 0237            value : throw new InvalidDataException(EndOfBufferMessage);
 238
 239    /// <summary>Decodes a Slice varint32 into an int.</summary>
 240    /// <returns>The int decoded by this decoder.</returns>
 241    public int DecodeVarInt32()
 324242    {
 243        try
 324244        {
 324245            return checked((int)DecodeVarInt62());
 246        }
 2247        catch (OverflowException exception)
 2248        {
 2249            throw new InvalidDataException("The value is out of the varint32 accepted range.", exception);
 250        }
 322251    }
 252
 253    /// <summary>Decodes a Slice varint62 into a long.</summary>
 254    /// <returns>The long decoded by this decoder.</returns>
 255    public long DecodeVarInt62() =>
 344256        (PeekByte() & 0x03) switch
 344257        {
 323258            0 => (sbyte)DecodeUInt8() >> 2,
 2259            1 => DecodeInt16() >> 2,
 11260            2 => DecodeInt32() >> 2,
 8261            _ => DecodeInt64() >> 2
 344262        };
 263
 264    /// <summary>Decodes a Slice varuint32 into a uint.</summary>
 265    /// <returns>The uint decoded by this decoder.</returns>
 266    public uint DecodeVarUInt32()
 1267    {
 268        try
 1269        {
 1270            return checked((uint)DecodeVarUInt62());
 271        }
 1272        catch (OverflowException exception)
 1273        {
 1274            throw new InvalidDataException("The value is out of the varuint32 accepted range.", exception);
 275        }
 0276    }
 277
 278    /// <summary>Decodes a Slice varuint62 into a ulong.</summary>
 279    /// <returns>The ulong decoded by this decoder.</returns>
 280    public ulong DecodeVarUInt62() =>
 153552281        TryDecodeVarUInt62(out ulong value) ? value : throw new InvalidDataException(EndOfBufferMessage);
 282
 283    /// <summary>Tries to decode a Slice uint8 into a byte.</summary>
 284    /// <param name="value">When this method returns <see langword="true" />, this value is set to the decoded byte.
 285    /// Otherwise, this value is set to its default value.</param>
 286    /// <returns><see langword="true" /> if the decoder is not at the end of the buffer and the decode operation
 287    /// succeeded; otherwise, <see langword="false" />.</returns>
 13407288    public bool TryDecodeUInt8(out byte value) => _reader.TryRead(out value);
 289
 290    /// <summary>Tries to decode a Slice varuint62 into a ulong.</summary>
 291    /// <param name="value">When this method returns <see langword="true" />, this value is set to the decoded ulong.
 292    /// Otherwise, this value is set to its default value.</param>
 293    /// <returns><see langword="true" /> if the decoder is not at the end of the buffer and the decode operation
 294    /// succeeded; otherwise, <see langword="false" />.</returns>
 295    public bool TryDecodeVarUInt62(out ulong value)
 182008296    {
 182008297        if (_reader.TryPeek(out byte b))
 182004298        {
 182004299            switch (b & 0x03)
 300            {
 301                case 0:
 158033302                {
 158033303                    if (_reader.TryRead(out byte byteValue))
 158033304                    {
 158033305                        value = (uint)byteValue >> 2;
 158033306                        return true;
 307                    }
 0308                    break;
 309                }
 310                case 1:
 11571311                {
 11571312                    if (SequenceMarshal.TryRead(ref _reader, out ushort ushortValue))
 11569313                    {
 11569314                        value = (uint)ushortValue >> 2;
 11569315                        return true;
 316                    }
 2317                    break;
 318                }
 319                case 2:
 12390320                {
 12390321                    if (SequenceMarshal.TryRead(ref _reader, out uint uintValue))
 12388322                    {
 12388323                        value = uintValue >> 2;
 12388324                        return true;
 325                    }
 2326                    break;
 327                }
 328                default:
 10329                {
 10330                    if (SequenceMarshal.TryRead(ref _reader, out ulong ulongValue))
 8331                    {
 8332                        value = ulongValue >> 2;
 8333                        return true;
 334                    }
 2335                    break;
 336                }
 337            }
 6338        }
 10339        value = 0;
 10340        return false;
 182008341    }
 342
 343    // Other methods
 344
 345    /// <summary>Copy bytes from the underlying reader into the destination to fill completely destination.
 346    /// </summary>
 347    /// <param name="destination">The span to which bytes of this decoder will be copied.</param>
 348    /// <remarks>This method also moves the reader's Consumed property.</remarks>
 349    public void CopyTo(Span<byte> destination)
 3092350    {
 3092351        if (_reader.TryCopyTo(destination))
 3092352        {
 3092353            _reader.Advance(destination.Length);
 3092354        }
 355        else
 0356        {
 0357            throw new InvalidDataException(EndOfBufferMessage);
 358        }
 3092359    }
 360
 361    /// <summary>Copy all bytes from the underlying reader into the destination buffer writer.</summary>
 362    /// <param name="destination">The destination buffer writer.</param>
 363    /// <remarks>This method also moves the reader's Consumed property.</remarks>
 364    public void CopyTo(IBufferWriter<byte> destination)
 17365    {
 17366        destination.Write(_reader.UnreadSequence);
 17367        _reader.AdvanceToEnd();
 17368    }
 369
 370    /// <summary>Decodes a tagged field.</summary>
 371    /// <typeparam name="T">The type of the decoded value.</typeparam>
 372    /// <param name="tag">The tag.</param>
 373    /// <param name="decodeFunc">A decode function that decodes the value of this tagged field.</param>
 374    /// <returns>The decoded value of the tagged field, or <see langword="null" /> if not found.</returns>
 375    /// <remarks>We return a T? and not a T to avoid ambiguities in the generated code with nullable reference types
 376    /// such as string?.</remarks>
 377    public T? DecodeTagged<T>(int tag, DecodeFunc<T> decodeFunc)
 59378    {
 59379        int requestedTag = tag;
 380
 59381        while (true)
 59382        {
 59383            long startPos = _reader.Consumed;
 59384            tag = DecodeVarInt32();
 385
 59386            if (tag == requestedTag)
 35387            {
 388                // Found requested tag, so skip size:
 35389                SkipSize();
 35390                return decodeFunc(ref this);
 391            }
 24392            else if (tag == SliceDefinitions.TagEndMarker || tag > requestedTag)
 24393            {
 24394                _reader.Rewind(_reader.Consumed - startPos); // rewind
 24395                break; // while
 396            }
 397            else
 0398            {
 0399                Skip(DecodeSize());
 400                // and continue while loop
 0401            }
 0402        }
 24403        return default;
 59404    }
 405
 406    /// <summary>Gets a bit sequence reader to read the underlying bit sequence later on.</summary>
 407    /// <param name="bitSequenceSize">The minimum number of bits in the sequence.</param>
 408    /// <returns>A bit sequence reader.</returns>
 409    public BitSequenceReader GetBitSequenceReader(int bitSequenceSize)
 1096410    {
 1096411        if (bitSequenceSize <= 0)
 0412        {
 0413            throw new ArgumentOutOfRangeException(
 0414                nameof(bitSequenceSize),
 0415                $"The {nameof(bitSequenceSize)} argument must be greater than 0.");
 416        }
 417
 1096418        int size = SliceEncoder.GetBitSequenceByteCount(bitSequenceSize);
 1096419        if (_reader.Remaining < size)
 1420        {
 1421            throw new InvalidDataException(EndOfBufferMessage);
 422        }
 1095423        ReadOnlySequence<byte> bitSequence = _reader.UnreadSequence.Slice(0, size);
 1095424        _reader.Advance(size);
 1095425        Debug.Assert(bitSequence.Length == size);
 1095426        return new BitSequenceReader(bitSequence);
 1095427    }
 428
 429    /// <summary>Increases the number of bytes in the decoder's collection allocation.</summary>
 430    /// <param name="count">The number of elements.</param>
 431    /// <param name="elementSize">The size of each element in bytes.</param>
 432    /// <seealso cref="SliceDecoder(ReadOnlySequence{byte}, object?, int)" />
 433    public void IncreaseCollectionAllocation(int count, int elementSize)
 141724434    {
 141724435        Debug.Assert(count >= 0, $"{nameof(count)} must be greater than or equal to 0.");
 141724436        Debug.Assert(elementSize > 0, $"{nameof(elementSize)} must be greater than 0.");
 437
 438        // Widen count to long to avoid overflow when multiplying by elementSize.
 141724439        long byteCount = (long)count * elementSize;
 440
 141724441        int remainingAllocation = _maxCollectionAllocation - _currentCollectionAllocation;
 141724442        if (byteCount > remainingAllocation)
 17443        {
 17444            throw new InvalidDataException(
 17445                $"The decoding exceeds the max collection allocation of '{_maxCollectionAllocation}'.");
 446        }
 141707447        _currentCollectionAllocation += (int)byteCount;
 141707448    }
 449
 450    /// <summary>Skip the given number of bytes.</summary>
 451    /// <param name="count">The number of bytes to skip.</param>
 452    public void Skip(int count)
 71453    {
 71454        if (_reader.Remaining >= count)
 71455        {
 71456            _reader.Advance(count);
 71457        }
 458        else
 0459        {
 0460            throw new InvalidDataException(EndOfBufferMessage);
 461        }
 71462    }
 463
 464    /// <summary>Skips the remaining tagged fields.</summary>
 465    public void SkipTagged()
 187466    {
 198467        while (true)
 198468        {
 198469            if (DecodeVarInt32() == SliceDefinitions.TagEndMarker)
 187470            {
 187471                break; // while
 472            }
 473
 474            // Skip tagged value
 11475            Skip(DecodeSize());
 11476        }
 187477    }
 478
 479    /// <summary>Skip Slice size.</summary>
 41480    public void SkipSize() => Skip(DecodeVarInt62Length(PeekByte()));
 481
 482    // Applies to all var type: varint62, varuint62 etc.
 41483    internal static int DecodeVarInt62Length(byte from) => 1 << (from & 0x03);
 484
 485    /// <summary>Decreases the number of bytes in the decoder's collection allocation.</summary>
 486    /// <param name="count">The number of elements.</param>
 487    /// <param name="elementSize">The size of each element in bytes.</param>
 488    private void DecreaseCollectionAllocation(int count, int elementSize)
 137902489    {
 137902490        Debug.Assert(count >= 0, $"{nameof(count)} must be greater than or equal to 0.");
 137902491        Debug.Assert(elementSize > 0, $"{nameof(elementSize)} must be greater than 0.");
 492
 493        // Widen count to long to avoid overflow when multiplying by elementSize.
 137902494        long byteCount = (long)count * elementSize;
 495
 137902496        Debug.Assert(byteCount <= _currentCollectionAllocation, "Decreasing more than the current collection allocation.
 137902497        _currentCollectionAllocation -= (int)byteCount;
 137902498    }
 499
 500    private readonly byte PeekByte() =>
 385501        _reader.TryPeek(out byte value) ? value : throw new InvalidDataException(EndOfBufferMessage);
 502}