712 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			C#
		
	
	
			
		
		
	
	
			712 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			C#
		
	
	
| using System;
 | |
| using System.Reflection;
 | |
| namespace ProtoBuf
 | |
| {
 | |
|     internal enum TimeSpanScale
 | |
|     {
 | |
|         Days = 0,
 | |
|         Hours = 1,
 | |
|         Minutes = 2,
 | |
|         Seconds = 3,
 | |
|         Milliseconds = 4,
 | |
|         Ticks = 5,
 | |
| 
 | |
|         MinMax = 15
 | |
|     }
 | |
| 
 | |
|     /// <summary>
 | |
|     /// Provides support for common .NET types that do not have a direct representation
 | |
|     /// in protobuf, using the definitions from bcl.proto
 | |
|     /// </summary>
 | |
|     public static class BclHelpers
 | |
|     {
 | |
|         /// <summary>
 | |
|         /// Creates a new instance of the specified type, bypassing the constructor.
 | |
|         /// </summary>
 | |
|         /// <param name="type">The type to create</param>
 | |
|         /// <returns>The new instance</returns>
 | |
|         /// <exception cref="NotSupportedException">If the platform does not support constructor-skipping</exception>
 | |
|         public static object GetUninitializedObject(Type type)
 | |
|         {
 | |
| #if COREFX
 | |
|             object obj = TryGetUninitializedObjectWithFormatterServices(type);
 | |
|             if (obj != null) return obj;
 | |
| #endif
 | |
| #if PLAT_BINARYFORMATTER && !(COREFX || PROFILE259)
 | |
|             return System.Runtime.Serialization.FormatterServices.GetUninitializedObject(type);
 | |
| #else
 | |
|             throw new NotSupportedException("Constructor-skipping is not supported on this platform");
 | |
| #endif
 | |
|         }
 | |
| 
 | |
| #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
 | |
|         static Func<Type, object> getUninitializedObject;
 | |
|         static internal object TryGetUninitializedObjectWithFormatterServices(Type type)
 | |
|         {
 | |
|             if (getUninitializedObject == null)
 | |
|             {
 | |
|                 try {
 | |
|                     var formatterServiceType = typeof(string).GetTypeInfo().Assembly.GetType("System.Runtime.Serialization.FormatterServices");
 | |
|                     if (formatterServiceType == null)
 | |
|                     {
 | |
|                         // fallback for .Net Core 3.0
 | |
|                         var formatterAssembly = Assembly.Load(new AssemblyName("System.Runtime.Serialization.Formatters"));
 | |
|                         formatterServiceType = formatterAssembly.GetType("System.Runtime.Serialization.FormatterServices");
 | |
|                     }
 | |
|                     MethodInfo method = formatterServiceType?.GetMethod("GetUninitializedObject", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
 | |
|                     if (method != null)
 | |
|                     {
 | |
|                         getUninitializedObject = (Func<Type, object>)method.CreateDelegate(typeof(Func<Type, object>));
 | |
|                     }
 | |
|                 }
 | |
|                 catch  { /* best efforts only */ }
 | |
|                 if(getUninitializedObject == null) getUninitializedObject = x => null;
 | |
|             }
 | |
|             return getUninitializedObject(type);
 | |
|         }
 | |
| #endif
 | |
| 
 | |
|         const int FieldTimeSpanValue = 0x01, FieldTimeSpanScale = 0x02, FieldTimeSpanKind = 0x03;
 | |
| 
 | |
|         internal static readonly DateTime[] EpochOrigin = {
 | |
|             new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified),
 | |
|             new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc),
 | |
|             new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Local)
 | |
|         };
 | |
| 
 | |
|         /// <summary>
 | |
|         /// The default value for dates that are following google.protobuf.Timestamp semantics
 | |
|         /// </summary>
 | |
|         private static readonly DateTime TimestampEpoch = EpochOrigin[(int)DateTimeKind.Utc];
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Writes a TimeSpan to a protobuf stream using protobuf-net's own representation, bcl.TimeSpan
 | |
|         /// </summary>
 | |
|         public static void WriteTimeSpan(TimeSpan timeSpan, ProtoWriter dest)
 | |
