| #region Copyright notice and license |
| // Protocol Buffers - Google's data interchange format |
| // Copyright 2008 Google Inc. All rights reserved. |
| // |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file or at |
| // https://developers.google.com/open-source/licenses/bsd |
| #endregion |
| |
| using System; |
| using System.Buffers; |
| using System.Collections.Generic; |
| using System.Security; |
| using Google.Protobuf.Collections; |
| |
| namespace Google.Protobuf |
| { |
| /// <summary> |
| /// Reading and skipping messages / groups |
| /// </summary> |
| [SecuritySafeCritical] |
| internal static class ParsingPrimitivesMessages |
| { |
| private static readonly byte[] ZeroLengthMessageStreamData = new byte[] { 0 }; |
| |
| 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 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 KeyValuePair<TKey, TValue> ReadMapEntry<TKey, TValue>(ref ParseContext ctx, MapField<TKey, TValue>.Codec codec) |
| { |
| 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; |
| |
| TKey key = codec.KeyCodec.DefaultValue; |
| TValue value = codec.ValueCodec.DefaultValue; |
| |
| uint tag; |
| while ((tag = ctx.ReadTag()) != 0) |
| { |
| if (tag == codec.KeyCodec.Tag) |
| { |
| key = codec.KeyCodec.Read(ref ctx); |
| } |
| else if (tag == codec.ValueCodec.Tag) |
| { |
| value = codec.ValueCodec.Read(ref ctx); |
| } |
| else |
| { |
| SkipLastField(ref ctx.buffer, ref ctx.state); |
| } |
| } |
| |
| // Corner case: a map entry with a key but no value, where the value type is a message. |
| // Read it as if we'd seen input with no data (i.e. create a "default" message). |
| if (value == null) |
| { |
| if (ctx.state.CodedInputStream != null) |
| { |
| // the decoded message might not support parsing from ParseContext, so |
| // we need to allow fallback to the legacy MergeFrom(CodedInputStream) parsing. |
| value = codec.ValueCodec.Read(new CodedInputStream(ZeroLengthMessageStreamData)); |
| } |
| else |
| { |
| ParseContext.Initialize(new ReadOnlySequence<byte>(ZeroLengthMessageStreamData), out ParseContext zeroLengthCtx); |
| value = codec.ValueCodec.Read(ref zeroLengthCtx); |
| } |
| } |
| |
| 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); |
| |
| return new KeyValuePair<TKey, TValue>(key, value); |
| } |
| |
| public static void ReadGroup(ref ParseContext ctx, IMessage message) |
| { |
| if (ctx.state.recursionDepth >= ctx.state.recursionLimit) |
| { |
| throw InvalidProtocolBufferException.RecursionLimitExceeded(); |
| } |
| ++ctx.state.recursionDepth; |
| |
| uint tag = ctx.state.lastTag; |
| int fieldNumber = WireFormat.GetTagFieldNumber(tag); |
| ReadRawMessage(ref ctx, message); |
| CheckLastTagWas(ref ctx.state, WireFormat.MakeTag(fieldNumber, WireFormat.WireType.EndGroup)); |
| |
| --ctx.state.recursionDepth; |
| } |
| |
| public static void ReadGroup(ref ParseContext ctx, int fieldNumber, UnknownFieldSet set) |
| { |
| if (ctx.state.recursionDepth >= ctx.state.recursionLimit) |
| { |
| throw InvalidProtocolBufferException.RecursionLimitExceeded(); |
| } |
| ++ctx.state.recursionDepth; |
| |
| set.MergeGroupFrom(ref ctx); |
| CheckLastTagWas(ref ctx.state, WireFormat.MakeTag(fieldNumber, WireFormat.WireType.EndGroup)); |
| |
| --ctx.state.recursionDepth; |
| } |
| |
| public static void ReadRawMessage(ref ParseContext ctx, IMessage message) |
| { |
| if (message is IBufferMessage bufferMessage) |
| { |
| bufferMessage.InternalMergeFrom(ref ctx); |
| } |
| else |
| { |
| // If we reached here, it means we've ran into a nested message with older generated code |
| // which doesn't provide the InternalMergeFrom method that takes a ParseContext. |
| // With a slight performance overhead, we can still parse this message just fine, |
| // but we need to find the original CodedInputStream instance that initiated this |
| // parsing process and make sure its internal state is up to date. |
| // Note that this performance overhead is not very high (basically copying contents of a struct) |
| // and it will only be incurred in case the application mixes older and newer generated code. |
| // Regenerating the code from .proto files will remove this overhead because it will |
| // generate the InternalMergeFrom method we need. |
| |
| if (ctx.state.CodedInputStream == null) |
| { |
| // This can only happen when the parsing started without providing a CodedInputStream instance |
| // (e.g. ParseContext was created directly from a ReadOnlySequence). |
| // That also means that one of the new parsing APIs was used at the top level |
| // and in such case it is reasonable to require that all the nested message provide |
| // up-to-date generated code with ParseContext support (and fail otherwise). |
| throw new InvalidProtocolBufferException($"Message {message.GetType().Name} doesn't provide the generated method that enables ParseContext-based parsing. You might need to regenerate the generated protobuf code."); |
| } |
| |
| ctx.CopyStateTo(ctx.state.CodedInputStream); |
| try |
| { |
| // fallback parse using the CodedInputStream that started current parsing tree |
| message.MergeFrom(ctx.state.CodedInputStream); |
| } |
| finally |
| { |
| ctx.LoadStateFrom(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(); |
| } |
| } |
| |
| private static void CheckLastTagWas(ref ParserInternalState state, uint expectedTag) |
| { |
| if (state.lastTag != expectedTag) { |
| throw InvalidProtocolBufferException.InvalidEndTag(); |
| } |
| } |
| } |
| } |