1use std::io;
6use std::path::Path;
7use std::sync::atomic::{AtomicU64, Ordering};
8use std::sync::Arc;
9use std::thread::{self, JoinHandle};
10use std::time::Duration;
11
12use rns_core::transport::types::TransportConfig;
13use rns_crypto::identity::Identity;
14use rns_crypto::{OsRng, Rng};
15
16use crate::config;
17use crate::driver::{Callbacks, Driver};
18use crate::event::{self, Event, EventSender};
19use crate::ifac;
20#[cfg(feature = "iface-local")]
21use crate::interface::local::LocalServerConfig;
22use crate::interface::{InterfaceEntry, InterfaceStats};
23use crate::storage;
24use crate::time;
25
26fn parse_interface_mode(mode: &str) -> u8 {
29 match mode.to_lowercase().as_str() {
30 "full" => rns_core::constants::MODE_FULL,
31 "access_point" | "accesspoint" | "ap" => rns_core::constants::MODE_ACCESS_POINT,
32 "pointtopoint" | "ptp" => rns_core::constants::MODE_POINT_TO_POINT,
33 "roaming" => rns_core::constants::MODE_ROAMING,
34 "boundary" => rns_core::constants::MODE_BOUNDARY,
35 "gateway" | "gw" => rns_core::constants::MODE_GATEWAY,
36 _ => rns_core::constants::MODE_FULL,
37 }
38}
39
40fn extract_ifac_config(
43 params: &std::collections::HashMap<String, String>,
44 default_size: usize,
45) -> Option<IfacConfig> {
46 let netname = params
47 .get("networkname")
48 .or_else(|| params.get("network_name"))
49 .cloned();
50 let netkey = params
51 .get("passphrase")
52 .or_else(|| params.get("pass_phrase"))
53 .cloned();
54
55 if netname.is_none() && netkey.is_none() {
56 return None;
57 }
58
59 let size = params
61 .get("ifac_size")
62 .and_then(|v| v.parse::<usize>().ok())
63 .map(|bits| (bits / 8).max(1))
64 .unwrap_or(default_size);
65
66 Some(IfacConfig {
67 netname,
68 netkey,
69 size,
70 })
71}
72
73fn extract_discovery_config(
75 iface_name: &str,
76 iface_type: &str,
77 params: &std::collections::HashMap<String, String>,
78) -> Option<crate::discovery::DiscoveryConfig> {
79 let discoverable = params
80 .get("discoverable")
81 .and_then(|v| config::parse_bool_pub(v))
82 .unwrap_or(false);
83 if !discoverable {
84 return None;
85 }
86
87 let discovery_name = params
88 .get("discovery_name")
89 .cloned()
90 .unwrap_or_else(|| iface_name.to_string());
91
92 let announce_interval = params
94 .get("announce_interval")
95 .and_then(|v| v.parse::<u64>().ok())
96 .map(|secs| secs.max(300))
97 .unwrap_or(21600);
98
99 let stamp_value = params
100 .get("discovery_stamp_value")
101 .and_then(|v| v.parse::<u8>().ok())
102 .unwrap_or(crate::discovery::DEFAULT_STAMP_VALUE);
103
104 let reachable_on = params.get("reachable_on").cloned();
105
106 let listen_port = params
107 .get("listen_port")
108 .or_else(|| params.get("port"))
109 .and_then(|v| v.parse().ok());
110
111 let latitude = params
112 .get("latitude")
113 .or_else(|| params.get("lat"))
114 .and_then(|v| v.parse().ok());
115 let longitude = params
116 .get("longitude")
117 .or_else(|| params.get("lon"))
118 .and_then(|v| v.parse().ok());
119 let height = params.get("height").and_then(|v| v.parse().ok());
120
121 Some(crate::discovery::DiscoveryConfig {
122 discovery_name,
123 announce_interval,
124 stamp_value,
125 reachable_on,
126 interface_type: iface_type.to_string(),
127 listen_port,
128 latitude,
129 longitude,
130 height,
131 })
132}
133
134pub struct NodeConfig {
136 pub transport_enabled: bool,
137 pub identity: Option<Identity>,
138 pub interfaces: Vec<InterfaceConfig>,
140 pub share_instance: bool,
142 pub instance_name: String,
144 pub shared_instance_port: u16,
146 pub rpc_port: u16,
148 pub cache_dir: Option<std::path::PathBuf>,
150 pub management: crate::management::ManagementConfig,
152 pub probe_port: Option<u16>,
154 pub probe_addrs: Vec<std::net::SocketAddr>,
156 pub probe_protocol: rns_core::holepunch::ProbeProtocol,
158 pub device: Option<String>,
160 pub hooks: Vec<config::ParsedHook>,
162 pub discover_interfaces: bool,
164 pub discovery_required_value: Option<u8>,
166 pub respond_to_probes: bool,
168 pub prefer_shorter_path: bool,
172 pub max_paths_per_destination: usize,
175 pub registry: Option<crate::interface::registry::InterfaceRegistry>,
177 pub panic_on_interface_error: bool,
180}
181
182pub struct IfacConfig {
184 pub netname: Option<String>,
185 pub netkey: Option<String>,
186 pub size: usize,
187}
188
189pub struct InterfaceConfig {
191 pub name: String,
192 pub type_name: String,
193 pub config_data: Box<dyn crate::interface::InterfaceConfigData>,
194 pub mode: u8,
195 pub ifac: Option<IfacConfig>,
196 pub discovery: Option<crate::discovery::DiscoveryConfig>,
197}
198
199use crate::event::{QueryRequest, QueryResponse};
200
201#[derive(Debug)]
203pub struct SendError;
204
205impl std::fmt::Display for SendError {
206 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207 write!(f, "driver shut down")
208 }
209}
210
211impl std::error::Error for SendError {}
212
213pub struct RnsNode {
215 tx: EventSender,
216 driver_handle: Option<JoinHandle<()>>,
217 rpc_server: Option<crate::rpc::RpcServer>,
218 tick_interval_ms: Arc<AtomicU64>,
219 #[allow(dead_code)]
220 probe_server: Option<crate::holepunch::probe::ProbeServerHandle>,
221}
222
223impl RnsNode {
224 pub fn from_config(
227 config_path: Option<&Path>,
228 callbacks: Box<dyn Callbacks>,
229 ) -> io::Result<Self> {
230 let config_dir = storage::resolve_config_dir(config_path);
231 let paths = storage::ensure_storage_dirs(&config_dir)?;
232
233 let config_file = config_dir.join("config");
235 let rns_config = if config_file.exists() {
236 config::parse_file(&config_file)
237 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("{}", e)))?
238 } else {
239 config::parse("")
241 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("{}", e)))?
242 };
243
244 let identity = if let Some(ref id_path_str) = rns_config.reticulum.network_identity {
246 let id_path = std::path::PathBuf::from(id_path_str);
247 if id_path.exists() {
248 storage::load_identity(&id_path)?
249 } else {
250 let id = Identity::new(&mut OsRng);
251 storage::save_identity(&id, &id_path)?;
252 id
253 }
254 } else {
255 storage::load_or_create_identity(&paths.identities)?
256 };
257
258 let registry = crate::interface::registry::InterfaceRegistry::with_builtins();
260 let mut interface_configs = Vec::new();
261 let mut next_id_val = 1u64;
262
263 for iface in &rns_config.interfaces {
264 if !iface.enabled {
265 continue;
266 }
267
268 let iface_id = rns_core::transport::types::InterfaceId(next_id_val);
269 next_id_val += 1;
270
271 let factory = match registry.get(&iface.interface_type) {
272 Some(f) => f,
273 None => {
274 log::warn!(
275 "Unsupported interface type '{}' for '{}'",
276 iface.interface_type,
277 iface.name,
278 );
279 continue;
280 }
281 };
282
283 let mut iface_mode = parse_interface_mode(&iface.mode);
284
285 let has_discovery = match iface.interface_type.as_str() {
287 "AutoInterface" => true,
288 "RNodeInterface" => iface
289 .params
290 .get("discoverable")
291 .and_then(|v| config::parse_bool_pub(v))
292 .unwrap_or(false),
293 _ => false,
294 };
295 if has_discovery
296 && iface_mode != rns_core::constants::MODE_ACCESS_POINT
297 && iface_mode != rns_core::constants::MODE_GATEWAY
298 {
299 let new_mode = if iface.interface_type == "RNodeInterface" {
300 rns_core::constants::MODE_ACCESS_POINT
301 } else {
302 rns_core::constants::MODE_GATEWAY
303 };
304 log::info!(
305 "Interface '{}' has discovery enabled, auto-configuring mode to {}",
306 iface.name,
307 if new_mode == rns_core::constants::MODE_ACCESS_POINT {
308 "ACCESS_POINT"
309 } else {
310 "GATEWAY"
311 }
312 );
313 iface_mode = new_mode;
314 }
315
316 let default_ifac_size = factory.default_ifac_size();
317 let ifac_config = extract_ifac_config(&iface.params, default_ifac_size);
318 let discovery_config =
319 extract_discovery_config(&iface.name, &iface.interface_type, &iface.params);
320
321 let mut params = iface.params.clone();
323 if !params.contains_key("storage_dir") {
324 params.insert(
325 "storage_dir".to_string(),
326 paths.storage.to_string_lossy().to_string(),
327 );
328 }
329 if let Some(ref device) = rns_config.reticulum.device {
331 if !params.contains_key("device") {
332 params.insert("device".to_string(), device.clone());
333 }
334 }
335
336 let config_data = match factory.parse_config(&iface.name, iface_id, ¶ms) {
337 Ok(data) => data,
338 Err(e) => {
339 log::warn!("Failed to parse config for '{}': {}", iface.name, e);
340 continue;
341 }
342 };
343
344 interface_configs.push(InterfaceConfig {
345 name: iface.name.clone(),
346 type_name: iface.interface_type.clone(),
347 config_data,
348 mode: iface_mode,
349 ifac: ifac_config,
350 discovery: discovery_config,
351 });
352 }
353
354 let mut mgmt_allowed = Vec::new();
356 for hex_hash in &rns_config.reticulum.remote_management_allowed {
357 if hex_hash.len() == 32 {
358 if let Ok(bytes) = (0..hex_hash.len())
359 .step_by(2)
360 .map(|i| u8::from_str_radix(&hex_hash[i..i + 2], 16))
361 .collect::<Result<Vec<u8>, _>>()
362 {
363 if bytes.len() == 16 {
364 let mut h = [0u8; 16];
365 h.copy_from_slice(&bytes);
366 mgmt_allowed.push(h);
367 }
368 } else {
369 log::warn!("Invalid hex in remote_management_allowed: {}", hex_hash);
370 }
371 } else {
372 log::warn!(
373 "Invalid entry in remote_management_allowed (expected 32 hex chars, got {}): {}",
374 hex_hash.len(), hex_hash,
375 );
376 }
377 }
378
379 let probe_addrs: Vec<std::net::SocketAddr> = rns_config
381 .reticulum
382 .probe_addr
383 .as_ref()
384 .map(|s| {
385 s.split(',')
386 .filter_map(|entry| {
387 let trimmed = entry.trim();
388 if trimmed.is_empty() {
389 return None;
390 }
391 trimmed
392 .parse::<std::net::SocketAddr>()
393 .map_err(|e| {
394 log::warn!("Invalid probe_addr entry '{}': {}", trimmed, e);
395 e
396 })
397 .ok()
398 })
399 .collect()
400 })
401 .unwrap_or_default();
402
403 let probe_protocol = match rns_config
405 .reticulum
406 .probe_protocol
407 .as_deref()
408 .map(|s| s.to_lowercase())
409 {
410 Some(ref s) if s == "stun" => rns_core::holepunch::ProbeProtocol::Stun,
411 _ => rns_core::holepunch::ProbeProtocol::Rnsp,
412 };
413
414 let node_config = NodeConfig {
415 transport_enabled: rns_config.reticulum.enable_transport,
416 identity: Some(identity),
417 share_instance: rns_config.reticulum.share_instance,
418 instance_name: rns_config.reticulum.instance_name.clone(),
419 shared_instance_port: rns_config.reticulum.shared_instance_port,
420 rpc_port: rns_config.reticulum.instance_control_port,
421 cache_dir: Some(paths.cache),
422 management: crate::management::ManagementConfig {
423 enable_remote_management: rns_config.reticulum.enable_remote_management,
424 remote_management_allowed: mgmt_allowed,
425 publish_blackhole: rns_config.reticulum.publish_blackhole,
426 },
427 probe_port: rns_config.reticulum.probe_port,
428 probe_addrs,
429 probe_protocol,
430 device: rns_config.reticulum.device.clone(),
431 hooks: rns_config.hooks.clone(),
432 discover_interfaces: rns_config.reticulum.discover_interfaces,
433 discovery_required_value: rns_config.reticulum.required_discovery_value,
434 respond_to_probes: rns_config.reticulum.respond_to_probes,
435 prefer_shorter_path: rns_config.reticulum.prefer_shorter_path,
436 max_paths_per_destination: rns_config.reticulum.max_paths_per_destination,
437 interfaces: interface_configs,
438 registry: None,
439 panic_on_interface_error: rns_config.reticulum.panic_on_interface_error,
440 };
441
442 Self::start(node_config, callbacks)
443 }
444
445 pub fn start(config: NodeConfig, callbacks: Box<dyn Callbacks>) -> io::Result<Self> {
447 let identity = config.identity.unwrap_or_else(|| Identity::new(&mut OsRng));
448
449 let transport_config = TransportConfig {
450 transport_enabled: config.transport_enabled,
451 identity_hash: Some(*identity.hash()),
452 prefer_shorter_path: config.prefer_shorter_path,
453 max_paths_per_destination: config.max_paths_per_destination,
454 };
455
456 let (tx, rx) = event::channel();
457 let mut driver = Driver::new(transport_config, rx, tx.clone(), callbacks);
458
459 if let Some(ref cache_dir) = config.cache_dir {
461 let announces_dir = cache_dir.join("announces");
462 let _ = std::fs::create_dir_all(&announces_dir);
463 driver.announce_cache = Some(crate::announce_cache::AnnounceCache::new(announces_dir));
464 }
465
466 if !config.probe_addrs.is_empty() || config.device.is_some() {
468 driver.set_probe_config(
469 config.probe_addrs.clone(),
470 config.probe_protocol,
471 config.device.clone(),
472 );
473 }
474
475 let probe_server = if let Some(port) = config.probe_port {
477 let listen_addr: std::net::SocketAddr = ([0, 0, 0, 0], port).into();
478 match crate::holepunch::probe::start_probe_server(listen_addr) {
479 Ok(handle) => {
480 log::info!("Probe server started on 0.0.0.0:{}", port);
481 Some(handle)
482 }
483 Err(e) => {
484 log::error!("Failed to start probe server on port {}: {}", port, e);
485 None
486 }
487 }
488 } else {
489 None
490 };
491
492 driver.management_config = config.management.clone();
494
495 if let Some(prv_key) = identity.get_private_key() {
497 driver.transport_identity = Some(Identity::from_private_key(&prv_key));
498 }
499
500 #[cfg(feature = "rns-hooks")]
502 {
503 for hook_cfg in &config.hooks {
504 if !hook_cfg.enabled {
505 continue;
506 }
507 let point_idx = match config::parse_hook_point(&hook_cfg.attach_point) {
508 Some(idx) => idx,
509 None => {
510 log::warn!(
511 "Unknown hook point '{}' for hook '{}'",
512 hook_cfg.attach_point,
513 hook_cfg.name,
514 );
515 continue;
516 }
517 };
518 let mgr = match driver.hook_manager.as_ref() {
519 Some(m) => m,
520 None => {
521 log::warn!(
522 "Hook manager not available, skipping hook '{}'",
523 hook_cfg.name
524 );
525 continue;
526 }
527 };
528 match mgr.load_file(
529 hook_cfg.name.clone(),
530 std::path::Path::new(&hook_cfg.path),
531 hook_cfg.priority,
532 ) {
533 Ok(program) => {
534 driver.hook_slots[point_idx].attach(program);
535 log::info!(
536 "Loaded hook '{}' at point {} (priority {})",
537 hook_cfg.name,
538 hook_cfg.attach_point,
539 hook_cfg.priority,
540 );
541 }
542 Err(e) => {
543 log::error!(
544 "Failed to load hook '{}' from '{}': {}",
545 hook_cfg.name,
546 hook_cfg.path,
547 e,
548 );
549 }
550 }
551 }
552 }
553
554 driver.discover_interfaces = config.discover_interfaces;
556 if let Some(val) = config.discovery_required_value {
557 driver.discovery_required_value = val;
558 }
559
560 let next_dynamic_id = Arc::new(AtomicU64::new(10000));
562
563 let mut discoverable_interfaces = Vec::new();
565
566 let registry = config
568 .registry
569 .unwrap_or_else(crate::interface::registry::InterfaceRegistry::with_builtins);
570 for iface_config in config.interfaces {
571 let factory = match registry.get(&iface_config.type_name) {
572 Some(f) => f,
573 None => {
574 log::warn!(
575 "No factory registered for interface type '{}'",
576 iface_config.type_name
577 );
578 continue;
579 }
580 };
581
582 let mut ifac_state = iface_config.ifac.as_ref().and_then(|ic| {
583 if ic.netname.is_some() || ic.netkey.is_some() {
584 Some(ifac::derive_ifac(
585 ic.netname.as_deref(),
586 ic.netkey.as_deref(),
587 ic.size,
588 ))
589 } else {
590 None
591 }
592 });
593
594 let ctx = crate::interface::StartContext {
595 tx: tx.clone(),
596 next_dynamic_id: next_dynamic_id.clone(),
597 mode: iface_config.mode,
598 };
599
600 let result = match factory.start(iface_config.config_data, ctx) {
601 Ok(r) => r,
602 Err(e) => {
603 if config.panic_on_interface_error {
604 return Err(e);
605 }
606 log::error!(
607 "Interface '{}' ({}) failed to start: {}",
608 iface_config.name,
609 iface_config.type_name,
610 e
611 );
612 continue;
613 }
614 };
615
616 if let Some(ref disc) = iface_config.discovery {
617 discoverable_interfaces.push(crate::discovery::DiscoverableInterface {
618 config: disc.clone(),
619 transport_enabled: config.transport_enabled,
620 ifac_netname: iface_config.ifac.as_ref().and_then(|ic| ic.netname.clone()),
621 ifac_netkey: iface_config.ifac.as_ref().and_then(|ic| ic.netkey.clone()),
622 });
623 }
624
625 match result {
626 crate::interface::StartResult::Simple {
627 id,
628 info,
629 writer,
630 interface_type_name,
631 } => {
632 driver.engine.register_interface(info.clone());
633 driver.interfaces.insert(
634 id,
635 InterfaceEntry {
636 id,
637 info,
638 writer,
639 online: false,
640 dynamic: false,
641 ifac: ifac_state,
642 stats: InterfaceStats {
643 started: time::now(),
644 ..Default::default()
645 },
646 interface_type: interface_type_name,
647 },
648 );
649 }
650 crate::interface::StartResult::Listener => {
651 }
654 crate::interface::StartResult::Multi(subs) => {
655 let ifac_cfg = &iface_config.ifac;
656 let mut first = true;
657 for sub in subs {
658 let sub_ifac = if first {
659 first = false;
660 ifac_state.take()
661 } else if let Some(ref ic) = ifac_cfg {
662 Some(ifac::derive_ifac(
663 ic.netname.as_deref(),
664 ic.netkey.as_deref(),
665 ic.size,
666 ))
667 } else {
668 None
669 };
670
671 driver.engine.register_interface(sub.info.clone());
672 driver.interfaces.insert(
673 sub.id,
674 InterfaceEntry {
675 id: sub.id,
676 info: sub.info,
677 writer: sub.writer,
678 online: false,
679 dynamic: false,
680 ifac: sub_ifac,
681 stats: InterfaceStats {
682 started: time::now(),
683 ..Default::default()
684 },
685 interface_type: sub.interface_type_name,
686 },
687 );
688 }
689 }
690 }
691 }
692
693 if !discoverable_interfaces.is_empty() {
695 let transport_id = *identity.hash();
696 let announcer =
697 crate::discovery::InterfaceAnnouncer::new(transport_id, discoverable_interfaces);
698 log::info!("Interface discovery announcer initialized");
699 driver.interface_announcer = Some(announcer);
700 }
701
702 if let Some(ref cache_dir) = config.cache_dir {
704 let disc_path = std::path::PathBuf::from(cache_dir)
705 .parent()
706 .unwrap_or(std::path::Path::new("."))
707 .join("storage")
708 .join("discovery")
709 .join("interfaces");
710 let _ = std::fs::create_dir_all(&disc_path);
711 driver.discovered_interfaces =
712 crate::discovery::DiscoveredInterfaceStorage::new(disc_path);
713 }
714
715 if config.management.enable_remote_management {
717 if let Some(prv_key) = identity.get_private_key() {
718 let identity_hash = *identity.hash();
719 let mgmt_dest = crate::management::management_dest_hash(&identity_hash);
720
721 let sig_prv = rns_crypto::ed25519::Ed25519PrivateKey::from_bytes(
723 &prv_key[32..64].try_into().unwrap(),
724 );
725 let sig_pub_bytes: [u8; 32] = identity.get_public_key().unwrap()[32..64]
726 .try_into()
727 .unwrap();
728
729 driver
731 .engine
732 .register_destination(mgmt_dest, rns_core::constants::DESTINATION_SINGLE);
733 driver
734 .local_destinations
735 .insert(mgmt_dest, rns_core::constants::DESTINATION_SINGLE);
736
737 driver.link_manager.register_link_destination(
739 mgmt_dest,
740 sig_prv,
741 sig_pub_bytes,
742 crate::link_manager::ResourceStrategy::AcceptNone,
743 );
744
745 driver
747 .link_manager
748 .register_management_path(crate::management::status_path_hash());
749 driver
750 .link_manager
751 .register_management_path(crate::management::path_path_hash());
752
753 log::info!("Remote management enabled on {:02x?}", &mgmt_dest[..4],);
754
755 if !config.management.remote_management_allowed.is_empty() {
757 log::info!(
758 "Remote management allowed for {} identities",
759 config.management.remote_management_allowed.len(),
760 );
761 }
762 }
763 }
764
765 if config.management.publish_blackhole {
766 if let Some(prv_key) = identity.get_private_key() {
767 let identity_hash = *identity.hash();
768 let bh_dest = crate::management::blackhole_dest_hash(&identity_hash);
769
770 let sig_prv = rns_crypto::ed25519::Ed25519PrivateKey::from_bytes(
771 &prv_key[32..64].try_into().unwrap(),
772 );
773 let sig_pub_bytes: [u8; 32] = identity.get_public_key().unwrap()[32..64]
774 .try_into()
775 .unwrap();
776
777 driver
778 .engine
779 .register_destination(bh_dest, rns_core::constants::DESTINATION_SINGLE);
780 driver.link_manager.register_link_destination(
781 bh_dest,
782 sig_prv,
783 sig_pub_bytes,
784 crate::link_manager::ResourceStrategy::AcceptNone,
785 );
786 driver
787 .link_manager
788 .register_management_path(crate::management::list_path_hash());
789
790 log::info!(
791 "Blackhole list publishing enabled on {:02x?}",
792 &bh_dest[..4],
793 );
794 }
795 }
796
797 if config.respond_to_probes && config.transport_enabled {
799 let identity_hash = *identity.hash();
800 let probe_dest = crate::management::probe_dest_hash(&identity_hash);
801
802 driver
804 .engine
805 .register_destination(probe_dest, rns_core::constants::DESTINATION_SINGLE);
806 driver
807 .local_destinations
808 .insert(probe_dest, rns_core::constants::DESTINATION_SINGLE);
809
810 let probe_identity = rns_crypto::identity::Identity::from_private_key(
812 &identity.get_private_key().unwrap(),
813 );
814 driver.proof_strategies.insert(
815 probe_dest,
816 (
817 rns_core::types::ProofStrategy::ProveAll,
818 Some(probe_identity),
819 ),
820 );
821
822 driver.probe_responder_hash = Some(probe_dest);
823
824 log::info!("Probe responder enabled on {:02x?}", &probe_dest[..4],);
825 }
826
827 let tick_interval_ms = Arc::new(AtomicU64::new(1000));
829 let timer_tx = tx.clone();
830 let timer_interval = Arc::clone(&tick_interval_ms);
831 thread::Builder::new()
832 .name("rns-timer".into())
833 .spawn(move || {
834 loop {
835 let ms = timer_interval.load(Ordering::Relaxed);
836 thread::sleep(Duration::from_millis(ms));
837 if timer_tx.send(Event::Tick).is_err() {
838 break; }
840 }
841 })?;
842
843 #[cfg(feature = "iface-local")]
845 if config.share_instance {
846 let local_server_config = LocalServerConfig {
847 instance_name: config.instance_name.clone(),
848 port: config.shared_instance_port,
849 interface_id: rns_core::transport::types::InterfaceId(0), };
851 match crate::interface::local::start_server(
852 local_server_config,
853 tx.clone(),
854 next_dynamic_id.clone(),
855 ) {
856 Ok(()) => {
857 log::info!(
858 "Local shared instance server started (instance={}, port={})",
859 config.instance_name,
860 config.shared_instance_port
861 );
862 }
863 Err(e) => {
864 log::error!("Failed to start local shared instance server: {}", e);
865 }
866 }
867 }
868
869 let rpc_server = if config.share_instance {
871 let auth_key =
872 crate::rpc::derive_auth_key(&identity.get_private_key().unwrap_or([0u8; 64]));
873 let rpc_addr = crate::rpc::RpcAddr::Tcp("127.0.0.1".into(), config.rpc_port);
874 match crate::rpc::RpcServer::start(&rpc_addr, auth_key, tx.clone()) {
875 Ok(server) => {
876 log::info!("RPC server started on 127.0.0.1:{}", config.rpc_port);
877 Some(server)
878 }
879 Err(e) => {
880 log::error!("Failed to start RPC server: {}", e);
881 None
882 }
883 }
884 } else {
885 None
886 };
887
888 let driver_handle = thread::Builder::new()
890 .name("rns-driver".into())
891 .spawn(move || {
892 driver.run();
893 })?;
894
895 Ok(RnsNode {
896 tx,
897 driver_handle: Some(driver_handle),
898 rpc_server,
899 tick_interval_ms,
900 probe_server,
901 })
902 }
903
904 pub fn query(&self, request: QueryRequest) -> Result<QueryResponse, SendError> {
906 let (resp_tx, resp_rx) = std::sync::mpsc::channel();
907 self.tx
908 .send(Event::Query(request, resp_tx))
909 .map_err(|_| SendError)?;
910 resp_rx.recv().map_err(|_| SendError)
911 }
912
913 pub fn send_raw(
915 &self,
916 raw: Vec<u8>,
917 dest_type: u8,
918 attached_interface: Option<rns_core::transport::types::InterfaceId>,
919 ) -> Result<(), SendError> {
920 self.tx
921 .send(Event::SendOutbound {
922 raw,
923 dest_type,
924 attached_interface,
925 })
926 .map_err(|_| SendError)
927 }
928
929 pub fn register_destination(
931 &self,
932 dest_hash: [u8; 16],
933 dest_type: u8,
934 ) -> Result<(), SendError> {
935 self.tx
936 .send(Event::RegisterDestination {
937 dest_hash,
938 dest_type,
939 })
940 .map_err(|_| SendError)
941 }
942
943 pub fn deregister_destination(&self, dest_hash: [u8; 16]) -> Result<(), SendError> {
945 self.tx
946 .send(Event::DeregisterDestination { dest_hash })
947 .map_err(|_| SendError)
948 }
949
950 pub fn deregister_link_destination(&self, dest_hash: [u8; 16]) -> Result<(), SendError> {
952 self.tx
953 .send(Event::DeregisterLinkDestination { dest_hash })
954 .map_err(|_| SendError)
955 }
956
957 pub fn register_link_destination(
963 &self,
964 dest_hash: [u8; 16],
965 sig_prv_bytes: [u8; 32],
966 sig_pub_bytes: [u8; 32],
967 resource_strategy: u8,
968 ) -> Result<(), SendError> {
969 self.tx
970 .send(Event::RegisterLinkDestination {
971 dest_hash,
972 sig_prv_bytes,
973 sig_pub_bytes,
974 resource_strategy,
975 })
976 .map_err(|_| SendError)
977 }
978
979 pub fn register_request_handler<F>(
981 &self,
982 path: &str,
983 allowed_list: Option<Vec<[u8; 16]>>,
984 handler: F,
985 ) -> Result<(), SendError>
986 where
987 F: Fn([u8; 16], &str, &[u8], Option<&([u8; 16], [u8; 64])>) -> Option<Vec<u8>>
988 + Send
989 + 'static,
990 {
991 self.tx
992 .send(Event::RegisterRequestHandler {
993 path: path.to_string(),
994 allowed_list,
995 handler: Box::new(handler),
996 })
997 .map_err(|_| SendError)
998 }
999
1000 pub fn create_link(
1004 &self,
1005 dest_hash: [u8; 16],
1006 dest_sig_pub_bytes: [u8; 32],
1007 ) -> Result<[u8; 16], SendError> {
1008 let (response_tx, response_rx) = std::sync::mpsc::channel();
1009 self.tx
1010 .send(Event::CreateLink {
1011 dest_hash,
1012 dest_sig_pub_bytes,
1013 response_tx,
1014 })
1015 .map_err(|_| SendError)?;
1016 response_rx.recv().map_err(|_| SendError)
1017 }
1018
1019 pub fn send_request(
1021 &self,
1022 link_id: [u8; 16],
1023 path: &str,
1024 data: &[u8],
1025 ) -> Result<(), SendError> {
1026 self.tx
1027 .send(Event::SendRequest {
1028 link_id,
1029 path: path.to_string(),
1030 data: data.to_vec(),
1031 })
1032 .map_err(|_| SendError)
1033 }
1034
1035 pub fn identify_on_link(
1037 &self,
1038 link_id: [u8; 16],
1039 identity_prv_key: [u8; 64],
1040 ) -> Result<(), SendError> {
1041 self.tx
1042 .send(Event::IdentifyOnLink {
1043 link_id,
1044 identity_prv_key,
1045 })
1046 .map_err(|_| SendError)
1047 }
1048
1049 pub fn teardown_link(&self, link_id: [u8; 16]) -> Result<(), SendError> {
1051 self.tx
1052 .send(Event::TeardownLink { link_id })
1053 .map_err(|_| SendError)
1054 }
1055
1056 pub fn send_resource(
1058 &self,
1059 link_id: [u8; 16],
1060 data: Vec<u8>,
1061 metadata: Option<Vec<u8>>,
1062 ) -> Result<(), SendError> {
1063 self.tx
1064 .send(Event::SendResource {
1065 link_id,
1066 data,
1067 metadata,
1068 })
1069 .map_err(|_| SendError)
1070 }
1071
1072 pub fn set_resource_strategy(&self, link_id: [u8; 16], strategy: u8) -> Result<(), SendError> {
1076 self.tx
1077 .send(Event::SetResourceStrategy { link_id, strategy })
1078 .map_err(|_| SendError)
1079 }
1080
1081 pub fn accept_resource(
1083 &self,
1084 link_id: [u8; 16],
1085 resource_hash: Vec<u8>,
1086 accept: bool,
1087 ) -> Result<(), SendError> {
1088 self.tx
1089 .send(Event::AcceptResource {
1090 link_id,
1091 resource_hash,
1092 accept,
1093 })
1094 .map_err(|_| SendError)
1095 }
1096
1097 pub fn send_channel_message(
1099 &self,
1100 link_id: [u8; 16],
1101 msgtype: u16,
1102 payload: Vec<u8>,
1103 ) -> Result<(), SendError> {
1104 self.tx
1105 .send(Event::SendChannelMessage {
1106 link_id,
1107 msgtype,
1108 payload,
1109 })
1110 .map_err(|_| SendError)
1111 }
1112
1113 pub fn propose_direct_connect(&self, link_id: [u8; 16]) -> Result<(), SendError> {
1118 self.tx
1119 .send(Event::ProposeDirectConnect { link_id })
1120 .map_err(|_| SendError)
1121 }
1122
1123 pub fn set_direct_connect_policy(
1125 &self,
1126 policy: crate::holepunch::orchestrator::HolePunchPolicy,
1127 ) -> Result<(), SendError> {
1128 self.tx
1129 .send(Event::SetDirectConnectPolicy { policy })
1130 .map_err(|_| SendError)
1131 }
1132
1133 pub fn send_on_link(
1135 &self,
1136 link_id: [u8; 16],
1137 data: Vec<u8>,
1138 context: u8,
1139 ) -> Result<(), SendError> {
1140 self.tx
1141 .send(Event::SendOnLink {
1142 link_id,
1143 data,
1144 context,
1145 })
1146 .map_err(|_| SendError)
1147 }
1148
1149 pub fn announce(
1154 &self,
1155 dest: &crate::destination::Destination,
1156 identity: &Identity,
1157 app_data: Option<&[u8]>,
1158 ) -> Result<(), SendError> {
1159 let name_hash = rns_core::destination::name_hash(
1160 &dest.app_name,
1161 &dest.aspects.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
1162 );
1163
1164 let mut random_hash = [0u8; 10];
1165 OsRng.fill_bytes(&mut random_hash[..5]);
1166 let now_secs = std::time::SystemTime::now()
1170 .duration_since(std::time::UNIX_EPOCH)
1171 .unwrap_or_default()
1172 .as_secs();
1173 random_hash[5..10].copy_from_slice(&now_secs.to_be_bytes()[3..8]);
1174
1175 let (announce_data, _has_ratchet) = rns_core::announce::AnnounceData::pack(
1176 identity,
1177 &dest.hash.0,
1178 &name_hash,
1179 &random_hash,
1180 None, app_data,
1182 )
1183 .map_err(|_| SendError)?;
1184
1185 let context_flag = rns_core::constants::FLAG_UNSET;
1186
1187 let flags = rns_core::packet::PacketFlags {
1188 header_type: rns_core::constants::HEADER_1,
1189 context_flag,
1190 transport_type: rns_core::constants::TRANSPORT_BROADCAST,
1191 destination_type: rns_core::constants::DESTINATION_SINGLE,
1192 packet_type: rns_core::constants::PACKET_TYPE_ANNOUNCE,
1193 };
1194
1195 let packet = rns_core::packet::RawPacket::pack(
1196 flags,
1197 0,
1198 &dest.hash.0,
1199 None,
1200 rns_core::constants::CONTEXT_NONE,
1201 &announce_data,
1202 )
1203 .map_err(|_| SendError)?;
1204
1205 self.send_raw(packet.raw, dest.dest_type.to_wire_constant(), None)
1206 }
1207
1208 pub fn send_packet(
1213 &self,
1214 dest: &crate::destination::Destination,
1215 data: &[u8],
1216 ) -> Result<rns_core::types::PacketHash, SendError> {
1217 use rns_core::types::DestinationType;
1218
1219 let payload = match dest.dest_type {
1220 DestinationType::Single => {
1221 let pub_key = dest.public_key.ok_or(SendError)?;
1222 let remote_id = rns_crypto::identity::Identity::from_public_key(&pub_key);
1223 remote_id.encrypt(data, &mut OsRng).map_err(|_| SendError)?
1224 }
1225 DestinationType::Plain => data.to_vec(),
1226 DestinationType::Group => dest.encrypt(data).map_err(|_| SendError)?,
1227 };
1228
1229 let flags = rns_core::packet::PacketFlags {
1230 header_type: rns_core::constants::HEADER_1,
1231 context_flag: rns_core::constants::FLAG_UNSET,
1232 transport_type: rns_core::constants::TRANSPORT_BROADCAST,
1233 destination_type: dest.dest_type.to_wire_constant(),
1234 packet_type: rns_core::constants::PACKET_TYPE_DATA,
1235 };
1236
1237 let packet = rns_core::packet::RawPacket::pack(
1238 flags,
1239 0,
1240 &dest.hash.0,
1241 None,
1242 rns_core::constants::CONTEXT_NONE,
1243 &payload,
1244 )
1245 .map_err(|_| SendError)?;
1246
1247 let packet_hash = rns_core::types::PacketHash(packet.packet_hash);
1248
1249 self.tx
1250 .send(Event::SendOutbound {
1251 raw: packet.raw,
1252 dest_type: dest.dest_type.to_wire_constant(),
1253 attached_interface: None,
1254 })
1255 .map_err(|_| SendError)?;
1256
1257 Ok(packet_hash)
1258 }
1259
1260 pub fn register_destination_with_proof(
1265 &self,
1266 dest: &crate::destination::Destination,
1267 signing_key: Option<[u8; 64]>,
1268 ) -> Result<(), SendError> {
1269 self.register_destination(dest.hash.0, dest.dest_type.to_wire_constant())?;
1271
1272 if dest.proof_strategy != rns_core::types::ProofStrategy::ProveNone {
1274 self.tx
1275 .send(Event::RegisterProofStrategy {
1276 dest_hash: dest.hash.0,
1277 strategy: dest.proof_strategy,
1278 signing_key,
1279 })
1280 .map_err(|_| SendError)?;
1281 }
1282
1283 Ok(())
1284 }
1285
1286 pub fn request_path(&self, dest_hash: &rns_core::types::DestHash) -> Result<(), SendError> {
1288 self.tx
1289 .send(Event::RequestPath {
1290 dest_hash: dest_hash.0,
1291 })
1292 .map_err(|_| SendError)
1293 }
1294
1295 pub fn has_path(&self, dest_hash: &rns_core::types::DestHash) -> Result<bool, SendError> {
1297 match self.query(QueryRequest::HasPath {
1298 dest_hash: dest_hash.0,
1299 })? {
1300 QueryResponse::HasPath(v) => Ok(v),
1301 _ => Ok(false),
1302 }
1303 }
1304
1305 pub fn hops_to(&self, dest_hash: &rns_core::types::DestHash) -> Result<Option<u8>, SendError> {
1307 match self.query(QueryRequest::HopsTo {
1308 dest_hash: dest_hash.0,
1309 })? {
1310 QueryResponse::HopsTo(v) => Ok(v),
1311 _ => Ok(None),
1312 }
1313 }
1314
1315 pub fn recall_identity(
1317 &self,
1318 dest_hash: &rns_core::types::DestHash,
1319 ) -> Result<Option<crate::destination::AnnouncedIdentity>, SendError> {
1320 match self.query(QueryRequest::RecallIdentity {
1321 dest_hash: dest_hash.0,
1322 })? {
1323 QueryResponse::RecallIdentity(v) => Ok(v),
1324 _ => Ok(None),
1325 }
1326 }
1327
1328 pub fn load_hook(
1330 &self,
1331 name: String,
1332 wasm_bytes: Vec<u8>,
1333 attach_point: String,
1334 priority: i32,
1335 ) -> Result<Result<(), String>, SendError> {
1336 let (response_tx, response_rx) = std::sync::mpsc::channel();
1337 self.tx
1338 .send(Event::LoadHook {
1339 name,
1340 wasm_bytes,
1341 attach_point,
1342 priority,
1343 response_tx,
1344 })
1345 .map_err(|_| SendError)?;
1346 response_rx.recv().map_err(|_| SendError)
1347 }
1348
1349 pub fn unload_hook(
1351 &self,
1352 name: String,
1353 attach_point: String,
1354 ) -> Result<Result<(), String>, SendError> {
1355 let (response_tx, response_rx) = std::sync::mpsc::channel();
1356 self.tx
1357 .send(Event::UnloadHook {
1358 name,
1359 attach_point,
1360 response_tx,
1361 })
1362 .map_err(|_| SendError)?;
1363 response_rx.recv().map_err(|_| SendError)
1364 }
1365
1366 pub fn reload_hook(
1368 &self,
1369 name: String,
1370 attach_point: String,
1371 wasm_bytes: Vec<u8>,
1372 ) -> Result<Result<(), String>, SendError> {
1373 let (response_tx, response_rx) = std::sync::mpsc::channel();
1374 self.tx
1375 .send(Event::ReloadHook {
1376 name,
1377 attach_point,
1378 wasm_bytes,
1379 response_tx,
1380 })
1381 .map_err(|_| SendError)?;
1382 response_rx.recv().map_err(|_| SendError)
1383 }
1384
1385 pub fn list_hooks(&self) -> Result<Vec<crate::event::HookInfo>, SendError> {
1387 let (response_tx, response_rx) = std::sync::mpsc::channel();
1388 self.tx
1389 .send(Event::ListHooks { response_tx })
1390 .map_err(|_| SendError)?;
1391 response_rx.recv().map_err(|_| SendError)
1392 }
1393
1394 pub(crate) fn from_parts(
1397 tx: EventSender,
1398 driver_handle: thread::JoinHandle<()>,
1399 rpc_server: Option<crate::rpc::RpcServer>,
1400 tick_interval_ms: Arc<AtomicU64>,
1401 ) -> Self {
1402 RnsNode {
1403 tx,
1404 driver_handle: Some(driver_handle),
1405 rpc_server,
1406 tick_interval_ms,
1407 probe_server: None,
1408 }
1409 }
1410
1411 pub fn event_sender(&self) -> &EventSender {
1413 &self.tx
1414 }
1415
1416 pub fn set_tick_interval(&self, ms: u64) -> u64 {
1421 let clamped = ms.clamp(100, 10_000);
1422 if clamped != ms {
1423 log::warn!(
1424 "tick interval {}ms out of range, clamped to {}ms",
1425 ms,
1426 clamped
1427 );
1428 }
1429 self.tick_interval_ms.store(clamped, Ordering::Relaxed);
1430 clamped
1431 }
1432
1433 pub fn tick_interval(&self) -> u64 {
1435 self.tick_interval_ms.load(Ordering::Relaxed)
1436 }
1437
1438 pub fn shutdown(mut self) {
1440 if let Some(mut rpc) = self.rpc_server.take() {
1442 rpc.stop();
1443 }
1444 let _ = self.tx.send(Event::Shutdown);
1445 if let Some(handle) = self.driver_handle.take() {
1446 let _ = handle.join();
1447 }
1448 }
1449}
1450
1451#[cfg(test)]
1452mod tests {
1453 use super::*;
1454 use std::fs;
1455
1456 struct NoopCallbacks;
1457
1458 impl Callbacks for NoopCallbacks {
1459 fn on_announce(&mut self, _: crate::destination::AnnouncedIdentity) {}
1460 fn on_path_updated(&mut self, _: rns_core::types::DestHash, _: u8) {}
1461 fn on_local_delivery(
1462 &mut self,
1463 _: rns_core::types::DestHash,
1464 _: Vec<u8>,
1465 _: rns_core::types::PacketHash,
1466 ) {
1467 }
1468 }
1469
1470 #[test]
1471 fn start_and_shutdown() {
1472 let node = RnsNode::start(
1473 NodeConfig { panic_on_interface_error: false,
1474 transport_enabled: false,
1475 identity: None,
1476 interfaces: vec![],
1477 share_instance: false,
1478 instance_name: "default".into(),
1479 shared_instance_port: 37428,
1480 rpc_port: 0,
1481 cache_dir: None,
1482 management: Default::default(),
1483 probe_port: None,
1484 probe_addrs: vec![],
1485 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
1486 device: None,
1487 hooks: Vec::new(),
1488 discover_interfaces: false,
1489 discovery_required_value: None,
1490 respond_to_probes: false,
1491 prefer_shorter_path: false,
1492 max_paths_per_destination: 1,
1493 registry: None,
1494 },
1495 Box::new(NoopCallbacks),
1496 )
1497 .unwrap();
1498 node.shutdown();
1499 }
1500
1501 #[test]
1502 fn start_with_identity() {
1503 let identity = Identity::new(&mut OsRng);
1504 let hash = *identity.hash();
1505 let node = RnsNode::start(
1506 NodeConfig { panic_on_interface_error: false,
1507 transport_enabled: true,
1508 identity: Some(identity),
1509 interfaces: vec![],
1510 share_instance: false,
1511 instance_name: "default".into(),
1512 shared_instance_port: 37428,
1513 rpc_port: 0,
1514 cache_dir: None,
1515 management: Default::default(),
1516 probe_port: None,
1517 probe_addrs: vec![],
1518 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
1519 device: None,
1520 hooks: Vec::new(),
1521 discover_interfaces: false,
1522 discovery_required_value: None,
1523 respond_to_probes: false,
1524 prefer_shorter_path: false,
1525 max_paths_per_destination: 1,
1526 registry: None,
1527 },
1528 Box::new(NoopCallbacks),
1529 )
1530 .unwrap();
1531 let _ = hash;
1533 node.shutdown();
1534 }
1535
1536 #[test]
1537 fn start_generates_identity() {
1538 let node = RnsNode::start(
1539 NodeConfig { panic_on_interface_error: false,
1540 transport_enabled: false,
1541 identity: None,
1542 interfaces: vec![],
1543 share_instance: false,
1544 instance_name: "default".into(),
1545 shared_instance_port: 37428,
1546 rpc_port: 0,
1547 cache_dir: None,
1548 management: Default::default(),
1549 probe_port: None,
1550 probe_addrs: vec![],
1551 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
1552 device: None,
1553 hooks: Vec::new(),
1554 discover_interfaces: false,
1555 discovery_required_value: None,
1556 respond_to_probes: false,
1557 prefer_shorter_path: false,
1558 max_paths_per_destination: 1,
1559 registry: None,
1560 },
1561 Box::new(NoopCallbacks),
1562 )
1563 .unwrap();
1564 node.shutdown();
1566 }
1567
1568 #[test]
1569 fn from_config_creates_identity() {
1570 let dir = std::env::temp_dir().join(format!("rns-test-fc-{}", std::process::id()));
1571 let _ = fs::remove_dir_all(&dir);
1572 fs::create_dir_all(&dir).unwrap();
1573
1574 fs::write(
1576 dir.join("config"),
1577 "[reticulum]\nenable_transport = False\n",
1578 )
1579 .unwrap();
1580
1581 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
1582
1583 assert!(dir.join("storage/identities/identity").exists());
1585
1586 node.shutdown();
1587 let _ = fs::remove_dir_all(&dir);
1588 }
1589
1590 #[test]
1591 fn from_config_loads_identity() {
1592 let dir = std::env::temp_dir().join(format!("rns-test-fl-{}", std::process::id()));
1593 let _ = fs::remove_dir_all(&dir);
1594 fs::create_dir_all(dir.join("storage/identities")).unwrap();
1595
1596 let identity = Identity::new(&mut OsRng);
1598 let hash = *identity.hash();
1599 storage::save_identity(&identity, &dir.join("storage/identities/identity")).unwrap();
1600
1601 fs::write(
1602 dir.join("config"),
1603 "[reticulum]\nenable_transport = False\n",
1604 )
1605 .unwrap();
1606
1607 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
1608
1609 let loaded = storage::load_identity(&dir.join("storage/identities/identity")).unwrap();
1611 assert_eq!(*loaded.hash(), hash);
1612
1613 node.shutdown();
1614 let _ = fs::remove_dir_all(&dir);
1615 }
1616
1617 #[test]
1618 fn from_config_tcp_server() {
1619 let dir = std::env::temp_dir().join(format!("rns-test-fts-{}", std::process::id()));
1620 let _ = fs::remove_dir_all(&dir);
1621 fs::create_dir_all(&dir).unwrap();
1622
1623 let port = std::net::TcpListener::bind("127.0.0.1:0")
1625 .unwrap()
1626 .local_addr()
1627 .unwrap()
1628 .port();
1629
1630 let config = format!(
1631 r#"
1632[reticulum]
1633enable_transport = False
1634
1635[interfaces]
1636 [[Test TCP Server]]
1637 type = TCPServerInterface
1638 listen_ip = 127.0.0.1
1639 listen_port = {}
1640"#,
1641 port
1642 );
1643
1644 fs::write(dir.join("config"), config).unwrap();
1645
1646 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
1647
1648 thread::sleep(Duration::from_millis(100));
1650
1651 let _client = std::net::TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1653
1654 node.shutdown();
1655 let _ = fs::remove_dir_all(&dir);
1656 }
1657
1658 #[test]
1659 fn test_parse_interface_mode() {
1660 use rns_core::constants::*;
1661
1662 assert_eq!(parse_interface_mode("full"), MODE_FULL);
1663 assert_eq!(parse_interface_mode("Full"), MODE_FULL);
1664 assert_eq!(parse_interface_mode("access_point"), MODE_ACCESS_POINT);
1665 assert_eq!(parse_interface_mode("accesspoint"), MODE_ACCESS_POINT);
1666 assert_eq!(parse_interface_mode("ap"), MODE_ACCESS_POINT);
1667 assert_eq!(parse_interface_mode("AP"), MODE_ACCESS_POINT);
1668 assert_eq!(parse_interface_mode("pointtopoint"), MODE_POINT_TO_POINT);
1669 assert_eq!(parse_interface_mode("ptp"), MODE_POINT_TO_POINT);
1670 assert_eq!(parse_interface_mode("roaming"), MODE_ROAMING);
1671 assert_eq!(parse_interface_mode("boundary"), MODE_BOUNDARY);
1672 assert_eq!(parse_interface_mode("gateway"), MODE_GATEWAY);
1673 assert_eq!(parse_interface_mode("gw"), MODE_GATEWAY);
1674 assert_eq!(parse_interface_mode("invalid"), MODE_FULL);
1676 }
1677
1678 #[test]
1679 fn to_node_config_serial() {
1680 let dir = std::env::temp_dir().join(format!("rns-test-serial-{}", std::process::id()));
1684 let _ = fs::remove_dir_all(&dir);
1685 fs::create_dir_all(&dir).unwrap();
1686
1687 let config = r#"
1688[reticulum]
1689enable_transport = False
1690
1691[interfaces]
1692 [[Test Serial Port]]
1693 type = SerialInterface
1694 port = /dev/nonexistent_rns_test_serial
1695 speed = 115200
1696 databits = 8
1697 parity = E
1698 stopbits = 1
1699 interface_mode = ptp
1700 networkname = testnet
1701"#;
1702 fs::write(dir.join("config"), config).unwrap();
1703
1704 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks))
1706 .expect("Config should parse; interface failure is non-fatal");
1707 node.shutdown();
1708
1709 let _ = fs::remove_dir_all(&dir);
1710 }
1711
1712 #[test]
1713 fn to_node_config_kiss() {
1714 let dir = std::env::temp_dir().join(format!("rns-test-kiss-{}", std::process::id()));
1716 let _ = fs::remove_dir_all(&dir);
1717 fs::create_dir_all(&dir).unwrap();
1718
1719 let config = r#"
1720[reticulum]
1721enable_transport = False
1722
1723[interfaces]
1724 [[Test KISS TNC]]
1725 type = KISSInterface
1726 port = /dev/nonexistent_rns_test_kiss
1727 speed = 9600
1728 preamble = 500
1729 txtail = 30
1730 persistence = 128
1731 slottime = 40
1732 flow_control = True
1733 id_interval = 600
1734 id_callsign = TEST0
1735 interface_mode = full
1736 passphrase = secretkey
1737"#;
1738 fs::write(dir.join("config"), config).unwrap();
1739
1740 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks))
1742 .expect("Config should parse; interface failure is non-fatal");
1743 node.shutdown();
1744
1745 let _ = fs::remove_dir_all(&dir);
1746 }
1747
1748 #[test]
1749 fn test_extract_ifac_config() {
1750 use std::collections::HashMap;
1751
1752 let params: HashMap<String, String> = HashMap::new();
1754 assert!(extract_ifac_config(¶ms, 16).is_none());
1755
1756 let mut params = HashMap::new();
1758 params.insert("networkname".into(), "testnet".into());
1759 let ifac = extract_ifac_config(¶ms, 16).unwrap();
1760 assert_eq!(ifac.netname.as_deref(), Some("testnet"));
1761 assert!(ifac.netkey.is_none());
1762 assert_eq!(ifac.size, 16);
1763
1764 let mut params = HashMap::new();
1766 params.insert("passphrase".into(), "secret".into());
1767 params.insert("ifac_size".into(), "64".into()); let ifac = extract_ifac_config(¶ms, 16).unwrap();
1769 assert!(ifac.netname.is_none());
1770 assert_eq!(ifac.netkey.as_deref(), Some("secret"));
1771 assert_eq!(ifac.size, 8);
1772
1773 let mut params = HashMap::new();
1775 params.insert("network_name".into(), "mynet".into());
1776 params.insert("pass_phrase".into(), "mykey".into());
1777 let ifac = extract_ifac_config(¶ms, 8).unwrap();
1778 assert_eq!(ifac.netname.as_deref(), Some("mynet"));
1779 assert_eq!(ifac.netkey.as_deref(), Some("mykey"));
1780 assert_eq!(ifac.size, 8);
1781 }
1782
1783 #[test]
1784 fn to_node_config_rnode() {
1785 let dir = std::env::temp_dir().join(format!("rns-test-rnode-{}", std::process::id()));
1788 let _ = fs::remove_dir_all(&dir);
1789 fs::create_dir_all(&dir).unwrap();
1790
1791 let config = r#"
1792[reticulum]
1793enable_transport = False
1794
1795[interfaces]
1796 [[Test RNode]]
1797 type = RNodeInterface
1798 port = /dev/nonexistent_rns_test_rnode
1799 frequency = 867200000
1800 bandwidth = 125000
1801 txpower = 7
1802 spreadingfactor = 8
1803 codingrate = 5
1804 flow_control = True
1805 st_alock = 5.0
1806 lt_alock = 2.5
1807 interface_mode = full
1808 networkname = testnet
1809"#;
1810 fs::write(dir.join("config"), config).unwrap();
1811
1812 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks))
1814 .expect("Config should parse; interface failure is non-fatal");
1815 node.shutdown();
1816
1817 let _ = fs::remove_dir_all(&dir);
1818 }
1819
1820 #[test]
1821 fn to_node_config_pipe() {
1822 let dir = std::env::temp_dir().join(format!("rns-test-pipe-{}", std::process::id()));
1825 let _ = fs::remove_dir_all(&dir);
1826 fs::create_dir_all(&dir).unwrap();
1827
1828 let config = r#"
1829[reticulum]
1830enable_transport = False
1831
1832[interfaces]
1833 [[Test Pipe]]
1834 type = PipeInterface
1835 command = cat
1836 respawn_delay = 5000
1837 interface_mode = full
1838"#;
1839 fs::write(dir.join("config"), config).unwrap();
1840
1841 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
1842 node.shutdown();
1844
1845 let _ = fs::remove_dir_all(&dir);
1846 }
1847
1848 #[test]
1849 fn to_node_config_backbone() {
1850 let dir = std::env::temp_dir().join(format!("rns-test-backbone-{}", std::process::id()));
1852 let _ = fs::remove_dir_all(&dir);
1853 fs::create_dir_all(&dir).unwrap();
1854
1855 let port = std::net::TcpListener::bind("127.0.0.1:0")
1856 .unwrap()
1857 .local_addr()
1858 .unwrap()
1859 .port();
1860
1861 let config = format!(
1862 r#"
1863[reticulum]
1864enable_transport = False
1865
1866[interfaces]
1867 [[Test Backbone]]
1868 type = BackboneInterface
1869 listen_ip = 127.0.0.1
1870 listen_port = {}
1871 interface_mode = full
1872"#,
1873 port
1874 );
1875
1876 fs::write(dir.join("config"), config).unwrap();
1877
1878 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
1879
1880 thread::sleep(Duration::from_millis(100));
1882
1883 {
1885 let _client = std::net::TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1886 }
1888
1889 thread::sleep(Duration::from_millis(50));
1891
1892 node.shutdown();
1893 let _ = fs::remove_dir_all(&dir);
1894 }
1895
1896 #[test]
1897 fn rnode_config_defaults() {
1898 use crate::interface::rnode::{RNodeConfig, RNodeSubConfig};
1899
1900 let config = RNodeConfig::default();
1901 assert_eq!(config.speed, 115200);
1902 assert!(config.subinterfaces.is_empty());
1903 assert!(config.id_interval.is_none());
1904 assert!(config.id_callsign.is_none());
1905
1906 let sub = RNodeSubConfig {
1907 name: "test".into(),
1908 frequency: 868_000_000,
1909 bandwidth: 125_000,
1910 txpower: 7,
1911 spreading_factor: 8,
1912 coding_rate: 5,
1913 flow_control: false,
1914 st_alock: None,
1915 lt_alock: None,
1916 };
1917 assert_eq!(sub.frequency, 868_000_000);
1918 assert_eq!(sub.bandwidth, 125_000);
1919 assert!(!sub.flow_control);
1920 }
1921
1922 #[test]
1927 fn announce_builds_valid_packet() {
1928 let identity = Identity::new(&mut OsRng);
1929 let identity_hash = rns_core::types::IdentityHash(*identity.hash());
1930
1931 let node = RnsNode::start(
1932 NodeConfig { panic_on_interface_error: false,
1933 transport_enabled: false,
1934 identity: None,
1935 interfaces: vec![],
1936 share_instance: false,
1937 instance_name: "default".into(),
1938 shared_instance_port: 37428,
1939 rpc_port: 0,
1940 cache_dir: None,
1941 management: Default::default(),
1942 probe_port: None,
1943 probe_addrs: vec![],
1944 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
1945 device: None,
1946 hooks: Vec::new(),
1947 discover_interfaces: false,
1948 discovery_required_value: None,
1949 respond_to_probes: false,
1950 prefer_shorter_path: false,
1951 max_paths_per_destination: 1,
1952 registry: None,
1953 },
1954 Box::new(NoopCallbacks),
1955 )
1956 .unwrap();
1957
1958 let dest = crate::destination::Destination::single_in("test", &["echo"], identity_hash);
1959
1960 node.register_destination(dest.hash.0, dest.dest_type.to_wire_constant())
1962 .unwrap();
1963
1964 let result = node.announce(&dest, &identity, Some(b"hello"));
1966 assert!(result.is_ok());
1967
1968 node.shutdown();
1969 }
1970
1971 #[test]
1972 fn has_path_and_hops_to() {
1973 let node = RnsNode::start(
1974 NodeConfig { panic_on_interface_error: false,
1975 transport_enabled: false,
1976 identity: None,
1977 interfaces: vec![],
1978 share_instance: false,
1979 instance_name: "default".into(),
1980 shared_instance_port: 37428,
1981 rpc_port: 0,
1982 cache_dir: None,
1983 management: Default::default(),
1984 probe_port: None,
1985 probe_addrs: vec![],
1986 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
1987 device: None,
1988 hooks: Vec::new(),
1989 discover_interfaces: false,
1990 discovery_required_value: None,
1991 respond_to_probes: false,
1992 prefer_shorter_path: false,
1993 max_paths_per_destination: 1,
1994 registry: None,
1995 },
1996 Box::new(NoopCallbacks),
1997 )
1998 .unwrap();
1999
2000 let dh = rns_core::types::DestHash([0xAA; 16]);
2001
2002 assert_eq!(node.has_path(&dh).unwrap(), false);
2004 assert_eq!(node.hops_to(&dh).unwrap(), None);
2005
2006 node.shutdown();
2007 }
2008
2009 #[test]
2010 fn recall_identity_none_when_unknown() {
2011 let node = RnsNode::start(
2012 NodeConfig { panic_on_interface_error: false,
2013 transport_enabled: false,
2014 identity: None,
2015 interfaces: vec![],
2016 share_instance: false,
2017 instance_name: "default".into(),
2018 shared_instance_port: 37428,
2019 rpc_port: 0,
2020 cache_dir: None,
2021 management: Default::default(),
2022 probe_port: None,
2023 probe_addrs: vec![],
2024 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
2025 device: None,
2026 hooks: Vec::new(),
2027 discover_interfaces: false,
2028 discovery_required_value: None,
2029 respond_to_probes: false,
2030 prefer_shorter_path: false,
2031 max_paths_per_destination: 1,
2032 registry: None,
2033 },
2034 Box::new(NoopCallbacks),
2035 )
2036 .unwrap();
2037
2038 let dh = rns_core::types::DestHash([0xBB; 16]);
2039 assert!(node.recall_identity(&dh).unwrap().is_none());
2040
2041 node.shutdown();
2042 }
2043
2044 #[test]
2045 fn request_path_does_not_crash() {
2046 let node = RnsNode::start(
2047 NodeConfig { panic_on_interface_error: false,
2048 transport_enabled: false,
2049 identity: None,
2050 interfaces: vec![],
2051 share_instance: false,
2052 instance_name: "default".into(),
2053 shared_instance_port: 37428,
2054 rpc_port: 0,
2055 cache_dir: None,
2056 management: Default::default(),
2057 probe_port: None,
2058 probe_addrs: vec![],
2059 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
2060 device: None,
2061 hooks: Vec::new(),
2062 discover_interfaces: false,
2063 discovery_required_value: None,
2064 respond_to_probes: false,
2065 prefer_shorter_path: false,
2066 max_paths_per_destination: 1,
2067 registry: None,
2068 },
2069 Box::new(NoopCallbacks),
2070 )
2071 .unwrap();
2072
2073 let dh = rns_core::types::DestHash([0xCC; 16]);
2074 assert!(node.request_path(&dh).is_ok());
2075
2076 thread::sleep(Duration::from_millis(50));
2078
2079 node.shutdown();
2080 }
2081
2082 #[test]
2087 fn send_packet_plain() {
2088 let node = RnsNode::start(
2089 NodeConfig { panic_on_interface_error: false,
2090 transport_enabled: false,
2091 identity: None,
2092 interfaces: vec![],
2093 share_instance: false,
2094 instance_name: "default".into(),
2095 shared_instance_port: 37428,
2096 rpc_port: 0,
2097 cache_dir: None,
2098 management: Default::default(),
2099 probe_port: None,
2100 probe_addrs: vec![],
2101 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
2102 device: None,
2103 hooks: Vec::new(),
2104 discover_interfaces: false,
2105 discovery_required_value: None,
2106 respond_to_probes: false,
2107 prefer_shorter_path: false,
2108 max_paths_per_destination: 1,
2109 registry: None,
2110 },
2111 Box::new(NoopCallbacks),
2112 )
2113 .unwrap();
2114
2115 let dest = crate::destination::Destination::plain("test", &["echo"]);
2116 let result = node.send_packet(&dest, b"hello world");
2117 assert!(result.is_ok());
2118
2119 let packet_hash = result.unwrap();
2120 assert_ne!(packet_hash.0, [0u8; 32]);
2122
2123 thread::sleep(Duration::from_millis(50));
2125
2126 node.shutdown();
2127 }
2128
2129 #[test]
2130 fn send_packet_single_requires_public_key() {
2131 let node = RnsNode::start(
2132 NodeConfig { panic_on_interface_error: false,
2133 transport_enabled: false,
2134 identity: None,
2135 interfaces: vec![],
2136 share_instance: false,
2137 instance_name: "default".into(),
2138 shared_instance_port: 37428,
2139 rpc_port: 0,
2140 cache_dir: None,
2141 management: Default::default(),
2142 probe_port: None,
2143 probe_addrs: vec![],
2144 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
2145 device: None,
2146 hooks: Vec::new(),
2147 discover_interfaces: false,
2148 discovery_required_value: None,
2149 respond_to_probes: false,
2150 prefer_shorter_path: false,
2151 max_paths_per_destination: 1,
2152 registry: None,
2153 },
2154 Box::new(NoopCallbacks),
2155 )
2156 .unwrap();
2157
2158 let dest = crate::destination::Destination::single_in(
2160 "test",
2161 &["echo"],
2162 rns_core::types::IdentityHash([0x42; 16]),
2163 );
2164 let result = node.send_packet(&dest, b"hello");
2165 assert!(result.is_err(), "single_in has no public_key, should fail");
2166
2167 node.shutdown();
2168 }
2169
2170 #[test]
2171 fn send_packet_single_encrypts() {
2172 let node = RnsNode::start(
2173 NodeConfig { panic_on_interface_error: false,
2174 transport_enabled: false,
2175 identity: None,
2176 interfaces: vec![],
2177 share_instance: false,
2178 instance_name: "default".into(),
2179 shared_instance_port: 37428,
2180 rpc_port: 0,
2181 cache_dir: None,
2182 management: Default::default(),
2183 probe_port: None,
2184 probe_addrs: vec![],
2185 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
2186 device: None,
2187 hooks: Vec::new(),
2188 discover_interfaces: false,
2189 discovery_required_value: None,
2190 respond_to_probes: false,
2191 prefer_shorter_path: false,
2192 max_paths_per_destination: 1,
2193 registry: None,
2194 },
2195 Box::new(NoopCallbacks),
2196 )
2197 .unwrap();
2198
2199 let remote_identity = Identity::new(&mut OsRng);
2201 let recalled = crate::destination::AnnouncedIdentity {
2202 dest_hash: rns_core::types::DestHash([0xAA; 16]),
2203 identity_hash: rns_core::types::IdentityHash(*remote_identity.hash()),
2204 public_key: remote_identity.get_public_key().unwrap(),
2205 app_data: None,
2206 hops: 1,
2207 received_at: 0.0,
2208 receiving_interface: rns_core::transport::types::InterfaceId(0),
2209 };
2210 let dest = crate::destination::Destination::single_out("test", &["echo"], &recalled);
2211
2212 let result = node.send_packet(&dest, b"secret message");
2213 assert!(result.is_ok());
2214
2215 let packet_hash = result.unwrap();
2216 assert_ne!(packet_hash.0, [0u8; 32]);
2217
2218 thread::sleep(Duration::from_millis(50));
2219 node.shutdown();
2220 }
2221
2222 #[test]
2223 fn register_destination_with_proof_prove_all() {
2224 let node = RnsNode::start(
2225 NodeConfig { panic_on_interface_error: false,
2226 transport_enabled: false,
2227 identity: None,
2228 interfaces: vec![],
2229 share_instance: false,
2230 instance_name: "default".into(),
2231 shared_instance_port: 37428,
2232 rpc_port: 0,
2233 cache_dir: None,
2234 management: Default::default(),
2235 probe_port: None,
2236 probe_addrs: vec![],
2237 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
2238 device: None,
2239 hooks: Vec::new(),
2240 discover_interfaces: false,
2241 discovery_required_value: None,
2242 respond_to_probes: false,
2243 prefer_shorter_path: false,
2244 max_paths_per_destination: 1,
2245 registry: None,
2246 },
2247 Box::new(NoopCallbacks),
2248 )
2249 .unwrap();
2250
2251 let identity = Identity::new(&mut OsRng);
2252 let ih = rns_core::types::IdentityHash(*identity.hash());
2253 let dest = crate::destination::Destination::single_in("echo", &["request"], ih)
2254 .set_proof_strategy(rns_core::types::ProofStrategy::ProveAll);
2255 let prv_key = identity.get_private_key().unwrap();
2256
2257 let result = node.register_destination_with_proof(&dest, Some(prv_key));
2258 assert!(result.is_ok());
2259
2260 thread::sleep(Duration::from_millis(50));
2262
2263 node.shutdown();
2264 }
2265
2266 #[test]
2267 fn register_destination_with_proof_prove_none() {
2268 let node = RnsNode::start(
2269 NodeConfig { panic_on_interface_error: false,
2270 transport_enabled: false,
2271 identity: None,
2272 interfaces: vec![],
2273 share_instance: false,
2274 instance_name: "default".into(),
2275 shared_instance_port: 37428,
2276 rpc_port: 0,
2277 cache_dir: None,
2278 management: Default::default(),
2279 probe_port: None,
2280 probe_addrs: vec![],
2281 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
2282 device: None,
2283 hooks: Vec::new(),
2284 discover_interfaces: false,
2285 discovery_required_value: None,
2286 respond_to_probes: false,
2287 prefer_shorter_path: false,
2288 max_paths_per_destination: 1,
2289 registry: None,
2290 },
2291 Box::new(NoopCallbacks),
2292 )
2293 .unwrap();
2294
2295 let dest = crate::destination::Destination::plain("test", &["data"])
2297 .set_proof_strategy(rns_core::types::ProofStrategy::ProveNone);
2298
2299 let result = node.register_destination_with_proof(&dest, None);
2300 assert!(result.is_ok());
2301
2302 thread::sleep(Duration::from_millis(50));
2303 node.shutdown();
2304 }
2305}