unb-core 2.0.3

Core unb protocol types: envelope, session, routing, taxonomy
Documentation
//! Frame-codec cost: a length-prefixed JSON framing (frozen below as `legacy`)
//! against the standard HTTP/1.1 framing, on the hot path — a request and a
//! response with a few headers and a small JSON body.
//!
//!     cargo bench -p unb-core --bench codec

use std::hint::black_box;
use std::time::Instant;

use serde_json::json;
use unb_core::{Envelope, Kind, DEFAULT_HOPS, PROTOCOL_VERSION};

mod legacy {
    use bytes::{BufMut, Bytes, BytesMut};

    use unb_core::Envelope;

    pub fn decode(frame: Bytes) -> Envelope {
        let header_len =
            u32::from_le_bytes(frame[0..4].try_into().expect("length prefix")) as usize;
        let header_end = 4 + header_len;
        let mut envelope: Envelope =
            serde_json::from_slice(&frame[4..header_end]).expect("legacy header json");
        envelope.payload = frame.slice(header_end..);
        envelope
    }

    pub fn encode(envelope: &Envelope) -> Bytes {
        let header = serde_json::to_vec(envelope).expect("envelope header is plain json data");
        let header_len = header.len();
        let mut frame = BytesMut::with_capacity(4 + header_len + envelope.payload.len());
        frame.put_u32_le(header_len as u32);
        frame.put_slice(&header);
        frame.put_slice(&envelope.payload);
        frame.freeze()
    }
}

fn request_envelope() -> Envelope {
    let mut headers = serde_json::Map::new();
    headers.insert("authorization".into(), json!("Bearer jwt-abc"));
    headers.insert("x-player".into(), json!("alice"));
    Envelope {
        v: PROTOCOL_VERSION,
        id: "f42".into(),
        target: "games".into(),
        subject: "chess.move".into(),
        kind: Kind::Request,
        corr: Some("s7".into()),
        seq: None,
        hops: Some(DEFAULT_HOPS),
        body_token: None,
        payload: Envelope::encode_payload(&json!({"from": "e2", "to": "e4"})),
        path: Vec::new(),
        headers,
    }
}

fn response_envelope() -> Envelope {
    Envelope {
        v: PROTOCOL_VERSION,
        id: "f43".into(),
        target: String::new(),
        subject: String::new(),
        kind: Kind::Response,
        corr: Some("s7".into()),
        seq: None,
        hops: None,
        body_token: None,
        payload: Envelope::encode_payload(&json!({"ok": true, "fen": "rnbqkbnr/pppppppp"})),
        path: Vec::new(),
        headers: serde_json::Map::new(),
    }
}

fn measure(label: &str, iterations: u32, mut run: impl FnMut()) {
    for _ in 0..iterations / 10 {
        run();
    }
    let started = Instant::now();
    for _ in 0..iterations {
        run();
    }
    let nanos = started.elapsed().as_nanos() / iterations as u128;
    println!("{label:<32} {nanos:>6} ns/op");
}

fn main() {
    const ITERATIONS: u32 = 200_000;
    for (name, envelope) in [
        ("request", request_envelope()),
        ("response", response_envelope()),
    ] {
        let http_frame = envelope.encode();
        let legacy_frame = legacy::encode(&envelope);
        println!(
            "{name}: http frame {} bytes, legacy frame {} bytes",
            http_frame.len(),
            legacy_frame.len()
        );
        measure(&format!("{name} encode http"), ITERATIONS, || {
            black_box(black_box(&envelope).encode());
        });
        measure(&format!("{name} encode legacy"), ITERATIONS, || {
            black_box(legacy::encode(black_box(&envelope)));
        });
        measure(&format!("{name} decode http"), ITERATIONS, || {
            black_box(Envelope::decode(black_box(http_frame.clone())).expect("http frame"));
        });
        measure(&format!("{name} decode legacy"), ITERATIONS, || {
            black_box(legacy::decode(black_box(legacy_frame.clone())));
        });
    }
}