Files
mROA/mROA.Cbor/CborSerializationToolkit.cs
T

522 lines
17 KiB
C#
Raw Normal View History

2025-02-24 12:48:31 +03:00
using System;
using System.Collections;
using System.Collections.Generic;
2025-06-30 11:45:59 +03:00
using System.Diagnostics;
2025-07-26 19:51:54 +03:00
using System.Formats.Cbor;
2025-02-24 12:48:31 +03:00
using System.Linq;
using System.Reflection;
2025-08-16 19:51:35 +03:00
using Microsoft.Extensions.Options;
2025-02-24 12:48:31 +03:00
using mROA.Abstract;
using mROA.Implementation;
using mROA.Implementation.Attributes;
2025-07-09 17:15:55 +03:00
using mROA.Implementation.CommandExecution;
2025-02-24 12:48:31 +03:00
namespace mROA.Cbor
{
public class CborSerializationToolkit : IContextualSerializationToolKit
2025-02-24 12:48:31 +03:00
{
2025-08-06 14:32:48 +03:00
private readonly CborWriter _writer = new(initialCapacity: 2048);
2025-08-16 19:51:35 +03:00
private readonly int _offset;
2025-07-26 19:51:54 +03:00
2025-08-16 19:51:35 +03:00
public CborSerializationToolkit(IOptions<SerializationBufferOffset> offsetOptions) : this(offsetOptions.Value.Offset)
{
}
2025-07-26 19:51:54 +03:00
private readonly IOrdinaryStructureParser[] _parsers =
{
2025-08-03 00:32:32 +03:00
new CallRequestParser(), new FinalCommandExecutionParser(),
2025-07-10 10:25:40 +03:00
new FinalCommandExecutionResultlessParser()
};
2025-07-26 19:51:54 +03:00
private readonly Dictionary<Type, List<PropertyInfo>> _propertiesCache = new();
2025-08-16 19:51:35 +03:00
public CborSerializationToolkit(int offset)
{
_offset = offset;
}
2025-06-30 11:45:59 +03:00
public static TimeSpan SerializationTime = TimeSpan.Zero;
2025-07-10 10:25:40 +03:00
private bool FindParser(Type t, out IOrdinaryStructureParser parser)
{
2025-08-03 00:32:32 +03:00
if (t == typeof(CallRequest))
2025-07-10 10:25:40 +03:00
{
2025-07-27 23:46:54 +03:00
parser = _parsers[0];
2025-07-10 10:25:40 +03:00
return true;
}
if (t == typeof(FinalCommandExecution<object>))
{
2025-07-27 23:46:54 +03:00
parser = _parsers[1];
2025-07-10 10:25:40 +03:00
return true;
}
if (t == typeof(FinalCommandExecution))
{
2025-07-27 23:46:54 +03:00
parser = _parsers[2];
2025-07-10 10:25:40 +03:00
return true;
}
parser = null;
return false;
}
2025-07-26 19:51:54 +03:00
public byte[] Serialize(object objectToSerialize, IEndPointContext context)
2025-02-24 12:48:31 +03:00
{
2025-07-26 19:51:54 +03:00
byte[] result;
lock (_writer)
{
_writer.Reset();
WriteData(objectToSerialize, _writer, context);
2025-08-16 19:51:35 +03:00
result = new byte[_offset + _writer.BytesWritten];
var span = result.AsSpan();
_writer.Encode(span[_offset..]);
2025-07-26 19:51:54 +03:00
}
2025-08-16 12:14:03 +03:00
2025-06-30 11:45:59 +03:00
return result;
2025-02-24 12:48:31 +03:00
}
public int Serialize(object objectToSerialize, Span<byte> destination, IEndPointContext context)
2025-02-24 12:48:31 +03:00
{
2025-07-26 19:51:54 +03:00
lock (_writer)
{
_writer.Reset();
WriteData(objectToSerialize, _writer, context);
return _writer.Encode(destination);
}
2025-02-24 12:48:31 +03:00
}
public T Deserialize<T>(byte[] rawData, IEndPointContext? context)
2025-02-24 12:48:31 +03:00
{
return (T)Deserialize(rawData, typeof(T), context) ?? default;
2025-02-24 12:48:31 +03:00
}
public object? Deserialize(byte[] rawData, Type type, IEndPointContext? context)
2025-02-24 12:48:31 +03:00
{
return Deserialize(rawData.AsMemory(), type, context);
2025-02-24 12:48:31 +03:00
}
public T Deserialize<T>(ReadOnlyMemory<byte> rawMemory, IEndPointContext? context)
2025-02-24 12:48:31 +03:00
{
return (T)Deserialize(rawMemory, typeof(T), context);
2025-02-24 12:48:31 +03:00
}
public object? Deserialize(ReadOnlyMemory<byte> rawMemory, Type type, IEndPointContext? context)
2025-02-24 12:48:31 +03:00
{
2025-08-09 13:16:12 +03:00
try
{
var reader = new CborReader(rawMemory);
return ReadData(reader, type, context);
}
2025-08-16 19:51:35 +03:00
catch (Exception)
2025-08-09 13:16:12 +03:00
{
2025-09-24 00:11:10 +03:00
Console.WriteLine($"Bad deserialization for type {type}. Bytes: {BitConverter.ToString(rawMemory.ToArray())}");
2025-08-09 13:16:12 +03:00
throw;
}
2025-02-24 12:48:31 +03:00
}
2025-03-06 14:10:52 +03:00
public object? Cast(object? nonCasted, Type type, IEndPointContext? context)
2025-02-24 12:48:31 +03:00
{
2025-03-06 14:10:52 +03:00
if (nonCasted == null)
return null;
if (nonCasted.GetType() == type)
return nonCasted;
if (nonCasted is PreParsedValue preParsed)
return preParsed.ToObject(type, context);
2025-03-13 08:43:48 +03:00
2025-07-27 00:14:15 +03:00
if (type == typeof(RequestId))
2025-03-13 08:43:48 +03:00
{
2025-07-27 00:14:15 +03:00
return new RequestId((byte[])nonCasted);
2025-03-13 08:43:48 +03:00
}
2025-08-07 19:14:17 +03:00
if (type.IsInterface)
{
return ReadSharedShell(type, context, (ulong)nonCasted);
}
2025-08-16 12:14:03 +03:00
return Convert.ChangeType(nonCasted, type);
2025-02-24 12:48:31 +03:00
}
2025-08-06 14:32:48 +03:00
public IContextualSerializationToolKit Clone()
{
2025-08-16 19:51:35 +03:00
return new CborSerializationToolkit(_offset) ;
2025-08-06 14:32:48 +03:00
}
2025-07-09 17:15:55 +03:00
public void WriteData(object? obj, CborWriter writer, IEndPointContext? context)
2025-02-24 12:48:31 +03:00
{
2025-07-10 10:25:40 +03:00
if (obj is not null && FindParser(obj.GetType(), out var parser))
2025-07-09 17:15:55 +03:00
{
parser.Write(writer, obj, context, this);
return;
}
2025-07-26 19:51:54 +03:00
2025-02-24 12:48:31 +03:00
switch (obj)
{
case int i:
writer.WriteInt32(i);
break;
case long l:
writer.WriteInt64(l);
break;
case float f:
writer.WriteSingle(f);
break;
case double d:
writer.WriteDouble(d);
break;
case bool b:
writer.WriteBoolean(b);
break;
case string s:
writer.WriteTextString(s);
break;
case null:
writer.WriteNull();
break;
case uint ui:
writer.WriteUInt32(ui);
break;
case ulong ul:
writer.WriteUInt64(ul);
break;
case DateTimeOffset dto:
writer.WriteDateTimeOffset(dto);
break;
2025-07-27 00:14:15 +03:00
case RequestId g:
2025-07-27 23:46:54 +03:00
// writer.WriteByteString(g.ToByteArray());
g.WriteToCborInline(writer);
break;
2025-02-24 12:48:31 +03:00
case byte[] bytes:
writer.WriteByteString(bytes);
break;
case IDictionary dictionary:
WriteDictionary(dictionary, writer, context);
2025-02-24 12:48:31 +03:00
break;
case IList enumerable:
WriteList(enumerable, writer, context);
2025-02-24 12:48:31 +03:00
break;
case ISharedObjectShell sharedObject:
if (context != null)
sharedObject.EndPointContext = context;
WriteObject(sharedObject, writer, context);
2025-02-24 12:48:31 +03:00
break;
default:
2025-08-16 12:14:03 +03:00
var objType = obj.GetType();
if (objType.IsEnum)
{
2025-08-16 12:14:03 +03:00
var basicType = Enum.GetUnderlyingType(obj.GetType());
if (basicType == typeof(byte))
{
writer.WriteInt32((byte)obj);
}
else if (basicType == typeof(int))
{
writer.WriteInt32((byte)obj);
}
break;
}
WriteObject(obj, writer, context);
2025-02-24 12:48:31 +03:00
break;
}
}
2025-07-09 17:15:55 +03:00
public void WriteList(IList list, CborWriter writer, IEndPointContext? context)
2025-02-24 12:48:31 +03:00
{
writer.WriteStartArray(list.Count);
2025-07-09 17:29:57 +03:00
for (var index = 0; index < list.Count; index++)
{
var element = list[index];
WriteData(element, writer, context);
2025-07-09 17:29:57 +03:00
}
2025-02-24 12:48:31 +03:00
writer.WriteEndArray();
}
2025-07-09 17:15:55 +03:00
public void WriteDictionary(IDictionary dictionary, CborWriter writer, IEndPointContext? context)
2025-02-24 12:48:31 +03:00
{
writer.WriteStartMap(dictionary.Count);
var keysEnumerator = dictionary.Keys.GetEnumerator();
var valuesEnumerator = dictionary.Values.GetEnumerator();
for (int i = 0; i < dictionary.Count; i++)
{
keysEnumerator.MoveNext();
valuesEnumerator.MoveNext();
WriteData(keysEnumerator.Current, writer, context);
WriteData(valuesEnumerator.Current, writer, context);
2025-02-24 12:48:31 +03:00
}
2025-02-24 12:48:31 +03:00
writer.WriteEndMap();
(keysEnumerator as IDisposable)?.Dispose();
(valuesEnumerator as IDisposable)?.Dispose();
2025-02-24 12:48:31 +03:00
}
2025-07-09 17:15:55 +03:00
public void WriteObject(object obj, CborWriter writer, IEndPointContext? context)
2025-02-24 12:48:31 +03:00
{
var type = obj.GetType();
if (obj is IShared)
{
2025-05-07 09:55:15 +03:00
var sharedShell = typeof(SharedObjectShellShell<object>);
var so =
Activator.CreateInstance(sharedShell, obj, context) as
ISharedObjectShell;
writer.WriteUInt64(so.Identifier.Flat);
return;
}
2025-07-08 23:33:37 +03:00
var properties = GetAvailableProperties(type);
var values = properties.Select(property => property.GetValue(obj)).ToList();
WriteList(values, writer, context);
}
2025-07-09 17:15:55 +03:00
public object? ReadData(CborReader reader, Type? type, IEndPointContext? context)
{
2025-07-10 10:25:40 +03:00
if (type is not null && FindParser(type, out var parser))
2025-07-09 17:15:55 +03:00
{
return parser.Read(reader, context, this);
}
2025-07-26 19:51:54 +03:00
var state = reader.PeekState();
switch (state)
{
case CborReaderState.Boolean:
return reader.ReadBoolean();
case CborReaderState.UnsignedInteger:
case CborReaderState.NegativeInteger:
if (type == typeof(int) || type is { IsEnum: true })
return reader.ReadInt32();
if (type == typeof(long))
return reader.ReadInt64();
if (type == typeof(uint))
return reader.ReadUInt32();
2025-08-07 19:14:17 +03:00
if (type.IsInterface)
{
return ReadSharedShell(type, context, reader.ReadUInt64());
}
2025-08-16 12:14:03 +03:00
return reader.ReadUInt64();
case CborReaderState.ByteString:
2025-07-27 00:14:15 +03:00
if (type == typeof(RequestId))
return new RequestId(reader.ReadByteString());
return reader.ReadByteString();
case CborReaderState.TextString:
return reader.ReadTextString();
case CborReaderState.Null:
reader.ReadNull();
return null;
case CborReaderState.DoublePrecisionFloat:
return reader.ReadDouble();
case CborReaderState.SinglePrecisionFloat:
return reader.ReadSingle();
case CborReaderState.HalfPrecisionFloat:
return type == typeof(float) ? reader.ReadSingle() : reader.ReadDouble();
case CborReaderState.StartArray:
if (type == null)
return ReadList(reader, null, context);
if (type.IsSubclassOf(typeof(ISharedObjectShell)))
return ReadSharedObject(reader, type, context);
if (typeof(IList).IsAssignableFrom(type) || type.IsArray)
return ReadList(reader, type, context);
return ReadObject(reader, type, context);
case CborReaderState.StartMap:
return ReadDictionary(reader, type, context);
}
if (type == typeof(DateTimeOffset))
return reader.ReadDateTimeOffset();
return null;
}
2025-07-09 17:15:55 +03:00
public IList ReadList(CborReader reader, Type? type, IEndPointContext? context)
{
var length = reader.ReadStartArray();
if (length != null)
{
var elementType = typeof(object);
if (type is { IsArray: true })
{
2025-02-25 08:57:58 +03:00
elementType = type.GetElementType();
Array values = Array.CreateInstance(elementType, length.Value);
for (int i = 0; i < length; i++)
{
values.SetValue(ReadData(reader, elementType, context), i);
}
reader.ReadEndArray();
return values;
}
if (typeof(IList).IsAssignableFrom(type))
elementType = type.GetGenericArguments()[0];
else elementType = typeof(object);
Type genericListType = typeof(List<>).MakeGenericType(elementType);
var list = (IList)Activator.CreateInstance(genericListType, length);
for (int i = 0; i < length; i++)
{
list.Add(ReadData(reader, elementType, context));
}
reader.ReadEndArray();
return list;
return null;
}
return null;
}
2025-07-09 17:15:55 +03:00
public IDictionary ReadDictionary(CborReader reader, Type type, IEndPointContext? context)
{
var dictionaryInstance = (Activator.CreateInstance(type) as IDictionary)!;
var length = reader.ReadStartArray();
if (length != null)
{
for (int i = 0; i < length; i++)
{
var key = ReadData(reader, type, context);
var value = ReadData(reader, type, context);
dictionaryInstance.Add(key, value);
}
}
return dictionaryInstance;
}
2025-07-09 17:15:55 +03:00
public object ReadObject(CborReader reader, Type type, IEndPointContext? context)
{
if (type == typeof(object))
{
return new PreParsedValue(ReadList(reader, null, context) as List<object>);
}
if (type.IsInterface)
{
var identifier = reader.ReadUInt64();
2025-08-07 19:14:17 +03:00
return ReadSharedShell(type, context, identifier);
}
var instance = Activator.CreateInstance(type)!;
FillObject(instance, type, reader, context);
return instance;
}
2025-08-07 19:14:17 +03:00
private static object ReadSharedShell(Type type, IEndPointContext? context, ulong identifier)
{
var sharedShell = typeof(SharedObjectShellShell<>).MakeGenericType(type);
var so =
Activator.CreateInstance(sharedShell) as
ISharedObjectShell;
if (context != null)
so.EndPointContext = context;
so.Identifier = ComplexObjectIdentifier.FromFlat(identifier);
return so.UniversalValue;
}
private ISharedObjectShell ReadSharedObject(CborReader reader, Type type, IEndPointContext? context)
{
var sharedObject = (Activator.CreateInstance(type) as ISharedObjectShell)!;
if (context != null)
{
sharedObject.EndPointContext = context;
}
FillObject(sharedObject, type, reader, context);
return sharedObject;
}
2025-07-09 17:15:55 +03:00
public void FillObject(object obj, Type type, CborReader reader, IEndPointContext? context)
{
2025-07-08 23:33:37 +03:00
var properties = GetAvailableProperties(type);
var length = reader.ReadStartArray();
try
{
#if TRACE
Console.WriteLine($"Reading list of {length} objects, {properties.Count} properties found");
#endif
for (var index = 0; index < length; index++)
{
var property = properties[index];
var value = ReadData(reader, property.PropertyType, context);
2025-08-16 12:14:03 +03:00
if (property.PropertyType.IsEnum)
{
if (property.PropertyType.GetEnumUnderlyingType() == typeof(byte))
{
value = Convert.ChangeType(value, typeof(byte));
}
}
2025-08-16 19:51:35 +03:00
property.SetValue(obj, value);
}
2025-07-09 12:01:08 +03:00
if (reader.PeekState() == CborReaderState.EndArray)
reader.ReadEndArray();
}
catch (Exception e)
{
Console.WriteLine(e);
reader.ReadEndArray();
throw;
}
}
public static List<PropertyInfo> FilterProperties(PropertyInfo[] properties)
{
var finalProperties = new List<PropertyInfo>(properties.Length);
2025-07-26 19:51:54 +03:00
2025-07-09 17:29:57 +03:00
for (int i = 0; i < properties.Length; i++)
{
2025-07-09 17:29:57 +03:00
var property = properties[i];
2025-07-26 19:51:54 +03:00
if (property is not { CanRead: true, CanWrite: true } ||
property.GetCustomAttribute<SerializationIgnoreAttribute>() != null)
2025-07-09 17:29:57 +03:00
continue;
2025-07-26 19:51:54 +03:00
2025-07-09 17:29:57 +03:00
finalProperties.Add(property);
}
return finalProperties;
2025-02-24 12:48:31 +03:00
}
2025-07-03 18:12:04 +03:00
2025-07-08 23:33:37 +03:00
private List<PropertyInfo> GetAvailableProperties(Type type)
2025-07-03 18:12:04 +03:00
{
if (_propertiesCache.TryGetValue(type, out var properties))
{
return properties;
}
2025-07-09 12:01:08 +03:00
2025-07-03 18:12:04 +03:00
var propertiesList = FilterProperties(type.GetProperties());
_propertiesCache[type] = propertiesList;
return propertiesList;
}
2025-02-24 12:48:31 +03:00
}
}