Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ Versioning after 1.0 and uses prerelease versions while conformance is incomplet

## Unreleased

- Added deterministic populated conformance cases for all 72 DIS v7 PDU types,
independent byte vectors, and truncation checks at every byte boundary across
all 12 protocol families planned for versions 0.3 through 0.8.
- Corrected the 10-byte Live Entity header layout, offset-aware 64-bit padding,
per-field enumeration/bitfield widths, and synchronization of variable record
lengths and counts.
- Added conditional IFF information layers and polymorphic Information
Operations effect/communications records to the native C# codec.
- Added strongly typed SISO-REF-010-2025 v36 enumerations for every DIS v7
schema reference, with .NET-style public names.
- Added structured bitfield value types with validated accessors, immutable
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ OpenDisNet is a high-performance, type-safe .NET codec for Distributed
Interactive Simulation (DIS) Protocol Version 7, defined by IEEE 1278.1-2012.

> **Development status:** pre-release. All 72 DIS v7 PDU identifiers have typed
> models and generated binary codecs. Family-level semantic conformance and
> independent vectors are tracked in [`docs/conformance.md`](docs/conformance.md).
> models and native C# binary codecs. Every protocol family has populated
> round-trip and boundary-truncation coverage; remaining 1.0 release gates are
> tracked in [`docs/conformance.md`](docs/conformance.md).

## Install

Expand Down Expand Up @@ -99,6 +100,8 @@ through `Value`; parsing and reserialization preserve both exactly.
- Enumeration target: SISO-REF-010-2025 (version 36).
- Cross-check sources include NPS MOVES Open-DIS projects and independent packet
decoders. OpenDisNet is not a port and does not copy their public API design.
- The NuGet package has no Java code, dependency, runtime requirement, or
Java-facing API. Reference implementations supply test bytes only.

See [the standards baseline](docs/standards.md) and
[conformance matrix](docs/conformance.md) for exact, release-specific coverage,
Expand Down
37 changes: 22 additions & 15 deletions docs/conformance.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ input tests, and round-trip tests.

## Foundation

- [x] Common 12-byte PDU header
- [x] Common 12-byte PDU header and Live Entity 10-byte header
- [x] Big-endian integer and IEEE-754 primitives
- [x] Defensive length and version validation
- [x] Unknown/future PDU preservation
Expand All @@ -15,26 +15,33 @@ input tests, and round-trip tests.
- [x] Binary dispatch, parsing, and serialization for PDU types 1-72
- [x] Default construction and byte-identical round trip for PDU types 1-72
- [x] Automatic synchronization of list, octet, and bit-length fields
- [x] Populated Signal, Transmitter, Intercom Control, and Minefield Data cases
- [x] Deterministically populated round trips for every PDU type (1-72)
- [x] 70 independent default reference vectors and 44 compatible populated vectors
- [ ] Independent populated vectors for every variable-layout PDU
- [ ] Truncation-at-every-boundary and mutation coverage for every PDU family
- [x] Truncation-at-every-boundary coverage for every PDU family
- [x] SISO-REF-010-2025 v36 typed enumerations and structured bitfields
- [x] Lossless round trip of unknown enumeration values and reserved bitfield bits

## PDU families

- [ ] Entity Information/Interaction (Entity State implemented)
- [ ] Warfare (Fire and Detonation implemented)
- [ ] Logistics
- [ ] Simulation Management
- [ ] Distributed Emission Regeneration
- [ ] Radio Communications (populated Signal, Transmitter, and Intercom Control exercised)
- [ ] Entity Management
- [ ] Minefield (conditional Minefield Data fields exercised)
- [ ] Synthetic Environment
- [ ] Simulation Management with Reliability
- [ ] Live Entity
- [ ] Information Operations
- [x] 0.3 Entity Information/Interaction
- [x] 0.3 Warfare
- [x] 0.4 Logistics
- [x] 0.4 Simulation Management
- [x] 0.5 Distributed Emission Regeneration
- [x] 0.5 Radio Communications
- [x] 0.6 Entity Management
- [x] 0.6 Minefield
- [x] 0.7 Synthetic Environment
- [x] 0.7 Simulation Management with Reliability
- [x] 0.8 Live Entity
- [x] 0.8 Information Operations

Family coverage is implemented and exercised entirely in C#/.NET. Frozen bytes
from an independent implementation are test inputs only; they are not product
code or a runtime/build dependency. Two default vectors and 28 aggressively
populated vectors expose documented defects in that reference generator and are
retained as differential evidence rather than copied into OpenDisNet behavior.

This file is a release gate. Version 1.0 will not be labeled complete until all
applicable entries are checked and independently audited.
6 changes: 4 additions & 2 deletions docs/standards.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,7 @@ cross-checks:
- `open-dis/open-dis-csharp`

The Java source generator reports 72 DIS v7 PDUs and provides machine-readable
layout descriptions. OpenDisNet's conformance claim is based on its own coverage
matrix and tests, not merely the presence of a corresponding reference class.
layout descriptions. It is an offline reference only: OpenDisNet is a native
C#/.NET design with no Java code, dependency, or runtime requirement. Its
conformance claim is based on its own coverage matrix and tests, not merely the
presence of a corresponding reference class.
6 changes: 6 additions & 0 deletions src/OpenDisNet/Binary/DisBinaryReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ public uint ReadUInt32(string field = "value")
return value;
}

public readonly uint PeekUInt32(string field = "value")
{
Ensure(4, field);
return BinaryPrimitives.ReadUInt32BigEndian(_source[_offset..]);
}

public int ReadInt32(string field = "value") => unchecked((int)ReadUInt32(field));

