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