Skip to content

Latest commit

Β 

History

58 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Dual License Crates.io Downloads Stars Issues PRs

Build Tests Coverage Dependencies Documentation

IronFix

A FIX/FAST protocol engine for Rust, built as a Cargo workspace of focused crates: the wire layer, the session layer, and a client-side engine.

Overview

IronFix implements FIX tag=value messaging and the FAST encoding primitives from the ground up β€” there is no upstream protocol library underneath it. The tag=value decoder is zero-copy and dictionary-free: it scans bytes and hands out field slices that borrow the input buffer, with schema validation available as a separate, opt-in pass.

What works today, end to end:

  • Decoding and encoding FIX tag=value messages, including BodyLength (tag 9) and CheckSum (tag 10) handling and length-prefixed Length/Data field pairs. Every malformed-input path is a typed error, never a panic.
  • Framing over a Tokio codec (FixCodec) with a bounded read buffer and an unconditionally verified trailer.
  • A client-side session (Initiator): TCP dial, Logon handshake, heartbeats and TestRequests at the negotiated interval, CompID validation, sequence-gap detection, ResendRequest / SequenceReset / gap fill, PossDupFlag and OrigSendingTime handling, ResetSeqNumFlag, session-level Reject, and FIXT.1.1 BeginString with ApplVerID for FIX 5.0 sessions. Heartbeat/TestRequest handling is partial: HeartBtInt = 0 (legal FIX for "do not send heartbeats") is currently taken literally as a zero-length interval, and a pending TestRequest is cleared only by a Heartbeat echoing the matching TestReqID β€” see doc/fix_operations.md.
  • A typestate session FSM and checked sequence arithmetic in ironfix-session.
  • A QuickFIX XML dictionary loader and a Validator in ironfix-dictionary.
  • FAST primitives in ironfix-fast: stop-bit integer and string encode/decode and presence maps, all round-trip tested. The copy/delta/increment/tail/default operators are a classification vocabulary (the Operator enum with predicates such as uses_dictionary / requires_pmap), not wired-in codec operators β€” the decoder and encoder do not apply them yet.

What is not implemented yet

This list is deliberately explicit. If a capability is not named under "What works today" and appears below, it does not exist in the code β€” do not plan around it.

  • No Acceptor. ironfix-engine has Initiator only. The server-side examples hand-roll their own accept loop with Decoder / Encoder directly.
  • EngineBuilder has no terminal method. It collects sessions and timeouts but there is no build(); the working entry point is Initiator::new(config, app).connect(addr).
  • The engine never uses MessageStore. Resend-from-store is not wired up β€” an inbound ResendRequest is answered with a gap fill, not with the original messages. MemoryStore is the only store implementation; there is no FileStore and no memory-mapped store.
  • No TLS. ironfix-transport contains FixCodec and nothing else β€” no TCP connector, no acceptor, no rustls. Initiator calls TcpStream::connect directly.
  • Async only. Everything that touches a socket runs on Tokio. There is no synchronous mode, no kernel-bypass path, and no busy-polling transport.
  • Only FIX 4.4 has an embedded dictionary (ironfix-dictionary/spec/FIX44.xml, vendored from QuickFIX). Other versions require Dictionary::from_quickfix_xml with your own XML. The Validator is not invoked by the engine or the codec; you call it yourself.
  • The derive macros are stubs. #[derive(FixMessage)] and #[derive(FixField)] both expand to todo!(). Neither ironfix-derive nor ironfix-codegen has an in-workspace consumer.
  • FAST is standalone. There is no FAST template XML parser, no UDP multicast receiver, and no wiring into the session or engine path.
  • No recorded benchmark baseline. A criterion harness now exists (see Benchmarks below): ironfix-tagvalue, ironfix-fast and ironfix-transport each carry a benches/ target run by make bench. It ships no saved baseline and no published figures, so every latency and throughput target in doc/ remains a design goal, unmeasured until you run it on hardware you name.

FIX version support

The session layer is version-parameterised by BeginString, and there is a runnable client/server example pair for each version below. "Dictionary" means a dictionary is embedded in the crate and loadable without extra files.

Version BeginString Example pair Dictionary embedded
FIX 4.0 FIX.4.0 yes no
FIX 4.1 FIX.4.1 yes no
FIX 4.2 FIX.4.2 yes no
FIX 4.3 FIX.4.3 yes no
FIX 4.4 FIX.4.4 yes yes
FIX 5.0 FIXT.1.1 yes no
FIX 5.0 SP1 FIXT.1.1 yes no
FIX 5.0 SP2 FIXT.1.1 yes no

Crate Organization

