using System.Buffers.Binary;
namespace ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests.Captures;
/// One EtherNet/IP encapsulation frame from a golden capture.
/// true for a client→server (>) frame.
/// The full frame: 24-byte encapsulation header plus data.
internal sealed record CapturedFrame(bool FromClient, byte[] Bytes);
///
/// Loads the golden libplctag ⇄ ab_server captures in Captures/*.hex. See
/// Captures/README.md for how each was taken.
///
internal static class CaptureFile
{
/// The directory the .hex fixtures are copied to.
public static string Dir => Path.Combine(AppContext.BaseDirectory, "Captures");
/// Loads every frame of a capture, in wire order; # lines are skipped.
/// The capture's file name, e.g. logix-read-dint.hex.
/// The frames.
public static IReadOnlyList Load(string name) =>
File.ReadAllLines(Path.Combine(Dir, name))
.Where(l => l.Length > 2 && l[0] is '>' or '<')
.Select(l => new CapturedFrame(l[0] == '>', Convert.FromHexString(l[2..].Trim())))
.ToList();
/// The CIP payload of a SendRRData / SendUnitData frame (the last CPF item's data,
/// minus the 2-byte sequence count for connected data).
/// The frame.
/// The CIP payload, or empty for any other encapsulation command.
public static byte[] CipPayload(CapturedFrame f)
{
var b = f.Bytes;
var cmd = BinaryPrimitives.ReadUInt16LittleEndian(b.AsSpan(0));
if (cmd is not (0x6F or 0x70)) return []; // only SendRRData / SendUnitData carry CPF items
var pos = 24 + 4 + 2; // header + interface handle + timeout
var itemCount = BinaryPrimitives.ReadUInt16LittleEndian(b.AsSpan(pos)); pos += 2;
byte[] last = [];
for (var i = 0; i < itemCount; i++)
{
var type = BinaryPrimitives.ReadUInt16LittleEndian(b.AsSpan(pos));
var len = BinaryPrimitives.ReadUInt16LittleEndian(b.AsSpan(pos + 2));
last = b.AsSpan(pos + 4, len).ToArray();
if (type == 0x00B1) last = last[2..]; // strip connected sequence count
pos += 4 + len;
}
return last;
}
}