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