Skip to main content

jacquard_batman_classic/
lib.rs

1//! Spec-faithful classic BATMAN next-hop routing engine.
2//!
3//! This engine implements the BATMAN protocol as described in the BATMAN IV
4//! specification. The key behavioural properties:
5//!
6//! - **TQ carried in OGM** — each `OriginatorAdvertisement` encodes a `tq`
7//!   field. Originators initialise it to 1000; re-broadcasting nodes apply
8//!   `tq_product(local_link_tq, received_tq)` before forwarding. Downstream
9//!   nodes read path quality directly from received OGMs.
10//! - **Hop-limit-bounded propagation** — OGMs carry a `remaining_hop_limit`
11//!   field decremented at each hop. OGMs reaching zero are not re-broadcast,
12//!   bounding propagation to `DEFAULT_OGM_HOP_LIMIT` hops.
13//! - **No Bellman-Ford** — path quality to remote originators is read from
14//!   received OGM TQ values, not computed locally over a gossip-merged topology
15//!   graph.
16//! - **No TQ enrichment** — quality is derived solely from
17//!   `ogm_equivalent_tq(LinkRuntimeState)`; Jacquard-specific link beliefs
18//!   (delivery confidence, symmetry, transfer rate, stability horizon) are not
19//!   incorporated.
20//! - **Echo-only bidirectionality** — a neighbor is confirmed bidirectional
21//!   only by receiving a local OGM echoed back via that neighbor. There is no
22//!   topology fallback.
23//! - **No bootstrap shortcut** — if no OGM receive-window data exists for a
24//!   path, no route candidate is produced. The engine starts silent and becomes
25//!   active only after OGMs have accumulated window data.
26//!
27//! These properties make `BatmanClassicEngine` a faithful baseline for
28//! comparison against Babel (which was designed to fix exactly the weaknesses
29//! of classic DV-gossip protocols: asymmetric-link handling, loop-freedom under
30//! topology change, and triggered rather than periodic-only updates).
31
32#![forbid(unsafe_code)]
33
34mod gossip;
35mod planner;
36mod planner_model;
37mod private_state;
38mod public_state;
39mod runtime;
40mod scoring;
41#[cfg(test)]
42mod validation;
43
44use std::collections::BTreeMap;
45
46use gossip::LearnedAdvertisement;
47use jacquard_core::{
48    Configuration, ConnectivityPosture, NodeId, Observation, RouteId, RoutePartitionClass,
49    RouteProtectionClass, RouteRepairClass, RouteShapeVisibility, RoutingEngineCapabilities,
50    RoutingEngineId,
51};
52pub use planner::{admit_route_from_snapshot, candidate_routes_from_snapshot};
53pub use planner_model::{
54    backend_route_id as batman_classic_backend_route_id, selected_neighbor_from_backend_route_id,
55    BatmanClassicPlannerModel, BatmanClassicPlannerSeed,
56};
57use public_state::{
58    ActiveBatmanClassicRoute, NeighborRanking, OgmReceiveWindow, OriginatorObservationTable,
59    ReceivedOgmInfo,
60};
61pub use public_state::{BatmanClassicPlannerSnapshot, BestNextHop, DecayWindow};
62
63pub const BATMAN_CLASSIC_ENGINE_ID: RoutingEngineId =
64    RoutingEngineId::from_contract_bytes(*b"jacquard.batmanc");
65
66pub const BATMAN_CLASSIC_CAPABILITIES: RoutingEngineCapabilities = RoutingEngineCapabilities {
67    engine: BATMAN_CLASSIC_ENGINE_ID,
68    max_protection: RouteProtectionClass::LinkProtected,
69    max_connectivity: ConnectivityPosture {
70        repair: RouteRepairClass::Repairable,
71        partition: RoutePartitionClass::ConnectedOnly,
72    },
73    repair_support: jacquard_core::RepairSupport::Unsupported,
74    hold_support: jacquard_core::HoldSupport::Unsupported,
75    decidable_admission: jacquard_core::DecidableSupport::Supported,
76    quantitative_bounds: jacquard_core::QuantitativeBoundSupport::ProductiveOnly,
77    reconfiguration_support: jacquard_core::ReconfigurationSupport::ReplaceOnly,
78    route_shape_visibility: RouteShapeVisibility::NextHopOnly,
79};
80
81pub struct BatmanClassicEngine<Transport, Effects> {
82    local_node_id: NodeId,
83    transport: Transport,
84    effects: Effects,
85    /// Most recently observed topology (direct, not gossip-merged).
86    latest_topology: Option<Observation<Configuration>>,
87    decay_window: DecayWindow,
88    /// Per originator, per forwarding neighbor: OGM receive windows used to
89    /// compute window-occupancy receive quality.
90    originator_receive_windows: BTreeMap<NodeId, BTreeMap<NodeId, OgmReceiveWindow>>,
91    /// Per originator, per forwarding neighbor: TQ scalar and hop count from
92    /// the most recently received OGM. Replaces the Bellman-Ford path
93    /// computation in the enhanced batman engine.
94    received_ogm_info: BTreeMap<NodeId, BTreeMap<NodeId, ReceivedOgmInfo>>,
95    /// Echo windows keyed by neighbor: populated when a local OGM is received
96    /// back via that neighbor. Used exclusively for bidirectionality gating
97    /// (no topology fallback).
98    bidirectional_receive_windows: BTreeMap<NodeId, OgmReceiveWindow>,
99    /// Best OGM per originator retained for TTL-bounded re-flooding.
100    learned_advertisements: BTreeMap<NodeId, LearnedAdvertisement>,
101    originator_observations: OriginatorObservationTable,
102    neighbor_rankings: BTreeMap<NodeId, NeighborRanking>,
103    best_next_hops: BTreeMap<NodeId, BestNextHop>,
104    active_routes: BTreeMap<RouteId, ActiveBatmanClassicRoute>,
105}
106
107impl<Transport, Effects> BatmanClassicEngine<Transport, Effects> {
108    #[must_use]
109    pub fn new(local_node_id: NodeId, transport: Transport, effects: Effects) -> Self {
110        Self::with_decay_window(local_node_id, transport, effects, DecayWindow::default())
111    }
112
113    #[must_use]
114    pub fn with_decay_window(
115        local_node_id: NodeId,
116        transport: Transport,
117        effects: Effects,
118        decay_window: DecayWindow,
119    ) -> Self {
120        Self {
121            local_node_id,
122            transport,
123            effects,
124            latest_topology: None,
125            decay_window,
126            originator_receive_windows: BTreeMap::new(),
127            received_ogm_info: BTreeMap::new(),
128            bidirectional_receive_windows: BTreeMap::new(),
129            learned_advertisements: BTreeMap::new(),
130            originator_observations: BTreeMap::new(),
131            neighbor_rankings: BTreeMap::new(),
132            best_next_hops: BTreeMap::new(),
133            active_routes: BTreeMap::new(),
134        }
135    }
136
137    pub(crate) fn planner_snapshot(&self) -> BatmanClassicPlannerSnapshot {
138        BatmanClassicPlannerSnapshot {
139            local_node_id: self.local_node_id,
140            stale_after_ticks: self.decay_window.stale_after_ticks,
141            best_next_hops: self.best_next_hops.clone(),
142        }
143    }
144}