2
0
mirror of https://github.com/esiur/esiur-dotnet.git synced 2026-06-13 14:38:43 +00:00

new tests

This commit is contained in:
2026-05-25 14:12:56 +03:00
parent eb323e8bf8
commit 7e27d3cfac
16 changed files with 1982 additions and 42 deletions
@@ -0,0 +1,511 @@
using System;
using System.Collections.Generic;
using System.Linq;
#nullable enable
namespace Esiur.Tests.ComplexModel;
// ============================================================================
// DdsCdrCodec.cs
// ----------------------------------------------------------------------------
// Implements ICodec by encoding BusinessDocument as OMG XCDR2 (Extended CDR
// version 2, PLAIN_CDR2, all types FINAL). This is the on-the-wire payload
// format used by every conformant DDS implementation per DDS-XTypes 1.3 for
// final-extensibility types.
//
// The corresponding IDL definition lives in BusinessDocument.idl alongside
// this file. The struct layout, member order, and union discriminator
// values below MUST match the IDL exactly; a divergence between this code
// and the IDL would produce a wire format incompatible with real DDS
// implementations, defeating the purpose of the benchmark.
//
// Encoding choices documented inline. Where the spec offered multiple
// equivalent options (e.g., DateTime as int64 ticks vs. struct), the choice
// that minimizes wire size was selected, so that this measurement reports
// the most favorable DDS payload size achievable for this schema.
// ============================================================================
public sealed class DdsCdrCodec : ICodec
{
public string Name => "DDS-XCDR2";
public byte[]? Serialize(BusinessDocument obj)
{
var w = new Xcdr2Writer(capacity: 8192);
WriteBusinessDocument(w, obj);
var bytes = w.ToArray();
// Optional self-check (cheap): decode and compare. Mirrors the
// pattern used by FlatBuffersCodec and ProtobufCodec in
// ModelRunner.cs for outside-the-timing-loop validation.
// Disabled by default to keep parity with most other codecs; the
// outer harness performs equality testing.
return bytes;
}
public BusinessDocument Deserialize(byte[] data)
{
var r = new Xcdr2Reader(data);
return ReadBusinessDocument(r);
}
// ---- BusinessDocument (FINAL struct, top-level) -----------------------
//
// In XCDR2 the top-level FINAL struct does NOT carry a DHEADER. It is
// emitted as a plain concatenation of members in declaration order.
// (DDS-XTypes 1.3 §7.4.3.5.3 rule 7: FINAL aggregated types use
// PLAIN_CDR2 without delimiter.)
private static void WriteBusinessDocument(Xcdr2Writer w, BusinessDocument d)
{
// member 0: optional<DocumentHeader> Header
WriteOptionalStruct(w, d.Header, WriteDocumentHeader);
// member 1: optional<Party> Seller
WriteOptionalStruct(w, d.Seller, WriteParty);
// member 2: optional<Party> Buyer
WriteOptionalStruct(w, d.Buyer, WriteParty);
// member 3: sequence<LineItem> Items
WriteSequenceOfStruct(w, d.Items, WriteLineItem);
// member 4: sequence<Payment> Payments
WriteSequenceOfStruct(w, d.Payments, WritePayment);
// member 5: sequence<Attachment> Attachments
WriteSequenceOfStruct(w, d.Attachments, WriteAttachment);
// member 6: sequence<int32> RiskScores (primitive, no DHEADER)
WriteSequenceOfInt32(w, d.RiskScores);
}
private static BusinessDocument ReadBusinessDocument(Xcdr2Reader r)
{
var d = new BusinessDocument
{
Header = ReadOptionalStruct(r, ReadDocumentHeader),
Seller = ReadOptionalStruct(r, ReadParty),
Buyer = ReadOptionalStruct(r, ReadParty),
Items = ReadSequenceOfStruct(r, ReadLineItem),
Payments = ReadSequenceOfStruct(r, ReadPayment),
Attachments = ReadSequenceOfStruct(r, ReadAttachment),
RiskScores = ReadSequenceOfInt32(r),
};
return d;
}
// ---- DocumentHeader ---------------------------------------------------
private static void WriteDocumentHeader(Xcdr2Writer w, DocumentHeader h)
{
// member 0: sequence<octet> DocId (primitive seq, no DHEADER)
w.WriteOctetSequence(h.DocId ?? Array.Empty<byte>());
// member 1: DocType Type (enum -> int32)
w.WriteInt32((int)h.Type);
// member 2: int32 Version
w.WriteInt32(h.Version);
// member 3: int64 CreatedAtTicks
w.WriteInt64(h.CreatedAt.Ticks);
// member 4: optional<int64> UpdatedAtTicks
if (h.UpdatedAt.HasValue)
{
w.WriteBool(true);
w.WriteInt64(h.UpdatedAt.Value.Ticks);
}
else
{
w.WriteBool(false);
}
// member 5: Currency (enum -> int32)
w.WriteInt32((int)h.Currency);
// member 6: optional<string> Notes
WriteOptionalString(w, h.Notes);
// member 7: sequence<MetaEntry> Meta (non-primitive seq -> DHEADER)
WriteVariantDictionary(w, h.Meta);
}
private static DocumentHeader ReadDocumentHeader(Xcdr2Reader r)
{
var h = new DocumentHeader();
h.DocId = r.ReadOctetSequence();
h.Type = (DocType)r.ReadInt32();
h.Version = r.ReadInt32();
h.CreatedAt = new DateTime(r.ReadInt64());
h.UpdatedAt = r.ReadBool() ? new DateTime(r.ReadInt64()) : (DateTime?)null;
h.Currency = (Currency)r.ReadInt32();
h.Notes = ReadOptionalString(r);
h.Meta = ReadVariantDictionary(r);
if (h.Meta != null)
{
h.MetaKeys = h.Meta.Keys.ToArray();
h.MetaValues = h.Meta.Values.ToArray();
}
return h;
}
// ---- Party ------------------------------------------------------------
private static void WriteParty(Xcdr2Writer w, Party p)
{
w.WriteUInt64(p.Id);
w.WriteString(p.Name);
WriteOptionalString(w, p.TaxId);
WriteOptionalString(w, p.Email);
WriteOptionalString(w, p.Phone);
// optional<Address>
if (p.Address != null)
{
w.WriteBool(true);
WriteAddress(w, p.Address);
}
else
{
w.WriteBool(false);
}
WriteOptionalString(w, p.PreferredLanguage);
}
private static Party ReadParty(Xcdr2Reader r)
{
return new Party
{
Id = r.ReadUInt64(),
Name = r.ReadString(),
TaxId = ReadOptionalString(r),
Email = ReadOptionalString(r),
Phone = ReadOptionalString(r),
Address = r.ReadBool() ? ReadAddress(r) : null,
PreferredLanguage = ReadOptionalString(r),
};
}
// ---- Address ----------------------------------------------------------
private static void WriteAddress(Xcdr2Writer w, Address a)
{
w.WriteString(a.Line1);
WriteOptionalString(w, a.Line2);
w.WriteString(a.City);
w.WriteString(a.Region);
w.WriteString(a.Country);
WriteOptionalString(w, a.PostalCode);
}
private static Address ReadAddress(Xcdr2Reader r)
{
return new Address
{
Line1 = r.ReadString(),
Line2 = ReadOptionalString(r),
City = r.ReadString(),
Region = r.ReadString(),
Country = r.ReadString(),
PostalCode = ReadOptionalString(r),
};
}
// ---- LineItem ---------------------------------------------------------
private static void WriteLineItem(Xcdr2Writer w, LineItem li)
{
w.WriteInt32(li.LineNo);
w.WriteInt32((int)li.Type);
w.WriteString(li.SKU);
w.WriteString(li.Description);
w.WriteDouble(li.Qty);
w.WriteString(li.QtyUnit);
w.WriteDouble(li.UnitPrice);
WriteOptionalDouble(w, li.VatRate);
WriteOptionalDouble(w, li.Discount);
WriteVariantDictionary(w, li.Ext);
}
private static LineItem ReadLineItem(Xcdr2Reader r)
{
var li = new LineItem
{
LineNo = r.ReadInt32(),
Type = (LineType)r.ReadInt32(),
SKU = r.ReadString(),
Description = r.ReadString(),
Qty = r.ReadDouble(),
QtyUnit = r.ReadString(),
UnitPrice = r.ReadDouble(),
VatRate = ReadOptionalDouble(r),
Discount = ReadOptionalDouble(r),
Ext = ReadVariantDictionary(r),
};
if (li.Ext != null)
{
li.ExtKeys = li.Ext.Keys.ToArray();
li.ExtValues = li.Ext.Values.ToArray();
}
return li;
}
// ---- Payment ----------------------------------------------------------
private static void WritePayment(Xcdr2Writer w, Payment p)
{
w.WriteInt32((int)p.Method);
w.WriteDouble(p.Amount);
WriteOptionalString(w, p.Reference);
w.WriteInt64(p.Timestamp.Ticks);
WriteOptionalDouble(w, p.Fee);
}
private static Payment ReadPayment(Xcdr2Reader r)
{
return new Payment
{
Method = (PaymentMethod)r.ReadInt32(),
Amount = r.ReadDouble(),
Reference = ReadOptionalString(r),
Timestamp = new DateTime(r.ReadInt64()),
Fee = ReadOptionalDouble(r),
};
}
// ---- Attachment -------------------------------------------------------
private static void WriteAttachment(Xcdr2Writer w, Attachment a)
{
w.WriteString(a.Name);
w.WriteString(a.MimeType);
w.WriteOctetSequence(a.Data);
}
private static Attachment ReadAttachment(Xcdr2Reader r)
{
return new Attachment
{
Name = r.ReadString(),
MimeType = r.ReadString(),
Data = r.ReadOctetSequence(),
};
}
// ---- Variant (union discriminated by Kind) ----------------------------
//
// IDL mapping (see BusinessDocument.idl):
// union Variant switch(int32 /* Kind */) {
// case 0 (Null): /* no member */;
// case 1 (Bool): boolean b;
// case 2 (Int64): int64 i64;
// case 3 (UInt64): uint64 u64;
// case 4 (Double): double d;
// case 6 (String): string s;
// case 7 (Bytes): sequence<octet> by;
// case 8 (DateTime): int64 dt; // ticks
// case 9 (Guid): octet[16] g;
// };
//
// XCDR2 union encoding: int32 discriminator + the selected branch.
private static void WriteVariant(Xcdr2Writer w, Variant v)
{
int tag = (int)v.Tag;
w.WriteInt32(tag);
switch (v.Tag)
{
case Variant.Kind.Null:
break;
case Variant.Kind.Bool:
w.WriteBool(v.Bool ?? false);
break;
case Variant.Kind.Int64:
w.WriteInt64(v.I64 ?? 0);
break;
case Variant.Kind.UInt64:
w.WriteUInt64(v.U64 ?? 0);
break;
case Variant.Kind.Double:
case Variant.Kind.Decimal: // Decimal mapped to double in IDL
w.WriteDouble(v.F64 ?? 0.0);
break;
case Variant.Kind.String:
w.WriteString(v.Str ?? "");
break;
case Variant.Kind.Bytes:
w.WriteOctetSequence(v.Bytes ?? Array.Empty<byte>());
break;
case Variant.Kind.DateTime:
w.WriteInt64(v.Dt?.Ticks ?? v.DtAsLong);
break;
case Variant.Kind.Guid:
w.WriteOctetArrayFixed(v.Guid ?? new byte[16], 16);
break;
default:
throw new InvalidOperationException($"Unknown Variant kind {v.Tag}");
}
}
private static Variant ReadVariant(Xcdr2Reader r)
{
var tag = (Variant.Kind)r.ReadInt32();
var v = new Variant { Tag = tag };
switch (tag)
{
case Variant.Kind.Null:
break;
case Variant.Kind.Bool:
v.Bool = r.ReadBool();
break;
case Variant.Kind.Int64:
v.I64 = r.ReadInt64();
break;
case Variant.Kind.UInt64:
v.U64 = r.ReadUInt64();
break;
case Variant.Kind.Double:
case Variant.Kind.Decimal:
v.F64 = r.ReadDouble();
break;
case Variant.Kind.String:
v.Str = r.ReadString();
break;
case Variant.Kind.Bytes:
v.Bytes = r.ReadOctetSequence();
break;
case Variant.Kind.DateTime:
{
long ticks = r.ReadInt64();
v.Dt = new DateTime(ticks);
v.DtAsLong = ticks;
break;
}
case Variant.Kind.Guid:
v.Guid = r.ReadOctetArrayFixed(16);
break;
default:
throw new InvalidOperationException($"Unknown Variant kind {tag}");
}
return v;
}
// ---- Dictionary<string, Variant> -> sequence<MetaEntry> ---------------
//
// IDL: struct MetaEntry { string key; Variant value; };
// sequence<MetaEntry> ...
//
// Non-primitive sequence: per XCDR2 rule 15, MUST emit DHEADER before
// the sequence length and elements.
private static void WriteVariantDictionary(Xcdr2Writer w, Dictionary<string, Variant>? dict)
{
int token = w.BeginDHeader();
if (dict == null)
{
w.WriteUInt32(0);
}
else
{
w.WriteUInt32((uint)dict.Count);
foreach (var kv in dict)
{
w.WriteString(kv.Key);
WriteVariant(w, kv.Value);
}
}
w.EndDHeader(token);
}
private static Dictionary<string, Variant>? ReadVariantDictionary(Xcdr2Reader r)
{
_ = r.ReadDHeader(); // size, ignored (schema-driven decode)
uint n = r.ReadUInt32();
if (n == 0) return new Dictionary<string, Variant>();
var d = new Dictionary<string, Variant>((int)n);
for (uint i = 0; i < n; i++)
{
var k = r.ReadString();
var v = ReadVariant(r);
d[k] = v;
}
return d;
}
// ---- helpers: optional<T> and sequence<T> -----------------------------
private static void WriteOptionalString(Xcdr2Writer w, string? s)
{
if (s is null) { w.WriteBool(false); }
else { w.WriteBool(true); w.WriteString(s); }
}
private static string? ReadOptionalString(Xcdr2Reader r)
=> r.ReadBool() ? r.ReadString() : null;
private static void WriteOptionalDouble(Xcdr2Writer w, double? v)
{
if (v is null) { w.WriteBool(false); }
else { w.WriteBool(true); w.WriteDouble(v.Value); }
}
private static double? ReadOptionalDouble(Xcdr2Reader r)
=> r.ReadBool() ? r.ReadDouble() : null;
private static void WriteOptionalStruct<T>(
Xcdr2Writer w, T? value, Action<Xcdr2Writer, T> writeInner)
where T : class
{
if (value is null) { w.WriteBool(false); }
else { w.WriteBool(true); writeInner(w, value); }
}
private static T? ReadOptionalStruct<T>(
Xcdr2Reader r, Func<Xcdr2Reader, T> readInner)
where T : class
{
return r.ReadBool() ? readInner(r) : null;
}
private static void WriteSequenceOfStruct<T>(
Xcdr2Writer w, T[]? arr, Action<Xcdr2Writer, T> writeInner)
{
// Non-primitive sequence: DHEADER required.
int token = w.BeginDHeader();
if (arr is null)
{
w.WriteUInt32(0);
}
else
{
w.WriteUInt32((uint)arr.Length);
for (int i = 0; i < arr.Length; i++)
writeInner(w, arr[i]);
}
w.EndDHeader(token);
}
private static T[] ReadSequenceOfStruct<T>(
Xcdr2Reader r, Func<Xcdr2Reader, T> readInner)
{
_ = r.ReadDHeader();
uint n = r.ReadUInt32();
var arr = new T[n];
for (uint i = 0; i < n; i++)
arr[i] = readInner(r);
return arr;
}
private static void WriteSequenceOfInt32(Xcdr2Writer w, int[]? arr)
{
// Primitive sequence: NO DHEADER per XCDR2 rule 14.
if (arr is null)
{
w.WriteUInt32(0);
}
else
{
w.WriteUInt32((uint)arr.Length);
for (int i = 0; i < arr.Length; i++)
w.WriteInt32(arr[i]);
}
}
private static int[] ReadSequenceOfInt32(Xcdr2Reader r)
{
uint n = r.ReadUInt32();
var arr = new int[n];
for (uint i = 0; i < n; i++)
arr[i] = r.ReadInt32();
return arr;
}
}
@@ -279,13 +279,14 @@ public sealed class ModelRunner
_codecs = new ICodec[]
{
new JsonCodec(),
new EsiurCodec(),
new EsiurCodec(),
new DdsCdrCodec(),
new MessagePackCodec(),
new ProtobufCodec(),
new FlatBuffersCodec(),
new CborCodec(),
new BsonCodec(),
new AvroCodec()
new AvroCodec(),
};
}
@@ -380,7 +381,7 @@ public sealed class ModelRunner
Console.WriteLine();
Console.WriteLine("{0,-14} {1,12} {2,12} {3,10} {4,26} {5,18} {6,18}",
"Codec", "Mean(B)", "Median(B)", "Ratio", "Class vs JSON", "Enc CPU (µs)", "Dec CPU (µs)");
"Codec", "Mean(B)", "Median(B)", "Ratio", "Reduction", "Enc CPU (µs)", "Dec CPU (µs)");
Console.WriteLine(new string('-', 118));
foreach (var c in _codecs)
@@ -397,18 +398,19 @@ public sealed class ModelRunner
string meanS = double.IsNaN(mean) ? "N/A" : mean.ToString("F1");
string medS = double.IsNaN(med) ? "N/A" : med.ToString("F1");
string ratioS = double.IsNaN(ratio) ? "N/A" : ratio.ToString("F3");
string reduction = double.IsNaN(ratio) ? "N/A" : ((1 - ratio) * 100).ToString("F3");
// average CPU µs/op across samples where serialization succeeded
string encCpuS = (r.Samples == 0) ? "N/A" : (r.EncodeCpuUsSum / r.Samples).ToString("F1");
string decCpuS = (r.Samples == 0) ? "N/A" : (r.DecodeCpuUsSum / r.Samples).ToString("F1");
Console.WriteLine("{0,-14} {1,12} {2,12} {3,10} {4,26} {5,18} {6,18}",
c.Name, meanS, medS, ratioS, cls, encCpuS, decCpuS);
c.Name, meanS, medS, ratioS, reduction, encCpuS, decCpuS);
}
Console.WriteLine();
Console.ReadLine();
//Console.ReadLine();
}
}
@@ -0,0 +1,332 @@
using System;
using System.Buffers.Binary;
using System.IO;
using System.Text;
#nullable enable
namespace Esiur.Tests.ComplexModel;
// ============================================================================
// Xcdr2Stream.cs
// ----------------------------------------------------------------------------
// OMG Extended Common Data Representation (XCDR) Version 2 encoder/decoder.
//
// Implements PLAIN_CDR2 for FINAL-extensibility structures, the most compact
// XCDR2 mode defined by DDS-XTypes 1.3 (OMG formal/2024-04-01). This mode is
// the on-the-wire format used by every conformant DDS implementation when
// the @final annotation is applied (or no extensibility annotation is given
// and the implementation defaults to final).
//
// Implemented rules (DDS-XTypes 1.3, §7.4.3.5.3 Complete Serialization Rules):
// - Encapsulation header (4 bytes): representation_identifier (2B) +
// options (2B). We use CDR2_LE = 0x00 0x09 for the identifier and
// 0x00 0x00 for the options field.
// - Maximum alignment is 4 bytes (vs 8 in XCDR1). 64-bit primitives align
// to 4, not 8.
// - Strings: uint32 length-including-null + UTF-8 bytes + 0x00 terminator.
// - Sequences of primitives: uint32 length + elements (rule 14, no DHEADER).
// - Sequences of non-primitives: DHEADER (uint32, bytes-remaining) +
// uint32 length + elements (rule 15).
// - Optionals: 1-byte is_present + value if present (rule 9).
// - Unions (Variant): int32 discriminator aligned to 4 + selected branch.
// - Octet arrays of fixed length: emitted as-is, no length prefix.
//
// Reference implementations consulted:
// - foxglove/cdr (https://github.com/foxglove/cdr)
// - eclipse-cyclonedds dds_cdrstream.c
// - eProsima Fast-CDR
// ============================================================================
internal sealed class Xcdr2Writer
{
private byte[] _buf;
private int _pos;
public Xcdr2Writer(int capacity = 4096)
{
_buf = new byte[capacity];
_pos = 0;
WriteEncapsulationHeader();
}
public int Position => _pos;
public byte[] ToArray()
{
var result = new byte[_pos];
Buffer.BlockCopy(_buf, 0, result, 0, _pos);
return result;
}
private void WriteEncapsulationHeader()
{
// CDR2_LE representation_identifier (DDS-RTPS table 10.3)
Ensure(4);
_buf[_pos++] = 0x00;
_buf[_pos++] = 0x09;
_buf[_pos++] = 0x00;
_buf[_pos++] = 0x00;
}
// The encapsulation header is NOT counted when computing alignment, per
// DDS-XTypes 1.3 §7.4.3.4: alignment is measured from the start of the
// user payload (byte 4).
private int PayloadPos => _pos - 4;
private void Align(int n)
{
// XCDR2 caps max alignment at 4. Callers pass 1, 2, 4 only.
int mis = PayloadPos & (n - 1);
if (mis == 0) return;
int pad = n - mis;
Ensure(pad);
for (int i = 0; i < pad; i++) _buf[_pos++] = 0x00;
}
private void Ensure(int extra)
{
if (_pos + extra <= _buf.Length) return;
int newCap = _buf.Length * 2;
while (newCap < _pos + extra) newCap *= 2;
var nb = new byte[newCap];
Buffer.BlockCopy(_buf, 0, nb, 0, _pos);
_buf = nb;
}
// ---- primitive writers ----
public void WriteByte(byte v)
{
Ensure(1);
_buf[_pos++] = v;
}
public void WriteBool(bool v) => WriteByte(v ? (byte)1 : (byte)0);
public void WriteInt16(short v)
{
Align(2);
Ensure(2);
BinaryPrimitives.WriteInt16LittleEndian(_buf.AsSpan(_pos), v);
_pos += 2;
}
public void WriteUInt16(ushort v)
{
Align(2);
Ensure(2);
BinaryPrimitives.WriteUInt16LittleEndian(_buf.AsSpan(_pos), v);
_pos += 2;
}
public void WriteInt32(int v)
{
Align(4);
Ensure(4);
BinaryPrimitives.WriteInt32LittleEndian(_buf.AsSpan(_pos), v);
_pos += 4;
}
public void WriteUInt32(uint v)
{
Align(4);
Ensure(4);
BinaryPrimitives.WriteUInt32LittleEndian(_buf.AsSpan(_pos), v);
_pos += 4;
}
// XCDR2: 64-bit primitives align to 4, NOT 8 (per max-alignment rule)
public void WriteInt64(long v)
{
Align(4);
Ensure(8);
BinaryPrimitives.WriteInt64LittleEndian(_buf.AsSpan(_pos), v);
_pos += 8;
}
public void WriteUInt64(ulong v)
{
Align(4);
Ensure(8);
BinaryPrimitives.WriteUInt64LittleEndian(_buf.AsSpan(_pos), v);
_pos += 8;
}
public void WriteDouble(double v) => WriteInt64(BitConverter.DoubleToInt64Bits(v));
public void WriteString(string s)
{
var bytes = Encoding.UTF8.GetBytes(s);
WriteUInt32((uint)(bytes.Length + 1)); // includes null terminator
Ensure(bytes.Length + 1);
Buffer.BlockCopy(bytes, 0, _buf, _pos, bytes.Length);
_pos += bytes.Length;
_buf[_pos++] = 0x00; // null terminator
}
// Fixed-length octet array (e.g., 16-byte GUID).
// No length prefix; just the bytes.
public void WriteOctetArrayFixed(byte[] data, int expectedLen)
{
if (data.Length != expectedLen)
throw new ArgumentException($"Expected {expectedLen} bytes, got {data.Length}");
Ensure(expectedLen);
Buffer.BlockCopy(data, 0, _buf, _pos, expectedLen);
_pos += expectedLen;
}
// Variable-length octet sequence: uint32 length + bytes.
// No DHEADER (octet is primitive).
public void WriteOctetSequence(byte[] data)
{
WriteUInt32((uint)data.Length);
Ensure(data.Length);
Buffer.BlockCopy(data, 0, _buf, _pos, data.Length);
_pos += data.Length;
}
// DHEADER for sequences of non-primitive types and for non-final structs
// and for optionals containing complex types. Reserves 4 bytes now,
// returns a token used by EndDHeader to backfill the size.
public int BeginDHeader()
{
Align(4);
Ensure(4);
int token = _pos;
// placeholder, will be backfilled
_buf[_pos++] = 0; _buf[_pos++] = 0; _buf[_pos++] = 0; _buf[_pos++] = 0;
return token;
}
public void EndDHeader(int token)
{
// Size = number of bytes after the DHEADER, exclusive of the DHEADER
// itself. (DDS-XTypes 1.3 §7.4.3.5.1 D-HEADER definition.)
int sizeAfter = _pos - (token + 4);
BinaryPrimitives.WriteUInt32LittleEndian(_buf.AsSpan(token), (uint)sizeAfter);
}
}
internal sealed class Xcdr2Reader
{
private readonly byte[] _buf;
private int _pos;
private readonly bool _littleEndian;
public Xcdr2Reader(byte[] data)
{
_buf = data;
// Encapsulation header (4 bytes)
if (data.Length < 4) throw new IOException("Truncated XCDR2 stream");
if (data[0] != 0x00 || (data[1] != 0x09 && data[1] != 0x0A))
throw new IOException(
$"Not an XCDR2 stream (representation_identifier {data[0]:X2} {data[1]:X2})");
_littleEndian = data[1] == 0x09;
if (!_littleEndian)
throw new NotSupportedException("Only CDR2_LE is implemented in this benchmark.");
_pos = 4;
}
private int PayloadPos => _pos - 4;
private void Align(int n)
{
int mis = PayloadPos & (n - 1);
if (mis == 0) return;
_pos += (n - mis);
}
public byte ReadByte() => _buf[_pos++];
public bool ReadBool() => ReadByte() != 0;
public short ReadInt16()
{
Align(2);
var v = BinaryPrimitives.ReadInt16LittleEndian(_buf.AsSpan(_pos));
_pos += 2;
return v;
}
public ushort ReadUInt16()
{
Align(2);
var v = BinaryPrimitives.ReadUInt16LittleEndian(_buf.AsSpan(_pos));
_pos += 2;
return v;
}
public int ReadInt32()
{
Align(4);
var v = BinaryPrimitives.ReadInt32LittleEndian(_buf.AsSpan(_pos));
_pos += 4;
return v;
}
public uint ReadUInt32()
{
Align(4);
var v = BinaryPrimitives.ReadUInt32LittleEndian(_buf.AsSpan(_pos));
_pos += 4;
return v;
}
public long ReadInt64()
{
Align(4); // XCDR2 max alignment
var v = BinaryPrimitives.ReadInt64LittleEndian(_buf.AsSpan(_pos));
_pos += 8;
return v;
}
public ulong ReadUInt64()
{
Align(4);
var v = BinaryPrimitives.ReadUInt64LittleEndian(_buf.AsSpan(_pos));
_pos += 8;
return v;
}
public double ReadDouble() => BitConverter.Int64BitsToDouble(ReadInt64());
public string ReadString()
{
uint lenIncNull = ReadUInt32();
if (lenIncNull == 0)
throw new IOException("XCDR2 string length must include null terminator (>= 1)");
int payloadLen = (int)lenIncNull - 1;
var s = Encoding.UTF8.GetString(_buf, _pos, payloadLen);
_pos += payloadLen;
// consume null terminator
if (_buf[_pos] != 0x00)
throw new IOException("XCDR2 string missing null terminator");
_pos += 1;
return s;
}
public byte[] ReadOctetArrayFixed(int len)
{
var result = new byte[len];
Buffer.BlockCopy(_buf, _pos, result, 0, len);
_pos += len;
return result;
}
public byte[] ReadOctetSequence()
{
uint len = ReadUInt32();
var result = new byte[len];
Buffer.BlockCopy(_buf, _pos, result, 0, (int)len);
_pos += (int)len;
return result;
}
public int ReadDHeader()
{
// We don't actually need to use the size for decoding because we know
// the schema; we just consume the 4 bytes.
return (int)ReadUInt32();
}
}