use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use crate::testing::{fixed_str, uid_n};
use crate::wire::{op, protocol_for, DeviceFeature, HEADER_LEN};
use super::schedule::{next_hello_interval, HELLO_BASE, HELLO_JITTER};
use super::*;
#[derive(Default)]
pub(super) struct Tap(Mutex<Vec<Vec<u8>>>);
impl Tap {
pub(super) fn opcodes(&self) -> Vec<u16> {
self.0
.lock()
.expect("the tap is only ever locked to push or read")
.iter()
.filter_map(|f| f.get(20..22))
.filter_map(|b| <[u8; 2]>::try_from(b).ok())
.map(u16::from_le_bytes)
.collect()
}
pub(super) fn frames(&self) -> Vec<Vec<u8>> {
self.0
.lock()
.expect("the tap is only ever locked to push or read")
.clone()
}
pub(super) fn clear(&self) {
self.0
.lock()
.expect("the tap is only ever locked to push or read")
.clear();
}
}
fn client(n: u8) -> (Remote, Arc<Tap>) {
client_with(n, Arc::new(()))
}
pub(super) fn client_with(n: u8, handler: Arc<dyn EventHandler>) -> (Remote, Arc<Tap>) {
let remote = Remote::new(
Config {
uid: Some(uid_n(n)),
..Config::default()
},
handler,
)
.expect("a client given its own uid reads nothing from disk");
let tap = Arc::new(Tap::default());
let sink = Arc::clone(&tap);
lock(&remote.shared.tx).set_tap(Arc::new(move |frame: &[u8]| {
sink.0
.lock()
.expect("the tap is only ever locked to push or read")
.push(frame.to_vec())
}));
(remote, tap)
}
pub(super) fn hello_datagram(sender: DeviceUid, name: &str, serial: &str) -> Vec<u8> {
let mut payload = Vec::new();
payload.extend_from_slice(&0x28u16.to_le_bytes());
fixed_str(&mut payload, name, 16);
fixed_str(&mut payload, serial, 16);
fixed_str(&mut payload, "4.8.0", 16);
payload.extend_from_slice(&DeviceFeature::VIDEO_ROUTING.bits().to_le_bytes());
let mut out = Vec::with_capacity(HEADER_LEN + payload.len());
out.extend_from_slice(b"P8");
out.extend_from_slice(&protocol_for(op::SYS_HELLO).to_le_bytes());
out.extend_from_slice(sender.as_bytes());
out.extend_from_slice(&op::SYS_HELLO.0.to_le_bytes());
out.extend_from_slice(&(payload.len() as u16).to_le_bytes());
out.extend_from_slice(&payload);
out
}
#[test]
fn a_datagram_is_decoded_and_does_not_provoke_an_announcement() {
let (remote, tap) = client(200);
let peer = uid_n(201);
remote.shared.process_datagram(
&hello_datagram(peer, "Peer", "PR0001"),
Ipv4Addr::new(10, 8, 8, 9),
);
let device = remote.device(peer).expect("the datagram was not processed");
assert_eq!(device.serial, "PR0001");
assert_eq!(device.address, Some(Ipv4Addr::new(10, 8, 8, 9)));
assert!(
!tap.opcodes().contains(&op::SYS_HELLO.0),
"a received datagram triggered a hello; announcement is a timer, not a reply"
);
}
#[test]
fn a_clients_own_frame_is_not_taken_for_a_peers() {
let (remote, _) = client(205);
remote.shared.process_datagram(
&hello_datagram(remote.uid(), "Self", "SF0001"),
Ipv4Addr::new(10, 8, 8, 9),
);
assert!(
remote.devices().is_empty(),
"the client registered itself as a device"
);
}
#[test]
fn the_announcement_is_driven_by_a_timer() {
let (remote, _) = client(202);
let now = Instant::now();
lock(&remote.shared.schedule).set_hello_timer(Some(now), Duration::from_secs(3));
assert!(
!remote.shared.announce_due(now + Duration::from_secs(2)),
"announced early"
);
assert!(
remote.shared.announce_due(now + Duration::from_secs(3)),
"not due at the interval; a silent network would never announce"
);
assert!(
remote.shared.announce_due(now + Duration::from_secs(3600)),
"not due long after the interval"
);
remote.shared.closing.store(true, Ordering::SeqCst);
assert!(
!remote.shared.announce_due(now + Duration::from_secs(3600)),
"announced while closing"
);
}
#[test]
fn the_announcement_interval_is_jittered() {
let mut seen = std::collections::HashSet::new();
for _ in 0..200 {
let interval = next_hello_interval();
assert!(
(HELLO_BASE..=HELLO_BASE + HELLO_JITTER).contains(&interval),
"interval {interval:?} outside {HELLO_BASE:?}..{:?}",
HELLO_BASE + HELLO_JITTER
);
seen.insert(interval);
}
assert!(
seen.len() >= 50,
"only {} distinct intervals in 200 draws; the jitter is not varying",
seen.len()
);
}
#[test]
fn a_failed_announcement_does_not_consume_the_interval() {
let (remote, tap) = client(203);
lock(&remote.shared.schedule).set_hello_timer(None, Duration::ZERO);
remote.shared.announce();
assert_eq!(
tap.opcodes(),
vec![op::SYS_HELLO.0],
"the frame did not reach the gate at all, so the timer proves nothing"
);
let (last, interval) = lock(&remote.shared.schedule).hello_timer();
assert_eq!(
(last, interval),
(None, Duration::ZERO),
"a failed send re-armed the timer; it should retry on the next tick"
);
}
#[test]
fn the_probe_loop_announces() {
let (remote, tap) = client(204);
lock(&remote.shared.schedule).set_hello_timer(Some(Instant::now()), Duration::from_millis(1));
remote
.spawn_workers()
.expect("the worker threads could not start");
let deadline = Instant::now() + Duration::from_secs(5);
while Instant::now() < deadline && !tap.opcodes().contains(&op::SYS_HELLO.0) {
std::thread::sleep(Duration::from_millis(50));
}
remote.close();
assert!(
tap.opcodes().contains(&op::SYS_HELLO.0),
"the probe loop never announced, though the announcement was overdue"
);
}
#[test]
fn the_announcement_describes_this_client() {
let (remote, tap) = client(206);
remote.shared.announce();
let frame = tap.frames().pop().expect("nothing was announced");
let peer = Remote::new(
Config {
uid: Some(uid_n(207)),
..Config::default()
},
Arc::new(()),
)
.expect("a client given its own uid reads nothing from disk");
peer.shared
.process_datagram(&frame, Ipv4Addr::new(10, 8, 8, 1));
let seen = peer
.device(remote.uid())
.expect("a peer could not decode this client's announcement");
assert_eq!(seen.name, "MXR Rust");
assert_eq!(seen.serial, CLIENT_SERIAL);
assert_eq!(seen.version, VERSION);
assert_eq!(seen.supported_protocol, PROTOCOL_VERSION);
assert!(seen.features.has(DeviceFeature::MANAGER));
peer.close();
}
#[test]
fn a_handler_may_read_the_state_its_event_describes() {
struct Reentrant {
remote: Mutex<Option<Arc<Remote>>>,
seen: AtomicUsize,
}
impl EventHandler for Reentrant {
fn on_device_update(&self, device: DeviceUid) {
let remote = self.remote.lock().expect("set before any frame arrives");
let remote = remote.as_ref().expect("set before any frame arrives");
assert!(
remote.device(device).is_some(),
"the handler could not read the device its own event named"
);
self.seen.fetch_add(1, Ordering::SeqCst);
}
}
let handler = Arc::new(Reentrant {
remote: Mutex::new(None),
seen: AtomicUsize::new(0),
});
let remote = Arc::new(
Remote::new(
Config {
uid: Some(uid_n(208)),
..Config::default()
},
Arc::clone(&handler) as Arc<dyn EventHandler>,
)
.expect("a client given its own uid reads nothing from disk"),
);
*handler.remote.lock().expect("nothing else holds this") = Some(Arc::clone(&remote));
let peer = uid_n(209);
remote.shared.process_datagram(
&hello_datagram(peer, "Peer", "PR0002"),
Ipv4Addr::new(10, 8, 8, 9),
);
assert_eq!(handler.seen.load(Ordering::SeqCst), 0);
remote.shared.process_datagram(
&hello_datagram(peer, "Renamed", "PR0002"),
Ipv4Addr::new(10, 8, 8, 9),
);
assert!(
handler.seen.load(Ordering::SeqCst) > 0,
"no event reached the handler"
);
}