use std::sync::Arc;
use super::MeshState;
pub(crate) fn refresh(mesh: &Arc<MeshState>, endpoint_id: [u8; 32], observed: String) {
let mesh = mesh.clone();
tokio::task::spawn_blocking(move || write_if_changed(&mesh, endpoint_id, observed));
}
fn write_if_changed(mesh: &Arc<MeshState>, endpoint_id: [u8; 32], observed: String) {
match mesh.store.set_last_addr(&endpoint_id, &observed) {
Ok(true) => tracing::debug!("refreshed the dial hint from a live connection (#124)"),
Ok(false) => {} Err(e) => tracing::debug!(%e, "could not persist refreshed dial hint"),
}
}
pub(crate) fn observed_for(conn: &iroh::endpoint::Connection) -> Option<String> {
let paths = conn.paths();
let addrs: Vec<iroh::TransportAddr> = paths
.iter()
.filter(|p| p.is_ip())
.map(|p| p.remote_addr().clone())
.collect();
if addrs.is_empty() {
return None; }
serde_json::to_string(&iroh::EndpointAddr::from_parts(conn.remote_id(), addrs)).ok()
}
#[cfg(test)]
mod tests {
use crate::allowlist::PeerEntry;
#[tokio::test(flavor = "multi_thread")]
async fn a_stale_hint_is_replaced_and_an_unchanged_one_is_not_rewritten() {
let dir = tempfile::tempdir().unwrap();
let cfg = dir.path().join("config.toml");
std::fs::write(&cfg, "").unwrap();
let mesh = crate::daemon::testutil::hermetic_mesh(cfg).await;
let eid = [3u8; 32];
let stale = r#"{"id":"stale","addrs":[]}"#.to_string();
mesh.store
.add(PeerEntry {
endpoint_id: eid,
nickname: "bob".into(),
services: vec!["notes".into(), "kb".into()],
paired_at: Some("2026-07-29T00:00:00Z".into()),
user_id: Some("b64u:someuser".into()),
last_addr: Some(stale.clone()),
})
.unwrap();
let fresh = r#"{"id":"fresh","addrs":[]}"#.to_string();
super::write_if_changed(&mesh, eid, fresh.clone());
assert_eq!(
mesh.store.resolve(&eid).unwrap().unwrap().last_addr,
Some(fresh.clone()),
"a live observation must overwrite a stale hint — leaving it is #124: the peer is \
dialed at a dead address forever and every session falls back to the relay"
);
assert!(
!mesh
.store
.set_last_addr(&eid, &fresh)
.expect("store readable"),
"an unchanged observation must not write: every Selected event would otherwise cost a \
blocking redb write"
);
assert!(
mesh.store
.set_last_addr(&eid, r#"{"id":"newer","addrs":[]}"#)
.expect("store readable"),
"and a CHANGED observation must write"
);
super::write_if_changed(&mesh, [9u8; 32], fresh);
assert!(
mesh.store.resolve(&[9u8; 32]).unwrap().is_none(),
"refreshing must never CREATE a peer — that would be an authorization path"
);
let row = mesh.store.resolve(&eid).unwrap().unwrap();
assert_eq!(row.nickname, "bob");
assert_eq!(row.endpoint_id, eid);
assert_eq!(
row.user_id.as_deref(),
Some("b64u:someuser"),
"a verified user_id must NEVER be downgraded by a dial-hint refresh"
);
assert_eq!(
row.services,
vec!["notes".to_string(), "kb".to_string()],
"the dial directory must survive — clobbering it is the reverse-pairing bug"
);
assert_eq!(
row.paired_at.as_deref(),
Some("2026-07-29T00:00:00Z"),
"the original pairing stamp stands"
);
}
}