@@ -13,23 +13,13 @@ namespace Discord.ETF
public unsafe class ETFWriter : IDisposable
{
private readonly static byte[] _nilBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 3, (byte)'n', (byte)'i', (byte)'l' };
private readonly static byte[] _nilExtBytes = new byte[] { (byte)ETFType.NIL_EXT};
private readonly static byte[] _falseBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 5, (byte)'f', (byte)'a', (byte)'l', (byte)'s', (byte)'e' };
private readonly static byte[] _trueBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 4, (byte)'t', (byte)'r', (byte)'u', (byte)'e' };
private readonly static MethodInfo _writeTMethod = typeof(ETFWriter).GetTypeInfo()
.GetDeclaredMethods(nameof(Write))
.Where(x => x.IsGenericMethodDefinition && x.GetParameters()[0].ParameterType == x.GetGenericArguments()[0])
.Single();
private readonly static MethodInfo _writeNullableTMethod = typeof(ETFWriter).GetTypeInfo()
.GetDeclaredMethods(nameof(Write))
.Where(x =>
{
if (!x.IsGenericMethodDefinition) return false;
var p = x.GetParameters()[0].ParameterType.GetTypeInfo();
return p.IsGenericType && p.GetGenericTypeDefinition() == typeof(Nullable<>);
})
.Single();
private readonly static MethodInfo _writeTMethod = GetGenericWriteMethod(null);
private readonly static MethodInfo _writeNullableTMethod = GetGenericWriteMethod(typeof(Nullable<>));
private readonly static MethodInfo _writeDictionaryTMethod = GetGenericWriteMethod(typeof(IDictionary<,>));
private readonly static MethodInfo _writeEnumerableTMethod = GetGenericWriteMethod(typeof(IEnumerable<>));
private readonly static DateTime _epochTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private readonly Stream _stream;
@@ -58,29 +48,6 @@ namespace Discord.ETF
_serializers = new ConcurrentDictionary<Type, Delegate>();
_indirectSerializers = new ConcurrentDictionary<Type, Delegate>();
}
enum EnumTest1 { A, B, C }
public static byte[] Test()
{
using (var stream = new MemoryStream())
{
using (var writer = new ETFWriter(stream))
{
var request = new API.Client.Rest.SendMessageRequest(109384029348)
{
Content = "TestMsg",
Nonce = null,
IsTTS = false
};
writer.Write(request);
/*writer.Write<EnumTest1>((EnumTest1?)EnumTest1.C);
writer.Write((object)(EnumTest1?)EnumTest1.C);
writer.Write<EnumTest1>((EnumTest1?)null);
writer.Write((object)(EnumTest1?)null);*/
}
return stream.ToArray();
}
}
public void Write(bool value)
{
@@ -177,18 +144,19 @@ namespace Discord.ETF
_stream.Write(_buffer, 0, 9);
}
public void Write(DateTime value) => Write((ulong)((value.Ticks - _epochTime.Ticks) / TimeSpan.TicksPerMillis econd));
public void Write(DateTime value) => Write((ulong)((value.Ticks - _epochTime.Ticks) / TimeSpan.TicksPerS econd));
public void Write(byte? value) { if (value.HasValue) Write((ulong )value.Value); else WriteNil(); }
public void Write(bool? value) { if (value.HasValue) Write((bool )value.Value); else WriteNil(); }
public void Write(sbyte? value) { if (value.HasValue) Write((long)value.Value); else WriteNil(); }
public void Write(ushort ? value) { if (value.HasValue) Write((ulong)value.Value); else WriteNil(); }
public void Write(byte ? value) { if (value.HasValue) Write((ulong)value.Value); else WriteNil(); }
public void Write(short? value) { if (value.HasValue) Write((long)value.Value); else WriteNil(); }
public void Write(uin t? value) { if (value.HasValue) Write((ulong)value.Value); else WriteNil(); }
public void Write(ushor t? value) { if (value.HasValue) Write((ulong)value.Value); else WriteNil(); }
public void Write(int? value) { if (value.HasValue) Write(value.Value); else WriteNil(); }
public void Write(ulong? value) { if (value.HasValue) Write( value.Value); else WriteNil(); }
public void Write(uint? value) { if (value.HasValue) Write((ulong) value.Value); else WriteNil(); }
public void Write(long? value) { if (value.HasValue) Write(value.Value); else WriteNil(); }
public void Write(float? value) { if (value.HasValue) Write((double) value.Value); else WriteNil(); }
public void Write(ulong? value) { if (value.HasValue) Write( value.Value); else WriteNil(); }
public void Write(double? value) { if (value.HasValue) Write(value.Value); else WriteNil(); }
public void Write(float? value) { if (value.HasValue) Write((double)value.Value); else WriteNil(); }
public void Write(DateTime? value) { if (value.HasValue) Write(value.Value); else WriteNil(); }
public void Write(byte[] value)
@@ -251,10 +219,13 @@ namespace Discord.ETF
_buffer[2] = (byte)((length >> 16) & 0xFF);
_buffer[3] = (byte)((length >> 8) & 0xFF);
_buffer[4] = (byte)(length & 0xFF);
_stream.Write(_buffer, 0, 5);
for (int i = 0; i < array.Length; i++)
Write(array[i]);
WriteNilExt();
_stream.Write(_buffer, 0, 5);
_buffer[0] = (byte)ETFType.NIL_EXT;
_stream.Write(_buffer, 0, 1);
}
else
WriteNil();
@@ -269,12 +240,13 @@ namespace Discord.ETF
_buffer[2] = (byte)((length >> 16) & 0xFF);
_buffer[3] = (byte)((length >> 8) & 0xFF);
_buffer[4] = (byte)(length & 0xFF);
_stream.Write(_buffer, 0, 5);
foreach (var pair in obj)
{
Write(pair.Key);
Write(pair.Value);
}
_stream.Write(_buffer, 0, 5);
}
else
WriteNil();
@@ -292,147 +264,137 @@ namespace Discord.ETF
WriteNil();
}
private void WriteNil() => _stream.Write(_nilBytes, 0, _nilBytes.Length);
public virtual void Flush() => _stream.Flush();
public virtual long Seek(int offset, SeekOrigin origin) => _stream.Seek(offset, origin);
private Action<ETFWriter, T> CreateSerializer<T>(Type type, TypeInfo typeInfo, bool isObject)
#region Emit
private Action<ETFWriter, T> CreateSerializer<T>(Type type, TypeInfo typeInfo, bool isDirect)
{
var method = new DynamicMethod(!isObj ect ? "SerializeETF" : "SerializeIndirectETF",
null, new[] { typeof(ETFWriter), isObject? typeof(object) : type }, true);
var method = new DynamicMethod(isDir ect ? "SerializeETF" : "SerializeIndirectETF",
null, new[] { typeof(ETFWriter), isDirect ? type : typeof(object) }, true);
var generator = method.GetILGenerator();
if (typeInfo.IsPrimitive || typeInfo.IsEnum)
generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this)
generator.Emit(OpCodes.Ldarg_1); //ETFWriter(this), value
if (!isDirect)
{
generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this)
generator.Emit(OpCodes.Ldarg_1); //ETFWriter(this), value
if (isObject && typeInfo.IsValueType)
generator.Emit(OpCodes.Unbox_Any, type); //ETFWriter(this), value
EmitETFWriteValue(generator, type, typeInfo);
if (typeInfo.IsValueType) //Unbox value types
generator.Emit(OpCodes.Unbox_Any, type); //ETFWriter(this), real_ value
else //Cast reference types
generator.Emit(OpCodes.Castclass, type); //ETFWriter(this), real_ value
generator.EmitCall(OpCodes.Call, _writeTMethod.MakeGenericMethod(type), null); //Call generic version
}
else
{
//Scan for certain interfaces
Type dictionaryI = null, enumerableI = null;
foreach (var i in typeInfo.ImplementedInterfaces
.Where(x => x.IsConstructedGenericType))
{
if (i.GetGenericTypeDefinition() == typeof(IDictionary<,>))
{
//TODO: Emit null check
dictionaryI = i;
break;
}
else if (i.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
//TODO: Emit null check
enumerableI = i;
break;
}
}
if (dictionaryI != null)
{
throw new NotImplementedException();
}
else if (enumerableI != null)
{
throw new NotImplementedException();
}
else
{
//TODO: Add field/property names
typeInfo.ForEachField(f =>
{
string name;
if (!f.IsPublic || !IsETFProperty(f, out name)) return;
generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this)
generator.Emit(OpCodes.Ldstr, name); //ETFWriter(this), name
generator.EmitCall(OpCodes.Call, GetWriteMethod(typeof(string)), null);
generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this)
generator.Emit(OpCodes.Ldarg_1); //ETFWriter(this), obj
generator.Emit(OpCodes.Ldfld, f); //ETFWriter(this), obj.fieldValue
if (isObject && typeInfo.IsValueType)
generator.Emit(OpCodes.Unbox_Any, type); //ETFWriter(this), obj.fieldValue
EmitETFWriteValue(generator, f.FieldType);
});
typeInfo.ForEachProperty(p =>
{
string name;
if (!p.CanRead || !p.GetMethod.IsPublic || !IsETFProperty(p, out name)) return;
generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this)
generator.Emit(OpCodes.Ldstr, name); //ETFWriter(this), name
generator.EmitCall(OpCodes.Call, GetWriteMethod(typeof(string)), null);
generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this)
generator.Emit(OpCodes.Ldarg_1); //ETFWriter(this), obj
generator.EmitCall(OpCodes.Callvirt, p.GetMethod, null); //ETFWriter(this), obj.propValue
if (isObject && typeInfo.IsValueType)
generator.Emit(OpCodes.Unbox_Any, type); //ETFWriter(this), obj.propValue
EmitETFWriteValue(generator, p.PropertyType);
});
}
}
EmitWriteValue(generator, type, typeInfo, true);
generator.Emit(OpCodes.Ret);
return method.CreateDelegate(typeof(Action<ETFWriter, T>)) as Action<ETFWriter, T>;
}
private void EmitETFWriteValue(ILGenerator generator, Type type, TypeInfo typeInfo = null)
{
if (typeInfo == null)
typeInfo = type.GetTypeInfo();
private void EmitWriteValue(ILGenerator generator, Type type, TypeInfo typeInfo, bool isTop)
{
//Convert enum types to their base type
if (typeInfo.IsEnum)
{
type = Enum.GetUnderlyingType(type);
typeInfo = type.GetTypeInfo();
}
}
//Check if this type already has a direct call or simple conversion
//Primitives/Enums
Type targetType = null;
if (!typeInfo.IsEnum && IsType(type, 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)))
targetType = type; //Direct
{
//No conversion needed
targetType = type;
}
else if (IsType(type, typeof(sbyte), typeof(short), typeof(int)))
{
//Convert to long
generator.Emit(OpCodes.Conv_I8);
targetType = typeof(long);
}
else if (IsType(type, typeof(byte), typeof(ushort), typeof(uint)))
{
//Convert to ulong
generator.Emit(OpCodes.Conv_U8);
targetType = typeof(ulong);
}
else if (IsType(type, typeof(float)))
{
//Convert to double
generator.Emit(OpCodes.Conv_R8);
targetType = typeof(double);
}
//Primitives/Enums
if (targetType != null)
generator.EmitCall(OpCodes.Call, GetWriteMethod(targetType), null);
//Nullable Non-Primitives
else if (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Nullable<>))
//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<>)))
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))
generator.EmitCall(OpCodes.Call, _writeTMethod.MakeGenericMethod(type), null);
{
if (isTop)
{
typeInfo.ForEachField(f =>
{
string name;
if (!f.IsPublic || !IsETFProperty(f, out name)) return;
generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this)
generator.Emit(OpCodes.Ldstr, name); //ETFWriter(this), name
generator.EmitCall(OpCodes.Call, GetWriteMethod(typeof(string)), null);
generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this)
generator.Emit(OpCodes.Ldarg_1); //ETFWriter(this), obj
generator.Emit(OpCodes.Ldfld, f); //ETFWriter(this), obj.fieldValue
EmitWriteValue(generator, f.FieldType, f.FieldType.GetTypeInfo(), false);
});
typeInfo.ForEachProperty(p =>
{
string name;
if (!p.CanRead || !p.GetMethod.IsPublic || !IsETFProperty(p, out name)) return;
generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this)
generator.Emit(OpCodes.Ldstr, name); //ETFWriter(this), name
generator.EmitCall(OpCodes.Call, GetWriteMethod(typeof(string)), null);
generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this)
generator.Emit(OpCodes.Ldarg_1); //ETFWriter(this), obj
generator.EmitCall(OpCodes.Callvirt, p.GetMethod, null); //ETFWriter(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<T> call.
generator.EmitCall(OpCodes.Call, _writeTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null);
}
}
//Unsupported (decimal, char)
else
throw new InvalidOperationException($"Serializing {type.Name} is not supported.");
}
private bool IsType(Type type, params Type[] types)
private static bool IsType(Type type, params Type[] types)
{
for (int i = 0; i < types.Length; i++)
{
@@ -441,17 +403,7 @@ namespace Discord.ETF
}
return false;
}
private MethodInfo GetWriteMethod(Type paramType)
{
return typeof(ETFWriter).GetTypeInfo().GetDeclaredMethods(nameof(Write))
.Where(x => x.GetParameters()[0].ParameterType == paramType)
.FirstOrDefault();
}
private void WriteNil() => _stream.Write(_nilBytes, 0, _nilBytes.Length);
private void WriteNilExt() => _stream.Write(_nilExtBytes, 0, _nilExtBytes.Length);
private bool IsETFProperty(FieldInfo f, out string name)
private static bool IsETFProperty(FieldInfo f, out string name)
{
var attrib = f.CustomAttributes.Where(x => x.AttributeType == typeof(JsonPropertyAttribute)).FirstOrDefault();
if (attrib != null)
@@ -462,7 +414,7 @@ namespace Discord.ETF
name = null;
return false;
}
private bool IsETFProperty(PropertyInfo p, out string name)
private static bool IsETFProperty(PropertyInfo p, out string name)
{
var attrib = p.CustomAttributes.Where(x => x.AttributeType == typeof(JsonPropertyAttribute)).FirstOrDefault();
if (attrib != null)
@@ -474,6 +426,36 @@ namespace Discord.ETF
return false;
}
private static MethodInfo GetWriteMethod(Type paramType)
{
return typeof(ETFWriter).GetTypeInfo().GetDeclaredMethods(nameof(Write))
.Where(x => x.GetParameters()[0].ParameterType == paramType)
.FirstOrDefault();
}
private static MethodInfo GetGenericWriteMethod(Type genericType)
{
if (genericType == null)
{
return typeof(ETFWriter).GetTypeInfo()
.GetDeclaredMethods(nameof(Write))
.Where(x => x.IsGenericMethodDefinition && x.GetParameters()[0].ParameterType == x.GetGenericArguments()[0])
.Single();
}
else
{
return typeof(ETFWriter).GetTypeInfo()
.GetDeclaredMethods(nameof(Write))
.Where(x =>
{
if (!x.IsGenericMethodDefinition) return false;
var p = x.GetParameters()[0].ParameterType.GetTypeInfo();
return p.IsGenericType && p.GetGenericTypeDefinition() == genericType;
})
.Single();
}
}
#endregion
#region IDisposable
private bool _isDisposed = false;