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 private static readonly bool TypeSupportsPacking = typeof(T).IsValueType() && Nullable.GetUnderlyingType(typeof(T)) == null; 351 FieldCodec()352 static FieldCodec() 353 { 354 if (typeof(T) == typeof(string)) 355 { 356 DefaultDefault = (T)(object)""; 357 } 358 else if (typeof(T) == typeof(ByteString)) 359 { 360 DefaultDefault = (T)(object)ByteString.Empty; 361 } 362 // Otherwise it's the default value of the CLR type 363 } 364 IsPackedRepeatedField(uint tag)365 internal static bool IsPackedRepeatedField(uint tag) => 366 TypeSupportsPacking && WireFormat.GetTagWireType(tag) == WireFormat.WireType.LengthDelimited; 367 368 internal bool PackedRepeatedField { get; } 369 370 /// <summary> 371 /// Returns a delegate to write a value (unconditionally) to a coded output stream. 372 /// </summary> 373 internal Action<CodedOutputStream, T> ValueWriter { get; } 374 375 /// <summary> 376 /// Returns the size calculator for just a value. 377 /// </summary> 378 internal Func<T, int> ValueSizeCalculator { get; } 379 380 /// <summary> 381 /// Returns a delegate to read a value from a coded input stream. It is assumed that 382 /// the stream is already positioned on the appropriate tag. 383 /// </summary> 384 internal Func<CodedInputStream, T> ValueReader { get; } 385 386 /// <summary> 387 /// Returns the fixed size for an entry, or 0 if sizes vary. 388 /// </summary> 389 internal int FixedSize { get; } 390 391 /// <summary> 392 /// Gets the tag of the codec. 393 /// </summary> 394 /// <value> 395 /// The tag of the codec. 396 /// </value> 397 internal uint Tag { get; } 398 399 /// <summary> 400 /// Default value for this codec. Usually the same for every instance of the same type, but 401 /// for string/ByteString wrapper fields the codec's default value is null, whereas for 402 /// other string/ByteString fields it's "" or ByteString.Empty. 403 /// </summary> 404 /// <value> 405 /// The default value of the codec's type. 406 /// </value> 407 internal T DefaultValue { get; } 408 409 private readonly int tagSize; 410 FieldCodec( Func<CodedInputStream, T> reader, Action<CodedOutputStream, T> writer, int fixedSize, uint tag)411 internal FieldCodec( 412 Func<CodedInputStream, T> reader, 413 Action<CodedOutputStream, T> writer, 414 int fixedSize, 415 uint tag) : this(reader, writer, _ => fixedSize, tag) 416 { 417 FixedSize = fixedSize; 418 } 419 FieldCodec( Func<CodedInputStream, T> reader, Action<CodedOutputStream, T> writer, Func<T, int> sizeCalculator, uint tag)420 internal FieldCodec( 421 Func<CodedInputStream, T> reader, 422 Action<CodedOutputStream, T> writer, 423 Func<T, int> sizeCalculator, 424 uint tag) : this(reader, writer, sizeCalculator, tag, DefaultDefault) 425 { 426 } 427 FieldCodec( Func<CodedInputStream, T> reader, Action<CodedOutputStream, T> writer, Func<T, int> sizeCalculator, uint tag, T defaultValue)428 internal FieldCodec( 429 Func<CodedInputStream, T> reader, 430 Action<CodedOutputStream, T> writer, 431 Func<T, int> sizeCalculator, 432 uint tag, 433 T defaultValue) 434 { 435 ValueReader = reader; 436 ValueWriter = writer; 437 ValueSizeCalculator = sizeCalculator; 438 FixedSize = 0; 439 Tag = tag; 440 DefaultValue = defaultValue; 441 tagSize = CodedOutputStream.ComputeRawVarint32Size(tag); 442 // Detect packed-ness once, so we can check for it within RepeatedField<T>. 443 PackedRepeatedField = IsPackedRepeatedField(tag); 444 } 445 446 /// <summary> 447 /// Write a tag and the given value, *if* the value is not the default. 448 /// </summary> WriteTagAndValue(CodedOutputStream output, T value)449 public void WriteTagAndValue(CodedOutputStream output, T value) 450 { 451 if (!IsDefault(value)) 452 { 453 output.WriteTag(Tag); 454 ValueWriter(output, value); 455 } 456 } 457 458 /// <summary> 459 /// Reads a value of the codec type from the given <see cref="CodedInputStream"/>. 460 /// </summary> 461 /// <param name="input">The input stream to read from.</param> 462 /// <returns>The value read from the stream.</returns> 463 public T Read(CodedInputStream input) => ValueReader(input); 464 465 /// <summary> 466 /// Calculates the size required to write the given value, with a tag, 467 /// if the value is not the default. 468 /// </summary> 469 public int CalculateSizeWithTag(T value) => IsDefault(value) ? 0 : ValueSizeCalculator(value) + tagSize; 470 471 private bool IsDefault(T value) => EqualityComparer<T>.Default.Equals(value, DefaultValue); 472 } 473 } 474