123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617 |
- 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
- #if FX11
- sealed
- #else
- static
- #endif
- 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 && !(WINRT || PHONE8 || COREFX)
- 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");
- 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
- #if FX11
- private BclHelpers() { } // not a static class for C# 1.2 reasons
- #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>
- /// Writes a TimeSpan to a protobuf stream
- /// </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("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
- /// </summary>
- public static TimeSpan ReadTimeSpan(ProtoReader source)
- {
- DateTimeKind kind;
- long ticks = ReadTimeSpanTicks(source, out kind);
- if (ticks == long.MinValue) return TimeSpan.MinValue;
- if (ticks == long.MaxValue) return TimeSpan.MaxValue;
- return TimeSpan.FromTicks(ticks);
- }
- /// <summary>
- /// Parses a DateTime from a protobuf stream
- /// </summary>
- public static DateTime ReadDateTime(ProtoReader source)
- {
- DateTimeKind kind;
- long ticks = ReadTimeSpanTicks(source, out 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("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);
- if (low == 0 && high == 0) return decimal.Zero;
- 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)
- {
- #if FEAT_IKVM
- throw new NotSupportedException();
- #else
- 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;
- #endif
- }
- /// <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 FEAT_IKVM
- throw new NotSupportedException();
- #else
- 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)
- {
- bool existing;
- int objectKey = dest.NetCache.AddObjectKey(value, out existing);
- ProtoWriter.WriteFieldHeader(existing ? FieldExistingObjectKey : FieldNewObjectKey, WireType.Variant, dest);
- ProtoWriter.WriteInt32(objectKey, dest);
- if (existing)
- {
- writeObject = false;
- }
- }
- if (writeObject)
- {
- if (dynamicType)
- {
- bool existing;
- 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 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);
- #endif
- }
- }
- }
|