xref: /aosp_15_r20/external/protobuf/csharp/src/Google.Protobuf/ParsingPrimitivesMessages.cs (revision 1b3f573f81763fcece89efc2b6a5209149e44ab8)
1 #region Copyright notice and license
2 // Protocol Buffers - Google's data interchange format
3 // Copyright 2008 Google Inc.  All rights reserved.
4 // https://developers.google.com/protocol-buffers/
5 //
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are
8 // met:
9 //
10 //     * Redistributions of source code must retain the above copyright
11 // notice, this list of conditions and the following disclaimer.
12 //     * Redistributions in binary form must reproduce the above
13 // copyright notice, this list of conditions and the following disclaimer
14 // in the documentation and/or other materials provided with the
15 // distribution.
16 //     * Neither the name of Google Inc. nor the names of its
17 // contributors may be used to endorse or promote products derived from
18 // this software without specific prior written permission.
19 //
20 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #endregion
32 
33 using System;
34 using System.Buffers;
35 using System.Collections.Generic;
36 using System.IO;
37 using System.Runtime.CompilerServices;
38 using System.Security;
39 using Google.Protobuf.Collections;
40 
41 namespace Google.Protobuf
42 {
43     /// <summary>
44     /// Reading and skipping messages / groups
45     /// </summary>
46     [SecuritySafeCritical]
47     internal static class ParsingPrimitivesMessages
48     {
49         private static readonly byte[] ZeroLengthMessageStreamData = new byte[] { 0 };
50 
SkipLastField(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)51         public static void SkipLastField(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
52         {
53             if (state.lastTag == 0)
54             {
55                 throw new InvalidOperationException("SkipLastField cannot be called at the end of a stream");
56             }
57             switch (WireFormat.GetTagWireType(state.lastTag))
58             {
59                 case WireFormat.WireType.StartGroup:
60                     SkipGroup(ref buffer, ref state, state.lastTag);
61                     break;
62                 case WireFormat.WireType.EndGroup:
63                     throw new InvalidProtocolBufferException(
64                         "SkipLastField called on an end-group tag, indicating that the corresponding start-group was missing");
65                 case WireFormat.WireType.Fixed32:
66                     ParsingPrimitives.ParseRawLittleEndian32(ref buffer, ref state);
67                     break;
68                 case WireFormat.WireType.Fixed64:
69                     ParsingPrimitives.ParseRawLittleEndian64(ref buffer, ref state);
70                     break;
71                 case WireFormat.WireType.LengthDelimited:
72                     var length = ParsingPrimitives.ParseLength(ref buffer, ref state);
73                     ParsingPrimitives.SkipRawBytes(ref buffer, ref state, length);
74                     break;
75                 case WireFormat.WireType.Varint:
76                     ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
77                     break;
78             }
79         }
80 
81         /// <summary>
82         /// Skip a group.
83         /// </summary>
SkipGroup(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, uint startGroupTag)84         public static void SkipGroup(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, uint startGroupTag)
85         {
86             // Note: Currently we expect this to be the way that groups are read. We could put the recursion
87             // depth changes into the ReadTag method instead, potentially...
88             state.recursionDepth++;
89             if (state.recursionDepth >= state.recursionLimit)
90             {
91                 throw InvalidProtocolBufferException.RecursionLimitExceeded();
92             }
93             uint tag;
94             while (true)
95             {
96                 tag = ParsingPrimitives.ParseTag(ref buffer, ref state);
97                 if (tag == 0)
98                 {
99                     throw InvalidProtocolBufferException.TruncatedMessage();
100                 }
101                 // Can't call SkipLastField for this case- that would throw.
102                 if (WireFormat.GetTagWireType(tag) == WireFormat.WireType.EndGroup)
103                 {
104                     break;
105                 }
106                 // This recursion will allow us to handle nested groups.
107                 SkipLastField(ref buffer, ref state);
108             }
109             int startField = WireFormat.GetTagFieldNumber(startGroupTag);
110             int endField = WireFormat.GetTagFieldNumber(tag);
111             if (startField != endField)
112             {
113                 throw new InvalidProtocolBufferException(
114                     $"Mismatched end-group tag. Started with field {startField}; ended with field {endField}");
115             }
116             state.recursionDepth--;
117         }
118 
ReadMessage(ref ParseContext ctx, IMessage message)119         public static void ReadMessage(ref ParseContext ctx, IMessage message)
120         {
121             int length = ParsingPrimitives.ParseLength(ref ctx.buffer, ref ctx.state);
122             if (ctx.state.recursionDepth >= ctx.state.recursionLimit)
123             {
124                 throw InvalidProtocolBufferException.RecursionLimitExceeded();
125             }
126             int oldLimit = SegmentedBufferHelper.PushLimit(ref ctx.state, length);
127             ++ctx.state.recursionDepth;
128 
129             ReadRawMessage(ref ctx, message);
130 
131             CheckReadEndOfStreamTag(ref ctx.state);
132             // Check that we've read exactly as much data as expected.
133             if (!SegmentedBufferHelper.IsReachedLimit(ref ctx.state))
134             {
135                 throw InvalidProtocolBufferException.TruncatedMessage();
136             }
137             --ctx.state.recursionDepth;
138             SegmentedBufferHelper.PopLimit(ref ctx.state, oldLimit);
139         }
140 
ReadMapEntry(ref ParseContext ctx, MapField<TKey, TValue>.Codec codec)141         public static KeyValuePair<TKey, TValue> ReadMapEntry<TKey, TValue>(ref ParseContext ctx, MapField<TKey, TValue>.Codec codec)
142         {
143             int length = ParsingPrimitives.ParseLength(ref ctx.buffer, ref ctx.state);
144             if (ctx.state.recursionDepth >= ctx.state.recursionLimit)
145             {
146                 throw InvalidProtocolBufferException.RecursionLimitExceeded();
147             }
148             int oldLimit = SegmentedBufferHelper.PushLimit(ref ctx.state, length);
149             ++ctx.state.recursionDepth;
150 
151             TKey key = codec.KeyCodec.DefaultValue;
152             TValue value = codec.ValueCodec.DefaultValue;
153 
154             uint tag;
155             while ((tag = ctx.ReadTag()) != 0)
156             {
157                 if (tag == codec.KeyCodec.Tag)
158                 {
159                     key = codec.KeyCodec.Read(ref ctx);
160                 }
161                 else if (tag == codec.ValueCodec.Tag)
162                 {
163                     value = codec.ValueCodec.Read(ref ctx);
164                 }
165                 else
166                 {
167                     SkipLastField(ref ctx.buffer, ref ctx.state);
168                 }
169             }
170 
171             // Corner case: a map entry with a key but no value, where the value type is a message.
172             // Read it as if we'd seen input with no data (i.e. create a "default" message).
173             if (value == null)
174             {
175                 if (ctx.state.CodedInputStream != null)
176                 {
177                     // the decoded message might not support parsing from ParseContext, so
178                     // we need to allow fallback to the legacy MergeFrom(CodedInputStream) parsing.
179                     value = codec.ValueCodec.Read(new CodedInputStream(ZeroLengthMessageStreamData));
180                 }
181                 else
182                 {
183                     ParseContext.Initialize(new ReadOnlySequence<byte>(ZeroLengthMessageStreamData), out ParseContext zeroLengthCtx);
184                     value = codec.ValueCodec.Read(ref zeroLengthCtx);
185                 }
186             }
187 
188             CheckReadEndOfStreamTag(ref ctx.state);
189             // Check that we've read exactly as much data as expected.
190             if (!SegmentedBufferHelper.IsReachedLimit(ref ctx.state))
191             {
192                 throw InvalidProtocolBufferException.TruncatedMessage();
193             }
194             --ctx.state.recursionDepth;
195             SegmentedBufferHelper.PopLimit(ref ctx.state, oldLimit);
196 
197             return new KeyValuePair<TKey, TValue>(key, value);
198         }
199 
ReadGroup(ref ParseContext ctx, IMessage message)200         public static void ReadGroup(ref ParseContext ctx, IMessage message)
201         {
202             if (ctx.state.recursionDepth >= ctx.state.recursionLimit)
203             {
204                 throw InvalidProtocolBufferException.RecursionLimitExceeded();
205             }
206             ++ctx.state.recursionDepth;
207 
208             uint tag = ctx.state.lastTag;
209             int fieldNumber = WireFormat.GetTagFieldNumber(tag);
210             ReadRawMessage(ref ctx, message);
211             CheckLastTagWas(ref ctx.state, WireFormat.MakeTag(fieldNumber, WireFormat.WireType.EndGroup));
212 
213             --ctx.state.recursionDepth;
214         }
215 
ReadGroup(ref ParseContext ctx, int fieldNumber, UnknownFieldSet set)216         public static void ReadGroup(ref ParseContext ctx, int fieldNumber, UnknownFieldSet set)
217         {
218             if (ctx.state.recursionDepth >= ctx.state.recursionLimit)
219             {
220                 throw InvalidProtocolBufferException.RecursionLimitExceeded();
221             }
222             ++ctx.state.recursionDepth;
223 
224             set.MergeGroupFrom(ref ctx);
225             CheckLastTagWas(ref ctx.state, WireFormat.MakeTag(fieldNumber, WireFormat.WireType.EndGroup));
226 
227             --ctx.state.recursionDepth;
228         }
229 
ReadRawMessage(ref ParseContext ctx, IMessage message)230         public static void ReadRawMessage(ref ParseContext ctx, IMessage message)
231         {
232             if (message is IBufferMessage bufferMessage)
233             {
234                 bufferMessage.InternalMergeFrom(ref ctx);
235             }
236             else
237             {
238                 // If we reached here, it means we've ran into a nested message with older generated code
239                 // which doesn't provide the InternalMergeFrom method that takes a ParseContext.
240                 // With a slight performance overhead, we can still parse this message just fine,
241                 // but we need to find the original CodedInputStream instance that initiated this
242                 // parsing process and make sure its internal state is up to date.
243                 // Note that this performance overhead is not very high (basically copying contents of a struct)
244                 // and it will only be incurred in case the application mixes older and newer generated code.
245                 // Regenerating the code from .proto files will remove this overhead because it will
246                 // generate the InternalMergeFrom method we need.
247 
248                 if (ctx.state.CodedInputStream == null)
249                 {
250                     // This can only happen when the parsing started without providing a CodedInputStream instance
251                     // (e.g. ParseContext was created directly from a ReadOnlySequence).
252                     // That also means that one of the new parsing APIs was used at the top level
253                     // and in such case it is reasonable to require that all the nested message provide
254                     // up-to-date generated code with ParseContext support (and fail otherwise).
255                     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.");
256                 }
257 
258                 ctx.CopyStateTo(ctx.state.CodedInputStream);
259                 try
260                 {
261                     // fallback parse using the CodedInputStream that started current parsing tree
262                     message.MergeFrom(ctx.state.CodedInputStream);
263                 }
264                 finally
265                 {
266                     ctx.LoadStateFrom(ctx.state.CodedInputStream);
267                 }
268             }
269         }
270 
271         /// <summary>
272         /// Verifies that the last call to ReadTag() returned tag 0 - in other words,
273         /// we've reached the end of the stream when we expected to.
274         /// </summary>
275         /// <exception cref="InvalidProtocolBufferException">The
276         /// tag read was not the one specified</exception>
CheckReadEndOfStreamTag(ref ParserInternalState state)277         public static void CheckReadEndOfStreamTag(ref ParserInternalState state)
278         {
279             if (state.lastTag != 0)
280             {
281                 throw InvalidProtocolBufferException.MoreDataAvailable();
282             }
283         }
284 
CheckLastTagWas(ref ParserInternalState state, uint expectedTag)285         private static void CheckLastTagWas(ref ParserInternalState state, uint expectedTag)
286         {
287             if (state.lastTag != expectedTag) {
288                throw InvalidProtocolBufferException.InvalidEndTag();
289             }
290         }
291     }
292 }