Binary-FIX (SBE + SOFH + FIXP) — Conformance & Certification
Senticore's binary-FIX gateway is a CME iLink-3-class stack:
- SBE (Simple Binary Encoding) — fixed-layout binary messages from the published schema
(
backend/protocol/sbe-fix/schema/senticore-orderentry-1.0.xml,schemaId=1). - SOFH (Simple Open Framing Header) — 6-byte big-endian frame delimiter.
- FIXP (FIX Performance session layer) — binary Recoverable-flow session (Negotiate / Establish / Sequence / RetransmitRequest / Retransmission / Terminate).
Application messages (templateId 1–12) and FIXP session messages (templateId 100–111) ride the same SOFH stream, distinguished by templateId.
Wire framing
SOFH (6 bytes, BIG-endian):
u32 message_length # total bytes INCLUDING these 6
u16 encoding_type # 0x5BE0 = SBE little-endian
SBE message header (8 bytes, LITTLE-endian):
u16 blockLength # fixed root block size
u16 templateId
u16 schemaId # = 1
u16 version # = 1
<root block: blockLength bytes> [ <repeating groups> ] [ <var-data: u16 length + bytes> ]
schemaId is fixed forever. version is bumped only for additive, backward-compatible changes
(append trailing fields, grow blockLength; old decoders skip via blockLength). Breaking changes
ship as a new schemaId on a new port, with both running during migration.
Session authentication
Credentials are presented once, at FIXP Negotiate, in the credentials var-field:
apiKey:apiSecret:apiPassphrase:accountHex
verified against your issued agent credential (the same FixLogon credential as tag=value FIX).
After Establish there is no per-order signing — the session is the authenticated identity.
Every order's embedded account field must equal the authenticated account (cross-account orders
are rejected). Credentials travel in cleartext inside TLS; the gateway refuses plaintext in
guarded environments.
Golden vectors
The reference encoder (scripts/fixp_conformance_smoke.py) and the Rust codec
(backend/gateway/fixp/tests/golden.rs) emit these byte-identical full-frame (SOFH-wrapped) hex
vectors. Your codec must reproduce them exactly. Canonical inputs: account = 0x000102…13,
orderId = 0xAB×32, sessionId = 0x11×16, timestamp = 1700000000000.
| Message | templateId | golden hex (SOFH-framed) |
|---|---|---|
| NewOrderSingle | 1 | 000000885be07a00010001000100434f4e462d31…010201000000 |
| ExecutionReport | 10 | 000000cb5be0bb000a0001000100abab…0101010000 |
| Negotiate | 100 | 0000002d5be01900640001000100111111…030400434f4e46 |
| Establish | 103 | 000000385be02800670001000100111111…01000…00 |
| EstablishmentAck | 104 | 000000365be02800680001000100111111…0100…00 |
| Sequence | 106 | 000000165be008006a00010001000100000000000000 |
(Full untruncated vectors are in EXPECTED_HEX in the Python script and EXPECTED_* in golden.rs.)
Reference client
Stdlib-only Python, runnable in restricted certification environments:
# 1) prove your/our encoder is byte-compatible with the Rust codec
python scripts/fixp_conformance_smoke.py verify-golden
# 2) drive a live handshake + order against a gateway
python scripts/fixp_conformance_smoke.py session \
--host fix-bin.example --port 9879 --tls \
--api-key spk_… --api-secret sps_… --api-passphrase spp_… \
--account 0xabc… --market 1 --side bid --price 1000000 --qty 1
Certification checklist
An MM is certified once it passes:
- Encoding —
verify-goldengreen (all golden vectors byte-identical). - Handshake — Negotiate → NegotiationResponse, Establish → EstablishmentAck;
Recoverableflow required (non-Recoverable is rejected). - Order lifecycle — NewOrderSingle → ExecutionReport drop-copy; OrderCancelRequest and OrderCancelReplaceRequest by OrderID and by OrigClOrdID.
- Idempotency — a duplicate ClOrdID placement is rejected (
BusinessMessageReject). - Recovery — on a sequence gap the server emits
RetransmitRequest; a clientRetransmitRequestis answered withRetransmission+ replayed frames. - Liveness —
Sequencekeepalives are exchanged on the negotiated interval; a silent connection is closed after the grace window. - Rate limits — sustained order flow stays within the per-account budget (shared MM limiter).
Current support matrix
| Capability | Status |
|---|---|
| NewOrderSingle / Cancel / CancelReplace (by OrderID or OrigClOrdID) | ✅ |
| ExecutionReport drop-copy | ✅ |
| Duplicate-ClOrdID rejection | ✅ |
| FIXP Negotiate/Establish/Sequence/RetransmitRequest/Retransmission/Terminate | ✅ |
| OrderMassCancelRequest (cancel-all / per-market / per-side) | ✅ |
| Cancel-on-disconnect (resting orders swept on any teardown) | ✅ |
| Reject-on-unsupported (no silent semantic downgrade) | ✅ |
| Source-IP allowlist / mTLS admission control | ✅ |
| OrderStatusRequest reply | ⏳ no reply yet (track via drop-copy) |
| Cross-connection resume (ResumeRegistry) | ⏳ not yet wired |
Cancel-on-disconnect & mass-cancel (MM risk controls)
- Cancel-on-disconnect is armed per credential (
cancelOnDisconnecton the agent). When enabled, every resting order placed on the session is registered, and on ANY connection teardown — clean close, timeout, network drop, or error — all of that account's resting orders are swept. A dropped TCP connection never leaves a live quote stack in the book. - OrderMassCancelRequest cancels all open orders for the account in one message — optionally filtered to specific markets and/or a side — fanned out as qty=0 quote-replaces by the engine. This is the low-latency panic-kill; you do not enumerate orders client-side.
- Liveness as a soft dead-man's-switch: stop sending
Sequencekeepalives and the session dies on the negotiated interval, which triggers the cancel-on-disconnect sweep.