public ulong ReadUInt64(string field = "value")
Expand Down
28 changes: 17 additions & 11 deletions src/OpenDisNet/Binary/DisHeaderCodec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@ namespace OpenDisNet.Binary;

public static class DisHeaderCodec
{
public static DisHeader Read(ref DisBinaryReader reader) => new(
(DisProtocolVersion)reader.ReadByte(nameof(DisHeader.ProtocolVersion)),
reader.ReadByte(nameof(DisHeader.ExerciseId)),
(PduType)reader.ReadByte(nameof(DisHeader.PduType)),
(ProtocolFamily)reader.ReadByte(nameof(DisHeader.ProtocolFamily)),
reader.ReadUInt32(nameof(DisHeader.Timestamp)),
reader.ReadUInt16(nameof(DisHeader.Length)),
reader.ReadByte(nameof(DisHeader.PduStatus)),
reader.ReadByte(nameof(DisHeader.Padding)));
public static DisHeader Read(ref DisBinaryReader reader)
{
var version = (DisProtocolVersion)reader.ReadByte(nameof(DisHeader.ProtocolVersion));
byte exerciseId = reader.ReadByte(nameof(DisHeader.ExerciseId));
var pduType = (PduType)reader.ReadByte(nameof(DisHeader.PduType));
var family = (ProtocolFamily)reader.ReadByte(nameof(DisHeader.ProtocolFamily));
uint timestamp = reader.ReadUInt32(nameof(DisHeader.Timestamp));
ushort length = reader.ReadUInt16(nameof(DisHeader.Length));
byte status = family == ProtocolFamily.LiveEntity ? (byte)0 : reader.ReadByte(nameof(DisHeader.PduStatus));
byte padding = family == ProtocolFamily.LiveEntity ? (byte)0 : reader.ReadByte(nameof(DisHeader.Padding));
return new(version, exerciseId, pduType, family, timestamp, length, status, padding);
}

public static void Write(ref DisBinaryWriter writer, in DisHeader header)
{
Expand All @@ -22,7 +25,10 @@ public static void Write(ref DisBinaryWriter writer, in DisHeader header)
writer.WriteByte((byte)header.ProtocolFamily, nameof(header.ProtocolFamily));
writer.WriteUInt32(header.Timestamp, nameof(header.Timestamp));
writer.WriteUInt16(header.Length, nameof(header.Length));
writer.WriteByte(header.PduStatus, nameof(header.PduStatus));
writer.WriteByte(header.Padding, nameof(header.Padding));
if (header.ProtocolFamily != ProtocolFamily.LiveEntity)
{
writer.WriteByte(header.PduStatus, nameof(header.PduStatus));
writer.WriteByte(header.Padding, nameof(header.Padding));
}
}
}
19 changes: 13 additions & 6 deletions src/OpenDisNet/DisSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,24 +51,31 @@ public static bool TryDeserialize(
pdu = null;
error = default;

if (datagram.Length < DisHeader.Size)
return Fail(DisParseErrorCode.TruncatedHeader, "A DIS header requires 12 bytes.", datagram.Length, out error);
if (datagram.Length < 4)
return Fail(DisParseErrorCode.TruncatedHeader, "A DIS header requires at least 4 bytes to identify its layout.", datagram.Length, out error);

int requiredHeaderSize = datagram[3] == (byte)ProtocolFamily.LiveEntity
? DisHeader.MinimumSize
: DisHeader.Size;
if (datagram.Length < requiredHeaderSize)
return Fail(DisParseErrorCode.TruncatedHeader, $"This DIS header requires {requiredHeaderSize} bytes.", datagram.Length, out error);

try
{
var reader = new DisBinaryReader(datagram);
DisHeader header = DisHeaderCodec.Read(ref reader);
int headerSize = header.EncodedSize;

if (options.RequireVersion7 && header.ProtocolVersion != DisProtocolVersion.Ieee1278_1_2012)
return Fail(DisParseErrorCode.UnsupportedProtocolVersion, $"Expected DIS protocol version 7; received {(byte)header.ProtocolVersion}.", 0, out error);
if (header.Length < DisHeader.Size || header.Length > options.MaximumPduLength)
if (header.Length < headerSize || header.Length > options.MaximumPduLength)
return Fail(DisParseErrorCode.InvalidLength, $"Invalid PDU length {header.Length}.", 8, out error);
if (header.Length > datagram.Length)
return Fail(DisParseErrorCode.TruncatedPdu, $"The header declares {header.Length} bytes; only {datagram.Length} were received.", datagram.Length, out error);
if (options.RequireExactDatagramLength && header.Length != datagram.Length)
return Fail(DisParseErrorCode.TrailingData, $"The datagram contains {datagram.Length - header.Length} trailing bytes.", header.Length, out error);

pdu = PduRegistry.Parse(header, datagram.Slice(DisHeader.Size, header.Length - DisHeader.Size));
pdu = PduRegistry.Parse(header, datagram.Slice(headerSize, header.Length - headerSize));
return true;
}
catch (DisParseException exception)
Expand All @@ -77,11 +84,11 @@ public static bool TryDeserialize(
}
catch (FormatException exception)
{
return Fail(DisParseErrorCode.InvalidField, exception.Message, DisHeader.Size, out error);
return Fail(DisParseErrorCode.InvalidField, exception.Message, DisHeader.MinimumSize, out error);
}
catch (OverflowException exception)
{
return Fail(DisParseErrorCode.InvalidField, exception.Message, DisHeader.Size, out error);
return Fail(DisParseErrorCode.InvalidField, exception.Message, DisHeader.MinimumSize, out error);
}
}

Expand Down
Loading
Loading