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