Crate Description
ironfix-core Fundamental types, traits, and error definitions; depends on no other IronFix crate
ironfix-dictionary QuickFIX XML loading, schema types, and the opt-in Validator
ironfix-tagvalue Zero-copy tag=value decoding and encoding; dictionary-free
ironfix-session Session-layer protocol logic: typestate FSM, sequences, heartbeats. No I/O
ironfix-store The MessageStore trait and MemoryStore
ironfix-transport FixCodec, a Tokio codec that frames FIX messages. No TCP helpers, no TLS
ironfix-fast FAST encoding/decoding primitives: stop-bit, presence maps, operators
ironfix-codegen Build-time Rust generation from a Dictionary (no in-workspace consumer)
ironfix-derive Procedural macros β€” currently stubs that expand to todo!()
ironfix-engine The composition root: Initiator, Connection, the Application trait
ironfix-example Umbrella facade (prelude) plus the runnable examples

There is no ironfix facade crate. The umbrella re-exports live in ironfix-example.

Quick Start

Connect as an initiator, send a NewOrderSingle, then log out. The engine owns the socket: it dials, frames, performs the Logon handshake, and stamps the header, MsgSeqNum and trailer on everything you send.

use std::sync::Arc;
use std::time::Duration;

use ironfix_engine::{Initiator, NoOpApplication, OutboundMessage};
use ironfix_engine::SessionConfig;
use ironfix_core::MsgType;
use ironfix_core::types::CompId;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = SessionConfig::new(
        CompId::new("SENDER")?,
        CompId::new("TARGET")?,
        "FIX.4.4",
    )
    .with_heartbeat_interval(Duration::from_secs(30));

    // Replace NoOpApplication with your own `Application` impl to receive
    // on_logon / from_app / from_admin callbacks.
    let initiator = Initiator::new(config, Arc::new(NoOpApplication))
        .with_connect_timeout(Duration::from_secs(5));
    let connection = initiator.connect("127.0.0.1:9876").await?;

    let mut order = OutboundMessage::new(MsgType::NewOrderSingle);
    order
        .push_str(11, "ORD001")
        .push_str(55, "AAPL")
        .push_char(54, '1')
        .push_uint(38, 100)
        .push_str(44, "150.50")
        .push_char(40, '2');
    connection.send(order).await?;

    connection.logout().await?;
    connection.wait_closed().await;
    Ok(())
}

Or terminate an [EngineBuilder] directly: into_initiator() produces the client above, and into_acceptor() produces a server-side [Acceptor] that establishes sessions on inbound connections. Both engines hand the socket to the same session reactor once the Logon handshake completes.

use ironfix_core::types::CompId;
use ironfix_engine::EngineBuilder;
use ironfix_session::SessionConfig;

# async fn run() -> Result<(), Box<dyn std::error::Error>> {
// Create a session configuration (sender = you, target = the counterparty).
let config = SessionConfig::new(
    CompId::new("CLIENT")?,
    CompId::new("VENUE")?,
    "FIX.4.4",
);

// The builder terminates in a runnable engine. `into_initiator` produces a
// client that dials the counterparty; `into_acceptor` produces a server that
// establishes sessions on inbound connections.
let initiator = EngineBuilder::new()
    .add_session(config)
    .into_initiator()?;

let connection = initiator.connect("127.0.0.1:9876").await?;
connection.logout().await?;
connection.wait_closed().await;
# Ok(())
# }

See ironfix-example/examples/fix44_engine_client.rs for the same flow with a real Application implementation, and fix44_server.rs for the matching Acceptor-based server.

πŸ›  Makefile Commands

This project includes a Makefile with common tasks to simplify development. Here's a list of useful commands:

πŸ”§ Build & Run

make build         # Compile the workspace
make release       # Build the workspace in release mode
make run EXAMPLE=fix44_server   # Run one example (the workspace has no binaries)

πŸ§ͺ Test & Quality

make test          # Run all tests
make fmt           # Format code
make fmt-check     # Check formatting without applying
make lint          # Run clippy with warnings as errors
make lint-fix      # Auto-fix lint issues
make fix           # Auto-fix Rust compiler suggestions
make check         # Run fmt-check + lint + test + doc + check-spanish
make pre-push      # Run fix + fmt + lint-fix + test + check-spanish + readme + doc

πŸ“¦ Packaging & Docs

make doc           # Fail on any undocumented public item (clippy -D missing_docs)
make doc-open      # Build and open Rust documentation
make create-doc    # Generate internal docs
make readme        # No-op: README.md is hand-maintained in this workspace
make publish       # Explain how to publish; see publish-all
make publish-all   # Publish all crates in dependency order, fail-fast

