feat(server): generate echo payload fill

This commit is contained in:
Nils Andreas Svee committed 2026-08-14 12:58:30 +07:00
1 parent 36500e1ea0
commit e4239f45c5
12 files changed
+721 -41

No files matched your search

+14 -3
View File
@@ -725,7 +725,11 @@ fn a_single_clock_midpoint_emits_exactly_one_field() {
}
#[test]
fn a_reply_payload_is_zero_filled_and_never_reflects_the_request() {
fn a_reply_payload_is_server_generated_and_never_reflects_the_request() {
// The inbound tail is opaque, and ServerFill describes the *server's* bytes:
// a request's payload is neither echoed, mixed in, nor used to seed
// anything. This session expressed no fill preference, so the region
// carries the server's default fill; `fill` covers the modes themselves.
let params = echo_params(ReceivedStats::None, StampAt::None, Clock::Unspecified, 64);
let (mut core, token, negotiated) = session_with(&params);
@@ -744,7 +748,14 @@ fn a_reply_payload_is_zero_filled_and_never_reflects_the_request() {
assert_eq!(packet.bytes().len(), 64);
assert_eq!(reply.payload.len(), 48);
assert!(
reply.payload.iter().all(|byte| *byte == 0),
"the payload region must be zero-filled, never request or buffer residue"
!reply.payload.contains(&0xa5),
"no request byte may appear in the reply payload"
);
assert!(
reply
.payload
.chunks(4)
.all(|chunk| chunk == &b"irtt"[..chunk.len()]),
"the region carries the server's default fill"
);
}
+241 -10
View File
@@ -1,21 +1,31 @@
//! Server fill negotiation: what an open reply reports, and what the session
//! will actually fill with.
//! Server fill: what an open reply reports, what the session will fill with,
//! and the bytes an echo reply actually carries.
//!
//! The two are deliberately different questions. A client that expressed no
//! preference keeps its absent or empty descriptor in the negotiated parameters
//! while the session uses this server's default fill, and only a descriptor the
//! server could not honor is rewritten. Asserting the reply alone would not
//! separate those, so these tests read the session's effective mode too.
//! The first two are deliberately different questions. A client that expressed
//! no preference keeps its absent or empty descriptor in the negotiated
//! parameters while the session uses this server's default fill, and only a
//! descriptor the server could not honor is rewritten. Asserting the reply alone
//! would not separate those, so the negotiation tests read the session's
//! effective mode too, and the payload tests then pin the bytes on the wire.
//!
//! Payload lengths here are derived from `irtt-proto`'s own layout rather than
//! written as offsets, and are deliberately not multiples of the pattern length.
use irtt_proto::{Params, ServerFill};
use irtt_proto::{echo_header_len, Clock, Params, ReceivedStats, ServerFill, StampAt};
use super::support::{
client_params, core_with_tokens, expect_no_test_reply, expect_normal_open_reply,
no_test_request, open_request, peer, unthrottled, ScriptedTokens,
client_params, core_for, core_with_tokens, echo_params, echo_request, expect_echo_reply,
expect_no_test_reply, expect_normal_open_reply, no_test_request, open_negotiated, open_request,
other_peer, peer, unthrottled, ScriptedTokens, KEY,
};
use crate::{core::ServerCore, fill::FillMode};
const TOKEN_A: u64 = 0x0102_0304_0506_0708;
const TOKEN_B: u64 = 0x1112_1314_1516_1718;
/// Request payload bytes an echo carries, so a reply reflecting them would be
/// obvious.
const REQUEST_FILLER: u8 = 0xa5;
/// Params requesting `descriptor` as the server fill.
fn requesting(descriptor: Option<&str>) -> Params {
@@ -152,3 +162,224 @@ fn a_no_test_open_negotiates_fill_exactly_as_a_normal_open_does() {
Some(crate::fill::DEFAULT_FILL_DESCRIPTOR)
);
}
/// Params for a session requesting `descriptor` whose echo replies have exactly
/// `payload_len` payload bytes.
///
/// The negotiated length is the mandatory field block plus the region wanted,
/// asked of `irtt-proto` rather than written as a constant: the block grows with
/// the negotiated statistics, the timestamps and authentication's 16 bytes, and
/// a test that hard-coded an offset would silently drift from the encoder.
fn payload_params(descriptor: Option<&str>, payload_len: usize, hmac: bool) -> Params {
let mut params = Params {
server_fill: requesting(descriptor).server_fill,
..echo_params(ReceivedStats::None, StampAt::None, Clock::Unspecified, 0)
};
params.length = (echo_header_len(hmac, &params) + payload_len) as i64;
params
}
/// The payload regions of `count` successive echo replies on one session
/// requesting `descriptor`.
///
/// Every request carries [`REQUEST_FILLER`] payload bytes, so a reply that
/// reflected its request would be visible in any of these tests rather than only
/// in the one about it.
fn echo_payloads(
descriptor: Option<&str>,
payload_len: usize,
hmac_key: Option<&[u8]>,
count: u32,
) -> Vec<Vec<u8>> {
let requested = payload_params(descriptor, payload_len, hmac_key.is_some());
let mut core = core_for(hmac_key, ScriptedTokens::new([TOKEN_A]));
let (token, negotiated) = open_negotiated(&mut core, peer(), &requested, hmac_key);
// The request fills its own payload region, which is the same size as the
// reply's: the strongest shape for "a reply never reflects its request".
let request_payload = vec![REQUEST_FILLER; payload_len];
(0..count)
.map(|sequence| {
let request = echo_request(token, sequence, &negotiated, &request_payload, hmac_key);
let packet = core
.handle_datagram(peer(), &request)
.unwrap()
.expect("an admissible echo must be answered");
let reply = expect_echo_reply(&packet, &negotiated, hmac_key);
assert_eq!(
packet.bytes().len() as i64,
negotiated.length,
"a reply is exactly the negotiated length"
);
assert_eq!(reply.payload.len(), payload_len, "payload region length");
reply.payload
})
.collect()
}
/// The payload region of one echo reply on a session requesting `descriptor`.
fn echo_payload(descriptor: Option<&str>, payload_len: usize) -> Vec<u8> {
echo_payloads(descriptor, payload_len, None, 1).remove(0)
}
/// `pattern` repeated to `len` bytes, starting at its first byte.
fn repeated(pattern: &[u8], len: usize) -> Vec<u8> {
pattern.iter().copied().cycle().take(len).collect()
}
#[test]
fn no_preference_fills_with_the_default_pattern() {
// The wire consequence of the negotiation test above: absent descriptor,
// default bytes. 7 is not a multiple of 4, so the last repeat is partial.
assert_eq!(echo_payload(None, 7), b"irttirt".to_vec());
}
#[test]
fn an_empty_preference_fills_with_the_default_pattern() {
assert_eq!(echo_payload(Some(""), 7), b"irttirt".to_vec());
}
#[test]
fn an_unknown_or_malformed_descriptor_fills_with_the_default_pattern() {
for descriptor in ["bogus", "RAND", "pattern:", "pattern:abc", "pattern:zz"] {
assert_eq!(
echo_payload(Some(descriptor), 7),
b"irttirt".to_vec(),
"{descriptor}"
);
}
}
#[test]
fn no_fill_produces_a_zero_payload_and_never_the_request() {
// `irtt-rs` policy, and a deliberate divergence: the clean evidence records
// the reference server leaving this region as unspecified residual buffer
// content — returning the requester's own payload, or fragments of earlier
// traffic from another client. Zeroes disclose nothing.
let payload = echo_payload(Some("none"), 7);
assert_eq!(payload, vec![0; 7]);
assert!(!payload.contains(&REQUEST_FILLER));
}
#[test]
fn patterns_repeat_from_their_first_byte() {
for (descriptor, expected) in [
("pattern:00", &[0x00][..]),
("pattern:ff00", &[0xff, 0x00][..]),
// The reply follows the bytes the descriptor decodes to, whatever case
// it was written in.
("pattern:AaBb", &[0xaa, 0xbb][..]),
("pattern:69727474", b"irtt"),
] {
assert_eq!(
echo_payload(Some(descriptor), 7),
repeated(expected, 7),
"{descriptor}"
);
}
}
#[test]
fn a_pattern_restarts_for_every_reply() {
// Deliberate `irtt-rs` policy. The clean evidence records the reference
// implementation advancing one phase continuously across replies, sessions
// and listeners, and states that payload phase carries no protocol meaning
// and that a conforming client must not depend on it — so this server keeps
// no fill state at all and every reply starts at byte zero.
let payloads = echo_payloads(Some("pattern:010203"), 5, None, 2);
assert_eq!(payloads[0], vec![0x01, 0x02, 0x03, 0x01, 0x02]);
assert_eq!(
payloads[1], payloads[0],
"the phase resets rather than continuing at 03 01 02 03 01"
);
}
#[test]
fn sessions_fill_independently() {
// Interleaved on one core, so a shared cursor or a global phase would show
// up as one session's bytes drifting into the other's.
let mut core = core_with_tokens(unthrottled(), ScriptedTokens::new([TOKEN_A, TOKEN_B]));
let a_requested = payload_params(Some("pattern:aabb"), 5, false);
let b_requested = payload_params(Some("pattern:112233"), 5, false);
let (a_token, a) = open_negotiated(&mut core, peer(), &a_requested, None);
let (b_token, b) = open_negotiated(&mut core, other_peer(), &b_requested, None);
let mut payload_of = |endpoint, token, sequence, params: &Params| {
let packet = core
.handle_datagram(endpoint, &echo_request(token, sequence, params, &[], None))
.unwrap()
.expect("an admissible echo must be answered");
expect_echo_reply(&packet, params, None).payload
};
assert_eq!(
payload_of(peer(), a_token, 0, &a),
vec![0xaa, 0xbb, 0xaa, 0xbb, 0xaa]
);
assert_eq!(
payload_of(other_peer(), b_token, 0, &b),
vec![0x11, 0x22, 0x33, 0x11, 0x22]
);
assert_eq!(
payload_of(peer(), a_token, 1, &a),
vec![0xaa, 0xbb, 0xaa, 0xbb, 0xaa],
"session A is unaffected by session B's reply"
);
}
#[test]
fn a_random_session_fills_the_whole_region() {
// Deterministic assertions only: the region is the negotiated one and the
// reply decodes and authenticates. What the operating system's random
// source returns is not a property this suite can assert without flipping a
// coin, and `fill`'s unit tests cover propagation and the failure fallback.
for hmac_key in [None, Some(KEY)] {
// `echo_payloads` already asserts the datagram length and the region
// length of each reply, and decoding verifies the MAC. Nothing is
// asserted about the byte values, deliberately: a random payload
// containing any particular byte is chance, not behavior.
assert_eq!(echo_payloads(Some("rand"), 7, hmac_key, 2).len(), 2);
}
}
#[test]
fn authentication_consumes_payload_space_at_the_same_negotiated_length() {
// One negotiated length, two field blocks: authentication's 16 bytes come
// out of the payload region, not out of the datagram. Every remaining
// payload byte is still filled, and the authenticated reply's MAC covers
// those bytes — `expect_echo_reply` verifies it while decoding.
let unauthenticated = payload_params(Some("pattern:aa"), 48, false);
let length = unauthenticated.length;
let authenticated = Params {
length,
..payload_params(Some("pattern:aa"), 0, true)
};
let plain = {
let mut core = core_for(None, ScriptedTokens::new([TOKEN_A]));
let (token, negotiated) = open_negotiated(&mut core, peer(), &unauthenticated, None);
let packet = core
.handle_datagram(peer(), &echo_request(token, 0, &negotiated, &[], None))
.unwrap()
.expect("an admissible echo must be answered");
assert_eq!(packet.bytes().len() as i64, length);
expect_echo_reply(&packet, &negotiated, None).payload
};
let signed = {
let mut core = core_for(Some(KEY), ScriptedTokens::new([TOKEN_A]));
let (token, negotiated) = open_negotiated(&mut core, peer(), &authenticated, Some(KEY));
let packet = core
.handle_datagram(peer(), &echo_request(token, 0, &negotiated, &[], Some(KEY)))
.unwrap()
.expect("an admissible echo must be answered");
assert_eq!(packet.bytes().len() as i64, length);
expect_echo_reply(&packet, &negotiated, Some(KEY)).payload
};
assert_eq!(
signed.len() + irtt_proto::HMAC_SIZE,
plain.len(),
"authentication takes its 16 bytes from the payload region"
);
assert!(plain.iter().chain(&signed).all(|byte| *byte == 0xaa));
}
+18 -3
View File
@@ -15,7 +15,7 @@
use std::time::Duration;
use irtt_proto::{Clock, Params, ReceivedStats, StampAt, FLAG_CLOSE};
use irtt_proto::{echo_header_len, Clock, Params, ReceivedStats, ServerFill, StampAt, FLAG_CLOSE};
use super::support::{
close_request, core_with_sources, echo_at, echo_params, echo_request,
@@ -416,9 +416,20 @@ fn the_maximum_duration_deadline_starts_at_the_first_served_echo() {
#[test]
fn the_first_echo_past_the_maximum_duration_carries_close_and_ends_the_session() {
// The session negotiates a fill and a payload region, so this covers the
// close's payload as well: a server close is an ordinary echo reply with a
// flag added, and it fills exactly as the session's other replies do.
let requested = Params {
server_fill: Some(ServerFill {
value: "pattern:aabb".to_owned(),
}),
length: (echo_header_len(false, &params_at(SECOND)) + 5) as i64,
..params_at(SECOND)
};
let patterned = vec![0xaa, 0xbb, 0xaa, 0xbb, 0xaa];
let (mut core, clock, token, negotiated) = manual_core(
max_duration_of(Duration::from_secs(1)),
&params_at(SECOND),
&requested,
ScriptedTokens::new([TOKEN_A]),
None,
);
@@ -437,6 +448,7 @@ fn the_first_echo_past_the_maximum_duration_carries_close_and_ends_the_session()
.expect("short of the deadline must be answered normally");
let reply = expect_echo_reply(&packet, &negotiated, None);
assert_eq!(reply.flags & FLAG_CLOSE, 0, "not yet");
assert_eq!(reply.payload, patterned, "an ordinary reply's fill");
// At the deadline exactly.
let packet = echo_at(&mut core, &clock, 13 * SECOND, token, 2, &negotiated, None)
@@ -449,7 +461,10 @@ fn the_first_echo_past_the_maximum_duration_carries_close_and_ends_the_session()
(Some(3), Some(0x7)),
"the triggering request is included in the statistics it reports"
);
assert!(reply.payload.is_empty());
assert_eq!(
reply.payload, patterned,
"the close keeps the session's fill; it is not a special packet"
);
assert_eq!(
packet.bytes().len(),
irtt_proto::echo_packet_len(false, &negotiated).unwrap(),