1use std::collections::HashSet;
6use std::io;
7use std::path::Path;
8use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
9use std::sync::Arc;
10use std::thread::{self, JoinHandle};
11use std::time::Duration;
12
13use rns_core::transport::announce_verify_queue::OverflowPolicy as AnnounceQueueOverflowPolicy;
14use rns_core::transport::types::TransportConfig;
15use rns_crypto::identity::Identity;
16use rns_crypto::{OsRng, Rng};
17
18use crate::config;
19use crate::driver::{AnnounceRateDefaults, Callbacks, Driver};
20#[cfg(feature = "iface-backbone")]
21use crate::driver::{BackbonePeerPoolCandidateConfig, BackbonePeerPoolSettings};
22use crate::event::{self, Event, EventSender};
23use crate::ifac;
24#[cfg(feature = "iface-auto")]
25use crate::interface::auto::{auto_runtime_handle_from_config, AutoConfig};
26#[cfg(feature = "iface-backbone")]
27use crate::interface::backbone::{
28 client_config_from_mode, client_priority_from_mode, client_runtime_handle_from_mode,
29 peer_state_handle_from_mode, runtime_handle_from_mode, BackboneMode,
30};
31#[cfg(feature = "iface-i2p")]
32use crate::interface::i2p::{i2p_runtime_handle_from_config, I2pConfig};
33#[cfg(feature = "iface-local")]
34use crate::interface::local::LocalServerConfig;
35#[cfg(feature = "iface-pipe")]
36use crate::interface::pipe::{pipe_runtime_handle_from_config, PipeConfig};
37#[cfg(feature = "iface-rnode")]
38use crate::interface::rnode::{rnode_runtime_handle_from_config, RNodeConfig};
39#[cfg(feature = "iface-tcp")]
40use crate::interface::tcp::{tcp_client_runtime_handle_from_config, TcpClientConfig};
41#[cfg(feature = "iface-tcp")]
42use crate::interface::tcp_server::{
43 runtime_handle_from_config as tcp_runtime_handle_from_config, TcpServerConfig,
44};
45#[cfg(feature = "iface-udp")]
46use crate::interface::udp::{udp_runtime_handle_from_config, UdpConfig};
47use crate::interface::{InterfaceEntry, InterfaceStats};
48use crate::storage;
49use crate::time;
50
51#[cfg(test)]
52const DEFAULT_KNOWN_DESTINATIONS_TTL: Duration = Duration::from_secs(48 * 60 * 60);
53const DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES: usize = 8192;
54
55fn parse_interface_mode(mode: &str) -> u8 {
58 match mode.to_lowercase().as_str() {
59 "full" => rns_core::constants::MODE_FULL,
60 "access_point" | "accesspoint" | "ap" => rns_core::constants::MODE_ACCESS_POINT,
61 "pointtopoint" | "ptp" => rns_core::constants::MODE_POINT_TO_POINT,
62 "roaming" => rns_core::constants::MODE_ROAMING,
63 "boundary" => rns_core::constants::MODE_BOUNDARY,
64 "gateway" | "gw" => rns_core::constants::MODE_GATEWAY,
65 "internal" => rns_core::constants::MODE_INTERNAL,
66 _ => rns_core::constants::MODE_FULL,
67 }
68}
69
70fn default_ingress_control_for_type(
71 iface_type: &str,
72) -> rns_core::transport::types::IngressControlConfig {
73 match iface_type {
74 "AutoInterface" | "BackboneInterface" | "TCPClientInterface" | "TCPServerInterface"
75 | "UDPInterface" | "I2PInterface" => {
76 rns_core::transport::types::IngressControlConfig::enabled()
77 }
78 _ => rns_core::transport::types::IngressControlConfig::disabled(),
79 }
80}
81
82fn parse_ingress_control_config(
83 iface_type: &str,
84 params: &std::collections::HashMap<String, String>,
85) -> Result<rns_core::transport::types::IngressControlConfig, String> {
86 let mut config = default_ingress_control_for_type(iface_type);
87
88 if let Some(v) = params.get("ingress_control") {
89 config.enabled = config::parse_bool_pub(v)
90 .ok_or_else(|| format!("ingress_control must be a boolean, got '{}'", v))?;
91 }
92 if let Some(v) = params.get("ic_max_held_announces") {
93 config.max_held_announces = v
94 .parse::<usize>()
95 .map_err(|_| format!("ic_max_held_announces must be an integer, got '{}'", v))?;
96 }
97 if let Some(v) = params.get("ic_burst_hold") {
98 config.burst_hold = parse_nonnegative_f64("ic_burst_hold", v)?;
99 }
100 if let Some(v) = params.get("ic_burst_freq_new") {
101 config.burst_freq_new = parse_nonnegative_f64("ic_burst_freq_new", v)?;
102 }
103 if let Some(v) = params.get("ic_burst_freq") {
104 config.burst_freq = parse_nonnegative_f64("ic_burst_freq", v)?;
105 }
106 if let Some(v) = params.get("ic_pr_burst_freq_new") {
107 config.pr_burst_freq_new = parse_nonnegative_f64("ic_pr_burst_freq_new", v)?;
108 }
109 if let Some(v) = params.get("ic_pr_burst_freq") {
110 config.pr_burst_freq = parse_nonnegative_f64("ic_pr_burst_freq", v)?;
111 }
112 if let Some(v) = params.get("egress_control") {
113 config.egress_enabled = config::parse_bool_pub(v)
114 .ok_or_else(|| format!("egress_control must be a boolean, got '{}'", v))?;
115 }
116 if let Some(v) = params.get("ec_pr_freq") {
117 config.egress_pr_freq = parse_nonnegative_f64("ec_pr_freq", v)?;
118 }
119 if let Some(v) = params.get("ic_new_time") {
120 config.new_time = parse_nonnegative_f64("ic_new_time", v)?;
121 }
122 if let Some(v) = params.get("ic_burst_penalty") {
123 config.burst_penalty = parse_nonnegative_f64("ic_burst_penalty", v)?;
124 }
125 if let Some(v) = params.get("ic_held_release_interval") {
126 config.held_release_interval = parse_nonnegative_f64("ic_held_release_interval", v)?;
127 }
128
129 Ok(config)
130}
131
132fn parse_nonnegative_f64(key: &str, value: &str) -> Result<f64, String> {
133 let parsed = value
134 .parse::<f64>()
135 .map_err(|_| format!("{} must be numeric, got '{}'", key, value))?;
136 if parsed < 0.0 {
137 return Err(format!("{} must be >= 0, got '{}'", key, value));
138 }
139 Ok(parsed)
140}
141
142fn extract_ifac_config(
145 params: &std::collections::HashMap<String, String>,
146 default_size: usize,
147) -> Option<IfacConfig> {
148 let netname = params
149 .get("networkname")
150 .or_else(|| params.get("network_name"))
151 .cloned();
152 let netkey = params
153 .get("passphrase")
154 .or_else(|| params.get("pass_phrase"))
155 .cloned();
156
157 if netname.is_none() && netkey.is_none() {
158 return None;
159 }
160
161 let size = params
163 .get("ifac_size")
164 .and_then(|v| v.parse::<usize>().ok())
165 .map(|bits| (bits / 8).max(1))
166 .unwrap_or(default_size);
167
168 Some(IfacConfig {
169 netname,
170 netkey,
171 size,
172 })
173}
174
175fn extract_discovery_config(
177 iface_name: &str,
178 iface_type: &str,
179 params: &std::collections::HashMap<String, String>,
180) -> Option<crate::discovery::DiscoveryConfig> {
181 let discoverable = params
182 .get("discoverable")
183 .and_then(|v| config::parse_bool_pub(v))
184 .unwrap_or(false);
185 if !discoverable {
186 return None;
187 }
188
189 if iface_type == "TCPClientInterface" {
190 log::error!(
191 "Invalid interface discovery configuration for {}, aborting discovery announce",
192 iface_name
193 );
194 return None;
195 }
196
197 let discovery_name = params
198 .get("discovery_name")
199 .cloned()
200 .unwrap_or_else(|| iface_name.to_string());
201
202 let announce_interval = params
204 .get("announce_interval")
205 .and_then(|v| v.parse::<u64>().ok())
206 .map(|secs| secs.max(300))
207 .unwrap_or(21600);
208
209 let stamp_value = params
210 .get("discovery_stamp_value")
211 .and_then(|v| v.parse::<u8>().ok())
212 .unwrap_or(crate::discovery::DEFAULT_STAMP_VALUE);
213
214 let reachable_on = params.get("reachable_on").cloned();
215
216 let listen_port = params
217 .get("listen_port")
218 .or_else(|| params.get("port"))
219 .and_then(|v| v.parse().ok());
220
221 let latitude = params
222 .get("latitude")
223 .or_else(|| params.get("lat"))
224 .and_then(|v| v.parse().ok());
225 let longitude = params
226 .get("longitude")
227 .or_else(|| params.get("lon"))
228 .and_then(|v| v.parse().ok());
229 let height = params.get("height").and_then(|v| v.parse().ok());
230
231 Some(crate::discovery::DiscoveryConfig {
232 discovery_name,
233 announce_interval,
234 stamp_value,
235 reachable_on,
236 interface_type: iface_type.to_string(),
237 listen_port,
238 latitude,
239 longitude,
240 height,
241 })
242}
243
244fn default_discovery_runtime_config(
245 interface_name: &str,
246 interface_type: &str,
247 listen_port: Option<u16>,
248) -> crate::discovery::DiscoveryConfig {
249 crate::discovery::DiscoveryConfig {
250 discovery_name: interface_name.to_string(),
251 announce_interval: 21600,
252 stamp_value: crate::discovery::DEFAULT_STAMP_VALUE,
253 reachable_on: None,
254 interface_type: interface_type.to_string(),
255 listen_port,
256 latitude: None,
257 longitude: None,
258 height: None,
259 }
260}
261
262fn discovery_runtime_ifac_fields(ifac: Option<&IfacConfig>) -> (Option<String>, Option<String>) {
263 (
264 ifac.and_then(|cfg| cfg.netname.clone()),
265 ifac.and_then(|cfg| cfg.netkey.clone()),
266 )
267}
268
269#[cfg(feature = "iface-backbone")]
270fn backbone_discovery_runtime_from_interface(
271 interface_name: &str,
272 mode: &BackboneMode,
273 discovery: Option<&crate::discovery::DiscoveryConfig>,
274 transport_enabled: bool,
275 ifac: Option<&IfacConfig>,
276) -> Option<crate::driver::BackboneDiscoveryRuntimeHandle> {
277 let config = match mode {
278 BackboneMode::Server(config) => config,
279 BackboneMode::Client(_, _) => return None,
280 };
281
282 let startup_config = discovery.cloned().unwrap_or_else(|| {
283 default_discovery_runtime_config(
284 interface_name,
285 "BackboneInterface",
286 Some(config.listen_port),
287 )
288 });
289 let (ifac_netname, ifac_netkey) = discovery_runtime_ifac_fields(ifac);
290
291 Some(crate::driver::BackboneDiscoveryRuntimeHandle::from_parts(
292 config.name.clone(),
293 startup_config,
294 transport_enabled,
295 ifac_netname,
296 ifac_netkey,
297 discovery.is_some(),
298 ))
299}
300
301#[cfg(feature = "iface-tcp")]
302fn tcp_server_discovery_runtime_from_interface(
303 interface_name: &str,
304 config: &crate::interface::tcp_server::TcpServerConfig,
305 discovery: Option<&crate::discovery::DiscoveryConfig>,
306 transport_enabled: bool,
307 ifac: Option<&IfacConfig>,
308) -> crate::driver::TcpServerDiscoveryRuntimeHandle {
309 let startup_config = discovery.cloned().unwrap_or_else(|| {
310 default_discovery_runtime_config(
311 interface_name,
312 "TCPServerInterface",
313 Some(config.listen_port),
314 )
315 });
316 let (ifac_netname, ifac_netkey) = discovery_runtime_ifac_fields(ifac);
317
318 crate::driver::TcpServerDiscoveryRuntimeHandle::from_parts(
319 config.name.clone(),
320 startup_config,
321 transport_enabled,
322 ifac_netname,
323 ifac_netkey,
324 discovery.is_some(),
325 )
326}
327
328fn ifac_runtime_from_config(
329 ifac: Option<&IfacConfig>,
330 default_size: usize,
331) -> crate::driver::IfacRuntimeConfig {
332 crate::driver::IfacRuntimeConfig::from_parts(
333 ifac.and_then(|cfg| cfg.netname.clone()),
334 ifac.and_then(|cfg| cfg.netkey.clone()),
335 ifac.map(|cfg| cfg.size).unwrap_or(default_size),
336 )
337}
338
339fn discoverable_interface_from_config(
340 interface_name: &str,
341 discovery: &crate::discovery::DiscoveryConfig,
342 transport_enabled: bool,
343 ifac: Option<&IfacConfig>,
344) -> crate::discovery::DiscoverableInterface {
345 crate::discovery::DiscoverableInterface {
346 interface_name: interface_name.to_string(),
347 config: discovery.clone(),
348 transport_enabled,
349 ifac_netname: ifac.and_then(|cfg| cfg.netname.clone()),
350 ifac_netkey: ifac.and_then(|cfg| cfg.netkey.clone()),
351 }
352}
353
354fn derive_ifac_state(
355 ifac: Option<&IfacConfig>,
356 interface_name: &str,
357) -> io::Result<Option<crate::ifac::IfacState>> {
358 let Some(ifac) = ifac else {
359 return Ok(None);
360 };
361 if ifac.netname.is_none() && ifac.netkey.is_none() {
362 return Ok(None);
363 }
364
365 ifac::derive_ifac(ifac.netname.as_deref(), ifac.netkey.as_deref(), ifac.size)
366 .map(Some)
367 .map_err(|err| {
368 io::Error::other(format!(
369 "failed to derive IFAC for {}: {}",
370 interface_name, err
371 ))
372 })
373}
374
375fn register_started_interface(
376 driver: &mut Driver,
377 tx: &EventSender,
378 queue_capacity: usize,
379 id: rns_core::transport::types::InterfaceId,
380 mut info: rns_core::transport::types::InterfaceInfo,
381 writer: Box<dyn crate::interface::Writer>,
382 interface_type_name: String,
383 ifac_state: Option<crate::ifac::IfacState>,
384 ifac_runtime: &crate::driver::IfacRuntimeConfig,
385) {
386 driver.apply_announce_rate_defaults(&mut info);
387 driver.apply_ingress_control_defaults(&mut info);
388 let (writer, async_writer_metrics) =
389 crate::interface::wrap_async_writer(writer, id, &info.name, tx.clone(), queue_capacity);
390 driver.register_interface_runtime_defaults(&info);
391 driver.register_interface_ifac_runtime(&info.name, ifac_runtime.clone());
392 driver.engine.register_interface(info.clone());
393 driver.interfaces.insert(
394 id,
395 InterfaceEntry {
396 id,
397 info,
398 writer,
399 async_writer_metrics: Some(async_writer_metrics),
400 enabled: true,
401 online: false,
402 dynamic: false,
403 ifac: ifac_state,
404 stats: InterfaceStats {
405 started: time::now(),
406 ..Default::default()
407 },
408 interface_type: interface_type_name,
409 send_retry_at: None,
410 send_retry_backoff: Duration::ZERO,
411 },
412 );
413}
414
415pub struct NodeConfig {
417 pub transport_enabled: bool,
418 pub static_transport_identity: bool,
419 pub local_hops_delta: bool,
420 pub identity: Option<Identity>,
421 pub interfaces: Vec<InterfaceConfig>,
423 pub share_instance: bool,
425 pub instance_name: String,
427 pub shared_instance_port: u16,
429 pub rpc_port: u16,
431 pub cache_dir: Option<std::path::PathBuf>,
433 pub ratchet_store: Option<Arc<dyn storage::RatchetStore>>,
435 pub ratchet_expiry: Duration,
437 pub management: crate::management::ManagementConfig,
439 pub probe_port: Option<u16>,
441 pub probe_addrs: Vec<std::net::SocketAddr>,
443 pub probe_protocol: rns_core::holepunch::ProbeProtocol,
445 pub device: Option<String>,
447 pub hooks: Vec<config::ParsedHook>,
449 pub discover_interfaces: bool,
451 pub discovery_required_value: Option<u8>,
453 pub respond_to_probes: bool,
455 pub prefer_shorter_path: bool,
459 pub max_paths_per_destination: usize,
462 pub packet_hashlist_max_entries: usize,
464 pub max_discovery_pr_tags: usize,
466 pub max_path_destinations: usize,
468 pub max_tunnel_destinations_total: usize,
470 pub known_destinations_ttl: Duration,
472 pub known_destinations_max_entries: usize,
474 pub announce_table_ttl: Duration,
476 pub announce_table_max_bytes: usize,
478 pub driver_event_queue_capacity: usize,
480 pub interface_writer_queue_capacity: usize,
482 pub announce_rate_defaults: AnnounceRateDefaults,
484 pub ingress_control_defaults: rns_core::transport::types::IngressControlConfig,
486 #[cfg(feature = "iface-backbone")]
488 pub backbone_peer_pool: Option<BackbonePeerPoolSettings>,
489 pub announce_sig_cache_enabled: bool,
491 pub announce_sig_cache_max_entries: usize,
493 pub announce_sig_cache_ttl: Duration,
495 pub registry: Option<crate::interface::registry::InterfaceRegistry>,
497 pub panic_on_interface_error: bool,
500 #[cfg(feature = "hooks")]
502 pub provider_bridge: Option<crate::provider_bridge::ProviderBridgeConfig>,
503}
504
505impl Default for NodeConfig {
506 fn default() -> Self {
507 Self {
508 transport_enabled: false,
509 static_transport_identity: false,
510 local_hops_delta: false,
511 identity: None,
512 interfaces: Vec::new(),
513 share_instance: false,
514 instance_name: "default".into(),
515 shared_instance_port: 37428,
516 rpc_port: 0,
517 cache_dir: None,
518 ratchet_store: None,
519 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
520 management: Default::default(),
521 probe_port: None,
522 probe_addrs: vec![],
523 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
524 device: None,
525 hooks: Vec::new(),
526 discover_interfaces: false,
527 discovery_required_value: None,
528 respond_to_probes: false,
529 prefer_shorter_path: false,
530 max_paths_per_destination: 1,
531 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
532 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
533 max_path_destinations: rns_core::transport::types::DEFAULT_MAX_PATH_DESTINATIONS,
534 max_tunnel_destinations_total: usize::MAX,
535 known_destinations_ttl: Duration::from_secs(48 * 60 * 60),
536 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
537 announce_table_ttl: Duration::from_secs(rns_core::constants::ANNOUNCE_TABLE_TTL as u64),
538 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
539 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
540 interface_writer_queue_capacity: crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
541 announce_rate_defaults: AnnounceRateDefaults::default(),
542 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(),
543 #[cfg(feature = "iface-backbone")]
544 backbone_peer_pool: None,
545 announce_sig_cache_enabled: true,
546 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
547 announce_sig_cache_ttl: Duration::from_secs(
548 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
549 ),
550 registry: None,
551 panic_on_interface_error: false,
552 #[cfg(feature = "hooks")]
553 provider_bridge: None,
554 }
555 }
556}
557
558pub struct IfacConfig {
560 pub netname: Option<String>,
561 pub netkey: Option<String>,
562 pub size: usize,
563}
564
565pub struct InterfaceConfig {
567 pub name: String,
568 pub type_name: String,
569 pub config_data: Box<dyn crate::interface::InterfaceConfigData>,
570 pub mode: u8,
571 pub recursive_prs: bool,
572 pub announces_from_internal: bool,
573 pub ingress_control: rns_core::transport::types::IngressControlConfig,
574 pub ifac: Option<IfacConfig>,
575 pub discovery: Option<crate::discovery::DiscoveryConfig>,
576}
577
578use crate::event::{QueryRequest, QueryResponse};
579
580#[derive(Debug)]
582pub struct SendError;
583
584impl std::fmt::Display for SendError {
585 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
586 write!(f, "driver shut down")
587 }
588}
589
590impl std::error::Error for SendError {}
591
592pub struct RnsNode {
594 tx: EventSender,
595 driver_handle: Option<JoinHandle<()>>,
596 verify_handle: Option<JoinHandle<()>>,
597 verify_shutdown: Arc<AtomicBool>,
598 rpc_server: Option<crate::rpc::RpcServer>,
599 tick_interval_ms: Arc<AtomicU64>,
600 #[allow(dead_code)]
601 probe_server: Option<crate::holepunch::probe::ProbeServerHandle>,
602 known_destinations_path: Option<std::path::PathBuf>,
603 ratchet_store: Option<Arc<dyn storage::RatchetStore>>,
604 ratchet_expiry: Duration,
605}
606
607impl RnsNode {
608 pub fn from_config(
611 config_path: Option<&Path>,
612 callbacks: Box<dyn Callbacks>,
613 ) -> io::Result<Self> {
614 let config_dir = storage::resolve_config_dir(config_path);
615 let paths = storage::ensure_storage_dirs(&config_dir)?;
616 let known_destinations_path = paths.storage.join("known_destinations");
617 let ratchet_store: Arc<dyn storage::RatchetStore> =
618 Arc::new(storage::FsRatchetStore::new(paths.ratchets.clone()));
619
620 let config_file = config_dir.join("config");
622 let rns_config = if config_file.exists() {
623 config::parse_file(&config_file)
624 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("{}", e)))?
625 } else {
626 config::parse("")
628 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("{}", e)))?
629 };
630
631 let identity = if let Some(ref id_path_str) = rns_config.reticulum.network_identity {
633 let id_path = std::path::PathBuf::from(id_path_str);
634 if id_path.exists() {
635 storage::load_identity(&id_path)?
636 } else {
637 let id = Identity::new(&mut OsRng);
638 storage::save_identity(&id, &id_path)?;
639 id
640 }
641 } else {
642 storage::load_or_create_identity(&paths.identities)?
643 };
644
645 let registry = crate::interface::registry::InterfaceRegistry::with_builtins();
647 let mut interface_configs = Vec::new();
648 let mut next_id_val = 1u64;
649
650 for iface in &rns_config.interfaces {
651 if !iface.enabled {
652 continue;
653 }
654
655 let iface_id = rns_core::transport::types::InterfaceId(next_id_val);
656 next_id_val += 1;
657
658 let factory = match registry.get(&iface.interface_type) {
659 Some(f) => f,
660 None => {
661 log::warn!(
662 "Unsupported interface type '{}' for '{}'",
663 iface.interface_type,
664 iface.name,
665 );
666 continue;
667 }
668 };
669
670 let mut iface_mode = parse_interface_mode(&iface.mode);
671
672 let has_discovery = match iface.interface_type.as_str() {
674 "AutoInterface" => true,
675 "RNodeInterface" => iface
676 .params
677 .get("discoverable")
678 .and_then(|v| config::parse_bool_pub(v))
679 .unwrap_or(false),
680 _ => false,
681 };
682 if has_discovery
683 && iface_mode != rns_core::constants::MODE_ACCESS_POINT
684 && iface_mode != rns_core::constants::MODE_GATEWAY
685 {
686 let new_mode = if iface.interface_type == "RNodeInterface" {
687 rns_core::constants::MODE_ACCESS_POINT
688 } else {
689 rns_core::constants::MODE_GATEWAY
690 };
691 log::info!(
692 "Interface '{}' has discovery enabled, auto-configuring mode to {}",
693 iface.name,
694 if new_mode == rns_core::constants::MODE_ACCESS_POINT {
695 "ACCESS_POINT"
696 } else {
697 "GATEWAY"
698 }
699 );
700 iface_mode = new_mode;
701 }
702
703 let default_ifac_size = factory.default_ifac_size();
704 let ifac_config = extract_ifac_config(&iface.params, default_ifac_size);
705 let discovery_config =
706 extract_discovery_config(&iface.name, &iface.interface_type, &iface.params);
707 let ingress_control =
708 match parse_ingress_control_config(&iface.interface_type, &iface.params) {
709 Ok(config) => config,
710 Err(e) => {
711 log::warn!(
712 "Failed to parse ingress control config for '{}': {}",
713 iface.name,
714 e
715 );
716 continue;
717 }
718 };
719 let recursive_prs = iface
720 .params
721 .get("recursive_prs")
722 .and_then(|v| config::parse_bool_pub(v))
723 .unwrap_or(false);
724 let announces_from_internal = iface
725 .params
726 .get("announces_from_internal")
727 .and_then(|v| config::parse_bool_pub(v))
728 .unwrap_or(true);
729
730 let mut params = iface.params.clone();
732 if !params.contains_key("storage_dir") {
733 params.insert(
734 "storage_dir".to_string(),
735 paths.storage.to_string_lossy().to_string(),
736 );
737 }
738 if let Some(ref device) = rns_config.reticulum.device {
740 if !params.contains_key("device") {
741 params.insert("device".to_string(), device.clone());
742 }
743 }
744
745 let config_data = match factory.parse_config(&iface.name, iface_id, ¶ms) {
746 Ok(data) => data,
747 Err(e) => {
748 log::warn!("Failed to parse config for '{}': {}", iface.name, e);
749 continue;
750 }
751 };
752
753 interface_configs.push(InterfaceConfig {
754 name: iface.name.clone(),
755 type_name: iface.interface_type.clone(),
756 config_data,
757 mode: iface_mode,
758 recursive_prs,
759 announces_from_internal,
760 ingress_control,
761 ifac: ifac_config,
762 discovery: discovery_config,
763 });
764 }
765
766 let mut mgmt_allowed = Vec::new();
768 for hex_hash in &rns_config.reticulum.remote_management_allowed {
769 if hex_hash.len() == 32 {
770 if let Ok(bytes) = (0..hex_hash.len())
771 .step_by(2)
772 .map(|i| u8::from_str_radix(&hex_hash[i..i + 2], 16))
773 .collect::<Result<Vec<u8>, _>>()
774 {
775 if bytes.len() == 16 {
776 let mut h = [0u8; 16];
777 h.copy_from_slice(&bytes);
778 mgmt_allowed.push(h);
779 }
780 } else {
781 log::warn!("Invalid hex in remote_management_allowed: {}", hex_hash);
782 }
783 } else {
784 log::warn!(
785 "Invalid entry in remote_management_allowed (expected 32 hex chars, got {}): {}",
786 hex_hash.len(), hex_hash,
787 );
788 }
789 }
790
791 let probe_addrs: Vec<std::net::SocketAddr> = rns_config
793 .reticulum
794 .probe_addr
795 .as_ref()
796 .map(|s| {
797 s.split(',')
798 .filter_map(|entry| {
799 let trimmed = entry.trim();
800 if trimmed.is_empty() {
801 return None;
802 }
803 trimmed
804 .parse::<std::net::SocketAddr>()
805 .map_err(|e| {
806 log::warn!("Invalid probe_addr entry '{}': {}", trimmed, e);
807 e
808 })
809 .ok()
810 })
811 .collect()
812 })
813 .unwrap_or_default();
814
815 let probe_protocol = match rns_config
817 .reticulum
818 .probe_protocol
819 .as_deref()
820 .map(|s| s.to_lowercase())
821 {
822 Some(ref s) if s == "stun" => rns_core::holepunch::ProbeProtocol::Stun,
823 _ => rns_core::holepunch::ProbeProtocol::Rnsp,
824 };
825
826 let known_destinations = match storage::load_known_destinations(&known_destinations_path) {
827 Ok(destinations) => destinations,
828 Err(err) if err.kind() == io::ErrorKind::NotFound => Default::default(),
829 Err(err) => {
830 log::warn!("failed to load known destinations: {}", err);
831 Default::default()
832 }
833 };
834 let known_destination_hashes: HashSet<[u8; 16]> =
835 known_destinations.keys().copied().collect();
836 match ratchet_store.cleanup(
837 &known_destination_hashes,
838 time::now(),
839 rns_config.reticulum.ratchet_expiry as f64,
840 ) {
841 Ok(stats) if stats.processed > 0 => log::debug!(
842 "Processed {} ratchets, not in use {}, removed {}",
843 stats.processed,
844 stats.not_known,
845 stats.removed
846 ),
847 Ok(_) => {}
848 Err(err) => log::warn!("failed to clean ratchets: {}", err),
849 }
850
851 let node_config = NodeConfig {
852 transport_enabled: rns_config.reticulum.enable_transport,
853 static_transport_identity: rns_config.reticulum.static_transport_identity,
854 local_hops_delta: rns_config.reticulum.local_hops_delta,
855 identity: Some(identity),
856 share_instance: rns_config.reticulum.share_instance,
857 instance_name: rns_config.reticulum.instance_name.clone(),
858 shared_instance_port: rns_config.reticulum.shared_instance_port,
859 rpc_port: rns_config.reticulum.instance_control_port,
860 cache_dir: Some(paths.cache),
861 ratchet_store: Some(Arc::clone(&ratchet_store)),
862 ratchet_expiry: Duration::from_secs(rns_config.reticulum.ratchet_expiry),
863 management: crate::management::ManagementConfig {
864 enable_remote_management: rns_config.reticulum.enable_remote_management,
865 remote_management_allowed: mgmt_allowed,
866 publish_blackhole: rns_config.reticulum.publish_blackhole,
867 },
868 probe_port: rns_config.reticulum.probe_port,
869 probe_addrs,
870 probe_protocol,
871 device: rns_config.reticulum.device.clone(),
872 hooks: rns_config.hooks.clone(),
873 discover_interfaces: rns_config.reticulum.discover_interfaces,
874 discovery_required_value: rns_config.reticulum.required_discovery_value,
875 respond_to_probes: rns_config.reticulum.respond_to_probes,
876 prefer_shorter_path: rns_config.reticulum.prefer_shorter_path,
877 max_paths_per_destination: rns_config.reticulum.max_paths_per_destination,
878 packet_hashlist_max_entries: rns_config.reticulum.packet_hashlist_max_entries,
879 max_discovery_pr_tags: rns_config.reticulum.max_discovery_pr_tags,
880 max_path_destinations: rns_config.reticulum.max_path_destinations,
881 max_tunnel_destinations_total: rns_config.reticulum.max_tunnel_destinations_total,
882 known_destinations_ttl: Duration::from_secs(
883 rns_config.reticulum.known_destinations_ttl,
884 ),
885 known_destinations_max_entries: rns_config.reticulum.known_destinations_max_entries,
886 announce_table_ttl: Duration::from_secs(rns_config.reticulum.announce_table_ttl),
887 announce_table_max_bytes: rns_config.reticulum.announce_table_max_bytes,
888 driver_event_queue_capacity: rns_config.reticulum.driver_event_queue_capacity,
889 interface_writer_queue_capacity: rns_config.reticulum.interface_writer_queue_capacity,
890 announce_rate_defaults: AnnounceRateDefaults {
891 target: rns_config.reticulum.default_ar_target,
892 penalty: rns_config.reticulum.default_ar_penalty,
893 grace: rns_config.reticulum.default_ar_grace,
894 },
895 ingress_control_defaults: rns_core::transport::types::IngressControlConfig {
896 enabled: true,
897 egress_enabled: rns_config.reticulum.default_egress_control,
898 max_held_announces: rns_config.reticulum.default_ic_max_held_announces,
899 burst_freq_new: rns_config.reticulum.default_ic_burst_freq_new,
900 burst_freq: rns_config.reticulum.default_ic_burst_freq,
901 pr_burst_freq_new: rns_config.reticulum.default_ic_pr_burst_freq_new,
902 pr_burst_freq: rns_config.reticulum.default_ic_pr_burst_freq,
903 egress_pr_freq: rns_config.reticulum.default_ec_pr_freq,
904 new_time: rns_config.reticulum.default_ic_new_time,
905 burst_hold: rns_config.reticulum.default_ic_burst_hold,
906 burst_penalty: rns_config.reticulum.default_ic_burst_penalty,
907 held_release_interval: rns_config.reticulum.default_ic_held_release_interval,
908 },
909 #[cfg(feature = "iface-backbone")]
910 backbone_peer_pool: if rns_config.reticulum.backbone_peer_pool_max_connected > 0 {
911 Some(BackbonePeerPoolSettings {
912 max_connected: rns_config.reticulum.backbone_peer_pool_max_connected,
913 failure_threshold: rns_config.reticulum.backbone_peer_pool_failure_threshold,
914 failure_window: Duration::from_secs(
915 rns_config.reticulum.backbone_peer_pool_failure_window,
916 ),
917 cooldown: Duration::from_secs(rns_config.reticulum.backbone_peer_pool_cooldown),
918 })
919 } else {
920 None
921 },
922 announce_sig_cache_enabled: rns_config.reticulum.announce_sig_cache_enabled,
923 announce_sig_cache_max_entries: rns_config.reticulum.announce_sig_cache_max_entries,
924 announce_sig_cache_ttl: Duration::from_secs(
925 rns_config.reticulum.announce_sig_cache_ttl,
926 ),
927 interfaces: interface_configs,
928 registry: None,
929 panic_on_interface_error: rns_config.reticulum.panic_on_interface_error,
930 #[cfg(feature = "hooks")]
931 provider_bridge: if rns_config.reticulum.provider_bridge {
932 Some(crate::provider_bridge::ProviderBridgeConfig {
933 enabled: true,
934 socket_path: rns_config
935 .reticulum
936 .provider_socket_path
937 .as_ref()
938 .map(std::path::PathBuf::from)
939 .unwrap_or_else(|| config_dir.join("provider.sock")),
940 queue_max_events: rns_config.reticulum.provider_queue_max_events,
941 queue_max_bytes: rns_config.reticulum.provider_queue_max_bytes,
942 overflow_policy: match rns_config.reticulum.provider_overflow_policy.as_str() {
943 "drop_oldest" => crate::provider_bridge::OverflowPolicy::DropOldest,
944 _ => crate::provider_bridge::OverflowPolicy::DropNewest,
945 },
946 node_instance: rns_config.reticulum.instance_name.clone(),
947 })
948 } else {
949 None
950 },
951 };
952
953 let mut node = Self::start_with_announce_queue_max_entries(
954 node_config,
955 callbacks,
956 rns_config.reticulum.announce_queue_max_entries,
957 rns_config.reticulum.announce_queue_max_interfaces,
958 rns_config.reticulum.announce_queue_max_bytes,
959 rns_config.reticulum.announce_queue_ttl as f64,
960 match rns_config.reticulum.announce_queue_overflow_policy.as_str() {
961 "drop_newest" => AnnounceQueueOverflowPolicy::DropNewest,
962 "drop_oldest" => AnnounceQueueOverflowPolicy::DropOldest,
963 _ => AnnounceQueueOverflowPolicy::DropWorst,
964 },
965 )?;
966
967 node.known_destinations_path = Some(known_destinations_path.clone());
968 for (dest_hash, known) in known_destinations {
969 let _ = node.query(QueryRequest::RestoreKnownDestination(
970 crate::event::KnownDestinationEntry {
971 dest_hash,
972 identity_hash: known.identity_hash,
973 public_key: known.public_key,
974 app_data: known.app_data,
975 hops: known.hops,
976 received_at: known.received_at,
977 receiving_interface: rns_core::transport::types::InterfaceId(
978 known.receiving_interface,
979 ),
980 was_used: known.was_used,
981 last_used_at: known.last_used_at,
982 retained: known.retained,
983 },
984 ));
985 }
986
987 Ok(node)
988 }
989
990 pub fn start(config: NodeConfig, callbacks: Box<dyn Callbacks>) -> io::Result<Self> {
992 Self::start_with_announce_queue_max_entries(
993 config,
994 callbacks,
995 256,
996 1024,
997 256 * 1024,
998 30.0,
999 AnnounceQueueOverflowPolicy::DropWorst,
1000 )
1001 }
1002
1003 fn start_with_announce_queue_max_entries(
1004 config: NodeConfig,
1005 callbacks: Box<dyn Callbacks>,
1006 announce_queue_max_entries: usize,
1007 announce_queue_max_interfaces: usize,
1008 announce_queue_max_bytes: usize,
1009 announce_queue_ttl_secs: f64,
1010 announce_queue_overflow_policy: AnnounceQueueOverflowPolicy,
1011 ) -> io::Result<Self> {
1012 let identity = config.identity.unwrap_or_else(|| Identity::new(&mut OsRng));
1013 let transport_identity = if config.transport_enabled || config.static_transport_identity {
1014 Identity::from_private_key(
1015 &identity
1016 .get_private_key()
1017 .expect("node identity must expose a private key"),
1018 )
1019 } else {
1020 Identity::new(&mut OsRng)
1021 };
1022
1023 let local_hops_delta = if config.local_hops_delta {
1024 let mut byte = [0u8; 1];
1025 OsRng.fill_bytes(&mut byte);
1026 byte[0] % 6 + 2
1027 } else {
1028 0
1029 };
1030
1031 let transport_config = TransportConfig {
1032 transport_enabled: config.transport_enabled,
1033 identity_hash: Some(*transport_identity.hash()),
1034 local_hops_delta,
1035 prefer_shorter_path: config.prefer_shorter_path,
1036 max_paths_per_destination: config.max_paths_per_destination,
1037 packet_hashlist_max_entries: config.packet_hashlist_max_entries,
1038 max_discovery_pr_tags: config.max_discovery_pr_tags,
1039 max_path_destinations: config.max_path_destinations,
1040 max_tunnel_destinations_total: config.max_tunnel_destinations_total,
1041 destination_timeout_secs: config.known_destinations_ttl.as_secs_f64(),
1042 announce_table_ttl_secs: config.announce_table_ttl.as_secs_f64(),
1043 announce_table_max_bytes: config.announce_table_max_bytes,
1044 announce_sig_cache_enabled: config.announce_sig_cache_enabled,
1045 announce_sig_cache_max_entries: config.announce_sig_cache_max_entries,
1046 announce_sig_cache_ttl_secs: config.announce_sig_cache_ttl.as_secs_f64(),
1047 announce_queue_max_entries,
1048 announce_queue_max_interfaces,
1049 };
1050
1051 let (tx, rx) = event::channel_with_capacity(config.driver_event_queue_capacity);
1052 let tick_interval_ms = Arc::new(AtomicU64::new(1000));
1053 let mut driver = Driver::new(transport_config, rx, tx.clone(), callbacks);
1054 driver.set_announce_verify_queue_config(
1055 announce_queue_max_entries,
1056 announce_queue_max_bytes,
1057 announce_queue_ttl_secs,
1058 announce_queue_overflow_policy,
1059 );
1060 driver.async_announce_verification = true;
1061 driver.set_tick_interval_handle(Arc::clone(&tick_interval_ms));
1062 driver.set_packet_hashlist_max_entries(config.packet_hashlist_max_entries);
1063 driver.known_destinations_ttl = config.known_destinations_ttl.as_secs_f64();
1064 driver.known_destinations_max_entries = config.known_destinations_max_entries;
1065 driver.ratchet_store = config.ratchet_store.clone();
1066 driver.ratchet_expiry_secs = config.ratchet_expiry.as_secs_f64();
1067 driver.interface_writer_queue_capacity = config.interface_writer_queue_capacity;
1068 driver.set_announce_rate_defaults(config.announce_rate_defaults);
1069 driver.set_ingress_control_defaults(config.ingress_control_defaults);
1070 driver.runtime_config_defaults.known_destinations_ttl =
1071 config.known_destinations_ttl.as_secs_f64();
1072 #[cfg(feature = "hooks")]
1073 if let Some(provider_config) = config.provider_bridge.clone() {
1074 driver.runtime_config_defaults.provider_queue_max_events =
1075 provider_config.queue_max_events;
1076 driver.runtime_config_defaults.provider_queue_max_bytes =
1077 provider_config.queue_max_bytes;
1078 if provider_config.enabled {
1079 match crate::provider_bridge::ProviderBridge::start(provider_config) {
1080 Ok(bridge) => driver.provider_bridge = Some(bridge),
1081 Err(err) => log::warn!("failed to start provider bridge: {}", err),
1082 }
1083 }
1084 }
1085
1086 if let Some(ref cache_dir) = config.cache_dir {
1088 let announces_dir = cache_dir.join("announces");
1089 let _ = std::fs::create_dir_all(&announces_dir);
1090 driver.announce_cache = Some(crate::announce_cache::AnnounceCache::new(announces_dir));
1091 }
1092
1093 if !config.probe_addrs.is_empty() || config.device.is_some() {
1095 driver.set_probe_config(
1096 config.probe_addrs.clone(),
1097 config.probe_protocol,
1098 config.device.clone(),
1099 );
1100 }
1101
1102 let probe_server = if let Some(port) = config.probe_port {
1104 let listen_addr: std::net::SocketAddr = ([0, 0, 0, 0], port).into();
1105 match crate::holepunch::probe::start_probe_server(listen_addr) {
1106 Ok(handle) => {
1107 log::info!("Probe server started on 0.0.0.0:{}", port);
1108 Some(handle)
1109 }
1110 Err(e) => {
1111 log::error!("Failed to start probe server on port {}: {}", port, e);
1112 None
1113 }
1114 }
1115 } else {
1116 None
1117 };
1118
1119 driver.management_config = config.management.clone();
1121
1122 driver.transport_identity = Some(transport_identity);
1124
1125 #[cfg(feature = "hooks")]
1127 {
1128 for hook_cfg in &config.hooks {
1129 if !hook_cfg.enabled {
1130 continue;
1131 }
1132 let point_idx = match config::parse_hook_point(&hook_cfg.attach_point) {
1133 Some(idx) => idx,
1134 None => {
1135 log::warn!(
1136 "Unknown hook point '{}' for hook '{}'",
1137 hook_cfg.attach_point,
1138 hook_cfg.name,
1139 );
1140 continue;
1141 }
1142 };
1143 let mgr = match driver.hook_manager.as_ref() {
1144 Some(m) => m,
1145 None => {
1146 log::warn!(
1147 "Hook manager not available, skipping hook '{}'",
1148 hook_cfg.name
1149 );
1150 continue;
1151 }
1152 };
1153 let hook_backend = match config::parse_hook_backend(&hook_cfg.hook_type) {
1154 Ok(backend) => backend,
1155 Err(e) => {
1156 log::warn!(
1157 "Invalid hook type '{}' for hook '{}': {}",
1158 hook_cfg.hook_type,
1159 hook_cfg.name,
1160 e,
1161 );
1162 continue;
1163 }
1164 };
1165 let load_result = if hook_backend == rns_hooks::HookBackend::Builtin {
1166 let builtin_id = hook_cfg
1167 .builtin_id
1168 .as_deref()
1169 .filter(|id| !id.is_empty())
1170 .or_else(|| (!hook_cfg.path.is_empty()).then_some(hook_cfg.path.as_str()));
1171 match builtin_id {
1172 Some(id) => mgr.load_builtin(hook_cfg.name.clone(), id, hook_cfg.priority),
1173 None => Err(rns_hooks::HookError::CompileError(
1174 "built-in hook requires builtin/id or path".to_string(),
1175 )),
1176 }
1177 } else {
1178 mgr.load_file_backend(
1179 hook_cfg.name.clone(),
1180 std::path::Path::new(&hook_cfg.path),
1181 hook_cfg.priority,
1182 hook_backend,
1183 )
1184 };
1185 match load_result {
1186 Ok(program) => {
1187 driver.hook_slots[point_idx].attach(program);
1188 log::info!(
1189 "Loaded hook '{}' at point {} (priority {})",
1190 hook_cfg.name,
1191 hook_cfg.attach_point,
1192 hook_cfg.priority,
1193 );
1194 }
1195 Err(e) => {
1196 log::error!(
1197 "Failed to load hook '{}' from '{}': {}",
1198 hook_cfg.name,
1199 hook_cfg.path,
1200 e,
1201 );
1202 }
1203 }
1204 }
1205 }
1206
1207 driver.discover_interfaces = config.discover_interfaces;
1209 if let Some(val) = config.discovery_required_value {
1210 driver.discovery_required_value = val;
1211 }
1212
1213 let next_dynamic_id = Arc::new(AtomicU64::new(10000));
1215 #[cfg(feature = "iface-backbone")]
1216 {
1217 driver.next_dynamic_interface_id = Arc::clone(&next_dynamic_id);
1218 }
1219
1220 let mut discoverable_interfaces = Vec::new();
1222 #[cfg(feature = "iface-backbone")]
1223 let mut backbone_peer_pool_candidates = Vec::new();
1224
1225 let registry = config
1227 .registry
1228 .unwrap_or_else(crate::interface::registry::InterfaceRegistry::with_builtins);
1229 for iface_config in config.interfaces {
1230 #[cfg(feature = "iface-backbone")]
1231 if iface_config.type_name == "BackboneInterface" {
1232 if let Some(mode) = iface_config
1233 .config_data
1234 .as_any()
1235 .downcast_ref::<BackboneMode>()
1236 {
1237 if let Some(handle) = runtime_handle_from_mode(mode) {
1238 driver.register_backbone_runtime(handle);
1239 }
1240 if let Some(handle) = peer_state_handle_from_mode(mode) {
1241 driver.register_backbone_peer_state(handle);
1242 }
1243 if let Some(handle) = client_runtime_handle_from_mode(mode) {
1244 driver.register_backbone_client_runtime(handle);
1245 }
1246 if let Some(handle) = backbone_discovery_runtime_from_interface(
1247 &iface_config.name,
1248 mode,
1249 iface_config.discovery.as_ref(),
1250 config.transport_enabled,
1251 iface_config.ifac.as_ref(),
1252 ) {
1253 driver.register_backbone_discovery_runtime(handle);
1254 }
1255 }
1256 }
1257 #[cfg(feature = "iface-tcp")]
1258 if iface_config.type_name == "TCPClientInterface" {
1259 if let Some(tcp_config) = iface_config
1260 .config_data
1261 .as_any()
1262 .downcast_ref::<TcpClientConfig>()
1263 {
1264 driver.register_tcp_client_runtime(tcp_client_runtime_handle_from_config(
1265 tcp_config,
1266 ));
1267 }
1268 }
1269 #[cfg(feature = "iface-tcp")]
1270 if iface_config.type_name == "TCPServerInterface" {
1271 if let Some(tcp_config) = iface_config
1272 .config_data
1273 .as_any()
1274 .downcast_ref::<TcpServerConfig>()
1275 {
1276 driver.register_tcp_server_runtime(tcp_runtime_handle_from_config(tcp_config));
1277 driver.register_tcp_server_discovery_runtime(
1278 tcp_server_discovery_runtime_from_interface(
1279 &iface_config.name,
1280 tcp_config,
1281 iface_config.discovery.as_ref(),
1282 config.transport_enabled,
1283 iface_config.ifac.as_ref(),
1284 ),
1285 );
1286 }
1287 }
1288 #[cfg(feature = "iface-udp")]
1289 if iface_config.type_name == "UDPInterface" {
1290 if let Some(udp_config) = iface_config
1291 .config_data
1292 .as_any()
1293 .downcast_ref::<UdpConfig>()
1294 {
1295 driver.register_udp_runtime(udp_runtime_handle_from_config(udp_config));
1296 }
1297 }
1298 #[cfg(feature = "iface-auto")]
1299 if iface_config.type_name == "AutoInterface" {
1300 if let Some(auto_config) = iface_config
1301 .config_data
1302 .as_any()
1303 .downcast_ref::<AutoConfig>()
1304 {
1305 driver.register_auto_runtime(auto_runtime_handle_from_config(auto_config));
1306 }
1307 }
1308 #[cfg(feature = "iface-i2p")]
1309 if iface_config.type_name == "I2PInterface" {
1310 if let Some(i2p_config) = iface_config
1311 .config_data
1312 .as_any()
1313 .downcast_ref::<I2pConfig>()
1314 {
1315 driver.register_i2p_runtime(i2p_runtime_handle_from_config(i2p_config));
1316 }
1317 }
1318 #[cfg(feature = "iface-pipe")]
1319 if iface_config.type_name == "PipeInterface" {
1320 if let Some(pipe_config) = iface_config
1321 .config_data
1322 .as_any()
1323 .downcast_ref::<PipeConfig>()
1324 {
1325 driver.register_pipe_runtime(pipe_runtime_handle_from_config(pipe_config));
1326 }
1327 }
1328 #[cfg(feature = "iface-rnode")]
1329 if iface_config.type_name == "RNodeInterface" {
1330 if let Some(rnode_config) = iface_config
1331 .config_data
1332 .as_any()
1333 .downcast_ref::<RNodeConfig>()
1334 {
1335 driver.register_rnode_runtime(rnode_runtime_handle_from_config(rnode_config));
1336 }
1337 }
1338
1339 let factory = match registry.get(&iface_config.type_name) {
1340 Some(f) => f,
1341 None => {
1342 log::warn!(
1343 "No factory registered for interface type '{}'",
1344 iface_config.type_name
1345 );
1346 continue;
1347 }
1348 };
1349
1350 let mut ifac_state = derive_ifac_state(iface_config.ifac.as_ref(), &iface_config.name)?;
1351 let ifac_runtime =
1352 ifac_runtime_from_config(iface_config.ifac.as_ref(), factory.default_ifac_size());
1353
1354 #[cfg(feature = "iface-backbone")]
1355 if config.backbone_peer_pool.is_some() && iface_config.type_name == "BackboneInterface"
1356 {
1357 if let Some(mode) = iface_config
1358 .config_data
1359 .as_any()
1360 .downcast_ref::<BackboneMode>()
1361 {
1362 if let Some(client) = client_config_from_mode(mode) {
1363 let priority = client_priority_from_mode(mode).unwrap_or(
1364 crate::driver::BACKBONE_PEER_POOL_CONFIGURED_DEFAULT_PRIORITY,
1365 );
1366 backbone_peer_pool_candidates.push(BackbonePeerPoolCandidateConfig {
1367 client,
1368 mode: iface_config.mode,
1369 recursive_prs: iface_config.recursive_prs,
1370 announces_from_internal: iface_config.announces_from_internal,
1371 ingress_control: iface_config.ingress_control,
1372 ifac_runtime: ifac_runtime.clone(),
1373 ifac_enabled: ifac_state.is_some(),
1374 interface_type_name: iface_config.type_name.clone(),
1375 source: crate::driver::BackbonePeerPoolCandidateSource::Configured,
1376 priority,
1377 discovery: None,
1378 });
1379 if let Some(ref disc) = iface_config.discovery {
1380 discoverable_interfaces.push(discoverable_interface_from_config(
1381 &iface_config.name,
1382 disc,
1383 config.transport_enabled,
1384 iface_config.ifac.as_ref(),
1385 ));
1386 }
1387 continue;
1388 }
1389 }
1390 }
1391
1392 let ctx = crate::interface::StartContext {
1393 tx: tx.clone(),
1394 next_dynamic_id: next_dynamic_id.clone(),
1395 mode: iface_config.mode,
1396 recursive_prs: iface_config.recursive_prs,
1397 announces_from_internal: iface_config.announces_from_internal,
1398 ingress_control: iface_config.ingress_control,
1399 };
1400
1401 let result = match factory.start(iface_config.config_data, ctx) {
1402 Ok(r) => r,
1403 Err(e) => {
1404 if config.panic_on_interface_error {
1405 return Err(e);
1406 }
1407 log::error!(
1408 "Interface '{}' ({}) failed to start: {}",
1409 iface_config.name,
1410 iface_config.type_name,
1411 e
1412 );
1413 continue;
1414 }
1415 };
1416
1417 if let Some(ref disc) = iface_config.discovery {
1418 discoverable_interfaces.push(discoverable_interface_from_config(
1419 &iface_config.name,
1420 disc,
1421 config.transport_enabled,
1422 iface_config.ifac.as_ref(),
1423 ));
1424 }
1425
1426 match result {
1427 crate::interface::StartResult::Simple {
1428 id,
1429 info,
1430 writer,
1431 interface_type_name,
1432 } => {
1433 register_started_interface(
1434 &mut driver,
1435 &tx,
1436 config.interface_writer_queue_capacity,
1437 id,
1438 info,
1439 writer,
1440 interface_type_name,
1441 ifac_state,
1442 &ifac_runtime,
1443 );
1444 }
1445 crate::interface::StartResult::Listener { control } => {
1446 if let Some(control) = control {
1449 driver.register_listener_control(control);
1450 }
1451 }
1452 crate::interface::StartResult::Multi(subs) => {
1453 let ifac_cfg = &iface_config.ifac;
1454 let mut first = true;
1455 for sub in subs {
1456 let sub_ifac = if first {
1457 first = false;
1458 ifac_state.take()
1459 } else {
1460 derive_ifac_state(ifac_cfg.as_ref(), &sub.info.name)?
1461 };
1462 register_started_interface(
1463 &mut driver,
1464 &tx,
1465 config.interface_writer_queue_capacity,
1466 sub.id,
1467 sub.info,
1468 sub.writer,
1469 sub.interface_type_name,
1470 sub_ifac,
1471 &ifac_runtime,
1472 );
1473 }
1474 }
1475 }
1476 }
1477
1478 if !discoverable_interfaces.is_empty() {
1480 let transport_id = *identity.hash();
1481 let announcer =
1482 crate::discovery::InterfaceAnnouncer::new(transport_id, discoverable_interfaces);
1483 log::info!("Interface discovery announcer initialized");
1484 driver.interface_announcer = Some(announcer);
1485 }
1486
1487 if let Some(ref cache_dir) = config.cache_dir {
1489 let disc_path = std::path::PathBuf::from(cache_dir)
1490 .parent()
1491 .unwrap_or(std::path::Path::new("."))
1492 .join("storage")
1493 .join("discovery")
1494 .join("interfaces");
1495 let _ = std::fs::create_dir_all(&disc_path);
1496 driver.discovered_interfaces =
1497 crate::discovery::DiscoveredInterfaceStorage::new(disc_path);
1498 }
1499
1500 #[cfg(feature = "iface-backbone")]
1501 if let Some(settings) = config.backbone_peer_pool.clone() {
1502 driver.configure_backbone_peer_pool(settings, backbone_peer_pool_candidates);
1503 driver.seed_backbone_peer_pool_from_discovery_cache();
1504 }
1505
1506 if config.management.enable_remote_management {
1508 if let Some(prv_key) = identity.get_private_key() {
1509 let identity_hash = *identity.hash();
1510 let mgmt_dest = crate::management::management_dest_hash(&identity_hash);
1511
1512 let sig_prv = rns_crypto::ed25519::Ed25519PrivateKey::from_bytes(
1514 &prv_key[32..64].try_into().unwrap(),
1515 );
1516 let sig_pub_bytes: [u8; 32] = identity.get_public_key().unwrap()[32..64]
1517 .try_into()
1518 .unwrap();
1519
1520 driver
1522 .engine
1523 .register_destination(mgmt_dest, rns_core::constants::DESTINATION_SINGLE);
1524 driver
1525 .local_destinations
1526 .insert(mgmt_dest, rns_core::constants::DESTINATION_SINGLE);
1527
1528 driver.link_manager.register_link_destination(
1530 mgmt_dest,
1531 sig_prv,
1532 sig_pub_bytes,
1533 crate::link_manager::ResourceStrategy::AcceptNone,
1534 );
1535
1536 driver
1538 .link_manager
1539 .register_management_path(crate::management::status_path_hash());
1540 driver
1541 .link_manager
1542 .register_management_path(crate::management::path_path_hash());
1543
1544 log::info!("Remote management enabled on {:02x?}", &mgmt_dest[..4],);
1545
1546 if !config.management.remote_management_allowed.is_empty() {
1548 log::info!(
1549 "Remote management allowed for {} identities",
1550 config.management.remote_management_allowed.len(),
1551 );
1552 }
1553 }
1554 }
1555
1556 if config.management.publish_blackhole {
1557 if let Some(prv_key) = identity.get_private_key() {
1558 let identity_hash = *identity.hash();
1559 let bh_dest = crate::management::blackhole_dest_hash(&identity_hash);
1560
1561 let sig_prv = rns_crypto::ed25519::Ed25519PrivateKey::from_bytes(
1562 &prv_key[32..64].try_into().unwrap(),
1563 );
1564 let sig_pub_bytes: [u8; 32] = identity.get_public_key().unwrap()[32..64]
1565 .try_into()
1566 .unwrap();
1567
1568 driver
1569 .engine
1570 .register_destination(bh_dest, rns_core::constants::DESTINATION_SINGLE);
1571 driver.link_manager.register_link_destination(
1572 bh_dest,
1573 sig_prv,
1574 sig_pub_bytes,
1575 crate::link_manager::ResourceStrategy::AcceptNone,
1576 );
1577 driver
1578 .link_manager
1579 .register_management_path(crate::management::list_path_hash());
1580
1581 log::info!(
1582 "Blackhole list publishing enabled on {:02x?}",
1583 &bh_dest[..4],
1584 );
1585 }
1586 }
1587
1588 if config.respond_to_probes && config.transport_enabled {
1590 let identity_hash = *identity.hash();
1591 let probe_dest = crate::management::probe_dest_hash(&identity_hash);
1592
1593 driver
1595 .engine
1596 .register_destination(probe_dest, rns_core::constants::DESTINATION_SINGLE);
1597 driver
1598 .local_destinations
1599 .insert(probe_dest, rns_core::constants::DESTINATION_SINGLE);
1600
1601 let probe_identity = rns_crypto::identity::Identity::from_private_key(
1603 &identity.get_private_key().unwrap(),
1604 );
1605 driver.proof_strategies.insert(
1606 probe_dest,
1607 (
1608 rns_core::types::ProofStrategy::ProveAll,
1609 Some(probe_identity),
1610 ),
1611 );
1612
1613 driver.probe_responder_hash = Some(probe_dest);
1614
1615 log::info!("Probe responder enabled on {:02x?}", &probe_dest[..4],);
1616 }
1617
1618 let timer_tx = tx.clone();
1620 let timer_interval = Arc::clone(&tick_interval_ms);
1621 thread::Builder::new()
1622 .name("rns-timer".into())
1623 .spawn(move || {
1624 loop {
1625 let ms = timer_interval.load(Ordering::Relaxed);
1626 thread::sleep(Duration::from_millis(ms));
1627 if timer_tx.send(Event::Tick).is_err() {
1628 break; }
1630 }
1631 })?;
1632
1633 #[cfg(feature = "iface-local")]
1635 if config.share_instance {
1636 let local_server_config = LocalServerConfig {
1637 instance_name: config.instance_name.clone(),
1638 port: config.shared_instance_port,
1639 interface_id: rns_core::transport::types::InterfaceId(0), };
1641 match crate::interface::local::start_server(
1642 local_server_config,
1643 tx.clone(),
1644 next_dynamic_id.clone(),
1645 ) {
1646 Ok(control) => {
1647 driver.register_listener_control(control);
1648 log::info!(
1649 "Local shared instance server started (instance={}, port={})",
1650 config.instance_name,
1651 config.shared_instance_port
1652 );
1653 }
1654 Err(e) => {
1655 log::error!("Failed to start local shared instance server: {}", e);
1656 }
1657 }
1658 }
1659
1660 let rpc_server = if config.share_instance {
1662 let auth_key =
1663 crate::rpc::derive_auth_key(&identity.get_private_key().unwrap_or([0u8; 64]));
1664 let rpc_addr = crate::rpc::RpcAddr::Tcp("127.0.0.1".into(), config.rpc_port);
1665 match crate::rpc::RpcServer::start(&rpc_addr, auth_key, tx.clone()) {
1666 Ok(server) => {
1667 log::info!("RPC server started on 127.0.0.1:{}", config.rpc_port);
1668 Some(server)
1669 }
1670 Err(e) => {
1671 log::error!("Failed to start RPC server: {}", e);
1672 None
1673 }
1674 }
1675 } else {
1676 None
1677 };
1678
1679 let announce_verify_queue = Arc::clone(&driver.announce_verify_queue);
1680 let verify_shutdown = Arc::new(AtomicBool::new(false));
1681 let verify_shutdown_thread = Arc::clone(&verify_shutdown);
1682 let verify_tx = tx.clone();
1683 let verify_handle = thread::Builder::new()
1684 .name("rns-verify".into())
1685 .spawn(move || {
1686 #[cfg(target_family = "unix")]
1687 {
1688 unsafe {
1689 libc::nice(5);
1690 }
1691 }
1692
1693 while !verify_shutdown_thread.load(Ordering::Relaxed) {
1694 let batch = {
1695 let mut queue = announce_verify_queue
1696 .lock()
1697 .unwrap_or_else(|poisoned| poisoned.into_inner());
1698 queue.take_pending(time::now())
1699 };
1700
1701 if batch.is_empty() {
1702 thread::sleep(Duration::from_millis(50));
1703 continue;
1704 }
1705
1706 for (key, pending) in batch {
1707 if verify_shutdown_thread.load(Ordering::Relaxed) {
1708 break;
1709 }
1710 let has_ratchet =
1711 pending.packet.flags.context_flag == rns_core::constants::FLAG_SET;
1712 let announce = match rns_core::announce::AnnounceData::unpack(
1713 &pending.packet.data,
1714 has_ratchet,
1715 ) {
1716 Ok(announce) => announce,
1717 Err(_) => {
1718 let signature = [0u8; 64];
1719 let sig_cache_key = {
1720 let mut material = [0u8; 80];
1721 material[..16]
1722 .copy_from_slice(&pending.packet.destination_hash);
1723 material[16..].copy_from_slice(&signature);
1724 rns_core::hash::full_hash(&material)
1725 };
1726 if verify_tx
1727 .send(Event::AnnounceVerifyFailed { key, sig_cache_key })
1728 .is_err()
1729 {
1730 return;
1731 }
1732 continue;
1733 }
1734 };
1735 let mut material = [0u8; 80];
1736 material[..16].copy_from_slice(&pending.packet.destination_hash);
1737 material[16..].copy_from_slice(&announce.signature);
1738 let sig_cache_key = rns_core::hash::full_hash(&material);
1739 match announce.validate(&pending.packet.destination_hash) {
1740 Ok(validated) => {
1741 if verify_tx
1742 .send(Event::AnnounceVerified {
1743 key,
1744 validated,
1745 sig_cache_key,
1746 })
1747 .is_err()
1748 {
1749 return;
1750 }
1751 }
1752 Err(_) => {
1753 if verify_tx
1754 .send(Event::AnnounceVerifyFailed { key, sig_cache_key })
1755 .is_err()
1756 {
1757 return;
1758 }
1759 }
1760 }
1761 }
1762 }
1763 })?;
1764
1765 let driver_handle = thread::Builder::new()
1769 .name("rns-driver".into())
1770 .spawn(move || {
1771 driver.run();
1772 })?;
1773
1774 Ok(RnsNode {
1775 tx,
1776 driver_handle: Some(driver_handle),
1777 verify_handle: Some(verify_handle),
1778 verify_shutdown,
1779 rpc_server,
1780 tick_interval_ms,
1781 probe_server,
1782 known_destinations_path: None,
1783 ratchet_store: config.ratchet_store,
1784 ratchet_expiry: config.ratchet_expiry,
1785 })
1786 }
1787
1788 pub fn query(&self, request: QueryRequest) -> Result<QueryResponse, SendError> {
1790 let (resp_tx, resp_rx) = std::sync::mpsc::channel();
1791 self.tx
1792 .send(Event::Query(request, resp_tx))
1793 .map_err(|_| SendError)?;
1794 resp_rx.recv().map_err(|_| SendError)
1795 }
1796
1797 pub fn begin_drain(&self, timeout: Duration) -> Result<(), SendError> {
1799 self.tx
1800 .send(Event::BeginDrain { timeout })
1801 .map_err(|_| SendError)
1802 }
1803
1804 pub fn drain_status(&self) -> Result<crate::event::DrainStatus, SendError> {
1806 match self.query(QueryRequest::DrainStatus)? {
1807 QueryResponse::DrainStatus(status) => Ok(status),
1808 _ => Err(SendError),
1809 }
1810 }
1811
1812 fn reject_new_work_if_draining(&self) -> Result<(), SendError> {
1813 let status = self.drain_status()?;
1814 if matches!(status.state, crate::event::LifecycleState::Active) {
1815 Ok(())
1816 } else {
1817 Err(SendError)
1818 }
1819 }
1820
1821 pub fn send_raw(
1823 &self,
1824 raw: Vec<u8>,
1825 dest_type: u8,
1826 attached_interface: Option<rns_core::transport::types::InterfaceId>,
1827 ) -> Result<(), SendError> {
1828 self.tx
1829 .send(Event::SendOutbound {
1830 raw,
1831 dest_type,
1832 attached_interface,
1833 })
1834 .map_err(|_| SendError)
1835 }
1836
1837 pub fn register_destination(
1839 &self,
1840 dest_hash: [u8; 16],
1841 dest_type: u8,
1842 ) -> Result<(), SendError> {
1843 self.tx
1844 .send(Event::RegisterDestination {
1845 dest_hash,
1846 dest_type,
1847 })
1848 .map_err(|_| SendError)
1849 }
1850
1851 pub fn deregister_destination(&self, dest_hash: [u8; 16]) -> Result<(), SendError> {
1853 self.tx
1854 .send(Event::DeregisterDestination { dest_hash })
1855 .map_err(|_| SendError)
1856 }
1857
1858 pub fn deregister_link_destination(&self, dest_hash: [u8; 16]) -> Result<(), SendError> {
1860 self.tx
1861 .send(Event::DeregisterLinkDestination { dest_hash })
1862 .map_err(|_| SendError)
1863 }
1864
1865 pub fn register_link_destination(
1871 &self,
1872 dest_hash: [u8; 16],
1873 sig_prv_bytes: [u8; 32],
1874 sig_pub_bytes: [u8; 32],
1875 resource_strategy: u8,
1876 ) -> Result<(), SendError> {
1877 self.tx
1878 .send(Event::RegisterLinkDestination {
1879 dest_hash,
1880 sig_prv_bytes,
1881 sig_pub_bytes,
1882 resource_strategy,
1883 })
1884 .map_err(|_| SendError)
1885 }
1886
1887 pub fn register_request_handler<F>(
1889 &self,
1890 path: &str,
1891 allowed_list: Option<Vec<[u8; 16]>>,
1892 handler: F,
1893 ) -> Result<(), SendError>
1894 where
1895 F: Fn([u8; 16], &str, &[u8], Option<&([u8; 16], [u8; 64])>) -> Option<Vec<u8>>
1896 + Send
1897 + 'static,
1898 {
1899 self.tx
1900 .send(Event::RegisterRequestHandler {
1901 path: path.to_string(),
1902 allowed_list,
1903 handler: Box::new(handler),
1904 })
1905 .map_err(|_| SendError)
1906 }
1907
1908 pub fn register_request_handler_response<F>(
1910 &self,
1911 path: &str,
1912 allowed_list: Option<Vec<[u8; 16]>>,
1913 handler: F,
1914 ) -> Result<(), SendError>
1915 where
1916 F: Fn(
1917 [u8; 16],
1918 &str,
1919 &[u8],
1920 Option<&([u8; 16], [u8; 64])>,
1921 ) -> Option<crate::link_manager::RequestResponse>
1922 + Send
1923 + 'static,
1924 {
1925 self.tx
1926 .send(Event::RegisterRequestHandlerResponse {
1927 path: path.to_string(),
1928 allowed_list,
1929 handler: Box::new(handler),
1930 })
1931 .map_err(|_| SendError)
1932 }
1933
1934 pub fn create_link(
1938 &self,
1939 dest_hash: [u8; 16],
1940 dest_sig_pub_bytes: [u8; 32],
1941 ) -> Result<[u8; 16], SendError> {
1942 self.reject_new_work_if_draining()?;
1943 let (response_tx, response_rx) = std::sync::mpsc::channel();
1944 self.tx
1945 .send(Event::CreateLink {
1946 dest_hash,
1947 dest_sig_pub_bytes,
1948 response_tx,
1949 })
1950 .map_err(|_| SendError)?;
1951 let link_id = response_rx.recv().map_err(|_| SendError)?;
1952 if link_id == [0u8; 16] {
1953 Err(SendError)
1954 } else {
1955 Ok(link_id)
1956 }
1957 }
1958
1959 pub fn send_request(
1961 &self,
1962 link_id: [u8; 16],
1963 path: &str,
1964 data: &[u8],
1965 ) -> Result<(), SendError> {
1966 self.reject_new_work_if_draining()?;
1967 self.tx
1968 .send(Event::SendRequest {
1969 link_id,
1970 path: path.to_string(),
1971 data: data.to_vec(),
1972 })
1973 .map_err(|_| SendError)
1974 }
1975
1976 pub fn identify_on_link(
1978 &self,
1979 link_id: [u8; 16],
1980 identity_prv_key: [u8; 64],
1981 ) -> Result<(), SendError> {
1982 self.reject_new_work_if_draining()?;
1983 self.tx
1984 .send(Event::IdentifyOnLink {
1985 link_id,
1986 identity_prv_key,
1987 })
1988 .map_err(|_| SendError)
1989 }
1990
1991 pub fn teardown_link(&self, link_id: [u8; 16]) -> Result<(), SendError> {
1993 self.tx
1994 .send(Event::TeardownLink { link_id })
1995 .map_err(|_| SendError)
1996 }
1997
1998 pub fn send_resource(
2000 &self,
2001 link_id: [u8; 16],
2002 data: Vec<u8>,
2003 metadata: Option<Vec<u8>>,
2004 ) -> Result<(), SendError> {
2005 self.send_resource_with_auto_compress(link_id, data, metadata, true)
2006 }
2007
2008 pub fn send_resource_with_auto_compress(
2010 &self,
2011 link_id: [u8; 16],
2012 data: Vec<u8>,
2013 metadata: Option<Vec<u8>>,
2014 auto_compress: bool,
2015 ) -> Result<(), SendError> {
2016 self.reject_new_work_if_draining()?;
2017 self.tx
2018 .send(Event::SendResource {
2019 link_id,
2020 data,
2021 metadata,
2022 auto_compress,
2023 })
2024 .map_err(|_| SendError)
2025 }
2026
2027 pub fn set_resource_strategy(&self, link_id: [u8; 16], strategy: u8) -> Result<(), SendError> {
2031 self.tx
2032 .send(Event::SetResourceStrategy { link_id, strategy })
2033 .map_err(|_| SendError)
2034 }
2035
2036 pub fn accept_resource(
2038 &self,
2039 link_id: [u8; 16],
2040 resource_hash: Vec<u8>,
2041 accept: bool,
2042 ) -> Result<(), SendError> {
2043 if accept {
2044 self.reject_new_work_if_draining()?;
2045 }
2046 self.tx
2047 .send(Event::AcceptResource {
2048 link_id,
2049 resource_hash,
2050 accept,
2051 })
2052 .map_err(|_| SendError)
2053 }
2054
2055 pub fn send_channel_message(
2057 &self,
2058 link_id: [u8; 16],
2059 msgtype: u16,
2060 payload: Vec<u8>,
2061 ) -> Result<(), SendError> {
2062 self.reject_new_work_if_draining()?;
2063 let (response_tx, response_rx) = std::sync::mpsc::channel();
2064 self.tx
2065 .send(Event::SendChannelMessage {
2066 link_id,
2067 msgtype,
2068 payload,
2069 response_tx,
2070 })
2071 .map_err(|_| SendError)?;
2072 response_rx
2073 .recv()
2074 .map_err(|_| SendError)?
2075 .map_err(|_| SendError)
2076 }
2077
2078 pub fn propose_direct_connect(&self, link_id: [u8; 16]) -> Result<(), SendError> {
2083 self.reject_new_work_if_draining()?;
2084 self.tx
2085 .send(Event::ProposeDirectConnect { link_id })
2086 .map_err(|_| SendError)
2087 }
2088
2089 pub fn set_direct_connect_policy(
2091 &self,
2092 policy: crate::holepunch::orchestrator::HolePunchPolicy,
2093 ) -> Result<(), SendError> {
2094 self.tx
2095 .send(Event::SetDirectConnectPolicy { policy })
2096 .map_err(|_| SendError)
2097 }
2098
2099 pub fn send_on_link(
2101 &self,
2102 link_id: [u8; 16],
2103 data: Vec<u8>,
2104 context: u8,
2105 ) -> Result<(), SendError> {
2106 self.reject_new_work_if_draining()?;
2107 self.tx
2108 .send(Event::SendOnLink {
2109 link_id,
2110 data,
2111 context,
2112 })
2113 .map_err(|_| SendError)
2114 }
2115
2116 pub fn announce(
2121 &self,
2122 dest: &crate::destination::Destination,
2123 identity: &Identity,
2124 app_data: Option<&[u8]>,
2125 ) -> Result<(), SendError> {
2126 self.reject_new_work_if_draining()?;
2127 let name_hash = rns_core::destination::name_hash(
2128 &dest.app_name,
2129 &dest.aspects.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
2130 );
2131
2132 let mut random_hash = [0u8; 10];
2133 OsRng.fill_bytes(&mut random_hash[..5]);
2134 let now_secs = std::time::SystemTime::now()
2138 .duration_since(std::time::UNIX_EPOCH)
2139 .unwrap_or_default()
2140 .as_secs();
2141 random_hash[5..10].copy_from_slice(&now_secs.to_be_bytes()[3..8]);
2142
2143 let (announce_data, _has_ratchet) = rns_core::announce::AnnounceData::pack(
2144 identity,
2145 &dest.hash.0,
2146 &name_hash,
2147 &random_hash,
2148 None, app_data,
2150 )
2151 .map_err(|_| SendError)?;
2152
2153 let context_flag = rns_core::constants::FLAG_UNSET;
2154
2155 let flags = rns_core::packet::PacketFlags {
2156 header_type: rns_core::constants::HEADER_1,
2157 context_flag,
2158 transport_type: rns_core::constants::TRANSPORT_BROADCAST,
2159 destination_type: rns_core::constants::DESTINATION_SINGLE,
2160 packet_type: rns_core::constants::PACKET_TYPE_ANNOUNCE,
2161 };
2162
2163 let packet = rns_core::packet::RawPacket::pack(
2164 flags,
2165 0,
2166 &dest.hash.0,
2167 None,
2168 rns_core::constants::CONTEXT_NONE,
2169 &announce_data,
2170 )
2171 .map_err(|_| SendError)?;
2172
2173 if dest.dest_type == rns_core::types::DestinationType::Single {
2174 if let Some(identity_prv_key) = identity.get_private_key() {
2175 self.tx
2176 .send(Event::StoreSharedAnnounce {
2177 dest_hash: dest.hash.0,
2178 name_hash,
2179 identity_prv_key,
2180 app_data: app_data.map(|d| d.to_vec()),
2181 })
2182 .map_err(|_| SendError)?;
2183 }
2184 }
2185
2186 self.send_raw(packet.raw, dest.dest_type.to_wire_constant(), None)
2187 }
2188
2189 pub fn send_packet(
2194 &self,
2195 dest: &crate::destination::Destination,
2196 data: &[u8],
2197 ) -> Result<rns_core::types::PacketHash, SendError> {
2198 self.reject_new_work_if_draining()?;
2199 use rns_core::types::DestinationType;
2200
2201 let payload = match dest.dest_type {
2202 DestinationType::Single => self.encrypt_single_payload(dest, data)?,
2203 DestinationType::Plain => data.to_vec(),
2204 DestinationType::Group => dest.encrypt(data).map_err(|_| SendError)?,
2205 };
2206
2207 let flags = rns_core::packet::PacketFlags {
2208 header_type: rns_core::constants::HEADER_1,
2209 context_flag: rns_core::constants::FLAG_UNSET,
2210 transport_type: rns_core::constants::TRANSPORT_BROADCAST,
2211 destination_type: dest.dest_type.to_wire_constant(),
2212 packet_type: rns_core::constants::PACKET_TYPE_DATA,
2213 };
2214
2215 let packet = rns_core::packet::RawPacket::pack(
2216 flags,
2217 0,
2218 &dest.hash.0,
2219 None,
2220 rns_core::constants::CONTEXT_NONE,
2221 &payload,
2222 )
2223 .map_err(|_| SendError)?;
2224
2225 let packet_hash = rns_core::types::PacketHash(packet.packet_hash);
2226
2227 self.tx
2228 .send(Event::SendOutbound {
2229 raw: packet.raw,
2230 dest_type: dest.dest_type.to_wire_constant(),
2231 attached_interface: None,
2232 })
2233 .map_err(|_| SendError)?;
2234
2235 Ok(packet_hash)
2236 }
2237
2238 fn encrypt_single_payload(
2239 &self,
2240 dest: &crate::destination::Destination,
2241 data: &[u8],
2242 ) -> Result<Vec<u8>, SendError> {
2243 let pub_key = dest.public_key.ok_or(SendError)?;
2244 let remote_id = rns_crypto::identity::Identity::from_public_key(&pub_key);
2245 let ratchet = self.ratchet_store.as_ref().and_then(|store| {
2246 match store.current(&dest.hash.0, time::now(), self.ratchet_expiry.as_secs_f64()) {
2247 Ok(entry) => entry.map(|entry| entry.ratchet),
2248 Err(err) => {
2249 log::warn!(
2250 "failed to load ratchet for {:02x}{:02x}{:02x}{:02x}..: {}",
2251 dest.hash.0[0],
2252 dest.hash.0[1],
2253 dest.hash.0[2],
2254 dest.hash.0[3],
2255 err
2256 );
2257 None
2258 }
2259 }
2260 });
2261 remote_id
2262 .encrypt_with_ratchet(data, ratchet.as_ref(), &mut OsRng)
2263 .map_err(|_| SendError)
2264 }
2265
2266 pub fn register_destination_with_proof(
2271 &self,
2272 dest: &crate::destination::Destination,
2273 signing_key: Option<[u8; 64]>,
2274 ) -> Result<(), SendError> {
2275 self.register_destination(dest.hash.0, dest.dest_type.to_wire_constant())?;
2277
2278 if dest.proof_strategy != rns_core::types::ProofStrategy::ProveNone {
2280 self.tx
2281 .send(Event::RegisterProofStrategy {
2282 dest_hash: dest.hash.0,
2283 strategy: dest.proof_strategy,
2284 signing_key,
2285 })
2286 .map_err(|_| SendError)?;
2287 }
2288
2289 Ok(())
2290 }
2291
2292 pub fn request_path(&self, dest_hash: &rns_core::types::DestHash) -> Result<(), SendError> {
2294 self.reject_new_work_if_draining()?;
2295 self.tx
2296 .send(Event::RequestPath {
2297 dest_hash: dest_hash.0,
2298 })
2299 .map_err(|_| SendError)
2300 }
2301
2302 pub fn has_path(&self, dest_hash: &rns_core::types::DestHash) -> Result<bool, SendError> {
2304 match self.query(QueryRequest::HasPath {
2305 dest_hash: dest_hash.0,
2306 })? {
2307 QueryResponse::HasPath(v) => Ok(v),
2308 _ => Ok(false),
2309 }
2310 }
2311
2312 pub fn hops_to(&self, dest_hash: &rns_core::types::DestHash) -> Result<Option<u8>, SendError> {
2314 match self.query(QueryRequest::HopsTo {
2315 dest_hash: dest_hash.0,
2316 })? {
2317 QueryResponse::HopsTo(v) => Ok(v),
2318 _ => Ok(None),
2319 }
2320 }
2321
2322 pub fn recall_identity(
2324 &self,
2325 dest_hash: &rns_core::types::DestHash,
2326 ) -> Result<Option<crate::destination::AnnouncedIdentity>, SendError> {
2327 match self.query(QueryRequest::RecallIdentity {
2328 dest_hash: dest_hash.0,
2329 })? {
2330 QueryResponse::RecallIdentity(v) => Ok(v),
2331 _ => Ok(None),
2332 }
2333 }
2334
2335 pub fn known_destinations(
2337 &self,
2338 ) -> Result<Vec<crate::event::KnownDestinationEntry>, SendError> {
2339 match self.query(QueryRequest::KnownDestinations)? {
2340 QueryResponse::KnownDestinations(entries) => Ok(entries),
2341 _ => Ok(Vec::new()),
2342 }
2343 }
2344
2345 pub fn retain_known_destination(
2347 &self,
2348 dest_hash: &rns_core::types::DestHash,
2349 ) -> Result<bool, SendError> {
2350 match self.query(QueryRequest::RetainKnownDestination {
2351 dest_hash: dest_hash.0,
2352 })? {
2353 QueryResponse::RetainKnownDestination(ok) => Ok(ok),
2354 _ => Ok(false),
2355 }
2356 }
2357
2358 pub fn retain_identity(
2360 &self,
2361 identity_hash: &rns_core::types::IdentityHash,
2362 ) -> Result<bool, SendError> {
2363 match self.query(QueryRequest::RetainIdentity {
2364 identity_hash: identity_hash.0,
2365 })? {
2366 QueryResponse::RetainIdentity(ok) => Ok(ok),
2367 _ => Ok(false),
2368 }
2369 }
2370
2371 pub fn unretain_known_destination(
2373 &self,
2374 dest_hash: &rns_core::types::DestHash,
2375 ) -> Result<bool, SendError> {
2376 match self.query(QueryRequest::UnretainKnownDestination {
2377 dest_hash: dest_hash.0,
2378 })? {
2379 QueryResponse::UnretainKnownDestination(ok) => Ok(ok),
2380 _ => Ok(false),
2381 }
2382 }
2383
2384 pub fn mark_known_destination_used(
2386 &self,
2387 dest_hash: &rns_core::types::DestHash,
2388 ) -> Result<bool, SendError> {
2389 match self.query(QueryRequest::MarkKnownDestinationUsed {
2390 dest_hash: dest_hash.0,
2391 })? {
2392 QueryResponse::MarkKnownDestinationUsed(ok) => Ok(ok),
2393 _ => Ok(false),
2394 }
2395 }
2396
2397 fn persist_known_destinations(&self) {
2398 let Some(path) = self.known_destinations_path.as_ref() else {
2399 return;
2400 };
2401
2402 let Ok(entries) = self.known_destinations() else {
2403 return;
2404 };
2405
2406 let destinations: std::collections::HashMap<[u8; 16], storage::KnownDestination> = entries
2407 .into_iter()
2408 .map(|entry| {
2409 (
2410 entry.dest_hash,
2411 storage::KnownDestination {
2412 identity_hash: entry.identity_hash,
2413 public_key: entry.public_key,
2414 app_data: entry.app_data,
2415 hops: entry.hops,
2416 received_at: entry.received_at,
2417 receiving_interface: entry.receiving_interface.0,
2418 was_used: entry.was_used,
2419 last_used_at: entry.last_used_at,
2420 retained: entry.retained,
2421 },
2422 )
2423 })
2424 .collect();
2425
2426 if let Err(err) = storage::save_known_destinations(&destinations, path) {
2427 log::warn!("failed to persist known destinations: {}", err);
2428 }
2429 }
2430
2431 pub fn load_hook(
2433 &self,
2434 name: String,
2435 wasm_bytes: Vec<u8>,
2436 attach_point: String,
2437 priority: i32,
2438 ) -> Result<Result<(), String>, SendError> {
2439 let (response_tx, response_rx) = std::sync::mpsc::channel();
2440 self.tx
2441 .send(Event::LoadHook {
2442 name,
2443 wasm_bytes,
2444 attach_point,
2445 priority,
2446 response_tx,
2447 })
2448 .map_err(|_| SendError)?;
2449 response_rx.recv().map_err(|_| SendError)
2450 }
2451
2452 pub fn load_hook_file(
2454 &self,
2455 name: String,
2456 path: String,
2457 hook_type: String,
2458 attach_point: String,
2459 priority: i32,
2460 ) -> Result<Result<(), String>, SendError> {
2461 let (response_tx, response_rx) = std::sync::mpsc::channel();
2462 self.tx
2463 .send(Event::LoadHookFile {
2464 name,
2465 path,
2466 hook_type,
2467 attach_point,
2468 priority,
2469 response_tx,
2470 })
2471 .map_err(|_| SendError)?;
2472 response_rx.recv().map_err(|_| SendError)
2473 }
2474
2475 pub fn load_builtin_hook(
2477 &self,
2478 name: String,
2479 builtin_id: String,
2480 attach_point: String,
2481 priority: i32,
2482 ) -> Result<Result<(), String>, SendError> {
2483 let (response_tx, response_rx) = std::sync::mpsc::channel();
2484 self.tx
2485 .send(Event::LoadBuiltinHook {
2486 name,
2487 builtin_id,
2488 attach_point,
2489 priority,
2490 response_tx,
2491 })
2492 .map_err(|_| SendError)?;
2493 response_rx.recv().map_err(|_| SendError)
2494 }
2495
2496 pub fn unload_hook(
2498 &self,
2499 name: String,
2500 attach_point: String,
2501 ) -> Result<Result<(), String>, SendError> {
2502 let (response_tx, response_rx) = std::sync::mpsc::channel();
2503 self.tx
2504 .send(Event::UnloadHook {
2505 name,
2506 attach_point,
2507 response_tx,
2508 })
2509 .map_err(|_| SendError)?;
2510 response_rx.recv().map_err(|_| SendError)
2511 }
2512
2513 pub fn reload_hook(
2515 &self,
2516 name: String,
2517 attach_point: String,
2518 wasm_bytes: Vec<u8>,
2519 ) -> Result<Result<(), String>, SendError> {
2520 let (response_tx, response_rx) = std::sync::mpsc::channel();
2521 self.tx
2522 .send(Event::ReloadHook {
2523 name,
2524 attach_point,
2525 wasm_bytes,
2526 response_tx,
2527 })
2528 .map_err(|_| SendError)?;
2529 response_rx.recv().map_err(|_| SendError)
2530 }
2531
2532 pub fn reload_hook_file(
2534 &self,
2535 name: String,
2536 attach_point: String,
2537 path: String,
2538 hook_type: String,
2539 ) -> Result<Result<(), String>, SendError> {
2540 let (response_tx, response_rx) = std::sync::mpsc::channel();
2541 self.tx
2542 .send(Event::ReloadHookFile {
2543 name,
2544 attach_point,
2545 path,
2546 hook_type,
2547 response_tx,
2548 })
2549 .map_err(|_| SendError)?;
2550 response_rx.recv().map_err(|_| SendError)
2551 }
2552
2553 pub fn reload_builtin_hook(
2555 &self,
2556 name: String,
2557 attach_point: String,
2558 builtin_id: String,
2559 ) -> Result<Result<(), String>, SendError> {
2560 let (response_tx, response_rx) = std::sync::mpsc::channel();
2561 self.tx
2562 .send(Event::ReloadBuiltinHook {
2563 name,
2564 attach_point,
2565 builtin_id,
2566 response_tx,
2567 })
2568 .map_err(|_| SendError)?;
2569 response_rx.recv().map_err(|_| SendError)
2570 }
2571
2572 pub fn set_hook_enabled(
2574 &self,
2575 name: String,
2576 attach_point: String,
2577 enabled: bool,
2578 ) -> Result<Result<(), String>, SendError> {
2579 let (response_tx, response_rx) = std::sync::mpsc::channel();
2580 self.tx
2581 .send(Event::SetHookEnabled {
2582 name,
2583 attach_point,
2584 enabled,
2585 response_tx,
2586 })
2587 .map_err(|_| SendError)?;
2588 response_rx.recv().map_err(|_| SendError)
2589 }
2590
2591 pub fn set_hook_priority(
2593 &self,
2594 name: String,
2595 attach_point: String,
2596 priority: i32,
2597 ) -> Result<Result<(), String>, SendError> {
2598 let (response_tx, response_rx) = std::sync::mpsc::channel();
2599 self.tx
2600 .send(Event::SetHookPriority {
2601 name,
2602 attach_point,
2603 priority,
2604 response_tx,
2605 })
2606 .map_err(|_| SendError)?;
2607 response_rx.recv().map_err(|_| SendError)
2608 }
2609
2610 pub fn list_hooks(&self) -> Result<Vec<crate::event::HookInfo>, SendError> {
2612 let (response_tx, response_rx) = std::sync::mpsc::channel();
2613 self.tx
2614 .send(Event::ListHooks { response_tx })
2615 .map_err(|_| SendError)?;
2616 response_rx.recv().map_err(|_| SendError)
2617 }
2618
2619 pub(crate) fn from_parts(
2622 tx: EventSender,
2623 driver_handle: thread::JoinHandle<()>,
2624 rpc_server: Option<crate::rpc::RpcServer>,
2625 tick_interval_ms: Arc<AtomicU64>,
2626 ) -> Self {
2627 RnsNode {
2628 tx,
2629 driver_handle: Some(driver_handle),
2630 verify_handle: None,
2631 verify_shutdown: Arc::new(AtomicBool::new(false)),
2632 rpc_server,
2633 tick_interval_ms,
2634 probe_server: None,
2635 known_destinations_path: None,
2636 ratchet_store: None,
2637 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
2638 }
2639 }
2640
2641 pub fn event_sender(&self) -> &EventSender {
2643 &self.tx
2644 }
2645
2646 pub fn set_tick_interval(&self, ms: u64) -> u64 {
2651 let clamped = ms.clamp(100, 10_000);
2652 if clamped != ms {
2653 log::warn!(
2654 "tick interval {}ms out of range, clamped to {}ms",
2655 ms,
2656 clamped
2657 );
2658 }
2659 self.tick_interval_ms.store(clamped, Ordering::Relaxed);
2660 clamped
2661 }
2662
2663 pub fn tick_interval(&self) -> u64 {
2665 self.tick_interval_ms.load(Ordering::Relaxed)
2666 }
2667
2668 pub fn shutdown(mut self) {
2670 if let Some(mut rpc) = self.rpc_server.take() {
2672 rpc.stop();
2673 }
2674 self.persist_known_destinations();
2675 self.verify_shutdown.store(true, Ordering::Relaxed);
2676 let _ = self.tx.send(Event::Shutdown);
2677 if let Some(handle) = self.driver_handle.take() {
2678 let _ = handle.join();
2679 }
2680 if let Some(handle) = self.verify_handle.take() {
2681 let _ = handle.join();
2682 }
2683 }
2684}
2685
2686#[cfg(test)]
2687mod tests {
2688 use super::*;
2689 use crate::driver::IfacRuntimeConfig;
2690 use crate::storage::RatchetStore;
2691 use std::fs;
2692 use tempfile::tempdir;
2693
2694 struct NoopCallbacks;
2695
2696 impl Callbacks for NoopCallbacks {
2697 fn on_announce(&mut self, _: crate::destination::AnnouncedIdentity) {}
2698 fn on_path_updated(&mut self, _: rns_core::types::DestHash, _: u8) {}
2699 fn on_local_delivery(
2700 &mut self,
2701 _: rns_core::types::DestHash,
2702 _: Vec<u8>,
2703 _: rns_core::types::PacketHash,
2704 ) {
2705 }
2706 }
2707
2708 struct TestWriter;
2709
2710 impl crate::interface::Writer for TestWriter {
2711 fn send_frame(&mut self, _data: &[u8]) -> io::Result<()> {
2712 Ok(())
2713 }
2714 }
2715
2716 fn test_transport_config(transport_enabled: bool) -> TransportConfig {
2717 TransportConfig {
2718 transport_enabled,
2719 identity_hash: None,
2720 local_hops_delta: 0,
2721 prefer_shorter_path: false,
2722 max_paths_per_destination: 1,
2723 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
2724 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
2725 max_path_destinations: usize::MAX,
2726 max_tunnel_destinations_total: usize::MAX,
2727 destination_timeout_secs: rns_core::constants::DESTINATION_TIMEOUT,
2728 announce_table_ttl_secs: rns_core::constants::ANNOUNCE_TABLE_TTL,
2729 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
2730 announce_sig_cache_enabled: true,
2731 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
2732 announce_sig_cache_ttl_secs: rns_core::constants::ANNOUNCE_SIG_CACHE_TTL,
2733 announce_queue_max_entries: 256,
2734 announce_queue_max_interfaces: 1024,
2735 }
2736 }
2737
2738 fn test_interface_info(id: u64) -> rns_core::transport::types::InterfaceInfo {
2739 rns_core::transport::types::InterfaceInfo {
2740 id: rns_core::transport::types::InterfaceId(id),
2741 name: format!("test-{id}"),
2742 mode: rns_core::constants::MODE_FULL,
2743 recursive_prs: false,
2744 announces_from_internal: true,
2745 out_capable: true,
2746 in_capable: true,
2747 bitrate: None,
2748 airtime_profile: None,
2749 announce_rate_target: None,
2750 announce_rate_grace: 0,
2751 announce_rate_penalty: 0.0,
2752 announce_cap: rns_core::constants::ANNOUNCE_CAP,
2753 is_local_client: false,
2754 wants_tunnel: false,
2755 tunnel_id: None,
2756 mtu: rns_core::constants::MTU as u32,
2757 ingress_control: rns_core::transport::types::IngressControlConfig::disabled(),
2758 ia_freq: 0.0,
2759 ip_freq: 0.0,
2760 op_freq: 0.0,
2761 op_samples: 0,
2762 started: 0.0,
2763 }
2764 }
2765
2766 #[test]
2767 fn static_interface_registration_applies_transport_announce_rate_defaults() {
2768 let (tx, rx) = crate::event::channel();
2769 let mut driver = Driver::new(
2770 test_transport_config(true),
2771 rx,
2772 tx.clone(),
2773 Box::new(NoopCallbacks),
2774 );
2775 driver.set_announce_rate_defaults(AnnounceRateDefaults {
2776 target: Some(7200.0),
2777 penalty: 15.0,
2778 grace: 7,
2779 });
2780
2781 let id = rns_core::transport::types::InterfaceId(7);
2782 register_started_interface(
2783 &mut driver,
2784 &tx,
2785 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
2786 id,
2787 test_interface_info(id.0),
2788 Box::new(TestWriter),
2789 "TestInterface".to_string(),
2790 None,
2791 &IfacRuntimeConfig {
2792 netname: None,
2793 netkey: None,
2794 size: 16,
2795 },
2796 );
2797
2798 let info = &driver.interfaces[&id].info;
2799 assert_eq!(info.announce_rate_target, Some(7200.0));
2800 assert_eq!(info.announce_rate_penalty, 15.0);
2801 assert_eq!(info.announce_rate_grace, 7);
2802 }
2803
2804 #[test]
2805 fn static_interface_registration_applies_transport_ingress_control_defaults() {
2806 let (tx, rx) = crate::event::channel();
2807 let mut driver = Driver::new(
2808 test_transport_config(true),
2809 rx,
2810 tx.clone(),
2811 Box::new(NoopCallbacks),
2812 );
2813 let mut defaults = rns_core::transport::types::IngressControlConfig::enabled();
2814 defaults.burst_hold = 1.5;
2815 defaults.pr_burst_freq = 5.5;
2816 defaults.egress_enabled = true;
2817 defaults.egress_pr_freq = 9.5;
2818 driver.set_ingress_control_defaults(defaults);
2819
2820 let id = rns_core::transport::types::InterfaceId(8);
2821 register_started_interface(
2822 &mut driver,
2823 &tx,
2824 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
2825 id,
2826 test_interface_info(id.0),
2827 Box::new(TestWriter),
2828 "TestInterface".to_string(),
2829 None,
2830 &IfacRuntimeConfig {
2831 netname: None,
2832 netkey: None,
2833 size: 16,
2834 },
2835 );
2836
2837 let ic = driver.interfaces[&id].info.ingress_control;
2838 assert_eq!(ic.burst_hold, 1.5);
2839 assert_eq!(ic.pr_burst_freq, 5.5);
2840 assert!(ic.egress_enabled);
2841 assert_eq!(ic.egress_pr_freq, 9.5);
2842 }
2843
2844 struct TestNodeRatchetStore {
2845 entry: storage::RatchetEntry,
2846 current_calls: std::sync::Mutex<Vec<[u8; 16]>>,
2847 }
2848
2849 impl storage::RatchetStore for TestNodeRatchetStore {
2850 fn remember(&self, _dest_hash: [u8; 16], _entry: storage::RatchetEntry) -> io::Result<()> {
2851 Ok(())
2852 }
2853
2854 fn current(
2855 &self,
2856 dest_hash: &[u8; 16],
2857 _now: f64,
2858 _expiry_secs: f64,
2859 ) -> io::Result<Option<storage::RatchetEntry>> {
2860 self.current_calls.lock().unwrap().push(*dest_hash);
2861 Ok(Some(self.entry))
2862 }
2863
2864 fn cleanup(
2865 &self,
2866 _known_destinations: &HashSet<[u8; 16]>,
2867 _now: f64,
2868 _expiry_secs: f64,
2869 ) -> io::Result<storage::RatchetCleanupStats> {
2870 Ok(Default::default())
2871 }
2872 }
2873
2874 #[test]
2875 fn send_packet_checks_ratchet_store_for_single_destinations() {
2876 let store = Arc::new(TestNodeRatchetStore {
2877 entry: storage::RatchetEntry {
2878 ratchet: [0x55; 32],
2879 received_at: time::now(),
2880 },
2881 current_calls: std::sync::Mutex::new(Vec::new()),
2882 });
2883 let ratchet_store: Arc<dyn storage::RatchetStore> = store.clone();
2884 let node = RnsNode::start(
2885 NodeConfig {
2886 ratchet_store: Some(ratchet_store),
2887 ..Default::default()
2888 },
2889 Box::new(NoopCallbacks),
2890 )
2891 .unwrap();
2892
2893 let mut rng = OsRng;
2894 let remote_identity = Identity::new(&mut rng);
2895 let public_key = remote_identity.get_public_key().unwrap();
2896 let announced = crate::destination::AnnouncedIdentity {
2897 dest_hash: rns_core::types::DestHash([0x22; 16]),
2898 identity_hash: rns_core::types::IdentityHash(*remote_identity.hash()),
2899 public_key,
2900 app_data: None,
2901 hops: 1,
2902 received_at: time::now(),
2903 receiving_interface: rns_core::transport::types::InterfaceId(0),
2904 rssi: Some(-100),
2905 snr: Some(10.5),
2906 };
2907 let dest = crate::destination::Destination::single_out("test", &["ratchet"], &announced);
2908
2909 node.send_packet(&dest, b"hello").unwrap();
2910 assert_eq!(
2911 store.current_calls.lock().unwrap().as_slice(),
2912 &[dest.hash.0]
2913 );
2914
2915 node.shutdown();
2916 }
2917
2918 #[test]
2919 fn single_payload_uses_stored_ratchet_key_material() {
2920 let ratchet_prv = rns_crypto::x25519::X25519PrivateKey::from_bytes(&[0x42; 32]);
2921 let ratchet_pub = ratchet_prv.public_key().public_bytes();
2922 let store = Arc::new(TestNodeRatchetStore {
2923 entry: storage::RatchetEntry {
2924 ratchet: ratchet_pub,
2925 received_at: time::now(),
2926 },
2927 current_calls: std::sync::Mutex::new(Vec::new()),
2928 });
2929 let ratchet_store: Arc<dyn storage::RatchetStore> = store.clone();
2930 let (tx, _rx) = crate::event::channel();
2931 let node = RnsNode {
2932 tx,
2933 driver_handle: None,
2934 verify_handle: None,
2935 verify_shutdown: Arc::new(AtomicBool::new(false)),
2936 rpc_server: None,
2937 tick_interval_ms: Arc::new(AtomicU64::new(1000)),
2938 probe_server: None,
2939 known_destinations_path: None,
2940 ratchet_store: Some(ratchet_store),
2941 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
2942 };
2943
2944 let mut rng = OsRng;
2945 let remote_identity = Identity::new(&mut rng);
2946 let public_key = remote_identity.get_public_key().unwrap();
2947 let announced = crate::destination::AnnouncedIdentity {
2948 dest_hash: rns_core::types::DestHash([0x33; 16]),
2949 identity_hash: rns_core::types::IdentityHash(*remote_identity.hash()),
2950 public_key,
2951 app_data: None,
2952 hops: 1,
2953 received_at: time::now(),
2954 receiving_interface: rns_core::transport::types::InterfaceId(0),
2955 rssi: Some(-100),
2956 snr: Some(10.5),
2957 };
2958 let dest = crate::destination::Destination::single_out("test", &["ratchet"], &announced);
2959 let payload = node
2960 .encrypt_single_payload(&dest, b"ratchet message")
2961 .unwrap();
2962
2963 assert_eq!(
2964 store.current_calls.lock().unwrap().as_slice(),
2965 &[dest.hash.0]
2966 );
2967 assert!(remote_identity.decrypt(&payload).is_err());
2968
2969 let peer_pub_bytes: [u8; 32] = payload[..32].try_into().unwrap();
2970 let peer_pub = rns_crypto::x25519::X25519PublicKey::from_bytes(&peer_pub_bytes);
2971 let shared_key = ratchet_prv.exchange(&peer_pub);
2972 let derived_key = rns_crypto::hkdf::hkdf(
2973 rns_crypto::identity::DERIVED_KEY_LENGTH,
2974 &shared_key,
2975 Some(remote_identity.hash()),
2976 None,
2977 )
2978 .unwrap();
2979 let token = rns_crypto::token::Token::new(&derived_key).unwrap();
2980 let plaintext = token.decrypt(&payload[32..]).unwrap();
2981 assert_eq!(plaintext, b"ratchet message");
2982 }
2983
2984 #[test]
2985 fn tcp_client_interface_is_not_discoverable_without_kiss_framing() {
2986 let mut params = std::collections::HashMap::new();
2987 params.insert("discoverable".to_string(), "yes".to_string());
2988 params.insert(
2989 "discovery_name".to_string(),
2990 "invalid-tcp-client".to_string(),
2991 );
2992 params.insert("reachable_on".to_string(), "example.com".to_string());
2993 params.insert("target_port".to_string(), "4242".to_string());
2994
2995 let discovery =
2996 super::extract_discovery_config("tcp-client", "TCPClientInterface", ¶ms);
2997
2998 assert!(
2999 discovery.is_none(),
3000 "TCPClientInterface discovery must be rejected unless KISS framing is supported"
3001 );
3002 }
3003
3004 #[test]
3005 fn ingress_control_config_defaults_by_interface_type() {
3006 let params = std::collections::HashMap::new();
3007
3008 let tcp = super::parse_ingress_control_config("TCPServerInterface", ¶ms).unwrap();
3009 assert!(tcp.enabled);
3010 assert_eq!(
3011 tcp.max_held_announces,
3012 rns_core::constants::IC_MAX_HELD_ANNOUNCES
3013 );
3014 assert_eq!(tcp.burst_hold, rns_core::constants::IC_BURST_HOLD);
3015
3016 let pipe = super::parse_ingress_control_config("PipeInterface", ¶ms).unwrap();
3017 assert!(!pipe.enabled);
3018 assert_eq!(
3019 pipe.held_release_interval,
3020 rns_core::constants::IC_HELD_RELEASE_INTERVAL
3021 );
3022 }
3023
3024 #[test]
3025 fn ingress_control_config_parses_python_ic_keys() {
3026 let mut params = std::collections::HashMap::new();
3027 params.insert("ingress_control".to_string(), "No".to_string());
3028 params.insert("ic_max_held_announces".to_string(), "17".to_string());
3029 params.insert("ic_burst_hold".to_string(), "1.5".to_string());
3030 params.insert("ic_burst_freq_new".to_string(), "2.5".to_string());
3031 params.insert("ic_burst_freq".to_string(), "3.5".to_string());
3032 params.insert("ic_pr_burst_freq_new".to_string(), "3.25".to_string());
3033 params.insert("ic_pr_burst_freq".to_string(), "8.25".to_string());
3034 params.insert("egress_control".to_string(), "Yes".to_string());
3035 params.insert("ec_pr_freq".to_string(), "5.25".to_string());
3036 params.insert("ic_new_time".to_string(), "4.5".to_string());
3037 params.insert("ic_burst_penalty".to_string(), "5.5".to_string());
3038 params.insert("ic_held_release_interval".to_string(), "6.5".to_string());
3039
3040 let config = super::parse_ingress_control_config("TCPServerInterface", ¶ms).unwrap();
3041
3042 assert!(!config.enabled);
3043 assert_eq!(config.max_held_announces, 17);
3044 assert_eq!(config.burst_hold, 1.5);
3045 assert_eq!(config.burst_freq_new, 2.5);
3046 assert_eq!(config.burst_freq, 3.5);
3047 assert_eq!(config.pr_burst_freq_new, 3.25);
3048 assert_eq!(config.pr_burst_freq, 8.25);
3049 assert!(config.egress_enabled);
3050 assert_eq!(config.egress_pr_freq, 5.25);
3051 assert_eq!(config.new_time, 4.5);
3052 assert_eq!(config.burst_penalty, 5.5);
3053 assert_eq!(config.held_release_interval, 6.5);
3054 }
3055
3056 #[test]
3057 fn ingress_control_config_rejects_invalid_values() {
3058 let mut params = std::collections::HashMap::new();
3059 params.insert("ic_burst_hold".to_string(), "-1".to_string());
3060
3061 let err = super::parse_ingress_control_config("TCPServerInterface", ¶ms).unwrap_err();
3062
3063 assert!(err.contains("ic_burst_hold"));
3064 }
3065
3066 #[test]
3067 fn start_and_shutdown() {
3068 let node = RnsNode::start(
3069 NodeConfig {
3070 panic_on_interface_error: false,
3071 transport_enabled: false,
3072 static_transport_identity: false,
3073 local_hops_delta: false,
3074 identity: None,
3075 interfaces: vec![],
3076 share_instance: false,
3077 instance_name: "default".into(),
3078 shared_instance_port: 37428,
3079 rpc_port: 0,
3080 cache_dir: None,
3081 ratchet_store: None,
3082 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
3083 management: Default::default(),
3084 probe_port: None,
3085 probe_addrs: vec![],
3086 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
3087 device: None,
3088 hooks: Vec::new(),
3089 discover_interfaces: false,
3090 discovery_required_value: None,
3091 respond_to_probes: false,
3092 prefer_shorter_path: false,
3093 max_paths_per_destination: 1,
3094 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
3095 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
3096 max_path_destinations: usize::MAX,
3097 max_tunnel_destinations_total: usize::MAX,
3098 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
3099 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
3100 announce_table_ttl: Duration::from_secs(
3101 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
3102 ),
3103 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
3104 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
3105 interface_writer_queue_capacity:
3106 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
3107 announce_rate_defaults: AnnounceRateDefaults::default(),
3108 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
3109 ),
3110 #[cfg(feature = "iface-backbone")]
3111 backbone_peer_pool: None,
3112 announce_sig_cache_enabled: true,
3113 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
3114 announce_sig_cache_ttl: Duration::from_secs(
3115 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
3116 ),
3117 registry: None,
3118 #[cfg(feature = "hooks")]
3119 provider_bridge: None,
3120 },
3121 Box::new(NoopCallbacks),
3122 )
3123 .unwrap();
3124 node.shutdown();
3125 }
3126
3127 #[test]
3128 fn known_destinations_persist_across_restart() {
3129 let dir = tempdir().unwrap();
3130 let dest_hash = [0x91; 16];
3131 let identity = Identity::new(&mut OsRng);
3132 let last_used_at = 77.0;
3133 let receiving_interface = rns_core::transport::types::InterfaceId(42);
3134
3135 let node = RnsNode::from_config(Some(dir.path()), Box::new(NoopCallbacks)).unwrap();
3136 let (response_tx, response_rx) = std::sync::mpsc::channel();
3137 node.event_sender()
3138 .send(crate::event::Event::Query(
3139 QueryRequest::RestoreKnownDestination(crate::event::KnownDestinationEntry {
3140 dest_hash,
3141 identity_hash: *identity.hash(),
3142 public_key: identity.get_public_key().unwrap(),
3143 app_data: Some(b"persisted".to_vec()),
3144 hops: 2,
3145 received_at: 55.0,
3146 receiving_interface,
3147 was_used: true,
3148 last_used_at: Some(last_used_at),
3149 retained: true,
3150 }),
3151 response_tx,
3152 ))
3153 .unwrap();
3154 assert!(matches!(
3155 response_rx.recv().unwrap(),
3156 QueryResponse::RestoreKnownDestination(true)
3157 ));
3158 node.shutdown();
3159
3160 let restarted = RnsNode::from_config(Some(dir.path()), Box::new(NoopCallbacks)).unwrap();
3161 let entries = restarted.known_destinations().unwrap();
3162 let entry = entries
3163 .iter()
3164 .find(|entry| entry.dest_hash == dest_hash)
3165 .expect("reloaded destination should appear in lifecycle listing");
3166 assert!(entry.retained);
3167 assert!(entry.was_used);
3168 assert_eq!(entry.hops, 2);
3169 assert_eq!(entry.receiving_interface, receiving_interface);
3170 assert_eq!(entry.last_used_at, Some(last_used_at));
3171
3172 let recalled = restarted
3173 .recall_identity(&rns_core::types::DestHash(dest_hash))
3174 .unwrap()
3175 .expect("known destination should reload from storage");
3176 assert_eq!(recalled.identity_hash.0, *identity.hash());
3177 assert_eq!(recalled.app_data, Some(b"persisted".to_vec()));
3178 restarted.shutdown();
3179 }
3180
3181 #[test]
3182 fn from_config_cleans_persistent_ratchets_after_loading_known_destinations() {
3183 fn hex(bytes: &[u8]) -> String {
3184 bytes.iter().map(|byte| format!("{:02x}", byte)).collect()
3185 }
3186
3187 let dir = tempdir().unwrap();
3188 fs::write(
3189 dir.path().join("config"),
3190 "[reticulum]\nenable_transport = False\nshare_instance = False\nratchet_expiry = 300\n",
3191 )
3192 .unwrap();
3193 let paths = storage::ensure_storage_dirs(dir.path()).unwrap();
3194 let known_live = [0x01; 16];
3195 let known_expired = [0x02; 16];
3196 let unknown = [0x03; 16];
3197 let known_corrupt = [0x04; 16];
3198 let now = time::now();
3199
3200 let identity = Identity::new(&mut OsRng);
3201 let known = std::collections::HashMap::from([
3202 (
3203 known_live,
3204 storage::KnownDestination {
3205 identity_hash: *identity.hash(),
3206 public_key: identity.get_public_key().unwrap(),
3207 app_data: None,
3208 hops: 1,
3209 received_at: now,
3210 receiving_interface: 0,
3211 was_used: false,
3212 last_used_at: None,
3213 retained: false,
3214 },
3215 ),
3216 (
3217 known_expired,
3218 storage::KnownDestination {
3219 identity_hash: *identity.hash(),
3220 public_key: identity.get_public_key().unwrap(),
3221 app_data: None,
3222 hops: 1,
3223 received_at: now,
3224 receiving_interface: 0,
3225 was_used: false,
3226 last_used_at: None,
3227 retained: false,
3228 },
3229 ),
3230 (
3231 known_corrupt,
3232 storage::KnownDestination {
3233 identity_hash: *identity.hash(),
3234 public_key: identity.get_public_key().unwrap(),
3235 app_data: None,
3236 hops: 1,
3237 received_at: now,
3238 receiving_interface: 0,
3239 was_used: false,
3240 last_used_at: None,
3241 retained: false,
3242 },
3243 ),
3244 ]);
3245 storage::save_known_destinations(&known, &paths.storage.join("known_destinations"))
3246 .unwrap();
3247
3248 let store = storage::FsRatchetStore::new(paths.ratchets.clone());
3249 store
3250 .remember(
3251 known_live,
3252 storage::RatchetEntry {
3253 ratchet: [0x11; 32],
3254 received_at: now,
3255 },
3256 )
3257 .unwrap();
3258 store
3259 .remember(
3260 known_expired,
3261 storage::RatchetEntry {
3262 ratchet: [0x22; 32],
3263 received_at: now - 1000.0,
3264 },
3265 )
3266 .unwrap();
3267 store
3268 .remember(
3269 unknown,
3270 storage::RatchetEntry {
3271 ratchet: [0x33; 32],
3272 received_at: now,
3273 },
3274 )
3275 .unwrap();
3276 fs::write(paths.ratchets.join(hex(&known_corrupt)), b"not msgpack").unwrap();
3277 fs::write(paths.ratchets.join("0102.out"), b"temp").unwrap();
3278
3279 let node = RnsNode::from_config(Some(dir.path()), Box::new(NoopCallbacks)).unwrap();
3280
3281 assert!(paths.ratchets.join(hex(&known_live)).exists());
3282 assert!(!paths.ratchets.join(hex(&known_expired)).exists());
3283 assert!(!paths.ratchets.join(hex(&unknown)).exists());
3284 assert!(!paths.ratchets.join(hex(&known_corrupt)).exists());
3285 assert!(!paths.ratchets.join("0102.out").exists());
3286
3287 node.shutdown();
3288 }
3289
3290 #[test]
3291 fn start_with_identity() {
3292 let identity = Identity::new(&mut OsRng);
3293 let hash = *identity.hash();
3294 let node = RnsNode::start(
3295 NodeConfig {
3296 panic_on_interface_error: false,
3297 transport_enabled: true,
3298 static_transport_identity: false,
3299 local_hops_delta: false,
3300 identity: Some(identity),
3301 interfaces: vec![],
3302 share_instance: false,
3303 instance_name: "default".into(),
3304 shared_instance_port: 37428,
3305 rpc_port: 0,
3306 cache_dir: None,
3307 ratchet_store: None,
3308 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
3309 management: Default::default(),
3310 probe_port: None,
3311 probe_addrs: vec![],
3312 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
3313 device: None,
3314 hooks: Vec::new(),
3315 discover_interfaces: false,
3316 discovery_required_value: None,
3317 respond_to_probes: false,
3318 prefer_shorter_path: false,
3319 max_paths_per_destination: 1,
3320 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
3321 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
3322 max_path_destinations: usize::MAX,
3323 max_tunnel_destinations_total: usize::MAX,
3324 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
3325 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
3326 announce_table_ttl: Duration::from_secs(
3327 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
3328 ),
3329 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
3330 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
3331 interface_writer_queue_capacity:
3332 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
3333 announce_rate_defaults: AnnounceRateDefaults::default(),
3334 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
3335 ),
3336 #[cfg(feature = "iface-backbone")]
3337 backbone_peer_pool: None,
3338 announce_sig_cache_enabled: true,
3339 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
3340 announce_sig_cache_ttl: Duration::from_secs(
3341 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
3342 ),
3343 registry: None,
3344 #[cfg(feature = "hooks")]
3345 provider_bridge: None,
3346 },
3347 Box::new(NoopCallbacks),
3348 )
3349 .unwrap();
3350 let _ = hash;
3352 node.shutdown();
3353 }
3354
3355 #[test]
3356 fn start_generates_identity() {
3357 let node = RnsNode::start(
3358 NodeConfig {
3359 panic_on_interface_error: false,
3360 transport_enabled: false,
3361 static_transport_identity: false,
3362 local_hops_delta: false,
3363 identity: None,
3364 interfaces: vec![],
3365 share_instance: false,
3366 instance_name: "default".into(),
3367 shared_instance_port: 37428,
3368 rpc_port: 0,
3369 cache_dir: None,
3370 ratchet_store: None,
3371 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
3372 management: Default::default(),
3373 probe_port: None,
3374 probe_addrs: vec![],
3375 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
3376 device: None,
3377 hooks: Vec::new(),
3378 discover_interfaces: false,
3379 discovery_required_value: None,
3380 respond_to_probes: false,
3381 prefer_shorter_path: false,
3382 max_paths_per_destination: 1,
3383 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
3384 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
3385 max_path_destinations: usize::MAX,
3386 max_tunnel_destinations_total: usize::MAX,
3387 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
3388 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
3389 announce_table_ttl: Duration::from_secs(
3390 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
3391 ),
3392 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
3393 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
3394 interface_writer_queue_capacity:
3395 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
3396 announce_rate_defaults: AnnounceRateDefaults::default(),
3397 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
3398 ),
3399 #[cfg(feature = "iface-backbone")]
3400 backbone_peer_pool: None,
3401 announce_sig_cache_enabled: true,
3402 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
3403 announce_sig_cache_ttl: Duration::from_secs(
3404 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
3405 ),
3406 registry: None,
3407 #[cfg(feature = "hooks")]
3408 provider_bridge: None,
3409 },
3410 Box::new(NoopCallbacks),
3411 )
3412 .unwrap();
3413 node.shutdown();
3415 }
3416
3417 #[test]
3418 fn from_config_creates_identity() {
3419 let dir = std::env::temp_dir().join(format!("rns-test-fc-{}", std::process::id()));
3420 let _ = fs::remove_dir_all(&dir);
3421 fs::create_dir_all(&dir).unwrap();
3422
3423 fs::write(
3425 dir.join("config"),
3426 "[reticulum]\nenable_transport = False\n",
3427 )
3428 .unwrap();
3429
3430 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
3431
3432 assert!(dir.join("storage/identities/identity").exists());
3434
3435 node.shutdown();
3436 let _ = fs::remove_dir_all(&dir);
3437 }
3438
3439 #[test]
3440 fn from_config_loads_identity() {
3441 let dir = std::env::temp_dir().join(format!("rns-test-fl-{}", std::process::id()));
3442 let _ = fs::remove_dir_all(&dir);
3443 fs::create_dir_all(dir.join("storage/identities")).unwrap();
3444
3445 let identity = Identity::new(&mut OsRng);
3447 let hash = *identity.hash();
3448 storage::save_identity(&identity, &dir.join("storage/identities/identity")).unwrap();
3449
3450 fs::write(
3451 dir.join("config"),
3452 "[reticulum]\nenable_transport = False\n",
3453 )
3454 .unwrap();
3455
3456 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
3457
3458 let loaded = storage::load_identity(&dir.join("storage/identities/identity")).unwrap();
3460 assert_eq!(*loaded.hash(), hash);
3461
3462 node.shutdown();
3463 let _ = fs::remove_dir_all(&dir);
3464 }
3465
3466 #[test]
3467 fn from_config_tcp_server() {
3468 let dir = std::env::temp_dir().join(format!("rns-test-fts-{}", std::process::id()));
3469 let _ = fs::remove_dir_all(&dir);
3470 fs::create_dir_all(&dir).unwrap();
3471
3472 let port = std::net::TcpListener::bind("127.0.0.1:0")
3474 .unwrap()
3475 .local_addr()
3476 .unwrap()
3477 .port();
3478
3479 let config = format!(
3480 r#"
3481[reticulum]
3482enable_transport = False
3483
3484[interfaces]
3485 [[Test TCP Server]]
3486 type = TCPServerInterface
3487 listen_ip = 127.0.0.1
3488 listen_port = {}
3489"#,
3490 port
3491 );
3492
3493 fs::write(dir.join("config"), config).unwrap();
3494
3495 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
3496
3497 thread::sleep(Duration::from_millis(100));
3499
3500 let _client = std::net::TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
3502
3503 node.shutdown();
3504 let _ = fs::remove_dir_all(&dir);
3505 }
3506
3507 #[test]
3508 fn from_config_starts_rpc_when_share_instance_enabled() {
3509 let dir = std::env::temp_dir().join(format!("rns-test-rpc-{}", std::process::id()));
3510 let _ = fs::remove_dir_all(&dir);
3511 fs::create_dir_all(&dir).unwrap();
3512
3513 let rpc_port = std::net::TcpListener::bind("127.0.0.1:0")
3514 .unwrap()
3515 .local_addr()
3516 .unwrap()
3517 .port();
3518
3519 let config = format!(
3520 r#"
3521[reticulum]
3522enable_transport = False
3523share_instance = Yes
3524instance_control_port = {}
3525
3526[interfaces]
3527"#,
3528 rpc_port
3529 );
3530
3531 fs::write(dir.join("config"), config).unwrap();
3532
3533 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
3534
3535 thread::sleep(Duration::from_millis(100));
3536
3537 let _client = std::net::TcpStream::connect(format!("127.0.0.1:{}", rpc_port)).unwrap();
3538
3539 node.shutdown();
3540 let _ = fs::remove_dir_all(&dir);
3541 }
3542
3543 #[test]
3544 fn from_config_starts_rpc_when_transport_enabled() {
3545 let dir =
3546 std::env::temp_dir().join(format!("rns-test-rpc-transport-{}", std::process::id()));
3547 let _ = fs::remove_dir_all(&dir);
3548 fs::create_dir_all(&dir).unwrap();
3549
3550 let rpc_port = std::net::TcpListener::bind("127.0.0.1:0")
3551 .unwrap()
3552 .local_addr()
3553 .unwrap()
3554 .port();
3555
3556 let config = format!(
3557 r#"
3558[reticulum]
3559enable_transport = True
3560share_instance = Yes
3561instance_control_port = {}
3562
3563[interfaces]
3564"#,
3565 rpc_port
3566 );
3567
3568 fs::write(dir.join("config"), config).unwrap();
3569
3570 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
3571
3572 thread::sleep(Duration::from_millis(100));
3573
3574 let _client = std::net::TcpStream::connect(format!("127.0.0.1:{}", rpc_port)).unwrap();
3575
3576 node.shutdown();
3577 let _ = fs::remove_dir_all(&dir);
3578 }
3579
3580 #[test]
3581 fn from_config_starts_rpc_when_tcp_client_is_unreachable() {
3582 let dir =
3583 std::env::temp_dir().join(format!("rns-test-rpc-unreachable-{}", std::process::id()));
3584 let _ = fs::remove_dir_all(&dir);
3585 fs::create_dir_all(&dir).unwrap();
3586
3587 let rpc_port = std::net::TcpListener::bind("127.0.0.1:0")
3588 .unwrap()
3589 .local_addr()
3590 .unwrap()
3591 .port();
3592 let unreachable_port = std::net::TcpListener::bind("127.0.0.1:0")
3593 .unwrap()
3594 .local_addr()
3595 .unwrap()
3596 .port();
3597
3598 let config = format!(
3599 r#"
3600[reticulum]
3601enable_transport = True
3602share_instance = Yes
3603instance_control_port = {}
3604
3605[interfaces]
3606 [[Unreachable Upstream]]
3607 type = TCPClientInterface
3608 target_host = 127.0.0.1
3609 target_port = {}
3610"#,
3611 rpc_port, unreachable_port
3612 );
3613
3614 fs::write(dir.join("config"), config).unwrap();
3615
3616 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
3617
3618 thread::sleep(Duration::from_millis(100));
3619
3620 let _client = std::net::TcpStream::connect(format!("127.0.0.1:{}", rpc_port)).unwrap();
3621
3622 node.shutdown();
3623 let _ = fs::remove_dir_all(&dir);
3624 }
3625
3626 #[test]
3627 fn test_parse_interface_mode() {
3628 use rns_core::constants::*;
3629
3630 assert_eq!(parse_interface_mode("full"), MODE_FULL);
3631 assert_eq!(parse_interface_mode("Full"), MODE_FULL);
3632 assert_eq!(parse_interface_mode("access_point"), MODE_ACCESS_POINT);
3633 assert_eq!(parse_interface_mode("accesspoint"), MODE_ACCESS_POINT);
3634 assert_eq!(parse_interface_mode("ap"), MODE_ACCESS_POINT);
3635 assert_eq!(parse_interface_mode("AP"), MODE_ACCESS_POINT);
3636 assert_eq!(parse_interface_mode("pointtopoint"), MODE_POINT_TO_POINT);
3637 assert_eq!(parse_interface_mode("ptp"), MODE_POINT_TO_POINT);
3638 assert_eq!(parse_interface_mode("roaming"), MODE_ROAMING);
3639 assert_eq!(parse_interface_mode("boundary"), MODE_BOUNDARY);
3640 assert_eq!(parse_interface_mode("gateway"), MODE_GATEWAY);
3641 assert_eq!(parse_interface_mode("gw"), MODE_GATEWAY);
3642 assert_eq!(parse_interface_mode("internal"), MODE_INTERNAL);
3643 assert_eq!(parse_interface_mode("Internal"), MODE_INTERNAL);
3644 assert_eq!(parse_interface_mode("invalid"), MODE_FULL);
3646 }
3647
3648 #[test]
3649 fn to_node_config_serial() {
3650 let dir = std::env::temp_dir().join(format!("rns-test-serial-{}", std::process::id()));
3654 let _ = fs::remove_dir_all(&dir);
3655 fs::create_dir_all(&dir).unwrap();
3656
3657 let config = r#"
3658[reticulum]
3659enable_transport = False
3660
3661[interfaces]
3662 [[Test Serial Port]]
3663 type = SerialInterface
3664 port = /dev/nonexistent_rns_test_serial
3665 speed = 115200
3666 databits = 8
3667 parity = E
3668 stopbits = 1
3669 interface_mode = ptp
3670 networkname = testnet
3671"#;
3672 fs::write(dir.join("config"), config).unwrap();
3673
3674 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks))
3676 .expect("Config should parse; interface failure is non-fatal");
3677 node.shutdown();
3678
3679 let _ = fs::remove_dir_all(&dir);
3680 }
3681
3682 #[test]
3683 fn to_node_config_kiss() {
3684 let dir = std::env::temp_dir().join(format!("rns-test-kiss-{}", std::process::id()));
3686 let _ = fs::remove_dir_all(&dir);
3687 fs::create_dir_all(&dir).unwrap();
3688
3689 let config = r#"
3690[reticulum]
3691enable_transport = False
3692
3693[interfaces]
3694 [[Test KISS TNC]]
3695 type = KISSInterface
3696 port = /dev/nonexistent_rns_test_kiss
3697 speed = 9600
3698 preamble = 500
3699 txtail = 30
3700 persistence = 128
3701 slottime = 40
3702 flow_control = True
3703 id_interval = 600
3704 id_callsign = TEST0
3705 interface_mode = full
3706 passphrase = secretkey
3707"#;
3708 fs::write(dir.join("config"), config).unwrap();
3709
3710 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks))
3712 .expect("Config should parse; interface failure is non-fatal");
3713 node.shutdown();
3714
3715 let _ = fs::remove_dir_all(&dir);
3716 }
3717
3718 #[test]
3719 fn config_parser_accepts_recursive_prs_interface_option() {
3720 let config = r#"
3721[reticulum]
3722enable_transport = True
3723
3724[interfaces]
3725 [[Recursive Client]]
3726 type = TCPClientInterface
3727 target_host = 127.0.0.1
3728 target_port = 4242
3729 interface_mode = full
3730 recursive_prs = yes
3731
3732 [[Default Client]]
3733 type = TCPClientInterface
3734 target_host = 127.0.0.1
3735 target_port = 4243
3736 interface_mode = gateway
3737"#;
3738
3739 let parsed = config::parse(config).unwrap();
3740
3741 assert_eq!(parsed.interfaces.len(), 2);
3742 assert_eq!(parsed.interfaces[0].name, "Recursive Client");
3743 assert_eq!(parsed.interfaces[0].mode, "full");
3744 assert_eq!(
3745 parsed.interfaces[0]
3746 .params
3747 .get("recursive_prs")
3748 .map(String::as_str),
3749 Some("yes")
3750 );
3751 assert_eq!(parsed.interfaces[1].name, "Default Client");
3752 assert_eq!(parsed.interfaces[1].mode, "gateway");
3753 assert!(!parsed.interfaces[1].params.contains_key("recursive_prs"));
3754 }
3755
3756 #[test]
3757 fn config_parser_accepts_announces_from_internal_interface_option() {
3758 let config = r#"
3759[reticulum]
3760enable_transport = True
3761
3762[interfaces]
3763 [[Filtered Client]]
3764 type = TCPClientInterface
3765 target_host = 127.0.0.1
3766 target_port = 4242
3767 interface_mode = gateway
3768 announces_from_internal = no
3769
3770 [[Default Client]]
3771 type = TCPClientInterface
3772 target_host = 127.0.0.1
3773 target_port = 4243
3774 interface_mode = full
3775"#;
3776
3777 let parsed = config::parse(config).unwrap();
3778
3779 assert_eq!(parsed.interfaces.len(), 2);
3780 assert_eq!(parsed.interfaces[0].name, "Filtered Client");
3781 assert_eq!(parsed.interfaces[0].mode, "gateway");
3782 assert_eq!(
3783 parsed.interfaces[0]
3784 .params
3785 .get("announces_from_internal")
3786 .map(String::as_str),
3787 Some("no")
3788 );
3789 assert_eq!(parsed.interfaces[1].name, "Default Client");
3790 assert_eq!(parsed.interfaces[1].mode, "full");
3791 assert!(!parsed.interfaces[1]
3792 .params
3793 .contains_key("announces_from_internal"));
3794 }
3795
3796 #[test]
3797 fn config_parser_accepts_local_hops_delta_option() {
3798 let cfg = r#"
3799[reticulum]
3800local_hops_delta = yes
3801"#;
3802 let parsed = config::parse(cfg).unwrap();
3803 assert!(parsed.reticulum.local_hops_delta);
3804 }
3805
3806 #[test]
3807 fn config_parser_accepts_static_transport_identity_option() {
3808 let config = r#"
3809[reticulum]
3810enable_transport = false
3811static_transport_identity = yes
3812"#;
3813
3814 let parsed = config::parse(config).unwrap();
3815
3816 assert!(!parsed.reticulum.enable_transport);
3817 assert!(parsed.reticulum.static_transport_identity);
3818 }
3819
3820 #[test]
3821 fn non_transport_node_uses_ephemeral_transport_identity_by_default() {
3822 let identity = Identity::new(&mut rns_crypto::FixedRng::new(&[0x41; 32]));
3823 let stored_hash = *identity.hash();
3824 let node = RnsNode::start(
3825 NodeConfig {
3826 transport_enabled: false,
3827 static_transport_identity: false,
3828 local_hops_delta: false,
3829 identity: Some(identity),
3830 share_instance: false,
3831 ..Default::default()
3832 },
3833 Box::new(NoopCallbacks),
3834 )
3835 .unwrap();
3836
3837 let response = node.query(QueryRequest::TransportIdentity).unwrap();
3838 let QueryResponse::TransportIdentity(Some(hash)) = response else {
3839 panic!("transport identity query returned {response:?}");
3840 };
3841 assert_ne!(hash, stored_hash);
3842
3843 node.shutdown();
3844 }
3845
3846 #[test]
3847 fn non_transport_node_can_use_static_transport_identity() {
3848 let identity = Identity::new(&mut rns_crypto::FixedRng::new(&[0x42; 32]));
3849 let stored_hash = *identity.hash();
3850 let node = RnsNode::start(
3851 NodeConfig {
3852 transport_enabled: false,
3853 static_transport_identity: true,
3854 local_hops_delta: false,
3855 identity: Some(identity),
3856 share_instance: false,
3857 ..Default::default()
3858 },
3859 Box::new(NoopCallbacks),
3860 )
3861 .unwrap();
3862
3863 let response = node.query(QueryRequest::TransportIdentity).unwrap();
3864 let QueryResponse::TransportIdentity(Some(hash)) = response else {
3865 panic!("transport identity query returned {response:?}");
3866 };
3867 assert_eq!(hash, stored_hash);
3868
3869 node.shutdown();
3870 }
3871
3872 #[test]
3873 fn test_extract_ifac_config() {
3874 use std::collections::HashMap;
3875
3876 let params: HashMap<String, String> = HashMap::new();
3878 assert!(extract_ifac_config(¶ms, 16).is_none());
3879
3880 let mut params = HashMap::new();
3882 params.insert("networkname".into(), "testnet".into());
3883 let ifac = extract_ifac_config(¶ms, 16).unwrap();
3884 assert_eq!(ifac.netname.as_deref(), Some("testnet"));
3885 assert!(ifac.netkey.is_none());
3886 assert_eq!(ifac.size, 16);
3887
3888 let mut params = HashMap::new();
3890 params.insert("passphrase".into(), "secret".into());
3891 params.insert("ifac_size".into(), "64".into()); let ifac = extract_ifac_config(¶ms, 16).unwrap();
3893 assert!(ifac.netname.is_none());
3894 assert_eq!(ifac.netkey.as_deref(), Some("secret"));
3895 assert_eq!(ifac.size, 8);
3896
3897 let mut params = HashMap::new();
3899 params.insert("network_name".into(), "mynet".into());
3900 params.insert("pass_phrase".into(), "mykey".into());
3901 let ifac = extract_ifac_config(¶ms, 8).unwrap();
3902 assert_eq!(ifac.netname.as_deref(), Some("mynet"));
3903 assert_eq!(ifac.netkey.as_deref(), Some("mykey"));
3904 assert_eq!(ifac.size, 8);
3905 }
3906
3907 #[test]
3908 fn to_node_config_rnode() {
3909 let dir = std::env::temp_dir().join(format!("rns-test-rnode-{}", std::process::id()));
3912 let _ = fs::remove_dir_all(&dir);
3913 fs::create_dir_all(&dir).unwrap();
3914
3915 let config = r#"
3916[reticulum]
3917enable_transport = False
3918
3919[interfaces]
3920 [[Test RNode]]
3921 type = RNodeInterface
3922 port = /dev/nonexistent_rns_test_rnode
3923 frequency = 867200000
3924 bandwidth = 125000
3925 txpower = 7
3926 spreadingfactor = 8
3927 codingrate = 5
3928 flow_control = True
3929 st_alock = 5.0
3930 lt_alock = 2.5
3931 interface_mode = full
3932 networkname = testnet
3933"#;
3934 fs::write(dir.join("config"), config).unwrap();
3935
3936 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks))
3938 .expect("Config should parse; interface failure is non-fatal");
3939 node.shutdown();
3940
3941 let _ = fs::remove_dir_all(&dir);
3942 }
3943
3944 #[test]
3945 fn to_node_config_pipe() {
3946 let dir = std::env::temp_dir().join(format!("rns-test-pipe-{}", std::process::id()));
3949 let _ = fs::remove_dir_all(&dir);
3950 fs::create_dir_all(&dir).unwrap();
3951
3952 let config = r#"
3953[reticulum]
3954enable_transport = False
3955
3956[interfaces]
3957 [[Test Pipe]]
3958 type = PipeInterface
3959 command = cat
3960 respawn_delay = 5000
3961 interface_mode = full
3962"#;
3963 fs::write(dir.join("config"), config).unwrap();
3964
3965 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
3966 node.shutdown();
3968
3969 let _ = fs::remove_dir_all(&dir);
3970 }
3971
3972 #[test]
3973 fn to_node_config_backbone() {
3974 let dir = std::env::temp_dir().join(format!("rns-test-backbone-{}", std::process::id()));
3976 let _ = fs::remove_dir_all(&dir);
3977 fs::create_dir_all(&dir).unwrap();
3978
3979 let port = std::net::TcpListener::bind("127.0.0.1:0")
3980 .unwrap()
3981 .local_addr()
3982 .unwrap()
3983 .port();
3984
3985 let config = format!(
3986 r#"
3987[reticulum]
3988enable_transport = False
3989
3990[interfaces]
3991 [[Test Backbone]]
3992 type = BackboneInterface
3993 listen_ip = 127.0.0.1
3994 listen_port = {}
3995 interface_mode = full
3996"#,
3997 port
3998 );
3999
4000 fs::write(dir.join("config"), config).unwrap();
4001
4002 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
4003
4004 thread::sleep(Duration::from_millis(100));
4006
4007 {
4009 let _client = std::net::TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
4010 }
4012
4013 thread::sleep(Duration::from_millis(50));
4015
4016 node.shutdown();
4017 let _ = fs::remove_dir_all(&dir);
4018 }
4019
4020 #[test]
4021 fn rnode_config_defaults() {
4022 use crate::interface::rnode::{RNodeConfig, RNodeSubConfig};
4023
4024 let config = RNodeConfig::default();
4025 assert_eq!(config.speed, 115200);
4026 assert!(config.subinterfaces.is_empty());
4027 assert!(config.id_interval.is_none());
4028 assert!(config.id_callsign.is_none());
4029
4030 let sub = RNodeSubConfig {
4031 name: "test".into(),
4032 frequency: 868_000_000,
4033 bandwidth: 125_000,
4034 txpower: 7,
4035 spreading_factor: 8,
4036 coding_rate: 5,
4037 flow_control: false,
4038 st_alock: None,
4039 lt_alock: None,
4040 };
4041 assert_eq!(sub.frequency, 868_000_000);
4042 assert_eq!(sub.bandwidth, 125_000);
4043 assert!(!sub.flow_control);
4044 }
4045
4046 #[test]
4051 fn announce_builds_valid_packet() {
4052 let identity = Identity::new(&mut OsRng);
4053 let identity_hash = rns_core::types::IdentityHash(*identity.hash());
4054
4055 let node = RnsNode::start(
4056 NodeConfig {
4057 panic_on_interface_error: false,
4058 transport_enabled: false,
4059 static_transport_identity: false,
4060 local_hops_delta: false,
4061 identity: None,
4062 interfaces: vec![],
4063 share_instance: false,
4064 instance_name: "default".into(),
4065 shared_instance_port: 37428,
4066 rpc_port: 0,
4067 cache_dir: None,
4068 ratchet_store: None,
4069 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
4070 management: Default::default(),
4071 probe_port: None,
4072 probe_addrs: vec![],
4073 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
4074 device: None,
4075 hooks: Vec::new(),
4076 discover_interfaces: false,
4077 discovery_required_value: None,
4078 respond_to_probes: false,
4079 prefer_shorter_path: false,
4080 max_paths_per_destination: 1,
4081 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
4082 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
4083 max_path_destinations: usize::MAX,
4084 max_tunnel_destinations_total: usize::MAX,
4085 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
4086 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
4087 announce_table_ttl: Duration::from_secs(
4088 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
4089 ),
4090 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
4091 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
4092 interface_writer_queue_capacity:
4093 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
4094 announce_rate_defaults: AnnounceRateDefaults::default(),
4095 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
4096 ),
4097 #[cfg(feature = "iface-backbone")]
4098 backbone_peer_pool: None,
4099 announce_sig_cache_enabled: true,
4100 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
4101 announce_sig_cache_ttl: Duration::from_secs(
4102 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
4103 ),
4104 registry: None,
4105 #[cfg(feature = "hooks")]
4106 provider_bridge: None,
4107 },
4108 Box::new(NoopCallbacks),
4109 )
4110 .unwrap();
4111
4112 let dest = crate::destination::Destination::single_in("test", &["echo"], identity_hash);
4113
4114 node.register_destination(dest.hash.0, dest.dest_type.to_wire_constant())
4116 .unwrap();
4117
4118 let result = node.announce(&dest, &identity, Some(b"hello"));
4120 assert!(result.is_ok());
4121
4122 node.shutdown();
4123 }
4124
4125 #[test]
4126 fn has_path_and_hops_to() {
4127 let node = RnsNode::start(
4128 NodeConfig {
4129 panic_on_interface_error: false,
4130 transport_enabled: false,
4131 static_transport_identity: false,
4132 local_hops_delta: false,
4133 identity: None,
4134 interfaces: vec![],
4135 share_instance: false,
4136 instance_name: "default".into(),
4137 shared_instance_port: 37428,
4138 rpc_port: 0,
4139 cache_dir: None,
4140 ratchet_store: None,
4141 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
4142 management: Default::default(),
4143 probe_port: None,
4144 probe_addrs: vec![],
4145 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
4146 device: None,
4147 hooks: Vec::new(),
4148 discover_interfaces: false,
4149 discovery_required_value: None,
4150 respond_to_probes: false,
4151 prefer_shorter_path: false,
4152 max_paths_per_destination: 1,
4153 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
4154 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
4155 max_path_destinations: usize::MAX,
4156 max_tunnel_destinations_total: usize::MAX,
4157 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
4158 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
4159 announce_table_ttl: Duration::from_secs(
4160 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
4161 ),
4162 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
4163 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
4164 interface_writer_queue_capacity:
4165 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
4166 announce_rate_defaults: AnnounceRateDefaults::default(),
4167 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
4168 ),
4169 #[cfg(feature = "iface-backbone")]
4170 backbone_peer_pool: None,
4171 announce_sig_cache_enabled: true,
4172 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
4173 announce_sig_cache_ttl: Duration::from_secs(
4174 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
4175 ),
4176 registry: None,
4177 #[cfg(feature = "hooks")]
4178 provider_bridge: None,
4179 },
4180 Box::new(NoopCallbacks),
4181 )
4182 .unwrap();
4183
4184 let dh = rns_core::types::DestHash([0xAA; 16]);
4185
4186 assert_eq!(node.has_path(&dh).unwrap(), false);
4188 assert_eq!(node.hops_to(&dh).unwrap(), None);
4189
4190 node.shutdown();
4191 }
4192
4193 #[test]
4194 fn recall_identity_none_when_unknown() {
4195 let node = RnsNode::start(
4196 NodeConfig {
4197 panic_on_interface_error: false,
4198 transport_enabled: false,
4199 static_transport_identity: false,
4200 local_hops_delta: false,
4201 identity: None,
4202 interfaces: vec![],
4203 share_instance: false,
4204 instance_name: "default".into(),
4205 shared_instance_port: 37428,
4206 rpc_port: 0,
4207 cache_dir: None,
4208 ratchet_store: None,
4209 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
4210 management: Default::default(),
4211 probe_port: None,
4212 probe_addrs: vec![],
4213 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
4214 device: None,
4215 hooks: Vec::new(),
4216 discover_interfaces: false,
4217 discovery_required_value: None,
4218 respond_to_probes: false,
4219 prefer_shorter_path: false,
4220 max_paths_per_destination: 1,
4221 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
4222 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
4223 max_path_destinations: usize::MAX,
4224 max_tunnel_destinations_total: usize::MAX,
4225 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
4226 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
4227 announce_table_ttl: Duration::from_secs(
4228 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
4229 ),
4230 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
4231 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
4232 interface_writer_queue_capacity:
4233 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
4234 announce_rate_defaults: AnnounceRateDefaults::default(),
4235 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
4236 ),
4237 #[cfg(feature = "iface-backbone")]
4238 backbone_peer_pool: None,
4239 announce_sig_cache_enabled: true,
4240 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
4241 announce_sig_cache_ttl: Duration::from_secs(
4242 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
4243 ),
4244 registry: None,
4245 #[cfg(feature = "hooks")]
4246 provider_bridge: None,
4247 },
4248 Box::new(NoopCallbacks),
4249 )
4250 .unwrap();
4251
4252 let dh = rns_core::types::DestHash([0xBB; 16]);
4253 assert!(node.recall_identity(&dh).unwrap().is_none());
4254
4255 node.shutdown();
4256 }
4257
4258 #[test]
4259 fn request_path_does_not_crash() {
4260 let node = RnsNode::start(
4261 NodeConfig {
4262 panic_on_interface_error: false,
4263 transport_enabled: false,
4264 static_transport_identity: false,
4265 local_hops_delta: false,
4266 identity: None,
4267 interfaces: vec![],
4268 share_instance: false,
4269 instance_name: "default".into(),
4270 shared_instance_port: 37428,
4271 rpc_port: 0,
4272 cache_dir: None,
4273 ratchet_store: None,
4274 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
4275 management: Default::default(),
4276 probe_port: None,
4277 probe_addrs: vec![],
4278 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
4279 device: None,
4280 hooks: Vec::new(),
4281 discover_interfaces: false,
4282 discovery_required_value: None,
4283 respond_to_probes: false,
4284 prefer_shorter_path: false,
4285 max_paths_per_destination: 1,
4286 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
4287 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
4288 max_path_destinations: usize::MAX,
4289 max_tunnel_destinations_total: usize::MAX,
4290 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
4291 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
4292 announce_table_ttl: Duration::from_secs(
4293 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
4294 ),
4295 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
4296 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
4297 interface_writer_queue_capacity:
4298 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
4299 announce_rate_defaults: AnnounceRateDefaults::default(),
4300 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
4301 ),
4302 #[cfg(feature = "iface-backbone")]
4303 backbone_peer_pool: None,
4304 announce_sig_cache_enabled: true,
4305 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
4306 announce_sig_cache_ttl: Duration::from_secs(
4307 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
4308 ),
4309 registry: None,
4310 #[cfg(feature = "hooks")]
4311 provider_bridge: None,
4312 },
4313 Box::new(NoopCallbacks),
4314 )
4315 .unwrap();
4316
4317 let dh = rns_core::types::DestHash([0xCC; 16]);
4318 assert!(node.request_path(&dh).is_ok());
4319
4320 thread::sleep(Duration::from_millis(50));
4322
4323 node.shutdown();
4324 }
4325
4326 #[test]
4327 fn create_link_returns_error_while_draining() {
4328 let node = RnsNode::start(NodeConfig::default(), Box::new(NoopCallbacks)).unwrap();
4329
4330 node.begin_drain(Duration::from_secs(1)).unwrap();
4331 assert!(node.create_link([0xAB; 16], [0xCD; 32]).is_err());
4332
4333 node.shutdown();
4334 }
4335
4336 #[test]
4337 fn request_path_returns_error_while_draining() {
4338 let node = RnsNode::start(NodeConfig::default(), Box::new(NoopCallbacks)).unwrap();
4339
4340 node.begin_drain(Duration::from_secs(1)).unwrap();
4341 assert!(node
4342 .request_path(&rns_core::types::DestHash([0xAB; 16]))
4343 .is_err());
4344
4345 node.shutdown();
4346 }
4347
4348 #[test]
4353 fn send_packet_returns_error_while_draining() {
4354 let node = RnsNode::start(NodeConfig::default(), Box::new(NoopCallbacks)).unwrap();
4355 let dest = crate::destination::Destination::plain("drain-test", &["send"]);
4356
4357 node.begin_drain(Duration::from_secs(1)).unwrap();
4358 assert!(node.send_packet(&dest, b"hello").is_err());
4359
4360 node.shutdown();
4361 }
4362
4363 #[test]
4364 fn send_packet_plain() {
4365 let node = RnsNode::start(
4366 NodeConfig {
4367 panic_on_interface_error: false,
4368 transport_enabled: false,
4369 static_transport_identity: false,
4370 local_hops_delta: false,
4371 identity: None,
4372 interfaces: vec![],
4373 share_instance: false,
4374 instance_name: "default".into(),
4375 shared_instance_port: 37428,
4376 rpc_port: 0,
4377 cache_dir: None,
4378 ratchet_store: None,
4379 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
4380 management: Default::default(),
4381 probe_port: None,
4382 probe_addrs: vec![],
4383 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
4384 device: None,
4385 hooks: Vec::new(),
4386 discover_interfaces: false,
4387 discovery_required_value: None,
4388 respond_to_probes: false,
4389 prefer_shorter_path: false,
4390 max_paths_per_destination: 1,
4391 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
4392 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
4393 max_path_destinations: usize::MAX,
4394 max_tunnel_destinations_total: usize::MAX,
4395 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
4396 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
4397 announce_table_ttl: Duration::from_secs(
4398 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
4399 ),
4400 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
4401 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
4402 interface_writer_queue_capacity:
4403 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
4404 announce_rate_defaults: AnnounceRateDefaults::default(),
4405 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
4406 ),
4407 #[cfg(feature = "iface-backbone")]
4408 backbone_peer_pool: None,
4409 announce_sig_cache_enabled: true,
4410 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
4411 announce_sig_cache_ttl: Duration::from_secs(
4412 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
4413 ),
4414 registry: None,
4415 #[cfg(feature = "hooks")]
4416 provider_bridge: None,
4417 },
4418 Box::new(NoopCallbacks),
4419 )
4420 .unwrap();
4421
4422 let dest = crate::destination::Destination::plain("test", &["echo"]);
4423 let result = node.send_packet(&dest, b"hello world");
4424 assert!(result.is_ok());
4425
4426 let packet_hash = result.unwrap();
4427 assert_ne!(packet_hash.0, [0u8; 32]);
4429
4430 thread::sleep(Duration::from_millis(50));
4432
4433 node.shutdown();
4434 }
4435
4436 #[test]
4437 fn send_packet_single_requires_public_key() {
4438 let node = RnsNode::start(
4439 NodeConfig {
4440 panic_on_interface_error: false,
4441 transport_enabled: false,
4442 static_transport_identity: false,
4443 local_hops_delta: false,
4444 identity: None,
4445 interfaces: vec![],
4446 share_instance: false,
4447 instance_name: "default".into(),
4448 shared_instance_port: 37428,
4449 rpc_port: 0,
4450 cache_dir: None,
4451 ratchet_store: None,
4452 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
4453 management: Default::default(),
4454 probe_port: None,
4455 probe_addrs: vec![],
4456 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
4457 device: None,
4458 hooks: Vec::new(),
4459 discover_interfaces: false,
4460 discovery_required_value: None,
4461 respond_to_probes: false,
4462 prefer_shorter_path: false,
4463 max_paths_per_destination: 1,
4464 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
4465 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
4466 max_path_destinations: usize::MAX,
4467 max_tunnel_destinations_total: usize::MAX,
4468 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
4469 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
4470 announce_table_ttl: Duration::from_secs(
4471 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
4472 ),
4473 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
4474 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
4475 interface_writer_queue_capacity:
4476 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
4477 announce_rate_defaults: AnnounceRateDefaults::default(),
4478 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
4479 ),
4480 #[cfg(feature = "iface-backbone")]
4481 backbone_peer_pool: None,
4482 announce_sig_cache_enabled: true,
4483 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
4484 announce_sig_cache_ttl: Duration::from_secs(
4485 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
4486 ),
4487 registry: None,
4488 #[cfg(feature = "hooks")]
4489 provider_bridge: None,
4490 },
4491 Box::new(NoopCallbacks),
4492 )
4493 .unwrap();
4494
4495 let dest = crate::destination::Destination::single_in(
4497 "test",
4498 &["echo"],
4499 rns_core::types::IdentityHash([0x42; 16]),
4500 );
4501 let result = node.send_packet(&dest, b"hello");
4502 assert!(result.is_err(), "single_in has no public_key, should fail");
4503
4504 node.shutdown();
4505 }
4506
4507 #[test]
4508 fn send_packet_single_encrypts() {
4509 let node = RnsNode::start(
4510 NodeConfig {
4511 panic_on_interface_error: false,
4512 transport_enabled: false,
4513 static_transport_identity: false,
4514 local_hops_delta: false,
4515 identity: None,
4516 interfaces: vec![],
4517 share_instance: false,
4518 instance_name: "default".into(),
4519 shared_instance_port: 37428,
4520 rpc_port: 0,
4521 cache_dir: None,
4522 ratchet_store: None,
4523 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
4524 management: Default::default(),
4525 probe_port: None,
4526 probe_addrs: vec![],
4527 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
4528 device: None,
4529 hooks: Vec::new(),
4530 discover_interfaces: false,
4531 discovery_required_value: None,
4532 respond_to_probes: false,
4533 prefer_shorter_path: false,
4534 max_paths_per_destination: 1,
4535 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
4536 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
4537 max_path_destinations: usize::MAX,
4538 max_tunnel_destinations_total: usize::MAX,
4539 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
4540 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
4541 announce_table_ttl: Duration::from_secs(
4542 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
4543 ),
4544 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
4545 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
4546 interface_writer_queue_capacity:
4547 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
4548 announce_rate_defaults: AnnounceRateDefaults::default(),
4549 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
4550 ),
4551 #[cfg(feature = "iface-backbone")]
4552 backbone_peer_pool: None,
4553 announce_sig_cache_enabled: true,
4554 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
4555 announce_sig_cache_ttl: Duration::from_secs(
4556 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
4557 ),
4558 registry: None,
4559 #[cfg(feature = "hooks")]
4560 provider_bridge: None,
4561 },
4562 Box::new(NoopCallbacks),
4563 )
4564 .unwrap();
4565
4566 let remote_identity = Identity::new(&mut OsRng);
4568 let recalled = crate::destination::AnnouncedIdentity {
4569 dest_hash: rns_core::types::DestHash([0xAA; 16]),
4570 identity_hash: rns_core::types::IdentityHash(*remote_identity.hash()),
4571 public_key: remote_identity.get_public_key().unwrap(),
4572 app_data: None,
4573 hops: 1,
4574 received_at: 0.0,
4575 receiving_interface: rns_core::transport::types::InterfaceId(0),
4576 rssi: Some(-100),
4577 snr: Some(10.5),
4578 };
4579 let dest = crate::destination::Destination::single_out("test", &["echo"], &recalled);
4580
4581 let result = node.send_packet(&dest, b"secret message");
4582 assert!(result.is_ok());
4583
4584 let packet_hash = result.unwrap();
4585 assert_ne!(packet_hash.0, [0u8; 32]);
4586
4587 thread::sleep(Duration::from_millis(50));
4588 node.shutdown();
4589 }
4590
4591 #[test]
4592 fn register_destination_with_proof_prove_all() {
4593 let node = RnsNode::start(
4594 NodeConfig {
4595 panic_on_interface_error: false,
4596 transport_enabled: false,
4597 static_transport_identity: false,
4598 local_hops_delta: false,
4599 identity: None,
4600 interfaces: vec![],
4601 share_instance: false,
4602 instance_name: "default".into(),
4603 shared_instance_port: 37428,
4604 rpc_port: 0,
4605 cache_dir: None,
4606 ratchet_store: None,
4607 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
4608 management: Default::default(),
4609 probe_port: None,
4610 probe_addrs: vec![],
4611 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
4612 device: None,
4613 hooks: Vec::new(),
4614 discover_interfaces: false,
4615 discovery_required_value: None,
4616 respond_to_probes: false,
4617 prefer_shorter_path: false,
4618 max_paths_per_destination: 1,
4619 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
4620 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
4621 max_path_destinations: usize::MAX,
4622 max_tunnel_destinations_total: usize::MAX,
4623 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
4624 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
4625 announce_table_ttl: Duration::from_secs(
4626 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
4627 ),
4628 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
4629 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
4630 interface_writer_queue_capacity:
4631 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
4632 announce_rate_defaults: AnnounceRateDefaults::default(),
4633 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
4634 ),
4635 #[cfg(feature = "iface-backbone")]
4636 backbone_peer_pool: None,
4637 announce_sig_cache_enabled: true,
4638 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
4639 announce_sig_cache_ttl: Duration::from_secs(
4640 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
4641 ),
4642 registry: None,
4643 #[cfg(feature = "hooks")]
4644 provider_bridge: None,
4645 },
4646 Box::new(NoopCallbacks),
4647 )
4648 .unwrap();
4649
4650 let identity = Identity::new(&mut OsRng);
4651 let ih = rns_core::types::IdentityHash(*identity.hash());
4652 let dest = crate::destination::Destination::single_in("echo", &["request"], ih)
4653 .set_proof_strategy(rns_core::types::ProofStrategy::ProveAll);
4654 let prv_key = identity.get_private_key().unwrap();
4655
4656 let result = node.register_destination_with_proof(&dest, Some(prv_key));
4657 assert!(result.is_ok());
4658
4659 thread::sleep(Duration::from_millis(50));
4661
4662 node.shutdown();
4663 }
4664
4665 #[test]
4666 fn register_destination_with_proof_prove_none() {
4667 let node = RnsNode::start(
4668 NodeConfig {
4669 panic_on_interface_error: false,
4670 transport_enabled: false,
4671 static_transport_identity: false,
4672 local_hops_delta: false,
4673 identity: None,
4674 interfaces: vec![],
4675 share_instance: false,
4676 instance_name: "default".into(),
4677 shared_instance_port: 37428,
4678 rpc_port: 0,
4679 cache_dir: None,
4680 ratchet_store: None,
4681 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
4682 management: Default::default(),
4683 probe_port: None,
4684 probe_addrs: vec![],
4685 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
4686 device: None,
4687 hooks: Vec::new(),
4688 discover_interfaces: false,
4689 discovery_required_value: None,
4690 respond_to_probes: false,
4691 prefer_shorter_path: false,
4692 max_paths_per_destination: 1,
4693 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
4694 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
4695 max_path_destinations: usize::MAX,
4696 max_tunnel_destinations_total: usize::MAX,
4697 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
4698 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
4699 announce_table_ttl: Duration::from_secs(
4700 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
4701 ),
4702 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
4703 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
4704 interface_writer_queue_capacity:
4705 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
4706 announce_rate_defaults: AnnounceRateDefaults::default(),
4707 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
4708 ),
4709 #[cfg(feature = "iface-backbone")]
4710 backbone_peer_pool: None,
4711 announce_sig_cache_enabled: true,
4712 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
4713 announce_sig_cache_ttl: Duration::from_secs(
4714 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
4715 ),
4716 registry: None,
4717 #[cfg(feature = "hooks")]
4718 provider_bridge: None,
4719 },
4720 Box::new(NoopCallbacks),
4721 )
4722 .unwrap();
4723
4724 let dest = crate::destination::Destination::plain("test", &["data"])
4726 .set_proof_strategy(rns_core::types::ProofStrategy::ProveNone);
4727
4728 let result = node.register_destination_with_proof(&dest, None);
4729 assert!(result.is_ok());
4730
4731 thread::sleep(Duration::from_millis(50));
4732 node.shutdown();
4733 }
4734}