bring in new files

pull/7351/head
Jan Tattermusch 5 years ago
parent 50e03cdde3
commit 175c96565f
  1. 1179
      csharp/src/Google.Protobuf/CodedInputStream.cs
  2. 63
      csharp/src/Google.Protobuf/IBufferMessage.cs
  3. 322
      csharp/src/Google.Protobuf/ParseContext.cs
  4. 116
      csharp/src/Google.Protobuf/ParserInternalState.cs
  5. 726
      csharp/src/Google.Protobuf/ParsingPrimitives.cs
  6. 231
      csharp/src/Google.Protobuf/ParsingPrimitivesMessages.cs
  7. 352
      csharp/src/Google.Protobuf/ParsingPrimitivesWrappers.cs
  8. 283
      csharp/src/Google.Protobuf/SegmentedBufferHelper.cs

File diff suppressed because it is too large Load Diff

@ -0,0 +1,63 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
namespace Google.Protobuf
{
#if GOOGLE_PROTOBUF_SUPPORT_SYSTEM_MEMORY
/// <summary>
/// Interface for a Protocol Buffers message, supporting
/// <see cref="CodedInputReader"/> and <see cref="CodedOutputWriter"/>
/// serialization operations.
/// </summary>
public interface IBufferMessage : IMessage
{
/// <summary>
/// Merges the data from the specified <see cref="CodedInputReader"/> with the current message.
/// </summary>
/// <remarks>See the user guide for precise merge semantics.</remarks>
/// <param name="input"><see cref="CodedInputReader"/> to read data from. Must not be null.</param>
void MergeFrom(ref CodedInputReader input);
/// <summary>
/// Internal implementation of merging data from given parse context into this message.
/// Users should never invoke this method directly.
/// </summary>
void MergeFrom_Internal(ref ParseContext ctx);
/// <summary>
/// Writes the data to the given <see cref="CodedOutputWriter"/>.
/// </summary>
/// <param name="output"><see cref="CodedOutputWriter"/> to write the data to. Must not be null.</param>
void WriteTo(ref CodedOutputWriter output);
}
#endif
}

@ -0,0 +1,322 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using Google.Protobuf.Collections;
namespace Google.Protobuf
{
/// <summary>
/// An opaque struct that represents the current parsing state and is passed along
/// as the parsing proceeds.
/// All the public methods are intended to be invoked only by the generated code,
/// users should never invoke them directly.
/// </summary>
[SecuritySafeCritical]
public ref struct ParseContext
{
internal const int DefaultRecursionLimit = 100;
internal const int DefaultSizeLimit = Int32.MaxValue;
internal ReadOnlySpan<byte> buffer;
internal ParserInternalState state;
internal ParseContext(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
this.buffer = buffer;
this.state = state;
}
/// <summary>
/// Creates a ParseContext instance from CodedInputStream.
/// WARNING: internally this copies the CodedInputStream's state, so after done with the ParseContext,
/// the CodedInputStream's state needs to be updated.
/// </summary>
internal ParseContext(CodedInputStream input)
{
this.buffer = new ReadOnlySpan<byte>(input.InternalBuffer);
// TODO: ideally we would use a reference to the original state, but that doesn't seem possible
this.state = input.InternalState; // creates copy of the state
}
internal ParseContext(ReadOnlySequence<byte> input) : this(input, DefaultRecursionLimit)
{
}
internal ParseContext(ReadOnlySequence<byte> input, int recursionLimit)
{
this.buffer = default;
this.state = default;
this.state.lastTag = 0;
this.state.recursionDepth = 0;
this.state.sizeLimit = DefaultSizeLimit;
this.state.recursionLimit = recursionLimit;
this.state.currentLimit = int.MaxValue;
this.state.segmentedBufferHelper = new SegmentedBufferHelper(input, out this.buffer);
this.state.bufferPos = 0;
this.state.bufferSize = this.buffer.Length;
this.state.codedInputStream = null;
this.state.DiscardUnknownFields = false;
this.state.ExtensionRegistry = null;
}
/// <summary>
/// Returns the last tag read, or 0 if no tags have been read or we've read beyond
/// the end of the input.
/// </summary>
internal uint LastTag { get { return state.lastTag; } }
/// <summary>
/// Internal-only property; when set to true, unknown fields will be discarded while parsing.
/// </summary>
internal bool DiscardUnknownFields {
get { return state.DiscardUnknownFields; }
set { state.DiscardUnknownFields = value; }
}
/// <summary>
/// Internal-only property; provides extension identifiers to compatible messages while parsing.
/// </summary>
internal ExtensionRegistry ExtensionRegistry
{
get { return state.ExtensionRegistry; }
set { state.ExtensionRegistry = value; }
}
/// <summary>
/// Reads a field tag, returning the tag of 0 for "end of input".
/// </summary>
/// <remarks>
/// If this method returns 0, it doesn't necessarily mean the end of all
/// the data in this CodedInputReader; it may be the end of the logical input
/// for an embedded message, for example.
/// </remarks>
/// <returns>The next field tag, or 0 for end of input. (0 is never a valid tag.)</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint ReadTag()
{
return ParsingPrimitives.ParseTag(ref buffer, ref state);
}
/// <summary>
/// Reads a double field from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public double ReadDouble()
{
return ParsingPrimitives.ParseDouble(ref buffer, ref state);
}
/// <summary>
/// Reads a float field from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float ReadFloat()
{
return ParsingPrimitives.ParseFloat(ref buffer, ref state);
}
/// <summary>
/// Reads a uint64 field from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ulong ReadUInt64()
{
return ParsingPrimitives.ParseRawVarint64(ref buffer, ref state);
}
/// <summary>
/// Reads an int64 field from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public long ReadInt64()
{
return (long)ParsingPrimitives.ParseRawVarint64(ref buffer, ref state);
}
/// <summary>
/// Reads an int32 field from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadInt32()
{
return (int)ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
}
/// <summary>
/// Reads a fixed64 field from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ulong ReadFixed64()
{
return ParsingPrimitives.ParseRawLittleEndian64(ref buffer, ref state);
}
/// <summary>
/// Reads a fixed32 field from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint ReadFixed32()
{
return ParsingPrimitives.ParseRawLittleEndian32(ref buffer, ref state);
}
/// <summary>
/// Reads a bool field from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool ReadBool()
{
return ParsingPrimitives.ParseRawVarint64(ref buffer, ref state) != 0;
}
/// <summary>
/// Reads a string field from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadString()
{
int length = ParsingPrimitives.ParseLength(ref buffer, ref state);
return ParsingPrimitives.ReadRawString(ref buffer, ref state, length);
}
/// <summary>
/// Reads an embedded message field value from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadMessage(IMessage message)
{
// TODO: add a fallback if IMessage does not implement IBufferMessage
ParsingPrimitivesMessages.ReadMessage(ref this, message);
}
/// <summary>
/// Reads an embedded group field from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadGroup(IMessage message)
{
ParsingPrimitivesMessages.ReadGroup(ref this, message);
}
/// <summary>
/// Reads a bytes field value from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ByteString ReadBytes()
{
int length = ParsingPrimitives.ParseLength(ref buffer, ref state);
return ByteString.AttachBytes(ParsingPrimitives.ReadRawBytes(ref buffer, ref state, length));
}
/// <summary>
/// Reads a uint32 field value from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint ReadUInt32()
{
return ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
}
/// <summary>
/// Reads an enum field value from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadEnum()
{
// Currently just a pass-through, but it's nice to separate it logically from WriteInt32.
return (int)ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
}
/// <summary>
/// Reads an sfixed32 field value from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadSFixed32()
{
return (int)ParsingPrimitives.ParseRawLittleEndian32(ref buffer, ref state);
}
/// <summary>
/// Reads an sfixed64 field value from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public long ReadSFixed64()
{
return (long)ParsingPrimitives.ParseRawLittleEndian64(ref buffer, ref state);
}
/// <summary>
/// Reads an sint32 field value from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadSInt32()
{
return ParsingPrimitives.DecodeZigZag32(ParsingPrimitives.ParseRawVarint32(ref buffer, ref state));
}
/// <summary>
/// Reads an sint64 field value from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public long ReadSInt64()
{
return ParsingPrimitives.DecodeZigZag64(ParsingPrimitives.ParseRawVarint64(ref buffer, ref state));
}
/// <summary>
/// Reads a length for length-delimited data.
/// </summary>
/// <remarks>
/// This is internally just reading a varint, but this method exists
/// to make the calling code clearer.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadLength()
{
return (int)ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
}
internal void CopyStateTo(CodedInputStream input)
{
input.InternalState = state;
}
}
}

