1use super::*;
2
3pub fn lowest_interface_bitrate(interfaces: &BTreeMap<InterfaceId, InterfaceInfo>) -> Option<u64> {
4 interfaces
7 .values()
8 .filter_map(|interface| interface.bitrate)
9 .filter(|bitrate| *bitrate > 0)
10 .min()
11}
12
13pub fn medium_path_timeout(interfaces: &BTreeMap<InterfaceId, InterfaceInfo>) -> f64 {
14 let Some(lowest_bitrate) = lowest_interface_bitrate(interfaces) else {
15 return 0.0;
16 };
17 let effective_bitrate = lowest_bitrate.max(constants::MINIMUM_BITRATE);
18 2.0 * (constants::MTU as f64 * 8.0 / effective_bitrate as f64)
19 + constants::LINK_ESTABLISHMENT_TIMEOUT_PER_HOP
20}
21
22pub(super) fn discovery_path_request_timeout(
23 interfaces: &BTreeMap<InterfaceId, InterfaceInfo>,
24) -> f64 {
25 constants::PATH_REQUEST_TIMEOUT.max(medium_path_timeout(interfaces))
26}
27
28impl TransportEngine {
29 #[doc(hidden)]
31 pub fn should_egress_limit_path_request(
32 &mut self,
33 interface_id: InterfaceId,
34 pr_freq: f64,
35 sample_count: usize,
36 ) -> bool {
37 let Some(config) = self
38 .interfaces
39 .get(&interface_id)
40 .map(|info| info.ingress_control)
41 else {
42 return false;
43 };
44 self.ingress_control
45 .should_egress_limit_pr(interface_id, &config, pr_freq, sample_count)
46 }
47
48 pub fn handle_path_request(
49 &mut self,
50 data: &[u8],
51 interface_id: InterfaceId,
52 now: f64,
53 ) -> Vec<TransportAction> {
54 self.handle_path_request_with_ingress_limit(data, interface_id, now, false)
55 }
56
57 #[doc(hidden)]
60 pub fn handle_path_request_with_ingress_limit(
61 &mut self,
62 data: &[u8],
63 interface_id: InterfaceId,
64 now: f64,
65 ingress_limited: bool,
66 ) -> Vec<TransportAction> {
67 let Some(request) = self.accept_path_request(data, interface_id, now) else {
68 return Vec::new();
69 };
70 self.handle_accepted_path_request_with_ingress_limit(request, ingress_limited)
71 }
72
73 #[doc(hidden)]
75 pub fn accept_path_request(
76 &mut self,
77 data: &[u8],
78 interface_id: InterfaceId,
79 now: f64,
80 ) -> Option<AcceptedPathRequest> {
81 self.parse_path_request(data, interface_id, now)
82 }
83
84 #[doc(hidden)]
86 pub fn handle_accepted_path_request_with_ingress_limit(
87 &mut self,
88 ctx: AcceptedPathRequest,
89 ingress_limited: bool,
90 ) -> Vec<TransportAction> {
91 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
92 "Path request for {:02x?} on interface {}",
93 &ctx.destination_hash[..4],
94 ctx.interface_id.0,
95 );
96 if ctx.already_in_flight {
97 self.batch_inflight_path_request(&ctx, ingress_limited);
98 return Vec::new();
99 }
100 if self.local_destinations.contains_key(&ctx.destination_hash) {
101 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
102 "Ignoring path request for {:02x?}: destination is local",
103 &ctx.destination_hash[..4],
104 );
105 return Vec::new();
106 }
107 if self.config.transport_enabled && self.handle_known_path_request(&ctx) {
108 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
109 "Answering path request for {:02x?}: path is known",
110 &ctx.destination_hash[..4],
111 );
112 return Vec::new();
113 }
114 if self.config.transport_enabled {
115 return self.handle_discovery_path_request(&ctx, ingress_limited);
116 }
117 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
118 "Ignoring path request for {:02x?}: transport is disabled",
119 &ctx.destination_hash[..4],
120 );
121 Vec::new()
122 }
123
124 fn parse_path_request(
125 &mut self,
126 data: &[u8],
127 interface_id: InterfaceId,
128 now: f64,
129 ) -> Option<AcceptedPathRequest> {
130 if data.len() < 16 {
131 return None;
132 }
133
134 let mut destination_hash = [0u8; 16];
135 destination_hash.copy_from_slice(&data[..16]);
136
137 let tag_bytes = if data.len() > 32 {
138 Some(&data[32..])
139 } else if data.len() > 16 {
140 Some(&data[16..])
141 } else {
142 None
143 };
144 let Some(tag_bytes) = tag_bytes else {
145 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
146 "Ignoring tagless path request for {:02x?}",
147 &destination_hash[..4],
148 );
149 return None;
150 };
151
152 let tag_len = tag_bytes.len().min(16);
153 let mut unique_tag = [0u8; 32];
154 unique_tag[..16].copy_from_slice(&destination_hash);
155 unique_tag[16..16 + tag_len].copy_from_slice(&tag_bytes[..tag_len]);
156 if !self.insert_discovery_pr_tag(unique_tag) {
157 return None;
158 }
159
160 let already_in_flight = self.path_requests.contains_key(&destination_hash)
161 && !self.path_table.contains_key(&destination_hash)
162 && !self.local_destinations.contains_key(&destination_hash);
163 self.path_requests.entry(destination_hash).or_insert(now);
164
165 let mut tag = [0u8; 16];
166 tag[..tag_len].copy_from_slice(&tag_bytes[..tag_len]);
167 Some(AcceptedPathRequest {
168 tag,
169 tag_len,
170 interface_id,
171 now,
172 destination_hash,
173 already_in_flight,
174 })
175 }
176
177 fn batch_inflight_path_request(&mut self, ctx: &AcceptedPathRequest, ingress_limited: bool) {
178 let Some((ingress_control, ip_freq, started)) = self
179 .interfaces
180 .get(&ctx.interface_id)
181 .map(|info| (info.ingress_control, info.ip_freq, info.started))
182 else {
183 return;
184 };
185 if ingress_limited
186 || self.ingress_control.should_ingress_limit_pr(
187 ctx.interface_id,
188 &ingress_control,
189 ip_freq,
190 started,
191 ctx.now,
192 )
193 {
194 return;
195 }
196
197 let timeout = discovery_path_request_timeout(&self.interfaces);
198 let request = self
199 .discovery_path_requests
200 .entry(ctx.destination_hash)
201 .or_insert_with(|| DiscoveryPathRequest {
202 timestamp: ctx.now,
203 requesting_interfaces: Vec::new(),
204 engaged: false,
205 });
206 if !request.requesting_interfaces.contains(&ctx.interface_id) {
207 request.requesting_interfaces.push(ctx.interface_id);
208 }
209 self.discovery_path_request_deadlines
210 .entry(ctx.destination_hash)
211 .or_insert(ctx.now + timeout);
212 }
213
214 #[doc(hidden)]
216 pub fn record_outbound_path_request(&mut self, destination_hash: [u8; 16], now: f64) {
217 self.path_requests.insert(destination_hash, now);
218 }
219
220 fn handle_known_path_request(&mut self, ctx: &AcceptedPathRequest) -> bool {
221 let Some(path) = self
222 .path_table
223 .get(&ctx.destination_hash)
224 .and_then(|ps| ps.primary())
225 .cloned()
226 else {
227 return false;
228 };
229
230 if let Some(recv_info) = self.interfaces.get(&ctx.interface_id) {
231 if recv_info.mode == constants::MODE_ROAMING
232 && path.receiving_interface == ctx.interface_id
233 {
234 return true;
235 }
236 }
237
238 let Some(raw) = path.announce_raw.as_ref() else {
239 return false;
240 };
241 if let Some(existing) = self.announce_table.get(&ctx.destination_hash).cloned() {
244 self.insert_held_announce(ctx.destination_hash, existing, ctx.now);
245 }
246 let retransmit_timeout = if let Some(iface_info) = self.interfaces.get(&ctx.interface_id) {
247 let base = ctx.now + constants::PATH_REQUEST_GRACE;
248 if iface_info.mode == constants::MODE_ROAMING {
249 base + constants::PATH_REQUEST_RG
250 } else {
251 base
252 }
253 } else {
254 ctx.now + constants::PATH_REQUEST_GRACE
255 };
256
257 let Ok(parsed) = RawPacket::unpack(raw) else {
258 return false;
259 };
260
261 let entry = AnnounceEntry {
262 timestamp: ctx.now,
263 retransmit_timeout,
264 retries: constants::PATHFINDER_R,
265 received_from: path.next_hop,
266 hops: path.hops,
267 packet_raw: raw.clone(),
268 packet_data: parsed.data().to_vec(),
269 destination_hash: ctx.destination_hash,
270 context_flag: parsed.flags.context_flag,
271 local_rebroadcasts: 0,
272 block_rebroadcasts: true,
273 attached_interface: Some(ctx.interface_id),
274 };
275
276 self.insert_announce_entry(ctx.destination_hash, entry, ctx.now);
277 true
278 }
279
280 fn handle_discovery_path_request(
281 &mut self,
282 ctx: &AcceptedPathRequest,
283 ingress_limited: bool,
284 ) -> Vec<TransportAction> {
285 let Some((mode, recursive_prs, ingress_control, ip_freq, started)) =
286 self.interfaces.get(&ctx.interface_id).map(|info| {
287 (
288 info.mode,
289 info.recursive_prs,
290 info.ingress_control,
291 info.ip_freq,
292 info.started,
293 )
294 })
295 else {
296 return Vec::new();
297 };
298
299 let search_mode_filter: Option<&[u8]> = if recursive_prs
300 || constants::DISCOVER_PATHS_FOR.contains(&mode)
301 {
302 None
303 } else if mode == constants::MODE_BOUNDARY {
304 Some(&constants::BOUNDARY_SEARCH_MODES)
305 } else {
306 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
307 "Not discovering path to {:02x?}: recursive path discovery is disabled on interface {}",
308 &ctx.destination_hash[..4],
309 ctx.interface_id.0,
310 );
311 return Vec::new();
312 };
313
314 if ingress_limited
315 || self.ingress_control.should_ingress_limit_pr(
316 ctx.interface_id,
317 &ingress_control,
318 ip_freq,
319 started,
320 ctx.now,
321 )
322 {
323 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
324 "Not discovering path to {:02x?}: ingress path-request limiting is active on interface {}",
325 &ctx.destination_hash[..4],
326 ctx.interface_id.0,
327 );
328 return Vec::new();
329 }
330
331 let egress_candidates: Vec<_> = self
332 .interfaces
333 .values()
334 .filter(|info| info.id != ctx.interface_id && info.out_capable)
335 .filter(|info| search_mode_filter.is_none_or(|modes| modes.contains(&info.mode)))
336 .map(|info| {
337 (
338 info.id,
339 info.ingress_control,
340 info.op_freq,
341 info.op_samples,
342 info.bitrate,
343 info.airtime_profile,
344 info.announce_cap,
345 )
346 })
347 .collect();
348
349 let Some((path_request_raw, path_request_len)) = build_path_request_packet(
350 &ctx.destination_hash,
351 self.config.identity_hash.as_ref(),
352 &ctx.tag[..ctx.tag_len],
353 ) else {
354 return Vec::new();
355 };
356
357 let mut actions = Vec::new();
358 for (id, ingress_control, op_freq, op_samples, bitrate, airtime_profile, announce_cap) in
359 egress_candidates
360 {
361 if self.ingress_control.should_egress_limit_pr(
362 id,
363 &ingress_control,
364 op_freq,
365 op_samples,
366 ) || self
367 .announce_queues
368 .blocks_recursive_path_request(id, ctx.now)
369 {
370 continue;
371 }
372
373 self.announce_queues.reserve_recursive_path_request(
374 id,
375 path_request_len + constants::HEADER_MINSIZE,
376 ctx.now,
377 bitrate,
378 airtime_profile,
379 announce_cap,
380 );
381 actions.push(TransportAction::SendOnInterface {
382 interface: id,
383 raw: path_request_raw.clone().into(),
384 });
385 }
386
387 if !actions.is_empty() {
388 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
389 "Discovering unknown path to {:02x?} on behalf of interface {} via {} interfaces",
390 &ctx.destination_hash[..4],
391 ctx.interface_id.0,
392 actions.len(),
393 );
394 let request = self
395 .discovery_path_requests
396 .entry(ctx.destination_hash)
397 .or_insert_with(|| DiscoveryPathRequest {
398 timestamp: ctx.now,
399 requesting_interfaces: Vec::new(),
400 engaged: false,
401 });
402 if !request.requesting_interfaces.contains(&ctx.interface_id) {
403 request.requesting_interfaces.push(ctx.interface_id);
404 }
405 request.engaged = true;
406 let timeout = discovery_path_request_timeout(&self.interfaces);
407 self.discovery_path_request_deadlines
408 .insert(ctx.destination_hash, ctx.now + timeout);
409 } else {
410 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
411 "Not discovering path to {:02x?}: no eligible egress interface",
412 &ctx.destination_hash[..4],
413 );
414 }
415
416 actions
417 }
418}
419
420fn build_path_request_packet(
421 destination_hash: &[u8; 16],
422 transport_identity_hash: Option<&[u8; 16]>,
423 tag: &[u8],
424) -> Option<(Vec<u8>, usize)> {
425 let mut data = Vec::with_capacity(16 + transport_identity_hash.map_or(0, |_| 16) + tag.len());
426 data.extend_from_slice(destination_hash);
427 if let Some(identity_hash) = transport_identity_hash {
428 data.extend_from_slice(identity_hash);
429 }
430 data.extend_from_slice(tag);
431
432 let flags = crate::packet::PacketFlags {
433 header_type: constants::HEADER_1,
434 context_flag: constants::FLAG_UNSET,
435 transport_type: constants::TRANSPORT_BROADCAST,
436 destination_type: constants::DESTINATION_PLAIN,
437 packet_type: constants::PACKET_TYPE_DATA,
438 };
439 let path_request_dest =
440 crate::destination::destination_hash("rnstransport", &["path", "request"], None);
441
442 let data_len = data.len();
443 RawPacket::pack(
444 flags,
445 0,
446 &path_request_dest,
447 None,
448 constants::CONTEXT_NONE,
449 &data,
450 )
451 .ok()
452 .map(|packet| (packet.raw, data_len))
453}