test(proto): verify HMAC field placement in protocol packets

This commit is contained in:
Nils Andreas Svee committed 2026-05-10 20:56:43 +07:00
1 parent 5831f4a3b5
commit ab855338af
4 files changed
+203

No files matched your search

+19
View File
@@ -29,6 +29,7 @@ pub fn encode_close_request(request: &CloseRequest, hmac_key: Option<&[u8]>) ->
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::{hmac, FLAG_HMAC};
#[test] #[test]
fn close_request_sizes() { fn close_request_sizes() {
@@ -43,4 +44,22 @@ mod tests {
28 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();
}
} }
+47
View File
@@ -179,6 +179,32 @@ mod tests {
assert!(packet[12..].iter().all(|byte| *byte == 0)); 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] #[test]
fn echo_reply_decodes_default_fields() { fn echo_reply_decodes_default_fields() {
let packet = [ let packet = [
@@ -196,4 +222,25 @@ mod tests {
assert!(reply.timestamps.recv_wall.is_some()); assert!(reply.timestamps.recv_wall.is_some());
assert!(reply.timestamps.midpoint_wall.is_none()); 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, &params);
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, &params, 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);
}
} }
+100
View File
@@ -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] #[test]
fn verified_layout_lengths() { fn verified_layout_lengths() {
assert_eq!( 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, &params);
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, &params) - echo_header_len(false, &params),
HMAC_SIZE
);
}
}
}
}
}
}
#[test] #[test]
fn no_timestamp_layout_ignores_clock() { fn no_timestamp_layout_ignores_clock() {
for clock in [Clock::Wall, Clock::Monotonic, Clock::Both] { for clock in [Clock::Wall, Clock::Monotonic, Clock::Both] {
+37
View File
@@ -130,6 +130,26 @@ mod tests {
assert_eq!(&packet[4..], &default_params().encode()); 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] #[test]
fn open_reply_decodes_token() { fn open_reply_decodes_token() {
let mut packet = vec![0x14, 0xa7, 0x5b, 0x03]; 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.token, 0x7896_b6ab_8771_5213);
assert_eq!(reply.params, default_params()); 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());
}
} }