1use super::*;
2
3impl TransportEngine {
4 pub fn handle_path_request(
5 &mut self,
6 data: &[u8],
7 interface_id: InterfaceId,
8 now: f64,
9 ) -> Vec<TransportAction> {
10 let Some(ctx) = self.parse_path_request(data, interface_id, now) else {
11 return Vec::new();
12 };
13 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
14 "Path request for {:02x?} on interface {}",
15 &ctx.destination_hash[..4],
16 interface_id.0,
17 );
18 if self.local_destinations.contains_key(&ctx.destination_hash) {
19 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
20 "Ignoring path request for {:02x?}: destination is local",
21 &ctx.destination_hash[..4],
22 );
23 return Vec::new();
24 }
25 if self.config.transport_enabled && self.handle_known_path_request(&ctx) {
26 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
27 "Answering path request for {:02x?}: path is known",
28 &ctx.destination_hash[..4],
29 );
30 return Vec::new();
31 }
32 if self.config.transport_enabled {
33 return self.handle_discovery_path_request(&ctx);
34 }
35 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
36 "Ignoring path request for {:02x?}: transport is disabled",
37 &ctx.destination_hash[..4],
38 );
39 Vec::new()
40 }
41
42 fn parse_path_request<'a>(
43 &mut self,
44 data: &'a [u8],
45 interface_id: InterfaceId,
46 now: f64,
47 ) -> Option<PathRequestCtx<'a>> {
48 if data.len() < 16 {
49 return None;
50 }
51
52 let mut destination_hash = [0u8; 16];
53 destination_hash.copy_from_slice(&data[..16]);
54
55 let tag_bytes = if data.len() > 32 {
56 Some(&data[32..])
57 } else if data.len() > 16 {
58 Some(&data[16..])
59 } else {
60 None
61 }?;
62
63 let tag_len = tag_bytes.len().min(16);
64 let mut unique_tag = [0u8; 32];
65 unique_tag[..16].copy_from_slice(&destination_hash);
66 unique_tag[16..16 + tag_len].copy_from_slice(&tag_bytes[..tag_len]);
67 if !self.insert_discovery_pr_tag(unique_tag) {
68 return None;
69 }
70
71 Some(PathRequestCtx {
72 tag: &tag_bytes[..tag_len],
73 interface_id,
74 now,
75 destination_hash,
76 })
77 }
78
79 fn handle_known_path_request(&mut self, ctx: &PathRequestCtx<'_>) -> bool {
80 let Some(path) = self
81 .path_table
82 .get(&ctx.destination_hash)
83 .and_then(|ps| ps.primary())
84 .cloned()
85 else {
86 return false;
87 };
88
89 if let Some(recv_info) = self.interfaces.get(&ctx.interface_id) {
90 if recv_info.mode == constants::MODE_ROAMING
91 && path.receiving_interface == ctx.interface_id
92 {
93 return true;
94 }
95 }
96
97 let Some(raw) = path.announce_raw.as_ref() else {
98 return false;
99 };
100 if let Some(existing) = self.announce_table.remove(&ctx.destination_hash) {
101 self.insert_held_announce(ctx.destination_hash, existing, ctx.now);
102 }
103 let retransmit_timeout = if let Some(iface_info) = self.interfaces.get(&ctx.interface_id) {
104 let base = ctx.now + constants::PATH_REQUEST_GRACE;
105 if iface_info.mode == constants::MODE_ROAMING {
106 base + constants::PATH_REQUEST_RG
107 } else {
108 base
109 }
110 } else {
111 ctx.now + constants::PATH_REQUEST_GRACE
112 };
113
114 let Ok(parsed) = RawPacket::unpack(raw) else {
115 return false;
116 };
117
118 let entry = AnnounceEntry {
119 timestamp: ctx.now,
120 retransmit_timeout,
121 retries: constants::PATHFINDER_R,
122 received_from: path.next_hop,
123 hops: path.hops,
124 packet_raw: raw.clone(),
125 packet_data: parsed.data,
126 destination_hash: ctx.destination_hash,
127 context_flag: parsed.flags.context_flag,
128 local_rebroadcasts: 0,
129 block_rebroadcasts: true,
130 attached_interface: Some(ctx.interface_id),
131 };
132
133 self.insert_announce_entry(ctx.destination_hash, entry, ctx.now);
134 true
135 }
136
137 fn handle_discovery_path_request(&mut self, ctx: &PathRequestCtx<'_>) -> Vec<TransportAction> {
138 let Some((mode, recursive_prs, ingress_control, ip_freq, started)) =
139 self.interfaces.get(&ctx.interface_id).map(|info| {
140 (
141 info.mode,
142 info.recursive_prs,
143 info.ingress_control,
144 info.ip_freq,
145 info.started,
146 )
147 })
148 else {
149 return Vec::new();
150 };
151
152 let search_mode_filter: Option<&[u8]> = if recursive_prs
153 || constants::DISCOVER_PATHS_FOR.contains(&mode)
154 {
155 None
156 } else if mode == constants::MODE_BOUNDARY {
157 Some(&constants::BOUNDARY_SEARCH_MODES)
158 } else {
159 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
160 "Not discovering path to {:02x?}: recursive path discovery is disabled on interface {}",
161 &ctx.destination_hash[..4],
162 ctx.interface_id.0,
163 );
164 return Vec::new();
165 };
166
167 if self.ingress_control.should_ingress_limit_pr(
168 ctx.interface_id,
169 &ingress_control,
170 ip_freq,
171 started,
172 ctx.now,
173 ) {
174 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
175 "Not discovering path to {:02x?}: ingress path-request limiting is active on interface {}",
176 &ctx.destination_hash[..4],
177 ctx.interface_id.0,
178 );
179 return Vec::new();
180 }
181
182 let egress_candidates: Vec<_> = self
183 .interfaces
184 .values()
185 .filter(|info| info.id != ctx.interface_id && info.out_capable)
186 .filter(|info| search_mode_filter.map_or(true, |modes| modes.contains(&info.mode)))
187 .map(|info| {
188 (
189 info.id,
190 info.ingress_control,
191 info.op_freq,
192 info.op_samples,
193 info.bitrate,
194 info.airtime_profile,
195 info.announce_cap,
196 )
197 })
198 .collect();
199
200 let Some((path_request_raw, path_request_len)) = build_path_request_packet(
201 &ctx.destination_hash,
202 self.config.identity_hash.as_ref(),
203 ctx.tag,
204 ) else {
205 return Vec::new();
206 };
207
208 let mut actions = Vec::new();
209 for (id, ingress_control, op_freq, op_samples, bitrate, airtime_profile, announce_cap) in
210 egress_candidates
211 {
212 if self.ingress_control.should_egress_limit_pr(
213 id,
214 &ingress_control,
215 op_freq,
216 op_samples,
217 ) || self
218 .announce_queues
219 .blocks_recursive_path_request(id, ctx.now)
220 {
221 continue;
222 }
223
224 self.announce_queues.reserve_recursive_path_request(
225 id,
226 path_request_len + constants::HEADER_MINSIZE,
227 ctx.now,
228 bitrate,
229 airtime_profile,
230 announce_cap,
231 );
232 actions.push(TransportAction::SendOnInterface {
233 interface: id,
234 raw: path_request_raw.clone().into(),
235 });
236 }
237
238 if !actions.is_empty() {
239 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
240 "Discovering unknown path to {:02x?} on behalf of interface {} via {} interfaces",
241 &ctx.destination_hash[..4],
242 ctx.interface_id.0,
243 actions.len(),
244 );
245 self.discovery_path_requests.insert(
246 ctx.destination_hash,
247 DiscoveryPathRequest {
248 timestamp: ctx.now,
249 requesting_interface: ctx.interface_id,
250 },
251 );
252 } else {
253 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
254 "Not discovering path to {:02x?}: no eligible egress interface",
255 &ctx.destination_hash[..4],
256 );
257 }
258
259 actions
260 }
261}
262
263fn build_path_request_packet(
264 destination_hash: &[u8; 16],
265 transport_identity_hash: Option<&[u8; 16]>,
266 tag: &[u8],
267) -> Option<(Vec<u8>, usize)> {
268 let mut data = Vec::with_capacity(16 + transport_identity_hash.map_or(0, |_| 16) + tag.len());
269 data.extend_from_slice(destination_hash);
270 if let Some(identity_hash) = transport_identity_hash {
271 data.extend_from_slice(identity_hash);
272 }
273 data.extend_from_slice(tag);
274
275 let flags = crate::packet::PacketFlags {
276 header_type: constants::HEADER_1,
277 context_flag: constants::FLAG_UNSET,
278 transport_type: constants::TRANSPORT_BROADCAST,
279 destination_type: constants::DESTINATION_PLAIN,
280 packet_type: constants::PACKET_TYPE_DATA,
281 };
282 let path_request_dest =
283 crate::destination::destination_hash("rnstransport", &["path", "request"], None);
284
285 let data_len = data.len();
286 RawPacket::pack(
287 flags,
288 0,
289 &path_request_dest,
290 None,
291 constants::CONTEXT_NONE,
292 &data,
293 )
294 .ok()
295 .map(|packet| (packet.raw, data_len))
296}