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        // Transport.py culls these only when the requesting interface is
100        // gone; also expire them, as a local client answers right away.
101        self.pending_local_path_requests
102            .retain(|_, (interface, requested_at)| {
103                self.interfaces.contains_key(interface)
104                    && ctx.now < *requested_at + constants::PATH_REQUEST_TIMEOUT
105            });
106        self.tunnel_table
107            .void_missing_interfaces(|id| self.interfaces.contains_key(id));
108        self.tunnel_table.cull(ctx.now);
109        self.announce_sig_cache.cull(ctx.now);
110        self.tables_last_culled = ctx.now;
111    }
112
113    /// Gate retransmitted announce actions through per-interface bandwidth queues.
114    ///
115    /// Retransmitted announces always have hops > 0.
116    /// `BroadcastOnAllInterfaces` is expanded to per-interface sends gated through queues.
117    pub(super) fn gate_retransmit_actions(
118        &mut self,
119        actions: Vec<TransportAction>,
120        now: f64,
121    ) -> Vec<TransportAction> {
122        let mut result = Vec::new();
123        for action in actions {
124            match action {
125                TransportAction::SendOnInterface { interface, raw } => {
126                    // Extract dest_hash from raw (bytes 2..18 for H1, 18..34 for H2)
127                    let (dest_hash, hops) = Self::extract_announce_info(&raw);
128                    let (bitrate, airtime_profile, announce_cap) =
129                        if let Some(info) = self.interfaces.get(&interface) {
130                            (info.bitrate, info.airtime_profile, info.announce_cap)
131                        } else {
132                            (None, None, constants::ANNOUNCE_CAP)
133                        };
134                    if let Some(send_action) = self.announce_queues.gate_announce(
135                        interface,
136                        raw,
137                        dest_hash,
138                        hops,
139                        now,
140                        now,
141                        bitrate,
142                        airtime_profile,
143                        announce_cap,
144                    ) {
145                        result.push(send_action);
146                    }
147                }
148                TransportAction::BroadcastOnAllInterfaces { raw, exclude } => {
149                    let (dest_hash, hops) = Self::extract_announce_info(&raw);
150                    // Expand to per-interface sends gated through queues,
151                    // applying mode filtering (AP blocks non-local announces, etc.)
152                    let iface_ids: Vec<(
153                        InterfaceId,
154                        Option<u64>,
155                        Option<types::AirtimeProfile>,
156                        f64,
157                    )> = self
158                        .interfaces
159                        .iter()
160                        .filter(|(_, info)| info.out_capable)
161                        // Without transport, the only rebroadcasts are local
162                        // client announces, which the local client relay has
163                        // already delivered to the other local clients.
164                        .filter(|(_, info)| self.config.transport_enabled || !info.is_local_client)
165                        .filter(|(id, _)| {
166                            if let Some(ref ex) = exclude {
167                                **id != *ex
168                            } else {
169                                true
170                            }
171                        })
172                        .filter(|(_, info)| {
173                            should_transmit_announce(
174                                info,
175                                &dest_hash,
176                                hops,
177                                &self.local_destinations,
178                                &self.path_table,
179                                &self.interfaces,
180                            )
181                        })
182                        .map(|(id, info)| {
183                            (*id, info.bitrate, info.airtime_profile, info.announce_cap)
184                        })
185                        .collect();
186
187                    for (iface_id, bitrate, airtime_profile, announce_cap) in iface_ids {
188                        if let Some(send_action) = self.announce_queues.gate_announce(
189                            iface_id,
190                            raw.clone(),
191                            dest_hash,
192                            hops,
193                            now,
194                            now,
195                            bitrate,
196                            airtime_profile,
197                            announce_cap,
198                        ) {
199                            result.push(send_action);
200                        }
201                    }
202                }
203                other => result.push(other),
204            }
205        }
206        result
207    }
208
209    /// Extract destination hash and hops from raw announce bytes.
210    fn extract_announce_info(raw: &[u8]) -> ([u8; 16], u8) {
211        if raw.len() < 18 {
212            return ([0; 16], 0);
213        }
214        let header_type = (raw[0] >> 6) & 0x03;
215        let hops = raw[1];
216        if header_type == constants::HEADER_2 && raw.len() >= 34 {
217            // H2: transport_id at [2..18], dest_hash at [18..34]
218            let mut dest = [0u8; 16];
219            dest.copy_from_slice(&raw[18..34]);
220            (dest, hops)
221        } else {
222            // H1: dest_hash at [2..18]
223            let mut dest = [0u8; 16];
224            dest.copy_from_slice(&raw[2..18]);
225            (dest, hops)
226        }
227    }
228
229    #[cfg(test)]
230    #[allow(dead_code)]
231    pub(crate) fn link_table_ref(&self) -> &BTreeMap<[u8; 16], LinkEntry> {
232        &self.link_table
233    }
234}