using Esiur.Core; using Esiur.Data.Gvwie; using Esiur.Data.Types; using Esiur.Protocol; using Esiur.Resource; using Microsoft.CodeAnalysis; using System; using System.Buffers.Binary; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; namespace Esiur.Data; public static class DataSerializer { public delegate byte[] Serializer(object value); public static Tdu Int32Composer(object value, Warehouse warehouse, EpConnection connection) { var v = (int)value; if (v >= sbyte.MinValue && v <= sbyte.MaxValue) { return new Tdu(TduIdentifier.Int8, new byte[] { (byte)(sbyte)v }, 1, null, null); } else if (v >= short.MinValue && v <= short.MaxValue) { // Fits in 2 bytes var rt = new byte[2]; BinaryPrimitives.WriteInt16LittleEndian(rt, (short)v); return new Tdu(TduIdentifier.Int16, rt, 2, null, null); } else { // Use full 4 bytes var rt = new byte[4]; BinaryPrimitives.WriteInt32LittleEndian(rt, v); return new Tdu(TduIdentifier.Int32, rt, 4, null, null); } } public static Tdu UInt32Composer(object value, Warehouse warehouse, EpConnection connection) { var v = (uint)value; if (v <= byte.MaxValue) { // Fits in 1 byte return new Tdu(TduIdentifier.UInt8, new byte[] { (byte)v }, 1, null, null); } else if (v <= ushort.MaxValue) { // Fits in 2 bytes var rt = new byte[2]; BinaryPrimitives.WriteUInt16LittleEndian(rt, (ushort)v); return new Tdu(TduIdentifier.UInt16, rt, 2, null, null); } else { // Use full 4 bytes var rt = new byte[4]; BinaryPrimitives.WriteUInt32LittleEndian(rt, v); return new Tdu(TduIdentifier.UInt32, rt, 4, null, null); } } public static Tdu Int16Composer(object value, Warehouse warehouse, EpConnection connection) { var v = (short)value; if (v >= sbyte.MinValue && v <= sbyte.MaxValue) { // Fits in 1 byte return new Tdu(TduIdentifier.Int8, new byte[] { (byte)(sbyte)v }, 1, null, null); } else { // Use full 2 bytes var rt = new byte[2]; BinaryPrimitives.WriteInt16LittleEndian(rt, v); return new Tdu(TduIdentifier.Int16, rt, 2, null, null); } } public static Tdu UInt16Composer(object value, Warehouse warehouse, EpConnection connection) { var v = (ushort)value; if (v <= byte.MaxValue) { // Fits in 1 byte return new Tdu(TduIdentifier.UInt8, new byte[] { (byte)v }, 1, null, null); } else { // Use full 2 bytes var rt = new byte[2]; BinaryPrimitives.WriteUInt16LittleEndian(rt, v); return new Tdu(TduIdentifier.UInt16, rt, 2, null, null); } } public static unsafe Tdu Float32Composer(object value, Warehouse warehouse, EpConnection connection) { float v = (float)value; // Special IEEE-754 values if (float.IsNaN(v) || float.IsInfinity(v)) { return new Tdu(TduIdentifier.Infinity, new byte[0], 0, null, null); } // If v is an exact integer, prefer smallest signed width up to Int32 if (v == Math.Truncate(v)) { // Note: casts are safe because we check bounds first. if (v >= sbyte.MinValue && v <= sbyte.MaxValue) { return new Tdu(TduIdentifier.Int8, new byte[] { (byte)(sbyte)v }, 1, null, null); } if (v >= short.MinValue && v <= short.MaxValue) { var rt = new byte[2]; fixed (byte* ptr = rt) *((short*)ptr) = (short)v; return new Tdu(TduIdentifier.Int16, rt, 2, null, null); } } // Default: Float32 { var rt = new byte[4]; fixed (byte* ptr = rt) *((float*)ptr) = v; return new Tdu(TduIdentifier.Float32, rt, 4, null, null); } } public unsafe static Tdu Float64Composer(object value, Warehouse warehouse, EpConnection connection) { double v = (double)value; // Special IEEE-754 values if (double.IsNaN(v) || double.IsInfinity(v)) { return new Tdu(TduIdentifier.Infinity, new byte[0], 0, null, null); } // If v is an exact integer, choose the smallest signed width if (v == Math.Truncate(v)) { if (v >= sbyte.MinValue && v <= sbyte.MaxValue) return new Tdu(TduIdentifier.Int8, new byte[] { (byte)(sbyte)v }, 1, null, null); if (v >= short.MinValue && v <= short.MaxValue) { var rt = new byte[2]; fixed (byte* ptr = rt) *((short*)ptr) = (short)v; return new Tdu(TduIdentifier.Int16, rt, 2, null, null); } if (v >= int.MinValue && v <= int.MaxValue) { var rt = new byte[4]; fixed (byte* ptr = rt) *((int*)ptr) = (int)v; return new Tdu(TduIdentifier.Int32, rt, 4, null, null); } // If it's integral but outside Int64 range, fall through to Float64. } // Try exact Float32 (decimal subset of doubles) var f = (float)v; if ((double)f == v) { var rt = new byte[4]; fixed (byte* ptr = rt) *((float*)ptr) = f; return new Tdu(TduIdentifier.Float32, rt, 4, null, null); } // Default: Float64 { var rt = new byte[8]; fixed (byte* ptr = rt) *((double*)ptr) = v; return new Tdu(TduIdentifier.Float64, rt, 8, null, null); } } public static Tdu Int64Composer(object value, Warehouse warehouse, EpConnection connection) { var v = (long)value; if (v >= sbyte.MinValue && v <= sbyte.MaxValue) { // Fits in 1 byte return new Tdu(TduIdentifier.Int8, new byte[] { (byte)(sbyte)v }, 1, null, null); } else if (v >= short.MinValue && v <= short.MaxValue) { // Fits in 2 bytes var rt = new byte[2]; BinaryPrimitives.WriteInt16LittleEndian(rt, (short)v); return new Tdu(TduIdentifier.Int16, rt, 2, null, null); } else if (v >= int.MinValue && v <= int.MaxValue) { // Fits in 4 bytes var rt = new byte[4]; BinaryPrimitives.WriteInt32LittleEndian(rt, (int)v); return new Tdu(TduIdentifier.Int32, rt, 4, null, null); } else { // Use full 8 bytes var rt = new byte[8]; BinaryPrimitives.WriteInt64LittleEndian(rt, v); return new Tdu(TduIdentifier.Int64, rt, 8, null, null); } } public static Tdu UInt64Composer(object value, Warehouse warehouse, EpConnection connection) { var v = (ulong)value; if (v <= byte.MaxValue) { // Fits in 1 byte return new Tdu(TduIdentifier.UInt8, new byte[] { (byte)v }, 1, null, null); } else if (v <= ushort.MaxValue) { // Fits in 2 bytes var rt = new byte[2]; BinaryPrimitives.WriteUInt16LittleEndian(rt, (ushort)v); return new Tdu(TduIdentifier.UInt16, rt, 2, null, null); } else if (v <= uint.MaxValue) { // Fits in 4 bytes var rt = new byte[4]; BinaryPrimitives.WriteUInt32LittleEndian(rt, (uint)v); return new Tdu(TduIdentifier.UInt32, rt, 4, null, null); } else { // Use full 8 bytes var rt = new byte[8]; BinaryPrimitives.WriteUInt64LittleEndian(rt, v); return new Tdu(TduIdentifier.UInt64, rt, 8, null, null); } } public static Tdu DateTimeComposer(object value, Warehouse warehouse, EpConnection connection) { var v = ((DateTime)value).ToUniversalTime().Ticks; var rt = new byte[8]; BinaryPrimitives.WriteInt64LittleEndian(rt, v); return new Tdu(TduIdentifier.DateTime, rt, 8, null, null); } //public static unsafe TDU Decimal128Composer(object value, Warehouse warehouse, EpConnection connection) //{ // var v = (decimal)value; // var rt = new byte[16]; // fixed (byte* ptr = rt) // *((decimal*)ptr) = v; // return new TDU(TDUIdentifier.Decimal128, rt, 16); //} public static unsafe Tdu Decimal128Composer(object value, Warehouse warehouse, EpConnection connection) { var v = (decimal)value; // Prefer smallest exact signed integer if no fractional part int[] bits = decimal.GetBits(v); int flags = bits[3]; int scale = (flags >> 16) & 0x7F; if (scale == 0) { if (v >= sbyte.MinValue && v <= sbyte.MaxValue) return new Tdu(TduIdentifier.Int8, new byte[] { (byte)(sbyte)v }, 1, null, null); if (v >= short.MinValue && v <= short.MaxValue) { var b = new byte[2]; BinaryPrimitives.WriteInt16LittleEndian(b, (short)v); return new Tdu(TduIdentifier.Int16, b, 2, null, null); } if (v >= int.MinValue && v <= int.MaxValue) { var b = new byte[4]; BinaryPrimitives.WriteInt32LittleEndian(b, (int)v); return new Tdu(TduIdentifier.Int32, b, 4, null, null); } if (v >= long.MinValue && v <= long.MaxValue) { var b = new byte[8]; BinaryPrimitives.WriteInt64LittleEndian(b, (long)v); return new Tdu(TduIdentifier.Int64, b, 8, null, null); } // else fall through (needs 96+ bits) } // Try exact Float32 (4 bytes) // Exactness test: decimal -> float -> decimal must equal original float f = (float)v; if ((decimal)f == v) { var rt = new byte[4]; fixed (byte* ptr = rt) *((float*)ptr) = f; return new Tdu(TduIdentifier.Float32, rt, 4, null, null); } // Try exact Float64 (8 bytes) double d = (double)v; if ((decimal)d == v) { var rt = new byte[8]; fixed (byte* ptr = rt) *((double*)ptr) = d; return new Tdu(TduIdentifier.Float64, rt, 8, null, null); } { // Fallback: full .NET decimal (16 bytes): lo, mid, hi, flags (scale/sign) var rt = new byte[16]; fixed (byte* ptr = rt) *((decimal*)ptr) = v; return new Tdu(TduIdentifier.Decimal128, rt, 16, null, null); } } public static Tdu StringComposer(object value, Warehouse warehouse, EpConnection connection) { var b = Encoding.UTF8.GetBytes((string)value); return new Tdu(TduIdentifier.String, b, (uint)b.Length, null, null); } public static Tdu ResourceLinkComposer(object value, Warehouse warehouse, EpConnection connection) { var b = Encoding.UTF8.GetBytes((ResourceLink)value); return new Tdu(TduIdentifier.ResourceLink, b, (uint)b.Length, null, null); } public static Tdu EnumComposer(object value, Warehouse warehouse, EpConnection connection) { if (value == null) return new Tdu(TduIdentifier.Null, null, 0, null, null); var valueType = value.GetType(); var typeDef = warehouse.GetLocalTypeDefByType(valueType); var intVal = Convert.ChangeType(value, (value as Enum).GetTypeCode()); var ct = typeDef.Constants.FirstOrDefault(x => x.Value.Equals(intVal)); if (ct == null) return new Tdu(TduIdentifier.Null, null, 0, null, null); return new Tdu(TduIdentifier.Typed, new byte[] { ct.Index }, 1, Tru.FromType(valueType, warehouse), connection); } public static Tdu UInt8Composer(object value, Warehouse warehouse, EpConnection connection) { return new Tdu(TduIdentifier.UInt8, new byte[] { (byte)value }, 1, null, null); } public static Tdu Int8Composer(object value, Warehouse warehouse, EpConnection connection) { return new Tdu(TduIdentifier.Int8, new byte[] { (byte)(sbyte)value }, 1, null, null); } public static Tdu Char8Composer(object value, Warehouse warehouse, EpConnection connection) { return new Tdu(TduIdentifier.Int8, new byte[] { (byte)(char)value }, 1, null, null); } public static Tdu Char16Composer(object value, Warehouse warehouse, EpConnection connection) { var v = (char)value; var rt = new byte[2]; BinaryPrimitives.WriteUInt16LittleEndian(rt, v); return new Tdu(TduIdentifier.Char16, rt, 2, null, null); } public static Tdu BoolComposer(object value, Warehouse warehouse, EpConnection connection) { if ((bool)value) { return new Tdu(TduIdentifier.True, null, 0, null, null); } else { return new Tdu(TduIdentifier.False, null, 0, null, null); } } public static Tdu NotModifiedComposer(object value, Warehouse warehouse, EpConnection connection) { return new Tdu(TduIdentifier.NotModified, null, 0, null, null); } public static Tdu RawDataComposerFromArray(object value, Warehouse warehouse, EpConnection connection) { var b = (byte[])value; return new Tdu(TduIdentifier.RawData, b, (uint)b.Length, null, null); } public static Tdu RawDataComposerFromList(dynamic value, Warehouse warehouse, EpConnection connection) { var b = value as List; return new Tdu(TduIdentifier.RawData, b.ToArray(), (uint)b.Count, null, null); } //public static (TDUIdentifier, byte[]) ListComposerFromArray(dynamic value, EpConnection connection) //{ // var rt = new List(); // var array = (object[])value; // for (var i = 0; i < array.Length; i++) // rt.AddRange(Codec.Compose(array[i], connection)); // return (TDUIdentifier.List, rt.ToArray()); //} public static Tdu ListComposer(object value, Warehouse warehouse, EpConnection connection) { var composed = DynamicArrayComposer((IEnumerable)value, warehouse, connection); if (composed == null) return new Tdu(TduIdentifier.Null, new byte[0], 0, null, null); else { return new Tdu(TduIdentifier.List, composed, (uint)composed.Length, null, null); } //if (value == null) // return new TDU(TDUIdentifier.Null, new byte[0], 0); //var list = value; //var rt = new List(); //TDU? previous = null; //foreach (var i in list) //{ // var tdu = Codec.ComposeInternal(i, warehouse, connection); // if (previous != null && tdu.MatchType(previous.Value)) // { // var d = tdu.Composed.Clip(tdu.ContentOffset, // (uint)tdu.Composed.Length - tdu.ContentOffset); // var ntd = new TDU(TDUIdentifier.TypeContinuation, d, (ulong)d.Length); // rt.AddRange(ntd.Composed); // } // else // { // rt.AddRange(tdu.Composed); // } // previous = tdu; //} //return new TDU(TDUIdentifier.List, rt.ToArray(), (uint)rt.Count); } public static byte[] TypedArrayComposer(IEnumerable value, Tru tru, Warehouse warehouse, EpConnection connection) { byte[] composed; if (value == null) return null; if (tru.Identifier == TruIdentifier.Int32) { composed = GroupInt32Codec.Encode((IList)value); } else if (tru.Identifier == TruIdentifier.Int64) { composed = GroupInt64Codec.Encode((IList)value); } else if (tru.Identifier == TruIdentifier.Int16) { composed = GroupInt16Codec.Encode((IList)value); } else if (tru.Identifier == TruIdentifier.UInt32) { composed = GroupUInt32Codec.Encode((IList)value); } else if (tru.Identifier == TruIdentifier.UInt64) { composed = GroupUInt64Codec.Encode((IList)value); } else if (tru.Identifier == TruIdentifier.UInt16) { composed = GroupUInt16Codec.Encode((IList)value); } //else if (tru.Identifier == TruIdentifier.Enum) //{ // var rt = new List(); // var typeDef = warehouse.GetTypeDefByType(tru.GetRuntimeType(warehouse)); // foreach (var v in value) // { // var intVal = Convert.ChangeType(v, (v as Enum).GetTypeCode()); // var ct = typeDef.Constants.FirstOrDefault(x => x.Value.Equals(intVal)); // if (ct == null) // throw new Exception("Unknown Enum."); // rt.Add(ct.Index); // } // composed = rt.ToArray(); //} else { var rt = new List(); Tdu? previous = null; //var isTyped = tru.TypeDefId != null;// tru.IsTyped(); foreach (var i in value) { var tdu = Codec.ComposeInternal(i, warehouse, connection); var currentTru = Tru.FromType(i?.GetType(), warehouse); if (tdu.Class == TduClass.Typed && tru.Match(currentTru)) { var d = tdu.Composed.Clip(tdu.ContentOffset, (uint)tdu.Composed.Length - tdu.ContentOffset); var ntd = new Tdu(TduIdentifier.TypeOfTarget, d, (ulong)d.Length, null, null); rt.AddRange(ntd.Composed); } else if (previous != null && tdu.MatchType(previous.Value)) { var d = tdu.Composed.Clip(tdu.ContentOffset, (uint)tdu.Composed.Length - tdu.ContentOffset); var ntd = new Tdu(TduIdentifier.TypeContinuation, d, (ulong)d.Length, null, null); rt.AddRange(ntd.Composed); } else { rt.AddRange(tdu.Composed); } previous = tdu; } composed = rt.ToArray(); } return composed; } public static Tdu TypedListComposer(IEnumerable value, Type type, Warehouse warehouse, EpConnection connection) { var elementTru = Tru.FromType(type, warehouse); byte[] composed = TypedArrayComposer(value, elementTru, warehouse, connection); if (composed == null) return new Tdu(TduIdentifier.Null, new byte[0], 0, null, null); var metadata = new TruComposite(TruIdentifier.TypedList, false, new Tru[] { elementTru }, value.GetType()); return new Tdu(TduIdentifier.Typed, composed, (uint)composed.Length, metadata, connection); } public static Tdu PropertyValueArrayComposer(object value, Warehouse warehouse, EpConnection connection) { if (value == null) return new Tdu(TduIdentifier.Null, new byte[0], 0, null, null); var rt = new List(); var ar = value as PropertyValue[]; foreach (var pv in ar) { rt.AddRange(Codec.Compose(pv.Age, warehouse, connection)); rt.AddRange(Codec.Compose(pv.Date, warehouse, connection)); rt.AddRange(Codec.Compose(pv.Value, warehouse, connection)); } return new Tdu(TduIdentifier.RawData, rt.ToArray(), (uint)rt.Count, null, null); } public static Tdu TypedMapComposer(object value, Type keyType, Type valueType, Warehouse warehouse, EpConnection connection) { if (value == null) return new Tdu(TduIdentifier.Null, new byte[0], 0, null, null); var kt = Tru.FromType(keyType, warehouse); var vt = Tru.FromType(valueType, warehouse); //var rt = new List(); var map = (IMap)value; var keys = map.GetKeys(); var values = map.GetValues(); var compsedKeys = TypedArrayComposer(keys, kt, warehouse, connection); var compsedValues = TypedArrayComposer(values, vt, warehouse, connection); //var ktb = kt.Compose(); //var vtb = vt.Compose(); //var metadata = DC.Combine(ktb, 0, (uint)ktb.Length, vtb, 0, (uint)vtb.Length); var keysTdu = new Tdu(TduIdentifier.TypeOfTarget, compsedKeys, (uint)compsedKeys.Length, null, null).Composed; var valuesTdu = new Tdu(TduIdentifier.TypeOfTarget, compsedValues, (uint)compsedValues.Length, null, null).Composed; var all = DC.Combine(keysTdu, 0, (uint)keysTdu.Length, valuesTdu, 0, (uint)valuesTdu.Length); return new Tdu(TduIdentifier.Typed, all, (uint)all.Length, new TruComposite(TruIdentifier.TypedMap, false, new Tru[] { kt, vt }, value.GetType()), connection); //return new Tdu(TduIdentifier.TypedMap, all, (uint)all.Length, metadata); } public static Tdu TypedDictionaryComposer(object value, Type keyType, Type valueType, Warehouse warehouse, EpConnection connection) { if (value == null) return new Tdu(TduIdentifier.Null, new byte[0], 0, null, null); var kt = Tru.FromType(keyType, warehouse); var vt = Tru.FromType(valueType, warehouse); //var rt = new List(); var map = (IDictionary)value; var keys = map.Keys; var values = map.Values; var compsedKeys = TypedArrayComposer(keys, kt, warehouse, connection); var compsedValues = TypedArrayComposer(values, vt, warehouse, connection); var keysTdu = new Tdu(TduIdentifier.TypeOfTarget, compsedKeys, (uint)compsedKeys.Length, null, null).Composed; var valuesTdu = new Tdu(TduIdentifier.TypeOfTarget, compsedValues, (uint)compsedValues.Length, null, null).Composed; var all = DC.Combine(keysTdu, 0, (uint)keysTdu.Length, valuesTdu, 0, (uint)valuesTdu.Length); return new Tdu(TduIdentifier.Typed, all, (uint)all.Length, new TruComposite(TruIdentifier.TypedMap, false, new Tru[] { kt, vt }, value.GetType()), connection); } public static byte[] DynamicArrayComposer(IEnumerable value, Warehouse warehouse, EpConnection connection) { if (value == null) return null; // Pre-size the buffer from the element count (when known) to avoid repeated // List reallocations as items are appended. 4 bytes/element is a rough hint. var rt = new List(value is ICollection collection ? collection.Count * 4 : 16); Tdu? previous = null; foreach (var i in value) { var tdu = Codec.ComposeInternal(i, warehouse, connection); if (previous != null && tdu.MatchType(previous.Value)) { var d = tdu.Composed.Clip(tdu.ContentOffset, (uint)tdu.Composed.Length - tdu.ContentOffset); var ntd = new Tdu(TduIdentifier.TypeContinuation, d, (ulong)d.Length, null, null); rt.AddRange(ntd.Composed); } else { rt.AddRange(tdu.Composed); } previous = tdu; } return rt.ToArray(); } public static Tdu ResourceListComposer(object value, Warehouse warehouse, EpConnection connection) { if (value == null) return new Tdu(TduIdentifier.Null, new byte[0], 0, null, null); var composed = DynamicArrayComposer((IEnumerable)value, warehouse, connection); return new Tdu(TduIdentifier.ResourceList, composed, (uint)composed.Length, null, null); } public static Tdu RecordListComposer(object value, Warehouse warehouse, EpConnection connection) { if (value == null) return new Tdu(TduIdentifier.Null, new byte[0], 0, null, null); var composed = DynamicArrayComposer((IEnumerable)value, warehouse, connection); return new Tdu(TduIdentifier.RecordList, composed, (uint)composed.Length, null, null); } public static unsafe Tdu ResourceComposer(object value, Warehouse warehouse, EpConnection connection) { var resource = (IResource)value; if (resource.Instance == null || resource.Instance.IsDestroyed) { return new Tdu(TduIdentifier.Null, null, 0, null, null); } if (Codec.IsLocalResource(resource, connection)) { var rid = (resource as EpResource).ResourceInstanceId; if (rid <= 0xFF) return new Tdu(TduIdentifier.LocalResource8, new byte[] { (byte)rid }, 1, null, null); else if (rid <= 0xFFFF) { var rt = new byte[2]; fixed (byte* ptr = rt) *((ushort*)ptr) = (ushort)rid; return new Tdu(TduIdentifier.LocalResource16, rt, 2, null, null); } else { var rt = new byte[4]; fixed (byte* ptr = rt) *((uint*)ptr) = rid; return new Tdu(TduIdentifier.LocalResource32, rt, 4, null, null); } } else { connection._cache.Add(value as IResource, DateTime.UtcNow); var rid = resource.Instance.Id; if (rid <= 0xFF) return new Tdu(TduIdentifier.RemoteResource8, new byte[] { (byte)rid }, 1, null, null); else if (rid <= 0xFFFF) { var rt = new byte[2]; fixed (byte* ptr = rt) *((ushort*)ptr) = (ushort)rid; return new Tdu(TduIdentifier.RemoteResource16, rt, 2, null, null); } else { var rt = new byte[4]; fixed (byte* ptr = rt) *((uint*)ptr) = rid; return new Tdu(TduIdentifier.RemoteResource32, rt, 4, null, null); } } } public static unsafe Tdu MapComposer(object value, Warehouse warehouse, EpConnection connection) { if (value == null) return new Tdu(TduIdentifier.Null, new byte[0], 1, null, null); var rt = new List(); var map = (IMap)value; foreach (var el in map.Serialize()) rt.AddRange(Codec.Compose(el, warehouse, connection)); return new Tdu(TduIdentifier.Map, rt.ToArray(), (uint)rt.Count, null, null); } public static unsafe Tdu UUIDComposer(object value, Warehouse warehouse, EpConnection connection) { return new Tdu(TduIdentifier.UUID, ((Uuid)value).Data, 16, null, null); } public static unsafe Tdu RecordComposer(object value, Warehouse warehouse, EpConnection connection) { var rt = new List(); var record = (IRecord)value; var recordTru = Tru.FromType(value.GetType(), warehouse) ; TypeDef typeDef = null; if (value is Record typedRecord) { typeDef = typedRecord.TypeDef; } else if (recordTru is TruTypeDef recordTypeDefTru) { // @TODO need to enhance performance, maybe cache this in the connection or something typeDef = recordTypeDefTru.TypeDef;// recordTru.type;// .GetTypeDef(warehouse, connection.RemoteDomain); } else { throw new Exception("Unsupported."); } foreach (var pt in typeDef.Properties) { var propValue = pt.PropertyInfo.GetValue(record, null); var tru = Tru.FromType(propValue?.GetType(), warehouse); var tdu = Codec.ComposeInternal(propValue, warehouse, connection); if (tdu.Class == TduClass.Typed && // pt.ValueType.IsTyped() && pt.ValueType.Match(tru)) { // strip metadata var len = (uint)tdu.Composed.Length - tdu.ContentOffset; tdu = new Tdu(TduIdentifier.TypeOfTarget, tdu.Composed.Clip(tdu.ContentOffset, len), len, null, null); } rt.AddRange(tdu.Composed); } // @TODO: serialize metadata type Id to byte, ushort or uint depending on size. return new Tdu(TduIdentifier.Typed, rt.ToArray(), (uint)rt.Count, recordTru, connection); } public static byte[] HistoryComposer(KeyList history, Warehouse warehouse, EpConnection connection, bool prependLength = false) { //@TODO:Test var rt = new BinaryList(); for (var i = 0; i < history.Count; i++) rt.AddUInt8(history.Keys.ElementAt(i).Index) .AddUInt8Array(Codec.Compose(history.Values.ElementAt(i), warehouse, connection)); if (prependLength) rt.InsertInt32(0, rt.Length); return rt.ToArray(); } public static Tdu TupleComposer(object value, Warehouse warehouse, EpConnection connection) { if (value == null) return new Tdu(TduIdentifier.Null, new byte[0], 0, null, null); var fields = value.GetType().GetFields(); var list = fields.Select(x => x.GetValue(value)).ToArray(); var trus = fields.Select(x => Tru.FromType(x.FieldType, warehouse)).ToArray(); var rt = new List(fields.Length * 4); for (var i = 0; i < fields.Length; i++) { var tupleValue = list[i]; var targetTru = trus[i]; var tdu = Codec.ComposeInternal(tupleValue, warehouse, connection); var valueTru = Tru.FromType(tupleValue?.GetType(), warehouse); if (tdu.Class == TduClass.Typed && // targetTru.IsTyped() && targetTru.Match(valueTru)) { // strip metadata var len = (uint)tdu.Composed.Length - tdu.ContentOffset; tdu = new Tdu(TduIdentifier.TypeOfTarget, tdu.Composed.Clip(tdu.ContentOffset, len), len, null, null); } rt.AddRange(tdu.Composed); } //return new Tdu(TduIdentifier.TypedTuple, rt.ToArray(), // (uint)rt.Count, metadata.ToArray()); var truIdentifier = trus.Length switch { 2 => TruIdentifier.Tuple2, 3 => TruIdentifier.Tuple3, 4 => TruIdentifier.Tuple4, 5 => TruIdentifier.Tuple5, 6 => TruIdentifier.Tuple6, 7 => TruIdentifier.Tuple7, _ => throw new NotSupportedException("Tuples with more than 7 or less than 2 elements are not supported.") }; return new Tdu(TduIdentifier.Typed, rt.ToArray(), (uint)rt.Count, new TruComposite(truIdentifier, false, trus, value.GetType()), connection); } }