diff --git a/csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs b/csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs index 8795fa65f4d5..7c3df59d16ff 100644 --- a/csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs +++ b/csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs @@ -417,7 +417,7 @@ public void TestSlowPathAvoidance() output.Flush(); ms.Position = 0; - CodedInputStream input = new CodedInputStream(ms, new byte[ms.Length / 2], 0, 0, false); + CodedInputStream input = new CodedInputStream(ms, new byte[ms.Length / 2], null, null, 0, 0, false); uint tag = input.ReadTag(); Assert.AreEqual(1, WireFormat.GetTagFieldNumber(tag)); diff --git a/csharp/src/Google.Protobuf.Test/CodedOutputStreamTest.cs b/csharp/src/Google.Protobuf.Test/CodedOutputStreamTest.cs index 98cabd55adc4..f664250ee9df 100644 --- a/csharp/src/Google.Protobuf.Test/CodedOutputStreamTest.cs +++ b/csharp/src/Google.Protobuf.Test/CodedOutputStreamTest.cs @@ -334,7 +334,7 @@ public void TestCodedInputOutputPosition() } // Now test Input stream: { - CodedInputStream cin = new CodedInputStream(new MemoryStream(bytes), new byte[50], 0, 0, false); + CodedInputStream cin = new CodedInputStream(new MemoryStream(bytes), new byte[50], null, null, 0, 0, false); Assert.AreEqual(0, cin.Position); // Field 1: uint tag = cin.ReadTag(); diff --git a/csharp/src/Google.Protobuf/ByteString.cs b/csharp/src/Google.Protobuf/ByteString.cs index ade751c63661..706c812a4fe3 100644 --- a/csharp/src/Google.Protobuf/ByteString.cs +++ b/csharp/src/Google.Protobuf/ByteString.cs @@ -34,6 +34,7 @@ using System.Collections; using System.Collections.Generic; using System.IO; +using System.Security; using System.Text; #if !NET35 using System.Threading; @@ -114,7 +115,11 @@ public bool IsEmpty /// Provides read-only access to the data of this . /// No data is copied so this is the most efficient way of accessing. /// - public ReadOnlySpan Span => new ReadOnlySpan(bytes); + public ReadOnlySpan Span + { + [SecurityCritical] + get => bytes; + } /// /// Converts this into a byte array. @@ -221,6 +226,7 @@ public static ByteString CopyFrom(byte[] bytes, int offset, int count) /// are copied, so further modifications to the span will not /// be reflected in the returned . /// + [SecurityCritical] public static ByteString CopyFrom(ReadOnlySpan bytes) { return new ByteString(bytes.ToArray()); diff --git a/csharp/src/Google.Protobuf/CodedInputStream.cs b/csharp/src/Google.Protobuf/CodedInputStream.cs index 0a829545e257..7d75b01b3466 100644 --- a/csharp/src/Google.Protobuf/CodedInputStream.cs +++ b/csharp/src/Google.Protobuf/CodedInputStream.cs @@ -32,8 +32,13 @@ using Google.Protobuf.Collections; using System; +using System.Buffers; +using System.Buffers.Binary; using System.Collections.Generic; +using System.ComponentModel; using System.IO; +using System.Runtime.CompilerServices; +using System.Security; namespace Google.Protobuf { @@ -81,6 +86,11 @@ public sealed class CodedInputStream : IDisposable /// private readonly Stream input; + private ReadOnlySequence.Enumerator nativeInput; + private bool hasNativeInput; + private long nativeInputPastBuffersLength; + private ReadOnlyMemory currentNativeBuffer; + /// /// The last tag we read. 0 indicates we've read to the end of the stream /// (or haven't read anything yet). @@ -121,16 +131,35 @@ public sealed class CodedInputStream : IDisposable /// /// Creates a new CodedInputStream reading data from the given byte array. /// - public CodedInputStream(byte[] buffer) : this(null, ProtoPreconditions.CheckNotNull(buffer, "buffer"), 0, buffer.Length, true) - { + [SecuritySafeCritical] + public CodedInputStream(byte[] buffer) : this(null, ProtoPreconditions.CheckNotNull(buffer, "buffer"), null, null, 0, buffer.Length, true) + { + } + + /// + /// Creates a new CodedInputStream reading data from the given contiguous memory segment. + /// + [SecurityCritical] + public CodedInputStream(ReadOnlyMemory buffer) : this(null, null, null, buffer, 0, buffer.Length, true) + { + } + + /// + /// Creates a new CodedInputStream reading data from the given non-contiguous memory sequence. + /// + [SecurityCritical] + public CodedInputStream(ReadOnlySequence buffer) + : this(null, null, buffer.IsSingleSegment ? default(ReadOnlySequence?) : buffer, buffer.First, 0, buffer.First.Length, true) + { } /// /// Creates a new that reads from the given byte array slice. /// + [SecuritySafeCritical] public CodedInputStream(byte[] buffer, int offset, int length) - : this(null, ProtoPreconditions.CheckNotNull(buffer, "buffer"), offset, offset + length, true) - { + : this(null, ProtoPreconditions.CheckNotNull(buffer, "buffer"), null, null, offset, offset + length, true) + { if (offset < 0 || offset > buffer.Length) { throw new ArgumentOutOfRangeException("offset", "Offset must be within the buffer"); @@ -146,6 +175,7 @@ public CodedInputStream(byte[] buffer, int offset, int length) /// when the returned object is disposed. /// /// The stream to read from. + [SecuritySafeCritical] public CodedInputStream(Stream input) : this(input, false) { } @@ -157,19 +187,32 @@ public CodedInputStream(Stream input) : this(input, false) /// true to leave open when the returned /// is disposed; false to dispose of the given stream when the /// returned object is disposed. + [SecuritySafeCritical] public CodedInputStream(Stream input, bool leaveOpen) - : this(ProtoPreconditions.CheckNotNull(input, "input"), new byte[BufferSize], 0, 0, leaveOpen) + : this(ProtoPreconditions.CheckNotNull(input, "input"), new byte[BufferSize], null, null, 0, 0, leaveOpen) { } - + /// /// Creates a new CodedInputStream reading data from the given /// stream and buffer, using the default limits. /// - internal CodedInputStream(Stream input, byte[] buffer, int bufferPos, int bufferSize, bool leaveOpen) + [SecurityCritical] + internal CodedInputStream(Stream input, byte[] buffer, ReadOnlySequence? nativeInput, ReadOnlyMemory? nativeBuffer, int bufferPos, int bufferSize, bool leaveOpen) { this.input = input; this.buffer = buffer; + if (nativeInput.HasValue) + { + var en = nativeInput.Value.GetEnumerator(); + en.MoveNext(); + this.nativeInput = en; + hasNativeInput = true; + } + if (nativeBuffer.HasValue) + { + this.currentNativeBuffer = nativeBuffer.Value; + } this.bufferPos = bufferPos; this.bufferSize = bufferSize; this.sizeLimit = DefaultSizeLimit; @@ -185,8 +228,9 @@ internal CodedInputStream(Stream input, byte[] buffer, int bufferPos, int buffer /// This chains to the version with the default limits instead of vice versa to avoid /// having to check that the default values are valid every time. /// + [SecuritySafeCritical] internal CodedInputStream(Stream input, byte[] buffer, int bufferPos, int bufferSize, int sizeLimit, int recursionLimit, bool leaveOpen) - : this(input, buffer, bufferPos, bufferSize, leaveOpen) + : this(input, buffer, null, null, bufferPos, bufferSize, leaveOpen) { if (sizeLimit <= 0) { @@ -224,7 +268,7 @@ public static CodedInputStream CreateWithLimits(Stream input, int sizeLimit, int /// /// Returns the current position in the input stream, or the position in the input buffer /// - public long Position + public long Position { get { @@ -232,7 +276,14 @@ public long Position { return input.Position - ((bufferSize + bufferSizeAfterLimit) - bufferPos); } - return bufferPos; + else if (hasNativeInput) + { + return nativeInputPastBuffersLength + bufferPos; + } + else + { + return bufferPos; + } } } @@ -242,6 +293,12 @@ public long Position /// internal uint LastTag { get { return lastTag; } } + internal ReadOnlySpan ImmediateBuffer + { + [SecurityCritical] + get => buffer != null ? buffer : currentNativeBuffer.Span; + } + /// /// Returns the size limit for this stream. /// @@ -307,8 +364,8 @@ internal void CheckReadEndOfStreamTag() #region Reading of tags etc /// - /// Peeks at the next field tag. This is like calling , but the - /// tag is not consumed. (So a subsequent call to will return the + /// Peeks at the next field tag. This is like calling , but the + /// tag is not consumed. (So a subsequent call to will return the /// same value.) /// public uint PeekTag() @@ -334,7 +391,19 @@ public uint PeekTag() /// for an embedded message, for example. /// /// The next field tag, or 0 for end of stream. (0 is never a valid tag.) + [SecuritySafeCritical] public uint ReadTag() + { + var immediateBuffer = ImmediateBuffer; + return ReadTag(ref immediateBuffer); + } + + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + public uint ReadTag(ref ReadOnlySpan immediateBuffer) { if (hasNextTag) { @@ -347,7 +416,7 @@ public uint ReadTag() // and those two bytes being enough to get the tag. This will be true for fields up to 4095. if (bufferPos + 2 <= bufferSize) { - int tmp = buffer[bufferPos++]; + int tmp = immediateBuffer[bufferPos++]; if (tmp < 128) { lastTag = (uint)tmp; @@ -355,28 +424,28 @@ public uint ReadTag() else { int result = tmp & 0x7f; - if ((tmp = buffer[bufferPos++]) < 128) + if ((tmp = immediateBuffer[bufferPos++]) < 128) { result |= tmp << 7; - lastTag = (uint) result; + lastTag = (uint)result; } else { // Nope, rewind and go the potentially slow route. bufferPos -= 2; - lastTag = ReadRawVarint32(); + lastTag = ReadRawVarint32(ref immediateBuffer); } } } else { - if (IsAtEnd) + if (IsAtEndCore(ref immediateBuffer)) { lastTag = 0; return 0; // This is the only case in which we return 0. } - lastTag = ReadRawVarint32(); + lastTag = ReadRawVarint32(ref immediateBuffer); } if (WireFormat.GetTagFieldNumber(lastTag) == 0) { @@ -388,7 +457,7 @@ public uint ReadTag() /// /// Skips the data for the field with the tag we've just read. - /// This should be called directly after , when + /// This should be called directly after , when /// the caller wishes to skip an unknown field. /// /// @@ -399,7 +468,19 @@ public uint ReadTag() /// /// The last tag was an end-group tag /// The last read operation read to the end of the logical stream + [SecuritySafeCritical] public void SkipLastField() + { + var immediateBuffer = ImmediateBuffer; + SkipLastField(ref immediateBuffer); + } + + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + public void SkipLastField(ref ReadOnlySpan immediateBuffer) { if (lastTag == 0) { @@ -408,23 +489,23 @@ public void SkipLastField() switch (WireFormat.GetTagWireType(lastTag)) { case WireFormat.WireType.StartGroup: - SkipGroup(lastTag); + SkipGroup(lastTag, ref immediateBuffer); break; case WireFormat.WireType.EndGroup: throw new InvalidProtocolBufferException( "SkipLastField called on an end-group tag, indicating that the corresponding start-group was missing"); case WireFormat.WireType.Fixed32: - ReadFixed32(); + ReadFixed32(ref immediateBuffer); break; case WireFormat.WireType.Fixed64: - ReadFixed64(); + ReadFixed64(ref immediateBuffer); break; case WireFormat.WireType.LengthDelimited: - var length = ReadLength(); - SkipRawBytes(length); + var length = ReadLength(ref immediateBuffer); + SkipRawBytes(length, ref immediateBuffer); break; case WireFormat.WireType.Varint: - ReadRawVarint32(); + ReadRawVarint32(ref immediateBuffer); break; } } @@ -433,6 +514,14 @@ public void SkipLastField() /// Skip a group. /// internal void SkipGroup(uint startGroupTag) + { + var immediateBuffer = ImmediateBuffer; + SkipGroup(startGroupTag, ref immediateBuffer); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + internal void SkipGroup(uint startGroupTag, ref ReadOnlySpan immediateBuffer) { // Note: Currently we expect this to be the way that groups are read. We could put the recursion // depth changes into the ReadTag method instead, potentially... @@ -444,7 +533,7 @@ internal void SkipGroup(uint startGroupTag) uint tag; while (true) { - tag = ReadTag(); + tag = ReadTag(ref immediateBuffer); if (tag == 0) { throw InvalidProtocolBufferException.TruncatedMessage(); @@ -455,7 +544,7 @@ internal void SkipGroup(uint startGroupTag) break; } // This recursion will allow us to handle nested groups. - SkipLastField(); + SkipLastField(ref immediateBuffer); } int startField = WireFormat.GetTagFieldNumber(startGroupTag); int endField = WireFormat.GetTagFieldNumber(tag); @@ -470,87 +559,240 @@ internal void SkipGroup(uint startGroupTag) /// /// Reads a double field from the stream. /// + [SecuritySafeCritical] public double ReadDouble() { - return BitConverter.Int64BitsToDouble((long) ReadRawLittleEndian64()); + var immediateBuffer = ImmediateBuffer; + return ReadDouble(ref immediateBuffer); } /// - /// Reads a float field from the stream. + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. /// - public float ReadFloat() + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + public double ReadDouble(ref ReadOnlySpan immediateBuffer) => BitConverter.Int64BitsToDouble((long)ReadRawLittleEndian64(ref immediateBuffer)); + + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + public double ReadWrappedDouble(ref ReadOnlySpan immediateBuffer) { - if (BitConverter.IsLittleEndian && 4 <= bufferSize - bufferPos) + int oldLimit = BeginReadNested(ref immediateBuffer); + uint tag; + double value = default(double); + while ((tag = ReadTag(ref immediateBuffer)) != 0) { - float ret = BitConverter.ToSingle(buffer, bufferPos); - bufferPos += 4; - return ret; + if (tag == WellKnownTypes.WrappersReflection.WrapperValueFixed64Tag) + { + value = ReadDouble(ref immediateBuffer); + } + else + { + SkipLastField(ref immediateBuffer); + } } - else + EndReadNested(oldLimit); + + return value; + } + + /// + /// Reads a float field from the stream. + /// + [SecuritySafeCritical] + public float ReadFloat() + { + var immediateBuffer = ImmediateBuffer; + return ReadFloat(ref immediateBuffer); + } + + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + //NOTE: We cannot use BitConverter.Int32BitsToSingle because it requires .NET Core 2.1 + public float ReadFloat(ref ReadOnlySpan immediateBuffer) => Int32BitsToSingleSlow((int)ReadRawLittleEndian32(ref immediateBuffer)); + + private static float Int32BitsToSingleSlow(int value) => BitConverter.ToSingle(BitConverter.GetBytes(value), 0); + + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + public float ReadWrappedFloat(ref ReadOnlySpan immediateBuffer) + { + int oldLimit = BeginReadNested(ref immediateBuffer); + uint tag; + float value = default(float); + while ((tag = ReadTag(ref immediateBuffer)) != 0) { - byte[] rawBytes = ReadRawBytes(4); - if (!BitConverter.IsLittleEndian) + if (tag == WellKnownTypes.WrappersReflection.WrapperValueFixed32Tag) { - ByteArray.Reverse(rawBytes); + value = ReadFloat(ref immediateBuffer); + } + else + { + SkipLastField(ref immediateBuffer); } - return BitConverter.ToSingle(rawBytes, 0); } + EndReadNested(oldLimit); + + return value; } /// /// Reads a uint64 field from the stream. /// + [SecuritySafeCritical] public ulong ReadUInt64() { - return ReadRawVarint64(); + var immediateBuffer = ImmediateBuffer; + return ReadUInt64(ref immediateBuffer); } + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + public ulong ReadUInt64(ref ReadOnlySpan immediateBuffer) => ReadRawVarint64(ref immediateBuffer); + + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + public ulong ReadWrappedUInt64(ref ReadOnlySpan immediateBuffer) => ReadRawWrappedVarint64(ref immediateBuffer); + /// /// Reads an int64 field from the stream. /// + [SecuritySafeCritical] public long ReadInt64() { - return (long) ReadRawVarint64(); + var immediateBuffer = ImmediateBuffer; + return ReadInt64(ref immediateBuffer); } + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + public long ReadInt64(ref ReadOnlySpan immediateBuffer) => (long)ReadRawVarint64(ref immediateBuffer); + + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + public long ReadWrappedInt64(ref ReadOnlySpan immediateBuffer) => (long)ReadRawWrappedVarint64(ref immediateBuffer); + /// /// Reads an int32 field from the stream. /// + [SecuritySafeCritical] public int ReadInt32() { - return (int) ReadRawVarint32(); + var immediateBuffer = ImmediateBuffer; + return ReadInt32(ref immediateBuffer); } + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + public int ReadInt32(ref ReadOnlySpan immediateBuffer) => (int)ReadRawVarint32(ref immediateBuffer); + + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + public int ReadWrappedInt32(ref ReadOnlySpan immediateBuffer) => (int)ReadRawWrappedVarint32(ref immediateBuffer); + /// /// Reads a fixed64 field from the stream. /// + [SecuritySafeCritical] public ulong ReadFixed64() { - return ReadRawLittleEndian64(); + var immediateBuffer = ImmediateBuffer; + return ReadFixed64(ref immediateBuffer); } + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + public ulong ReadFixed64(ref ReadOnlySpan immediateBuffer) => ReadRawLittleEndian64(ref immediateBuffer); + /// /// Reads a fixed32 field from the stream. /// + [SecuritySafeCritical] public uint ReadFixed32() { - return ReadRawLittleEndian32(); + var immediateBuffer = ImmediateBuffer; + return ReadFixed32(ref immediateBuffer); } + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + public uint ReadFixed32(ref ReadOnlySpan immediateBuffer) => ReadRawLittleEndian32(ref immediateBuffer); + /// /// Reads a bool field from the stream. /// + [SecuritySafeCritical] public bool ReadBool() { - return ReadRawVarint32() != 0; + var immediateBuffer = ImmediateBuffer; + return ReadBool(ref immediateBuffer); } + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + public bool ReadBool(ref ReadOnlySpan immediateBuffer) => ReadRawVarint32(ref immediateBuffer) != 0; + + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + public bool ReadWrappedBool(ref ReadOnlySpan immediateBuffer) => ReadRawWrappedVarint32(ref immediateBuffer) != 0; + /// /// Reads a string field from the stream. /// + [SecuritySafeCritical] public string ReadString() { - int length = ReadLength(); + var immediateBuffer = ImmediateBuffer; + return ReadString(ref immediateBuffer); + } + + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + public string ReadString(ref ReadOnlySpan immediateBuffer) + { + int length = ReadLength(ref immediateBuffer); // No need to read any data for an empty string. if (length == 0) { @@ -560,27 +802,77 @@ public string ReadString() { // Fast path: We already have the bytes in a contiguous buffer, so // just copy directly from it. - String result = CodedOutputStream.Utf8Encoding.GetString(buffer, bufferPos, length); + //NOTE: We cannot use span-based overload because it is available only in .NET Core 2.1 + String result = buffer != null ? CodedOutputStream.Utf8Encoding.GetString(buffer, bufferPos, length) : CodedOutputStream.Utf8Encoding.GetString(immediateBuffer.Slice(bufferPos, length).ToArray(), 0, length); bufferPos += length; return result; } // Slow path: Build a byte array first then copy it. - return CodedOutputStream.Utf8Encoding.GetString(ReadRawBytes(length), 0, length); + return CodedOutputStream.Utf8Encoding.GetString(ReadRawBytes(length, ref immediateBuffer), 0, length); + } + + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + public string ReadWrappedString(ref ReadOnlySpan immediateBuffer) + { + int oldLimit = BeginReadNested(ref immediateBuffer); + uint tag; + string value = string.Empty; + while ((tag = ReadTag(ref immediateBuffer)) != 0) + { + if (tag == WellKnownTypes.WrappersReflection.WrapperValueLengthDelimitedTag) + { + value = ReadString(ref immediateBuffer); + } + else + { + SkipLastField(ref immediateBuffer); + } + } + EndReadNested(oldLimit); + + return value; } /// /// Reads an embedded message field value from the stream. - /// + /// + [SecuritySafeCritical] public void ReadMessage(IMessage builder) { - int length = ReadLength(); + var immediateBuffer = ImmediateBuffer; + var oldLimit = BeginReadNested(ref immediateBuffer); + //NOTE: IMessage does not support MergeFrom with immediateBuffer yet - so we are using legacy path for reverse compat + builder.MergeFrom(this); + EndReadNested(oldLimit); + } + + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + public int BeginReadNested(ref ReadOnlySpan immediateBuffer) + { + int length = ReadLength(ref immediateBuffer); if (recursionDepth >= recursionLimit) { throw InvalidProtocolBufferException.RecursionLimitExceeded(); } int oldLimit = PushLimit(length); ++recursionDepth; - builder.MergeFrom(this); + return oldLimit; + } + + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public void EndReadNested(int oldLimit) + { CheckReadEndOfStreamTag(); // Check that we've read exactly as much data as expected. if (!ReachedLimit) @@ -594,85 +886,188 @@ public void ReadMessage(IMessage builder) /// /// Reads a bytes field value from the stream. /// + [SecuritySafeCritical] public ByteString ReadBytes() { - int length = ReadLength(); + var immediateBuffer = ImmediateBuffer; + return ReadBytes(ref immediateBuffer); + } + + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + public ByteString ReadBytes(ref ReadOnlySpan immediateBuffer) + { + int length = ReadLength(ref immediateBuffer); if (length <= bufferSize - bufferPos && length > 0) { // Fast path: We already have the bytes in a contiguous buffer, so // just copy directly from it. - ByteString result = ByteString.CopyFrom(buffer, bufferPos, length); + ByteString result = ByteString.CopyFrom(immediateBuffer.Slice(bufferPos, length)); bufferPos += length; return result; } else { // Slow path: Build a byte array and attach it to a new ByteString. - return ByteString.AttachBytes(ReadRawBytes(length)); + return ByteString.AttachBytes(ReadRawBytes(length, ref immediateBuffer)); + } + } + + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + public ByteString ReadWrappedBytes(ref ReadOnlySpan immediateBuffer) + { + int oldLimit = BeginReadNested(ref immediateBuffer); + uint tag; + ByteString value = ByteString.Empty; + while ((tag = ReadTag(ref immediateBuffer)) != 0) + { + if (tag == WellKnownTypes.WrappersReflection.WrapperValueLengthDelimitedTag) + { + value = ReadBytes(ref immediateBuffer); + } + else + { + SkipLastField(ref immediateBuffer); + } } + EndReadNested(oldLimit); + + return value; } /// /// Reads a uint32 field value from the stream. - /// + /// + [SecuritySafeCritical] public uint ReadUInt32() { - return ReadRawVarint32(); + var immediateBuffer = ImmediateBuffer; + return ReadUInt32(ref immediateBuffer); } + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + public uint ReadUInt32(ref ReadOnlySpan immediateBuffer) => ReadRawVarint32(ref immediateBuffer); + + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + public uint ReadWrappedUInt32(ref ReadOnlySpan immediateBuffer) => ReadRawWrappedVarint32(ref immediateBuffer); + /// /// Reads an enum field value from the stream. - /// + /// + [SecuritySafeCritical] public int ReadEnum() { - // Currently just a pass-through, but it's nice to separate it logically from WriteInt32. - return (int) ReadRawVarint32(); + var immediateBuffer = ImmediateBuffer; + return ReadEnum(ref immediateBuffer); } + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + public int ReadEnum(ref ReadOnlySpan immediateBuffer) => (int)ReadRawVarint32(ref immediateBuffer); + /// /// Reads an sfixed32 field value from the stream. - /// + /// + [SecuritySafeCritical] public int ReadSFixed32() { - return (int) ReadRawLittleEndian32(); + var immediateBuffer = ImmediateBuffer; + return ReadSFixed32(ref immediateBuffer); } + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + public int ReadSFixed32(ref ReadOnlySpan immediateBuffer) => (int)ReadRawLittleEndian32(ref immediateBuffer); + /// /// Reads an sfixed64 field value from the stream. - /// + /// + [SecuritySafeCritical] public long ReadSFixed64() { - return (long) ReadRawLittleEndian64(); + var immediateBuffer = ImmediateBuffer; + return ReadSFixed64(ref immediateBuffer); } + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + public long ReadSFixed64(ref ReadOnlySpan immediateBuffer) => (long)ReadRawLittleEndian64(ref immediateBuffer); + /// /// Reads an sint32 field value from the stream. - /// + /// + [SecuritySafeCritical] public int ReadSInt32() { - return DecodeZigZag32(ReadRawVarint32()); + var immediateBuffer = ImmediateBuffer; + return ReadSInt32(ref immediateBuffer); } + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + public int ReadSInt32(ref ReadOnlySpan immediateBuffer) => DecodeZigZag32(ReadRawVarint32(ref immediateBuffer)); + /// /// Reads an sint64 field value from the stream. /// + [SecuritySafeCritical] public long ReadSInt64() { - return DecodeZigZag64(ReadRawVarint64()); + var immediateBuffer = ImmediateBuffer; + return ReadSInt64(ref immediateBuffer); } + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + public long ReadSInt64(ref ReadOnlySpan immediateBuffer) => DecodeZigZag64(ReadRawVarint64(ref immediateBuffer)); + /// /// Reads a length for length-delimited data. /// - /// - /// This is internally just reading a varint, but this method exists - /// to make the calling code clearer. - /// + [SecuritySafeCritical] public int ReadLength() { - return (int) ReadRawVarint32(); + var immediateBuffer = ImmediateBuffer; + return ReadLength(ref immediateBuffer); } + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + public int ReadLength(ref ReadOnlySpan immediateBuffer) => (int)ReadRawVarint32(ref immediateBuffer); + /// /// Peeks at the next tag in the stream. If it matches , /// the tag is consumed and the method returns true; otherwise, the @@ -696,44 +1091,45 @@ public bool MaybeConsumeTag(uint tag) /// Same code as ReadRawVarint32, but read each byte individually, checking for /// buffer overflow. /// - private uint SlowReadRawVarint32() + [SecurityCritical] + private uint SlowReadRawVarint32(ref ReadOnlySpan immediateBuffer) { - int tmp = ReadRawByte(); + int tmp = ReadRawByte(ref immediateBuffer); if (tmp < 128) { - return (uint) tmp; + return (uint)tmp; } int result = tmp & 0x7f; - if ((tmp = ReadRawByte()) < 128) + if ((tmp = ReadRawByte(ref immediateBuffer)) < 128) { result |= tmp << 7; } else { result |= (tmp & 0x7f) << 7; - if ((tmp = ReadRawByte()) < 128) + if ((tmp = ReadRawByte(ref immediateBuffer)) < 128) { result |= tmp << 14; } else { result |= (tmp & 0x7f) << 14; - if ((tmp = ReadRawByte()) < 128) + if ((tmp = ReadRawByte(ref immediateBuffer)) < 128) { result |= tmp << 21; } else { result |= (tmp & 0x7f) << 21; - result |= (tmp = ReadRawByte()) << 28; + result |= (tmp = ReadRawByte(ref immediateBuffer)) << 28; if (tmp >= 128) { // Discard upper 32 bits. for (int i = 0; i < 5; i++) { - if (ReadRawByte() < 128) + if (ReadRawByte(ref immediateBuffer) < 128) { - return (uint) result; + return (uint)result; } } throw InvalidProtocolBufferException.MalformedVarint(); @@ -741,7 +1137,29 @@ private uint SlowReadRawVarint32() } } } - return (uint) result; + return (uint)result; + } + + [SecurityCritical] + private uint ReadRawWrappedVarint32(ref ReadOnlySpan immediateBuffer) + { + int oldLimit = BeginReadNested(ref immediateBuffer); + uint tag; + uint value = default(uint); + while ((tag = ReadTag(ref immediateBuffer)) != 0) + { + if (tag == WellKnownTypes.WrappersReflection.WrapperValueVarintTag) + { + value = ReadRawVarint32(ref immediateBuffer); + } + else + { + SkipLastField(ref immediateBuffer); + } + } + EndReadNested(oldLimit); + + return value; } /// @@ -750,41 +1168,53 @@ private uint SlowReadRawVarint32() /// That means we can check the size just once, then just read directly from the buffer /// without constant rechecking of the buffer length. /// + [SecuritySafeCritical] internal uint ReadRawVarint32() + { + var immediateBuffer = ImmediateBuffer; + return ReadRawVarint32(ref immediateBuffer); + } + + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + private uint ReadRawVarint32(ref ReadOnlySpan immediateBuffer) { if (bufferPos + 5 > bufferSize) { - return SlowReadRawVarint32(); + return SlowReadRawVarint32(ref immediateBuffer); } - int tmp = buffer[bufferPos++]; + int tmp = immediateBuffer[bufferPos++]; if (tmp < 128) { - return (uint) tmp; + return (uint)tmp; } int result = tmp & 0x7f; - if ((tmp = buffer[bufferPos++]) < 128) + if ((tmp = immediateBuffer[bufferPos++]) < 128) { result |= tmp << 7; } else { result |= (tmp & 0x7f) << 7; - if ((tmp = buffer[bufferPos++]) < 128) + if ((tmp = immediateBuffer[bufferPos++]) < 128) { result |= tmp << 14; } else { result |= (tmp & 0x7f) << 14; - if ((tmp = buffer[bufferPos++]) < 128) + if ((tmp = immediateBuffer[bufferPos++]) < 128) { result |= tmp << 21; } else { result |= (tmp & 0x7f) << 21; - result |= (tmp = buffer[bufferPos++]) << 28; + result |= (tmp = immediateBuffer[bufferPos++]) << 28; if (tmp >= 128) { // Discard upper 32 bits. @@ -793,9 +1223,9 @@ internal uint ReadRawVarint32() // use the fast path in more cases, and we rarely hit this section of code. for (int i = 0; i < 5; i++) { - if (ReadRawByte() < 128) + if (ReadRawByte(ref immediateBuffer) < 128) { - return (uint) result; + return (uint)result; } } throw InvalidProtocolBufferException.MalformedVarint(); @@ -803,7 +1233,7 @@ internal uint ReadRawVarint32() } } } - return (uint) result; + return (uint)result; } /// @@ -829,7 +1259,7 @@ internal static uint ReadRawVarint32(Stream input) result |= (b & 0x7f) << offset; if ((b & 0x80) == 0) { - return (uint) result; + return (uint)result; } } // Keep reading up to 64 bits. @@ -842,7 +1272,7 @@ internal static uint ReadRawVarint32(Stream input) } if ((b & 0x80) == 0) { - return (uint) result; + return (uint)result; } } throw InvalidProtocolBufferException.MalformedVarint(); @@ -851,14 +1281,27 @@ internal static uint ReadRawVarint32(Stream input) /// /// Reads a raw varint from the stream. /// + [SecuritySafeCritical] internal ulong ReadRawVarint64() { + var immediateBuffer = ImmediateBuffer; + return ReadRawVarint64(ref immediateBuffer); + } + + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + internal ulong ReadRawVarint64(ref ReadOnlySpan immediateBuffer) + { + //TODO: Implement fast path int shift = 0; ulong result = 0; while (shift < 64) { - byte b = ReadRawByte(); - result |= (ulong) (b & 0x7F) << shift; + byte b = ReadRawByte(ref immediateBuffer); + result |= (ulong)(b & 0x7F) << shift; if ((b & 0x80) == 0) { return result; @@ -868,31 +1311,107 @@ internal ulong ReadRawVarint64() throw InvalidProtocolBufferException.MalformedVarint(); } + [SecurityCritical] + private ulong ReadRawWrappedVarint64(ref ReadOnlySpan immediateBuffer) + { + int oldLimit = BeginReadNested(ref immediateBuffer); + uint tag; + ulong value = default(ulong); + while ((tag = ReadTag(ref immediateBuffer)) != 0) + { + if (tag == WellKnownTypes.WrappersReflection.WrapperValueVarintTag) + { + value = ReadRawVarint64(ref immediateBuffer); + } + else + { + SkipLastField(ref immediateBuffer); + } + } + EndReadNested(oldLimit); + + return value; + } + /// /// Reads a 32-bit little-endian integer from the stream. /// + [SecuritySafeCritical] internal uint ReadRawLittleEndian32() { - uint b1 = ReadRawByte(); - uint b2 = ReadRawByte(); - uint b3 = ReadRawByte(); - uint b4 = ReadRawByte(); + var immediateBuffer = ImmediateBuffer; + return ReadRawLittleEndian32(ref immediateBuffer); + } + + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + private uint ReadRawLittleEndian32(ref ReadOnlySpan immediateBuffer) + { + if (bufferPos + 4 > bufferSize) + { + return SlowReadRawLittleEndian32(ref immediateBuffer); + } + else + { + var ret = BinaryPrimitives.ReadUInt32LittleEndian(immediateBuffer.Slice(bufferPos, 4)); + bufferPos += 4; + return ret; + } + } + + [SecurityCritical] + private uint SlowReadRawLittleEndian32(ref ReadOnlySpan immediateBuffer) + { + uint b1 = ReadRawByte(ref immediateBuffer); + uint b2 = ReadRawByte(ref immediateBuffer); + uint b3 = ReadRawByte(ref immediateBuffer); + uint b4 = ReadRawByte(ref immediateBuffer); return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24); } /// /// Reads a 64-bit little-endian integer from the stream. /// - internal ulong ReadRawLittleEndian64() + [SecuritySafeCritical] + public ulong ReadRawLittleEndian64() + { + var immediateBuffer = ImmediateBuffer; + return ReadRawLittleEndian64(ref immediateBuffer); + } + + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + internal ulong ReadRawLittleEndian64(ref ReadOnlySpan immediateBuffer) { - ulong b1 = ReadRawByte(); - ulong b2 = ReadRawByte(); - ulong b3 = ReadRawByte(); - ulong b4 = ReadRawByte(); - ulong b5 = ReadRawByte(); - ulong b6 = ReadRawByte(); - ulong b7 = ReadRawByte(); - ulong b8 = ReadRawByte(); + if (bufferPos + 8 > bufferSize) + { + return SlowReadRawLittleEndian64(ref immediateBuffer); + } + else + { + var ret = BinaryPrimitives.ReadUInt64LittleEndian(immediateBuffer.Slice(bufferPos, 8)); + bufferPos += 8; + return ret; + } + } + + [SecurityCritical] + private ulong SlowReadRawLittleEndian64(ref ReadOnlySpan immediateBuffer) + { + ulong b1 = ReadRawByte(ref immediateBuffer); + ulong b2 = ReadRawByte(ref immediateBuffer); + ulong b3 = ReadRawByte(ref immediateBuffer); + ulong b4 = ReadRawByte(ref immediateBuffer); + ulong b5 = ReadRawByte(ref immediateBuffer); + ulong b6 = ReadRawByte(ref immediateBuffer); + ulong b7 = ReadRawByte(ref immediateBuffer); + ulong b8 = ReadRawByte(ref immediateBuffer); return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24) | (b5 << 32) | (b6 << 40) | (b7 << 48) | (b8 << 56); } @@ -934,7 +1453,8 @@ internal static long DecodeZigZag64(ulong n) /// limit is returned. /// /// The old limit. - internal int PushLimit(int byteLimit) + [EditorBrowsable(EditorBrowsableState.Never)] + public int PushLimit(int byteLimit) { if (byteLimit < 0) { @@ -972,7 +1492,8 @@ private void RecomputeBufferSizeAfterLimit() /// /// Discards the current limit, returning the previous limit. /// - internal void PopLimit(int oldLimit) + [EditorBrowsable(EditorBrowsableState.Never)] + public void PopLimit(int oldLimit) { currentLimit = oldLimit; RecomputeBufferSizeAfterLimit(); @@ -982,7 +1503,8 @@ internal void PopLimit(int oldLimit) /// Returns whether or not all the data before the limit has been read. /// /// - internal bool ReachedLimit + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ReachedLimit { get { @@ -1002,19 +1524,29 @@ internal bool ReachedLimit /// public bool IsAtEnd { - get { return bufferPos == bufferSize && !RefillBuffer(false); } + [SecuritySafeCritical] + get + { + var immediateBuffer = ImmediateBuffer; + return IsAtEndCore(ref immediateBuffer); + } } + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [SecurityCritical] + internal bool IsAtEndCore(ref ReadOnlySpan immediateBuffer) => bufferPos == bufferSize && (input == null && !hasNativeInput || !RefillBuffer(false, ref immediateBuffer)); + /// /// Called when buffer is empty to read more bytes from the - /// input. If is true, RefillBuffer() gurantees that + /// input. If mustSucceed is true, RefillBuffer() gurantees that /// either there will be at least one byte in the buffer when it returns - /// or it will throw an exception. If is false, + /// or it will throw an exception. If mustSucceed is false, /// RefillBuffer() returns false if no more bytes were available. /// - /// - /// - private bool RefillBuffer(bool mustSucceed) + [SecurityCritical] + private bool RefillBuffer(bool mustSucceed, ref ReadOnlySpan immediateBuffer) { if (bufferPos < bufferSize) { @@ -1037,7 +1569,29 @@ private bool RefillBuffer(bool mustSucceed) totalBytesRetired += bufferSize; bufferPos = 0; - bufferSize = (input == null) ? 0 : input.Read(buffer, 0, buffer.Length); + if (input != null) + { + bufferSize = input.Read(buffer, 0, buffer.Length); + } + else if (hasNativeInput) + { + nativeInputPastBuffersLength += bufferSize; + if (nativeInput.MoveNext()) + { + currentNativeBuffer = nativeInput.Current; + immediateBuffer = nativeInput.Current.Span; + bufferSize = immediateBuffer.Length; + } + else + { + bufferSize = 0; + } + } + else + { + bufferSize = 0; + } + if (bufferSize < 0) { throw new InvalidOperationException("Stream.Read returned a negative count"); @@ -1072,13 +1626,25 @@ private bool RefillBuffer(bool mustSucceed) /// /// the end of the stream or the current limit was reached /// + [SecuritySafeCritical] internal byte ReadRawByte() + { + var immediateBuffer = ImmediateBuffer; + return ReadRawByte(ref immediateBuffer); + } + + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + private byte ReadRawByte(ref ReadOnlySpan immediateBuffer) { if (bufferPos == bufferSize) { - RefillBuffer(true); + RefillBuffer(true, ref immediateBuffer); } - return buffer[bufferPos++]; + return immediateBuffer[bufferPos++]; } /// @@ -1087,7 +1653,19 @@ internal byte ReadRawByte() /// /// the end of the stream or the current limit was reached /// + [SecuritySafeCritical] internal byte[] ReadRawBytes(int size) + { + var immediateBuffer = ImmediateBuffer; + return ReadRawBytes(size, ref immediateBuffer); + } + + /// + /// This supports the Protocol Buffers infrastructure and is not meant to be used directly from your code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [SecurityCritical] + private byte[] ReadRawBytes(int size, ref ReadOnlySpan immediateBuffer) { if (size < 0) { @@ -1097,7 +1675,7 @@ internal byte[] ReadRawBytes(int size) if (totalBytesRetired + bufferPos + size > currentLimit) { // Read to the end of the stream (up to the current limit) anyway. - SkipRawBytes(currentLimit - totalBytesRetired - bufferPos); + SkipRawBytes(currentLimit - totalBytesRetired - bufferPos, ref immediateBuffer); // Then fail. throw InvalidProtocolBufferException.TruncatedMessage(); } @@ -1106,35 +1684,36 @@ internal byte[] ReadRawBytes(int size) { // We have all the bytes we need already. byte[] bytes = new byte[size]; - ByteArray.Copy(buffer, bufferPos, bytes, 0, size); + immediateBuffer.Slice(bufferPos, size).CopyTo(bytes); bufferPos += size; return bytes; } - else if (size < buffer.Length) + else if (size < immediateBuffer.Length || hasNativeInput) { // 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. + // of bytes or all the bytes are already in a contiguous buffer. + // We can safely allocate the resulting array ahead of time. // First copy what we have. byte[] bytes = new byte[size]; int pos = bufferSize - bufferPos; - ByteArray.Copy(buffer, bufferPos, bytes, 0, pos); + immediateBuffer.Slice(bufferPos, pos).CopyTo(bytes); bufferPos = 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. - RefillBuffer(true); + RefillBuffer(true, ref immediateBuffer); while (size - pos > bufferSize) { - Buffer.BlockCopy(buffer, 0, bytes, pos, bufferSize); + immediateBuffer.Slice(0, bufferSize).CopyTo(bytes.AsSpan(pos, bufferSize)); pos += bufferSize; bufferPos = bufferSize; - RefillBuffer(true); + RefillBuffer(true, ref immediateBuffer); } - ByteArray.Copy(buffer, 0, bytes, pos, size - pos); + immediateBuffer.Slice(0, size - pos).CopyTo(bytes.AsSpan(pos)); bufferPos = size - pos; return bytes; @@ -1165,7 +1744,7 @@ internal byte[] ReadRawBytes(int size) while (sizeLeft > 0) { - byte[] chunk = new byte[Math.Min(sizeLeft, buffer.Length)]; + byte[] chunk = new byte[Math.Min(sizeLeft, immediateBuffer.Length)]; int pos = 0; while (pos < chunk.Length) { @@ -1186,7 +1765,7 @@ internal byte[] ReadRawBytes(int size) // Start by copying the leftover bytes from this.buffer. int newPos = originalBufferSize - originalBufferPos; - ByteArray.Copy(buffer, originalBufferPos, bytes, 0, newPos); + immediateBuffer.Slice(originalBufferPos, newPos).CopyTo(bytes); // And now all the chunks. foreach (byte[] chunk in chunks) @@ -1205,7 +1784,8 @@ internal byte[] ReadRawBytes(int size) /// /// the end of the stream /// or the current limit was reached - private void SkipRawBytes(int size) + [SecurityCritical] + private void SkipRawBytes(int size, ref ReadOnlySpan immediateBuffer) { if (size < 0) { @@ -1215,7 +1795,7 @@ private void SkipRawBytes(int size) if (totalBytesRetired + bufferPos + size > currentLimit) { // Read to the end of the stream anyway. - SkipRawBytes(currentLimit - totalBytesRetired - bufferPos); + SkipRawBytes(currentLimit - totalBytesRetired - bufferPos, ref immediateBuffer); // Then fail. throw InvalidProtocolBufferException.TruncatedMessage(); } @@ -1233,18 +1813,18 @@ private void SkipRawBytes(int size) // ROK 5/7/2013 Issue #54: should retire all bytes in buffer (bufferSize) // totalBytesRetired += pos; totalBytesRetired += bufferSize; - + bufferPos = 0; bufferSize = 0; // Then skip directly from the InputStream for the rest. if (pos < size) { - if (input == null) + if (input == null && !hasNativeInput) { throw InvalidProtocolBufferException.TruncatedMessage(); } - SkipImpl(size - pos); + SkipImpl(size - pos, ref immediateBuffer); totalBytesRetired += size - pos; } } @@ -1253,9 +1833,34 @@ private void SkipRawBytes(int size) /// /// Abstraction of skipping to cope with streams which can't really skip. /// - private void SkipImpl(int amountToSkip) + [SecuritySafeCritical] + private void SkipImpl(int amountToSkip, ref ReadOnlySpan immediateBuffer) { - if (input.CanSeek) + if (hasNativeInput) + { + while (amountToSkip > 0) + { + if (!nativeInput.MoveNext()) + { + throw InvalidProtocolBufferException.TruncatedMessage(); + } + currentNativeBuffer = nativeInput.Current; + immediateBuffer = nativeInput.Current.Span; + bufferSize = immediateBuffer.Length; + bufferPos = 0; + if (bufferSize < amountToSkip) + { + bufferPos = bufferSize; + amountToSkip -= bufferSize; + } + else + { + bufferPos += amountToSkip; + amountToSkip = 0; + } + } + } + else if (input.CanSeek) { long previousPosition = input.Position; input.Position += amountToSkip; diff --git a/csharp/src/Google.Protobuf/WellKnownTypes/WrappersPartial.cs b/csharp/src/Google.Protobuf/WellKnownTypes/WrappersPartial.cs index 9f620eb4012e..fbef8424a78c 100644 --- a/csharp/src/Google.Protobuf/WellKnownTypes/WrappersPartial.cs +++ b/csharp/src/Google.Protobuf/WellKnownTypes/WrappersPartial.cs @@ -38,5 +38,24 @@ public static partial class WrappersReflection /// Field number for the single "value" field in all wrapper types. /// internal const int WrapperValueFieldNumber = Int32Value.ValueFieldNumber; + + + internal static readonly uint WrapperValueVarintTag = WireFormat.MakeTag(WrapperValueFieldNumber, WireFormat.WireType.Varint); + + internal static readonly uint WrapperValueFixed32Tag = WireFormat.MakeTag(WrapperValueFieldNumber, WireFormat.WireType.Fixed32); + + internal static readonly uint WrapperValueFixed64Tag = WireFormat.MakeTag(WrapperValueFieldNumber, WireFormat.WireType.Fixed64); + + internal static readonly uint WrapperValueLengthDelimitedTag = WireFormat.MakeTag(WrapperValueFieldNumber, WireFormat.WireType.LengthDelimited); + + + + internal static readonly byte WrapperValueVarintTagByte = (byte)WireFormat.MakeTag(WrapperValueFieldNumber, WireFormat.WireType.Varint); + + internal static readonly byte WrapperValueFixed32TagByte = (byte)WireFormat.MakeTag(WrapperValueFieldNumber, WireFormat.WireType.Fixed32); + + internal static readonly byte WrapperValueFixed64TagByte = (byte)WireFormat.MakeTag(WrapperValueFieldNumber, WireFormat.WireType.Fixed64); + + internal static readonly byte WrapperValueLengthDelimitedTagByte = (byte)WireFormat.MakeTag(WrapperValueFieldNumber, WireFormat.WireType.LengthDelimited); } }