1 #region Copyright notice and license 2 // Protocol Buffers - Google's data interchange format 3 // Copyright 2015 Google Inc. All rights reserved. 4 // 5 // Use of this source code is governed by a BSD-style 6 // license that can be found in the LICENSE file or at 7 // https://developers.google.com/open-source/licenses/bsd 8 #endregion 9 10 using Google.Protobuf.Collections; 11 using Google.Protobuf.WellKnownTypes; 12 using System; 13 using System.Collections.Generic; 14 using System.Security; 15 16 namespace Google.Protobuf 17 { 18 /// <summary> 19 /// Factory methods for <see cref="FieldCodec{T}"/>. 20 /// </summary> 21 public static class FieldCodec 22 { 23 // TODO: Avoid the "dual hit" of lambda expressions: create open delegates instead. (At least test...) 24 25 /// <summary> 26 /// Retrieves a codec suitable for a string field with the given tag. 27 /// </summary> 28 /// <param name="tag">The tag.</param> 29 /// <returns>A codec for the given tag.</returns> ForString(uint tag)30 public static FieldCodec<string> ForString(uint tag) => ForString(tag, ""); 31 32 /// <summary> 33 /// Retrieves a codec suitable for a bytes field with the given tag. 34 /// </summary> 35 /// <param name="tag">The tag.</param> 36 /// <returns>A codec for the given tag.</returns> ForBytes(uint tag)37 public static FieldCodec<ByteString> ForBytes(uint tag) => ForBytes(tag, ByteString.Empty); 38 39 /// <summary> 40 /// Retrieves a codec suitable for a bool field with the given tag. 41 /// </summary> 42 /// <param name="tag">The tag.</param> 43 /// <returns>A codec for the given tag.</returns> ForBool(uint tag)44 public static FieldCodec<bool> ForBool(uint tag) => ForBool(tag, false); 45 46 /// <summary> 47 /// Retrieves a codec suitable for an int32 field with the given tag. 48 /// </summary> 49 /// <param name="tag">The tag.</param> 50 /// <returns>A codec for the given tag.</returns> ForInt32(uint tag)51 public static FieldCodec<int> ForInt32(uint tag) => ForInt32(tag, 0); 52 53 /// <summary> 54 /// Retrieves a codec suitable for an sint32 field with the given tag. 55 /// </summary> 56 /// <param name="tag">The tag.</param> 57 /// <returns>A codec for the given tag.</returns> ForSInt32(uint tag)58 public static FieldCodec<int> ForSInt32(uint tag) => ForSInt32(tag, 0); 59 60 /// <summary> 61 /// Retrieves a codec suitable for a fixed32 field with the given tag. 62 /// </summary> 63 /// <param name="tag">The tag.</param> 64 /// <returns>A codec for the given tag.</returns> ForFixed32(uint tag)65 public static FieldCodec<uint> ForFixed32(uint tag) => ForFixed32(tag, 0); 66 67 /// <summary> 68 /// Retrieves a codec suitable for an sfixed32 field with the given tag. 69 /// </summary> 70 /// <param name="tag">The tag.</param> 71 /// <returns>A codec for the given tag.</returns> ForSFixed32(uint tag)72 public static FieldCodec<int> ForSFixed32(uint tag) => ForSFixed32(tag, 0); 73 74 /// <summary> 75 /// Retrieves a codec suitable for a uint32 field with the given tag. 76 /// </summary> 77 /// <param name="tag">The tag.</param> 78 /// <returns>A codec for the given tag.</returns> ForUInt32(uint tag)79 public static FieldCodec<uint> ForUInt32(uint tag) => ForUInt32(tag, 0); 80 81 /// <summary> 82 /// Retrieves a codec suitable for an int64 field with the given tag. 83 /// </summary> 84 /// <param name="tag">The tag.</param> 85 /// <returns>A codec for the given tag.</returns> ForInt64(uint tag)86 public static FieldCodec<long> ForInt64(uint tag) => ForInt64(tag, 0); 87 88 /// <summary> 89 /// Retrieves a codec suitable for an sint64 field with the given tag. 90 /// </summary> 91 /// <param name="tag">The tag.</param> 92 /// <returns>A codec for the given tag.</returns> ForSInt64(uint tag)93 public static FieldCodec<long> ForSInt64(uint tag) => ForSInt64(tag, 0); 94 95 /// <summary> 96 /// Retrieves a codec suitable for a fixed64 field with the given tag. 97 /// </summary> 98 /// <param name="tag">The tag.</param> 99 /// <returns>A codec for the given tag.</returns> ForFixed64(uint tag)100 public static FieldCodec<ulong> ForFixed64(uint tag) => ForFixed64(tag, 0); 101 102 /// <summary> 103 /// Retrieves a codec suitable for an sfixed64 field with the given tag. 104 /// </summary> 105 /// <param name="tag">The tag.</param> 106 /// <returns>A codec for the given tag.</returns> ForSFixed64(uint tag)107 public static FieldCodec<long> ForSFixed64(uint tag) => ForSFixed64(tag, 0); 108 109 /// <summary> 110 /// Retrieves a codec suitable for a uint64 field with the given tag. 111 /// </summary> 112 /// <param name="tag">The tag.</param> 113 /// <returns>A codec for the given tag.</returns> ForUInt64(uint tag)114 public static FieldCodec<ulong> ForUInt64(uint tag) => ForUInt64(tag, 0); 115 116 /// <summary> 117 /// Retrieves a codec suitable for a float field with the given tag. 118 /// </summary> 119 /// <param name="tag">The tag.</param> 120 /// <returns>A codec for the given tag.</returns> ForFloat(uint tag)121 public static FieldCodec<float> ForFloat(uint tag) => ForFloat(tag, 0); 122 123 /// <summary> 124 /// Retrieves a codec suitable for a double field with the given tag. 125 /// </summary> 126 /// <param name="tag">The tag.</param> 127 /// <returns>A codec for the given tag.</returns> ForDouble(uint tag)128 public static FieldCodec<double> ForDouble(uint tag) => ForDouble(tag, 0); 129 130 // Enums are tricky. We can probably use expression trees to build these delegates automatically, 131 // but it's easy to generate the code for it. 132 133 /// <summary> 134 /// Retrieves a codec suitable for an enum field with the given tag. 135 /// </summary> 136 /// <param name="tag">The tag.</param> 137 /// <param name="toInt32">A conversion function from <see cref="Int32"/> to the enum type.</param> 138 /// <param name="fromInt32">A conversion function from the enum type to <see cref="Int32"/>.</param> 139 /// <returns>A codec for the given tag.</returns> ForEnum(uint tag, Func<T, int> toInt32, Func<int, T> fromInt32)140 public static FieldCodec<T> ForEnum<T>(uint tag, Func<T, int> toInt32, Func<int, T> fromInt32) => 141 ForEnum(tag, toInt32, fromInt32, default); 142 143 /// <summary> 144 /// Retrieves a codec suitable for a string field with the given tag. 145 /// </summary> 146 /// <param name="tag">The tag.</param> 147 /// <param name="defaultValue">The default value.</param> 148 /// <returns>A codec for the given tag.</returns> ForString(uint tag, string defaultValue)149 public static FieldCodec<string> ForString(uint tag, string defaultValue) 150 { 151 return new FieldCodec<string>((ref ParseContext ctx) => ctx.ReadString(), (ref WriteContext ctx, string value) => ctx.WriteString(value), CodedOutputStream.ComputeStringSize, tag, defaultValue); 152 } 153 154 /// <summary> 155 /// Retrieves a codec suitable for a bytes field with the given tag. 156 /// </summary> 157 /// <param name="tag">The tag.</param> 158 /// <param name="defaultValue">The default value.</param> 159 /// <returns>A codec for the given tag.</returns> ForBytes(uint tag, ByteString defaultValue)160 public static FieldCodec<ByteString> ForBytes(uint tag, ByteString defaultValue) 161 { 162 return new FieldCodec<ByteString>((ref ParseContext ctx) => ctx.ReadBytes(), (ref WriteContext ctx, ByteString value) => ctx.WriteBytes(value), CodedOutputStream.ComputeBytesSize, tag, defaultValue); 163 } 164 165 /// <summary> 166 /// Retrieves a codec suitable for a bool field with the given tag. 167 /// </summary> 168 /// <param name="tag">The tag.</param> 169 /// <param name="defaultValue">The default value.</param> 170 /// <returns>A codec for the given tag.</returns> ForBool(uint tag, bool defaultValue)171 public static FieldCodec<bool> ForBool(uint tag, bool defaultValue) 172 { 173 return new FieldCodec<bool>((ref ParseContext ctx) => ctx.ReadBool(), (ref WriteContext ctx, bool value) => ctx.WriteBool(value), CodedOutputStream.BoolSize, tag, defaultValue); 174 } 175 176 /// <summary> 177 /// Retrieves a codec suitable for an int32 field with the given tag. 178 /// </summary> 179 /// <param name="tag">The tag.</param> 180 /// <param name="defaultValue">The default value.</param> 181 /// <returns>A codec for the given tag.</returns> ForInt32(uint tag, int defaultValue)182 public static FieldCodec<int> ForInt32(uint tag, int defaultValue) 183 { 184 return new FieldCodec<int>((ref ParseContext ctx) => ctx.ReadInt32(), (ref WriteContext output, int value) => output.WriteInt32(value), CodedOutputStream.ComputeInt32Size, tag, defaultValue); 185 } 186 187 /// <summary> 188 /// Retrieves a codec suitable for an sint32 field with the given tag. 189 /// </summary> 190 /// <param name="tag">The tag.</param> 191 /// <param name="defaultValue">The default value.</param> 192 /// <returns>A codec for the given tag.</returns> ForSInt32(uint tag, int defaultValue)193 public static FieldCodec<int> ForSInt32(uint tag, int defaultValue) 194 { 195 return new FieldCodec<int>((ref ParseContext ctx) => ctx.ReadSInt32(), (ref WriteContext output, int value) => output.WriteSInt32(value), CodedOutputStream.ComputeSInt32Size, tag, defaultValue); 196 } 197 198 /// <summary> 199 /// Retrieves a codec suitable for a fixed32 field with the given tag. 200 /// </summary> 201 /// <param name="tag">The tag.</param> 202 /// <param name="defaultValue">The default value.</param> 203 /// <returns>A codec for the given tag.</returns> ForFixed32(uint tag, uint defaultValue)204 public static FieldCodec<uint> ForFixed32(uint tag, uint defaultValue) 205 { 206 return new FieldCodec<uint>((ref ParseContext ctx) => ctx.ReadFixed32(), (ref WriteContext output, uint value) => output.WriteFixed32(value), 4, tag, defaultValue); 207 } 208 209 /// <summary> 210 /// Retrieves a codec suitable for an sfixed32 field with the given tag. 211 /// </summary> 212 /// <param name="tag">The tag.</param> 213 /// <param name="defaultValue">The default value.</param> 214 /// <returns>A codec for the given tag.</returns> ForSFixed32(uint tag, int defaultValue)215 public static FieldCodec<int> ForSFixed32(uint tag, int defaultValue) 216 { 217 return new FieldCodec<int>((ref ParseContext ctx) => ctx.ReadSFixed32(), (ref WriteContext output, int value) => output.WriteSFixed32(value), 4, tag, defaultValue); 218 } 219 220 /// <summary> 221 /// Retrieves a codec suitable for a uint32 field with the given tag. 222 /// </summary> 223 /// <param name="tag">The tag.</param> 224 /// <param name="defaultValue">The default value.</param> 225 /// <returns>A codec for the given tag.</returns> ForUInt32(uint tag, uint defaultValue)226 public static FieldCodec<uint> ForUInt32(uint tag, uint defaultValue) 227 { 228 return new FieldCodec<uint>((ref ParseContext ctx) => ctx.ReadUInt32(), (ref WriteContext output, uint value) => output.WriteUInt32(value), CodedOutputStream.ComputeUInt32Size, tag, defaultValue); 229 } 230 231 /// <summary> 232 /// Retrieves a codec suitable for an int64 field with the given tag. 233 /// </summary> 234 /// <param name="tag">The tag.</param> 235 /// <param name="defaultValue">The default value.</param> 236 /// <returns>A codec for the given tag.</returns> ForInt64(uint tag, long defaultValue)237 public static FieldCodec<long> ForInt64(uint tag, long defaultValue) 238 { 239 return new FieldCodec<long>((ref ParseContext ctx) => ctx.ReadInt64(), (ref WriteContext output, long value) => output.WriteInt64(value), CodedOutputStream.ComputeInt64Size, tag, defaultValue); 240 } 241 242 /// <summary> 243 /// Retrieves a codec suitable for an sint64 field with the given tag. 244 /// </summary> 245 /// <param name="tag">The tag.</param> 246 /// <param name="defaultValue">The default value.</param> 247 /// <returns>A codec for the given tag.</returns> ForSInt64(uint tag, long defaultValue)248 public static FieldCodec<long> ForSInt64(uint tag, long defaultValue) 249 { 250 return new FieldCodec<long>((ref ParseContext ctx) => ctx.ReadSInt64(), (ref WriteContext output, long value) => output.WriteSInt64(value), CodedOutputStream.ComputeSInt64Size, tag, defaultValue); 251 } 252 253 /// <summary> 254 /// Retrieves a codec suitable for a fixed64 field with the given tag. 255 /// </summary> 256 /// <param name="tag">The tag.</param> 257 /// <param name="defaultValue">The default value.</param> 258 /// <returns>A codec for the given tag.</returns> ForFixed64(uint tag, ulong defaultValue)259 public static FieldCodec<ulong> ForFixed64(uint tag, ulong defaultValue) 260 { 261 return new FieldCodec<ulong>((ref ParseContext ctx) => ctx.ReadFixed64(), (ref WriteContext output, ulong value) => output.WriteFixed64(value), 8, tag, defaultValue); 262 } 263 264 /// <summary> 265 /// Retrieves a codec suitable for an sfixed64 field with the given tag. 266 /// </summary> 267 /// <param name="tag">The tag.</param> 268 /// <param name="defaultValue">The default value.</param> 269 /// <returns>A codec for the given tag.</returns> ForSFixed64(uint tag, long defaultValue)270 public static FieldCodec<long> ForSFixed64(uint tag, long defaultValue) 271 { 272 return new FieldCodec<long>((ref ParseContext ctx) => ctx.ReadSFixed64(), (ref WriteContext output, long value) => output.WriteSFixed64(value), 8, tag, defaultValue); 273 } 274 275 /// <summary> 276 /// Retrieves a codec suitable for a uint64 field with the given tag. 277 /// </summary> 278 /// <param name="tag">The tag.</param> 279 /// <param name="defaultValue">The default value.</param> 280 /// <returns>A codec for the given tag.</returns> ForUInt64(uint tag, ulong defaultValue)281 public static FieldCodec<ulong> ForUInt64(uint tag, ulong defaultValue) 282 { 283 return new FieldCodec<ulong>((ref ParseContext ctx) => ctx.ReadUInt64(), (ref WriteContext output, ulong value) => output.WriteUInt64(value), CodedOutputStream.ComputeUInt64Size, tag, defaultValue); 284 } 285 286 /// <summary> 287 /// Retrieves a codec suitable for a float field with the given tag. 288 /// </summary> 289 /// <param name="tag">The tag.</param> 290 /// <param name="defaultValue">The default value.</param> 291 /// <returns>A codec for the given tag.</returns> ForFloat(uint tag, float defaultValue)292 public static FieldCodec<float> ForFloat(uint tag, float defaultValue) 293 { 294 return new FieldCodec<float>((ref ParseContext ctx) => ctx.ReadFloat(), (ref WriteContext output, float value) => output.WriteFloat(value), CodedOutputStream.FloatSize, tag, defaultValue); 295 } 296 297 /// <summary> 298 /// Retrieves a codec suitable for a double field with the given tag. 299 /// </summary> 300 /// <param name="tag">The tag.</param> 301 /// <param name="defaultValue">The default value.</param> 302 /// <returns>A codec for the given tag.</returns> ForDouble(uint tag, double defaultValue)303 public static FieldCodec<double> ForDouble(uint tag, double defaultValue) 304 { 305 return new FieldCodec<double>((ref ParseContext ctx) => ctx.ReadDouble(), (ref WriteContext output, double value) => output.WriteDouble(value), CodedOutputStream.DoubleSize, tag, defaultValue); 306 } 307 308 // Enums are tricky. We can probably use expression trees to build these delegates automatically, 309 // but it's easy to generate the code for it. 310 311 /// <summary> 312 /// Retrieves a codec suitable for an enum field with the given tag. 313 /// </summary> 314 /// <param name="tag">The tag.</param> 315 /// <param name="toInt32">A conversion function from <see cref="Int32"/> to the enum type.</param> 316 /// <param name="fromInt32">A conversion function from the enum type to <see cref="Int32"/>.</param> 317 /// <param name="defaultValue">The default value.</param> 318 /// <returns>A codec for the given tag.</returns> ForEnum(uint tag, Func<T, int> toInt32, Func<int, T> fromInt32, T defaultValue)319 public static FieldCodec<T> ForEnum<T>(uint tag, Func<T, int> toInt32, Func<int, T> fromInt32, T defaultValue) 320 { 321 return new FieldCodec<T>((ref ParseContext ctx) => fromInt32( 322 ctx.ReadEnum()), 323 (ref WriteContext output, T value) => output.WriteEnum(toInt32(value)), 324 value => CodedOutputStream.ComputeEnumSize(toInt32(value)), tag, defaultValue); 325 } 326 327 /// <summary> 328 /// Retrieves a codec suitable for a message field with the given tag. 329 /// </summary> 330 /// <param name="tag">The tag.</param> 331 /// <param name="parser">A parser to use for the message type.</param> 332 /// <returns>A codec for the given tag.</returns> 333 public static FieldCodec<T> ForMessage<T>(uint tag, MessageParser<T> parser) where T : class, IMessage<T> 334 { 335 return new FieldCodec<T>( 336 (ref ParseContext ctx) => 337 { 338 T message = parser.CreateTemplate(); ctx.ReadMessage(message)339 ctx.ReadMessage(message); 340 return message; 341 }, 342 (ref WriteContext output, T value) => output.WriteMessage(value), 343 (ref ParseContext ctx, ref T v) => 344 { 345 if (v == null) 346 { 347 v = parser.CreateTemplate(); 348 } 349 350 ctx.ReadMessage(v); 351 }, 352 (ref T v, T v2) => 353 { 354 if (v2 == null) 355 { 356 return false; 357 } 358 else if (v == null) 359 { 360 v = v2.Clone(); 361 } 362 else 363 { 364 v.MergeFrom(v2); 365 } 366 return true; 367 }, 368 message => CodedOutputStream.ComputeMessageSize(message), tag); 369 } 370 371 /// <summary> 372 /// Retrieves a codec suitable for a group field with the given tag. 373 /// </summary> 374 /// <param name="startTag">The start group tag.</param> 375 /// <param name="endTag">The end group tag.</param> 376 /// <param name="parser">A parser to use for the group message type.</param> 377 /// <returns>A codec for given tag</returns> 378 public static FieldCodec<T> ForGroup<T>(uint startTag, uint endTag, MessageParser<T> parser) where T : class, IMessage<T> 379 { 380 return new FieldCodec<T>( 381 (ref ParseContext ctx) => 382 { 383 T message = parser.CreateTemplate(); ctx.ReadGroup(message)384 ctx.ReadGroup(message); 385 return message; 386 }, 387 (ref WriteContext output, T value) => output.WriteGroup(value), 388 (ref ParseContext ctx, ref T v) => 389 { 390 if (v == null) 391 { 392 v = parser.CreateTemplate(); 393 } 394 395 ctx.ReadGroup(v); 396 }, 397 (ref T v, T v2) => 398 { 399 if (v2 == null) 400 { 401 return v == null; 402 } 403 else if (v == null) 404 { 405 v = v2.Clone(); 406 } 407 else 408 { 409 v.MergeFrom(v2); 410 } 411 return true; 412 }, 413 message => CodedOutputStream.ComputeGroupSize(message), startTag, endTag); 414 } 415 416 /// <summary> 417 /// Creates a codec for a wrapper type of a class - which must be string or ByteString. 418 /// </summary> 419 public static FieldCodec<T> ForClassWrapper<T>(uint tag) where T : class 420 { 421 var nestedCodec = WrapperCodecs.GetCodec<T>(); 422 return new FieldCodec<T>( 423 (ref ParseContext ctx) => WrapperCodecs.Read<T>(ref ctx, nestedCodec), 424 (ref WriteContext output, T value) => WrapperCodecs.Write<T>(ref output, value, nestedCodec), 425 (ref ParseContext ctx, ref T v) => v = WrapperCodecs.Read<T>(ref ctx, nestedCodec), 426 (ref T v, T v2) => { v = v2; return v == null; }, 427 value => WrapperCodecs.CalculateSize<T>(value, nestedCodec), 428 tag, 0, 429 null); // Default value for the wrapper 430 } 431 432 /// <summary> 433 /// Creates a codec for a wrapper type of a struct - which must be Int32, Int64, UInt32, UInt64, 434 /// Bool, Single or Double. 435 /// </summary> 436 public static FieldCodec<T?> ForStructWrapper<T>(uint tag) where T : struct 437 { 438 var nestedCodec = WrapperCodecs.GetCodec<T>(); 439 return new FieldCodec<T?>( 440 WrapperCodecs.GetReader<T>(), 441 (ref WriteContext output, T? value) => WrapperCodecs.Write<T>(ref output, value.Value, nestedCodec), 442 (ref ParseContext ctx, ref T? v) => v = WrapperCodecs.Read<T>(ref ctx, nestedCodec), 443 (ref T? v, T? v2) => { if (v2.HasValue) { v = v2; } return v.HasValue; }, 444 value => value == null ? 0 : WrapperCodecs.CalculateSize<T>(value.Value, nestedCodec), 445 tag, 0, 446 null); // Default value for the wrapper 447 } 448 449 /// <summary> 450 /// Helper code to create codecs for wrapper types. 451 /// </summary> 452 /// <remarks> 453 /// Somewhat ugly with all the static methods, but the conversions involved to/from nullable types make it 454 /// slightly tricky to improve. So long as we keep the public API (ForClassWrapper, ForStructWrapper) in place, 455 /// we can refactor later if we come up with something cleaner. 456 /// </remarks> 457 private static class WrapperCodecs 458 { 459 private static readonly Dictionary<System.Type, object> Codecs = new Dictionary<System.Type, object> 460 { 461 { typeof(bool), ForBool(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Varint)) }, 462 { typeof(int), ForInt32(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Varint)) }, 463 { typeof(long), ForInt64(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Varint)) }, 464 { typeof(uint), ForUInt32(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Varint)) }, 465 { typeof(ulong), ForUInt64(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Varint)) }, 466 { typeof(float), ForFloat(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Fixed32)) }, 467 { typeof(double), ForDouble(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.Fixed64)) }, 468 { typeof(string), ForString(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.LengthDelimited)) }, 469 { typeof(ByteString), ForBytes(WireFormat.MakeTag(WrappersReflection.WrapperValueFieldNumber, WireFormat.WireType.LengthDelimited)) } 470 }; 471 472 private static readonly Dictionary<System.Type, object> Readers = new Dictionary<System.Type, object> 473 { 474 // TODO: Provide more optimized readers. 475 { typeof(bool), (ValueReader<bool?>)ParsingPrimitivesWrappers.ReadBoolWrapper }, 476 { typeof(int), (ValueReader<int?>)ParsingPrimitivesWrappers.ReadInt32Wrapper }, 477 { typeof(long), (ValueReader<long?>)ParsingPrimitivesWrappers.ReadInt64Wrapper }, 478 { typeof(uint), (ValueReader<uint?>)ParsingPrimitivesWrappers.ReadUInt32Wrapper }, 479 { typeof(ulong), (ValueReader<ulong?>)ParsingPrimitivesWrappers.ReadUInt64Wrapper }, 480 { typeof(float), BitConverter.IsLittleEndian ? 481 (ValueReader<float?>)ParsingPrimitivesWrappers.ReadFloatWrapperLittleEndian : 482 (ValueReader<float?>)ParsingPrimitivesWrappers.ReadFloatWrapperSlow }, 483 { typeof(double), BitConverter.IsLittleEndian ? 484 (ValueReader<double?>)ParsingPrimitivesWrappers.ReadDoubleWrapperLittleEndian : 485 (ValueReader<double?>)ParsingPrimitivesWrappers.ReadDoubleWrapperSlow }, 486 // `string` and `ByteString` less performance-sensitive. Do not implement for now. 487 { typeof(string), null }, 488 { typeof(ByteString), null }, 489 }; 490 491 /// <summary> 492 /// Returns a field codec which effectively wraps a value of type T in a message. 493 /// 494 /// </summary> 495 internal static FieldCodec<T> GetCodec<T>() 496 { 497 if (!Codecs.TryGetValue(typeof(T), out object value)) 498 { 499 throw new InvalidOperationException("Invalid type argument requested for wrapper codec: " + typeof(T)); 500 } 501 return (FieldCodec<T>) value; 502 } 503 504 internal static ValueReader<T?> GetReader<T>() where T : struct 505 { 506 if (!Readers.TryGetValue(typeof(T), out object value)) 507 { 508 throw new InvalidOperationException("Invalid type argument requested for wrapper reader: " + typeof(T)); 509 } 510 if (value == null) 511 { 512 // Return default unoptimized reader for the wrapper type. 513 var nestedCoded = GetCodec<T>(); 514 return (ref ParseContext ctx) => Read<T>(ref ctx, nestedCoded); 515 } 516 // Return optimized read for the wrapper type. 517 return (ValueReader<T?>)value; 518 } 519 520 [SecuritySafeCritical] 521 internal static T Read<T>(ref ParseContext ctx, FieldCodec<T> codec) 522 { 523 int length = ctx.ReadLength(); 524 int oldLimit = SegmentedBufferHelper.PushLimit(ref ctx.state, length); 525 526 uint tag; 527 T value = codec.DefaultValue; 528 while ((tag = ctx.ReadTag()) != 0) 529 { 530 if (tag == codec.Tag) 531 { 532 value = codec.Read(ref ctx); 533 } 534 else 535 { 536 ParsingPrimitivesMessages.SkipLastField(ref ctx.buffer, ref ctx.state); 537 } 538 539 } 540 ParsingPrimitivesMessages.CheckReadEndOfStreamTag(ref ctx.state); 541 SegmentedBufferHelper.PopLimit(ref ctx.state, oldLimit); 542 543 return value; 544 } 545 546 internal static void Write<T>(ref WriteContext ctx, T value, FieldCodec<T> codec) 547 { 548 ctx.WriteLength(codec.CalculateSizeWithTag(value)); 549 codec.WriteTagAndValue(ref ctx, value); 550 } 551 552 internal static int CalculateSize<T>(T value, FieldCodec<T> codec) 553 { 554 int fieldLength = codec.CalculateSizeWithTag(value); 555 return CodedOutputStream.ComputeLengthSize(fieldLength) + fieldLength; 556 } 557 } 558 } 559 560 internal delegate TValue ValueReader<out TValue>(ref ParseContext ctx); 561 internal delegate void ValueWriter<T>(ref WriteContext ctx, T value); 562 563 /// <summary> 564 /// <para> 565 /// An encode/decode pair for a single field. This effectively encapsulates 566 /// all the information needed to read or write the field value from/to a coded 567 /// stream. 568 /// </para> 569 /// <para> 570 /// This class is public and has to be as it is used by generated code, but its public 571 /// API is very limited - just what the generated code needs to call directly. 572 /// </para> 573 /// </summary> 574 /// <remarks> 575 /// This never writes default values to the stream, and does not address "packedness" 576 /// in repeated fields itself, other than to know whether or not the field *should* be packed. 577 /// </remarks> 578 public sealed class FieldCodec<T> 579 { 580 private static readonly EqualityComparer<T> EqualityComparer = ProtobufEqualityComparers.GetEqualityComparer<T>(); 581 private static readonly T DefaultDefault; 582 // Only non-nullable value types support packing. This is the simplest way of detecting that. 583 private static readonly bool TypeSupportsPacking = default(T) != null; 584 585 /// <summary> 586 /// Merges an input stream into a value 587 /// </summary> 588 internal delegate void InputMerger(ref ParseContext ctx, ref T value); 589 590 /// <summary> 591 /// Merges a value into a reference to another value, returning a boolean if the value was set 592 /// </summary> 593 internal delegate bool ValuesMerger(ref T value, T other); 594 595 static FieldCodec() 596 { 597 if (typeof(T) == typeof(string)) 598 { 599 DefaultDefault = (T)(object)""; 600 } 601 else if (typeof(T) == typeof(ByteString)) 602 { 603 DefaultDefault = (T)(object)ByteString.Empty; 604 } 605 // Otherwise it's the default value of the CLR type 606 } 607 608 internal static bool IsPackedRepeatedField(uint tag) => 609 TypeSupportsPacking && WireFormat.GetTagWireType(tag) == WireFormat.WireType.LengthDelimited; 610 611 internal bool PackedRepeatedField { get; } 612 613 /// <summary> 614 /// Returns a delegate to write a value (unconditionally) to a coded output stream. 615 /// </summary> 616 internal ValueWriter<T> ValueWriter { get; } 617 618 /// <summary> 619 /// Returns the size calculator for just a value. 620 /// </summary> 621 internal Func<T, int> ValueSizeCalculator { get; } 622 623 /// <summary> 624 /// Returns a delegate to read a value from a coded input stream. It is assumed that 625 /// the stream is already positioned on the appropriate tag. 626 /// </summary> 627 internal ValueReader<T> ValueReader { get; } 628 629 /// <summary> 630 /// Returns a delegate to merge a value from a coded input stream. 631 /// It is assumed that the stream is already positioned on the appropriate tag 632 /// </summary> 633 internal InputMerger ValueMerger { get; } 634 635 /// <summary> 636 /// Returns a delegate to merge two values together. 637 /// </summary> 638 internal ValuesMerger FieldMerger { get; } 639 640 /// <summary> 641 /// Returns the fixed size for an entry, or 0 if sizes vary. 642 /// </summary> 643 internal int FixedSize { get; } 644 645 /// <summary> 646 /// Gets the tag of the codec. 647 /// </summary> 648 /// <value> 649 /// The tag of the codec. 650 /// </value> 651 internal uint Tag { get; } 652 653 /// <summary> 654 /// Gets the end tag of the codec or 0 if there is no end tag 655 /// </summary> 656 /// <value> 657 /// The end tag of the codec. 658 /// </value> 659 internal uint EndTag { get; } 660 661 /// <summary> 662 /// Default value for this codec. Usually the same for every instance of the same type, but 663 /// for string/ByteString wrapper fields the codec's default value is null, whereas for 664 /// other string/ByteString fields it's "" or ByteString.Empty. 665 /// </summary> 666 /// <value> 667 /// The default value of the codec's type. 668 /// </value> 669 internal T DefaultValue { get; } 670 671 private readonly int tagSize; 672 673 internal FieldCodec( 674 ValueReader<T> reader, 675 ValueWriter<T> writer, 676 int fixedSize, 677 uint tag, 678 T defaultValue) : this(reader, writer, _ => fixedSize, tag, defaultValue) 679 { 680 FixedSize = fixedSize; 681 } 682 683 internal FieldCodec( 684 ValueReader<T> reader, 685 ValueWriter<T> writer, 686 Func<T, int> sizeCalculator, 687 uint tag, 688 T defaultValue) : this(reader, writer, (ref ParseContext ctx, ref T v) => v = reader(ref ctx), (ref T v, T v2) => { v = v2; return true; }, sizeCalculator, tag, 0, defaultValue) 689 { 690 } 691 692 internal FieldCodec( 693 ValueReader<T> reader, 694 ValueWriter<T> writer, 695 InputMerger inputMerger, 696 ValuesMerger valuesMerger, 697 Func<T, int> sizeCalculator, 698 uint tag, 699 uint endTag = 0) : this(reader, writer, inputMerger, valuesMerger, sizeCalculator, tag, endTag, DefaultDefault) 700 { 701 } 702 703 internal FieldCodec( 704 ValueReader<T> reader, 705 ValueWriter<T> writer, 706 InputMerger inputMerger, 707 ValuesMerger valuesMerger, 708 Func<T, int> sizeCalculator, 709 uint tag, 710 uint endTag, 711 T defaultValue) 712 { 713 ValueReader = reader; 714 ValueWriter = writer; 715 ValueMerger = inputMerger; 716 FieldMerger = valuesMerger; 717 ValueSizeCalculator = sizeCalculator; 718 FixedSize = 0; 719 Tag = tag; 720 EndTag = endTag; 721 DefaultValue = defaultValue; 722 tagSize = CodedOutputStream.ComputeRawVarint32Size(tag); 723 if (endTag != 0) 724 tagSize += CodedOutputStream.ComputeRawVarint32Size(endTag); 725 // Detect packed-ness once, so we can check for it within RepeatedField<T>. 726 PackedRepeatedField = IsPackedRepeatedField(tag); 727 } 728 729 /// <summary> 730 /// Write a tag and the given value, *if* the value is not the default. 731 /// </summary> 732 public void WriteTagAndValue(CodedOutputStream output, T value) 733 { 734 WriteContext.Initialize(output, out WriteContext ctx); 735 try 736 { 737 WriteTagAndValue(ref ctx, value); 738 } 739 finally 740 { 741 ctx.CopyStateTo(output); 742 } 743 744 745 //if (!IsDefault(value)) 746 //{ 747 // output.WriteTag(Tag); 748 // ValueWriter(output, value); 749 // if (EndTag != 0) 750 // { 751 // output.WriteTag(EndTag); 752 // } 753 //} 754 } 755 756 /// <summary> 757 /// Write a tag and the given value, *if* the value is not the default. 758 /// </summary> 759 public void WriteTagAndValue(ref WriteContext ctx, T value) 760 { 761 if (!IsDefault(value)) 762 { 763 ctx.WriteTag(Tag); 764 ValueWriter(ref ctx, value); 765 if (EndTag != 0) 766 { 767 ctx.WriteTag(EndTag); 768 } 769 } 770 } 771 772 /// <summary> 773 /// Reads a value of the codec type from the given <see cref="CodedInputStream"/>. 774 /// </summary> 775 /// <param name="input">The input stream to read from.</param> 776 /// <returns>The value read from the stream.</returns> 777 public T Read(CodedInputStream input) 778 { 779 ParseContext.Initialize(input, out ParseContext ctx); 780 try 781 { 782 return ValueReader(ref ctx); 783 } 784 finally 785 { 786 ctx.CopyStateTo(input); 787 } 788 } 789 790 /// <summary> 791 /// Reads a value of the codec type from the given <see cref="ParseContext"/>. 792 /// </summary> 793 /// <param name="ctx">The parse context to read from.</param> 794 /// <returns>The value read.</returns> 795 public T Read(ref ParseContext ctx) 796 { 797 return ValueReader(ref ctx); 798 } 799 800 /// <summary> 801 /// Calculates the size required to write the given value, with a tag, 802 /// if the value is not the default. 803 /// </summary> 804 public int CalculateSizeWithTag(T value) => IsDefault(value) ? 0 : ValueSizeCalculator(value) + tagSize; 805 806 /// <summary> 807 /// Calculates the size required to write the given value, with a tag, even 808 /// if the value is the default. 809 /// </summary> 810 internal int CalculateUnconditionalSizeWithTag(T value) => ValueSizeCalculator(value) + tagSize; 811 812 private bool IsDefault(T value) => EqualityComparer.Equals(value, DefaultValue); 813 } 814 } 815