use std::time::Duration;
use crate::Identity;
use super::{LanDiscovery, LanDiscoveryConfig, LanEvent};
fn isolated_service_type(tag: &str) -> String {
let rand: u32 = rand::random();
format!("_fipstest-{tag}-{rand:08x}._udp.local.")
}
fn config_for(service_type: String) -> LanDiscoveryConfig {
LanDiscoveryConfig {
enabled: true,
service_type,
}
}
async fn wait_for_peer(
discovery: &LanDiscovery,
expected_npub: &str,
timeout: Duration,
) -> Option<super::LanDiscoveredPeer> {
let deadline = tokio::time::Instant::now() + timeout;
while tokio::time::Instant::now() < deadline {
for event in discovery.drain_events().await {
let LanEvent::Discovered(peer) = event;
if peer.npub == expected_npub {
return Some(peer);
}
}
tokio::time::sleep(Duration::from_millis(100)).await;
}
None
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn isolated_service_types_do_not_cross_feed() {
let identity_a = Identity::generate();
let identity_b = Identity::generate();
let service_a = isolated_service_type("isolated-a");
let service_b = isolated_service_type("isolated-b");
let lan_a = LanDiscovery::start(
&identity_a,
Some("scope-x".to_string()),
61001,
config_for(service_a.clone()),
)
.await
.expect("start a");
let lan_b = LanDiscovery::start(
&identity_b,
Some("scope-x".to_string()),
61002,
config_for(service_b.clone()),
)
.await
.expect("start b");
tokio::time::sleep(Duration::from_secs(2)).await;
let saw_b_from_a = wait_for_peer(
&lan_a,
identity_b.npub().as_str(),
Duration::from_millis(500),
)
.await
.is_some();
let saw_a_from_b = wait_for_peer(
&lan_b,
identity_a.npub().as_str(),
Duration::from_millis(500),
)
.await
.is_some();
lan_a.shutdown().await;
lan_b.shutdown().await;
assert!(!saw_b_from_a, "isolated service types must not cross-feed");
assert!(!saw_a_from_b, "isolated service types must not cross-feed");
}
#[ignore]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn matched_scope_peers_observe_each_other() {
let identity_a = Identity::generate();
let identity_b = Identity::generate();
let service = isolated_service_type("matched");
let lan_a = LanDiscovery::start(
&identity_a,
Some("scope-shared".to_string()),
61101,
config_for(service.clone()),
)
.await
.expect("start a");
let lan_b = LanDiscovery::start(
&identity_b,
Some("scope-shared".to_string()),
61102,
config_for(service.clone()),
)
.await
.expect("start b");
let observed_b =
wait_for_peer(&lan_a, identity_b.npub().as_str(), Duration::from_secs(10)).await;
let observed_a =
wait_for_peer(&lan_b, identity_a.npub().as_str(), Duration::from_secs(10)).await;
lan_a.shutdown().await;
lan_b.shutdown().await;
let observed_b = observed_b.expect("a must see b");
let observed_a = observed_a.expect("b must see a");
assert_eq!(observed_b.scope.as_deref(), Some("scope-shared"));
assert_eq!(observed_a.scope.as_deref(), Some("scope-shared"));
assert_eq!(observed_b.addr.port(), 61102);
assert_eq!(observed_a.addr.port(), 61101);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn cross_scope_advert_is_filtered() {
let identity_a = Identity::generate();
let identity_b = Identity::generate();
let service = isolated_service_type("cross-scope");
let lan_a = LanDiscovery::start(
&identity_a,
Some("scope-a".to_string()),
61201,
config_for(service.clone()),
)
.await
.expect("start a");
let lan_b = LanDiscovery::start(
&identity_b,
Some("scope-b".to_string()),
61202,
config_for(service.clone()),
)
.await
.expect("start b");
tokio::time::sleep(Duration::from_secs(3)).await;
let saw_b = wait_for_peer(
&lan_a,
identity_b.npub().as_str(),
Duration::from_millis(500),
)
.await;
lan_a.shutdown().await;
lan_b.shutdown().await;
assert!(saw_b.is_none(), "cross-scope advert must be filtered");
}