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();
}
}