using System.Buffers.Binary; using System.Text; namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests; /// /// Builds an @udt/<id> template buffer in the layout libplctag really returns — /// the layout pinned by the real-controller captures in CipTemplateObjectWireCaptureTests. /// Keep it in step with those captures, never with the decoder: a builder that restates the /// decoder's assumption proves nothing (#468, and the template decoder after it). /// internal static class TemplateBufferBuilder { /// Build a template buffer. /// UDT type name (written as name;n + an encoded tail, as a controller does). /// Bytes per instance. /// Per member: name, CIP type word (atomic code, or 0x8000 | nestedTemplateId), /// array length (0 = scalar; > 0 sets the array flag and the info word), byte offset. /// The buffer. public static byte[] Build( string udtName, uint instanceSize, params (string name, ushort type, ushort arraySize, uint offset)[] members) { const int header = 14; var buf = new MemoryStream(); var head = new byte[header + (8 * members.Length)]; BinaryPrimitives.WriteUInt16LittleEndian(head.AsSpan(0), 0x0123); // template id BinaryPrimitives.WriteUInt32LittleEndian(head.AsSpan(2), 0); // def size (words) BinaryPrimitives.WriteUInt32LittleEndian(head.AsSpan(6), instanceSize); BinaryPrimitives.WriteUInt16LittleEndian(head.AsSpan(10), (ushort)members.Length); BinaryPrimitives.WriteUInt16LittleEndian(head.AsSpan(12), 0xABCD); // struct handle for (var i = 0; i < members.Length; i++) { var o = header + (i * 8); var (_, type, arraySize, offset) = members[i]; var typeWord = arraySize > 0 ? (ushort)(type | 0x2000) : type; BinaryPrimitives.WriteUInt16LittleEndian(head.AsSpan(o), arraySize); BinaryPrimitives.WriteUInt16LittleEndian(head.AsSpan(o + 2), typeWord); BinaryPrimitives.WriteUInt32LittleEndian(head.AsSpan(o + 4), offset); } buf.Write(head); buf.Write(Encoding.ASCII.GetBytes(udtName + ";nEAIA\0")); foreach (var m in members) buf.Write(Encoding.ASCII.GetBytes(m.name + "\0")); return buf.ToArray(); } }