< Summary

Line coverage
86%
Covered lines: 622
Uncovered lines: 95
Coverable lines: 717
Total lines: 1361
Line coverage: 86.7%
Branch coverage
79%
Covered branches: 245
Total branches: 308
Branch coverage: 79.5%
Method coverage
94%
Covered methods: 52
Total methods: 55
Method coverage: 94.5%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
File 1: DecodeClass()50%22100%
File 1: DecodeException(...)81.25%16.11692.59%
File 1: DecodeNullableClass()66.66%6.84671.42%
File 1: EndSlice()75%8.09888.88%
File 1: StartSlice()75%4.05485.71%
File 1: DecodeClass()75%131280.95%
File 1: DecodeIndirectionTable()66.66%6.47676.47%
File 1: DecodeIndirectionTableIntoCurrent()75%4.05485.71%
File 1: DecodeInstance(...)92.1%38.013898.48%
File 1: DecodeSliceHeaderIntoCurrent()87.5%8.09888.88%
File 1: DecodeSliceSize()50%2.09271.42%
File 1: DecodeTypeId(...)83.33%12.021294.73%
File 1: SkipIndirectionTable()85.71%14.821483.87%
File 1: SkipSlice(...)88.88%18.341889.79%
File 2: get_Consumed()100%11100%
File 2: get_DecodingContext()100%11100%
File 2: get_Encoding()100%11100%
File 2: get_End()100%11100%
File 2: get_Remaining()100%11100%
File 2: .cctor()100%11100%
File 2: .ctor(...)66.66%66100%
File 2: .ctor(...)100%11100%
File 2: CheckBoolValue(...)100%22100%
File 2: DecodeBool()100%66100%
File 2: DecodeFloat32()0%620%
File 2: DecodeFloat64()0%620%
File 2: DecodeInt8()100%11100%
File 2: DecodeInt16()50%22100%
File 2: DecodeInt32()50%22100%
File 2: DecodeInt64()50%22100%
File 2: DecodeSize()83.33%6.04690%
File 2: DecodeString()83.33%6.08687.09%
File 2: DecodeUInt8()100%22100%
File 2: DecodeUInt16()50%22100%
File 2: DecodeUInt32()50%22100%
File 2: DecodeUInt64()0%620%
File 2: DecodeVarInt32()100%11100%
File 2: DecodeVarInt62()100%44100%
File 2: DecodeVarUInt32()100%1185.71%
File 2: DecodeVarUInt62()100%22100%
File 2: TryDecodeUInt8(...)100%11100%
File 2: TryDecodeVarUInt62(...)100%14.051493.75%
File 2: CopyTo(...)50%2.06275%
File 2: CopyTo(...)100%11100%
File 2: DecodeTagged(...)75%9.14873.91%
File 2: DecodeTagged(...)87.5%8.09888.88%
File 2: GetBitSequenceReader(...)50%5.02460%
File 2: IncreaseCollectionAllocation(...)100%22100%
File 2: Skip(...)50%2.06275%
File 2: SkipTagged(...)68.18%29.562275%
File 2: SkipSize()75%4.2476.92%
File 2: DecodeVarInt62Length(...)100%11100%
File 2: DecodeTagHeader(...)86.66%44.853074.54%
File 2: PeekByte()50%22100%
File 2: SkipTaggedValue(...)80%10.221086.95%

