using System;
using System.Buffers.Binary;
using System.Text;
namespace ZeroDDS.Cdr;
public ref struct Xcdr2Reader
{
private readonly ReadOnlySpan<byte> _buf;
private readonly EndianMode _endian;
private int _pos;
private int _origin;
public Xcdr2Reader(ReadOnlySpan<byte> bytes) : this(bytes, EndianMode.LittleEndian) { }
public Xcdr2Reader(ReadOnlySpan<byte> bytes, EndianMode endian)
{
_buf = bytes;
_endian = endian;
_pos = 0;
_origin = 0;
}
public EndianMode Endian => _endian;
public int Position => _pos;
public int Remaining => _buf.Length - _pos;
public void Align(int alignment)
{
if (alignment != 1 && alignment != 2 && alignment != 4 && alignment != 8)
{
throw new ArgumentOutOfRangeException(nameof(alignment),
"alignment must be one of {1,2,4,8}");
}
int offset = _pos - _origin;
int pad = (alignment - (offset % alignment)) % alignment;
if (_pos + pad > _buf.Length)
{
throw new XcdrException(
$"alignment skip past end-of-buffer (pos={_pos}, pad={pad}, len={_buf.Length})");
}
_pos += pad;
}
public ReadOnlySpan<byte> ReadBytes(int count)
{
if (count < 0) throw new ArgumentOutOfRangeException(nameof(count));
if (_pos + count > _buf.Length)
{
throw new XcdrException(
$"read past end-of-buffer (pos={_pos}, count={count}, len={_buf.Length})");
}
var slice = _buf.Slice(_pos, count);
_pos += count;
return slice;
}
public bool ReadBool()
{
var b = ReadByte();
if (b != 0 && b != 1)
{
throw new XcdrException($"invalid boolean encoding 0x{b:X2}");
}
return b != 0;
}
public byte ReadByte()
{
if (_pos >= _buf.Length)
{
throw new XcdrException("read past end-of-buffer");
}
return _buf[_pos++];
}
public byte ReadOctet() => ReadByte();
public short ReadInt16()
{
Align(2);
var s = ReadBytes(2);
return _endian == EndianMode.LittleEndian
? BinaryPrimitives.ReadInt16LittleEndian(s)
: BinaryPrimitives.ReadInt16BigEndian(s);
}
public ushort ReadUInt16()
{
Align(2);
var s = ReadBytes(2);
return _endian == EndianMode.LittleEndian
? BinaryPrimitives.ReadUInt16LittleEndian(s)
: BinaryPrimitives.ReadUInt16BigEndian(s);
}
public int ReadInt32()
{
Align(4);
var s = ReadBytes(4);
return _endian == EndianMode.LittleEndian
? BinaryPrimitives.ReadInt32LittleEndian(s)
: BinaryPrimitives.ReadInt32BigEndian(s);
}
public uint ReadUInt32()
{
Align(4);
var s = ReadBytes(4);
return _endian == EndianMode.LittleEndian
? BinaryPrimitives.ReadUInt32LittleEndian(s)
: BinaryPrimitives.ReadUInt32BigEndian(s);
}
public long ReadInt64()
{
Align(8);
var s = ReadBytes(8);
return _endian == EndianMode.LittleEndian
? BinaryPrimitives.ReadInt64LittleEndian(s)
: BinaryPrimitives.ReadInt64BigEndian(s);
}
public ulong ReadUInt64()
{
Align(8);
var s = ReadBytes(8);
return _endian == EndianMode.LittleEndian
? BinaryPrimitives.ReadUInt64LittleEndian(s)
: BinaryPrimitives.ReadUInt64BigEndian(s);
}
public float ReadFloat32()
{
Align(4);
var s = ReadBytes(4);
return _endian == EndianMode.LittleEndian
? BinaryPrimitives.ReadSingleLittleEndian(s)
: BinaryPrimitives.ReadSingleBigEndian(s);
}
public double ReadFloat64()
{
Align(8);
var s = ReadBytes(8);
return _endian == EndianMode.LittleEndian
? BinaryPrimitives.ReadDoubleLittleEndian(s)
: BinaryPrimitives.ReadDoubleBigEndian(s);
}
public char ReadWChar() => (char)ReadUInt16();
public string ReadString()
{
uint len = ReadUInt32();
if (len == 0)
{
throw new XcdrException("string length must be >= 1 (NUL terminator required)");
}
var bytes = ReadBytes((int)len);
if (bytes[bytes.Length - 1] != 0)
{
throw new XcdrException("string is not NUL-terminated");
}
return Encoding.UTF8.GetString(bytes.Slice(0, bytes.Length - 1));
}
public string ReadWString()
{
uint len = ReadUInt32();
var sb = new StringBuilder((int)len);
for (uint i = 0; i < len; i++)
{
sb.Append((char)ReadUInt16());
}
return sb.ToString();
}
public int ReadSequenceLength()
{
uint len = ReadUInt32();
if (len > int.MaxValue)
{
throw new XcdrException($"sequence length overflow: {len}");
}
return (int)len;
}
public DHeaderReadScope BeginDHeader()
{
Align(4);
uint size = ReadUInt32();
int previousOrigin = _origin;
_origin = _pos;
if (_pos + size > _buf.Length)
{
throw new XcdrException(
$"DHEADER size {size} exceeds buffer (pos={_pos}, len={_buf.Length})");
}
return new DHeaderReadScope(_pos, _origin + (int)size, previousOrigin);
}
public void EndDHeader(DHeaderReadScope scope)
{
if (_pos > scope.BodyEnd)
{
throw new XcdrException(
$"read past DHEADER body-end (pos={_pos}, body-end={scope.BodyEnd})");
}
_pos = scope.BodyEnd;
_origin = scope.PreviousOrigin;
}
public bool DHeaderDone(DHeaderReadScope scope) => _pos >= scope.BodyEnd;
public (uint MemberId, int Lc, bool MustUnderstand) ReadEmHeader()
{
uint header = ReadUInt32();
bool mu = (header & 0x80000000u) != 0;
int lc = (int)((header >> 28) & 0x7u);
uint id = header & 0x0FFFFFFFu;
return (id, lc, mu);
}
}
public readonly struct DHeaderReadScope
{
public int BodyStart { get; }
public int BodyEnd { get; }
public int PreviousOrigin { get; }
internal DHeaderReadScope(int bodyStart, int bodyEnd, int previousOrigin)
{
BodyStart = bodyStart;
BodyEnd = bodyEnd;
PreviousOrigin = previousOrigin;
}
}