| #region Copyright notice and license |
| // Protocol Buffers - Google's data interchange format |
| // Copyright 2008 Google Inc. All rights reserved. |
| // |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file or at |
| // https://developers.google.com/open-source/licenses/bsd |
| #endregion |
| |
| using System; |
| using System.Buffers.Binary; |
| using System.Runtime.CompilerServices; |
| using System.Runtime.InteropServices; |
| #if GOOGLE_PROTOBUF_SIMD |
| using System.Runtime.Intrinsics; |
| using System.Runtime.Intrinsics.Arm; |
| using System.Runtime.Intrinsics.X86; |
| #endif |
| using System.Security; |
| using System.Text; |
| |
| namespace Google.Protobuf |
| { |
| /// <summary> |
| /// Primitives for encoding protobuf wire format. |
| /// </summary> |
| [SecuritySafeCritical] |
| internal static class WritingPrimitives |
| { |
| // Ideally we would use the same UTF8Encoding as parse, but we should be able to serialize |
| // invalid UTF-8 that make it in via unvalidated setters without throwing an exception. |
| #if NET5_0_OR_GREATER |
| internal static Encoding Utf8Encoding => Encoding.UTF8; // allows JIT to devirtualize |
| #else |
| internal static readonly Encoding Utf8Encoding = |
| Encoding.UTF8; // "Local" copy of Encoding.UTF8, for efficiency. (Yes, it makes a |
| // difference.) |
| #endif |
| |
| #region Writing of values (not including tags) |
| |
| /// <summary> |
| /// Writes a double field value, without a tag, to the stream. |
| /// </summary> |
| public static void WriteDouble(ref Span<byte> buffer, ref WriterInternalState state, double value) |
| { |
| WriteRawLittleEndian64(ref buffer, ref state, (ulong)BitConverter.DoubleToInt64Bits(value)); |
| } |
| |
| /// <summary> |
| /// Writes a float field value, without a tag, to the stream. |
| /// </summary> |
| public static unsafe void WriteFloat(ref Span<byte> buffer, ref WriterInternalState state, float value) |
| { |
| const int length = sizeof(float); |
| if (buffer.Length - state.position >= length) |
| { |
| // if there's enough space in the buffer, write the float directly into the buffer |
| var floatSpan = buffer.Slice(state.position, length); |
| Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(floatSpan), value); |
| |
| if (!BitConverter.IsLittleEndian) |
| { |
| floatSpan.Reverse(); |
| } |
| state.position += length; |
| } |
| else |
| { |
| WriteFloatSlowPath(ref buffer, ref state, value); |
| } |
| } |
| |
| [MethodImpl(MethodImplOptions.NoInlining)] |
| private static unsafe void WriteFloatSlowPath(ref Span<byte> buffer, ref WriterInternalState state, float value) |
| { |
| const int length = sizeof(float); |
| |
| // TODO: deduplicate the code. Populating the span is the same as for the fastpath. |
| Span<byte> floatSpan = stackalloc byte[length]; |
| Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(floatSpan), value); |
| if (!BitConverter.IsLittleEndian) |
| { |
| floatSpan.Reverse(); |
| } |
| |
| WriteRawByte(ref buffer, ref state, floatSpan[0]); |
| WriteRawByte(ref buffer, ref state, floatSpan[1]); |
| WriteRawByte(ref buffer, ref state, floatSpan[2]); |
| WriteRawByte(ref buffer, ref state, floatSpan[3]); |
| } |
| |
| /// <summary> |
| /// Writes a uint64 field value, without a tag, to the stream. |
| /// </summary> |
| public static void WriteUInt64(ref Span<byte> buffer, ref WriterInternalState state, ulong value) |
| { |
| WriteRawVarint64(ref buffer, ref state, value); |
| } |
| |
| /// <summary> |
| /// Writes an int64 field value, without a tag, to the stream. |
| /// </summary> |
| public static void WriteInt64(ref Span<byte> buffer, ref WriterInternalState state, long value) |
| { |
| WriteRawVarint64(ref buffer, ref state, (ulong)value); |
| } |
| |
| /// <summary> |
| /// Writes an int32 field value, without a tag, to the stream. |
| /// </summary> |
| public static void WriteInt32(ref Span<byte> buffer, ref WriterInternalState state, int value) |
| { |
| if (value >= 0) |
| { |
| WriteRawVarint32(ref buffer, ref state, (uint)value); |
| } |
| else |
| { |
| // Must sign-extend. |
| WriteRawVarint64(ref buffer, ref state, (ulong)value); |
| } |
| } |
| |
| /// <summary> |
| /// Writes a fixed64 field value, without a tag, to the stream. |
| /// </summary> |
| public static void WriteFixed64(ref Span<byte> buffer, ref WriterInternalState state, ulong value) |
| { |
| WriteRawLittleEndian64(ref buffer, ref state, value); |
| } |
| |
| /// <summary> |
| /// Writes a fixed32 field value, without a tag, to the stream. |
| /// </summary> |
| public static void WriteFixed32(ref Span<byte> buffer, ref WriterInternalState state, uint value) |
| { |
| WriteRawLittleEndian32(ref buffer, ref state, value); |
| } |
| |
| /// <summary> |
| /// Writes a bool field value, without a tag, to the stream. |
| /// </summary> |
| public static void WriteBool(ref Span<byte> buffer, ref WriterInternalState state, bool value) |
| { |
| WriteRawByte(ref buffer, ref state, value ? (byte)1 : (byte)0); |
| } |
| |
| /// <summary> |
| /// Writes a string field value, without a tag, to the stream. |
| /// The data is length-prefixed. |
| /// </summary> |
| public static void WriteString(ref Span<byte> buffer, ref WriterInternalState state, string value) |
| { |
| const int MaxBytesPerChar = 3; |
| const int MaxSmallStringLength = 128 / MaxBytesPerChar; |
| |
| // The string is small enough that the length will always be a 1 byte varint. |
| // Also there is enough space to write length + bytes to buffer. |
| // Write string directly to the buffer, and then write length. |
| // This saves calling GetByteCount on the string. We get the string length from GetBytes. |
| if (value.Length <= MaxSmallStringLength && buffer.Length - state.position - 1 >= value.Length * MaxBytesPerChar) |
| { |
| int indexOfLengthDelimiter = state.position++; |
| buffer[indexOfLengthDelimiter] = (byte)WriteStringToBuffer(buffer, ref state, value); |
| return; |
| } |
| |
| int length = Utf8Encoding.GetByteCount(value); |
| WriteLength(ref buffer, ref state, length); |
| |
| // Optimise the case where we have enough space to write |
| // the string directly to the buffer, which should be common. |
| if (buffer.Length - state.position >= length) |
| { |
| if (length == value.Length) // Must be all ASCII... |
| { |
| WriteAsciiStringToBuffer(buffer, ref state, value, length); |
| } |
| else |
| { |
| WriteStringToBuffer(buffer, ref state, value); |
| } |
| } |
| else |
| { |
| // Opportunity for future optimization: |
| // Large strings that don't fit into the current buffer segment |
| // can probably be optimized by using Utf8Encoding.GetEncoder() |
| // but more benchmarks would need to be added as evidence. |
| byte[] bytes = Utf8Encoding.GetBytes(value); |
| WriteRawBytes(ref buffer, ref state, bytes); |
| } |
| } |
| |
| // Calling this method with non-ASCII content will break. |
| // Content must be verified to be all ASCII before using this method. |
| private static void WriteAsciiStringToBuffer(Span<byte> buffer, ref WriterInternalState state, string value, int length) |
| { |
| ref char sourceChars = ref MemoryMarshal.GetReference(value.AsSpan()); |
| ref byte destinationBytes = ref MemoryMarshal.GetReference(buffer.Slice(state.position)); |
| |
| int currentIndex = 0; |
| // If 64bit, process 4 chars at a time. |
| // The logic inside this check will be elided by JIT in 32bit programs. |
| if (IntPtr.Size == 8) |
| { |
| // Need at least 4 chars available to use this optimization. |
| if (length >= 4) |
| { |
| ref byte sourceBytes = ref Unsafe.As<char, byte>(ref sourceChars); |
| |
| // Process 4 chars at a time until there are less than 4 remaining. |
| // We already know all characters are ASCII so there is no need to validate the source. |
| int lastIndexWhereCanReadFourChars = value.Length - 4; |
| do |
| { |
| NarrowFourUtf16CharsToAsciiAndWriteToBuffer( |
| ref Unsafe.AddByteOffset(ref destinationBytes, (IntPtr)currentIndex), |
| Unsafe.ReadUnaligned<ulong>(ref Unsafe.AddByteOffset(ref sourceBytes, (IntPtr)(currentIndex * 2)))); |
| |
| } while ((currentIndex += 4) <= lastIndexWhereCanReadFourChars); |
| } |
| } |
| |
| // Process any remaining, 1 char at a time. |
| // Avoid bounds checking with ref + Unsafe |
| for (; currentIndex < length; currentIndex++) |
| { |
| Unsafe.AddByteOffset(ref destinationBytes, (IntPtr)currentIndex) = (byte)Unsafe.AddByteOffset(ref sourceChars, (IntPtr)(currentIndex * 2)); |
| } |
| |
| state.position += length; |
| } |
| |
| // Copied with permission from https://github.com/dotnet/runtime/blob/1cdafd27e4afd2c916af5df949c13f8b373c4335/src/libraries/System.Private.CoreLib/src/System/Text/ASCIIUtility.cs#L1119-L1171 |
| // |
| /// <summary> |
| /// Given a QWORD which represents a buffer of 4 ASCII chars in machine-endian order, |
| /// narrows each WORD to a BYTE, then writes the 4-byte result to the output buffer |
| /// also in machine-endian order. |
| /// </summary> |
| [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| private static void NarrowFourUtf16CharsToAsciiAndWriteToBuffer(ref byte outputBuffer, ulong value) |
| { |
| #if GOOGLE_PROTOBUF_SIMD |
| if (Sse2.X64.IsSupported) |
| { |
| // Narrows a vector of words [ w0 w1 w2 w3 ] to a vector of bytes |
| // [ b0 b1 b2 b3 b0 b1 b2 b3 ], then writes 4 bytes (32 bits) to the destination. |
| |
| Vector128<short> vecWide = Sse2.X64.ConvertScalarToVector128UInt64(value).AsInt16(); |
| Vector128<uint> vecNarrow = Sse2.PackUnsignedSaturate(vecWide, vecWide).AsUInt32(); |
| Unsafe.WriteUnaligned<uint>(ref outputBuffer, Sse2.ConvertToUInt32(vecNarrow)); |
| } |
| else if (AdvSimd.IsSupported) |
| { |
| // Narrows a vector of words [ w0 w1 w2 w3 ] to a vector of bytes |
| // [ b0 b1 b2 b3 * * * * ], then writes 4 bytes (32 bits) to the destination. |
| |
| Vector128<short> vecWide = Vector128.CreateScalarUnsafe(value).AsInt16(); |
| Vector64<byte> lower = AdvSimd.ExtractNarrowingSaturateUnsignedLower(vecWide); |
| Unsafe.WriteUnaligned<uint>(ref outputBuffer, lower.AsUInt32().ToScalar()); |
| } |
| else |
| #endif |
| { |
| // Fallback to non-SIMD approach when SIMD is not available. |
| // This could happen either because the APIs are not available, or hardware doesn't support it. |
| // Processing 4 chars at a time in this fallback is still faster than casting one char at a time. |
| if (BitConverter.IsLittleEndian) |
| { |
| outputBuffer = (byte)value; |
| value >>= 16; |
| Unsafe.Add(ref outputBuffer, 1) = (byte)value; |
| value >>= 16; |
| Unsafe.Add(ref outputBuffer, 2) = (byte)value; |
| value >>= 16; |
| Unsafe.Add(ref outputBuffer, 3) = (byte)value; |
| } |
| else |
| { |
| Unsafe.Add(ref outputBuffer, 3) = (byte)value; |
| value >>= 16; |
| Unsafe.Add(ref outputBuffer, 2) = (byte)value; |
| value >>= 16; |
| Unsafe.Add(ref outputBuffer, 1) = (byte)value; |
| value >>= 16; |
| outputBuffer = (byte)value; |
| } |
| } |
| } |
| |
| private static int WriteStringToBuffer(Span<byte> buffer, ref WriterInternalState state, string value) |
| { |
| #if NETSTANDARD1_1 |
| // slowpath when Encoding.GetBytes(Char*, Int32, Byte*, Int32) is not available |
| byte[] bytes = Utf8Encoding.GetBytes(value); |
| WriteRawBytes(ref buffer, ref state, bytes); |
| return bytes.Length; |
| #else |
| ReadOnlySpan<char> source = value.AsSpan(); |
| int bytesUsed; |
| unsafe |
| { |
| fixed (char* sourceChars = &MemoryMarshal.GetReference(source)) |
| fixed (byte* destinationBytes = &MemoryMarshal.GetReference(buffer)) |
| { |
| bytesUsed = Utf8Encoding.GetBytes( |
| sourceChars, |
| source.Length, |
| destinationBytes + state.position, |
| buffer.Length - state.position); |
| } |
| } |
| state.position += bytesUsed; |
| return bytesUsed; |
| #endif |
| } |
| |
| /// <summary> |
| /// Write a byte string, without a tag, to the stream. |
| /// The data is length-prefixed. |
| /// </summary> |
| public static void WriteBytes(ref Span<byte> buffer, ref WriterInternalState state, ByteString value) |
| { |
| WriteLength(ref buffer, ref state, value.Length); |
| WriteRawBytes(ref buffer, ref state, value.Span); |
| } |
| |
| /// <summary> |
| /// Writes a uint32 value, without a tag, to the stream. |
| /// </summary> |
| public static void WriteUInt32(ref Span<byte> buffer, ref WriterInternalState state, uint value) |
| { |
| WriteRawVarint32(ref buffer, ref state, value); |
| } |
| |
| /// <summary> |
| /// Writes an enum value, without a tag, to the stream. |
| /// </summary> |
| public static void WriteEnum(ref Span<byte> buffer, ref WriterInternalState state, int value) |
| { |
| WriteInt32(ref buffer, ref state, value); |
| } |
| |
| /// <summary> |
| /// Writes an sfixed32 value, without a tag, to the stream. |
| /// </summary> |
| public static void WriteSFixed32(ref Span<byte> buffer, ref WriterInternalState state, int value) |
| { |
| WriteRawLittleEndian32(ref buffer, ref state, (uint)value); |
| } |
| |
| /// <summary> |
| /// Writes an sfixed64 value, without a tag, to the stream. |
| /// </summary> |
| public static void WriteSFixed64(ref Span<byte> buffer, ref WriterInternalState state, long value) |
| { |
| WriteRawLittleEndian64(ref buffer, ref state, (ulong)value); |
| } |
| |
| /// <summary> |
| /// Writes an sint32 value, without a tag, to the stream. |
| /// </summary> |
| public static void WriteSInt32(ref Span<byte> buffer, ref WriterInternalState state, int value) |
| { |
| WriteRawVarint32(ref buffer, ref state, EncodeZigZag32(value)); |
| } |
| |
| /// <summary> |
| /// Writes an sint64 value, without a tag, to the stream. |
| /// </summary> |
| public static void WriteSInt64(ref Span<byte> buffer, ref WriterInternalState state, long value) |
| { |
| WriteRawVarint64(ref buffer, ref state, EncodeZigZag64(value)); |
| } |
| |
| /// <summary> |
| /// Writes a length (in bytes) for length-delimited data. |
| /// </summary> |
| /// <remarks> |
| /// This method simply writes a rawint, but exists for clarity in calling code. |
| /// </remarks> |
| public static void WriteLength(ref Span<byte> buffer, ref WriterInternalState state, int length) |
| { |
| WriteRawVarint32(ref buffer, ref state, (uint)length); |
| } |
| |
| #endregion |
| |
| #region Writing primitives |
| /// <summary> |
| /// Writes a 32 bit value as a varint. The fast route is taken when |
| /// there's enough buffer space left to whizz through without checking |
| /// for each byte; otherwise, we resort to calling WriteRawByte each time. |
| /// </summary> |
| public static void WriteRawVarint32(ref Span<byte> buffer, ref WriterInternalState state, uint value) |
| { |
| // Optimize for the common case of a single byte value |
| if (value < 128 && state.position < buffer.Length) |
| { |
| buffer[state.position++] = (byte)value; |
| return; |
| } |
| |
| // Fast path when capacity is available |
| while (state.position < buffer.Length) |
| { |
| if (value > 127) |
| { |
| buffer[state.position++] = (byte)((value & 0x7F) | 0x80); |
| value >>= 7; |
| } |
| else |
| { |
| buffer[state.position++] = (byte)value; |
| return; |
| } |
| } |
| |
| while (value > 127) |
| { |
| WriteRawByte(ref buffer, ref state, (byte)((value & 0x7F) | 0x80)); |
| value >>= 7; |
| } |
| |
| WriteRawByte(ref buffer, ref state, (byte)value); |
| } |
| |
| public static void WriteRawVarint64(ref Span<byte> buffer, ref WriterInternalState state, ulong value) |
| { |
| // Optimize for the common case of a single byte value |
| if (value < 128 && state.position < buffer.Length) |
| { |
| buffer[state.position++] = (byte)value; |
| return; |
| } |
| |
| // Fast path when capacity is available |
| while (state.position < buffer.Length) |
| { |
| if (value > 127) |
| { |
| buffer[state.position++] = (byte)((value & 0x7F) | 0x80); |
| value >>= 7; |
| } |
| else |
| { |
| buffer[state.position++] = (byte)value; |
| return; |
| } |
| } |
| |
| while (value > 127) |
| { |
| WriteRawByte(ref buffer, ref state, (byte)((value & 0x7F) | 0x80)); |
| value >>= 7; |
| } |
| |
| WriteRawByte(ref buffer, ref state, (byte)value); |
| } |
| |
| public static void WriteRawLittleEndian32(ref Span<byte> buffer, ref WriterInternalState state, uint value) |
| { |
| const int length = sizeof(uint); |
| if (state.position + length > buffer.Length) |
| { |
| WriteRawLittleEndian32SlowPath(ref buffer, ref state, value); |
| } |
| else |
| { |
| BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(state.position), value); |
| state.position += length; |
| } |
| } |
| |
| [MethodImpl(MethodImplOptions.NoInlining)] |
| private static void WriteRawLittleEndian32SlowPath(ref Span<byte> buffer, ref WriterInternalState state, uint value) |
| { |
| WriteRawByte(ref buffer, ref state, (byte)value); |
| WriteRawByte(ref buffer, ref state, (byte)(value >> 8)); |
| WriteRawByte(ref buffer, ref state, (byte)(value >> 16)); |
| WriteRawByte(ref buffer, ref state, (byte)(value >> 24)); |
| } |
| |
| public static void WriteRawLittleEndian64(ref Span<byte> buffer, ref WriterInternalState state, ulong value) |
| { |
| const int length = sizeof(ulong); |
| if (state.position + length > buffer.Length) |
| { |
| WriteRawLittleEndian64SlowPath(ref buffer, ref state, value); |
| } |
| else |
| { |
| BinaryPrimitives.WriteUInt64LittleEndian(buffer.Slice(state.position), value); |
| state.position += length; |
| } |
| } |
| |
| [MethodImpl(MethodImplOptions.NoInlining)] |
| public static void WriteRawLittleEndian64SlowPath(ref Span<byte> buffer, ref WriterInternalState state, ulong value) |
| { |
| WriteRawByte(ref buffer, ref state, (byte)value); |
| WriteRawByte(ref buffer, ref state, (byte)(value >> 8)); |
| WriteRawByte(ref buffer, ref state, (byte)(value >> 16)); |
| WriteRawByte(ref buffer, ref state, (byte)(value >> 24)); |
| WriteRawByte(ref buffer, ref state, (byte)(value >> 32)); |
| WriteRawByte(ref buffer, ref state, (byte)(value >> 40)); |
| WriteRawByte(ref buffer, ref state, (byte)(value >> 48)); |
| WriteRawByte(ref buffer, ref state, (byte)(value >> 56)); |
| } |
| |
| private static void WriteRawByte(ref Span<byte> buffer, ref WriterInternalState state, byte value) |
| { |
| if (state.position == buffer.Length) |
| { |
| WriteBufferHelper.RefreshBuffer(ref buffer, ref state); |
| } |
| |
| buffer[state.position++] = value; |
| } |
| |
| /// <summary> |
| /// Writes out an array of bytes. |
| /// </summary> |
| public static void WriteRawBytes(ref Span<byte> buffer, ref WriterInternalState state, byte[] value) |
| { |
| WriteRawBytes(ref buffer, ref state, new ReadOnlySpan<byte>(value)); |
| } |
| |
| /// <summary> |
| /// Writes out part of an array of bytes. |
| /// </summary> |
| public static void WriteRawBytes(ref Span<byte> buffer, ref WriterInternalState state, byte[] value, int offset, int length) |
| { |
| WriteRawBytes(ref buffer, ref state, new ReadOnlySpan<byte>(value, offset, length)); |
| } |
| |
| /// <summary> |
| /// Writes out part of an array of bytes. |
| /// </summary> |
| public static void WriteRawBytes(ref Span<byte> buffer, ref WriterInternalState state, ReadOnlySpan<byte> value) |
| { |
| if (buffer.Length - state.position >= value.Length) |
| { |
| // We have room in the current buffer. |
| value.CopyTo(buffer.Slice(state.position, value.Length)); |
| state.position += value.Length; |
| } |
| else |
| { |
| // When writing to a CodedOutputStream backed by a Stream, we could avoid |
| // copying the data twice (first copying to the current buffer and |
| // and later writing from the current buffer to the underlying Stream) |
| // in some circumstances by writing the data directly to the underlying Stream. |
| // Current this is not being done to avoid specialcasing the code for |
| // CodedOutputStream vs IBufferWriter<byte>. |
| int bytesWritten = 0; |
| while (buffer.Length - state.position < value.Length - bytesWritten) |
| { |
| int length = buffer.Length - state.position; |
| value.Slice(bytesWritten, length).CopyTo(buffer.Slice(state.position, length)); |
| bytesWritten += length; |
| state.position += length; |
| WriteBufferHelper.RefreshBuffer(ref buffer, ref state); |
| } |
| |
| // copy the remaining data |
| int remainderLength = value.Length - bytesWritten; |
| value.Slice(bytesWritten, remainderLength).CopyTo(buffer.Slice(state.position, remainderLength)); |
| state.position += remainderLength; |
| } |
| } |
| #endregion |
| |
| #region Raw tag writing |
| /// <summary> |
| /// Encodes and writes a tag. |
| /// </summary> |
| public static void WriteTag(ref Span<byte> buffer, ref WriterInternalState state, int fieldNumber, WireFormat.WireType type) |
| { |
| WriteRawVarint32(ref buffer, ref state, WireFormat.MakeTag(fieldNumber, type)); |
| } |
| |
| /// <summary> |
| /// Writes an already-encoded tag. |
| /// </summary> |
| public static void WriteTag(ref Span<byte> buffer, ref WriterInternalState state, uint tag) |
| { |
| WriteRawVarint32(ref buffer, ref state, tag); |
| } |
| |
| /// <summary> |
| /// Writes the given single-byte tag directly to the stream. |
| /// </summary> |
| public static void WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1) |
| { |
| WriteRawByte(ref buffer, ref state, b1); |
| } |
| |
| /// <summary> |
| /// Writes the given two-byte tag directly to the stream. |
| /// </summary> |
| public static void WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2) |
| { |
| if (state.position + 2 > buffer.Length) |
| { |
| WriteRawTagSlowPath(ref buffer, ref state, b1, b2); |
| } |
| else |
| { |
| buffer[state.position++] = b1; |
| buffer[state.position++] = b2; |
| } |
| } |
| |
| [MethodImpl(MethodImplOptions.NoInlining)] |
| private static void WriteRawTagSlowPath(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2) |
| { |
| WriteRawByte(ref buffer, ref state, b1); |
| WriteRawByte(ref buffer, ref state, b2); |
| } |
| |
| /// <summary> |
| /// Writes the given three-byte tag directly to the stream. |
| /// </summary> |
| public static void WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3) |
| { |
| if (state.position + 3 > buffer.Length) |
| { |
| WriteRawTagSlowPath(ref buffer, ref state, b1, b2, b3); |
| } |
| else |
| { |
| buffer[state.position++] = b1; |
| buffer[state.position++] = b2; |
| buffer[state.position++] = b3; |
| } |
| } |
| |
| [MethodImpl(MethodImplOptions.NoInlining)] |
| private static void WriteRawTagSlowPath(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3) |
| { |
| WriteRawByte(ref buffer, ref state, b1); |
| WriteRawByte(ref buffer, ref state, b2); |
| WriteRawByte(ref buffer, ref state, b3); |
| } |
| |
| /// <summary> |
| /// Writes the given four-byte tag directly to the stream. |
| /// </summary> |
| public static void WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4) |
| { |
| if (state.position + 4 > buffer.Length) |
| { |
| WriteRawTagSlowPath(ref buffer, ref state, b1, b2, b3, b4); |
| } |
| else |
| { |
| buffer[state.position++] = b1; |
| buffer[state.position++] = b2; |
| buffer[state.position++] = b3; |
| buffer[state.position++] = b4; |
| } |
| } |
| |
| [MethodImpl(MethodImplOptions.NoInlining)] |
| |
| private static void WriteRawTagSlowPath(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4) |
| { |
| WriteRawByte(ref buffer, ref state, b1); |
| WriteRawByte(ref buffer, ref state, b2); |
| WriteRawByte(ref buffer, ref state, b3); |
| WriteRawByte(ref buffer, ref state, b4); |
| } |
| |
| /// <summary> |
| /// Writes the given five-byte tag directly to the stream. |
| /// </summary> |
| public static void WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4, byte b5) |
| { |
| if (state.position + 5 > buffer.Length) |
| { |
| WriteRawTagSlowPath(ref buffer, ref state, b1, b2, b3, b4, b5); |
| } |
| else |
| { |
| buffer[state.position++] = b1; |
| buffer[state.position++] = b2; |
| buffer[state.position++] = b3; |
| buffer[state.position++] = b4; |
| buffer[state.position++] = b5; |
| } |
| } |
| |
| [MethodImpl(MethodImplOptions.NoInlining)] |
| private static void WriteRawTagSlowPath(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4, byte b5) |
| { |
| WriteRawByte(ref buffer, ref state, b1); |
| WriteRawByte(ref buffer, ref state, b2); |
| WriteRawByte(ref buffer, ref state, b3); |
| WriteRawByte(ref buffer, ref state, b4); |
| WriteRawByte(ref buffer, ref state, b5); |
| } |
| #endregion |
| |
| /// <summary> |
| /// Encode a 32-bit value with ZigZag encoding. |
| /// </summary> |
| /// <remarks> |
| /// ZigZag encodes signed integers into values that can be efficiently |
| /// encoded with varint. (Otherwise, negative values must be |
| /// sign-extended to 64 bits to be varint encoded, thus always taking |
| /// 10 bytes on the wire.) |
| /// </remarks> |
| public static uint EncodeZigZag32(int n) |
| { |
| // Note: the right-shift must be arithmetic |
| return (uint)((n << 1) ^ (n >> 31)); |
| } |
| |
| /// <summary> |
| /// Encode a 64-bit value with ZigZag encoding. |
| /// </summary> |
| /// <remarks> |
| /// ZigZag encodes signed integers into values that can be efficiently |
| /// encoded with varint. (Otherwise, negative values must be |
| /// sign-extended to 64 bits to be varint encoded, thus always taking |
| /// 10 bytes on the wire.) |
| /// </remarks> |
| public static ulong EncodeZigZag64(long n) |
| { |
| return (ulong)((n << 1) ^ (n >> 63)); |
| } |
| } |
| } |