Publishing reads CARGO_REGISTRY_TOKEN from the environment β€” it is never passed on a command line. Preview the sequence without touching crates.io with make publish-all DRY_RUN=1.

πŸ“ˆ Coverage & Benchmarks

make coverage            # Generate code coverage report (XML)
make coverage-html       # Generate HTML coverage report
make open-coverage       # Open HTML report
make bench               # Run the criterion benchmarks
make bench-quick         # Reduced sample count; a smoke run, not a measurement
make bench-build         # Compile the benchmarks without running them
make bench-show          # Open the criterion HTML report
make bench-save          # Save the current run as a named baseline
make bench-compare       # Compare the current run against a saved baseline
make bench-clean         # Remove benchmark data

Benchmarks cover the hot paths: tag=value decode/encode and checksum (ironfix-tagvalue), the FAST stop-bit and presence-map primitives (ironfix-fast), and the FixCodec framing loop (ironfix-transport).

ℹ️ The harness ships no recorded baseline and no published figures. Every performance number in doc/ is a design target; treat any figure as unmeasured until you have produced it yourself with make bench on hardware you name.

πŸ§ͺ Git & Workflow Helpers

make git-log             # Show commits on current branch vs main
make check-spanish       # Enforce English-only sources and docs (runs in CI)
make zip                 # Create zip without target/ and temp files
make tree                # Visualize project tree (excludes common clutter)

πŸ€– GitHub Actions (via act)

make workflow-build      # Simulate build workflow
make workflow-lint       # Simulate lint workflow
make workflow-test       # Simulate test workflow
make workflow-coverage   # Simulate coverage workflow
make workflow            # Run all workflows

ℹ️ Requires act for local workflow simulation and cargo-tarpaulin for coverage.

Examples

All examples live in ironfix-example/examples/. Each FIX version has a client/server pair; run the server first, then the client in another terminal.

# Start a FIX 4.4 server
cargo run --example fix44_server

# In another terminal, start the client
cargo run --example fix44_client

Per-version pairs β€” note that the servers hand-roll their accept loop and session handling, because there is no Acceptor in ironfix-engine:

  • fix40_server / fix40_client β€” FIX 4.0 (port 9870)
  • fix41_server / fix41_client β€” FIX 4.1 (port 9871)
  • fix42_server / fix42_client β€” FIX 4.2 (port 9872)
  • fix43_server / fix43_client β€” FIX 4.3 (port 9873)
  • fix44_server / fix44_client β€” FIX 4.4 (port 9876)
  • fix50_server / fix50_client β€” FIX 5.0 over FIXT.1.1 (port 9880)
  • fix50sp1_server / fix50sp1_client β€” FIX 5.0 SP1 (port 9881)
  • fix50sp2_server / fix50sp2_client β€” FIX 5.0 SP2 (port 9882)

Engine and concurrency examples:

  • fix44_engine_client β€” the same client flow driven by Initiator, which owns the socket, framing, Logon and heartbeats. Pairs with fix44_server.
  • fix44_server_channel β€” server-side message hand-off over a channel.

FAST examples:

  • fast_server / fast_client β€” FAST encode/decode over a socket
  • fast_server_spsc β€” FAST server with single-producer/single-consumer hand-off

Documentation

  • doc/fix_operations.md β€” the FIX operations specification this engine conforms to, plus the implementation checklist. This is the authority for message layouts, required tags and session semantics.
  • doc/architecture.md β€” a forward-looking design target, not a description of the current code. Read its status banner before relying on anything in it.

Contribution and Contact

We welcome contributions to this project! If you would like to contribute, please follow these steps:

  1. Fork the repository.
  2. Create a new branch for your feature or bug fix.
  3. Make your changes and ensure that the project still builds and all tests pass.
  4. Commit your changes and push your branch to your forked repository.
  5. Submit a pull request to the main repository.

If you have any questions, issues, or would like to provide feedback, please feel free to contact the project maintainer:

Contact Information

We appreciate your interest and look forward to your contributions!

License: MIT

Related projects

Repositories by the same author that this project depends on, and repositories that depend on it.

Used by

Repository Description
fauxchange Exchange-in-a-box: local options exchange simulator with realistic matching, FIX/WS/REST APIs and historical replay.
otc-rfq OTC Request-for-Quote engine with REST, SBE streaming and FIX/WebSocket/gRPC venue connectivity.

About

IronFix is a robust and efficient Rust-based implementation of the Financial Information eXchange (FIX) protocol, designed for secure and high-performance financial messaging.

Topics

Resources

Stars

11 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages