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