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