• 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 // 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.Buffers.Binary;
36 using System.Collections.Generic;
37 using System.Diagnostics;
38 using System.IO;
39 using System.Runtime.CompilerServices;
40 using System.Runtime.InteropServices;
41 using System.Security;
42 using System.Text;
43 using Google.Protobuf.Collections;
44 
45 namespace Google.Protobuf
46 {
47     /// <summary>
48     /// Primitives for parsing protobuf wire format.
49     /// </summary>
50     [SecuritySafeCritical]
51     internal static class ParsingPrimitives
52     {
53         private const int StackallocThreshold = 256;
54 
55         /// <summary>
56         /// Reads a length for length-delimited data.
57         /// </summary>
58         /// <remarks>
59         /// This is internally just reading a varint, but this method exists
60         /// to make the calling code clearer.
61         /// </remarks>
62         [MethodImpl(MethodImplOptions.AggressiveInlining)]
ParseLength(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)63         public static int ParseLength(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
64         {
65             return (int)ParseRawVarint32(ref buffer, ref state);
66         }
67 
68         /// <summary>
69         /// Parses the next tag.
70         /// If the end of logical stream was reached, an invalid tag of 0 is returned.
71         /// </summary>
ParseTag(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)72         public static uint ParseTag(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
73         {
74             // The "nextTag" logic is there only as an optimization for reading non-packed repeated / map
75             // fields and is strictly speaking not necessary.
76             // TODO(jtattermusch): look into simplifying the ParseTag logic.
77             if (state.hasNextTag)
78             {
79                 state.lastTag = state.nextTag;
80                 state.hasNextTag = false;
81                 return state.lastTag;
82             }
83 
84             // Optimize for the incredibly common case of having at least two bytes left in the buffer,
85             // and those two bytes being enough to get the tag. This will be true for fields up to 4095.
86             if (state.bufferPos + 2 <= state.bufferSize)
87             {
88                 int tmp = buffer[state.bufferPos++];
89                 if (tmp < 128)
90                 {
91                     state.lastTag = (uint)tmp;
92                 }
93                 else
94                 {
95                     int result = tmp & 0x7f;
96                     if ((tmp = buffer[state.bufferPos++]) < 128)
97                     {
98                         result |= tmp << 7;
99                         state.lastTag = (uint) result;
100                     }
101                     else
102                     {
103                         // Nope, rewind and go the potentially slow route.
104                         state.bufferPos -= 2;
105                         state.lastTag = ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
106                     }
107                 }
108             }
109             else
110             {
111                 if (SegmentedBufferHelper.IsAtEnd(ref buffer, ref state))
112                 {
113                     state.lastTag = 0;
114                     return 0;
115                 }
116 
117                 state.lastTag = ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
118             }
119             if (WireFormat.GetTagFieldNumber(state.lastTag) == 0)
120             {
121                 // If we actually read a tag with a field of 0, that's not a valid tag.
122                 throw InvalidProtocolBufferException.InvalidTag();
123             }
124             return state.lastTag;
125         }
126 
127         /// <summary>
128         /// Peeks at the next tag in the stream. If it matches <paramref name="tag"/>,
129         /// the tag is consumed and the method returns <c>true</c>; otherwise, the
130         /// stream is left in the original position and the method returns <c>false</c>.
131         /// </summary>
MaybeConsumeTag(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, uint tag)132         public static bool MaybeConsumeTag(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, uint tag)
133         {
134             if (PeekTag(ref buffer, ref state) == tag)
135             {
136                 state.hasNextTag = false;
137                 return true;
138             }
139             return false;
140         }
141 
142         /// <summary>
143         /// Peeks at the next field tag. This is like calling <see cref="ParseTag"/>, but the
144         /// tag is not consumed. (So a subsequent call to <see cref="ParseTag"/> will return the
145         /// same value.)
146         /// </summary>
PeekTag(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)147         public static uint PeekTag(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
148         {
149             if (state.hasNextTag)
150             {
151                 return state.nextTag;
152             }
153 
154             uint savedLast = state.lastTag;
155             state.nextTag = ParseTag(ref buffer, ref state);
156             state.hasNextTag = true;
157             state.lastTag = savedLast; // Undo the side effect of ReadTag
158             return state.nextTag;
159         }
160 
161         /// <summary>
162         /// Parses a raw varint.
163         /// </summary>
ParseRawVarint64(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)164         public static ulong ParseRawVarint64(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
165         {
166             if (state.bufferPos + 10 > state.bufferSize)
167             {
168                 return ParseRawVarint64SlowPath(ref buffer, ref state);
169             }
170 
171             ulong result = buffer[state.bufferPos++];
172             if (result < 128)
173             {
174                 return result;
175             }
176             result &= 0x7f;
177             int shift = 7;
178             do
179             {
180                 byte b = buffer[state.bufferPos++];
181                 result |= (ulong)(b & 0x7F) << shift;
182                 if (b < 0x80)
183                 {
184                     return result;
185                 }
186                 shift += 7;
187             }
188             while (shift < 64);
189 
190             throw InvalidProtocolBufferException.MalformedVarint();
191         }
192 
ParseRawVarint64SlowPath(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)193         private static ulong ParseRawVarint64SlowPath(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
194         {
195             int shift = 0;
196             ulong result = 0;
197             do
198             {
199                 byte b = ReadRawByte(ref buffer, ref state);
200                 result |= (ulong)(b & 0x7F) << shift;
201                 if (b < 0x80)
202                 {
203                     return result;
204                 }
205                 shift += 7;
206             }
207             while (shift < 64);
208 
209             throw InvalidProtocolBufferException.MalformedVarint();
210         }
211 
212         /// <summary>
213         /// Parses a raw Varint.  If larger than 32 bits, discard the upper bits.
214         /// This method is optimised for the case where we've got lots of data in the buffer.
215         /// That means we can check the size just once, then just read directly from the buffer
216         /// without constant rechecking of the buffer length.
217         /// </summary>
ParseRawVarint32(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)218         public static uint ParseRawVarint32(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
219         {
220             if (state.bufferPos + 5 > state.bufferSize)
221             {
222                 return ParseRawVarint32SlowPath(ref buffer, ref state);
223             }
224 
225             int tmp = buffer[state.bufferPos++];
226             if (tmp < 128)
227             {
228                 return (uint)tmp;
229             }
230             int result = tmp & 0x7f;
231             if ((tmp = buffer[state.bufferPos++]) < 128)
232             {
233                 result |= tmp << 7;
234             }
235             else
236             {
237                 result |= (tmp & 0x7f) << 7;
238                 if ((tmp = buffer[state.bufferPos++]) < 128)
239                 {
240                     result |= tmp << 14;
241                 }
242                 else
243                 {
244                     result |= (tmp & 0x7f) << 14;
245                     if ((tmp = buffer[state.bufferPos++]) < 128)
246                     {
247                         result |= tmp << 21;
248                     }
249                     else
250                     {
251                         result |= (tmp & 0x7f) << 21;
252                         result |= (tmp = buffer[state.bufferPos++]) << 28;
253                         if (tmp >= 128)
254                         {
255                             // Discard upper 32 bits.
256                             // Note that this has to use ReadRawByte() as we only ensure we've
257                             // got at least 5 bytes at the start of the method. This lets us
258                             // use the fast path in more cases, and we rarely hit this section of code.
259                             for (int i = 0; i < 5; i++)
260                             {
261                                 if (ReadRawByte(ref buffer, ref state) < 128)
262                                 {
263                                     return (uint) result;
264                                 }
265                             }
266                             throw InvalidProtocolBufferException.MalformedVarint();
267                         }
268                     }
269                 }
270             }
271             return (uint)result;
272         }
273 
ParseRawVarint32SlowPath(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)274         private static uint ParseRawVarint32SlowPath(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
275         {
276             int tmp = ReadRawByte(ref buffer, ref state);
277             if (tmp < 128)
278             {
279                 return (uint) tmp;
280             }
281             int result = tmp & 0x7f;
282             if ((tmp = ReadRawByte(ref buffer, ref state)) < 128)
283             {
284                 result |= tmp << 7;
285             }
286             else
287             {
288                 result |= (tmp & 0x7f) << 7;
289                 if ((tmp = ReadRawByte(ref buffer, ref state)) < 128)
290                 {
291                     result |= tmp << 14;
292                 }
293                 else
294                 {
295                     result |= (tmp & 0x7f) << 14;
296                     if ((tmp = ReadRawByte(ref buffer, ref state)) < 128)
297                     {
298                         result |= tmp << 21;
299                     }
300                     else
301                     {
302                         result |= (tmp & 0x7f) << 21;
303                         result |= (tmp = ReadRawByte(ref buffer, ref state)) << 28;
304                         if (tmp >= 128)
305                         {
306                             // Discard upper 32 bits.
307                             for (int i = 0; i < 5; i++)
308                             {
309                                 if (ReadRawByte(ref buffer, ref state) < 128)
310                                 {
311                                     return (uint) result;
312                                 }
313                             }
314                             throw InvalidProtocolBufferException.MalformedVarint();
315                         }
316                     }
317                 }
318             }
319             return (uint) result;
320         }
321 
322         /// <summary>
323         /// Parses a 32-bit little-endian integer.
324         /// </summary>
ParseRawLittleEndian32(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)325         public static uint ParseRawLittleEndian32(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
326         {
327             const int uintLength = sizeof(uint);
328             const int ulongLength = sizeof(ulong);
329             if (state.bufferPos + ulongLength > state.bufferSize)
330             {
331                 return ParseRawLittleEndian32SlowPath(ref buffer, ref state);
332             }
333             // ReadUInt32LittleEndian is many times slower than ReadUInt64LittleEndian (at least on some runtimes)
334             // so it's faster better to use ReadUInt64LittleEndian and truncate the result.
335             uint result = (uint) BinaryPrimitives.ReadUInt64LittleEndian(buffer.Slice(state.bufferPos, ulongLength));
336             state.bufferPos += uintLength;
337             return result;
338         }
339 
ParseRawLittleEndian32SlowPath(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)340         private static uint ParseRawLittleEndian32SlowPath(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
341         {
342             uint b1 = ReadRawByte(ref buffer, ref state);
343             uint b2 = ReadRawByte(ref buffer, ref state);
344             uint b3 = ReadRawByte(ref buffer, ref state);
345             uint b4 = ReadRawByte(ref buffer, ref state);
346             return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24);
347         }
348 
349         /// <summary>
350         /// Parses a 64-bit little-endian integer.
351         /// </summary>
ParseRawLittleEndian64(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)352         public static ulong ParseRawLittleEndian64(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
353         {
354             const int length = sizeof(ulong);
355             if (state.bufferPos + length > state.bufferSize)
356             {
357                 return ParseRawLittleEndian64SlowPath(ref buffer, ref state);
358             }
359             ulong result = BinaryPrimitives.ReadUInt64LittleEndian(buffer.Slice(state.bufferPos, length));
360             state.bufferPos += length;
361             return result;
362         }
363 
ParseRawLittleEndian64SlowPath(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)364         private static ulong ParseRawLittleEndian64SlowPath(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
365         {
366             ulong b1 = ReadRawByte(ref buffer, ref state);
367             ulong b2 = ReadRawByte(ref buffer, ref state);
368             ulong b3 = ReadRawByte(ref buffer, ref state);
369             ulong b4 = ReadRawByte(ref buffer, ref state);
370             ulong b5 = ReadRawByte(ref buffer, ref state);
371             ulong b6 = ReadRawByte(ref buffer, ref state);
372             ulong b7 = ReadRawByte(ref buffer, ref state);
373             ulong b8 = ReadRawByte(ref buffer, ref state);
374             return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24)
375                     | (b5 << 32) | (b6 << 40) | (b7 << 48) | (b8 << 56);
376         }
377 
378         /// <summary>
379         /// Parses a double value.
380         /// </summary>
ParseDouble(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)381         public static double ParseDouble(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
382         {
383             const int length = sizeof(double);
384             if (!BitConverter.IsLittleEndian || state.bufferPos + length > state.bufferSize)
385             {
386                 return BitConverter.Int64BitsToDouble((long)ParseRawLittleEndian64(ref buffer, ref state));
387             }
388             // ReadUnaligned uses processor architecture for endianness.
389             double result = Unsafe.ReadUnaligned<double>(ref MemoryMarshal.GetReference(buffer.Slice(state.bufferPos, length)));
390             state.bufferPos += length;
391             return result;
392         }
393 
394         /// <summary>
395         /// Parses a float value.
396         /// </summary>
ParseFloat(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)397         public static float ParseFloat(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
398         {
399             const int length = sizeof(float);
400             if (!BitConverter.IsLittleEndian || state.bufferPos + length > state.bufferSize)
401             {
402                 return ParseFloatSlow(ref buffer, ref state);
403             }
404             // ReadUnaligned uses processor architecture for endianness.
405             float result = Unsafe.ReadUnaligned<float>(ref MemoryMarshal.GetReference(buffer.Slice(state.bufferPos, length)));
406             state.bufferPos += length;
407             return result;
408         }
409 
ParseFloatSlow(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)410         private static unsafe float ParseFloatSlow(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
411         {
412             const int length = sizeof(float);
413             byte* stackBuffer = stackalloc byte[length];
414             Span<byte> tempSpan = new Span<byte>(stackBuffer, length);
415             for (int i = 0; i < length; i++)
416             {
417                 tempSpan[i] = ReadRawByte(ref buffer, ref state);
418             }
419 
420             // Content is little endian. Reverse if needed to match endianness of architecture.
421             if (!BitConverter.IsLittleEndian)
422             {
423                 tempSpan.Reverse();
424             }
425             return Unsafe.ReadUnaligned<float>(ref MemoryMarshal.GetReference(tempSpan));
426         }
427 
428         /// <summary>
429         /// Reads a fixed size of bytes from the input.
430         /// </summary>
431         /// <exception cref="InvalidProtocolBufferException">
432         /// the end of the stream or the current limit was reached
433         /// </exception>
ReadRawBytes(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int size)434         public static byte[] ReadRawBytes(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int size)
435         {
436             if (size < 0)
437             {
438                 throw InvalidProtocolBufferException.NegativeSize();
439             }
440 
441             if (size <= state.bufferSize - state.bufferPos)
442             {
443                 // We have all the bytes we need already.
444                 byte[] bytes = new byte[size];
445                 buffer.Slice(state.bufferPos, size).CopyTo(bytes);
446                 state.bufferPos += size;
447                 return bytes;
448             }
449 
450             return ReadRawBytesSlow(ref buffer, ref state, size);
451         }
452 
ReadRawBytesSlow(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int size)453         private static byte[] ReadRawBytesSlow(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int size)
454         {
455             ValidateCurrentLimit(ref buffer, ref state, size);
456 
457             if ((!state.segmentedBufferHelper.TotalLength.HasValue && size < buffer.Length) ||
458                 IsDataAvailableInSource(ref state, size))
459             {
460                 // Reading more bytes than are in the buffer, but not an excessive number
461                 // of bytes.  We can safely allocate the resulting array ahead of time.
462 
463                 byte[] bytes = new byte[size];
464                 ReadRawBytesIntoSpan(ref buffer, ref state, size, bytes);
465                 return bytes;
466             }
467             else
468             {
469                 // The size is very large.  For security reasons, we can't allocate the
470                 // entire byte array yet.  The size comes directly from the input, so a
471                 // maliciously-crafted message could provide a bogus very large size in
472                 // order to trick the app into allocating a lot of memory.  We avoid this
473                 // by allocating and reading only a small chunk at a time, so that the
474                 // malicious message must actually *be* extremely large to cause
475                 // problems.  Meanwhile, we limit the allowed size of a message elsewhere.
476 
477                 List<byte[]> chunks = new List<byte[]>();
478 
479                 int pos = state.bufferSize - state.bufferPos;
480                 byte[] firstChunk = new byte[pos];
481                 buffer.Slice(state.bufferPos, pos).CopyTo(firstChunk);
482                 chunks.Add(firstChunk);
483                 state.bufferPos = state.bufferSize;
484 
485                 // Read all the rest of the bytes we need.
486                 int sizeLeft = size - pos;
487                 while (sizeLeft > 0)
488                 {
489                     state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, true);
490                     byte[] chunk = new byte[Math.Min(sizeLeft, state.bufferSize)];
491 
492                     buffer.Slice(0, chunk.Length)
493                         .CopyTo(chunk);
494                     state.bufferPos += chunk.Length;
495                     sizeLeft -= chunk.Length;
496                     chunks.Add(chunk);
497                 }
498 
499                 // OK, got everything.  Now concatenate it all into one buffer.
500                 byte[] bytes = new byte[size];
501                 int newPos = 0;
502                 foreach (byte[] chunk in chunks)
503                 {
504                     Buffer.BlockCopy(chunk, 0, bytes, newPos, chunk.Length);
505                     newPos += chunk.Length;
506                 }
507 
508                 // Done.
509                 return bytes;
510             }
511         }
512 
513         /// <summary>
514         /// Reads and discards <paramref name="size"/> bytes.
515         /// </summary>
516         /// <exception cref="InvalidProtocolBufferException">the end of the stream
517         /// or the current limit was reached</exception>
SkipRawBytes(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int size)518         public static void SkipRawBytes(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int size)
519         {
520             if (size < 0)
521             {
522                 throw InvalidProtocolBufferException.NegativeSize();
523             }
524 
525             ValidateCurrentLimit(ref buffer, ref state, size);
526 
527             if (size <= state.bufferSize - state.bufferPos)
528             {
529                 // We have all the bytes we need already.
530                 state.bufferPos += size;
531             }
532             else
533             {
534                 // Skipping more bytes than are in the buffer.  First skip what we have.
535                 int pos = state.bufferSize - state.bufferPos;
536                 state.bufferPos = state.bufferSize;
537 
538                 // TODO: If our segmented buffer is backed by a Stream that is seekable, we could skip the bytes more efficiently
539                 // by simply updating stream's Position property. This used to be supported in the past, but the support was dropped
540                 // because it would make the segmentedBufferHelper more complex. Support can be reintroduced if needed.
541                 state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, true);
542 
543                 while (size - pos > state.bufferSize)
544                 {
545                     pos += state.bufferSize;
546                     state.bufferPos = state.bufferSize;
547                     state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, true);
548                 }
549 
550                 state.bufferPos = size - pos;
551             }
552         }
553 
554         /// <summary>
555         /// Reads a string field value from the input.
556         /// </summary>
557         [MethodImpl(MethodImplOptions.AggressiveInlining)]
ReadString(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)558         public static string ReadString(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
559         {
560             int length = ParsingPrimitives.ParseLength(ref buffer, ref state);
561             return ParsingPrimitives.ReadRawString(ref buffer, ref state, length);
562         }
563 
564         /// <summary>
565         /// Reads a bytes field value from the input.
566         /// </summary>
567         [MethodImpl(MethodImplOptions.AggressiveInlining)]
ReadBytes(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)568         public static ByteString ReadBytes(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
569         {
570             int length = ParsingPrimitives.ParseLength(ref buffer, ref state);
571             return ByteString.AttachBytes(ParsingPrimitives.ReadRawBytes(ref buffer, ref state, length));
572         }
573 
574         /// <summary>
575         /// Reads a UTF-8 string from the next "length" bytes.
576         /// </summary>
577         /// <exception cref="InvalidProtocolBufferException">
578         /// the end of the stream or the current limit was reached
579         /// </exception>
580         [SecuritySafeCritical]
ReadRawString(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int length)581         public static string ReadRawString(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int length)
582         {
583             // No need to read any data for an empty string.
584             if (length == 0)
585             {
586                 return string.Empty;
587             }
588 
589             if (length < 0)
590             {
591                 throw InvalidProtocolBufferException.NegativeSize();
592             }
593 
594 #if GOOGLE_PROTOBUF_SUPPORT_FAST_STRING
595             if (length <= state.bufferSize - state.bufferPos)
596             {
597                 // Fast path: all bytes to decode appear in the same span.
598                 ReadOnlySpan<byte> data = buffer.Slice(state.bufferPos, length);
599 
600                 string value;
601                 unsafe
602                 {
603                     fixed (byte* sourceBytes = &MemoryMarshal.GetReference(data))
604                     {
605                         value = WritingPrimitives.Utf8Encoding.GetString(sourceBytes, length);
606                     }
607                 }
608 
609                 state.bufferPos += length;
610                 return value;
611             }
612 #endif
613 
614             return ReadStringSlow(ref buffer, ref state, length);
615         }
616 
617         /// <summary>
618         /// Reads a string assuming that it is spread across multiple spans in a <see cref="ReadOnlySequence{T}"/>.
619         /// </summary>
ReadStringSlow(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int length)620         private static string ReadStringSlow(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int length)
621         {
622             ValidateCurrentLimit(ref buffer, ref state, length);
623 
624 #if GOOGLE_PROTOBUF_SUPPORT_FAST_STRING
625             if (IsDataAvailable(ref state, length))
626             {
627                 // Read string data into a temporary buffer, either stackalloc'ed or from ArrayPool
628                 // Once all data is read then call Encoding.GetString on buffer and return to pool if needed.
629 
630                 byte[] byteArray = null;
631                 Span<byte> byteSpan = length <= StackallocThreshold ?
632                     stackalloc byte[length] :
633                     (byteArray = ArrayPool<byte>.Shared.Rent(length));
634 
635                 try
636                 {
637                     unsafe
638                     {
639                         fixed (byte* pByteSpan = &MemoryMarshal.GetReference(byteSpan))
640                         {
641                             // Compiler doesn't like that a potentially stackalloc'd Span<byte> is being used
642                             // in a method with a "ref Span<byte> buffer" argument. If the stackalloc'd span was assigned
643                             // to the ref argument then bad things would happen. We'll never do that so it is ok.
644                             // Make compiler happy by passing a new span created from pointer.
645                             var tempSpan = new Span<byte>(pByteSpan, byteSpan.Length);
646                             ReadRawBytesIntoSpan(ref buffer, ref state, length, tempSpan);
647 
648                             return WritingPrimitives.Utf8Encoding.GetString(pByteSpan, length);
649                         }
650                     }
651                 }
652                 finally
653                 {
654                     if (byteArray != null)
655                     {
656                         ArrayPool<byte>.Shared.Return(byteArray);
657                     }
658                 }
659             }
660 #endif
661 
662             // Slow path: Build a byte array first then copy it.
663             // This will be called when reading from a Stream because we don't know the length of the stream,
664             // or there is not enough data in the sequence. If there is not enough data then ReadRawBytes will
665             // throw an exception.
666             return WritingPrimitives.Utf8Encoding.GetString(ReadRawBytes(ref buffer, ref state, length), 0, length);
667         }
668 
669         /// <summary>
670         /// Validates that the specified size doesn't exceed the current limit. If it does then remaining bytes
671         /// are skipped and an error is thrown.
672         /// </summary>
ValidateCurrentLimit(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int size)673         private static void ValidateCurrentLimit(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int size)
674         {
675             if (state.totalBytesRetired + state.bufferPos + size > state.currentLimit)
676             {
677                 // Read to the end of the stream (up to the current limit) anyway.
678                 SkipRawBytes(ref buffer, ref state, state.currentLimit - state.totalBytesRetired - state.bufferPos);
679                 // Then fail.
680                 throw InvalidProtocolBufferException.TruncatedMessage();
681             }
682         }
683 
684         [SecuritySafeCritical]
ReadRawByte(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)685         private static byte ReadRawByte(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
686         {
687             if (state.bufferPos == state.bufferSize)
688             {
689                 state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, true);
690             }
691             return buffer[state.bufferPos++];
692         }
693 
694         /// <summary>
695         /// Reads a varint from the input one byte at a time, so that it does not
696         /// read any bytes after the end of the varint. If you simply wrapped the
697         /// stream in a CodedInputStream and used ReadRawVarint32(Stream)
698         /// then you would probably end up reading past the end of the varint since
699         /// CodedInputStream buffers its input.
700         /// </summary>
701         /// <param name="input"></param>
702         /// <returns></returns>
ReadRawVarint32(Stream input)703         public static uint ReadRawVarint32(Stream input)
704         {
705             int result = 0;
706             int offset = 0;
707             for (; offset < 32; offset += 7)
708             {
709                 int b = input.ReadByte();
710                 if (b == -1)
711                 {
712                     throw InvalidProtocolBufferException.TruncatedMessage();
713                 }
714                 result |= (b & 0x7f) << offset;
715                 if ((b & 0x80) == 0)
716                 {
717                     return (uint) result;
718                 }
719             }
720             // Keep reading up to 64 bits.
721             for (; offset < 64; offset += 7)
722             {
723                 int b = input.ReadByte();
724                 if (b == -1)
725                 {
726                     throw InvalidProtocolBufferException.TruncatedMessage();
727                 }
728                 if ((b & 0x80) == 0)
729                 {
730                     return (uint) result;
731                 }
732             }
733             throw InvalidProtocolBufferException.MalformedVarint();
734         }
735 
736         /// <summary>
737         /// Decode a 32-bit value with ZigZag encoding.
738         /// </summary>
739         /// <remarks>
740         /// ZigZag encodes signed integers into values that can be efficiently
741         /// encoded with varint.  (Otherwise, negative values must be
742         /// sign-extended to 32 bits to be varint encoded, thus always taking
743         /// 5 bytes on the wire.)
744         /// </remarks>
DecodeZigZag32(uint n)745         public static int DecodeZigZag32(uint n)
746         {
747             return (int)(n >> 1) ^ -(int)(n & 1);
748         }
749 
750         /// <summary>
751         /// Decode a 64-bit value with ZigZag encoding.
752         /// </summary>
753         /// <remarks>
754         /// ZigZag encodes signed integers into values that can be efficiently
755         /// encoded with varint.  (Otherwise, negative values must be
756         /// sign-extended to 64 bits to be varint encoded, thus always taking
757         /// 10 bytes on the wire.)
758         /// </remarks>
DecodeZigZag64(ulong n)759         public static long DecodeZigZag64(ulong n)
760         {
761             return (long)(n >> 1) ^ -(long)(n & 1);
762         }
763 
764         /// <summary>
765         /// Checks whether there is known data available of the specified size remaining to parse.
766         /// When parsing from a Stream this can return false because we have no knowledge of the amount
767         /// of data remaining in the stream until it is read.
768         /// </summary>
IsDataAvailable(ref ParserInternalState state, int size)769         public static bool IsDataAvailable(ref ParserInternalState state, int size)
770         {
771             // Data fits in remaining buffer
772             if (size <= state.bufferSize - state.bufferPos)
773             {
774                 return true;
775             }
776 
777             return IsDataAvailableInSource(ref state, size);
778         }
779 
780         /// <summary>
781         /// Checks whether there is known data available of the specified size remaining to parse
782         /// in the underlying data source.
783         /// When parsing from a Stream this will return false because we have no knowledge of the amount
784         /// of data remaining in the stream until it is read.
785         /// </summary>
IsDataAvailableInSource(ref ParserInternalState state, int size)786         private static bool IsDataAvailableInSource(ref ParserInternalState state, int size)
787         {
788             // Data fits in remaining source data.
789             // Note that this will never be true when reading from a stream as the total length is unknown.
790             return size <= state.segmentedBufferHelper.TotalLength - state.totalBytesRetired - state.bufferPos;
791         }
792 
793         /// <summary>
794         /// Read raw bytes of the specified length into a span. The amount of data available and the current limit should
795         /// be checked before calling this method.
796         /// </summary>
ReadRawBytesIntoSpan(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int length, Span<byte> byteSpan)797         private static void ReadRawBytesIntoSpan(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, int length, Span<byte> byteSpan)
798         {
799             int remainingByteLength = length;
800             while (remainingByteLength > 0)
801             {
802                 if (state.bufferSize - state.bufferPos == 0)
803                 {
804                     state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, true);
805                 }
806 
807                 ReadOnlySpan<byte> unreadSpan = buffer.Slice(state.bufferPos, Math.Min(remainingByteLength, state.bufferSize - state.bufferPos));
808                 unreadSpan.CopyTo(byteSpan.Slice(length - remainingByteLength));
809 
810                 remainingByteLength -= unreadSpan.Length;
811                 state.bufferPos += unreadSpan.Length;
812             }
813         }
814     }
815 }
816