use crate::config::Config;
use crate::connection::Transmit;
use crate::endpoint::{ConnHandle, DatagramEvent, Endpoint};
use std::cmp::Reverse;
use std::collections::BinaryHeap;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::{Duration, Instant};
#[derive(Clone, Debug)]
pub struct Link {
pub delay: Duration,
pub jitter: Duration,
pub loss_ppm: u32,
pub duplicate_ppm: u32,
pub reorder_ppm: u32,
pub reorder_delay: Duration,
pub bandwidth: Option<u64>,
pub queue: usize,
pub mtu: usize,
}
impl Default for Link {
fn default() -> Self {
Link {
delay: Duration::from_millis(10),
jitter: Duration::ZERO,
loss_ppm: 0,
duplicate_ppm: 0,
reorder_ppm: 0,
reorder_delay: Duration::ZERO,
bandwidth: None,
queue: usize::MAX,
mtu: 1500,
}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct LinkStats {
pub sent: u64,
pub delivered: u64,
pub dropped: u64,
pub duplicated: u64,
pub bytes: u64,
}
struct Dgram {
from: SocketAddr,
to: SocketAddr,
data: Vec<u8>,
}
struct Queued(Instant, u64, Dgram);
impl PartialEq for Queued {
fn eq(&self, o: &Self) -> bool {
(self.0, self.1) == (o.0, o.1)
}
}
impl Eq for Queued {}
impl PartialOrd for Queued {
fn partial_cmp(&self, o: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(o))
}
}
impl Ord for Queued {
fn cmp(&self, o: &Self) -> std::cmp::Ordering {
(self.0, self.1).cmp(&(o.0, o.1))
}
}
#[derive(Clone)]
pub struct Rng(u64);
impl Rng {
pub fn new(seed: u64) -> Self {
Rng(seed.wrapping_mul(0x9e3779b97f4a7c15) | 1)
}
pub fn next(&mut self) -> u64 {
self.0 ^= self.0 >> 12;
self.0 ^= self.0 << 25;
self.0 ^= self.0 >> 27;
self.0.wrapping_mul(0x2545f4914f6cdd1d)
}
pub fn below(&mut self, n: u64) -> u64 {
if n == 0 {
0
} else {
self.next() % n
}
}
pub fn chance(&mut self, ppm: u32) -> bool {
ppm > 0 && self.below(1_000_000) < ppm as u64
}
}
pub struct Pair {
pub now: Instant,
pub client: Endpoint,
pub server: Endpoint,
pub client_addr: SocketAddr,
pub server_addr: SocketAddr,
pub client_aliases: Vec<SocketAddr>,
pub to_server: Link,
pub to_client: Link,
pub to_server_stats: LinkStats,
pub to_client_stats: LinkStats,
pub rng: Rng,
queue: BinaryHeap<Reverse<Queued>>,
seq: u64,
busy_until: [Instant; 2],
queued_bytes: [usize; 2],
buf: Vec<u8>,
resp: Vec<u8>,
pub accepted: Vec<ConnHandle>,
pub batch: usize,
pub tamper: Option<Box<dyn FnMut(bool, &mut Vec<u8>) -> bool>>,
}
impl Pair {
pub fn new(client_cfg: Arc<Config>, server_cfg: Arc<Config>, link: Link, seed: u64) -> Self {
let now = Instant::now();
Pair {
now,
client: Endpoint::new(client_cfg, false),
server: Endpoint::new(server_cfg, true),
client_addr: "10.0.0.1:5000".parse().unwrap(),
server_addr: "10.0.0.2:443".parse().unwrap(),
client_aliases: Vec::new(),
to_server: link.clone(),
to_client: link,
to_server_stats: LinkStats::default(),
to_client_stats: LinkStats::default(),
rng: Rng::new(seed),
queue: BinaryHeap::new(),
seq: 0,
busy_until: [now; 2],
queued_bytes: [0; 2],
buf: Vec::with_capacity(64 * 1024),
resp: Vec::new(),
accepted: Vec::new(),
batch: 8,
tamper: None,
}
}
pub fn connect(&mut self) -> ConnHandle {
let addr = self.server_addr;
self.client.connect(self.now, addr, "localhost", &[]).unwrap()
}
fn enqueue(&mut self, to_server: bool, from: SocketAddr, to: SocketAddr, mut data: Vec<u8>) {
let d = if to_server { 0 } else { 1 };
let link = if to_server { self.to_server.clone() } else { self.to_client.clone() };
let stats = if to_server { &mut self.to_server_stats } else { &mut self.to_client_stats };
stats.sent += 1;
if let Some(t) = self.tamper.as_mut() {
if !t(to_server, &mut data) {
stats.dropped += 1;
return;
}
}
if data.len() > link.mtu || self.rng.chance(link.loss_ppm) {
stats.dropped += 1;
return;
}
let mut depart = self.now;
if let Some(bw) = link.bandwidth {
if self.queued_bytes[d] + data.len() > link.queue && self.busy_until[d] > self.now {
stats.dropped += 1;
return;
}
let start = self.busy_until[d].max(self.now);
let tx = Duration::from_nanos(data.len() as u64 * 1_000_000_000 / bw);
self.busy_until[d] = start + tx;
depart = start + tx;
self.queued_bytes[d] += data.len();
}
let mut at = depart + link.delay;
if !link.jitter.is_zero() {
at += Duration::from_nanos(self.rng.below(link.jitter.as_nanos() as u64));
}
if self.rng.chance(link.reorder_ppm) {
at += Duration::from_nanos(self.rng.below(link.reorder_delay.as_nanos() as u64 + 1));
}
let dup = self.rng.chance(link.duplicate_ppm);
if dup {
stats.duplicated += 1;
}
stats.bytes += data.len() as u64;
let n = data.len();
if dup {
let copy = data.clone();
self.push(at + Duration::from_micros(50), Dgram { from, to, data: copy });
}
let _ = n;
self.push(at, Dgram { from, to, data });
}
fn push(&mut self, at: Instant, d: Dgram) {
self.seq += 1;
self.queue.push(Reverse(Queued(at, self.seq, d)));
}
fn flush(&mut self) {
loop {
let mut any = false;
while let Some((_, t)) = self.client.poll_transmit(self.now, &mut self.buf, self.batch) {
any = true;
let from = self.client_addr;
self.split_send(true, from, t);
}
while let Some((_, t)) = self.server.poll_transmit(self.now, &mut self.buf, self.batch) {
any = true;
let from = self.server_addr;
self.split_send(false, from, t);
}
if !any {
break;
}
}
}
fn split_send(&mut self, to_server: bool, from: SocketAddr, t: Transmit) {
let seg = t.segment_size.unwrap_or(t.size);
let data = std::mem::take(&mut self.buf);
for chunk in data[..t.size].chunks(seg) {
self.enqueue(to_server, from, t.destination, chunk.to_vec());
}
self.buf = data;
}
pub fn step(&mut self) -> bool {
self.step_until(None)
}
pub fn step_until(&mut self, limit: Option<Instant>) -> bool {
self.flush();
let next_dgram = self.queue.peek().map(|r| r.0 .0);
let next_timer = [self.client.poll_timeout(), self.server.poll_timeout()].into_iter().flatten().min();
let next = match (next_dgram, next_timer) {
(None, None) => return false,
(Some(a), None) => a,
(None, Some(b)) => b,
(Some(a), Some(b)) => a.min(b),
};
if let Some(l) = limit {
if next > l {
self.now = self.now.max(l);
return false;
}
}
if next > self.now {
self.now = next;
}
if next_dgram.is_some_and(|a| a <= self.now) {
let Reverse(Queued(_, _, mut d)) = self.queue.pop().unwrap();
let to_server = d.to == self.server_addr;
let di = if to_server { 0 } else { 1 };
self.queued_bytes[di] = self.queued_bytes[di].saturating_sub(d.data.len());
if to_server {
self.to_server_stats.delivered += 1;
let ev = self.server.recv(self.now, d.from, &mut d.data, &mut self.resp);
match ev {
DatagramEvent::NewConnection(h) => self.accepted.push(h),
DatagramEvent::Response(t) => {
let r = self.resp[..t.size].to_vec();
let from = self.server_addr;
self.enqueue(false, from, t.destination, r);
}
_ => {}
}
} else if d.to == self.client_addr || self.client_aliases.contains(&d.to) {
self.to_client_stats.delivered += 1;
let ev = self.client.recv(self.now, d.from, &mut d.data, &mut self.resp);
if let DatagramEvent::Response(t) = ev {
let r = self.resp[..t.size].to_vec();
let from = self.client_addr;
self.enqueue(true, from, t.destination, r);
}
} else {
self.to_client_stats.dropped += 1;
}
} else {
let mut touched = Vec::new();
self.client.handle_timeout(self.now, &mut touched);
self.server.handle_timeout(self.now, &mut touched);
}
true
}
pub fn run_until(&mut self, limit: Duration, mut cond: impl FnMut(&mut Pair) -> bool) -> bool {
let deadline = self.now + limit;
loop {
if cond(self) {
return true;
}
if self.now >= deadline || !self.step_until(Some(deadline)) {
return cond(self);
}
}
}
pub fn run_for(&mut self, limit: Duration) {
let deadline = self.now + limit;
while self.now < deadline {
if !self.step_until(Some(deadline)) {
break;
}
}
}
pub fn server_conn(&self) -> Option<ConnHandle> {
self.accepted.first().copied()
}
pub fn rebind_client(&mut self, addr: SocketAddr) {
let old = std::mem::replace(&mut self.client_addr, addr);
self.client_aliases.push(old);
}
pub fn in_flight(&self) -> usize {
self.queue.len()
}
}