| #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(); | |
| } | |
| } | |
| } | |
| } |