@ -0,0 +1,116 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using Google.Protobuf.Collections;
namespace Google.Protobuf
{
// warning: this is a mutable struct, so it needs to be only passed as a ref!
internal struct ParserInternalState
{
// NOTE: the Span representing the current buffer is kept separate so that this doesn't have to be a ref struct and so it can live
// be included in CodedInputStream's internal state
/// <summary>
/// The position within the current buffer (i.e. the next byte to read)
/// </summary>
internal int bufferPos;
/// <summary>
/// Size of the current buffer
/// </summary>
internal int bufferSize;
/// <summary>
/// If we are currently inside a length-delimited block, this is the number of
/// bytes in the buffer that are still available once we leave the delimited block.
/// </summary>
internal int bufferSizeAfterLimit;
/// <summary>
/// The absolute position of the end of the current length-delimited block (including totalBytesRetired)
/// </summary>
internal int currentLimit;
/// <summary>
/// The total number of consumed before the start of the current buffer. The
/// total bytes read up to the current position can be computed as
/// totalBytesRetired + bufferPos.
/// </summary>
internal int totalBytesRetired;
internal int recursionDepth; // current recursion depth
internal SegmentedBufferHelper segmentedBufferHelper;
// TODO: remember if this context is supposed to call MergeFrom(CodedInputStream cis) or MergeFrom(ref ParseContext ....) for submessages.
// if non-null, the top level parse method was started with given cis as an argument
internal CodedInputStream codedInputStream;
/// <summary>
/// The last tag we read. 0 indicates we've read to the end of the stream
/// (or haven't read anything yet).
/// </summary>
internal uint lastTag;
/// <summary>
/// The next tag, used to store the value read by PeekTag.
/// </summary>
internal uint nextTag;
internal bool hasNextTag;
// TODO: these fields are configuration, they should be readonly
internal int sizeLimit;
internal int recursionLimit;
/// <summary>
/// Internal-only property; when set to true, unknown fields will be discarded while parsing.
/// </summary>
internal bool DiscardUnknownFields { get; set; }
/// <summary>
/// Internal-only property; provides extension identifiers to compatible messages while parsing.
/// </summary>
internal ExtensionRegistry ExtensionRegistry { get; set; }
}
}

