Perimeter: gateways, protocols, control plane¶
The perimeter is where untrusted bytes become InboundCommands.
Gateways¶
| Gateway | Transport |
|---|---|
TcpGateway |
length-prefixed frames over TCP |
WsGateway |
RFC 6455 WebSocket (handshake + frames) |
TlsGateway |
TLS termination via OpenSSL (optional dependency) |
UdpMdPublisher / UdpMdSubscriber |
outbound market data over IP multicast |
TcpGateway gw([](const uint8_t* p, size_t n) { return OuchCodec::decode(p, n); });
gw.start(port, [&](const InboundCommand& cmd, const Responder& respond) {
venue.submit(cmd);
});
gw.stop();
SocketAcceptor carries the shared accept machinery. Each connection runs its
loop inside a try/catch: a decode or allocation failure drops that one
connection and the process keeps running.
Sessions¶
GatewaySession sits between the socket and the engine:
- Authentication. API-key HMAC logon (
apiKey:timestampsigned with the shared secret), constant-time comparison, timestamp-skew window. - Account binding. A session bound to an account stamps that account onto
every command, overwriting whatever the payload carried. Otherwise a client
could act as any account by writing a different id into the message. Account
0is the explicit "unbound / trusted transport" sentinel. - Rate limiting, per session, via
flox::RateLimitPolicy. - Cancel-on-disconnect optionally pulls the session's resting orders when the connection drops.
Wire protocols¶
| Codec | Notes |
|---|---|
OuchCodec |
binary, full fidelity: every field round-trips, including reduceOnly, peg, expiryNs, ocoGroup, lastLook |
FixCodec |
FIX 4.4: D/F/G in, ExecutionReport out, with BodyLength and validated CheckSum |
RestJson |
REST/JSON adoption path |
ItchCodec |
outbound market data |
Round-trip tests pin every field, because a dropped field here changes
behaviour: a reduceOnly flag lost on the wire turns a risk-reducing order
into one that can open a position.
Hostile input¶
The perimeter is fuzzed, and the rules are explicit:
- Length prefixes are capped before allocation:
flox::net::kMaxFrame(flox/util/transport.h) andflox::ws::kMaxFramePayload(flox/util/websocket.h), 16 MiB, both in core alongside the framing they guard. A 4-byte header cannot reserve gigabytes. - WebSocket extended length is overflow-safe. The naive completeness check
n < off + lenwraps for a 64-bit length; the comparison is done aslen > n - off, and an absurd length is a protocol error that closes the connection before anyresize. - A parse error closes one connection, and only that connection.
test_venue_parser_fuzz drives all decoders with random and adversarial
input; the sanitizer gate runs it under ASAN/UBSAN.
Control plane¶
InstrumentRegistry is the venue admin surface (list instruments, halt and
resume, adjust tick, lot, bands, trigger reference). ControlApi exposes it
as JSON-RPC and ControlServer serves it over TCP.
api.handle(R"({"method":"halt","symbol":1,"halted":true})");
api.handle(R"({"method":"setBand","symbol":1,"minPrice":90,"maxPrice":110})");
Actions that change matchable state (auction transitions, emergency
cancel-all, halts) go through the sequenced AdminCmd path instead, so they
are journalled and survive replay. Configuration knobs recover from the
configuration store. See Runtime and recovery.
Deploy the control plane on an internal interface: it has no authentication of its own.
Observability¶
Metrics counts orders, trades, rejects by reason, and fills. Gauges
samples venue state (open interest, position count, best bid/ask, mark age,
feed-breaker state). prometheus.h renders the exposition format and
MetricsServer serves it over HTTP for scraping.