nordisk-pairing 0.5.28

The boxes of an estate find each other: a seed each box is installed with, a one-time join against the front, a tunnel (WireGuard by default) they meet inside, roles decided once, a pairing loop each box runs itself, and the signal it reports. Shared by gunnar and holger; the laptop's xtask only lays machines, writes seeds and reads the signal. Design: .nornir/pairing-design.md (2026-09-24).
Documentation
//! **The pull channel's money-free rules** (lane G9, A7 rows 16, 23 and 31): what a box and a
//! front decide about the ONE monetize's door before any money code runs. Pure, std only, so
//! every build links them, the open one too (Gunnar's community build runs its pull channel
//! without monetize-embed).
//!
//! * [`seam_refusal`]: a fed `fleet_at` / `fleet_ca` is honoured only as a bench feeds it
//!   (Gunnar 8134ec6f, ST-19).
//! * [`after_refusal`]: an `unauthenticated` answer early in the channel's life means "not
//!   known yet", asked again soon and said quietly (Gunnar 59fa3800).

use std::net::{IpAddr, SocketAddr};
use std::time::Duration;

/// The feed key of the address the pull channel is dialled at (`ip:port`), beside `fleet`.
pub const FEED_FLEET_AT: &str = "fleet_at";
/// The feed key of the PEM the door's certificate is checked under, beside `fleet_at`.
pub const FEED_FLEET_CA: &str = "fleet_ca";
/// The refusal word of a seam that is not a bench's.
pub const SEAM_WORD: &str = "fleet-seam-bench-only";

/// Pure: **the mock's seam, honoured only as the mock feeds it** (Gunnar 8134ec6f, ST-19). A
/// fed `fleet_at` is a bench forward: an address literal `ip:port` on a private, loopback,
/// link-local or shared (100.64/10) network. A fed `fleet_ca` comes only beside one. Anything
/// else (a public address, a name, a CA with no bench address) is refused by name, so a release
/// box never dials elsewhere, nor re-roots its trust, on a feed's word. `None`: the seam is
/// taken (or there is none). A blank value counts as not fed.
pub fn seam_refusal(at: Option<&str>, ca: bool) -> Option<String> {
    match at.map(str::trim).filter(|a| !a.is_empty()) {
        None if ca => Some(format!(
            "REFUSED [{SEAM_WORD}] the installer fed `{FEED_FLEET_CA}` with no `{FEED_FLEET_AT}`: a door's own CA is a bench's, beside a bench address only; a public door is checked under the public roots"
        )),
        None => None,
        Some(at) => match at.parse::<SocketAddr>() {
            Ok(a) if bench_address(a.ip()) => None,
            _ => Some(format!(
                "REFUSED [{SEAM_WORD}] the installer fed `{FEED_FLEET_AT}` = {at}: the pull channel is dialled elsewhere than its door's name only on a bench (a private, loopback, link-local or shared `ip:port`)"
            )),
        },
    }
}

/// Pure: **a bench guest's network**: private, loopback, link-local or shared address space
/// (IPv6: loopback, unique local `fc00::/7`, link-local `fe80::/10`).
pub fn bench_address(ip: IpAddr) -> bool {
    match ip {
        IpAddr::V4(v) => v.is_private() || v.is_loopback() || v.is_link_local() || (v.octets()[0] == 100 && (v.octets()[1] & 0xc0) == 64),
        IpAddr::V6(v) => v.is_loopback() || (v.segments()[0] & 0xfe00) == 0xfc00 || (v.segments()[0] & 0xffc0) == 0xfe80,
    }
}

/// **How long after the pull channel starts an `unauthenticated` refusal means "not known
/// YET"** (Gunnar 59fa3800, i54a run16): a box laid for an attached estate is let in by
/// monetize only once the estate's owner opened its pin window, which may land after the box's
/// first report. It covers a whole install (monetize's attached-install window is as long).
pub const NOT_YET_KNOWN_GRACE: Duration = Duration::from_secs(45 * 60);
/// How soon a box asks again while monetize does not know it yet ([`NOT_YET_KNOWN_GRACE`]).
pub const NOT_YET_KNOWN_RETRY: Duration = Duration::from_secs(10);

