Release the session a client close names. A close is bound to a session only when the token matches and the datagram arrived from that session's exact endpoint, so holding a token is not by itself authority to tear the session down: a close from a foreign source port, address, family or IPv6 scope leaves the session live, and one close cannot take down a sibling session the same peer opened separately. A token that is unknown, already closed or zero matches nothing and leaves the table untouched; zero is reserved for no-test replies and never issued. Nothing is sent either way. Protocol version 1 defines no acknowledgement for a client close, so a successful close returns the same `Ok(None)` as a rejected one and a delivered close is indistinguishable to the client from a lost one. A standalone `Reply|Close` datagram is tolerated by clients but has no demonstrated use, so none is encoded. Authentication still runs before the session is looked up. That keeps a bad MAC on a live token indistinguishable from an unknown token, and means a close that fails authentication — missing, wrongly keyed, corrupted, or unexpected on a server with no key — cannot remove a session. Trailing bytes after the token stay acceptable: the structural decoder already tolerates them and the server does not re-parse the request. The session table remains the only record of how full the server is, so a close frees capacity immediately and a refused open can succeed once one is released. Close draws no token and touches no other state; the endpoint lookup borrows the table only until the comparison is made, so the removal needs no clone. Echo remains ignored. The test that pinned close as a no-op is replaced by `tests/close.rs`, which covers removal, silence, repeated close, echo after close, endpoint binding, unknown/zero/stale tokens, authentication failure, trailing bytes, sibling sessions and capacity reuse; `unsupported.rs` keeps echo and structurally invalid traffic.
75 lines
2.3 KiB
Rust
75 lines
2.3 KiB
Rust
//! Requests this slice deliberately does not implement.
|
|
//!
|
|
//! Echo is recognized by the admission boundary and then ignored. These tests
|
|
//! exist to keep the scope boundary honest: receive state must not appear by
|
|
//! accident before the slice that owns it.
|
|
|
|
use irtt_proto::{encode_request, RequestToEncode};
|
|
|
|
use super::support::{
|
|
client_params, core_for, open_request, open_session, peer, ScriptedTokens, KEY,
|
|
};
|
|
|
|
const TOKEN_A: u64 = 0x0102_0304_0506_0708;
|
|
|
|
fn open_one_session(hmac_key: Option<&[u8]>) -> crate::ServerCore {
|
|
let mut core = core_for(hmac_key, ScriptedTokens::new([TOKEN_A]));
|
|
open_session(&mut core, peer(), hmac_key);
|
|
core
|
|
}
|
|
|
|
#[test]
|
|
fn a_valid_echo_is_neither_answered_nor_counted_yet() {
|
|
for hmac_key in [None, Some(KEY)] {
|
|
let mut core = open_one_session(hmac_key);
|
|
let session = core.session(TOKEN_A).cloned().expect("session must exist");
|
|
|
|
let echo = encode_request(
|
|
RequestToEncode::Echo {
|
|
token: TOKEN_A,
|
|
sequence: 0,
|
|
params: session.params(),
|
|
payload: &[],
|
|
},
|
|
hmac_key,
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(core.handle_datagram(peer(), &echo).unwrap(), None);
|
|
assert_eq!(core.session_count(), 1);
|
|
assert_eq!(core.session(TOKEN_A), Some(&session));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn structurally_invalid_datagrams_are_discarded() {
|
|
let mut core = open_one_session(None);
|
|
let session = core.session(TOKEN_A).cloned().expect("session must exist");
|
|
|
|
let mut bad_magic = open_request(&client_params(), None);
|
|
bad_magic[0] ^= 0x01;
|
|
|
|
let mut reserved_flag = open_request(&client_params(), None);
|
|
reserved_flag[3] |= 0x10;
|
|
|
|
let mut reply_flag = open_request(&client_params(), None);
|
|
reply_flag[3] |= irtt_proto::FLAG_REPLY;
|
|
|
|
for (name, packet) in [
|
|
("empty datagram", Vec::new()),
|
|
("datagram shorter than the header", vec![0x14, 0xa7, 0x5b]),
|
|
("bad magic", bad_magic),
|
|
("reserved flag bit", reserved_flag),
|
|
("reply flag", reply_flag),
|
|
] {
|
|
assert_eq!(
|
|
core.handle_datagram(peer(), &packet).unwrap(),
|
|
None,
|
|
"{name} must be discarded"
|
|
);
|
|
}
|
|
|
|
assert_eq!(core.session_count(), 1);
|
|
assert_eq!(core.session(TOKEN_A), Some(&session));
|
|
}
|