use std::sync::Arc;
use mcpmesh_local_api::{BackendKind, PeerInfo, PresencePeer, RosterStatus, ServiceInfo};
use mcpmesh_trust::roster::validate::RosterState;
use crate::allowlist::PeerStore;
use crate::config::{Backend, Config};
use crate::pairing;
use crate::util::epoch_now_i64;
use super::{MeshState, dial};
pub(crate) fn roster_status(mesh: &Arc<MeshState>, cfg: Option<&Config>) -> Option<RosterStatus> {
let org_root_fingerprint = cfg
.and_then(|c| c.identity.org_root_pk.as_deref())
.and_then(|s| crate::roster::parse_org_root_pk(s).ok())
.map(|vk| pairing::sas::fingerprint_words(&vk.to_bytes()))
.unwrap_or_default();
match mesh.roster.view() {
Some(view) => {
let state = match mesh
.roster
.effective_state(epoch_now_i64())
.unwrap_or(RosterState::Approved)
{
RosterState::Approved => "approved",
RosterState::DegradedGrace => "degraded",
RosterState::DegradedStopped => "stopped",
};
Some(RosterStatus {
org_id: view.org_id().to_string(),
serial: view.serial(),
state: state.to_string(),
org_root_fingerprint,
})
}
None => {
let cfg = cfg?;
cfg.identity.org_root_pk.as_deref()?;
Some(RosterStatus {
org_id: cfg.identity.org_id.clone().unwrap_or_default(),
serial: 0,
state: "pending".to_string(),
org_root_fingerprint,
})
}
}
}
pub(crate) fn presence_peers(mesh: &Arc<MeshState>) -> Vec<PresencePeer> {
let Some(view) = mesh.roster.view() else {
return Vec::new();
};
let now = epoch_now_i64();
let active: std::collections::HashMap<[u8; 32], crate::roster::presence::PresenceEntry> =
mesh.presence_table.active(now).into_iter().collect();
let self_eid = *mesh.endpoint.id().as_bytes();
let self_meta = mesh.app_metadata();
let mut peers: Vec<PresencePeer> = view
.devices()
.map(|(eid, d)| PresencePeer {
user_id: d.user_id.clone(),
device_label: d.label.clone(),
role: d.role.clone(),
online: active.contains_key(eid),
meta: if *eid == self_eid {
self_meta.clone()
} else {
active.get(eid).map(|e| e.meta.clone()).unwrap_or_default()
},
})
.collect();
peers.sort_by(|a, b| {
a.user_id
.cmp(&b.user_id)
.then_with(|| dial::dial_role_rank(&a.role).cmp(&dial::dial_role_rank(&b.role)))
.then_with(|| a.device_label.cmp(&b.device_label))
});
peers
}
fn display_principal(principal: &str, peers: &[crate::allowlist::PeerEntry]) -> String {
if principal.starts_with("eid:") {
return peers
.iter()
.find(|p| mcpmesh_net::EndpointId::from_bytes(p.endpoint_id).principal() == principal)
.map(|p| p.nickname.clone())
.unwrap_or_else(|| "unpaired-device".to_owned());
}
if principal.starts_with("b64u:") {
return peers
.iter()
.find(|p| p.user_id.as_deref() == Some(principal))
.map(|p| p.nickname.clone())
.unwrap_or_else(|| "unpaired-peer".to_owned());
}
principal.to_owned()
}
pub(crate) fn service_infos(
cfg: &Config,
ephemeral: &std::collections::HashMap<String, crate::daemon::EphemeralService>,
peers: &[crate::allowlist::PeerEntry],
) -> Vec<ServiceInfo> {
let mut out: Vec<ServiceInfo> = cfg
.services
.iter()
.filter_map(|(name, svc)| {
let backend = match svc.backend_result() {
Ok(Backend::Run(_)) => BackendKind::Run,
Ok(Backend::Socket(_)) => BackendKind::Socket,
Err(_) => return None,
};
Some(ServiceInfo {
name: name.clone(),
allow: svc.allow.clone(),
allow_display: svc
.allow
.iter()
.map(|p| display_principal(p, peers))
.collect(),
backend,
ephemeral: false,
})
})
.collect();
for (name, eph) in ephemeral {
let backend = match &eph.backend {
mcpmesh_local_api::BackendSpec::Run { .. } => BackendKind::Run,
mcpmesh_local_api::BackendSpec::Socket { .. } => BackendKind::Socket,
};
out.push(ServiceInfo {
name: name.clone(),
allow: eph.allow.clone(),
allow_display: eph
.allow
.iter()
.map(|p| display_principal(p, peers))
.collect(),
backend,
ephemeral: true,
});
}
out
}
pub(crate) fn peer_infos(store: &PeerStore) -> Vec<PeerInfo> {
store
.list()
.unwrap_or_default()
.into_iter()
.map(|e| PeerInfo {
name: e.nickname,
services: e.services,
user_id: e.user_id,
principal: Some(mcpmesh_net::EndpointId::from_bytes(e.endpoint_id).principal()),
})
.collect()
}
#[cfg(test)]
mod tests {
use crate::allowlist::PeerEntry;
use crate::daemon::config_write::append_allow_to_config;
use crate::daemon::testutil::hermetic_mesh;
#[tokio::test(flavor = "multi_thread")]
async fn status_reads_the_live_config_and_store() {
let dir = tempfile::tempdir().unwrap();
let config_path = dir.path().join("config.toml");
std::fs::write(
&config_path,
"[services.kb]\nsocket = \"/run/kb.sock\"\nallow = []\n",
)
.unwrap();
let mesh = hermetic_mesh(config_path.clone()).await;
let state = crate::control::DaemonState::with_mesh("test", mesh.clone());
append_allow_to_config(&config_path, "alice", &["kb".to_string()]).unwrap();
mesh.store
.add(PeerEntry {
endpoint_id: [9u8; 32],
nickname: "alice".into(),
services: Vec::new(),
paired_at: None,
user_id: None,
last_addr: None,
})
.unwrap();
let status = crate::control::status_result(&state).unwrap();
let kb = status
.services
.iter()
.find(|s| s.name == "kb")
.expect("kb service in status");
assert!(
kb.allow.contains(&"alice".to_string()),
"status must show the live grant, got allow={:?}",
kb.allow
);
let alice = status
.peers
.iter()
.find(|p| p.name == "alice")
.expect("status must show the live peer");
assert_eq!(
alice.principal.as_deref(),
Some(
mcpmesh_net::EndpointId::from_bytes([9u8; 32])
.principal()
.as_str()
),
"peer principal must be the eid: of its endpoint id"
);
}
#[tokio::test]
async fn status_surfaces_self_and_peer_user_ids() {
let dir = tempfile::tempdir().unwrap();
let config_path = dir.path().join("config.toml");
std::fs::write(
&config_path,
"[services.kb]\nsocket = \"/run/kb.sock\"\nallow = []\n",
)
.unwrap();
let mesh = hermetic_mesh(config_path.clone()).await;
mesh.set_self_binding(Some(crate::pairing::rendezvous::SelfBinding {
user_pk: "b64u:selfpk".into(),
sig: "b64u:selfsig".into(),
}));
mesh.store
.add(PeerEntry {
endpoint_id: [1u8; 32],
nickname: "alice".into(),
services: Vec::new(),
paired_at: Some("1".into()),
user_id: Some("b64u:alicepk".into()),
last_addr: None,
})
.unwrap();
mesh.store
.add(PeerEntry {
endpoint_id: [2u8; 32],
nickname: "legacy".into(),
services: Vec::new(),
paired_at: None,
user_id: None,
last_addr: None,
})
.unwrap();
let state = crate::control::DaemonState::with_mesh("test", mesh.clone());
let status = crate::control::status_result(&state).unwrap();
assert_eq!(
status.self_user_id.as_deref(),
Some("b64u:selfpk"),
"status must surface this daemon's own self-sovereign user_id"
);
let alice = status
.peers
.iter()
.find(|p| p.name == "alice")
.expect("alice in status");
assert_eq!(
alice.user_id.as_deref(),
Some("b64u:alicepk"),
"a paired peer's PROVEN user_id must be surfaced in status"
);
let legacy = status
.peers
.iter()
.find(|p| p.name == "legacy")
.expect("legacy in status");
assert!(
legacy.user_id.is_none(),
"a nickname-only peer stays user_id: None"
);
}
#[tokio::test]
async fn recent_pairings_ring_is_bounded_newest_first_and_surfaced_by_status() {
let dir = tempfile::tempdir().unwrap();
let config_path = dir.path().join("config.toml");
std::fs::write(&config_path, "").unwrap();
let mesh = hermetic_mesh(config_path).await;
for i in 0..10u64 {
mesh.record_pairing(format!("peer{i}"), format!("code-{i}"), i);
}
let recent = mesh.recent_pairings();
assert_eq!(recent.len(), 8, "the ring is capped at 8");
assert_eq!(recent[0].peer_nickname, "peer9", "newest first");
assert_eq!(
recent[7].peer_nickname, "peer2",
"the two oldest were dropped"
);
let state = crate::control::DaemonState::with_mesh("test", mesh);
let status = crate::control::status_result(&state).unwrap();
assert_eq!(status.recent_pairings.len(), 8);
assert_eq!(status.recent_pairings[0].sas_code, "code-9");
assert_eq!(status.recent_pairings[0].paired_at_epoch, 9);
}
}