@ -0,0 +1,726 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using Google.Protobuf.Collections;
namespace Google.Protobuf
{
/// <summary>
/// Primitives for parsing protobuf wire format.
/// </summary>
internal static class ParsingPrimitives
{
/// <summary>
/// Reads a length for length-delimited data.
/// </summary>
/// <remarks>
/// This is internally just reading a varint, but this method exists
/// 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);
}
/// <summary>
/// Parses the next tag.
/// If the end of logical stream was reached, an invalid tag of 0 is returned.
/// </summary>
public static uint ParseTag(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
// The "nextTag" logic is there only as an optimization for reading non-packed repeated / map
// fields and is strictly speaking not necessary.
// TODO(jtattermusch): look into simplifying the ParseTag logic.
if (state.hasNextTag)
{
state.lastTag = state.nextTag;
state.hasNextTag = false;
return state.lastTag;
}
// Optimize for the incredibly common case of having at least two bytes left in the buffer,
// and those two bytes being enough to get the tag. This will be true for fields up to 4095.
if (state.bufferPos + 2 <= state.bufferSize)
{
int tmp = buffer[state.bufferPos++];
if (tmp < 128)
{
state.lastTag = (uint)tmp;
}
else
{
int result = tmp & 0x7f;
if ((tmp = buffer[state.bufferPos++]) < 128)
{
result |= tmp << 7;
state.lastTag = (uint) result;
}
else
{
// Nope, rewind and go the potentially slow route.
state.bufferPos -= 2;
state.lastTag = ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
}
}
}
else
{
if (SegmentedBufferHelper.IsAtEnd(ref buffer, ref state))
{
state.lastTag = 0;
return 0;
}
state.lastTag = ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
}
if (WireFormat.GetTagFieldNumber(state.lastTag) == 0)
{
// If we actually read a tag with a field of 0, that's not a valid tag.
throw InvalidProtocolBufferException.InvalidTag();
}
return state.lastTag;
}
/// <summary>
/// Peeks at the next tag in the stream. If it matches <paramref name="tag"/>,
/// the tag is consumed and the method returns <c>true</c>; otherwise, the
/// stream is left in the original position and the method returns <c>false</c>.
/// </summary>
public static bool MaybeConsumeTag(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, uint tag)
{
if (PeekTag(ref buffer, ref state) == tag)
{
state.hasNextTag = false;
return true;
}
return false;
}
/// <summary>
/// Peeks at the next field tag. This is like calling <see cref="ReadTag"/>, but the
/// tag is not consumed. (So a subsequent call to <see cref="ReadTag"/> will return the
/// same value.)
/// </summary>
public static uint PeekTag(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
if (state.hasNextTag)
{
return state.nextTag;
}
uint savedLast = state.lastTag;
state.nextTag = ParseTag(ref buffer, ref state);
state.hasNextTag = true;
state.lastTag = savedLast; // Undo the side effect of ReadTag
return state.nextTag;
}
/// <summary>
/// Parses a raw varint.
/// </summary>
public static ulong ParseRawVarint64(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
if (state.bufferPos + 10 > state.bufferSize)
{
return ParseRawVarint64SlowPath(ref buffer, ref state);
}
ulong result = buffer[state.bufferPos++];
if (result < 128)
{
return result;
}
result &= 0x7f;
int shift = 7;
do
{
byte b = buffer[state.bufferPos++];
result |= (ulong)(b & 0x7F) << shift;
if (b < 0x80)
{
return result;
}
shift += 7;
}
while (shift < 64);
throw InvalidProtocolBufferException.MalformedVarint();
}
private static ulong ParseRawVarint64SlowPath(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
int shift = 0;
ulong result = 0;
do
{
byte b = ReadRawByte(ref buffer, ref state);
result |= (ulong)(b & 0x7F) << shift;
if (b < 0x80)
{
return result;
}
shift += 7;
}
while (shift < 64);
throw InvalidProtocolBufferException.MalformedVarint();
}
/// <summary>
/// Parses a raw Varint. If larger than 32 bits, discard the upper bits.
/// This method is optimised for the case where we've got lots of data in the buffer.
/// That means we can check the size just once, then just read directly from the buffer
/// without constant rechecking of the buffer length.
/// </summary>
public static uint ParseRawVarint32(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
if (state.bufferPos + 5 > state.bufferSize)
{
return ParseRawVarint32SlowPath(ref buffer, ref state);
}
int tmp = buffer[state.bufferPos++];
if (tmp < 128)
{
return (uint)tmp;
}
int result = tmp & 0x7f;
if ((tmp = buffer[state.bufferPos++]) < 128)
{
result |= tmp << 7;
}
else
{
result |= (tmp & 0x7f) << 7;
if ((tmp = buffer[state.bufferPos++]) < 128)
{
result |= tmp << 14;
}
else
{
result |= (tmp & 0x7f) << 14;
if ((tmp = buffer[state.bufferPos++]) < 128)
{
result |= tmp << 21;
}
else
{
result |= (tmp & 0x7f) << 21;
result |= (tmp = buffer[state.bufferPos++]) << 28;
if (tmp >= 128)
{
// Discard upper 32 bits.
// Note that this has to use ReadRawByte() as we only ensure we've
// got at least 5 bytes at the start of the method. This lets us
// use the fast path in more cases, and we rarely hit this section of code.
for (int i = 0; i < 5; i++)
{
if (ReadRawByte(ref buffer, ref state) < 128)
{
return (uint) result;
}
}
throw InvalidProtocolBufferException.MalformedVarint();
}
}
}
}
return (uint)result;
}
private static uint ParseRawVarint32SlowPath(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
int tmp = ReadRawByte(ref buffer, ref state);
if (tmp < 128)
{
return (uint) tmp;
}
int result = tmp & 0x7f;
if ((tmp = ReadRawByte(ref buffer, ref state)) < 128)
{
result |= tmp << 7;
}
else
{
result |= (tmp & 0x7f) << 7;
if ((tmp = ReadRawByte(ref buffer, ref state)) < 128)
{
result |= tmp << 14;
}
else
{
result |= (tmp & 0x7f) << 14;
if ((tmp = ReadRawByte(ref buffer, ref state)) < 128)
{
result |= tmp << 21;
}
else
{
result |= (tmp & 0x7f) << 21;
result |= (tmp = ReadRawByte(ref buffer, ref state)) << 28;
if (tmp >= 128)
{
// Discard upper 32 bits.
for (int i = 0; i < 5; i++)
{
if (ReadRawByte(ref buffer, ref state) < 128)
{
return (uint) result;
}
}
throw InvalidProtocolBufferException.MalformedVarint();
}
}
}
}
return (uint) result;
}
/// <summary>
/// Parses a 32-bit little-endian integer.
/// </summary>
public static uint ParseRawLittleEndian32(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
const int length = sizeof(uint);
if (state.bufferPos + length > state.bufferSize)
{
return ParseRawLittleEndian32SlowPath(ref buffer, ref state);
}
uint result = BinaryPrimitives.ReadUInt32LittleEndian(buffer.Slice(state.bufferPos, length));
state.bufferPos += length;
return result;
}
private static uint ParseRawLittleEndian32SlowPath(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
uint b1 = ReadRawByte(ref buffer, ref state);
uint b2 = ReadRawByte(ref buffer, ref state);
uint b3 = ReadRawByte(ref buffer, ref state);
uint b4 = ReadRawByte(ref buffer, ref state);
return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24);
}
/// <summary>
/// Parses a 64-bit little-endian integer.
/// </summary>
public static ulong ParseRawLittleEndian64(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
const int length = sizeof(ulong);
if (state.bufferPos + length > state.bufferSize)
{
return ParseRawLittleEndian64SlowPath(ref buffer, ref state);
}
ulong result = BinaryPrimitives.ReadUInt64LittleEndian(buffer.Slice(state.bufferPos, length));
state.bufferPos += length;
return result;
}
private static ulong ParseRawLittleEndian64SlowPath(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
ulong b1 = ReadRawByte(ref buffer, ref state);
ulong b2 = ReadRawByte(ref buffer, ref state);
ulong b3 = ReadRawByte(ref buffer, ref state);
ulong b4 = ReadRawByte(ref buffer, ref state);
ulong b5 = ReadRawByte(ref buffer, ref state);
ulong b6 = ReadRawByte(ref buffer, ref state);
ulong b7 = ReadRawByte(ref buffer, ref state);
ulong b8 = ReadRawByte(ref buffer, ref state);
return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24)
| (b5 << 32) | (b6 << 40) | (b7 << 48) | (b8 << 56);
}
/// <summary>
/// Parses a double value.
/// </summary>
public static double ParseDouble(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
const int length = sizeof(double);
if (!BitConverter.IsLittleEndian || state.bufferPos + length > state.bufferSize)
{
return BitConverter.Int64BitsToDouble((long)ParseRawLittleEndian64(ref buffer, ref state));
}
// ReadUnaligned uses processor architecture for endianness.
double result = Unsafe.ReadUnaligned<double>(ref MemoryMarshal.GetReference(buffer.Slice(state.bufferPos, length)));
state.bufferPos += length;
return result;
}
/// <summary>
/// Parses a float value.
/// </summary>
public static float ParseFloat(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
const int length = sizeof(float);
if (!BitConverter.IsLittleEndian || state.bufferPos + length > state.bufferSize)
{
return ParseFloatSlow(ref buffer, ref state);
}
// ReadUnaligned uses processor architecture for endianness.
float result = Unsafe.ReadUnaligned<float>(ref MemoryMarshal.GetReference(buffer.Slice(state.bufferPos, length)));
state.bufferPos += length;
return result;
}
private static unsafe float ParseFloatSlow(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
const int length = sizeof(float);
byte* stackBuffer = stackalloc byte[length];
Span<byte> tempSpan = new Span<byte>(stackBuffer, length);
for (int i = 0; i < length; i++)
{
tempSpan[i] = ReadRawByte(ref buffer, ref state);
}
// Content is little endian. Reverse if needed to match endianness of architecture.
if (!BitConverter.IsLittleEndian)
{
tempSpan.Reverse();
}
return Unsafe.ReadUnaligned<float>(ref MemoryMarshal.GetReference(tempSpan));
}
/// <summary>
/// Reads a fixed size of bytes from the input.
/// </summary>
/// <exception cref="InvalidProtocolBufferException">
/// the end of the stream or the current limit was reached
/// </exception>
public static byte[] ReadRawBytes(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int size)
{
if (size < 0)
{
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();
}
if (size <= state.bufferSize - state.bufferPos)
{
// We have all the bytes we need already.
byte[] bytes = new byte[size];
buffer.Slice(state.bufferPos, size).CopyTo(bytes);
state.bufferPos += size;
return bytes;
}
else if (size < buffer.Length || size < state.segmentedBufferHelper.TotalLength)
{
// 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;
return bytes;
}
else
{
// The size is very large. For security reasons, we can't allocate the
// entire byte array yet. The size comes directly from the input, so a
// maliciously-crafted message could provide a bogus very large size in
// order to trick the app into allocating a lot of memory. We avoid this
// by allocating and reading only a small chunk at a time, so that the
// malicious message must actually *be* extremely large to cause
// problems. Meanwhile, we limit the allowed size of a message elsewhere.
List<byte[]> chunks = new List<byte[]>();
int pos = state.bufferSize - state.bufferPos;
byte[] firstChunk = new byte[pos];
buffer.Slice(state.bufferPos, pos).CopyTo(firstChunk);
chunks.Add(firstChunk);
state.bufferPos = state.bufferSize;
// Read all the rest of the bytes we need.
int sizeLeft = size - pos;
while (sizeLeft > 0)
{
state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, true);
byte[] chunk = new byte[Math.Min(sizeLeft, state.bufferSize)];
buffer.Slice(0, chunk.Length)
.CopyTo(chunk);
state.bufferPos += chunk.Length;
sizeLeft -= chunk.Length;
chunks.Add(chunk);
}
// OK, got everything. Now concatenate it all into one buffer.
byte[] bytes = new byte[size];
int newPos = 0;
foreach (byte[] chunk in chunks)
{
Buffer.BlockCopy(chunk, 0, bytes, newPos, chunk.Length);
newPos += chunk.Length;
}
// Done.
return bytes;
}
}
/// <summary>
/// Reads and discards <paramref name="size"/> bytes.
/// </summary>
/// <exception cref="InvalidProtocolBufferException">the end of the stream
/// or the current limit was reached</exception>
public static void SkipRawBytes(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int size)
{
if (size < 0)
{
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();
}
if (size <= state.bufferSize - state.bufferPos)
{
// We have all the bytes we need already.
state.bufferPos += size;
}
else
{
// TODO: do we need to support skipping in seekable Streams?
// Skipping more bytes than are in the buffer. First skip what we have.
int pos = state.bufferSize - state.bufferPos;
state.bufferPos = state.bufferSize;
state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, true);
while (size - pos > state.bufferSize)
{
pos += state.bufferSize;
state.bufferPos = state.bufferSize;
state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, true);
}
state.bufferPos = size - pos;
}
}
public static string ReadString(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
int length = ParsingPrimitives.ParseLength(ref buffer, ref state);
return ParsingPrimitives.ReadRawString(ref buffer, ref state, length);
}
/// <summary>
/// Reads a bytes field value from the input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ByteString ReadBytes(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
int length = ParsingPrimitives.ParseLength(ref buffer, ref state);
return ByteString.AttachBytes(ParsingPrimitives.ReadRawBytes(ref buffer, ref state, length));
}
/// <summary>
/// Reads a UTF-8 string from the next "length" bytes.
/// </summary>
/// <exception cref="InvalidProtocolBufferException">
/// the end of the stream or the current limit was reached
/// </exception>
public static string ReadRawString(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int length)
{
// No need to read any data for an empty string.
if (length == 0)
{
return string.Empty;
}
if (length < 0)
{
throw InvalidProtocolBufferException.NegativeSize();
}
#if GOOGLE_PROTOBUF_SUPPORT_FAST_STRING
if (length <= state.bufferSize - state.bufferPos && length > 0)
{
// Fast path: all bytes to decode appear in the same span.
ReadOnlySpan<byte> data = buffer.Slice(state.bufferPos, length);
string value;
unsafe
{
fixed (byte* sourceBytes = &MemoryMarshal.GetReference(data))
{
value = CodedOutputStream.Utf8Encoding.GetString(sourceBytes, length);
}
}
state.bufferPos += length;
return value;
}
#endif
// TODO: what if GOOGLE_PROTOBUF_SUPPORT_FAST_STRING is not supported?
// -> can we still try to grab an array from the span?
// if (length <= state.bufferSize - state.bufferPos && length > 0)
// {
// // Fast path: We already have the bytes in a contiguous buffer, so
// // just copy directly from it.
// String result = CodedOutputStream.Utf8Encoding.GetString(buffer, state.bufferPos, length);
// state.bufferPos += length;
// return result;
// }
// TODO: creating a char[] and decoding into it and then creating a string from that array might be more efficient
// Slow path: Build a byte array first then copy it.
return CodedOutputStream.Utf8Encoding.GetString(ReadRawBytes(ref buffer, ref state, length), 0, length);
}
private static byte ReadRawByte(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
if (state.bufferPos == state.bufferSize)
{
state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, true);
}
return buffer[state.bufferPos++];
}
/// <summary>
/// Reads a varint from the input one byte at a time, so that it does not
/// read any bytes after the end of the varint. If you simply wrapped the
/// stream in a CodedInputStream and used ReadRawVarint32(Stream)
/// then you would probably end up reading past the end of the varint since
/// CodedInputStream buffers its input.
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static uint ReadRawVarint32(Stream input)
{
int result = 0;
int offset = 0;
for (; offset < 32; offset += 7)
{
int b = input.ReadByte();
if (b == -1)
{
throw InvalidProtocolBufferException.TruncatedMessage();
}
result |= (b & 0x7f) << offset;
if ((b & 0x80) == 0)
{
return (uint) result;
}
}
// Keep reading up to 64 bits.
for (; offset < 64; offset += 7)
{
int b = input.ReadByte();
if (b == -1)
{
throw InvalidProtocolBufferException.TruncatedMessage();
}
if ((b & 0x80) == 0)
{
return (uint) result;
}
}
throw InvalidProtocolBufferException.MalformedVarint();
}
/// <summary>
/// Decode a 32-bit value with ZigZag encoding.
/// </summary>
/// <remarks>
/// ZigZag encodes signed integers into values that can be efficiently
/// encoded with varint. (Otherwise, negative values must be
/// sign-extended to 32 bits to be varint encoded, thus always taking
/// 5 bytes on the wire.)
/// </remarks>
public static int DecodeZigZag32(uint n)
{
return (int)(n >> 1) ^ -(int)(n & 1);
}
/// <summary>
/// Decode a 64-bit value with ZigZag encoding.
/// </summary>
/// <remarks>
/// ZigZag encodes signed integers into values that can be efficiently
/// encoded with varint. (Otherwise, negative values must be
/// sign-extended to 64 bits to be varint encoded, thus always taking
/// 10 bytes on the wire.)
/// </remarks>
public static long DecodeZigZag64(ulong n)
{
return (long)(n >> 1) ^ -(long)(n & 1);
}
}
}

