< Summary

Line coverage
94%
Covered lines: 471
Uncovered lines: 28
Coverable lines: 499
Total lines: 946
Line coverage: 94.3%
Branch coverage
94%
Covered branches: 163
Total branches: 173
Branch coverage: 94.2%
Method coverage
89%
Covered methods: 44
Total methods: 49
Method coverage: 89.7%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
File 1: EncodeClass(...)100%11100%
File 1: EncodeNullableClass(...)100%1414100%
File 1: EndSlice(...)100%1818100%
File 1: StartSlice(...)100%88100%
File 1: EncodeInstance(...)100%1010100%
File 1: EncodeTypeId(...)100%88100%
File 1: EncodeUnknownSlices(...)92.85%15.081482.35%
File 1: RegisterTypeId(...)100%44100%
File 1: .ctor(...)100%11100%
File 2: get_EncodedByteCount()100%11100%
File 2: get_Encoding()100%11100%
File 2: .cctor()100%11100%
File 2: EncodeInt32(...)100%11100%
File 2: EncodeVarUInt62(...)100%1313100%
File 2: GetVarInt62EncodedSize(...)100%210%
File 2: GetVarUInt62EncodedSize(...)100%11100%
File 2: .ctor(...)100%11100%
File 2: EncodeBool(...)100%22100%
File 2: EncodeFloat32(...)100%210%
File 2: EncodeFloat64(...)100%210%
File 2: EncodeInt8(...)100%11100%
File 2: EncodeInt16(...)100%11100%
File 2: EncodeInt32(...)100%11100%
File 2: EncodeInt64(...)100%11100%
File 2: EncodeSize(...)100%66100%
File 2: EncodeString(...)100%66100%
File 2: EncodeSizeIntoPlaceholder()100%44100%
File 2: EncodeUInt8(...)100%11100%
File 2: EncodeUInt16(...)100%11100%
File 2: EncodeUInt32(...)100%11100%
File 2: EncodeUInt64(...)100%210%
File 2: EncodeVarInt32(...)100%11100%
File 2: EncodeVarInt62(...)100%11100%
File 2: EncodeVarUInt32(...)100%210%
File 2: EncodeVarUInt62(...)100%11100%
File 2: EncodeTagged(...)50%2.03280%
File 2: EncodeTagged(...)66.66%6.84671.42%
File 2: EncodeTagged(...)66.66%12.571284.21%
File 2: GetBitSequenceWriter(...)83.33%12.251288%
File 2: GetPlaceholderSpan(...)100%11100%
File 2: GetSizeLength(...)100%44100%
File 2: WriteByteSpan(...)100%11100%
File 2: GetBitSequenceByteCount(...)100%22100%
File 2: EncodeFixedSizeNumeric(...)100%11100%
File 2: GetPlaceholderMemory(...)100%11100%
File 2: GetVarInt62EncodedSizeExponent(...)100%1616100%
File 2: GetVarUInt62EncodedSizeExponent(...)100%88100%
File 2: Advance(...)100%11100%
File 2: EncodeTaggedFieldHeader(...)100%44100%

