irtt_proto/lib.rs
1//! Low-level wire encode/decode support for the IRTT protocol.
2//!
3//! `irtt-proto` intentionally mirrors protocol fields closely: request/reply
4//! structs expose wire-oriented values, flags, counters, timestamps, and
5//! parameter enums rather than higher-level policy decisions. Higher-level
6//! crates such as `irtt-client` are responsible for session behavior and
7//! user-facing validation.
8//!
9//! Most applications should depend on `irtt-client` or `irtt-server` instead
10//! of this crate directly; use `irtt-proto` only when implementing another
11//! IRTT-compatible client or server that needs the raw packet layer itself.
12//!
13//! [`Params::decode`] rejects malformed or incompatible incoming parameter
14//! values, including invalid enum discriminants, malformed UTF-8, and oversized
15//! `server_fill` values. Direct construction followed by [`Params::encode`]
16//! performs no additional validation, so callers that build `Params` manually
17//! are responsible for validating those values before sending them.
18//!
19//! Parameters are optional on the wire, and an absent tag takes its wire
20//! default — zero for every integer field, and [`Clock::Unspecified`] for the
21//! clock. A receiver that must distinguish an omitted tag from one explicitly
22//! encoded as zero uses [`Params::decode_with_presence`], which runs the same
23//! parser and additionally reports a [`ParamPresence`].
24//!
25//! The crate also provides packet layout calculation, packet encoding and
26//! decoding, and optional HMAC placement, computation, and verification
27//! helpers.
28//! Encoder `hmac_key` arguments are authoritative: `Some(key)` adds
29//! `FLAG_HMAC`, while `None` removes it from caller-supplied reply flags.
30//!
31//! # Directional model
32//!
33//! Requests use one encoder and one decoder for all three request kinds:
34//! [`encode_request`] builds a request from borrowed sender-side values, and
35//! [`decode_request`] structurally classifies an inbound request without a key,
36//! negotiated [`Params`], or any session state. [`verify_packet_hmac`]
37//! authenticates a packet separately, once the applicable key is known —
38//! `FLAG_HMAC` being present is a statement about layout, not about validity.
39//!
40//! Replies keep type-specific decoders ([`decode_open_reply`],
41//! [`decode_echo_reply`]) because a client already knows from its session state
42//! which reply kind to expect, and the two need genuinely different semantic
43//! context.
44//!
45//! # Example
46//!
47//! The normal path is directional: a sender encodes with [`encode_request`], a
48//! receiver classifies with [`decode_request`], and authentication is a
49//! separate step once the applicable key is known.
50//!
51//! ```
52//! use irtt_proto::{
53//! decode_request, encode_request, verify_packet_hmac, DecodedRequestKind, Params,
54//! RequestToEncode,
55//! };
56//!
57//! // Sender side: encode an authenticated open request.
58//! let params = Params::with_protocol_defaults();
59//! let key = b"example key";
60//! let packet = encode_request(
61//! RequestToEncode::Open { params: ¶ms, no_test: false },
62//! Some(key),
63//! ).unwrap();
64//!
65//! // Receiver side: classify structurally, before any key is known.
66//! let request = decode_request(&packet).unwrap();
67//! assert!(request.hmac_present);
68//! let DecodedRequestKind::Open { no_test, params: encoded } = request.kind else {
69//! panic!("expected an open request");
70//! };
71//! assert!(!no_test);
72//! assert_eq!(Params::decode(encoded).unwrap(), params);
73//!
74//! // Authentication is a separate step, once the applicable key is known.
75//! verify_packet_hmac(key, &packet).unwrap();
76//! ```
77//!
78#![forbid(unsafe_code)]
79
80pub mod echo;
81mod envelope;
82pub mod error;
83pub mod flags;
84pub mod hmac;
85pub mod layout;
86pub mod open;
87pub mod params;
88pub mod request;
89pub mod varint;
90
91pub use echo::{decode_echo_reply, encode_echo_reply, EchoReply, TimestampFields};
92pub use error::{ProtoError, Result};
93pub use flags::*;
94pub use hmac::{compute_hmac, compute_hmac_in_place, verify_hmac, verify_packet_hmac};
95pub use layout::{echo_header_len, echo_packet_len, PacketLayout};
96pub use open::{decode_open_reply, encode_open_reply, OpenReply};
97pub use params::{
98 Clock, DecodedParams, ParamPresence, Params, ReceivedStats, ServerFill, StampAt,
99 MAX_SERVER_FILL_BYTES,
100};
101pub use request::{
102 decode_request, encode_request, DecodedRequest, DecodedRequestKind, RequestToEncode,
103};
104
105pub const MAGIC: [u8; 3] = [0x14, 0xA7, 0x5B];
106pub const PROTOCOL_VERSION: i64 = 1;
107
108pub const HMAC_SIZE: usize = 16;
109pub const TOKEN_SIZE: usize = 8;
110pub const SEQ_SIZE: usize = 4;
111pub const RECV_COUNT_SIZE: usize = 4;
112pub const RECV_WINDOW_SIZE: usize = 8;
113pub const TIMESTAMP_SIZE: usize = 8;
114
115pub(crate) const HEADER_SIZE: usize = 4;
116
117pub(crate) fn write_header(out: &mut Vec<u8>, flags: u8) {
118 out.extend_from_slice(&MAGIC);
119 out.push(flags);
120}
121
122pub(crate) fn validate_header(packet: &[u8]) -> Result<u8> {
123 if packet.len() < HEADER_SIZE {
124 return Err(ProtoError::PacketTooShort {
125 needed: HEADER_SIZE,
126 actual: packet.len(),
127 });
128 }
129 if packet[..3] != MAGIC {
130 return Err(ProtoError::BadMagic);
131 }
132 let flags = packet[3];
133 flags::validate_flags(flags)?;
134 Ok(flags)
135}