< Summary

Line coverage
94%
Covered lines: 325
Uncovered lines: 18
Coverable lines: 343
Total lines: 674
Line coverage: 94.7%
Branch coverage
93%
Covered branches: 103
Total branches: 110
Branch coverage: 93.6%
Method coverage
93%
Covered methods: 29
Fully covered methods: 25
Total methods: 31
Method coverage: 93.5%
Full method coverage: 80.6%

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)
 54417    {
 54418        if (v is null)
 25619        {
 25620            EncodeSize(0);
 25621        }
 22        else
 28823        {
 28824            if (_classContext.Current.InstanceType != InstanceType.None &&
 28825                _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
 25746            {
 25747                EncodeInstance(v); // Encodes the instance or a reference if already encoded.
 25748            }
 28849        }
 54450    }
 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)
 49156    {
 49157        Debug.Assert(_classContext.Current.InstanceType != InstanceType.None);
 58
 49159        if (lastSlice)
 30560        {
 30561            _classContext.Current.SliceFlags |= SliceFlags.IsLastSlice;
 30562        }
 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.
 49166        if ((_classContext.Current.SliceFlags & SliceFlags.HasTaggedFields) != 0)
 2567        {
 2568            EncodeByte(TagEndMarker);
 2569        }
 70
 71        // Encodes the slice size if necessary.
 49172        if ((_classContext.Current.SliceFlags & SliceFlags.HasSliceSize) != 0)
 15173        {
 74            // Size includes the size length.
 15175            EncodeInt(
 15176                EncodedByteCount - _classContext.Current.SliceSizeStartPos,
 15177                _classContext.Current.SliceSizePlaceholder.Span);
 15178        }
 79
 49180        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.
 49195        _classContext.Current.SliceFlagsPlaceholder.Span[0] = (byte)_classContext.Current.SliceFlags;
 96
 97        // If this is the last slice in an exception, reset the current context.
 49198        if (lastSlice && _classContext.Current.InstanceType == InstanceType.Exception)
 3199        {
 31100            _classContext.Current = default;
 31101        }
 491102    }
 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)
 491109    {
 110        // This will only be called with an InstanceType of 'None' when we're starting to encode the first slice
 111        // of an exception.
 491112        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
 491119        _classContext.Current.SliceFlags = default;
 491120        _classContext.Current.SliceFlagsPlaceholder = GetPlaceholderMemory(1);
 121
 491122        if (_classContext.ClassFormat == ClassFormat.Sliced)
 151123        {
 151124            EncodeTypeId(typeId, compactId);
 125            // Encode the slice size if using the sliced format.
 151126            _classContext.Current.SliceFlags |= SliceFlags.HasSliceSize;
 151127            _classContext.Current.SliceSizeStartPos = EncodedByteCount; // size includes size-length
 151128            _classContext.Current.SliceSizePlaceholder = GetPlaceholderMemory(4);
 151129        }
 340130        else if (_classContext.Current.FirstSlice)
 227131        {
 227132            EncodeTypeId(typeId, compactId);
 227133        }
 134
 491135        if (_classContext.Current.FirstSlice)
 305136        {
 305137            _classContext.Current.FirstSlice = false;
 305138        }
 491139    }
 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)
 295145    {
 146        // If the instance was already encoded, just encode its instance ID.
 295147        if (_classContext.InstanceMap is not null && _classContext.InstanceMap.TryGetValue(v, out int instanceId))
 21148        {
 21149            EncodeSize(instanceId);
 21150        }
 151        else
 274152        {
 274153            _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).
 274158            instanceId = _classContext.InstanceMap.Count + 2;
 274159            _classContext.InstanceMap.Add(v, instanceId);
 160
 274161            EncodeSize(1); // Class instance marker.
 162
 163            // Save _current in case we're encoding a nested instance.
 274164            InstanceData previousCurrent = _classContext.Current;
 274165            _classContext.Current = default;
 274166            _classContext.Current.InstanceType = InstanceType.Class;
 274167            _classContext.Current.FirstSlice = true;
 168
 274169            if (v.UnknownSlices.Count > 0 && _classContext.ClassFormat == ClassFormat.Sliced)
 12170            {
 12171                EncodeUnknownSlices(v.UnknownSlices, fullySliced: v is UnknownIceClass);
 12172                _classContext.Current.FirstSlice = false;
 12173            }
 274174            v.Encode(ref this);
 175
 176            // Restore previous _current.
 274177            _classContext.Current = previousCurrent;
 274178        }
 295179    }
 180
 181    /// <summary>Encodes the type ID or compact ID immediately after the slice flags byte, and updates the slice
 182    /// flags byte as needed.</summary>
 183    /// <param name="typeId">The type ID of the current slice.</param>
 184    /// <param name="compactId">The compact ID of the current slice.</param>
 185    private void EncodeTypeId(string typeId, int? compactId)
 378186    {
 378187        Debug.Assert(_classContext.Current.InstanceType != InstanceType.None);
 188
 378189        TypeIdKind typeIdKind = TypeIdKind.None;
 190
 378191        if (_classContext.Current.InstanceType == InstanceType.Class)
 339192        {
 339193            if (compactId is int compactIdValue)
 27194            {
 27195                typeIdKind = TypeIdKind.CompactId;
 27196                EncodeSize(compactIdValue);
 27197            }
 198            else
 312199            {
 312200                int index = RegisterTypeId(typeId);
 312201                if (index < 0)
 72202                {
 72203                    typeIdKind = TypeIdKind.String;
 72204                    EncodeString(typeId);
 72205                }
 206                else
 240207                {
 240208                    typeIdKind = TypeIdKind.Index;
 240209                    EncodeSize(index);
 240210                }
 312211            }
 339212        }
 213        else
 39214        {
 39215            Debug.Assert(compactId is null);
 216            // We always encode a string and don't set a type ID kind in SliceFlags.
 39217            EncodeString(typeId);
 39218        }
 219
 378220        _classContext.Current.SliceFlags |= (SliceFlags)typeIdKind;
 378221    }
 222
 223    /// <summary>Encodes sliced-off slices.</summary>
 224    /// <param name="unknownSlices">The sliced-off slices to encode.</param>
 225    /// <param name="fullySliced">When <see langword="true" />, slicedData holds all the data of this instance.</param>
 226    private void EncodeUnknownSlices(ImmutableList<SliceInfo> unknownSlices, bool fullySliced)
 12227    {
 12228        Debug.Assert(_classContext.Current.InstanceType != InstanceType.None);
 229
 230        // We only re-encode preserved slices if we are using the sliced format. Otherwise, we ignore the preserved
 231        // slices, which essentially "slices" the instance into the most-derived type known by the sender.
 12232        if (_classContext.ClassFormat != ClassFormat.Sliced)
 0233        {
 0234            throw new NotSupportedException(
 0235                $"Cannot encode sliced data into payload using {_classContext.ClassFormat} format.");
 236        }
 237
 72238        for (int i = 0; i < unknownSlices.Count; ++i)
 24239        {
 24240            SliceInfo sliceInfo = unknownSlices[i];
 241
 242            // If type ID is a compact ID, extract it.
 24243            int? compactId = null;
 24244            if (!sliceInfo.TypeId.StartsWith("::", StringComparison.Ordinal))
 8245            {
 246                try
 8247                {
 8248                    compactId = int.Parse(sliceInfo.TypeId, CultureInfo.InvariantCulture);
 8249                }
 0250                catch (FormatException exception)
 0251                {
 0252                    throw new InvalidDataException($"Received invalid type ID {sliceInfo.TypeId}.", exception);
 253                }
 8254            }
 255
 24256            StartSlice(sliceInfo.TypeId, compactId);
 257
 258            // Writes the bytes associated with this slice.
 24259            WriteByteSpan(sliceInfo.Bytes.Span);
 260
 24261            if (sliceInfo.HasTaggedFields)
 4262            {
 4263                _classContext.Current.SliceFlags |= SliceFlags.HasTaggedFields;
 4264            }
 265
 266            // Make sure to also encode the instance indirection table.
 267            // These instances will be encoded (and assigned instance IDs) in EndSlice.
 24268            if (sliceInfo.Instances.Count > 0)
 8269            {
 8270                _classContext.Current.IndirectionTable ??= new List<IceClass>();
 8271                Debug.Assert(_classContext.Current.IndirectionTable.Count == 0);
 8272                _classContext.Current.IndirectionTable.AddRange(sliceInfo.Instances);
 8273            }
 24274            EndSlice(lastSlice: fullySliced && (i == unknownSlices.Count - 1));
 24275        }
 12276    }
 277
 278    /// <summary>Registers or looks up a type ID in the _typeIdMap.</summary>
 279    /// <param name="typeId">The type ID to register or lookup.</param>
 280    /// <returns>The index in _typeIdMap if this type ID was previously registered; otherwise, -1.</returns>
 281    private int RegisterTypeId(string typeId)
 312282    {
 312283        _classContext.TypeIdMap ??= new Dictionary<string, int>();
 284
 312285        if (_classContext.TypeIdMap.TryGetValue(typeId, out int index))
 240286        {
 240287            return index;
 288        }
 289        else
 72290        {
 72291            index = _classContext.TypeIdMap.Count + 1;
 72292            _classContext.TypeIdMap.Add(typeId, index);
 72293            return -1;
 294        }
 312295    }
 296
 297    private struct ClassContext
 298    {
 299        // The current class/exception format, can be either Compact or Iced.
 300        internal ClassFormat ClassFormat;
 301
 302        // Data for the class or exception instance that is currently getting encoded.
 303        internal InstanceData Current;
 304
 305        // Map of class instance to instance ID, where the instance IDs start at 2.
 306        //  - Instance ID = 0 means null.
 307        //  - Instance ID = 1 means the instance is encoded inline afterwards.
 308        //  - Instance ID > 1 means a reference to a previously encoded instance, found in this map.
 309        internal Dictionary<IceClass, int>? InstanceMap;
 310
 311        // Map of type ID string to type ID index.
 312        // We assign a type ID index (starting with 1) to each type ID we write, in order.
 313        internal Dictionary<string, int>? TypeIdMap;
 314
 315        internal ClassContext(ClassFormat classFormat)
 6460316            : this() => ClassFormat = classFormat;
 317    }
 318
 319    private struct InstanceData
 320    {
 321        // The following fields are used and reused for all the slices of a class or exception instance.
 322
 323        internal InstanceType InstanceType;
 324
 325        // The following fields are used for the current slice:
 326
 327        internal bool FirstSlice;
 328
 329        // The indirection map and indirection table are only used for the sliced format.
 330        internal Dictionary<IceClass, int>? IndirectionMap;
 331        internal List<IceClass>? IndirectionTable;
 332
 333        internal SliceFlags SliceFlags;
 334
 335        // The Ice flags byte.
 336        internal Memory<byte> SliceFlagsPlaceholder;
 337
 338        // The place holder for the Slice size. Used only for the sliced format.
 339        internal Memory<byte> SliceSizePlaceholder;
 340
 341        // The starting position for computing the size of the slice. It's just before the SliceSizePlaceholder as
 342        // the size includes the size length.
 343        internal int SliceSizeStartPos;
 344    }
 345
 346    private enum InstanceType : byte
 347    {
 348        None = 0,
 349        Class,
 350        Exception
 351    }
 352}

