// 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.Diagnostics.CodeAnalysis; 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> /// The minimum number of bytes that previously-read container headers have committed to their children /// but that have not yet been consumed. /// </summary> private long minimumRemainingChildBytes; /// <summary> /// The value of <see cref="Consumed"/> when <see cref="minimumRemainingChildBytes"/> was last updated. /// </summary> private long minimumRemainingChildBytesCheckpoint; /// <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><see langword="true"/> if the entire structure beginning at the current <see cref="Position"/> is found in the <see cref="Sequence"/>; <see langword="false"/> 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() { long remainingStructures = 1; while (remainingStructures > 0) { if (this.reader.Remaining == 0) { return false; } remainingStructures--; byte code = this.NextCode; switch (code) { case byte x when (x >= MessagePackCode.MinNegativeFixInt && x <= MessagePackCode.MaxNegativeFixInt) || (x >= MessagePackCode.MinFixInt && x <= MessagePackCode.MaxFixInt): case MessagePackCode.Nil: case MessagePackCode.True: case MessagePackCode.False: if (!this.reader.TryAdvance(1)) { return false; } break; case MessagePackCode.Int8: case MessagePackCode.UInt8: if (!this.reader.TryAdvance(2)) { return false; } break; case MessagePackCode.Int16: case MessagePackCode.UInt16: if (!this.reader.TryAdvance(3)) { return false; } break; case MessagePackCode.Int32: case MessagePackCode.UInt32: case MessagePackCode.Float32: if (!this.reader.TryAdvance(5)) { return false; } break; case MessagePackCode.Int64: case MessagePackCode.UInt64: case MessagePackCode.Float64: if (!this.reader.TryAdvance(9)) { return false; } break; case byte x when x >= MessagePackCode.MinFixMap && x <= MessagePackCode.MaxFixMap: case MessagePackCode.Map16: case MessagePackCode.Map32: if (!this.TryReadMapHeader(out int count)) { return false; } remainingStructures = checked(remainingStructures + ((long)count * 2)); break; case byte x when x >= MessagePackCode.MinFixArray && x <= MessagePackCode.MaxFixArray: case MessagePackCode.Array16: case MessagePackCode.Array32: if (!this.TryReadArrayHeader(out count)) { return false; } remainingStructures = checked(remainingStructures + count); break; case byte x when x >= MessagePackCode.MinFixStr && x <= MessagePackCode.MaxFixStr: case MessagePackCode.Str8: case MessagePackCode.Str16: case MessagePackCode.Str32: if (!this.TryGetStringLengthInBytes(out int length) || !this.reader.TryAdvance(length)) { return false; } break; case MessagePackCode.Bin8: case MessagePackCode.Bin16: case MessagePackCode.Bin32: if (!this.TryGetBytesLength(out length) || !this.reader.TryAdvance(length)) { return false; } break; case MessagePackCode.FixExt1: case MessagePackCode.FixExt2: case MessagePackCode.FixExt4: case MessagePackCode.FixExt8: case MessagePackCode.FixExt16: case MessagePackCode.Ext8: case MessagePackCode.Ext16: case MessagePackCode.Ext32: if (!this.TryReadExtensionFormatHeader(out ExtensionHeader header) || !this.reader.TryAdvance(header.Length)) { return false; } break; default: // 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); } } return true; } /// <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><see langword="true"/> if the next token was nil; <see langword="false"/> 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. this.ReserveMinimumChildBytes(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><see langword="true"/> if there was sufficient buffer and an array header was found; <see langword="false"/> 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 <see langword="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. /// </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. this.ReserveMinimumChildBytes((long)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><see langword="true"/> if there was sufficient buffer and a map header was found; <see langword="false"/> 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 <see langword="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. /// </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 <see langword="null"/> 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 <see langword="null"/> 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> /// <see langword="true"/> if the string is contiguous in memory such that it could be set as a single span. /// <see langword="false"/> 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 <see langword="false"/> result and failover to calling <see cref="ReadStringSequence"/> /// which can represent a <see langword="null"/> 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 <see langword="null"/> 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 <see langword="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. /// </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> [DoesNotReturn] 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 <see langword="false"/>.</exception> private static void ThrowInsufficientBufferUnless(bool condition) { if (!condition) { ThrowNotEnoughBytesException(); } } private void ReserveMinimumChildBytes(long minimumChildBytes) { long consumed = this.reader.Consumed; long bytesConsumedSinceCheckpoint = consumed - this.minimumRemainingChildBytesCheckpoint; this.minimumRemainingChildBytes = Math.Max(0, this.minimumRemainingChildBytes - bytesConsumedSinceCheckpoint); this.minimumRemainingChildBytesCheckpoint = consumed; ThrowInsufficientBufferUnless( minimumChildBytes >= 0 && this.minimumRemainingChildBytes <= this.reader.Remaining && minimumChildBytes <= this.reader.Remaining - this.minimumRemainingChildBytes); this.minimumRemainingChildBytes += minimumChildBytes; } 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><see langword="true"/> if there were enough bytes to read the length of the next string; <see langword="false"/> 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; } } }