|  | @@ -34,6 +34,7 @@ using System;
 | 
	
		
			
				|  |  |  using System.Buffers;
 | 
	
		
			
				|  |  |  using System.Buffers.Binary;
 | 
	
		
			
				|  |  |  using System.Collections.Generic;
 | 
	
		
			
				|  |  | +using System.Diagnostics;
 | 
	
		
			
				|  |  |  using System.IO;
 | 
	
		
			
				|  |  |  using System.Runtime.CompilerServices;
 | 
	
		
			
				|  |  |  using System.Runtime.InteropServices;
 | 
	
	
		
			
				|  | @@ -49,6 +50,7 @@ namespace Google.Protobuf
 | 
	
		
			
				|  |  |      [SecuritySafeCritical]
 | 
	
		
			
				|  |  |      internal static class ParsingPrimitives
 | 
	
		
			
				|  |  |      {
 | 
	
		
			
				|  |  | +        private const int StackallocThreshold = 256;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// Reads a length for length-delimited data.
 | 
	
	
		
			
				|  | @@ -58,7 +60,6 @@ namespace Google.Protobuf
 | 
	
		
			
				|  |  |          /// to make the calling code clearer.
 | 
	
		
			
				|  |  |          /// </remarks>
 | 
	
		
			
				|  |  |          [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |          public static int ParseLength(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |              return (int)ParseRawVarint32(ref buffer, ref state);
 | 
	
	
		
			
				|  | @@ -437,13 +438,7 @@ namespace Google.Protobuf
 | 
	
		
			
				|  |  |                  throw InvalidProtocolBufferException.NegativeSize();
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            if (state.totalBytesRetired + state.bufferPos + size > state.currentLimit)
 | 
	
		
			
				|  |  | -            {
 | 
	
		
			
				|  |  | -                // Read to the end of the stream (up to the current limit) anyway.
 | 
	
		
			
				|  |  | -                SkipRawBytes(ref buffer, ref state, state.currentLimit - state.totalBytesRetired - state.bufferPos);
 | 
	
		
			
				|  |  | -                // Then fail.
 | 
	
		
			
				|  |  | -                throw InvalidProtocolBufferException.TruncatedMessage();
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | +            ValidateCurrentLimit(ref buffer, ref state, size);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              if (size <= state.bufferSize - state.bufferPos)
 | 
	
		
			
				|  |  |              {
 | 
	
	
		
			
				|  | @@ -453,36 +448,13 @@ namespace Google.Protobuf
 | 
	
		
			
				|  |  |                  state.bufferPos += size;
 | 
	
		
			
				|  |  |                  return bytes;
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | -            else if (size < buffer.Length || size < state.segmentedBufferHelper.TotalLength)
 | 
	
		
			
				|  |  | +            else if (IsDataAvailableInSource(ref state, size))
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  // Reading more bytes than are in the buffer, but not an excessive number
 | 
	
		
			
				|  |  |                  // of bytes.  We can safely allocate the resulting array ahead of time.
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                // First copy what we have.
 | 
	
		
			
				|  |  |                  byte[] bytes = new byte[size];
 | 
	
		
			
				|  |  | -                var bytesSpan = new Span<byte>(bytes);
 | 
	
		
			
				|  |  | -                int pos = state.bufferSize - state.bufferPos;
 | 
	
		
			
				|  |  | -                buffer.Slice(state.bufferPos, pos).CopyTo(bytesSpan.Slice(0, pos));
 | 
	
		
			
				|  |  | -                state.bufferPos = state.bufferSize;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                // We want to use RefillBuffer() and then copy from the buffer into our
 | 
	
		
			
				|  |  | -                // byte array rather than reading directly into our byte array because
 | 
	
		
			
				|  |  | -                // the input may be unbuffered.
 | 
	
		
			
				|  |  | -                state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, true);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                while (size - pos > state.bufferSize)
 | 
	
		
			
				|  |  | -                {
 | 
	
		
			
				|  |  | -                    buffer.Slice(0, state.bufferSize)
 | 
	
		
			
				|  |  | -                        .CopyTo(bytesSpan.Slice(pos, state.bufferSize));
 | 
	
		
			
				|  |  | -                    pos += state.bufferSize;
 | 
	
		
			
				|  |  | -                    state.bufferPos = state.bufferSize;
 | 
	
		
			
				|  |  | -                    state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, true);
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                buffer.Slice(0, size - pos)
 | 
	
		
			
				|  |  | -                        .CopyTo(bytesSpan.Slice(pos, size - pos));
 | 
	
		
			
				|  |  | -                state.bufferPos = size - pos;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | +                ReadRawBytesIntoSpan(ref buffer, ref state, size, bytes);
 | 
	
		
			
				|  |  |                  return bytes;
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |              else
 | 
	
	
		
			
				|  | @@ -543,13 +515,7 @@ namespace Google.Protobuf
 | 
	
		
			
				|  |  |                  throw InvalidProtocolBufferException.NegativeSize();
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            if (state.totalBytesRetired + state.bufferPos + size > state.currentLimit)
 | 
	
		
			
				|  |  | -            {
 | 
	
		
			
				|  |  | -                // Read to the end of the stream anyway.
 | 
	
		
			
				|  |  | -                SkipRawBytes(ref buffer, ref state, state.currentLimit - state.totalBytesRetired - state.bufferPos);
 | 
	
		
			
				|  |  | -                // Then fail.
 | 
	
		
			
				|  |  | -                throw InvalidProtocolBufferException.TruncatedMessage();
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | +            ValidateCurrentLimit(ref buffer, ref state, size);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              if (size <= state.bufferSize - state.bufferPos)
 | 
	
		
			
				|  |  |              {
 | 
	
	
		
			
				|  | @@ -619,7 +585,7 @@ namespace Google.Protobuf
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #if GOOGLE_PROTOBUF_SUPPORT_FAST_STRING
 | 
	
		
			
				|  |  | -            if (length <= state.bufferSize - state.bufferPos && length > 0)
 | 
	
		
			
				|  |  | +            if (length <= state.bufferSize - state.bufferPos)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  // Fast path: all bytes to decode appear in the same span.
 | 
	
		
			
				|  |  |                  ReadOnlySpan<byte> data = buffer.Slice(state.bufferPos, length);
 | 
	
	
		
			
				|  | @@ -638,20 +604,76 @@ namespace Google.Protobuf
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  #endif
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            var decoder = WritingPrimitives.Utf8Encoding.GetDecoder();
 | 
	
		
			
				|  |  | +            return ReadStringSlow(ref buffer, ref state, length);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        /// <summary>
 | 
	
		
			
				|  |  | +        /// Reads a string assuming that it is spread across multiple spans in a <see cref="ReadOnlySequence{T}"/>.
 | 
	
		
			
				|  |  | +        /// </summary>
 | 
	
		
			
				|  |  | +        private static string ReadStringSlow(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int length)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            ValidateCurrentLimit(ref buffer, ref state, length);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            // TODO: even if GOOGLE_PROTOBUF_SUPPORT_FAST_STRING is not supported,
 | 
	
		
			
				|  |  | -            // we could still create a string efficiently by using Utf8Encoding.GetString(byte[] bytes, int index, int count)
 | 
	
		
			
				|  |  | -            // whenever the buffer is backed by a byte array (and avoid creating a new byte array), but the problem is
 | 
	
		
			
				|  |  | -            // there is no way to get the underlying byte array from a span.
 | 
	
		
			
				|  |  | +#if GOOGLE_PROTOBUF_SUPPORT_FAST_STRING
 | 
	
		
			
				|  |  | +            if (IsDataAvailable(ref state, length))
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                // Read string data into a temporary buffer, either stackalloc'ed or from ArrayPool
 | 
	
		
			
				|  |  | +                // Once all data is read then call Encoding.GetString on buffer and return to pool if needed.
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            // TODO: in case the string spans multiple buffer segments, creating a char[] and decoding into it and then
 | 
	
		
			
				|  |  | -            // creating a string from that array might be more efficient than creating a string from the copied bytes.
 | 
	
		
			
				|  |  | +                byte[] byteArray = null;
 | 
	
		
			
				|  |  | +                Span<byte> byteSpan = length <= StackallocThreshold ?
 | 
	
		
			
				|  |  | +                    stackalloc byte[length] :
 | 
	
		
			
				|  |  | +                    (byteArray = ArrayPool<byte>.Shared.Rent(length));
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                try
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    unsafe
 | 
	
		
			
				|  |  | +                    {
 | 
	
		
			
				|  |  | +                        fixed (byte* pByteSpan = &MemoryMarshal.GetReference(byteSpan))
 | 
	
		
			
				|  |  | +                        {
 | 
	
		
			
				|  |  | +                            // Compiler doesn't like that a potentially stackalloc'd Span<byte> is being used
 | 
	
		
			
				|  |  | +                            // in a method with a "ref Span<byte> buffer" argument. If the stackalloc'd span was assigned
 | 
	
		
			
				|  |  | +                            // to the ref argument then bad things would happen. We'll never do that so it is ok.
 | 
	
		
			
				|  |  | +                            // Make compiler happy by passing a new span created from pointer.
 | 
	
		
			
				|  |  | +                            var tempSpan = new Span<byte>(pByteSpan, byteSpan.Length);
 | 
	
		
			
				|  |  | +                            ReadRawBytesIntoSpan(ref buffer, ref state, length, tempSpan);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                            return WritingPrimitives.Utf8Encoding.GetString(pByteSpan, length);
 | 
	
		
			
				|  |  | +                        }
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                finally
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    if (byteArray != null)
 | 
	
		
			
				|  |  | +                    {
 | 
	
		
			
				|  |  | +                        ArrayPool<byte>.Shared.Return(byteArray);
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +#endif
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              // Slow path: Build a byte array first then copy it.
 | 
	
		
			
				|  |  | +            // This will be called when reading from a Stream because we don't know the length of the stream,
 | 
	
		
			
				|  |  | +            // or there is not enough data in the sequence. If there is not enough data then ReadRawBytes will
 | 
	
		
			
				|  |  | +            // throw an exception.
 | 
	
		
			
				|  |  |              return WritingPrimitives.Utf8Encoding.GetString(ReadRawBytes(ref buffer, ref state, length), 0, length);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +        /// <summary>
 | 
	
		
			
				|  |  | +        /// Validates that the specified size doesn't exceed the current limit. If it does then remaining bytes
 | 
	
		
			
				|  |  | +        /// are skipped and an error is thrown.
 | 
	
		
			
				|  |  | +        /// </summary>
 | 
	
		
			
				|  |  | +        private static void ValidateCurrentLimit(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int size)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            if (state.totalBytesRetired + state.bufferPos + size > state.currentLimit)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                // Read to the end of the stream (up to the current limit) anyway.
 | 
	
		
			
				|  |  | +                SkipRawBytes(ref buffer, ref state, state.currentLimit - state.totalBytesRetired - state.bufferPos);
 | 
	
		
			
				|  |  | +                // Then fail.
 | 
	
		
			
				|  |  | +                throw InvalidProtocolBufferException.TruncatedMessage();
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          [SecuritySafeCritical]
 | 
	
		
			
				|  |  |          private static byte ReadRawByte(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
 | 
	
		
			
				|  |  |          {
 | 
	
	
		
			
				|  | @@ -731,5 +753,56 @@ namespace Google.Protobuf
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |              return (long)(n >> 1) ^ -(long)(n & 1);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        /// <summary>
 | 
	
		
			
				|  |  | +        /// Checks whether there is known data available of the specified size remaining to parse.
 | 
	
		
			
				|  |  | +        /// When parsing from a Stream this can return false because we have no knowledge of the amount
 | 
	
		
			
				|  |  | +        /// of data remaining in the stream until it is read.
 | 
	
		
			
				|  |  | +        /// </summary>
 | 
	
		
			
				|  |  | +        public static bool IsDataAvailable(ref ParserInternalState state, int size)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            // Data fits in remaining buffer
 | 
	
		
			
				|  |  | +            if (size <= state.bufferSize - state.bufferPos)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                return true;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            return IsDataAvailableInSource(ref state, size);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        /// <summary>
 | 
	
		
			
				|  |  | +        /// Checks whether there is known data available of the specified size remaining to parse
 | 
	
		
			
				|  |  | +        /// in the underlying data source.
 | 
	
		
			
				|  |  | +        /// When parsing from a Stream this will return false because we have no knowledge of the amount
 | 
	
		
			
				|  |  | +        /// of data remaining in the stream until it is read.
 | 
	
		
			
				|  |  | +        /// </summary>
 | 
	
		
			
				|  |  | +        private static bool IsDataAvailableInSource(ref ParserInternalState state, int size)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            // Data fits in remaining source data.
 | 
	
		
			
				|  |  | +            // Note that this will never be true when reading from a stream as the total length is unknown.
 | 
	
		
			
				|  |  | +            return size <= state.segmentedBufferHelper.TotalLength - state.totalBytesRetired - state.bufferPos;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        /// <summary>
 | 
	
		
			
				|  |  | +        /// Read raw bytes of the specified length into a span. The amount of data available and the current limit should
 | 
	
		
			
				|  |  | +        /// be checked before calling this method.
 | 
	
		
			
				|  |  | +        /// </summary>
 | 
	
		
			
				|  |  | +        private static void ReadRawBytesIntoSpan(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int length, Span<byte> byteSpan)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            int remainingByteLength = length;
 | 
	
		
			
				|  |  | +            while (remainingByteLength > 0)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                if (state.bufferSize - state.bufferPos == 0)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, true);
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                ReadOnlySpan<byte> unreadSpan = buffer.Slice(state.bufferPos, Math.Min(remainingByteLength, state.bufferSize - state.bufferPos));
 | 
	
		
			
				|  |  | +                unreadSpan.CopyTo(byteSpan.Slice(length - remainingByteLength));
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                remainingByteLength -= unreadSpan.Length;
 | 
	
		
			
				|  |  | +                state.bufferPos += unreadSpan.Length;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | +}
 |