diff --git a/src/Discord.Net/ETF/ETFReader.cs b/src/Discord.Net/ETF/ETFReader.cs index d7617c344..4641d8234 100644 --- a/src/Discord.Net/ETF/ETFReader.cs +++ b/src/Discord.Net/ETF/ETFReader.cs @@ -1,26 +1,49 @@ -/*using System; +using Newtonsoft.Json; +using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; +using System.Reflection.Emit; using System.Text; namespace Discord.ETF { - public class ETFReader + public class ETFReader : IDisposable { - + private static readonly ConcurrentDictionary _deserializers; + private static readonly Dictionary _readMethods = GetPrimitiveReadMethods(); + private readonly Stream _stream; private readonly byte[] _buffer; private readonly bool _leaveOpen; private readonly Encoding _encoding; - private readonly ConcurrentDictionary _serializers, _indirectSerializers; + + public ETFReader(Stream stream, bool leaveOpen = false) + { + if (stream == null) throw new ArgumentNullException(nameof(stream)); - private void ReadNil(bool allow) + _stream = stream; + _leaveOpen = leaveOpen; + _buffer = new byte[11]; + _encoding = Encoding.UTF8; + } + + private bool ReadNil(bool ignoreLength = false) { - if (!allow) throw new InvalidDataException(); + if (!ignoreLength) + { + _stream.Read(_buffer, 0, 1); + byte length = _buffer[0]; + if (length != 3) return false; + } + _stream.Read(_buffer, 0, 3); - if (_buffer[0] != 'n' || _buffer[1] != 'i' || _buffer[2] != 'l') - throw new InvalidDataException(); + if (_buffer[0] == 'n' && _buffer[1] == 'i' && _buffer[2] == 'l') + return true; + + return false; } private void ReadTrue() { @@ -35,138 +58,407 @@ namespace Discord.ETF throw new InvalidDataException(); } - public bool? ReadBool(bool allowNil) + public bool? ReadNullableBool() { - _stream.Read(_buffer, 0, 2); - switch ((ETFType)_buffer[0]) + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + if (type == ETFType.SMALL_ATOM_EXT) { - case ETFType.SMALL_ATOM_EXT: - switch (_buffer[1]) //Length - { - case 3: - ReadNil(allowNil); + _stream.Read(_buffer, 0, 1); + switch (_buffer[0]) //Length + { + case 3: + if (ReadNil()) return null; - case 4: - ReadTrue(); - return true; - case 5: - ReadFalse(); - return false; - } - break; + break; + case 4: + ReadTrue(); + return true; + case 5: + ReadFalse(); + return false; + } + } + throw new InvalidDataException(); + } + public bool ReadBool() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + if (type == ETFType.SMALL_ATOM_EXT) + { + _stream.Read(_buffer, 0, 1); + switch (_buffer[0]) //Length + { + case 4: + ReadTrue(); + return true; + case 5: + ReadFalse(); + return false; + } } throw new InvalidDataException(); } - public long? ReadInteger(bool allowNil) + public int ReadSByte() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + return (sbyte)ReadLongInternal(type); + } + public int? ReadNullableSByte() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; + return (sbyte)ReadLongInternal(type); + } + public uint ReadByte() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + return (byte)ReadLongInternal(type); + } + public uint? ReadNullableByte() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; + return (byte)ReadLongInternal(type); + } + public int ReadShort() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + return (short)ReadLongInternal(type); + } + public int? ReadNullableShort() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; + return (short)ReadLongInternal(type); + } + public uint ReadUShort() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + return (ushort)ReadLongInternal(type); + } + public uint? ReadNullableUShort() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; + return (ushort)ReadLongInternal(type); + } + public int ReadInt() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + return (int)ReadLongInternal(type); + } + public int? ReadNullableInt() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; + return (int)ReadLongInternal(type); + } + public uint ReadUInt() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + return (uint)ReadLongInternal(type); + } + public uint? ReadNullableUInt() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; + return (uint)ReadLongInternal(type); + } + public long ReadLong() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + return ReadLongInternal(type); + } + public long? ReadNullableLong() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; + return ReadLongInternal(type); + } + public ulong ReadULong() { _stream.Read(_buffer, 0, 1); - ETFType type = (ETFType)reader.ReadByte(); + ETFType type = (ETFType)_buffer[0]; + return (ulong)ReadLongInternal(type); + } + public ulong? ReadNullableULong() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; + return (ulong)ReadLongInternal(type); + } + public long ReadLongInternal(ETFType type) + { switch (type) { - case ETFType.SMALL_ATOM_EXT: - ReadNil(allowNil); - return null; case ETFType.SMALL_INTEGER_EXT: _stream.Read(_buffer, 0, 1); - return (_buffer[0] << 24) | (_buffer[1] << 16) | - (_buffer[2] << 8) | (_buffer[3]); + return _buffer[0]; case ETFType.INTEGER_EXT: _stream.Read(_buffer, 0, 4); - return ??; + return (_buffer[0] << 24) | (_buffer[1] << 16) | (_buffer[2] << 8) | (_buffer[3]); case ETFType.SMALL_BIG_EXT: - return ??; + _stream.Read(_buffer, 0, 2); + bool isPositive = _buffer[0] == 0; + byte count = _buffer[1]; + + int shiftValue = (count - 1) * 8; + ulong value = 0; + _stream.Read(_buffer, 0, count); + for (int i = 0; i < count; i++, shiftValue -= 8) + value = value + _buffer[i] << shiftValue; + if (!isPositive) + return -(long)value; + else + return (long)value; } throw new InvalidDataException(); } - public void Write(sbyte value) => Write((long)value); - public void Write(byte value) => Write((ulong)value); - public void Write(short value) => Write((long)value); - public void Write(ushort value) => Write((ulong)value); - public void Write(int value) => Write((long)value); - public void Write(uint value) => Write((ulong)value); - public void Write(long value) + public float ReadSingle() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + return (float)ReadDoubleInternal(type); + } + public float? ReadNullableSingle() { - if (value >= byte.MinValue && value <= byte.MaxValue) + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; + return (float)ReadDoubleInternal(type); + } + public double ReadDouble() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + return ReadDoubleInternal(type); + } + public double? ReadNullableDouble() + { + _stream.Read(_buffer, 0, 1); + ETFType type = (ETFType)_buffer[0]; + if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; + return ReadDoubleInternal(type); + } + public double ReadDoubleInternal(ETFType type) + { + throw new NotImplementedException(); + } + + public string ReadString() + { + throw new NotImplementedException(); + } + public byte[] ReadByteArray() + { + throw new NotImplementedException(); + } + + #region Emit + private static Func CreateDeserializer(Type type, TypeInfo typeInfo) + where T : new() + { + var method = new DynamicMethod("DeserializeETF", type, new[] { typeof(ETFReader) }, true); + var generator = method.GetILGenerator(); + + generator.Emit(OpCodes.Ldarg_0); //ETFReader(this) + EmitReadValue(generator, type, typeInfo, true); + + generator.Emit(OpCodes.Ret); + return method.CreateDelegate(typeof(Func)) as Func; + } + private static void EmitReadValue(ILGenerator generator, Type type, TypeInfo typeInfo, bool isTop) + { + //Convert enum types to their base type + if (typeInfo.IsEnum) { - _buffer[0] = (byte)ETFType.SMALL_INTEGER_EXT; - _buffer[1] = (byte)value; - _stream.Write(_buffer, 0, 2); + type = Enum.GetUnderlyingType(type); + typeInfo = type.GetTypeInfo(); } - else if (value >= int.MinValue && value <= int.MaxValue) + //Primitives/Enums + if (!typeInfo.IsEnum && IsType(type, typeof(sbyte), typeof(byte), typeof(short), + typeof(ushort), typeof(int), typeof(uint), typeof(long), + typeof(ulong), typeof(double), typeof(bool), typeof(string), + typeof(sbyte?), typeof(byte?), typeof(short?), typeof(ushort?), + typeof(int?), typeof(uint?), typeof(long?), typeof(ulong?), + typeof(bool?), typeof(float?), typeof(double?) + /*typeof(object), typeof(DateTime)*/)) { - _buffer[0] = (byte)ETFType.INTEGER_EXT; - _buffer[1] = (byte)(value >> 24); - _buffer[2] = (byte)(value >> 16); - _buffer[3] = (byte)(value >> 8); - _buffer[4] = (byte)value; - _stream.Write(_buffer, 0, 5); + //No conversion needed + generator.EmitCall(OpCodes.Call, GetReadMethod(type), null); } - else + //Dictionaries + /*else if (!typeInfo.IsValueType && typeInfo.ImplementedInterfaces + .Any(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(IDictionary<,>))) + { + generator.EmitCall(OpCodes.Call, _writeDictionaryTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); + } + //Enumerable + else if (!typeInfo.IsValueType && typeInfo.ImplementedInterfaces + .Any(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(IEnumerable<>))) { - _buffer[0] = (byte)ETFType.SMALL_BIG_EXT; - if (value < 0) + generator.EmitCall(OpCodes.Call, _writeEnumerableTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); + } + //Nullable Structs + else if (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Nullable<>) && + typeInfo.GenericTypeParameters[0].GetTypeInfo().IsValueType) + { + generator.EmitCall(OpCodes.Call, _writeNullableTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); + } + //Structs/Classes + else if (typeInfo.IsClass || (typeInfo.IsValueType && !typeInfo.IsPrimitive)) + { + if (isTop) { - _buffer[2] = 1; //Is negative - value = -value; - } + typeInfo.ForEachField(f => + { + string name; + if (!f.IsPublic || !IsETFProperty(f, out name)) return; - byte bytes = 0; - while (value > 0) - _buffer[3 + bytes++] = (byte)(value >>= 8); - _buffer[1] = bytes; //Encoded bytes + generator.Emit(OpCodes.Ldarg_0); //ETFReader(this) + generator.Emit(OpCodes.Ldstr, name); //ETFReader(this), name + generator.EmitCall(OpCodes.Call, GetWriteMethod(typeof(string)), null); + generator.Emit(OpCodes.Ldarg_0); //ETFReader(this) + generator.Emit(OpCodes.Ldarg_1); //ETFReader(this), obj + generator.Emit(OpCodes.Ldfld, f); //ETFReader(this), obj.fieldValue + EmitWriteValue(generator, f.FieldType, f.FieldType.GetTypeInfo(), false); + }); - _stream.Write(_buffer, 0, 3 + bytes); - } + typeInfo.ForEachProperty(p => + { + string name; + if (!p.CanRead || !p.GetMethod.IsPublic || !IsETFProperty(p, out name)) return; + + generator.Emit(OpCodes.Ldarg_0); //ETFReader(this) + generator.Emit(OpCodes.Ldstr, name); //ETFReader(this), name + generator.EmitCall(OpCodes.Call, GetWriteMethod(typeof(string)), null); + generator.Emit(OpCodes.Ldarg_0); //ETFReader(this) + generator.Emit(OpCodes.Ldarg_1); //ETFReader(this), obj + generator.EmitCall(OpCodes.Callvirt, p.GetMethod, null); //ETFReader(this), obj.propValue + EmitWriteValue(generator, p.PropertyType, p.PropertyType.GetTypeInfo(), false); + }); + } + else + { + //While we could drill deeper and make a large serializer that also serializes all subclasses, + //it's more efficient to serialize on a per-type basis via another Write call. + generator.EmitCall(OpCodes.Call, _writeTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); + } + }*/ + //Unsupported (decimal, char) + else + throw new InvalidOperationException($"Deserializing {type.Name} is not supported."); } - public void Write(ulong value) + + private static bool IsType(Type type, params Type[] types) { - if (value <= byte.MaxValue) + for (int i = 0; i < types.Length; i++) { - _buffer[0] = (byte)ETFType.SMALL_INTEGER_EXT; - _buffer[1] = (byte)value; - _stream.Write(_buffer, 0, 2); + if (type == types[i]) + return true; } - else if (value <= int.MaxValue) + return false; + } + private static bool IsETFProperty(FieldInfo f, out string name) + { + var attrib = f.CustomAttributes.Where(x => x.AttributeType == typeof(JsonPropertyAttribute)).FirstOrDefault(); + if (attrib != null) { - _buffer[0] = (byte)ETFType.INTEGER_EXT; - _buffer[1] = (byte)(value >> 24); - _buffer[2] = (byte)(value >> 16); - _buffer[3] = (byte)(value >> 8); - _buffer[4] = (byte)value; - _stream.Write(_buffer, 0, 5); + name = attrib.ConstructorArguments.FirstOrDefault().Value as string ?? f.Name; + return true; } - else + name = null; + return false; + } + private static bool IsETFProperty(PropertyInfo p, out string name) + { + var attrib = p.CustomAttributes.Where(x => x.AttributeType == typeof(JsonPropertyAttribute)).FirstOrDefault(); + if (attrib != null) { - _buffer[0] = (byte)ETFType.SMALL_BIG_EXT; - _buffer[2] = 0; //Always positive - - byte bytes = 0; - while (value > 0) - _buffer[3 + bytes++] = (byte)(value >>= 8); - _buffer[1] = bytes; //Encoded bytes - - _stream.Write(_buffer, 0, 3 + bytes); + name = attrib.ConstructorArguments.FirstOrDefault().Value as string ?? p.Name; + return true; } + name = null; + return false; + } + + private static MethodInfo GetReadMethod(string name) + => typeof(ETFReader).GetTypeInfo().GetDeclaredMethods(name).Single(); + private static MethodInfo GetReadMethod(Type type) + { + MethodInfo method; + if (_readMethods.TryGetValue(type, out method)) + return method; + return null; } + private static Dictionary GetPrimitiveReadMethods() + { + return new Dictionary + { + { typeof(bool), GetReadMethod(nameof(ReadBool)) }, + { typeof(bool?), GetReadMethod(nameof(ReadNullableBool)) }, + { typeof(byte), GetReadMethod(nameof(ReadByte)) }, + { typeof(byte?), GetReadMethod(nameof(ReadNullableByte)) }, + { typeof(sbyte), GetReadMethod(nameof(ReadSByte)) }, + { typeof(sbyte?), GetReadMethod(nameof(ReadNullableSByte)) }, + { typeof(short), GetReadMethod(nameof(ReadShort)) }, + { typeof(short?), GetReadMethod(nameof(ReadNullableShort)) }, + { typeof(ushort), GetReadMethod(nameof(ReadUShort)) }, + { typeof(ushort?), GetReadMethod(nameof(ReadNullableUShort)) }, + { typeof(int), GetReadMethod(nameof(ReadInt)) }, + { typeof(int?), GetReadMethod(nameof(ReadNullableInt)) }, + { typeof(uint), GetReadMethod(nameof(ReadUInt)) }, + { typeof(uint?), GetReadMethod(nameof(ReadNullableUInt)) }, + { typeof(long), GetReadMethod(nameof(ReadLong)) }, + { typeof(long?), GetReadMethod(nameof(ReadNullableLong)) }, + { typeof(ulong), GetReadMethod(nameof(ReadULong)) }, + { typeof(ulong?), GetReadMethod(nameof(ReadNullableULong)) }, + { typeof(float), GetReadMethod(nameof(ReadSingle)) }, + { typeof(float?), GetReadMethod(nameof(ReadNullableSingle)) }, + { typeof(double), GetReadMethod(nameof(ReadDouble)) }, + { typeof(double?), GetReadMethod(nameof(ReadNullableDouble)) }, + }; + } + #endregion + + #region IDisposable + private bool _isDisposed = false; - public void Write(float value) => Write((double)value); - public unsafe void Write(double value) + protected virtual void Dispose(bool disposing) { - ulong value2 = *(ulong*)&value; - _buffer[0] = (byte)ETFType.NEW_FLOAT_EXT; - _buffer[1] = (byte)(value2 >> 56); - _buffer[2] = (byte)(value2 >> 48); - _buffer[3] = (byte)(value2 >> 40); - _buffer[4] = (byte)(value2 >> 32); - _buffer[5] = (byte)(value2 >> 24); - _buffer[6] = (byte)(value2 >> 16); - _buffer[7] = (byte)(value2 >> 8); - _buffer[8] = (byte)value2; - _stream.Write(_buffer, 0, 9); + if (!_isDisposed) + { + if (disposing) + { + if (_leaveOpen) + _stream.Flush(); + else + _stream.Dispose(); + } + _isDisposed = true; + } } - public void Write(DateTime value) => Write((ulong)((value.Ticks - _epochTime.Ticks) / TimeSpan.TicksPerSecond)); + public void Dispose() => Dispose(true); + #endregion } -}*/ \ No newline at end of file +} \ No newline at end of file