crates/installer/nordisk-pairing/src/frontside.rs
//! **The FRONT medium's side of the installer** (0.5.6) — vm1, as the installer
//! sees it, with no product type in it.
//!
//! [`crate::boxside`] is a PRODUCT box's half (main and twin); this is vm1's.
//! Until 0.5.6 it existed only inside gunnar (`gunnar-front/src/vm.rs`, the
//! front's `Vm`, and the relay half of `gunnar-pairing-door/src/front.rs`); holger's
//! estate grew a front of its own (holger `holger-estate-front`, 2026-09-27) and
//! the parts that know nothing of either product moved here:
//!
//! * [`Front`] — the [`Vm`]: it presents kind **`front`** with the relay's
//! tunnel key and no capacity (the election compares the product boxes
//! only), keeps what it is fed in `<data>/pairing/joined.toml`, sealed
//! ([`crate::fed_at_rest`], 0.5.17; the same files as a box, [`crate::boxside`]), refuses any role but `front` BY NAME
//! (this medium laid where a product box belongs would make vm1 half of a
//! pair), and reports PAIRED once its relay is up and the fed reply lists
//! every box of the estate (`peers_expected` in the seed);
//! * [`Verdict`] — the election as the front reads it from its feed: which box
//! is primary and which is twin, at which utility address — what the front
//! certifies the twin against;
//! * [`relay_list`] — what the estate's WireGuard relay lists (the relay is edda
//! `estate-front::relay`) on
//! udp/[`crate::RELAY_PORT`], keyed with the front's own tunnel key (the key
//! its presentation carried, so the key the door feeds every box as the
//! `front` peer), listing each fed box by its overlay /32.
//!
//! What stays the product's ([`FrontProduct`]): the tunnel key's public half,
//! what the front OFFERS (its build, its keys, where its doors are), what it
//! hands its successor as a SECRET (the web certificate), and what a feed
//! means to it.
use std::collections::BTreeMap;
use std::net::Ipv4Addr;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;
use crate::bootstrap::{SecretOffer, Vm};
use crate::boxside::{write_atomic, CLOSED_REL, JOINED_REL, OWN_CERT_REL, OWN_KEY_REL, SIGNAL_REL};
use crate::join::{JoinReply, JoinRequest};
use crate::mtls::{self, Issued};
use crate::pair::{Event, Machine, State};
use crate::seed::{Kind, Seed};
use crate::signal::Signal;
use crate::Role;
/// **What only the product knows** about its front.
pub trait FrontProduct: Send + Sync {
/// The relay's public key (WireGuard base64) — what the door derives the
/// `front` peer every box dials from. Empty for `tunnel = none`.
fn tunnel_pubkey(&self) -> String;
/// What the front offers the installer on every visit (`offer.<vm>.<key>`):
/// PUBLIC values only it can mint. Never a private key: the offer is logged.
fn offer(&self, fed: Option<&JoinReply>) -> BTreeMap<String, String>;
/// What its SUCCESSOR must be fed (asked once, before the close): the web
/// certificate and key per host. Empty by default.
fn secret_offer(&self) -> SecretOffer {
SecretOffer::default()
}
/// Called after a feed was KEPT (idempotent; apply live, never restart).
fn fed(&self, _reply: &JoinReply) {}
/// **What the front runs** (0.5.14, live update): the default refuses by name.
fn live_running(&self) -> Result<crate::live::Running, String> {
Err("REFUSED [live-unsupported] this VM takes no live update".into())
}
/// **A new app for the front's other slot** (0.5.14).
fn live_stage(&self, _stage: &crate::live::Stage) -> Result<String, String> {
Err("REFUSED [live-unsupported] this VM takes no live update".into())
}
/// **A new app with what its release declares** (lane B5): the default takes an
/// undeclared stage and refuses a declared one by name ([`crate::live::declared_unsupported`]).
fn live_stage_declared(&self, stage: &crate::live::Stage, declared: &crate::live::Declared) -> Result<String, String> {
if declared.is_empty() {
self.live_stage(stage)
} else {
Err(crate::live::declared_unsupported())
}
}
}
/// Pure: whether a feed may be taken by the FRONT medium. Only role `front`.
pub fn refuses(reply: &JoinReply) -> Option<String> {
(reply.role != Some(Role::Front)).then(|| {
format!(
"REFUSED [front-medium-is-only-the-front] the installer fed `{}` role {:?}; this VM booted the FRONT medium, which is vm1 and nothing else. Lay main and twin with the product medium",
reply.me.name, reply.role
)
})
}
/// **The election, as the front reads it from its feed** — which box is the
/// primary and which the twin, and where each one's utility address is. `None`
/// until the verdict is fed with both addresses (a 0.4.0 feed carries none).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Verdict {
pub primary: String,
/// The primary's utility address — the reply's `primary_utility` when the
/// door set it (the twin's own loop becomes the twin of exactly that), else
/// the peer's.
pub primary_utility: Ipv4Addr,
pub twin: String,
pub twin_utility: Ipv4Addr,
}
/// Pure: the [`Verdict`] in `reply`.
pub fn verdict(reply: &JoinReply) -> Option<Verdict> {
let of = |role: Role| reply.peers.iter().find(|p| p.role == role);
let twin = of(Role::Twin)?;
let primary = of(Role::Primary)?;
Some(Verdict { primary: primary.name.clone(), primary_utility: reply.primary_utility.or(primary.utility)?, twin: twin.name.clone(), twin_utility: twin.utility? })
}
/// Pure: the primary's utility address alone — known before a twin is.
pub fn primary_utility(reply: &JoinReply) -> Option<Ipv4Addr> {
reply.primary_utility.or_else(|| reply.peers.iter().find(|p| p.role == Role::Primary).and_then(|p| p.utility))
}
/// Pure: **what the front's relay lists** — `(name, tunnel key, overlay)` of
/// every fed box peer with a key, the front's own entry excluded. The relay
/// itself is edda `estate-front::relay` (wg-appliance-core's HubRelay).
pub fn relay_list(peers: &[crate::join::Peer]) -> Vec<(String, String, Ipv4Addr)> {
peers.iter().filter(|p| p.role != Role::Front && !p.tunnel_pubkey.trim().is_empty()).map(|p| (p.name.clone(), p.tunnel_pubkey.trim().to_string(), p.overlay)).collect()
}
/// Pure: **what the front's relay lists, each box with its pre-shared key** (0.5.17, A7 row
/// 13; Gunnar 356e8dd2 `FrontRole::take`): [`relay_list`], and for each box the PSK the
/// installer fed the FRONT for it (`wg_psk.<box>`, [`crate::installer::feed_wg_psk_for`]),
/// trimmed; `None` when none was fed (an estate laid before PSKs: exactly the old listing), a
/// blank one handed on as the blank it is (lane D14), which the relay refuses.
/// The relay (edda `estate-front::relay::Relay::take_with_psk`) keys every session with it.
pub fn relay_list_with_psk(reply: &JoinReply) -> Vec<(String, String, Ipv4Addr, Option<String>)> {
relay_list(&reply.peers)
.into_iter()
.map(|(name, key, overlay)| {
// A fed but BLANK key stays `Some("")` (lane D14, decision c1): the relay refuses
// the box, never lists it without one.
let psk = reply.feed.get(&crate::installer::feed_wg_psk_for(&name)).map(|p| p.trim().to_string());
(name, key, overlay, psk)
})
.collect()
}
/// **The box an update works on is BUSY** (0.5.17, A7 row 10; Gunnar aa14357b
/// `update::busy`): while a front's update step holds a role ([`on`]), the recharge watcher
/// leaves that box alone — a box in its keep boot or its re-exec is DOWN, not lost — and a
/// daily upgrade waits ([`busy`]`(None)`). Counted per role, process-wide.
/// **The update step** every product's front runs (0.5.26, P4a; feature `frontside-update`).
#[cfg(any(feature = "frontside-update", test))]
pub mod update;
/// The front proves the tunnel is the only path (P4c, A28 M11; it dials the boxes: off by default).
#[cfg(any(feature = "mesh-probe", test))]
pub mod mesh_probe;
/// The paired links' renewal: the schedule and the steps (P4c, A28 M11; off by default).
#[cfg(any(feature = "front-renew", test))]
pub mod renew;
/// The front re-admits a lost box from the kept material (P4c, A28 M11).
#[cfg(feature = "kept")]
pub mod recharge;
pub mod busy {
use std::collections::BTreeMap;
use std::sync::Mutex;
static BUSY: Mutex<BTreeMap<String, u32>> = Mutex::new(BTreeMap::new());
/// While held, its role is busy; dropped, it is not (once every holder dropped).
#[must_use = "the role is busy only while the guard lives"]
pub struct Guard(String);
impl Drop for Guard {
fn drop(&mut self) {
let mut b = BUSY.lock().unwrap_or_else(|p| p.into_inner());
if let Some(n) = b.get_mut(&self.0) {
*n = n.saturating_sub(1);
if *n == 0 {
b.remove(&self.0);
}
}
}
}
/// The update works on `role` from now until the guard drops.
pub fn on(role: &str) -> Guard {
*BUSY.lock().unwrap_or_else(|p| p.into_inner()).entry(role.to_string()).or_default() += 1;
Guard(role.to_string())
}
/// Whether an update works on `role` now (`None`: on any box).
pub fn busy(role: Option<&str>) -> bool {
let b = BUSY.lock().unwrap_or_else(|p| p.into_inner());
match role {
Some(r) => b.get(r).is_some_and(|n| *n > 0),
None => b.values().any(|n| *n > 0),
}
}
}
/// **vm1, as the installer sees it.**
pub struct Front<P: FrontProduct> {
root: PathBuf,
seed: Seed,
own_key: String,
fed: Mutex<Option<JoinReply>>,
machine: Mutex<Machine>,
relay_up: AtomicBool,
pub product: P,
}
impl<P: FrontProduct> Front<P> {
/// This front's own key (minted once, 0600) and what it was fed before —
/// only a front feed of THIS estate counts.
pub fn open(root: &Path, seed: Seed, product: P) -> Result<Front<P>, String> {
let own_key = match std::fs::read_to_string(root.join(OWN_KEY_REL)) {
Ok(k) if k.contains("PRIVATE KEY") => k,
_ => {
let (_, key) = mtls::box_request().map_err(|e| e.to_string())?;
write_atomic(root, OWN_KEY_REL, key.as_bytes(), true).map_err(|e| format!("{}: {e}", root.join(OWN_KEY_REL).display()))?;
key
}
};
let fed = crate::fed_at_rest::read(root, JOINED_REL).and_then(|t| JoinReply::parse(&t).ok()).filter(|r| r.estate == seed.estate && refuses(r).is_none());
let f = Front { root: root.to_path_buf(), seed, own_key, fed: Mutex::new(fed), machine: Mutex::new(Machine::default()), relay_up: AtomicBool::new(false), product };
f.advance();
Ok(f)
}
pub fn root(&self) -> &Path {
&self.root
}
pub fn seed(&self) -> &Seed {
&self.seed
}
/// The installer CA (PEM): what the front's doors chain to.
pub fn installer_ca_pem(&self) -> &str {
&self.seed.installer_ca_pem
}
/// What the installer fed last.
pub fn latest(&self) -> Option<JoinReply> {
self.fed.lock().ok().and_then(|f| f.clone())
}
/// Whether the installer closed this front's listener on this installation.
pub fn closed(&self) -> bool {
self.root.join(CLOSED_REL).exists()
}
fn own_cert(&self) -> Option<String> {
std::fs::read_to_string(self.root.join(OWN_CERT_REL)).ok().filter(|c| c.contains("BEGIN CERTIFICATE"))
}
/// The front's own identity — `None` while it holds only the medium's
/// bootstrap certificate (which is nobody's).
pub fn own_identity(&self) -> Option<Issued> {
self.own_cert().map(|cert_pem| Issued { cert_pem, key_pem: self.own_key.clone() })
}
/// The relay is up: the front's "tunnel".
pub fn relay_started(&self) {
self.relay_up.store(true, Ordering::SeqCst);
self.advance();
}
/// **Walk the pairing machine** from what is known now — the fed reply and
/// whether the relay is up — and write the signal when it moved.
pub fn advance(&self) {
let Some(reply) = self.latest() else { return };
let Ok(mut m) = self.machine.lock() else { return };
let before = m.clone();
let joined = || Event::Joined { role: Role::Front, primary: None, peers_admitted: reply.peers.len().min(u8::MAX as usize) as u8, peers_expected: self.seed.peers_expected };
if m.state == State::Booted {
m.apply(joined());
}
if matches!(m.state, State::Joined { .. }) && self.relay_up.load(Ordering::SeqCst) {
m.apply(Event::TunnelUp);
}
if matches!(m.state, State::RoleKnown { .. }) {
m.apply(joined());
}
if *m != before {
let s = Signal::of(&reply.me.name, self.seed.tunnel, &m);
if let Some(text) = s.render() {
let _ = write_atomic(&self.root, SIGNAL_REL, text.as_bytes(), false);
}
}
}
}
impl<P: FrontProduct> Vm for Front<P> {
fn live_running(&self) -> Result<crate::live::Running, String> {
self.product.live_running()
}
fn live_stage(&self, stage: &crate::live::Stage) -> Result<String, String> {
self.product.live_stage(stage)
}
fn live_stage_declared(&self, stage: &crate::live::Stage, declared: &crate::live::Declared) -> Result<String, String> {
self.product.live_stage_declared(stage, declared)
}
fn hello(&self) -> Result<JoinRequest, String> {
let csr = if self.own_cert().is_some() { String::new() } else { mtls::box_request_for(&self.own_key).map_err(|e| e.to_string())?.0 };
// No capacity: the election compares the product boxes only.
Ok(JoinRequest { estate: self.seed.estate.clone(), kind: Kind::Front, tunnel_pubkey: self.product.tunnel_pubkey(), capacity: None, csr_pem: csr })
}
fn fed(&self, reply: &JoinReply) -> Result<(), String> {
if reply.estate != self.seed.estate {
return Err(format!("REFUSED [installer-estate] this VM is of `{}`, the installer fed `{}`", self.seed.estate, reply.estate));
}
if let Some(why) = refuses(reply) {
return Err(why);
}
if let Some(cert) = &reply.cert_pem {
write_atomic(&self.root, OWN_CERT_REL, cert.as_bytes(), true).map_err(|e| e.to_string())?;
}
let text = reply.render().map_err(|e| e.to_string())?;
crate::fed_at_rest::write(&self.root, JOINED_REL, &text)?;
*self.fed.lock().map_err(|e| e.to_string())? = Some(reply.clone());
self.advance();
self.product.fed(reply);
Ok(())
}
fn status(&self) -> Signal {
let name = self.latest().map(|r| r.me.name).unwrap_or_else(|| "?".into());
let m = self.machine.lock().map(|m| m.clone()).unwrap_or_default();
Signal::of(&name, self.seed.tunnel, &m)
}
fn close(&self) -> Result<(), String> {
write_atomic(&self.root, CLOSED_REL, b"closed by the installer\n", false).map_err(|e| e.to_string())
}
fn identity(&self) -> Issued {
self.own_identity().unwrap_or_else(|| Issued { cert_pem: self.seed.bootstrap_cert_pem.clone(), key_pem: self.seed.bootstrap_key_pem.clone() })
}
fn offer(&self) -> BTreeMap<String, String> {
self.product.offer(self.latest().as_ref())
}
fn secret_offer(&self) -> SecretOffer {
self.product.secret_offer()
}
/// 0.5.8: after the close, the front learns what changed around it — a
/// re-admitted box's new address and tunnel key, which its relay lists and
/// its routes dial — kept exactly like a feed, once
/// [`crate::bootstrap::refeed_refusal`] found nothing that changes vm1.
fn refed(&self, reply: &JoinReply) -> Result<(), String> {
if let Some(why) = crate::bootstrap::refeed_refusal(self.latest().as_ref(), reply) {
return Err(why);
}
self.fed(reply)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::join::{Assigned, Peer};
use crate::seed::TunnelKind;
use std::time::Duration;
struct P;
impl FrontProduct for P {
fn tunnel_pubkey(&self) -> String {
"KF".into()
}
fn offer(&self, _fed: Option<&JoinReply>) -> BTreeMap<String, String> {
BTreeMap::from([("version".into(), "t".into())])
}
fn secret_offer(&self) -> SecretOffer {
SecretOffer::new(BTreeMap::from([("tls.x.cert".into(), "C".into())]))
}
}
/// A front whose box carries a live slot.
struct LiveF;
impl FrontProduct for LiveF {
fn tunnel_pubkey(&self) -> String {
String::new()
}
fn offer(&self, _fed: Option<&JoinReply>) -> BTreeMap<String, String> {
BTreeMap::new()
}
fn live_running(&self) -> Result<crate::live::Running, String> {
Ok(crate::live::Running { base: "b".into(), app: "f".into(), slot: "b".into(), live_update: true })
}
fn live_stage(&self, s: &crate::live::Stage) -> Result<String, String> {
Ok(format!("front staged {}", s.pin_hex))
}
}
/// ★ The front answers the installer's live door with its product's slot; one without
/// refuses by name (0.5.14).
#[test]
fn the_front_answers_the_live_door_with_its_products_slot_or_refuses_by_name() {
let d = dir("live");
let f = Front::open(&d, seed(), LiveF).unwrap();
assert_eq!(f.live_running().unwrap().slot, "b");
let st = crate::live::Stage { pin_hex: "9".repeat(64), signature: vec![1], base: "b".into(), archive: vec![2] };
assert!(f.live_stage(&st).unwrap().starts_with("front staged"));
let plain = Front::open(&d, seed(), P).unwrap();
assert!(plain.live_running().unwrap_err().contains("[live-unsupported]"));
let _ = std::fs::remove_dir_all(&d);
}
fn seed() -> Seed {
let ca = mtls::InstallerCa::mint("front.test", Duration::from_secs(3600)).unwrap();
let boot = ca.issue_bootstrap(Duration::from_secs(3600)).unwrap();
Seed {
estate: "front.test".into(),
tunnel: TunnelKind::Wireguard,
front: Default::default(),
kind: Kind::Front,
installer_ca_pem: ca.cert_pem,
bootstrap_cert_pem: boot.cert_pem,
bootstrap_key_pem: boot.key_pem,
peers_expected: 3,
mullvad: None,
tailscale: None,
installer_link: None,
}
}
fn peer(name: &str, role: Role, util: Option<[u8; 4]>) -> Peer {
Peer { name: name.into(), role, tunnel_pubkey: String::new(), overlay: crate::OVERLAY_BOX_A, endpoint: None, utility: util.map(Ipv4Addr::from) }
}
fn fed(role: Option<Role>, peers: Vec<Peer>) -> JoinReply {
JoinReply { estate: "front.test".into(), me: Assigned { name: "vm1".into(), overlay: crate::OVERLAY_FRONT }, cert_pem: None, role, primary_overlay: None, primary_utility: None, peers, feed: BTreeMap::new() }
}
fn dir(tag: &str) -> PathBuf {
let d = std::env::temp_dir().join(format!("frontside-{tag}-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&d);
d
}
#[test]
fn the_front_presents_kind_front_with_its_relay_key_no_capacity_and_refuses_any_other_role() {
let d = dir("refuse");
let f = Front::open(&d, seed(), P).unwrap();
let h = f.hello().unwrap();
assert_eq!((h.kind, h.tunnel_pubkey.as_str(), h.capacity.is_none(), h.csr_pem.is_empty()), (Kind::Front, "KF", true, false));
for role in [None, Some(Role::Primary), Some(Role::Twin)] {
assert!(f.fed(&fed(role, vec![])).unwrap_err().contains("[front-medium-is-only-the-front]"));
}
assert!(f.latest().is_none() && !d.join(JOINED_REL).exists(), "nothing kept");
assert_eq!(f.secret_offer().keys(), vec!["tls.x.cert".to_string()], "the product's secret offer");
let _ = std::fs::remove_dir_all(&d);
}
#[test]
fn the_front_is_paired_when_its_relay_is_up_and_every_box_is_listed_and_resumes_after_a_restart() {
let d = dir("paired");
let f = Front::open(&d, seed(), P).unwrap();
f.fed(&fed(Some(Role::Front), vec![peer("vm1", Role::Front, None)])).unwrap();
assert!(f.status().joined && !f.status().paired);
f.relay_started();
assert!(!f.status().paired, "main and twin not listed yet");
f.fed(&fed(Some(Role::Front), vec![peer("vm1", Role::Front, None), peer("vm2", Role::Primary, Some([10, 0, 0, 2])), peer("vm3", Role::Twin, Some([10, 0, 0, 3]))])).unwrap();
assert!(f.status().paired, "{:?}", f.status());
assert!(d.join(SIGNAL_REL).exists());
let again = Front::open(&d, seed(), P).unwrap();
assert_eq!(again.latest().map(|r| r.me.name).as_deref(), Some("vm1"));
again.relay_started();
assert!(again.status().paired);
let _ = std::fs::remove_dir_all(&d);
}
/// 0.5.8: a closed front is re-fed a re-admitted primary's new address —
/// its verdict follows at once — and never a role but its own.
#[test]
fn a_closed_front_takes_a_refeed_with_the_new_primary_and_refuses_a_change_of_itself() {
let d = dir("refeed");
let f = Front::open(&d, seed(), P).unwrap();
f.fed(&fed(Some(Role::Front), vec![peer("vm2", Role::Primary, Some([10, 0, 0, 2])), peer("vm3", Role::Twin, Some([10, 0, 0, 3]))])).unwrap();
f.close().unwrap();
f.refed(&fed(Some(Role::Front), vec![peer("vm2", Role::Primary, Some([10, 0, 0, 9])), peer("vm3", Role::Twin, Some([10, 0, 0, 3]))])).unwrap();
assert_eq!(f.latest().as_ref().and_then(primary_utility), Some(Ipv4Addr::new(10, 0, 0, 9)), "the front dials the new primary");
assert!(f.refed(&fed(Some(Role::Primary), vec![])).unwrap_err().contains("REFUSED ["));
assert_eq!(f.latest().as_ref().and_then(primary_utility), Some(Ipv4Addr::new(10, 0, 0, 9)));
let _ = std::fs::remove_dir_all(&d);
}
#[test]
fn the_verdict_names_primary_and_twin_on_their_utility_addresses() {
let r = fed(Some(Role::Front), vec![peer("vm2", Role::Primary, Some([10, 0, 0, 2])), peer("vm3", Role::Twin, Some([10, 0, 0, 3]))]);
let v = verdict(&r).unwrap();
assert_eq!((v.primary.as_str(), v.primary_utility, v.twin.as_str(), v.twin_utility), ("vm2", Ipv4Addr::new(10, 0, 0, 2), "vm3", Ipv4Addr::new(10, 0, 0, 3)));
assert_eq!(primary_utility(&r), Some(Ipv4Addr::new(10, 0, 0, 2)));
let no_twin_address = fed(Some(Role::Front), vec![peer("vm2", Role::Primary, Some([10, 0, 0, 2])), peer("vm3", Role::Twin, None)]);
assert!(verdict(&no_twin_address).is_none());
}
#[test]
fn the_relay_list_is_every_fed_box_with_a_key_never_the_front() {
let k = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
let reply = fed(Some(Role::Front), vec![Peer { tunnel_pubkey: k.into(), ..peer("vm1", Role::Front, None) }, Peer { tunnel_pubkey: format!(" {k} "), ..peer("vm2", Role::Primary, None) }, peer("vm3", Role::Twin, None)]);
assert_eq!(relay_list(&reply.peers), vec![("vm2".to_string(), k.to_string(), crate::OVERLAY_BOX_A)]);
}
/// ★ 0.5.17 (A7 row 21): what the front was fed (the shop's key, the tokens, each box's
/// PSK) is SEALED at rest and the front reads it back after a restart.
#[test]
fn the_front_keeps_its_feed_sealed_at_rest() {
let d = dir("sealed");
let f = Front::open(&d, seed(), P).unwrap();
let mut r = fed(Some(Role::Front), vec![]);
r.feed.insert("secret.shop_client_key".into(), "shop-key-NEVER-in-the-clear".into());
f.fed(&r).unwrap();
let raw = std::fs::read(d.join(JOINED_REL)).unwrap();
assert!(crate::fed_at_rest::is_sealed(&raw) && !String::from_utf8_lossy(&raw).contains("NEVER-in-the-clear"));
let again = Front::open(&d, seed(), P).unwrap();
assert_eq!(again.latest().and_then(|r| r.feed.get("secret.shop_client_key").cloned()).as_deref(), Some("shop-key-NEVER-in-the-clear"));
let _ = std::fs::remove_dir_all(&d);
}
/// ★ 0.5.17 (A7 row 13, Gunnar 356e8dd2): the relay lists each box with the PSK the
/// installer fed the front for it (`wg_psk.<box>`); a box with none is listed without.
#[test]
fn the_relay_lists_each_box_with_the_psk_the_front_was_fed_for_it() {
let k = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
let psk = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=";
let mut reply = fed(Some(Role::Front), vec![Peer { tunnel_pubkey: k.into(), ..peer("vm2", Role::Primary, None) }, Peer { tunnel_pubkey: k.into(), overlay: crate::OVERLAY_BOX_B, ..peer("vm3", Role::Twin, None) }]);
reply.feed.insert(crate::installer::feed_wg_psk_for("vm2"), format!(" {psk} "));
let list = relay_list_with_psk(&reply);
assert_eq!(list[0], ("vm2".to_string(), k.to_string(), crate::OVERLAY_BOX_A, Some(psk.to_string())));
assert_eq!(list[1].3, None, "no PSK fed for vm3");
}
/// ★ Lane D14, decision c1: a `wg_psk.<box>` fed BLANK is handed to the relay as the blank
/// it is (which refuses the box), never as "no PSK" (which would list it without one).
#[test]
fn a_blank_fed_psk_reaches_the_relay_as_blank_never_as_none() {
let k = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
let mut reply = fed(Some(Role::Front), vec![Peer { tunnel_pubkey: k.into(), ..peer("vm2", Role::Primary, None) }]);
reply.feed.insert(crate::installer::feed_wg_psk_for("vm2"), " ".into());
assert_eq!(relay_list_with_psk(&reply)[0].3.as_deref(), Some(""));
}
/// ★ 0.5.17 (A7 row 10, Gunnar aa14357b): a box is BUSY while an update holds it, per
/// role and counted, and only then.
#[test]
fn a_box_is_busy_while_an_update_holds_it_and_only_then() {
assert!(!busy::busy(Some("g5-main")));
let one = busy::on("g5-main");
let two = busy::on("g5-main");
assert!(busy::busy(Some("g5-main")) && busy::busy(None));
assert!(!busy::busy(Some("g5-twin")), "per role");
drop(one);
assert!(busy::busy(Some("g5-main")), "counted: the second holder still works on it");
drop(two);
assert!(!busy::busy(Some("g5-main")));
}
}