use std::{
collections::VecDeque,
net::SocketAddr,
sync::{Arc, Mutex},
time::Duration,
};
use irtt_proto::{
decode_echo_reply, decode_open_reply, encode_request, varint, EchoReply, OpenReply,
PacketLayout, Params, RequestToEncode, FLAG_CLOSE, FLAG_HMAC, FLAG_OPEN, FLAG_REPLY,
};
use crate::{
clock::{ClockSample, ClockSource},
core::{OutboundDatagram, ServerCore},
error::ServerError,
token::TokenSource,
ServerConfig,
};
pub(crate) const KEY: &[u8] = b"correctkey";
pub(crate) const OTHER_KEY: &[u8] = b"wrongkey";
pub(crate) fn unthrottled() -> ServerConfig {
ServerConfig::default().with_min_send_interval(Duration::ZERO)
}
pub(crate) fn peer() -> SocketAddr {
"198.51.100.7:41234".parse().unwrap()
}
pub(crate) fn other_peer() -> SocketAddr {
"198.51.100.7:41235".parse().unwrap()
}
#[derive(Debug, Clone)]
pub(crate) struct ScriptedTokens {
values: Arc<Mutex<VecDeque<Result<u64, ServerError>>>>,
}
impl ScriptedTokens {
pub(crate) fn new<I>(values: I) -> Self
where
I: IntoIterator<Item = u64>,
{
Self {
values: Arc::new(Mutex::new(values.into_iter().map(Ok).collect())),
}
}
pub(crate) fn failing() -> Self {
Self {
values: Arc::new(Mutex::new(VecDeque::new())),
}
}
pub(crate) fn remaining(&self) -> usize {
self.values.lock().unwrap().len()
}
}
impl TokenSource for ScriptedTokens {
fn next_token(&mut self) -> Result<u64, ServerError> {
self.values
.lock()
.unwrap()
.pop_front()
.unwrap_or(Err(ServerError::RandomSource {
reason: "scripted failure".to_owned(),
}))
}
}
#[derive(Debug, Clone)]
pub(crate) struct ScriptedClock {
samples: Arc<Mutex<VecDeque<ClockSample>>>,
}
impl ScriptedClock {
pub(crate) fn new<I>(samples: I) -> Self
where
I: IntoIterator<Item = ClockSample>,
{
Self {
samples: Arc::new(Mutex::new(samples.into_iter().collect())),
}
}
pub(crate) fn remaining(&self) -> usize {
self.samples.lock().unwrap().len()
}
}
impl ClockSource for ScriptedClock {
fn sample(&mut self) -> ClockSample {
self.samples
.lock()
.unwrap()
.pop_front()
.expect("the scripted clock ran out: script one sample per echo received, and one more per echo answered")
}
}
pub(crate) fn sample(wall_ns: i64, mono_ns: i64) -> ClockSample {
ClockSample { wall_ns, mono_ns }
}
#[derive(Debug, Clone)]
pub(crate) struct ManualClock {
now_ns: Arc<Mutex<i64>>,
}
impl ManualClock {
pub(crate) fn at(now_ns: i64) -> Self {
Self {
now_ns: Arc::new(Mutex::new(now_ns)),
}
}
pub(crate) fn set(&self, now_ns: i64) {
*self.now_ns.lock().unwrap() = now_ns;
}
}
impl ClockSource for ManualClock {
fn sample(&mut self) -> ClockSample {
let now_ns = *self.now_ns.lock().unwrap();
ClockSample {
wall_ns: now_ns,
mono_ns: now_ns,
}
}
}
pub(crate) fn core_with_tokens(config: ServerConfig, tokens: ScriptedTokens) -> ServerCore {
ServerCore::with_token_source(config, Box::new(tokens))
}
pub(crate) fn core_with_sources(
config: ServerConfig,
tokens: ScriptedTokens,
clock: impl ClockSource + 'static,
) -> ServerCore {
ServerCore::with_sources(config, Box::new(tokens), Box::new(clock))
}
pub(crate) fn core_for(hmac_key: Option<&[u8]>, tokens: ScriptedTokens) -> ServerCore {
let mut config = unthrottled();
if let Some(key) = hmac_key {
config = config.with_hmac_key(key);
}
core_with_tokens(config, tokens)
}
pub(crate) fn manual_core(
config: ServerConfig,
requested: &Params,
tokens: ScriptedTokens,
hmac_key: Option<&[u8]>,
) -> (ServerCore, ManualClock, u64, Params) {
let clock = ManualClock::at(0);
let mut core = core_with_sources(config, tokens, clock.clone());
let (token, negotiated) = open_negotiated(&mut core, peer(), requested, hmac_key);
(core, clock, token, negotiated)
}
pub(crate) fn echo_at(
core: &mut ServerCore,
clock: &ManualClock,
at_ns: i64,
token: u64,
sequence: u32,
params: &Params,
hmac_key: Option<&[u8]>,
) -> Option<OutboundDatagram> {
clock.set(at_ns);
core.handle_datagram(
peer(),
&echo_request(token, sequence, params, &[], hmac_key),
)
.expect("a rejected or rate-limited echo is not an internal error")
}
pub(crate) fn open_session(
core: &mut ServerCore,
endpoint: SocketAddr,
hmac_key: Option<&[u8]>,
) -> u64 {
let packet = core
.handle_datagram(endpoint, &open_request(&client_params(), hmac_key))
.unwrap()
.expect("the session-creating open must be answered");
expect_normal_open_reply(&packet, hmac_key).token
}
pub(crate) fn open_negotiated(
core: &mut ServerCore,
endpoint: SocketAddr,
params: &Params,
hmac_key: Option<&[u8]>,
) -> (u64, Params) {
let packet = core
.handle_datagram(endpoint, &open_request(params, hmac_key))
.unwrap()
.expect("the session-creating open must be answered");
let reply = expect_normal_open_reply(&packet, hmac_key);
(reply.token, reply.params)
}
pub(crate) fn echo_params(
received_stats: irtt_proto::ReceivedStats,
stamp_at: irtt_proto::StampAt,
clock: irtt_proto::Clock,
length: i64,
) -> Params {
Params {
received_stats,
stamp_at,
clock,
length,
..client_params()
}
}
pub(crate) fn echo_request(
token: u64,
sequence: u32,
params: &Params,
payload: &[u8],
hmac_key: Option<&[u8]>,
) -> Vec<u8> {
encode_request(
RequestToEncode::Echo {
token,
sequence,
params,
payload,
},
hmac_key,
)
.unwrap()
}
pub(crate) fn expect_echo_reply(
packet: impl AsRef<[u8]>,
params: &Params,
hmac_key: Option<&[u8]>,
) -> EchoReply {
let reply = decode_echo_reply(packet.as_ref(), params, hmac_key)
.expect("server echo reply must decode");
let expected = FLAG_REPLY | if hmac_key.is_some() { FLAG_HMAC } else { 0 };
assert_eq!(reply.flags, expected, "echo reply flags");
reply
}
pub(crate) fn expect_closing_echo_reply(
packet: impl AsRef<[u8]>,
params: &Params,
hmac_key: Option<&[u8]>,
) -> EchoReply {
let reply = decode_echo_reply(packet.as_ref(), params, hmac_key)
.expect("a closing echo reply must decode");
let expected = FLAG_REPLY | FLAG_CLOSE | if hmac_key.is_some() { FLAG_HMAC } else { 0 };
assert_eq!(reply.flags, expected, "closing echo reply flags");
reply
}
pub(crate) fn open_request(params: &Params, hmac_key: Option<&[u8]>) -> Vec<u8> {
encode_request(
RequestToEncode::Open {
params,
no_test: false,
},
hmac_key,
)
.unwrap()
}
pub(crate) fn no_test_request(params: &Params, hmac_key: Option<&[u8]>) -> Vec<u8> {
encode_request(
RequestToEncode::Open {
params,
no_test: true,
},
hmac_key,
)
.unwrap()
}
pub(crate) fn close_request(token: u64, hmac_key: Option<&[u8]>) -> Vec<u8> {
encode_request(RequestToEncode::Close { token }, hmac_key).unwrap()
}
pub(crate) fn close_request_with_trailing(
token: u64,
trailing: &[u8],
hmac_key: Option<&[u8]>,
) -> Vec<u8> {
let mut packet = close_request(token, hmac_key);
packet.extend_from_slice(trailing);
if let Some(key) = hmac_key {
irtt_proto::compute_hmac_in_place(key, &mut packet, hmac_field_offset()).unwrap();
}
packet
}
pub(crate) fn open_request_with_raw_params(payload: &[u8], hmac_key: Option<&[u8]>) -> Vec<u8> {
let mut packet = irtt_proto::MAGIC.to_vec();
packet.push(if hmac_key.is_some() {
FLAG_OPEN | FLAG_HMAC
} else {
FLAG_OPEN
});
if hmac_key.is_some() {
packet.extend_from_slice(&[0; irtt_proto::HMAC_SIZE]);
}
packet.extend_from_slice(payload);
if let Some(key) = hmac_key {
irtt_proto::compute_hmac_in_place(key, &mut packet, hmac_field_offset()).unwrap();
}
packet
}
fn hmac_field_offset() -> usize {
PacketLayout::open_request(false).header_len()
}
pub(crate) fn param_int(tag: u64, value: i64) -> Vec<u8> {
let mut encoded = Vec::new();
varint::encode_uvarint(tag, &mut encoded);
varint::encode_varint(value, &mut encoded);
encoded
}
pub(crate) fn param_server_fill(declared_len: u64, value: &[u8]) -> Vec<u8> {
let mut encoded = Vec::new();
varint::encode_uvarint(9, &mut encoded);
varint::encode_uvarint(declared_len, &mut encoded);
encoded.extend_from_slice(value);
encoded
}
pub(crate) fn client_params() -> Params {
Params {
protocol_version: 1,
duration_ns: 3_000_000_000,
interval_ns: 1_000_000_000,
length: 1472,
received_stats: irtt_proto::ReceivedStats::Both,
stamp_at: irtt_proto::StampAt::Both,
clock: irtt_proto::Clock::Both,
dscp: 184,
server_fill: None,
}
}
pub(crate) fn decode_reply(packet: impl AsRef<[u8]>, hmac_key: Option<&[u8]>) -> OpenReply {
decode_open_reply(packet.as_ref(), hmac_key).expect("server reply must decode")
}
pub(crate) fn expect_normal_open_reply(
packet: impl AsRef<[u8]>,
hmac_key: Option<&[u8]>,
) -> OpenReply {
let reply = decode_reply(packet, hmac_key);
let expected = FLAG_OPEN | FLAG_REPLY | if hmac_key.is_some() { FLAG_HMAC } else { 0 };
assert_eq!(reply.flags, expected, "normal open reply flags");
assert_ne!(reply.token, 0, "a session-creating reply needs a token");
assert_eq!(reply.params.protocol_version, 1);
reply
}
pub(crate) fn expect_no_test_reply(packet: impl AsRef<[u8]>, hmac_key: Option<&[u8]>) -> OpenReply {
let reply = decode_reply(packet, hmac_key);
let expected =
FLAG_OPEN | FLAG_REPLY | FLAG_CLOSE | if hmac_key.is_some() { FLAG_HMAC } else { 0 };
assert_eq!(reply.flags, expected, "no-test reply flags");
assert_eq!(reply.token, 0, "a no-test reply carries a zero token");
reply
}