• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }