// spec, Network layer -> "Reachability sensing and auto-steering" // (ReachabilityAdvert layout + invariants). // // Transport-layer telemetry, outside consensus state ([I-3] orthogonal). // The advert propagates over peer exchange; the aggregated map is advisory // and ranks candidate entry points only. No state-root participation. use alloc::vec::Vec; use mt_codec::{write_bytes, write_u16, write_u32, write_u8}; use crate::error::NetError; /// Wire size of a ReachabilityAdvert: 2 + 4 + 32 + 1 + 2 + 2 + 4. pub const REACHABILITY_ADVERT_SIZE: usize = 47; /// Per-vantage retained observation bound (mirrors the IBT online-nonce bound). pub const MAX_OBSERVATIONS_PER_VANTAGE: usize = 256; /// Distinct /16 source groups required to act on a reachability triple. pub const REACHABILITY_QUORUM: usize = 3; /// Highest transport profile index (T0..T4). pub const PROFILE_MAX: u8 = 4; /// Advisory record of one vantage's reachability to one peer on one transport /// profile. Propagated over peer exchange; aggregated into the reachability map. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ReachabilityAdvert { /// ISO-3166-1 alpha-2 of the observing vantage. pub country_code: [u8; 2], /// Autonomous system of the observing vantage. pub asn: u32, /// node_id of the observed peer. pub target_ref: [u8; 32], /// Transport profile observed (T0..T4 = 0..4). pub profile: u8, /// Corroborating observations with outcome = reachable. pub reachable_num: u16, /// Total observations for the triple. pub reachable_den: u16, /// Cached window_index of the latest observation. pub observed_window: u32, } fn is_iso_alpha(b: u8) -> bool { b.is_ascii_uppercase() } impl ReachabilityAdvert { pub fn encode(&self, buf: &mut Vec) { write_bytes(buf, &self.country_code); write_u32(buf, self.asn); write_bytes(buf, &self.target_ref); write_u8(buf, self.profile); write_u16(buf, self.reachable_num); write_u16(buf, self.reachable_den); write_u32(buf, self.observed_window); } pub fn decode(input: &[u8]) -> Result { if input.len() != REACHABILITY_ADVERT_SIZE { return Err(NetError::PayloadLengthMismatch); } let mut country_code = [0u8; 2]; country_code.copy_from_slice(&input[0..2]); let mut asn_b = [0u8; 4]; asn_b.copy_from_slice(&input[2..6]); let asn = u32::from_le_bytes(asn_b); let mut target_ref = [0u8; 32]; target_ref.copy_from_slice(&input[6..38]); let profile = input[38]; let mut num_b = [0u8; 2]; num_b.copy_from_slice(&input[39..41]); let reachable_num = u16::from_le_bytes(num_b); let mut den_b = [0u8; 2]; den_b.copy_from_slice(&input[41..43]); let reachable_den = u16::from_le_bytes(den_b); let mut win_b = [0u8; 4]; win_b.copy_from_slice(&input[43..47]); let observed_window = u32::from_le_bytes(win_b); // Invariants ReachabilityAdvert (spec): // country_code is two ISO-3166-1 alpha-2 letters. if !is_iso_alpha(country_code[0]) || !is_iso_alpha(country_code[1]) { return Err(NetError::InvalidPayloadField); } // profile in T0..T4. if profile > PROFILE_MAX { return Err(NetError::InvalidPayloadField); } // reachable_den >= 1 and reachable_num <= reachable_den. if reachable_den == 0 || reachable_num > reachable_den { return Err(NetError::InvalidPayloadField); } Ok(ReachabilityAdvert { country_code, asn, target_ref, profile, reachable_num, reachable_den, observed_window, }) } /// Ranking ratio (num, den). Advisory only; forms no consensus state. pub fn reachable_fraction(&self) -> (u16, u16) { (self.reachable_num, self.reachable_den) } /// Staleness gate: the advert is fresh when its observed_window lies within /// [known_window - staleness_bound, known_window]. The mesh-IBT staleness /// bound (7 * tau1) is supplied by the caller. pub fn is_fresh(&self, known_window: u32, staleness_bound: u32) -> bool { let lo = known_window.saturating_sub(staleness_bound); self.observed_window >= lo && self.observed_window <= known_window } } /// Aggregated, advisory reachability map. Ingests adverts keyed by /// (country_code, asn, target_ref, profile); a triple becomes actionable only /// when corroborated by at least REACHABILITY_QUORUM distinct /16 source groups /// (the diversity unit of the outgoing-connection constraints). Bounded per /// vantage by MAX_OBSERVATIONS_PER_VANTAGE; outside consensus state. use alloc::collections::{BTreeMap, BTreeSet}; type TripleKey = ([u8; 2], u32, [u8; 32], u8); #[derive(Debug, Clone)] struct TripleAgg { sources: BTreeSet<[u8; 2]>, latest: ReachabilityAdvert, } #[derive(Debug, Default)] pub struct ReachabilityMap { entries: BTreeMap, } /// A ranked, actionable entry candidate for auto-steering. #[derive(Debug, Clone, PartialEq, Eq)] pub struct RankedEntry { pub target_ref: [u8; 32], pub profile: u8, pub reachable_num: u16, pub reachable_den: u16, } impl ReachabilityMap { pub fn new() -> Self { ReachabilityMap { entries: BTreeMap::new(), } } fn key(a: &ReachabilityAdvert) -> TripleKey { (a.country_code, a.asn, a.target_ref, a.profile) } /// Ingest an advert reported by a peer in `source_prefix16` (/16 of the /// reporter), valid at `known_window`. Stale adverts are rejected. The /// per-vantage entry count is bounded; on overflow the lowest-fraction /// triple for that vantage is evicted. pub fn ingest( &mut self, advert: ReachabilityAdvert, source_prefix16: [u8; 2], known_window: u32, staleness_bound: u32, ) -> bool { if !advert.is_fresh(known_window, staleness_bound) { return false; } let k = Self::key(&advert); let vantage = (advert.country_code, advert.asn); let entry = self.entries.entry(k).or_insert_with(|| TripleAgg { sources: BTreeSet::new(), latest: advert.clone(), }); entry.sources.insert(source_prefix16); entry.latest = advert; self.enforce_vantage_bound(vantage); true } fn enforce_vantage_bound(&mut self, vantage: ([u8; 2], u32)) { let count = self .entries .keys() .filter(|(cc, asn, _, _)| (*cc, *asn) == vantage) .count(); if count <= MAX_OBSERVATIONS_PER_VANTAGE { return; } // Evict the lowest reachable_fraction triple for this vantage. let victim = self .entries .iter() .filter(|((cc, asn, _, _), _)| (*cc, *asn) == vantage) .min_by(|(_, a), (_, b)| { let fa = a.latest.reachable_num as u32 * b.latest.reachable_den as u32; let fb = b.latest.reachable_num as u32 * a.latest.reachable_den as u32; fa.cmp(&fb) }) .map(|(k, _)| *k); if let Some(k) = victim { self.entries.remove(&k); } } /// True when the triple is corroborated by at least REACHABILITY_QUORUM /// distinct /16 source groups. pub fn is_actionable(&self, advert: &ReachabilityAdvert) -> bool { self.entries .get(&Self::key(advert)) .map(|e| e.sources.len() >= REACHABILITY_QUORUM) .unwrap_or(false) } /// Actionable entry candidates for a vantage, ranked by reachable_fraction /// descending (cross-multiplication, no floating point). Advisory ranking /// for auto-steering; the local IBT probe remains authoritative. pub fn ranked_for_vantage(&self, country_code: [u8; 2], asn: u32) -> Vec { let mut out: Vec = self .entries .iter() .filter(|((cc, a, _, _), agg)| { *cc == country_code && *a == asn && agg.sources.len() >= REACHABILITY_QUORUM }) .map(|((_, _, target, profile), agg)| RankedEntry { target_ref: *target, profile: *profile, reachable_num: agg.latest.reachable_num, reachable_den: agg.latest.reachable_den, }) .collect(); out.sort_by(|x, y| { let lhs = x.reachable_num as u32 * y.reachable_den as u32; let rhs = y.reachable_num as u32 * x.reachable_den as u32; rhs.cmp(&lhs) }); out } /// Reorder diversity-selected candidate node_ids by reachable_fraction for /// the given vantage, highest first. Candidates with an actionable map entry /// (>= REACHABILITY_QUORUM distinct /16) are ranked ahead of candidates with /// none; the latter keep their input relative order. Diversity is enforced by /// the caller (PeerTable::select_diverse_outbound); steering only reorders /// within the already-satisfied set, and the local IBT probe stays /// authoritative over this advisory order. pub fn steer(&self, candidates: &[[u8; 32]], country_code: [u8; 2], asn: u32) -> Vec<[u8; 32]> { let ranked = self.ranked_for_vantage(country_code, asn); // best (num, den) per target across its profiles let mut best: BTreeMap<[u8; 32], (u16, u16)> = BTreeMap::new(); for e in &ranked { let cur = best.get(&e.target_ref).copied(); let better = match cur { None => true, Some((n, d)) => { (e.reachable_num as u32 * d as u32) > (n as u32 * e.reachable_den as u32) }, }; if better { best.insert(e.target_ref, (e.reachable_num, e.reachable_den)); } } let mut have: Vec<[u8; 32]> = Vec::new(); let mut none: Vec<[u8; 32]> = Vec::new(); for c in candidates { if best.contains_key(c) { have.push(*c); } else { none.push(*c); } } have.sort_by(|x, y| { let (xn, xd) = best[x]; let (yn, yd) = best[y]; let lhs = xn as u32 * yd as u32; let rhs = yn as u32 * xd as u32; rhs.cmp(&lhs) }); have.extend(none); have } pub fn len(&self) -> usize { self.entries.len() } pub fn is_empty(&self) -> bool { self.entries.is_empty() } } #[cfg(test)] mod tests { use super::*; use alloc::vec::Vec; fn sample() -> ReachabilityAdvert { ReachabilityAdvert { country_code: *b"AM", asn: 0x0001_0203, target_ref: [0x11u8; 32], profile: 1, reachable_num: 3, reachable_den: 4, observed_window: 0x0000_4ec0, } } #[test] fn roundtrip() { let a = sample(); let mut buf = Vec::new(); a.encode(&mut buf); assert_eq!(buf.len(), REACHABILITY_ADVERT_SIZE); let b = ReachabilityAdvert::decode(&buf).unwrap(); assert_eq!(a, b); } #[test] fn kat_byte_exact() { // Binding KAT: fixed advert -> exact 47-byte little-endian encoding. let a = sample(); let mut buf = Vec::new(); a.encode(&mut buf); let mut expected = Vec::new(); expected.extend_from_slice(b"AM"); // country_code expected.extend_from_slice(&[0x03, 0x02, 0x01, 0x00]); // asn LE expected.extend_from_slice(&[0x11u8; 32]); // target_ref expected.push(0x01); // profile expected.extend_from_slice(&[0x03, 0x00]); // reachable_num LE expected.extend_from_slice(&[0x04, 0x00]); // reachable_den LE expected.extend_from_slice(&[0xc0, 0x4e, 0x00, 0x00]); // observed_window LE assert_eq!(buf, expected); } #[test] fn reject_wrong_length() { assert!(matches!( ReachabilityAdvert::decode(&[0u8; 46]), Err(NetError::PayloadLengthMismatch) )); } #[test] fn reject_bad_country() { let mut a = sample(); a.country_code = *b"a1"; let mut buf = Vec::new(); a.encode(&mut buf); assert!(matches!( ReachabilityAdvert::decode(&buf), Err(NetError::InvalidPayloadField) )); } #[test] fn reject_bad_profile() { let mut a = sample(); a.profile = 5; let mut buf = Vec::new(); a.encode(&mut buf); assert!(matches!( ReachabilityAdvert::decode(&buf), Err(NetError::InvalidPayloadField) )); } #[test] fn reject_den_zero_and_num_gt_den() { let mut a = sample(); a.reachable_den = 0; let mut buf = Vec::new(); a.encode(&mut buf); assert!(matches!( ReachabilityAdvert::decode(&buf), Err(NetError::InvalidPayloadField) )); let mut a2 = sample(); a2.reachable_num = 5; a2.reachable_den = 4; let mut buf2 = Vec::new(); a2.encode(&mut buf2); assert!(matches!( ReachabilityAdvert::decode(&buf2), Err(NetError::InvalidPayloadField) )); } #[test] fn freshness_window() { let a = sample(); // observed_window = 0x4ec0 = 20160 assert!(a.is_fresh(20160, 100)); assert!(a.is_fresh(20200, 100)); // within [20100, 20200] assert!(!a.is_fresh(20300, 100)); // below 20200 lower bound assert!(!a.is_fresh(20159, 100)); // above known } #[test] fn map_quorum_gate() { let mut m = ReachabilityMap::new(); let a = sample(); // two distinct /16 -> not actionable m.ingest(a.clone(), [10, 0], 20160, 100); m.ingest(a.clone(), [11, 0], 20160, 100); assert!(!m.is_actionable(&a)); // third distinct /16 -> actionable m.ingest(a.clone(), [12, 0], 20160, 100); assert!(m.is_actionable(&a)); } #[test] fn map_same_prefix_not_quorum() { let mut m = ReachabilityMap::new(); let a = sample(); // three reports from the SAME /16 -> still one distinct source m.ingest(a.clone(), [10, 0], 20160, 100); m.ingest(a.clone(), [10, 0], 20160, 100); m.ingest(a.clone(), [10, 0], 20160, 100); assert!(!m.is_actionable(&a)); } #[test] fn map_stale_rejected() { let mut m = ReachabilityMap::new(); let a = sample(); // observed_window = 20160 assert!(!m.ingest(a.clone(), [10, 0], 21000, 100)); // 20160 < 20900 lower bound assert!(m.is_empty()); } #[test] fn map_ranking_desc() { let mut m = ReachabilityMap::new(); let mut hi = sample(); hi.target_ref = [0xAAu8; 32]; hi.reachable_num = 9; hi.reachable_den = 10; // 0.9 let mut lo = sample(); lo.target_ref = [0xBBu8; 32]; lo.reachable_num = 1; lo.reachable_den = 10; // 0.1 for src in [[1u8, 0], [2, 0], [3, 0]] { m.ingest(hi.clone(), src, 20160, 100); m.ingest(lo.clone(), src, 20160, 100); } let ranked = m.ranked_for_vantage(*b"AM", 0x0001_0203); assert_eq!(ranked.len(), 2); assert_eq!(ranked[0].target_ref, [0xAAu8; 32]); // higher fraction first assert_eq!(ranked[1].target_ref, [0xBBu8; 32]); } #[test] fn steer_orders_ranked_first_then_unranked() { let mut m = ReachabilityMap::new(); let a = [0xAAu8; 32]; let b = [0xBBu8; 32]; let c = [0xCCu8; 32]; let mk = |t: [u8; 32], n: u16, d: u16| { let mut x = sample(); x.target_ref = t; x.reachable_num = n; x.reachable_den = d; x }; for src in [[1u8, 0], [2, 0], [3, 0]] { m.ingest(mk(a, 1, 10), src, 20160, 100); // 0.1 m.ingest(mk(c, 9, 10), src, 20160, 100); // 0.9 } // b has no map entry; candidates in arbitrary order [a, b, c] let out = m.steer(&[a, b, c], *b"AM", 0x0001_0203); // ranked desc (c=0.9, a=0.1) then unranked b assert_eq!(out, vec![c, a, b]); } #[test] fn steer_empty_map_preserves_order() { let m = ReachabilityMap::new(); let a = [0xAAu8; 32]; let b = [0xBBu8; 32]; assert_eq!(m.steer(&[a, b], *b"AM", 1), vec![a, b]); } }