feat(proto): add directional echo codecs

This commit is contained in:
Nils Andreas Svee committed 2026-08-04 00:34:12 +07:00
1 parent cc7f7fc9b2
commit cabe0f6c24
6 files changed
+447 -50

No files matched your search

+1 -2
View File
@@ -218,10 +218,9 @@ impl SessionMachine {
let request = EchoRequest { let request = EchoRequest {
token: session.token, token: session.token,
sequence: wire_seq, sequence: wire_seq,
params: session.negotiated.params.clone(),
payload: vec![], payload: vec![],
}; };
let packet = encode_echo_request(&request, hmac_key)?; let packet = encode_echo_request(&request, &session.negotiated.params, hmac_key)?;
let send_result = send(&packet)?; let send_result = send(&packet)?;
let pending = PendingProbe { let pending = PendingProbe {
+130 -19
View File
@@ -10,7 +10,6 @@ use crate::{
pub struct EchoRequest { pub struct EchoRequest {
pub token: u64, pub token: u64,
pub sequence: u32, pub sequence: u32,
pub params: Params,
pub payload: Vec<u8>, pub payload: Vec<u8>,
} }
@@ -35,9 +34,13 @@ pub struct TimestampFields {
pub send_mono: Option<i64>, pub send_mono: Option<i64>,
} }
pub fn encode_echo_request(request: &EchoRequest, hmac_key: Option<&[u8]>) -> Result<Vec<u8>> { pub fn encode_echo_request(
let layout = PacketLayout::echo(hmac_key.is_some(), &request.params); request: &EchoRequest,
let len = echo_packet_len(hmac_key.is_some(), &request.params)?; params: &Params,
hmac_key: Option<&[u8]>,
) -> Result<Vec<u8>> {
let layout = PacketLayout::echo(hmac_key.is_some(), params);
let len = echo_packet_len(hmac_key.is_some(), params)?;
let payload_offset = layout.header_len(); let payload_offset = layout.header_len();
let available_payload_len = len.saturating_sub(payload_offset); let available_payload_len = len.saturating_sub(payload_offset);
if request.payload.len() > available_payload_len { if request.payload.len() > available_payload_len {
@@ -65,6 +68,66 @@ pub fn encode_echo_request(request: &EchoRequest, hmac_key: Option<&[u8]>) -> Re
envelope::finish(out, hmac_key) envelope::finish(out, hmac_key)
} }
pub fn decode_echo_request(
packet: &[u8],
params: &Params,
hmac_key: Option<&[u8]>,
) -> Result<EchoRequest> {
let envelope = envelope::decode(
packet,
hmac_key,
&[
FlagRule::Reject(FLAG_OPEN),
FlagRule::Reject(FLAG_REPLY),
FlagRule::Reject(FLAG_CLOSE),
],
)?;
let layout = PacketLayout::echo(hmac_key.is_some(), params);
validate_echo_length(packet, params, layout)?;
envelope::verify(packet, hmac_key)?;
let mut pos = envelope.body_offset;
let token = read_u64(packet, &mut pos);
let sequence = read_u32(packet, &mut pos);
Ok(EchoRequest {
token,
sequence,
payload: packet[layout.header_len()..].to_vec(),
})
}
pub fn encode_echo_reply(
reply: &EchoReply,
params: &Params,
hmac_key: Option<&[u8]>,
) -> Result<Vec<u8>> {
let layout = PacketLayout::echo(hmac_key.is_some(), params);
let len = echo_packet_len(hmac_key.is_some(), params)?;
let payload_offset = layout.header_len();
let available_payload_len = len.saturating_sub(payload_offset);
if reply.payload.len() > available_payload_len {
return Err(ProtoError::PayloadTooLarge {
available: available_payload_len,
provided: reply.payload.len(),
});
}
let mut out = envelope::begin(
reply.flags,
hmac_key,
&[FlagRule::Reject(FLAG_OPEN), FlagRule::Require(FLAG_REPLY)],
len,
)?;
out.extend_from_slice(&reply.token.to_le_bytes());
out.extend_from_slice(&reply.sequence.to_le_bytes());
push_echo_reply_tail(reply, layout, &mut out)?;
out.resize(len, 0);
out[payload_offset..payload_offset + reply.payload.len()].copy_from_slice(&reply.payload);
envelope::finish(out, hmac_key)
}
pub fn decode_echo_reply( pub fn decode_echo_reply(
packet: &[u8], packet: &[u8],
params: &Params, params: &Params,
@@ -135,6 +198,47 @@ fn push_zeroed_layout_tail(layout: PacketLayout, out: &mut Vec<u8>) {
} }
} }
fn push_echo_reply_tail(reply: &EchoReply, layout: PacketLayout, out: &mut Vec<u8>) -> Result<()> {
if let Some(value) = field_value(layout.recv_count, reply.recv_count, "recv_count")? {
out.extend_from_slice(&value.to_le_bytes());
}
if let Some(value) = field_value(layout.recv_window, reply.recv_window, "recv_window")? {
out.extend_from_slice(&value.to_le_bytes());
}
let timestamps = &reply.timestamps;
for (present, value, name) in [
(layout.recv_wall, timestamps.recv_wall, "recv_wall"),
(layout.recv_mono, timestamps.recv_mono, "recv_mono"),
(
layout.midpoint_wall,
timestamps.midpoint_wall,
"midpoint_wall",
),
(
layout.midpoint_mono,
timestamps.midpoint_mono,
"midpoint_mono",
),
(layout.send_wall, timestamps.send_wall, "send_wall"),
(layout.send_mono, timestamps.send_mono, "send_mono"),
] {
if let Some(value) = field_value(present, value, name)? {
out.extend_from_slice(&value.to_le_bytes());
}
}
Ok(())
}
fn field_value<T: Copy>(present: bool, value: Option<T>, name: &'static str) -> Result<Option<T>> {
match (present, value) {
(true, Some(value)) => Ok(Some(value)),
(true, None) => Err(ProtoError::MissingField(name)),
(false, Some(_)) => Err(ProtoError::UnexpectedField(name)),
(false, None) => Ok(None),
}
}
fn read_u32(packet: &[u8], pos: &mut usize) -> u32 { fn read_u32(packet: &[u8], pos: &mut usize) -> u32 {
let value = u32::from_le_bytes(packet[*pos..*pos + SEQ_SIZE].try_into().unwrap()); let value = u32::from_le_bytes(packet[*pos..*pos + SEQ_SIZE].try_into().unwrap());
*pos += SEQ_SIZE; *pos += SEQ_SIZE;
@@ -179,24 +283,24 @@ mod tests {
} }
} }
fn echo_request_with_payload(payload_space: usize, payload: Vec<u8>) -> EchoRequest { fn echo_request_with_payload(payload: Vec<u8>) -> EchoRequest {
EchoRequest { EchoRequest {
token: 0x7896_b6ab_8771_5213, token: 0x7896_b6ab_8771_5213,
sequence: 9, sequence: 9,
params: params_with_payload_space(payload_space),
payload, payload,
} }
} }
#[test] #[test]
fn echo_request_encodes_default_placeholders() { fn echo_request_encodes_default_placeholders() {
let params = default_params();
let packet = encode_echo_request( let packet = encode_echo_request(
&EchoRequest { &EchoRequest {
token: 0x7896_b6ab_8771_5213, token: 0x7896_b6ab_8771_5213,
sequence: 0, sequence: 0,
params: default_params(),
payload: Vec::new(), payload: Vec::new(),
}, },
&params,
None, None,
) )
.unwrap(); .unwrap();
@@ -208,29 +312,32 @@ mod tests {
#[test] #[test]
fn echo_request_encodes_exact_fit_payload() { fn echo_request_encodes_exact_fit_payload() {
let request = echo_request_with_payload(4, vec![1, 2, 3, 4]); let params = params_with_payload_space(4);
let packet = encode_echo_request(&request, None).unwrap(); let request = echo_request_with_payload(vec![1, 2, 3, 4]);
let payload_offset = PacketLayout::echo(false, &request.params).header_len(); let packet = encode_echo_request(&request, &params, None).unwrap();
let payload_offset = PacketLayout::echo(false, &params).header_len();
assert_eq!(&packet[payload_offset..], &[1, 2, 3, 4]); assert_eq!(&packet[payload_offset..], &[1, 2, 3, 4]);
} }
#[test] #[test]
fn echo_request_encodes_shorter_payload_and_zero_fills_remainder() { fn echo_request_encodes_shorter_payload_and_zero_fills_remainder() {
let request = echo_request_with_payload(4, vec![1, 2]); let params = params_with_payload_space(4);
let packet = encode_echo_request(&request, None).unwrap(); let request = echo_request_with_payload(vec![1, 2]);
let payload_offset = PacketLayout::echo(false, &request.params).header_len(); let packet = encode_echo_request(&request, &params, None).unwrap();
let payload_offset = PacketLayout::echo(false, &params).header_len();
assert_eq!(&packet[payload_offset..], &[1, 2, 0, 0]); assert_eq!(&packet[payload_offset..], &[1, 2, 0, 0]);
} }
#[test] #[test]
fn echo_request_rejects_oversized_payload() { fn echo_request_rejects_oversized_payload() {
let request = echo_request_with_payload(4, vec![1, 2, 3, 4, 5]); let params = params_with_payload_space(4);
let request = echo_request_with_payload(vec![1, 2, 3, 4, 5]);
let original = request.clone(); let original = request.clone();
assert_eq!( assert_eq!(
encode_echo_request(&request, None), encode_echo_request(&request, &params, None),
Err(ProtoError::PayloadTooLarge { Err(ProtoError::PayloadTooLarge {
available: 4, available: 4,
provided: 5, provided: 5,
@@ -241,24 +348,28 @@ mod tests {
#[test] #[test]
fn echo_request_rejects_negative_requested_length() { fn echo_request_rejects_negative_requested_length() {
let mut request = echo_request_with_payload(0, Vec::new()); let request = echo_request_with_payload(Vec::new());
request.params.length = -1; let params = Params {
length: -1,
..Params::default()
};
assert_eq!( assert_eq!(
encode_echo_request(&request, None), encode_echo_request(&request, &params, None),
Err(ProtoError::NegativePacketLength { length: -1 }) Err(ProtoError::NegativePacketLength { length: -1 })
); );
} }
#[test] #[test]
fn hmac_echo_request_places_token_and_sequence_after_hmac() { fn hmac_echo_request_places_token_and_sequence_after_hmac() {
let params = default_params();
let packet = encode_echo_request( let packet = encode_echo_request(
&EchoRequest { &EchoRequest {
token: 0x7896_b6ab_8771_5213, token: 0x7896_b6ab_8771_5213,
sequence: 9, sequence: 9,
params: default_params(),
payload: Vec::new(), payload: Vec::new(),
}, },
&params,
Some(b"testkey"), Some(b"testkey"),
) )
.unwrap(); .unwrap();
+4
View File
@@ -40,4 +40,8 @@ pub enum ProtoError {
MalformedParams, MalformedParams,
#[error("payload is too large: {provided} bytes provided, {available} bytes available")] #[error("payload is too large: {provided} bytes provided, {available} bytes available")]
PayloadTooLarge { available: usize, provided: usize }, PayloadTooLarge { available: usize, provided: usize },
#[error("required packet field {0} is missing")]
MissingField(&'static str),
#[error("packet field {0} is not present in the negotiated layout")]
UnexpectedField(&'static str),
} }
+4 -1
View File
@@ -32,7 +32,10 @@ pub mod params;
pub mod varint; pub mod varint;
pub use close::{decode_close_request, encode_close_request, CloseRequest}; pub use close::{decode_close_request, encode_close_request, CloseRequest};
pub use echo::{decode_echo_reply, encode_echo_request, EchoReply, EchoRequest, TimestampFields}; pub use echo::{
decode_echo_reply, decode_echo_request, encode_echo_reply, encode_echo_request, EchoReply,
EchoRequest, TimestampFields,
};
pub use error::{ProtoError, Result}; pub use error::{ProtoError, Result};
pub use flags::*; pub use flags::*;
pub use hmac::{compute_hmac, compute_hmac_in_place, verify_hmac}; pub use hmac::{compute_hmac, compute_hmac_in_place, verify_hmac};
+240 -14
View File
@@ -1,30 +1,80 @@
use irtt_proto::{ use irtt_proto::{
decode_close_request, decode_open_reply, decode_open_request, encode_close_request, decode_close_request, decode_echo_reply, decode_echo_request, decode_open_reply,
encode_open_reply, encode_open_request, CloseRequest, OpenReply, OpenRequest, Params, decode_open_request, encode_close_request, encode_echo_reply, encode_echo_request,
ProtoError, FLAG_CLOSE, FLAG_HMAC, FLAG_OPEN, FLAG_REPLY, MAGIC, encode_open_reply, encode_open_request, Clock, CloseRequest, EchoReply, EchoRequest, OpenReply,
OpenRequest, PacketLayout, Params, ProtoError, ReceivedStats, StampAt, TimestampFields,
FLAG_CLOSE, FLAG_HMAC, FLAG_OPEN, FLAG_REPLY, MAGIC,
}; };
const KEY: &[u8] = b"testkey"; const KEY: &[u8] = b"testkey";
const TOKEN: u64 = 0x7896_b6ab_8771_5213; const TOKEN: u64 = 0x7896_b6ab_8771_5213;
fn params() -> Params {
Params {
protocol_version: 1,
duration_ns: 3_000_000_000,
interval_ns: 1_000_000_000,
received_stats: ReceivedStats::Both,
stamp_at: StampAt::Both,
clock: Clock::Both,
..Params::default()
}
}
fn echo_request(payload: Vec<u8>) -> EchoRequest {
EchoRequest {
token: TOKEN,
sequence: 17,
payload,
}
}
fn echo_reply(params: &Params, flags: u8) -> EchoReply {
let layout = PacketLayout::echo(false, params);
EchoReply {
flags,
token: TOKEN,
sequence: u32::MAX,
recv_count: layout.recv_count.then_some(u32::MAX),
recv_window: layout.recv_window.then_some(u64::MAX),
timestamps: TimestampFields {
recv_wall: layout.recv_wall.then_some(i64::MIN),
recv_mono: layout.recv_mono.then_some(-2),
midpoint_wall: layout.midpoint_wall.then_some(-1),
midpoint_mono: layout.midpoint_mono.then_some(0),
send_wall: layout.send_wall.then_some(1),
send_mono: layout.send_mono.then_some(i64::MAX),
},
payload: Vec::new(),
}
}
#[test] #[test]
fn authenticated_open_reply_encoder_sets_hmac_from_key() { fn authenticated_reply_encoders_set_hmac_from_key() {
let reply = OpenReply { let params = params();
let open_reply = OpenReply {
flags: FLAG_OPEN | FLAG_REPLY, flags: FLAG_OPEN | FLAG_REPLY,
token: TOKEN, token: TOKEN,
params: Params { params: params.clone(),
protocol_version: 1,
..Params::default()
},
}; };
let packet = encode_open_reply(&reply, Some(KEY)).unwrap(); let packet = encode_open_reply(&open_reply, Some(KEY)).unwrap();
assert_eq!(packet[3], FLAG_OPEN | FLAG_REPLY | FLAG_HMAC); assert_eq!(packet[3], FLAG_OPEN | FLAG_REPLY | FLAG_HMAC);
assert_eq!( assert_eq!(
decode_open_reply(&packet, Some(KEY)), decode_open_reply(&packet, Some(KEY)),
Ok(OpenReply { Ok(OpenReply {
flags: FLAG_OPEN | FLAG_REPLY | FLAG_HMAC, flags: FLAG_OPEN | FLAG_REPLY | FLAG_HMAC,
..reply ..open_reply
})
);
let echo_reply = echo_reply(&params, FLAG_REPLY);
let packet = encode_echo_reply(&echo_reply, &params, Some(KEY)).unwrap();
assert_eq!(packet[3], FLAG_REPLY | FLAG_HMAC);
assert_eq!(
decode_echo_reply(&packet, &params, Some(KEY)),
Ok(EchoReply {
flags: FLAG_REPLY | FLAG_HMAC,
..echo_reply
}) })
); );
} }
@@ -55,7 +105,17 @@ fn rejected_open_reply_round_trips_and_zero_token_requires_close() {
} }
#[test] #[test]
fn open_and_close_codecs_enforce_directional_flags() { fn peer_close_echo_reply_round_trips_without_a_close_reply_codec() {
let params = Params::default();
let reply = echo_reply(&params, FLAG_REPLY | FLAG_CLOSE);
let packet = encode_echo_reply(&reply, &params, None).unwrap();
assert_eq!(packet[3], FLAG_REPLY | FLAG_CLOSE);
assert_eq!(decode_echo_reply(&packet, &params, None), Ok(reply));
}
#[test]
fn inverse_codecs_supply_their_packet_type_rules() {
let params = Params::default(); let params = Params::default();
let open_reply_packet = encode_open_reply( let open_reply_packet = encode_open_reply(
&OpenReply { &OpenReply {
@@ -82,6 +142,30 @@ fn open_and_close_codecs_enforce_directional_flags() {
Err(ProtoError::MissingFlag(FLAG_OPEN)) Err(ProtoError::MissingFlag(FLAG_OPEN))
); );
let echo_reply_packet =
encode_echo_reply(&echo_reply(&params, FLAG_REPLY), &params, None).unwrap();
assert_eq!(
decode_echo_request(&echo_reply_packet, &params, None),
Err(ProtoError::UnexpectedFlag(FLAG_REPLY))
);
assert_eq!(
encode_echo_reply(
&EchoReply {
flags: 0,
..echo_reply(&params, FLAG_REPLY)
},
&params,
None,
),
Err(ProtoError::MissingFlag(FLAG_REPLY))
);
let close_packet = encode_close_request(&CloseRequest { token: TOKEN }, None).unwrap();
assert_eq!(
decode_echo_request(&close_packet, &params, None),
Err(ProtoError::UnexpectedFlag(FLAG_CLOSE))
);
let no_test_packet = encode_open_request( let no_test_packet = encode_open_request(
&OpenRequest { &OpenRequest {
params, params,
@@ -97,7 +181,7 @@ fn open_and_close_codecs_enforce_directional_flags() {
} }
#[test] #[test]
fn open_and_close_malformed_bodies_and_exact_lengths_are_rejected() { fn codec_specific_malformed_bodies_and_exact_lengths_are_rejected() {
let mut malformed_open_request = MAGIC.to_vec(); let mut malformed_open_request = MAGIC.to_vec();
malformed_open_request.push(FLAG_OPEN); malformed_open_request.push(FLAG_OPEN);
malformed_open_request.extend_from_slice(&[1, 0x80]); malformed_open_request.extend_from_slice(&[1, 0x80]);
@@ -115,6 +199,28 @@ fn open_and_close_malformed_bodies_and_exact_lengths_are_rejected() {
Err(ProtoError::TruncatedVarint) Err(ProtoError::TruncatedVarint)
); );
let params = Params {
length: 20,
..Params::default()
};
let echo_packet = encode_echo_request(&echo_request(Vec::new()), &params, None).unwrap();
assert_eq!(
decode_echo_request(&echo_packet[..19], &params, None),
Err(ProtoError::PacketLengthMismatch {
expected: 20,
actual: 19,
})
);
let mut long_echo_packet = echo_packet;
long_echo_packet.push(0);
assert_eq!(
decode_echo_request(&long_echo_packet, &params, None),
Err(ProtoError::PacketLengthMismatch {
expected: 20,
actual: 21,
})
);
let close_packet = encode_close_request(&CloseRequest { token: TOKEN }, None).unwrap(); let close_packet = encode_close_request(&CloseRequest { token: TOKEN }, None).unwrap();
assert_eq!( assert_eq!(
decode_close_request(&close_packet[..11], None), decode_close_request(&close_packet[..11], None),
@@ -133,3 +239,123 @@ fn open_and_close_malformed_bodies_and_exact_lengths_are_rejected() {
}) })
); );
} }
#[test]
fn echo_layout_combinations_round_trip() {
for received_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 key in [None, Some(KEY)] {
let params = Params {
protocol_version: 1,
received_stats,
stamp_at,
clock,
..Params::default()
};
let reply = echo_reply(&params, FLAG_REPLY);
let packet = encode_echo_reply(&reply, &params, key).unwrap();
let decoded = decode_echo_reply(&packet, &params, key).unwrap();
assert_eq!(decoded.token, reply.token);
assert_eq!(decoded.sequence, reply.sequence);
assert_eq!(decoded.recv_count, reply.recv_count);
assert_eq!(decoded.recv_window, reply.recv_window);
assert_eq!(decoded.timestamps, reply.timestamps);
assert_eq!(decoded.payload, reply.payload);
}
}
}
}
}
#[test]
fn echo_reply_requires_exact_negotiated_optional_fields() {
let params = Params {
received_stats: ReceivedStats::Count,
stamp_at: StampAt::Send,
clock: Clock::Wall,
..Params::default()
};
let missing_count = EchoReply {
recv_count: None,
..echo_reply(&params, FLAG_REPLY)
};
assert_eq!(
encode_echo_reply(&missing_count, &params, None),
Err(ProtoError::MissingField("recv_count"))
);
let no_optional_params = Params::default();
let unexpected_timestamp = EchoReply {
timestamps: TimestampFields {
send_wall: Some(1),
..TimestampFields::default()
},
..echo_reply(&no_optional_params, FLAG_REPLY)
};
assert_eq!(
encode_echo_reply(&unexpected_timestamp, &no_optional_params, None),
Err(ProtoError::UnexpectedField("send_wall"))
);
}
#[test]
fn echo_payload_is_copied_zero_filled_and_bounded_in_both_directions() {
let params = Params {
length: 20,
..Params::default()
};
let request = echo_request(vec![1, 2]);
let packet = encode_echo_request(&request, &params, None).unwrap();
assert_eq!(&packet[16..], &[1, 2, 0, 0]);
let decoded = decode_echo_request(&packet, &params, None).unwrap();
assert_eq!(decoded.payload, vec![1, 2, 0, 0]);
assert_eq!(
encode_echo_request(&decoded, &params, None).unwrap(),
packet
);
assert_eq!(
encode_echo_request(&echo_request(vec![0; 5]), &params, None),
Err(ProtoError::PayloadTooLarge {
available: 4,
provided: 5,
})
);
let reply = EchoReply {
payload: vec![3, 4],
..echo_reply(&params, FLAG_REPLY)
};
let packet = encode_echo_reply(&reply, &params, None).unwrap();
assert_eq!(&packet[16..], &[3, 4, 0, 0]);
let decoded = decode_echo_reply(&packet, &params, None).unwrap();
assert_eq!(decoded.payload, vec![3, 4, 0, 0]);
assert_eq!(encode_echo_reply(&decoded, &params, None).unwrap(), packet);
assert_eq!(
encode_echo_reply(
&EchoReply {
payload: vec![0; 5],
..echo_reply(&params, FLAG_REPLY)
},
&params,
None,
),
Err(ProtoError::PayloadTooLarge {
available: 4,
provided: 5,
})
);
}
+68 -14
View File
@@ -1,7 +1,8 @@
use irtt_proto::{ use irtt_proto::{
decode_close_request, decode_echo_reply, decode_open_reply, decode_open_request, decode_close_request, decode_echo_reply, decode_echo_request, decode_open_reply,
encode_close_request, encode_echo_request, encode_open_reply, encode_open_request, verify_hmac, decode_open_request, encode_close_request, encode_echo_reply, encode_echo_request,
Clock, CloseRequest, EchoRequest, OpenRequest, Params, ReceivedStats, StampAt, encode_open_reply, encode_open_request, verify_hmac, Clock, CloseRequest, EchoRequest,
OpenRequest, Params, ProtoError, ReceivedStats, StampAt, FLAG_REPLY,
}; };
fn hex(input: &str) -> Vec<u8> { fn hex(input: &str) -> Vec<u8> {
@@ -79,13 +80,21 @@ fn vector_3_echo_request_no_hmac() {
&EchoRequest { &EchoRequest {
token: 0x7896_b6ab_8771_5213, token: 0x7896_b6ab_8771_5213,
sequence: 0, sequence: 0,
params: default_params_3s(),
payload: Vec::new(), payload: Vec::new(),
}, },
&default_params_3s(),
None, None,
) )
.unwrap(); .unwrap();
assert_eq!(packet, expected); assert_eq!(packet, expected);
assert_eq!(
decode_echo_request(&expected, &default_params_3s(), None),
Ok(EchoRequest {
token: 0x7896_b6ab_8771_5213,
sequence: 0,
payload: Vec::new(),
})
);
} }
#[test] #[test]
@@ -98,6 +107,10 @@ fn vector_4_echo_reply_no_hmac() {
assert_eq!(reply.sequence, 2); assert_eq!(reply.sequence, 2);
assert_eq!(reply.recv_count, Some(3)); assert_eq!(reply.recv_count, Some(3));
assert_eq!(reply.recv_window, Some(7)); assert_eq!(reply.recv_window, Some(7));
assert_eq!(
encode_echo_reply(&reply, &default_params_3s(), None).unwrap(),
packet
);
} }
#[test] #[test]
@@ -154,13 +167,21 @@ fn vector_7_hmac_echo_request() {
&EchoRequest { &EchoRequest {
token: 0x4387_e9eb_5d3f_ca59, token: 0x4387_e9eb_5d3f_ca59,
sequence: 0, sequence: 0,
params: default_params_3s(),
payload: Vec::new(), payload: Vec::new(),
}, },
&default_params_3s(),
Some(b"testkey"), Some(b"testkey"),
) )
.unwrap(); .unwrap();
assert_eq!(packet, expected); assert_eq!(packet, expected);
assert_eq!(
decode_echo_request(&expected, &default_params_3s(), Some(b"testkey")),
Ok(EchoRequest {
token: 0x4387_e9eb_5d3f_ca59,
sequence: 0,
payload: Vec::new(),
})
);
} }
#[test] #[test]
@@ -223,13 +244,21 @@ fn vector_11_minimal_echo_packet() {
&EchoRequest { &EchoRequest {
token: 0xa031_6fa2_5c61_154e, token: 0xa031_6fa2_5c61_154e,
sequence: 0, sequence: 0,
params: Params::default(),
payload: Vec::new(), payload: Vec::new(),
}, },
&Params::default(),
None, None,
) )
.unwrap(); .unwrap();
assert_eq!(packet, expected); assert_eq!(packet, expected);
assert_eq!(
decode_echo_request(&expected, &Params::default(), None),
Ok(EchoRequest {
token: 0xa031_6fa2_5c61_154e,
sequence: 0,
payload: Vec::new(),
})
);
} }
#[test] #[test]
@@ -249,6 +278,7 @@ fn vector_12_midpoint_reply() {
assert!(reply.timestamps.midpoint_wall.is_some()); assert!(reply.timestamps.midpoint_wall.is_some());
assert!(reply.timestamps.recv_wall.is_none()); assert!(reply.timestamps.recv_wall.is_none());
assert!(reply.timestamps.send_wall.is_none()); assert!(reply.timestamps.send_wall.is_none());
assert_eq!(encode_echo_reply(&reply, &params, None).unwrap(), packet);
} }
#[test] #[test]
@@ -270,27 +300,51 @@ fn spec_18_1_hmac_echo_request_fixture() {
&EchoRequest { &EchoRequest {
token: 0x886b_c9a7_22b3_3eea, token: 0x886b_c9a7_22b3_3eea,
sequence: 0x6fe2_a1bb, sequence: 0x6fe2_a1bb,
params,
payload: [0xff, 0xfe, 0xfd, 0xfc].repeat(4), payload: [0xff, 0xfe, 0xfd, 0xfc].repeat(4),
}, },
&params,
Some(&[0x3c, 0x68, 0x1d, 0x39, 0x41, 0x1d, 0x72, 0x43]), Some(&[0x3c, 0x68, 0x1d, 0x39, 0x41, 0x1d, 0x72, 0x43]),
) )
.unwrap(); .unwrap();
assert_eq!(packet, expected); assert_eq!(packet, expected);
assert_eq!(
decode_echo_request(
&expected,
&Params {
length: 92,
received_stats: ReceivedStats::Both,
stamp_at: StampAt::Both,
clock: Clock::Both,
..Params::default()
},
Some(&[0x3c, 0x68, 0x1d, 0x39, 0x41, 0x1d, 0x72, 0x43]),
)
.unwrap()
.payload,
[0xff, 0xfe, 0xfd, 0xfc].repeat(4)
);
} }
#[test] #[test]
fn spec_18_2_hmac_echo_reply_fixture() { fn spec_18_2_ambiguous_hmac_fixture_omits_reply_flag() {
let packet = hex("14 a7 5b 08 d2 98 a3 4a 6a 13 41 02 68 b2 67 a8 let packet = hex("14 a7 5b 08 d2 98 a3 4a 6a 13 41 02 68 b2 67 a8
d6 7e 28 25 c3 cb 6f 76 b6 ce 66 e6 06 07 3b 1d d6 7e 28 25 c3 cb 6f 76 b6 ce 66 e6 06 07 3b 1d
19 9f bc a3 9b 3b f8 86 e5 39 e9 d7 a1 75 2a ee 19 9f bc a3 9b 3b f8 86 e5 39 e9 d7 a1 75 2a ee
3d f1 5a 52 9a c6 4a 3e 22 62 55 2d 69 3f 29 46 3d f1 5a 52 9a c6 4a 3e 22 62 55 2d 69 3f 29 46
d4 05 97 58 66 50 2c dd 2f f1 1d 46 ff fe fd fc d4 05 97 58 66 50 2c dd 2f f1 1d 46 ff fe fd fc
ff fe fd fc ff fe fd fc ff fe fd fc"); ff fe fd fc ff fe fd fc ff fe fd fc");
verify_hmac( let key = [0xda, 0xb3, 0xe9, 0x04, 0xa6, 0x87, 0x92, 0x49];
&[0xda, 0xb3, 0xe9, 0x04, 0xa6, 0x87, 0x92, 0x49], verify_hmac(&key, &packet, 4).unwrap();
&packet,
4, let params = Params {
) length: 92,
.unwrap(); received_stats: ReceivedStats::Both,
stamp_at: StampAt::Both,
clock: Clock::Both,
..Params::default()
};
assert_eq!(
decode_echo_reply(&packet, &params, Some(&key)),
Err(ProtoError::MissingFlag(FLAG_REPLY))
);
} }