File(s)

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

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3using System.Collections.Immutable;
 4using System.ComponentModel;
 5using System.Diagnostics;
 6using System.Globalization;
 7using static ZeroC.Slice.Internal.Slice1Definitions;
 8
 9namespace ZeroC.Slice;
 10
 11/// <summary>Provides methods to encode data with Slice.</summary>
 12public ref partial struct SliceEncoder
 13{
 14    /// <summary>Encodes a class instance.</summary>
 15    /// <param name="v">The class instance to encode.</param>
 4716    public void EncodeClass(SliceClass v) => EncodeNullableClass(v);
 17
 18    /// <summary>Encodes a class instance, or <see langword="null" />.</summary>
 19    /// <param name="v">The class instance to encode, or <see langword="null" />.</param>
 20    public void EncodeNullableClass(SliceClass? v)
 54621    {
 54622        if (v is null)
 25723        {
 25724            EncodeSize(0);
 25725        }
 26        else
 28927        {
 28928            if (_classContext.Current.InstanceType != InstanceType.None &&
 28929                _classContext.ClassFormat == ClassFormat.Sliced)
 3130            {
 31                // If encoding an instance within a slice and using the sliced format, encode an index of that
 32                // slice's indirection table.
 3133                if (_classContext.Current.IndirectionMap is not null &&
 3134                    _classContext.Current.IndirectionMap.TryGetValue(v, out int index))
 135                {
 36                    // Found, index is position in indirection table + 1
 137                    Debug.Assert(index > 0);
 138                }
 39                else
 3040                {
 3041                    _classContext.Current.IndirectionTable ??= new List<SliceClass>();
 3042                    _classContext.Current.IndirectionMap ??= new Dictionary<SliceClass, int>();
 3043                    _classContext.Current.IndirectionTable.Add(v);
 3044                    index = _classContext.Current.IndirectionTable.Count; // Position + 1 (0 is reserved for null)
 3045                    _classContext.Current.IndirectionMap.Add(v, index);
 3046                }
 3147                EncodeSize(index);
 3148            }
 49            else
 25850            {
 25851                EncodeInstance(v); // Encodes the instance or a reference if already encoded.
 25852            }
 28953        }
 54654    }
 55
 56    /// <summary>Marks the end of the encoding of a class or exception slice.</summary>
 57    /// <param name="lastSlice">Whether this is the last Slice or not.</param>
 58    [EditorBrowsable(EditorBrowsableState.Never)]
 59    public void EndSlice(bool lastSlice)
 49260    {
 49261        Debug.Assert(_classContext.Current.InstanceType != InstanceType.None);
 62
 49263        if (lastSlice)
 30664        {
 30665            _classContext.Current.SliceFlags |= SliceFlags.IsLastSlice;
 30666        }
 67
 68        // Encodes the tagged end marker if some tagged fields were encoded. Note that tagged fields are encoded before
 69        // the indirection table and are included in the slice size.
 49270        if ((_classContext.Current.SliceFlags & SliceFlags.HasTaggedFields) != 0)
 2671        {
 2672            EncodeUInt8(TagEndMarker);
 2673        }
 74
 75        // Encodes the slice size if necessary.
 49276        if ((_classContext.Current.SliceFlags & SliceFlags.HasSliceSize) != 0)
 15177        {
 78            // Size includes the size length.
 15179            EncodeInt32(
 15180                EncodedByteCount - _classContext.Current.SliceSizeStartPos,
 15181                _classContext.Current.SliceSizePlaceholder.Span);
 15182        }
 83
 49284        if (_classContext.Current.IndirectionTable?.Count > 0)
 3685        {
 3686            Debug.Assert(_classContext.ClassFormat == ClassFormat.Sliced);
 3687            _classContext.Current.SliceFlags |= SliceFlags.HasIndirectionTable;
 88
 3689            EncodeSize(_classContext.Current.IndirectionTable.Count);
 18490            foreach (SliceClass v in _classContext.Current.IndirectionTable)
 3891            {
 3892                EncodeInstance(v);
 3893            }
 3694            _classContext.Current.IndirectionTable.Clear();
 3695            _classContext.Current.IndirectionMap?.Clear(); // IndirectionMap is null when encoding unknown slices.
 3696        }
 97
 98        // Update SliceFlags in case they were updated.
 49299        _classContext.Current.SliceFlagsPlaceholder.Span[0] = (byte)_classContext.Current.SliceFlags;
 100
 101        // If this is the last slice in an exception, reset the current context.
 492102        if (lastSlice && _classContext.Current.InstanceType == InstanceType.Exception)
 31103        {
 31104            _classContext.Current = default;
 31105        }
 492106    }
 107
 108    /// <summary>Marks the start of the encoding of a class or exception slice.</summary>
 109    /// <param name="typeId">The type ID of this slice.</param>
 110    /// <param name="compactId ">The compact ID of this slice, if any.</param>
 111    [EditorBrowsable(EditorBrowsableState.Never)]
 112    public void StartSlice(string typeId, int? compactId = null)
 492113    {
 114        // This will only be called with an InstanceType of 'None' when we're starting to encode the first slice
 115        // of an exception.
 492116        if (_classContext.Current.InstanceType == InstanceType.None)
 31117        {
 31118            _classContext.ClassFormat = ClassFormat.Sliced; // always encode exceptions in sliced format
 31119            _classContext.Current.InstanceType = InstanceType.Exception;
 31120            _classContext.Current.FirstSlice = true;
 31121        }
 122
 492123        _classContext.Current.SliceFlags = default;
 492124        _classContext.Current.SliceFlagsPlaceholder = GetPlaceholderMemory(1);
 125
 492126        if (_classContext.ClassFormat == ClassFormat.Sliced)
 151127        {
 151128            EncodeTypeId(typeId, compactId);
 129            // Encode the slice size if using the sliced format.
 151130            _classContext.Current.SliceFlags |= SliceFlags.HasSliceSize;
 151131            _classContext.Current.SliceSizeStartPos = EncodedByteCount; // size includes size-length
 151132            _classContext.Current.SliceSizePlaceholder = GetPlaceholderMemory(4);
 151133        }
 341134        else if (_classContext.Current.FirstSlice)
 228135        {
 228136            EncodeTypeId(typeId, compactId);
 228137        }
 138
 492139        if (_classContext.Current.FirstSlice)
 306140        {
 306141            _classContext.Current.FirstSlice = false;
 306142        }
 492143    }
 144
 145    /// <summary>Encodes this class instance inline if not previously encoded, otherwise just encode its instance
 146    /// ID.</summary>
 147    /// <param name="v">The class instance.</param>
 148    private void EncodeInstance(SliceClass v)
 296149    {
 150        // If the instance was already encoded, just encode its instance ID.
 296151        if (_classContext.InstanceMap is not null && _classContext.InstanceMap.TryGetValue(v, out int instanceId))
 21152        {
 21153            EncodeSize(instanceId);
 21154        }
 155        else
 275156        {
 275157            _classContext.InstanceMap ??= new Dictionary<SliceClass, int>();
 158
 159            // We haven't seen this instance previously, so we create a new instance ID and insert the instance
 160            // and its ID in the encoded map, before encoding the instance inline.
 161            // The instance IDs start at 2 (0 means null and 1 means the instance is encoded immediately after).
 275162            instanceId = _classContext.InstanceMap.Count + 2;
 275163            _classContext.InstanceMap.Add(v, instanceId);
 164
 275165            EncodeSize(1); // Class instance marker.
 166
 167            // Save _current in case we're encoding a nested instance.
 275168            InstanceData previousCurrent = _classContext.Current;
 275169            _classContext.Current = default;
 275170            _classContext.Current.InstanceType = InstanceType.Class;
 275171            _classContext.Current.FirstSlice = true;
 172
 275173            if (v.UnknownSlices.Count > 0 && _classContext.ClassFormat == ClassFormat.Sliced)
 12174            {
 12175                EncodeUnknownSlices(v.UnknownSlices, fullySliced: v is UnknownSliceClass);
 12176                _classContext.Current.FirstSlice = false;
 12177            }
 275178            v.Encode(ref this);
 179
 180            // Restore previous _current.
 275181            _classContext.Current = previousCurrent;
 275182        }
 296183    }
 184
 185    /// <summary>Encodes the type ID or compact ID immediately after the slice flags byte, and updates the slice
 186    /// flags byte as needed.</summary>
 187    /// <param name="typeId">The type ID of the current slice.</param>
 188    /// <param name="compactId">The compact ID of the current slice.</param>
 189    private void EncodeTypeId(string typeId, int? compactId)
 379190    {
 379191        Debug.Assert(_classContext.Current.InstanceType != InstanceType.None);
 192
 379193        TypeIdKind typeIdKind = TypeIdKind.None;
 194
 379195        if (_classContext.Current.InstanceType == InstanceType.Class)
 340196        {
 340197            if (compactId is int compactIdValue)
 27198            {
 27199                typeIdKind = TypeIdKind.CompactId;
 27200                EncodeSize(compactIdValue);
 27201            }
 202            else
 313203            {
 313204                int index = RegisterTypeId(typeId);
 313205                if (index < 0)
 73206                {
 73207                    typeIdKind = TypeIdKind.String;
 73208                    EncodeString(typeId);
 73209                }
 210                else
 240211                {
 240212                    typeIdKind = TypeIdKind.Index;
 240213                    EncodeSize(index);
 240214                }
 313215            }
 340216        }
 217        else
 39218        {
 39219            Debug.Assert(compactId is null);
 220            // We always encode a string and don't set a type ID kind in SliceFlags.
 39221            EncodeString(typeId);
 39222        }
 223
 379224        _classContext.Current.SliceFlags |= (SliceFlags)typeIdKind;
 379225    }
 226
 227    /// <summary>Encodes sliced-off slices.</summary>
 228    /// <param name="unknownSlices">The sliced-off slices to encode.</param>
 229    /// <param name="fullySliced">When <see langword="true" />, slicedData holds all the data of this instance.</param>
 230    private void EncodeUnknownSlices(ImmutableList<SliceInfo> unknownSlices, bool fullySliced)
 12231    {
 12232        Debug.Assert(_classContext.Current.InstanceType != InstanceType.None);
 233
 234        // We only re-encode preserved slices if we are using the sliced format. Otherwise, we ignore the preserved
 235        // slices, which essentially "slices" the instance into the most-derived type known by the sender.
 12236        if (_classContext.ClassFormat != ClassFormat.Sliced)
 0237        {
 0238            throw new NotSupportedException(
 0239                $"Cannot encode sliced data into payload using {_classContext.ClassFormat} format.");
 240        }
 241
 72242        for (int i = 0; i < unknownSlices.Count; ++i)
 24243        {
 24244            SliceInfo sliceInfo = unknownSlices[i];
 245
 246            // If type ID is a compact ID, extract it.
 24247            int? compactId = null;
 24248            if (!sliceInfo.TypeId.StartsWith("::", StringComparison.Ordinal))
 8249            {
 250                try
 8251                {
 8252                    compactId = int.Parse(sliceInfo.TypeId, CultureInfo.InvariantCulture);
 8253                }
 0254                catch (FormatException exception)
 0255                {
 0256                    throw new InvalidDataException($"Received invalid type ID {sliceInfo.TypeId}.", exception);
 257                }
 8258            }
 259
 24260            StartSlice(sliceInfo.TypeId, compactId);
 261
 262            // Writes the bytes associated with this slice.
 24263            WriteByteSpan(sliceInfo.Bytes.Span);
 264
 24265            if (sliceInfo.HasTaggedFields)
 4266            {
 4267                _classContext.Current.SliceFlags |= SliceFlags.HasTaggedFields;
 4268            }
 269
 270            // Make sure to also encode the instance indirection table.
 271            // These instances will be encoded (and assigned instance IDs) in EndSlice.
 24272            if (sliceInfo.Instances.Count > 0)
 8273            {
 8274                _classContext.Current.IndirectionTable ??= new List<SliceClass>();
 8275                Debug.Assert(_classContext.Current.IndirectionTable.Count == 0);
 8276                _classContext.Current.IndirectionTable.AddRange(sliceInfo.Instances);
 8277            }
 24278            EndSlice(lastSlice: fullySliced && (i == unknownSlices.Count - 1));
 24279        }
 12280    }
 281
 282    /// <summary>Registers or looks up a type ID in the _typeIdMap.</summary>
 283    /// <param name="typeId">The type ID to register or lookup.</param>
 284    /// <returns>The index in _typeIdMap if this type ID was previously registered; otherwise, -1.</returns>
 285    private int RegisterTypeId(string typeId)
 313286    {
 313287        _classContext.TypeIdMap ??= new Dictionary<string, int>();
 288
 313289        if (_classContext.TypeIdMap.TryGetValue(typeId, out int index))
 240290        {
 240291            return index;
 292        }
 293        else
 73294        {
 73295            index = _classContext.TypeIdMap.Count + 1;
 73296            _classContext.TypeIdMap.Add(typeId, index);
 73297            return -1;
 298        }
 313299    }
 300
 301    private struct ClassContext
 302    {
 303        // The current class/exception format, can be either Compact or Sliced.
 304        internal ClassFormat ClassFormat;
 305
 306        // Data for the class or exception instance that is currently getting encoded.
 307        internal InstanceData Current;
 308
 309        // Map of class instance to instance ID, where the instance IDs start at 2.
 310        //  - Instance ID = 0 means null.
 311        //  - Instance ID = 1 means the instance is encoded inline afterwards.
 312        //  - Instance ID > 1 means a reference to a previously encoded instance, found in this map.
 313        internal Dictionary<SliceClass, int>? InstanceMap;
 314
 315        // Map of type ID string to type ID index.
 316        // We assign a type ID index (starting with 1) to each type ID we write, in order.
 317        internal Dictionary<string, int>? TypeIdMap;
 318
 319        internal ClassContext(ClassFormat classFormat)
 74344320            : this() => ClassFormat = classFormat;
 321    }
 322
 323    private struct InstanceData
 324    {
 325        // The following fields are used and reused for all the slices of a class or exception instance.
 326
 327        internal InstanceType InstanceType;
 328
 329        // The following fields are used for the current slice:
 330
 331        internal bool FirstSlice;
 332
 333        // The indirection map and indirection table are only used for the sliced format.
 334        internal Dictionary<SliceClass, int>? IndirectionMap;
 335        internal List<SliceClass>? IndirectionTable;
 336
 337        internal SliceFlags SliceFlags;
 338
 339        // The slice flags byte.
 340        internal Memory<byte> SliceFlagsPlaceholder;
 341
 342        // The place holder for the Slice size. Used only for the sliced format.
 343        internal Memory<byte> SliceSizePlaceholder;
 344
 345        // The starting position for computing the size of the slice. It's just before the SliceSizePlaceholder as
 346        // the size includes the size length.
 347        internal int SliceSizeStartPos;
 348    }
 349
 350    private enum InstanceType : byte
 351    {
 352        None = 0,
 353        Class,
 354        Exception
 355    }
 356}

