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.Binary; 35 using System.Diagnostics; 36 using System.Runtime.CompilerServices; 37 using System.Runtime.InteropServices; 38 #if GOOGLE_PROTOBUF_SIMD 39 using System.Runtime.Intrinsics; 40 using System.Runtime.Intrinsics.Arm; 41 using System.Runtime.Intrinsics.X86; 42 #endif 43 using System.Security; 44 using System.Text; 45 46 namespace Google.Protobuf 47 { 48 /// <summary> 49 /// Primitives for encoding protobuf wire format. 50 /// </summary> 51 [SecuritySafeCritical] 52 internal static class WritingPrimitives 53 { 54 #if NET5_0 55 internal static Encoding Utf8Encoding => Encoding.UTF8; // allows JIT to devirtualize 56 #else 57 internal static readonly Encoding Utf8Encoding = Encoding.UTF8; // "Local" copy of Encoding.UTF8, for efficiency. (Yes, it makes a difference.) 58 #endif 59 60 #region Writing of values (not including tags) 61 62 /// <summary> 63 /// Writes a double field value, without a tag, to the stream. 64 /// </summary> WriteDouble(ref Span<byte> buffer, ref WriterInternalState state, double value)65 public static void WriteDouble(ref Span<byte> buffer, ref WriterInternalState state, double value) 66 { 67 WriteRawLittleEndian64(ref buffer, ref state, (ulong)BitConverter.DoubleToInt64Bits(value)); 68 } 69 70 /// <summary> 71 /// Writes a float field value, without a tag, to the stream. 72 /// </summary> WriteFloat(ref Span<byte> buffer, ref WriterInternalState state, float value)73 public static unsafe void WriteFloat(ref Span<byte> buffer, ref WriterInternalState state, float value) 74 { 75 const int length = sizeof(float); 76 if (buffer.Length - state.position >= length) 77 { 78 // if there's enough space in the buffer, write the float directly into the buffer 79 var floatSpan = buffer.Slice(state.position, length); 80 Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(floatSpan), value); 81 82 if (!BitConverter.IsLittleEndian) 83 { 84 floatSpan.Reverse(); 85 } 86 state.position += length; 87 } 88 else 89 { 90 WriteFloatSlowPath(ref buffer, ref state, value); 91 } 92 } 93 94 [MethodImpl(MethodImplOptions.NoInlining)] WriteFloatSlowPath(ref Span<byte> buffer, ref WriterInternalState state, float value)95 private static unsafe void WriteFloatSlowPath(ref Span<byte> buffer, ref WriterInternalState state, float value) 96 { 97 const int length = sizeof(float); 98 99 // TODO(jtattermusch): deduplicate the code. Populating the span is the same as for the fastpath. 100 Span<byte> floatSpan = stackalloc byte[length]; 101 Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(floatSpan), value); 102 if (!BitConverter.IsLittleEndian) 103 { 104 floatSpan.Reverse(); 105 } 106 107 WriteRawByte(ref buffer, ref state, floatSpan[0]); 108 WriteRawByte(ref buffer, ref state, floatSpan[1]); 109 WriteRawByte(ref buffer, ref state, floatSpan[2]); 110 WriteRawByte(ref buffer, ref state, floatSpan[3]); 111 } 112 113 /// <summary> 114 /// Writes a uint64 field value, without a tag, to the stream. 115 /// </summary> WriteUInt64(ref Span<byte> buffer, ref WriterInternalState state, ulong value)116 public static void WriteUInt64(ref Span<byte> buffer, ref WriterInternalState state, ulong value) 117 { 118 WriteRawVarint64(ref buffer, ref state, value); 119 } 120 121 /// <summary> 122 /// Writes an int64 field value, without a tag, to the stream. 123 /// </summary> WriteInt64(ref Span<byte> buffer, ref WriterInternalState state, long value)124 public static void WriteInt64(ref Span<byte> buffer, ref WriterInternalState state, long value) 125 { 126 WriteRawVarint64(ref buffer, ref state, (ulong)value); 127 } 128 129 /// <summary> 130 /// Writes an int32 field value, without a tag, to the stream. 131 /// </summary> WriteInt32(ref Span<byte> buffer, ref WriterInternalState state, int value)132 public static void WriteInt32(ref Span<byte> buffer, ref WriterInternalState state, int value) 133 { 134 if (value >= 0) 135 { 136 WriteRawVarint32(ref buffer, ref state, (uint)value); 137 } 138 else 139 { 140 // Must sign-extend. 141 WriteRawVarint64(ref buffer, ref state, (ulong)value); 142 } 143 } 144 145 /// <summary> 146 /// Writes a fixed64 field value, without a tag, to the stream. 147 /// </summary> WriteFixed64(ref Span<byte> buffer, ref WriterInternalState state, ulong value)148 public static void WriteFixed64(ref Span<byte> buffer, ref WriterInternalState state, ulong value) 149 { 150 WriteRawLittleEndian64(ref buffer, ref state, value); 151 } 152 153 /// <summary> 154 /// Writes a fixed32 field value, without a tag, to the stream. 155 /// </summary> WriteFixed32(ref Span<byte> buffer, ref WriterInternalState state, uint value)156 public static void WriteFixed32(ref Span<byte> buffer, ref WriterInternalState state, uint value) 157 { 158 WriteRawLittleEndian32(ref buffer, ref state, value); 159 } 160 161 /// <summary> 162 /// Writes a bool field value, without a tag, to the stream. 163 /// </summary> WriteBool(ref Span<byte> buffer, ref WriterInternalState state, bool value)164 public static void WriteBool(ref Span<byte> buffer, ref WriterInternalState state, bool value) 165 { 166 WriteRawByte(ref buffer, ref state, value ? (byte)1 : (byte)0); 167 } 168 169 /// <summary> 170 /// Writes a string field value, without a tag, to the stream. 171 /// The data is length-prefixed. 172 /// </summary> WriteString(ref Span<byte> buffer, ref WriterInternalState state, string value)173 public static void WriteString(ref Span<byte> buffer, ref WriterInternalState state, string value) 174 { 175 const int MaxBytesPerChar = 3; 176 const int MaxSmallStringLength = 128 / MaxBytesPerChar; 177 178 // The string is small enough that the length will always be a 1 byte varint. 179 // Also there is enough space to write length + bytes to buffer. 180 // Write string directly to the buffer, and then write length. 181 // This saves calling GetByteCount on the string. We get the string length from GetBytes. 182 if (value.Length <= MaxSmallStringLength && buffer.Length - state.position - 1 >= value.Length * MaxBytesPerChar) 183 { 184 int indexOfLengthDelimiter = state.position++; 185 buffer[indexOfLengthDelimiter] = (byte)WriteStringToBuffer(buffer, ref state, value); 186 return; 187 } 188 189 int length = Utf8Encoding.GetByteCount(value); 190 WriteLength(ref buffer, ref state, length); 191 192 // Optimise the case where we have enough space to write 193 // the string directly to the buffer, which should be common. 194 if (buffer.Length - state.position >= length) 195 { 196 if (length == value.Length) // Must be all ASCII... 197 { 198 WriteAsciiStringToBuffer(buffer, ref state, value, length); 199 } 200 else 201 { 202 WriteStringToBuffer(buffer, ref state, value); 203 } 204 } 205 else 206 { 207 // Opportunity for future optimization: 208 // Large strings that don't fit into the current buffer segment 209 // can probably be optimized by using Utf8Encoding.GetEncoder() 210 // but more benchmarks would need to be added as evidence. 211 byte[] bytes = Utf8Encoding.GetBytes(value); 212 WriteRawBytes(ref buffer, ref state, bytes); 213 } 214 } 215 216 // Calling this method with non-ASCII content will break. 217 // Content must be verified to be all ASCII before using this method. WriteAsciiStringToBuffer(Span<byte> buffer, ref WriterInternalState state, string value, int length)218 private static void WriteAsciiStringToBuffer(Span<byte> buffer, ref WriterInternalState state, string value, int length) 219 { 220 ref char sourceChars = ref MemoryMarshal.GetReference(value.AsSpan()); 221 ref byte destinationBytes = ref MemoryMarshal.GetReference(buffer.Slice(state.position)); 222 223 int currentIndex = 0; 224 // If 64bit, process 4 chars at a time. 225 // The logic inside this check will be elided by JIT in 32bit programs. 226 if (IntPtr.Size == 8) 227 { 228 // Need at least 4 chars available to use this optimization. 229 if (length >= 4) 230 { 231 ref byte sourceBytes = ref Unsafe.As<char, byte>(ref sourceChars); 232 233 // Process 4 chars at a time until there are less than 4 remaining. 234 // We already know all characters are ASCII so there is no need to validate the source. 235 int lastIndexWhereCanReadFourChars = value.Length - 4; 236 do 237 { 238 NarrowFourUtf16CharsToAsciiAndWriteToBuffer( 239 ref Unsafe.AddByteOffset(ref destinationBytes, (IntPtr)currentIndex), 240 Unsafe.ReadUnaligned<ulong>(ref Unsafe.AddByteOffset(ref sourceBytes, (IntPtr)(currentIndex * 2)))); 241 242 } while ((currentIndex += 4) <= lastIndexWhereCanReadFourChars); 243 } 244 } 245 246 // Process any remaining, 1 char at a time. 247 // Avoid bounds checking with ref + Unsafe 248 for (; currentIndex < length; currentIndex++) 249 { 250 Unsafe.AddByteOffset(ref destinationBytes, (IntPtr)currentIndex) = (byte)Unsafe.AddByteOffset(ref sourceChars, (IntPtr)(currentIndex * 2)); 251 } 252 253 state.position += length; 254 } 255 256 // Copied with permission from https://github.com/dotnet/runtime/blob/1cdafd27e4afd2c916af5df949c13f8b373c4335/src/libraries/System.Private.CoreLib/src/System/Text/ASCIIUtility.cs#L1119-L1171 257 // 258 /// <summary> 259 /// Given a QWORD which represents a buffer of 4 ASCII chars in machine-endian order, 260 /// narrows each WORD to a BYTE, then writes the 4-byte result to the output buffer 261 /// also in machine-endian order. 262 /// </summary> 263 [MethodImpl(MethodImplOptions.AggressiveInlining)] NarrowFourUtf16CharsToAsciiAndWriteToBuffer(ref byte outputBuffer, ulong value)264 private static void NarrowFourUtf16CharsToAsciiAndWriteToBuffer(ref byte outputBuffer, ulong value) 265 { 266 #if GOOGLE_PROTOBUF_SIMD 267 if (Sse2.X64.IsSupported) 268 { 269 // Narrows a vector of words [ w0 w1 w2 w3 ] to a vector of bytes 270 // [ b0 b1 b2 b3 b0 b1 b2 b3 ], then writes 4 bytes (32 bits) to the destination. 271 272 Vector128<short> vecWide = Sse2.X64.ConvertScalarToVector128UInt64(value).AsInt16(); 273 Vector128<uint> vecNarrow = Sse2.PackUnsignedSaturate(vecWide, vecWide).AsUInt32(); 274 Unsafe.WriteUnaligned<uint>(ref outputBuffer, Sse2.ConvertToUInt32(vecNarrow)); 275 } 276 else if (AdvSimd.IsSupported) 277 { 278 // Narrows a vector of words [ w0 w1 w2 w3 ] to a vector of bytes 279 // [ b0 b1 b2 b3 * * * * ], then writes 4 bytes (32 bits) to the destination. 280 281 Vector128<short> vecWide = Vector128.CreateScalarUnsafe(value).AsInt16(); 282 Vector64<byte> lower = AdvSimd.ExtractNarrowingSaturateUnsignedLower(vecWide); 283 Unsafe.WriteUnaligned<uint>(ref outputBuffer, lower.AsUInt32().ToScalar()); 284 } 285 else 286 #endif 287 { 288 // Fallback to non-SIMD approach when SIMD is not available. 289 // This could happen either because the APIs are not available, or hardware doesn't support it. 290 // Processing 4 chars at a time in this fallback is still faster than casting one char at a time. 291 if (BitConverter.IsLittleEndian) 292 { 293 outputBuffer = (byte)value; 294 value >>= 16; 295 Unsafe.Add(ref outputBuffer, 1) = (byte)value; 296 value >>= 16; 297 Unsafe.Add(ref outputBuffer, 2) = (byte)value; 298 value >>= 16; 299 Unsafe.Add(ref outputBuffer, 3) = (byte)value; 300 } 301 else 302 { 303 Unsafe.Add(ref outputBuffer, 3) = (byte)value; 304 value >>= 16; 305 Unsafe.Add(ref outputBuffer, 2) = (byte)value; 306 value >>= 16; 307 Unsafe.Add(ref outputBuffer, 1) = (byte)value; 308 value >>= 16; 309 outputBuffer = (byte)value; 310 } 311 } 312 } 313 WriteStringToBuffer(Span<byte> buffer, ref WriterInternalState state, string value)314 private static int WriteStringToBuffer(Span<byte> buffer, ref WriterInternalState state, string value) 315 { 316 #if NETSTANDARD1_1 317 // slowpath when Encoding.GetBytes(Char*, Int32, Byte*, Int32) is not available 318 byte[] bytes = Utf8Encoding.GetBytes(value); 319 WriteRawBytes(ref buffer, ref state, bytes); 320 return bytes.Length; 321 #else 322 ReadOnlySpan<char> source = value.AsSpan(); 323 int bytesUsed; 324 unsafe 325 { 326 fixed (char* sourceChars = &MemoryMarshal.GetReference(source)) 327 fixed (byte* destinationBytes = &MemoryMarshal.GetReference(buffer)) 328 { 329 bytesUsed = Utf8Encoding.GetBytes( 330 sourceChars, 331 source.Length, 332 destinationBytes + state.position, 333 buffer.Length - state.position); 334 } 335 } 336 state.position += bytesUsed; 337 return bytesUsed; 338 #endif 339 } 340 341 /// <summary> 342 /// Write a byte string, without a tag, to the stream. 343 /// The data is length-prefixed. 344 /// </summary> WriteBytes(ref Span<byte> buffer, ref WriterInternalState state, ByteString value)345 public static void WriteBytes(ref Span<byte> buffer, ref WriterInternalState state, ByteString value) 346 { 347 WriteLength(ref buffer, ref state, value.Length); 348 WriteRawBytes(ref buffer, ref state, value.Span); 349 } 350 351 /// <summary> 352 /// Writes a uint32 value, without a tag, to the stream. 353 /// </summary> WriteUInt32(ref Span<byte> buffer, ref WriterInternalState state, uint value)354 public static void WriteUInt32(ref Span<byte> buffer, ref WriterInternalState state, uint value) 355 { 356 WriteRawVarint32(ref buffer, ref state, value); 357 } 358 359 /// <summary> 360 /// Writes an enum value, without a tag, to the stream. 361 /// </summary> WriteEnum(ref Span<byte> buffer, ref WriterInternalState state, int value)362 public static void WriteEnum(ref Span<byte> buffer, ref WriterInternalState state, int value) 363 { 364 WriteInt32(ref buffer, ref state, value); 365 } 366 367 /// <summary> 368 /// Writes an sfixed32 value, without a tag, to the stream. 369 /// </summary> WriteSFixed32(ref Span<byte> buffer, ref WriterInternalState state, int value)370 public static void WriteSFixed32(ref Span<byte> buffer, ref WriterInternalState state, int value) 371 { 372 WriteRawLittleEndian32(ref buffer, ref state, (uint)value); 373 } 374 375 /// <summary> 376 /// Writes an sfixed64 value, without a tag, to the stream. 377 /// </summary> WriteSFixed64(ref Span<byte> buffer, ref WriterInternalState state, long value)378 public static void WriteSFixed64(ref Span<byte> buffer, ref WriterInternalState state, long value) 379 { 380 WriteRawLittleEndian64(ref buffer, ref state, (ulong)value); 381 } 382 383 /// <summary> 384 /// Writes an sint32 value, without a tag, to the stream. 385 /// </summary> WriteSInt32(ref Span<byte> buffer, ref WriterInternalState state, int value)386 public static void WriteSInt32(ref Span<byte> buffer, ref WriterInternalState state, int value) 387 { 388 WriteRawVarint32(ref buffer, ref state, EncodeZigZag32(value)); 389 } 390 391 /// <summary> 392 /// Writes an sint64 value, without a tag, to the stream. 393 /// </summary> WriteSInt64(ref Span<byte> buffer, ref WriterInternalState state, long value)394 public static void WriteSInt64(ref Span<byte> buffer, ref WriterInternalState state, long value) 395 { 396 WriteRawVarint64(ref buffer, ref state, EncodeZigZag64(value)); 397 } 398 399 /// <summary> 400 /// Writes a length (in bytes) for length-delimited data. 401 /// </summary> 402 /// <remarks> 403 /// This method simply writes a rawint, but exists for clarity in calling code. 404 /// </remarks> WriteLength(ref Span<byte> buffer, ref WriterInternalState state, int length)405 public static void WriteLength(ref Span<byte> buffer, ref WriterInternalState state, int length) 406 { 407 WriteRawVarint32(ref buffer, ref state, (uint)length); 408 } 409 410 #endregion 411 412 #region Writing primitives 413 /// <summary> 414 /// Writes a 32 bit value as a varint. The fast route is taken when 415 /// there's enough buffer space left to whizz through without checking 416 /// for each byte; otherwise, we resort to calling WriteRawByte each time. 417 /// </summary> WriteRawVarint32(ref Span<byte> buffer, ref WriterInternalState state, uint value)418 public static void WriteRawVarint32(ref Span<byte> buffer, ref WriterInternalState state, uint value) 419 { 420 // Optimize for the common case of a single byte value 421 if (value < 128 && state.position < buffer.Length) 422 { 423 buffer[state.position++] = (byte)value; 424 return; 425 } 426 427 // Fast path when capacity is available 428 while (state.position < buffer.Length) 429 { 430 if (value > 127) 431 { 432 buffer[state.position++] = (byte)((value & 0x7F) | 0x80); 433 value >>= 7; 434 } 435 else 436 { 437 buffer[state.position++] = (byte)value; 438 return; 439 } 440 } 441 442 while (value > 127) 443 { 444 WriteRawByte(ref buffer, ref state, (byte)((value & 0x7F) | 0x80)); 445 value >>= 7; 446 } 447 448 WriteRawByte(ref buffer, ref state, (byte)value); 449 } 450 WriteRawVarint64(ref Span<byte> buffer, ref WriterInternalState state, ulong value)451 public static void WriteRawVarint64(ref Span<byte> buffer, ref WriterInternalState state, ulong value) 452 { 453 // Optimize for the common case of a single byte value 454 if (value < 128 && state.position < buffer.Length) 455 { 456 buffer[state.position++] = (byte)value; 457 return; 458 } 459 460 // Fast path when capacity is available 461 while (state.position < buffer.Length) 462 { 463 if (value > 127) 464 { 465 buffer[state.position++] = (byte)((value & 0x7F) | 0x80); 466 value >>= 7; 467 } 468 else 469 { 470 buffer[state.position++] = (byte)value; 471 return; 472 } 473 } 474 475 while (value > 127) 476 { 477 WriteRawByte(ref buffer, ref state, (byte)((value & 0x7F) | 0x80)); 478 value >>= 7; 479 } 480 481 WriteRawByte(ref buffer, ref state, (byte)value); 482 } 483 WriteRawLittleEndian32(ref Span<byte> buffer, ref WriterInternalState state, uint value)484 public static void WriteRawLittleEndian32(ref Span<byte> buffer, ref WriterInternalState state, uint value) 485 { 486 const int length = sizeof(uint); 487 if (state.position + length > buffer.Length) 488 { 489 WriteRawLittleEndian32SlowPath(ref buffer, ref state, value); 490 } 491 else 492 { 493 BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(state.position), value); 494 state.position += length; 495 } 496 } 497 498 [MethodImpl(MethodImplOptions.NoInlining)] WriteRawLittleEndian32SlowPath(ref Span<byte> buffer, ref WriterInternalState state, uint value)499 private static void WriteRawLittleEndian32SlowPath(ref Span<byte> buffer, ref WriterInternalState state, uint value) 500 { 501 WriteRawByte(ref buffer, ref state, (byte)value); 502 WriteRawByte(ref buffer, ref state, (byte)(value >> 8)); 503 WriteRawByte(ref buffer, ref state, (byte)(value >> 16)); 504 WriteRawByte(ref buffer, ref state, (byte)(value >> 24)); 505 } 506 WriteRawLittleEndian64(ref Span<byte> buffer, ref WriterInternalState state, ulong value)507 public static void WriteRawLittleEndian64(ref Span<byte> buffer, ref WriterInternalState state, ulong value) 508 { 509 const int length = sizeof(ulong); 510 if (state.position + length > buffer.Length) 511 { 512 WriteRawLittleEndian64SlowPath(ref buffer, ref state, value); 513 } 514 else 515 { 516 BinaryPrimitives.WriteUInt64LittleEndian(buffer.Slice(state.position), value); 517 state.position += length; 518 } 519 } 520 521 [MethodImpl(MethodImplOptions.NoInlining)] WriteRawLittleEndian64SlowPath(ref Span<byte> buffer, ref WriterInternalState state, ulong value)522 public static void WriteRawLittleEndian64SlowPath(ref Span<byte> buffer, ref WriterInternalState state, ulong value) 523 { 524 WriteRawByte(ref buffer, ref state, (byte)value); 525 WriteRawByte(ref buffer, ref state, (byte)(value >> 8)); 526 WriteRawByte(ref buffer, ref state, (byte)(value >> 16)); 527 WriteRawByte(ref buffer, ref state, (byte)(value >> 24)); 528 WriteRawByte(ref buffer, ref state, (byte)(value >> 32)); 529 WriteRawByte(ref buffer, ref state, (byte)(value >> 40)); 530 WriteRawByte(ref buffer, ref state, (byte)(value >> 48)); 531 WriteRawByte(ref buffer, ref state, (byte)(value >> 56)); 532 } 533 WriteRawByte(ref Span<byte> buffer, ref WriterInternalState state, byte value)534 private static void WriteRawByte(ref Span<byte> buffer, ref WriterInternalState state, byte value) 535 { 536 if (state.position == buffer.Length) 537 { 538 WriteBufferHelper.RefreshBuffer(ref buffer, ref state); 539 } 540 541 buffer[state.position++] = value; 542 } 543 544 /// <summary> 545 /// Writes out an array of bytes. 546 /// </summary> WriteRawBytes(ref Span<byte> buffer, ref WriterInternalState state, byte[] value)547 public static void WriteRawBytes(ref Span<byte> buffer, ref WriterInternalState state, byte[] value) 548 { 549 WriteRawBytes(ref buffer, ref state, new ReadOnlySpan<byte>(value)); 550 } 551 552 /// <summary> 553 /// Writes out part of an array of bytes. 554 /// </summary> WriteRawBytes(ref Span<byte> buffer, ref WriterInternalState state, byte[] value, int offset, int length)555 public static void WriteRawBytes(ref Span<byte> buffer, ref WriterInternalState state, byte[] value, int offset, int length) 556 { 557 WriteRawBytes(ref buffer, ref state, new ReadOnlySpan<byte>(value, offset, length)); 558 } 559 560 /// <summary> 561 /// Writes out part of an array of bytes. 562 /// </summary> WriteRawBytes(ref Span<byte> buffer, ref WriterInternalState state, ReadOnlySpan<byte> value)563 public static void WriteRawBytes(ref Span<byte> buffer, ref WriterInternalState state, ReadOnlySpan<byte> value) 564 { 565 if (buffer.Length - state.position >= value.Length) 566 { 567 // We have room in the current buffer. 568 value.CopyTo(buffer.Slice(state.position, value.Length)); 569 state.position += value.Length; 570 } 571 else 572 { 573 // When writing to a CodedOutputStream backed by a Stream, we could avoid 574 // copying the data twice (first copying to the current buffer and 575 // and later writing from the current buffer to the underlying Stream) 576 // in some circumstances by writing the data directly to the underlying Stream. 577 // Current this is not being done to avoid specialcasing the code for 578 // CodedOutputStream vs IBufferWriter<byte>. 579 int bytesWritten = 0; 580 while (buffer.Length - state.position < value.Length - bytesWritten) 581 { 582 int length = buffer.Length - state.position; 583 value.Slice(bytesWritten, length).CopyTo(buffer.Slice(state.position, length)); 584 bytesWritten += length; 585 state.position += length; 586 WriteBufferHelper.RefreshBuffer(ref buffer, ref state); 587 } 588 589 // copy the remaining data 590 int remainderLength = value.Length - bytesWritten; 591 value.Slice(bytesWritten, remainderLength).CopyTo(buffer.Slice(state.position, remainderLength)); 592 state.position += remainderLength; 593 } 594 } 595 #endregion 596 597 #region Raw tag writing 598 /// <summary> 599 /// Encodes and writes a tag. 600 /// </summary> WriteTag(ref Span<byte> buffer, ref WriterInternalState state, int fieldNumber, WireFormat.WireType type)601 public static void WriteTag(ref Span<byte> buffer, ref WriterInternalState state, int fieldNumber, WireFormat.WireType type) 602 { 603 WriteRawVarint32(ref buffer, ref state, WireFormat.MakeTag(fieldNumber, type)); 604 } 605 606 /// <summary> 607 /// Writes an already-encoded tag. 608 /// </summary> WriteTag(ref Span<byte> buffer, ref WriterInternalState state, uint tag)609 public static void WriteTag(ref Span<byte> buffer, ref WriterInternalState state, uint tag) 610 { 611 WriteRawVarint32(ref buffer, ref state, tag); 612 } 613 614 /// <summary> 615 /// Writes the given single-byte tag directly to the stream. 616 /// </summary> WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1)617 public static void WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1) 618 { 619 WriteRawByte(ref buffer, ref state, b1); 620 } 621 622 /// <summary> 623 /// Writes the given two-byte tag directly to the stream. 624 /// </summary> WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2)625 public static void WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2) 626 { 627 if (state.position + 2 > buffer.Length) 628 { 629 WriteRawTagSlowPath(ref buffer, ref state, b1, b2); 630 } 631 else 632 { 633 buffer[state.position++] = b1; 634 buffer[state.position++] = b2; 635 } 636 } 637 638 [MethodImpl(MethodImplOptions.NoInlining)] WriteRawTagSlowPath(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2)639 private static void WriteRawTagSlowPath(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2) 640 { 641 WriteRawByte(ref buffer, ref state, b1); 642 WriteRawByte(ref buffer, ref state, b2); 643 } 644 645 /// <summary> 646 /// Writes the given three-byte tag directly to the stream. 647 /// </summary> WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3)648 public static void WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3) 649 { 650 if (state.position + 3 > buffer.Length) 651 { 652 WriteRawTagSlowPath(ref buffer, ref state, b1, b2, b3); 653 } 654 else 655 { 656 buffer[state.position++] = b1; 657 buffer[state.position++] = b2; 658 buffer[state.position++] = b3; 659 } 660 } 661 662 [MethodImpl(MethodImplOptions.NoInlining)] WriteRawTagSlowPath(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3)663 private static void WriteRawTagSlowPath(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3) 664 { 665 WriteRawByte(ref buffer, ref state, b1); 666 WriteRawByte(ref buffer, ref state, b2); 667 WriteRawByte(ref buffer, ref state, b3); 668 } 669 670 /// <summary> 671 /// Writes the given four-byte tag directly to the stream. 672 /// </summary> WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4)673 public static void WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4) 674 { 675 if (state.position + 4 > buffer.Length) 676 { 677 WriteRawTagSlowPath(ref buffer, ref state, b1, b2, b3, b4); 678 } 679 else 680 { 681 buffer[state.position++] = b1; 682 buffer[state.position++] = b2; 683 buffer[state.position++] = b3; 684 buffer[state.position++] = b4; 685 } 686 } 687 688 [MethodImpl(MethodImplOptions.NoInlining)] 689 WriteRawTagSlowPath(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4)690 private static void WriteRawTagSlowPath(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4) 691 { 692 WriteRawByte(ref buffer, ref state, b1); 693 WriteRawByte(ref buffer, ref state, b2); 694 WriteRawByte(ref buffer, ref state, b3); 695 WriteRawByte(ref buffer, ref state, b4); 696 } 697 698 /// <summary> 699 /// Writes the given five-byte tag directly to the stream. 700 /// </summary> WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4, byte b5)701 public static void WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4, byte b5) 702 { 703 if (state.position + 5 > buffer.Length) 704 { 705 WriteRawTagSlowPath(ref buffer, ref state, b1, b2, b3, b4, b5); 706 } 707 else 708 { 709 buffer[state.position++] = b1; 710 buffer[state.position++] = b2; 711 buffer[state.position++] = b3; 712 buffer[state.position++] = b4; 713 buffer[state.position++] = b5; 714 } 715 } 716 717 [MethodImpl(MethodImplOptions.NoInlining)] WriteRawTagSlowPath(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4, byte b5)718 private static void WriteRawTagSlowPath(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4, byte b5) 719 { 720 WriteRawByte(ref buffer, ref state, b1); 721 WriteRawByte(ref buffer, ref state, b2); 722 WriteRawByte(ref buffer, ref state, b3); 723 WriteRawByte(ref buffer, ref state, b4); 724 WriteRawByte(ref buffer, ref state, b5); 725 } 726 #endregion 727 728 /// <summary> 729 /// Encode a 32-bit value with ZigZag encoding. 730 /// </summary> 731 /// <remarks> 732 /// ZigZag encodes signed integers into values that can be efficiently 733 /// encoded with varint. (Otherwise, negative values must be 734 /// sign-extended to 64 bits to be varint encoded, thus always taking 735 /// 10 bytes on the wire.) 736 /// </remarks> EncodeZigZag32(int n)737 public static uint EncodeZigZag32(int n) 738 { 739 // Note: the right-shift must be arithmetic 740 return (uint)((n << 1) ^ (n >> 31)); 741 } 742 743 /// <summary> 744 /// Encode a 64-bit value with ZigZag encoding. 745 /// </summary> 746 /// <remarks> 747 /// ZigZag encodes signed integers into values that can be efficiently 748 /// encoded with varint. (Otherwise, negative values must be 749 /// sign-extended to 64 bits to be varint encoded, thus always taking 750 /// 10 bytes on the wire.) 751 /// </remarks> EncodeZigZag64(long n)752 public static ulong EncodeZigZag64(long n) 753 { 754 return (ulong)((n << 1) ^ (n >> 63)); 755 } 756 } 757 }