WritingPrimitives.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  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. using System;
  33. using System.Runtime.CompilerServices;
  34. using System.Security;
  35. using System.Text;
  36. namespace Google.Protobuf
  37. {
  38. /// <summary>
  39. /// Primitives for encoding protobuf wire format.
  40. /// </summary>
  41. [SecuritySafeCritical]
  42. internal static class WritingPrimitives
  43. {
  44. // "Local" copy of Encoding.UTF8, for efficiency. (Yes, it makes a difference.)
  45. internal static readonly Encoding Utf8Encoding = Encoding.UTF8;
  46. #region Writing of values (not including tags)
  47. /// <summary>
  48. /// Writes a double field value, without a tag, to the stream.
  49. /// </summary>
  50. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  51. public static void WriteDouble(ref Span<byte> buffer, ref WriterInternalState state, double value)
  52. {
  53. WriteRawLittleEndian64(ref buffer, ref state, (ulong)BitConverter.DoubleToInt64Bits(value));
  54. }
  55. /// <summary>
  56. /// Writes a float field value, without a tag, to the stream.
  57. /// </summary>
  58. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  59. public static void WriteFloat(ref Span<byte> buffer, ref WriterInternalState state, float value)
  60. {
  61. // TODO: avoid allocating a byte array!!!
  62. byte[] rawBytes = BitConverter.GetBytes(value);
  63. if (!BitConverter.IsLittleEndian)
  64. {
  65. ByteArray.Reverse(rawBytes);
  66. }
  67. if (state.limit - state.position >= 4)
  68. {
  69. buffer[state.position++] = rawBytes[0];
  70. buffer[state.position++] = rawBytes[1];
  71. buffer[state.position++] = rawBytes[2];
  72. buffer[state.position++] = rawBytes[3];
  73. }
  74. else
  75. {
  76. WriteRawBytes(ref buffer, ref state, rawBytes, 0, 4);
  77. }
  78. }
  79. /// <summary>
  80. /// Writes a uint64 field value, without a tag, to the stream.
  81. /// </summary>
  82. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  83. public static void WriteUInt64(ref Span<byte> buffer, ref WriterInternalState state, ulong value)
  84. {
  85. WriteRawVarint64(ref buffer, ref state, value);
  86. }
  87. /// <summary>
  88. /// Writes an int64 field value, without a tag, to the stream.
  89. /// </summary>
  90. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  91. public static void WriteInt64(ref Span<byte> buffer, ref WriterInternalState state, long value)
  92. {
  93. WriteRawVarint64(ref buffer, ref state, (ulong)value);
  94. }
  95. /// <summary>
  96. /// Writes an int32 field value, without a tag, to the stream.
  97. /// </summary>
  98. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  99. public static void WriteInt32(ref Span<byte> buffer, ref WriterInternalState state, int value)
  100. {
  101. if (value >= 0)
  102. {
  103. WriteRawVarint32(ref buffer, ref state, (uint)value);
  104. }
  105. else
  106. {
  107. // Must sign-extend.
  108. WriteRawVarint64(ref buffer, ref state, (ulong)value);
  109. }
  110. }
  111. /// <summary>
  112. /// Writes a fixed64 field value, without a tag, to the stream.
  113. /// </summary>
  114. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  115. public static void WriteFixed64(ref Span<byte> buffer, ref WriterInternalState state, ulong value)
  116. {
  117. WriteRawLittleEndian64(ref buffer, ref state, value);
  118. }
  119. /// <summary>
  120. /// Writes a fixed32 field value, without a tag, to the stream.
  121. /// </summary>
  122. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  123. public static void WriteFixed32(ref Span<byte> buffer, ref WriterInternalState state, uint value)
  124. {
  125. WriteRawLittleEndian32(ref buffer, ref state, value);
  126. }
  127. /// <summary>
  128. /// Writes a bool field value, without a tag, to the stream.
  129. /// </summary>
  130. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  131. public static void WriteBool(ref Span<byte> buffer, ref WriterInternalState state, bool value)
  132. {
  133. WriteRawByte(ref buffer, ref state, value ? (byte)1 : (byte)0);
  134. }
  135. /// <summary>
  136. /// Writes a string field value, without a tag, to the stream.
  137. /// The data is length-prefixed.
  138. /// </summary>
  139. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  140. public static void WriteString(ref Span<byte> buffer, ref WriterInternalState state, string value)
  141. {
  142. // Optimise the case where we have enough space to write
  143. // the string directly to the buffer, which should be common.
  144. int length = Utf8Encoding.GetByteCount(value);
  145. WriteLength(ref buffer, ref state, length);
  146. if (state.limit - state.position >= length)
  147. {
  148. if (length == value.Length) // Must be all ASCII...
  149. {
  150. for (int i = 0; i < length; i++)
  151. {
  152. buffer[state.position + i] = (byte)value[i];
  153. }
  154. state.position += length;
  155. }
  156. else
  157. {
  158. // TODO: optimize this part!!!!
  159. byte[] bytes = Utf8Encoding.GetBytes(value);
  160. WriteRawBytes(ref buffer, ref state, bytes);
  161. // TODO: we need to write to a span...
  162. //Utf8Encoding.GetBytes(value, 0, value.Length, buffer, state.position);
  163. //state.position += length;
  164. }
  165. }
  166. else
  167. {
  168. // TODO: do this more efficiently
  169. byte[] bytes = Utf8Encoding.GetBytes(value);
  170. WriteRawBytes(ref buffer, ref state, bytes);
  171. }
  172. }
  173. /// <summary>
  174. /// Write a byte string, without a tag, to the stream.
  175. /// The data is length-prefixed.
  176. /// </summary>
  177. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  178. public static void WriteBytes(ref Span<byte> buffer, ref WriterInternalState state, ByteString value)
  179. {
  180. WriteLength(ref buffer, ref state, value.Length);
  181. WriteRawBytes(ref buffer, ref state, value.Span);
  182. }
  183. /// <summary>
  184. /// Writes a uint32 value, without a tag, to the stream.
  185. /// </summary>
  186. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  187. public static void WriteUInt32(ref Span<byte> buffer, ref WriterInternalState state, uint value)
  188. {
  189. WriteRawVarint32(ref buffer, ref state, value);
  190. }
  191. /// <summary>
  192. /// Writes an enum value, without a tag, to the stream.
  193. /// </summary>
  194. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  195. public static void WriteEnum(ref Span<byte> buffer, ref WriterInternalState state, int value)
  196. {
  197. WriteInt32(ref buffer, ref state, value);
  198. }
  199. /// <summary>
  200. /// Writes an sfixed32 value, without a tag, to the stream.
  201. /// </summary>
  202. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  203. public static void WriteSFixed32(ref Span<byte> buffer, ref WriterInternalState state, int value)
  204. {
  205. WriteRawLittleEndian32(ref buffer, ref state, (uint)value);
  206. }
  207. /// <summary>
  208. /// Writes an sfixed64 value, without a tag, to the stream.
  209. /// </summary>
  210. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  211. public static void WriteSFixed64(ref Span<byte> buffer, ref WriterInternalState state, long value)
  212. {
  213. WriteRawLittleEndian64(ref buffer, ref state, (ulong)value);
  214. }
  215. /// <summary>
  216. /// Writes an sint32 value, without a tag, to the stream.
  217. /// </summary>
  218. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  219. public static void WriteSInt32(ref Span<byte> buffer, ref WriterInternalState state, int value)
  220. {
  221. WriteRawVarint32(ref buffer, ref state, EncodeZigZag32(value));
  222. }
  223. /// <summary>
  224. /// Writes an sint64 value, without a tag, to the stream.
  225. /// </summary>
  226. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  227. public static void WriteSInt64(ref Span<byte> buffer, ref WriterInternalState state, long value)
  228. {
  229. WriteRawVarint64(ref buffer, ref state, EncodeZigZag64(value));
  230. }
  231. /// <summary>
  232. /// Writes a length (in bytes) for length-delimited data.
  233. /// </summary>
  234. /// <remarks>
  235. /// This method simply writes a rawint, but exists for clarity in calling code.
  236. /// </remarks>
  237. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  238. public static void WriteLength(ref Span<byte> buffer, ref WriterInternalState state, int length)
  239. {
  240. WriteRawVarint32(ref buffer, ref state, (uint)length);
  241. }
  242. #endregion
  243. #region Writing primitives
  244. /// <summary>
  245. /// Writes a 32 bit value as a varint. The fast route is taken when
  246. /// there's enough buffer space left to whizz through without checking
  247. /// for each byte; otherwise, we resort to calling WriteRawByte each time.
  248. /// </summary>
  249. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  250. public static void WriteRawVarint32(ref Span<byte> buffer, ref WriterInternalState state, uint value)
  251. {
  252. // Optimize for the common case of a single byte value
  253. if (value < 128 && state.position < state.limit)
  254. {
  255. buffer[state.position++] = (byte)value;
  256. return;
  257. }
  258. while (value > 127 && state.position < state.limit)
  259. {
  260. buffer[state.position++] = (byte)((value & 0x7F) | 0x80);
  261. value >>= 7;
  262. }
  263. while (value > 127)
  264. {
  265. WriteRawByte(ref buffer, ref state, (byte)((value & 0x7F) | 0x80));
  266. value >>= 7;
  267. }
  268. if (state.position < state.limit)
  269. {
  270. buffer[state.position++] = (byte)value;
  271. }
  272. else
  273. {
  274. WriteRawByte(ref buffer, ref state, (byte)value);
  275. }
  276. }
  277. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  278. public static void WriteRawVarint64(ref Span<byte> buffer, ref WriterInternalState state, ulong value)
  279. {
  280. while (value > 127 && state.position < state.limit)
  281. {
  282. buffer[state.position++] = (byte)((value & 0x7F) | 0x80);
  283. value >>= 7;
  284. }
  285. while (value > 127)
  286. {
  287. WriteRawByte(ref buffer, ref state, (byte)((value & 0x7F) | 0x80));
  288. value >>= 7;
  289. }
  290. if (state.position < state.limit)
  291. {
  292. buffer[state.position++] = (byte)value;
  293. }
  294. else
  295. {
  296. WriteRawByte(ref buffer, ref state, (byte)value);
  297. }
  298. }
  299. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  300. public static void WriteRawLittleEndian32(ref Span<byte> buffer, ref WriterInternalState state, uint value)
  301. {
  302. if (state.position + 4 > state.limit)
  303. {
  304. WriteRawByte(ref buffer, ref state, (byte)value);
  305. WriteRawByte(ref buffer, ref state, (byte)(value >> 8));
  306. WriteRawByte(ref buffer, ref state, (byte)(value >> 16));
  307. WriteRawByte(ref buffer, ref state, (byte)(value >> 24));
  308. }
  309. else
  310. {
  311. buffer[state.position++] = ((byte)value);
  312. buffer[state.position++] = ((byte)(value >> 8));
  313. buffer[state.position++] = ((byte)(value >> 16));
  314. buffer[state.position++] = ((byte)(value >> 24));
  315. }
  316. }
  317. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  318. public static void WriteRawLittleEndian64(ref Span<byte> buffer, ref WriterInternalState state, ulong value)
  319. {
  320. if (state.position + 8 > state.limit)
  321. {
  322. WriteRawByte(ref buffer, ref state, (byte)value);
  323. WriteRawByte(ref buffer, ref state, (byte)(value >> 8));
  324. WriteRawByte(ref buffer, ref state, (byte)(value >> 16));
  325. WriteRawByte(ref buffer, ref state, (byte)(value >> 24));
  326. WriteRawByte(ref buffer, ref state, (byte)(value >> 32));
  327. WriteRawByte(ref buffer, ref state, (byte)(value >> 40));
  328. WriteRawByte(ref buffer, ref state, (byte)(value >> 48));
  329. WriteRawByte(ref buffer, ref state, (byte)(value >> 56));
  330. }
  331. else
  332. {
  333. buffer[state.position++] = ((byte)value);
  334. buffer[state.position++] = ((byte)(value >> 8));
  335. buffer[state.position++] = ((byte)(value >> 16));
  336. buffer[state.position++] = ((byte)(value >> 24));
  337. buffer[state.position++] = ((byte)(value >> 32));
  338. buffer[state.position++] = ((byte)(value >> 40));
  339. buffer[state.position++] = ((byte)(value >> 48));
  340. buffer[state.position++] = ((byte)(value >> 56));
  341. }
  342. }
  343. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  344. public static void WriteRawByte(ref Span<byte> buffer, ref WriterInternalState state, byte value)
  345. {
  346. if (state.position == state.limit)
  347. {
  348. WriteBufferHelper.RefreshBuffer(ref buffer, ref state);
  349. }
  350. buffer[state.position++] = value;
  351. }
  352. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  353. public static void WriteRawByte(ref Span<byte> buffer, ref WriterInternalState state, uint value)
  354. {
  355. WriteRawByte(ref buffer, ref state, (byte)value);
  356. }
  357. /// <summary>
  358. /// Writes out an array of bytes.
  359. /// </summary>
  360. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  361. public static void WriteRawBytes(ref Span<byte> buffer, ref WriterInternalState state, byte[] value)
  362. {
  363. WriteRawBytes(ref buffer, ref state, new ReadOnlySpan<byte>(value));
  364. }
  365. /// <summary>
  366. /// Writes out part of an array of bytes.
  367. /// </summary>
  368. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  369. public static void WriteRawBytes(ref Span<byte> buffer, ref WriterInternalState state, byte[] value, int offset, int length)
  370. {
  371. WriteRawBytes(ref buffer, ref state, new ReadOnlySpan<byte>(value, offset, length));
  372. }
  373. /// <summary>
  374. /// Writes out part of an array of bytes.
  375. /// </summary>
  376. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  377. public static void WriteRawBytes(ref Span<byte> buffer, ref WriterInternalState state, ReadOnlySpan<byte> value)
  378. {
  379. if (state.limit - state.position >= value.Length)
  380. {
  381. // We have room in the current buffer.
  382. value.CopyTo(buffer.Slice(state.position, value.Length));
  383. state.position += value.Length;
  384. }
  385. else
  386. {
  387. // TODO: save copies when using coded output stream and there's a lot of data to write...
  388. int bytesWritten = 0;
  389. while (state.limit - state.position < value.Length - bytesWritten)
  390. {
  391. int length = state.limit - state.position;
  392. value.Slice(bytesWritten, length).CopyTo(buffer.Slice(state.position, length));
  393. bytesWritten += length;
  394. state.position += length;
  395. WriteBufferHelper.RefreshBuffer(ref buffer, ref state);
  396. }
  397. // copy the remaining data
  398. int remainderLength = value.Length - bytesWritten;
  399. value.Slice(bytesWritten, remainderLength).CopyTo(buffer.Slice(state.position, remainderLength));
  400. state.position += remainderLength;
  401. }
  402. }
  403. #endregion
  404. #region Raw tag writing
  405. /// <summary>
  406. /// Encodes and writes a tag.
  407. /// </summary>
  408. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  409. public static void WriteTag(ref Span<byte> buffer, ref WriterInternalState state, int fieldNumber, WireFormat.WireType type)
  410. {
  411. WriteRawVarint32(ref buffer, ref state, WireFormat.MakeTag(fieldNumber, type));
  412. }
  413. /// <summary>
  414. /// Writes an already-encoded tag.
  415. /// </summary>
  416. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  417. public static void WriteTag(ref Span<byte> buffer, ref WriterInternalState state, uint tag)
  418. {
  419. WriteRawVarint32(ref buffer, ref state, tag);
  420. }
  421. /// <summary>
  422. /// Writes the given single-byte tag directly to the stream.
  423. /// </summary>
  424. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  425. public static void WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1)
  426. {
  427. WriteRawByte(ref buffer, ref state, b1);
  428. }
  429. /// <summary>
  430. /// Writes the given two-byte tag directly to the stream.
  431. /// </summary>
  432. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  433. public static void WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2)
  434. {
  435. WriteRawByte(ref buffer, ref state, b1);
  436. WriteRawByte(ref buffer, ref state, b2);
  437. }
  438. /// <summary>
  439. /// Writes the given three-byte tag directly to the stream.
  440. /// </summary>
  441. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  442. public static void WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3)
  443. {
  444. WriteRawByte(ref buffer, ref state, b1);
  445. WriteRawByte(ref buffer, ref state, b2);
  446. WriteRawByte(ref buffer, ref state, b3);
  447. }
  448. /// <summary>
  449. /// Writes the given four-byte tag directly to the stream.
  450. /// </summary>
  451. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  452. public static void WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4)
  453. {
  454. WriteRawByte(ref buffer, ref state, b1);
  455. WriteRawByte(ref buffer, ref state, b2);
  456. WriteRawByte(ref buffer, ref state, b3);
  457. WriteRawByte(ref buffer, ref state, b4);
  458. }
  459. /// <summary>
  460. /// Writes the given five-byte tag directly to the stream.
  461. /// </summary>
  462. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  463. public static void WriteRawTag(ref Span<byte> buffer, ref WriterInternalState state, byte b1, byte b2, byte b3, byte b4, byte b5)
  464. {
  465. WriteRawByte(ref buffer, ref state, b1);
  466. WriteRawByte(ref buffer, ref state, b2);
  467. WriteRawByte(ref buffer, ref state, b3);
  468. WriteRawByte(ref buffer, ref state, b4);
  469. WriteRawByte(ref buffer, ref state, b5);
  470. }
  471. #endregion
  472. /// <summary>
  473. /// Encode a 32-bit value with ZigZag encoding.
  474. /// </summary>
  475. /// <remarks>
  476. /// ZigZag encodes signed integers into values that can be efficiently
  477. /// encoded with varint. (Otherwise, negative values must be
  478. /// sign-extended to 64 bits to be varint encoded, thus always taking
  479. /// 10 bytes on the wire.)
  480. /// </remarks>
  481. public static uint EncodeZigZag32(int n)
  482. {
  483. // Note: the right-shift must be arithmetic
  484. return (uint)((n << 1) ^ (n >> 31));
  485. }
  486. /// <summary>
  487. /// Encode a 64-bit value with ZigZag encoding.
  488. /// </summary>
  489. /// <remarks>
  490. /// ZigZag encodes signed integers into values that can be efficiently
  491. /// encoded with varint. (Otherwise, negative values must be
  492. /// sign-extended to 64 bits to be varint encoded, thus always taking
  493. /// 10 bytes on the wire.)
  494. /// </remarks>
  495. public static ulong EncodeZigZag64(long n)
  496. {
  497. return (ulong)((n << 1) ^ (n >> 63));
  498. }
  499. }
  500. }