/home/runner/work/icerpc-csharp/icerpc-csharp/src/ZeroC.Slice/SliceEncoder.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 static ZeroC.Slice.Internal.Slice1Definitions;
 9
 10namespace ZeroC.Slice;
 11
 12/// <summary>Provides methods to encode data with Slice.</summary>
 13public ref partial struct SliceEncoder
 14{
 15    /// <summary>Gets the number of bytes encoded by this encoder into the underlying buffer writer.</summary>
 142016916    public int EncodedByteCount { get; private set; }
 17
 18    /// <summary>Gets the Slice encoding of this encoder.</summary>
 32720219    public SliceEncoding Encoding { get; }
 20
 21    internal const long VarInt62MinValue = -2_305_843_009_213_693_952; // -2^61
 22    internal const long VarInt62MaxValue = 2_305_843_009_213_693_951; // 2^61 - 1
 23    internal const ulong VarUInt62MinValue = 0;
 24    internal const ulong VarUInt62MaxValue = 4_611_686_018_427_387_903; // 2^62 - 1
 25
 826    private static readonly UTF8Encoding _utf8 =
 827        new(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); // no BOM
 28
 29    private readonly IBufferWriter<byte> _bufferWriter;
 30
 31    private ClassContext _classContext;
 32
 33    private Encoder? _utf8Encoder; // initialized lazily
 34
 35    /// <summary>Encodes an int as a Slice int32 into a span of 4 bytes.</summary>
 36    /// <param name="value">The value to encode.</param>
 37    /// <param name="into">The destination byte buffer, which must be 4 bytes long.</param>
 38    public static void EncodeInt32(int value, Span<byte> into)
 561239    {
 561240        Debug.Assert(into.Length == 4);
 561241        MemoryMarshal.Write(into, in value);
 561242    }
 43
 44    /// <summary>Encodes a ulong as a Slice varuint62 into a span of bytes using a fixed number of bytes.</summary>
 45    /// <param name="value">The value to encode.</param>
 46    /// <param name="into">The destination byte buffer, which must be 1, 2, 4 or 8 bytes long.</param>
 47    public static void EncodeVarUInt62(ulong value, Span<byte> into)
 17613448    {
 17613449        int sizeLength = into.Length;
 17613450        Debug.Assert(sizeLength == 1 || sizeLength == 2 || sizeLength == 4 || sizeLength == 8);
 51
 17613452        (uint encodedSizeExponent, long maxSize) = sizeLength switch
 17613453        {
 13881054            1 => (0x00u, 63), // 2^6 - 1
 670055            2 => (0x01u, 16_383), // 2^14 - 1
 3062256            4 => (0x02u, 1_073_741_823), // 2^30 - 1
 257            _ => (0x03u, (long)VarUInt62MaxValue)
 17613458        };
 59
 17613460        if (value > (ulong)maxSize)
 461        {
 462            throw new ArgumentOutOfRangeException(
 463                nameof(value),
 464                $"The value '{value}' cannot be encoded on {sizeLength} bytes.");
 65        }
 66
 17613067        Span<byte> ulongBuf = stackalloc byte[8];
 17613068        value <<= 2;
 69
 17613070        value |= encodedSizeExponent;
 17613071        MemoryMarshal.Write(ulongBuf, in value);
 17613072        ulongBuf[0..sizeLength].CopyTo(into);
 17613073    }
 74
 75    /// <summary>Computes the minimum number of bytes required to encode a long value using the Slice encoding's
 76    /// variable-size encoded representation.</summary>
 77    /// <param name="value">The long value.</param>
 78    /// <returns>The minimum number of bytes required to encode <paramref name="value" />. Can be 1, 2, 4 or 8.
 79    /// </returns>
 080    public static int GetVarInt62EncodedSize(long value) => 1 << GetVarInt62EncodedSizeExponent(value);
 81
 82    /// <summary>Computes the minimum number of bytes required to encode a ulong value using the Slice encoding's
 83    /// variable-size encoded representation.</summary>
 84    /// <param name="value">The ulong value.</param>
 85    /// <returns>The minimum number of bytes required to encode <paramref name="value" />. Can be 1, 2, 4 or 8.
 86    /// </returns>
 14500787    public static int GetVarUInt62EncodedSize(ulong value) => 1 << GetVarUInt62EncodedSizeExponent(value);
 88
 89    /// <summary>Constructs a Slice encoder.</summary>
 90    /// <param name="bufferWriter">A buffer writer that writes to byte buffers. See important remarks below.</param>
 91    /// <param name="encoding">The Slice encoding.</param>
 92    /// <param name="classFormat">The class format (Slice1 only).</param>
 93    /// <remarks>Warning: the Slice encoding requires rewriting buffers, and many buffer writers do not support this
 94    /// behavior. It is safe to use a pipe writer or a buffer writer that writes to a single fixed-size buffer (without
 95    /// reallocation).</remarks>
 96    public SliceEncoder(IBufferWriter<byte> bufferWriter, SliceEncoding encoding, ClassFormat classFormat = default)
 3717297        : this()
 3717298    {
 3717299        Encoding = encoding;
 37172100        _bufferWriter = bufferWriter;
 37172101        _classContext = new ClassContext(classFormat);
 37172102    }
 103
 104    // Encode methods for basic types
 105
 106    /// <summary>Encodes a bool into a Slice bool.</summary>
 107    /// <param name="v">The boolean to encode.</param>
 3252108    public void EncodeBool(bool v) => EncodeUInt8(v ? (byte)1 : (byte)0);
 109
 110    /// <summary>Encodes a float into a Slice float32.</summary>
 111    /// <param name="v">The float to encode.</param>
 0112    public void EncodeFloat32(float v) => EncodeFixedSizeNumeric(v);
 113
 114    /// <summary>Encodes a double into a Slice float64.</summary>
 115    /// <param name="v">The double to encode.</param>
 0116    public void EncodeFloat64(double v) => EncodeFixedSizeNumeric(v);
 117
 118    /// <summary>Encodes an sbyte into a Slice int8.</summary>
 119    /// <param name="v">The sbyte to encode.</param>
 2120    public void EncodeInt8(sbyte v) => EncodeUInt8((byte)v);
 121
 122    /// <summary>Encodes a short into a Slice int16.</summary>
 123    /// <param name="v">The short to encode.</param>
 2125124    public void EncodeInt16(short v) => EncodeFixedSizeNumeric(v);
 125
 126    /// <summary>Encodes an int into a Slice int32.</summary>
 127    /// <param name="v">The int to encode.</param>
 150578128    public void EncodeInt32(int v) => EncodeFixedSizeNumeric(v);
 129
 130    /// <summary>Encodes a long into a Slice int64.</summary>
 131    /// <param name="v">The long to encode.</param>
 111132    public void EncodeInt64(long v) => EncodeFixedSizeNumeric(v);
 133
 134    /// <summary>Encodes a size on variable number of bytes.</summary>
 135    /// <param name="value">The size to encode.</param>
 136    public void EncodeSize(int value)
 40581137    {
 40581138        if (value < 0)
 2139        {
 2140            throw new ArgumentException(
 2141                $"The {nameof(value)} argument must be greater than 0.",
 2142                nameof(value));
 143        }
 144
 40579145        if (Encoding == SliceEncoding.Slice1)
 25796146        {
 25796147            if (value < 255)
 25777148            {
 25777149                EncodeUInt8((byte)value);
 25777150            }
 151            else
 19152            {
 19153                EncodeUInt8(255);
 19154                EncodeInt32(value);
 19155            }
 25796156        }
 157        else
 14783158        {
 14783159            EncodeVarUInt62((ulong)value);
 14783160        }
 40579161    }
 162
 163    /// <summary>Encodes a string into a Slice string.</summary>
 164    /// <param name="v">The string to encode.</param>
 165    public void EncodeString(string v)
 153448166    {
 153448167        if (v.Length == 0)
 10883168        {
 10883169            EncodeSize(0);
 10883170        }
 171        else
 142565172        {
 142565173            int maxSize = _utf8.GetMaxByteCount(v.Length);
 142565174            int sizeLength = GetSizeLength(maxSize);
 142565175            Span<byte> sizePlaceholder = GetPlaceholderSpan(sizeLength);
 176
 142565177            Span<byte> currentSpan = _bufferWriter.GetSpan();
 142565178            if (currentSpan.Length >= maxSize)
 141417179            {
 180                // Encode directly into currentSpan
 141417181                int size = _utf8.GetBytes(v, currentSpan);
 141417182                EncodeSizeIntoPlaceholder(Encoding, size, sizePlaceholder);
 141417183                Advance(size);
 141417184            }
 185            else
 1148186            {
 187                // Encode piecemeal using _utf8Encoder
 1148188                if (_utf8Encoder is null)
 65189                {
 65190                    _utf8Encoder = _utf8.GetEncoder();
 65191                }
 192                else
 1083193                {
 1083194                    _utf8Encoder.Reset();
 1083195                }
 196
 1148197                ReadOnlySpan<char> chars = v.AsSpan();
 1148198                _utf8Encoder.Convert(chars, _bufferWriter, flush: true, out long bytesUsed, out bool completed);
 199
 1148200                Debug.Assert(completed); // completed is always true when flush is true
 1148201                int size = checked((int)bytesUsed);
 1148202                EncodedByteCount += size;
 1148203                EncodeSizeIntoPlaceholder(Encoding, size, sizePlaceholder);
 1148204            }
 142565205        }
 206
 207        static void EncodeSizeIntoPlaceholder(SliceEncoding encoding, int size, Span<byte> into)
 142565208        {
 142565209            if (encoding == SliceEncoding.Slice1)
 3830210            {
 3830211                if (into.Length == 1)
 3812212                {
 3812213                    Debug.Assert(size < 255);
 3812214                    into[0] = (byte)size;
 3812215                }
 216                else
 18217                {
 18218                    Debug.Assert(into.Length == 5);
 18219                    into[0] = 255;
 18220                    EncodeInt32(size, into[1..]);
 18221                }
 3830222            }
 223            else
 138735224            {
 138735225                EncodeVarUInt62((ulong)size, into);
 138735226            }
 142565227        }
 153448228    }
 229
 230    /// <summary>Encodes a byte into a Slice uint8.</summary>
 231    /// <param name="v">The byte to encode.</param>
 232    public void EncodeUInt8(byte v)
 74986233    {
 74986234        Span<byte> span = _bufferWriter.GetSpan();
 74986235        span[0] = v;
 74986236        Advance(1);
 74986237    }
 238
 239    /// <summary>Encodes a ushort into a Slice uint16.</summary>
 240    /// <param name="v">The ushort to encode.</param>
 2241    public void EncodeUInt16(ushort v) => EncodeFixedSizeNumeric(v);
 242
 243    /// <summary>Encodes a uint into a Slice uint32.</summary>
 244    /// <param name="v">The uint to encode.</param>
 22245    public void EncodeUInt32(uint v) => EncodeFixedSizeNumeric(v);
 246
 247    /// <summary>Encodes a ulong into a Slice uint64.</summary>
 248    /// <param name="v">The ulong to encode.</param>
 0249    public void EncodeUInt64(ulong v) => EncodeFixedSizeNumeric(v);
 250
 251    /// <summary>Encodes an int into a Slice varint32.</summary>
 252    /// <param name="v">The int to encode.</param>
 300253    public void EncodeVarInt32(int v) => EncodeVarInt62(v);
 254
 255    /// <summary>Encodes a long into a Slice varint62, with the minimum number of bytes required
 256    /// by the encoding.</summary>
 257    /// <param name="v">The long to encode. It must be in the range [-2^61..2^61 - 1].</param>
 258    public void EncodeVarInt62(long v)
 321259    {
 321260        int encodedSizeExponent = GetVarInt62EncodedSizeExponent(v);
 319261        v <<= 2;
 319262        v |= (uint)encodedSizeExponent;
 263
 319264        Span<byte> data = _bufferWriter.GetSpan(sizeof(long));
 319265        MemoryMarshal.Write(data, in v);
 319266        Advance(1 << encodedSizeExponent);
 319267    }
 268
 269    /// <summary>Encodes a uint into a Slice varuint32.</summary>
 270    /// <param name="v">The uint to encode.</param>
 0271    public void EncodeVarUInt32(uint v) => EncodeVarUInt62(v);
 272
 273    /// <summary>Encodes a ulong into a Slice varuint62, with the minimum number of bytes
 274    /// required by the encoding.</summary>
 275    /// <param name="v">The ulong to encode. It must be in the range [0..2^62 - 1].</param>
 276    public void EncodeVarUInt62(ulong v)
 49983277    {
 49983278        int encodedSizeExponent = GetVarUInt62EncodedSizeExponent(v);
 49982279        v <<= 2;
 49982280        v |= (uint)encodedSizeExponent;
 281
 49982282        Span<byte> data = _bufferWriter.GetSpan(sizeof(ulong));
 49982283        MemoryMarshal.Write(data, in v);
 49982284        Advance(1 << encodedSizeExponent);
 49982285    }
 286
 287    // Other methods
 288
 289    /// <summary>Encodes a non-null Slice2 encoded tagged value. The number of bytes needed to encode the value is
 290    /// not known before encoding this value (Slice2 only).</summary>
 291    /// <typeparam name="T">The type of the value being encoded.</typeparam>
 292    /// <param name="tag">The tag.</param>
 293    /// <param name="v">The value to encode.</param>
 294    /// <param name="encodeAction">The delegate that encodes the value after the tag header.</param>
 295    public void EncodeTagged<T>(int tag, T v, EncodeAction<T> encodeAction) where T : notnull
 24296    {
 24297        if (Encoding == SliceEncoding.Slice1)
 0298        {
 0299            throw new InvalidOperationException("Slice1 encoded tags must be encoded with tag formats.");
 300        }
 301
 24302        EncodeVarInt32(tag); // the key
 24303        Span<byte> sizePlaceholder = GetPlaceholderSpan(4);
 24304        int startPos = EncodedByteCount;
 24305        encodeAction(ref this, v);
 24306        EncodeVarUInt62((ulong)(EncodedByteCount - startPos), sizePlaceholder);
 24307    }
 308
 309    /// <summary>Encodes a non-null encoded tagged value. The number of bytes needed to encode the value is
 310    /// known before encoding the value. With Slice1 encoding this method always use the VSize tag format.</summary>
 311    /// <typeparam name="T">The type of the value being encoded.</typeparam>
 312    /// <param name="tag">The tag.</param>
 313    /// <param name="size">The number of bytes needed to encode the value.</param>
 314    /// <param name="v">The value to encode.</param>
 315    /// <param name="encodeAction">The delegate that encodes the value after the tag header.</param>
 316    public void EncodeTagged<T>(int tag, int size, T v, EncodeAction<T> encodeAction) where T : notnull
 41317    {
 41318        if (size <= 0)
 0319        {
 0320            throw new ArgumentException("Invalid size value, size must be greater than zero.", nameof(size));
 321        }
 322
 41323        if (Encoding == SliceEncoding.Slice1)
 16324        {
 16325            EncodeTaggedFieldHeader(tag, TagFormat.VSize);
 16326        }
 327        else
 25328        {
 25329            EncodeVarInt32(tag);
 25330        }
 331
 41332        EncodeSize(size);
 41333        int startPos = EncodedByteCount;
 41334        encodeAction(ref this, v);
 335
 41336        int actualSize = EncodedByteCount - startPos;
 41337        if (actualSize != size)
 0338        {
 0339            throw new ArgumentException(
 0340                $"The value of size ({size}) does not match encoded size ({actualSize}).",
 0341                nameof(size));
 342        }
 41343    }
 344
 345    /// <summary>Encodes a non-null Slice1 encoded tagged value. The number of bytes needed to encode the value is
 346    /// not known before encoding this value.</summary>
 347    /// <typeparam name="T">The type of the value being encoded.</typeparam>
 348    /// <param name="tag">The tag. Must be either FSize or OptimizedVSize.</param>
 349    /// <param name="tagFormat">The tag format.</param>
 350    /// <param name="v">The value to encode.</param>
 351    /// <param name="encodeAction">The delegate that encodes the value after the tag header.</param>
 352    /// <exception cref="ArgumentException">Thrown if <paramref name="tagFormat" /> is VSize.</exception>
 353    public void EncodeTagged<T>(
 354        int tag,
 355        TagFormat tagFormat,
 356        T v,
 357        EncodeAction<T> encodeAction) where T : notnull
 75358    {
 75359        if (Encoding != SliceEncoding.Slice1)
 0360        {
 0361            throw new InvalidOperationException("Tag formats can only be used with the Slice1 encoding.");
 362        }
 363
 75364        switch (tagFormat)
 365        {
 366            case TagFormat.F1:
 367            case TagFormat.F2:
 368            case TagFormat.F4:
 369            case TagFormat.F8:
 370            case TagFormat.Size:
 51371                EncodeTaggedFieldHeader(tag, tagFormat);
 51372                encodeAction(ref this, v);
 51373                break;
 374            case TagFormat.FSize:
 6375                EncodeTaggedFieldHeader(tag, tagFormat);
 6376                Span<byte> placeholder = GetPlaceholderSpan(4);
 6377                int startPos = EncodedByteCount;
 6378                encodeAction(ref this, v);
 379                // We don't include the size-length in the size we encode.
 6380                EncodeInt32(EncodedByteCount - startPos, placeholder);
 6381                break;
 382
 383            case TagFormat.OptimizedVSize:
 384                // Used to encode string, and sequences of non optional elements with 1 byte min wire size,
 385                // in this case OptimizedVSize is always used to optimize out the size.
 18386                EncodeTaggedFieldHeader(tag, TagFormat.VSize);
 18387                encodeAction(ref this, v);
 18388                break;
 389
 390            default:
 0391                throw new ArgumentException($"Invalid tag format value: '{tagFormat}'.", nameof(tagFormat));
 392        }
 75393    }
 394
 395    /// <summary>Allocates a new bit sequence in the underlying buffer(s) and returns a writer for this bit
 396    /// sequence.</summary>
 397    /// <param name="bitSequenceSize">The minimum number of bits in the bit sequence.</param>
 398    /// <returns>The bit sequence writer.</returns>
 399    public BitSequenceWriter GetBitSequenceWriter(int bitSequenceSize)
 1126400    {
 1126401        if (Encoding == SliceEncoding.Slice1)
 0402        {
 0403            throw new InvalidOperationException("The bit sequence writer cannot be used with the Slice1 encoding.");
 404        }
 405
 1126406        if (bitSequenceSize <= 0)
 0407        {
 0408            throw new ArgumentOutOfRangeException(
 0409                nameof(bitSequenceSize),
 0410                $"The {nameof(bitSequenceSize)} argument must be greater than 0.");
 411        }
 412
 1126413        int remaining = GetBitSequenceByteCount(bitSequenceSize);
 414
 1126415        Span<byte> firstSpan = _bufferWriter.GetSpan();
 1126416        Span<byte> secondSpan = default;
 417
 418        // We only create this additionalMemory list in the rare situation where 2 spans are not sufficient.
 1126419        List<Memory<byte>>? additionalMemory = null;
 420
 1126421        if (firstSpan.Length >= remaining)
 1114422        {
 1114423            firstSpan = firstSpan[0..remaining];
 1114424            Advance(remaining);
 1114425        }
 426        else
 12427        {
 12428            Advance(firstSpan.Length);
 12429            remaining -= firstSpan.Length;
 430
 12431            secondSpan = _bufferWriter.GetSpan();
 12432            if (secondSpan.Length >= remaining)
 6433            {
 6434                secondSpan = secondSpan[0..remaining];
 6435                Advance(remaining);
 6436            }
 437            else
 6438            {
 6439                Advance(secondSpan.Length);
 6440                remaining -= secondSpan.Length;
 6441                additionalMemory = new List<Memory<byte>>();
 442
 443                do
 78444                {
 78445                    Memory<byte> memory = _bufferWriter.GetMemory();
 78446                    if (memory.Length >= remaining)
 6447                    {
 6448                        additionalMemory.Add(memory[0..remaining]);
 6449                        Advance(remaining);
 6450                        remaining = 0;
 6451                    }
 452                    else
 72453                    {
 72454                        additionalMemory.Add(memory);
 72455                        Advance(memory.Length);
 72456                        remaining -= memory.Length;
 72457                    }
 78458                }
 78459                while (remaining > 0);
 6460            }
 12461        }
 462
 1126463        return new BitSequenceWriter(firstSpan, secondSpan, additionalMemory);
 1126464    }
 465
 466    /// <summary>Gets a placeholder to be filled-in later.</summary>
 467    /// <param name="size">The size of the placeholder, typically a small number like 4.</param>
 468    /// <returns>A buffer of length <paramref name="size" />.</returns>
 469    /// <remarks>We make the assumption the underlying buffer writer allows rewriting memory it provided even after
 470    /// successive calls to GetMemory/GetSpan and Advance.</remarks>
 471    public Span<byte> GetPlaceholderSpan(int size)
 179056472    {
 179056473        Debug.Assert(size > 0);
 179056474        Span<byte> placeholder = _bufferWriter.GetSpan(size)[0..size];
 179056475        Advance(size);
 179056476        return placeholder;
 179056477    }
 478
 479    /// <summary>Computes the minimum number of bytes needed to encode a variable-length size.</summary>
 480    /// <param name="size">The size.</param>
 481    /// <returns>The minimum number of bytes.</returns>
 142576482    public readonly int GetSizeLength(int size) => Encoding == SliceEncoding.Slice1 ?
 142576483        (size < 255 ? 1 : 5) : GetVarUInt62EncodedSize(checked((ulong)size));
 484
 485    /// <summary>Copies a span of bytes to the underlying buffer writer.</summary>
 486    /// <param name="span">The span to copy.</param>
 487    public void WriteByteSpan(ReadOnlySpan<byte> span)
 11861488    {
 11861489        _bufferWriter.Write(span);
 11861490        EncodedByteCount += span.Length;
 11861491    }
 492
 2220493    internal static int GetBitSequenceByteCount(int bitCount) => (bitCount >> 3) + ((bitCount & 0x07) != 0 ? 1 : 0);
 494
 495    /// <summary>Encodes a fixed-size numeric value.</summary>
 496    /// <param name="v">The numeric value to encode.</param>
 497    internal void EncodeFixedSizeNumeric<T>(T v) where T : struct
 153350498    {
 153350499        int elementSize = Unsafe.SizeOf<T>();
 153350500        Span<byte> data = _bufferWriter.GetSpan(elementSize)[0..elementSize];
 153350501        MemoryMarshal.Write(data, in v);
 153350502        Advance(elementSize);
 153350503    }
 504
 505    /// <summary>Gets a placeholder to be filled-in later.</summary>
 506    /// <param name="size">The size of the placeholder, typically a small number like 4.</param>
 507    /// <returns>A buffer of length <paramref name="size" />.</returns>
 508    /// <remarks>We make the assumption the underlying buffer writer allows rewriting memory it provided even after
 509    /// successive calls to GetMemory/GetSpan and Advance.</remarks>
 510    internal Memory<byte> GetPlaceholderMemory(int size)
 643511    {
 643512        Debug.Assert(size > 0);
 643513        Memory<byte> placeholder = _bufferWriter.GetMemory(size)[0..size];
 643514        Advance(size);
 643515        return placeholder;
 643516    }
 517
 518    /// <summary>Gets the minimum number of bytes needed to encode a long value with the varint62 encoding as an
 519    /// exponent of 2.</summary>
 520    /// <param name="value">The value to encode.</param>
 521    /// <returns>N where 2^N is the number of bytes needed to encode value with Slice's varint62 encoding.</returns>
 522    private static int GetVarInt62EncodedSizeExponent(long value)
 321523    {
 321524        if (value < VarInt62MinValue || value > VarInt62MaxValue)
 2525        {
 2526            throw new ArgumentOutOfRangeException(nameof(value), $"The value '{value}' is out of the varint62 range.");
 527        }
 528
 319529        return (value << 2) switch
 319530        {
 620531            long b when b >= sbyte.MinValue && b <= sbyte.MaxValue => 0,
 20532            long s when s >= short.MinValue && s <= short.MaxValue => 1,
 26533            long i when i >= int.MinValue && i <= int.MaxValue => 2,
 6534            _ => 3
 319535        };
 319536    }
 537
 538    /// <summary>Gets the minimum number of bytes needed to encode a ulong value with the varuint62 encoding as an
 539    /// exponent of 2.</summary>
 540    /// <param name="value">The value to encode.</param>
 541    /// <returns>N where 2^N is the number of bytes needed to encode value with Slice's varuint62 encoding.</returns>
 542    private static int GetVarUInt62EncodedSizeExponent(ulong value)
 194990543    {
 194990544        if (value > VarUInt62MaxValue)
 1545        {
 1546            throw new ArgumentOutOfRangeException(nameof(value), $"The value '{value}' is out of the varuint62 range.");
 547        }
 548
 194989549        return (value << 2) switch
 194989550        {
 373974551            ulong b when b <= byte.MaxValue => 0,
 26195552            ulong s when s <= ushort.MaxValue => 1,
 11608553            ulong i when i <= uint.MaxValue => 2,
 18554            _ => 3
 194989555        };
 194989556    }
 557
 558    private void Advance(int count)
 600969559    {
 600969560        _bufferWriter.Advance(count);
 600969561        EncodedByteCount += count;
 600969562    }
 563
 564    /// <summary>Encodes the header for a tagged field. Slice1 only.</summary>
 565    /// <param name="tag">The numeric tag associated with the field.</param>
 566    /// <param name="format">The tag format.</param>
 567    private void EncodeTaggedFieldHeader(int tag, TagFormat format)
 91568    {
 91569        Debug.Assert(Encoding == SliceEncoding.Slice1);
 91570        Debug.Assert(format != TagFormat.OptimizedVSize); // OptimizedVSize cannot be encoded
 571
 91572        int v = (int)format;
 91573        if (tag < 30)
 83574        {
 83575            v |= tag << 3;
 83576            EncodeUInt8((byte)v);
 83577        }
 578        else
 8579        {
 8580            v |= 0x0F0; // tag = 30
 8581            EncodeUInt8((byte)v);
 8582            EncodeSize(tag);
 8583        }
 584
 91585        if (_classContext.Current.InstanceType != InstanceType.None)
 63586        {
 63587            _classContext.Current.SliceFlags |= SliceFlags.HasTaggedFields;
 63588        }
 91589    }
 590}