@ -0,0 +1,231 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
namespace Google.Protobuf
{
/// <summary>
/// Reading and skipping messages / groups
/// </summary>
internal static class ParsingPrimitivesMessages
{
public static void SkipLastField(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
if (state.lastTag == 0)
{
throw new InvalidOperationException("SkipLastField cannot be called at the end of a stream");
}
switch (WireFormat.GetTagWireType(state.lastTag))
{
case WireFormat.WireType.StartGroup:
SkipGroup(ref buffer, ref state, state.lastTag);
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:
ParsingPrimitives.ParseRawLittleEndian32(ref buffer, ref state);
break;
case WireFormat.WireType.Fixed64:
ParsingPrimitives.ParseRawLittleEndian64(ref buffer, ref state);
break;
case WireFormat.WireType.LengthDelimited:
var length = ParsingPrimitives.ParseLength(ref buffer, ref state);
ParsingPrimitives.SkipRawBytes(ref buffer, ref state, length);
break;
case WireFormat.WireType.Varint:
ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
break;
}
}
/// <summary>
/// Skip a group.
/// </summary>
public static void SkipGroup(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, uint startGroupTag)
{
// 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...
state.recursionDepth++;
if (state.recursionDepth >= state.recursionLimit)
{
throw InvalidProtocolBufferException.RecursionLimitExceeded();
}
uint tag;
while (true)
{
tag = ParsingPrimitives.ParseTag(ref buffer, ref state);
if (tag == 0)
{
throw InvalidProtocolBufferException.TruncatedMessage();
}
// Can't call SkipLastField for this case- that would throw.
if (WireFormat.GetTagWireType(tag) == WireFormat.WireType.EndGroup)
{
break;
}
// This recursion will allow us to handle nested groups.
SkipLastField(ref buffer, ref state);
}
int startField = WireFormat.GetTagFieldNumber(startGroupTag);
int endField = WireFormat.GetTagFieldNumber(tag);
if (startField != endField)
{
throw new InvalidProtocolBufferException(
$"Mismatched end-group tag. Started with field {startField}; ended with field {endField}");
}
state.recursionDepth--;
}
public static void ReadMessage(ref CodedInputReader ctx, IMessage message)
{
int length = ParsingPrimitives.ParseLength(ref ctx.buffer, ref ctx.state);
if (ctx.state.recursionDepth >= ctx.state.recursionLimit)
{
throw InvalidProtocolBufferException.RecursionLimitExceeded();
}
int oldLimit = SegmentedBufferHelper.PushLimit(ref ctx.state, length);
++ctx.state.recursionDepth;
ReadRawMessage(ref ctx, message);
CheckReadEndOfStreamTag(ref ctx.state);
// Check that we've read exactly as much data as expected.
if (!SegmentedBufferHelper.IsReachedLimit(ref ctx.state))
{
throw InvalidProtocolBufferException.TruncatedMessage();
}
--ctx.state.recursionDepth;
SegmentedBufferHelper.PopLimit(ref ctx.state, oldLimit);
}
public static void ReadMessage(ref ParseContext ctx, IMessage message)
{
int length = ParsingPrimitives.ParseLength(ref ctx.buffer, ref ctx.state);
if (ctx.state.recursionDepth >= ctx.state.recursionLimit)
{
throw InvalidProtocolBufferException.RecursionLimitExceeded();
}
int oldLimit = SegmentedBufferHelper.PushLimit(ref ctx.state, length);
++ctx.state.recursionDepth;
ReadRawMessage(ref ctx, message);
CheckReadEndOfStreamTag(ref ctx.state);
// Check that we've read exactly as much data as expected.
if (!SegmentedBufferHelper.IsReachedLimit(ref ctx.state))
{
throw InvalidProtocolBufferException.TruncatedMessage();
}
--ctx.state.recursionDepth;
SegmentedBufferHelper.PopLimit(ref ctx.state, oldLimit);
}
public static void ReadGroup(ref CodedInputReader ctx, IMessage message)
{
if (ctx.state.recursionDepth >= ctx.state.recursionLimit)
{
throw InvalidProtocolBufferException.RecursionLimitExceeded();
}
++ctx.state.recursionDepth;
ReadRawMessage(ref ctx, message);
--ctx.state.recursionDepth;
}
public static void ReadGroup(ref ParseContext ctx, IMessage message)
{
if (ctx.state.recursionDepth >= ctx.state.recursionLimit)
{
throw InvalidProtocolBufferException.RecursionLimitExceeded();
}
++ctx.state.recursionDepth;
ReadRawMessage(ref ctx, message);
--ctx.state.recursionDepth;
}
public static void ReadRawMessage(ref CodedInputReader ctx, IMessage message)
{
if (message is IBufferMessage bufferMessage)
{
bufferMessage.MergeFrom(ref ctx);
}
else
{
if (ctx.state.codedInputStream == null)
{
// TODO: improve the msg
throw new InvalidProtocolBufferException("Cannot parse message with current parse context. Do you need to regenerate the code?");
}
message.MergeFrom(ctx.state.codedInputStream);
}
}
public static void ReadRawMessage(ref ParseContext ctx, IMessage message)
{
if (message is IBufferMessage bufferMessage)
{
bufferMessage.MergeFrom_Internal(ref ctx);
}
else
{
if (ctx.state.codedInputStream == null)
{
// TODO: improve the msg
throw new InvalidProtocolBufferException("Cannot parse message with current parse context. Do you need to regenerate the code?");
}
message.MergeFrom(ctx.state.codedInputStream);
}
}
/// <summary>
/// Verifies that the last call to ReadTag() returned tag 0 - in other words,
/// we've reached the end of the stream when we expected to.
/// </summary>
/// <exception cref="InvalidProtocolBufferException">The
/// tag read was not the one specified</exception>
public static void CheckReadEndOfStreamTag(ref ParserInternalState state)
{
if (state.lastTag != 0)
{
throw InvalidProtocolBufferException.MoreDataAvailable();
}
}
}
}

