// 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 } } }