use crate::{ params::{Params, StampAt}, ProtoError, Result, HEADER_SIZE, HMAC_SIZE, RECV_COUNT_SIZE, RECV_WINDOW_SIZE, SEQ_SIZE, TIMESTAMP_SIZE, TOKEN_SIZE, }; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct PacketLayout { pub hmac: bool, pub token: bool, pub sequence: bool, pub recv_count: bool, pub recv_window: bool, pub recv_wall: bool, pub recv_mono: bool, pub midpoint_wall: bool, pub midpoint_mono: bool, pub send_wall: bool, pub send_mono: bool, } impl PacketLayout { pub fn open_request(hmac: bool) -> Self { Self { hmac, token: false, sequence: false, recv_count: false, recv_window: false, recv_wall: false, recv_mono: false, midpoint_wall: false, midpoint_mono: false, send_wall: false, send_mono: false, } } pub fn open_reply(hmac: bool) -> Self { Self { token: true, ..Self::open_request(hmac) } } pub fn echo(hmac: bool, params: &Params) -> Self { let clock = params.clock; Self { hmac, token: true, sequence: true, recv_count: params.received_stats.has_count(), recv_window: params.received_stats.has_window(), recv_wall: matches!(params.stamp_at, StampAt::Receive | StampAt::Both) && clock.has_wall(), recv_mono: matches!(params.stamp_at, StampAt::Receive | StampAt::Both) && clock.has_mono(), midpoint_wall: matches!(params.stamp_at, StampAt::Midpoint) && clock.has_wall(), midpoint_mono: matches!(params.stamp_at, StampAt::Midpoint) && clock.has_mono(), send_wall: matches!(params.stamp_at, StampAt::Send | StampAt::Both) && clock.has_wall(), send_mono: matches!(params.stamp_at, StampAt::Send | StampAt::Both) && clock.has_mono(), } } pub fn close_request(hmac: bool) -> Self { Self { hmac, token: true, sequence: false, recv_count: false, recv_window: false, recv_wall: false, recv_mono: false, midpoint_wall: false, midpoint_mono: false, send_wall: false, send_mono: false, } } pub fn header_len(self) -> usize { HEADER_SIZE + if self.hmac { HMAC_SIZE } else { 0 } + if self.token { TOKEN_SIZE } else { 0 } + if self.sequence { SEQ_SIZE } else { 0 } + if self.recv_count { RECV_COUNT_SIZE } else { 0 } + if self.recv_window { RECV_WINDOW_SIZE } else { 0 } + self.timestamp_count() * TIMESTAMP_SIZE } pub fn timestamp_count(self) -> usize { [ self.recv_wall, self.recv_mono, self.midpoint_wall, self.midpoint_mono, self.send_wall, self.send_mono, ] .into_iter() .filter(|present| *present) .count() } } pub fn echo_header_len(hmac: bool, params: &Params) -> usize { PacketLayout::echo(hmac, params).header_len() } pub fn echo_packet_len(hmac: bool, params: &Params) -> usize { let header_len = echo_header_len(hmac, params); let requested = usize::try_from(params.length).unwrap_or(0); header_len.max(requested) } pub fn try_echo_packet_len(hmac: bool, params: &Params) -> Result { let header_len = echo_header_len(hmac, params); let requested = usize::try_from(params.length).map_err(|_| ProtoError::NegativePacketLength { length: params.length, })?; Ok(header_len.max(requested)) } #[cfg(test)] mod tests { use super::*; use crate::params::{Clock, ReceivedStats, StampAt}; fn params(stats: ReceivedStats, stamp_at: StampAt, clock: Clock) -> Params { Params { received_stats: stats, stamp_at, clock, ..Params::default() } } 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!( echo_header_len( false, ¶ms(ReceivedStats::None, StampAt::None, Clock::Both) ), 16 ); assert_eq!( echo_header_len( false, ¶ms(ReceivedStats::Count, StampAt::Send, Clock::Wall) ), 28 ); assert_eq!( echo_header_len( false, ¶ms(ReceivedStats::Window, StampAt::Receive, Clock::Monotonic) ), 32 ); assert_eq!( echo_header_len( false, ¶ms(ReceivedStats::Both, StampAt::Midpoint, Clock::Both) ), 44 ); assert_eq!( echo_header_len( false, ¶ms(ReceivedStats::Both, StampAt::Both, Clock::Both) ), 60 ); assert_eq!( echo_header_len( true, ¶ms(ReceivedStats::Both, StampAt::Both, Clock::Both) ), 76 ); } #[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] { assert_eq!( echo_header_len(false, ¶ms(ReceivedStats::None, StampAt::None, clock)), 16 ); } } #[test] fn negotiated_length_never_smaller_than_header() { let mut p = params(ReceivedStats::Both, StampAt::Both, Clock::Both); p.length = 20; assert_eq!(echo_packet_len(false, &p), 60); p.length = 92; assert_eq!(echo_packet_len(false, &p), 92); } #[test] fn checked_packet_len_rejects_negative_requested_length() { let mut p = params(ReceivedStats::Both, StampAt::Both, Clock::Both); p.length = -1; assert_eq!( try_echo_packet_len(false, &p), Err(ProtoError::NegativePacketLength { length: -1 }) ); } #[test] fn checked_packet_len_preserves_non_negative_header_floor() { let mut p = params(ReceivedStats::Both, StampAt::Both, Clock::Both); p.length = 0; assert_eq!(try_echo_packet_len(false, &p), Ok(60)); p.length = 92; assert_eq!(try_echo_packet_len(false, &p), Ok(92)); } }