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 }