#![cfg(all(feature = "net", feature = "nat-traversal"))]
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use net::adapter::net::behavior::capability::CapabilitySet;
use net::adapter::net::traversal::classify::NatClass;
use net::adapter::net::traversal::TraversalError;
use net::adapter::net::{EntityKeypair, MeshNode, MeshNodeConfig, SocketBufferConfig};
const TEST_BUFFER_SIZE: usize = 256 * 1024;
const PSK: [u8; 32] = [0x42u8; 32];
fn test_config() -> MeshNodeConfig {
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
let mut cfg = MeshNodeConfig::new(addr, PSK)
.with_heartbeat_interval(Duration::from_millis(200))
.with_session_timeout(Duration::from_secs(5))
.with_handshake(4, Duration::from_secs(4));
cfg.socket_buffers = SocketBufferConfig {
send_buffer_size: TEST_BUFFER_SIZE,
recv_buffer_size: TEST_BUFFER_SIZE,
};
cfg
}
async fn build_node() -> Arc<MeshNode> {
Arc::new(
MeshNode::new(EntityKeypair::generate(), test_config())
.await
.expect("MeshNode::new"),
)
}
async fn connect_pair(a: &Arc<MeshNode>, b: &Arc<MeshNode>) {
let a_id = a.node_id();
let b_pub = *b.public_key();
let b_addr = b.local_addr();
let b_id = b.node_id();
let b_clone = b.clone();
let accept = tokio::spawn(async move { b_clone.accept(a_id).await });
a.connect(b_addr, &b_pub, b_id)
.await
.expect("connect failed");
accept
.await
.expect("accept task panicked")
.expect("accept failed");
}
async fn wait_for<F: Fn() -> bool>(limit: Duration, check: F) -> bool {
let start = tokio::time::Instant::now();
while start.elapsed() < limit {
if check() {
return true;
}
tokio::time::sleep(Duration::from_millis(50)).await;
}
check()
}
const PHANTOM_TARGET: u64 = 0xDEAD_BEEF_0000_9999;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn select_prefers_relay_capable_over_lower_id_peer() {
let a = build_node().await;
let p1 = build_node().await;
let p2 = build_node().await;
let (relay_peer, plain_peer) = if p1.node_id() > p2.node_id() {
(p1, p2)
} else {
(p2, p1)
};
assert!(
relay_peer.node_id() > plain_peer.node_id(),
"precondition: relay-capable peer has the higher node id",
);
connect_pair(&a, &relay_peer).await;
connect_pair(&a, &plain_peer).await;
a.start();
relay_peer.start();
plain_peer.start();
relay_peer
.announce_capabilities(CapabilitySet::new().with_relay_capable())
.await
.expect("relay peer announce");
plain_peer
.announce_capabilities(CapabilitySet::new())
.await
.expect("plain peer announce");
let relay_id = relay_peer.node_id();
let a_poll = a.clone();
let stable_relay =
|| (0..20).all(|_| a_poll.select_punch_coordinator(PHANTOM_TARGET) == Some(relay_id));
assert!(
wait_for(Duration::from_secs(3), stable_relay).await,
"A should stably pick the relay-capable peer as coordinator; got {:?}",
a.select_punch_coordinator(PHANTOM_TARGET),
);
for _ in 0..20 {
assert_ne!(
a.select_punch_coordinator(PHANTOM_TARGET),
Some(plain_peer.node_id()),
"must not fall back to the lower-id plain peer once relay-capable is known",
);
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn select_falls_back_to_any_mutual_peer() {
let a = build_node().await;
let q1 = build_node().await;
let q2 = build_node().await;
connect_pair(&a, &q1).await;
connect_pair(&a, &q2).await;
a.start();
q1.start();
q2.start();
let candidates = [q1.node_id(), q2.node_id()];
let picked = a
.select_punch_coordinator(PHANTOM_TARGET)
.expect("tier 3 should yield a mutual coordinator");
assert!(
candidates.contains(&picked),
"tier 3 must return one of the mutual peers; got {picked:#x}",
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn select_spreads_across_equal_candidates() {
let a = build_node().await;
let peers = [
build_node().await,
build_node().await,
build_node().await,
build_node().await,
];
for p in &peers {
connect_pair(&a, p).await;
p.start();
}
a.start();
let valid: std::collections::HashSet<u64> = peers.iter().map(|p| p.node_id()).collect();
let mut seen = std::collections::HashSet::new();
for _ in 0..200 {
let picked = a
.select_punch_coordinator(PHANTOM_TARGET)
.expect("a mutual coordinator should be selected");
assert!(
valid.contains(&picked),
"selection must stay within the mutual-peer set; got {picked:#x}",
);
seen.insert(picked);
}
assert!(
seen.len() > 1,
"coordinator selection should spread across candidates, but every \
one of 200 calls returned the same node ({seen:?})",
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn no_coordinator_yields_none_and_rendezvous_no_relay() {
let a = build_node().await;
a.start();
assert_eq!(
a.select_punch_coordinator(PHANTOM_TARGET),
None,
"a peerless node has no coordinator candidate",
);
a.force_nat_class_for_test(NatClass::Cone);
let dummy_pubkey = [7u8; 32];
let result = a.connect_direct_auto(PHANTOM_TARGET, &dummy_pubkey).await;
match result {
Err(TraversalError::RendezvousNoRelay) => {}
other => panic!("expected RendezvousNoRelay with no coordinator, got {other:?}"),
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn connect_direct_auto_open_pair_needs_no_coordinator() {
let a = build_node().await;
let b = build_node().await;
let x = build_node().await;
connect_pair(&a, &x).await;
connect_pair(&b, &x).await;
let y = build_node().await;
connect_pair(&a, &y).await;
connect_pair(&b, &y).await;
a.start();
b.start();
x.start();
y.start();
a.reclassify_nat().await;
b.reclassify_nat().await;
a.announce_capabilities(CapabilitySet::new())
.await
.expect("A announce");
b.announce_capabilities(CapabilitySet::new())
.await
.expect("B announce");
let b_id = b.node_id();
let b_bind = b.local_addr();
let a_poll = a.clone();
assert!(
wait_for(Duration::from_secs(3), || {
a_poll.peer_reflex_addr(b_id) == Some(b_bind)
})
.await,
"A should see B's reflex before connecting",
);
let b_pub = *b.public_key();
let sid = a
.connect_direct_auto(b_id, &b_pub)
.await
.expect("connect_direct_auto should succeed for an Open pair");
assert_eq!(sid, b_id, "returns the peer's node_id");
assert_eq!(
a.peer_addr(b_id),
Some(b_bind),
"Direct auto path resolves on B's reflex",
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn routed_peer_is_never_a_coordinator_candidate() {
let a = build_node().await;
let r = build_node().await;
let p = build_node().await;
connect_pair(&a, &r).await;
connect_pair(&p, &r).await;
a.start();
r.start();
p.start();
p.announce_capabilities(CapabilitySet::new().with_relay_capable())
.await
.expect("P announce");
let p_id = p.node_id();
let a_poll = a.clone();
assert!(
wait_for(Duration::from_secs(5), || {
a_poll.test_capability_fold_has(p_id)
})
.await,
"P's announcement should reach A's index via R",
);
let r_bind = r.local_addr();
let p_pub = *p.public_key();
a.connect_via(r_bind, &p_pub, p_id)
.await
.expect("relay-routed connect_via");
assert_eq!(
a.peer_addr(p_id),
Some(r_bind),
"precondition: A's session to P rides the relay",
);
let selected = a.select_punch_coordinator(PHANTOM_TARGET);
assert_ne!(
selected,
Some(p_id),
"a relay-routed peer must never be selected as coordinator",
);
assert_eq!(
selected,
Some(r.node_id()),
"the only direct peer (R) should win tier 3",
);
}