ironsbe 0.1.2

High-performance SBE (Simple Binary Encoding) server/client for Rust
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
<p align="center">
  <img src="doc/assets/ironsbe-logo.png" alt="IronSBE Logo" width="200"/>
</p>

<h1 align="center">IronSBE</h1>

<p align="center">
  <strong>High-Performance Simple Binary Encoding (SBE) Server/Client for Rust</strong>
</p>

<p align="center">
  <a href="https://crates.io/crates/ironsbe"><img src="https://img.shields.io/crates/v/ironsbe.svg" alt="Crates.io"/></a>
  <a href="https://docs.rs/ironsbe"><img src="https://docs.rs/ironsbe/badge.svg" alt="Documentation"/></a>
  <a href="https://github.com/joaquinbejar/IronSBE/actions"><img src="https://github.com/joaquinbejar/IronSBE/workflows/Build/badge.svg" alt="Build Status"/></a>
  <a href="https://codecov.io/gh/joaquinbejar/IronSBE"><img src="https://codecov.io/gh/joaquinbejar/IronSBE/branch/main/graph/badge.svg" alt="Coverage"/></a>
  <a href="https://github.com/joaquinbejar/IronSBE/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"/></a>
</p>

<p align="center">
  Zero-copy • Schema-driven codegen • Sub-microsecond latency • Millions of msg/sec
</p>

---

## Overview

