Skip to main content

rns_net/driver/
queries.rs

1use super::*;
2
3fn traffic_detail(stats: &crate::interface::InterfaceStats) -> TrafficDetail {
4    TrafficDetail {
5        rxs: stats.traffic_rates.rxs,
6        txs: stats.traffic_rates.txs,
7        arxb: stats.arxb,
8        atxb: stats.atxb,
9        arxc: stats.arxc,
10        atxc: stats.atxc,
11        arxs: stats.traffic_rates.arxs,
12        atxs: stats.traffic_rates.atxs,
13        prxb: stats.prxb,
14        ptxb: stats.ptxb,
15        prxc: stats.prxc,
16        ptxc: stats.ptxc,
17        prxs: stats.traffic_rates.prxs,
18        ptxs: stats.traffic_rates.ptxs,
19        arxf: stats.incoming_announce_freq(),
20        atxf: stats.outgoing_announce_freq(),
21        prxf: stats.incoming_path_request_freq(),
22        ptxf: stats.outgoing_path_request_freq(),
23    }
24}
25
26fn add_traffic(total: &mut TrafficDetail, detail: TrafficDetail) {
27    total.rxs += detail.rxs;
28    total.txs += detail.txs;
29    total.arxb += detail.arxb;
30    total.atxb += detail.atxb;
31    total.arxc += detail.arxc;
32    total.atxc += detail.atxc;
33    total.arxs += detail.arxs;
34    total.atxs += detail.atxs;
35    total.prxb += detail.prxb;
36    total.ptxb += detail.ptxb;
37    total.prxc += detail.prxc;
38    total.ptxc += detail.ptxc;
39    total.prxs += detail.prxs;
40    total.ptxs += detail.ptxs;
41    total.arxf += detail.arxf;
42    total.atxf += detail.atxf;
43    total.prxf += detail.prxf;
44    total.ptxf += detail.ptxf;
45}
46
47fn hex_bytes(bytes: &[u8]) -> String {
48    bytes.iter().map(|byte| format!("{byte:02x}")).collect()
49}
50
51impl Driver {
52    /// `Transport._wants_destination_type`: returns `false` for a blocked
53    /// type.
54    pub(crate) fn wants_destination_type(&mut self, full_name: &str, requester: &[u8]) -> bool {
55        use rns_core::transport::destination_filter::destination_type;
56        let registered = self
57            .engine
58            .destination_filter_mut()
59            .want(destination_type(full_name), requester);
60        if registered {
61            log::debug!(
62                "Registered <{full_name}> destinations requirement for <{}>",
63                hex_bytes(requester)
64            );
65        }
66        registered
67    }
68
69    /// `Transport._unwants_destination_type`: returns whether the requester
70    /// had registered the type.
71    pub(crate) fn unwants_destination_type(&mut self, full_name: &str, requester: &[u8]) -> bool {
72        use rns_core::transport::destination_filter::destination_type;
73        let destination_type = destination_type(full_name);
74        let filter = self.engine.destination_filter_mut();
75        let was_registered = filter.unwant(destination_type, requester);
76        if was_registered {
77            log::debug!(
78                "Deregistered <{full_name}> destinations requirement for <{}>",
79                hex_bytes(requester)
80            );
81            if !filter.is_wanted(&destination_type) {
82                log::debug!(
83                    "No remaining requesters for <{full_name}> destinations, type deregistered"
84                );
85            }
86        }
87        was_registered
88    }
89
90    pub(super) fn dynamic_burst_counts(&self, parent_id: InterfaceId) -> (usize, usize) {
91        self.dynamic_interface_parents
92            .iter()
93            .filter(|(_, candidate_parent)| **candidate_parent == parent_id)
94            .fold((0, 0), |(announce_count, path_request_count), (id, _)| {
95                (
96                    announce_count + usize::from(self.engine.burst_active(id)),
97                    path_request_count + usize::from(self.engine.pr_burst_active(id)),
98                )
99            })
100    }
101
102    /// Return active announce and path-request burst counts for a Backbone listener.
103    #[cfg(feature = "iface-backbone")]
104    pub fn backbone_burst_counts(&self, parent_id: InterfaceId) -> Option<(usize, usize)> {
105        self.backbone_peer_state
106            .values()
107            .any(|handle| handle.interface_id == parent_id)
108            .then(|| self.dynamic_burst_counts(parent_id))
109    }
110
111    pub(crate) fn handle_interface_stats_query(&self) -> QueryResponse {
112        QueryResponse::InterfaceStats(self.interface_stats())
113    }
114
115    pub(crate) fn interface_stats(&self) -> InterfaceStatsResponse {
116        let now = time::now();
117        let mut interfaces = Vec::new();
118        let mut total_rxb: u64 = 0;
119        let mut total_txb: u64 = 0;
120        let mut total_rx_packets: u64 = 0;
121        let mut total_tx_packets: u64 = 0;
122        let mut total_announce_rx: u64 = 0;
123        let mut total_announce_tx: u64 = 0;
124        let mut total_path_request_rx: u64 = 0;
125        let mut total_path_request_tx: u64 = 0;
126        let mut rx_pps = 0.0;
127        let mut tx_pps = 0.0;
128        let mut total_traffic = TrafficDetail::default();
129        for entry in self.interfaces.values() {
130            let traffic = traffic_detail(&entry.stats);
131            if !entry.info.is_local_client {
132                total_rxb += entry.stats.rxb;
133                total_txb += entry.stats.txb;
134                total_rx_packets += entry.stats.rx_packets;
135                total_tx_packets += entry.stats.tx_packets;
136                total_announce_rx += entry.stats.arxc;
137                total_announce_tx += entry.stats.atxc;
138                total_path_request_rx += entry.stats.prxc;
139                total_path_request_tx += entry.stats.ptxc;
140                rx_pps += entry.stats.traffic_rates.rxpps;
141                tx_pps += entry.stats.traffic_rates.txpps;
142                add_traffic(&mut total_traffic, traffic);
143            }
144            let interface_id = entry.info.id;
145            interfaces.push(SingleInterfaceStat {
146                id: interface_id.0,
147                name: entry.info.name.clone(),
148                status: entry.online && entry.enabled,
149                mode: entry.info.mode,
150                gravity: entry.info.gravity,
151                announces_to_internal: entry.info.announces_to_internal,
152                rxb: entry.stats.rxb,
153                txb: entry.stats.txb,
154                tx_drops: entry
155                    .async_writer_metrics
156                    .as_ref()
157                    .map(crate::interface::AsyncWriterMetrics::tx_drops)
158                    .unwrap_or(0),
159                tx_dropped_bytes: entry
160                    .async_writer_metrics
161                    .as_ref()
162                    .map(crate::interface::AsyncWriterMetrics::tx_dropped_bytes)
163                    .unwrap_or(0),
164                tx_stalled: entry
165                    .async_writer_metrics
166                    .as_ref()
167                    .is_some_and(crate::interface::AsyncWriterMetrics::tx_stalled),
168                tx_buffered: entry
169                    .async_writer_metrics
170                    .as_ref()
171                    .map(crate::interface::AsyncWriterMetrics::tx_buffered)
172                    .unwrap_or(0),
173                traffic,
174                protocol_violations: entry.stats.protocol_violations,
175                ifac_violations: entry.stats.ifac_violations,
176                packet_filter_hits: entry.stats.packet_filter_hits,
177                hop_limiter_hits: entry.stats.hop_limiter_hits,
178                stamped_path_requests: entry.stats.stamped_path_requests,
179                rx_packets: entry.stats.rx_packets,
180                tx_packets: entry.stats.tx_packets,
181                cpu_load: entry.stats.cpu_load,
182                mem_load: entry.stats.mem_load,
183                switch_id: entry.stats.switch_id,
184                endpoint_id: entry.stats.endpoint_id,
185                via_switch_id: entry.stats.via_switch_id,
186                peers: entry.stats.peers,
187                rssi: entry.stats.rssi,
188                snr: entry.stats.snr,
189                signal_quality: entry.stats.signal_quality,
190                frequency: entry.stats.frequency,
191                bandwidth: entry.stats.bandwidth,
192                txpower: entry.stats.txpower,
193                spreading_factor: entry.stats.spreading_factor,
194                coding_rate: entry.stats.coding_rate,
195                channel: entry.stats.channel,
196                modulation: entry.stats.modulation.clone(),
197                bitrate: entry.info.bitrate,
198                mtu: Some(entry.info.mtu),
199                ifac_size: entry.ifac.as_ref().map(|s| s.size),
200                started: entry.stats.started,
201                ia_freq: entry.stats.incoming_announce_freq(),
202                oa_freq: entry.stats.outgoing_announce_freq(),
203                ip_freq: entry.stats.incoming_path_request_freq(),
204                op_freq: entry.stats.outgoing_path_request_freq(),
205                op_samples: entry.stats.outgoing_path_request_samples(),
206                burst_active: self.engine.burst_active(&interface_id),
207                burst_activated: self.engine.burst_activated(&interface_id),
208                pr_burst_active: self.engine.pr_burst_active(&interface_id),
209                pr_burst_activated: self.engine.pr_burst_activated(&interface_id),
210                clients: None,
211                blocked_ips: None,
212                blocked_ip_list: None,
213                announce_rate_target: entry.info.announce_rate_target,
214                announce_rate_grace: entry.info.announce_rate_grace,
215                announce_rate_penalty: entry.info.announce_rate_penalty,
216                interface_type: entry.interface_type.clone(),
217            });
218        }
219        #[cfg(feature = "iface-backbone")]
220        for handle in self.backbone_peer_state.values() {
221            let clients = recover_mutex_guard(&handle.peer_state, "backbone peer state")
222                .list(&handle.interface_name)
223                .into_iter()
224                .map(|entry| entry.connected_count as u64)
225                .sum();
226            let blocked_ip_list =
227                recover_mutex_guard(&handle.fast_flap_state, "backbone fast-flap state")
228                    .blocked_ip_list(&handle.fast_flap, std::time::Instant::now())
229                    .into_iter()
230                    .map(|peer_ip| peer_ip.to_string())
231                    .collect::<Vec<_>>();
232            let blocked_ips = blocked_ip_list.len() as u64;
233            let mut traffic = TrafficDetail::default();
234            let mut protocol_violations = 0;
235            let mut ifac_violations = 0;
236            let mut packet_filter_hits = 0;
237            let mut hop_limiter_hits = 0;
238            let mut stamped_path_requests = 0;
239            let mut tx_drops = 0;
240            let mut tx_dropped_bytes = 0;
241            let mut tx_stalled = false;
242            let mut tx_buffered = 0;
243            for (child_id, parent_id) in &self.dynamic_interface_parents {
244                if *parent_id == handle.interface_id {
245                    if let Some(child) = self.interfaces.get(child_id) {
246                        add_traffic(&mut traffic, traffic_detail(&child.stats));
247                        protocol_violations += child.stats.protocol_violations;
248                        ifac_violations += child.stats.ifac_violations;
249                        packet_filter_hits += child.stats.packet_filter_hits;
250                        hop_limiter_hits += child.stats.hop_limiter_hits;
251                        stamped_path_requests += child.stats.stamped_path_requests;
252                        if let Some(metrics) = &child.async_writer_metrics {
253                            tx_drops += metrics.tx_drops();
254                            tx_dropped_bytes += metrics.tx_dropped_bytes();
255                            tx_stalled |= metrics.tx_stalled();
256                            tx_buffered += metrics.tx_buffered();
257                        }
258                    }
259                }
260            }
261            interfaces.push(SingleInterfaceStat {
262                id: handle.interface_id.0,
263                name: handle.interface_name.clone(),
264                status: true,
265                mode: handle.mode,
266                gravity: handle.gravity,
267                announces_to_internal: handle.announces_to_internal,
268                rxb: 0,
269                txb: 0,
270                tx_drops,
271                tx_dropped_bytes,
272                tx_stalled,
273                tx_buffered,
274                traffic,
275                protocol_violations,
276                ifac_violations,
277                packet_filter_hits,
278                hop_limiter_hits,
279                stamped_path_requests,
280                rx_packets: 0,
281                tx_packets: 0,
282                cpu_load: None,
283                mem_load: None,
284                switch_id: None,
285                endpoint_id: None,
286                via_switch_id: None,
287                peers: None,
288                rssi: None,
289                snr: None,
290                signal_quality: None,
291                frequency: None,
292                bandwidth: None,
293                txpower: None,
294                spreading_factor: None,
295                coding_rate: None,
296                channel: None,
297                modulation: None,
298                bitrate: Some(crate::interface::backbone::SERVER_BITRATE_GUESS),
299                mtu: Some(crate::interface::backbone::HW_MTU as u32),
300                ifac_size: None,
301                started: self.started,
302                ia_freq: 0.0,
303                oa_freq: 0.0,
304                ip_freq: 0.0,
305                op_freq: 0.0,
306                op_samples: 0,
307                burst_active: false,
308                burst_activated: 0.0,
309                pr_burst_active: false,
310                pr_burst_activated: 0.0,
311                clients: Some(clients),
312                blocked_ips: Some(blocked_ips),
313                blocked_ip_list: Some(blocked_ip_list),
314                announce_rate_target: None,
315                announce_rate_grace: 0,
316                announce_rate_penalty: 0.0,
317                interface_type: "BackboneInterface".into(),
318            });
319        }
320        interfaces.sort_by(|a, b| a.name.cmp(&b.name));
321        let (path_pool_total, path_pool_unused) = self.engine.path_pool_counts();
322        let total_links = self.engine.link_table_count() + self.link_manager.link_count();
323        let active_links = self.engine.active_link_count() + self.link_manager.active_link_count();
324        InterfaceStatsResponse {
325            interfaces,
326            transport_id: self.engine.identity_hash().copied(),
327            transport_enabled: self.engine.transport_enabled(),
328            transport_uptime: now - self.started,
329            total_rxb,
330            total_txb,
331            total_rx_packets,
332            total_tx_packets,
333            total_announce_rx,
334            total_announce_tx,
335            total_path_request_rx,
336            total_path_request_tx,
337            total_links,
338            active_links,
339            pending_links: total_links.saturating_sub(active_links),
340            path_pool_total,
341            path_pool_unused,
342            path_pool_active: path_pool_total.saturating_sub(path_pool_unused),
343            rx_pps,
344            tx_pps,
345            traffic: total_traffic,
346            probe_responder: self.probe_responder_hash,
347            #[cfg(feature = "iface-backbone")]
348            backbone_peer_pool: self.backbone_peer_pool_status(),
349            #[cfg(not(feature = "iface-backbone"))]
350            backbone_peer_pool: None,
351        }
352    }
353
354    /// Managed links with their local send backlog.
355    pub(crate) fn link_info_entries(&self) -> Vec<crate::event::LinkInfoEntry> {
356        let counts = self.event_tx.link_send_pool().snapshot();
357        let mut links = self.link_manager.link_entries();
358        for link in &mut links {
359            if let Some(counts) = counts.get(&link.link_id) {
360                link.pending_send_packets = counts.pending;
361                link.waiting_send_packets = counts.waiting;
362            }
363        }
364        links
365    }
366
367    /// Replace the shared link and interface statistics snapshot that
368    /// `RnsNode::state_snapshot` reads without a driver round trip.
369    pub(crate) fn publish_state_snapshot(&mut self) {
370        let links = self.link_info_entries();
371        let interfaces = self.interface_stats();
372        let sequence = self.state_snapshot_schedule.published(links.len());
373        self.event_tx
374            .state_snapshot()
375            .store(crate::state_snapshot::StateSnapshot {
376                sequence,
377                published_at: time::now(),
378                links,
379                interfaces,
380            });
381    }
382
383    pub(crate) fn handle_path_table_query(&self, max_hops: Option<u8>) -> QueryResponse {
384        let entries: Vec<PathTableEntry> = self
385            .engine
386            .path_table_entries()
387            .filter(|(_, entry)| max_hops.is_none_or(|max| entry.hops <= max))
388            .map(|(hash, entry)| {
389                let iface_name = self
390                    .interfaces
391                    .get(&entry.receiving_interface)
392                    .map(|e| e.info.name.clone())
393                    .or_else(|| {
394                        self.engine
395                            .interface_info(&entry.receiving_interface)
396                            .map(|i| i.name.clone())
397                    })
398                    .unwrap_or_default();
399                let known = self.known_destinations.get(hash);
400                PathTableEntry {
401                    hash: *hash,
402                    timestamp: entry.timestamp,
403                    via: entry.next_hop,
404                    hops: entry.hops,
405                    expires: entry.expires,
406                    used: entry.used,
407                    interface: entry.receiving_interface,
408                    interface_name: iface_name,
409                    stamp_value: known.map(|known| known.stamp_value),
410                    lr_cost: known.and_then(|known| known.lr_cost),
411                }
412            })
413            .collect();
414        QueryResponse::PathTable(entries)
415    }
416
417    pub(crate) fn handle_runtime_config_query(
418        &self,
419        request: QueryRequest,
420    ) -> Option<QueryResponse> {
421        match request {
422            QueryRequest::ListRuntimeConfig => {
423                Some(QueryResponse::RuntimeConfigList(self.list_runtime_config()))
424            }
425            QueryRequest::GetRuntimeConfig { key } => Some(QueryResponse::RuntimeConfigEntry(
426                self.runtime_config_entry(&key),
427            )),
428            QueryRequest::BackbonePeerState { interface_name } => {
429                Some(QueryResponse::BackbonePeerState(
430                    self.list_backbone_peer_state(interface_name.as_deref()),
431                ))
432            }
433            QueryRequest::SetRuntimeConfig { .. } => {
434                Some(QueryResponse::RuntimeConfigSet(Err(RuntimeConfigError {
435                    code: RuntimeConfigErrorCode::Unsupported,
436                    message: "mutating runtime config is handled separately".to_string(),
437                })))
438            }
439            QueryRequest::ResetRuntimeConfig { .. } => {
440                Some(QueryResponse::RuntimeConfigReset(Err(RuntimeConfigError {
441                    code: RuntimeConfigErrorCode::Unsupported,
442                    message: "mutating runtime config is handled separately".to_string(),
443                })))
444            }
445            QueryRequest::ClearBackbonePeerState { .. } => {
446                Some(QueryResponse::ClearBackbonePeerState(false))
447            }
448            QueryRequest::BlacklistBackbonePeer { .. } => {
449                Some(QueryResponse::BlacklistBackbonePeer(false))
450            }
451            _ => None,
452        }
453    }
454
455    pub(crate) fn handle_mutation_query(&mut self, request: QueryRequest) -> Option<QueryResponse> {
456        match request {
457            QueryRequest::BlackholeIdentity {
458                identity_hash,
459                duration_hours,
460                reason,
461            } => {
462                let now = time::now();
463                self.engine
464                    .blackhole_identity(identity_hash, now, duration_hours, reason);
465                Some(QueryResponse::BlackholeResult(true))
466            }
467            QueryRequest::UnblackholeIdentity { identity_hash } => Some(
468                QueryResponse::UnblackholeResult(self.engine.unblackhole_identity(&identity_hash)),
469            ),
470            QueryRequest::DropPath { dest_hash } => {
471                Some(QueryResponse::DropPath(self.engine.drop_path(&dest_hash)))
472            }
473            QueryRequest::DropAllVia { transport_hash } => Some(QueryResponse::DropAllVia(
474                self.engine.drop_all_via(&transport_hash),
475            )),
476            QueryRequest::DropAnnounceQueues => {
477                self.engine.drop_announce_queues();
478                Some(QueryResponse::DropAnnounceQueues)
479            }
480            QueryRequest::ClearBackbonePeerState {
481                interface_name,
482                peer_ip,
483            } => Some(QueryResponse::ClearBackbonePeerState(
484                self.clear_backbone_peer_state(&interface_name, peer_ip),
485            )),
486            QueryRequest::BlacklistBackbonePeer {
487                interface_name,
488                peer_ip,
489                duration,
490                reason,
491                penalty_level,
492            } => Some(QueryResponse::BlacklistBackbonePeer(
493                self.blacklist_backbone_peer(
494                    &interface_name,
495                    peer_ip,
496                    duration,
497                    reason,
498                    penalty_level,
499                ),
500            )),
501            QueryRequest::InjectPath {
502                dest_hash,
503                next_hop,
504                hops,
505                expires,
506                interface_name,
507                packet_hash,
508            } => {
509                let iface_id = self
510                    .interfaces
511                    .iter()
512                    .find(|(_, entry)| entry.info.name == interface_name)
513                    .map(|(id, _)| *id);
514                Some(match iface_id {
515                    Some(id) => {
516                        let entry = PathEntry {
517                            timestamp: time::now(),
518                            next_hop,
519                            hops,
520                            expires,
521                            random_blobs: Vec::new(),
522                            receiving_interface: id,
523                            packet_hash,
524                            used: 0.0,
525                            announce_raw: None,
526                        };
527                        self.engine.inject_path(dest_hash, entry);
528                        QueryResponse::InjectPath(true)
529                    }
530                    None => QueryResponse::InjectPath(false),
531                })
532            }
533            QueryRequest::InjectIdentity {
534                dest_hash,
535                identity_hash,
536                public_key,
537                app_data,
538                hops,
539                received_at,
540            } => {
541                self.upsert_known_destination(
542                    dest_hash,
543                    crate::destination::AnnouncedIdentity {
544                        dest_hash: rns_core::types::DestHash(dest_hash),
545                        identity_hash: rns_core::types::IdentityHash(identity_hash),
546                        public_key,
547                        app_data,
548                        hops,
549                        received_at,
550                        receiving_interface: rns_core::transport::types::InterfaceId(0),
551                        rssi: None,
552                        snr: None,
553                    },
554                );
555                Some(QueryResponse::InjectIdentity(true))
556            }
557            QueryRequest::RestoreKnownDestination(entry) => {
558                self.known_destinations.insert(
559                    entry.dest_hash,
560                    KnownDestinationState {
561                        announced: crate::destination::AnnouncedIdentity {
562                            dest_hash: rns_core::types::DestHash(entry.dest_hash),
563                            identity_hash: rns_core::types::IdentityHash(entry.identity_hash),
564                            public_key: entry.public_key,
565                            app_data: entry.app_data,
566                            hops: entry.hops,
567                            received_at: entry.received_at,
568                            receiving_interface: entry.receiving_interface,
569                            rssi: None,
570                            snr: None,
571                        },
572                        was_used: entry.was_used,
573                        last_used_at: entry.last_used_at,
574                        retained: entry.retained,
575                        stamp: entry.stamp,
576                        stamp_value: entry.stamp_value,
577                        lr_cost: entry.lr_cost,
578                    },
579                );
580                Some(QueryResponse::RestoreKnownDestination(true))
581            }
582            QueryRequest::RetainKnownDestination { dest_hash } => Some(
583                QueryResponse::RetainKnownDestination(self.retain_known_destination(&dest_hash)),
584            ),
585            QueryRequest::RetainIdentity { identity_hash } => Some(QueryResponse::RetainIdentity(
586                self.retain_identity(&identity_hash),
587            )),
588            QueryRequest::UnretainKnownDestination { dest_hash } => {
589                Some(QueryResponse::UnretainKnownDestination(
590                    self.unretain_known_destination(&dest_hash),
591                ))
592            }
593            QueryRequest::MarkKnownDestinationUsed { dest_hash } => {
594                Some(QueryResponse::MarkKnownDestinationUsed(
595                    self.mark_known_destination_used(&dest_hash),
596                ))
597            }
598            _ => None,
599        }
600    }
601
602    pub(crate) fn runtime_config_query_fallback(request: &QueryRequest) -> QueryResponse {
603        match request {
604            QueryRequest::ListRuntimeConfig => QueryResponse::RuntimeConfigList(Vec::new()),
605            QueryRequest::GetRuntimeConfig { .. } => QueryResponse::RuntimeConfigEntry(None),
606            QueryRequest::BackbonePeerState { .. } => QueryResponse::BackbonePeerState(Vec::new()),
607            QueryRequest::SetRuntimeConfig { key, .. } => {
608                QueryResponse::RuntimeConfigSet(Err(RuntimeConfigError {
609                    code: RuntimeConfigErrorCode::ApplyFailed,
610                    message: format!(
611                        "internal error: no response generated for runtime-config set '{}'",
612                        key
613                    ),
614                }))
615            }
616            QueryRequest::ResetRuntimeConfig { key } => {
617                QueryResponse::RuntimeConfigReset(Err(RuntimeConfigError {
618                    code: RuntimeConfigErrorCode::ApplyFailed,
619                    message: format!(
620                        "internal error: no response generated for runtime-config reset '{}'",
621                        key
622                    ),
623                }))
624            }
625            QueryRequest::ClearBackbonePeerState { .. } => {
626                QueryResponse::ClearBackbonePeerState(false)
627            }
628            QueryRequest::BlacklistBackbonePeer { .. } => {
629                QueryResponse::BlacklistBackbonePeer(false)
630            }
631            _ => QueryResponse::RuntimeConfigEntry(None),
632        }
633    }
634
635    pub(crate) fn mutation_query_fallback(request: &QueryRequest) -> QueryResponse {
636        match request {
637            QueryRequest::BlackholeIdentity { .. } => QueryResponse::BlackholeResult(false),
638            QueryRequest::UnblackholeIdentity { .. } => QueryResponse::UnblackholeResult(false),
639            QueryRequest::DropPath { .. } => QueryResponse::DropPath(false),
640            QueryRequest::DropAllVia { .. } => QueryResponse::DropAllVia(0),
641            QueryRequest::DropAnnounceQueues => QueryResponse::DropAnnounceQueues,
642            QueryRequest::ClearBackbonePeerState { .. } => {
643                QueryResponse::ClearBackbonePeerState(false)
644            }
645            QueryRequest::BlacklistBackbonePeer { .. } => {
646                QueryResponse::BlacklistBackbonePeer(false)
647            }
648            QueryRequest::InjectPath { .. } => QueryResponse::InjectPath(false),
649            QueryRequest::InjectIdentity { .. } => QueryResponse::InjectIdentity(false),
650            QueryRequest::RestoreKnownDestination(..) => {
651                QueryResponse::RestoreKnownDestination(false)
652            }
653            QueryRequest::RetainKnownDestination { .. } => {
654                QueryResponse::RetainKnownDestination(false)
655            }
656            QueryRequest::RetainIdentity { .. } => QueryResponse::RetainIdentity(false),
657            QueryRequest::UnretainKnownDestination { .. } => {
658                QueryResponse::UnretainKnownDestination(false)
659            }
660            QueryRequest::MarkKnownDestinationUsed { .. } => {
661                QueryResponse::MarkKnownDestinationUsed(false)
662            }
663            _ => QueryResponse::InjectIdentity(false),
664        }
665    }
666
667    /// Handle a query request and produce a response.
668    pub(crate) fn handle_query(&self, request: QueryRequest) -> QueryResponse {
669        match request {
670            QueryRequest::ManageInterface { .. } => QueryResponse::InterfaceManagement(Some(false)),
671            QueryRequest::InterfaceStats => self.handle_interface_stats_query(),
672            QueryRequest::BackboneInterfaces => {
673                QueryResponse::BackboneInterfaces(self.list_backbone_interfaces())
674            }
675            QueryRequest::ProviderBridgeStats => {
676                #[cfg(feature = "hooks")]
677                {
678                    QueryResponse::ProviderBridgeStats(
679                        self.provider_bridge.as_ref().map(|bridge| bridge.stats()),
680                    )
681                }
682                #[cfg(not(feature = "hooks"))]
683                {
684                    QueryResponse::ProviderBridgeStats(None::<crate::event::ProviderBridgeStats>)
685                }
686            }
687            QueryRequest::DrainStatus => QueryResponse::DrainStatus(self.drain_status()),
688            QueryRequest::PathTable { max_hops } => self.handle_path_table_query(max_hops),
689            QueryRequest::RateTable => {
690                let entries: Vec<RateTableEntry> = self
691                    .engine
692                    .rate_limiter()
693                    .entries()
694                    .map(|(hash, entry)| RateTableEntry {
695                        hash: *hash,
696                        last: entry.last,
697                        rate_violations: entry.rate_violations,
698                        blocked_until: entry.blocked_until,
699                        timestamps: entry.timestamps.clone(),
700                    })
701                    .collect();
702                QueryResponse::RateTable(entries)
703            }
704            QueryRequest::LowestInterfaceBitrate => {
705                QueryResponse::LowestInterfaceBitrate(self.engine.lowest_interface_bitrate())
706            }
707            QueryRequest::MediumPathTimeout => {
708                QueryResponse::MediumPathTimeout(self.engine.medium_path_timeout())
709            }
710            QueryRequest::FirstHopTimeout { dest_hash } => {
711                let bitrate = self
712                    .engine
713                    .next_hop_interface(&dest_hash)
714                    .and_then(|id| self.interfaces.get(&id))
715                    .and_then(|entry| entry.info.bitrate)
716                    .filter(|bitrate| *bitrate > 0);
717                let timeout = rns_core::constants::LINK_ESTABLISHMENT_TIMEOUT_PER_HOP
718                    + bitrate
719                        .map(|bitrate| rns_core::constants::MTU as f64 * 8.0 / bitrate as f64)
720                        .unwrap_or(0.0);
721                QueryResponse::FirstHopTimeout(timeout)
722            }
723            QueryRequest::NextHop { dest_hash } => {
724                let resp = self
725                    .engine
726                    .next_hop(&dest_hash)
727                    .map(|next_hop| NextHopResponse {
728                        next_hop,
729                        hops: self.engine.hops_to(&dest_hash).unwrap_or(0),
730                        interface: self
731                            .engine
732                            .next_hop_interface(&dest_hash)
733                            .unwrap_or(InterfaceId(0)),
734                    });
735                QueryResponse::NextHop(resp)
736            }
737            QueryRequest::NextHopIfName { dest_hash } => {
738                let name = self
739                    .engine
740                    .next_hop_interface(&dest_hash)
741                    .and_then(|id| self.interfaces.get(&id))
742                    .map(|entry| entry.info.name.clone());
743                QueryResponse::NextHopIfName(name)
744            }
745            QueryRequest::LinkCount => QueryResponse::LinkCount(
746                self.engine.link_table_count() + self.link_manager.link_count(),
747            ),
748            QueryRequest::ActiveLinkCount => QueryResponse::ActiveLinkCount(
749                self.engine.active_link_count() + self.link_manager.active_link_count(),
750            ),
751            QueryRequest::LinkDistribution => QueryResponse::LinkDistribution(
752                self.engine
753                    .link_distribution()
754                    .into_iter()
755                    .map(
756                        |(destination_hash, total, active)| crate::event::LinkDistributionEntry {
757                            destination_hash,
758                            total,
759                            active,
760                        },
761                    )
762                    .collect(),
763            ),
764            QueryRequest::DropPath { .. } => {
765                // Mutating queries are handled by handle_query_mut
766                QueryResponse::DropPath(false)
767            }
768            QueryRequest::DropAllVia { .. } => QueryResponse::DropAllVia(0),
769            QueryRequest::DropAnnounceQueues => QueryResponse::DropAnnounceQueues,
770            QueryRequest::WantsDestinationType { .. }
771            | QueryRequest::UnwantsDestinationType { .. }
772            | QueryRequest::UnwantsAllDestinationTypes { .. }
773            | QueryRequest::SetRequiredDestinationsOnly { .. } => {
774                // Mutating queries are handled by handle_query_mut
775                QueryResponse::DestinationTypeRegistration(false)
776            }
777            QueryRequest::TransportIdentity => {
778                QueryResponse::TransportIdentity(self.engine.identity_hash().copied())
779            }
780            QueryRequest::TransportStateSnapshot => {
781                QueryResponse::TransportStateSnapshot(self.engine.persistence_snapshot())
782            }
783            QueryRequest::GetBlackholed => {
784                let now = time::now();
785                let entries: Vec<BlackholeInfo> = self
786                    .engine
787                    .blackholed_entries()
788                    .filter(|(_, e)| e.expires == 0.0 || e.expires > now)
789                    .map(|(hash, entry)| BlackholeInfo {
790                        identity_hash: *hash,
791                        created: entry.created,
792                        expires: entry.expires,
793                        reason: entry.reason.clone(),
794                    })
795                    .collect();
796                QueryResponse::Blackholed(entries)
797            }
798            QueryRequest::BlackholeIdentity { .. } | QueryRequest::UnblackholeIdentity { .. } => {
799                // Mutating queries handled by handle_query_mut
800                QueryResponse::BlackholeResult(false)
801            }
802            QueryRequest::InjectPath { .. } => {
803                // Mutating queries handled by handle_query_mut
804                QueryResponse::InjectPath(false)
805            }
806            QueryRequest::InjectIdentity { .. } => {
807                // Mutating queries handled by handle_query_mut
808                QueryResponse::InjectIdentity(false)
809            }
810            QueryRequest::RestoreKnownDestination(..) => {
811                QueryResponse::RestoreKnownDestination(false)
812            }
813            QueryRequest::RetainKnownDestination { .. } => {
814                QueryResponse::RetainKnownDestination(false)
815            }
816            QueryRequest::RetainIdentity { .. } => QueryResponse::RetainIdentity(false),
817            QueryRequest::UnretainKnownDestination { .. } => {
818                QueryResponse::UnretainKnownDestination(false)
819            }
820            QueryRequest::MarkKnownDestinationUsed { .. } => {
821                QueryResponse::MarkKnownDestinationUsed(false)
822            }
823            QueryRequest::HasPath { dest_hash } => {
824                QueryResponse::HasPath(self.engine.has_path(&dest_hash))
825            }
826            QueryRequest::HopsTo { dest_hash } => {
827                QueryResponse::HopsTo(self.engine.hops_to(&dest_hash))
828            }
829            QueryRequest::RecallIdentity { .. } => QueryResponse::RecallIdentity(None),
830            QueryRequest::RecallDestinationStamp { .. } => {
831                QueryResponse::RecallDestinationStamp(None)
832            }
833            QueryRequest::KnownDestinations => {
834                QueryResponse::KnownDestinations(self.known_destination_entries())
835            }
836            QueryRequest::LocalDestinations => {
837                let entries: Vec<LocalDestinationEntry> = self
838                    .local_destinations
839                    .iter()
840                    .map(|(hash, dest_type)| LocalDestinationEntry {
841                        hash: *hash,
842                        dest_type: *dest_type,
843                    })
844                    .collect();
845                QueryResponse::LocalDestinations(entries)
846            }
847            QueryRequest::Links => QueryResponse::Links(self.link_info_entries()),
848            QueryRequest::Resources => {
849                QueryResponse::Resources(self.link_manager.resource_entries())
850            }
851            QueryRequest::DiscoveredInterfaces {
852                only_available,
853                only_transport,
854            } => {
855                let mut interfaces = self.discovered_interfaces.list().unwrap_or_default();
856                crate::discovery::filter_and_sort_interfaces(
857                    &mut interfaces,
858                    only_available,
859                    only_transport,
860                );
861                QueryResponse::DiscoveredInterfaces(interfaces)
862            }
863            request @ (QueryRequest::ListRuntimeConfig
864            | QueryRequest::GetRuntimeConfig { .. }
865            | QueryRequest::BackbonePeerState { .. }
866            | QueryRequest::SetRuntimeConfig { .. }
867            | QueryRequest::ResetRuntimeConfig { .. }
868            | QueryRequest::ClearBackbonePeerState { .. }
869            | QueryRequest::BlacklistBackbonePeer { .. }) => {
870                let fallback = Self::runtime_config_query_fallback(&request);
871                self.handle_runtime_config_query(request)
872                    .unwrap_or_else(|| {
873                        log::error!(
874                            "runtime-config query branch unexpectedly returned no response"
875                        );
876                        fallback
877                    })
878            }
879            // Mutating queries handled by handle_query_mut
880            QueryRequest::SendProbe { .. } => {
881                QueryResponse::SendProbe(Err(crate::event::SendProbeError::PackFailed))
882            }
883            QueryRequest::CheckProof { .. } => QueryResponse::CheckProof(None),
884        }
885    }
886
887    /// Handle a mutating query request.
888    pub(crate) fn handle_query_mut(&mut self, request: QueryRequest) -> QueryResponse {
889        match request {
890            QueryRequest::WantsDestinationType {
891                full_name,
892                requester,
893            } => QueryResponse::DestinationTypeRegistration(
894                self.wants_destination_type(&full_name, &requester),
895            ),
896            QueryRequest::UnwantsDestinationType {
897                full_name,
898                requester,
899            } => QueryResponse::DestinationTypeRegistration(
900                self.unwants_destination_type(&full_name, &requester),
901            ),
902            QueryRequest::UnwantsAllDestinationTypes { requester } => {
903                self.engine.destination_filter_mut().unwant_all(&requester);
904                log::debug!(
905                    "Deregistered all destination requirements for <{}>",
906                    hex_bytes(&requester)
907                );
908                QueryResponse::DestinationTypeRegistration(true)
909            }
910            QueryRequest::SetRequiredDestinationsOnly { enabled } => {
911                self.engine
912                    .destination_filter_mut()
913                    .set_required_only(enabled);
914                if enabled {
915                    log::debug!("Setting announce filtering to required only");
916                } else {
917                    log::debug!("Setting announce filtering to all");
918                }
919                QueryResponse::DestinationTypeRegistration(true)
920            }
921            QueryRequest::ManageInterface { operation, name } => {
922                QueryResponse::InterfaceManagement(self.manage_interface(operation, &name))
923            }
924            request @ (QueryRequest::BlackholeIdentity { .. }
925            | QueryRequest::UnblackholeIdentity { .. }
926            | QueryRequest::DropPath { .. }
927            | QueryRequest::DropAllVia { .. }
928            | QueryRequest::DropAnnounceQueues
929            | QueryRequest::ClearBackbonePeerState { .. }
930            | QueryRequest::BlacklistBackbonePeer { .. }
931            | QueryRequest::InjectPath { .. }
932            | QueryRequest::InjectIdentity { .. }
933            | QueryRequest::RestoreKnownDestination(..)
934            | QueryRequest::RetainKnownDestination { .. }
935            | QueryRequest::RetainIdentity { .. }
936            | QueryRequest::UnretainKnownDestination { .. }
937            | QueryRequest::MarkKnownDestinationUsed { .. }) => {
938                let fallback = Self::mutation_query_fallback(&request);
939                self.handle_mutation_query(request).unwrap_or_else(|| {
940                    log::error!("mutation query branch unexpectedly returned no response");
941                    fallback
942                })
943            }
944            QueryRequest::RecallDestinationStamp { dest_hash } => {
945                QueryResponse::RecallDestinationStamp(self.known_destinations.get(&dest_hash).map(
946                    |state| crate::event::DestinationStampInfo {
947                        stamp_value: state.stamp_value,
948                        lr_cost: state.lr_cost,
949                    },
950                ))
951            }
952            QueryRequest::RecallIdentity { dest_hash } => {
953                let recalled = self.known_destination_announced(&dest_hash);
954                if recalled.is_some() {
955                    let _ = self.mark_known_destination_used(&dest_hash);
956                }
957                QueryResponse::RecallIdentity(recalled)
958            }
959            QueryRequest::DrainStatus => QueryResponse::DrainStatus(self.drain_status()),
960            QueryRequest::SendProbe {
961                dest_hash,
962                payload_size,
963            } => {
964                use crate::event::{SendProbeError, MAX_PROBE_PAYLOAD_SIZE};
965                if payload_size > MAX_PROBE_PAYLOAD_SIZE {
966                    log::warn!(
967                        "Probe payload of {} bytes exceeds maximum of {} bytes",
968                        payload_size,
969                        MAX_PROBE_PAYLOAD_SIZE,
970                    );
971                    return QueryResponse::SendProbe(Err(SendProbeError::PayloadTooLarge {
972                        max: MAX_PROBE_PAYLOAD_SIZE,
973                    }));
974                }
975                // Look up the identity for this destination hash
976                let announced = self.known_destination_announced(&dest_hash);
977                match announced {
978                    Some(recalled) => {
979                        let _ = self.mark_known_destination_used(&dest_hash);
980                        // Encrypt random payload with remote public key
981                        let remote_id =
982                            rns_crypto::identity::Identity::from_public_key(&recalled.public_key);
983                        let mut payload = vec![0u8; payload_size];
984                        self.rng.fill_bytes(&mut payload);
985                        match remote_id.and_then(|id| id.encrypt(&payload, &mut self.rng)) {
986                            Ok(ciphertext) => {
987                                // Build DATA SINGLE BROADCAST packet to dest_hash
988                                let flags = rns_core::packet::PacketFlags {
989                                    header_type: rns_core::constants::HEADER_1,
990                                    context_flag: rns_core::constants::FLAG_UNSET,
991                                    transport_type: rns_core::constants::TRANSPORT_BROADCAST,
992                                    destination_type: rns_core::constants::DESTINATION_SINGLE,
993                                    packet_type: rns_core::constants::PACKET_TYPE_DATA,
994                                };
995                                match RawPacket::pack(
996                                    flags,
997                                    0,
998                                    &dest_hash,
999                                    None,
1000                                    rns_core::constants::CONTEXT_NONE,
1001                                    &ciphertext,
1002                                ) {
1003                                    Ok(packet) => {
1004                                        let packet_hash = packet.packet_hash;
1005                                        let hops = self.engine.hops_to(&dest_hash).unwrap_or(0);
1006                                        // Track for proof matching, and record
1007                                        // its proof for CheckProof polling.
1008                                        self.sent_packets
1009                                            .insert(packet_hash, (dest_hash, time::now()));
1010                                        self.pending_probes.insert(packet_hash);
1011                                        // Send via engine
1012                                        let actions = self.engine.handle_outbound(
1013                                            &packet,
1014                                            rns_core::constants::DESTINATION_SINGLE,
1015                                            None,
1016                                            time::now(),
1017                                        );
1018                                        self.dispatch_all(actions);
1019                                        log::debug!(
1020                                            "Sent probe ({} bytes) to {:02x?}",
1021                                            payload_size,
1022                                            &dest_hash[..4],
1023                                        );
1024                                        QueryResponse::SendProbe(Ok((packet_hash, hops)))
1025                                    }
1026                                    Err(e) => {
1027                                        log::warn!("Failed to pack probe packet: {:?}", e);
1028                                        QueryResponse::SendProbe(Err(SendProbeError::PackFailed))
1029                                    }
1030                                }
1031                            }
1032                            Err(e) => {
1033                                log::warn!("Failed to encrypt probe payload: {:?}", e);
1034                                QueryResponse::SendProbe(Err(SendProbeError::EncryptFailed))
1035                            }
1036                        }
1037                    }
1038                    None => {
1039                        log::debug!("No known identity for probe dest {:02x?}", &dest_hash[..4]);
1040                        QueryResponse::SendProbe(Err(SendProbeError::UnknownIdentity))
1041                    }
1042                }
1043            }
1044            QueryRequest::CheckProof { packet_hash } => {
1045                match self.completed_proofs.remove(&packet_hash) {
1046                    Some((rtt, _received)) => QueryResponse::CheckProof(Some(rtt)),
1047                    None => QueryResponse::CheckProof(None),
1048                }
1049            }
1050            QueryRequest::SetRuntimeConfig { key, value } => {
1051                let result = match key.as_str() {
1052                    "global.tick_interval_ms" => match Self::expect_u64(value, &key) {
1053                        Ok(value) => {
1054                            let clamped = value.clamp(100, 10_000);
1055                            self.tick_interval_ms.store(clamped, Ordering::Relaxed);
1056                            Ok(())
1057                        }
1058                        Err(err) => Err(err),
1059                    },
1060                    "global.known_destinations_ttl_secs" => match Self::expect_f64(value, &key) {
1061                        Ok(value) => {
1062                            self.known_destinations_ttl = value;
1063                            Ok(())
1064                        }
1065                        Err(err) => Err(err),
1066                    },
1067                    "global.rate_limiter_ttl_secs" => match Self::expect_f64(value, &key) {
1068                        Ok(value) if value >= 0.0 => {
1069                            self.rate_limiter_ttl_secs = value;
1070                            Ok(())
1071                        }
1072                        Ok(_) => Err(RuntimeConfigError {
1073                            code: RuntimeConfigErrorCode::InvalidValue,
1074                            message: format!("{} must be >= 0", key),
1075                        }),
1076                        Err(err) => Err(err),
1077                    },
1078                    "global.known_destinations_cleanup_interval_ticks" => {
1079                        match Self::expect_u64(value, &key) {
1080                            Ok(value) if value > 0 => {
1081                                self.known_destinations_cleanup_interval_ticks = value as u32;
1082                                Ok(())
1083                            }
1084                            Ok(_) => Err(RuntimeConfigError {
1085                                code: RuntimeConfigErrorCode::InvalidValue,
1086                                message: format!("{} must be >= 1", key),
1087                            }),
1088                            Err(err) => Err(err),
1089                        }
1090                    }
1091                    "global.announce_cache_cleanup_interval_ticks" => {
1092                        match Self::expect_u64(value, &key) {
1093                            Ok(value) if value > 0 => {
1094                                self.announce_cache_cleanup_interval_ticks = value as u32;
1095                                Ok(())
1096                            }
1097                            Ok(_) => Err(RuntimeConfigError {
1098                                code: RuntimeConfigErrorCode::InvalidValue,
1099                                message: format!("{} must be >= 1", key),
1100                            }),
1101                            Err(err) => Err(err),
1102                        }
1103                    }
1104                    "global.announce_cache_cleanup_batch_size" => {
1105                        match Self::expect_u64(value, &key) {
1106                            Ok(value) if value > 0 => {
1107                                self.announce_cache_cleanup_batch_size = value as usize;
1108                                Ok(())
1109                            }
1110                            Ok(_) => Err(RuntimeConfigError {
1111                                code: RuntimeConfigErrorCode::InvalidValue,
1112                                message: format!("{} must be >= 1", key),
1113                            }),
1114                            Err(err) => Err(err),
1115                        }
1116                    }
1117                    "global.discovery_cleanup_interval_ticks" => {
1118                        match Self::expect_u64(value, &key) {
1119                            Ok(value) if value > 0 => {
1120                                self.discovery_cleanup_interval_ticks = value as u32;
1121                                Ok(())
1122                            }
1123                            Ok(_) => Err(RuntimeConfigError {
1124                                code: RuntimeConfigErrorCode::InvalidValue,
1125                                message: format!("{} must be >= 1", key),
1126                            }),
1127                            Err(err) => Err(err),
1128                        }
1129                    }
1130                    "global.management_announce_interval_secs" => {
1131                        match Self::expect_f64(value, &key) {
1132                            Ok(value) => {
1133                                self.management_announce_interval_secs = value;
1134                                Ok(())
1135                            }
1136                            Err(err) => Err(err),
1137                        }
1138                    }
1139                    "global.stamp_destinations" => match Self::expect_bool(value, &key) {
1140                        Ok(value) => {
1141                            self.stamp_destinations.store(value, Ordering::Relaxed);
1142                            Ok(())
1143                        }
1144                        Err(err) => Err(err),
1145                    },
1146                    "global.stamp_path_requests" => match Self::expect_bool(value, &key) {
1147                        Ok(value) => {
1148                            self.engine.set_stamp_prs(value);
1149                            Ok(())
1150                        }
1151                        Err(err) => Err(err),
1152                    },
1153                    "global.stamps_enforced" => match Self::expect_bool(value, &key) {
1154                        Ok(value) => {
1155                            self.set_stamps_enforced(value);
1156                            Ok(())
1157                        }
1158                        Err(err) => Err(err),
1159                    },
1160                    "global.lr_stamps_enforced" => match Self::expect_bool(value, &key) {
1161                        Ok(value) => {
1162                            self.link_manager.link_request_costs().set_enforced(value);
1163                            Ok(())
1164                        }
1165                        Err(err) => Err(err),
1166                    },
1167                    "global.stamp_link_requests" => match Self::expect_bool(value, &key) {
1168                        Ok(value) => {
1169                            self.stamp_settings.stamp_lrs = value;
1170                            Ok(())
1171                        }
1172                        Err(err) => Err(err),
1173                    },
1174                    "global.always_stamp_link_requests" => match Self::expect_bool(value, &key) {
1175                        Ok(value) => {
1176                            self.stamp_settings.always_stamp_lrs = value;
1177                            Ok(())
1178                        }
1179                        Err(err) => Err(err),
1180                    },
1181                    "global.direct_connect_policy" => {
1182                        let policy = match Self::parse_holepunch_policy(&value) {
1183                            Some(policy) => policy,
1184                            None => {
1185                                return QueryResponse::RuntimeConfigSet(Err(RuntimeConfigError {
1186                                    code: RuntimeConfigErrorCode::InvalidValue,
1187                                    message: format!(
1188                                        "{} must be one of: reject, accept_all, identified_only, ask_app",
1189                                        key
1190                                    ),
1191                                }))
1192                            }
1193                        };
1194                        self.holepunch_manager.set_policy(policy);
1195                        Ok(())
1196                    }
1197                    #[cfg(feature = "hooks")]
1198                    "provider.queue_max_events" => match Self::expect_u64(value, &key) {
1199                        Ok(v) if v > 0 => {
1200                            if let Some(ref bridge) = self.provider_bridge {
1201                                bridge.set_queue_max_events(v as usize);
1202                            }
1203                            Ok(())
1204                        }
1205                        Ok(_) => Err(RuntimeConfigError {
1206                            code: RuntimeConfigErrorCode::InvalidValue,
1207                            message: format!("{} must be >= 1", key),
1208                        }),
1209                        Err(err) => Err(err),
1210                    },
1211                    #[cfg(feature = "hooks")]
1212                    "provider.queue_max_bytes" => match Self::expect_u64(value, &key) {
1213                        Ok(v) if v > 0 => {
1214                            if let Some(ref bridge) = self.provider_bridge {
1215                                bridge.set_queue_max_bytes(v as usize);
1216                            }
1217                            Ok(())
1218                        }
1219                        Ok(_) => Err(RuntimeConfigError {
1220                            code: RuntimeConfigErrorCode::InvalidValue,
1221                            message: format!("{} must be >= 1", key),
1222                        }),
1223                        Err(err) => Err(err),
1224                    },
1225                    _ => match Self::runtime_config_family_for_key(&key) {
1226                        Some(family) => self.set_runtime_config_family_value(family, &key, value),
1227                        None => {
1228                            return QueryResponse::RuntimeConfigSet(Err(RuntimeConfigError {
1229                                code: RuntimeConfigErrorCode::UnknownKey,
1230                                message: format!("unknown runtime-config key '{}'", key),
1231                            }))
1232                        }
1233                    },
1234                };
1235
1236                QueryResponse::RuntimeConfigSet(match result {
1237                    Ok(()) => self.runtime_config_entry(&key).ok_or(RuntimeConfigError {
1238                        code: RuntimeConfigErrorCode::ApplyFailed,
1239                        message: format!("failed to read back runtime-config key '{}'", key),
1240                    }),
1241                    Err(err) => Err(err),
1242                })
1243            }
1244            QueryRequest::ResetRuntimeConfig { key } => {
1245                let defaults = self.runtime_config_defaults;
1246                let result = match key.as_str() {
1247                    "global.tick_interval_ms" => {
1248                        self.tick_interval_ms
1249                            .store(defaults.tick_interval_ms, Ordering::Relaxed);
1250                        Ok(())
1251                    }
1252                    "global.known_destinations_ttl_secs" => {
1253                        self.known_destinations_ttl = defaults.known_destinations_ttl;
1254                        Ok(())
1255                    }
1256                    "global.rate_limiter_ttl_secs" => {
1257                        self.rate_limiter_ttl_secs = defaults.rate_limiter_ttl_secs;
1258                        Ok(())
1259                    }
1260                    "global.known_destinations_cleanup_interval_ticks" => {
1261                        self.known_destinations_cleanup_interval_ticks =
1262                            defaults.known_destinations_cleanup_interval_ticks;
1263                        Ok(())
1264                    }
1265                    "global.announce_cache_cleanup_interval_ticks" => {
1266                        self.announce_cache_cleanup_interval_ticks =
1267                            defaults.announce_cache_cleanup_interval_ticks;
1268                        Ok(())
1269                    }
1270                    "global.announce_cache_cleanup_batch_size" => {
1271                        self.announce_cache_cleanup_batch_size =
1272                            defaults.announce_cache_cleanup_batch_size;
1273                        Ok(())
1274                    }
1275                    "global.discovery_cleanup_interval_ticks" => {
1276                        self.discovery_cleanup_interval_ticks =
1277                            defaults.discovery_cleanup_interval_ticks;
1278                        Ok(())
1279                    }
1280                    "global.management_announce_interval_secs" => {
1281                        self.management_announce_interval_secs =
1282                            defaults.management_announce_interval_secs;
1283                        Ok(())
1284                    }
1285                    "global.stamp_destinations" => {
1286                        self.stamp_destinations
1287                            .store(defaults.stamp_destinations, Ordering::Relaxed);
1288                        Ok(())
1289                    }
1290                    "global.stamp_path_requests" => {
1291                        self.engine.set_stamp_prs(defaults.stamp_path_requests);
1292                        Ok(())
1293                    }
1294                    "global.stamps_enforced" => {
1295                        self.set_stamps_enforced(defaults.stamps_enforced);
1296                        Ok(())
1297                    }
1298                    "global.lr_stamps_enforced" => {
1299                        self.link_manager
1300                            .link_request_costs()
1301                            .set_enforced(defaults.lr_stamps_enforced);
1302                        Ok(())
1303                    }
1304                    "global.stamp_link_requests" => {
1305                        self.stamp_settings.stamp_lrs = defaults.stamp_link_requests;
1306                        Ok(())
1307                    }
1308                    "global.always_stamp_link_requests" => {
1309                        self.stamp_settings.always_stamp_lrs = defaults.always_stamp_link_requests;
1310                        Ok(())
1311                    }
1312                    "global.direct_connect_policy" => {
1313                        self.holepunch_manager
1314                            .set_policy(defaults.direct_connect_policy);
1315                        Ok(())
1316                    }
1317                    #[cfg(feature = "hooks")]
1318                    "provider.queue_max_events" => {
1319                        if let Some(ref bridge) = self.provider_bridge {
1320                            bridge.set_queue_max_events(defaults.provider_queue_max_events);
1321                        }
1322                        Ok(())
1323                    }
1324                    #[cfg(feature = "hooks")]
1325                    "provider.queue_max_bytes" => {
1326                        if let Some(ref bridge) = self.provider_bridge {
1327                            bridge.set_queue_max_bytes(defaults.provider_queue_max_bytes);
1328                        }
1329                        Ok(())
1330                    }
1331                    _ => match Self::runtime_config_family_for_key(&key) {
1332                        Some(family) => self.reset_runtime_config_family_value(family, &key),
1333                        None => {
1334                            return QueryResponse::RuntimeConfigReset(Err(RuntimeConfigError {
1335                                code: RuntimeConfigErrorCode::UnknownKey,
1336                                message: format!("unknown runtime-config key '{}'", key),
1337                            }))
1338                        }
1339                    },
1340                };
1341
1342                QueryResponse::RuntimeConfigReset(match result {
1343                    Ok(()) => self.runtime_config_entry(&key).ok_or(RuntimeConfigError {
1344                        code: RuntimeConfigErrorCode::ApplyFailed,
1345                        message: format!("failed to read back runtime-config key '{}'", key),
1346                    }),
1347                    Err(err) => Err(err),
1348                })
1349            }
1350            other => self.handle_query(other),
1351        }
1352    }
1353}