File(s)

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

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3using System.Collections.Immutable;
 4using System.ComponentModel;
 5using System.Diagnostics;
 6using System.Globalization;
 7using System.Runtime.CompilerServices;
 8using ZeroC.Slice.Internal;
 9using static ZeroC.Slice.Internal.Slice1Definitions;
 10
 11namespace ZeroC.Slice;
 12
 13/// <summary>Provides methods to decode data encoded with Slice.</summary>
 14public ref partial struct SliceDecoder
 15{
 16    /// <summary>Decodes a class instance.</summary>
 17    /// <typeparam name="T">The class type.</typeparam>
 18    /// <returns>The decoded class instance.</returns>
 19    public T DecodeClass<T>() where T : SliceClass =>
 4420        DecodeNullableClass<T>() ??
 4421           throw new InvalidDataException("Decoded a null class instance, but expected a non-null instance.");
 22
 23    /// <summary>Decodes a Slice exception.</summary>
 24    /// <param name="message">The error message. It's used only when this method fails to find an exception class to
 25    /// instantiate.</param>
 26    /// <returns>The decoded Slice exception.</returns>
 27    public SliceException DecodeException(string? message = null)
 3128    {
 3129        if (Encoding != SliceEncoding.Slice1)
 030        {
 031            throw new InvalidOperationException($"{nameof(DecodeException)} is not compatible with {Encoding}.");
 32        }
 33
 3134        Debug.Assert(_classContext.Current.InstanceType == InstanceType.None);
 3135        _classContext.Current.InstanceType = InstanceType.Exception;
 36
 37        // We can decode the indirection table (if there is one) immediately after decoding each slice header
 38        // because the indirection table cannot reference the exception itself.
 39        // Each slice contains its type ID as a string.
 40
 3141        string? mostDerivedTypeId = null;
 3142        IActivator activator = _activator ?? _defaultActivator;
 43        SliceException? sliceException;
 44
 45        do
 3446        {
 47            // The type ID is always decoded for an exception and cannot be null.
 3448            string? typeId = DecodeSliceHeaderIntoCurrent();
 3449            Debug.Assert(typeId is not null);
 3450            mostDerivedTypeId ??= typeId;
 51
 3452            DecodeIndirectionTableIntoCurrent(); // we decode the indirection table immediately.
 53
 3454            sliceException = activator.CreateInstance(typeId) as SliceException;
 3455            if (sliceException is null && SkipSlice(typeId))
 156            {
 57                // Cannot decode this exception. The message should be set only when the exception was received over
 58                // icerpc.
 159                throw new InvalidDataException(
 160                    message is null || message.Length == 0 ?
 161                    $"The dispatch returned a Slice exception with type ID '{mostDerivedTypeId}' that the configured act
 162                    $"The dispatch returned a Slice exception with type ID '{mostDerivedTypeId}' that the configured act
 63            }
 3364        }
 3365        while (sliceException is null);
 66
 3067        _classContext.Current.FirstSlice = true;
 3068        sliceException.Decode(ref this);
 3069        _classContext.Current = default;
 3070        return sliceException;
 3071    }
 72
 73    /// <summary>Decodes a nullable class instance.</summary>
 74    /// <typeparam name="T">The class type.</typeparam>
 75    /// <returns>The class instance, or <see langword="null" />.</returns>
 76    public T? DecodeNullableClass<T>() where T : class
 30077    {
 30078        if (Encoding != SliceEncoding.Slice1)
 079        {
 080            throw new InvalidOperationException($"{nameof(DecodeNullableClass)} is not compatible with {Encoding}.");
 81        }
 82
 30083        SliceClass? obj = DecodeClass();
 84
 19885        if (obj is T result)
 7186        {
 7187            return result;
 88        }
 12789        else if (obj is null)
 12790        {
 12791            return null;
 92        }
 093        throw new InvalidDataException(
 094            $"Decoded instance of type '{obj.GetType()}' but expected instance of type '{typeof(T)}'.");
 19895    }
 96
 97    /// <summary>Tells the decoder the end of a class or exception slice was reached.</summary>
 98    [EditorBrowsable(EditorBrowsableState.Never)]
 99    public void EndSlice()
 186100    {
 186101        if (Encoding != SliceEncoding.Slice1)
 0102        {
 0103            throw new InvalidOperationException($"{nameof(EndSlice)} is not compatible with encoding {Encoding}.");
 104        }
 105
 106        // Note that EndSlice is not called when we call SkipSlice.
 186107        Debug.Assert(_classContext.Current.InstanceType != InstanceType.None);
 108
 186109        if ((_classContext.Current.SliceFlags & SliceFlags.HasTaggedFields) != 0)
 28110        {
 28111            SkipTagged();
 28112        }
 186113        if ((_classContext.Current.SliceFlags & SliceFlags.HasIndirectionTable) != 0)
 18114        {
 18115            Debug.Assert(_classContext.Current.PosAfterIndirectionTable is not null &&
 18116                         _classContext.Current.IndirectionTable is not null);
 117
 18118            _reader.Advance(_classContext.Current.PosAfterIndirectionTable.Value - _reader.Consumed);
 18119            _classContext.Current.PosAfterIndirectionTable = null;
 18120            _classContext.Current.IndirectionTable = null;
 18121        }
 186122    }
 123
 124    /// <summary>Marks the start of the decoding of a class or remote exception slice.</summary>
 125    [EditorBrowsable(EditorBrowsableState.Never)]
 126    public void StartSlice()
 286127    {
 286128        if (Encoding != SliceEncoding.Slice1)
 0129        {
 0130            throw new InvalidOperationException($"{nameof(StartSlice)} is not compatible with encoding {Encoding}.");
 131        }
 132
 286133        Debug.Assert(_classContext.Current.InstanceType != InstanceType.None);
 286134        if (_classContext.Current.FirstSlice)
 186135        {
 186136            _classContext.Current.FirstSlice = false;
 186137        }
 138        else
 100139        {
 100140            _ = DecodeSliceHeaderIntoCurrent();
 100141            DecodeIndirectionTableIntoCurrent();
 100142        }
 286143    }
 144
 145    /// <summary>Decodes a class instance.</summary>
 146    /// <returns>The class instance. Can be <see langword="null" />.</returns>
 147    private SliceClass? DecodeClass()
 300148    {
 300149        Debug.Assert(Encoding == SliceEncoding.Slice1);
 150
 300151        int index = DecodeSize();
 300152        if (index < 0)
 0153        {
 0154            throw new InvalidDataException($"Found invalid index {index} while decoding a class.");
 155        }
 300156        else if (index == 0)
 127157        {
 127158            return null;
 159        }
 173160        else if (_classContext.Current.InstanceType != InstanceType.None &&
 173161            (_classContext.Current.SliceFlags & SliceFlags.HasIndirectionTable) != 0)
 21162        {
 163            // When decoding an instance within a slice and there is an indirection table, we have an index within
 164            // this indirection table.
 165            // We need to decrement index since position 0 in the indirection table corresponds to index 1.
 21166            index--;
 21167            if (index < _classContext.Current.IndirectionTable?.Length)
 21168            {
 21169                return _classContext.Current.IndirectionTable[index];
 170            }
 171            else
 0172            {
 0173                throw new InvalidDataException("The index is too big for the indirection table.");
 174            }
 175        }
 176        else
 152177        {
 152178            return DecodeInstance(index);
 179        }
 198180    }
 181
 182    /// <summary>Decodes an indirection table without updating _current.</summary>
 183    /// <returns>The indirection table.</returns>
 184    private SliceClass[] DecodeIndirectionTable()
 28185    {
 28186        int size = DecodeSize();
 28187        if (size == 0)
 0188        {
 0189            throw new InvalidDataException("Invalid empty indirection table.");
 190        }
 28191        IncreaseCollectionAllocation(size * Unsafe.SizeOf<SliceClass>());
 28192        var indirectionTable = new SliceClass[size];
 116193        for (int i = 0; i < indirectionTable.Length; ++i)
 30194        {
 30195            int index = DecodeSize();
 30196            if (index < 1)
 0197            {
 0198                throw new InvalidDataException($"Found invalid index {index} decoding the indirection table.");
 199            }
 30200            indirectionTable[i] = DecodeInstance(index);
 30201        }
 28202        return indirectionTable;
 28203    }
 204
 205    /// <summary>Decodes the indirection table into _current's fields if there is an indirection table.
 206    /// Precondition: called after decoding the header of the current slice. This method does not change _pos.
 207    /// </summary>
 208    private void DecodeIndirectionTableIntoCurrent()
 290209    {
 290210        Debug.Assert(_classContext.Current.IndirectionTable is null);
 290211        if ((_classContext.Current.SliceFlags & SliceFlags.HasIndirectionTable) != 0)
 20212        {
 20213            if ((_classContext.Current.SliceFlags & SliceFlags.HasSliceSize) == 0)
 0214            {
 0215                throw new InvalidDataException("The Slice has indirection table flag but has not size flag.");
 216            }
 217
 20218            long savedPos = _reader.Consumed;
 20219            _reader.Advance(_classContext.Current.SliceSize);
 20220            _classContext.Current.IndirectionTable = DecodeIndirectionTable();
 20221            _classContext.Current.PosAfterIndirectionTable = _reader.Consumed;
 20222            _reader.Rewind(_reader.Consumed - savedPos);
 20223        }
 290224    }
 225
 226    /// <summary>Decodes a class instance.</summary>
 227    /// <param name="index">The index of the class instance. If greater than 1, it's a reference to a previously
 228    /// seen class; if 1, the class instance's bytes are next. Cannot be 0 or less.</param>
 229    private SliceClass DecodeInstance(int index)
 182230    {
 182231        Debug.Assert(index > 0);
 232
 182233        if (index > 1)
 17234        {
 17235            if (_classContext.InstanceMap is not null && _classContext.InstanceMap.Count > index - 2)
 17236            {
 17237                return _classContext.InstanceMap[index - 2];
 238            }
 0239            throw new InvalidDataException($"Cannot find instance index {index} in the instance map.");
 240        }
 241
 165242        if (++_currentDepth > _maxDepth)
 1243        {
 1244            throw new InvalidDataException("The maximum decoder depth was reached while decoding a class.");
 245        }
 246
 247        // Save current in case we're decoding a nested instance.
 164248        InstanceData previousCurrent = _classContext.Current;
 164249        _classContext.Current = default;
 164250        _classContext.Current.InstanceType = InstanceType.Class;
 251
 164252        SliceClass? instance = null;
 164253        _classContext.InstanceMap ??= new List<SliceClass>();
 254
 164255        bool decodeIndirectionTable = true;
 164256        IActivator activator = _activator ?? _defaultActivator;
 257        do
 182258        {
 259            // Decode the slice header.
 182260            string? typeId = DecodeSliceHeaderIntoCurrent();
 261
 262            // We cannot decode the indirection table at this point as it may reference the new instance that is
 263            // not created yet.
 182264            if (typeId is not null)
 182265            {
 182266                instance = activator.CreateInstance(typeId) as SliceClass;
 182267            }
 268
 182269            if (instance is null && SkipSlice(typeId))
 7270            {
 271                // Slice off what we don't understand.
 7272                instance = new UnknownSliceClass();
 273                // Don't decode the indirection table as it's the last entry in DeferredIndirectionTableList.
 7274                decodeIndirectionTable = false;
 7275            }
 181276        }
 181277        while (instance is null);
 278
 279        // Add the instance to the map/list of instances. This must be done before decoding the instances (for
 280        // circular references).
 163281        _classContext.InstanceMap!.Add(instance);
 282
 283        // Decode all the deferred indirection tables now that the instance is inserted in _instanceMap.
 163284        if (_classContext.Current.DeferredIndirectionTableList?.Count > 0)
 13285        {
 13286            long savedPos = _reader.Consumed;
 287
 13288            Debug.Assert(_classContext.Current.Slices?.Count ==
 13289                _classContext.Current.DeferredIndirectionTableList.Count);
 76290            for (int i = 0; i < _classContext.Current.DeferredIndirectionTableList.Count; ++i)
 25291            {
 25292                long pos = _classContext.Current.DeferredIndirectionTableList[i];
 25293                if (pos > 0)
 8294                {
 8295                    long distance = pos - _reader.Consumed;
 8296                    if (distance > 0)
 2297                    {
 2298                        _reader.Advance(distance);
 2299                    }
 300                    else
 6301                    {
 6302                        _reader.Rewind(-distance);
 6303                    }
 8304                    _classContext.Current.Slices[i].Instances = DecodeIndirectionTable();
 8305                }
 306                // else remains empty
 25307            }
 308
 13309            _reader.Advance(savedPos - _reader.Consumed);
 13310        }
 311
 163312        if (decodeIndirectionTable)
 156313        {
 156314            DecodeIndirectionTableIntoCurrent();
 156315        }
 316
 163317        instance.UnknownSlices = _classContext.Current.Slices?.ToImmutableList() ?? ImmutableList<SliceInfo>.Empty;
 163318        _classContext.Current.FirstSlice = true;
 163319        instance.Decode(ref this);
 320
 63321        _classContext.Current = previousCurrent;
 63322        --_currentDepth;
 63323        return instance;
 80324    }
 325
 326    /// <summary>Decodes the header of the current slice into _current.</summary>
 327    /// <returns>The type ID or the compact ID of the current slice.</returns>
 328    private string? DecodeSliceHeaderIntoCurrent()
 316329    {
 316330        _classContext.Current.SliceFlags = (SliceFlags)DecodeUInt8();
 331
 332        string? typeId;
 333        // Decode the type ID. For class slices, the type ID is encoded as a string or as an index or as a compact
 334        // ID, for exceptions it's always encoded as a string.
 316335        if (_classContext.Current.InstanceType == InstanceType.Class)
 277336        {
 277337            typeId = DecodeTypeId(_classContext.Current.SliceFlags.GetTypeIdKind());
 338
 277339            if (typeId is null)
 62340            {
 62341                if ((_classContext.Current.SliceFlags & SliceFlags.HasSliceSize) != 0)
 0342                {
 0343                    throw new InvalidDataException(
 0344                        "Invalid Slice flags; a Slice in compact format cannot carry a size.");
 345                }
 62346            }
 277347        }
 348        else
 39349        {
 350            // Exception slices always include the type ID, even when using the compact format.
 39351            typeId = DecodeString();
 39352        }
 353
 354        // Decode the slice size if available.
 316355        if ((_classContext.Current.SliceFlags & SliceFlags.HasSliceSize) != 0)
 129356        {
 129357            _classContext.Current.SliceSize = DecodeSliceSize();
 129358        }
 359        else
 187360        {
 187361            _classContext.Current.SliceSize = 0;
 187362        }
 363
 364        // Clear other per-slice fields:
 316365        _classContext.Current.IndirectionTable = null;
 316366        _classContext.Current.PosAfterIndirectionTable = null;
 367
 316368        return typeId;
 316369    }
 370
 371    /// <summary>Decodes the size of the current slice.</summary>
 372    /// <returns>The slice of the current slice, not including the size length.</returns>
 373    private int DecodeSliceSize()
 149374    {
 149375        int size = DecodeInt32();
 149376        if (size < 4)
 0377        {
 0378            throw new InvalidDataException($"Invalid Slice size: {size}.");
 379        }
 380        // The encoded size includes the size length.
 149381        return size - 4;
 149382    }
 383
 384    /// <summary>Decodes the type ID of a class instance.</summary>
 385    /// <param name="typeIdKind">The kind of type ID to decode.</param>
 386    /// <returns>The type ID or the compact ID, if any.</returns>
 387    private string? DecodeTypeId(TypeIdKind typeIdKind)
 297388    {
 297389        _classContext.TypeIdMap ??= new List<string>();
 390
 297391        switch (typeIdKind)
 392        {
 393            case TypeIdKind.Index:
 131394                int index = DecodeSize();
 131395                if (index > 0 && index - 1 < _classContext.TypeIdMap.Count)
 131396                {
 397                    // The encoded type-id indexes start at 1, not 0.
 131398                    return _classContext.TypeIdMap[index - 1];
 399                }
 0400                throw new InvalidDataException($"Decoded invalid type ID index {index}.");
 401
 402            case TypeIdKind.String:
 71403                string typeId = DecodeString();
 404
 405                // The typeIds of slices in indirection tables can be decoded several times: when we skip the
 406                // indirection table and later on when we decode it. We only want to add this type ID to the list and
 407                // assign it an index when it's the first time we decode it, so we save the largest position we
 408                // decode to figure out when to add to the list.
 71409                if (_reader.Consumed > _classContext.PosAfterLatestInsertedTypeId)
 63410                {
 63411                    _classContext.PosAfterLatestInsertedTypeId = _reader.Consumed;
 63412                    _classContext.TypeIdMap.Add(typeId);
 63413                }
 71414                return typeId;
 415
 416            case TypeIdKind.CompactId:
 33417                return DecodeSize().ToString(CultureInfo.InvariantCulture);
 418
 419            default:
 420                // TypeIdKind has only 4 possible values.
 62421                Debug.Assert(typeIdKind == TypeIdKind.None);
 62422                return null;
 423        }
 297424    }
 425
 426    /// <summary>Skips the indirection table. The caller must save the current position before calling
 427    /// SkipIndirectionTable (to decode the indirection table at a later point) except when the caller is
 428    /// SkipIndirectionTable itself.</summary>
 429    private void SkipIndirectionTable()
 11430    {
 431        // We should never skip an exception's indirection table
 11432        Debug.Assert(_classContext.Current.InstanceType == InstanceType.Class);
 433
 11434        int tableSize = DecodeSize();
 38435        for (int i = 0; i < tableSize; ++i)
 11436        {
 11437            int index = DecodeSize();
 11438            if (index <= 0)
 0439            {
 0440                throw new InvalidDataException($"Decoded invalid index {index} in indirection table.");
 441            }
 11442            if (index == 1)
 9443            {
 9444                if (++_currentDepth > _maxDepth)
 1445                {
 1446                    throw new InvalidDataException("Maximum decoder depth reached while decoding a class.");
 447                }
 448
 449                // Decode/skip this instance
 450                SliceFlags sliceFlags;
 451                do
 20452                {
 20453                    sliceFlags = (SliceFlags)DecodeUInt8();
 454
 455                    // Skip type ID - can update _typeIdMap
 20456                    _ = DecodeTypeId(sliceFlags.GetTypeIdKind());
 457
 458                    // Decode the slice size, then skip the slice
 20459                    if ((sliceFlags & SliceFlags.HasSliceSize) == 0)
 0460                    {
 0461                        throw new InvalidDataException("The Slice size flag is missing.");
 462                    }
 20463                    _reader.Advance(DecodeSliceSize());
 464
 465                    // If this slice has an indirection table, skip it too.
 20466                    if ((sliceFlags & SliceFlags.HasIndirectionTable) != 0)
 2467                    {
 2468                        SkipIndirectionTable();
 0469                    }
 18470                }
 18471                while ((sliceFlags & SliceFlags.IsLastSlice) == 0);
 6472                _currentDepth--;
 6473            }
 8474        }
 8475    }
 476
 477    /// <summary>Skips and saves the body of the current slice (save only for classes); also skips and save the
 478    /// indirection table (if any).</summary>
 479    /// <param name="typeId">The type ID or compact ID of the current slice.</param>
 480    /// <returns><see langword="true" /> when the current slice is the last slice; otherwise, <see langword="false" />.
 481    /// </returns>
 482    private bool SkipSlice(string? typeId)
 30483    {
 30484        if (typeId is null)
 0485        {
 0486            throw new InvalidDataException("Cannot skip a class slice with no type ID.");
 487        }
 488
 30489        if ((_classContext.Current.SliceFlags & SliceFlags.HasSliceSize) == 0)
 0490        {
 0491            throw new InvalidDataException(
 0492                $"The configured activator cannot find a class for type ID '{typeId}' and the compact format prevents sl
 493        }
 494
 30495        bool hasTaggedFields = (_classContext.Current.SliceFlags & SliceFlags.HasTaggedFields) != 0;
 496        byte[] bytes;
 30497        if (hasTaggedFields)
 4498        {
 499            // Don't include the tag end marker. It will be re-written by SliceEncoder.EndSlice when the sliced data
 500            // is re-written.
 4501            bytes = new byte[_classContext.Current.SliceSize - 1];
 4502            CopyTo(bytes.AsSpan());
 4503            Skip(1);
 4504        }
 505        else
 26506        {
 26507            bytes = new byte[_classContext.Current.SliceSize];
 26508            CopyTo(bytes.AsSpan());
 26509        }
 510
 30511        bool hasIndirectionTable = (_classContext.Current.SliceFlags & SliceFlags.HasIndirectionTable) != 0;
 512
 513        // SkipSlice for a class skips the indirection table and preserves its position in
 514        // _current.DeferredIndirectionTableList for later decoding.
 30515        if (_classContext.Current.InstanceType == InstanceType.Class)
 26516        {
 26517            _classContext.Current.DeferredIndirectionTableList ??= new List<long>();
 26518            if (hasIndirectionTable)
 9519            {
 9520                long savedPos = _reader.Consumed;
 9521                SkipIndirectionTable();
 522
 523                // we want to later read the deepest first
 8524                _classContext.Current.DeferredIndirectionTableList.Add(savedPos);
 8525            }
 526            else
 17527            {
 17528                _classContext.Current.DeferredIndirectionTableList.Add(0); // keep a slot for each slice
 17529            }
 530
 25531            var info = new SliceInfo(
 25532                typeId,
 25533                new ReadOnlyMemory<byte>(bytes),
 25534                _classContext.Current.IndirectionTable ?? Array.Empty<SliceClass>(),
 25535                hasTaggedFields);
 536
 25537            _classContext.Current.Slices ??= new List<SliceInfo>();
 25538            _classContext.Current.Slices.Add(info);
 25539        }
 4540        else if (hasIndirectionTable)
 2541        {
 2542            Debug.Assert(_classContext.Current.PosAfterIndirectionTable is not null);
 543
 544            // Move past indirection table
 2545            _reader.Advance(_classContext.Current.PosAfterIndirectionTable.Value - _reader.Consumed);
 2546            _classContext.Current.PosAfterIndirectionTable = null;
 2547        }
 548
 549        // If we decoded the indirection table previously, we don't need it anymore since we're skipping this slice.
 29550        _classContext.Current.IndirectionTable = null;
 551
 29552        return (_classContext.Current.SliceFlags & SliceFlags.IsLastSlice) != 0;
 29553    }
 554
 555    /// <summary>Holds various fields used for class and exception decoding.</summary>
 556    private struct ClassContext
 557    {
 558        // Data for the class or exception instance that is currently getting decoded.
 559        internal InstanceData Current;
 560
 561        // Map of class instance ID to class instance.
 562        // When decoding a buffer:
 563        //  - Instance ID = 0 means null
 564        //  - Instance ID = 1 means the instance is encoded inline afterwards
 565        //  - Instance ID > 1 means a reference to a previously decoded instance, found in this map.
 566        // Since the map is actually a list, we use instance ID - 2 to lookup an instance.
 567        internal List<SliceClass>? InstanceMap;
 568
 569        // See DecodeTypeId.
 570        internal long PosAfterLatestInsertedTypeId;
 571
 572        // Map of type ID index to type ID sequence, used only for classes.
 573        // We assign a type ID index (starting with 1) to each type ID (type ID sequence) we decode, in order.
 574        // Since this map is a list, we lookup a previously assigned type ID (type ID sequence) with
 575        // _typeIdMap[index - 1].
 576        internal List<string>? TypeIdMap;
 577    }
 578
 579    private struct InstanceData
 580    {
 581        // Instance fields
 582
 583        internal List<long>? DeferredIndirectionTableList;
 584        internal InstanceType InstanceType;
 585        internal List<SliceInfo>? Slices; // Preserved slices.
 586
 587        // Slice fields
 588
 589        internal bool FirstSlice;
 590        internal SliceClass[]? IndirectionTable; // Indirection table of the current slice
 591        internal long? PosAfterIndirectionTable;
 592
 593        internal SliceFlags SliceFlags;
 594        internal int SliceSize;
 595    }
 596
 597    private enum InstanceType : byte
 598    {
 599        None = 0,
 600        Class,
 601        Exception
 602    }
 603}

/home/runner/work/icerpc-csharp/icerpc-csharp/src/ZeroC.Slice/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.Internal;
 9using static ZeroC.Slice.Internal.Slice1Definitions;
 10
 11namespace ZeroC.Slice;
 12
 13/// <summary>Provides methods to decode data encoded with Slice.</summary>
 14public ref partial struct SliceDecoder
 15{
 16    /// <summary>Gets the number of bytes decoded in the underlying buffer.</summary>
 28824117    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>
 5222    public object? DecodingContext { get; }
 23
 24    /// <summary>Gets the Slice encoding decoded by this decoder.</summary>
 18232625    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>
 2255930    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>
 10134    public readonly long Remaining => _reader.Remaining;
 35
 36    private const string EndOfBufferMessage = "Attempting to decode past the end of the Slice decoder buffer.";
 37
 838    private static readonly IActivator _defaultActivator =
 839        ActivatorFactory.Instance.Get(typeof(SliceDecoder).Assembly);
 40
 841    private static readonly UTF8Encoding _utf8 =
 842        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)
 5639579    {
 5639580        Encoding = encoding;
 5639581        DecodingContext = decodingContext;
 82
 5639583        _currentCollectionAllocation = 0;
 84
 5639585        _maxCollectionAllocation = maxCollectionAllocation == -1 ? 8 * (int)buffer.Length :
 5639586            (maxCollectionAllocation >= 0 ? maxCollectionAllocation :
 5639587                throw new ArgumentException(
 5639588                    $"The {nameof(maxCollectionAllocation)} argument must be greater than or equal to -1.",
 5639589                    nameof(maxCollectionAllocation)));
 90
 5639591        _activator = activator;
 5639592        _classContext = default;
 5639593        _currentDepth = 0;
 5639594        _maxDepth = maxDepth >= 1 ? maxDepth :
 5639595            throw new ArgumentException($"The {nameof(maxDepth)} argument must be greater than 0.", nameof(maxDepth));
 96
 5639597        _reader = new SequenceReader<byte>(buffer);
 5639598    }
 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)
 314116        : this(
 314117            new ReadOnlySequence<byte>(buffer),
 314118            encoding,
 314119            decodingContext,
 314120            maxCollectionAllocation,
 314121            activator,
 314122            maxDepth)
 314123    {
 314124    }
 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)
 12132    {
 12133        if (Unsafe.As<bool, byte>(ref value) > 1)
 2134        {
 2135            throw new InvalidDataException("The value is out of the bool type accepted range.");
 136        }
 10137    }
 138
 139    /// <summary>Decodes a slice bool into a bool.</summary>
 140    /// <returns>The bool decoded by this decoder.</returns>
 141    public bool DecodeBool()
 2217142    {
 2217143        if (_reader.TryRead(out byte value))
 2216144        {
 2216145            return value switch
 2216146            {
 1637147                0 => false,
 578148                1 => true,
 1149                _ => throw new InvalidDataException("The value is out of the bool type accepted range.")
 2216150            };
 151        }
 152        else
 1153        {
 1154            throw new InvalidDataException(EndOfBufferMessage);
 155        }
 2215156    }
 157
 158    /// <summary>Decodes a Slice float32 into a float.</summary>
 159    /// <returns>The float decoded by this decoder.</returns>
 160    public float DecodeFloat32() =>
 0161        SequenceMarshal.TryRead(ref _reader, out float value) ?
 0162            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() =>
 0167        SequenceMarshal.TryRead(ref _reader, out double value) ?
 0168            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>
 2172    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() =>
 1094177        SequenceMarshal.TryRead(ref _reader, out short value) ?
 1094178            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() =>
 156161183        SequenceMarshal.TryRead(ref _reader, out int value) ?
 156161184            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() =>
 113189        SequenceMarshal.TryRead(ref _reader, out long value) ?
 113190            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()
 179312195    {
 179312196        if (Encoding == SliceEncoding.Slice1)
 26527197        {
 26527198            byte firstByte = DecodeUInt8();
 26527199            if (firstByte < 255)
 26486200            {
 26486201                return firstByte;
 202            }
 203            else
 41204            {
 41205                int size = DecodeInt32();
 41206                if (size < 0)
 0207                {
 0208                    throw new InvalidDataException($"Decoded invalid size: {size}.");
 209                }
 41210                return size;
 211            }
 212        }
 213        else
 152785214        {
 215            try
 152785216            {
 152785217                return checked((int)DecodeVarUInt62());
 218            }
 2219            catch (OverflowException exception)
 2220            {
 2221                throw new InvalidDataException("Cannot decode size larger than int.MaxValue.", exception);
 222            }
 223        }
 179306224    }
 225
 226    /// <summary>Decodes a Slice string into a string.</summary>
 227    /// <returns>The string decoded by this decoder.</returns>
 228    public string DecodeString()
 153335229    {
 153335230        int size = DecodeSize();
 153335231        if (size == 0)
 10827232        {
 10827233            return "";
 234        }
 235        else
 142508236        {
 237            string result;
 142508238            if (_reader.UnreadSpan.Length >= size)
 142505239            {
 240                try
 142505241                {
 142505242                    result = _utf8.GetString(_reader.UnreadSpan[0..size]);
 142504243                }
 1244                catch (Exception exception) when (exception is ArgumentException or DecoderFallbackException)
 1245                {
 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.
 1249                    throw new InvalidDataException("Invalid UTF-8 string.", exception);
 250                }
 142504251            }
 252            else
 3253            {
 3254                ReadOnlySequence<byte> bytes = _reader.UnreadSequence;
 3255                if (size > bytes.Length)
 0256                {
 0257                    throw new InvalidDataException(EndOfBufferMessage);
 258                }
 259                try
 3260                {
 3261                    result = _utf8.GetString(bytes.Slice(0, size));
 2262                }
 1263                catch (Exception exception) when (exception is ArgumentException or DecoderFallbackException)
 1264                {
 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.
 1268                    throw new InvalidDataException("Invalid UTF-8 string.", exception);
 269                }
 2270            }
 271
 142506272            _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.
 142506276            IncreaseCollectionAllocation(result.Length * Unsafe.SizeOf<char>());
 142506277            return result;
 278        }
 153333279    }
 280
 281    /// <summary>Decodes a Slice uint8 into a byte.</summary>
 282    /// <returns>The byte decoded by this decoder.</returns>
 283    public byte DecodeUInt8() =>
 84788284        _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() =>
 1289        SequenceMarshal.TryRead(ref _reader, out ushort value) ?
 1290            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() =>
 15295        SequenceMarshal.TryRead(ref _reader, out uint value) ?
 15296            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() =>
 0301        SequenceMarshal.TryRead(ref _reader, out ulong value) ?
 0302            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()
 332307    {
 308        try
 332309        {
 332310            return checked((int)DecodeVarInt62());
 311        }
 2312        catch (OverflowException exception)
 2313        {
 2314            throw new InvalidDataException("The value is out of the varint32 accepted range.", exception);
 315        }
 330316    }
 317
 318    /// <summary>Decodes a Slice varint62 into a long.</summary>
 319    /// <returns>The long decoded by this decoder.</returns>
 320    public long DecodeVarInt62() =>
 352321        (PeekByte() & 0x03) switch
 352322        {
 331323            0 => (sbyte)DecodeUInt8() >> 2,
 2324            1 => DecodeInt16() >> 2,
 11325            2 => DecodeInt32() >> 2,
 8326            _ => DecodeInt64() >> 2
 352327        };
 328
 329    /// <summary>Decodes a Slice varuint32 into a uint.</summary>
 330    /// <returns>The uint decoded by this decoder.</returns>
 331    public uint DecodeVarUInt32()
 1332    {
 333        try
 1334        {
 1335            return checked((uint)DecodeVarUInt62());
 336        }
 1337        catch (OverflowException exception)
 1338        {
 1339            throw new InvalidDataException("The value is out of the varuint32 accepted range.", exception);
 340        }
 0341    }
 342
 343    /// <summary>Decodes a Slice varuint62 into a ulong.</summary>
 344    /// <returns>The ulong decoded by this decoder.</returns>
 345    public ulong DecodeVarUInt62() =>
 168427346        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>
 26049353    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)
 223439361    {
 223439362        if (_reader.TryPeek(out byte b))
 223431363        {
 223431364            switch (b & 0x03)
 365            {
 366                case 0:
 177340367                {
 177340368                    if (_reader.TryRead(out byte byteValue))
 177340369                    {
 177340370                        value = (uint)byteValue >> 2;
 177340371                        return true;
 372                    }
 0373                    break;
 374                }
 375                case 1:
 14211376                {
 14211377                    if (SequenceMarshal.TryRead(ref _reader, out ushort ushortValue))
 14208378                    {
 14208379                        value = (uint)ushortValue >> 2;
 14208380                        return true;
 381                    }
 3382                    break;
 383                }
 384                case 2:
 31866385                {
 31866386                    if (SequenceMarshal.TryRead(ref _reader, out uint uintValue))
 31864387                    {
 31864388                        value = uintValue >> 2;
 31864389                        return true;
 390                    }
 2391                    break;
 392                }
 393                default:
 14394                {
 14395                    if (SequenceMarshal.TryRead(ref _reader, out ulong ulongValue))
 10396                    {
 10397                        value = ulongValue >> 2;
 10398                        return true;
 399                    }
 4400                    break;
 401                }
 402            }
 9403        }
 17404        value = 0;
 17405        return false;
 223439406    }
 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)
 5998415    {
 5998416        if (_reader.TryCopyTo(destination))
 5998417        {
 5998418            _reader.Advance(destination.Length);
 5998419        }
 420        else
 0421        {
 0422            throw new InvalidDataException(EndOfBufferMessage);
 423        }
 5998424    }
 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)
 19430    {
 19431        destination.Write(_reader.UnreadSequence);
 19432        _reader.AdvanceToEnd();
 19433    }
 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)
 63443    {
 63444        if (Encoding == SliceEncoding.Slice1)
 0445        {
 0446            throw new InvalidOperationException("Slice1 encoded tags must be decoded with tag formats.");
 447        }
 448
 63449        int requestedTag = tag;
 450
 63451        while (true)
 63452        {
 63453            long startPos = _reader.Consumed;
 63454            tag = DecodeVarInt32();
 455
 63456            if (tag == requestedTag)
 37457            {
 458                // Found requested tag, so skip size:
 37459                SkipSize();
 37460                return decodeFunc(ref this);
 461            }
 26462            else if (tag == Slice2Definitions.TagEndMarker || tag > requestedTag)
 26463            {
 26464                _reader.Rewind(_reader.Consumed - startPos); // rewind
 26465                break; // while
 466            }
 467            else
 0468            {
 0469                Skip(DecodeSize());
 470                // and continue while loop
 0471            }
 0472        }
 26473        return default;
 63474    }
 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)
 102487    {
 102488        if (Encoding != SliceEncoding.Slice1)
 0489        {
 0490            throw new InvalidOperationException("Tag formats can only be used with the Slice1 encoding.");
 491        }
 492
 102493        if (DecodeTagHeader(tag, tagFormat, useTagEndMarker))
 56494        {
 56495            if (tagFormat == TagFormat.VSize)
 8496            {
 8497                SkipSize();
 8498            }
 48499            else if (tagFormat == TagFormat.FSize)
 4500            {
 4501                Skip(4);
 4502            }
 56503            return decodeFunc(ref this);
 504        }
 505        else
 46506        {
 46507            return default!; // i.e. null
 508        }
 102509    }
 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)
 1094515    {
 1094516        if (Encoding == SliceEncoding.Slice1)
 0517        {
 0518            throw new InvalidOperationException("Cannot create a bit sequence reader using the Slice1 encoding.");
 519        }
 520
 1094521        if (bitSequenceSize <= 0)
 0522        {
 0523            throw new ArgumentOutOfRangeException(
 0524                nameof(bitSequenceSize),
 0525                $"The {nameof(bitSequenceSize)} argument must be greater than 0.");
 526        }
 527
 1094528        int size = SliceEncoder.GetBitSequenceByteCount(bitSequenceSize);
 1094529        ReadOnlySequence<byte> bitSequence = _reader.UnreadSequence.Slice(0, size);
 1094530        _reader.Advance(size);
 1094531        Debug.Assert(bitSequence.Length == size);
 1094532        return new BitSequenceReader(bitSequence);
 1094533    }
 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)
 149842541    {
 149842542        _currentCollectionAllocation += byteCount;
 149842543        if (_currentCollectionAllocation > _maxCollectionAllocation)
 4544        {
 4545            throw new InvalidDataException(
 4546                $"The decoding exceeds the max collection allocation of '{_maxCollectionAllocation}'.");
 547        }
 149838548    }
 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)
 169553    {
 169554        if (_reader.Remaining >= count)
 169555        {
 169556            _reader.Advance(count);
 169557        }
 558        else
 0559        {
 0560            throw new InvalidDataException(EndOfBufferMessage);
 561        }
 169562    }
 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)
 269567    {
 269568        if (Encoding == SliceEncoding.Slice1)
 78569        {
 78570            if (!useTagEndMarker && _classContext.Current.InstanceType != InstanceType.None)
 0571            {
 0572                throw new ArgumentException(
 0573                    $"The {nameof(useTagEndMarker)} argument must be true when decoding a class/exception fields.",
 0574                    nameof(useTagEndMarker));
 575            }
 78576            else if (useTagEndMarker && _classContext.Current.InstanceType == InstanceType.None)
 0577            {
 0578                throw new ArgumentException(
 0579                    $"The {nameof(useTagEndMarker)} argument must be false when decoding parameters.",
 0580                    nameof(useTagEndMarker));
 581            }
 582
 113583            while (true)
 113584            {
 113585                if (!useTagEndMarker && _reader.End)
 50586                {
 587                    // When we don't use an end marker, the end of the buffer indicates the end of the tagged fields.
 50588                    break;
 589                }
 590
 63591                int v = DecodeUInt8();
 63592                if (useTagEndMarker && v == TagEndMarker)
 28593                {
 594                    // When we use an end marker, the end marker (and only the end marker) indicates the end of the
 595                    // tagged fields.
 28596                    break;
 597                }
 598
 35599                var format = (TagFormat)(v & 0x07); // Read first 3 bits.
 35600                if ((v >> 3) == 30)
 4601                {
 4602                    SkipSize();
 4603                }
 35604                SkipTaggedValue(format);
 35605            }
 78606        }
 607        else
 191608        {
 202609            while (true)
 202610            {
 202611                if (DecodeVarInt32() == Slice2Definitions.TagEndMarker)
 191612                {
 191613                    break; // while
 614                }
 615
 616                // Skip tagged value
 11617                Skip(DecodeSize());
 11618            }
 191619        }
 269620    }
 621
 622    /// <summary>Skip Slice size.</summary>
 623    public void SkipSize()
 59624    {
 59625        if (Encoding == SliceEncoding.Slice1)
 16626        {
 16627            byte b = DecodeUInt8();
 16628            if (b == 255)
 0629            {
 0630                Skip(4);
 0631            }
 16632        }
 633        else
 43634        {
 43635            Skip(DecodeVarInt62Length(PeekByte()));
 43636        }
 59637    }
 638
 639    // Applies to all var type: varint62, varuint62 etc.
 43640    internal static int DecodeVarInt62Length(byte from) => 1 << (from & 0x03);
 641
 642    private bool DecodeTagHeader(int tag, TagFormat expectedFormat, bool useTagEndMarker)
 102643    {
 102644        Debug.Assert(Encoding == SliceEncoding.Slice1);
 645
 102646        if (!useTagEndMarker && _classContext.Current.InstanceType != InstanceType.None)
 0647        {
 0648            throw new ArgumentException(
 0649                $"The {nameof(useTagEndMarker)} argument must be true when decoding the fields of a class or exception."
 0650                nameof(useTagEndMarker));
 651        }
 102652        else if (useTagEndMarker && _classContext.Current.InstanceType == InstanceType.None)
 0653        {
 0654            throw new ArgumentException(
 0655                $"The {nameof(useTagEndMarker)} argument must be false when decoding parameters.",
 0656                nameof(useTagEndMarker));
 657        }
 658
 102659        if (_classContext.Current.InstanceType != InstanceType.None)
 67660        {
 661            // tagged fields of a class or exception
 67662            if ((_classContext.Current.SliceFlags & SliceFlags.HasTaggedFields) == 0)
 20663            {
 664                // The current slice has no tagged field.
 20665                return false;
 666            }
 47667        }
 668
 82669        int requestedTag = tag;
 670
 82671        while (true)
 82672        {
 82673            if (!useTagEndMarker && _reader.End)
 10674            {
 10675                return false; // End of buffer indicates end of tagged fields.
 676            }
 677
 72678            long savedPos = _reader.Consumed;
 679
 72680            int v = DecodeUInt8();
 72681            if (useTagEndMarker && v == TagEndMarker)
 3682            {
 3683                _reader.Rewind(_reader.Consumed - savedPos);
 3684                return false;
 685            }
 686
 69687            var format = (TagFormat)(v & 0x07); // First 3 bits.
 69688            tag = v >> 3;
 69689            if (tag == 30)
 8690            {
 8691                tag = DecodeSize();
 8692            }
 693
 69694            if (tag > requestedTag)
 13695            {
 13696                _reader.Rewind(_reader.Consumed - savedPos);
 13697                return false; // No tagged field with the requested tag.
 698            }
 56699            else if (tag < requestedTag)
 0700            {
 0701                SkipTaggedValue(format);
 0702            }
 703            else
 56704            {
 56705                if (expectedFormat == TagFormat.OptimizedVSize)
 14706                {
 14707                    expectedFormat = TagFormat.VSize; // fix virtual tag format
 14708                }
 709
 56710                if (format != expectedFormat)
 0711                {
 0712                    throw new InvalidDataException($"Invalid tagged field '{tag}': unexpected format.");
 713                }
 56714                return true;
 715            }
 0716        }
 102717    }
 718
 719    private readonly byte PeekByte() =>
 395720        _reader.TryPeek(out byte value) ? value : throw new InvalidDataException(EndOfBufferMessage);
 721
 722    private void SkipTaggedValue(TagFormat format)
 35723    {
 35724        Debug.Assert(Encoding == SliceEncoding.Slice1);
 725
 35726        switch (format)
 727        {
 728            case TagFormat.F1:
 4729                Skip(1);
 4730                break;
 731            case TagFormat.F2:
 2732                Skip(2);
 2733                break;
 734            case TagFormat.F4:
 5735                Skip(4);
 5736                break;
 737            case TagFormat.F8:
 6738                Skip(8);
 6739                break;
 740            case TagFormat.Size:
 4741                SkipSize();
 4742                break;
 743            case TagFormat.VSize:
 12744                Skip(DecodeSize());
 12745                break;
 746            case TagFormat.FSize:
 2747                int size = DecodeInt32();
 2748                if (size < 0)
 0749                {
 0750                    throw new InvalidDataException($"Decoded invalid size: {size}.");
 751                }
 2752                Skip(size);
 2753                break;
 754            default:
 0755                throw new InvalidDataException($"Cannot skip tagged field with tag format '{format}'.");
 756        }
 35757    }
 758}