IronSBE is a complete Rust implementation of the [Simple Binary Encoding (SBE)](https://www.fixtrading.org/standards/sbe/) protocol, designed for ultra-low-latency financial systems. It provides both server and client capabilities with a focus on performance, type safety, and ease of use.

SBE is the binary encoding standard used by major exchanges including CME, Eurex, LSE, NASDAQ, and Binance for market data feeds and order entry systems.

### Key Features

- **Zero-copy decoding** - Direct buffer access with compile-time offset calculation
- **Schema-driven code generation** - Type-safe messages from XML specifications
- **Sub-microsecond latency** - Cache-friendly memory layouts with aligned buffers
- **Multi-transport support** - TCP, UDP unicast/multicast, shared memory IPC
- **A/B feed arbitration** - First-arrival-wins deduplication for redundant feeds
- **Market data patterns** - Order book management, gap detection, snapshot recovery
- **100% safe Rust** - No unsafe code in core library
- **Async/await support** - Built on Tokio for high-performance async I/O

---

## Performance

Benchmarked on Apple M4 Max 64GB, macOS Tahoe 26.2:

| Operation | Latency (p50) | Latency (p99) | Throughput |
|-----------|---------------|---------------|------------|
| Encode NewOrderSingle | 3 ns | 4 ns | 342.8M msg/sec |
| Decode NewOrderSingle | 1 ns | 1 ns | 1262.6M msg/sec |
| Encode MarketData (10 entries) | 6 ns | 8 ns | 165.5M msg/sec |
| Decode MarketData (10 entries) | 0 ns | 1 ns | 2178.6M msg/sec |
| SPSC channel send | 2 ns | 2 ns | 648.5M msg/sec |
| MPSC channel send | 7 ns | 9 ns | 145.5M msg/sec |
| TCP round-trip (localhost) | 16.8 μs | 23.7 μs | 60K msg/sec |

Run your own benchmarks:
```bash
cargo run --example benchmark_report --release
```

---

## Quick Start

### Installation

Add IronSBE to your `Cargo.toml`:

```toml
[dependencies]
ironsbe = "0.1"

[build-dependencies]
ironsbe-codegen = "0.1"
```

### Define Your Schema

Create an SBE schema file (`schemas/trading.xml`):

```xml
<?xml version="1.0" encoding="UTF-8"?>
<sbe:messageSchema xmlns:sbe="http://fixprotocol.io/2016/sbe"
                   package="trading"
                   id="1"
                   version="1"
                   byteOrder="littleEndian">
    <types>
        <composite name="messageHeader">
            <type name="blockLength" primitiveType="uint16"/>
            <type name="templateId" primitiveType="uint16"/>
            <type name="schemaId" primitiveType="uint16"/>
            <type name="version" primitiveType="uint16"/>
        </composite>
        
        <enum name="Side" encodingType="uint8">
            <validValue name="Buy">1</validValue>
            <validValue name="Sell">2</validValue>
        </enum>
        
        <type name="Symbol" primitiveType="char" length="8"/>
        <type name="ClOrdId" primitiveType="char" length="20"/>
    </types>
    
    <sbe:message name="NewOrderSingle" id="1" blockLength="48">
        <field name="clOrdId" id="11" type="ClOrdId" offset="0"/>
        <field name="symbol" id="55" type="Symbol" offset="20"/>
        <field name="side" id="54" type="Side" offset="28"/>
        <field name="price" id="44" type="int64" offset="29"/>
        <field name="quantity" id="38" type="uint64" offset="37"/>
    </sbe:message>
</sbe:messageSchema>
```

### Generate Code

Add to your `build.rs`:

```rust
fn main() {
    ironsbe_codegen::generate(
        "schemas/trading.xml",
        &format!("{}/trading.rs", std::env::var("OUT_DIR").unwrap()),
    ).expect("Failed to generate SBE codecs");
}
```

### Encode Messages

```rust
use ironsbe::prelude::*;

// Include generated code
mod trading {
    include!(concat!(env!("OUT_DIR"), "/trading.rs"));
}

use trading::{NewOrderSingleEncoder, Side};

fn main() {
    // Allocate buffer (stack or pool)
    let mut buffer = [0u8; 256];
    
    // Create encoder (writes header automatically)
    let mut encoder = NewOrderSingleEncoder::wrap(&mut buffer, 0);
    
    // Set fields with builder pattern
    encoder
        .set_cl_ord_id(b"ORDER-001           ")
        .set_symbol(b"AAPL    ")
        .set_side(Side::Buy)
        .set_price(15050)      // $150.50 as fixed-point
        .set_quantity(100);
    
    // Get encoded length
    let len = encoder.encoded_length();
    
    // Send buffer[..len] over network
    println!("Encoded {} bytes", len);
}
```

### Decode Messages

```rust
use ironsbe::prelude::*;

mod trading {
    include!(concat!(env!("OUT_DIR"), "/trading.rs"));
}

use trading::{NewOrderSingleDecoder, SCHEMA_VERSION};

fn main() {
    // Received from network
    let buffer: &[u8] = /* ... */;
    
    // Zero-copy decode (no allocation)
    let decoder = NewOrderSingleDecoder::wrap(
        buffer,
        MessageHeader::ENCODED_LENGTH,
        SCHEMA_VERSION,
    );
    
    // Access fields directly from buffer
    println!("ClOrdId: {:?}", decoder.cl_ord_id());
    println!("Symbol: {:?}", decoder.symbol());
    println!("Side: {:?}", decoder.side());
    println!("Price: {}", decoder.price());
    println!("Quantity: {}", decoder.quantity());
}
```

---

## Architecture

```mermaid
graph TB
    subgraph Application["Application Layer"]
        Server["Server Engine"]
        Client["Client Engine"]
        MarketData["Market Data Handler"]
    end

    subgraph Channel["Channel Layer"]
        SPSC["SPSC<br/>~20 ns"]
        MPSC["MPSC<br/>~100 ns"]
        Broadcast["Broadcast<br/>1-to-N"]
    end

    subgraph Transport["Transport Layer"]
        TCP["TCP<br/>Tokio"]
        UDP["UDP<br/>Unicast"]
        Multicast["Multicast<br/>A/B"]
        IPC["IPC<br/>SHM"]
    end

    subgraph Codec["Codec Layer"]
        Encoders["Generated Encoders / Decoders<br/>Zero-copy, compile-time offsets, type-safe"]
        Core["ironsbe-core<br/>Buffer traits, headers, primitives"]
    end

    Server --> SPSC
    Server --> MPSC
    Client --> SPSC
    Client --> MPSC
    MarketData --> Broadcast

    SPSC --> TCP
    SPSC --> UDP
    MPSC --> TCP
    MPSC --> Multicast
    Broadcast --> Multicast
    Broadcast --> IPC

    TCP --> Encoders
    UDP --> Encoders
    Multicast --> Encoders
    IPC --> Encoders
    Encoders --> Core
```

---

## Crate Structure

IronSBE is organized as a Cargo workspace with 11 crates:

| Crate | Description |
|-------|-------------|
| [`ironsbe`](ironsbe/) | Facade crate re-exporting public API |
| [`ironsbe-core`](ironsbe-core/) | Buffer traits, message headers, primitive types, encoder/decoder traits |
| [`ironsbe-schema`](ironsbe-schema/) | SBE XML schema parser and validation |
| [`ironsbe-codegen`](ironsbe-codegen/) | Build-time Rust code generation from SBE schemas |
| [`ironsbe-derive`](ironsbe-derive/) | Procedural macros (`#[derive(SbeMessage)]`) |
| [`ironsbe-channel`](ironsbe-channel/) | Lock-free SPSC/MPSC/Broadcast channels |
| [`ironsbe-transport`](ironsbe-transport/) | TCP, UDP unicast/multicast, shared memory IPC |
| [`ironsbe-server`](ironsbe-server/) | Async server engine with session management |
| [`ironsbe-client`](ironsbe-client/) | Async client with auto-reconnection |
| [`ironsbe-marketdata`](ironsbe-marketdata/) | Order book, gap detection, A/B feed arbitration |
| [`ironsbe-bench`](ironsbe-bench/) | Benchmarks using Criterion |

### Dependency Graph

```
ironsbe (facade)
├── ironsbe-core
├── ironsbe-schema
├── ironsbe-codegen
├── ironsbe-channel
├── ironsbe-transport
├── ironsbe-server
├── ironsbe-client
└── ironsbe-marketdata

ironsbe-server
├── ironsbe-core
├── ironsbe-channel
└── ironsbe-transport

ironsbe-client
├── ironsbe-core
├── ironsbe-channel
└── ironsbe-transport

ironsbe-codegen
├── ironsbe-core
└── ironsbe-schema
```

---

## Examples

### Running the Examples

IronSBE includes working server and client examples:

```bash
# Terminal 1: Start the server
cd ironsbe && cargo run --example server

# Terminal 2: Run the client
cd ironsbe && cargo run --example client
```

**Expected output:**

Server:
```
Starting IronSBE server on 127.0.0.1:9000
[Server] Session 1 connected
[Server] Message #1 from session 1: template_id=101, size=45 bytes
[Server] Message #2 from session 1: template_id=102, size=45 bytes
...
[Server] Session 1 disconnected
```

Client:
```
Connecting to IronSBE server at 127.0.0.1:9000
[Client] Connected to server
[Client] Sent message #1
[Client] Received response: 45 bytes
[Client] Response payload: Hello from IronSBE client! Message #1
...
Client stopped
```

### TCP Echo Server

```rust
use ironsbe_core::header::MessageHeader;
use ironsbe_server::builder::ServerBuilder;
use ironsbe_server::handler::{MessageHandler, Responder};
use std::net::SocketAddr;
use std::sync::atomic::{AtomicU64, Ordering};

struct EchoHandler {
    message_count: AtomicU64,
}

impl MessageHandler for EchoHandler {
    fn on_message(
        &self,
        session_id: u64,
        header: &MessageHeader,
        buffer: &[u8],
        responder: &dyn Responder,
    ) {
        let count = self.message_count.fetch_add(1, Ordering::Relaxed) + 1;
        println!(
            "Message #{} from session {}: template_id={}, size={} bytes",
            count, session_id, header.template_id, buffer.len()
        );
        
        // Echo the message back
        responder.send(buffer).ok();
    }

    fn on_session_start(&self, session_id: u64) {
        println!("Session {} connected", session_id);
    }

    fn on_session_end(&self, session_id: u64) {
        println!("Session {} disconnected", session_id);
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let addr: SocketAddr = "127.0.0.1:9000".parse()?;
    let handler = EchoHandler { message_count: AtomicU64::new(0) };

    let (mut server, handle) = ServerBuilder::new()
        .bind(addr)
        .handler(handler)
        .max_connections(100)
        .build();

    // Handle Ctrl+C for graceful shutdown
    let shutdown_handle = std::sync::Arc::new(handle);
    let sh = shutdown_handle.clone();
    tokio::spawn(async move {
        tokio::signal::ctrl_c().await.ok();
        sh.shutdown();
    });

    server.run().await?;
    Ok(())
}
```

### TCP Client

```rust
use ironsbe_client::builder::{ClientBuilder, ClientEvent};
use ironsbe_core::buffer::{AlignedBuffer, ReadBuffer, WriteBuffer};
use ironsbe_core::header::MessageHeader;
use std::net::SocketAddr;
use std::time::Duration;

fn create_message(template_id: u16, payload: &[u8]) -> Vec<u8> {
    let mut buffer = AlignedBuffer::<256>::new();
    let header = MessageHeader::new(payload.len() as u16, template_id, 1, 1);
    header.encode(&mut buffer, 0);
    
    let header_size = MessageHeader::ENCODED_LENGTH;
    buffer.as_mut_slice()[header_size..header_size + payload.len()]
        .copy_from_slice(payload);
    
    buffer.as_slice()[..header_size + payload.len()].to_vec()
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let addr: SocketAddr = "127.0.0.1:9000".parse()?;

    let (mut client, mut handle) = ClientBuilder::new(addr)
        .connect_timeout(Duration::from_secs(5))
        .max_reconnect_attempts(3)
        .build();

    // Run client in background
    tokio::spawn(async move { client.run().await });
    tokio::time::sleep(Duration::from_millis(100)).await;

    // Send messages
    for i in 1..=5 {
        let payload = format!("Hello from client! Message #{}", i);
        let message = create_message(100 + i as u16, payload.as_bytes());
        handle.send(message)?;
        
        // Poll for responses
        while let Some(event) = handle.poll() {
            match event {
                ClientEvent::Message(data) => {
                    println!("Received: {} bytes", data.len());
                }
                ClientEvent::Connected => println!("Connected"),
                ClientEvent::Disconnected => println!("Disconnected"),
                ClientEvent::Error(e) => eprintln!("Error: {}", e),
            }
        }
        
        tokio::time::sleep(Duration::from_millis(100)).await;
    }

    handle.disconnect();
    Ok(())
}
```

### SPSC Channel (Ultra-Low Latency)

```rust
use ironsbe_channel::spsc;

fn main() {
    // Create a lock-free SPSC channel
    let (mut tx, mut rx) = spsc::channel::<u64>(4096);
    
    // Producer thread
    std::thread::spawn(move || {
        for i in 0..1_000_000 {
            // ~12ns send latency
            while tx.send(i).is_err() {
                std::hint::spin_loop();
            }
        }
    });
    
    // Consumer thread - busy-poll for lowest latency
    let mut count = 0u64;
    loop {
        if let Some(value) = rx.recv() {
            count += 1;
            if count >= 1_000_000 {
                break;
            }
        }
    }
    
    println!("Received {} messages", count);
}
```

### Broadcast Channel

```rust
use ironsbe_channel::broadcast::BroadcastChannel;

fn main() {
    let channel = BroadcastChannel::<u64>::new(1024);
    
    // Create multiple subscribers
    let mut sub1 = channel.subscribe();
    let mut sub2 = channel.subscribe();
    
    // Publish messages
    channel.send(42);
    channel.send(100);
    
    // All subscribers receive all messages
    assert_eq!(sub1.recv(), Some(42));
    assert_eq!(sub2.recv(), Some(42));
    assert_eq!(sub1.recv(), Some(100));
    assert_eq!(sub2.recv(), Some(100));
}
```

---

## Supported SBE Features

| Feature | Status |
|---------|--------|
| Primitive types (int8-64, uint8-64, float, double, char) | ✅ |
| Fixed-length arrays | ✅ |
| Enums | ✅ |
| Bitsets (sets) | ✅ |
| Composite types | ✅ |
| Repeating groups | ✅ |
| Nested repeating groups | ✅ |
| Variable-length data | ✅ |
| Optional fields (null values) | ✅ |
| Schema versioning | ✅ |
| Little-endian byte order | ✅ |
| Big-endian byte order | ✅ |
| Message header customization | ✅ |
| Constant fields | ✅ |

---

## System Requirements

### Minimum
- Rust 1.75+
- Linux, macOS, or Windows

### Recommended for Production
- Linux kernel 5.10+ (for io_uring support)
- CPU with AVX2 support
- Dedicated CPU cores (isolcpus)
- 10GbE+ network interface
- Kernel bypass capable NIC (optional)

### System Tuning

```bash
# CPU performance mode
echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor

# Network tuning
sudo sysctl -w net.core.busy_read=50
sudo sysctl -w net.core.busy_poll=50
sudo sysctl -w net.core.rmem_max=16777216

# NIC tuning (disable interrupt coalescing)
sudo ethtool -C eth0 rx-usecs 0 tx-usecs 0
```

---

## Documentation

- [API Reference](https://docs.rs/ironsbe)
- [Architecture Guide](doc/architecture.md)
- [Examples](ironsbe/examples/)

---

## Development

### Prerequisites

- Rust 1.85+ (Edition 2024)
- Cargo

### Building

```bash
git clone https://github.com/joaquinbejar/IronSBE.git
cd IronSBE

# Build all crates
cargo build --workspace

# Run tests
cargo test --workspace

# Run linting
cargo clippy --all-targets --all-features -- -D warnings

# Format code
cargo fmt --all

# Generate documentation
cargo doc --workspace --no-deps --open
```

### Using the Makefile

The project includes a comprehensive Makefile:

```bash
# Format and lint
make fmt
make lint

# Run tests
make test

# Pre-push checks (recommended before committing)
make pre-push

# Run benchmarks
make bench

# Generate documentation
make doc

# Set version across all crates
make version VERSION=0.2.0

# Publish all crates to crates.io (in dependency order)
make publish-all
```

### Running Examples

```bash
# Run the server example
cd ironsbe && cargo run --example server

# Run the client example (in another terminal)
cd ironsbe && cargo run --example client
```

### Code Style

- Follow Rust standard formatting (`cargo fmt`)
- Pass all clippy lints (`cargo clippy -- -D warnings`)
- Add tests for new functionality
- Update documentation for API changes
- All comments and documentation in English

---

## Contributing

Contributions are welcome! 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. Run `make pre-push` to verify everything works
5. Commit your changes and push your branch to your forked repository
6. Submit a pull request to the main repository

---

## Contact

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

- **Author**: Joaquín Béjar García
- **Email**: jb@taunais.com
- **Telegram**: [@joaquin_bejar](https://t.me/joaquin_bejar)
- **Repository**: <https://github.com/joaquinbejar/IronSBE>
- **Documentation**: <https://docs.rs/ironsbe>

---

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.