|         {
 | |
|             WriteTimeSpanImpl(timeSpan, dest, DateTimeKind.Unspecified);
 | |
|         }
 | |
| 
 | |
|         private static void WriteTimeSpanImpl(TimeSpan timeSpan, ProtoWriter dest, DateTimeKind kind)
 | |
|         {
 | |
|             if (dest == null) throw new ArgumentNullException(nameof(dest));
 | |
|             long value;
 | |
|             switch (dest.WireType)
 | |
|             {
 | |
|                 case WireType.String:
 | |
|                 case WireType.StartGroup:
 | |
|                     TimeSpanScale scale;
 | |
|                     value = timeSpan.Ticks;
 | |
|                     if (timeSpan == TimeSpan.MaxValue)
 | |
|                     {
 | |
|                         value = 1;
 | |
|                         scale = TimeSpanScale.MinMax;
 | |
|                     }
 | |
|                     else if (timeSpan == TimeSpan.MinValue)
 | |
|                     {
 | |
|                         value = -1;
 | |
|                         scale = TimeSpanScale.MinMax;
 | |
|                     }
 | |
|                     else if (value % TimeSpan.TicksPerDay == 0)
 | |
|                     {
 | |
|                         scale = TimeSpanScale.Days;
 | |
|                         value /= TimeSpan.TicksPerDay;
 | |
|                     }
 | |
|                     else if (value % TimeSpan.TicksPerHour == 0)
 | |
|                     {
 | |
|                         scale = TimeSpanScale.Hours;
 | |
|                         value /= TimeSpan.TicksPerHour;
 | |
|                     }
 | |
|                     else if (value % TimeSpan.TicksPerMinute == 0)
 | |
|                     {
 | |
|                         scale = TimeSpanScale.Minutes;
 | |
|                         value /= TimeSpan.TicksPerMinute;
 | |
|                     }
 | |
|                     else if (value % TimeSpan.TicksPerSecond == 0)
 | |
|                     {
 | |
|                         scale = TimeSpanScale.Seconds;
 | |
|                         value /= TimeSpan.TicksPerSecond;
 | |
|                     }
 | |
|                     else if (value % TimeSpan.TicksPerMillisecond == 0)
 | |
|                     {
 | |
|                         scale = TimeSpanScale.Milliseconds;
 | |
|                         value /= TimeSpan.TicksPerMillisecond;
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         scale = TimeSpanScale.Ticks;
 | |
|                     }
 | |
| 
 | |
|                     SubItemToken token = ProtoWriter.StartSubItem(null, dest);
 | |
| 
 | |
|                     if (value != 0)
 | |
|                     {
 | |
|                         ProtoWriter.WriteFieldHeader(FieldTimeSpanValue, WireType.SignedVariant, dest);
 | |
|                         ProtoWriter.WriteInt64(value, dest);
 | |
|                     }
 | |
|                     if (scale != TimeSpanScale.Days)
 | |
|                     {
 | |
|                         ProtoWriter.WriteFieldHeader(FieldTimeSpanScale, WireType.Variant, dest);
 | |
|                         ProtoWriter.WriteInt32((int)scale, dest);
 | |
|                     }
 | |
|                     if (kind != DateTimeKind.Unspecified)
 | |
|                     {
 | |
|                         ProtoWriter.WriteFieldHeader(FieldTimeSpanKind, WireType.Variant, dest);
 | |
|                         ProtoWriter.WriteInt32((int)kind, dest);
 | |
|                     }
 | |
|                     ProtoWriter.EndSubItem(token, dest);
 | |
|                     break;
 | |
|                 case WireType.Fixed64:
 | |
|                     ProtoWriter.WriteInt64(timeSpan.Ticks, dest);
 | |
|                     break;
 | |
|                 default:
 | |
|                     throw new ProtoException("Unexpected wire-type: " + dest.WireType.ToString());
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Parses a TimeSpan from a protobuf stream using protobuf-net's own representation, bcl.TimeSpan
 | |
|         /// </summary>        
 | |
|         public static TimeSpan ReadTimeSpan(ProtoReader source)
 | |
|         {
 | |
|             long ticks = ReadTimeSpanTicks(source, out DateTimeKind kind);
 | |
|             if (ticks == long.MinValue) return TimeSpan.MinValue;
 | |
|             if (ticks == long.MaxValue) return TimeSpan.MaxValue;
 | |
|             return TimeSpan.FromTicks(ticks);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Parses a TimeSpan from a protobuf stream using the standardized format, google.protobuf.Duration
 | |
|         /// </summary>
 | |
|         public static TimeSpan ReadDuration(ProtoReader source)
 | |
|         {
 | |
|             long seconds = 0;
 | |
|             int nanos = 0;
 | |
|             SubItemToken token = ProtoReader.StartSubItem(source);
 | |
|             int fieldNumber;
 | |
|             while ((fieldNumber = source.ReadFieldHeader()) > 0)
 | |
|             {
 | |
|                 switch (fieldNumber)
 | |
|                 {
 | |
|                     case 1:
 | |
|                         seconds = source.ReadInt64();
 | |
|                         break;
 | |
|                     case 2:
 | |
|                         nanos = source.ReadInt32();
 | |
|                         break;
 | |
|                     default:
 | |
|                         source.SkipField();
 | |
|                         break;
 | |
|                 }
 | |
|             }
 | |
|             ProtoReader.EndSubItem(token, source);
 | |
|             return FromDurationSeconds(seconds, nanos);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Writes a TimeSpan to a protobuf stream using the standardized format, google.protobuf.Duration
 | |
|         /// </summary>
 | |
|         public static void WriteDuration(TimeSpan value, ProtoWriter dest)
 | |
|         {
 | |
|             var seconds = ToDurationSeconds(value, out int nanos);
 | |
|             WriteSecondsNanos(seconds, nanos, dest);
 | |
|         }
 | |
| 
 | |
|         private static void WriteSecondsNanos(long seconds, int nanos, ProtoWriter dest)
 | |
|         {
 | |
|             SubItemToken token = ProtoWriter.StartSubItem(null, dest);
 | |
|             if (seconds != 0)
 | |
|             {
 | |
|                 ProtoWriter.WriteFieldHeader(1, WireType.Variant, dest);
 | |
|                 ProtoWriter.WriteInt64(seconds, dest);
 | |
|             }
 | |
|             if (nanos != 0)
 | |
|             {
 | |
|                 ProtoWriter.WriteFieldHeader(2, WireType.Variant, dest);
 | |
|                 ProtoWriter.WriteInt32(nanos, dest);
 | |
|             }
 | |
|             ProtoWriter.EndSubItem(token, dest);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Parses a DateTime from a protobuf stream using the standardized format, google.protobuf.Timestamp
 | |
|         /// </summary>
 | |
|         public static DateTime ReadTimestamp(ProtoReader source)
 | |
|         {
 | |
|             // note: DateTime is only defined for just over 0000 to just below 10000;
 | |
|             // TimeSpan has a range of +/- 10,675,199 days === 29k years;
 | |
|             // so we can just use epoch time delta
 | |
|             return TimestampEpoch + ReadDuration(source);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Writes a DateTime to a protobuf stream using the standardized format, google.protobuf.Timestamp
 | |
|         /// </summary>
 | |
|         public static void WriteTimestamp(DateTime value, ProtoWriter dest)
 | |
|         {
 | |
|             var seconds = ToDurationSeconds(value - TimestampEpoch, out int nanos);
 | |
| 
 | |
|             if (nanos < 0)
 | |
|             {   // from Timestamp.proto:
 | |
|                 // "Negative second values with fractions must still have
 | |
|                 // non -negative nanos values that count forward in time."
 | |
|                 seconds--;
 | |
|                 nanos += 1000000000;
 | |
|             }
 | |
|             WriteSecondsNanos(seconds, nanos, dest);
 | |
|         }
 | |
| 
 | |
|         static TimeSpan FromDurationSeconds(long seconds, int nanos)
 | |
|         {
 | |
| 
 | |
|             long ticks = checked((seconds * TimeSpan.TicksPerSecond)
 | |
|                 + (nanos * TimeSpan.TicksPerMillisecond) / 1000000);
 | |
|             return TimeSpan.FromTicks(ticks);
 | |
|         }
 | |
| 
 | |
|         static long ToDurationSeconds(TimeSpan value, out int nanos)
 | |
|         {
 | |
|             nanos = (int)(((value.Ticks % TimeSpan.TicksPerSecond) * 1000000)
 | |
|                 / TimeSpan.TicksPerMillisecond);
 | |
|             return value.Ticks / TimeSpan.TicksPerSecond;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Parses a DateTime from a protobuf stream
 | |
|         /// </summary>
 | |
|         public static DateTime ReadDateTime(ProtoReader source)
 | |
|         {
 | |
|             long ticks = ReadTimeSpanTicks(source, out DateTimeKind kind);
 | |
|             if (ticks == long.MinValue) return DateTime.MinValue;
 | |
|             if (ticks == long.MaxValue) return DateTime.MaxValue;
 | |
|             return EpochOrigin[(int)kind].AddTicks(ticks);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Writes a DateTime to a protobuf stream, excluding the <c>Kind</c>
 | |
|         /// </summary>
 | |
|         public static void WriteDateTime(DateTime value, ProtoWriter dest)
 | |
|         {
 | |
|             WriteDateTimeImpl(value, dest, false);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Writes a DateTime to a protobuf stream, including the <c>Kind</c>
 | |
|         /// </summary>
 | |
|         public static void WriteDateTimeWithKind(DateTime value, ProtoWriter dest)
 | |
|         {
 | |
|             WriteDateTimeImpl(value, dest, true);
 | |
|         }
 | |
| 
 | |
|         private static void WriteDateTimeImpl(DateTime value, ProtoWriter dest, bool includeKind)
 | |
|         {
 | |
|             if (dest == null) throw new ArgumentNullException(nameof(dest));
 | |
|             TimeSpan delta;
 | |
|             switch (dest.WireType)
 | |
|             {
 | |
|                 case WireType.StartGroup:
 | |
|                 case WireType.String:
 | |
|                     if (value == DateTime.MaxValue)
 | |
|                     {
 | |
|                         delta = TimeSpan.MaxValue;
 | |
|                         includeKind = false;
 | |
|                     }
 | |
|                     else if (value == DateTime.MinValue)
 | |
|                     {
 | |
|                         delta = TimeSpan.MinValue;
 | |
|                         includeKind = false;
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         delta = value - EpochOrigin[0];
 | |
|                     }
 | |
|                     break;
 | |
|                 default:
 | |
|                     delta = value - EpochOrigin[0];
 | |
|                     break;
 | |
|             }
 | |
|             WriteTimeSpanImpl(delta, dest, includeKind ? value.Kind : DateTimeKind.Unspecified);
 | |
|         }
 | |
| 
 | |
|         private static long ReadTimeSpanTicks(ProtoReader source, out DateTimeKind kind)
 | |
|         {
 | |
|             kind = DateTimeKind.Unspecified;
 | |
|             switch (source.WireType)
 | |
|             {
 | |
|                 case WireType.String:
 | |
|                 case WireType.StartGroup:
 | |
|                     SubItemToken token = ProtoReader.StartSubItem(source);
 | |
|                     int fieldNumber;
 | |
|                     TimeSpanScale scale = TimeSpanScale.Days;
 | |
|                     long value = 0;
 | |
|                     while ((fieldNumber = source.ReadFieldHeader()) > 0)
 | |
|                     {
 | |
|                         switch (fieldNumber)
 | |
|                         {
 | |
|                             case FieldTimeSpanScale:
 | |
|                                 scale = (TimeSpanScale)source.ReadInt32();
 | |
|                                 break;
 | |
|                             case FieldTimeSpanValue:
 | |
|                                 source.Assert(WireType.SignedVariant);
 | |
|                                 value = source.ReadInt64();
 | |
|                                 break;
 | |
|                             case FieldTimeSpanKind:
 | |
|                                 kind = (DateTimeKind)source.ReadInt32();
 | |
|                                 switch (kind)
 | |
|                                 {
 | |
|                                     case DateTimeKind.Unspecified:
 | |
|                                     case DateTimeKind.Utc:
 | |
|                                     case DateTimeKind.Local:
 | |
|                                         break; // fine
 | |
|                                     default:
 | |
|                                         throw new ProtoException("Invalid date/time kind: " + kind.ToString());
 | |
|                                 }
 | |
|                                 break;
 | |
|                             default:
 | |
|                                 source.SkipField();
 | |
|                                 break;
 | |
|                         }
 | |
|                     }
 | |
|                     ProtoReader.EndSubItem(token, source);
 | |
|                     switch (scale)
 | |
|                     {
 | |
|                         case TimeSpanScale.Days:
 | |
|                             return value * TimeSpan.TicksPerDay;
 | |
|                         case TimeSpanScale.Hours:
 | |
|                             return value * TimeSpan.TicksPerHour;
 | |
|                         case TimeSpanScale.Minutes:
 | |
|                             return value * TimeSpan.TicksPerMinute;
 | |
|                         case TimeSpanScale.Seconds:
 | |
|                             return value * TimeSpan.TicksPerSecond;
 | |
|                         case TimeSpanScale.Milliseconds:
 | |
|                             return value * TimeSpan.TicksPerMillisecond;
 | |
|                         case TimeSpanScale.Ticks:
 | |
|                             return value;
 | |
|                         case TimeSpanScale.MinMax:
 | |
|                             switch (value)
 | |
|                             {
 | |
|                                 case 1: return long.MaxValue;
 | |
|                                 case -1: return long.MinValue;
 | |
|                                 default: throw new ProtoException("Unknown min/max value: " + value.ToString());
 | |
|                             }
 | |
|                         default:
 | |
|                             throw new ProtoException("Unknown timescale: " + scale.ToString());
 | |
|                     }
 | |
|                 case WireType.Fixed64:
 | |
|                     return source.ReadInt64();
 | |
|                 default:
 | |
|                     throw new ProtoException("Unexpected wire-type: " + source.WireType.ToString());
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         const int FieldDecimalLow = 0x01, FieldDecimalHigh = 0x02, FieldDecimalSignScale = 0x03;
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Parses a decimal from a protobuf stream
 | |
|         /// </summary>
 | |
|         public static decimal ReadDecimal(ProtoReader reader)
 | |
|         {
 | |
|             ulong low = 0;
 | |
|             uint high = 0;
 | |
|             uint signScale = 0;
 | |
| 
 | |
|             int fieldNumber;
 | |
|             SubItemToken token = ProtoReader.StartSubItem(reader);
 | |
|             while ((fieldNumber = reader.ReadFieldHeader()) > 0)
 | |
|             {
 | |
|                 switch (fieldNumber)
 | |
|                 {
 | |
|                     case FieldDecimalLow: low = reader.ReadUInt64(); break;
 | |
|                     case FieldDecimalHigh: high = reader.ReadUInt32(); break;
 | |
|                     case FieldDecimalSignScale: signScale = reader.ReadUInt32(); break;
 | |
|                     default: reader.SkipField(); break;
 | |
|                 }
 | |
| 
 | |
|             }
 | |
|             ProtoReader.EndSubItem(token, reader);
 | |
| 
 | |
|             int lo = (int)(low & 0xFFFFFFFFL),
 | |
|                 mid = (int)((low >> 32) & 0xFFFFFFFFL),
 | |
|                 hi = (int)high;
 | |
|             bool isNeg = (signScale & 0x0001) == 0x0001;
 | |
|             byte scale = (byte)((signScale & 0x01FE) >> 1);
 | |
|             return new decimal(lo, mid, hi, isNeg, scale);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Writes a decimal to a protobuf stream
 | |
|         /// </summary>
 | |
|         public static void WriteDecimal(decimal value, ProtoWriter writer)
 | |
|         {
 | |
|             int[] bits = decimal.GetBits(value);
 | |
|             ulong a = ((ulong)bits[1]) << 32, b = ((ulong)bits[0]) & 0xFFFFFFFFL;
 | |
|             ulong low = a | b;
 | |
|             uint high = (uint)bits[2];
 | |
|             uint signScale = (uint)(((bits[3] >> 15) & 0x01FE) | ((bits[3] >> 31) & 0x0001));
 | |
| 
 | |
|             SubItemToken token = ProtoWriter.StartSubItem(null, writer);
 | |
|             if (low != 0)
 | |
|             {
 | |
|                 ProtoWriter.WriteFieldHeader(FieldDecimalLow, WireType.Variant, writer);
 | |
|                 ProtoWriter.WriteUInt64(low, writer);
 | |
|             }
 | |
|             if (high != 0)
 | |
|             {
 | |
|                 ProtoWriter.WriteFieldHeader(FieldDecimalHigh, WireType.Variant, writer);
 | |
|                 ProtoWriter.WriteUInt32(high, writer);
 | |
|             }
 | |
|             if (signScale != 0)
 | |
|             {
 | |
|                 ProtoWriter.WriteFieldHeader(FieldDecimalSignScale, WireType.Variant, writer);
 | |
|                 ProtoWriter.WriteUInt32(signScale, writer);
 | |
|             }
 | |
|             ProtoWriter.EndSubItem(token, writer);
 | |
|         }
 | |
| 
 | |
|         const int FieldGuidLow = 1, FieldGuidHigh = 2;
 | |
|         /// <summary>
 | |
|         /// Writes a Guid to a protobuf stream
 | |
|         /// </summary>        
 | |
|         public static void WriteGuid(Guid value, ProtoWriter dest)
 | |
|         {
 | |
|             byte[] blob = value.ToByteArray();
 | |
| 
 | |
|             SubItemToken token = ProtoWriter.StartSubItem(null, dest);
 | |
|             if (value != Guid.Empty)
 | |
|             {
 | |
|                 ProtoWriter.WriteFieldHeader(FieldGuidLow, WireType.Fixed64, dest);
 | |
|                 ProtoWriter.WriteBytes(blob, 0, 8, dest);
 | |
|                 ProtoWriter.WriteFieldHeader(FieldGuidHigh, WireType.Fixed64, dest);
 | |
|                 ProtoWriter.WriteBytes(blob, 8, 8, dest);
 | |
|             }
 | |
|             ProtoWriter.EndSubItem(token, dest);
 | |
|         }
 | |
|         /// <summary>
 | |
|         /// Parses a Guid from a protobuf stream
 | |
|         /// </summary>
 | |
|         public static Guid ReadGuid(ProtoReader source)
 | |
|         {
 | |
|             ulong low = 0, high = 0;
 | |
|             int fieldNumber;
 | |
|             SubItemToken token = ProtoReader.StartSubItem(source);
 | |
|             while ((fieldNumber = source.ReadFieldHeader()) > 0)
 | |
|             {
 | |
|                 switch (fieldNumber)
 | |
|                 {
 | |
|                     case FieldGuidLow: low = source.ReadUInt64(); break;
 | |
|                     case FieldGuidHigh: high = source.ReadUInt64(); break;
 | |
|                     default: source.SkipField(); break;
 | |
|                 }
 | |
|             }
 | |
|             ProtoReader.EndSubItem(token, source);
 | |
|             if (low == 0 && high == 0) return Guid.Empty;
 | |
|             uint a = (uint)(low >> 32), b = (uint)low, c = (uint)(high >> 32), d = (uint)high;
 | |
|             return new Guid((int)b, (short)a, (short)(a >> 16),
 | |
|                 (byte)d, (byte)(d >> 8), (byte)(d >> 16), (byte)(d >> 24),
 | |
|                 (byte)c, (byte)(c >> 8), (byte)(c >> 16), (byte)(c >> 24));
 | |
| 
 | |
|         }
 | |
| 
 | |
| 
 | |
|         private const int
 | |
|             FieldExistingObjectKey = 1,
 | |
|             FieldNewObjectKey = 2,
 | |
|             FieldExistingTypeKey = 3,
 | |
|             FieldNewTypeKey = 4,
 | |
|             FieldTypeName = 8,
 | |
|             FieldObject = 10;
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Optional behaviours that introduce .NET-specific functionality
 | |
|         /// </summary>
 | |
|         [Flags]
 | |
|         public enum NetObjectOptions : byte
 | |
|         {
 | |
|             /// <summary>
 | |
|             /// No special behaviour
 | |
|             /// </summary>
 | |
|             None = 0,
 | |
|             /// <summary>
 | |
|             /// Enables full object-tracking/full-graph support.
 | |
|             /// </summary>
 | |
|             AsReference = 1,
 | |
|             /// <summary>
 | |
|             /// Embeds the type information into the stream, allowing usage with types not known in advance.
 | |
|             /// </summary>
 | |
|             DynamicType = 2,
 | |
|             /// <summary>
 | |
|             /// If false, the constructor for the type is bypassed during deserialization, meaning any field initializers
 | |
|             /// or other initialization code is skipped.
 | |
|             /// </summary>
 | |
|             UseConstructor = 4,
 | |
|             /// <summary>
 | |
|             /// Should the object index be reserved, rather than creating an object promptly
 | |
|             /// </summary>
 | |
|             LateSet = 8
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Reads an *implementation specific* bundled .NET object, including (as options) type-metadata, identity/re-use, etc.
 | |
|         /// </summary>
 | |
|         public static object ReadNetObject(object value, ProtoReader source, int key, Type type, NetObjectOptions options)
 | |
|         {
 | |
|             SubItemToken token = ProtoReader.StartSubItem(source);
 | |
|             int fieldNumber;
 | |
|             int newObjectKey = -1, newTypeKey = -1, tmp;
 | |
|             while ((fieldNumber = source.ReadFieldHeader()) > 0)
 | |
|             {
 | |
|                 switch (fieldNumber)
 | |
|                 {
 | |
|                     case FieldExistingObjectKey:
 | |
|                         tmp = source.ReadInt32();
 | |
|                         value = source.NetCache.GetKeyedObject(tmp);
 | |
|                         break;
 | |
|                     case FieldNewObjectKey:
 | |
|                         newObjectKey = source.ReadInt32();
 | |
|                         break;
 | |
|                     case FieldExistingTypeKey:
 | |
|                         tmp = source.ReadInt32();
 | |
|                         type = (Type)source.NetCache.GetKeyedObject(tmp);
 | |
|                         key = source.GetTypeKey(ref type);
 | |
|                         break;
 | |
|                     case FieldNewTypeKey:
 | |
|                         newTypeKey = source.ReadInt32();
 | |
|                         break;
 | |
|                     case FieldTypeName:
 | |
|                         string typeName = source.ReadString();
 | |
|                         type = source.DeserializeType(typeName);
 | |
|                         if (type == null)
 | |
|                         {
 | |
|                             throw new ProtoException("Unable to resolve type: " + typeName + " (you can use the TypeModel.DynamicTypeFormatting event to provide a custom mapping)");
 | |
|                         }
 | |
|                         if (type == typeof(string))
 | |
|                         {
 | |
|                             key = -1;
 | |
|                         }
 | |
|                         else
 | |
|                         {
 | |
|                             key = source.GetTypeKey(ref type);
 | |
|                             if (key < 0)
 | |
|                                 throw new InvalidOperationException("Dynamic type is not a contract-type: " + type.Name);
 | |
|                         }
 | |
|                         break;
 | |
|                     case FieldObject:
 | |
|                         bool isString = type == typeof(string);
 | |
|                         bool wasNull = value == null;
 | |
|                         bool lateSet = wasNull && (isString || ((options & NetObjectOptions.LateSet) != 0));
 | |
| 
 | |
|                         if (newObjectKey >= 0 && !lateSet)
 | |
|                         {
 | |
|                             if (value == null)
 | |
|                             {
 | |
|                                 source.TrapNextObject(newObjectKey);
 | |
|                             }
 | |
|                             else
 | |
|                             {
 | |
|                                 source.NetCache.SetKeyedObject(newObjectKey, value);
 | |
|                             }
 | |
|                             if (newTypeKey >= 0) source.NetCache.SetKeyedObject(newTypeKey, type);
 | |
|                         }
 | |
|                         object oldValue = value;
 | |
|                         if (isString)
 | |
|                         {
 | |
|                             value = source.ReadString();
 | |
|                         }
 | |
|                         else
 | |
|                         {
 | |
|                             value = ProtoReader.ReadTypedObject(oldValue, key, source, type);
 | |
|                         }
 | |
| 
 | |
|                         if (newObjectKey >= 0)
 | |
|                         {
 | |
|                             if (wasNull && !lateSet)
 | |
|                             { // this both ensures (via exception) that it *was* set, and makes sure we don't shout
 | |
|                                 // about changed references
 | |
|                                 oldValue = source.NetCache.GetKeyedObject(newObjectKey);
 | |
|                             }
 | |
|                             if (lateSet)
 | |
|                             {
 | |
|                                 source.NetCache.SetKeyedObject(newObjectKey, value);
 | |
|                                 if (newTypeKey >= 0) source.NetCache.SetKeyedObject(newTypeKey, type);
 | |
|                             }
 | |
|                         }
 | |
|                         if (newObjectKey >= 0 && !lateSet && !ReferenceEquals(oldValue, value))
 | |
|                         {
 | |
|                             throw new ProtoException("A reference-tracked object changed reference during deserialization");
 | |
|                         }
 | |
|                         if (newObjectKey < 0 && newTypeKey >= 0)
 | |
|                         {  // have a new type, but not a new object
 | |
|                             source.NetCache.SetKeyedObject(newTypeKey, type);
 | |
|                         }
 | |
|                         break;
 | |
|                     default:
 | |
|                         source.SkipField();
 | |
|                         break;
 | |
|                 }
 | |
|             }
 | |
|             if (newObjectKey >= 0 && (options & NetObjectOptions.AsReference) == 0)
 | |
|             {
 | |
|                 throw new ProtoException("Object key in input stream, but reference-tracking was not expected");
 | |
|             }
 | |
|             ProtoReader.EndSubItem(token, source);
 | |
| 
 | |
|             return value;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Writes an *implementation specific* bundled .NET object, including (as options) type-metadata, identity/re-use, etc.
 | |
|         /// </summary>
 | |
|         public static void WriteNetObject(object value, ProtoWriter dest, int key, NetObjectOptions options)
 | |
|         {
 | |
|             if (dest == null) throw new ArgumentNullException("dest");
 | |
|             bool dynamicType = (options & NetObjectOptions.DynamicType) != 0,
 | |
|                  asReference = (options & NetObjectOptions.AsReference) != 0;
 | |
|             WireType wireType = dest.WireType;
 | |
|             SubItemToken token = ProtoWriter.StartSubItem(null, dest);
 | |
|             bool writeObject = true;
 | |
|             if (asReference)
 | |
|             {
 | |
|                 int objectKey = dest.NetCache.AddObjectKey(value, out bool existing);
 | |
|                 ProtoWriter.WriteFieldHeader(existing ? FieldExistingObjectKey : FieldNewObjectKey, WireType.Variant, dest);
 | |
|                 ProtoWriter.WriteInt32(objectKey, dest);
 | |
|                 if (existing)
 | |
|                 {
 | |
|                     writeObject = false;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (writeObject)
 | |
|             {
 | |
|                 if (dynamicType)
 | |
|                 {
 | |
|                     Type type = value.GetType();
 | |
| 
 | |
|                     if (!(value is string))
 | |
|                     {
 | |
|                         key = dest.GetTypeKey(ref type);
 | |
|                         if (key < 0) throw new InvalidOperationException("Dynamic type is not a contract-type: " + type.Name);
 | |
|                     }
 | |
|                     int typeKey = dest.NetCache.AddObjectKey(type, out bool existing);
 | |
|                     ProtoWriter.WriteFieldHeader(existing ? FieldExistingTypeKey : FieldNewTypeKey, WireType.Variant, dest);
 | |
|                     ProtoWriter.WriteInt32(typeKey, dest);
 | |
|                     if (!existing)
 | |
|                     {
 | |
|                         ProtoWriter.WriteFieldHeader(FieldTypeName, WireType.String, dest);
 | |
|                         ProtoWriter.WriteString(dest.SerializeType(type), dest);
 | |
|                     }
 | |
| 
 | |
|                 }
 | |
|                 ProtoWriter.WriteFieldHeader(FieldObject, wireType, dest);
 | |
|                 if (value is string)
 | |
|                 {
 | |
|                     ProtoWriter.WriteString((string)value, dest);
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     ProtoWriter.WriteObject(value, key, dest);
 | |
|                 }
 | |
|             }
 | |
|             ProtoWriter.EndSubItem(token, dest);
 | |
|         }
 | |
|     }
 | |
| } |