Methods/Properties

DecodeClass()
DecodeException(System.String)
DecodeNullableClass()
EndSlice()
StartSlice()
DecodeClass()
DecodeIndirectionTable()
DecodeIndirectionTableIntoCurrent()
DecodeInstance(System.Int32)
DecodeSliceHeaderIntoCurrent()
DecodeSliceSize()
DecodeTypeId(ZeroC.Slice.Internal.Slice1Definitions/TypeIdKind)
SkipIndirectionTable()
SkipSlice(System.String)
get_Consumed()
get_DecodingContext()
get_Encoding()
get_End()
get_Remaining()
.cctor()
.ctor(System.Buffers.ReadOnlySequence`1<System.Byte>,ZeroC.Slice.SliceEncoding,System.Object,System.Int32,ZeroC.Slice.IActivator,System.Int32)
.ctor(System.ReadOnlyMemory`1<System.Byte>,ZeroC.Slice.SliceEncoding,System.Object,System.Int32,ZeroC.Slice.IActivator,System.Int32)
CheckBoolValue(System.Boolean)
DecodeBool()
DecodeFloat32()
DecodeFloat64()
DecodeInt8()
DecodeInt16()
DecodeInt32()
DecodeInt64()
DecodeSize()
DecodeString()
DecodeUInt8()
DecodeUInt16()
DecodeUInt32()
DecodeUInt64()
DecodeVarInt32()
DecodeVarInt62()
DecodeVarUInt32()
DecodeVarUInt62()
TryDecodeUInt8(System.Byte&)
TryDecodeVarUInt62(System.UInt64&)
CopyTo(System.Span`1<System.Byte>)
CopyTo(System.Buffers.IBufferWriter`1<System.Byte>)
DecodeTagged(System.Int32,ZeroC.Slice.DecodeFunc`1<T>)
DecodeTagged(System.Int32,ZeroC.Slice.TagFormat,ZeroC.Slice.DecodeFunc`1<T>,System.Boolean)
GetBitSequenceReader(System.Int32)
IncreaseCollectionAllocation(System.Int32)
Skip(System.Int32)
SkipTagged(System.Boolean)
SkipSize()
DecodeVarInt62Length(System.Byte)
DecodeTagHeader(System.Int32,ZeroC.Slice.TagFormat,System.Boolean)
PeekByte()
SkipTaggedValue(ZeroC.Slice.TagFormat)