< Summary

Information
Class: ZeroC.Slice.Codec.SliceEncoder
Assembly: ZeroC.Slice.Codec
File(s): /home/runner/work/icerpc-csharp/icerpc-csharp/src/ZeroC.Slice.Codec/SliceEncoder.cs
Tag: 1321_24790053727
Line coverage
88%
Covered lines: 202
Uncovered lines: 25
Coverable lines: 227
Total lines: 446
Line coverage: 88.9%
Branch coverage
95%
Covered branches: 60
Total branches: 63
Branch coverage: 95.2%
Method coverage
80%
Covered methods: 29
Fully covered methods: 27
Total methods: 36
Method coverage: 80.5%
Full method coverage: 75%

Metrics

File(s)

/home/runner/work/icerpc-csharp/icerpc-csharp/src/ZeroC.Slice.Codec/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;
 8
 9namespace ZeroC.Slice.Codec;
 10
 11/// <summary>Provides methods to encode data with Slice.</summary>
 12public ref partial struct SliceEncoder
 13{
 14    /// <summary>Gets the number of bytes encoded by this encoder into the underlying buffer writer.</summary>
 112114315    public int EncodedByteCount { get; private set; }
 16
 17    internal const long VarInt62MinValue = -2_305_843_009_213_693_952; // -2^61
 18    internal const long VarInt62MaxValue = 2_305_843_009_213_693_951; // 2^61 - 1
 19    internal const ulong VarUInt62MinValue = 0;
 20    internal const ulong VarUInt62MaxValue = 4_611_686_018_427_387_903; // 2^62 - 1
 21
 922    private static readonly UTF8Encoding _utf8 =
 923        new(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); // no BOM
 24
 25    private readonly IBufferWriter<byte> _bufferWriter;
 26
 27    private Encoder? _utf8Encoder; // initialized lazily
 28
 29    /// <summary>Encodes an int as a Slice int32 into a span of 4 bytes.</summary>
 30    /// <param name="value">The value to encode.</param>
 31    /// <param name="into">The destination byte buffer, which must be 4 bytes long.</param>
 32    public static void EncodeInt32(int value, Span<byte> into)
 033    {
 034        Debug.Assert(into.Length == 4);
 035        MemoryMarshal.Write(into, in value);
 036    }
 37
 38    /// <summary>Encodes a ulong as a Slice varuint62 into a span of bytes using a fixed number of bytes.</summary>
 39    /// <param name="value">The value to encode.</param>
 40    /// <param name="into">The destination byte buffer, which must be 1, 2, 4 or 8 bytes long.</param>
 41    public static void EncodeVarUInt62(ulong value, Span<byte> into)
 15740142    {
 15740143        int sizeLength = into.Length;
 15740144        Debug.Assert(sizeLength == 1 || sizeLength == 2 || sizeLength == 4 || sizeLength == 8);
 45
 15740146        (uint encodedSizeExponent, long maxSize) = sizeLength switch
 15740147        {
 13807648            1 => (0x00u, 63), // 2^6 - 1
 787349            2 => (0x01u, 16_383), // 2^14 - 1
 1145050            4 => (0x02u, 1_073_741_823), // 2^30 - 1
 251            _ => (0x03u, (long)VarUInt62MaxValue)
 15740152        };
 53
 15740154        if (value > (ulong)maxSize)
 455        {
 456            throw new ArgumentOutOfRangeException(
 457                nameof(value),
 458                $"The value '{value}' cannot be encoded on {sizeLength} bytes.");
 59        }
 60
 15739761        Span<byte> ulongBuf = stackalloc byte[8];
 15739762        value <<= 2;
 63
 15739764        value |= encodedSizeExponent;
 15739765        MemoryMarshal.Write(ulongBuf, in value);
 15739766        ulongBuf[0..sizeLength].CopyTo(into);
 15739767    }
 68
 69    /// <summary>Computes the minimum number of bytes required to encode a long value using the Slice encoding's
 70    /// variable-size encoded representation.</summary>
 71    /// <param name="value">The long value.</param>
 72    /// <returns>The minimum number of bytes required to encode <paramref name="value" />. Can be 1, 2, 4 or 8.
 73    /// </returns>
 074    public static int GetVarInt62EncodedSize(long value) => 1 << GetVarInt62EncodedSizeExponent(value);
 75
 76    /// <summary>Computes the minimum number of bytes required to encode a ulong value using the Slice encoding's
 77    /// variable-size encoded representation.</summary>
 78    /// <param name="value">The ulong value.</param>
 79    /// <returns>The minimum number of bytes required to encode <paramref name="value" />. Can be 1, 2, 4 or 8.
 80    /// </returns>
 14128281    public static int GetVarUInt62EncodedSize(ulong value) => 1 << GetVarUInt62EncodedSizeExponent(value);
 82
 83    /// <summary>Computes the minimum number of bytes needed to encode a variable-length size.</summary>
 84    /// <param name="size">The size.</param>
 85    /// <returns>The minimum number of bytes.</returns>
 13806286    public static int GetSizeLength(int size) => GetVarUInt62EncodedSize(checked((ulong)size));
 87
 88    /// <summary>Constructs a Slice encoder.</summary>
 89    /// <param name="bufferWriter">A buffer writer that writes to byte buffers. See important remarks below.</param>
 90    /// <remarks>Warning: the Slice encoding requires rewriting buffers, and many buffer writers do not support this
 91    /// behavior. It is safe to use a pipe writer or a buffer writer that writes to a single fixed-size buffer (without
 92    /// reallocation).</remarks>
 93    public SliceEncoder(IBufferWriter<byte> bufferWriter)
 1636794        : this() =>
 1636795        _bufferWriter = bufferWriter;
 96
 97    // Encode methods for basic types
 98
 99    /// <summary>Encodes a bool into a Slice bool.</summary>
 100    /// <param name="v">The boolean to encode.</param>
 3165101    public void EncodeBool(bool v) => EncodeUInt8(v ? (byte)1 : (byte)0);
 102
 103    /// <summary>Encodes a float into a Slice float32.</summary>
 104    /// <param name="v">The float to encode.</param>
 0105    public void EncodeFloat32(float v) => EncodeFixedSizeNumeric(v);
 106
 107    /// <summary>Encodes a double into a Slice float64.</summary>
 108    /// <param name="v">The double to encode.</param>
 0109    public void EncodeFloat64(double v) => EncodeFixedSizeNumeric(v);
 110
 111    /// <summary>Encodes an sbyte into a Slice int8.</summary>
 112    /// <param name="v">The sbyte to encode.</param>
 2113    public void EncodeInt8(sbyte v) => EncodeUInt8((byte)v);
 114
 115    /// <summary>Encodes a short into a Slice int16.</summary>
 116    /// <param name="v">The short to encode.</param>
 2084117    public void EncodeInt16(short v) => EncodeFixedSizeNumeric(v);
 118
 119    /// <summary>Encodes an int into a Slice int32.</summary>
 120    /// <param name="v">The int to encode.</param>
 137681121    public void EncodeInt32(int v) => EncodeFixedSizeNumeric(v);
 122
 123    /// <summary>Encodes a long into a Slice int64.</summary>
 124    /// <param name="v">The long to encode.</param>
 403125    public void EncodeInt64(long v) => EncodeFixedSizeNumeric(v);
 126
 127    /// <summary>Encodes a size on variable number of bytes.</summary>
 128    /// <param name="value">The size to encode.</param>
 129    public void EncodeSize(int value)
 7715130    {
 7715131        if (value < 0)
 1132        {
 1133            throw new ArgumentException(
 1134                $"The {nameof(value)} argument must be greater than or equal to 0.",
 1135                nameof(value));
 136        }
 137
 7714138        EncodeVarUInt62((ulong)value);
 7714139    }
 140
 141    /// <summary>Encodes a string into a Slice string.</summary>
 142    /// <param name="v">The string to encode.</param>
 143    public void EncodeString(string v)
 139446144    {
 139446145        if (v.Length == 0)
 1386146        {
 1386147            EncodeSize(0);
 1386148        }
 149        else
 138060150        {
 138060151            int maxSize = _utf8.GetMaxByteCount(v.Length);
 138060152            int sizeLength = GetSizeLength(maxSize);
 138060153            Span<byte> sizePlaceholder = GetPlaceholderSpan(sizeLength);
 154
 138060155            Span<byte> currentSpan = _bufferWriter.GetSpan();
 138060156            if (currentSpan.Length >= maxSize)
 136920157            {
 158                // Encode directly into currentSpan
 136920159                int size = _utf8.GetBytes(v, currentSpan);
 136920160                EncodeVarUInt62((ulong)size, sizePlaceholder);
 136920161                Advance(size);
 136920162            }
 163            else
 1140164            {
 165                // Encode piecemeal using _utf8Encoder
 1140166                if (_utf8Encoder is null)
 57167                {
 57168                    _utf8Encoder = _utf8.GetEncoder();
 57169                }
 170                else
 1083171                {
 1083172                    _utf8Encoder.Reset();
 1083173                }
 174
 1140175                ReadOnlySpan<char> chars = v.AsSpan();
 1140176                _utf8Encoder.Convert(chars, _bufferWriter, flush: true, out long bytesUsed, out bool completed);
 177
 1140178                Debug.Assert(completed); // completed is always true when flush is true
 1140179                int size = checked((int)bytesUsed);
 1140180                EncodedByteCount += size;
 1140181                EncodeVarUInt62((ulong)size, sizePlaceholder);
 1140182            }
 138060183        }
 139446184    }
 185
 186    /// <summary>Encodes a byte into a Slice uint8.</summary>
 187    /// <param name="v">The byte to encode.</param>
 188    public void EncodeUInt8(byte v)
 17200189    {
 17200190        Span<byte> span = _bufferWriter.GetSpan();
 17200191        span[0] = v;
 17200192        Advance(1);
 17200193    }
 194
 195    /// <summary>Encodes a ushort into a Slice uint16.</summary>
 196    /// <param name="v">The ushort to encode.</param>
 2197    public void EncodeUInt16(ushort v) => EncodeFixedSizeNumeric(v);
 198
 199    /// <summary>Encodes a uint into a Slice uint32.</summary>
 200    /// <param name="v">The uint to encode.</param>
 22201    public void EncodeUInt32(uint v) => EncodeFixedSizeNumeric(v);
 202
 203    /// <summary>Encodes a ulong into a Slice uint64.</summary>
 204    /// <param name="v">The ulong to encode.</param>
 0205    public void EncodeUInt64(ulong v) => EncodeFixedSizeNumeric(v);
 206
 207    /// <summary>Encodes an int into a Slice varint32.</summary>
 208    /// <param name="v">The int to encode.</param>
 294209    public void EncodeVarInt32(int v) => EncodeVarInt62(v);
 210
 211    /// <summary>Encodes a long into a Slice varint62, with the minimum number of bytes required
 212    /// by the encoding.</summary>
 213    /// <param name="v">The long to encode. It must be in the range [-2^61..2^61 - 1].</param>
 214    public void EncodeVarInt62(long v)
 315215    {
 315216        int encodedSizeExponent = GetVarInt62EncodedSizeExponent(v);
 313217        v <<= 2;
 313218        v |= (uint)encodedSizeExponent;
 219
 313220        Span<byte> data = _bufferWriter.GetSpan(sizeof(long));
 313221        MemoryMarshal.Write(data, in v);
 313222        Advance(1 << encodedSizeExponent);
 313223    }
 224
 225    /// <summary>Encodes a uint into a Slice varuint32.</summary>
 226    /// <param name="v">The uint to encode.</param>
 0227    public void EncodeVarUInt32(uint v) => EncodeVarUInt62(v);
 228
 229    /// <summary>Encodes a ulong into a Slice varuint62, with the minimum number of bytes
 230    /// required by the encoding.</summary>
 231    /// <param name="v">The ulong to encode. It must be in the range [0..2^62 - 1].</param>
 232    public void EncodeVarUInt62(ulong v)
 25955233    {
 25955234        int encodedSizeExponent = GetVarUInt62EncodedSizeExponent(v);
 25954235        v <<= 2;
 25954236        v |= (uint)encodedSizeExponent;
 237
 25954238        Span<byte> data = _bufferWriter.GetSpan(sizeof(ulong));
 25954239        MemoryMarshal.Write(data, in v);
 25954240        Advance(1 << encodedSizeExponent);
 25954241    }
 242
 243    // Other methods
 244
 245    /// <summary>Encodes a non-null tagged value. The number of bytes needed to encode the value is not known before
 246    /// encoding this value.</summary>
 247    /// <typeparam name="T">The type of the value being encoded.</typeparam>
 248    /// <param name="tag">The tag.</param>
 249    /// <param name="v">The value to encode.</param>
 250    /// <param name="encodeAction">The delegate that encodes the value after the tag header.</param>
 251    public void EncodeTagged<T>(int tag, T v, EncodeAction<T> encodeAction) where T : notnull
 26252    {
 26253        EncodeVarInt32(tag); // the key
 26254        Span<byte> sizePlaceholder = GetPlaceholderSpan(4);
 26255        int startPos = EncodedByteCount;
 26256        encodeAction(ref this, v);
 26257        EncodeVarUInt62((ulong)(EncodedByteCount - startPos), sizePlaceholder);
 26258    }
 259
 260    /// <summary>Encodes a non-null tagged value. The number of bytes needed to encode the value is known before
 261    /// encoding the value.</summary>
 262    /// <typeparam name="T">The type of the value being encoded.</typeparam>
 263    /// <param name="tag">The tag.</param>
 264    /// <param name="size">The number of bytes needed to encode the value.</param>
 265    /// <param name="v">The value to encode.</param>
 266    /// <param name="encodeAction">The delegate that encodes the value after the tag header.</param>
 267    public void EncodeTagged<T>(int tag, int size, T v, EncodeAction<T> encodeAction) where T : notnull
 21268    {
 21269        if (size <= 0)
 0270        {
 0271            throw new ArgumentException("Invalid size value, size must be greater than 0.", nameof(size));
 272        }
 273
 21274        EncodeVarInt32(tag);
 275
 21276        EncodeSize(size);
 21277        int startPos = EncodedByteCount;
 21278        encodeAction(ref this, v);
 279
 21280        int actualSize = EncodedByteCount - startPos;
 21281        if (actualSize != size)
 0282        {
 0283            throw new ArgumentException(
 0284                $"The value of size ({size}) does not match encoded size ({actualSize}).",
 0285                nameof(size));
 286        }
 21287    }
 288
 289    /// <summary>Allocates a new bit sequence in the underlying buffer(s) and returns a writer for this bit sequence.
 290    /// </summary>
 291    /// <param name="bitSequenceSize">The minimum number of bits in the bit sequence.</param>
 292    /// <returns>The bit sequence writer.</returns>
 293    public BitSequenceWriter GetBitSequenceWriter(int bitSequenceSize)
 1130294    {
 1130295        if (bitSequenceSize <= 0)
 0296        {
 0297            throw new ArgumentOutOfRangeException(
 0298                nameof(bitSequenceSize),
 0299                $"The {nameof(bitSequenceSize)} argument must be greater than 0.");
 300        }
 301
 1130302        int remaining = GetBitSequenceByteCount(bitSequenceSize);
 303
 1130304        Span<byte> firstSpan = _bufferWriter.GetSpan();
 1130305        Span<byte> secondSpan = default;
 306
 307        // We only create this additionalMemory list in the rare situation where 2 spans are not sufficient.
 1130308        List<Memory<byte>>? additionalMemory = null;
 309
 1130310        if (firstSpan.Length >= remaining)
 1118311        {
 1118312            firstSpan = firstSpan[0..remaining];
 1118313            Advance(remaining);
 1118314        }
 315        else
 12316        {
 12317            Advance(firstSpan.Length);
 12318            remaining -= firstSpan.Length;
 319
 12320            secondSpan = _bufferWriter.GetSpan();
 12321            if (secondSpan.Length >= remaining)
 6322            {
 6323                secondSpan = secondSpan[0..remaining];
 6324                Advance(remaining);
 6325            }
 326            else
 6327            {
 6328                Advance(secondSpan.Length);
 6329                remaining -= secondSpan.Length;
 6330                additionalMemory = new List<Memory<byte>>();
 331
 332                do
 78333                {
 78334                    Memory<byte> memory = _bufferWriter.GetMemory();
 78335                    if (memory.Length >= remaining)
 6336                    {
 6337                        additionalMemory.Add(memory[0..remaining]);
 6338                        Advance(remaining);
 6339                        remaining = 0;
 6340                    }
 341                    else
 72342                    {
 72343                        additionalMemory.Add(memory);
 72344                        Advance(memory.Length);
 72345                        remaining -= memory.Length;
 72346                    }
 78347                }
 78348                while (remaining > 0);
 6349            }
 12350        }
 351
 1130352        return new BitSequenceWriter(firstSpan, secondSpan, additionalMemory);
 1130353    }
 354
 355    /// <summary>Gets a placeholder to be filled-in later.</summary>
 356    /// <param name="size">The size of the placeholder, typically a small number like 4.</param>
 357    /// <returns>A buffer of length <paramref name="size" />.</returns>
 358    /// <remarks>We make the assumption the underlying buffer writer allows rewriting memory it provided even after
 359    /// successive calls to GetMemory/GetSpan and Advance.</remarks>
 360    public Span<byte> GetPlaceholderSpan(int size)
 154135361    {
 154135362        Debug.Assert(size > 0);
 154135363        Span<byte> placeholder = _bufferWriter.GetSpan(size)[0..size];
 154135364        Advance(size);
 154135365        return placeholder;
 154135366    }
 367
 368    /// <summary>Copies a span of bytes to the underlying buffer writer.</summary>
 369    /// <param name="span">The span to copy.</param>
 370    public void WriteByteSpan(ReadOnlySpan<byte> span)
 3329371    {
 3329372        _bufferWriter.Write(span);
 3329373        EncodedByteCount += span.Length;
 3329374    }
 375
 2226376    internal static int GetBitSequenceByteCount(int bitCount) => (bitCount >> 3) + ((bitCount & 0x07) != 0 ? 1 : 0);
 377
 378    /// <summary>Encodes a fixed-size numeric value.</summary>
 379    /// <param name="v">The numeric value to encode.</param>
 380    internal void EncodeFixedSizeNumeric<T>(T v) where T : struct
 140457381    {
 140457382        int elementSize = Unsafe.SizeOf<T>();
 140457383        Span<byte> data = _bufferWriter.GetSpan(elementSize)[0..elementSize];
 140457384        MemoryMarshal.Write(data, in v);
 140457385        Advance(elementSize);
 140457386    }
 387
 388    /// <summary>Gets a placeholder to be filled-in later.</summary>
 389    /// <param name="size">The size of the placeholder, typically a small number like 4.</param>
 390    /// <returns>A buffer of length <paramref name="size" />.</returns>
 391    /// <remarks>We make the assumption the underlying buffer writer allows rewriting memory it provided even after
 392    /// successive calls to GetMemory/GetSpan and Advance.</remarks>
 393    internal Memory<byte> GetPlaceholderMemory(int size)
 0394    {
 0395        Debug.Assert(size > 0);
 0396        Memory<byte> placeholder = _bufferWriter.GetMemory(size)[0..size];
 0397        Advance(size);
 0398        return placeholder;
 0399    }
 400
 401    /// <summary>Gets the minimum number of bytes needed to encode a long value with the varint62 encoding as an
 402    /// exponent of 2.</summary>
 403    /// <param name="value">The value to encode.</param>
 404    /// <returns>N where 2^N is the number of bytes needed to encode value with Slice's varint62 encoding.</returns>
 405    private static int GetVarInt62EncodedSizeExponent(long value)
 315406    {
 315407        if (value < VarInt62MinValue || value > VarInt62MaxValue)
 2408        {
 2409            throw new ArgumentOutOfRangeException(nameof(value), $"The value '{value}' is out of the varint62 range.");
 410        }
 411
 313412        return (value << 2) switch
 313413        {
 608414            long b when b >= sbyte.MinValue && b <= sbyte.MaxValue => 0,
 20415            long s when s >= short.MinValue && s <= short.MaxValue => 1,
 26416            long i when i >= int.MinValue && i <= int.MaxValue => 2,
 6417            _ => 3
 313418        };
 313419    }
 420
 421    /// <summary>Gets the minimum number of bytes needed to encode a ulong value with the varuint62 encoding as an
 422    /// exponent of 2.</summary>
 423    /// <param name="value">The value to encode.</param>
 424    /// <returns>N where 2^N is the number of bytes needed to encode value with Slice's varuint62 encoding.</returns>
 425    private static int GetVarUInt62EncodedSizeExponent(ulong value)
 167237426    {
 167237427        if (value > VarUInt62MaxValue)
 1428        {
 1429            throw new ArgumentOutOfRangeException(nameof(value), $"The value '{value}' is out of the varuint62 range.");
 430        }
 431
 167236432        return (value << 2) switch
 167236433        {
 326154434            ulong b when b <= byte.MaxValue => 0,
 13496435            ulong s when s <= ushort.MaxValue => 1,
 6268436            ulong i when i <= uint.MaxValue => 2,
 12437            _ => 3
 167236438        };
 167236439    }
 440
 441    private void Advance(int count)
 476199442    {
 476199443        _bufferWriter.Advance(count);
 476199444        EncodedByteCount += count;
 476199445    }
 446}