< Summary

Line coverage
96%
Covered lines: 330
Uncovered lines: 12
Coverable lines: 342
Total lines: 674
Line coverage: 96.4%
Branch coverage
94%
Covered branches: 104
Total branches: 110
Branch coverage: 94.5%
Method coverage
93%
Covered methods: 29
Fully covered methods: 26
Total methods: 31
Method coverage: 93.5%
Full method coverage: 83.8%

Metrics

File(s)

/home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc/Ice/Codec/IceEncoder.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 IceRpc.Ice.Codec.Internal.IceEncodingDefinitions;
 8
 9namespace IceRpc.Ice.Codec;
 10
 11/// <summary>Provides methods to encode data with Ice.</summary>
 12public ref partial struct IceEncoder
 13{
 14    /// <summary>Encodes a class instance, or <see langword="null" />.</summary>
 15    /// <param name="v">The class instance to encode, or <see langword="null" />.</param>
 16    public void EncodeClass(IceClass? v)
 54917    {
 54918        if (v is null)
 25819        {
 25820            EncodeSize(0);
 25821        }
 22        else
 29123        {
 29124            if (_classContext.Current.InstanceType != InstanceType.None &&
 29125                _classContext.ClassFormat == ClassFormat.Sliced)
 3126            {
 27                // If encoding an instance within a slice and using the sliced format, encode an index of that
 28                // slice's indirection table.
 3129                if (_classContext.Current.IndirectionMap is not null &&
 3130                    _classContext.Current.IndirectionMap.TryGetValue(v, out int index))
 131                {
 32                    // Found, index is position in indirection table + 1
 133                    Debug.Assert(index > 0);
 134                }
 35                else
 3036                {
 3037                    _classContext.Current.IndirectionTable ??= new List<IceClass>();
 3038                    _classContext.Current.IndirectionMap ??= new Dictionary<IceClass, int>();
 3039                    _classContext.Current.IndirectionTable.Add(v);
 3040                    index = _classContext.Current.IndirectionTable.Count; // Position + 1 (0 is reserved for null)
 3041                    _classContext.Current.IndirectionMap.Add(v, index);
 3042                }
 3143                EncodeSize(index);
 3144            }
 45            else
 26046            {
 26047                EncodeInstance(v); // Encodes the instance or a reference if already encoded.
 25948            }
 29049        }
 54850    }
 51
 52    /// <summary>Marks the end of the encoding of a class or exception slice.</summary>
 53    /// <param name="lastSlice">Whether this is the last slice or not.</param>
 54    [EditorBrowsable(EditorBrowsableState.Never)]
 55    public void EndSlice(bool lastSlice)
 49556    {
 49557        Debug.Assert(_classContext.Current.InstanceType != InstanceType.None);
 58
 49559        if (lastSlice)
 30760        {
 30761            _classContext.Current.SliceFlags |= SliceFlags.IsLastSlice;
 30762        }
 63
 64        // Encodes the tagged end marker if some tagged fields were encoded. Note that tagged fields are encoded before
 65        // the indirection table and are included in the slice size.
 49566        if ((_classContext.Current.SliceFlags & SliceFlags.HasTaggedFields) != 0)
 2567        {
 2568            EncodeByte(TagEndMarker);
 2569        }
 70
 71        // Encodes the slice size if necessary.
 49572        if ((_classContext.Current.SliceFlags & SliceFlags.HasSliceSize) != 0)
 15573        {
 74            // Size includes the size length.
 15575            EncodeInt(
 15576                EncodedByteCount - _classContext.Current.SliceSizeStartPos,
 15577                _classContext.Current.SliceSizePlaceholder.Span);
 15578        }
 79
 49580        if (_classContext.Current.IndirectionTable?.Count > 0)
 3681        {
 3682            Debug.Assert(_classContext.ClassFormat == ClassFormat.Sliced);
 3683            _classContext.Current.SliceFlags |= SliceFlags.HasIndirectionTable;
 84
 3685            EncodeSize(_classContext.Current.IndirectionTable.Count);
 18486            foreach (IceClass v in _classContext.Current.IndirectionTable)
 3887            {
 3888                EncodeInstance(v);
 3889            }
 3690            _classContext.Current.IndirectionTable.Clear();
 3691            _classContext.Current.IndirectionMap?.Clear(); // IndirectionMap is null when encoding unknown slices.
 3692        }
 93
 94        // Update SliceFlags in case they were updated.
 49595        _classContext.Current.SliceFlagsPlaceholder.Span[0] = (byte)_classContext.Current.SliceFlags;
 96
 97        // If this is the last slice in an exception, reset the current context.
 49598        if (lastSlice && _classContext.Current.InstanceType == InstanceType.Exception)
 3199        {
 31100            _classContext.Current = default;
 31101        }
 495102    }
 103
 104    /// <summary>Marks the start of the encoding of a class or exception slice.</summary>
 105    /// <param name="typeId">The type ID of this slice.</param>
 106    /// <param name="compactId">The compact ID of this slice, if any.</param>
 107    [EditorBrowsable(EditorBrowsableState.Never)]
 108    public void StartSlice(string typeId, int? compactId = null)
 495109    {
 110        // This will only be called with an InstanceType of 'None' when we're starting to encode the first slice
 111        // of an exception.
 495112        if (_classContext.Current.InstanceType == InstanceType.None)
 31113        {
 31114            _classContext.ClassFormat = ClassFormat.Sliced; // always encode exceptions in sliced format
 31115            _classContext.Current.InstanceType = InstanceType.Exception;
 31116            _classContext.Current.FirstSlice = true;
 31117        }
 118
 495119        _classContext.Current.SliceFlags = default;
 495120        _classContext.Current.SliceFlagsPlaceholder = GetPlaceholderMemory(1);
 121
 495122        if (_classContext.ClassFormat == ClassFormat.Sliced)
 155123        {
 155124            EncodeTypeId(typeId, compactId);
 125            // Encode the slice size if using the sliced format.
 155126            _classContext.Current.SliceFlags |= SliceFlags.HasSliceSize;
 155127            _classContext.Current.SliceSizeStartPos = EncodedByteCount; // size includes size-length
 155128            _classContext.Current.SliceSizePlaceholder = GetPlaceholderMemory(4);
 155129        }
 340130        else if (_classContext.Current.FirstSlice)
 227131        {
 227132            EncodeTypeId(typeId, compactId);
 227133        }
 134
 495135        if (_classContext.Current.FirstSlice)
 307136        {
 307137            _classContext.Current.FirstSlice = false;
 307138        }
 495139    }
 140
 141    /// <summary>Encodes this class instance inline if not previously encoded, otherwise just encode its instance
 142    /// ID.</summary>
 143    /// <param name="v">The class instance.</param>
 144    private void EncodeInstance(IceClass v)
 298145    {
 146        // If the instance was already encoded, just encode its instance ID.
 298147        if (_classContext.InstanceMap is not null && _classContext.InstanceMap.TryGetValue(v, out int instanceId))
 21148        {
 21149            EncodeSize(instanceId);
 21150        }
 151        else
 277152        {
 277153            _classContext.InstanceMap ??= new Dictionary<IceClass, int>();
 154
 155            // We haven't seen this instance previously, so we create a new instance ID and insert the instance
 156            // and its ID in the encoded map, before encoding the instance inline.
 157            // The instance IDs start at 2 (0 means null and 1 means the instance is encoded immediately after).
 277158            instanceId = _classContext.InstanceMap.Count + 2;
 277159            _classContext.InstanceMap.Add(v, instanceId);
 160
 277161            EncodeSize(1); // Class instance marker.
 162
 163            // Save _current in case we're encoding a nested instance.
 277164            InstanceData previousCurrent = _classContext.Current;
 277165            _classContext.Current = default;
 277166            _classContext.Current.InstanceType = InstanceType.Class;
 277167            _classContext.Current.FirstSlice = true;
 168
 277169            if (v.UnknownSlices.Count > 0)
 13170            {
 13171                if (_classContext.ClassFormat == ClassFormat.Sliced)
 12172                {
 12173                    EncodeUnknownSlices(v.UnknownSlices, fullySliced: v is UnknownIceClass);
 12174                    _classContext.Current.FirstSlice = false;
 12175                }
 1176                else if (v is UnknownIceClass)
 1177                {
 1178                    throw new NotSupportedException(
 1179                        $"Cannot encode a fully-sliced class instance using the {_classContext.ClassFormat} class format
 180                }
 12181            }
 276182            v.Encode(ref this);
 183
 184            // Restore previous _current.
 276185            _classContext.Current = previousCurrent;
 276186        }
 297187    }
 188
 189    /// <summary>Encodes the type ID or compact ID immediately after the slice flags byte, and updates the slice
 190    /// flags byte as needed.</summary>
 191    /// <param name="typeId">The type ID of the current slice.</param>
 192    /// <param name="compactId">The compact ID of the current slice.</param>
 193    private void EncodeTypeId(string typeId, int? compactId)
 382194    {
 382195        Debug.Assert(_classContext.Current.InstanceType != InstanceType.None);
 196
 382197        TypeIdKind typeIdKind = TypeIdKind.None;
 198
 382199        if (_classContext.Current.InstanceType == InstanceType.Class)
 343200        {
 343201            if (compactId is int compactIdValue)
 27202            {
 27203                typeIdKind = TypeIdKind.CompactId;
 27204                EncodeSize(compactIdValue);
 27205            }
 206            else
 316207            {
 316208                int index = RegisterTypeId(typeId);
 316209                if (index < 0)
 76210                {
 76211                    typeIdKind = TypeIdKind.String;
 76212                    EncodeString(typeId);
 76213                }
 214                else
 240215                {
 240216                    typeIdKind = TypeIdKind.Index;
 240217                    EncodeSize(index);
 240218                }
 316219            }
 343220        }
 221        else
 39222        {
 39223            Debug.Assert(compactId is null);
 224            // We always encode a string and don't set a type ID kind in SliceFlags.
 39225            EncodeString(typeId);
 39226        }
 227
 382228        _classContext.Current.SliceFlags |= (SliceFlags)typeIdKind;
 382229    }
 230
 231    /// <summary>Encodes sliced-off slices.</summary>
 232    /// <param name="unknownSlices">The sliced-off slices to encode.</param>
 233    /// <param name="fullySliced">When <see langword="true" />, <paramref name="unknownSlices" /> holds all the data of
 234    /// this instance.</param>
 235    private void EncodeUnknownSlices(ImmutableList<SliceInfo> unknownSlices, bool fullySliced)
 12236    {
 12237        Debug.Assert(_classContext.Current.InstanceType != InstanceType.None);
 238
 239        // The caller only re-encodes preserved slices when using the Sliced format. With another format, it ignores
 240        // the preserved slices, which "slices" the instance into the most-derived type known by the sender.
 12241        Debug.Assert(_classContext.ClassFormat == ClassFormat.Sliced);
 242
 72243        for (int i = 0; i < unknownSlices.Count; ++i)
 24244        {
 24245            SliceInfo sliceInfo = unknownSlices[i];
 246
 247            // If type ID is a compact ID, extract it.
 24248            int? compactId = null;
 24249            if (!sliceInfo.TypeId.StartsWith("::", StringComparison.Ordinal))
 8250            {
 8251                compactId = int.Parse(sliceInfo.TypeId, CultureInfo.InvariantCulture);
 8252            }
 253
 24254            StartSlice(sliceInfo.TypeId, compactId);
 255
 256            // Writes the bytes associated with this slice.
 24257            WriteByteSpan(sliceInfo.Bytes.Span);
 258
 24259            if (sliceInfo.HasTaggedFields)
 4260            {
 4261                _classContext.Current.SliceFlags |= SliceFlags.HasTaggedFields;
 4262            }
 263
 264            // Make sure to also encode the instance indirection table.
 265            // These instances will be encoded (and assigned instance IDs) in EndSlice.
 24266            if (sliceInfo.Instances.Count > 0)
 8267            {
 8268                _classContext.Current.IndirectionTable ??= new List<IceClass>();
 8269                Debug.Assert(_classContext.Current.IndirectionTable.Count == 0);
 8270                _classContext.Current.IndirectionTable.AddRange(sliceInfo.Instances);
 8271            }
 24272            EndSlice(lastSlice: fullySliced && (i == unknownSlices.Count - 1));
 24273        }
 12274    }
 275
 276    /// <summary>Registers or looks up a type ID in the _typeIdMap.</summary>
 277    /// <param name="typeId">The type ID to register or lookup.</param>
 278    /// <returns>The index in _typeIdMap if this type ID was previously registered; otherwise, -1.</returns>
 279    private int RegisterTypeId(string typeId)
 316280    {
 316281        _classContext.TypeIdMap ??= new Dictionary<string, int>();
 282
 316283        if (_classContext.TypeIdMap.TryGetValue(typeId, out int index))
 240284        {
 240285            return index;
 286        }
 287        else
 76288        {
 76289            index = _classContext.TypeIdMap.Count + 1;
 76290            _classContext.TypeIdMap.Add(typeId, index);
 76291            return -1;
 292        }
 316293    }
 294
 295    private struct ClassContext
 296    {
 297        // The current class/exception format, can be either Compact or Iced.
 298        internal ClassFormat ClassFormat;
 299
 300        // Data for the class or exception instance that is currently getting encoded.
 301        internal InstanceData Current;
 302
 303        // Map of class instance to instance ID, where the instance IDs start at 2.
 304        //  - Instance ID = 0 means null.
 305        //  - Instance ID = 1 means the instance is encoded inline afterwards.
 306        //  - Instance ID > 1 means a reference to a previously encoded instance, found in this map.
 307        internal Dictionary<IceClass, int>? InstanceMap;
 308
 309        // Map of type ID string to type ID index.
 310        // We assign a type ID index (starting with 1) to each type ID we write, in order.
 311        internal Dictionary<string, int>? TypeIdMap;
 312
 313        internal ClassContext(ClassFormat classFormat)
 6532314            : this() => ClassFormat = classFormat;
 315    }
 316
 317    private struct InstanceData
 318    {
 319        // The following fields are used and reused for all the slices of a class or exception instance.
 320
 321        internal InstanceType InstanceType;
 322
 323        // The following fields are used for the current slice:
 324
 325        internal bool FirstSlice;
 326
 327        // The indirection map and indirection table are only used for the sliced format.
 328        internal Dictionary<IceClass, int>? IndirectionMap;
 329        internal List<IceClass>? IndirectionTable;
 330
 331        internal SliceFlags SliceFlags;
 332
 333        // The Ice flags byte.
 334        internal Memory<byte> SliceFlagsPlaceholder;
 335
 336        // The place holder for the Slice size. Used only for the sliced format.
 337        internal Memory<byte> SliceSizePlaceholder;
 338
 339        // The starting position for computing the size of the slice. It's just before the SliceSizePlaceholder as
 340        // the size includes the size length.
 341        internal int SliceSizeStartPos;
 342    }
 343
 344    private enum InstanceType : byte
 345    {
 346        None = 0,
 347        Class,
 348        Exception
 349    }
 350}

/home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc/Ice/Codec/IceEncoder.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 IceRpc.Ice.Codec.Internal.IceEncodingDefinitions;
 9
 10namespace IceRpc.Ice.Codec;
 11
 12/// <summary>Provides methods to encode data with Ice.</summary>
 13public ref partial struct IceEncoder
 14{
 15    /// <summary>Gets the number of bytes encoded by this encoder into the underlying buffer writer.</summary>
 9909416    public int EncodedByteCount { get; private set; }
 17
 818    private static readonly UTF8Encoding _utf8 =
 819        new(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); // no BOM
 20
 21    private readonly IBufferWriter<byte> _bufferWriter;
 22
 23    private ClassContext _classContext;
 24
 25    private Encoder? _utf8Encoder; // initialized lazily
 26
 27    /// <summary>Encodes an int as an Ice int into a span of 4 bytes.</summary>
 28    /// <param name="value">The value to encode.</param>
 29    /// <param name="into">The destination byte buffer, which must be 4 bytes long.</param>
 30    public static void EncodeInt(int value, Span<byte> into)
 299231    {
 299232        Debug.Assert(into.Length == 4);
 299233        MemoryMarshal.Write(into, in value);
 299234    }
 35
 36    /// <summary>Computes the minimum number of bytes needed to encode a variable-length size.</summary>
 37    /// <param name="size">The size.</param>
 38    /// <returns>The minimum number of bytes.</returns>
 394339    public static int GetSizeLength(int size) => size < 255 ? 1 : 5;
 40
 41    /// <summary>Constructs an Ice encoder.</summary>
 42    /// <param name="bufferWriter">A buffer writer that writes to byte buffers. See important remarks below.</param>
 43    /// <param name="classFormat">The class format.</param>
 44    /// <remarks>Warning: the Ice encoding requires rewriting buffers, and many buffer writers do not support this
 45    /// behavior. It is safe to use a pipe writer or a buffer writer that writes to a single fixed-size buffer (without
 46    /// reallocation).</remarks>
 47    public IceEncoder(IBufferWriter<byte> bufferWriter, ClassFormat classFormat = default)
 326648        : this()
 326649    {
 326650        _bufferWriter = bufferWriter;
 326651        _classContext = new ClassContext(classFormat);
 326652    }
 53
 54    // Encode methods for basic types
 55
 56    /// <summary>Encodes a bool into an Ice bool.</summary>
 57    /// <param name="v">The boolean to encode.</param>
 8858    public void EncodeBool(bool v) => EncodeByte(v ? (byte)1 : (byte)0);
 59
 60    /// <summary>Encodes a byte into an Ice byte.</summary>
 61    /// <param name="v">The byte to encode.</param>
 62    public void EncodeByte(byte v)
 2418563    {
 2418564        Span<byte> span = _bufferWriter.GetSpan();
 2418565        span[0] = v;
 2418566        Advance(1);
 2418567    }
 68
 69    /// <summary>Encodes a double into an Ice double.</summary>
 70    /// <param name="v">The double to encode.</param>
 071    public void EncodeDouble(double v) => EncodeFixedSizeNumeric(v);
 72
 73    /// <summary>Encodes a float into an Ice float.</summary>
 74    /// <param name="v">The float to encode.</param>
 075    public void EncodeFloat(float v) => EncodeFixedSizeNumeric(v);
 76
 77    /// <summary>Encodes an int into an Ice int.</summary>
 78    /// <param name="v">The int to encode.</param>
 894279    public void EncodeInt(int v) => EncodeFixedSizeNumeric(v);
 80
 81    /// <summary>Encodes a long into an Ice long.</summary>
 82    /// <param name="v">The long to encode.</param>
 33383    public void EncodeLong(long v) => EncodeFixedSizeNumeric(v);
 84
 85    /// <summary>Encodes a short into an Ice short.</summary>
 86    /// <param name="v">The short to encode.</param>
 4187    public void EncodeShort(short v) => EncodeFixedSizeNumeric(v);
 88
 89    /// <summary>Encodes a size on variable number of bytes.</summary>
 90    /// <param name="value">The size to encode.</param>
 91    public void EncodeSize(int value)
 1401192    {
 1401193        if (value < 0)
 194        {
 195            throw new ArgumentException(
 196                $"The {nameof(value)} argument must be greater than or equal to 0.",
 197                nameof(value));
 98        }
 99
 14010100        if (value < 255)
 13989101        {
 13989102            EncodeByte((byte)value);
 13989103        }
 104        else
 21105        {
 21106            EncodeByte(255);
 21107            EncodeInt(value);
 21108        }
 14010109    }
 110
 111    /// <summary>Encodes a string into an Ice string.</summary>
 112    /// <param name="v">The string to encode.</param>
 113    public void EncodeString(string v)
 8103114    {
 8103115        if (v.Length == 0)
 4170116        {
 4170117            EncodeSize(0);
 4170118        }
 119        else
 3933120        {
 3933121            int maxSize = _utf8.GetMaxByteCount(v.Length);
 3933122            int sizeLength = GetSizeLength(maxSize);
 3933123            Span<byte> sizePlaceholder = GetPlaceholderSpan(sizeLength);
 124
 3933125            Span<byte> currentSpan = _bufferWriter.GetSpan();
 3933126            if (currentSpan.Length >= maxSize)
 3923127            {
 128                // Encode directly into currentSpan
 3923129                int size = _utf8.GetBytes(v, currentSpan);
 3923130                EncodeSizeIntoPlaceholder(size, sizePlaceholder);
 3923131                Advance(size);
 3923132            }
 133            else
 10134            {
 135                // Encode piecemeal using _utf8Encoder
 10136                if (_utf8Encoder is null)
 10137                {
 10138                    _utf8Encoder = _utf8.GetEncoder();
 10139                }
 140                else
 0141                {
 0142                    _utf8Encoder.Reset();
 0143                }
 144
 10145                ReadOnlySpan<char> chars = v.AsSpan();
 10146                _utf8Encoder.Convert(chars, _bufferWriter, flush: true, out long bytesUsed, out bool completed);
 147
 10148                Debug.Assert(completed); // completed is always true when flush is true
 10149                int size = checked((int)bytesUsed);
 10150                EncodedByteCount += size;
 10151                EncodeSizeIntoPlaceholder(size, sizePlaceholder);
 10152            }
 3933153        }
 154
 155        static void EncodeSizeIntoPlaceholder(int size, Span<byte> into)
 3933156        {
 3933157            if (into.Length == 1)
 3916158            {
 3916159                Debug.Assert(size < 255);
 3916160                into[0] = (byte)size;
 3916161            }
 162            else
 17163            {
 17164                Debug.Assert(into.Length == 5);
 17165                into[0] = 255;
 17166                EncodeInt(size, into[1..]);
 17167            }
 3933168        }
 8103169    }
 170
 171    // Other methods
 172
 173    /// <summary>Encodes a non-null tagged value. The number of bytes needed to encode the value is known before
 174    /// encoding the value. This method always uses the VSize tag format.</summary>
 175    /// <typeparam name="T">The type of the value being encoded.</typeparam>
 176    /// <param name="tag">The tag.</param>
 177    /// <param name="size">The number of bytes needed to encode the value.</param>
 178    /// <param name="v">The value to encode.</param>
 179    /// <param name="encodeAction">The delegate that encodes the value after the tag header.</param>
 180    public void EncodeTagged<T>(int tag, int size, T v, EncodeAction<T> encodeAction) where T : notnull
 18181    {
 18182        if (size <= 0)
 0183        {
 0184            throw new ArgumentException("Invalid size value, size must be greater than zero.", nameof(size));
 185        }
 186
 18187        EncodeTaggedFieldHeader(tag, TagFormat.VSize);
 188
 18189        EncodeSize(size);
 18190        int startPos = EncodedByteCount;
 18191        encodeAction(ref this, v);
 192
 18193        int actualSize = EncodedByteCount - startPos;
 18194        if (actualSize != size)
 0195        {
 0196            throw new ArgumentException(
 0197                $"The value of size ({size}) does not match encoded size ({actualSize}).",
 0198                nameof(size));
 199        }
 18200    }
 201
 202    /// <summary>Encodes a tagged value. The number of bytes needed to encode the value is not known before
 203    /// encoding this value. T can be a proxy such as IceObjectProxy? and therefore nullable.</summary>
 204    /// <typeparam name="T">The type of the value being encoded.</typeparam>
 205    /// <param name="tag">The tag.</param>
 206    /// <param name="tagFormat">The tag format. Must not be <see cref="TagFormat.VSize" /> or
 207    /// <see cref="TagFormat.Class" />.</param>
 208    /// <param name="v">The value to encode.</param>
 209    /// <param name="encodeAction">The delegate that encodes the value after the tag header.</param>
 210    /// <exception cref="ArgumentException">Thrown when <paramref name="tagFormat" /> is not a tag format supported by
 211    /// this method.</exception>
 212    public void EncodeTagged<T>(
 213        int tag,
 214        TagFormat tagFormat,
 215        T v,
 216        EncodeAction<T> encodeAction)
 88217    {
 88218        switch (tagFormat)
 219        {
 220            case TagFormat.F1:
 221            case TagFormat.F2:
 222            case TagFormat.F4:
 223            case TagFormat.F8:
 224            case TagFormat.Size:
 56225                EncodeTaggedFieldHeader(tag, tagFormat);
 56226                encodeAction(ref this, v);
 56227                break;
 228            case TagFormat.FSize:
 9229                EncodeTaggedFieldHeader(tag, tagFormat);
 9230                Span<byte> placeholder = GetPlaceholderSpan(4);
 9231                int startPos = EncodedByteCount;
 9232                encodeAction(ref this, v);
 233                // We don't include the size-length in the size we encode.
 9234                EncodeInt(EncodedByteCount - startPos, placeholder);
 9235                break;
 236
 237            case TagFormat.OptimizedVSize:
 238                // Used to encode string, and sequences of non optional elements with 1 byte min wire size,
 239                // in this case OptimizedVSize is always used to optimize out the size.
 23240                EncodeTaggedFieldHeader(tag, TagFormat.VSize);
 23241                encodeAction(ref this, v);
 23242                break;
 243
 244            default:
 0245                throw new ArgumentException($"Invalid tag format value: '{tagFormat}'.", nameof(tagFormat));
 246        }
 88247    }
 248
 249    /// <summary>Gets a placeholder to be filled-in later.</summary>
 250    /// <param name="size">The size of the placeholder, typically a small number like 4.</param>
 251    /// <returns>A buffer of length <paramref name="size" />.</returns>
 252    /// <remarks>We make the assumption the underlying buffer writer allows rewriting memory it provided even after
 253    /// successive calls to GetMemory/GetSpan and Advance.</remarks>
 254    public Span<byte> GetPlaceholderSpan(int size)
 6753255    {
 6753256        Debug.Assert(size > 0);
 6753257        Span<byte> placeholder = _bufferWriter.GetSpan(size)[0..size];
 6753258        Advance(size);
 6753259        return placeholder;
 6753260    }
 261
 262    /// <summary>Copies a span of bytes to the underlying buffer writer.</summary>
 263    /// <param name="span">The span to copy.</param>
 264    public void WriteByteSpan(ReadOnlySpan<byte> span)
 2837265    {
 2837266        _bufferWriter.Write(span);
 2837267        EncodedByteCount += span.Length;
 2837268    }
 269
 270    /// <summary>Encodes a fixed-size numeric value.</summary>
 271    /// <param name="v">The numeric value to encode.</param>
 272    internal void EncodeFixedSizeNumeric<T>(T v) where T : struct
 9572273    {
 9572274        int elementSize = Unsafe.SizeOf<T>();
 9572275        Span<byte> data = _bufferWriter.GetSpan(elementSize)[0..elementSize];
 9572276        MemoryMarshal.Write(data, in v);
 9572277        Advance(elementSize);
 9572278    }
 279
 280    /// <summary>Gets a placeholder to be filled-in later.</summary>
 281    /// <param name="size">The size of the placeholder, typically a small number like 4.</param>
 282    /// <returns>A buffer of length <paramref name="size" />.</returns>
 283    /// <remarks>We make the assumption the underlying buffer writer allows rewriting memory it provided even after
 284    /// successive calls to GetMemory/GetSpan and Advance.</remarks>
 285    internal Memory<byte> GetPlaceholderMemory(int size)
 650286    {
 650287        Debug.Assert(size > 0);
 650288        Memory<byte> placeholder = _bufferWriter.GetMemory(size)[0..size];
 650289        Advance(size);
 650290        return placeholder;
 650291    }
 292
 293    private void Advance(int count)
 45083294    {
 45083295        _bufferWriter.Advance(count);
 45083296        EncodedByteCount += count;
 45083297    }
 298
 299    /// <summary>Encodes the header for a tagged field.</summary>
 300    /// <param name="tag">The numeric tag associated with the field.</param>
 301    /// <param name="format">The tag format.</param>
 302    private void EncodeTaggedFieldHeader(int tag, TagFormat format)
 106303    {
 106304        Debug.Assert(format != TagFormat.OptimizedVSize); // OptimizedVSize cannot be encoded
 305
 106306        int v = (int)format;
 106307        if (tag < 30)
 98308        {
 98309            v |= tag << 3;
 98310            EncodeByte((byte)v);
 98311        }
 312        else
 8313        {
 8314            v |= 0x0F0; // tag = 30
 8315            EncodeByte((byte)v);
 8316            EncodeSize(tag);
 8317        }
 318
 106319        if (_classContext.Current.InstanceType != InstanceType.None)
 68320        {
 68321            _classContext.Current.SliceFlags |= SliceFlags.HasTaggedFields;
 68322        }
 106323    }
 324}