use std::collections::HashMap;
use std::sync::{Arc, Mutex, MutexGuard};
use std::time::Duration;
use anyhow::Result;
use mcpmesh_net::SessionTransport;
use mcpmesh_net::framing::MAX_FRAME_BYTES;
type Connection = iroh::endpoint::Connection;
type LeadOutcome = tokio::sync::watch::Receiver<Option<Arc<str>>>;
pub(crate) const REUSE_OPEN_TIMEOUT: Duration = Duration::from_secs(1);
enum Slot {
Live(iroh::endpoint::WeakConnectionHandle),
Dialing(LeadOutcome),
}
#[derive(Clone, Default)]
pub(crate) struct McpConnCache {
inner: Arc<Mutex<HashMap<[u8; 32], Slot>>>,
}
pub(crate) enum Opened {
Reused(SessionTransport),
Fresh(SessionTransport, Connection),
}
enum Stream {
Open(SessionTransport),
Stale,
Saturated,
}
enum Claim {
Live([u8; 32], Connection),
Wait(LeadOutcome),
Lead(DialLead),
}
impl McpConnCache {
pub(crate) fn new() -> Self {
Self::default()
}
fn map(&self) -> MutexGuard<'_, HashMap<[u8; 32], Slot>> {
self.inner
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
#[cfg(test)]
pub(crate) fn live(&self, peer: [u8; 32]) -> Option<Connection> {
let mut map = self.map();
match map.get(&peer) {
Some(Slot::Live(weak)) => match open(weak) {
Some(conn) => Some(conn),
None => {
map.remove(&peer);
None
}
},
_ => None,
}
}
async fn open_on(&self, peer: [u8; 32], conn: &Connection) -> Stream {
match tokio::time::timeout(REUSE_OPEN_TIMEOUT, conn.open_bi()).await {
Ok(Ok((send, recv))) => {
Stream::Open(SessionTransport::new(recv, send, MAX_FRAME_BYTES))
}
Ok(Err(e)) => {
tracing::debug!(%e, "cached mesh connection refused a new stream; dialling fresh");
let mut map = self.map();
if let Some(Slot::Live(weak)) = map.get(&peer)
&& weak
.upgrade()
.is_some_and(|c| c.stable_id() == conn.stable_id())
{
map.remove(&peer);
}
Stream::Stale
}
Err(_) => {
tracing::debug!(
"cached mesh connection has no stream credit; this session dials its own"
);
Stream::Saturated
}
}
}
fn claim(&self, peers: &[[u8; 32]]) -> Claim {
let mut map = self.map();
for peer in peers {
if let Some(Slot::Live(weak)) = map.get(peer) {
match open(weak) {
Some(conn) => return Claim::Live(*peer, conn),
None => {
map.remove(peer);
}
}
}
}
for peer in peers {
if let Some(Slot::Dialing(rx)) = map.get(peer) {
return Claim::Wait(rx.clone());
}
}
let (tx, rx) = tokio::sync::watch::channel(None);
for peer in peers {
map.insert(*peer, Slot::Dialing(rx.clone()));
}
Claim::Lead(DialLead {
cache: self.clone(),
peers: peers.to_vec(),
settled: false,
tx,
})
}
pub(crate) async fn session_on<F, Fut, R, RFut>(
&self,
peers: &[[u8; 32]],
deadline: Duration,
refused: R,
dial: F,
) -> Result<Opened>
where
F: FnOnce() -> Fut,
Fut: std::future::Future<Output = Result<(SessionTransport, Connection)>>,
R: Fn([u8; 32]) -> RFut,
RFut: std::future::Future<Output = Result<bool>>,
{
let mut leader_failure: Option<Arc<str>> = None;
let claimed = self.claim_loop(peers, refused, dial, &mut leader_failure);
match tokio::time::timeout(deadline, claimed).await {
Ok(opened) => opened,
Err(_) => match leader_failure {
Some(why) => anyhow::bail!(
"dial timed out after {deadline:?}; the dial this session was waiting on had \
already failed: {why}"
),
None => anyhow::bail!("dial timed out after {deadline:?}"),
},
}
}
async fn claim_loop<F, Fut, R, RFut>(
&self,
peers: &[[u8; 32]],
refused: R,
dial: F,
leader_failure: &mut Option<Arc<str>>,
) -> Result<Opened>
where
F: FnOnce() -> Fut,
Fut: std::future::Future<Output = Result<(SessionTransport, Connection)>>,
R: Fn([u8; 32]) -> RFut,
RFut: std::future::Future<Output = Result<bool>>,
{
let mut dial = Some(dial);
let mut peers = peers.to_vec();
loop {
match self.claim(&peers) {
Claim::Live(peer, conn) => {
match refused(peer).await {
Ok(false) => {}
Ok(true) => {
peers.retain(|p| *p != peer);
anyhow::ensure!(
!peers.is_empty(),
"the device this session would reuse a connection to is REVOKED \
on this node"
);
continue;
}
Err(e) => {
return Err(e.context(
"the revocation check failed, so this session does not reuse the \
cached connection",
));
}
}
match self.open_on(peer, &conn).await {
Stream::Open(t) => return Ok(Opened::Reused(t)),
Stream::Stale => {}
Stream::Saturated => {
let dial = dial.take().expect("a caller dials at most once");
let (transport, conn) = dial().await?;
return Ok(Opened::Fresh(transport, conn));
}
}
}
Claim::Wait(mut rx) => {
if rx.changed().await.is_ok() {
leader_failure.clone_from(&rx.borrow());
}
}
Claim::Lead(lead) => {
let dial = dial.take().expect("a caller dials at most once");
return match dial().await {
Ok((transport, conn)) => {
lead.settle(&conn);
Ok(Opened::Fresh(transport, conn))
}
Err(e) => {
lead.fail(&e);
Err(e)
}
};
}
}
}
}
}
fn open(weak: &iroh::endpoint::WeakConnectionHandle) -> Option<Connection> {
weak.upgrade().filter(|c| c.close_reason().is_none())
}
struct DialLead {
cache: McpConnCache,
peers: Vec<[u8; 32]>,
settled: bool,
tx: tokio::sync::watch::Sender<Option<Arc<str>>>,
}
impl DialLead {
fn settle(mut self, conn: &Connection) {
let winner = *conn.remote_id().as_bytes();
{
let mut map = self.cache.map();
for peer in &self.peers {
if *peer == winner {
map.insert(*peer, Slot::Live(conn.weak_handle()));
} else if matches!(map.get(peer), Some(Slot::Dialing(_))) {
map.remove(peer);
}
}
}
self.settled = true;
}
fn fail(self, e: &anyhow::Error) {
self.tx.send_replace(Some(Arc::from(format!("{e:#}"))));
}
}
impl Drop for DialLead {
fn drop(&mut self) {
if self.settled {
return;
}
let mut map = match self.cache.inner.lock() {
Ok(map) => map,
Err(poisoned) => poisoned.into_inner(),
};
for peer in &self.peers {
if matches!(map.get(peer), Some(Slot::Dialing(_))) {
map.remove(peer);
}
}
}
}
#[cfg(test)]
pub(crate) mod testpeer {
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use mcpmesh_net::registry::ConnRegistry;
use mcpmesh_net::{
ALPN_MCP, ALPN_PING, LiveServices, ServiceEntry, ServiceKind, Services, SessionBackend,
SessionTransport, TrustGate,
};
use crate::allowlist::{AllowlistGate, PeerEntry, PeerStore};
pub(crate) struct LoopbackPeer {
pub(crate) id: [u8; 32],
pub(crate) addr: iroh::EndpointAddr,
pub(crate) mcp_accepts: Arc<AtomicUsize>,
pub(crate) ping_accepts: Arc<AtomicUsize>,
pub(crate) registry: Arc<ConnRegistry>,
_accept: tokio::task::JoinHandle<()>,
}
struct Echo;
#[async_trait::async_trait]
impl SessionBackend for Echo {
async fn run(
&self,
_identity: Option<mcpmesh_net::PeerIdentity>,
initialize: serde_json::Value,
mut transport: SessionTransport,
) -> anyhow::Result<()> {
transport.send_value(initialize).await?;
while let Ok(Some(f)) = transport.recv_value().await {
transport.send_value(f).await?;
}
let _ = transport.shutdown().await;
Ok(())
}
}
pub(crate) async fn loopback_peer(
dir: &std::path::Path,
seed: u8,
dialer: [u8; 32],
services: &[(&str, &[&str])],
) -> LoopbackPeer {
loopback_peer_with(dir, seed, dialer, services, None, false).await
}
pub(crate) async fn loopback_peer_with(
dir: &std::path::Path,
seed: u8,
dialer: [u8; 32],
services: &[(&str, &[&str])],
max_bidi: Option<u32>,
throttle_ping: bool,
) -> LoopbackPeer {
let store = Arc::new(PeerStore::open(&dir.join(format!("peer-{seed}.redb"))).unwrap());
store
.add(PeerEntry {
endpoint_id: dialer,
nickname: "dialer".into(),
services: vec![],
paired_at: None,
user_id: None,
last_addr: None,
})
.unwrap();
let gate: Arc<dyn TrustGate> = Arc::new(AllowlistGate::new(store));
let services = Arc::new(LiveServices::new(Arc::new(Services::new(
services
.iter()
.map(|(name, allow)| {
(
(*name).to_string(),
ServiceEntry {
backend: Arc::new(Echo),
allow: allow.iter().map(|a| (*a).to_string()).collect(),
kind: ServiceKind::Run,
ephemeral: false,
},
)
})
.collect(),
))));
let hermetic = crate::config::NetworkCfg {
relay_mode: "disabled".into(),
..Default::default()
};
let endpoint = match max_bidi {
None => crate::daemon::boot::build_endpoint(
iroh::SecretKey::from_bytes(&[seed; 32]),
&hermetic,
false,
None,
)
.await
.unwrap(),
Some(n) => iroh::Endpoint::builder(iroh::endpoint::presets::Minimal)
.relay_mode(iroh::RelayMode::Disabled)
.secret_key(iroh::SecretKey::from_bytes(&[seed; 32]))
.alpns(vec![ALPN_MCP.to_vec(), ALPN_PING.to_vec()])
.transport_config(
iroh::endpoint::QuicTransportConfig::builder()
.max_concurrent_bidi_streams(iroh::endpoint::VarInt::from_u32(n))
.build(),
)
.bind()
.await
.unwrap(),
};
let id = *endpoint.id().as_bytes();
let addr = endpoint.addr();
let mcp_accepts = Arc::new(AtomicUsize::new(0));
let ping_accepts = Arc::new(AtomicUsize::new(0));
let registry = Arc::new(ConnRegistry::new());
let accept = {
let (endpoint, mcp, ping, registry) = (
endpoint.clone(),
mcp_accepts.clone(),
ping_accepts.clone(),
registry.clone(),
);
tokio::spawn(async move {
while let Some(incoming) = endpoint.accept().await {
let Ok(conn) = incoming.await else { continue };
let alpn = conn.alpn().to_vec();
if alpn == ALPN_MCP {
mcp.fetch_add(1, Ordering::SeqCst);
let (gate, services, registry) =
(gate.clone(), services.clone(), registry.clone());
tokio::spawn(mcpmesh_net::run_mesh_connection(
conn, gate, services, registry,
));
} else if alpn == ALPN_PING && throttle_ping {
ping.fetch_add(1, Ordering::SeqCst);
conn.close(0u32.into(), crate::daemon::reach::PING_THROTTLE_CLOSE);
} else if alpn == ALPN_PING {
ping.fetch_add(1, Ordering::SeqCst);
tokio::spawn(async move {
if let Ok((mut send, _recv)) = conn.accept_bi().await {
let pong = serde_json::json!({
"stack_version": "test",
"services": ["echo"],
});
if mcpmesh_net::framing::write_frame(&mut send, &pong)
.await
.is_ok()
{
let _ = send.finish();
let _ = send.stopped().await;
}
}
});
} else {
conn.close(0u32.into(), b"unexpected alpn");
}
}
})
};
LoopbackPeer {
id,
addr,
mcp_accepts,
ping_accepts,
registry,
_accept: accept,
}
}
pub(crate) async fn dialer_mesh(
dir: &std::path::Path,
peer: &LoopbackPeer,
) -> Arc<crate::daemon::MeshState> {
let cfg = dir.join("dialer.toml");
std::fs::write(&cfg, "").unwrap();
let mesh = crate::daemon::testutil::hermetic_hooked_mesh(cfg).await;
mesh.store
.add(PeerEntry {
endpoint_id: peer.id,
nickname: "bob".into(),
services: vec![],
paired_at: None,
user_id: None,
last_addr: Some(serde_json::to_string(&peer.addr).unwrap()),
})
.unwrap();
mesh
}
pub(crate) fn dialer_id() -> [u8; 32] {
*iroh::SecretKey::from_bytes(&[7u8; 32]).public().as_bytes()
}
pub(crate) fn dialer_principal() -> String {
mcpmesh_net::EndpointId::from_bytes(dialer_id()).principal()
}
pub(crate) async fn first_reply(t: &mut SessionTransport, service: &str) -> serde_json::Value {
let init = crate::daemon::dial::inject_service(
serde_json::json!({"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {}}),
service,
);
t.send_value(init).await.expect("send initialize");
tokio::time::timeout(std::time::Duration::from_secs(10), t.recv_value())
.await
.expect("a reply within 10s")
.expect("a readable reply")
.expect("a frame, not EOF")
}
pub(crate) async fn eventually(mut cond: impl FnMut() -> bool) -> bool {
for _ in 0..200 {
if cond() {
return true;
}
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
}
cond()
}
}
#[cfg(test)]
mod tests {
async fn never_refused(_: [u8; 32]) -> anyhow::Result<bool> {
Ok(false)
}
use std::sync::atomic::Ordering;
use super::testpeer::{
dialer_id, dialer_mesh, dialer_principal, eventually, first_reply, loopback_peer,
};
use crate::daemon::dial::dial_service;
#[tokio::test(flavor = "multi_thread")]
async fn two_sessions_to_one_peer_share_one_connection() {
let dir = tempfile::tempdir().unwrap();
let me = dialer_principal();
let peer = loopback_peer(dir.path(), 41, dialer_id(), &[("echo", &[me.as_str()])]).await;
let mesh = dialer_mesh(dir.path(), &peer).await;
let mut a = dial_service(&mesh, "bob", "echo").await.expect("session A");
let mut b = dial_service(&mesh, "bob", "echo").await.expect("session B");
let ra = first_reply(&mut a, "echo").await;
let rb = first_reply(&mut b, "echo").await;
assert_eq!(
ra["method"], "initialize",
"session A echoes on its own stream: {ra}"
);
assert_eq!(
rb["method"], "initialize",
"session B echoes on its own stream: {rb}"
);
assert_eq!(
peer.mcp_accepts.load(Ordering::SeqCst),
1,
"a second session to the same peer must ride the first session's connection"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn the_shared_connection_closes_when_its_last_session_ends() {
let dir = tempfile::tempdir().unwrap();
let me = dialer_principal();
let peer = loopback_peer(dir.path(), 42, dialer_id(), &[("echo", &[me.as_str()])]).await;
let mesh = dialer_mesh(dir.path(), &peer).await;
let a = dial_service(&mesh, "bob", "echo").await.expect("session A");
let b = dial_service(&mesh, "bob", "echo").await.expect("session B");
assert!(
eventually(|| peer.registry.len() == 1).await,
"precondition: the peer tracks the one shared connection"
);
drop(a);
assert!(
mesh.conn_cache.live(peer.id).is_some(),
"one session still holds the connection open"
);
drop(b);
assert!(
eventually(|| peer.registry.is_empty()).await,
"the peer must see the connection close once the last session ends — a strong handle \
in the cache keeps it open forever"
);
assert!(
mesh.conn_cache.live(peer.id).is_none(),
"and the cache must report no live connection"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn a_closed_connection_is_not_reused() {
let dir = tempfile::tempdir().unwrap();
let me = dialer_principal();
let peer = loopback_peer(dir.path(), 43, dialer_id(), &[("echo", &[me.as_str()])]).await;
let mesh = dialer_mesh(dir.path(), &peer).await;
let mut a = dial_service(&mesh, "bob", "echo").await.expect("session A");
assert!(eventually(|| peer.registry.len() == 1).await);
assert_eq!(
peer.registry
.sever_matching(401, b"test sever", |_, _| true),
1
);
let observed = tokio::time::timeout(std::time::Duration::from_secs(10), a.recv_value())
.await
.expect("the close reaches the dialer within 10s");
assert!(
!matches!(observed, Ok(Some(_))),
"precondition: session A's stream is dead: {observed:?}"
);
let mut b = dial_service(&mesh, "bob", "echo")
.await
.expect("a fresh dial after the peer closed the pooled connection");
assert_eq!(first_reply(&mut b, "echo").await["method"], "initialize");
assert_eq!(
peer.mcp_accepts.load(Ordering::SeqCst),
2,
"session B must be a NEW connection, not a stream on the closed one"
);
drop(a);
}
#[tokio::test(flavor = "multi_thread")]
async fn a_session_with_its_own_idle_timeout_keeps_its_own_connection() {
use crate::daemon::dial::dial_service_with_idle_timeout;
let dir = tempfile::tempdir().unwrap();
let me = dialer_principal();
let peer = loopback_peer(dir.path(), 44, dialer_id(), &[("echo", &[me.as_str()])]).await;
let mesh = dialer_mesh(dir.path(), &peer).await;
let secs = mesh.keep_alive_secs() + 25;
let mut timed = dial_service_with_idle_timeout(&mesh, "bob", "echo", Some(secs))
.await
.expect("timed session");
assert_eq!(
first_reply(&mut timed, "echo").await["method"],
"initialize"
);
assert_eq!(peer.mcp_accepts.load(Ordering::SeqCst), 1);
assert!(
mesh.conn_cache.live(peer.id).is_none(),
"a per-session connection must never enter the shared cache"
);
let mut plain = dial_service(&mesh, "bob", "echo")
.await
.expect("plain session");
assert_eq!(
first_reply(&mut plain, "echo").await["method"],
"initialize"
);
assert_eq!(
peer.mcp_accepts.load(Ordering::SeqCst),
2,
"a plain session must not join a connection carrying someone else's idle timeout"
);
let mut plain2 = dial_service(&mesh, "bob", "echo")
.await
.expect("second plain session");
assert_eq!(
first_reply(&mut plain2, "echo").await["method"],
"initialize"
);
assert_eq!(
peer.mcp_accepts.load(Ordering::SeqCst),
2,
"…but it does share the plain connection"
);
let mut timed2 = dial_service_with_idle_timeout(&mesh, "bob", "echo", Some(secs))
.await
.expect("second timed session");
assert_eq!(
first_reply(&mut timed2, "echo").await["method"],
"initialize"
);
assert_eq!(
peer.mcp_accepts.load(Ordering::SeqCst),
3,
"and a second timed session dials its own connection rather than reusing the plain one"
);
drop((timed, plain, plain2, timed2));
}
#[tokio::test(flavor = "multi_thread")]
async fn simultaneous_first_dials_share_one_connection() {
use crate::daemon::dial::{DIAL_TIMEOUT, connect_with_timeout};
let dir = tempfile::tempdir().unwrap();
let me = dialer_principal();
let peer = loopback_peer(dir.path(), 45, dialer_id(), &[("echo", &[me.as_str()])]).await;
let mesh = dialer_mesh(dir.path(), &peer).await;
let (release, gate) = tokio::sync::oneshot::channel::<()>();
let follower_dials = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
let leader = {
let (mesh, addr) = (mesh.clone(), peer.addr.clone());
let endpoint = mesh.endpoint.clone();
tokio::spawn(async move {
mesh.conn_cache
.session_on(&[peer.id], DIAL_TIMEOUT, never_refused, || async move {
let _ = gate.await;
connect_with_timeout(&endpoint, addr, "echo", DIAL_TIMEOUT, None).await
})
.await
})
};
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
let follower = {
let (mesh, addr, dials) = (mesh.clone(), peer.addr.clone(), follower_dials.clone());
let endpoint = mesh.endpoint.clone();
tokio::spawn(async move {
mesh.conn_cache
.session_on(&[peer.id], DIAL_TIMEOUT, never_refused, || async move {
dials.fetch_add(1, Ordering::SeqCst);
connect_with_timeout(&endpoint, addr, "echo", DIAL_TIMEOUT, None).await
})
.await
})
};
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
assert_eq!(
peer.mcp_accepts.load(Ordering::SeqCst),
0,
"nothing is dialled while the leader's dial is parked — the follower must be waiting"
);
release.send(()).unwrap();
let (l, f) = tokio::join!(leader, follower);
let l = l.unwrap().expect("leader session");
let f = f.unwrap().expect("follower session");
assert!(matches!(l, super::Opened::Fresh(..)), "the leader dialled");
let mut f = match f {
super::Opened::Reused(t) => t,
super::Opened::Fresh(..) => panic!("the follower must reuse, not dial"),
};
assert_eq!(first_reply(&mut f, "echo").await["method"], "initialize");
assert_eq!(
follower_dials.load(Ordering::SeqCst),
0,
"the follower never dialled"
);
assert_eq!(peer.mcp_accepts.load(Ordering::SeqCst), 1);
}
#[tokio::test(flavor = "multi_thread")]
async fn a_failed_leader_releases_its_waiters() {
use crate::daemon::dial::{DIAL_TIMEOUT, connect_with_timeout};
let dir = tempfile::tempdir().unwrap();
let me = dialer_principal();
let peer = loopback_peer(dir.path(), 46, dialer_id(), &[("echo", &[me.as_str()])]).await;
let mesh = dialer_mesh(dir.path(), &peer).await;
let (release, gate) = tokio::sync::oneshot::channel::<()>();
let leader = {
let mesh = mesh.clone();
tokio::spawn(async move {
mesh.conn_cache
.session_on(&[peer.id], DIAL_TIMEOUT, never_refused, || async move {
let _ = gate.await;
anyhow::bail!("leader's dial failed")
})
.await
})
};
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
let follower = {
let (mesh, addr) = (mesh.clone(), peer.addr.clone());
let endpoint = mesh.endpoint.clone();
tokio::spawn(async move {
tokio::time::timeout(
std::time::Duration::from_secs(10),
mesh.conn_cache.session_on(
&[peer.id],
DIAL_TIMEOUT,
never_refused,
|| async move {
connect_with_timeout(&endpoint, addr, "echo", DIAL_TIMEOUT, None).await
},
),
)
.await
})
};
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
release.send(()).unwrap();
assert!(leader.await.unwrap().is_err());
let f = follower
.await
.unwrap()
.expect("the follower must be woken when the leader fails")
.expect("and then dial for itself");
let mut f = match f {
super::Opened::Fresh(t, _) => t,
super::Opened::Reused(_) => panic!("nothing was live to reuse"),
};
assert_eq!(first_reply(&mut f, "echo").await["method"], "initialize");
assert_eq!(peer.mcp_accepts.load(Ordering::SeqCst), 1);
}
#[tokio::test(flavor = "multi_thread")]
async fn a_cancelled_leader_releases_its_waiters() {
use crate::daemon::dial::{DIAL_TIMEOUT, connect_with_timeout};
let dir = tempfile::tempdir().unwrap();
let me = dialer_principal();
let peer = loopback_peer(dir.path(), 51, dialer_id(), &[("echo", &[me.as_str()])]).await;
let mesh = dialer_mesh(dir.path(), &peer).await;
let leader = {
let mesh = mesh.clone();
tokio::spawn(async move {
mesh.conn_cache
.session_on(&[peer.id], DIAL_TIMEOUT, never_refused, || async move {
std::future::pending::<()>().await; unreachable!("a parked dial never completes")
})
.await
})
};
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
let follower = {
let (mesh, addr) = (mesh.clone(), peer.addr.clone());
let endpoint = mesh.endpoint.clone();
tokio::spawn(async move {
tokio::time::timeout(
std::time::Duration::from_secs(10),
mesh.conn_cache.session_on(
&[peer.id],
DIAL_TIMEOUT,
never_refused,
|| async move {
connect_with_timeout(&endpoint, addr, "echo", DIAL_TIMEOUT, None).await
},
),
)
.await
})
};
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
leader.abort();
match leader.await {
Err(e) => assert!(e.is_cancelled()),
Ok(_) => panic!("the parked leader cannot have completed"),
}
let f = follower
.await
.unwrap()
.expect("the follower must be woken when the leader is cancelled")
.expect("and then dial for itself");
let mut f = match f {
super::Opened::Fresh(t, _) => t,
super::Opened::Reused(_) => panic!("nothing was live to reuse"),
};
assert_eq!(first_reply(&mut f, "echo").await["method"], "initialize");
assert_eq!(peer.mcp_accepts.load(Ordering::SeqCst), 1);
}
#[tokio::test(flavor = "multi_thread")]
async fn a_second_stream_on_a_shared_connection_is_still_authorized_per_stream() {
let dir = tempfile::tempdir().unwrap();
let me = dialer_principal();
let peer = loopback_peer(
dir.path(),
47,
dialer_id(),
&[("echo", &[me.as_str()]), ("private", &["eid:nobody"])],
)
.await;
let mesh = dialer_mesh(dir.path(), &peer).await;
let mut ok = dial_service(&mesh, "bob", "echo")
.await
.expect("granted session");
let reply = first_reply(&mut ok, "echo").await;
assert_eq!(
reply["method"], "initialize",
"the granted service answers: {reply}"
);
let mut refused = dial_service(&mesh, "bob", "private")
.await
.expect("stream opens");
let reply = first_reply(&mut refused, "private").await;
assert_eq!(
reply["error"]["code"],
mcpmesh_net::errors::ERR_SERVICE,
"an ungranted service on a SHARED connection must still be refused: {reply}"
);
assert_eq!(
peer.mcp_accepts.load(Ordering::SeqCst),
1,
"and it was the shared connection that refused it"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn a_raced_dial_reuses_and_records_the_winner() {
let dir = tempfile::tempdir().unwrap();
let me = dialer_principal();
let peer = loopback_peer(dir.path(), 48, dialer_id(), &[("echo", &[me.as_str()])]).await;
let mesh = dialer_mesh(dir.path(), &peer).await;
let dead = *iroh::SecretKey::from_bytes(&[49u8; 32]).public().as_bytes();
assert!(mesh.store.remove("bob").unwrap());
for (eid, nick, hint) in [
(dead, "bob-dead", None),
(
peer.id,
"bob-live",
Some(serde_json::to_string(&peer.addr).unwrap()),
),
] {
mesh.store
.add(crate::allowlist::PeerEntry {
endpoint_id: eid,
nickname: nick.into(),
services: vec![],
paired_at: None,
user_id: Some("b64u:bob".into()),
last_addr: hint,
})
.unwrap();
}
let mut a = dial_service(&mesh, "b64u:bob", "echo")
.await
.expect("raced session");
assert_eq!(first_reply(&mut a, "echo").await["method"], "initialize");
assert_eq!(peer.mcp_accepts.load(Ordering::SeqCst), 1);
let mut b = dial_service(&mesh, "b64u:bob", "echo")
.await
.expect("second session");
assert_eq!(first_reply(&mut b, "echo").await["method"], "initialize");
assert_eq!(
peer.mcp_accepts.load(Ordering::SeqCst),
1,
"the race's winner must be recorded and reused — racing again is a second connection"
);
drop((a, b));
}
async fn two_device_person(
dir: &std::path::Path,
seeds: (u8, u8),
) -> (
super::testpeer::LoopbackPeer,
super::testpeer::LoopbackPeer,
std::sync::Arc<crate::daemon::MeshState>,
) {
let me = dialer_principal();
let x = loopback_peer(dir, seeds.0, dialer_id(), &[("echo", &[me.as_str()])]).await;
let y = loopback_peer(dir, seeds.1, dialer_id(), &[("echo", &[me.as_str()])]).await;
let mesh = dialer_mesh(dir, &x).await;
assert!(mesh.store.remove("bob").unwrap());
for (p, nick) in [(&x, "bob-x"), (&y, "bob-y")] {
mesh.store
.add(crate::allowlist::PeerEntry {
endpoint_id: p.id,
nickname: nick.into(),
services: vec![],
paired_at: None,
user_id: Some("b64u:bob".into()),
last_addr: Some(serde_json::to_string(&p.addr).unwrap()),
})
.unwrap();
}
(x, y, mesh)
}
fn eid(peer: &super::testpeer::LoopbackPeer) -> String {
format!("eid:{}", data_encoding::HEXLOWER.encode(&peer.id))
}
#[tokio::test(flavor = "multi_thread")]
async fn a_raced_dial_never_reuses_a_connection_to_a_revoked_device() {
let dir = tempfile::tempdir().unwrap();
let (x, y, mesh) = two_device_person(dir.path(), (60, 61)).await;
let mut a = dial_service(&mesh, &eid(&x), "echo")
.await
.expect("session to X");
assert_eq!(first_reply(&mut a, "echo").await["method"], "initialize");
assert!(
mesh.conn_cache.live(x.id).is_some(),
"precondition: X is cached"
);
mesh.store
.revoke(crate::allowlist::RevokedEntry {
endpoint_id: x.id,
revoked_at: 0,
reason: None,
source: "local".into(),
signer_user_id: None,
issued_at: None,
})
.unwrap();
assert!(
mesh.conn_cache.live(x.id).is_some(),
"precondition: a bare store write leaves X's connection warm"
);
let mut b = dial_service(&mesh, "b64u:bob", "echo")
.await
.expect("the person is still reachable on Y");
assert_eq!(first_reply(&mut b, "echo").await["method"], "initialize");
assert_eq!(
y.mcp_accepts.load(Ordering::SeqCst),
1,
"the session must go to Y — reusing X hands the request to a revoked device"
);
assert_eq!(x.mcp_accepts.load(Ordering::SeqCst), 1, "X got nothing new");
drop(a);
}
#[tokio::test(flavor = "multi_thread")]
async fn a_session_that_waited_is_not_handed_a_connection_to_a_device_revoked_meanwhile() {
use crate::daemon::dial::{DIAL_TIMEOUT, connect_with_timeout};
let dir = tempfile::tempdir().unwrap();
let me = dialer_principal();
let peer = loopback_peer(dir.path(), 70, dialer_id(), &[("echo", &[me.as_str()])]).await;
let mesh = dialer_mesh(dir.path(), &peer).await;
let (release, gate) = tokio::sync::oneshot::channel::<()>();
let leader = {
let (mesh, addr) = (mesh.clone(), peer.addr.clone());
let endpoint = mesh.endpoint.clone();
tokio::spawn(async move {
mesh.conn_cache
.session_on(&[peer.id], DIAL_TIMEOUT, never_refused, || async move {
let opened =
connect_with_timeout(&endpoint, addr, "echo", DIAL_TIMEOUT, None).await;
let _ = gate.await;
opened
})
.await
})
};
assert!(
eventually(|| peer.mcp_accepts.load(Ordering::SeqCst) == 1).await,
"precondition: the leader's connection is up before the revoke"
);
let follower = {
let mesh = mesh.clone();
let target = eid(&peer);
tokio::spawn(async move { dial_service(&mesh, &target, "echo").await })
};
tokio::time::sleep(std::time::Duration::from_millis(300)).await;
mesh.store
.revoke(crate::allowlist::RevokedEntry {
endpoint_id: peer.id,
revoked_at: 0,
reason: None,
source: "local".into(),
signer_user_id: None,
issued_at: None,
})
.unwrap();
release.send(()).unwrap();
let _leader = leader
.await
.unwrap()
.expect("the leader's own dial predates the revoke");
let got = tokio::time::timeout(std::time::Duration::from_secs(10), follower)
.await
.expect("the follower resolves within 10s")
.unwrap();
let err = match got {
Ok(_) => panic!(
"a session that waited must not be handed a connection to a device revoked while \
it waited"
),
Err(e) => format!("{e:#}"),
};
assert!(err.contains("REVOKED"), "and it says why: {err}");
assert_eq!(
peer.mcp_accepts.load(Ordering::SeqCst),
1,
"nothing new reached the revoked device"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn a_failed_revocation_check_refuses_reuse_without_calling_the_device_revoked() {
use crate::daemon::dial::{DIAL_TIMEOUT, connect_with_timeout};
let dir = tempfile::tempdir().unwrap();
let me = dialer_principal();
let peer = loopback_peer(dir.path(), 71, dialer_id(), &[("echo", &[me.as_str()])]).await;
let mesh = dialer_mesh(dir.path(), &peer).await;
let (endpoint, addr) = (mesh.endpoint.clone(), peer.addr.clone());
let _first = mesh
.conn_cache
.session_on(&[peer.id], DIAL_TIMEOUT, never_refused, || async move {
connect_with_timeout(&endpoint, addr, "echo", DIAL_TIMEOUT, None).await
})
.await
.expect("first session caches a connection");
let err = match mesh
.conn_cache
.session_on(
&[peer.id],
DIAL_TIMEOUT,
|_| async { Err(anyhow::anyhow!("join error")) },
|| async { unreachable!("a live connection is claimed, nothing dials") },
)
.await
{
Ok(_) => panic!("a failed check must refuse the reuse"),
Err(e) => format!("{e:#}"),
};
assert!(err.contains("revocation check failed"), "{err}");
assert!(!err.contains("REVOKED"), "must not claim revocation: {err}");
}
#[tokio::test(flavor = "multi_thread")]
async fn peer_revoke_closes_this_nodes_own_connection_to_the_device() {
let dir = tempfile::tempdir().unwrap();
let (x, _y, mesh) = two_device_person(dir.path(), (62, 63)).await;
let state = crate::control::DaemonState::with_mesh("test", mesh.clone());
let mut a = dial_service(&mesh, &eid(&x), "echo")
.await
.expect("session to X");
assert_eq!(first_reply(&mut a, "echo").await["method"], "initialize");
assert!(eventually(|| x.registry.len() == 1).await);
crate::daemon::handlers::peer_revoke(
&state,
mcpmesh_local_api::PeerRevokeParams {
peer: "bob-x".into(),
reason: None,
},
)
.await
.expect("revoke");
assert!(
eventually(|| x.registry.is_empty()).await,
"the revoked device must see our session connection close — a warm connection to it \
is exactly what the next dial would otherwise be handed"
);
assert!(
mesh.conn_cache.live(x.id).is_none(),
"and the cache forgot it"
);
let observed = tokio::time::timeout(std::time::Duration::from_secs(10), a.recv_value())
.await
.expect("the close reaches session A within 10s");
assert!(
!matches!(observed, Ok(Some(_))),
"session A is ended by the revoke: {observed:?}"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn a_saturated_shared_connection_falls_back_to_a_fresh_connection() {
use super::testpeer::loopback_peer_with;
let dir = tempfile::tempdir().unwrap();
let me = dialer_principal();
let peer = loopback_peer_with(
dir.path(),
64,
dialer_id(),
&[("echo", &[me.as_str()])],
Some(1),
false,
)
.await;
let mesh = dialer_mesh(dir.path(), &peer).await;
let bound = std::time::Duration::from_secs(10);
let mut a = dial_service(&mesh, "bob", "echo").await.expect("session A");
assert_eq!(first_reply(&mut a, "echo").await["method"], "initialize");
let a_conn = mesh
.conn_cache
.live(peer.id)
.expect("A's connection is cached")
.stable_id();
let mut b = tokio::time::timeout(bound, dial_service(&mesh, "bob", "echo"))
.await
.expect("session B must not wait out the dial timeout on a saturated connection")
.expect("session B opens");
assert_eq!(first_reply(&mut b, "echo").await["method"], "initialize");
assert_eq!(
peer.mcp_accepts.load(Ordering::SeqCst),
2,
"B rides its own connection, because A's has no stream credit left"
);
assert_eq!(
mesh.conn_cache.live(peer.id).map(|c| c.stable_id()),
Some(a_conn),
"and A's connection is still the cached one — saturation is not staleness, and B's \
overflow connection must not replace it"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn a_saturated_candidate_does_not_hang_a_raced_dial() {
use super::testpeer::loopback_peer_with;
let dir = tempfile::tempdir().unwrap();
let me = dialer_principal();
let x = loopback_peer_with(
dir.path(),
65,
dialer_id(),
&[("echo", &[me.as_str()])],
Some(1),
false,
)
.await;
let mesh = dialer_mesh(dir.path(), &x).await;
assert!(mesh.store.remove("bob").unwrap());
mesh.store
.add(crate::allowlist::PeerEntry {
endpoint_id: x.id,
nickname: "bob-x".into(),
services: vec![],
paired_at: None,
user_id: Some("b64u:bob".into()),
last_addr: Some(serde_json::to_string(&x.addr).unwrap()),
})
.unwrap();
let dead = *iroh::SecretKey::from_bytes(&[66u8; 32]).public().as_bytes();
mesh.store
.add(crate::allowlist::PeerEntry {
endpoint_id: dead,
nickname: "bob-dead".into(),
services: vec![],
paired_at: None,
user_id: Some("b64u:bob".into()),
last_addr: None,
})
.unwrap();
let mut a = dial_service(&mesh, &eid(&x), "echo")
.await
.expect("session A");
assert_eq!(first_reply(&mut a, "echo").await["method"], "initialize");
let mut b = tokio::time::timeout(
std::time::Duration::from_secs(10),
dial_service(&mesh, "b64u:bob", "echo"),
)
.await
.expect("a raced dial must not hang on a saturated candidate connection")
.expect("session B opens");
assert_eq!(first_reply(&mut b, "echo").await["method"], "initialize");
}
#[tokio::test(flavor = "multi_thread")]
async fn a_raced_dial_waits_for_a_dial_already_in_flight() {
use crate::daemon::dial::{DIAL_TIMEOUT, connect_with_timeout};
let dir = tempfile::tempdir().unwrap();
let (x, y, mesh) = two_device_person(dir.path(), (67, 68)).await;
let (release, gate) = tokio::sync::oneshot::channel::<()>();
let leader = {
let (mesh, addr) = (mesh.clone(), x.addr.clone());
let endpoint = mesh.endpoint.clone();
tokio::spawn(async move {
mesh.conn_cache
.session_on(&[x.id], DIAL_TIMEOUT, never_refused, || async move {
let _ = gate.await;
connect_with_timeout(&endpoint, addr, "echo", DIAL_TIMEOUT, None).await
})
.await
})
};
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
let follower = {
let mesh = mesh.clone();
tokio::spawn(async move { dial_service(&mesh, "b64u:bob", "echo").await })
};
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
assert_eq!(
x.mcp_accepts.load(Ordering::SeqCst) + y.mcp_accepts.load(Ordering::SeqCst),
0,
"a raced dial must wait on the device dial already in flight, not race beside it"
);
release.send(()).unwrap();
let _l = leader.await.unwrap().expect("leader");
let mut f = follower.await.unwrap().expect("follower session");
assert_eq!(first_reply(&mut f, "echo").await["method"], "initialize");
assert_eq!(
x.mcp_accepts.load(Ordering::SeqCst) + y.mcp_accepts.load(Ordering::SeqCst),
1,
"one connection for both sessions"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn a_failed_open_never_forgets_a_newer_connection() {
let dir = tempfile::tempdir().unwrap();
let me = dialer_principal();
let peer = loopback_peer(dir.path(), 69, dialer_id(), &[("echo", &[me.as_str()])]).await;
let mesh = dialer_mesh(dir.path(), &peer).await;
let old = mesh
.endpoint
.connect(peer.addr.clone(), mcpmesh_net::ALPN_MCP)
.await
.expect("old connection");
let newer = mesh
.endpoint
.connect(peer.addr.clone(), mcpmesh_net::ALPN_MCP)
.await
.expect("newer connection");
let cache = super::McpConnCache::new();
cache
.map()
.insert(peer.id, super::Slot::Live(newer.weak_handle()));
old.close(0u32.into(), b"gone");
assert!(matches!(
cache.open_on(peer.id, &old).await,
super::Stream::Stale
));
assert_eq!(
cache.live(peer.id).map(|c| c.stable_id()),
Some(newer.stable_id()),
"the failure of an OLD connection must not evict the newer one cached meanwhile"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn a_waiter_that_times_out_reports_the_leaders_failure() {
let peer = [0x7Au8; 32];
let cache = super::McpConnCache::new();
let (release, gate) = tokio::sync::oneshot::channel::<()>();
let leader = {
let cache = cache.clone();
tokio::spawn(async move {
cache
.session_on(
&[peer],
std::time::Duration::from_secs(10),
never_refused,
|| async move {
let _ = gate.await;
anyhow::bail!("the peer refused: leader-reason-7a")
},
)
.await
.map(|_| ())
})
};
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
let follower = {
let cache = cache.clone();
tokio::spawn(async move {
cache
.session_on(
&[peer],
std::time::Duration::from_secs(2),
never_refused,
|| async move {
std::future::pending::<()>().await;
unreachable!("a parked dial never completes")
},
)
.await
.map(|_| ())
})
};
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
release.send(()).unwrap();
assert!(leader.await.unwrap().is_err());
let err = follower
.await
.unwrap()
.expect_err("the follower's own dial never completes");
let msg = format!("{err:#}");
assert!(
msg.contains("dial timed out") && msg.contains("leader-reason-7a"),
"the timeout must carry the failure it waited on: {msg}"
);
}
#[test]
fn dropping_a_dial_lead_survives_a_poisoned_lock() {
let cache = super::McpConnCache::new();
let peer = [0x7Bu8; 32];
let super::Claim::Lead(lead) = cache.claim(&[peer]) else {
panic!("an empty cache must hand out a lead");
};
let poisoner = cache.clone();
let _ = std::thread::spawn(move || {
let _held = poisoner.inner.lock().unwrap();
panic!("poison the conn cache lock");
})
.join();
assert!(
cache.inner.is_poisoned(),
"precondition: the lock is poisoned"
);
let dropped = std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || drop(lead)));
assert!(
dropped.is_ok(),
"a lead's Drop must not panic on a poisoned lock"
);
let slots = match cache.inner.lock() {
Ok(m) => m.len(),
Err(p) => p.into_inner().len(),
};
assert_eq!(slots, 0, "and it still clears its Dialing slot");
}
}