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 }