/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>
 9826716    public int EncodedByteCount { get; private set; }
 17
 718    private static readonly UTF8Encoding _utf8 =
 719        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)
 297131    {
 297132        Debug.Assert(into.Length == 4);
 297133        MemoryMarshal.Write(into, in value);
 297134    }
 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>
 391139    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)
 323048        : this()
 323049    {
 323050        _bufferWriter = bufferWriter;
 323051        _classContext = new ClassContext(classFormat);
 323052    }
 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>
 7858    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)
 2391963    {
 2391964        Span<byte> span = _bufferWriter.GetSpan();
 2391965        span[0] = v;
 2391966        Advance(1);
 2391967    }
 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>
 891079    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>
 3787    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)
 1391192    {
 1391193        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
 13910100        if (value < 255)
 13889101        {
 13889102            EncodeByte((byte)value);
 13889103        }
 104        else
 21105        {
 21106            EncodeByte(255);
 21107            EncodeInt(value);
 21108        }
 13910109    }
 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)
 8046114    {
 8046115        if (v.Length == 0)
 4145116        {
 4145117            EncodeSize(0);
 4145118        }
 119        else
 3901120        {
 3901121            int maxSize = _utf8.GetMaxByteCount(v.Length);
 3901122            int sizeLength = GetSizeLength(maxSize);
 3901123            Span<byte> sizePlaceholder = GetPlaceholderSpan(sizeLength);
 124
 3901125            Span<byte> currentSpan = _bufferWriter.GetSpan();
 3901126            if (currentSpan.Length >= maxSize)
 3891127            {
 128                // Encode directly into currentSpan
 3891129                int size = _utf8.GetBytes(v, currentSpan);
 3891130                EncodeSizeIntoPlaceholder(size, sizePlaceholder);
 3891131                Advance(size);
 3891132            }
 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            }
 3901153        }
 154
 155        static void EncodeSizeIntoPlaceholder(int size, Span<byte> into)
 3901156        {
 3901157            if (into.Length == 1)
 3886158            {
 3886159                Debug.Assert(size < 255);
 3886160                into[0] = (byte)size;
 3886161            }
 162            else
 15163            {
 15164                Debug.Assert(into.Length == 5);
 15165                into[0] = 255;
 15166                EncodeInt(size, into[1..]);
 15167            }
 3901168        }
 8046169    }
 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 use 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. Must be either FSize or OptimizedVSize.</param>
 206    /// <param name="tagFormat">The tag format.</param>
 207    /// <param name="v">The value to encode.</param>
 208    /// <param name="encodeAction">The delegate that encodes the value after the tag header.</param>
 209    /// <exception cref="ArgumentException">Thrown if <paramref name="tagFormat" /> is VSize.</exception>
 210    public void EncodeTagged<T>(
 211        int tag,
 212        TagFormat tagFormat,
 213        T v,
 214        EncodeAction<T> encodeAction)
 84215    {
 84216        switch (tagFormat)
 217        {
 218            case TagFormat.F1:
 219            case TagFormat.F2:
 220            case TagFormat.F4:
 221            case TagFormat.F8:
 222            case TagFormat.Size:
 53223                EncodeTaggedFieldHeader(tag, tagFormat);
 53224                encodeAction(ref this, v);
 53225                break;
 226            case TagFormat.FSize:
 9227                EncodeTaggedFieldHeader(tag, tagFormat);
 9228                Span<byte> placeholder = GetPlaceholderSpan(4);
 9229                int startPos = EncodedByteCount;
 9230                encodeAction(ref this, v);
 231                // We don't include the size-length in the size we encode.
 9232                EncodeInt(EncodedByteCount - startPos, placeholder);
 9233                break;
 234
 235            case TagFormat.OptimizedVSize:
 236                // Used to encode string, and sequences of non optional elements with 1 byte min wire size,
 237                // in this case OptimizedVSize is always used to optimize out the size.
 22238                EncodeTaggedFieldHeader(tag, TagFormat.VSize);
 22239                encodeAction(ref this, v);
 22240                break;
 241
 242            default:
 0243                throw new ArgumentException($"Invalid tag format value: '{tagFormat}'.", nameof(tagFormat));
 244        }
 84245    }
 246
 247    /// <summary>Gets a placeholder to be filled-in later.</summary>
 248    /// <param name="size">The size of the placeholder, typically a small number like 4.</param>
 249    /// <returns>A buffer of length <paramref name="size" />.</returns>
 250    /// <remarks>We make the assumption the underlying buffer writer allows rewriting memory it provided even after
 251    /// successive calls to GetMemory/GetSpan and Advance.</remarks>
 252    public Span<byte> GetPlaceholderSpan(int size)
 6706253    {
 6706254        Debug.Assert(size > 0);
 6706255        Span<byte> placeholder = _bufferWriter.GetSpan(size)[0..size];
 6706256        Advance(size);
 6706257        return placeholder;
 6706258    }
 259
 260    /// <summary>Copies a span of bytes to the underlying buffer writer.</summary>
 261    /// <param name="span">The span to copy.</param>
 262    public void WriteByteSpan(ReadOnlySpan<byte> span)
 2826263    {
 2826264        _bufferWriter.Write(span);
 2826265        EncodedByteCount += span.Length;
 2826266    }
 267
 268    /// <summary>Encodes a fixed-size numeric value.</summary>
 269    /// <param name="v">The numeric value to encode.</param>
 270    internal void EncodeFixedSizeNumeric<T>(T v) where T : struct
 9536271    {
 9536272        int elementSize = Unsafe.SizeOf<T>();
 9536273        Span<byte> data = _bufferWriter.GetSpan(elementSize)[0..elementSize];
 9536274        MemoryMarshal.Write(data, in v);
 9536275        Advance(elementSize);
 9536276    }
 277
 278    /// <summary>Gets a placeholder to be filled-in later.</summary>
 279    /// <param name="size">The size of the placeholder, typically a small number like 4.</param>
 280    /// <returns>A buffer of length <paramref name="size" />.</returns>
 281    /// <remarks>We make the assumption the underlying buffer writer allows rewriting memory it provided even after
 282    /// successive calls to GetMemory/GetSpan and Advance.</remarks>
 283    internal Memory<byte> GetPlaceholderMemory(int size)
 642284    {
 642285        Debug.Assert(size > 0);
 642286        Memory<byte> placeholder = _bufferWriter.GetMemory(size)[0..size];
 642287        Advance(size);
 642288        return placeholder;
 642289    }
 290
 291    private void Advance(int count)
 44694292    {
 44694293        _bufferWriter.Advance(count);
 44694294        EncodedByteCount += count;
 44694295    }
 296
 297    /// <summary>Encodes the header for a tagged field.</summary>
 298    /// <param name="tag">The numeric tag associated with the field.</param>
 299    /// <param name="format">The tag format.</param>
 300    private void EncodeTaggedFieldHeader(int tag, TagFormat format)
 102301    {
 102302        Debug.Assert(format != TagFormat.OptimizedVSize); // OptimizedVSize cannot be encoded
 303
 102304        int v = (int)format;
 102305        if (tag < 30)
 94306        {
 94307            v |= tag << 3;
 94308            EncodeByte((byte)v);
 94309        }
 310        else
 8311        {
 8312            v |= 0x0F0; // tag = 30
 8313            EncodeByte((byte)v);
 8314            EncodeSize(tag);
 8315        }
 316
 102317        if (_classContext.Current.InstanceType != InstanceType.None)
 68318        {
 68319            _classContext.Current.SliceFlags |= SliceFlags.HasTaggedFields;
 68320        }
 102321    }
 322}