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