362 lines
11 KiB
Rust
362 lines
11 KiB
Rust
use irtt_proto::{
|
|
decode_close_request, decode_echo_reply, decode_echo_request, decode_open_reply,
|
|
decode_open_request, encode_close_request, encode_echo_reply, encode_echo_request,
|
|
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 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]
|
|
fn authenticated_reply_encoders_set_hmac_from_key() {
|
|
let params = params();
|
|
let open_reply = OpenReply {
|
|
flags: FLAG_OPEN | FLAG_REPLY,
|
|
token: TOKEN,
|
|
params: params.clone(),
|
|
};
|
|
let packet = encode_open_reply(&open_reply, Some(KEY)).unwrap();
|
|
assert_eq!(packet[3], FLAG_OPEN | FLAG_REPLY | FLAG_HMAC);
|
|
assert_eq!(
|
|
decode_open_reply(&packet, Some(KEY)),
|
|
Ok(OpenReply {
|
|
flags: FLAG_OPEN | FLAG_REPLY | FLAG_HMAC,
|
|
..open_reply
|
|
})
|
|
);
|
|
|
|
let echo_reply = echo_reply(¶ms, FLAG_REPLY);
|
|
let packet = encode_echo_reply(&echo_reply, ¶ms, Some(KEY)).unwrap();
|
|
assert_eq!(packet[3], FLAG_REPLY | FLAG_HMAC);
|
|
assert_eq!(
|
|
decode_echo_reply(&packet, ¶ms, Some(KEY)),
|
|
Ok(EchoReply {
|
|
flags: FLAG_REPLY | FLAG_HMAC,
|
|
..echo_reply
|
|
})
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn rejected_open_reply_round_trips_and_zero_token_requires_close() {
|
|
let rejected = OpenReply {
|
|
flags: FLAG_OPEN | FLAG_REPLY | FLAG_CLOSE,
|
|
token: 0,
|
|
params: Params {
|
|
protocol_version: 1,
|
|
..Params::default()
|
|
},
|
|
};
|
|
let packet = encode_open_reply(&rejected, None).unwrap();
|
|
assert_eq!(decode_open_reply(&packet, None), Ok(rejected.clone()));
|
|
|
|
assert_eq!(
|
|
encode_open_reply(
|
|
&OpenReply {
|
|
flags: FLAG_OPEN | FLAG_REPLY,
|
|
..rejected
|
|
},
|
|
None,
|
|
),
|
|
Err(ProtoError::ZeroToken)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn peer_close_echo_reply_round_trips_without_a_close_reply_codec() {
|
|
let params = Params::default();
|
|
let reply = echo_reply(¶ms, FLAG_REPLY | FLAG_CLOSE);
|
|
let packet = encode_echo_reply(&reply, ¶ms, None).unwrap();
|
|
|
|
assert_eq!(packet[3], FLAG_REPLY | FLAG_CLOSE);
|
|
assert_eq!(decode_echo_reply(&packet, ¶ms, None), Ok(reply));
|
|
}
|
|
|
|
#[test]
|
|
fn inverse_codecs_supply_their_packet_type_rules() {
|
|
let params = Params::default();
|
|
let open_reply_packet = encode_open_reply(
|
|
&OpenReply {
|
|
flags: FLAG_OPEN | FLAG_REPLY,
|
|
token: TOKEN,
|
|
params: params.clone(),
|
|
},
|
|
None,
|
|
)
|
|
.unwrap();
|
|
assert_eq!(
|
|
decode_open_request(&open_reply_packet, None),
|
|
Err(ProtoError::UnexpectedFlag(FLAG_REPLY))
|
|
);
|
|
assert_eq!(
|
|
encode_open_reply(
|
|
&OpenReply {
|
|
flags: FLAG_REPLY,
|
|
token: TOKEN,
|
|
params: params.clone(),
|
|
},
|
|
None,
|
|
),
|
|
Err(ProtoError::MissingFlag(FLAG_OPEN))
|
|
);
|
|
|
|
let echo_reply_packet =
|
|
encode_echo_reply(&echo_reply(¶ms, FLAG_REPLY), ¶ms, None).unwrap();
|
|
assert_eq!(
|
|
decode_echo_request(&echo_reply_packet, ¶ms, None),
|
|
Err(ProtoError::UnexpectedFlag(FLAG_REPLY))
|
|
);
|
|
assert_eq!(
|
|
encode_echo_reply(
|
|
&EchoReply {
|
|
flags: 0,
|
|
..echo_reply(¶ms, FLAG_REPLY)
|
|
},
|
|
¶ms,
|
|
None,
|
|
),
|
|
Err(ProtoError::MissingFlag(FLAG_REPLY))
|
|
);
|
|
|
|
let close_packet = encode_close_request(&CloseRequest { token: TOKEN }, None).unwrap();
|
|
assert_eq!(
|
|
decode_echo_request(&close_packet, ¶ms, None),
|
|
Err(ProtoError::UnexpectedFlag(FLAG_CLOSE))
|
|
);
|
|
|
|
let no_test_packet = encode_open_request(
|
|
&OpenRequest {
|
|
params,
|
|
close: true,
|
|
},
|
|
None,
|
|
)
|
|
.unwrap();
|
|
assert_eq!(
|
|
decode_close_request(&no_test_packet, None),
|
|
Err(ProtoError::UnexpectedFlag(FLAG_OPEN))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn codec_specific_malformed_bodies_and_exact_lengths_are_rejected() {
|
|
let mut malformed_open_request = MAGIC.to_vec();
|
|
malformed_open_request.push(FLAG_OPEN);
|
|
malformed_open_request.extend_from_slice(&[1, 0x80]);
|
|
assert_eq!(
|
|
decode_open_request(&malformed_open_request, None),
|
|
Err(ProtoError::TruncatedVarint)
|
|
);
|
|
|
|
let mut malformed_open_reply = MAGIC.to_vec();
|
|
malformed_open_reply.push(FLAG_OPEN | FLAG_REPLY);
|
|
malformed_open_reply.extend_from_slice(&TOKEN.to_le_bytes());
|
|
malformed_open_reply.extend_from_slice(&[1, 0x80]);
|
|
assert_eq!(
|
|
decode_open_reply(&malformed_open_reply, None),
|
|
Err(ProtoError::TruncatedVarint)
|
|
);
|
|
|
|
let params = Params {
|
|
length: 20,
|
|
..Params::default()
|
|
};
|
|
let echo_packet = encode_echo_request(&echo_request(Vec::new()), ¶ms, None).unwrap();
|
|
assert_eq!(
|
|
decode_echo_request(&echo_packet[..19], ¶ms, 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, ¶ms, None),
|
|
Err(ProtoError::PacketLengthMismatch {
|
|
expected: 20,
|
|
actual: 21,
|
|
})
|
|
);
|
|
|
|
let close_packet = encode_close_request(&CloseRequest { token: TOKEN }, None).unwrap();
|
|
assert_eq!(
|
|
decode_close_request(&close_packet[..11], None),
|
|
Err(ProtoError::PacketLengthMismatch {
|
|
expected: 12,
|
|
actual: 11,
|
|
})
|
|
);
|
|
let mut long_close_packet = close_packet;
|
|
long_close_packet.push(0);
|
|
assert_eq!(
|
|
decode_close_request(&long_close_packet, None),
|
|
Err(ProtoError::PacketLengthMismatch {
|
|
expected: 12,
|
|
actual: 13,
|
|
})
|
|
);
|
|
}
|
|
|
|
#[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(¶ms, FLAG_REPLY);
|
|
let packet = encode_echo_reply(&reply, ¶ms, key).unwrap();
|
|
let decoded = decode_echo_reply(&packet, ¶ms, 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(¶ms, FLAG_REPLY)
|
|
};
|
|
assert_eq!(
|
|
encode_echo_reply(&missing_count, ¶ms, 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, ¶ms, None).unwrap();
|
|
assert_eq!(&packet[16..], &[1, 2, 0, 0]);
|
|
let decoded = decode_echo_request(&packet, ¶ms, None).unwrap();
|
|
assert_eq!(decoded.payload, vec![1, 2, 0, 0]);
|
|
assert_eq!(
|
|
encode_echo_request(&decoded, ¶ms, None).unwrap(),
|
|
packet
|
|
);
|
|
assert_eq!(
|
|
encode_echo_request(&echo_request(vec![0; 5]), ¶ms, None),
|
|
Err(ProtoError::PayloadTooLarge {
|
|
available: 4,
|
|
provided: 5,
|
|
})
|
|
);
|
|
|
|
let reply = EchoReply {
|
|
payload: vec![3, 4],
|
|
..echo_reply(¶ms, FLAG_REPLY)
|
|
};
|
|
let packet = encode_echo_reply(&reply, ¶ms, None).unwrap();
|
|
assert_eq!(&packet[16..], &[3, 4, 0, 0]);
|
|
let decoded = decode_echo_reply(&packet, ¶ms, None).unwrap();
|
|
assert_eq!(decoded.payload, vec![3, 4, 0, 0]);
|
|
assert_eq!(encode_echo_reply(&decoded, ¶ms, None).unwrap(), packet);
|
|
assert_eq!(
|
|
encode_echo_reply(
|
|
&EchoReply {
|
|
payload: vec![0; 5],
|
|
..echo_reply(¶ms, FLAG_REPLY)
|
|
},
|
|
¶ms,
|
|
None,
|
|
),
|
|
Err(ProtoError::PayloadTooLarge {
|
|
available: 4,
|
|
provided: 5,
|
|
})
|
|
);
|
|
}
|