1use crate::{
2 envelope::{self, FlagRule},
3 flags::{FLAG_OPEN, FLAG_REPLY},
4 layout::{echo_packet_len, PacketLayout},
5 params::{Clock, Params, StampAt},
6 ProtoError, Result, SEQ_SIZE, TIMESTAMP_SIZE, TOKEN_SIZE,
7};
8
9const MIDPOINT_COMPAT_EXTRA: usize = TIMESTAMP_SIZE;
15
16fn is_midpoint_single_clock(params: &Params) -> bool {
22 params.stamp_at == StampAt::Midpoint && matches!(params.clock, Clock::Wall | Clock::Monotonic)
23}
24
25fn midpoint_compat_packet_len(layout: PacketLayout, normal_len: usize) -> usize {
39 (layout.header_len() + MIDPOINT_COMPAT_EXTRA).max(normal_len)
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct EchoReply {
44 pub flags: u8,
45 pub token: u64,
46 pub sequence: u32,
47 pub recv_count: Option<u32>,
48 pub recv_window: Option<u64>,
49 pub timestamps: TimestampFields,
50 pub payload: Vec<u8>,
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, Default)]
54pub struct TimestampFields {
55 pub recv_wall: Option<i64>,
56 pub recv_mono: Option<i64>,
57 pub midpoint_wall: Option<i64>,
58 pub midpoint_mono: Option<i64>,
59 pub send_wall: Option<i64>,
60 pub send_mono: Option<i64>,
61}
62
63pub fn encode_echo_reply(
108 reply: &EchoReply,
109 params: &Params,
110 hmac_key: Option<&[u8]>,
111) -> Result<Vec<u8>> {
112 let layout = PacketLayout::echo(hmac_key.is_some(), params);
113 let len = echo_packet_len(hmac_key.is_some(), params)?;
114 let payload_offset = layout.header_len();
115 let available_payload_len = len.saturating_sub(payload_offset);
116 if reply.payload.len() > available_payload_len {
117 return Err(ProtoError::PayloadTooLarge {
118 available: available_payload_len,
119 provided: reply.payload.len(),
120 });
121 }
122
123 let mut out = envelope::begin_checked(
124 reply.flags,
125 hmac_key,
126 &[FlagRule::Reject(FLAG_OPEN), FlagRule::Require(FLAG_REPLY)],
127 len,
128 )?;
129 out.extend_from_slice(&reply.token.to_le_bytes());
130 out.extend_from_slice(&reply.sequence.to_le_bytes());
131 push_echo_reply_tail(reply, layout, &mut out)?;
132 out.resize(len, 0);
133 out[payload_offset..payload_offset + reply.payload.len()].copy_from_slice(&reply.payload);
134
135 envelope::finish(out, hmac_key)
136}
137
138pub fn decode_echo_reply(
155 packet: &[u8],
156 params: &Params,
157 hmac_key: Option<&[u8]>,
158) -> Result<EchoReply> {
159 let envelope = envelope::decode(
160 packet,
161 hmac_key,
162 &[FlagRule::Reject(FLAG_OPEN), FlagRule::Require(FLAG_REPLY)],
163 )?;
164 let layout = PacketLayout::echo(hmac_key.is_some(), params);
165 let midpoint_compat = validate_echo_length(packet, params, layout)?;
166 envelope::verify(packet, hmac_key)?;
167
168 let header_len = layout.header_len()
169 + if midpoint_compat {
170 MIDPOINT_COMPAT_EXTRA
171 } else {
172 0
173 };
174 let mut pos = envelope.body_offset;
175 let token = read_u64(packet, &mut pos);
176 let sequence = read_u32(packet, &mut pos);
177 let recv_count = layout.recv_count.then(|| read_u32(packet, &mut pos));
178 let recv_window = layout.recv_window.then(|| read_u64(packet, &mut pos));
179 let recv_wall = layout.recv_wall.then(|| read_i64(packet, &mut pos));
180 let recv_mono = layout.recv_mono.then(|| read_i64(packet, &mut pos));
181 let (midpoint_wall, midpoint_mono) = if midpoint_compat {
182 let wall = read_i64(packet, &mut pos);
183 let mono = read_i64(packet, &mut pos);
184 match params.clock {
185 Clock::Wall => (Some(wall), None),
186 Clock::Monotonic => (None, Some(mono)),
187 Clock::Unspecified | Clock::Both => {
188 unreachable!("midpoint compat only applies to single-clock negotiations")
189 }
190 }
191 } else {
192 (
193 layout.midpoint_wall.then(|| read_i64(packet, &mut pos)),
194 layout.midpoint_mono.then(|| read_i64(packet, &mut pos)),
195 )
196 };
197 let send_wall = layout.send_wall.then(|| read_i64(packet, &mut pos));
198 let send_mono = layout.send_mono.then(|| read_i64(packet, &mut pos));
199
200 Ok(EchoReply {
201 flags: envelope.flags,
202 token,
203 sequence,
204 recv_count,
205 recv_window,
206 timestamps: TimestampFields {
207 recv_wall,
208 recv_mono,
209 midpoint_wall,
210 midpoint_mono,
211 send_wall,
212 send_mono,
213 },
214 payload: packet[header_len..].to_vec(),
215 })
216}
217
218fn validate_echo_length(packet: &[u8], params: &Params, layout: PacketLayout) -> Result<bool> {
226 let header_len = layout.header_len();
227 if packet.len() < header_len {
228 return Err(ProtoError::PacketTooShort {
229 needed: header_len,
230 actual: packet.len(),
231 });
232 }
233 let expected_len = echo_packet_len(layout.hmac, params)?;
234 if packet.len() == expected_len {
235 return Ok(false);
236 }
237 if is_midpoint_single_clock(params) {
238 let compat_len = midpoint_compat_packet_len(layout, expected_len);
239 if compat_len > expected_len && packet.len() == compat_len {
240 return Ok(true);
241 }
242 }
243 Err(ProtoError::PacketLengthMismatch {
244 expected: expected_len,
245 actual: packet.len(),
246 })
247}
248
249fn push_echo_reply_tail(reply: &EchoReply, layout: PacketLayout, out: &mut Vec<u8>) -> Result<()> {
250 if let Some(value) = field_value(layout.recv_count, reply.recv_count, "recv_count")? {
251 out.extend_from_slice(&value.to_le_bytes());
252 }
253 if let Some(value) = field_value(layout.recv_window, reply.recv_window, "recv_window")? {
254 out.extend_from_slice(&value.to_le_bytes());
255 }
256
257 let timestamps = &reply.timestamps;
258 for (present, value, name) in [
259 (layout.recv_wall, timestamps.recv_wall, "recv_wall"),
260 (layout.recv_mono, timestamps.recv_mono, "recv_mono"),
261 (
262 layout.midpoint_wall,
263 timestamps.midpoint_wall,
264 "midpoint_wall",
265 ),
266 (
267 layout.midpoint_mono,
268 timestamps.midpoint_mono,
269 "midpoint_mono",
270 ),
271 (layout.send_wall, timestamps.send_wall, "send_wall"),
272 (layout.send_mono, timestamps.send_mono, "send_mono"),
273 ] {
274 if let Some(value) = field_value(present, value, name)? {
275 out.extend_from_slice(&value.to_le_bytes());
276 }
277 }
278 Ok(())
279}
280
281fn field_value<T: Copy>(present: bool, value: Option<T>, name: &'static str) -> Result<Option<T>> {
282 match (present, value) {
283 (true, Some(value)) => Ok(Some(value)),
284 (true, None) => Err(ProtoError::MissingField(name)),
285 (false, Some(_)) => Err(ProtoError::UnexpectedField(name)),
286 (false, None) => Ok(None),
287 }
288}
289
290fn read_u32(packet: &[u8], pos: &mut usize) -> u32 {
291 let value = u32::from_le_bytes(packet[*pos..*pos + SEQ_SIZE].try_into().unwrap());
292 *pos += SEQ_SIZE;
293 value
294}
295
296fn read_u64(packet: &[u8], pos: &mut usize) -> u64 {
297 let value = u64::from_le_bytes(packet[*pos..*pos + TOKEN_SIZE].try_into().unwrap());
298 *pos += TOKEN_SIZE;
299 value
300}
301
302fn read_i64(packet: &[u8], pos: &mut usize) -> i64 {
303 let value = i64::from_le_bytes(packet[*pos..*pos + TIMESTAMP_SIZE].try_into().unwrap());
304 *pos += TIMESTAMP_SIZE;
305 value
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311 use crate::{
312 hmac,
313 params::{Clock, ReceivedStats, StampAt},
314 write_header, FLAG_HMAC, HMAC_SIZE, RECV_COUNT_SIZE, RECV_WINDOW_SIZE,
315 };
316
317 fn default_params() -> Params {
318 Params {
319 received_stats: ReceivedStats::Both,
320 stamp_at: StampAt::Both,
321 clock: Clock::Both,
322 ..Params::default()
323 }
324 }
325
326 fn params_with_payload_space(payload_space: usize) -> Params {
327 let header_len = PacketLayout::echo(false, &Params::default()).header_len();
328 Params {
329 length: (header_len + payload_space) as i64,
330 ..Params::default()
331 }
332 }
333
334 fn push_zeroed_layout_tail(layout: PacketLayout, out: &mut Vec<u8>) {
337 if layout.recv_count {
338 out.extend_from_slice(&[0; RECV_COUNT_SIZE]);
339 }
340 if layout.recv_window {
341 out.extend_from_slice(&[0; RECV_WINDOW_SIZE]);
342 }
343 for _ in 0..layout.timestamp_count() {
344 out.extend_from_slice(&[0; TIMESTAMP_SIZE]);
345 }
346 }
347
348 #[test]
349 fn echo_reply_decodes_default_fields() {
350 let packet = [
351 0x14, 0xa7, 0x5b, 0x02, 0x13, 0x52, 0x71, 0x87, 0xab, 0xb6, 0x96, 0x78, 0x02, 0x00,
352 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
353 0xb8, 0x1a, 0x33, 0x0c, 0x86, 0x6d, 0xaa, 0x18, 0xde, 0x26, 0x35, 0x95, 0x00, 0x00,
354 0x00, 0x00, 0x80, 0x4d, 0x33, 0x0c, 0x86, 0x6d, 0xaa, 0x18, 0xb2, 0x57, 0x35, 0x95,
355 0x00, 0x00, 0x00, 0x00,
356 ];
357 let reply = decode_echo_reply(&packet, &default_params(), None).unwrap();
358 assert_eq!(reply.token, 0x7896_b6ab_8771_5213);
359 assert_eq!(reply.sequence, 2);
360 assert_eq!(reply.recv_count, Some(3));
361 assert_eq!(reply.recv_window, Some(7));
362 assert!(reply.timestamps.recv_wall.is_some());
363 assert!(reply.timestamps.midpoint_wall.is_none());
364 }
365
366 #[test]
367 fn echo_reply_decodes_exact_negotiated_length_with_payload() {
368 let params = params_with_payload_space(4);
369 let mut packet = Vec::new();
370 write_header(&mut packet, FLAG_REPLY);
371 packet.extend_from_slice(&0x7896_b6ab_8771_5213u64.to_le_bytes());
372 packet.extend_from_slice(&2_u32.to_le_bytes());
373 packet.extend_from_slice(&[1, 2, 3, 4]);
374
375 let reply = decode_echo_reply(&packet, ¶ms, None).unwrap();
376 assert_eq!(reply.payload, vec![1, 2, 3, 4]);
377 }
378
379 #[test]
380 fn echo_reply_rejects_shorter_than_negotiated_length() {
381 let params = params_with_payload_space(4);
382 let mut packet = Vec::new();
383 write_header(&mut packet, FLAG_REPLY);
384 packet.extend_from_slice(&0x7896_b6ab_8771_5213u64.to_le_bytes());
385 packet.extend_from_slice(&2_u32.to_le_bytes());
386
387 assert_eq!(
388 decode_echo_reply(&packet, ¶ms, None),
389 Err(ProtoError::PacketLengthMismatch {
390 expected: 20,
391 actual: 16,
392 })
393 );
394 }
395
396 #[test]
397 fn echo_reply_rejects_longer_than_negotiated_length() {
398 let params = Params::default();
399 let mut packet = Vec::new();
400 write_header(&mut packet, FLAG_REPLY);
401 packet.extend_from_slice(&0x7896_b6ab_8771_5213u64.to_le_bytes());
402 packet.extend_from_slice(&2_u32.to_le_bytes());
403 packet.push(0);
404
405 assert_eq!(
406 decode_echo_reply(&packet, ¶ms, None),
407 Err(ProtoError::PacketLengthMismatch {
408 expected: 16,
409 actual: 17,
410 })
411 );
412 }
413
414 #[test]
415 fn hmac_echo_reply_decodes_default_fields_after_hmac() {
416 let params = default_params();
417 let layout = PacketLayout::echo(true, ¶ms);
418 let mut packet = Vec::with_capacity(layout.header_len());
419 write_header(&mut packet, FLAG_REPLY | FLAG_HMAC);
420 packet.extend_from_slice(&[0; HMAC_SIZE]);
421 packet.extend_from_slice(&0x7896_b6ab_8771_5213u64.to_le_bytes());
422 packet.extend_from_slice(&2_u32.to_le_bytes());
423 push_zeroed_layout_tail(layout, &mut packet);
424 hmac::compute_hmac_in_place(b"testkey", &mut packet, hmac::hmac_offset()).unwrap();
425
426 assert_eq!(packet.len(), 76);
427 let reply = decode_echo_reply(&packet, ¶ms, Some(b"testkey")).unwrap();
428 assert_eq!(reply.token, 0x7896_b6ab_8771_5213);
429 assert_eq!(reply.sequence, 2);
430 assert_eq!(reply.recv_count, Some(0));
431 assert_eq!(reply.recv_window, Some(0));
432 assert_eq!(reply.payload.len(), 0);
433 }
434
435 #[test]
436 fn hmac_echo_reply_decodes_exact_negotiated_length_with_payload() {
437 let mut params = Params {
438 length: 48,
439 ..Params::default()
440 };
441 params.received_stats = ReceivedStats::Both;
442 let layout = PacketLayout::echo(true, ¶ms);
443 let mut packet = Vec::with_capacity(echo_packet_len(true, ¶ms).unwrap());
444 write_header(&mut packet, FLAG_REPLY | FLAG_HMAC);
445 packet.extend_from_slice(&[0; HMAC_SIZE]);
446 packet.extend_from_slice(&0x7896_b6ab_8771_5213u64.to_le_bytes());
447 packet.extend_from_slice(&2_u32.to_le_bytes());
448 push_zeroed_layout_tail(layout, &mut packet);
449 packet.extend_from_slice(&[1, 2, 3, 4]);
450 hmac::compute_hmac_in_place(b"testkey", &mut packet, hmac::hmac_offset()).unwrap();
451
452 let reply = decode_echo_reply(&packet, ¶ms, Some(b"testkey")).unwrap();
453 assert_eq!(reply.payload, vec![1, 2, 3, 4]);
454 }
455
456 #[test]
457 fn hmac_echo_reply_rejects_length_mismatch_before_hmac_verification() {
458 let params = Params::default();
459 let mut packet = Vec::new();
460 write_header(&mut packet, FLAG_REPLY | FLAG_HMAC);
461 packet.extend_from_slice(&[0; HMAC_SIZE]);
462 packet.extend_from_slice(&0x7896_b6ab_8771_5213u64.to_le_bytes());
463 packet.extend_from_slice(&2_u32.to_le_bytes());
464 hmac::compute_hmac_in_place(b"testkey", &mut packet, hmac::hmac_offset()).unwrap();
465 packet.push(0);
466
467 assert_eq!(
468 decode_echo_reply(&packet, ¶ms, Some(b"testkey")),
469 Err(ProtoError::PacketLengthMismatch {
470 expected: 32,
471 actual: 33,
472 })
473 );
474 }
475}