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.Runtime.CompilerServices; 36 using System.Runtime.InteropServices; 37 using System.Security; 38 using System.Text; 39 40 namespace Google.Protobuf 41 { 42 /// <summary> 43 /// Primitives for encoding protobuf wire format. 44 /// </summary> 45 [SecuritySafeCritical] 46 internal static class WritingPrimitives 47 { 48 // "Local" copy of Encoding.UTF8, for efficiency. (Yes, it makes a difference.) 49 internal static readonly Encoding Utf8Encoding = Encoding.UTF8; 50 51 #region Writing of values (not including tags) 52 53 /// <summary> 54 /// Writes a double field value, without a tag, to the stream. 55 /// </summary> WriteDouble(ref Span<byte> buffer, ref WriterInternalState state, double value)56 public static void WriteDouble(ref Span<byte> buffer, ref WriterInternalState state, double value) 57 { 58 WriteRawLittleEndian64(ref buffer, ref state, (ulong)BitConverter.DoubleToInt64Bits(value)); 59 } 60 61 /// <summary> 62 /// Writes a float field value, without a tag, to the stream. 63 /// </summary> WriteFloat(ref Span<byte> buffer, ref WriterInternalState state, float value)64 public static unsafe void WriteFloat(ref Span<byte> buffer, ref WriterInternalState state, float value) 65 { 66 const int length = sizeof(float); 67 if (buffer.Length - state.position >= length) 68 { 69 // if there's enough space in the buffer, write the float directly into the buffer 70 var floatSpan = buffer.Slice(state.position, length); 71 Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(floatSpan), value); 72 73 if (!BitConverter.IsLittleEndian) 74 { 75 floatSpan.Reverse(); 76 } 77 state.position += length; 78 } 79 else 80 { 81 WriteFloatSlowPath(ref buffer, ref state, value); 82 } 83 } 84 85 [MethodImpl(MethodImplOptions.NoInlining)] WriteFloatSlowPath(ref Span<byte> buffer, ref WriterInternalState state, float value)86 private static unsafe void WriteFloatSlowPath(ref Span<byte> buffer, ref WriterInternalState state, float value) 87 { 88 const int length = sizeof(float); 89 90 // TODO(jtattermusch): deduplicate the code. Populating the span is the same as for the fastpath. 91 Span<byte> floatSpan = stackalloc byte[length]; 92 Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(floatSpan), value); 93 if (!BitConverter.IsLittleEndian) 94 { 95 floatSpan.Reverse(); 96 } 97 98 WriteRawByte(ref buffer, ref state, floatSpan[0]); 99 WriteRawByte(ref buffer, ref state, floatSpan[1]); 100 WriteRawByte(ref buffer, ref state, floatSpan[2]); 101 WriteRawByte(ref buffer, ref state, floatSpan[3]); 102 } 103 104 /// <summary> 105 /// Writes a uint64 field value, without a tag, to the stream. 106 /// </summary> WriteUInt64(ref Span<byte> buffer, ref WriterInternalState state, ulong value)107 public static void WriteUInt64(ref Span<byte> buffer, ref WriterInternalState state, ulong value) 108 { 109 WriteRawVarint64(ref buffer, ref state, value); 110 } 111 112 /// <summary> 113 /// Writes an int64 field value, without a tag, to the stream. 114 /// </summary> WriteInt64(ref Span<byte> buffer, ref WriterInternalState state, long value)115 public static void WriteInt64(ref Span<byte> buffer, ref WriterInternalState state, long value) 116 { 117 WriteRawVarint64(ref buffer, ref state, (ulong)value); 118 } 119 120 /// <summary> 121 /// Writes an int32 field value, without a tag, to the stream. 122 /// </summary> WriteInt32(ref Span<byte> buffer, ref WriterInternalState state, int value)123 public static void WriteInt32(ref Span<byte> buffer, ref WriterInternalState state, int value) 124 { 125 if (value >= 0) 126 { 127 WriteRawVarint32(ref buffer, ref state, (uint)value); 128 } 129 else 130 { 131 // Must sign-extend. 132 WriteRawVarint64(ref buffer, ref state, (ulong)value); 133 } 134 } 135 136 /// <summary> 137 /// Writes a fixed64 field value, without a tag, to the stream. 138 /// </summary> WriteFixed64(ref Span<byte> buffer, ref WriterInternalState state, ulong value)139 public static void WriteFixed64(ref Span<byte> buffer, ref WriterInternalState state, ulong value) 140 { 141 WriteRawLittleEndian64(ref buffer, ref state, value); 142 } 143 144 /// <summary> 145 /// Writes a fixed32 field value, without a tag, to the stream. 146 /// </summary> WriteFixed32(ref Span<byte> buffer, ref WriterInternalState state, uint value)147 public static void WriteFixed32(ref Span<byte> buffer, ref WriterInternalState state, uint value) 148 { 149 WriteRawLittleEndian32(ref buffer, ref state, value); 150 } 151 152 /// <summary> 153 /// Writes a bool field value, without a tag, to the stream. 154 /// </summary> WriteBool(ref Span<byte> buffer, ref WriterInternalState state, bool value)155 public static void WriteBool(ref Span<byte> buffer, ref WriterInternalState state, bool value) 156 { 157 WriteRawByte(ref buffer, ref state, value ? (byte)1 : (byte)0); 158 } 159 160 /// <summary> 161 /// Writes a string field value, without a tag, to the stream. 162 /// The data is length-prefixed. 163 /// </summary> WriteString(ref Span<byte> buffer, ref WriterInternalState state, string value)164 public static void WriteString(ref Span<byte> buffer, ref WriterInternalState state, string value) 165 { 166 // Optimise the case where we have enough space to write 167 // the string directly to the buffer, which should be common. 168 int length = Utf8Encoding.GetByteCount(value); 169 WriteLength(ref buffer, ref state, length); 170 if (buffer.Length - state.position >= length) 171 { 172 if (length == value.Length) // Must be all ASCII... 173 { 174 for (int i = 0; i < length; i++) 175 { 176 buffer[state.position + i] = (byte)value[i]; 177 } 178 state.position += length; 179 } 180 else 181 { 182 #if NETSTANDARD1_1 183 // slowpath when Encoding.GetBytes(Char*, Int32, Byte*, Int32) is not available 184 byte[] bytes = Utf8Encoding.GetBytes(value); 185 WriteRawBytes(ref buffer, ref state, bytes); 186 #else 187 ReadOnlySpan<char> source = value.AsSpan(); 188 int bytesUsed; 189 unsafe 190 { 191 fixed (char* sourceChars = &MemoryMarshal.GetReference(source)) 192 fixed (byte* destinationBytes = &MemoryMarshal.GetReference(buffer.Slice(state.position))) 193 { 194 bytesUsed = Utf8Encoding.GetBytes(sourceChars, source.Length, destinationBytes, buffer.Length); 195 } 196 } 197 state.position += bytesUsed; 198 #endif 199 } 200 } 201 else 202 { 203 // Opportunity for future optimization: 204 // Large strings that don't fit into the current buffer segment 205 // can probably be optimized by using Utf8Encoding.GetEncoder() 206 // but more benchmarks would need to be added as evidence. 207 byte[] bytes = Utf8Encoding.GetBytes(value); 208 WriteRawBytes(ref buffer, ref state, bytes); 209 } 210 } 211 212 /// <summary> 213 /// Write a byte string, without a tag, to the stream. 214 /// The data is length-prefixed. 215 /// </summary> WriteBytes(ref Span<byte> buffer, ref WriterInternalState state, ByteString value)216 public static void WriteBytes(ref Span<byte> buffer, ref WriterInternalState state, ByteString value) 217 { 218 WriteLength(ref buffer, ref state, value.Length); 219 WriteRawBytes(ref buffer, ref state, value.Span); 220 } 221 222 /// <summary> 223 /// Writes a uint32 value, without a tag, to the stream. 224 /// </summary> WriteUInt32(ref Span<byte> buffer, ref WriterInternalState state, uint value)225 public static void WriteUInt32(ref Span<byte> buffer, ref WriterInternalState state, uint value) 226 { 227 WriteRawVarint32(ref buffer, ref state, value); 228 } 229 230 /// <summary> 231 /// Writes an enum value, without a tag, to the stream. 232 /// </summary> WriteEnum(ref Span<byte> buffer, ref WriterInternalState state, int value)233 public static void WriteEnum(ref Span<byte> buffer, ref WriterInternalState state, int value) 234 { 235 WriteInt32(ref buffer, ref state, value); 236 } 237 238 /// <summary> 239 /// Writes an sfixed32 value, without a tag, to the stream. 240 /// </summary> WriteSFixed32(ref Span<byte> buffer, ref WriterInternalState state, int value)241 public static void WriteSFixed32(ref Span<byte> buffer, ref WriterInternalState state, int value) 242 { 243 WriteRawLittleEndian32(ref buffer, ref state, (uint)value); 244 } 245 246 /// <summary> 247 /// Writes an sfixed64 value, without a tag, to the stream. 248 /// </summary> WriteSFixed64(ref Span<byte> buffer, ref WriterInternalState state, long value)249 public static void WriteSFixed64(ref Span<byte> buffer, ref WriterInternalState state, long value) 250 { 251 WriteRawLittleEndian64(ref buffer, ref state, (ulong)value); 252 } 253 254 /// <summary> 255 /// Writes an sint32 value, without a tag, to the stream. 256 /// </summary> WriteSInt32(ref Span<byte> buffer, ref WriterInternalState state, int value)257 public static void WriteSInt32(ref Span<byte> buffer, ref WriterInternalState state, int value) 258 { 259 WriteRawVarint32(ref buffer, ref state, EncodeZigZag32(value)); 260 } 261 262 /// <summary> 263 /// Writes an sint64 value, without a tag, to the stream. 264 /// </summary> WriteSInt64(ref Span<byte> buffer, ref WriterInternalState state, long value)265 public static void WriteSInt64(ref Span<byte> buffer, ref WriterInternalState state, long value) 266 { 267 WriteRawVarint64(ref buffer, ref state, EncodeZigZag64(value)); 268 } 269 270 /// <summary> 271 /// Writes a length (in bytes) for length-delimited data. 272 /// </summary> 273 /// <remarks> 274 /// This method simply writes a rawint, but exists for clarity in calling code. 275 /// </remarks> WriteLength(ref Span<byte> buffer, ref WriterInternalState state, int length)276 public static void WriteLength(ref Span<byte> buffer, ref WriterInternalState state, int length) 277 { 278 WriteRawVarint32(ref buffer, ref state, (uint)length); 279 } 280 281 #endregion 282 283 #region Writing primitives 284 /// <summary> 285 /// Writes a 32 bit value as a varint. The fast route is taken when 286 /// there's enough buffer space left to whizz through without checking 287 /// for each byte; otherwise, we resort to calling WriteRawByte each time. 288 /// </summary> WriteRawVarint32(ref Span<byte> buffer, ref WriterInternalState state, uint value)289 public static void WriteRawVarint32(ref Span<byte> buffer, ref WriterInternalState state, uint value) 290 { 291 // Optimize for the common case of a single byte value 292 if (value < 128 && state.position < buffer.Length) 293 { 294 buffer[state.position++] = (byte)value; 295 return; 296 } 297 298 // Fast path when capacity is available 299 while (state.position < buffer.Length) 300 { 301 if (value > 127) 302 { 303 buffer[state.position++] = (byte)((value & 0x7F) | 0x80); 304 value >>= 7; 305 } 306 else 307 { 308 buffer[state.position++] = (byte)value; 309 return; 310 } 311 } 312 313 while (value > 127) 314 { 315 WriteRawByte(ref buffer, ref state, (byte)((value & 0x7F) | 0x80)); 316 value >>= 7; 317 } 318 319 WriteRawByte(ref buffer, ref state, (byte)value); 320 } 321 WriteRawVarint64(ref Span<byte> buffer, ref WriterInternalState state, ulong value)322 public static void WriteRawVarint64(ref Span<byte> buffer, ref WriterInternalState state, ulong value) 323 { 324 // Optimize for the common case of a single byte value 325 if (value < 128 && state.position < buffer.Length) 326 { 327 buffer[state.position++] = (byte)value; 328 return; 329 } 330 331 // Fast path when capacity is available 332 while (state.position < buffer.Length) 333 { 334 if (value > 127) 335 { 336 buffer[state.position++] = (byte)((value & 0x7F) | 0x80); 337 value >>= 7; 338 } 339 else 340 { 341 buffer[state.position++] = (byte)value; 342 return; 343 } 344 } 345 346 while (value > 127) 347 { 348 WriteRawByte(ref buffer, ref state, (byte)((value & 0x7F) | 0x80)); 349 value >>= 7; 350 } 351 352 WriteRawByte(ref buffer, ref state, (byte)value); 353 } 354 WriteRawLittleEndian32(ref Span<byte> buffer, ref WriterInternalState state, uint value)355 public static void WriteRawLittleEndian32(ref Span<byte> buffer, ref WriterInternalState state, uint value) 356 { 357 const int length = sizeof(uint); 358 if (state.position + length > buffer.Length) 359 { 360 WriteRawLittleEndian32SlowPath(ref buffer, ref state, value); 361 } 362 else 363 { 364 BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(state.position), value); 365 state.position += length; 366 } 367 } 368 369 [MethodImpl(MethodImplOptions.NoInlining)] WriteRawLittleEndian32SlowPath(ref Span<byte> buffer, ref WriterInternalState state, uint value)370 private static void WriteRawLittleEndian32SlowPath(ref Span<byte> buffer, ref WriterInternalState state, uint value) 371 { 372 WriteRawByte(ref buffer, ref state, (byte)value); 373 WriteRawByte(ref buffer, ref state, (byte)(value >> 8)); 374 WriteRawByte(ref buffer, ref state, (byte)(value >> 16)); 375 WriteRawByte(ref buffer, ref state, (byte)(value >> 24)); 376 } 377 WriteRawLittleEndian64(ref Span<byte> buffer, ref WriterInternalState state, ulong value)378 public static void WriteRawLittleEndian64(ref Span<byte> buffer, ref WriterInternalState state, ulong value) 379 { 380 const int length = sizeof(ulong); 381 if (state.position + length > buffer.Length) 382 { 383 WriteRawLittleEndian64SlowPath(ref buffer, ref state, value); 384 } 385 else 386 { 387 BinaryPrimitives.WriteUInt64LittleEndian(buffer.Slice(state.position), value); 388 state.position += length; 389 } 390 } 391 392 [MethodImpl(MethodImplOptions.NoInlining)] WriteRawLittleEndian64SlowPath(ref Span<byte> buffer, ref WriterInternalState state, ulong value)393 public static void WriteRawLittleEndian64SlowPath(ref Span<byte> buffer, ref WriterInternalState state, ulong value) 394 { 395 WriteRawByte(ref buffer, ref state, (byte)value); 396 WriteRawByte(ref buffer, ref state, (byte)(value >> 8)); 397 WriteRawByte(ref buffer, ref state, (byte)(value >> 16)); 398 WriteRawByte(ref buffer, ref state, (byte)(value >> 24)); 399 WriteRawByte(ref buffer, ref state, (byte)(value >> 32)); 400 WriteRawByte(ref buffer, ref state, (byte)(value >> 40)); 401 WriteRawByte(ref buffer, ref state, (byte)(value >> 48)); 402 WriteRawByte(ref buffer, ref state, (byte)(value >> 56)); 403 } 404 WriteRawByte(ref Span<byte> buffer, ref WriterInternalState state, byte value)405 private static void WriteRawByte(ref Span<byte> buffer, ref WriterInternalState state, byte value) 406 { 407 if (state.position == buffer.Length) 408 { 409 WriteBufferHelper.RefreshBuffer(ref buffer, ref state); 410 } 411 412 buffer[state.position++] = value; 413 } 414 415 /// <summary> 416 /// Writes out an array of bytes. 417 /// </summary> WriteRawBytes(ref Span<byte> buffer, ref WriterInternalState state, byte[] value)418 public static void WriteRawBytes(ref Span<byte> buffer, ref WriterInternalState state, byte[] value) 419 { 420 WriteRawBytes(ref buffer, ref state, new ReadOnlySpan<byte>(value)); 421 } 422 423 /// <summary> 424 /// Writes out part of an array of bytes. 425 /// </summary> WriteRawBytes(ref Span<byte> buffer, ref WriterInternalState state, byte[] value, int offset, int length)426 public static void WriteRawBytes(ref Span<byte> buffer, ref WriterInternalState state, byte[] value, int offset, int length) 427 { 428 WriteRawBytes(ref buffer, ref state, new ReadOnlySpan<byte>(value, offset, length)); 429 } 430 431 /// <summary> 432 /// Writes out part of an array of bytes. 433 /// </summary> WriteRawBytes(ref Span<byte> buffer, ref WriterInternalState state, ReadOnlySpan<byte> value)434 public static void WriteRawBytes(ref Span<byte> buffer, ref WriterInternalState state, ReadOnlySpan<byte> value) 435 { 436 if (buffer.Length - state.position >= value.Length) 437 { 438 // We have room in the current buffer. 439 value.CopyTo(buffer.Slice(state.position, value.Length)); 440 state.position += value.Length; 441 } 442 else 443 { 444 // When writing to a CodedOutputStream backed by a Stream, we could avoid 445 // copying the data twice (first copying to the current buffer and 446 // and later writing from the current buffer to the underlying Stream) 447 // in some circumstances by writing the data directly to the underlying Stream. 448 // Current this is not being done to avoid specialcasing the code for 449 // CodedOutputStream vs IBufferWriter<byte>. 450 int bytesWritten = 0; 451 while (buffer.Length - state.position < value.Length - bytesWritten) 452 { 453 int length = buffer.Length - state.position; 454 value.Slice(bytesWritten, length).CopyTo(buffer.Slice(state.position, length)); 455 bytesWritten += length; 456 state.position += length; 457 WriteBufferHelper.RefreshBuffer(ref buffer, ref state); 458 } 459 460 // copy the remaining data 461 int remainderLength = value.Length - bytesWritten; 462 value.Slice(bytesWritten, remainderLength).CopyTo(buffer.Slice(state.position, remainderLength)); 463 state.position += remainderLength; 464 } 465 } 466 #endregion 467 468 #region Raw tag writing 469 /// <summary> 470 /// Encodes and writes a tag. 471 /// </summary> WriteTag(ref Span<byte> buffer, ref WriterInternalState state, int fieldNumber, WireFormat.WireType type)472 public static void WriteTag(ref Span<byte> buffer, ref WriterInternalState state, int fieldNumber, WireFormat.WireType type) 473 { 474 WriteRawVarint32(ref buffer, ref state, WireFormat.MakeTag(fieldNumber, type)); 475 } 476 477 /// <summary> 478 /// Writes an already-encoded tag. 479 /// </summary> WriteTag(ref Span<byte> buffer, ref WriterInternalState state, uint tag)480 public static void WriteTag(ref Span<byte> buffer, ref WriterInternalState state, uint tag) 481 { 482 WriteRawVarint32(ref buffer, ref state, tag); 483 } 484 485 /// <summary> 486 /// Writes the given single-byte tag directly to the stream. 487 /// </summary> WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1)488 public static void WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1) 489 { 490 WriteRawByte(ref buffer, ref state, b1); 491 } 492 493 /// <summary> 494 /// Writes the given two-byte tag directly to the stream. 495 /// </summary> WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2)496 public static void WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2) 497 { 498 if (state.position + 2 > buffer.Length) 499 { 500 WriteRawTagSlowPath(ref buffer, ref state, b1, b2); 501 } 502 else 503 { 504 buffer[state.position++] = b1; 505 buffer[state.position++] = b2; 506 } 507 } 508 509 [MethodImpl(MethodImplOptions.NoInlining)] WriteRawTagSlowPath(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2)510 private static void WriteRawTagSlowPath(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2) 511 { 512 WriteRawByte(ref buffer, ref state, b1); 513 WriteRawByte(ref buffer, ref state, b2); 514 } 515 516 /// <summary> 517 /// Writes the given three-byte tag directly to the stream. 518 /// </summary> WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3)519 public static void WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3) 520 { 521 if (state.position + 3 > buffer.Length) 522 { 523 WriteRawTagSlowPath(ref buffer, ref state, b1, b2, b3); 524 } 525 else 526 { 527 buffer[state.position++] = b1; 528 buffer[state.position++] = b2; 529 buffer[state.position++] = b3; 530 } 531 } 532 533 [MethodImpl(MethodImplOptions.NoInlining)] WriteRawTagSlowPath(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3)534 private static void WriteRawTagSlowPath(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3) 535 { 536 WriteRawByte(ref buffer, ref state, b1); 537 WriteRawByte(ref buffer, ref state, b2); 538 WriteRawByte(ref buffer, ref state, b3); 539 } 540 541 /// <summary> 542 /// Writes the given four-byte tag directly to the stream. 543 /// </summary> WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4)544 public static void WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4) 545 { 546 if (state.position + 4 > buffer.Length) 547 { 548 WriteRawTagSlowPath(ref buffer, ref state, b1, b2, b3, b4); 549 } 550 else 551 { 552 buffer[state.position++] = b1; 553 buffer[state.position++] = b2; 554 buffer[state.position++] = b3; 555 buffer[state.position++] = b4; 556 } 557 } 558 559 [MethodImpl(MethodImplOptions.NoInlining)] 560 WriteRawTagSlowPath(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4)561 private static void WriteRawTagSlowPath(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4) 562 { 563 WriteRawByte(ref buffer, ref state, b1); 564 WriteRawByte(ref buffer, ref state, b2); 565 WriteRawByte(ref buffer, ref state, b3); 566 WriteRawByte(ref buffer, ref state, b4); 567 } 568 569 /// <summary> 570 /// Writes the given five-byte tag directly to the stream. 571 /// </summary> WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4, byte b5)572 public static void WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4, byte b5) 573 { 574 if (state.position + 5 > buffer.Length) 575 { 576 WriteRawTagSlowPath(ref buffer, ref state, b1, b2, b3, b4, b5); 577 } 578 else 579 { 580 buffer[state.position++] = b1; 581 buffer[state.position++] = b2; 582 buffer[state.position++] = b3; 583 buffer[state.position++] = b4; 584 buffer[state.position++] = b5; 585 } 586 } 587 588 [MethodImpl(MethodImplOptions.NoInlining)] WriteRawTagSlowPath(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4, byte b5)589 private static void WriteRawTagSlowPath(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4, byte b5) 590 { 591 WriteRawByte(ref buffer, ref state, b1); 592 WriteRawByte(ref buffer, ref state, b2); 593 WriteRawByte(ref buffer, ref state, b3); 594 WriteRawByte(ref buffer, ref state, b4); 595 WriteRawByte(ref buffer, ref state, b5); 596 } 597 #endregion 598 599 /// <summary> 600 /// Encode a 32-bit value with ZigZag encoding. 601 /// </summary> 602 /// <remarks> 603 /// ZigZag encodes signed integers into values that can be efficiently 604 /// encoded with varint. (Otherwise, negative values must be 605 /// sign-extended to 64 bits to be varint encoded, thus always taking 606 /// 10 bytes on the wire.) 607 /// </remarks> EncodeZigZag32(int n)608 public static uint EncodeZigZag32(int n) 609 { 610 // Note: the right-shift must be arithmetic 611 return (uint)((n << 1) ^ (n >> 31)); 612 } 613 614 /// <summary> 615 /// Encode a 64-bit value with ZigZag encoding. 616 /// </summary> 617 /// <remarks> 618 /// ZigZag encodes signed integers into values that can be efficiently 619 /// encoded with varint. (Otherwise, negative values must be 620 /// sign-extended to 64 bits to be varint encoded, thus always taking 621 /// 10 bytes on the wire.) 622 /// </remarks> EncodeZigZag64(long n)623 public static ulong EncodeZigZag64(long n) 624 { 625 return (ulong)((n << 1) ^ (n >> 63)); 626 } 627 } 628 }