// Copyright (c) All contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using MessagePack.Internal;
using Microsoft;
namespace MessagePack
{
///
/// A primitive types writer for the MessagePack format.
///
///
/// The MessagePack spec..
///
#if MESSAGEPACK_INTERNAL
internal
#else
public
#endif
ref struct MessagePackWriter
{
///
/// The writer to use.
///
private BufferWriter writer;
///
/// Initializes a new instance of the struct.
///
/// The writer to use.
public MessagePackWriter(IBufferWriter writer)
: this()
{
this.writer = new BufferWriter(writer);
this.OldSpec = false;
}
///
/// Initializes a new instance of the struct.
///
/// The pool from which to draw an if required..
/// An array to start with so we can avoid accessing the if possible.
internal MessagePackWriter(SequencePool sequencePool, byte[] array)
: this()
{
this.writer = new BufferWriter(sequencePool, array);
this.OldSpec = false;
}
///
/// Gets or sets the cancellation token for this serialization operation.
///
public CancellationToken CancellationToken { get; set; }
///
/// Gets or sets a value indicating whether to write in old spec compatibility mode.
///
public bool OldSpec { get; set; }
///
/// Initializes a new instance of the struct,
/// with the same settings as this one, but with its own buffer writer.
///
/// The writer to use for the new instance.
/// The new writer.
public MessagePackWriter Clone(IBufferWriter writer) => new MessagePackWriter(writer)
{
OldSpec = this.OldSpec,
CancellationToken = this.CancellationToken,
};
///
/// Ensures everything previously written has been flushed to the underlying .
///
public void Flush() => this.writer.Commit();
///
/// Writes a value.
///
public void WriteNil()
{
Span span = this.writer.GetSpan(1);
span[0] = MessagePackCode.Nil;
this.writer.Advance(1);
}
///
/// Copies bytes directly into the message pack writer.
///
/// The span of bytes to copy from.
public void WriteRaw(ReadOnlySpan rawMessagePackBlock) => this.writer.Write(rawMessagePackBlock);
///
/// Copies bytes directly into the message pack writer.
///
/// The span of bytes to copy from.
public void WriteRaw(in ReadOnlySequence rawMessagePackBlock)
{
foreach (ReadOnlyMemory segment in rawMessagePackBlock)
{
this.writer.Write(segment.Span);
}
}
///
/// Write the length of the next array to be written in the most compact form of
/// ,
/// , or
/// .
///
/// The number of elements that will be written in the array.
public void WriteArrayHeader(int count) => this.WriteArrayHeader((uint)count);
///
/// Write the length of the next array to be written in the most compact form of
/// ,
/// , or
/// .
///
/// The number of elements that will be written in the array.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteArrayHeader(uint count)
{
if (count <= MessagePackRange.MaxFixArrayCount)
{
Span span = this.writer.GetSpan(1);
span[0] = (byte)(MessagePackCode.MinFixArray | count);
this.writer.Advance(1);
}
else if (count <= ushort.MaxValue)
{
Span span = this.writer.GetSpan(3);
span[0] = MessagePackCode.Array16;
WriteBigEndian((ushort)count, span.Slice(1));
this.writer.Advance(3);
}
else
{
Span span = this.writer.GetSpan(5);
span[0] = MessagePackCode.Array32;
WriteBigEndian(count, span.Slice(1));
this.writer.Advance(5);
}
}
///
/// Write the length of the next map to be written in the most compact form of
/// ,
/// , or
/// .
///
/// The number of key=value pairs that will be written in the map.
public void WriteMapHeader(int count) => this.WriteMapHeader((uint)count);
///
/// Write the length of the next map to be written in the most compact form of
/// ,
/// , or
/// .
///
/// The number of key=value pairs that will be written in the map.
public void WriteMapHeader(uint count)
{
if (count <= MessagePackRange.MaxFixMapCount)
{
Span span = this.writer.GetSpan(1);
span[0] = (byte)(MessagePackCode.MinFixMap | count);
this.writer.Advance(1);
}
else if (count <= ushort.MaxValue)
{
Span span = this.writer.GetSpan(3);
span[0] = MessagePackCode.Map16;
WriteBigEndian((ushort)count, span.Slice(1));
this.writer.Advance(3);
}
else
{
Span span = this.writer.GetSpan(5);
span[0] = MessagePackCode.Map32;
WriteBigEndian(count, span.Slice(1));
this.writer.Advance(5);
}
}
///
/// Writes a value using a 1-byte code when possible, otherwise as .
///
/// The value.
public void Write(byte value)
{
if (value <= MessagePackCode.MaxFixInt)
{
Span span = this.writer.GetSpan(1);
span[0] = value;
this.writer.Advance(1);
}
else
{
this.WriteUInt8(value);
}
}
///
/// Writes a value using .
///
/// The value.
public void WriteUInt8(byte value)
{
Span span = this.writer.GetSpan(2);
span[0] = MessagePackCode.UInt8;
span[1] = value;
this.writer.Advance(2);
}
///
/// Writes an 8-bit value using a 1-byte code when possible, otherwise as .
///
/// The value.
public void Write(sbyte value)
{
if (value < MessagePackRange.MinFixNegativeInt)
{
this.WriteInt8(value);
}
else
{
Span span = this.writer.GetSpan(1);
span[0] = unchecked((byte)value);
this.writer.Advance(1);
}
}
///
/// Writes an 8-bit value using .
///
/// The value.
public void WriteInt8(sbyte value)
{
Span span = this.writer.GetSpan(2);
span[0] = MessagePackCode.Int8;
span[1] = unchecked((byte)value);
this.writer.Advance(2);
}
///
/// Writes a value using a 1-byte code when possible, otherwise as or .
///
/// The value.
public void Write(ushort value)
{
if (value <= MessagePackRange.MaxFixPositiveInt)
{
Span span = this.writer.GetSpan(1);
span[0] = unchecked((byte)value);
this.writer.Advance(1);
}
else if (value <= byte.MaxValue)
{
Span span = this.writer.GetSpan(2);
span[0] = MessagePackCode.UInt8;
span[1] = unchecked((byte)value);
this.writer.Advance(2);
}
else
{
this.WriteUInt16(value);
}
}
///
/// Writes a value using .
///
/// The value.
public void WriteUInt16(ushort value)
{
Span span = this.writer.GetSpan(3);
span[0] = MessagePackCode.UInt16;
WriteBigEndian(value, span.Slice(1));
this.writer.Advance(3);
}
///
/// Writes a using a built-in 1-byte code when within specific MessagePack-supported ranges,
/// or the most compact of
/// ,
/// ,
/// , or
/// .
///
/// The value to write.
public void Write(short value)
{
if (value >= 0)
{
this.Write((ushort)value);
}
else
{
// negative int(use int)
if (value >= MessagePackRange.MinFixNegativeInt)
{
Span span = this.writer.GetSpan(1);
span[0] = unchecked((byte)value);
this.writer.Advance(1);
}
else if (value >= sbyte.MinValue)
{
Span span = this.writer.GetSpan(2);
span[0] = MessagePackCode.Int8;
span[1] = unchecked((byte)value);
this.writer.Advance(2);
}
else
{
this.WriteInt16(value);
}
}
}
///
/// Writes a using .
///
/// The value to write.
public void WriteInt16(short value)
{
Span span = this.writer.GetSpan(3);
span[0] = MessagePackCode.Int16;
WriteBigEndian(value, span.Slice(1));
this.writer.Advance(3);
}
///
/// Writes an using a built-in 1-byte code when within specific MessagePack-supported ranges,
/// or the most compact of
/// ,
/// , or
/// .
///
/// The value to write.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(uint value)
{
if (value <= MessagePackRange.MaxFixPositiveInt)
{
Span span = this.writer.GetSpan(1);
span[0] = unchecked((byte)value);
this.writer.Advance(1);
}
else if (value <= byte.MaxValue)
{
Span span = this.writer.GetSpan(2);
span[0] = MessagePackCode.UInt8;
span[1] = unchecked((byte)value);
this.writer.Advance(2);
}
else if (value <= ushort.MaxValue)
{
Span span = this.writer.GetSpan(3);
span[0] = MessagePackCode.UInt16;
WriteBigEndian((ushort)value, span.Slice(1));
this.writer.Advance(3);
}
else
{
this.WriteUInt32(value);
}
}
///
/// Writes an using .
///
/// The value to write.
public void WriteUInt32(uint value)
{
Span span = this.writer.GetSpan(5);
span[0] = MessagePackCode.UInt32;
WriteBigEndian(value, span.Slice(1));
this.writer.Advance(5);
}
///
/// Writes an using a built-in 1-byte code when within specific MessagePack-supported ranges,
/// or the most compact of
/// ,
/// ,
/// ,
/// ,
/// ,
/// .
///
/// The value to write.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(int value)
{
if (value >= 0)
{
this.Write((uint)value);
}
else
{
// negative int(use int)
if (value >= MessagePackRange.MinFixNegativeInt)
{
Span span = this.writer.GetSpan(1);
span[0] = unchecked((byte)value);
this.writer.Advance(1);
}
else if (value >= sbyte.MinValue)
{
Span span = this.writer.GetSpan(2);
span[0] = MessagePackCode.Int8;
span[1] = unchecked((byte)value);
this.writer.Advance(2);
}
else if (value >= short.MinValue)
{
Span span = this.writer.GetSpan(3);
span[0] = MessagePackCode.Int16;
WriteBigEndian((short)value, span.Slice(1));
this.writer.Advance(3);
}
else
{
this.WriteInt32(value);
}
}
}
///
/// Writes an using .
///
/// The value to write.
public void WriteInt32(int value)
{
Span span = this.writer.GetSpan(5);
span[0] = MessagePackCode.Int32;
WriteBigEndian(value, span.Slice(1));
this.writer.Advance(5);
}
///
/// Writes an using a built-in 1-byte code when within specific MessagePack-supported ranges,
/// or the most compact of
/// ,
/// ,
/// ,
/// ,
/// ,
/// .
///
/// The value to write.
public void Write(ulong value)
{
if (value <= MessagePackRange.MaxFixPositiveInt)
{
Span span = this.writer.GetSpan(1);
span[0] = unchecked((byte)value);
this.writer.Advance(1);
}
else if (value <= byte.MaxValue)
{
Span span = this.writer.GetSpan(2);
span[0] = MessagePackCode.UInt8;
span[1] = unchecked((byte)value);
this.writer.Advance(2);
}
else if (value <= ushort.MaxValue)
{
Span span = this.writer.GetSpan(3);
span[0] = MessagePackCode.UInt16;
WriteBigEndian((ushort)value, span.Slice(1));
this.writer.Advance(3);
}
else if (value <= uint.MaxValue)
{
Span span = this.writer.GetSpan(5);
span[0] = MessagePackCode.UInt32;
WriteBigEndian((uint)value, span.Slice(1));
this.writer.Advance(5);
}
else
{
this.WriteUInt64(value);
}
}
///
/// Writes an using .
///
/// The value to write.
public void WriteUInt64(ulong value)
{
Span span = this.writer.GetSpan(9);
span[0] = MessagePackCode.UInt64;
WriteBigEndian(value, span.Slice(1));
this.writer.Advance(9);
}
///
/// Writes an using a built-in 1-byte code when within specific MessagePack-supported ranges,
/// or the most compact of
/// ,
/// ,
/// ,
/// ,
/// ,
/// ,
/// ,
/// .
///
/// The value to write.
public void Write(long value)
{
if (value >= 0)
{
this.Write((ulong)value);
}
else
{
// negative int(use int)
if (value >= MessagePackRange.MinFixNegativeInt)
{
Span span = this.writer.GetSpan(1);
span[0] = unchecked((byte)value);
this.writer.Advance(1);
}
else if (value >= sbyte.MinValue)
{
Span span = this.writer.GetSpan(2);
span[0] = MessagePackCode.Int8;
span[1] = unchecked((byte)value);
this.writer.Advance(2);
}
else if (value >= short.MinValue)
{
Span span = this.writer.GetSpan(3);
span[0] = MessagePackCode.Int16;
WriteBigEndian((short)value, span.Slice(1));
this.writer.Advance(3);
}
else if (value >= int.MinValue)
{
Span span = this.writer.GetSpan(5);
span[0] = MessagePackCode.Int32;
WriteBigEndian((int)value, span.Slice(1));
this.writer.Advance(5);
}
else
{
this.WriteInt64(value);
}
}
}
///
/// Writes a using .
///
/// The value to write.
public void WriteInt64(long value)
{
Span span = this.writer.GetSpan(9);
span[0] = MessagePackCode.Int64;
WriteBigEndian(value, span.Slice(1));
this.writer.Advance(9);
}
///
/// Writes a value using either or .
///
/// The value.
public void Write(bool value)
{
Span span = this.writer.GetSpan(1);
span[0] = value ? MessagePackCode.True : MessagePackCode.False;
this.writer.Advance(1);
}
///
/// Writes a value using a 1-byte code when possible, otherwise as or .
///
/// The value.
public void Write(char value) => this.Write((ushort)value);
///
/// Writes a value.
///
/// The value.
public void Write(float value)
{
Span span = this.writer.GetSpan(5);
span[0] = MessagePackCode.Float32;
WriteBigEndian(value, span.Slice(1));
this.writer.Advance(5);
}
///
/// Writes a value.
///
/// The value.
public void Write(double value)
{
Span span = this.writer.GetSpan(9);
span[0] = MessagePackCode.Float64;
WriteBigEndian(value, span.Slice(1));
this.writer.Advance(9);
}
///
/// Writes a using the message code .
///
/// The value to write.
/// Thrown when is true because the old spec does not define a format.
public void Write(DateTime dateTime)
{
if (this.OldSpec)
{
throw new NotSupportedException($"The MsgPack spec does not define a format for {nameof(DateTime)} in {nameof(this.OldSpec)} mode. Turn off {nameof(this.OldSpec)} mode or use NativeDateTimeFormatter.");
}
else
{
// Timestamp spec
// https://github.com/msgpack/msgpack/pull/209
// FixExt4(-1) => seconds | [1970-01-01 00:00:00 UTC, 2106-02-07 06:28:16 UTC) range
// FixExt8(-1) => nanoseconds + seconds | [1970-01-01 00:00:00.000000000 UTC, 2514-05-30 01:53:04.000000000 UTC) range
// Ext8(12,-1) => nanoseconds + seconds | [-584554047284-02-23 16:59:44 UTC, 584554051223-11-09 07:00:16.000000000 UTC) range
// The spec requires UTC. Convert to UTC if we're sure the value was expressed as Local time.
// If it's Unspecified, we want to leave it alone since .NET will change the value when we convert
// and we simply don't know, so we should leave it as-is.
if (dateTime.Kind == DateTimeKind.Local)
{
dateTime = dateTime.ToUniversalTime();
}
var secondsSinceBclEpoch = dateTime.Ticks / TimeSpan.TicksPerSecond;
var seconds = secondsSinceBclEpoch - DateTimeConstants.BclSecondsAtUnixEpoch;
var nanoseconds = (dateTime.Ticks % TimeSpan.TicksPerSecond) * DateTimeConstants.NanosecondsPerTick;
// reference pseudo code.
/*
struct timespec {
long tv_sec; // seconds
long tv_nsec; // nanoseconds
} time;
if ((time.tv_sec >> 34) == 0)
{
uint64_t data64 = (time.tv_nsec << 34) | time.tv_sec;
if (data & 0xffffffff00000000L == 0)
{
// timestamp 32
uint32_t data32 = data64;
serialize(0xd6, -1, data32)
}
else
{
// timestamp 64
serialize(0xd7, -1, data64)
}
}
else
{
// timestamp 96
serialize(0xc7, 12, -1, time.tv_nsec, time.tv_sec)
}
*/
if ((seconds >> 34) == 0)
{
var data64 = unchecked((ulong)((nanoseconds << 34) | seconds));
if ((data64 & 0xffffffff00000000L) == 0)
{
// timestamp 32(seconds in 32-bit unsigned int)
var data32 = (UInt32)data64;
Span span = this.writer.GetSpan(6);
span[0] = MessagePackCode.FixExt4;
span[1] = unchecked((byte)ReservedMessagePackExtensionTypeCode.DateTime);
WriteBigEndian(data32, span.Slice(2));
this.writer.Advance(6);
}
else
{
// timestamp 64(nanoseconds in 30-bit unsigned int | seconds in 34-bit unsigned int)
Span span = this.writer.GetSpan(10);
span[0] = MessagePackCode.FixExt8;
span[1] = unchecked((byte)ReservedMessagePackExtensionTypeCode.DateTime);
WriteBigEndian(data64, span.Slice(2));
this.writer.Advance(10);
}
}
else
{
// timestamp 96( nanoseconds in 32-bit unsigned int | seconds in 64-bit signed int )
Span span = this.writer.GetSpan(15);
span[0] = MessagePackCode.Ext8;
span[1] = 12;
span[2] = unchecked((byte)ReservedMessagePackExtensionTypeCode.DateTime);
WriteBigEndian((uint)nanoseconds, span.Slice(3));
WriteBigEndian(seconds, span.Slice(7));
this.writer.Advance(15);
}
}
}
///
/// Writes a [], prefixed with a length encoded as the smallest fitting from:
/// ,
/// ,
/// ,
/// or if is null.
///
/// The array of bytes to write. May be null.
public void Write(byte[] src)
{
if (src == null)
{
this.WriteNil();
}
else
{
this.Write(src.AsSpan());
}
}
///
/// Writes a span of bytes, prefixed with a length encoded as the smallest fitting from:
/// ,
/// , or
/// .
///
/// The span of bytes to write.
///
/// When is true, the msgpack code used is , or instead.
///
public void Write(ReadOnlySpan src)
{
int length = (int)src.Length;
this.WriteBinHeader(length);
var span = this.writer.GetSpan(length);
src.CopyTo(span);
this.writer.Advance(length);
}
///
/// Writes a sequence of bytes, prefixed with a length encoded as the smallest fitting from:
/// ,
/// , or
/// .
///
/// The span of bytes to write.
///
/// When is true, the msgpack code used is , or instead.
///
public void Write(in ReadOnlySequence src)
{
int length = (int)src.Length;
this.WriteBinHeader(length);
var span = this.writer.GetSpan(length);
src.CopyTo(span);
this.writer.Advance(length);
}
///
/// Writes the header that precedes a raw binary sequence with a length encoded as the smallest fitting from:
/// ,
/// , or
/// .
///
/// The length of bytes that will be written next.
///
///
/// The caller should use or
/// after calling this method to actually write the content.
/// Alternatively a single call to or will take care of the header and content in one call.
///
///
/// When is true, the msgpack code used is , or instead.
///
///
public void WriteBinHeader(int length)
{
if (this.OldSpec)
{
this.WriteStringHeader(length);
return;
}
// When we write the header, we'll ask for all the space we need for the payload as well
// as that may help ensure we only allocate a buffer once.
if (length <= byte.MaxValue)
{
var size = length + 2;
Span span = this.writer.GetSpan(size);
span[0] = MessagePackCode.Bin8;
span[1] = (byte)length;
this.writer.Advance(2);
}
else if (length <= UInt16.MaxValue)
{
var size = length + 3;
Span span = this.writer.GetSpan(size);
span[0] = MessagePackCode.Bin16;
WriteBigEndian((ushort)length, span.Slice(1));
this.writer.Advance(3);
}
else
{
var size = length + 5;
Span span = this.writer.GetSpan(size);
span[0] = MessagePackCode.Bin32;
WriteBigEndian(length, span.Slice(1));
this.writer.Advance(5);
}
}
///
/// Writes out an array of bytes that (may) represent a UTF-8 encoded string, prefixed with the length using one of these message codes:
/// ,
/// ,
/// , or
/// .
///
/// The bytes to write.
public void WriteString(in ReadOnlySequence utf8stringBytes)
{
var length = (int)utf8stringBytes.Length;
this.WriteStringHeader(length);
Span span = this.writer.GetSpan(length);
utf8stringBytes.CopyTo(span);
this.writer.Advance(length);
}
///
/// Writes out an array of bytes that (may) represent a UTF-8 encoded string, prefixed with the length using one of these message codes:
/// ,
/// ,
/// , or
/// .
///
/// The bytes to write.
public void WriteString(ReadOnlySpan utf8stringBytes)
{
var length = utf8stringBytes.Length;
this.WriteStringHeader(length);
Span span = this.writer.GetSpan(length);
utf8stringBytes.CopyTo(span);
this.writer.Advance(length);
}
///
/// Writes out the header that may precede a UTF-8 encoded string, prefixed with the length using one of these message codes:
/// ,
/// ,
/// , or
/// .
///
/// The number of bytes in the string that will follow this header.
///
/// The caller should use or
/// after calling this method to actually write the content.
/// Alternatively a single call to or will take care of the header and content in one call.
///
public void WriteStringHeader(int byteCount)
{
// When we write the header, we'll ask for all the space we need for the payload as well
// as that may help ensure we only allocate a buffer once.
if (byteCount <= MessagePackRange.MaxFixStringLength)
{
Span span = this.writer.GetSpan(byteCount + 1);
span[0] = (byte)(MessagePackCode.MinFixStr | byteCount);
this.writer.Advance(1);
}
else if (byteCount <= byte.MaxValue && !this.OldSpec)
{
Span span = this.writer.GetSpan(byteCount + 2);
span[0] = MessagePackCode.Str8;
span[1] = unchecked((byte)byteCount);
this.writer.Advance(2);
}
else if (byteCount <= ushort.MaxValue)
{
Span span = this.writer.GetSpan(byteCount + 3);
span[0] = MessagePackCode.Str16;
WriteBigEndian((ushort)byteCount, span.Slice(1));
this.writer.Advance(3);
}
else
{
Span span = this.writer.GetSpan(byteCount + 5);
span[0] = MessagePackCode.Str32;
WriteBigEndian(byteCount, span.Slice(1));
this.writer.Advance(5);
}
}
///
/// Writes out a , prefixed with the length using one of these message codes:
/// ,
/// ,
/// ,
/// ,
/// or if the is null.
///
/// The value to write. May be null.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void Write(string value)
{
if (value == null)
{
this.WriteNil();
return;
}
ref byte buffer = ref this.WriteString_PrepareSpan(value.Length, out int bufferSize, out int useOffset);
fixed (char* pValue = value)
fixed (byte* pBuffer = &buffer)
{
int byteCount = StringEncoding.UTF8.GetBytes(pValue, value.Length, pBuffer + useOffset, bufferSize);
this.WriteString_PostEncoding(pBuffer, useOffset, byteCount);
}
}
///
/// Writes out a , prefixed with the length using one of these message codes:
/// ,
/// ,
/// ,
/// .
///
/// The value to write.
public unsafe void Write(ReadOnlySpan value)
{
ref byte buffer = ref this.WriteString_PrepareSpan(value.Length, out int bufferSize, out int useOffset);
fixed (char* pValue = value)
fixed (byte* pBuffer = &buffer)
{
int byteCount = StringEncoding.UTF8.GetBytes(pValue, value.Length, pBuffer + useOffset, bufferSize);
this.WriteString_PostEncoding(pBuffer, useOffset, byteCount);
}
}
///
/// Writes the extension format header, using the smallest one of these codes:
/// ,
/// ,
/// ,
/// ,
/// ,
/// ,
/// , or
/// .
///
/// The extension header.
public void WriteExtensionFormatHeader(ExtensionHeader extensionHeader)
{
int dataLength = (int)extensionHeader.Length;
byte typeCode = unchecked((byte)extensionHeader.TypeCode);
switch (dataLength)
{
case 1:
Span span = this.writer.GetSpan(2);
span[0] = MessagePackCode.FixExt1;
span[1] = unchecked(typeCode);
this.writer.Advance(2);
return;
case 2:
span = this.writer.GetSpan(2);
span[0] = MessagePackCode.FixExt2;
span[1] = unchecked(typeCode);
this.writer.Advance(2);
return;
case 4:
span = this.writer.GetSpan(2);
span[0] = MessagePackCode.FixExt4;
span[1] = unchecked(typeCode);
this.writer.Advance(2);
return;
case 8:
span = this.writer.GetSpan(2);
span[0] = MessagePackCode.FixExt8;
span[1] = unchecked(typeCode);
this.writer.Advance(2);
return;
case 16:
span = this.writer.GetSpan(2);
span[0] = MessagePackCode.FixExt16;
span[1] = unchecked(typeCode);
this.writer.Advance(2);
return;
default:
unchecked
{
if (dataLength <= byte.MaxValue)
{
span = this.writer.GetSpan(dataLength + 3);
span[0] = MessagePackCode.Ext8;
span[1] = unchecked((byte)dataLength);
span[2] = unchecked(typeCode);
this.writer.Advance(3);
}
else if (dataLength <= UInt16.MaxValue)
{
span = this.writer.GetSpan(dataLength + 4);
span[0] = MessagePackCode.Ext16;
WriteBigEndian((ushort)dataLength, span.Slice(1));
span[3] = unchecked(typeCode);
this.writer.Advance(4);
}
else
{
span = this.writer.GetSpan(dataLength + 6);
span[0] = MessagePackCode.Ext32;
WriteBigEndian(dataLength, span.Slice(1));
span[5] = unchecked(typeCode);
this.writer.Advance(6);
}
}
break;
}
}
///
/// Writes an extension format, using the smallest one of these codes:
/// ,
/// ,
/// ,
/// ,
/// ,
/// ,
/// , or
/// .
///
/// The extension data.
public void WriteExtensionFormat(ExtensionResult extensionData)
{
this.WriteExtensionFormatHeader(extensionData.Header);
this.WriteRaw(extensionData.Data);
}
///
/// Gets memory where raw messagepack data can be written.
///
/// The size of the memory block required.
/// The span of memory to write to. This *may* exceed .
///
/// After initializing the resulting memory, always follow up with a call to .
///
/// This is similar in purpose to
/// but provides uninitialized memory for the caller to write to instead of copying initialized memory from elsewhere.
///
///
///
public Span GetSpan(int length) => this.writer.GetSpan(length);
///
/// Commits memory previously returned from as initialized.
///
/// The number of bytes initialized with messagepack data from the previously returned span.
///
public void Advance(int length) => this.writer.Advance(length);
///
/// Writes a 16-bit integer in big endian format.
///
/// The integer.
internal void WriteBigEndian(ushort value)
{
Span span = this.writer.GetSpan(2);
WriteBigEndian(value, span);
this.writer.Advance(2);
}
///
/// Writes a 32-bit integer in big endian format.
///
/// The integer.
internal void WriteBigEndian(uint value)
{
Span span = this.writer.GetSpan(4);
WriteBigEndian(value, span);
this.writer.Advance(4);
}
///
/// Writes a 64-bit integer in big endian format.
///
/// The integer.
internal void WriteBigEndian(ulong value)
{
Span span = this.writer.GetSpan(8);
WriteBigEndian(value, span);
this.writer.Advance(8);
}
internal byte[] FlushAndGetArray()
{
if (this.writer.TryGetUncommittedSpan(out ReadOnlySpan span))
{
return span.ToArray();
}
else
{
if (this.writer.SequenceRental.Value == null)
{
throw new NotSupportedException("This instance was not initialized to support this operation.");
}
this.Flush();
byte[] result = this.writer.SequenceRental.Value.AsReadOnlySequence.ToArray();
this.writer.SequenceRental.Dispose();
return result;
}
}
private static void WriteBigEndian(short value, Span span) => WriteBigEndian(unchecked((ushort)value), span);
private static void WriteBigEndian(int value, Span span) => WriteBigEndian(unchecked((uint)value), span);
private static void WriteBigEndian(long value, Span span) => WriteBigEndian(unchecked((ulong)value), span);
private static void WriteBigEndian(ushort value, Span span)
{
unchecked
{
// Write to highest index first so the JIT skips bounds checks on subsequent writes.
span[1] = (byte)value;
span[0] = (byte)(value >> 8);
}
}
private static unsafe void WriteBigEndian(ushort value, byte* span)
{
unchecked
{
span[0] = (byte)(value >> 8);
span[1] = (byte)value;
}
}
private static void WriteBigEndian(uint value, Span span)
{
unchecked
{
// Write to highest index first so the JIT skips bounds checks on subsequent writes.
span[3] = (byte)value;
span[2] = (byte)(value >> 8);
span[1] = (byte)(value >> 16);
span[0] = (byte)(value >> 24);
}
}
private static unsafe void WriteBigEndian(uint value, byte* span)
{
unchecked
{
span[0] = (byte)(value >> 24);
span[1] = (byte)(value >> 16);
span[2] = (byte)(value >> 8);
span[3] = (byte)value;
}
}
private static void WriteBigEndian(ulong value, Span span)
{
unchecked
{
// Write to highest index first so the JIT skips bounds checks on subsequent writes.
span[7] = (byte)value;
span[6] = (byte)(value >> 8);
span[5] = (byte)(value >> 16);
span[4] = (byte)(value >> 24);
span[3] = (byte)(value >> 32);
span[2] = (byte)(value >> 40);
span[1] = (byte)(value >> 48);
span[0] = (byte)(value >> 56);
}
}
private static unsafe void WriteBigEndian(float value, Span span) => WriteBigEndian(*(int*)&value, span);
private static unsafe void WriteBigEndian(double value, Span span) => WriteBigEndian(*(long*)&value, span);
///
/// Estimates the length of the header required for a given string.
///
/// The length of the string to be written, in characters.
/// Receives the guaranteed length of the returned buffer.
/// Receives the offset within the returned buffer to write the encoded string to.
///
/// A reference to the first byte in the buffer.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ref byte WriteString_PrepareSpan(int characterLength, out int bufferSize, out int encodedBytesOffset)
{
// MaxByteCount -> WritePrefix -> GetBytes has some overheads of `MaxByteCount`
// solves heuristic length check
// ensure buffer by MaxByteCount(faster than GetByteCount)
bufferSize = StringEncoding.UTF8.GetMaxByteCount(characterLength) + 5;
ref byte buffer = ref this.writer.GetPointer(bufferSize);
int useOffset;
if (characterLength <= MessagePackRange.MaxFixStringLength)
{
useOffset = 1;
}
else if (characterLength <= byte.MaxValue && !this.OldSpec)
{
useOffset = 2;
}
else if (characterLength <= ushort.MaxValue)
{
useOffset = 3;
}
else
{
useOffset = 5;
}
encodedBytesOffset = useOffset;
return ref buffer;
}
///
/// Finalizes an encoding of a string.
///
/// A pointer obtained from a prior call to .
/// The offset obtained from a prior call to .
/// The number of bytes used to actually encode the string.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe void WriteString_PostEncoding(byte* pBuffer, int estimatedOffset, int byteCount)
{
// move body and write prefix
if (byteCount <= MessagePackRange.MaxFixStringLength)
{
if (estimatedOffset != 1)
{
MemoryCopy(pBuffer + estimatedOffset, pBuffer + 1, byteCount, byteCount);
}
pBuffer[0] = (byte)(MessagePackCode.MinFixStr | byteCount);
this.writer.Advance(byteCount + 1);
}
else if (byteCount <= byte.MaxValue && !this.OldSpec)
{
if (estimatedOffset != 2)
{
MemoryCopy(pBuffer + estimatedOffset, pBuffer + 2, byteCount, byteCount);
}
pBuffer[0] = MessagePackCode.Str8;
pBuffer[1] = unchecked((byte)byteCount);
this.writer.Advance(byteCount + 2);
}
else if (byteCount <= ushort.MaxValue)
{
if (estimatedOffset != 3)
{
MemoryCopy(pBuffer + estimatedOffset, pBuffer + 3, byteCount, byteCount);
}
pBuffer[0] = MessagePackCode.Str16;
WriteBigEndian((ushort)byteCount, pBuffer + 1);
this.writer.Advance(byteCount + 3);
}
else
{
if (estimatedOffset != 5)
{
MemoryCopy(pBuffer + estimatedOffset, pBuffer + 5, byteCount, byteCount);
}
pBuffer[0] = MessagePackCode.Str32;
WriteBigEndian((uint)byteCount, pBuffer + 1);
this.writer.Advance(byteCount + 5);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe void MemoryCopy(void* source, void* destination, long destinationSizeInBytes, long sourceBytesToCopy)
{
#pragma warning disable 0162
if (Utilities.IsMono)
{
// mono does not guarantee overlapped memcpy so for Unity and NETSTANDARD use slow path.
// https://github.com/neuecc/MessagePack-CSharp/issues/562
var buffer = ArrayPool.Shared.Rent((int)sourceBytesToCopy);
try
{
fixed (byte* p = buffer)
{
Buffer.MemoryCopy(source, p, sourceBytesToCopy, sourceBytesToCopy);
Buffer.MemoryCopy(p, destination, destinationSizeInBytes, sourceBytesToCopy);
}
}
finally
{
ArrayPool.Shared.Return(buffer);
}
}
else
{
Buffer.MemoryCopy(source, destination, destinationSizeInBytes, sourceBytesToCopy);
}
#pragma warning restore 0162
}
}
}