diff --git a/mROA.Cbor/CborSerializaitonToolkit.cs b/mROA.Cbor/CborSerializaitonToolkit.cs index 07d4e8d..fa7d658 100644 --- a/mROA.Cbor/CborSerializaitonToolkit.cs +++ b/mROA.Cbor/CborSerializaitonToolkit.cs @@ -3,20 +3,28 @@ using System.Collections; using System.Collections.Generic; using System.Formats.Cbor; using System.Linq; +using System.Reflection; +using System.Text.Json; using mROA.Abstract; using mROA.Implementation; +using mROA.Implementation.Attributes; namespace mROA.Cbor { public class CborSerializaitonToolkit : IContextualSerializationToolKit { - public byte[] Serialize(T objectToSerialize, IEndPointContext context) + public byte[] Serialize(object objectToSerialize, IEndPointContext context) { - return Serialize(objectToSerialize, typeof(T), context); + var writer = new CborWriter(); + WriteData(objectToSerialize, writer, context); + return writer.Encode(); } - public byte[] Serialize(object objectToSerialize, Type type, IEndPointContext context) + public void Serialize(object objectToSerialize, Span destination, IEndPointContext context) { + var writer = new CborWriter(); + WriteData(objectToSerialize, writer, context); + writer.Encode(destination); } public T Deserialize(byte[] rawData, IEndPointContext context) @@ -24,19 +32,20 @@ namespace mROA.Cbor return (T)Deserialize(rawData, typeof(T), context); } - public object? Deserialize(byte[] rawData, Type type, IEndPointContext context) + public object Deserialize(byte[] rawData, Type type, IEndPointContext context) { - return Deserialize(rawData.AsSpan(), type, context); + return Deserialize(rawData.AsMemory(), type, context); } - public T Deserialize(Span rawData, IEndPointContext context) + public T Deserialize(ReadOnlyMemory rawData, IEndPointContext context) { return (T)Deserialize(rawData, typeof(T), context); } - public object? Deserialize(Span rawData, Type type, IEndPointContext context) + public object Deserialize(ReadOnlyMemory rawData, Type type, IEndPointContext context) { - return null; + var reader = new CborReader(rawData); + return ReadData(reader, type, context); } public T Cast(object nonCasted, IEndPointContext context) @@ -49,7 +58,7 @@ namespace mROA.Cbor return null; } - private void WriteData(object? obj, CborWriter writer) + private void WriteData(object? obj, CborWriter writer, IEndPointContext context) { switch (obj) { @@ -65,9 +74,9 @@ namespace mROA.Cbor case double d: writer.WriteDouble(d); break; - case decimal dec: - writer.WriteDecimal(dec); - break; + // case decimal dec: + // writer.WriteDecimal(dec); + // break; case bool b: writer.WriteBoolean(b); break; @@ -90,35 +99,41 @@ namespace mROA.Cbor writer.WriteByteString(bytes); break; case IDictionary dictionary: - WriteDictionary(dictionary, writer); + WriteDictionary(dictionary, writer, context); break; - case IEnumerable enumerable: - WriteEnumerable(enumerable, writer); + case IList enumerable: + WriteList(enumerable, writer, context); break; - case SharedObject sharedObject: + case ISharedObject sharedObject: + sharedObject.EndPointContext = context; + WriteObject(sharedObject, writer, context); break; default: - WriteObject(obj, writer); + if (obj.GetType().IsEnum) + { + writer.WriteUInt32((uint)obj); + break; + } + + WriteObject(obj, writer, context); break; } } - private void WriteEnumerable(IEnumerable enumerable, CborWriter writer) + private void WriteList(IList list, CborWriter writer, IEndPointContext context) { - List list = new List(); - foreach (var element in enumerable) - list.Add(element); + writer.WriteStartArray(list.Count); foreach (var element in list) - WriteData(element, writer); + WriteData(element, writer, context); writer.WriteEndArray(); } - private void WriteDictionary(IDictionary dictionary, CborWriter writer) + private void WriteDictionary(IDictionary dictionary, CborWriter writer, IEndPointContext context) { writer.WriteStartMap(dictionary.Count); var keysEnumerator = dictionary.Keys.GetEnumerator(); @@ -127,18 +142,158 @@ namespace mROA.Cbor { keysEnumerator.MoveNext(); valuesEnumerator.MoveNext(); - WriteData(keysEnumerator.Current, writer); - WriteData(valuesEnumerator.Current, writer); + WriteData(keysEnumerator.Current, writer, context); + WriteData(valuesEnumerator.Current, writer, context); } + writer.WriteEndMap(); + (keysEnumerator as IDisposable)?.Dispose(); + (valuesEnumerator as IDisposable)?.Dispose(); } - private void WriteObject(object obj, CborWriter writer) + private void WriteObject(object obj, CborWriter writer, IEndPointContext context) { var type = obj.GetType(); - var properties = type.GetProperties(); - var values = properties.Select(property => property.GetValue(obj)); - WriteEnumerable(values, writer); + var properties = FilterProperties(type.GetProperties()); + var values = properties.Select(property => property.GetValue(obj)).ToList(); + WriteList(values, writer, context); + } + + private object ReadData(CborReader reader, Type? type, IEndPointContext context) + { + var state = reader.PeekState(); + switch (state) + { + case CborReaderState.Boolean: + return reader.ReadBoolean(); + case CborReaderState.UnsignedInteger: + case CborReaderState.NegativeInteger: + if (type == typeof(int)) + return reader.ReadInt32(); + if (type == typeof(long)) + return reader.ReadInt64(); + if (type == typeof(uint) || type.IsEnum) + return reader.ReadUInt32(); + if (type == typeof(ulong)) + return reader.ReadUInt64(); + break; + case CborReaderState.ByteString: + return reader.ReadByteString(); + case CborReaderState.TextString: + return reader.ReadTextString(); + case CborReaderState.Null: + return null; + case CborReaderState.DoublePrecisionFloat: + return reader.ReadDouble(); + case CborReaderState.SinglePrecisionFloat: + return reader.ReadSingle(); + case CborReaderState.StartArray: + if (type == null) + return ReadList(reader, null, context); + + if (type.IsSubclassOf(typeof(ISharedObject))) + return ReadSharedObject(reader, type, context); + + if (type.IsSubclassOf(typeof(IList))) + 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; + } + + + private Array ReadList(CborReader reader, Type? type, IEndPointContext context) + { + var length = reader.ReadStartArray(); + if (length != null) + { + var values = new object[length.Value]; + + Type elementType = typeof(object); + + if (type is { IsArray: true }) + elementType = type.GetGenericArguments()[0]; + + + for (int i = 0; i < length; i++) + { + values[i] = ReadData(reader, elementType, context); + } + } + + return Array.Empty(); + } + + private 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; + } + + private object ReadObject(CborReader reader, Type type, IEndPointContext context) + { + var instance = Activator.CreateInstance(type)!; + + FillObject(instance, type, reader, context); + + return instance; + } + + private ISharedObject ReadSharedObject(CborReader reader, Type type, IEndPointContext context) + { + var sharedObject = (Activator.CreateInstance(type) as ISharedObject)!; + sharedObject.EndPointContext = context; + + FillObject(sharedObject, type, reader, context); + + return sharedObject; + } + + private void FillObject(object obj, Type type, CborReader reader, IEndPointContext context) + { + var properties = FilterProperties(type.GetProperties()); + + _ = reader.ReadStartArray(); + + foreach (var property in properties) + { + var value = ReadData(reader, property.PropertyType, context); + property.SetValue(obj, value); + } + + reader.ReadEndArray(); + } + + private List FilterProperties(PropertyInfo[] properties) + { + var finalProperties = new List(properties.Length); + foreach (var property in properties) + { + if (property.GetCustomAttribute() == null) + finalProperties.Add(property); + } + + return finalProperties; } } } \ No newline at end of file diff --git a/mROA.Cbor/IContextualSerializationToolKit.cs b/mROA.Cbor/IContextualSerializationToolKit.cs index b96b27e..c7ee66c 100644 --- a/mROA.Cbor/IContextualSerializationToolKit.cs +++ b/mROA.Cbor/IContextualSerializationToolKit.cs @@ -5,12 +5,12 @@ namespace mROA.Cbor { public interface IContextualSerializationToolKit { - byte[] Serialize(T objectToSerialize, IEndPointContext context); - byte[] Serialize(object objectToSerialize, Type type, IEndPointContext context); + byte[] Serialize(object objectToSerialize, IEndPointContext context); + void Serialize(object objectToSerialize, Span destination, IEndPointContext context); T Deserialize(byte[] rawData, IEndPointContext context); - object? Deserialize(byte[] rawData, Type type, IEndPointContext context); - T Deserialize(Span rawData, IEndPointContext context); - object? Deserialize(Span rawData, Type type, IEndPointContext context); + object Deserialize(byte[] rawData, Type type, IEndPointContext context); + T Deserialize(ReadOnlyMemory rawData, IEndPointContext context); + object Deserialize(ReadOnlyMemory rawData, Type type, IEndPointContext context); T Cast(object nonCasted, IEndPointContext context); object Cast(object nonCasted, Type type, IEndPointContext context); } diff --git a/mROA.Test/CborTest.cs b/mROA.Test/CborTest.cs new file mode 100644 index 0000000..95610e7 --- /dev/null +++ b/mROA.Test/CborTest.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using mROA.Cbor; + +namespace mROA.Test; + +public class CborTest +{ + private ComplexTestObject _complexTestObject; + private IContextualSerializationToolKit _serializationToolKit; + private BasicCollectionElement _basicCollectionElement; + + [SetUp] + public void Setup() + { + _complexTestObject = new () + { + IntValue = 123, + DoubleValue = 3.14159, + StringValue = "abc", + CollectionElements = + [ + _basicCollectionElement, + new BasicCollectionElement { A = 8_000_000, B = "Fi number", C = 1.618f } + ], + IntArray = [1, 4, 8, 16, 87] + }; + _basicCollectionElement = new() { A = 567565, B = "test text", C = 2.781f }; + _serializationToolKit = new CborSerializaitonToolkit(); + } + + [Test] + public void BasicOnly() + { + var value = 123; + var data = _serializationToolKit.Serialize(value, null); + + var deserialize = _serializationToolKit.Deserialize(data, null); + Assert.That(value, Is.EqualTo(deserialize)); + } + + [Test] + public void ComplexFlat() + { + var value = _basicCollectionElement; + List first = [1, 2, 3]; + List second = [1, 2, 3]; + var eq = first.Equals(second); + var data = _serializationToolKit.Serialize(value, null); + var deserialize = _serializationToolKit.Deserialize(data, null); + Assert.That(value, Is.EqualTo(deserialize)); + } + + [Test] + public void ComplexFull() + { + } + + public void SharedObject() + { + } + + + private class ComplexTestObject + { + public int IntValue { get; set; } + public double DoubleValue { get; set; } + public string StringValue { get; set; } + public int[] IntArray { get; set; } + public BasicCollectionElement[] CollectionElements { get; set; } + + protected bool Equals(ComplexTestObject other) + { + return IntValue == other.IntValue && DoubleValue.Equals(other.DoubleValue) && StringValue == other.StringValue && IntArray.Equals(other.IntArray) && CollectionElements.Equals(other.CollectionElements); + } + + public override bool Equals(object? obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((ComplexTestObject)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(IntValue, DoubleValue, StringValue, IntArray, CollectionElements); + } + } + + private class BasicCollectionElement + { + public int A { get; set; } + public string B { get; set; } + public float C { get; set; } + + protected bool Equals(BasicCollectionElement other) + { + return A == other.A && B == other.B && C.Equals(other.C); + } + + public override bool Equals(object? obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((BasicCollectionElement)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(A, B, C); + } + } +} \ No newline at end of file diff --git a/mROA.Test/NextGenTest.cs b/mROA.Test/NextGenTest.cs index 76a53c5..8eaebb0 100644 --- a/mROA.Test/NextGenTest.cs +++ b/mROA.Test/NextGenTest.cs @@ -70,6 +70,7 @@ namespace mROA.Test [TearDown] public void TearDown() { + _listener.Stop(); _listener.Dispose(); } } diff --git a/mROA.Test/mROA.Test.csproj b/mROA.Test/mROA.Test.csproj index 4d1d9e4..bff76ec 100644 --- a/mROA.Test/mROA.Test.csproj +++ b/mROA.Test/mROA.Test.csproj @@ -1,7 +1,7 @@  - netstandard2.1 + net9.0 latest enable @@ -27,6 +27,7 @@ + diff --git a/mROA/Implementation/Attributes/SerializationIgnoreAttribute.cs b/mROA/Implementation/Attributes/SerializationIgnoreAttribute.cs new file mode 100644 index 0000000..b63d4d3 --- /dev/null +++ b/mROA/Implementation/Attributes/SerializationIgnoreAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace mROA.Implementation.Attributes +{ + public class SerializationIgnoreAttribute : Attribute + { + + } +} \ No newline at end of file diff --git a/mROA/Implementation/SharedObject.cs b/mROA/Implementation/SharedObject.cs index a9e56dc..17f6d53 100644 --- a/mROA/Implementation/SharedObject.cs +++ b/mROA/Implementation/SharedObject.cs @@ -32,11 +32,12 @@ namespace mROA.Implementation } - public class SharedObject : SharedObject + public interface ISharedObject { - + IEndPointContext EndPointContext { get; set; } } - public class SharedObject where T : notnull + + public class SharedObject : ISharedObject where T : notnull { private IContextRepository GetDefaultContextRepository() => (OwnerId == TransmissionConfig.OwnershipRepository.GetHostOwnershipId() @@ -103,5 +104,7 @@ namespace mROA.Implementation public static implicit operator SharedObject(T value) => new(value); + + public IEndPointContext EndPointContext { get; set; } } } \ No newline at end of file