• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #region Copyright notice and license
2 // Protocol Buffers - Google's data interchange format
3 // Copyright 2008 Google Inc.  All rights reserved.
4 //
5 // Use of this source code is governed by a BSD-style
6 // license that can be found in the LICENSE file or at
7 // https://developers.google.com/open-source/licenses/bsd
8 #endregion
9 
10 using System;
11 using System.Buffers;
12 using System.Collections.Generic;
13 using System.Security;
14 using Google.Protobuf.Collections;
15 
16 namespace Google.Protobuf
17 {
18     /// <summary>
19     /// Reading and skipping messages / groups
20     /// </summary>
21     [SecuritySafeCritical]
22     internal static class ParsingPrimitivesMessages
23     {
24         private static readonly byte[] ZeroLengthMessageStreamData = new byte[] { 0 };
25 
SkipLastField(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)26         public static void SkipLastField(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
27         {
28             if (state.lastTag == 0)
29             {
30                 throw new InvalidOperationException("SkipLastField cannot be called at the end of a stream");
31             }
32             switch (WireFormat.GetTagWireType(state.lastTag))
33             {
34                 case WireFormat.WireType.StartGroup:
35                     SkipGroup(ref buffer, ref state, state.lastTag);
36                     break;
37                 case WireFormat.WireType.EndGroup:
38                     throw new InvalidProtocolBufferException(
39                         "SkipLastField called on an end-group tag, indicating that the corresponding start-group was missing");
40                 case WireFormat.WireType.Fixed32:
41                     ParsingPrimitives.ParseRawLittleEndian32(ref buffer, ref state);
42                     break;
43                 case WireFormat.WireType.Fixed64:
44                     ParsingPrimitives.ParseRawLittleEndian64(ref buffer, ref state);
45                     break;
46                 case WireFormat.WireType.LengthDelimited:
47                     var length = ParsingPrimitives.ParseLength(ref buffer, ref state);
48                     ParsingPrimitives.SkipRawBytes(ref buffer, ref state, length);
49                     break;
50                 case WireFormat.WireType.Varint:
51                     ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
52                     break;
53             }
54         }
55 
56         /// <summary>
57         /// Skip a group.
58         /// </summary>
SkipGroup(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, uint startGroupTag)59         public static void SkipGroup(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, uint startGroupTag)
60         {
61             // Note: Currently we expect this to be the way that groups are read. We could put the recursion
62             // depth changes into the ReadTag method instead, potentially...
63             state.recursionDepth++;
64             if (state.recursionDepth >= state.recursionLimit)
65             {
66                 throw InvalidProtocolBufferException.RecursionLimitExceeded();
67             }
68             uint tag;
69             while (true)
70             {
71                 tag = ParsingPrimitives.ParseTag(ref buffer, ref state);
72                 if (tag == 0)
73                 {
74                     throw InvalidProtocolBufferException.TruncatedMessage();
75                 }
76                 // Can't call SkipLastField for this case- that would throw.
77                 if (WireFormat.GetTagWireType(tag) == WireFormat.WireType.EndGroup)
78                 {
79                     break;
80                 }
81                 // This recursion will allow us to handle nested groups.
82                 SkipLastField(ref buffer, ref state);
83             }
84             int startField = WireFormat.GetTagFieldNumber(startGroupTag);
85             int endField = WireFormat.GetTagFieldNumber(tag);
86             if (startField != endField)
87             {
88                 throw new InvalidProtocolBufferException(
89                     $"Mismatched end-group tag. Started with field {startField}; ended with field {endField}");
90             }
91             state.recursionDepth--;
92         }
93 
ReadMessage(ref ParseContext ctx, IMessage message)94         public static void ReadMessage(ref ParseContext ctx, IMessage message)
95         {
96             int length = ParsingPrimitives.ParseLength(ref ctx.buffer, ref ctx.state);
97             if (ctx.state.recursionDepth >= ctx.state.recursionLimit)
98             {
99                 throw InvalidProtocolBufferException.RecursionLimitExceeded();
100             }
101             int oldLimit = SegmentedBufferHelper.PushLimit(ref ctx.state, length);
102             ++ctx.state.recursionDepth;
103 
104             ReadRawMessage(ref ctx, message);
105 
106             CheckReadEndOfStreamTag(ref ctx.state);
107             // Check that we've read exactly as much data as expected.
108             if (!SegmentedBufferHelper.IsReachedLimit(ref ctx.state))
109             {
110                 throw InvalidProtocolBufferException.TruncatedMessage();
111             }
112             --ctx.state.recursionDepth;
113             SegmentedBufferHelper.PopLimit(ref ctx.state, oldLimit);
114         }
115 
ReadMapEntry(ref ParseContext ctx, MapField<TKey, TValue>.Codec codec)116         public static KeyValuePair<TKey, TValue> ReadMapEntry<TKey, TValue>(ref ParseContext ctx, MapField<TKey, TValue>.Codec codec)
117         {
118             int length = ParsingPrimitives.ParseLength(ref ctx.buffer, ref ctx.state);
119             if (ctx.state.recursionDepth >= ctx.state.recursionLimit)
120             {
121                 throw InvalidProtocolBufferException.RecursionLimitExceeded();
122             }
123             int oldLimit = SegmentedBufferHelper.PushLimit(ref ctx.state, length);
124             ++ctx.state.recursionDepth;
125 
126             TKey key = codec.KeyCodec.DefaultValue;
127             TValue value = codec.ValueCodec.DefaultValue;
128 
129             uint tag;
130             while ((tag = ctx.ReadTag()) != 0)
131             {
132                 if (tag == codec.KeyCodec.Tag)
133                 {
134                     key = codec.KeyCodec.Read(ref ctx);
135                 }
136                 else if (tag == codec.ValueCodec.Tag)
137                 {
138                     value = codec.ValueCodec.Read(ref ctx);
139                 }
140                 else
141                 {
142                     SkipLastField(ref ctx.buffer, ref ctx.state);
143                 }
144             }
145 
146             // Corner case: a map entry with a key but no value, where the value type is a message.
147             // Read it as if we'd seen input with no data (i.e. create a "default" message).
148             if (value == null)
149             {
150                 if (ctx.state.CodedInputStream != null)
151                 {
152                     // the decoded message might not support parsing from ParseContext, so
153                     // we need to allow fallback to the legacy MergeFrom(CodedInputStream) parsing.
154                     value = codec.ValueCodec.Read(new CodedInputStream(ZeroLengthMessageStreamData));
155                 }
156                 else
157                 {
158                     ParseContext.Initialize(new ReadOnlySequence<byte>(ZeroLengthMessageStreamData), out ParseContext zeroLengthCtx);
159                     value = codec.ValueCodec.Read(ref zeroLengthCtx);
160                 }
161             }
162 
163             CheckReadEndOfStreamTag(ref ctx.state);
164             // Check that we've read exactly as much data as expected.
165             if (!SegmentedBufferHelper.IsReachedLimit(ref ctx.state))
166             {
167                 throw InvalidProtocolBufferException.TruncatedMessage();
168             }
169             --ctx.state.recursionDepth;
170             SegmentedBufferHelper.PopLimit(ref ctx.state, oldLimit);
171 
172             return new KeyValuePair<TKey, TValue>(key, value);
173         }
174 
ReadGroup(ref ParseContext ctx, IMessage message)175         public static void ReadGroup(ref ParseContext ctx, IMessage message)
176         {
177             if (ctx.state.recursionDepth >= ctx.state.recursionLimit)
178             {
179                 throw InvalidProtocolBufferException.RecursionLimitExceeded();
180             }
181             ++ctx.state.recursionDepth;
182 
183             uint tag = ctx.state.lastTag;
184             int fieldNumber = WireFormat.GetTagFieldNumber(tag);
185             ReadRawMessage(ref ctx, message);
186             CheckLastTagWas(ref ctx.state, WireFormat.MakeTag(fieldNumber, WireFormat.WireType.EndGroup));
187 
188             --ctx.state.recursionDepth;
189         }
190 
ReadGroup(ref ParseContext ctx, int fieldNumber, UnknownFieldSet set)191         public static void ReadGroup(ref ParseContext ctx, int fieldNumber, UnknownFieldSet set)
192         {
193             if (ctx.state.recursionDepth >= ctx.state.recursionLimit)
194             {
195                 throw InvalidProtocolBufferException.RecursionLimitExceeded();
196             }
197             ++ctx.state.recursionDepth;
198 
199             set.MergeGroupFrom(ref ctx);
200             CheckLastTagWas(ref ctx.state, WireFormat.MakeTag(fieldNumber, WireFormat.WireType.EndGroup));
201 
202             --ctx.state.recursionDepth;
203         }
204 
ReadRawMessage(ref ParseContext ctx, IMessage message)205         public static void ReadRawMessage(ref ParseContext ctx, IMessage message)
206         {
207             if (message is IBufferMessage bufferMessage)
208             {
209                 bufferMessage.InternalMergeFrom(ref ctx);
210             }
211             else
212             {
213                 // If we reached here, it means we've ran into a nested message with older generated code
214                 // which doesn't provide the InternalMergeFrom method that takes a ParseContext.
215                 // With a slight performance overhead, we can still parse this message just fine,
216                 // but we need to find the original CodedInputStream instance that initiated this
217                 // parsing process and make sure its internal state is up to date.
218                 // Note that this performance overhead is not very high (basically copying contents of a struct)
219                 // and it will only be incurred in case the application mixes older and newer generated code.
220                 // Regenerating the code from .proto files will remove this overhead because it will
221                 // generate the InternalMergeFrom method we need.
222 
223                 if (ctx.state.CodedInputStream == null)
224                 {
225                     // This can only happen when the parsing started without providing a CodedInputStream instance
226                     // (e.g. ParseContext was created directly from a ReadOnlySequence).
227                     // That also means that one of the new parsing APIs was used at the top level
228                     // and in such case it is reasonable to require that all the nested message provide
229                     // up-to-date generated code with ParseContext support (and fail otherwise).
230                     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.");
231                 }
232 
233                 ctx.CopyStateTo(ctx.state.CodedInputStream);
234                 try
235                 {
236                     // fallback parse using the CodedInputStream that started current parsing tree
237                     message.MergeFrom(ctx.state.CodedInputStream);
238                 }
239                 finally
240                 {
241                     ctx.LoadStateFrom(ctx.state.CodedInputStream);
242                 }
243             }
244         }
245 
246         /// <summary>
247         /// Verifies that the last call to ReadTag() returned tag 0 - in other words,
248         /// we've reached the end of the stream when we expected to.
249         /// </summary>
250         /// <exception cref="InvalidProtocolBufferException">The
251         /// tag read was not the one specified</exception>
CheckReadEndOfStreamTag(ref ParserInternalState state)252         public static void CheckReadEndOfStreamTag(ref ParserInternalState state)
253         {
254             if (state.lastTag != 0)
255             {
256                 throw InvalidProtocolBufferException.MoreDataAvailable();
257             }
258         }
259 
CheckLastTagWas(ref ParserInternalState state, uint expectedTag)260         private static void CheckLastTagWas(ref ParserInternalState state, uint expectedTag)
261         {
262             if (state.lastTag != expectedTag) {
263                throw InvalidProtocolBufferException.InvalidEndTag();
264             }
265         }
266     }
267 }