Methods/Properties

EncodeClass(ZeroC.Slice.SliceClass)
EncodeNullableClass(ZeroC.Slice.SliceClass)
EndSlice(System.Boolean)
StartSlice(System.String,System.Nullable`1<System.Int32>)
EncodeInstance(ZeroC.Slice.SliceClass)
EncodeTypeId(System.String,System.Nullable`1<System.Int32>)
EncodeUnknownSlices(System.Collections.Immutable.ImmutableList`1<ZeroC.Slice.SliceInfo>,System.Boolean)
RegisterTypeId(System.String)
.ctor(ZeroC.Slice.ClassFormat)
get_EncodedByteCount()
get_Encoding()
.cctor()
EncodeInt32(System.Int32,System.Span`1<System.Byte>)
EncodeVarUInt62(System.UInt64,System.Span`1<System.Byte>)
GetVarInt62EncodedSize(System.Int64)
GetVarUInt62EncodedSize(System.UInt64)
.ctor(System.Buffers.IBufferWriter`1<System.Byte>,ZeroC.Slice.SliceEncoding,ZeroC.Slice.ClassFormat)
EncodeBool(System.Boolean)
EncodeFloat32(System.Single)
EncodeFloat64(System.Double)
EncodeInt8(System.SByte)
EncodeInt16(System.Int16)
EncodeInt32(System.Int32)
EncodeInt64(System.Int64)
EncodeSize(System.Int32)
EncodeString(System.String)
EncodeSizeIntoPlaceholder()
EncodeUInt8(System.Byte)
EncodeUInt16(System.UInt16)
EncodeUInt32(System.UInt32)
EncodeUInt64(System.UInt64)
EncodeVarInt32(System.Int32)
EncodeVarInt62(System.Int64)
EncodeVarUInt32(System.UInt32)
EncodeVarUInt62(System.UInt64)
EncodeTagged(System.Int32,T,ZeroC.Slice.EncodeAction`1<T>)
EncodeTagged(System.Int32,System.Int32,T,ZeroC.Slice.EncodeAction`1<T>)
EncodeTagged(System.Int32,ZeroC.Slice.TagFormat,T,ZeroC.Slice.EncodeAction`1<T>)
GetBitSequenceWriter(System.Int32)
GetPlaceholderSpan(System.Int32)
GetSizeLength(System.Int32)
WriteByteSpan(System.ReadOnlySpan`1<System.Byte>)
GetBitSequenceByteCount(System.Int32)
EncodeFixedSizeNumeric(T)
GetPlaceholderMemory(System.Int32)
GetVarInt62EncodedSizeExponent(System.Int64)
GetVarUInt62EncodedSizeExponent(System.UInt64)
Advance(System.Int32)
EncodeTaggedFieldHeader(System.Int32,ZeroC.Slice.TagFormat)