diff --git a/crates/irtt-proto/src/close.rs b/crates/irtt-proto/src/close.rs index 11b7017..423fe35 100644 --- a/crates/irtt-proto/src/close.rs +++ b/crates/irtt-proto/src/close.rs @@ -29,6 +29,7 @@ pub fn encode_close_request(request: &CloseRequest, hmac_key: Option<&[u8]>) -> #[cfg(test)] mod tests { use super::*; + use crate::{hmac, FLAG_HMAC}; #[test] fn close_request_sizes() { @@ -43,4 +44,22 @@ mod tests { 28 ); } + + #[test] + fn hmac_close_request_places_token_after_hmac() { + let packet = encode_close_request( + &CloseRequest { + token: 0x7896_b6ab_8771_5213, + }, + Some(b"testkey"), + ) + .unwrap(); + + assert_eq!(packet[3], FLAG_CLOSE | FLAG_HMAC); + assert_eq!( + &packet[4 + HMAC_SIZE..], + &0x7896_b6ab_8771_5213u64.to_le_bytes() + ); + hmac::verify_hmac(b"testkey", &packet, hmac::hmac_offset()).unwrap(); + } } diff --git a/crates/irtt-proto/src/echo.rs b/crates/irtt-proto/src/echo.rs index 69c7c3f..8976b91 100644 --- a/crates/irtt-proto/src/echo.rs +++ b/crates/irtt-proto/src/echo.rs @@ -179,6 +179,32 @@ mod tests { assert!(packet[12..].iter().all(|byte| *byte == 0)); } + #[test] + fn hmac_echo_request_places_token_and_sequence_after_hmac() { + let packet = encode_echo_request( + &EchoRequest { + token: 0x7896_b6ab_8771_5213, + sequence: 9, + params: default_params(), + payload: Vec::new(), + }, + Some(b"testkey"), + ) + .unwrap(); + + assert_eq!(packet.len(), 76); + assert_eq!(&packet[..4], &[0x14, 0xa7, 0x5b, FLAG_HMAC]); + assert_eq!( + &packet[4 + HMAC_SIZE..4 + HMAC_SIZE + TOKEN_SIZE], + &0x7896_b6ab_8771_5213u64.to_le_bytes() + ); + assert_eq!( + &packet[4 + HMAC_SIZE + TOKEN_SIZE..4 + HMAC_SIZE + TOKEN_SIZE + SEQ_SIZE], + &9_u32.to_le_bytes() + ); + hmac::verify_hmac(b"testkey", &packet, hmac::hmac_offset()).unwrap(); + } + #[test] fn echo_reply_decodes_default_fields() { let packet = [ @@ -196,4 +222,25 @@ mod tests { assert!(reply.timestamps.recv_wall.is_some()); assert!(reply.timestamps.midpoint_wall.is_none()); } + + #[test] + fn hmac_echo_reply_decodes_default_fields_after_hmac() { + let params = default_params(); + let layout = PacketLayout::echo(true, ¶ms); + let mut packet = Vec::with_capacity(layout.header_len()); + write_header(&mut packet, FLAG_REPLY | FLAG_HMAC); + packet.extend_from_slice(&[0; HMAC_SIZE]); + packet.extend_from_slice(&0x7896_b6ab_8771_5213u64.to_le_bytes()); + packet.extend_from_slice(&2_u32.to_le_bytes()); + push_zeroed_layout_tail(layout, &mut packet); + hmac::compute_hmac_in_place(b"testkey", &mut packet, hmac::hmac_offset()).unwrap(); + + assert_eq!(packet.len(), 76); + let reply = decode_echo_reply(&packet, ¶ms, Some(b"testkey")).unwrap(); + assert_eq!(reply.token, 0x7896_b6ab_8771_5213); + assert_eq!(reply.sequence, 2); + assert_eq!(reply.recv_count, Some(0)); + assert_eq!(reply.recv_window, Some(0)); + assert_eq!(reply.payload.len(), 0); + } } diff --git a/crates/irtt-proto/src/layout.rs b/crates/irtt-proto/src/layout.rs index 740a3f2..6604be9 100644 --- a/crates/irtt-proto/src/layout.rs +++ b/crates/irtt-proto/src/layout.rs @@ -131,6 +131,25 @@ mod tests { } } + fn expected_optional_len(stats: ReceivedStats, stamp_at: StampAt, clock: Clock) -> usize { + let stats_len = match stats { + ReceivedStats::None => 0, + ReceivedStats::Count => RECV_COUNT_SIZE, + ReceivedStats::Window => RECV_WINDOW_SIZE, + ReceivedStats::Both => RECV_COUNT_SIZE + RECV_WINDOW_SIZE, + }; + let clock_count = match clock { + Clock::Wall | Clock::Monotonic => 1, + Clock::Both => 2, + }; + let timestamp_groups = match stamp_at { + StampAt::None => 0, + StampAt::Send | StampAt::Receive | StampAt::Midpoint => 1, + StampAt::Both => 2, + }; + stats_len + timestamp_groups * clock_count * TIMESTAMP_SIZE + } + #[test] fn verified_layout_lengths() { assert_eq!( @@ -177,6 +196,87 @@ mod tests { ); } + #[test] + fn open_and_close_layout_lengths_account_for_hmac() { + assert_eq!(PacketLayout::open_request(false).header_len(), 4); + assert_eq!(PacketLayout::open_request(true).header_len(), 20); + assert_eq!(PacketLayout::open_reply(false).header_len(), 12); + assert_eq!(PacketLayout::open_reply(true).header_len(), 28); + assert_eq!(PacketLayout::close_request(false).header_len(), 12); + assert_eq!(PacketLayout::close_request(true).header_len(), 28); + } + + #[test] + fn layout_matrix_matches_stats_timestamps_clock_and_hmac_rules() { + for stats in [ + ReceivedStats::None, + ReceivedStats::Count, + ReceivedStats::Window, + ReceivedStats::Both, + ] { + for stamp_at in [ + StampAt::None, + StampAt::Send, + StampAt::Receive, + StampAt::Both, + StampAt::Midpoint, + ] { + for clock in [Clock::Wall, Clock::Monotonic, Clock::Both] { + for hmac in [false, true] { + let params = params(stats, stamp_at, clock); + let layout = PacketLayout::echo(hmac, ¶ms); + let expected_len = HEADER_SIZE + + TOKEN_SIZE + + SEQ_SIZE + + if hmac { HMAC_SIZE } else { 0 } + + expected_optional_len(stats, stamp_at, clock); + + assert_eq!( + layout.header_len(), + expected_len, + "unexpected length for stats={stats:?} stamp_at={stamp_at:?} clock={clock:?} hmac={hmac}" + ); + assert_eq!(layout.recv_count, stats.has_count()); + assert_eq!(layout.recv_window, stats.has_window()); + assert_eq!( + layout.recv_wall, + matches!(stamp_at, StampAt::Receive | StampAt::Both) + && clock.has_wall() + ); + assert_eq!( + layout.recv_mono, + matches!(stamp_at, StampAt::Receive | StampAt::Both) + && clock.has_mono() + ); + assert_eq!( + layout.midpoint_wall, + matches!(stamp_at, StampAt::Midpoint) && clock.has_wall() + ); + assert_eq!( + layout.midpoint_mono, + matches!(stamp_at, StampAt::Midpoint) && clock.has_mono() + ); + assert_eq!( + layout.send_wall, + matches!(stamp_at, StampAt::Send | StampAt::Both) && clock.has_wall() + ); + assert_eq!( + layout.send_mono, + matches!(stamp_at, StampAt::Send | StampAt::Both) && clock.has_mono() + ); + + if hmac { + assert_eq!( + echo_header_len(true, ¶ms) - echo_header_len(false, ¶ms), + HMAC_SIZE + ); + } + } + } + } + } + } + #[test] fn no_timestamp_layout_ignores_clock() { for clock in [Clock::Wall, Clock::Monotonic, Clock::Both] { diff --git a/crates/irtt-proto/src/open.rs b/crates/irtt-proto/src/open.rs index d33ccab..23c1940 100644 --- a/crates/irtt-proto/src/open.rs +++ b/crates/irtt-proto/src/open.rs @@ -130,6 +130,26 @@ mod tests { assert_eq!(&packet[4..], &default_params().encode()); } + #[test] + fn hmac_open_request_inserts_hmac_before_params() { + let packet = encode_open_request( + &OpenRequest { + params: default_params(), + close: false, + }, + Some(b"testkey"), + ) + .unwrap(); + + assert_eq!( + packet.len(), + 4 + HMAC_SIZE + default_params().encode().len() + ); + assert_eq!(&packet[..4], &[0x14, 0xa7, 0x5b, FLAG_OPEN | FLAG_HMAC]); + assert_eq!(&packet[4 + HMAC_SIZE..], &default_params().encode()); + hmac::verify_hmac(b"testkey", &packet, hmac::hmac_offset()).unwrap(); + } + #[test] fn open_reply_decodes_token() { let mut packet = vec![0x14, 0xa7, 0x5b, 0x03]; @@ -140,4 +160,21 @@ mod tests { assert_eq!(reply.token, 0x7896_b6ab_8771_5213); assert_eq!(reply.params, default_params()); } + + #[test] + fn hmac_open_reply_decodes_token_after_hmac_field() { + let mut packet = vec![0x14, 0xa7, 0x5b, FLAG_OPEN | FLAG_REPLY | FLAG_HMAC]; + packet.extend_from_slice(&[0; HMAC_SIZE]); + packet.extend_from_slice(&0x7896_b6ab_8771_5213u64.to_le_bytes()); + packet.extend_from_slice(&default_params().encode()); + hmac::compute_hmac_in_place(b"testkey", &mut packet, hmac::hmac_offset()).unwrap(); + + assert_eq!( + packet.len(), + 4 + HMAC_SIZE + TOKEN_SIZE + default_params().encode().len() + ); + let reply = decode_open_reply(&packet, Some(b"testkey")).unwrap(); + assert_eq!(reply.token, 0x7896_b6ab_8771_5213); + assert_eq!(reply.params, default_params()); + } }