ZK_Framework/Assets/Plugins/MessagePack/MessagePackReader.cs

1150 lines
49 KiB
C#

// 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
{
/// <summary>
/// A primitive types reader for the MessagePack format.
/// </summary>
/// <remarks>
/// <see href="https://github.com/msgpack/msgpack/blob/master/spec.md">The MessagePack spec.</see>.
/// </remarks>
/// <exception cref="MessagePackSerializationException">Thrown when reading methods fail due to invalid data.</exception>
/// <exception cref="EndOfStreamException">Thrown by reading methods when there are not enough bytes to read the required value.</exception>
#if MESSAGEPACK_INTERNAL
internal
#else
public
#endif
ref partial struct MessagePackReader
{
/// <summary>
/// The reader over the sequence.
/// </summary>
private SequenceReader<byte> reader;
/// <summary>
/// Initializes a new instance of the <see cref="MessagePackReader"/> struct.
/// </summary>
/// <param name="memory">The buffer to read from.</param>
public MessagePackReader(ReadOnlyMemory<byte> memory)
: this()
{
this.reader = new SequenceReader<byte>(memory);
this.Depth = 0;
}
/// <summary>
/// Initializes a new instance of the <see cref="MessagePackReader"/> struct.
/// </summary>
/// <param name="readOnlySequence">The sequence to read from.</param>
public MessagePackReader(in ReadOnlySequence<byte> readOnlySequence)
: this()
{
this.reader = new SequenceReader<byte>(readOnlySequence);
this.Depth = 0;
}
/// <summary>
/// Gets or sets the cancellation token for this deserialization operation.
/// </summary>
public CancellationToken CancellationToken { get; set; }
/// <summary>
/// Gets or sets the present depth of the object graph being deserialized.
/// </summary>
public int Depth { get; set; }
/// <summary>
/// Gets the <see cref="ReadOnlySequence{T}"/> originally supplied to the constructor.
/// </summary>
public ReadOnlySequence<byte> Sequence => this.reader.Sequence;
/// <summary>
/// Gets the current position of the reader within <see cref="Sequence"/>.
/// </summary>
public SequencePosition Position => this.reader.Position;
/// <summary>
/// Gets the number of bytes consumed by the reader.
/// </summary>
public long Consumed => this.reader.Consumed;
/// <summary>
/// Gets a value indicating whether the reader is at the end of the sequence.
/// </summary>
public bool End => this.reader.End;
/// <summary>
/// Gets a value indicating whether the reader position is pointing at a nil value.
/// </summary>
/// <exception cref="EndOfStreamException">Thrown if the end of the sequence provided to the constructor is reached before the expected end of the data.</exception>
public bool IsNil => this.NextCode == MessagePackCode.Nil;
/// <summary>
/// Gets the next message pack type to be read.
/// </summary>
public MessagePackType NextMessagePackType => MessagePackCode.ToMessagePackType(this.NextCode);
/// <summary>
/// Gets the type of the next MessagePack block.
/// </summary>
/// <exception cref="EndOfStreamException">Thrown if the end of the sequence provided to the constructor is reached before the expected end of the data.</exception>
/// <remarks>
/// See <see cref="MessagePackCode"/> for valid message pack codes and ranges.
/// </remarks>
public byte NextCode
{
get
{
ThrowInsufficientBufferUnless(this.reader.TryPeek(out byte code));
return code;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="MessagePackReader"/> struct,
/// with the same settings as this one, but with its own buffer to read from.
/// </summary>
/// <param name="readOnlySequence">The sequence to read from.</param>
/// <returns>The new reader.</returns>
public MessagePackReader Clone(in ReadOnlySequence<byte> readOnlySequence) => new MessagePackReader(readOnlySequence)
{
CancellationToken = this.CancellationToken,
Depth = this.Depth,
};
/// <summary>
/// Creates a new <see cref="MessagePackReader"/> at this reader's current position.
/// The two readers may then be used independently without impacting each other.
/// </summary>
/// <returns>A new <see cref="MessagePackReader"/>.</returns>
/// <devremarks>
/// Since this is a struct, copying it completely is as simple as returning itself
/// from a property that isn't a "ref return" property.
/// </devremarks>
public MessagePackReader CreatePeekReader() => this;
/// <summary>
/// Advances the reader to the next MessagePack primitive to be read.
/// </summary>
/// <remarks>
/// 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 <see cref="ReadRaw()"/> instead.
/// </remarks>
public void Skip() => ThrowInsufficientBufferUnless(this.TrySkip());
/// <summary>
/// Advances the reader to the next MessagePack primitive to be read.
/// </summary>
/// <returns><c>true</c> if the entire structure beginning at the current <see cref="Position"/> is found in the <see cref="Sequence"/>; <c>false</c> otherwise.</returns>
/// <remarks>
/// 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 <see cref="ReadRaw()"/> instead.
/// WARNING: when false is returned, the position of the reader is undefined.
/// </remarks>
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);
}
}
/// <summary>
/// Reads a <see cref="MessagePackCode.Nil"/> value.
/// </summary>
/// <returns>A nil value.</returns>
public Nil ReadNil()
{
ThrowInsufficientBufferUnless(this.reader.TryRead(out byte code));
return code == MessagePackCode.Nil
? Nil.Default
: throw ThrowInvalidCode(code);
}
/// <summary>
/// Reads nil if it is the next token.
/// </summary>
/// <returns><c>true</c> if the next token was nil; <c>false</c> otherwise.</returns>
/// <exception cref="EndOfStreamException">Thrown if the end of the sequence provided to the constructor is reached before the expected end of the data.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryReadNil()
{
if (this.NextCode == MessagePackCode.Nil)
{
this.reader.Advance(1);
return true;
}
return false;
}
/// <summary>
/// Reads a sequence of bytes without any decoding.
/// </summary>
/// <param name="length">The number of bytes to read.</param>
/// <returns>The sequence of bytes read.</returns>
public ReadOnlySequence<byte> ReadRaw(long length)
{
try
{
ReadOnlySequence<byte> result = this.reader.Sequence.Slice(this.reader.Position, length);
this.reader.Advance(length);
return result;
}
catch (ArgumentOutOfRangeException ex)
{
throw ThrowNotEnoughBytesException(ex);
}
}
/// <summary>
/// Reads the next MessagePack primitive.
/// </summary>
/// <returns>The raw MessagePack sequence.</returns>
/// <remarks>
/// The entire primitive is read, including content of maps or arrays, or any other type with payloads.
/// </remarks>
public ReadOnlySequence<byte> ReadRaw()
{
SequencePosition initialPosition = this.Position;
this.Skip();
return this.Sequence.Slice(initialPosition, this.Position);
}
/// <summary>
/// Read an array header from
/// <see cref="MessagePackCode.Array16"/>,
/// <see cref="MessagePackCode.Array32"/>, or
/// some built-in code between <see cref="MessagePackCode.MinFixArray"/> and <see cref="MessagePackCode.MaxFixArray"/>.
/// </summary>
/// <exception cref="EndOfStreamException">
/// Thrown if the header cannot be read in the bytes left in the <see cref="Sequence"/>
/// 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.
/// </exception>
/// <exception cref="MessagePackSerializationException">Thrown if a code other than an array header is encountered.</exception>
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;
}
/// <summary>
/// Reads an array header from
/// <see cref="MessagePackCode.Array16"/>,
/// <see cref="MessagePackCode.Array32"/>, or
/// some built-in code between <see cref="MessagePackCode.MinFixArray"/> and <see cref="MessagePackCode.MaxFixArray"/>
/// if there is sufficient buffer to read it.
/// </summary>
/// <param name="count">Receives the number of elements in the array if the entire array header could be read.</param>
/// <returns><c>true</c> if there was sufficient buffer and an array header was found; <c>false</c> if the buffer incompletely describes an array header.</returns>
/// <exception cref="MessagePackSerializationException">Thrown if a code other than an array header is encountered.</exception>
/// <remarks>
/// When this method returns <c>false</c> 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.
/// </remarks>
[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;
}
/// <summary>
/// Read a map header from
/// <see cref="MessagePackCode.Map16"/>,
/// <see cref="MessagePackCode.Map32"/>, or
/// some built-in code between <see cref="MessagePackCode.MinFixMap"/> and <see cref="MessagePackCode.MaxFixMap"/>.
/// </summary>
/// <returns>The number of key=value pairs in the map.</returns>
/// <exception cref="EndOfStreamException">
/// Thrown if the header cannot be read in the bytes left in the <see cref="Sequence"/>
/// 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.
/// </exception>
/// <exception cref="MessagePackSerializationException">Thrown if a code other than an map header is encountered.</exception>
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;
}
/// <summary>
/// Reads a map header from
/// <see cref="MessagePackCode.Map16"/>,
/// <see cref="MessagePackCode.Map32"/>, or
/// some built-in code between <see cref="MessagePackCode.MinFixMap"/> and <see cref="MessagePackCode.MaxFixMap"/>
/// if there is sufficient buffer to read it.
/// </summary>
/// <param name="count">Receives the number of key=value pairs in the map if the entire map header can be read.</param>
/// <returns><c>true</c> if there was sufficient buffer and a map header was found; <c>false</c> if the buffer incompletely describes an map header.</returns>
/// <exception cref="MessagePackSerializationException">Thrown if a code other than an map header is encountered.</exception>
/// <remarks>
/// When this method returns <c>false</c> 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.
/// </remarks>
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;
}
/// <summary>
/// Reads a boolean value from either a <see cref="MessagePackCode.False"/> or <see cref="MessagePackCode.True"/>.
/// </summary>
/// <returns>The value.</returns>
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);
}
}
/// <summary>
/// Reads a <see cref="char"/> from any of:
/// <see cref="MessagePackCode.UInt8"/>,
/// <see cref="MessagePackCode.UInt16"/>,
/// or anything between <see cref="MessagePackCode.MinFixInt"/> and <see cref="MessagePackCode.MaxFixInt"/>.
/// </summary>
/// <returns>A character.</returns>
public char ReadChar() => (char)this.ReadUInt16();
/// <summary>
/// Reads an <see cref="float"/> value from any value encoded with:
/// <see cref="MessagePackCode.Float32"/>,
/// <see cref="MessagePackCode.Int8"/>,
/// <see cref="MessagePackCode.Int16"/>,
/// <see cref="MessagePackCode.Int32"/>,
/// <see cref="MessagePackCode.Int64"/>,
/// <see cref="MessagePackCode.UInt8"/>,
/// <see cref="MessagePackCode.UInt16"/>,
/// <see cref="MessagePackCode.UInt32"/>,
/// <see cref="MessagePackCode.UInt64"/>,
/// or some value between <see cref="MessagePackCode.MinNegativeFixInt"/> and <see cref="MessagePackCode.MaxNegativeFixInt"/>,
/// or some value between <see cref="MessagePackCode.MinFixInt"/> and <see cref="MessagePackCode.MaxFixInt"/>.
/// </summary>
/// <returns>The value.</returns>
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);
}
}
/// <summary>
/// Reads an <see cref="double"/> value from any value encoded with:
/// <see cref="MessagePackCode.Float64"/>,
/// <see cref="MessagePackCode.Float32"/>,
/// <see cref="MessagePackCode.Int8"/>,
/// <see cref="MessagePackCode.Int16"/>,
/// <see cref="MessagePackCode.Int32"/>,
/// <see cref="MessagePackCode.Int64"/>,
/// <see cref="MessagePackCode.UInt8"/>,
/// <see cref="MessagePackCode.UInt16"/>,
/// <see cref="MessagePackCode.UInt32"/>,
/// <see cref="MessagePackCode.UInt64"/>,
/// or some value between <see cref="MessagePackCode.MinNegativeFixInt"/> and <see cref="MessagePackCode.MaxNegativeFixInt"/>,
/// or some value between <see cref="MessagePackCode.MinFixInt"/> and <see cref="MessagePackCode.MaxFixInt"/>.
/// </summary>
/// <returns>The value.</returns>
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);
}
}
/// <summary>
/// Reads a <see cref="DateTime"/> from a value encoded with
/// <see cref="MessagePackCode.FixExt4"/>,
/// <see cref="MessagePackCode.FixExt8"/>, or
/// <see cref="MessagePackCode.Ext8"/>.
/// Expects extension type code <see cref="ReservedMessagePackExtensionTypeCode.DateTime"/>.
/// </summary>
/// <returns>The value.</returns>
public DateTime ReadDateTime() => this.ReadDateTime(this.ReadExtensionFormatHeader());
/// <summary>
/// Reads a <see cref="DateTime"/> from a value encoded with
/// <see cref="MessagePackCode.FixExt4"/>,
/// <see cref="MessagePackCode.FixExt8"/>,
/// <see cref="MessagePackCode.Ext8"/>.
/// Expects extension type code <see cref="ReservedMessagePackExtensionTypeCode.DateTime"/>.
/// </summary>
/// <param name="header">The extension header that was already read.</param>
/// <returns>The value.</returns>
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.");
}
}
/// <summary>
/// Reads a span of bytes, whose length is determined by a header of one of these types:
/// <see cref="MessagePackCode.Bin8"/>,
/// <see cref="MessagePackCode.Bin16"/>,
/// <see cref="MessagePackCode.Bin32"/>,
/// or to support OldSpec compatibility:
/// <see cref="MessagePackCode.Str16"/>,
/// <see cref="MessagePackCode.Str32"/>,
/// or something between <see cref="MessagePackCode.MinFixStr"/> and <see cref="MessagePackCode.MaxFixStr"/>.
/// </summary>
/// <returns>
/// A sequence of bytes, or <c>null</c> if the read token is <see cref="MessagePackCode.Nil"/>.
/// The data is a slice from the original sequence passed to this reader's constructor.
/// </returns>
public ReadOnlySequence<byte>? ReadBytes()
{
if (this.TryReadNil())
{
return null;
}
int length = this.GetBytesLength();
ThrowInsufficientBufferUnless(this.reader.Remaining >= length);
ReadOnlySequence<byte> result = this.reader.Sequence.Slice(this.reader.Position, length);
this.reader.Advance(length);
return result;
}
/// <summary>
/// Reads a string of bytes, whose length is determined by a header of one of these types:
/// <see cref="MessagePackCode.Str8"/>,
/// <see cref="MessagePackCode.Str16"/>,
/// <see cref="MessagePackCode.Str32"/>,
/// or a code between <see cref="MessagePackCode.MinFixStr"/> and <see cref="MessagePackCode.MaxFixStr"/>.
/// </summary>
/// <returns>
/// The sequence of bytes, or <c>null</c> if the read token is <see cref="MessagePackCode.Nil"/>.
/// The data is a slice from the original sequence passed to this reader's constructor.
/// </returns>
public ReadOnlySequence<byte>? ReadStringSequence()
{
if (this.TryReadNil())
{
return null;
}
int length = this.GetStringLengthInBytes();
ThrowInsufficientBufferUnless(this.reader.Remaining >= length);
ReadOnlySequence<byte> result = this.reader.Sequence.Slice(this.reader.Position, length);
this.reader.Advance(length);
return result;
}
/// <summary>
/// Reads a string of bytes, whose length is determined by a header of one of these types:
/// <see cref="MessagePackCode.Str8"/>,
/// <see cref="MessagePackCode.Str16"/>,
/// <see cref="MessagePackCode.Str32"/>,
/// or a code between <see cref="MessagePackCode.MinFixStr"/> and <see cref="MessagePackCode.MaxFixStr"/>.
/// </summary>
/// <param name="span">Receives the span to the string.</param>
/// <returns>
/// <c>true</c> if the string is contiguous in memory such that it could be set as a single span.
/// <c>false</c> if the read token is <see cref="MessagePackCode.Nil"/> or the string is not in a contiguous span.
/// </returns>
/// <remarks>
/// Callers should generally be prepared for a <c>false</c> result and failover to calling <see cref="ReadStringSequence"/>
/// which can represent a <c>null</c> result and handle strings that are not contiguous in memory.
/// </remarks>
public bool TryReadStringSpan(out ReadOnlySpan<byte> 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;
}
}
/// <summary>
/// Reads a string, whose length is determined by a header of one of these types:
/// <see cref="MessagePackCode.Str8"/>,
/// <see cref="MessagePackCode.Str16"/>,
/// <see cref="MessagePackCode.Str32"/>,
/// or a code between <see cref="MessagePackCode.MinFixStr"/> and <see cref="MessagePackCode.MaxFixStr"/>.
/// </summary>
/// <returns>A string, or <c>null</c> if the current msgpack token is <see cref="MessagePackCode.Nil"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadString()
{
if (this.TryReadNil())
{
return null;
}
int byteLength = this.GetStringLengthInBytes();
ReadOnlySpan<byte> 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);
}
}
/// <summary>
/// Reads an extension format header, based on one of these codes:
/// <see cref="MessagePackCode.FixExt1"/>,
/// <see cref="MessagePackCode.FixExt2"/>,
/// <see cref="MessagePackCode.FixExt4"/>,
/// <see cref="MessagePackCode.FixExt8"/>,
/// <see cref="MessagePackCode.FixExt16"/>,
/// <see cref="MessagePackCode.Ext8"/>,
/// <see cref="MessagePackCode.Ext16"/>, or
/// <see cref="MessagePackCode.Ext32"/>.
/// </summary>
/// <returns>The extension header.</returns>
/// <exception cref="EndOfStreamException">
/// Thrown if the header cannot be read in the bytes left in the <see cref="Sequence"/>
/// 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.
/// </exception>
/// <exception cref="MessagePackSerializationException">Thrown if a code other than an extension format header is encountered.</exception>
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;
}
/// <summary>
/// Reads an extension format header, based on one of these codes:
/// <see cref="MessagePackCode.FixExt1"/>,
/// <see cref="MessagePackCode.FixExt2"/>,
/// <see cref="MessagePackCode.FixExt4"/>,
/// <see cref="MessagePackCode.FixExt8"/>,
/// <see cref="MessagePackCode.FixExt16"/>,
/// <see cref="MessagePackCode.Ext8"/>,
/// <see cref="MessagePackCode.Ext16"/>, or
/// <see cref="MessagePackCode.Ext32"/>
/// if there is sufficient buffer to read it.
/// </summary>
/// <param name="extensionHeader">Receives the extension header if the remaining bytes in the <see cref="Sequence"/> fully describe the header.</param>
/// <returns>The number of key=value pairs in the map.</returns>
/// <exception cref="MessagePackSerializationException">Thrown if a code other than an extension format header is encountered.</exception>
/// <remarks>
/// When this method returns <c>false</c> 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.
/// </remarks>
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;
}
/// <summary>
/// Reads an extension format header and data, based on one of these codes:
/// <see cref="MessagePackCode.FixExt1"/>,
/// <see cref="MessagePackCode.FixExt2"/>,
/// <see cref="MessagePackCode.FixExt4"/>,
/// <see cref="MessagePackCode.FixExt8"/>,
/// <see cref="MessagePackCode.FixExt16"/>,
/// <see cref="MessagePackCode.Ext8"/>,
/// <see cref="MessagePackCode.Ext16"/>, or
/// <see cref="MessagePackCode.Ext32"/>.
/// </summary>
/// <returns>
/// The extension format.
/// The data is a slice from the original sequence passed to this reader's constructor.
/// </returns>
public ExtensionResult ReadExtensionFormat()
{
ExtensionHeader header = this.ReadExtensionFormatHeader();
try
{
ReadOnlySequence<byte> 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);
}
}
/// <summary>
/// Throws an exception indicating that there aren't enough bytes remaining in the buffer to store
/// the promised data.
/// </summary>
private static EndOfStreamException ThrowNotEnoughBytesException() => throw new EndOfStreamException();
/// <summary>
/// Throws an exception indicating that there aren't enough bytes remaining in the buffer to store
/// the promised data.
/// </summary>
private static EndOfStreamException ThrowNotEnoughBytesException(Exception innerException) => throw new EndOfStreamException(new EndOfStreamException().Message, innerException);
/// <summary>
/// Throws an <see cref="MessagePackSerializationException"/> explaining an unexpected code was encountered.
/// </summary>
/// <param name="code">The code that was encountered.</param>
/// <returns>Nothing. This method always throws.</returns>
private static Exception ThrowInvalidCode(byte code)
{
throw new MessagePackSerializationException(string.Format("Unexpected msgpack code {0} ({1}) encountered.", code, MessagePackCode.ToFormatName(code)));
}
/// <summary>
/// Throws <see cref="EndOfStreamException"/> if a condition is false.
/// </summary>
/// <param name="condition">A boolean value.</param>
/// <exception cref="EndOfStreamException">Thrown if <paramref name="condition"/> is <c>false</c>.</exception>
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;
}
/// <summary>
/// Gets the length of the next string.
/// </summary>
/// <param name="length">Receives the length of the next string, if there were enough bytes to read it.</param>
/// <returns><c>true</c> if there were enough bytes to read the length of the next string; <c>false</c> otherwise.</returns>
[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);
}
/// <summary>
/// Gets the length of the next string.
/// </summary>
/// <returns>The length of the next string.</returns>
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;
}
/// <summary>
/// Reads a string assuming that it is spread across multiple spans in the <see cref="ReadOnlySequence{T}"/>.
/// </summary>
/// <param name="byteLength">The length of the string to be decoded, in bytes.</param>
/// <returns>The decoded string.</returns>
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<char>.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<char>.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;
}
}
}