Skip to main content

rns_core/transport/
maintenance.rs

1use super::*;
2
3impl TransportEngine {
4    /// Periodic maintenance. Call regularly (e.g., every 250ms).
5    pub fn tick(&mut self, now: f64, rng: &mut dyn Rng) -> Vec<TransportAction> {
6        let mut ctx = TickCtx {
7            now,
8            rng,
9            actions: Vec::new(),
10        };
11        self.process_tick_pending_announces(&mut ctx);
12
13        let mut queue_actions = self.announce_queues.process_queues(now, &self.interfaces);
14        ctx.actions.append(&mut queue_actions);
15
16        self.process_tick_ingress_release(&mut ctx);
17        self.cull_tick_tables(&mut ctx);
18        ctx.actions
19    }
20
21    fn process_tick_pending_announces(&mut self, ctx: &mut TickCtx<'_>) {
22        if ctx.now <= self.announces_last_checked + constants::ANNOUNCES_CHECK_INTERVAL {
23            return;
24        }
25
26        self.cull_expired_announce_entries(ctx.now);
27        self.enforce_announce_retention_cap(ctx.now);
28        if let Some(identity_hash) = self.config.identity_hash {
29            let announce_actions = jobs::process_pending_announces(
30                &mut self.announce_table,
31                &mut self.held_announces,
32                &identity_hash,
33                ctx.now,
34            );
35            let gated = self.gate_retransmit_actions(announce_actions, ctx.now);
36            ctx.actions.extend(gated);
37        }
38        self.cull_expired_announce_entries(ctx.now);
39        self.enforce_announce_retention_cap(ctx.now);
40        self.announces_last_checked = ctx.now;
41    }
42
43    fn process_tick_ingress_release(&mut self, ctx: &mut TickCtx<'_>) {
44        let ic_interfaces = self.ingress_control.interfaces_with_held();
45        for iface_id in ic_interfaces {
46            let (ia_freq, started, ingress_config) = match self.interfaces.get(&iface_id) {
47                Some(info) => (info.ia_freq, info.started, info.ingress_control),
48                None => continue,
49            };
50            if !ingress_config.enabled {
51                continue;
52            }
53            if let Some(held) = self.ingress_control.process_held_announces(
54                iface_id,
55                &ingress_config,
56                ia_freq,
57                started,
58                ctx.now,
59            ) {
60                let released_actions = self.handle_inbound(
61                    InboundFrame {
62                        raw: &held.raw,
63                        iface: held.receiving_interface,
64                        now: ctx.now,
65                        rx: held.rx,
66                    },
67                    ctx.rng,
68                );
69                ctx.actions.extend(released_actions);
70            }
71        }
72    }
73
74    fn cull_tick_tables(&mut self, ctx: &mut TickCtx<'_>) {
75        if ctx.now <= self.tables_last_culled + constants::TABLES_CULL_INTERVAL {
76            return;
77        }
78
79        jobs::cull_path_table(&mut self.path_table, &self.interfaces, ctx.now);
80        jobs::cull_reverse_table(&mut self.reverse_table, &self.interfaces, ctx.now);
81        let (_culled, link_closed_actions) =
82            jobs::cull_link_table(&mut self.link_table, &self.interfaces, ctx.now);
83        ctx.actions.extend(link_closed_actions);
84        jobs::cull_path_states(&mut self.path_states, &self.path_table);
85        self.cull_blackholed(ctx.now);
86        self.path_requests.retain(|_, requested_at| {
87            ctx.now < *requested_at + constants::PATH_REQUEST_GATE_TIMEOUT
88        });
89        self.discovery_path_requests.retain(|destination, req| {
90            let deadline = self
91                .discovery_path_request_deadlines
92                .get(destination)
93                .copied()
94                .unwrap_or(req.timestamp + constants::DISCOVERY_PATH_REQUEST_TIMEOUT);
95            ctx.now < deadline
96        });
97        self.discovery_path_request_deadlines
98            .retain(|destination, _| self.discovery_path_requests.contains_key(destination));
99        self.tunnel_table
100            .void_missing_interfaces(|id| self.interfaces.contains_key(id));
101        self.tunnel_table.cull(ctx.now);
102        self.announce_sig_cache.cull(ctx.now);
103        self.tables_last_culled = ctx.now;
104    }
105
106    /// Gate retransmitted announce actions through per-interface bandwidth queues.
107    ///
108    /// Retransmitted announces always have hops > 0.
109    /// `BroadcastOnAllInterfaces` is expanded to per-interface sends gated through queues.
110    pub(super) fn gate_retransmit_actions(
111        &mut self,
112        actions: Vec<TransportAction>,
113        now: f64,
114    ) -> Vec<TransportAction> {
115        let mut result = Vec::new();
116        for action in actions {
117            match action {
118                TransportAction::SendOnInterface { interface, raw } => {
119                    // Extract dest_hash from raw (bytes 2..18 for H1, 18..34 for H2)
120                    let (dest_hash, hops) = Self::extract_announce_info(&raw);
121                    let (bitrate, airtime_profile, announce_cap) =
122                        if let Some(info) = self.interfaces.get(&interface) {
123                            (info.bitrate, info.airtime_profile, info.announce_cap)
124                        } else {
125                            (None, None, constants::ANNOUNCE_CAP)
126                        };
127                    if let Some(send_action) = self.announce_queues.gate_announce(
128                        interface,
129                        raw,
130                        dest_hash,
131                        hops,
132                        now,
133                        now,
134                        bitrate,
135                        airtime_profile,
136                        announce_cap,
137                    ) {
138                        result.push(send_action);
139                    }
140                }
141                TransportAction::BroadcastOnAllInterfaces { raw, exclude } => {
142                    let (dest_hash, hops) = Self::extract_announce_info(&raw);
143                    // Expand to per-interface sends gated through queues,
144                    // applying mode filtering (AP blocks non-local announces, etc.)
145                    let iface_ids: Vec<(
146                        InterfaceId,
147                        Option<u64>,
148                        Option<types::AirtimeProfile>,
149                        f64,
150                    )> = self
151                        .interfaces
152                        .iter()
153                        .filter(|(_, info)| info.out_capable)
154                        .filter(|(id, _)| {
155                            if let Some(ref ex) = exclude {
156                                **id != *ex
157                            } else {
158                                true
159                            }
160                        })
161                        .filter(|(_, info)| {
162                            should_transmit_announce(
163                                info,
164                                &dest_hash,
165                                hops,
166                                &self.local_destinations,
167                                &self.path_table,
168                                &self.interfaces,
169                            )
170                        })
171                        .map(|(id, info)| {
172                            (*id, info.bitrate, info.airtime_profile, info.announce_cap)
173                        })
174                        .collect();
175
176                    for (iface_id, bitrate, airtime_profile, announce_cap) in iface_ids {
177                        if let Some(send_action) = self.announce_queues.gate_announce(
178                            iface_id,
179                            raw.clone(),
180                            dest_hash,
181                            hops,
182                            now,
183                            now,
184                            bitrate,
185                            airtime_profile,
186                            announce_cap,
187                        ) {
188                            result.push(send_action);
189                        }
190                    }
191                }
192                other => result.push(other),
193            }
194        }
195        result
196    }
197
198    /// Extract destination hash and hops from raw announce bytes.
199    fn extract_announce_info(raw: &[u8]) -> ([u8; 16], u8) {
200        if raw.len() < 18 {
201            return ([0; 16], 0);
202        }
203        let header_type = (raw[0] >> 6) & 0x03;
204        let hops = raw[1];
205        if header_type == constants::HEADER_2 && raw.len() >= 34 {
206            // H2: transport_id at [2..18], dest_hash at [18..34]
207            let mut dest = [0u8; 16];
208            dest.copy_from_slice(&raw[18..34]);
209            (dest, hops)
210        } else {
211            // H1: dest_hash at [2..18]
212            let mut dest = [0u8; 16];
213            dest.copy_from_slice(&raw[2..18]);
214            (dest, hops)
215        }
216    }
217
218    #[cfg(test)]
219    #[allow(dead_code)]
220    pub(crate) fn link_table_ref(&self) -> &BTreeMap<[u8; 16], LinkEntry> {
221        &self.link_table
222    }
223}