using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace ZeroDDS.Cdr;
public sealed class Xcdr2Writer
{
private readonly MemoryStream _buf;
private readonly EndianMode _endian;
private long _alignOrigin;
public Xcdr2Writer() : this(EndianMode.LittleEndian) { }
public Xcdr2Writer(EndianMode endian)
{
_buf = new MemoryStream();
_endian = endian;
_alignOrigin = 0;
}
public EndianMode Endian => _endian;
public long Position => _buf.Position;
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}");
}
long offset = _buf.Position - _alignOrigin;
long pad = (alignment - (offset % alignment)) % alignment;
for (long i = 0; i < pad; i++)
{
_buf.WriteByte(0);
}
}
public void WriteByte(byte value) => _buf.WriteByte(value);
public void WriteBytes(ReadOnlySpan<byte> data)
{
#if NETSTANDARD2_0
var arr = data.ToArray();
_buf.Write(arr, 0, arr.Length);
#else
_buf.Write(data);
#endif
}
public void WriteBool(bool value) => _buf.WriteByte(value ? (byte)1 : (byte)0);
public void WriteOctet(byte value) => _buf.WriteByte(value);
public void WriteInt16(short value)
{
Align(2);
Span<byte> tmp = stackalloc byte[2];
if (_endian == EndianMode.LittleEndian)
{
BinaryPrimitives.WriteInt16LittleEndian(tmp, value);
}
else
{
BinaryPrimitives.WriteInt16BigEndian(tmp, value);
}
WriteBytes(tmp);
}
public void WriteUInt16(ushort value)
{
Align(2);
Span<byte> tmp = stackalloc byte[2];
if (_endian == EndianMode.LittleEndian)
{
BinaryPrimitives.WriteUInt16LittleEndian(tmp, value);
}
else
{
BinaryPrimitives.WriteUInt16BigEndian(tmp, value);
}
WriteBytes(tmp);
}
public void WriteInt32(int value)
{
Align(4);
Span<byte> tmp = stackalloc byte[4];
if (_endian == EndianMode.LittleEndian)
{
BinaryPrimitives.WriteInt32LittleEndian(tmp, value);
}
else
{
BinaryPrimitives.WriteInt32BigEndian(tmp, value);
}
WriteBytes(tmp);
}
public void WriteUInt32(uint value)
{
Align(4);
Span<byte> tmp = stackalloc byte[4];
if (_endian == EndianMode.LittleEndian)
{
BinaryPrimitives.WriteUInt32LittleEndian(tmp, value);
}
else
{
BinaryPrimitives.WriteUInt32BigEndian(tmp, value);
}
WriteBytes(tmp);
}
public void WriteInt64(long value)
{
Align(8);
Span<byte> tmp = stackalloc byte[8];
if (_endian == EndianMode.LittleEndian)
{
BinaryPrimitives.WriteInt64LittleEndian(tmp, value);
}
else
{
BinaryPrimitives.WriteInt64BigEndian(tmp, value);
}
WriteBytes(tmp);
}
public void WriteUInt64(ulong value)
{
Align(8);
Span<byte> tmp = stackalloc byte[8];
if (_endian == EndianMode.LittleEndian)
{
BinaryPrimitives.WriteUInt64LittleEndian(tmp, value);
}
else
{
BinaryPrimitives.WriteUInt64BigEndian(tmp, value);
}
WriteBytes(tmp);
}
public void WriteFloat32(float value)
{
Align(4);
Span<byte> tmp = stackalloc byte[4];
if (_endian == EndianMode.LittleEndian)
{
BinaryPrimitives.WriteSingleLittleEndian(tmp, value);
}
else
{
BinaryPrimitives.WriteSingleBigEndian(tmp, value);
}
WriteBytes(tmp);
}
public void WriteFloat64(double value)
{
Align(8);
Span<byte> tmp = stackalloc byte[8];
if (_endian == EndianMode.LittleEndian)
{
BinaryPrimitives.WriteDoubleLittleEndian(tmp, value);
}
else
{
BinaryPrimitives.WriteDoubleBigEndian(tmp, value);
}
WriteBytes(tmp);
}
public void WriteWChar(char value) => WriteUInt16(value);
public void WriteString(string value)
{
if (value is null) throw new ArgumentNullException(nameof(value));
var bytes = Encoding.UTF8.GetBytes(value);
WriteUInt32((uint)(bytes.Length + 1));
WriteBytes(bytes);
_buf.WriteByte(0);
}
public void WriteWString(string value)
{
if (value is null) throw new ArgumentNullException(nameof(value));
WriteUInt32((uint)value.Length);
for (int i = 0; i < value.Length; i++)
{
WriteUInt16(value[i]);
}
}
public void WriteSequenceLength(int count)
{
if (count < 0) throw new ArgumentOutOfRangeException(nameof(count));
WriteUInt32((uint)count);
}
public DHeaderScope BeginDHeader()
{
Align(4);
long headerPos = _buf.Position;
Span<byte> placeholder = stackalloc byte[4];
WriteBytes(placeholder);
long previousOrigin = _alignOrigin;
_alignOrigin = _buf.Position;
return new DHeaderScope(this, headerPos, previousOrigin, _alignOrigin);
}
internal void EndDHeader(DHeaderScope scope)
{
long endPos = _buf.Position;
long bodyStart = scope.BodyStart;
long size = endPos - bodyStart;
if (size < 0 || size > uint.MaxValue)
{
throw new XcdrException("DHEADER object-size overflow");
}
long current = _buf.Position;
_buf.Position = scope.HeaderPos;
Span<byte> tmp = stackalloc byte[4];
if (_endian == EndianMode.LittleEndian)
{
BinaryPrimitives.WriteUInt32LittleEndian(tmp, (uint)size);
}
else
{
BinaryPrimitives.WriteUInt32BigEndian(tmp, (uint)size);
}
WriteBytes(tmp);
_buf.Position = current;
_alignOrigin = scope.PreviousOrigin;
}
public DHeaderScope BeginAppendable() => BeginDHeader();
public DHeaderScope BeginMutable() => BeginDHeader();
public void WriteEmHeader(uint memberId, int lc, bool mustUnderstand)
{
if (lc < 0 || lc > 7) throw new ArgumentOutOfRangeException(nameof(lc));
if (memberId > 0x0FFFFFFF) throw new ArgumentOutOfRangeException(nameof(memberId));
Align(4);
uint header = (memberId & 0x0FFFFFFFu)
| (((uint)lc & 0x7u) << 28)
| (mustUnderstand ? 0x80000000u : 0u);
WriteUInt32(header);
}
public void WriteEmHeaderNextInt(uint memberId, int lc, bool mustUnderstand, uint nextInt)
{
if (lc < 4 || lc > 7) throw new ArgumentOutOfRangeException(nameof(lc));
WriteEmHeader(memberId, lc, mustUnderstand);
WriteUInt32(nextInt);
}
public byte[] ToArray() => _buf.ToArray();
}
public readonly struct DHeaderScope : IDisposable
{
private readonly Xcdr2Writer _writer;
internal long HeaderPos { get; }
internal long PreviousOrigin { get; }
internal long BodyStart { get; }
internal DHeaderScope(Xcdr2Writer writer, long headerPos, long previousOrigin, long bodyStart)
{
_writer = writer;
HeaderPos = headerPos;
PreviousOrigin = previousOrigin;
BodyStart = bodyStart;
}
public void Dispose() => _writer.EndDHeader(this);
}