@ -0,0 +1,352 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using Google.Protobuf.Collections;
namespace Google.Protobuf
{
/// <summary>
/// Fast parsing primitives for wrapper types
/// </summary>
internal static class ParsingPrimitivesWrappers
{
internal static float? ReadFloatWrapperLittleEndian(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
// length:1 + tag:1 + value:4 = 6 bytes
if (state.bufferPos + 6 <= state.bufferSize)
{
// The entire wrapper message is already contained in `buffer`.
int length = buffer[state.bufferPos];
if (length == 0)
{
state.bufferPos++;
return 0F;
}
// tag:1 + value:4 = length of 5 bytes
// field=1, type=32-bit = tag of 13
if (length != 5 || buffer[state.bufferPos + 1] != 13)
{
return ReadFloatWrapperSlow(ref buffer, ref state);
}
state.bufferPos += 2;
return ParsingPrimitives.ParseFloat(ref buffer, ref state);
}
else
{
return ReadFloatWrapperSlow(ref buffer, ref state);
}
}
internal static float? ReadFloatWrapperSlow(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
int length = ParsingPrimitives.ParseLength(ref buffer, ref state);
if (length == 0)
{
return 0F;
}
int finalBufferPos = state.totalBytesRetired + state.bufferPos + length;
float result = 0F;
do
{
// field=1, type=32-bit = tag of 13
if (ParsingPrimitives.ParseTag(ref buffer, ref state) == 13)
{
result = ParsingPrimitives.ParseFloat(ref buffer, ref state);
}
else
{
ParsingPrimitivesMessages.SkipLastField(ref buffer, ref state);
}
}
while (state.totalBytesRetired + state.bufferPos < finalBufferPos);
return result;
}
internal static double? ReadDoubleWrapperLittleEndian(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
// length:1 + tag:1 + value:8 = 10 bytes
if (state.bufferPos + 10 <= state.bufferSize)
{
// The entire wrapper message is already contained in `buffer`.
int length = buffer[state.bufferPos];
if (length == 0)
{
state.bufferPos++;
return 0D;
}
// tag:1 + value:8 = length of 9 bytes
// field=1, type=64-bit = tag of 9
if (length != 9 || buffer[state.bufferPos + 1] != 9)
{
return ReadDoubleWrapperSlow(ref buffer, ref state);
}
state.bufferPos += 2;
return ParsingPrimitives.ParseDouble(ref buffer, ref state);
}
else
{
return ReadDoubleWrapperSlow(ref buffer, ref state);
}
}
internal static double? ReadDoubleWrapperSlow(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
int length = ParsingPrimitives.ParseLength(ref buffer, ref state);
if (length == 0)
{
return 0D;
}
int finalBufferPos = state.totalBytesRetired + state.bufferPos + length;
double result = 0D;
do
{
// field=1, type=64-bit = tag of 9
if (ParsingPrimitives.ParseTag(ref buffer, ref state) == 9)
{
result = ParsingPrimitives.ParseDouble(ref buffer, ref state);
}
else
{
ParsingPrimitivesMessages.SkipLastField(ref buffer, ref state);
}
}
while (state.totalBytesRetired + state.bufferPos < finalBufferPos);
return result;
}
internal static bool? ReadBoolWrapper(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
return ReadUInt64Wrapper(ref buffer, ref state) != 0;
}
internal static uint? ReadUInt32Wrapper(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
// length:1 + tag:1 + value:5(varint32-max) = 7 bytes
if (state.bufferPos + 7 <= state.bufferSize)
{
// The entire wrapper message is already contained in `buffer`.
int pos0 = state.bufferPos;
int length = buffer[state.bufferPos++];
if (length == 0)
{
return 0;
}
// Length will always fit in a single byte.
if (length >= 128)
{
state.bufferPos = pos0;
return ReadUInt32WrapperSlow(ref buffer, ref state);
}
int finalBufferPos = state.bufferPos + length;
// field=1, type=varint = tag of 8
if (buffer[state.bufferPos++] != 8)
{
state.bufferPos = pos0;
return ReadUInt32WrapperSlow(ref buffer, ref state);
}
var result = ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
// Verify this message only contained a single field.
if (state.bufferPos != finalBufferPos)
{
state.bufferPos = pos0;
return ReadUInt32WrapperSlow(ref buffer, ref state);
}
return result;
}
else
{
return ReadUInt32WrapperSlow(ref buffer, ref state);
}
}
internal static uint? ReadUInt32WrapperSlow(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
int length = ParsingPrimitives.ParseLength(ref buffer, ref state);
if (length == 0)
{
return 0;
}
int finalBufferPos = state.totalBytesRetired + state.bufferPos + length;
uint result = 0;
do
{
// field=1, type=varint = tag of 8
if (ParsingPrimitives.ParseTag(ref buffer, ref state) == 8)
{
result = ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
}
else
{
ParsingPrimitivesMessages.SkipLastField(ref buffer, ref state);
}
}
while (state.totalBytesRetired + state.bufferPos < finalBufferPos);
return result;
}
internal static int? ReadInt32Wrapper(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
return (int?)ReadUInt32Wrapper(ref buffer, ref state);
}
internal static ulong? ReadUInt64Wrapper(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
// field=1, type=varint = tag of 8
const int expectedTag = 8;
// length:1 + tag:1 + value:10(varint64-max) = 12 bytes
if (state.bufferPos + 12 <= state.bufferSize)
{
// The entire wrapper message is already contained in `buffer`.
int pos0 = state.bufferPos;
int length = buffer[state.bufferPos++];
if (length == 0)
{
return 0L;
}
// Length will always fit in a single byte.
if (length >= 128)
{
state.bufferPos = pos0;
return ReadUInt64WrapperSlow(ref buffer, ref state);
}
int finalBufferPos = state.bufferPos + length;
if (buffer[state.bufferPos++] != expectedTag)
{
state.bufferPos = pos0;
return ReadUInt64WrapperSlow(ref buffer, ref state);
}
var result = ParsingPrimitives.ParseRawVarint64(ref buffer, ref state);
// Verify this message only contained a single field.
if (state.bufferPos != finalBufferPos)
{
state.bufferPos = pos0;
return ReadUInt64WrapperSlow(ref buffer, ref state);
}
return result;
}
else
{
return ReadUInt64WrapperSlow(ref buffer, ref state);
}
}
internal static ulong? ReadUInt64WrapperSlow(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
// field=1, type=varint = tag of 8
const int expectedTag = 8;
int length = ParsingPrimitives.ParseLength(ref buffer, ref state);
if (length == 0)
{
return 0L;
}
int finalBufferPos = state.totalBytesRetired + state.bufferPos + length;
ulong result = 0L;
do
{
if (ParsingPrimitives.ParseTag(ref buffer, ref state) == expectedTag)
{
result = ParsingPrimitives.ParseRawVarint64(ref buffer, ref state);
}
else
{
ParsingPrimitivesMessages.SkipLastField(ref buffer, ref state);
}
}
while (state.totalBytesRetired + state.bufferPos < finalBufferPos);
return result;
}
internal static long? ReadInt64Wrapper(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
return (long?)ReadUInt64Wrapper(ref buffer, ref state);
}
internal static float? ReadFloatWrapperLittleEndian(ref ParseContext ctx)
{
return ParsingPrimitivesWrappers.ReadFloatWrapperLittleEndian(ref ctx.buffer, ref ctx.state);
}
internal static float? ReadFloatWrapperSlow(ref ParseContext ctx)
{
return ParsingPrimitivesWrappers.ReadFloatWrapperSlow(ref ctx.buffer, ref ctx.state);
}
internal static double? ReadDoubleWrapperLittleEndian(ref ParseContext ctx)
{
return ParsingPrimitivesWrappers.ReadDoubleWrapperLittleEndian(ref ctx.buffer, ref ctx.state);
}
internal static double? ReadDoubleWrapperSlow(ref ParseContext ctx)
{
return ParsingPrimitivesWrappers.ReadDoubleWrapperSlow(ref ctx.buffer, ref ctx.state);
}
internal static bool? ReadBoolWrapper(ref ParseContext ctx)
{
return ParsingPrimitivesWrappers.ReadBoolWrapper(ref ctx.buffer, ref ctx.state);
}
internal static uint? ReadUInt32Wrapper(ref ParseContext ctx)
{
return ParsingPrimitivesWrappers.ReadUInt32Wrapper(ref ctx.buffer, ref ctx.state);
}
internal static int? ReadInt32Wrapper(ref ParseContext ctx)
{
return ParsingPrimitivesWrappers.ReadInt32Wrapper(ref ctx.buffer, ref ctx.state);
}
internal static ulong? ReadUInt64Wrapper(ref ParseContext ctx)
{
return ParsingPrimitivesWrappers.ReadUInt64Wrapper(ref ctx.buffer, ref ctx.state);
}
internal static ulong? ReadUInt64WrapperSlow(ref ParseContext ctx)
{
return ParsingPrimitivesWrappers.ReadUInt64WrapperSlow(ref ctx.buffer, ref ctx.state);
}
internal static long? ReadInt64Wrapper(ref ParseContext ctx)
{
return ParsingPrimitivesWrappers.ReadInt64Wrapper(ref ctx.buffer, ref ctx.state);
}
}
}

