feat(abcip): thread nested-struct template id so nested UDT members are addressable (#6)

This commit is contained in:
Joseph Doherty
2026-06-18 11:33:41 -04:00
parent 56c136b0fd
commit 3d8ce4e85f
5 changed files with 122 additions and 13 deletions
@@ -86,6 +86,31 @@ public sealed class CipTemplateObjectDecoderTests
shape.Members.Single().DataType.ShouldBe(AbCipDataType.Structure);
}
/// <summary>
/// A struct member's lower 12 bits carry the nested UDT's template instance id (same
/// encoding as the Symbol Object); the decoder captures it as <c>NestedTemplateId</c> so the
/// nested shape can be fetched. A scalar member carries no nested id. Uses the FULL 12-bit
/// mask (0x123 here exceeds a byte) to prove the id is not byte-truncated.
/// </summary>
[Fact]
public void Struct_member_captures_nested_template_id_scalar_member_does_not()
{
var bytes = BuildTemplate("ContainerUdt", instanceSize: 8,
("Inner", info: (ushort)(0x8000 | 0x123), arraySize: 0, offset: 0), // struct + nested id 0x123
("Count", info: 0xC4, arraySize: 0, offset: 4)); // scalar DINT, no nested id
var shape = CipTemplateObjectDecoder.Decode(bytes);
shape.ShouldNotBeNull();
shape.Members.Count.ShouldBe(2);
shape.Members[0].Name.ShouldBe("Inner");
shape.Members[0].DataType.ShouldBe(AbCipDataType.Structure);
shape.Members[0].NestedTemplateId.ShouldBe(0x123u); // full 12-bit id, not byte-truncated
shape.Members[1].Name.ShouldBe("Count");
shape.Members[1].DataType.ShouldBe(AbCipDataType.DInt);
shape.Members[1].NestedTemplateId.ShouldBeNull();
}
/// <summary>Verifies that array members carry correct non-one array lengths.</summary>
[Fact]
public void Array_member_carries_non_one_ArrayLength()