diff --git a/Libraries/Esiur/Data/TruTypeDef.cs b/Libraries/Esiur/Data/TruTypeDef.cs index 404854d..681c9ee 100644 --- a/Libraries/Esiur/Data/TruTypeDef.cs +++ b/Libraries/Esiur/Data/TruTypeDef.cs @@ -89,48 +89,20 @@ namespace Esiur.Data if (TypeDef is RemoteTypeDef remoteTypeDef) { if (connection.RemoteDomain == remoteTypeDef.Domain) - { // this is local in respect to the connection, send the remote typdef id. - if (Nullable) - rt.AddUInt8(0x80 | (byte)TruIdentifier.LocalType8); - else - rt.AddUInt8((byte)TruIdentifier.LocalType8); - - rt.AddUInt8((byte)remoteTypeDef.Id); - } + WriteTypeReference(rt, isLocal: true, Nullable, remoteTypeDef.Id); else - { // this is remote in respect to the connection and the local typedef id is used. - if (Nullable) - rt.AddUInt8(0x80 | (byte)TruIdentifier.RemoteType8); - else - rt.AddUInt8((byte)TruIdentifier.RemoteType8); - - rt.AddUInt8((byte)remoteTypeDef.LocalTypeDefId); - } + WriteTypeReference(rt, isLocal: false, Nullable, remoteTypeDef.LocalTypeDefId); } else if (TypeDef is LocalTypeDef localTypeDef) { if (connection == null) - { // if there is no connection, we assume it's local. - if (Nullable) - rt.AddUInt8(0x80 | (byte)TruIdentifier.LocalType8); - else - rt.AddUInt8((byte)TruIdentifier.LocalType8); - - rt.AddUInt8((byte)localTypeDef.Id); - } + WriteTypeReference(rt, isLocal: true, Nullable, localTypeDef.Id); else - { // this is remote, unless the connection is to self @TODO: solve for this state. - if (Nullable) - rt.AddUInt8(0x80 | (byte)TruIdentifier.RemoteType8); - else - rt.AddUInt8((byte)TruIdentifier.RemoteType8); - - rt.AddUInt8((byte)localTypeDef.Id); - } + WriteTypeReference(rt, isLocal: false, Nullable, localTypeDef.Id); } else throw new NotImplementedException(); @@ -138,6 +110,45 @@ namespace Esiur.Data return rt.ToArray(); } + // Picks the narrowest Local/RemoteType{8,16,32,64} identifier that fits `id`, + // matching the widths Tru.Parse already knows how to read. + // Internal (rather than private) so it can be unit-tested directly without + // needing a real LocalTypeDef/Warehouse registration for every width tier. + internal static void WriteTypeReference(BinaryList rt, bool isLocal, bool nullable, ulong id) + { + TruIdentifier identifier; + + if (id <= byte.MaxValue) + identifier = isLocal ? TruIdentifier.LocalType8 : TruIdentifier.RemoteType8; + else if (id <= ushort.MaxValue) + identifier = isLocal ? TruIdentifier.LocalType16 : TruIdentifier.RemoteType16; + else if (id <= uint.MaxValue) + identifier = isLocal ? TruIdentifier.LocalType32 : TruIdentifier.RemoteType32; + else + identifier = isLocal ? TruIdentifier.LocalType64 : TruIdentifier.RemoteType64; + + rt.AddUInt8((byte)((nullable ? 0x80 : 0) | (byte)identifier)); + + switch (identifier) + { + case TruIdentifier.LocalType8: + case TruIdentifier.RemoteType8: + rt.AddUInt8((byte)id); + break; + case TruIdentifier.LocalType16: + case TruIdentifier.RemoteType16: + rt.AddUInt16((ushort)id); + break; + case TruIdentifier.LocalType32: + case TruIdentifier.RemoteType32: + rt.AddUInt32((uint)id); + break; + default: + rt.AddUInt64(id); + break; + } + } + public override Tru ToNullable() { throw new NotImplementedException(); diff --git a/Libraries/Esiur/Data/Types/TypeDefInfo.cs b/Libraries/Esiur/Data/Types/TypeDefInfo.cs index 5d6ffe5..111209d 100644 --- a/Libraries/Esiur/Data/Types/TypeDefInfo.cs +++ b/Libraries/Esiur/Data/Types/TypeDefInfo.cs @@ -64,6 +64,7 @@ public class TypeDefInfo : IndexedStructure Version = definition.Version, Id = definition.Id, Name = definition.Name, + Namespace = (definition as LocalTypeDef)?.DefinedType?.Namespace, Kind = definition.Kind, Parent = definition.ParentTypeId, Usage = definition.Usage, diff --git a/Tests/Unit/TypeDefInfoSerializationTests.cs b/Tests/Unit/TypeDefInfoSerializationTests.cs index 7e6f472..a510f27 100644 --- a/Tests/Unit/TypeDefInfoSerializationTests.cs +++ b/Tests/Unit/TypeDefInfoSerializationTests.cs @@ -127,4 +127,70 @@ public class TypeDefInfoSerializationTests Assert.Equal(TruIdentifier.String, Assert.Single(parsed.Events!).ArgumentType.Identifier); Assert.Equal(100, Convert.ToInt32(Assert.Single(parsed.Constants!).Value)); } + + [Theory] + [InlineData(0ul, TruIdentifier.LocalType8, 1)] + [InlineData(255ul, TruIdentifier.LocalType8, 1)] + [InlineData(256ul, TruIdentifier.LocalType16, 2)] + [InlineData(65535ul, TruIdentifier.LocalType16, 2)] + [InlineData(65536ul, TruIdentifier.LocalType32, 4)] + [InlineData(4294967295ul, TruIdentifier.LocalType32, 4)] + [InlineData(4294967296ul, TruIdentifier.LocalType64, 8)] + public void WriteTypeReference_SelectsNarrowestSufficientWidth(ulong id, TruIdentifier expectedIdentifier, int expectedIdBytes) + { + var rt = new BinaryList(); + TruTypeDef.WriteTypeReference(rt, isLocal: true, nullable: false, id); + var bytes = rt.ToArray(); + + Assert.Equal(1 + expectedIdBytes, bytes.Length); + Assert.Equal((byte)expectedIdentifier, bytes[0]); + + ulong roundTripped = expectedIdBytes switch + { + 1 => bytes[1], + 2 => bytes.GetUInt16(1, Endian.Little), + 4 => bytes.GetUInt32(1, Endian.Little), + _ => bytes.GetUInt64(1, Endian.Little), + }; + Assert.Equal(id, roundTripped); + } + + [Fact] + public void WriteTypeReference_NullableAndRemote_SetHeaderBitsCorrectly() + { + var rt = new BinaryList(); + TruTypeDef.WriteTypeReference(rt, isLocal: false, nullable: true, 42ul); + var bytes = rt.ToArray(); + + Assert.Equal((byte)(0x80 | (byte)TruIdentifier.RemoteType8), bytes[0]); + Assert.Equal(42, bytes[1]); + } + + [Fact] + public void TypeDefInfo_FromTypeDef_PopulatesNamespaceFromDefinedType() + { + var typeDef = new LocalTypeDef(typeof(TruTypeDefTestRecord), Warehouse.Default); + var info = TypeDefInfo.FromTypeDef(typeDef); + + Assert.Equal(typeof(TruTypeDefTestRecord).Namespace, info.Namespace); + Assert.False(string.IsNullOrEmpty(info.Namespace)); + } + + [Fact] + public void TruTypeDef_Compose_LocalTypeDefWithoutConnection_RoundTrips() + { + var typeDef = new LocalTypeDef(typeof(TruTypeDefTestRecord), Warehouse.Default); + var tru = new TruTypeDef(false, typeDef); + + var bytes = tru.Compose(null); + var result = Tru.Parse(bytes, 0, Warehouse.Default); + + Assert.Equal((uint)bytes.Length, result.Size); + var parsed = Assert.IsType(result.Value); + Assert.Equal(typeDef.Id, parsed.TypeDef!.Id); + } +} + +public sealed class TruTypeDefTestRecord : IRecord +{ }