Skip to content

Feature-gated binary wire protocol (feature = wire)#80

Closed
joaquinbejar wants to merge 4 commits intomainfrom
issue-59-wire-protocol
Closed

Feature-gated binary wire protocol (feature = wire)#80
joaquinbejar wants to merge 4 commits intomainfrom
issue-59-wire-protocol

Conversation

@joaquinbejar
Copy link
Copy Markdown
Owner

Summary

  • New feature flag wire (default off) gates a length-prefixed binary
    framing — every frame is [len:u32 LE | kind:u8 | payload], where
    len covers kind + payload (the 4-byte len prefix itself is
    excluded). All multi-byte integers are little-endian. Existing JSON
    and bincode paths are unchanged — the wire protocol is purely
    additive.
  • Stable MessageKind enum (#[repr(u8)] #[non_exhaustive]) with
    inbound discriminants 0x01 NewOrder, 0x02 CancelOrder,
    0x03 CancelReplace, 0x04 MassCancel and outbound 0x81
    ExecReport, 0x82 TradePrint, 0x83 BookUpdate.
  • Inbound messages are #[repr(C, packed)] + the zerocopy 0.8 trait
    cohort (FromBytes, IntoBytes, Unaligned, Immutable,
    KnownLayout); each has a compile-time
    const _: () = assert!(size_of::<…>() == N) size guard. Decoding is
    safe — #![deny(unsafe_code)] stays on.
  • Outbound messages use byte-cursor encoders
    (Vec<u8>::extend_from_slice); status_to_wire maps OrderStatus
    STATUS_*. impl TryFrom<&NewOrderWire> for OrderType<()> does
    the wire ↔ domain conversion at the boundary, copying packed
    fields into stack locals first (taking a reference to a packed
    field is UB).
  • Errors via a manual-Display, #[non_exhaustive] WireError
    matching the crate's existing manual style — no thiserror.
  • Round-trip proptest tests in every
    src/wire/{inbound,outbound}/*.rs module — encode through the
    framer, decode back, assert byte-for-byte equality.
  • doc/wire-protocol.md ships per-message offset / size / field /
    type / notes layout tables, the MessageKind discriminant table,
    the framing rule, and the LE-endianness statement.
  • Example: examples/src/bin/wire_roundtrip.rs (gated by
    required-features = ["wire"]).

Test plan

  • cargo fmt --all
  • cargo clippy --all-targets --features wire -- -D warnings
  • cargo clippy --all-targets -- -D warnings
  • cargo test --features wire (567 + 25 + 386 + 41 passed)
  • cargo test (535 + 25 + 386 + 41 passed — the wire suite drops
    to the lib's normal test count when the feature is disabled, the
    rest is unchanged)
  • cargo build --release --features wire
  • cargo run --bin wire_roundtrip --manifest-path examples/Cargo.toml --features wire
  • make readme — README regenerated and committed

Add a feature-gated `wire` flag with optional `zerocopy = "0.8"`
dependency. Introduce the binary frame codec
(`[len:u32 LE | kind:u8 | payload]`), the `WireError` taxonomy
(manual `Display`, no `thiserror`), and the stable `MessageKind`
discriminants (`#[repr(u8)] #[non_exhaustive]`). Inbound codes
occupy `0x01..=0x7F`, outbound `0x80..=0xFF`.

This commit ships the wire scaffolding only — the per-message
inbound/outbound modules land in the next commit.
Inbound (`src/wire/inbound/`) — `NewOrderWire` (48 B),
`CancelOrderWire` (24 B), `CancelReplaceWire` (40 B), and
`MassCancelWire` (24 B). Each is `#[repr(C, packed)]` with
`zerocopy::{FromBytes, IntoBytes, Unaligned, Immutable,
KnownLayout}` derives, a `const _: () = assert!(size_of::<…>()
== N)` size guard, an `as_payload_bytes()` accessor (so callers
do not need to import `zerocopy`), and a `decode_*` helper that
returns `WireError::InvalidPayload` on size mismatch. Decoding is
safe — `#![deny(unsafe_code)]` stays on.

Outbound (`src/wire/outbound/`) — `ExecReport` (44 B),
`TradePrintWire` (48 B), `BookUpdateWire` (32 B). Outbound uses
explicit byte-cursor encoders (`Vec<u8>::extend_from_slice`).
`status_to_wire` maps `OrderStatus` to its `STATUS_*` discriminant.

Wire ↔ domain mapping at the boundary —
`impl TryFrom<&NewOrderWire> for OrderType<()>` copies each
packed field into a stack local first (taking a reference to a
packed field is UB), validates the side / TIF / order_type
discriminants, and rejects negative prices.

`MassCancel` rejects unknown `scope` bytes at decode time.

Round-trip `proptest` tests in every new module — encode through
the framer, decode back, assert byte-for-byte equality.
`examples/src/bin/wire_roundtrip.rs` builds a `NewOrderWire`,
encodes it via the framer, decodes the frame, validates the
`MessageKind` byte, decodes the payload, mirrors the packed
fields onto stack locals (taking a reference to a packed field
is UB), and converts the result into a domain `OrderType<()>`.
Every step is logged via `tracing::info!`.

The example is gated by `required-features = ["wire"]` in
`examples/Cargo.toml`, mirroring the existing `special_orders`
pattern.
Add `doc/wire-protocol.md` with per-message offset / size /
field / type / notes layout tables for every inbound and
outbound message, the `MessageKind` discriminant table, the
framing rule (`[len:u32 LE | kind:u8 | payload]` where `len`
covers `kind + payload`), and the LE-endianness statement.

Append a new sub-block to the existing 0.7.0 section in
`CHANGELOG.md` covering the feature flag, framing rule,
endianness, discriminant table, fixed-size packed inbound
layouts via `zerocopy`, byte-cursor outbound encoders,
round-trip proptests, and the `wire_roundtrip` example. Note
that `JSON` and `bincode` paths are unchanged — the wire
protocol is additive.

`README.md` is regenerated via `cargo readme` to mirror the new
What's New entry that already shipped with the lib.rs scaffolding
commit.

The unrelated `tests/unit/replay_determinism.rs` import-grouping
fix is a `cargo fmt --all` follow-up.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant