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 /// Abstraction for reading from a stream / read only sequence. 43 /// Parsing from the buffer is a loop of reading from current buffer / refreshing the buffer once done. 44 /// </summary> 45 [SecuritySafeCritical] 46 internal struct SegmentedBufferHelper 47 { 48 private int? totalLength; 49 private ReadOnlySequence<byte>.Enumerator readOnlySequenceEnumerator; 50 private CodedInputStream codedInputStream; 51 52 /// <summary> 53 /// Initialize an instance with a coded input stream. 54 /// This approach is faster than using a constructor because the instance to initialize is passed by reference 55 /// and we can write directly into it without copying. 56 /// </summary> 57 [MethodImpl(MethodImplOptions.AggressiveInlining)] InitializeGoogle.Protobuf.SegmentedBufferHelper58 public static void Initialize(CodedInputStream codedInputStream, out SegmentedBufferHelper instance) 59 { 60 instance.totalLength = codedInputStream.InternalInputStream == null ? (int?)codedInputStream.InternalBuffer.Length : null; 61 instance.readOnlySequenceEnumerator = default; 62 instance.codedInputStream = codedInputStream; 63 } 64 65 /// <summary> 66 /// Initialize an instance with a read only sequence. 67 /// This approach is faster than using a constructor because the instance to initialize is passed by reference 68 /// and we can write directly into it without copying. 69 /// </summary> 70 [MethodImpl(MethodImplOptions.AggressiveInlining)] InitializeGoogle.Protobuf.SegmentedBufferHelper71 public static void Initialize(ReadOnlySequence<byte> sequence, out SegmentedBufferHelper instance, out ReadOnlySpan<byte> firstSpan) 72 { 73 instance.codedInputStream = null; 74 if (sequence.IsSingleSegment) 75 { 76 firstSpan = sequence.First.Span; 77 instance.totalLength = firstSpan.Length; 78 instance.readOnlySequenceEnumerator = default; 79 } 80 else 81 { 82 instance.readOnlySequenceEnumerator = sequence.GetEnumerator(); 83 instance.totalLength = (int) sequence.Length; 84 85 // set firstSpan to the first segment 86 instance.readOnlySequenceEnumerator.MoveNext(); 87 firstSpan = instance.readOnlySequenceEnumerator.Current.Span; 88 } 89 } 90 RefillBufferGoogle.Protobuf.SegmentedBufferHelper91 public bool RefillBuffer(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, bool mustSucceed) 92 { 93 if (codedInputStream != null) 94 { 95 return RefillFromCodedInputStream(ref buffer, ref state, mustSucceed); 96 } 97 else 98 { 99 return RefillFromReadOnlySequence(ref buffer, ref state, mustSucceed); 100 } 101 } 102 103 public int? TotalLength => totalLength; 104 105 public CodedInputStream CodedInputStream => codedInputStream; 106 107 /// <summary> 108 /// Sets currentLimit to (current position) + byteLimit. This is called 109 /// when descending into a length-delimited embedded message. The previous 110 /// limit is returned. 111 /// </summary> 112 /// <returns>The old limit.</returns> PushLimitGoogle.Protobuf.SegmentedBufferHelper113 public static int PushLimit(ref ParserInternalState state, int byteLimit) 114 { 115 if (byteLimit < 0) 116 { 117 throw InvalidProtocolBufferException.NegativeSize(); 118 } 119 byteLimit += state.totalBytesRetired + state.bufferPos; 120 int oldLimit = state.currentLimit; 121 if (byteLimit > oldLimit) 122 { 123 throw InvalidProtocolBufferException.TruncatedMessage(); 124 } 125 state.currentLimit = byteLimit; 126 127 RecomputeBufferSizeAfterLimit(ref state); 128 129 return oldLimit; 130 } 131 132 /// <summary> 133 /// Discards the current limit, returning the previous limit. 134 /// </summary> PopLimitGoogle.Protobuf.SegmentedBufferHelper135 public static void PopLimit(ref ParserInternalState state, int oldLimit) 136 { 137 state.currentLimit = oldLimit; 138 RecomputeBufferSizeAfterLimit(ref state); 139 } 140 141 /// <summary> 142 /// Returns whether or not all the data before the limit has been read. 143 /// </summary> 144 /// <returns></returns> IsReachedLimitGoogle.Protobuf.SegmentedBufferHelper145 public static bool IsReachedLimit(ref ParserInternalState state) 146 { 147 if (state.currentLimit == int.MaxValue) 148 { 149 return false; 150 } 151 int currentAbsolutePosition = state.totalBytesRetired + state.bufferPos; 152 return currentAbsolutePosition >= state.currentLimit; 153 } 154 155 /// <summary> 156 /// Returns true if the stream has reached the end of the input. This is the 157 /// case if either the end of the underlying input source has been reached or 158 /// the stream has reached a limit created using PushLimit. 159 /// </summary> 160 [MethodImpl(MethodImplOptions.AggressiveInlining)] IsAtEndGoogle.Protobuf.SegmentedBufferHelper161 public static bool IsAtEnd(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state) 162 { 163 return state.bufferPos == state.bufferSize && !state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, false); 164 } 165 RefillFromReadOnlySequenceGoogle.Protobuf.SegmentedBufferHelper166 private bool RefillFromReadOnlySequence(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, bool mustSucceed) 167 { 168 CheckCurrentBufferIsEmpty(ref state); 169 170 if (state.totalBytesRetired + state.bufferSize == state.currentLimit) 171 { 172 // Oops, we hit a limit. 173 if (mustSucceed) 174 { 175 throw InvalidProtocolBufferException.TruncatedMessage(); 176 } 177 else 178 { 179 return false; 180 } 181 } 182 183 state.totalBytesRetired += state.bufferSize; 184 185 state.bufferPos = 0; 186 state.bufferSize = 0; 187 while (readOnlySequenceEnumerator.MoveNext()) 188 { 189 buffer = readOnlySequenceEnumerator.Current.Span; 190 state.bufferSize = buffer.Length; 191 if (buffer.Length != 0) 192 { 193 break; 194 } 195 } 196 197 if (state.bufferSize == 0) 198 { 199 if (mustSucceed) 200 { 201 throw InvalidProtocolBufferException.TruncatedMessage(); 202 } 203 else 204 { 205 return false; 206 } 207 } 208 else 209 { 210 RecomputeBufferSizeAfterLimit(ref state); 211 int totalBytesRead = 212 state.totalBytesRetired + state.bufferSize + state.bufferSizeAfterLimit; 213 if (totalBytesRead < 0 || totalBytesRead > state.sizeLimit) 214 { 215 throw InvalidProtocolBufferException.SizeLimitExceeded(); 216 } 217 return true; 218 } 219 } 220 RefillFromCodedInputStreamGoogle.Protobuf.SegmentedBufferHelper221 private bool RefillFromCodedInputStream(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, bool mustSucceed) 222 { 223 CheckCurrentBufferIsEmpty(ref state); 224 225 if (state.totalBytesRetired + state.bufferSize == state.currentLimit) 226 { 227 // Oops, we hit a limit. 228 if (mustSucceed) 229 { 230 throw InvalidProtocolBufferException.TruncatedMessage(); 231 } 232 else 233 { 234 return false; 235 } 236 } 237 238 Stream input = codedInputStream.InternalInputStream; 239 240 state.totalBytesRetired += state.bufferSize; 241 242 state.bufferPos = 0; 243 state.bufferSize = (input == null) ? 0 : input.Read(codedInputStream.InternalBuffer, 0, buffer.Length); 244 if (state.bufferSize < 0) 245 { 246 throw new InvalidOperationException("Stream.Read returned a negative count"); 247 } 248 if (state.bufferSize == 0) 249 { 250 if (mustSucceed) 251 { 252 throw InvalidProtocolBufferException.TruncatedMessage(); 253 } 254 else 255 { 256 return false; 257 } 258 } 259 else 260 { 261 RecomputeBufferSizeAfterLimit(ref state); 262 int totalBytesRead = 263 state.totalBytesRetired + state.bufferSize + state.bufferSizeAfterLimit; 264 if (totalBytesRead < 0 || totalBytesRead > state.sizeLimit) 265 { 266 throw InvalidProtocolBufferException.SizeLimitExceeded(); 267 } 268 return true; 269 } 270 } 271 RecomputeBufferSizeAfterLimitGoogle.Protobuf.SegmentedBufferHelper272 private static void RecomputeBufferSizeAfterLimit(ref ParserInternalState state) 273 { 274 state.bufferSize += state.bufferSizeAfterLimit; 275 int bufferEnd = state.totalBytesRetired + state.bufferSize; 276 if (bufferEnd > state.currentLimit) 277 { 278 // Limit is in current buffer. 279 state.bufferSizeAfterLimit = bufferEnd - state.currentLimit; 280 state.bufferSize -= state.bufferSizeAfterLimit; 281 } 282 else 283 { 284 state.bufferSizeAfterLimit = 0; 285 } 286 } 287 CheckCurrentBufferIsEmptyGoogle.Protobuf.SegmentedBufferHelper288 private static void CheckCurrentBufferIsEmpty(ref ParserInternalState state) 289 { 290 if (state.bufferPos < state.bufferSize) 291 { 292 throw new InvalidOperationException("RefillBuffer() called when buffer wasn't empty."); 293 } 294 } 295 } 296 }