Replace address-only trust and pushed peer state with installation identities, SPKI-pinned QUIC, candidate-only discovery, and bounded responder-owned protocol-8 pulls. The runtime now owns each network generation and all admitted work through shutdown. Add exact bundled content identities, reproducible manifest publishing, capability-confined downloads, streaming BLAKE3 verification, quarantine and retry, and crash-recoverable download and install transactions. Ship generated fixture catalogs and fail closed when production manifests are absent. The Tauri backend exposes durable sharing policy, redacted identity state, and attempt-keyed transfer snapshots. Frontend consumption follows in the next commit. Repository-wide test certificates and protocol-7 paths are removed. BREAKING CHANGE: peers must use protocol 8 and exact catalog content artifacts; protocol-7 frames and shared-certificate identities are no longer accepted. Test Plan: - `just test` -- passed on the completed stack (708 workspace tests) - `just clippy` -- passed on the completed stack - `just build` -- passed with fixture catalogs on the completed stack - `just catalog-check-production` -- failed closed because the external production manifest corpus is absent - `git diff --cached --check` -- passed
98 lines
3.2 KiB
Rust
98 lines
3.2 KiB
Rust
//! mDNS advertisement for the local peer server.
|
|
|
|
use std::{collections::HashMap, net::SocketAddr, time::Duration};
|
|
|
|
use lanspread_mdns::{DaemonEvent, LANSPREAD_SERVICE_TYPE, MdnsAdvertiser, MdnsMonitor};
|
|
use lanspread_proto::{PROTOCOL_VERSION, PeerId};
|
|
use tokio_util::sync::CancellationToken;
|
|
|
|
use crate::{context::PeerCtx, network::select_advertise_ip, scoped_blocking::scoped_blocking};
|
|
|
|
pub(super) async fn start_mdns_advertiser(
|
|
ctx: &PeerCtx,
|
|
server_addr: SocketAddr,
|
|
) -> eyre::Result<MdnsAdvertiser> {
|
|
let advertise_ip = select_advertise_ip()?;
|
|
let advertise_addr = SocketAddr::new(advertise_ip, server_addr.port());
|
|
log::info!("Advertising peer via mDNS from {advertise_addr}");
|
|
|
|
{
|
|
let mut guard = ctx.local_peer_addr.write().await;
|
|
*guard = Some(advertise_addr);
|
|
}
|
|
|
|
let peer_id = ctx.peer_id;
|
|
let hostname = gethostname::gethostname().to_string_lossy().into_owned();
|
|
let advertised_name = advertised_service_name(&hostname, peer_id);
|
|
let monitor_name = advertised_name.clone();
|
|
let properties = advertisement_properties(&hostname, peer_id);
|
|
|
|
let mdns = scoped_blocking(move || {
|
|
MdnsAdvertiser::new(
|
|
LANSPREAD_SERVICE_TYPE,
|
|
&advertised_name,
|
|
advertise_addr,
|
|
Some(properties),
|
|
)
|
|
})?;
|
|
|
|
log::info!("Registered mDNS service with name: {monitor_name}");
|
|
Ok(mdns)
|
|
}
|
|
|
|
pub(super) fn close_mdns_advertiser(advertiser: MdnsAdvertiser) -> eyre::Result<()> {
|
|
scoped_blocking(move || advertiser.close())
|
|
}
|
|
|
|
pub(super) async fn monitor_mdns_events(monitor: MdnsMonitor, shutdown: CancellationToken) {
|
|
loop {
|
|
let event = tokio::select! {
|
|
() = shutdown.cancelled() => break,
|
|
event = monitor.recv_async() => event,
|
|
};
|
|
|
|
match event {
|
|
Ok(DaemonEvent::Error(err)) => {
|
|
log::error!("mDNS error: {err}");
|
|
tokio::select! {
|
|
() = shutdown.cancelled() => break,
|
|
() = tokio::time::sleep(Duration::from_secs(1)) => {}
|
|
}
|
|
}
|
|
Ok(other_event) => {
|
|
log::trace!("mDNS event: {other_event:?}");
|
|
}
|
|
Err(err) => {
|
|
log::debug!("mDNS monitor channel closed: {err}");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn advertised_service_name(hostname: &str, peer_id: PeerId) -> String {
|
|
let peer_id = peer_id.to_string();
|
|
let max_hostname_len = 63usize.saturating_sub(peer_id.len() + 1);
|
|
let truncated_hostname = if hostname.len() > max_hostname_len {
|
|
hostname.get(..max_hostname_len).unwrap_or(hostname)
|
|
} else {
|
|
hostname
|
|
};
|
|
|
|
if truncated_hostname.is_empty() {
|
|
peer_id
|
|
} else {
|
|
format!("{truncated_hostname}-{peer_id}")
|
|
}
|
|
}
|
|
|
|
fn advertisement_properties(hostname: &str, peer_id: PeerId) -> HashMap<String, String> {
|
|
let mut properties = HashMap::new();
|
|
properties.insert("peer_id".to_string(), peer_id.to_string());
|
|
properties.insert("proto_ver".to_string(), PROTOCOL_VERSION.to_string());
|
|
if !hostname.is_empty() {
|
|
properties.insert("hostname".to_string(), hostname.to_string());
|
|
}
|
|
properties
|
|
}
|