1use crate::{
2 params::{Params, StampAt},
3 ProtoError, Result, HEADER_SIZE, HMAC_SIZE, RECV_COUNT_SIZE, RECV_WINDOW_SIZE, SEQ_SIZE,
4 TIMESTAMP_SIZE, TOKEN_SIZE,
5};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct PacketLayout {
9 pub hmac: bool,
10 pub token: bool,
11 pub sequence: bool,
12 pub recv_count: bool,
13 pub recv_window: bool,
14 pub recv_wall: bool,
15 pub recv_mono: bool,
16 pub midpoint_wall: bool,
17 pub midpoint_mono: bool,
18 pub send_wall: bool,
19 pub send_mono: bool,
20}
21
22impl PacketLayout {
23 pub fn open_request(hmac: bool) -> Self {
24 Self {
25 hmac,
26 token: false,
27 sequence: false,
28 recv_count: false,
29 recv_window: false,
30 recv_wall: false,
31 recv_mono: false,
32 midpoint_wall: false,
33 midpoint_mono: false,
34 send_wall: false,
35 send_mono: false,
36 }
37 }
38
39 pub fn open_reply(hmac: bool) -> Self {
40 Self {
41 token: true,
42 ..Self::open_request(hmac)
43 }
44 }
45
46 pub fn echo(hmac: bool, params: &Params) -> Self {
47 let clock = params.clock;
48 Self {
49 hmac,
50 token: true,
51 sequence: true,
52 recv_count: params.received_stats.has_count(),
53 recv_window: params.received_stats.has_window(),
54 recv_wall: matches!(params.stamp_at, StampAt::Receive | StampAt::Both)
55 && clock.has_wall(),
56 recv_mono: matches!(params.stamp_at, StampAt::Receive | StampAt::Both)
57 && clock.has_mono(),
58 midpoint_wall: matches!(params.stamp_at, StampAt::Midpoint) && clock.has_wall(),
59 midpoint_mono: matches!(params.stamp_at, StampAt::Midpoint) && clock.has_mono(),
60 send_wall: matches!(params.stamp_at, StampAt::Send | StampAt::Both) && clock.has_wall(),
61 send_mono: matches!(params.stamp_at, StampAt::Send | StampAt::Both) && clock.has_mono(),
62 }
63 }
64
65 pub fn close_request(hmac: bool) -> Self {
66 Self {
67 hmac,
68 token: true,
69 sequence: false,
70 recv_count: false,
71 recv_window: false,
72 recv_wall: false,
73 recv_mono: false,
74 midpoint_wall: false,
75 midpoint_mono: false,
76 send_wall: false,
77 send_mono: false,
78 }
79 }
80
81 pub fn header_len(self) -> usize {
82 HEADER_SIZE
83 + if self.hmac { HMAC_SIZE } else { 0 }
84 + if self.token { TOKEN_SIZE } else { 0 }
85 + if self.sequence { SEQ_SIZE } else { 0 }
86 + if self.recv_count { RECV_COUNT_SIZE } else { 0 }
87 + if self.recv_window {
88 RECV_WINDOW_SIZE
89 } else {
90 0
91 }
92 + self.timestamp_count() * TIMESTAMP_SIZE
93 }
94
95 pub fn timestamp_count(self) -> usize {
96 [
97 self.recv_wall,
98 self.recv_mono,
99 self.midpoint_wall,
100 self.midpoint_mono,
101 self.send_wall,
102 self.send_mono,
103 ]
104 .into_iter()
105 .filter(|present| *present)
106 .count()
107 }
108}
109
110pub fn echo_header_len(hmac: bool, params: &Params) -> usize {
111 PacketLayout::echo(hmac, params).header_len()
112}
113
114pub fn echo_packet_len(hmac: bool, params: &Params) -> Result<usize> {
153 let header_len = echo_header_len(hmac, params);
154 let requested = if params.length <= 0 {
155 0
158 } else {
159 usize::try_from(params.length).map_err(|_| ProtoError::PacketLengthUnrepresentable {
160 length: params.length,
161 })?
162 };
163 Ok(header_len.max(requested))
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169 use crate::params::{Clock, ReceivedStats, StampAt};
170
171 fn params(stats: ReceivedStats, stamp_at: StampAt, clock: Clock) -> Params {
172 Params {
173 received_stats: stats,
174 stamp_at,
175 clock,
176 ..Params::default()
177 }
178 }
179
180 fn expected_optional_len(stats: ReceivedStats, stamp_at: StampAt, clock: Clock) -> usize {
181 let stats_len = match stats {
182 ReceivedStats::None => 0,
183 ReceivedStats::Count => RECV_COUNT_SIZE,
184 ReceivedStats::Window => RECV_WINDOW_SIZE,
185 ReceivedStats::Both => RECV_COUNT_SIZE + RECV_WINDOW_SIZE,
186 };
187 let clock_count = match clock {
188 Clock::Unspecified => 0,
189 Clock::Wall | Clock::Monotonic => 1,
190 Clock::Both => 2,
191 };
192 let timestamp_groups = match stamp_at {
193 StampAt::None => 0,
194 StampAt::Send | StampAt::Receive | StampAt::Midpoint => 1,
195 StampAt::Both => 2,
196 };
197 stats_len + timestamp_groups * clock_count * TIMESTAMP_SIZE
198 }
199
200 #[test]
201 fn verified_layout_lengths() {
202 assert_eq!(PacketLayout::open_request(false).header_len(), 4);
203 assert_eq!(PacketLayout::open_request(true).header_len(), 20);
204 assert_eq!(PacketLayout::open_reply(false).header_len(), 12);
205 assert_eq!(PacketLayout::open_reply(true).header_len(), 28);
206 assert_eq!(PacketLayout::close_request(false).header_len(), 12);
207 assert_eq!(PacketLayout::close_request(true).header_len(), 28);
208 assert_eq!(
209 echo_header_len(
210 false,
211 ¶ms(ReceivedStats::None, StampAt::None, Clock::Both)
212 ),
213 16
214 );
215 assert_eq!(
216 echo_header_len(
217 false,
218 ¶ms(ReceivedStats::Count, StampAt::Send, Clock::Wall)
219 ),
220 28
221 );
222 assert_eq!(
223 echo_header_len(
224 false,
225 ¶ms(ReceivedStats::Window, StampAt::Receive, Clock::Monotonic)
226 ),
227 32
228 );
229 assert_eq!(
230 echo_header_len(
231 false,
232 ¶ms(ReceivedStats::Both, StampAt::Midpoint, Clock::Both)
233 ),
234 44
235 );
236 assert_eq!(
237 echo_header_len(
238 false,
239 ¶ms(ReceivedStats::Both, StampAt::Both, Clock::Both)
240 ),
241 60
242 );
243 assert_eq!(
244 echo_header_len(
245 true,
246 ¶ms(ReceivedStats::Both, StampAt::Both, Clock::Both)
247 ),
248 76
249 );
250 }
251
252 #[test]
253 fn layout_matrix_matches_stats_timestamps_clock_and_hmac_rules() {
254 for stats in [
255 ReceivedStats::None,
256 ReceivedStats::Count,
257 ReceivedStats::Window,
258 ReceivedStats::Both,
259 ] {
260 for stamp_at in [
261 StampAt::None,
262 StampAt::Send,
263 StampAt::Receive,
264 StampAt::Both,
265 StampAt::Midpoint,
266 ] {
267 for clock in [Clock::Wall, Clock::Monotonic, Clock::Both] {
268 for hmac in [false, true] {
269 let params = params(stats, stamp_at, clock);
270 let layout = PacketLayout::echo(hmac, ¶ms);
271 let expected_len = HEADER_SIZE
272 + TOKEN_SIZE
273 + SEQ_SIZE
274 + if hmac { HMAC_SIZE } else { 0 }
275 + expected_optional_len(stats, stamp_at, clock);
276
277 assert_eq!(
278 layout.header_len(),
279 expected_len,
280 "unexpected length for stats={stats:?} stamp_at={stamp_at:?} clock={clock:?} hmac={hmac}"
281 );
282 assert_eq!(layout.recv_count, stats.has_count());
283 assert_eq!(layout.recv_window, stats.has_window());
284 assert_eq!(
285 layout.recv_wall,
286 matches!(stamp_at, StampAt::Receive | StampAt::Both)
287 && clock.has_wall()
288 );
289 assert_eq!(
290 layout.recv_mono,
291 matches!(stamp_at, StampAt::Receive | StampAt::Both)
292 && clock.has_mono()
293 );
294 assert_eq!(
295 layout.midpoint_wall,
296 matches!(stamp_at, StampAt::Midpoint) && clock.has_wall()
297 );
298 assert_eq!(
299 layout.midpoint_mono,
300 matches!(stamp_at, StampAt::Midpoint) && clock.has_mono()
301 );
302 assert_eq!(
303 layout.send_wall,
304 matches!(stamp_at, StampAt::Send | StampAt::Both) && clock.has_wall()
305 );
306 assert_eq!(
307 layout.send_mono,
308 matches!(stamp_at, StampAt::Send | StampAt::Both) && clock.has_mono()
309 );
310
311 if hmac {
312 assert_eq!(
313 echo_header_len(true, ¶ms) - echo_header_len(false, ¶ms),
314 HMAC_SIZE
315 );
316 }
317 }
318 }
319 }
320 }
321 }
322
323 #[test]
324 fn negotiated_length_is_floored_at_the_required_field_block() {
325 let mut p = params(ReceivedStats::Both, StampAt::Both, Clock::Both);
329 for (length, expected) in [(-4096, 60), (-1, 60), (0, 60), (20, 60), (92, 92)] {
330 p.length = length;
331 assert_eq!(
332 echo_packet_len(false, &p),
333 Ok(expected),
334 "unexpected packet length for negotiated length {length}"
335 );
336 }
337 }
338
339 #[cfg(target_pointer_width = "32")]
343 #[test]
344 fn a_length_wider_than_usize_is_rejected_rather_than_saturated() {
345 let mut p = params(ReceivedStats::Both, StampAt::Both, Clock::Both);
346 p.length = 5_000_000_000;
347
348 assert_eq!(
349 echo_packet_len(false, &p),
350 Err(ProtoError::PacketLengthUnrepresentable {
351 length: 5_000_000_000
352 })
353 );
354 }
355}