FieldCodec.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. #region Copyright notice and license
  2. // Protocol Buffers - Google's data interchange format
  3. // Copyright 2015 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 Google.Protobuf.Collections;
  33. using Google.Protobuf.Compatibility;
  34. using Google.Protobuf.WellKnownTypes;
  35. using System;
  36. using System.Collections.Generic;
  37. namespace Google.Protobuf
  38. {
  39. /// <summary>
  40. /// Factory methods for <see cref="FieldCodec{T}"/>.
  41. /// </summary>
  42. public static class FieldCodec
  43. {
  44. // TODO: Avoid the "dual hit" of lambda expressions: create open delegates instead. (At least test...)
  45. /// <summary>
  46. /// Retrieves a codec suitable for a string field with the given tag.
  47. /// </summary>
  48. /// <param name="tag">The tag.</param>
  49. /// <returns>A codec for the given tag.</returns>
  50. public static FieldCodec<string> ForString(uint tag)
  51. {
  52. return new FieldCodec<string>(input => input.ReadString(), (output, value) => output.WriteString(value), CodedOutputStream.ComputeStringSize, tag);
  53. }
  54. /// <summary>
  55. /// Retrieves a codec suitable for a bytes field with the given tag.
  56. /// </summary>
  57. /// <param name="tag">The tag.</param>
  58. /// <returns>A codec for the given tag.</returns>
  59. public static FieldCodec<ByteString> ForBytes(uint tag)
  60. {
  61. return new FieldCodec<ByteString>(input => input.ReadBytes(), (output, value) => output.WriteBytes(value), CodedOutputStream.ComputeBytesSize, tag);
  62. }
  63. /// <summary>
  64. /// Retrieves a codec suitable for a bool field with the given tag.
  65. /// </summary>
  66. /// <param name="tag">The tag.</param>
  67. /// <returns>A codec for the given tag.</returns>
  68. public static FieldCodec<bool> ForBool(uint tag)
  69. {
  70. return new FieldCodec<bool>(input => input.ReadBool(), (output, value) => output.WriteBool(value), CodedOutputStream.ComputeBoolSize, tag);
  71. }
  72. /// <summary>
  73. /// Retrieves a codec suitable for an int32 field with the given tag.
  74. /// </summary>
  75. /// <param name="tag">The tag.</param>
  76. /// <returns>A codec for the given tag.</returns>
  77. public static FieldCodec<int> ForInt32(uint tag)
  78. {
  79. return new FieldCodec<int>(input => input.ReadInt32(), (output, value) => output.WriteInt32(value), CodedOutputStream.ComputeInt32Size, tag);
  80. }
  81. /// <summary>
  82. /// Retrieves a codec suitable for an sint32 field with the given tag.
  83. /// </summary>
  84. /// <param name="tag">The tag.</param>
  85. /// <returns>A codec for the given tag.</returns>
  86. public static FieldCodec<int> ForSInt32(uint tag)
  87. {
  88. return new FieldCodec<int>(input => input.ReadSInt32(), (output, value) => output.WriteSInt32(value), CodedOutputStream.ComputeSInt32Size, tag);
  89. }
  90. /// <summary>
  91. /// Retrieves a codec suitable for a fixed32 field with the given tag.
  92. /// </summary>
  93. /// <param name="tag">The tag.</param>
  94. /// <returns>A codec for the given tag.</returns>
  95. public static FieldCodec<uint> ForFixed32(uint tag)
  96. {
  97. return new FieldCodec<uint>(input => input.ReadFixed32(), (output, value) => output.WriteFixed32(value), 4, tag);
  98. }
  99. /// <summary>
  100. /// Retrieves a codec suitable for an sfixed32 field with the given tag.
  101. /// </summary>
  102. /// <param name="tag">The tag.</param>
  103. /// <returns>A codec for the given tag.</returns>
  104. public static FieldCodec<int> ForSFixed32(uint tag)
  105. {
  106. return new FieldCodec<int>(input => input.ReadSFixed32(), (output, value) => output.WriteSFixed32(value), 4, tag);
  107. }
  108. /// <summary>
  109. /// Retrieves a codec suitable for a uint32 field with the given tag.
  110. /// </summary>
  111. /// <param name="tag">The tag.</param>
  112. /// <returns>A codec for the given tag.</returns>
  113. public static FieldCodec<uint> ForUInt32(uint tag)
  114. {
  115. return new FieldCodec<uint>(input => input.ReadUInt32(), (output, value) => output.WriteUInt32(value), CodedOutputStream.ComputeUInt32Size, tag);
  116. }
  117. /// <summary>
  118. /// Retrieves a codec suitable for an int64 field with the given tag.
  119. /// </summary>
  120. /// <param name="tag">The tag.</param>
  121. /// <returns>A codec for the given tag.</returns>
  122. public static FieldCodec<long> ForInt64(uint tag)
  123. {
  124. return new FieldCodec<long>(input => input.ReadInt64(), (output, value) => output.WriteInt64(value), CodedOutputStream.ComputeInt64Size, tag);
  125. }
  126. /// <summary>
  127. /// Retrieves a codec suitable for an sint64 field with the given tag.
  128. /// </summary>
  129. /// <param name="tag">The tag.</param>
  130. /// <returns>A codec for the given tag.</returns>
  131. public static FieldCodec<long> ForSInt64(uint tag)
  132. {
  133. return new FieldCodec<long>(input => input.ReadSInt64(), (output, value) => output.WriteSInt64(value), CodedOutputStream.ComputeSInt64Size, tag);
  134. }
  135. /// <summary>
  136. /// Retrieves a codec suitable for a fixed64 field with the given tag.
  137. /// </summary>
  138. /// <param name="tag">The tag.</param>
  139. /// <returns>A codec for the given tag.</returns>
  140. public static FieldCodec<ulong> ForFixed64(uint tag)
  141. {
  142. return new FieldCodec<ulong>(input => input.ReadFixed64(), (output, value) => output.WriteFixed64(value), 8, tag);
  143. }
  144. /// <summary>
  145. /// Retrieves a codec suitable for an sfixed64 field with the given tag.
  146. /// </summary>
  147. /// <param name="tag">The tag.</param>
  148. /// <returns>A codec for the given tag.</returns>
  149. public static FieldCodec<long> ForSFixed64(uint tag)
  150. {
  151. return new FieldCodec<long>(input => input.ReadSFixed64(), (output, value) => output.WriteSFixed64(value), 8, tag);
  152. }
  153. /// <summary>
  154. /// Retrieves a codec suitable for a uint64 field with the given tag.
  155. /// </summary>
  156. /// <param name="tag">The tag.</param>
  157. /// <returns>A codec for the given tag.</returns>
  158. public static FieldCodec<ulong> ForUInt64(uint tag)
  159. {
  160. return new FieldCodec<ulong>(input => input.ReadUInt64(), (output, value) => output.WriteUInt64(value), CodedOutputStream.ComputeUInt64Size, tag);
  161. }
  162. /// <summary>
  163. /// Retrieves a codec suitable for a float field with the given tag.
  164. /// </summary>
  165. /// <param name="tag">The tag.</param>
  166. /// <returns>A codec for the given tag.</returns>
  167. public static FieldCodec<float> ForFloat(uint tag)
  168. {
  169. return new FieldCodec<float>(input => input.ReadFloat(), (output, value) => output.WriteFloat(value), CodedOutputStream.ComputeFloatSize, tag);
  170. }
  171. /// <summary>
  172. /// Retrieves a codec suitable for a double field with the given tag.
  173. /// </summary>
  174. /// <param name="tag">The tag.</param>
  175. /// <returns>A codec for the given tag.</returns>
  176. public static FieldCodec<double> ForDouble(uint tag)
  177. {
  178. return new FieldCodec<double>(input => input.ReadDouble(), (output, value) => output.WriteDouble(value), CodedOutputStream.ComputeDoubleSize, tag);
  179. }
  180. // Enums are tricky. We can probably use expression trees to build these delegates automatically,
  181. // but it's easy to generate the code for it.
  182. /// <summary>
  183. /// Retrieves a codec suitable for an enum field with the given tag.
  184. /// </summary>
  185. /// <param name="tag">The tag.</param>
  186. /// <param name="toInt32">A conversion function from <see cref="Int32"/> to the enum type.</param>
  187. /// <param name="fromInt32">A conversion function from the enum type to <see cref="Int32"/>.</param>
  188. /// <returns>A codec for the given tag.</returns>
  189. public static FieldCodec<T> ForEnum<T>(uint tag, Func<T, int> toInt32, Func<int, T> fromInt32)
  190. {
  191. return new FieldCodec<T>(input => fromInt32(
  192. input.ReadEnum()),
  193. (output, value) => output.WriteEnum(toInt32(value)),
  194. value => CodedOutputStream.ComputeEnumSize(toInt32(value)), tag);
  195. }
  196. /// <summary>
  197. /// Retrieves a codec suitable for a message field with the given tag.
  198. /// </summary>
  199. /// <param name="tag">The tag.</param>
  200. /// <param name="parser">A parser to use for the message type.</param>
  201. /// <returns>A codec for the given tag.</returns>
  202. public static FieldCodec<T> ForMessage<T>(uint tag, MessageParser<T> parser) where T : IMessage<T>
  203. {
  204. return new FieldCodec<T>(input => { T message = parser.CreateTemplate(); input.ReadMessage(message); return message; },
  205. (output, value) => output.WriteMessage(value), message => CodedOutputStream.ComputeMessageSize(message), tag);
  206. }
  207. /// <summary>
  208. /// Retrieves a codec suitable for a group field with the given tag.
  209. /// </summary>
  210. /// <param name="startTag">The start group tag.</param>
  211. /// <param name="endTag">The end group tag.</param>
  212. /// <param name="parser">A parser to use for the group message type.</param>
  213. /// <returns>A codec for given tag</returns>
  214. public static FieldCodec<T> ForGroup<T>(uint startTag, uint endTag, MessageParser<T> parser) where T : IMessage<T>
  215. {
  216. return new FieldCodec<T>(input => { T message = parser.CreateTemplate(); input.ReadGroup(message); return message; },
  217. (output, value) => output.WriteGroup(value), message => CodedOutputStream.ComputeGroupSize(message), startTag, endTag);
  218. }
  219. /// <summary>
  220. /// Creates a codec for a wrapper type of a class - which must be string or ByteString.
  221. /// </summary>
  222. public static FieldCodec<T> ForClassWrapper<T>(uint tag) where T : class
  223. {
  224. var nestedCodec = WrapperCodecs.GetCodec<T>();
  225. return new FieldCodec<T>(
  226. input => WrapperCodecs.Read<T>(input, nestedCodec),
  227. (output, value) => WrapperCodecs.Write<T>(output, value, nestedCodec),
  228. value => WrapperCodecs.CalculateSize<T>(value, nestedCodec),
  229. tag, 0,
  230. null); // Default value for the wrapper
  231. }
  232. /// <summary>
  233. /// Creates a codec for a wrapper type of a struct - which must be Int32, Int64, UInt32, UInt64,
  234. /// Bool, Single or Double.
  235. /// </summary>
  236. public static FieldCodec<T?> ForStructWrapper<T>(uint tag) where T : struct
  237. {
  238. var nestedCodec = WrapperCodecs.GetCodec<T>();
  239. return new FieldCodec<T?>(
  240. input => WrapperCodecs.Read<T>(input, nestedCodec),
  241. (output, value) => WrapperCodecs.Write<T>(output, value.Value, nestedCodec),
  242. value => value == null ? 0 : WrapperCodecs.CalculateSize<T>(value.Value, nestedCodec),
  243. tag, 0,
  244. null); // Default value for the wrapper
  245. }
  246. /// <summary>
  247. /// Helper code to create codecs for wrapper types.
  248. /// </summary>
  249. /// <remarks>
  250. /// Somewhat ugly with all the static methods, but the conversions involved to/from nullable types make it
  251. /// slightly tricky to improve. So long as we keep the public API (ForClassWrapper, ForStructWrapper) in place,
  252. /// we can refactor later if we come up with something cleaner.
  253. /// </remarks>
  254. private static class WrapperCodecs
  255. {
  256. private static readonly Dictionary<System.Type, object> Codecs = new Dictionary<System.Type, object>
  257. {
  258. { typeof(bool), ForBool(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Varint)) },
  259. { typeof(int), ForInt32(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Varint)) },
  260. { typeof(long), ForInt64(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Varint)) },
  261. { typeof(uint), ForUInt32(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Varint)) },
  262. { typeof(ulong), ForUInt64(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Varint)) },
  263. { typeof(float), ForFloat(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Fixed32)) },
  264. { typeof(double), ForDouble(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Fixed64)) },
  265. { typeof(string), ForString(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.LengthDelimited)) },
  266. { typeof(ByteString), ForBytes(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.LengthDelimited)) }
  267. };
  268. /// <summary>
  269. /// Returns a field codec which effectively wraps a value of type T in a message.
  270. ///
  271. /// </summary>
  272. internal static FieldCodec<T> GetCodec<T>()
  273. {
  274. object value;
  275. if (!Codecs.TryGetValue(typeof(T), out value))
  276. {
  277. throw new InvalidOperationException("Invalid type argument requested for wrapper codec: " + typeof(T));
  278. }
  279. return (FieldCodec<T>) value;
  280. }
  281. internal static T Read<T>(CodedInputStream input, FieldCodec<T> codec)
  282. {
  283. int length = input.ReadLength();
  284. int oldLimit = input.PushLimit(length);
  285. uint tag;
  286. T value = codec.DefaultValue;
  287. while ((tag = input.ReadTag()) != 0)
  288. {
  289. if (tag == codec.Tag)
  290. {
  291. value = codec.Read(input);
  292. }
  293. else
  294. {
  295. input.SkipLastField();
  296. }
  297. }
  298. input.CheckReadEndOfStreamTag();
  299. input.PopLimit(oldLimit);
  300. return value;
  301. }
  302. internal static void Write<T>(CodedOutputStream output, T value, FieldCodec<T> codec)
  303. {
  304. output.WriteLength(codec.CalculateSizeWithTag(value));
  305. codec.WriteTagAndValue(output, value);
  306. }
  307. internal static int CalculateSize<T>(T value, FieldCodec<T> codec)
  308. {
  309. int fieldLength = codec.CalculateSizeWithTag(value);
  310. return CodedOutputStream.ComputeLengthSize(fieldLength) + fieldLength;
  311. }
  312. }
  313. }
  314. /// <summary>
  315. /// <para>
  316. /// An encode/decode pair for a single field. This effectively encapsulates
  317. /// all the information needed to read or write the field value from/to a coded
  318. /// stream.
  319. /// </para>
  320. /// <para>
  321. /// This class is public and has to be as it is used by generated code, but its public
  322. /// API is very limited - just what the generated code needs to call directly.
  323. /// </para>
  324. /// </summary>
  325. /// <remarks>
  326. /// This never writes default values to the stream, and does not address "packedness"
  327. /// in repeated fields itself, other than to know whether or not the field *should* be packed.
  328. /// </remarks>
  329. public sealed class FieldCodec<T>
  330. {
  331. private static readonly EqualityComparer<T> EqualityComparer = ProtobufEqualityComparers.GetEqualityComparer<T>();
  332. private static readonly T DefaultDefault;
  333. // Only non-nullable value types support packing. This is the simplest way of detecting that.
  334. private static readonly bool TypeSupportsPacking = default(T) != null;
  335. static FieldCodec()
  336. {
  337. if (typeof(T) == typeof(string))
  338. {
  339. DefaultDefault = (T)(object)"";
  340. }
  341. else if (typeof(T) == typeof(ByteString))
  342. {
  343. DefaultDefault = (T)(object)ByteString.Empty;
  344. }
  345. // Otherwise it's the default value of the CLR type
  346. }
  347. internal static bool IsPackedRepeatedField(uint tag) =>
  348. TypeSupportsPacking && WireFormat.GetTagWireType(tag) == WireFormat.WireType.LengthDelimited;
  349. internal bool PackedRepeatedField { get; }
  350. /// <summary>
  351. /// Returns a delegate to write a value (unconditionally) to a coded output stream.
  352. /// </summary>
  353. internal Action<CodedOutputStream, T> ValueWriter { get; }
  354. /// <summary>
  355. /// Returns the size calculator for just a value.
  356. /// </summary>
  357. internal Func<T, int> ValueSizeCalculator { get; }
  358. /// <summary>
  359. /// Returns a delegate to read a value from a coded input stream. It is assumed that
  360. /// the stream is already positioned on the appropriate tag.
  361. /// </summary>
  362. internal Func<CodedInputStream, T> ValueReader { get; }
  363. /// <summary>
  364. /// Returns the fixed size for an entry, or 0 if sizes vary.
  365. /// </summary>
  366. internal int FixedSize { get; }
  367. /// <summary>
  368. /// Gets the tag of the codec.
  369. /// </summary>
  370. /// <value>
  371. /// The tag of the codec.
  372. /// </value>
  373. internal uint Tag { get; }
  374. /// <summary>
  375. /// Gets the end tag of the codec or 0 if there is no end tag
  376. /// </summary>
  377. /// <value>
  378. /// The end tag of the codec.
  379. /// </value>
  380. internal uint EndTag { get; }
  381. /// <summary>
  382. /// Default value for this codec. Usually the same for every instance of the same type, but
  383. /// for string/ByteString wrapper fields the codec's default value is null, whereas for
  384. /// other string/ByteString fields it's "" or ByteString.Empty.
  385. /// </summary>
  386. /// <value>
  387. /// The default value of the codec's type.
  388. /// </value>
  389. internal T DefaultValue { get; }
  390. private readonly int tagSize;
  391. internal FieldCodec(
  392. Func<CodedInputStream, T> reader,
  393. Action<CodedOutputStream, T> writer,
  394. int fixedSize,
  395. uint tag) : this(reader, writer, _ => fixedSize, tag)
  396. {
  397. FixedSize = fixedSize;
  398. }
  399. internal FieldCodec(
  400. Func<CodedInputStream, T> reader,
  401. Action<CodedOutputStream, T> writer,
  402. Func<T, int> sizeCalculator,
  403. uint tag,
  404. uint endTag = 0) : this(reader, writer, sizeCalculator, tag, endTag, DefaultDefault)
  405. {
  406. }
  407. internal FieldCodec(
  408. Func<CodedInputStream, T> reader,
  409. Action<CodedOutputStream, T> writer,
  410. Func<T, int> sizeCalculator,
  411. uint tag,
  412. uint endTag,
  413. T defaultValue)
  414. {
  415. ValueReader = reader;
  416. ValueWriter = writer;
  417. ValueSizeCalculator = sizeCalculator;
  418. FixedSize = 0;
  419. Tag = tag;
  420. DefaultValue = defaultValue;
  421. tagSize = CodedOutputStream.ComputeRawVarint32Size(tag);
  422. // Detect packed-ness once, so we can check for it within RepeatedField<T>.
  423. PackedRepeatedField = IsPackedRepeatedField(tag);
  424. }
  425. /// <summary>
  426. /// Write a tag and the given value, *if* the value is not the default.
  427. /// </summary>
  428. public void WriteTagAndValue(CodedOutputStream output, T value)
  429. {
  430. if (!IsDefault(value))
  431. {
  432. output.WriteTag(Tag);
  433. ValueWriter(output, value);
  434. if (EndTag != 0)
  435. {
  436. output.WriteTag(EndTag);
  437. }
  438. }
  439. }
  440. /// <summary>
  441. /// Reads a value of the codec type from the given <see cref="CodedInputStream"/>.
  442. /// </summary>
  443. /// <param name="input">The input stream to read from.</param>
  444. /// <returns>The value read from the stream.</returns>
  445. public T Read(CodedInputStream input) => ValueReader(input);
  446. /// <summary>
  447. /// Calculates the size required to write the given value, with a tag,
  448. /// if the value is not the default.
  449. /// </summary>
  450. public int CalculateSizeWithTag(T value) => IsDefault(value) ? 0 : ValueSizeCalculator(value) + tagSize;
  451. private bool IsDefault(T value) => EqualityComparer.Equals(value, DefaultValue);
  452. }
  453. }