@ -0,0 +1,283 @@
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
namespace Google.Protobuf
{
/// <summary>
/// Abstraction for reading from a stream / read only sequence.
/// Parsing from the buffer is a loop of reading from current buffer / refreshing the buffer once done.
/// </summary>
internal struct SegmentedBufferHelper
{
private readonly int? totalLength;
private ReadOnlySequence<byte>.Enumerator readOnlySequenceEnumerator;
private readonly CodedInputStream codedInputStream;
public SegmentedBufferHelper(ReadOnlySequence<byte> sequence, out ReadOnlySpan<byte> firstSpan)
{
this.codedInputStream = null;
if (sequence.IsSingleSegment)
{
firstSpan = sequence.First.Span;
this.totalLength = firstSpan.Length;
this.readOnlySequenceEnumerator = default;
}
else
{
// TODO(jtattermusch): try to initialize the first segment, otherwise the
// very first read will result in slowpath (because the first thing to do is to
// refill to get the first buffer segment)
firstSpan = default;
this.totalLength = (int) sequence.Length;
this.readOnlySequenceEnumerator = sequence.GetEnumerator();
}
}
public SegmentedBufferHelper(CodedInputStream codedInputStream)
{
this.totalLength = codedInputStream.InternalInputStream == null ? (int?)codedInputStream.InternalBuffer.Length : null;
this.readOnlySequenceEnumerator = default;
this.codedInputStream = codedInputStream;
}
public bool RefillBuffer(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, bool mustSucceed)
{
if (codedInputStream != null)
{
return RefillFromCodedInputStream(ref buffer, ref state, mustSucceed);
}
else
{
return RefillFromReadOnlySequence(ref buffer, ref state, mustSucceed);
}
}
public int? TotalLength => totalLength;
public CodedInputStream CodedInputStream => codedInputStream;
/// <summary>
/// Sets currentLimit to (current position) + byteLimit. This is called
/// when descending into a length-delimited embedded message. The previous
/// limit is returned.
/// </summary>
/// <returns>The old limit.</returns>
public static int PushLimit(ref ParserInternalState state, int byteLimit)
{
if (byteLimit < 0)
{
throw InvalidProtocolBufferException.NegativeSize();
}
byteLimit += state.totalBytesRetired + state.bufferPos;
int oldLimit = state.currentLimit;
if (byteLimit > oldLimit)
{
throw InvalidProtocolBufferException.TruncatedMessage();
}
state.currentLimit = byteLimit;
RecomputeBufferSizeAfterLimit(ref state);
return oldLimit;
}
/// <summary>
/// Discards the current limit, returning the previous limit.
/// </summary>
public static void PopLimit(ref ParserInternalState state, int oldLimit)
{
state.currentLimit = oldLimit;
RecomputeBufferSizeAfterLimit(ref state);
}
/// <summary>
/// Returns whether or not all the data before the limit has been read.
/// </summary>
/// <returns></returns>
public static bool IsReachedLimit(ref ParserInternalState state)
{
if (state.currentLimit == int.MaxValue)
{
return false;
}
int currentAbsolutePosition = state.totalBytesRetired + state.bufferPos;
return currentAbsolutePosition >= state.currentLimit;
}
/// <summary>
/// Returns true if the stream has reached the end of the input. This is the
/// case if either the end of the underlying input source has been reached or
/// the stream has reached a limit created using PushLimit.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsAtEnd(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
{
return state.bufferPos == state.bufferSize && !state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, false);
}
private bool RefillFromReadOnlySequence(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, bool mustSucceed)
{
CheckCurrentBufferIsEmpty(ref state);
if (state.totalBytesRetired + state.bufferSize == state.currentLimit)
{
// Oops, we hit a limit.
if (mustSucceed)
{
throw InvalidProtocolBufferException.TruncatedMessage();
}
else
{
return false;
}
}
state.totalBytesRetired += state.bufferSize;
state.bufferPos = 0;
state.bufferSize = 0;
while (readOnlySequenceEnumerator.MoveNext())
{
buffer = readOnlySequenceEnumerator.Current.Span;
state.bufferSize = buffer.Length;
if (buffer.Length != 0)
{
break;
}
}
if (state.bufferSize == 0)
{
if (mustSucceed)
{
throw InvalidProtocolBufferException.TruncatedMessage();
}
else
{
return false;
}
}
else
{
RecomputeBufferSizeAfterLimit(ref state);
int totalBytesRead =
state.totalBytesRetired + state.bufferSize + state.bufferSizeAfterLimit;
if (totalBytesRead < 0 || totalBytesRead > state.sizeLimit)
{
throw InvalidProtocolBufferException.SizeLimitExceeded();
}
return true;
}
}
private bool RefillFromCodedInputStream(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, bool mustSucceed)
{
CheckCurrentBufferIsEmpty(ref state);
if (state.totalBytesRetired + state.bufferSize == state.currentLimit)
{
// Oops, we hit a limit.
if (mustSucceed)
{
throw InvalidProtocolBufferException.TruncatedMessage();
}
else
{
return false;
}
}
Stream input = codedInputStream.InternalInputStream;
state.totalBytesRetired += state.bufferSize;
state.bufferPos = 0;
state.bufferSize = (input == null) ? 0 : input.Read(codedInputStream.InternalBuffer, 0, buffer.Length);
if (state.bufferSize < 0)
{
throw new InvalidOperationException("Stream.Read returned a negative count");
}
if (state.bufferSize == 0)
{
if (mustSucceed)
{
throw InvalidProtocolBufferException.TruncatedMessage();
}
else
{
return false;
}
}
else
{
RecomputeBufferSizeAfterLimit(ref state);
int totalBytesRead =
state.totalBytesRetired + state.bufferSize + state.bufferSizeAfterLimit;
if (totalBytesRead < 0 || totalBytesRead > state.sizeLimit)
{
throw InvalidProtocolBufferException.SizeLimitExceeded();
}
return true;
}
}
private static void RecomputeBufferSizeAfterLimit(ref ParserInternalState state)
{
state.bufferSize += state.bufferSizeAfterLimit;
int bufferEnd = state.totalBytesRetired + state.bufferSize;
if (bufferEnd > state.currentLimit)
{
// Limit is in current buffer.
state.bufferSizeAfterLimit = bufferEnd - state.currentLimit;
state.bufferSize -= state.bufferSizeAfterLimit;
}
else
{
state.bufferSizeAfterLimit = 0;
}
}
private static void CheckCurrentBufferIsEmpty(ref ParserInternalState state)
{
if (state.bufferPos < state.bufferSize)
{
throw new InvalidOperationException("RefillBuffer() called when buffer wasn't empty.");
}
}
}
}
Loading…
Cancel
Save