/// Pure: **after a refused report or pull**, the pause before asking again and whether it is
/// said quietly (INFO, "not known yet"): `unauthenticated` inside [`NOT_YET_KNOWN_GRACE`] since
/// the channel started pauses [`NOT_YET_KNOWN_RETRY`] at most and is quiet. Anything else:
/// `pause`, and a WARN.
pub fn after_refusal(code: &str, since_start: Duration, pause: Duration) -> (Duration, bool) {
    if code == "unauthenticated" && since_start < NOT_YET_KNOWN_GRACE {
        (pause.min(NOT_YET_KNOWN_RETRY), true)
    } else {
        (pause, false)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// ★ Gunnar 8134ec6f (ST-19, `the_fed_fleet_seam_is_refused_unless_it_is_a_bench_address`):
    /// a bench forward is taken; a public address, a name, a bare IP with no port and a CA with
    /// no bench address are refused by name.
    #[test]
    fn the_fed_fleet_seam_is_refused_unless_it_is_a_bench_address() {
        for at in ["10.0.2.2:40443", "127.0.0.1:443", "192.168.1.9:8443", "172.20.0.4:443", "169.254.1.1:443", "[::1]:443", "[fd00::2]:443", "[fe80::1]:443", "100.64.0.9:443", " 10.0.2.2:40443 "] {
            assert_eq!(seam_refusal(Some(at), true), None, "a bench forward {at} is dialled");
            assert_eq!(seam_refusal(Some(at), false), None, "a bench forward {at}, public roots");
        }
        for at in ["203.0.113.7:443", "8.8.8.8:443", "monetize.example:443", "[2001:db8::1]:443", "10.0.2.2", "100.128.0.1:443", "0.0.0.0:443"] {
            let e = seam_refusal(Some(at), false).unwrap_or_else(|| panic!("{at} is no bench address"));
            assert!(e.contains(&format!("[{SEAM_WORD}]")) && e.contains(FEED_FLEET_AT), "{at}: {e}");
        }
        let e = seam_refusal(None, true).expect("a CA with no bench address");
        assert!(e.contains(&format!("[{SEAM_WORD}]")) && e.contains(FEED_FLEET_CA), "{e}");
        assert_eq!(seam_refusal(None, false), None, "no seam: the name, under the public roots");
        assert_eq!(seam_refusal(Some("  "), false), None, "a blank fleet_at is not fed");
        assert!(seam_refusal(Some(""), true).is_some(), "a CA beside a blank fleet_at has no bench address");
    }

    /// ★ Gunnar 59fa3800 (`a_box_not_known_yet_asks_again_soon_and_quietly_inside_the_grace`):
    /// inside the grace an `unauthenticated` refusal is asked again within 10 s, quietly; after
    /// it, or for any other refusal, the pause as before and a WARN.
    #[test]
    fn a_box_not_known_yet_asks_again_soon_and_quietly_inside_the_grace() {
        let every = Duration::from_secs(60);
        assert_eq!(after_refusal("unauthenticated", Duration::from_secs(30), every), (NOT_YET_KNOWN_RETRY, true));
        assert_eq!(after_refusal("unauthenticated", NOT_YET_KNOWN_GRACE - Duration::from_secs(1), every), (NOT_YET_KNOWN_RETRY, true));
        assert_eq!(after_refusal("unauthenticated", Duration::from_secs(1), Duration::from_secs(3)), (Duration::from_secs(3), true), "never longer than it would have paused");
        assert_eq!(after_refusal("unauthenticated", NOT_YET_KNOWN_GRACE + Duration::from_secs(1), every), (every, false), "past the grace it is a WARN again");
        assert_eq!(after_refusal("unavailable", Duration::from_secs(30), every), (every, false), "a door that is down is not 'not known yet'");
        assert!(NOT_YET_KNOWN_GRACE >= Duration::from_secs(45 * 60), "covers a whole install");
        assert!(NOT_YET_KNOWN_RETRY <= Duration::from_secs(10));
    }

    #[test]
    fn a_bench_address_is_private_loopback_link_local_or_shared() {
        for ip in ["10.1.2.3", "172.16.0.1", "192.168.0.1", "127.0.0.1", "169.254.0.1", "100.64.0.1", "100.127.255.255", "::1", "fc00::1", "fe80::1"] {
            assert!(bench_address(ip.parse().unwrap()), "{ip}");
        }
        for ip in ["8.8.8.8", "100.63.255.255", "100.128.0.0", "2001:db8::1", "0.0.0.0", "::"] {
            assert!(!bench_address(ip.parse().unwrap()), "{ip}");
        }
    }
}