BclHelpers.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712
  1. using System;
  2. using System.Reflection;
  3. namespace ProtoBuf
  4. {
  5. internal enum TimeSpanScale
  6. {
  7. Days = 0,
  8. Hours = 1,
  9. Minutes = 2,
  10. Seconds = 3,
  11. Milliseconds = 4,
  12. Ticks = 5,
  13. MinMax = 15
  14. }
  15. /// <summary>
  16. /// Provides support for common .NET types that do not have a direct representation
  17. /// in protobuf, using the definitions from bcl.proto
  18. /// </summary>
  19. public static class BclHelpers
  20. {
  21. /// <summary>
  22. /// Creates a new instance of the specified type, bypassing the constructor.
  23. /// </summary>
  24. /// <param name="type">The type to create</param>
  25. /// <returns>The new instance</returns>
  26. /// <exception cref="NotSupportedException">If the platform does not support constructor-skipping</exception>
  27. public static object GetUninitializedObject(Type type)
  28. {
  29. #if COREFX
  30. object obj = TryGetUninitializedObjectWithFormatterServices(type);
  31. if (obj != null) return obj;
  32. #endif
  33. #if PLAT_BINARYFORMATTER && !(COREFX || PROFILE259)
  34. return System.Runtime.Serialization.FormatterServices.GetUninitializedObject(type);
  35. #else
  36. throw new NotSupportedException("Constructor-skipping is not supported on this platform");
  37. #endif
  38. }
  39. #if COREFX // this is inspired by DCS: https://github.com/dotnet/corefx/blob/c02d33b18398199f6acc17d375dab154e9a1df66/src/System.Private.DataContractSerialization/src/System/Runtime/Serialization/XmlFormatReaderGenerator.cs#L854-L894
  40. static Func<Type, object> getUninitializedObject;
  41. static internal object TryGetUninitializedObjectWithFormatterServices(Type type)
  42. {
  43. if (getUninitializedObject == null)
  44. {
  45. try {
  46. var formatterServiceType = typeof(string).GetTypeInfo().Assembly.GetType("System.Runtime.Serialization.FormatterServices");
  47. if (formatterServiceType == null)
  48. {
  49. // fallback for .Net Core 3.0
  50. var formatterAssembly = Assembly.Load(new AssemblyName("System.Runtime.Serialization.Formatters"));
  51. formatterServiceType = formatterAssembly.GetType("System.Runtime.Serialization.FormatterServices");
  52. }
  53. MethodInfo method = formatterServiceType?.GetMethod("GetUninitializedObject", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
  54. if (method != null)
  55. {
  56. getUninitializedObject = (Func<Type, object>)method.CreateDelegate(typeof(Func<Type, object>));
  57. }
  58. }
  59. catch { /* best efforts only */ }
  60. if(getUninitializedObject == null) getUninitializedObject = x => null;
  61. }
  62. return getUninitializedObject(type);
  63. }
  64. #endif
  65. const int FieldTimeSpanValue = 0x01, FieldTimeSpanScale = 0x02, FieldTimeSpanKind = 0x03;
  66. internal static readonly DateTime[] EpochOrigin = {
  67. new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified),
  68. new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc),
  69. new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Local)
  70. };
  71. /// <summary>
  72. /// The default value for dates that are following google.protobuf.Timestamp semantics
  73. /// </summary>
  74. private static readonly DateTime TimestampEpoch = EpochOrigin[(int)DateTimeKind.Utc];
  75. /// <summary>
  76. /// Writes a TimeSpan to a protobuf stream using protobuf-net's own representation, bcl.TimeSpan
  77. /// </summary>
  78. public static void WriteTimeSpan(TimeSpan timeSpan, ProtoWriter dest)
  79. {
  80. WriteTimeSpanImpl(timeSpan, dest, DateTimeKind.Unspecified);
  81. }
  82. private static void WriteTimeSpanImpl(TimeSpan timeSpan, ProtoWriter dest, DateTimeKind kind)
  83. {
  84. if (dest == null) throw new ArgumentNullException(nameof(dest));
  85. long value;
  86. switch (dest.WireType)
  87. {
  88. case WireType.String:
  89. case WireType.StartGroup:
  90. TimeSpanScale scale;
  91. value = timeSpan.Ticks;
  92. if (timeSpan == TimeSpan.MaxValue)
  93. {
  94. value = 1;
  95. scale = TimeSpanScale.MinMax;
  96. }
  97. else if (timeSpan == TimeSpan.MinValue)
  98. {
  99. value = -1;
  100. scale = TimeSpanScale.MinMax;
  101. }
  102. else if (value % TimeSpan.TicksPerDay == 0)
  103. {
  104. scale = TimeSpanScale.Days;
  105. value /= TimeSpan.TicksPerDay;
  106. }
  107. else if (value % TimeSpan.TicksPerHour == 0)
  108. {
  109. scale = TimeSpanScale.Hours;
  110. value /= TimeSpan.TicksPerHour;
  111. }
  112. else if (value % TimeSpan.TicksPerMinute == 0)
  113. {
  114. scale = TimeSpanScale.Minutes;
  115. value /= TimeSpan.TicksPerMinute;
  116. }
  117. else if (value % TimeSpan.TicksPerSecond == 0)
  118. {
  119. scale = TimeSpanScale.Seconds;
  120. value /= TimeSpan.TicksPerSecond;
  121. }
  122. else if (value % TimeSpan.TicksPerMillisecond == 0)
  123. {
  124. scale = TimeSpanScale.Milliseconds;
  125. value /= TimeSpan.TicksPerMillisecond;
  126. }
  127. else
  128. {
  129. scale = TimeSpanScale.Ticks;
  130. }
  131. SubItemToken token = ProtoWriter.StartSubItem(null, dest);
  132. if (value != 0)
  133. {
  134. ProtoWriter.WriteFieldHeader(FieldTimeSpanValue, WireType.SignedVariant, dest);
  135. ProtoWriter.WriteInt64(value, dest);
  136. }
  137. if (scale != TimeSpanScale.Days)
  138. {
  139. ProtoWriter.WriteFieldHeader(FieldTimeSpanScale, WireType.Variant, dest);
  140. ProtoWriter.WriteInt32((int)scale, dest);
  141. }
  142. if (kind != DateTimeKind.Unspecified)
  143. {
  144. ProtoWriter.WriteFieldHeader(FieldTimeSpanKind, WireType.Variant, dest);
  145. ProtoWriter.WriteInt32((int)kind, dest);
  146. }
  147. ProtoWriter.EndSubItem(token, dest);
  148. break;
  149. case WireType.Fixed64:
  150. ProtoWriter.WriteInt64(timeSpan.Ticks, dest);
  151. break;
  152. default:
  153. throw new ProtoException("Unexpected wire-type: " + dest.WireType.ToString());
  154. }
  155. }
  156. /// <summary>
  157. /// Parses a TimeSpan from a protobuf stream using protobuf-net's own representation, bcl.TimeSpan
  158. /// </summary>
  159. public static TimeSpan ReadTimeSpan(ProtoReader source)
  160. {
  161. long ticks = ReadTimeSpanTicks(source, out DateTimeKind kind);
  162. if (ticks == long.MinValue) return TimeSpan.MinValue;
  163. if (ticks == long.MaxValue) return TimeSpan.MaxValue;
  164. return TimeSpan.FromTicks(ticks);
  165. }
  166. /// <summary>
  167. /// Parses a TimeSpan from a protobuf stream using the standardized format, google.protobuf.Duration
  168. /// </summary>
  169. public static TimeSpan ReadDuration(ProtoReader source)
  170. {
  171. long seconds = 0;
  172. int nanos = 0;
  173. SubItemToken token = ProtoReader.StartSubItem(source);
  174. int fieldNumber;
  175. while ((fieldNumber = source.ReadFieldHeader()) > 0)
  176. {
  177. switch (fieldNumber)
  178. {
  179. case 1:
  180. seconds = source.ReadInt64();
  181. break;
  182. case 2:
  183. nanos = source.ReadInt32();
  184. break;
  185. default:
  186. source.SkipField();
  187. break;
  188. }
  189. }
  190. ProtoReader.EndSubItem(token, source);
  191. return FromDurationSeconds(seconds, nanos);
  192. }
  193. /// <summary>
  194. /// Writes a TimeSpan to a protobuf stream using the standardized format, google.protobuf.Duration
  195. /// </summary>
  196. public static void WriteDuration(TimeSpan value, ProtoWriter dest)
  197. {
  198. var seconds = ToDurationSeconds(value, out int nanos);
  199. WriteSecondsNanos(seconds, nanos, dest);
  200. }
  201. private static void WriteSecondsNanos(long seconds, int nanos, ProtoWriter dest)
  202. {
  203. SubItemToken token = ProtoWriter.StartSubItem(null, dest);
  204. if (seconds != 0)
  205. {
  206. ProtoWriter.WriteFieldHeader(1, WireType.Variant, dest);
  207. ProtoWriter.WriteInt64(seconds, dest);
  208. }
  209. if (nanos != 0)
  210. {
  211. ProtoWriter.WriteFieldHeader(2, WireType.Variant, dest);
  212. ProtoWriter.WriteInt32(nanos, dest);
  213. }
  214. ProtoWriter.EndSubItem(token, dest);
  215. }
  216. /// <summary>
  217. /// Parses a DateTime from a protobuf stream using the standardized format, google.protobuf.Timestamp
  218. /// </summary>
  219. public static DateTime ReadTimestamp(ProtoReader source)
  220. {
  221. // note: DateTime is only defined for just over 0000 to just below 10000;
  222. // TimeSpan has a range of +/- 10,675,199 days === 29k years;
  223. // so we can just use epoch time delta
  224. return TimestampEpoch + ReadDuration(source);
  225. }
  226. /// <summary>
  227. /// Writes a DateTime to a protobuf stream using the standardized format, google.protobuf.Timestamp
  228. /// </summary>
  229. public static void WriteTimestamp(DateTime value, ProtoWriter dest)
  230. {
  231. var seconds = ToDurationSeconds(value - TimestampEpoch, out int nanos);
  232. if (nanos < 0)
  233. { // from Timestamp.proto:
  234. // "Negative second values with fractions must still have
  235. // non -negative nanos values that count forward in time."
  236. seconds--;
  237. nanos += 1000000000;
  238. }
  239. WriteSecondsNanos(seconds, nanos, dest);
  240. }
  241. static TimeSpan FromDurationSeconds(long seconds, int nanos)
  242. {
  243. long ticks = checked((seconds * TimeSpan.TicksPerSecond)
  244. + (nanos * TimeSpan.TicksPerMillisecond) / 1000000);
  245. return TimeSpan.FromTicks(ticks);
  246. }
  247. static long ToDurationSeconds(TimeSpan value, out int nanos)
  248. {
  249. nanos = (int)(((value.Ticks % TimeSpan.TicksPerSecond) * 1000000)
  250. / TimeSpan.TicksPerMillisecond);
  251. return value.Ticks / TimeSpan.TicksPerSecond;
  252. }
  253. /// <summary>
  254. /// Parses a DateTime from a protobuf stream
  255. /// </summary>
  256. public static DateTime ReadDateTime(ProtoReader source)
  257. {
  258. long ticks = ReadTimeSpanTicks(source, out DateTimeKind kind);
  259. if (ticks == long.MinValue) return DateTime.MinValue;
  260. if (ticks == long.MaxValue) return DateTime.MaxValue;
  261. return EpochOrigin[(int)kind].AddTicks(ticks);
  262. }
  263. /// <summary>
  264. /// Writes a DateTime to a protobuf stream, excluding the <c>Kind</c>
  265. /// </summary>
  266. public static void WriteDateTime(DateTime value, ProtoWriter dest)
  267. {
  268. WriteDateTimeImpl(value, dest, false);
  269. }
  270. /// <summary>
  271. /// Writes a DateTime to a protobuf stream, including the <c>Kind</c>
  272. /// </summary>
  273. public static void WriteDateTimeWithKind(DateTime value, ProtoWriter dest)
  274. {
  275. WriteDateTimeImpl(value, dest, true);
  276. }
  277. private static void WriteDateTimeImpl(DateTime value, ProtoWriter dest, bool includeKind)
  278. {
  279. if (dest == null) throw new ArgumentNullException(nameof(dest));
  280. TimeSpan delta;
  281. switch (dest.WireType)
  282. {
  283. case WireType.StartGroup:
  284. case WireType.String:
  285. if (value == DateTime.MaxValue)
  286. {
  287. delta = TimeSpan.MaxValue;
  288. includeKind = false;
  289. }
  290. else if (value == DateTime.MinValue)
  291. {
  292. delta = TimeSpan.MinValue;
  293. includeKind = false;
  294. }
  295. else
  296. {
  297. delta = value - EpochOrigin[0];
  298. }
  299. break;
  300. default:
  301. delta = value - EpochOrigin[0];
  302. break;
  303. }
  304. WriteTimeSpanImpl(delta, dest, includeKind ? value.Kind : DateTimeKind.Unspecified);
  305. }
  306. private static long ReadTimeSpanTicks(ProtoReader source, out DateTimeKind kind)
  307. {
  308. kind = DateTimeKind.Unspecified;
  309. switch (source.WireType)
  310. {
  311. case WireType.String:
  312. case WireType.StartGroup:
  313. SubItemToken token = ProtoReader.StartSubItem(source);
  314. int fieldNumber;
  315. TimeSpanScale scale = TimeSpanScale.Days;
  316. long value = 0;
  317. while ((fieldNumber = source.ReadFieldHeader()) > 0)
  318. {
  319. switch (fieldNumber)
  320. {
  321. case FieldTimeSpanScale:
  322. scale = (TimeSpanScale)source.ReadInt32();
  323. break;
  324. case FieldTimeSpanValue:
  325. source.Assert(WireType.SignedVariant);
  326. value = source.ReadInt64();
  327. break;
  328. case FieldTimeSpanKind:
  329. kind = (DateTimeKind)source.ReadInt32();
  330. switch (kind)
  331. {
  332. case DateTimeKind.Unspecified:
  333. case DateTimeKind.Utc:
  334. case DateTimeKind.Local:
  335. break; // fine
  336. default:
  337. throw new ProtoException("Invalid date/time kind: " + kind.ToString());
  338. }
  339. break;
  340. default:
  341. source.SkipField();
  342. break;
  343. }
  344. }
  345. ProtoReader.EndSubItem(token, source);
  346. switch (scale)
  347. {
  348. case TimeSpanScale.Days:
  349. return value * TimeSpan.TicksPerDay;
  350. case TimeSpanScale.Hours:
  351. return value * TimeSpan.TicksPerHour;
  352. case TimeSpanScale.Minutes:
  353. return value * TimeSpan.TicksPerMinute;
  354. case TimeSpanScale.Seconds:
  355. return value * TimeSpan.TicksPerSecond;
  356. case TimeSpanScale.Milliseconds:
  357. return value * TimeSpan.TicksPerMillisecond;
  358. case TimeSpanScale.Ticks:
  359. return value;
  360. case TimeSpanScale.MinMax:
  361. switch (value)
  362. {
  363. case 1: return long.MaxValue;
  364. case -1: return long.MinValue;
  365. default: throw new ProtoException("Unknown min/max value: " + value.ToString());
  366. }
  367. default:
  368. throw new ProtoException("Unknown timescale: " + scale.ToString());
  369. }
  370. case WireType.Fixed64:
  371. return source.ReadInt64();
  372. default:
  373. throw new ProtoException("Unexpected wire-type: " + source.WireType.ToString());
  374. }
  375. }
  376. const int FieldDecimalLow = 0x01, FieldDecimalHigh = 0x02, FieldDecimalSignScale = 0x03;
  377. /// <summary>
  378. /// Parses a decimal from a protobuf stream
  379. /// </summary>
  380. public static decimal ReadDecimal(ProtoReader reader)
  381. {
  382. ulong low = 0;
  383. uint high = 0;
  384. uint signScale = 0;
  385. int fieldNumber;
  386. SubItemToken token = ProtoReader.StartSubItem(reader);
  387. while ((fieldNumber = reader.ReadFieldHeader()) > 0)
  388. {
  389. switch (fieldNumber)
  390. {
  391. case FieldDecimalLow: low = reader.ReadUInt64(); break;
  392. case FieldDecimalHigh: high = reader.ReadUInt32(); break;
  393. case FieldDecimalSignScale: signScale = reader.ReadUInt32(); break;
  394. default: reader.SkipField(); break;
  395. }
  396. }
  397. ProtoReader.EndSubItem(token, reader);
  398. int lo = (int)(low & 0xFFFFFFFFL),
  399. mid = (int)((low >> 32) & 0xFFFFFFFFL),
  400. hi = (int)high;
  401. bool isNeg = (signScale & 0x0001) == 0x0001;
  402. byte scale = (byte)((signScale & 0x01FE) >> 1);
  403. return new decimal(lo, mid, hi, isNeg, scale);
  404. }
  405. /// <summary>
  406. /// Writes a decimal to a protobuf stream
  407. /// </summary>
  408. public static void WriteDecimal(decimal value, ProtoWriter writer)
  409. {
  410. int[] bits = decimal.GetBits(value);
  411. ulong a = ((ulong)bits[1]) << 32, b = ((ulong)bits[0]) & 0xFFFFFFFFL;
  412. ulong low = a | b;
  413. uint high = (uint)bits[2];
  414. uint signScale = (uint)(((bits[3] >> 15) & 0x01FE) | ((bits[3] >> 31) & 0x0001));
  415. SubItemToken token = ProtoWriter.StartSubItem(null, writer);
  416. if (low != 0)
  417. {
  418. ProtoWriter.WriteFieldHeader(FieldDecimalLow, WireType.Variant, writer);
  419. ProtoWriter.WriteUInt64(low, writer);
  420. }
  421. if (high != 0)
  422. {
  423. ProtoWriter.WriteFieldHeader(FieldDecimalHigh, WireType.Variant, writer);
  424. ProtoWriter.WriteUInt32(high, writer);
  425. }
  426. if (signScale != 0)
  427. {
  428. ProtoWriter.WriteFieldHeader(FieldDecimalSignScale, WireType.Variant, writer);
  429. ProtoWriter.WriteUInt32(signScale, writer);
  430. }
  431. ProtoWriter.EndSubItem(token, writer);
  432. }
  433. const int FieldGuidLow = 1, FieldGuidHigh = 2;
  434. /// <summary>
  435. /// Writes a Guid to a protobuf stream
  436. /// </summary>
  437. public static void WriteGuid(Guid value, ProtoWriter dest)
  438. {
  439. byte[] blob = value.ToByteArray();
  440. SubItemToken token = ProtoWriter.StartSubItem(null, dest);
  441. if (value != Guid.Empty)
  442. {
  443. ProtoWriter.WriteFieldHeader(FieldGuidLow, WireType.Fixed64, dest);
  444. ProtoWriter.WriteBytes(blob, 0, 8, dest);
  445. ProtoWriter.WriteFieldHeader(FieldGuidHigh, WireType.Fixed64, dest);
  446. ProtoWriter.WriteBytes(blob, 8, 8, dest);
  447. }
  448. ProtoWriter.EndSubItem(token, dest);
  449. }
  450. /// <summary>
  451. /// Parses a Guid from a protobuf stream
  452. /// </summary>
  453. public static Guid ReadGuid(ProtoReader source)
  454. {
  455. ulong low = 0, high = 0;
  456. int fieldNumber;
  457. SubItemToken token = ProtoReader.StartSubItem(source);
  458. while ((fieldNumber = source.ReadFieldHeader()) > 0)
  459. {
  460. switch (fieldNumber)
  461. {
  462. case FieldGuidLow: low = source.ReadUInt64(); break;
  463. case FieldGuidHigh: high = source.ReadUInt64(); break;
  464. default: source.SkipField(); break;
  465. }
  466. }
  467. ProtoReader.EndSubItem(token, source);
  468. if (low == 0 && high == 0) return Guid.Empty;
  469. uint a = (uint)(low >> 32), b = (uint)low, c = (uint)(high >> 32), d = (uint)high;
  470. return new Guid((int)b, (short)a, (short)(a >> 16),
  471. (byte)d, (byte)(d >> 8), (byte)(d >> 16), (byte)(d >> 24),
  472. (byte)c, (byte)(c >> 8), (byte)(c >> 16), (byte)(c >> 24));
  473. }
  474. private const int
  475. FieldExistingObjectKey = 1,
  476. FieldNewObjectKey = 2,
  477. FieldExistingTypeKey = 3,
  478. FieldNewTypeKey = 4,
  479. FieldTypeName = 8,
  480. FieldObject = 10;
  481. /// <summary>
  482. /// Optional behaviours that introduce .NET-specific functionality
  483. /// </summary>
  484. [Flags]
  485. public enum NetObjectOptions : byte
  486. {
  487. /// <summary>
  488. /// No special behaviour
  489. /// </summary>
  490. None = 0,
  491. /// <summary>
  492. /// Enables full object-tracking/full-graph support.
  493. /// </summary>
  494. AsReference = 1,
  495. /// <summary>
  496. /// Embeds the type information into the stream, allowing usage with types not known in advance.
  497. /// </summary>
  498. DynamicType = 2,
  499. /// <summary>
  500. /// If false, the constructor for the type is bypassed during deserialization, meaning any field initializers
  501. /// or other initialization code is skipped.
  502. /// </summary>
  503. UseConstructor = 4,
  504. /// <summary>
  505. /// Should the object index be reserved, rather than creating an object promptly
  506. /// </summary>
  507. LateSet = 8
  508. }
  509. /// <summary>
  510. /// Reads an *implementation specific* bundled .NET object, including (as options) type-metadata, identity/re-use, etc.
  511. /// </summary>
  512. public static object ReadNetObject(object value, ProtoReader source, int key, Type type, NetObjectOptions options)
  513. {
  514. SubItemToken token = ProtoReader.StartSubItem(source);
  515. int fieldNumber;
  516. int newObjectKey = -1, newTypeKey = -1, tmp;
  517. while ((fieldNumber = source.ReadFieldHeader()) > 0)
  518. {
  519. switch (fieldNumber)
  520. {
  521. case FieldExistingObjectKey:
  522. tmp = source.ReadInt32();
  523. value = source.NetCache.GetKeyedObject(tmp);
  524. break;
  525. case FieldNewObjectKey:
  526. newObjectKey = source.ReadInt32();
  527. break;
  528. case FieldExistingTypeKey:
  529. tmp = source.ReadInt32();
  530. type = (Type)source.NetCache.GetKeyedObject(tmp);
  531. key = source.GetTypeKey(ref type);
  532. break;
  533. case FieldNewTypeKey:
  534. newTypeKey = source.ReadInt32();
  535. break;
  536. case FieldTypeName:
  537. string typeName = source.ReadString();
  538. type = source.DeserializeType(typeName);
  539. if (type == null)
  540. {
  541. throw new ProtoException("Unable to resolve type: " + typeName + " (you can use the TypeModel.DynamicTypeFormatting event to provide a custom mapping)");
  542. }
  543. if (type == typeof(string))
  544. {
  545. key = -1;
  546. }
  547. else
  548. {
  549. key = source.GetTypeKey(ref type);
  550. if (key < 0)
  551. throw new InvalidOperationException("Dynamic type is not a contract-type: " + type.Name);
  552. }
  553. break;
  554. case FieldObject:
  555. bool isString = type == typeof(string);
  556. bool wasNull = value == null;
  557. bool lateSet = wasNull && (isString || ((options & NetObjectOptions.LateSet) != 0));
  558. if (newObjectKey >= 0 && !lateSet)
  559. {
  560. if (value == null)
  561. {
  562. source.TrapNextObject(newObjectKey);
  563. }
  564. else
  565. {
  566. source.NetCache.SetKeyedObject(newObjectKey, value);
  567. }
  568. if (newTypeKey >= 0) source.NetCache.SetKeyedObject(newTypeKey, type);
  569. }
  570. object oldValue = value;
  571. if (isString)
  572. {
  573. value = source.ReadString();
  574. }
  575. else
  576. {
  577. value = ProtoReader.ReadTypedObject(oldValue, key, source, type);
  578. }
  579. if (newObjectKey >= 0)
  580. {
  581. if (wasNull && !lateSet)
  582. { // this both ensures (via exception) that it *was* set, and makes sure we don't shout
  583. // about changed references
  584. oldValue = source.NetCache.GetKeyedObject(newObjectKey);
  585. }
  586. if (lateSet)
  587. {
  588. source.NetCache.SetKeyedObject(newObjectKey, value);
  589. if (newTypeKey >= 0) source.NetCache.SetKeyedObject(newTypeKey, type);
  590. }
  591. }
  592. if (newObjectKey >= 0 && !lateSet && !ReferenceEquals(oldValue, value))
  593. {
  594. throw new ProtoException("A reference-tracked object changed reference during deserialization");
  595. }
  596. if (newObjectKey < 0 && newTypeKey >= 0)
  597. { // have a new type, but not a new object
  598. source.NetCache.SetKeyedObject(newTypeKey, type);
  599. }
  600. break;
  601. default:
  602. source.SkipField();
  603. break;
  604. }
  605. }
  606. if (newObjectKey >= 0 && (options & NetObjectOptions.AsReference) == 0)
  607. {
  608. throw new ProtoException("Object key in input stream, but reference-tracking was not expected");
  609. }
  610. ProtoReader.EndSubItem(token, source);
  611. return value;
  612. }
  613. /// <summary>
  614. /// Writes an *implementation specific* bundled .NET object, including (as options) type-metadata, identity/re-use, etc.
  615. /// </summary>
  616. public static void WriteNetObject(object value, ProtoWriter dest, int key, NetObjectOptions options)
  617. {
  618. if (dest == null) throw new ArgumentNullException("dest");
  619. bool dynamicType = (options & NetObjectOptions.DynamicType) != 0,
  620. asReference = (options & NetObjectOptions.AsReference) != 0;
  621. WireType wireType = dest.WireType;
  622. SubItemToken token = ProtoWriter.StartSubItem(null, dest);
  623. bool writeObject = true;
  624. if (asReference)
  625. {
  626. int objectKey = dest.NetCache.AddObjectKey(value, out bool existing);
  627. ProtoWriter.WriteFieldHeader(existing ? FieldExistingObjectKey : FieldNewObjectKey, WireType.Variant, dest);
  628. ProtoWriter.WriteInt32(objectKey, dest);
  629. if (existing)
  630. {
  631. writeObject = false;
  632. }
  633. }
  634. if (writeObject)
  635. {
  636. if (dynamicType)
  637. {
  638. Type type = value.GetType();
  639. if (!(value is string))
  640. {
  641. key = dest.GetTypeKey(ref type);
  642. if (key < 0) throw new InvalidOperationException("Dynamic type is not a contract-type: " + type.Name);
  643. }
  644. int typeKey = dest.NetCache.AddObjectKey(type, out bool existing);
  645. ProtoWriter.WriteFieldHeader(existing ? FieldExistingTypeKey : FieldNewTypeKey, WireType.Variant, dest);
  646. ProtoWriter.WriteInt32(typeKey, dest);
  647. if (!existing)
  648. {
  649. ProtoWriter.WriteFieldHeader(FieldTypeName, WireType.String, dest);
  650. ProtoWriter.WriteString(dest.SerializeType(type), dest);
  651. }
  652. }
  653. ProtoWriter.WriteFieldHeader(FieldObject, wireType, dest);
  654. if (value is string)
  655. {
  656. ProtoWriter.WriteString((string)value, dest);
  657. }
  658. else
  659. {
  660. ProtoWriter.WriteObject(value, key, dest);
  661. }
  662. }
  663. ProtoWriter.EndSubItem(token, dest);
  664. }
  665. }
  666. }