// 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.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using MessagePack.Internal;
namespace MessagePack
{
///
/// A primitive types reader for the MessagePack format.
///
///
/// The MessagePack spec..
///
/// Thrown when reading methods fail due to invalid data.
/// Thrown by reading methods when there are not enough bytes to read the required value.
#if MESSAGEPACK_INTERNAL
internal
#else
public
#endif
ref partial struct MessagePackReader
{
///
/// The reader over the sequence.
///
private SequenceReader reader;
///
/// Initializes a new instance of the struct.
///
/// The buffer to read from.
public MessagePackReader(ReadOnlyMemory memory)
: this()
{
this.reader = new SequenceReader(memory);
this.Depth = 0;
}
///
/// Initializes a new instance of the struct.
///
/// The sequence to read from.
public MessagePackReader(in ReadOnlySequence readOnlySequence)
: this()
{
this.reader = new SequenceReader(readOnlySequence);
this.Depth = 0;
}
///
/// Gets or sets the cancellation token for this deserialization operation.
///
public CancellationToken CancellationToken { get; set; }
///
/// Gets or sets the present depth of the object graph being deserialized.
///
public int Depth { get; set; }
///
/// Gets the originally supplied to the constructor.
///
public ReadOnlySequence Sequence => this.reader.Sequence;
///
/// Gets the current position of the reader within .
///
public SequencePosition Position => this.reader.Position;
///
/// Gets the number of bytes consumed by the reader.
///
public long Consumed => this.reader.Consumed;
///
/// Gets a value indicating whether the reader is at the end of the sequence.
///
public bool End => this.reader.End;
///
/// Gets a value indicating whether the reader position is pointing at a nil value.
///
/// Thrown if the end of the sequence provided to the constructor is reached before the expected end of the data.
public bool IsNil => this.NextCode == MessagePackCode.Nil;
///
/// Gets the next message pack type to be read.
///
public MessagePackType NextMessagePackType => MessagePackCode.ToMessagePackType(this.NextCode);
///
/// Gets the type of the next MessagePack block.
///
/// Thrown if the end of the sequence provided to the constructor is reached before the expected end of the data.
///
/// See for valid message pack codes and ranges.
///
public byte NextCode
{
get
{
ThrowInsufficientBufferUnless(this.reader.TryPeek(out byte code));
return code;
}
}
///
/// Initializes a new instance of the struct,
/// with the same settings as this one, but with its own buffer to read from.
///
/// The sequence to read from.
/// The new reader.
public MessagePackReader Clone(in ReadOnlySequence readOnlySequence) => new MessagePackReader(readOnlySequence)
{
CancellationToken = this.CancellationToken,
Depth = this.Depth,
};
///
/// Creates a new at this reader's current position.
/// The two readers may then be used independently without impacting each other.
///
/// A new .
///
/// Since this is a struct, copying it completely is as simple as returning itself
/// from a property that isn't a "ref return" property.
///
public MessagePackReader CreatePeekReader() => this;
///
/// Advances the reader to the next MessagePack primitive to be read.
///
///
/// The entire primitive is skipped, including content of maps or arrays, or any other type with payloads.
/// To get the raw MessagePack sequence that was skipped, use instead.
///
public void Skip() => ThrowInsufficientBufferUnless(this.TrySkip());
///
/// Advances the reader to the next MessagePack primitive to be read.
///
/// true if the entire structure beginning at the current is found in the ; false otherwise.
///
/// The entire primitive is skipped, including content of maps or arrays, or any other type with payloads.
/// To get the raw MessagePack sequence that was skipped, use instead.
/// WARNING: when false is returned, the position of the reader is undefined.
///
internal bool TrySkip()
{
if (this.reader.Remaining == 0)
{
return false;
}
byte code = this.NextCode;
switch (code)
{
case MessagePackCode.Nil:
case MessagePackCode.True:
case MessagePackCode.False:
return this.reader.TryAdvance(1);
case MessagePackCode.Int8:
case MessagePackCode.UInt8:
return this.reader.TryAdvance(2);
case MessagePackCode.Int16:
case MessagePackCode.UInt16:
return this.reader.TryAdvance(3);
case MessagePackCode.Int32:
case MessagePackCode.UInt32:
case MessagePackCode.Float32:
return this.reader.TryAdvance(5);
case MessagePackCode.Int64:
case MessagePackCode.UInt64:
case MessagePackCode.Float64:
return this.reader.TryAdvance(9);
case MessagePackCode.Map16:
case MessagePackCode.Map32:
return this.TrySkipNextMap();
case MessagePackCode.Array16:
case MessagePackCode.Array32:
return this.TrySkipNextArray();
case MessagePackCode.Str8:
case MessagePackCode.Str16:
case MessagePackCode.Str32:
return this.TryGetStringLengthInBytes(out int length) && this.reader.TryAdvance(length);
case MessagePackCode.Bin8:
case MessagePackCode.Bin16:
case MessagePackCode.Bin32:
return this.TryGetBytesLength(out length) && this.reader.TryAdvance(length);
case MessagePackCode.FixExt1:
case MessagePackCode.FixExt2:
case MessagePackCode.FixExt4:
case MessagePackCode.FixExt8:
case MessagePackCode.FixExt16:
case MessagePackCode.Ext8:
case MessagePackCode.Ext16:
case MessagePackCode.Ext32:
return this.TryReadExtensionFormatHeader(out ExtensionHeader header) && this.reader.TryAdvance(header.Length);
default:
if ((code >= MessagePackCode.MinNegativeFixInt && code <= MessagePackCode.MaxNegativeFixInt) ||
(code >= MessagePackCode.MinFixInt && code <= MessagePackCode.MaxFixInt))
{
return this.reader.TryAdvance(1);
}
if (code >= MessagePackCode.MinFixMap && code <= MessagePackCode.MaxFixMap)
{
return this.TrySkipNextMap();
}
if (code >= MessagePackCode.MinFixArray && code <= MessagePackCode.MaxFixArray)
{
return this.TrySkipNextArray();
}
if (code >= MessagePackCode.MinFixStr && code <= MessagePackCode.MaxFixStr)
{
return this.TryGetStringLengthInBytes(out length) && this.reader.TryAdvance(length);
}
// We don't actually expect to ever hit this point, since every code is supported.
Debug.Fail("Missing handler for code: " + code);
throw ThrowInvalidCode(code);
}
}
///
/// Reads a value.
///
/// A nil value.
public Nil ReadNil()
{
ThrowInsufficientBufferUnless(this.reader.TryRead(out byte code));
return code == MessagePackCode.Nil
? Nil.Default
: throw ThrowInvalidCode(code);
}
///
/// Reads nil if it is the next token.
///
/// true if the next token was nil; false otherwise.
/// Thrown if the end of the sequence provided to the constructor is reached before the expected end of the data.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryReadNil()
{
if (this.NextCode == MessagePackCode.Nil)
{
this.reader.Advance(1);
return true;
}
return false;
}
///
/// Reads a sequence of bytes without any decoding.
///
/// The number of bytes to read.
/// The sequence of bytes read.
public ReadOnlySequence ReadRaw(long length)
{
try
{
ReadOnlySequence result = this.reader.Sequence.Slice(this.reader.Position, length);
this.reader.Advance(length);
return result;
}
catch (ArgumentOutOfRangeException ex)
{
throw ThrowNotEnoughBytesException(ex);
}
}
///
/// Reads the next MessagePack primitive.
///
/// The raw MessagePack sequence.
///
/// The entire primitive is read, including content of maps or arrays, or any other type with payloads.
///
public ReadOnlySequence ReadRaw()
{
SequencePosition initialPosition = this.Position;
this.Skip();
return this.Sequence.Slice(initialPosition, this.Position);
}
///
/// Read an array header from
/// ,
/// , or
/// some built-in code between and .
///
///
/// Thrown if the header cannot be read in the bytes left in the
/// or if it is clear that there are insufficient bytes remaining after the header to include all the elements the header claims to be there.
///
/// Thrown if a code other than an array header is encountered.
public int ReadArrayHeader()
{
ThrowInsufficientBufferUnless(this.TryReadArrayHeader(out int count));
// Protect against corrupted or mischievious data that may lead to allocating way too much memory.
// We allow for each primitive to be the minimal 1 byte in size.
// Formatters that know each element is larger can optionally add a stronger check.
ThrowInsufficientBufferUnless(this.reader.Remaining >= count);
return count;
}
///
/// Reads an array header from
/// ,
/// , or
/// some built-in code between and
/// if there is sufficient buffer to read it.
///
/// Receives the number of elements in the array if the entire array header could be read.
/// true if there was sufficient buffer and an array header was found; false if the buffer incompletely describes an array header.
/// Thrown if a code other than an array header is encountered.
///
/// When this method returns false the position of the reader is left in an undefined position.
/// The caller is expected to recreate the reader (presumably with a longer sequence to read from) before continuing.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryReadArrayHeader(out int count)
{
count = -1;
if (!this.reader.TryRead(out byte code))
{
return false;
}
switch (code)
{
case MessagePackCode.Array16:
if (!this.reader.TryReadBigEndian(out short shortValue))
{
return false;
}
count = unchecked((ushort)shortValue);
break;
case MessagePackCode.Array32:
if (!this.reader.TryReadBigEndian(out int intValue))
{
return false;
}
count = intValue;
break;
default:
if (code >= MessagePackCode.MinFixArray && code <= MessagePackCode.MaxFixArray)
{
count = code & 0xF;
break;
}
throw ThrowInvalidCode(code);
}
return true;
}
///
/// Read a map header from
/// ,
/// , or
/// some built-in code between and .
///
/// The number of key=value pairs in the map.
///
/// Thrown if the header cannot be read in the bytes left in the
/// or if it is clear that there are insufficient bytes remaining after the header to include all the elements the header claims to be there.
///
/// Thrown if a code other than an map header is encountered.
public int ReadMapHeader()
{
ThrowInsufficientBufferUnless(this.TryReadMapHeader(out int count));
// Protect against corrupted or mischievious data that may lead to allocating way too much memory.
// We allow for each primitive to be the minimal 1 byte in size, and we have a key=value map, so that's 2 bytes.
// Formatters that know each element is larger can optionally add a stronger check.
ThrowInsufficientBufferUnless(this.reader.Remaining >= count * 2);
return count;
}
///
/// Reads a map header from
/// ,
/// , or
/// some built-in code between and
/// if there is sufficient buffer to read it.
///
/// Receives the number of key=value pairs in the map if the entire map header can be read.
/// true if there was sufficient buffer and a map header was found; false if the buffer incompletely describes an map header.
/// Thrown if a code other than an map header is encountered.
///
/// When this method returns false the position of the reader is left in an undefined position.
/// The caller is expected to recreate the reader (presumably with a longer sequence to read from) before continuing.
///
public bool TryReadMapHeader(out int count)
{
count = -1;
if (!this.reader.TryRead(out byte code))
{
return false;
}
switch (code)
{
case MessagePackCode.Map16:
if (!this.reader.TryReadBigEndian(out short shortValue))
{
return false;
}
count = unchecked((ushort)shortValue);
break;
case MessagePackCode.Map32:
if (!this.reader.TryReadBigEndian(out int intValue))
{
return false;
}
count = intValue;
break;
default:
if (code >= MessagePackCode.MinFixMap && code <= MessagePackCode.MaxFixMap)
{
count = (byte)(code & 0xF);
break;
}
throw ThrowInvalidCode(code);
}
return true;
}
///
/// Reads a boolean value from either a or .
///
/// The value.
public bool ReadBoolean()
{
ThrowInsufficientBufferUnless(this.reader.TryRead(out byte code));
switch (code)
{
case MessagePackCode.True:
return true;
case MessagePackCode.False:
return false;
default:
throw ThrowInvalidCode(code);
}
}
///
/// Reads a from any of:
/// ,
/// ,
/// or anything between and .
///
/// A character.
public char ReadChar() => (char)this.ReadUInt16();
///
/// Reads an value from any value encoded with:
/// ,
/// ,
/// ,
/// ,
/// ,
/// ,
/// ,
/// ,
/// ,
/// or some value between and ,
/// or some value between and .
///
/// The value.
public unsafe float ReadSingle()
{
ThrowInsufficientBufferUnless(this.reader.TryRead(out byte code));
switch (code)
{
case MessagePackCode.Float32:
ThrowInsufficientBufferUnless(this.reader.TryReadBigEndian(out float floatValue));
return floatValue;
case MessagePackCode.Float64:
ThrowInsufficientBufferUnless(this.reader.TryReadBigEndian(out double doubleValue));
return (float)doubleValue;
case MessagePackCode.Int8:
ThrowInsufficientBufferUnless(this.reader.TryRead(out sbyte sbyteValue));
return sbyteValue;
case MessagePackCode.Int16:
ThrowInsufficientBufferUnless(this.reader.TryReadBigEndian(out short shortValue));
return shortValue;
case MessagePackCode.Int32:
ThrowInsufficientBufferUnless(this.reader.TryReadBigEndian(out int intValue));
return intValue;
case MessagePackCode.Int64:
ThrowInsufficientBufferUnless(this.reader.TryReadBigEndian(out long longValue));
return longValue;
case MessagePackCode.UInt8:
ThrowInsufficientBufferUnless(this.reader.TryRead(out byte byteValue));
return byteValue;
case MessagePackCode.UInt16:
ThrowInsufficientBufferUnless(this.reader.TryReadBigEndian(out ushort ushortValue));
return ushortValue;
case MessagePackCode.UInt32:
ThrowInsufficientBufferUnless(this.reader.TryReadBigEndian(out uint uintValue));
return uintValue;
case MessagePackCode.UInt64:
ThrowInsufficientBufferUnless(this.reader.TryReadBigEndian(out ulong ulongValue));
return ulongValue;
default:
if (code >= MessagePackCode.MinNegativeFixInt && code <= MessagePackCode.MaxNegativeFixInt)
{
return unchecked((sbyte)code);
}
else if (code >= MessagePackCode.MinFixInt && code <= MessagePackCode.MaxFixInt)
{
return code;
}
throw ThrowInvalidCode(code);
}
}
///
/// Reads an value from any value encoded with:
/// ,
/// ,
/// ,
/// ,
/// ,
/// ,
/// ,
/// ,
/// ,
/// ,
/// or some value between and ,
/// or some value between and .
///
/// The value.
public unsafe double ReadDouble()
{
ThrowInsufficientBufferUnless(this.reader.TryRead(out byte code));
switch (code)
{
case MessagePackCode.Float64:
ThrowInsufficientBufferUnless(this.reader.TryReadBigEndian(out double doubleValue));
return doubleValue;
case MessagePackCode.Float32:
ThrowInsufficientBufferUnless(this.reader.TryReadBigEndian(out float floatValue));
return floatValue;
case MessagePackCode.Int8:
ThrowInsufficientBufferUnless(this.reader.TryRead(out byte byteValue));
return unchecked((sbyte)byteValue);
case MessagePackCode.Int16:
ThrowInsufficientBufferUnless(this.reader.TryReadBigEndian(out short shortValue));
return shortValue;
case MessagePackCode.Int32:
ThrowInsufficientBufferUnless(this.reader.TryReadBigEndian(out int intValue));
return intValue;
case MessagePackCode.Int64:
ThrowInsufficientBufferUnless(this.reader.TryReadBigEndian(out long longValue));
return longValue;
case MessagePackCode.UInt8:
ThrowInsufficientBufferUnless(this.reader.TryRead(out byteValue));
return byteValue;
case MessagePackCode.UInt16:
ThrowInsufficientBufferUnless(this.reader.TryReadBigEndian(out shortValue));
return unchecked((ushort)shortValue);
case MessagePackCode.UInt32:
ThrowInsufficientBufferUnless(this.reader.TryReadBigEndian(out intValue));
return unchecked((uint)intValue);
case MessagePackCode.UInt64:
ThrowInsufficientBufferUnless(this.reader.TryReadBigEndian(out longValue));
return unchecked((ulong)longValue);
default:
if (code >= MessagePackCode.MinNegativeFixInt && code <= MessagePackCode.MaxNegativeFixInt)
{
return unchecked((sbyte)code);
}
else if (code >= MessagePackCode.MinFixInt && code <= MessagePackCode.MaxFixInt)
{
return code;
}
throw ThrowInvalidCode(code);
}
}
///
/// Reads a from a value encoded with
/// ,
/// , or
/// .
/// Expects extension type code .
///
/// The value.
public DateTime ReadDateTime() => this.ReadDateTime(this.ReadExtensionFormatHeader());
///
/// Reads a from a value encoded with
/// ,
/// ,
/// .
/// Expects extension type code .
///
/// The extension header that was already read.
/// The value.
public DateTime ReadDateTime(ExtensionHeader header)
{
if (header.TypeCode != ReservedMessagePackExtensionTypeCode.DateTime)
{
throw new MessagePackSerializationException(string.Format("Extension TypeCode is invalid. typeCode: {0}", header.TypeCode));
}
switch (header.Length)
{
case 4:
ThrowInsufficientBufferUnless(this.reader.TryReadBigEndian(out int intValue));
return DateTimeConstants.UnixEpoch.AddSeconds(unchecked((uint)intValue));
case 8:
ThrowInsufficientBufferUnless(this.reader.TryReadBigEndian(out long longValue));
ulong ulongValue = unchecked((ulong)longValue);
long nanoseconds = (long)(ulongValue >> 34);
ulong seconds = ulongValue & 0x00000003ffffffffL;
return DateTimeConstants.UnixEpoch.AddSeconds(seconds).AddTicks(nanoseconds / DateTimeConstants.NanosecondsPerTick);
case 12:
ThrowInsufficientBufferUnless(this.reader.TryReadBigEndian(out intValue));
nanoseconds = unchecked((uint)intValue);
ThrowInsufficientBufferUnless(this.reader.TryReadBigEndian(out longValue));
return DateTimeConstants.UnixEpoch.AddSeconds(longValue).AddTicks(nanoseconds / DateTimeConstants.NanosecondsPerTick);
default:
throw new MessagePackSerializationException($"Length of extension was {header.Length}. Either 4 or 8 were expected.");
}
}
///
/// Reads a span of bytes, whose length is determined by a header of one of these types:
/// ,
/// ,
/// ,
/// or to support OldSpec compatibility:
/// ,
/// ,
/// or something between and .
///
///
/// A sequence of bytes, or null if the read token is .
/// The data is a slice from the original sequence passed to this reader's constructor.
///
public ReadOnlySequence? ReadBytes()
{
if (this.TryReadNil())
{
return null;
}
int length = this.GetBytesLength();
ThrowInsufficientBufferUnless(this.reader.Remaining >= length);
ReadOnlySequence result = this.reader.Sequence.Slice(this.reader.Position, length);
this.reader.Advance(length);
return result;
}
///
/// Reads a string of bytes, whose length is determined by a header of one of these types:
/// ,
/// ,
/// ,
/// or a code between and .
///
///
/// The sequence of bytes, or null if the read token is .
/// The data is a slice from the original sequence passed to this reader's constructor.
///
public ReadOnlySequence? ReadStringSequence()
{
if (this.TryReadNil())
{
return null;
}
int length = this.GetStringLengthInBytes();
ThrowInsufficientBufferUnless(this.reader.Remaining >= length);
ReadOnlySequence result = this.reader.Sequence.Slice(this.reader.Position, length);
this.reader.Advance(length);
return result;
}
///
/// Reads a string of bytes, whose length is determined by a header of one of these types:
/// ,
/// ,
/// ,
/// or a code between and .
///
/// Receives the span to the string.
///
/// true if the string is contiguous in memory such that it could be set as a single span.
/// false if the read token is or the string is not in a contiguous span.
///
///
/// Callers should generally be prepared for a false result and failover to calling
/// which can represent a null result and handle strings that are not contiguous in memory.
///
public bool TryReadStringSpan(out ReadOnlySpan span)
{
if (this.IsNil)
{
span = default;
return false;
}
long oldPosition = this.reader.Consumed;
int length = this.GetStringLengthInBytes();
ThrowInsufficientBufferUnless(this.reader.Remaining >= length);
if (this.reader.CurrentSpanIndex + length <= this.reader.CurrentSpan.Length)
{
span = this.reader.CurrentSpan.Slice(this.reader.CurrentSpanIndex, length);
this.reader.Advance(length);
return true;
}
else
{
this.reader.Rewind(this.reader.Consumed - oldPosition);
span = default;
return false;
}
}
///
/// Reads a string, whose length is determined by a header of one of these types:
/// ,
/// ,
/// ,
/// or a code between and .
///
/// A string, or null if the current msgpack token is .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadString()
{
if (this.TryReadNil())
{
return null;
}
int byteLength = this.GetStringLengthInBytes();
ReadOnlySpan unreadSpan = this.reader.UnreadSpan;
//UnityEngine.Debug.Log(reader.CurrentSpan[0]);
//UnityEngine.Debug.Log(unreadSpan[0]);
if (unreadSpan.Length >= byteLength)
{
// Fast path: all bytes to decode appear in the same span.
string value = StringEncoding.UTF8.GetString(unreadSpan.Slice(0, byteLength));
this.reader.Advance(byteLength);
return value;
}
else
{
return this.ReadStringSlow(byteLength);
}
}
///
/// Reads an extension format header, based on one of these codes:
/// ,
/// ,
/// ,
/// ,
/// ,
/// ,
/// , or
/// .
///
/// The extension header.
///
/// Thrown if the header cannot be read in the bytes left in the
/// or if it is clear that there are insufficient bytes remaining after the header to include all the bytes the header claims to be there.
///
/// Thrown if a code other than an extension format header is encountered.
public ExtensionHeader ReadExtensionFormatHeader()
{
ThrowInsufficientBufferUnless(this.TryReadExtensionFormatHeader(out ExtensionHeader header));
// Protect against corrupted or mischievious data that may lead to allocating way too much memory.
ThrowInsufficientBufferUnless(this.reader.Remaining >= header.Length);
return header;
}
///
/// Reads an extension format header, based on one of these codes:
/// ,
/// ,
/// ,
/// ,
/// ,
/// ,
/// , or
///
/// if there is sufficient buffer to read it.
///
/// Receives the extension header if the remaining bytes in the fully describe the header.
/// The number of key=value pairs in the map.
/// Thrown if a code other than an extension format header is encountered.
///
/// When this method returns false the position of the reader is left in an undefined position.
/// The caller is expected to recreate the reader (presumably with a longer sequence to read from) before continuing.
///
public bool TryReadExtensionFormatHeader(out ExtensionHeader extensionHeader)
{
extensionHeader = default;
if (!this.reader.TryRead(out byte code))
{
return false;
}
uint length;
switch (code)
{
case MessagePackCode.FixExt1:
length = 1;
break;
case MessagePackCode.FixExt2:
length = 2;
break;
case MessagePackCode.FixExt4:
length = 4;
break;
case MessagePackCode.FixExt8:
length = 8;
break;
case MessagePackCode.FixExt16:
length = 16;
break;
case MessagePackCode.Ext8:
if (!this.reader.TryRead(out byte byteLength))
{
return false;
}
length = byteLength;
break;
case MessagePackCode.Ext16:
if (!this.reader.TryReadBigEndian(out short shortLength))
{
return false;
}
length = unchecked((ushort)shortLength);
break;
case MessagePackCode.Ext32:
if (!this.reader.TryReadBigEndian(out int intLength))
{
return false;
}
length = unchecked((uint)intLength);
break;
default:
throw ThrowInvalidCode(code);
}
if (!this.reader.TryRead(out byte typeCode))
{
return false;
}
extensionHeader = new ExtensionHeader(unchecked((sbyte)typeCode), length);
return true;
}
///
/// Reads an extension format header and data, based on one of these codes:
/// ,
/// ,
/// ,
/// ,
/// ,
/// ,
/// , or
/// .
///
///
/// The extension format.
/// The data is a slice from the original sequence passed to this reader's constructor.
///
public ExtensionResult ReadExtensionFormat()
{
ExtensionHeader header = this.ReadExtensionFormatHeader();
try
{
ReadOnlySequence data = this.reader.Sequence.Slice(this.reader.Position, header.Length);
this.reader.Advance(header.Length);
return new ExtensionResult(header.TypeCode, data);
}
catch (ArgumentOutOfRangeException ex)
{
throw ThrowNotEnoughBytesException(ex);
}
}
///
/// Throws an exception indicating that there aren't enough bytes remaining in the buffer to store
/// the promised data.
///
private static EndOfStreamException ThrowNotEnoughBytesException() => throw new EndOfStreamException();
///
/// Throws an exception indicating that there aren't enough bytes remaining in the buffer to store
/// the promised data.
///
private static EndOfStreamException ThrowNotEnoughBytesException(Exception innerException) => throw new EndOfStreamException(new EndOfStreamException().Message, innerException);
///
/// Throws an explaining an unexpected code was encountered.
///
/// The code that was encountered.
/// Nothing. This method always throws.
private static Exception ThrowInvalidCode(byte code)
{
throw new MessagePackSerializationException(string.Format("Unexpected msgpack code {0} ({1}) encountered.", code, MessagePackCode.ToFormatName(code)));
}
///
/// Throws if a condition is false.
///
/// A boolean value.
/// Thrown if is false.
private static void ThrowInsufficientBufferUnless(bool condition)
{
if (!condition)
{
ThrowNotEnoughBytesException();
}
}
private int GetBytesLength()
{
ThrowInsufficientBufferUnless(this.TryGetBytesLength(out int length));
return length;
}
private bool TryGetBytesLength(out int length)
{
if (!this.reader.TryRead(out byte code))
{
length = 0;
return false;
}
// In OldSpec mode, Bin didn't exist, so Str was used. Str8 didn't exist either.
switch (code)
{
case MessagePackCode.Bin8:
if (this.reader.TryRead(out byte byteLength))
{
length = byteLength;
return true;
}
break;
case MessagePackCode.Bin16:
case MessagePackCode.Str16: // OldSpec compatibility
if (this.reader.TryReadBigEndian(out short shortLength))
{
length = unchecked((ushort)shortLength);
return true;
}
break;
case MessagePackCode.Bin32:
case MessagePackCode.Str32: // OldSpec compatibility
if (this.reader.TryReadBigEndian(out length))
{
return true;
}
break;
default:
// OldSpec compatibility
if (code >= MessagePackCode.MinFixStr && code <= MessagePackCode.MaxFixStr)
{
length = code & 0x1F;
return true;
}
throw ThrowInvalidCode(code);
}
length = 0;
return false;
}
///
/// Gets the length of the next string.
///
/// Receives the length of the next string, if there were enough bytes to read it.
/// true if there were enough bytes to read the length of the next string; false otherwise.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool TryGetStringLengthInBytes(out int length)
{
if (!this.reader.TryRead(out byte code))
{
length = 0;
return false;
}
if (code >= MessagePackCode.MinFixStr && code <= MessagePackCode.MaxFixStr)
{
length = code & 0x1F;
return true;
}
return this.TryGetStringLengthInBytesSlow(code, out length);
}
///
/// Gets the length of the next string.
///
/// The length of the next string.
private int GetStringLengthInBytes()
{
ThrowInsufficientBufferUnless(this.TryGetStringLengthInBytes(out int length));
return length;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool TryGetStringLengthInBytesSlow(byte code, out int length)
{
switch (code)
{
case MessagePackCode.Str8:
if (this.reader.TryRead(out byte byteValue))
{
length = byteValue;
return true;
}
break;
case MessagePackCode.Str16:
if (this.reader.TryReadBigEndian(out short shortValue))
{
length = unchecked((ushort)shortValue);
return true;
}
break;
case MessagePackCode.Str32:
if (this.reader.TryReadBigEndian(out int intValue))
{
length = intValue;
return true;
}
break;
default:
if (code >= MessagePackCode.MinFixStr && code <= MessagePackCode.MaxFixStr)
{
length = code & 0x1F;
return true;
}
throw ThrowInvalidCode(code);
}
length = 0;
return false;
}
///
/// Reads a string assuming that it is spread across multiple spans in the .
///
/// The length of the string to be decoded, in bytes.
/// The decoded string.
private string ReadStringSlow(int byteLength)
{
ThrowInsufficientBufferUnless(this.reader.Remaining >= byteLength);
// We need to decode bytes incrementally across multiple spans.
int maxCharLength = StringEncoding.UTF8.GetMaxCharCount(byteLength);
char[] charArray = ArrayPool.Shared.Rent(maxCharLength);
System.Text.Decoder decoder = StringEncoding.UTF8.GetDecoder();
int remainingByteLength = byteLength;
int initializedChars = 0;
while (remainingByteLength > 0)
{
int bytesRead = Math.Min(remainingByteLength, this.reader.UnreadSpan.Length);
remainingByteLength -= bytesRead;
bool flush = remainingByteLength == 0;
#if NETCOREAPP
initializedChars += decoder.GetChars(this.reader.UnreadSpan.Slice(0, bytesRead), charArray.AsSpan(initializedChars), flush);
#else
unsafe
{
fixed (byte* pUnreadSpan = this.reader.UnreadSpan)
fixed (char* pCharArray = &charArray[initializedChars])
{
initializedChars += decoder.GetChars(pUnreadSpan, bytesRead, pCharArray, charArray.Length - initializedChars, flush);
}
}
#endif
this.reader.Advance(bytesRead);
}
string value = new string(charArray, 0, initializedChars);
ArrayPool.Shared.Return(charArray);
return value;
}
private bool TrySkipNextArray() => this.TryReadArrayHeader(out int count) && this.TrySkip(count);
private bool TrySkipNextMap() => this.TryReadMapHeader(out int count) && this.TrySkip(count * 2);
private bool TrySkip(int count)
{
for (int i = 0; i < count; i++)
{
if (!this.TrySkip())
{
return false;
}
}
return true;
}
}
}