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.IO; 36 using System.Runtime.CompilerServices; 37 using System.Security; 38 39 namespace Google.Protobuf 40 { 41 /// <summary> 42 /// Reading and skipping messages / groups 43 /// </summary> 44 [SecuritySafeCritical] 45 internal static class ParsingPrimitivesMessages 46 { SkipLastField(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)47 public static void SkipLastField(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state) 48 { 49 if (state.lastTag == 0) 50 { 51 throw new InvalidOperationException("SkipLastField cannot be called at the end of a stream"); 52 } 53 switch (WireFormat.GetTagWireType(state.lastTag)) 54 { 55 case WireFormat.WireType.StartGroup: 56 SkipGroup(ref buffer, ref state, state.lastTag); 57 break; 58 case WireFormat.WireType.EndGroup: 59 throw new InvalidProtocolBufferException( 60 "SkipLastField called on an end-group tag, indicating that the corresponding start-group was missing"); 61 case WireFormat.WireType.Fixed32: 62 ParsingPrimitives.ParseRawLittleEndian32(ref buffer, ref state); 63 break; 64 case WireFormat.WireType.Fixed64: 65 ParsingPrimitives.ParseRawLittleEndian64(ref buffer, ref state); 66 break; 67 case WireFormat.WireType.LengthDelimited: 68 var length = ParsingPrimitives.ParseLength(ref buffer, ref state); 69 ParsingPrimitives.SkipRawBytes(ref buffer, ref state, length); 70 break; 71 case WireFormat.WireType.Varint: 72 ParsingPrimitives.ParseRawVarint32(ref buffer, ref state); 73 break; 74 } 75 } 76 77 /// <summary> 78 /// Skip a group. 79 /// </summary> SkipGroup(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, uint startGroupTag)80 public static void SkipGroup(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, uint startGroupTag) 81 { 82 // Note: Currently we expect this to be the way that groups are read. We could put the recursion 83 // depth changes into the ReadTag method instead, potentially... 84 state.recursionDepth++; 85 if (state.recursionDepth >= state.recursionLimit) 86 { 87 throw InvalidProtocolBufferException.RecursionLimitExceeded(); 88 } 89 uint tag; 90 while (true) 91 { 92 tag = ParsingPrimitives.ParseTag(ref buffer, ref state); 93 if (tag == 0) 94 { 95 throw InvalidProtocolBufferException.TruncatedMessage(); 96 } 97 // Can't call SkipLastField for this case- that would throw. 98 if (WireFormat.GetTagWireType(tag) == WireFormat.WireType.EndGroup) 99 { 100 break; 101 } 102 // This recursion will allow us to handle nested groups. 103 SkipLastField(ref buffer, ref state); 104 } 105 int startField = WireFormat.GetTagFieldNumber(startGroupTag); 106 int endField = WireFormat.GetTagFieldNumber(tag); 107 if (startField != endField) 108 { 109 throw new InvalidProtocolBufferException( 110 $"Mismatched end-group tag. Started with field {startField}; ended with field {endField}"); 111 } 112 state.recursionDepth--; 113 } 114 ReadMessage(ref ParseContext ctx, IMessage message)115 public static void ReadMessage(ref ParseContext ctx, IMessage message) 116 { 117 int length = ParsingPrimitives.ParseLength(ref ctx.buffer, ref ctx.state); 118 if (ctx.state.recursionDepth >= ctx.state.recursionLimit) 119 { 120 throw InvalidProtocolBufferException.RecursionLimitExceeded(); 121 } 122 int oldLimit = SegmentedBufferHelper.PushLimit(ref ctx.state, length); 123 ++ctx.state.recursionDepth; 124 125 ReadRawMessage(ref ctx, message); 126 127 CheckReadEndOfStreamTag(ref ctx.state); 128 // Check that we've read exactly as much data as expected. 129 if (!SegmentedBufferHelper.IsReachedLimit(ref ctx.state)) 130 { 131 throw InvalidProtocolBufferException.TruncatedMessage(); 132 } 133 --ctx.state.recursionDepth; 134 SegmentedBufferHelper.PopLimit(ref ctx.state, oldLimit); 135 } 136 ReadGroup(ref ParseContext ctx, IMessage message)137 public static void ReadGroup(ref ParseContext ctx, IMessage message) 138 { 139 if (ctx.state.recursionDepth >= ctx.state.recursionLimit) 140 { 141 throw InvalidProtocolBufferException.RecursionLimitExceeded(); 142 } 143 ++ctx.state.recursionDepth; 144 145 uint tag = ctx.state.lastTag; 146 int fieldNumber = WireFormat.GetTagFieldNumber(tag); 147 ReadRawMessage(ref ctx, message); 148 CheckLastTagWas(ref ctx.state, WireFormat.MakeTag(fieldNumber, WireFormat.WireType.EndGroup)); 149 150 --ctx.state.recursionDepth; 151 } 152 ReadGroup(ref ParseContext ctx, int fieldNumber, UnknownFieldSet set)153 public static void ReadGroup(ref ParseContext ctx, int fieldNumber, UnknownFieldSet set) 154 { 155 if (ctx.state.recursionDepth >= ctx.state.recursionLimit) 156 { 157 throw InvalidProtocolBufferException.RecursionLimitExceeded(); 158 } 159 ++ctx.state.recursionDepth; 160 161 set.MergeGroupFrom(ref ctx); 162 CheckLastTagWas(ref ctx.state, WireFormat.MakeTag(fieldNumber, WireFormat.WireType.EndGroup)); 163 164 --ctx.state.recursionDepth; 165 } 166 ReadRawMessage(ref ParseContext ctx, IMessage message)167 public static void ReadRawMessage(ref ParseContext ctx, IMessage message) 168 { 169 if (message is IBufferMessage bufferMessage) 170 { 171 bufferMessage.InternalMergeFrom(ref ctx); 172 } 173 else 174 { 175 // If we reached here, it means we've ran into a nested message with older generated code 176 // which doesn't provide the InternalMergeFrom method that takes a ParseContext. 177 // With a slight performance overhead, we can still parse this message just fine, 178 // but we need to find the original CodedInputStream instance that initiated this 179 // parsing process and make sure its internal state is up to date. 180 // Note that this performance overhead is not very high (basically copying contents of a struct) 181 // and it will only be incurred in case the application mixes older and newer generated code. 182 // Regenerating the code from .proto files will remove this overhead because it will 183 // generate the InternalMergeFrom method we need. 184 185 if (ctx.state.CodedInputStream == null) 186 { 187 // This can only happen when the parsing started without providing a CodedInputStream instance 188 // (e.g. ParseContext was created directly from a ReadOnlySequence). 189 // That also means that one of the new parsing APIs was used at the top level 190 // and in such case it is reasonable to require that all the nested message provide 191 // up-to-date generated code with ParseContext support (and fail otherwise). 192 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."); 193 } 194 195 ctx.CopyStateTo(ctx.state.CodedInputStream); 196 try 197 { 198 // fallback parse using the CodedInputStream that started current parsing tree 199 message.MergeFrom(ctx.state.CodedInputStream); 200 } 201 finally 202 { 203 ctx.LoadStateFrom(ctx.state.CodedInputStream); 204 } 205 } 206 } 207 208 /// <summary> 209 /// Verifies that the last call to ReadTag() returned tag 0 - in other words, 210 /// we've reached the end of the stream when we expected to. 211 /// </summary> 212 /// <exception cref="InvalidProtocolBufferException">The 213 /// tag read was not the one specified</exception> CheckReadEndOfStreamTag(ref ParserInternalState state)214 public static void CheckReadEndOfStreamTag(ref ParserInternalState state) 215 { 216 if (state.lastTag != 0) 217 { 218 throw InvalidProtocolBufferException.MoreDataAvailable(); 219 } 220 } 221 CheckLastTagWas(ref ParserInternalState state, uint expectedTag)222 private static void CheckLastTagWas(ref ParserInternalState state, uint expectedTag) 223 { 224 if (state.lastTag != expectedTag) { 225 throw InvalidProtocolBufferException.InvalidEndTag(); 226 } 227 } 228 } 229 }