use offline_protocol::{CustodyConfig, Event, OfflineProtocol, ProtocolConfig};
use offline_protocol_core::{AppId, Message, UserId};
use offline_protocol_transport::{mock::MockTransport, Transport, TransportType};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
struct Neighborhood {
nodes: HashMap<String, OfflineProtocol>,
radios: HashMap<String, MockTransport>,
links: HashMap<String, Vec<String>>,
transmissions: Vec<(String, String, String)>,
frames: Vec<(String, String, Message)>,
inboxes: HashMap<String, Vec<String>>,
}
impl Neighborhood {
fn new(node_ids: &[&str]) -> Self {
let mut neighborhood = Self {
nodes: HashMap::new(),
radios: HashMap::new(),
links: HashMap::new(),
transmissions: Vec::new(),
frames: Vec::new(),
inboxes: HashMap::new(),
};
for id in node_ids {
neighborhood.add_node(id, default_config(id));
}
neighborhood
}
fn add_node(&mut self, id: &str, config: ProtocolConfig) {
self.add_node_inner(id, config, false);
}
fn add_online_node(&mut self, id: &str) {
self.add_node_inner(id, default_config(id), true);
}
fn add_relay_first_node(&mut self, id: &str) {
let mut config = default_config(id);
config.dors.prefer_online = true;
self.add_node_inner(id, config, true);
}
fn add_node_inner(&mut self, id: &str, config: ProtocolConfig, online: bool) {
let radio = MockTransport::new(TransportType::BLE);
radio.start().unwrap();
radio.set_reject_unknown_recipients(true);
let handle = radio.clone();
let mut node = OfflineProtocol::new(config).unwrap();
node.transport_manager_mut()
.add_transport(TransportType::BLE, Box::new(radio));
if online {
let relay = MockTransport::new(TransportType::Internet);
relay.start().unwrap();
node.transport_manager_mut()
.add_transport(TransportType::Internet, Box::new(relay));
}
node.start().unwrap();
self.nodes.insert(id.to_string(), node);
self.radios.insert(id.to_string(), handle);
self.links.insert(id.to_string(), Vec::new());
self.inboxes.insert(id.to_string(), Vec::new());
}
fn relay_says_unreachable(&mut self, id: &str, message_id: &str) {
self.nodes
.get_mut(id)
.unwrap()
.on_transport_send_failed(
message_id,
Some("recipient_unreachable: peer offline".to_string()),
)
.unwrap();
}
fn link(&mut self, a: &str, b: &str) {
self.radios[a].add_connected_peer(b, -55);
self.radios[b].add_connected_peer(a, -55);
self.links.get_mut(a).unwrap().push(b.to_string());
self.links.get_mut(b).unwrap().push(a.to_string());
self.nodes.get_mut(a).unwrap().on_neighbor_discovered(b);
self.nodes.get_mut(b).unwrap().on_neighbor_discovered(a);
}
fn unlink_all(&mut self, id: &str) {
let peers = self.links[id].clone();
for peer in peers {
self.radios[&peer].remove_connected_peer(id);
self.radios[id].remove_connected_peer(&peer);
self.nodes.get_mut(&peer).unwrap().on_neighbor_lost(id);
self.links.get_mut(&peer).unwrap().retain(|p| p != id);
}
self.links.get_mut(id).unwrap().clear();
}
fn step(&mut self) -> usize {
for id in self.node_ids() {
let node = self.nodes.get_mut(&id).unwrap();
let mut delivered = Vec::new();
while let Some(message) = node.receive_message() {
delivered.push(message.content);
}
node.process().unwrap();
self.inboxes.get_mut(&id).unwrap().extend(delivered);
}
let mut moved = 0;
for from in self.node_ids() {
let mut handed: Vec<(String, Message)> = self.radios[&from]
.sent_messages()
.into_iter()
.map(|m| (m.recipient.as_str().to_string(), m))
.collect();
handed.extend(self.radios[&from].peer_sends());
self.radios[&from].clear_sent_messages();
self.radios[&from].clear_peer_sends();
for (to, message) in handed {
assert!(
self.links[&from].contains(&to),
"{from} transmitted to {to}, which it has no link to"
);
self.transmissions
.push((from.clone(), to.clone(), message.id.as_str()));
self.frames
.push((from.clone(), to.clone(), message.clone()));
self.radios[&to].queue_message_from(message, from.clone());
moved += 1;
}
}
moved
}
fn run_until_quiet(&mut self, max_rounds: usize) -> usize {
for round in 0..max_rounds {
if self.step() == 0 {
return round + 1;
}
}
panic!("network never went quiet after {max_rounds} rounds");
}
fn node_ids(&self) -> Vec<String> {
let mut ids: Vec<String> = self.nodes.keys().cloned().collect();
ids.sort();
ids
}
fn node(&mut self, id: &str) -> &mut OfflineProtocol {
self.nodes.get_mut(id).unwrap()
}
fn send(&mut self, from: &str, to: &str, content: &str) -> String {
self.nodes
.get_mut(from)
.unwrap()
.send_message(to, content, None, None::<String>)
.expect("send should be accepted locally even with no route")
.as_str()
}
fn inbox(&mut self, id: &str) -> Vec<String> {
let node = self.nodes.get_mut(id).unwrap();
let mut delivered = Vec::new();
while let Some(message) = node.receive_message() {
delivered.push(message.content);
}
self.inboxes.get_mut(id).unwrap().extend(delivered);
self.inboxes[id].clone()
}
fn transmission_count(&self, message_id: &str) -> usize {
self.transmissions
.iter()
.filter(|(_, _, id)| id == message_id)
.count()
}
fn deliveries_to(&self, node_id: &str, message_id: &str) -> usize {
self.transmissions
.iter()
.filter(|(_, to, id)| to == node_id && id == message_id)
.count()
}
}
fn default_config(user_id: &str) -> ProtocolConfig {
let mut config = ProtocolConfig::new("mesh-test", user_id);
config.encryption.require_encryption = false;
config.mesh_relay.jitter_min = std::time::Duration::from_millis(0);
config.mesh_relay.jitter_max = std::time::Duration::from_millis(0);
config
}
fn message(from: &str, to: &str, content: &str) -> Message {
Message::new(
UserId::new(from).unwrap(),
UserId::new(to).unwrap(),
AppId::new("mesh-test").unwrap(),
content,
)
}
#[test]
fn a_message_reaches_someone_two_rooms_away() {
let mut net = Neighborhood::new(&["alice", "bob", "carol"]);
net.link("alice", "bob");
net.link("bob", "carol");
let msg_id = net.send("alice", "carol", "meet at the north gate");
net.run_until_quiet(12);
assert_eq!(
net.inbox("carol"),
vec!["meet at the north gate".to_string()],
"carol must receive a message from someone she cannot hear"
);
assert_eq!(
net.deliveries_to("carol", &msg_id),
1,
"and receive it exactly once"
);
}
#[test]
fn the_answer_finds_its_way_back() {
let mut net = Neighborhood::new(&["alice", "bob", "carol"]);
net.link("alice", "bob");
net.link("bob", "carol");
net.send("alice", "carol", "are you here?");
net.run_until_quiet(12);
assert_eq!(net.inbox("carol").len(), 1);
let acked_back = net
.transmissions
.iter()
.any(|(from, to, _)| from == "bob" && to == "alice");
assert!(
acked_back,
"carol's answer must be carried back toward alice"
);
}
#[test]
fn an_online_sender_reaches_a_recipient_only_the_mesh_can_see() {
let mut net = Neighborhood::new(&["bob", "carol"]);
net.add_online_node("alice");
net.link("alice", "bob");
net.link("bob", "carol");
let delivered: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
let delivered_handle = Arc::clone(&delivered);
net.node("alice").on_event(move |event| {
if let Event::MessageDelivered { message_id, .. } = event {
delivered_handle.lock().unwrap().push(message_id);
}
});
let msg_id = net.send("alice", "carol", "meet at the north gate");
net.step();
assert_eq!(
net.transmission_count(&msg_id),
0,
"an online device must not spend the mesh before the relay has spoken"
);
net.relay_says_unreachable("alice", &msg_id);
net.run_until_quiet(12);
assert_eq!(
net.inbox("carol"),
vec!["meet at the north gate".to_string()],
"the relay's verdict must send the message to the neighbors instead"
);
assert_eq!(
net.deliveries_to("carol", &msg_id),
1,
"and it must arrive exactly once"
);
assert_eq!(
*delivered.lock().unwrap(),
vec![msg_id.clone()],
"alice must learn it was delivered — parking removed the pending ACK, \
so a delivery she cannot settle would be probed until it expired"
);
}
#[test]
fn an_online_sender_hands_the_frame_straight_to_a_neighbor_it_can_see() {
let mut net = Neighborhood::new(&["bob"]);
net.add_relay_first_node("alice");
net.link("alice", "bob");
let msg_id = net.send("alice", "bob", "you are right here");
net.run_until_quiet(6);
assert_eq!(
net.inbox("bob"),
vec!["you are right here".to_string()],
"a recipient one link away must be reached over that link, \
with no relay verdict needed first"
);
assert_eq!(net.deliveries_to("bob", &msg_id), 1, "and exactly once");
}
#[test]
fn a_message_crosses_a_line_of_five_devices() {
let mut net = Neighborhood::new(&["a", "b", "c", "d", "e"]);
net.link("a", "b");
net.link("b", "c");
net.link("c", "d");
net.link("d", "e");
let msg_id = net.send("a", "e", "all the way down");
net.run_until_quiet(20);
assert_eq!(net.inbox("e"), vec!["all the way down".to_string()]);
assert_eq!(net.deliveries_to("e", &msg_id), 1);
}
#[test]
fn a_message_with_two_paths_arrives_once() {
let mut net = Neighborhood::new(&["alice", "b", "c", "dave"]);
net.link("alice", "b");
net.link("alice", "c");
net.link("b", "dave");
net.link("c", "dave");
let msg_id = net.send("alice", "dave", "two ways round");
net.run_until_quiet(16);
assert_eq!(
net.inbox("dave"),
vec!["two ways round".to_string()],
"arriving twice must not mean being read twice"
);
assert!(
net.transmission_count(&msg_id) <= 6,
"message crossed links {} times, which is more than the topology needs",
net.transmission_count(&msg_id)
);
}
#[test]
fn a_message_does_not_circle_a_ring_forever() {
let mut net = Neighborhood::new(&["a", "b", "c", "d"]);
net.link("a", "b");
net.link("b", "c");
net.link("c", "d");
net.link("d", "a");
let msg_id = net.send("a", "c", "round the ring");
let rounds = net.run_until_quiet(20);
assert_eq!(net.inbox("c"), vec!["round the ring".to_string()]);
assert!(
net.transmission_count(&msg_id) <= 8,
"message crossed links {} times going round a four-device ring",
net.transmission_count(&msg_id)
);
assert!(rounds < 20, "the ring must settle, not keep turning");
}
#[test]
fn everyone_hearing_everyone_does_not_multiply_the_traffic() {
let cluster = ["n0", "n1", "n2", "n3", "n4", "n5", "n6"];
let mut net = Neighborhood::new(&["n0", "n1", "n2", "n3", "n4", "n5", "n6", "far"]);
for (i, a) in cluster.iter().enumerate() {
for b in cluster.iter().skip(i + 1) {
net.link(a, b);
}
}
net.link("n6", "far");
let msg_id = net.send("n0", "far", "in the crowd");
net.run_until_quiet(24);
assert_eq!(net.inbox("far"), vec!["in the crowd".to_string()]);
let crossings = net.transmission_count(&msg_id);
assert!(
crossings < 21,
"one message crossed links {crossings} times in a room of {} devices, \
which is more than the cluster has links",
cluster.len()
);
assert!(
net.transmissions
.iter()
.any(|(from, to, id)| from == "n6" && to == "far" && id == &msg_id),
"the message should have reached the recipient through the cluster"
);
}
#[test]
fn a_device_hearing_a_frame_from_every_side_still_carries_it_onward() {
let mut net = Neighborhood::new(&["sender", "a", "b", "c", "dave", "erin", "far"]);
for relay in ["a", "b", "c"] {
net.link("sender", relay);
net.link(relay, "dave");
}
net.link("a", "b");
net.link("a", "c");
net.link("b", "c");
net.link("dave", "erin");
net.link("erin", "far");
let msg_id = net.send("sender", "far", "around the crowd");
net.run_until_quiet(16);
assert_eq!(net.inbox("far"), vec!["around the crowd".to_string()]);
assert!(
net.transmissions
.iter()
.any(|(from, to, id)| from == "dave" && to == "erin" && id == &msg_id),
"dave must carry the frame on to the one neighbor that had no copy"
);
}
#[test]
fn the_only_device_that_can_reach_the_recipient_does_not_stand_down() {
let mut net = Neighborhood::new(&["alice", "bob", "carol", "dave"]);
net.link("alice", "bob");
net.link("alice", "carol");
net.link("bob", "carol");
net.link("carol", "dave");
net.send("alice", "dave", "only carol can finish this");
net.run_until_quiet(16);
assert_eq!(
net.inbox("dave"),
vec!["only carol can finish this".to_string()],
"the device holding the last link must deliver, not defer to a neighbor"
);
}
#[test]
fn a_message_survives_the_carrier_walking_away() {
let mut net = Neighborhood::new(&["alice", "bob", "carol"]);
net.link("alice", "bob");
net.link("bob", "carol");
net.send("alice", "carol", "still gets there");
net.step();
net.unlink_all("bob");
net.run_until_quiet(8);
assert!(
net.inbox("carol").is_empty(),
"with the only carrier gone, nothing should have arrived"
);
net.link("alice", "carol");
net.node("alice").process().unwrap();
net.run_until_quiet(12);
assert_eq!(
net.inbox("carol"),
vec!["still gets there".to_string()],
"the sender's own retry must still be able to deliver"
);
}
#[test]
fn a_device_that_opts_out_carries_nothing() {
let mut config = default_config("bob");
config.relay.allow_relay = false;
let mut net = Neighborhood::new(&["alice", "carol"]);
net.add_node("bob", config);
net.link("alice", "bob");
net.link("bob", "carol");
let msg_id = net.send("alice", "carol", "please pass this on");
net.run_until_quiet(10);
assert!(
net.inbox("carol").is_empty(),
"a device that declined to carry traffic must not have carried it"
);
assert_eq!(
net.transmissions
.iter()
.filter(|(from, _, id)| from == "bob" && id == &msg_id)
.count(),
0,
"and must not have transmitted it at all"
);
}
#[test]
fn a_device_that_carries_nothing_still_gets_its_own_answer_out() {
let mut config = default_config("carol");
config.relay.allow_relay = false;
let mut net = Neighborhood::new(&["alice", "bob"]);
net.add_node("carol", config);
net.link("alice", "bob");
net.link("bob", "carol");
net.send("alice", "carol", "do you copy?");
net.run_until_quiet(12);
assert_eq!(net.inbox("carol"), vec!["do you copy?".to_string()]);
assert!(
net.transmissions
.iter()
.any(|(from, to, _)| from == "carol" && to == "bob"),
"a device that carries nothing must still hand its own answer to a neighbor"
);
assert!(
net.transmissions
.iter()
.any(|(from, to, _)| from == "bob" && to == "alice"),
"and that answer must reach the sender"
);
}
#[test]
fn an_answer_is_carried_even_when_a_transport_accepts_a_peer_it_cannot_reach() {
let mut carol = OfflineProtocol::new(default_config("carol")).unwrap();
let ble = MockTransport::new(TransportType::BLE);
ble.start().unwrap();
ble.set_reject_unknown_recipients(true);
ble.add_connected_peer("bob", -55);
let ble_handle = ble.clone();
let swallowing = MockTransport::new(TransportType::WiFiDirect);
swallowing.start().unwrap();
let swallowing_handle = swallowing.clone();
carol
.transport_manager_mut()
.add_transport(TransportType::BLE, Box::new(ble));
carol
.transport_manager_mut()
.add_transport(TransportType::WiFiDirect, Box::new(swallowing));
carol.start().unwrap();
let msg = message("alice", "carol", "did this get through?");
ble_handle.queue_message_from(msg, "bob".to_string());
assert!(
carol.receive_message().is_some(),
"carol should have received the message"
);
carol.process().unwrap();
let carried: Vec<_> = ble_handle
.peer_sends()
.into_iter()
.filter(|(_, m)| m.recipient.as_str() == "alice")
.collect();
assert_eq!(
carried.len(),
1,
"carol's answer must be handed to a neighbor to carry"
);
assert_eq!(carried[0].0, "bob", "and handed to the neighbor she has");
assert!(
swallowing_handle
.sent_messages()
.iter()
.all(|m| m.recipient.as_str() != "alice"),
"the answer must not be spent on a carrier that cannot reach alice"
);
}
#[test]
fn an_answer_is_carried_when_the_last_hop_arrived_over_the_swallowing_carrier() {
let mut carol = OfflineProtocol::new(default_config("carol")).unwrap();
let ble = MockTransport::new(TransportType::BLE);
ble.start().unwrap();
ble.set_reject_unknown_recipients(true);
ble.add_connected_peer("bob", -55);
let ble_handle = ble.clone();
let swallowing = MockTransport::new(TransportType::WiFiDirect);
swallowing.start().unwrap();
let swallowing_handle = swallowing.clone();
carol
.transport_manager_mut()
.add_transport(TransportType::BLE, Box::new(ble));
carol
.transport_manager_mut()
.add_transport(TransportType::WiFiDirect, Box::new(swallowing));
carol.start().unwrap();
let msg = message("alice", "carol", "did this get through?");
swallowing_handle.queue_message_from(msg, "bob".to_string());
assert!(
carol.receive_message().is_some(),
"carol should have received the message"
);
carol.process().unwrap();
assert!(
swallowing_handle
.sent_messages()
.iter()
.all(|m| m.recipient.as_str() != "alice"),
"the answer must not be spent on the carrier that only pretends to reach alice"
);
let carried: Vec<_> = ble_handle
.peer_sends()
.into_iter()
.filter(|(_, m)| m.recipient.as_str() == "alice")
.collect();
assert_eq!(
carried.len(),
1,
"carol's answer must be carried by the neighbor she has"
);
assert_eq!(carried[0].0, "bob");
}
#[test]
fn a_frame_claiming_an_absurd_reach_is_cut_down() {
let mut net = Neighborhood::new(&["a", "b", "c"]);
net.link("a", "b");
net.link("b", "c");
let mut msg = message("a", "c", "travel forever");
msg.ttl = offline_protocol_core::TTL::new(255).unwrap();
net.radios["b"].queue_message_from(msg, "a".to_string());
net.step();
let onward = net.radios["c"]
.receive()
.unwrap()
.expect("b should have carried it to c");
assert!(
onward.ttl.value() <= 8,
"a frame claiming 255 hops was passed on with {}",
onward.ttl.value()
);
}
fn custodian_config(user_id: &str) -> ProtocolConfig {
let mut config = default_config(user_id);
config.custody = CustodyConfig {
enabled: true,
stranger_max_entries: 8,
stranger_max_bytes: 512 * 1024,
..CustodyConfig::default()
};
config
}
fn deposit(from: &str, to: &str) -> Message {
let mut frame = message(
from,
to,
&format!(
"{}opaque-ciphertext",
offline_protocol_sealed::prefixes::ENCRYPTED
),
);
frame
.metadata
.insert("__custody".to_string(), "data".to_string());
frame
}
#[test]
fn a_deposited_frame_outlives_the_carrier_walking_away() {
let mut net = Neighborhood::new(&["alice", "carol"]);
net.add_node("bob", custodian_config("bob"));
net.link("alice", "bob");
let frame = deposit("alice", "carol");
let frame_id = frame.id.as_str();
net.radios["bob"].queue_message_from(frame, "alice".to_string());
net.step();
assert_eq!(
net.node("bob").custody_stats().held,
0,
"a forward is not a deposit while the mesh can still carry it"
);
net.unlink_all("bob");
std::thread::sleep(std::time::Duration::from_millis(5_200));
net.run_until_quiet(8);
let stats = net.node("bob").custody_stats();
assert_eq!(stats.accepted, 1, "taken into custody at the drop point");
assert_eq!(stats.held, 1);
assert!(net.inbox("carol").is_empty());
assert_eq!(net.deliveries_to("carol", &frame_id), 0);
net.link("bob", "carol");
net.run_until_quiet(8);
assert_eq!(
net.deliveries_to("carol", &frame_id),
1,
"the custodian delivers the held frame to its recipient, once"
);
let stats = net.node("bob").custody_stats();
assert_eq!(stats.delivered, 1);
assert_eq!(
stats.held, 0,
"delivery to the recipient releases the frame"
);
let (_, _, delivered) = net
.frames
.iter()
.find(|(from, to, m)| from == "bob" && to == "carol" && m.id.as_str() == frame_id)
.expect("crossed the bob-carol link");
assert!(
!delivered.metadata.contains_key("__custody"),
"what the custodian transmits never carries the request"
);
}
#[test]
fn a_device_with_custody_off_still_strips_the_request_and_holds_nothing() {
let mut net = Neighborhood::new(&["alice", "bob", "dave"]);
net.link("alice", "bob");
net.link("bob", "dave");
let frame = deposit("alice", "carol");
let frame_id = frame.id.as_str();
net.radios["bob"].queue_message_from(frame.clone(), "alice".to_string());
net.run_until_quiet(8);
assert_eq!(
net.deliveries_to("dave", &frame_id),
1,
"carried on as before"
);
let (_, _, forwarded) = net
.frames
.iter()
.find(|(from, to, m)| from == "bob" && to == "dave" && m.id.as_str() == frame_id)
.expect("crossed the bob-dave link");
assert!(
!forwarded.metadata.contains_key("__custody"),
"the request travels exactly one hop"
);
assert_eq!(
forwarded.content, frame.content,
"only the outer metadata is touched"
);
assert_eq!(net.node("bob").custody_stats().held, 0);
assert_eq!(net.node("bob").custody_stats().accepted, 0);
}
fn watch_deliveries(net: &mut Neighborhood, id: &str) -> Arc<Mutex<Vec<String>>> {
let delivered = Arc::new(Mutex::new(Vec::<String>::new()));
let seen = Arc::clone(&delivered);
net.node(id).on_event(move |event| {
if let Event::MessageDelivered { message_id, .. } = event {
seen.lock().unwrap().push(message_id);
}
});
delivered
}
fn step_until_delivered(
net: &mut Neighborhood,
delivered: &Arc<Mutex<Vec<String>>>,
rounds: usize,
) {
for _ in 0..rounds {
net.step();
if !delivered.lock().unwrap().is_empty() {
return;
}
}
}
fn assert_settled(
net: &mut Neighborhood,
sender: &str,
message_id: &str,
other_carriers: &[&MockTransport],
) {
for carrier in other_carriers {
carrier.clear_sent_messages();
}
let before = net
.transmissions
.iter()
.filter(|(from, _, id)| from == sender && id == message_id)
.count();
net.node(sender).flush_outbox_all();
net.step();
let after = net
.transmissions
.iter()
.filter(|(from, _, id)| from == sender && id == message_id)
.count();
assert_eq!(
after, before,
"{sender} still holds {message_id} in its outbox: a flush sent it again"
);
for carrier in other_carriers {
assert!(
!carrier
.sent_messages()
.iter()
.any(|m| m.id.as_str() == message_id),
"{sender} still holds {message_id} in its outbox: a flush put it on a carrier"
);
}
assert_eq!(
net.node(sender).retry_queue_size(),
0,
"{sender} still has {message_id} on its retry queue"
);
}
#[test]
fn a_message_lost_on_a_dead_direct_link_is_carried_by_the_mesh_on_retry() {
let mut alice_config = default_config("alice");
alice_config.reliability.ack.default_timeout_ms = 50;
alice_config.reliability.retry.initial_delay_ms = 10;
let mut net = Neighborhood::new(&["bob", "carol"]);
net.add_node("alice", alice_config);
net.link("alice", "bob");
net.link("bob", "carol");
net.link("alice", "carol");
let delivered = Arc::new(Mutex::new(Vec::<String>::new()));
let seen = Arc::clone(&delivered);
net.node("alice").on_event(move |event| {
if let Event::MessageDelivered { message_id, .. } = event {
seen.lock().unwrap().push(message_id);
}
});
let msg_id = net.send("alice", "carol", "around the dead link");
assert_eq!(
net.radios["alice"].sent_messages().len(),
1,
"the first send should have gone straight to carol"
);
assert!(net.radios["alice"].peer_sends().is_empty());
net.radios["alice"].clear_sent_messages();
net.radios["alice"].remove_connected_peer("carol");
net.radios["carol"].remove_connected_peer("alice");
net.node("alice").on_neighbor_lost("carol");
net.node("carol").on_neighbor_lost("alice");
net.links.get_mut("alice").unwrap().retain(|p| p != "carol");
net.links.get_mut("carol").unwrap().retain(|p| p != "alice");
for _ in 0..50 {
std::thread::sleep(std::time::Duration::from_millis(20));
net.step();
if !delivered.lock().unwrap().is_empty() {
break;
}
}
assert_eq!(
net.inbox("carol"),
vec!["around the dead link".to_string()],
"the resend must be carried through bob"
);
assert!(
net.transmissions
.iter()
.any(|(from, to, id)| from == "bob" && to == "carol" && id == &msg_id),
"bob must have carried it"
);
assert_eq!(
*delivered.lock().unwrap(),
vec![msg_id.clone()],
"and alice must learn it was delivered"
);
assert_settled(&mut net, "alice", &msg_id, &[]);
}
#[test]
fn a_flushed_message_no_carrier_takes_is_handed_to_the_neighbors() {
let mut net = Neighborhood::new(&["alice", "bob", "carol"]);
net.link("bob", "carol");
let delivered = watch_deliveries(&mut net, "alice");
let msg_id = net.send("alice", "carol", "carried on the flush");
assert_eq!(net.transmission_count(&msg_id), 0, "alice was alone");
net.link("alice", "bob");
net.node("alice").flush_outbox_all();
step_until_delivered(&mut net, &delivered, 12);
assert_eq!(
net.inbox("carol"),
vec!["carried on the flush".to_string()],
"the flush must hand the message to bob"
);
assert_eq!(
*delivered.lock().unwrap(),
vec![msg_id.clone()],
"carol's acknowledgement must settle the message the mesh carried"
);
assert_settled(&mut net, "alice", &msg_id, &[]);
}
#[test]
fn a_resend_a_carrier_swallows_is_still_handed_to_the_neighbors() {
let mut alice_config = default_config("alice");
alice_config.reliability.ack.default_timeout_ms = 50;
alice_config.reliability.retry.initial_delay_ms = 10;
let mut net = Neighborhood::new(&["bob", "carol"]);
net.add_node("alice", alice_config);
let swallowing = MockTransport::new(TransportType::WiFiDirect);
swallowing.start().unwrap();
net.node("alice")
.transport_manager_mut()
.add_transport(TransportType::WiFiDirect, Box::new(swallowing.clone()));
net.link("bob", "carol");
let delivered = watch_deliveries(&mut net, "alice");
let msg_id = net.send("alice", "carol", "past the swallowing carrier");
assert!(
swallowing
.sent_messages()
.iter()
.any(|m| m.id.as_str() == msg_id),
"the first attempt should have been swallowed"
);
assert_eq!(net.transmission_count(&msg_id), 0, "alice was alone");
net.link("alice", "bob");
for _ in 0..50 {
std::thread::sleep(std::time::Duration::from_millis(20));
net.step();
if !delivered.lock().unwrap().is_empty() {
break;
}
}
assert_eq!(
net.inbox("carol"),
vec!["past the swallowing carrier".to_string()],
"the swallowed resend must also be handed to bob"
);
assert_eq!(
*delivered.lock().unwrap(),
vec![msg_id.clone()],
"carol's acknowledgement must settle the message the mesh carried"
);
assert_settled(&mut net, "alice", &msg_id, &[&swallowing]);
}
#[test]
fn a_flush_a_carrier_swallows_is_still_handed_to_the_neighbors() {
let mut net = Neighborhood::new(&["alice", "bob", "carol"]);
net.link("bob", "carol");
let delivered = watch_deliveries(&mut net, "alice");
let msg_id = net.send("alice", "carol", "flushed past the swallowing carrier");
assert_eq!(net.transmission_count(&msg_id), 0, "alice was alone");
let swallowing = MockTransport::new(TransportType::WiFiDirect);
swallowing.start().unwrap();
net.node("alice")
.transport_manager_mut()
.add_transport(TransportType::WiFiDirect, Box::new(swallowing.clone()));
net.link("alice", "bob");
net.node("alice").flush_outbox_all();
assert!(
swallowing
.sent_messages()
.iter()
.any(|m| m.id.as_str() == msg_id),
"the flush should have gone to the carrier that accepts anyone"
);
step_until_delivered(&mut net, &delivered, 12);
assert_eq!(
net.inbox("carol"),
vec!["flushed past the swallowing carrier".to_string()],
"the swallowed flush must also be handed to bob"
);
assert_eq!(
*delivered.lock().unwrap(),
vec![msg_id.clone()],
"carol's acknowledgement must settle the message the mesh carried"
);
assert_settled(&mut net, "alice", &msg_id, &[&swallowing]);
}
#[test]
fn a_resend_over_a_live_link_is_not_also_handed_to_the_mesh() {
let mut alice_config = default_config("alice");
alice_config.reliability.ack.default_timeout_ms = 50;
alice_config.reliability.retry.initial_delay_ms = 10;
let mut net = Neighborhood::new(&["bob", "carol"]);
net.add_node("alice", alice_config);
net.link("alice", "bob");
net.link("alice", "carol");
let delivered = Arc::new(Mutex::new(Vec::<String>::new()));
let seen = Arc::clone(&delivered);
net.node("alice").on_event(move |event| {
if let Event::MessageDelivered { message_id, .. } = event {
seen.lock().unwrap().push(message_id);
}
});
let msg_id = net.send("alice", "carol", "say again");
net.step();
assert_eq!(net.inbox("carol"), vec!["say again".to_string()]);
net.node("carol").process().unwrap();
net.radios["carol"].clear_sent_messages();
net.radios["carol"].clear_peer_sends();
for _ in 0..50 {
std::thread::sleep(std::time::Duration::from_millis(20));
net.step();
if !delivered.lock().unwrap().is_empty() {
break;
}
}
assert_eq!(*delivered.lock().unwrap(), vec![msg_id.clone()]);
assert_eq!(
net.deliveries_to("carol", &msg_id),
2,
"the first copy and one resend, nothing extra"
);
assert_eq!(net.deliveries_to("bob", &msg_id), 0);
}