Skip to main content

rns_net/
node.rs

1//! RnsNode: high-level lifecycle management.
2//!
3//! Wires together the driver, interfaces, and timer thread.
4
5use std::io;
6use std::path::Path;
7use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
8use std::sync::Arc;
9use std::thread::{self, JoinHandle};
10use std::time::Duration;
11
12use rns_core::transport::announce_verify_queue::OverflowPolicy as AnnounceQueueOverflowPolicy;
13use rns_core::transport::types::TransportConfig;
14use rns_crypto::identity::Identity;
15use rns_crypto::{OsRng, Rng};
16
17use crate::config;
18use crate::driver::{Callbacks, Driver};
19use crate::event::{self, Event, EventSender};
20use crate::ifac;
21#[cfg(feature = "iface-auto")]
22use crate::interface::auto::{auto_runtime_handle_from_config, AutoConfig};
23#[cfg(feature = "iface-backbone")]
24use crate::interface::backbone::{
25    client_runtime_handle_from_mode, peer_state_handle_from_mode, runtime_handle_from_mode,
26    BackboneMode,
27};
28#[cfg(feature = "iface-i2p")]
29use crate::interface::i2p::{i2p_runtime_handle_from_config, I2pConfig};
30#[cfg(feature = "iface-local")]
31use crate::interface::local::LocalServerConfig;
32#[cfg(feature = "iface-pipe")]
33use crate::interface::pipe::{pipe_runtime_handle_from_config, PipeConfig};
34#[cfg(feature = "iface-rnode")]
35use crate::interface::rnode::{rnode_runtime_handle_from_config, RNodeConfig};
36#[cfg(feature = "iface-tcp")]
37use crate::interface::tcp::{tcp_client_runtime_handle_from_config, TcpClientConfig};
38#[cfg(feature = "iface-tcp")]
39use crate::interface::tcp_server::{
40    runtime_handle_from_config as tcp_runtime_handle_from_config, TcpServerConfig,
41};
42#[cfg(feature = "iface-udp")]
43use crate::interface::udp::{udp_runtime_handle_from_config, UdpConfig};
44use crate::interface::{InterfaceEntry, InterfaceStats};
45use crate::storage;
46use crate::time;
47
48#[cfg(test)]
49const DEFAULT_KNOWN_DESTINATIONS_TTL: Duration = Duration::from_secs(48 * 60 * 60);
50const DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES: usize = 8192;
51
52/// Parse an interface mode string to the corresponding constant.
53/// Matches Python's `_synthesize_interface()` in `RNS/Reticulum.py`.
54fn parse_interface_mode(mode: &str) -> u8 {
55    match mode.to_lowercase().as_str() {
56        "full" => rns_core::constants::MODE_FULL,
57        "access_point" | "accesspoint" | "ap" => rns_core::constants::MODE_ACCESS_POINT,
58        "pointtopoint" | "ptp" => rns_core::constants::MODE_POINT_TO_POINT,
59        "roaming" => rns_core::constants::MODE_ROAMING,
60        "boundary" => rns_core::constants::MODE_BOUNDARY,
61        "gateway" | "gw" => rns_core::constants::MODE_GATEWAY,
62        _ => rns_core::constants::MODE_FULL,
63    }
64}
65
66/// Extract IFAC configuration from interface params, if present.
67/// Returns None if neither networkname/network_name nor passphrase/pass_phrase is set.
68fn extract_ifac_config(
69    params: &std::collections::HashMap<String, String>,
70    default_size: usize,
71) -> Option<IfacConfig> {
72    let netname = params
73        .get("networkname")
74        .or_else(|| params.get("network_name"))
75        .cloned();
76    let netkey = params
77        .get("passphrase")
78        .or_else(|| params.get("pass_phrase"))
79        .cloned();
80
81    if netname.is_none() && netkey.is_none() {
82        return None;
83    }
84
85    // ifac_size is specified in bits in config, divide by 8 for bytes
86    let size = params
87        .get("ifac_size")
88        .and_then(|v| v.parse::<usize>().ok())
89        .map(|bits| (bits / 8).max(1))
90        .unwrap_or(default_size);
91
92    Some(IfacConfig {
93        netname,
94        netkey,
95        size,
96    })
97}
98
99/// Extract discovery configuration from interface params, if `discoverable` is set.
100fn extract_discovery_config(
101    iface_name: &str,
102    iface_type: &str,
103    params: &std::collections::HashMap<String, String>,
104) -> Option<crate::discovery::DiscoveryConfig> {
105    let discoverable = params
106        .get("discoverable")
107        .and_then(|v| config::parse_bool_pub(v))
108        .unwrap_or(false);
109    if !discoverable {
110        return None;
111    }
112
113    let discovery_name = params
114        .get("discovery_name")
115        .cloned()
116        .unwrap_or_else(|| iface_name.to_string());
117
118    // Config value is in seconds. Min 300s (5min), default 21600s (6h).
119    let announce_interval = params
120        .get("announce_interval")
121        .and_then(|v| v.parse::<u64>().ok())
122        .map(|secs| secs.max(300))
123        .unwrap_or(21600);
124
125    let stamp_value = params
126        .get("discovery_stamp_value")
127        .and_then(|v| v.parse::<u8>().ok())
128        .unwrap_or(crate::discovery::DEFAULT_STAMP_VALUE);
129
130    let reachable_on = params.get("reachable_on").cloned();
131
132    let listen_port = params
133        .get("listen_port")
134        .or_else(|| params.get("port"))
135        .and_then(|v| v.parse().ok());
136
137    let latitude = params
138        .get("latitude")
139        .or_else(|| params.get("lat"))
140        .and_then(|v| v.parse().ok());
141    let longitude = params
142        .get("longitude")
143        .or_else(|| params.get("lon"))
144        .and_then(|v| v.parse().ok());
145    let height = params.get("height").and_then(|v| v.parse().ok());
146
147    Some(crate::discovery::DiscoveryConfig {
148        discovery_name,
149        announce_interval,
150        stamp_value,
151        reachable_on,
152        interface_type: iface_type.to_string(),
153        listen_port,
154        latitude,
155        longitude,
156        height,
157    })
158}
159
160#[cfg(feature = "iface-backbone")]
161fn backbone_discovery_runtime_from_interface(
162    interface_name: &str,
163    mode: &BackboneMode,
164    discovery: Option<&crate::discovery::DiscoveryConfig>,
165    transport_enabled: bool,
166    ifac: Option<&IfacConfig>,
167) -> Option<crate::driver::BackboneDiscoveryRuntimeHandle> {
168    let config = match mode {
169        BackboneMode::Server(config) => config,
170        BackboneMode::Client(_) => return None,
171    };
172
173    let startup_config = discovery
174        .cloned()
175        .unwrap_or(crate::discovery::DiscoveryConfig {
176            discovery_name: interface_name.to_string(),
177            announce_interval: 21600,
178            stamp_value: crate::discovery::DEFAULT_STAMP_VALUE,
179            reachable_on: None,
180            interface_type: "BackboneInterface".to_string(),
181            listen_port: Some(config.listen_port),
182            latitude: None,
183            longitude: None,
184            height: None,
185        });
186    let startup = crate::driver::BackboneDiscoveryRuntime {
187        discoverable: discovery.is_some(),
188        config: startup_config,
189        transport_enabled,
190        ifac_netname: ifac.and_then(|cfg| cfg.netname.clone()),
191        ifac_netkey: ifac.and_then(|cfg| cfg.netkey.clone()),
192    };
193
194    Some(crate::driver::BackboneDiscoveryRuntimeHandle {
195        interface_name: config.name.clone(),
196        current: startup.clone(),
197        startup,
198    })
199}
200
201#[cfg(feature = "iface-tcp")]
202fn tcp_server_discovery_runtime_from_interface(
203    interface_name: &str,
204    config: &crate::interface::tcp_server::TcpServerConfig,
205    discovery: Option<&crate::discovery::DiscoveryConfig>,
206    transport_enabled: bool,
207    ifac: Option<&IfacConfig>,
208) -> crate::driver::TcpServerDiscoveryRuntimeHandle {
209    let startup_config = discovery
210        .cloned()
211        .unwrap_or(crate::discovery::DiscoveryConfig {
212            discovery_name: interface_name.to_string(),
213            announce_interval: 21600,
214            stamp_value: crate::discovery::DEFAULT_STAMP_VALUE,
215            reachable_on: None,
216            interface_type: "TCPServerInterface".to_string(),
217            listen_port: Some(config.listen_port),
218            latitude: None,
219            longitude: None,
220            height: None,
221        });
222    let startup = crate::driver::TcpServerDiscoveryRuntime {
223        discoverable: discovery.is_some(),
224        config: startup_config,
225        transport_enabled,
226        ifac_netname: ifac.and_then(|cfg| cfg.netname.clone()),
227        ifac_netkey: ifac.and_then(|cfg| cfg.netkey.clone()),
228    };
229
230    crate::driver::TcpServerDiscoveryRuntimeHandle {
231        interface_name: config.name.clone(),
232        current: startup.clone(),
233        startup,
234    }
235}
236
237/// Top-level node configuration.
238pub struct NodeConfig {
239    pub transport_enabled: bool,
240    pub identity: Option<Identity>,
241    /// Interface configurations (parsed via registry factories).
242    pub interfaces: Vec<InterfaceConfig>,
243    /// Enable shared instance server for local clients (rns-ctl, etc.)
244    pub share_instance: bool,
245    /// Instance name for Unix socket namespace (default: "default").
246    pub instance_name: String,
247    /// Shared instance port for local client connections (default 37428).
248    pub shared_instance_port: u16,
249    /// RPC control port (default 37429). Only used when share_instance is true.
250    pub rpc_port: u16,
251    /// Cache directory for announce cache. If None, announce caching is disabled.
252    pub cache_dir: Option<std::path::PathBuf>,
253    /// Remote management configuration.
254    pub management: crate::management::ManagementConfig,
255    /// Port to run the STUN probe server on (for facilitator nodes).
256    pub probe_port: Option<u16>,
257    /// Addresses of STUN/RNSP probe servers (tried sequentially with failover).
258    pub probe_addrs: Vec<std::net::SocketAddr>,
259    /// Protocol for endpoint discovery: "rnsp" (default) or "stun".
260    pub probe_protocol: rns_core::holepunch::ProbeProtocol,
261    /// Network interface to bind outbound sockets to (e.g. "usb0").
262    pub device: Option<String>,
263    /// Hook configurations loaded from the config file.
264    pub hooks: Vec<config::ParsedHook>,
265    /// Enable interface discovery.
266    pub discover_interfaces: bool,
267    /// Minimum stamp value for accepting discovered interfaces (default: 14).
268    pub discovery_required_value: Option<u8>,
269    /// Respond to probe packets with automatic proof (like Python's respond_to_probes).
270    pub respond_to_probes: bool,
271    /// Accept an announce with strictly fewer hops even when the random_blob
272    /// is a duplicate of the existing path entry.  Default `false` preserves
273    /// Python-compatible anti-replay behaviour.
274    pub prefer_shorter_path: bool,
275    /// Maximum number of alternative paths stored per destination.
276    /// Default 1 (single path, backward-compatible).
277    pub max_paths_per_destination: usize,
278    /// Maximum number of packet hashes retained for duplicate suppression.
279    pub packet_hashlist_max_entries: usize,
280    /// Maximum number of discovery path-request tags remembered.
281    pub max_discovery_pr_tags: usize,
282    /// Maximum number of destination hashes retained in the live path table.
283    pub max_path_destinations: usize,
284    /// Maximum number of retained tunnel-known destinations.
285    pub max_tunnel_destinations_total: usize,
286    /// TTL for recalled known destinations without an active path.
287    pub known_destinations_ttl: Duration,
288    /// Maximum number of recalled known destinations retained.
289    pub known_destinations_max_entries: usize,
290    /// TTL for announce retransmission state.
291    pub announce_table_ttl: Duration,
292    /// Maximum retained bytes for announce retransmission state.
293    pub announce_table_max_bytes: usize,
294    /// Maximum queued events awaiting driver processing.
295    pub driver_event_queue_capacity: usize,
296    /// Maximum queued outbound frames per interface writer worker.
297    pub interface_writer_queue_capacity: usize,
298    /// Whether the announce signature verification cache is enabled.
299    pub announce_sig_cache_enabled: bool,
300    /// Maximum entries in the announce signature verification cache.
301    pub announce_sig_cache_max_entries: usize,
302    /// TTL for announce signature cache entries.
303    pub announce_sig_cache_ttl: Duration,
304    /// Custom interface registry. If `None`, uses `InterfaceRegistry::with_builtins()`.
305    pub registry: Option<crate::interface::registry::InterfaceRegistry>,
306    /// If true, a single interface failing to start will abort the entire node.
307    /// If false (default), the error is logged and remaining interfaces continue.
308    pub panic_on_interface_error: bool,
309    /// External provider bridge for hook-emitted events.
310    #[cfg(feature = "rns-hooks")]
311    pub provider_bridge: Option<crate::provider_bridge::ProviderBridgeConfig>,
312}
313
314impl Default for NodeConfig {
315    fn default() -> Self {
316        Self {
317            transport_enabled: false,
318            identity: None,
319            interfaces: Vec::new(),
320            share_instance: false,
321            instance_name: "default".into(),
322            shared_instance_port: 37428,
323            rpc_port: 0,
324            cache_dir: None,
325            management: Default::default(),
326            probe_port: None,
327            probe_addrs: vec![],
328            probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
329            device: None,
330            hooks: Vec::new(),
331            discover_interfaces: false,
332            discovery_required_value: None,
333            respond_to_probes: false,
334            prefer_shorter_path: false,
335            max_paths_per_destination: 1,
336            packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
337            max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
338            max_path_destinations: rns_core::transport::types::DEFAULT_MAX_PATH_DESTINATIONS,
339            max_tunnel_destinations_total: usize::MAX,
340            known_destinations_ttl: Duration::from_secs(48 * 60 * 60),
341            known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
342            announce_table_ttl: Duration::from_secs(rns_core::constants::ANNOUNCE_TABLE_TTL as u64),
343            announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
344            driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
345            interface_writer_queue_capacity: crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
346            announce_sig_cache_enabled: true,
347            announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
348            announce_sig_cache_ttl: Duration::from_secs(
349                rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
350            ),
351            registry: None,
352            panic_on_interface_error: false,
353            #[cfg(feature = "rns-hooks")]
354            provider_bridge: None,
355        }
356    }
357}
358
359/// IFAC configuration for an interface.
360pub struct IfacConfig {
361    pub netname: Option<String>,
362    pub netkey: Option<String>,
363    pub size: usize,
364}
365
366/// Interface configuration, parsed via an [`InterfaceFactory`] from the registry.
367pub struct InterfaceConfig {
368    pub name: String,
369    pub type_name: String,
370    pub config_data: Box<dyn crate::interface::InterfaceConfigData>,
371    pub mode: u8,
372    pub ifac: Option<IfacConfig>,
373    pub discovery: Option<crate::discovery::DiscoveryConfig>,
374}
375
376use crate::event::{QueryRequest, QueryResponse};
377
378/// Error returned when the driver thread has shut down.
379#[derive(Debug)]
380pub struct SendError;
381
382impl std::fmt::Display for SendError {
383    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
384        write!(f, "driver shut down")
385    }
386}
387
388impl std::error::Error for SendError {}
389
390/// A running RNS node.
391pub struct RnsNode {
392    tx: EventSender,
393    driver_handle: Option<JoinHandle<()>>,
394    verify_handle: Option<JoinHandle<()>>,
395    verify_shutdown: Arc<AtomicBool>,
396    rpc_server: Option<crate::rpc::RpcServer>,
397    tick_interval_ms: Arc<AtomicU64>,
398    #[allow(dead_code)]
399    probe_server: Option<crate::holepunch::probe::ProbeServerHandle>,
400}
401
402impl RnsNode {
403    /// Start the node from a config file path.
404    /// If `config_path` is None, uses `~/.reticulum/`.
405    pub fn from_config(
406        config_path: Option<&Path>,
407        callbacks: Box<dyn Callbacks>,
408    ) -> io::Result<Self> {
409        let config_dir = storage::resolve_config_dir(config_path);
410        let paths = storage::ensure_storage_dirs(&config_dir)?;
411
412        // Parse config file
413        let config_file = config_dir.join("config");
414        let rns_config = if config_file.exists() {
415            config::parse_file(&config_file)
416                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("{}", e)))?
417        } else {
418            // No config file, use defaults
419            config::parse("")
420                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("{}", e)))?
421        };
422
423        // Load or create identity
424        let identity = if let Some(ref id_path_str) = rns_config.reticulum.network_identity {
425            let id_path = std::path::PathBuf::from(id_path_str);
426            if id_path.exists() {
427                storage::load_identity(&id_path)?
428            } else {
429                let id = Identity::new(&mut OsRng);
430                storage::save_identity(&id, &id_path)?;
431                id
432            }
433        } else {
434            storage::load_or_create_identity(&paths.identities)?
435        };
436
437        // Build interface configs from parsed config using registry
438        let registry = crate::interface::registry::InterfaceRegistry::with_builtins();
439        let mut interface_configs = Vec::new();
440        let mut next_id_val = 1u64;
441
442        for iface in &rns_config.interfaces {
443            if !iface.enabled {
444                continue;
445            }
446
447            let iface_id = rns_core::transport::types::InterfaceId(next_id_val);
448            next_id_val += 1;
449
450            let factory = match registry.get(&iface.interface_type) {
451                Some(f) => f,
452                None => {
453                    log::warn!(
454                        "Unsupported interface type '{}' for '{}'",
455                        iface.interface_type,
456                        iface.name,
457                    );
458                    continue;
459                }
460            };
461
462            let mut iface_mode = parse_interface_mode(&iface.mode);
463
464            // Auto-configure mode when discovery is enabled (Python Reticulum.py).
465            let has_discovery = match iface.interface_type.as_str() {
466                "AutoInterface" => true,
467                "RNodeInterface" => iface
468                    .params
469                    .get("discoverable")
470                    .and_then(|v| config::parse_bool_pub(v))
471                    .unwrap_or(false),
472                _ => false,
473            };
474            if has_discovery
475                && iface_mode != rns_core::constants::MODE_ACCESS_POINT
476                && iface_mode != rns_core::constants::MODE_GATEWAY
477            {
478                let new_mode = if iface.interface_type == "RNodeInterface" {
479                    rns_core::constants::MODE_ACCESS_POINT
480                } else {
481                    rns_core::constants::MODE_GATEWAY
482                };
483                log::info!(
484                    "Interface '{}' has discovery enabled, auto-configuring mode to {}",
485                    iface.name,
486                    if new_mode == rns_core::constants::MODE_ACCESS_POINT {
487                        "ACCESS_POINT"
488                    } else {
489                        "GATEWAY"
490                    }
491                );
492                iface_mode = new_mode;
493            }
494
495            let default_ifac_size = factory.default_ifac_size();
496            let ifac_config = extract_ifac_config(&iface.params, default_ifac_size);
497            let discovery_config =
498                extract_discovery_config(&iface.name, &iface.interface_type, &iface.params);
499
500            // Inject storage_dir for I2P (and any future factories that need it)
501            let mut params = iface.params.clone();
502            if !params.contains_key("storage_dir") {
503                params.insert(
504                    "storage_dir".to_string(),
505                    paths.storage.to_string_lossy().to_string(),
506                );
507            }
508            // Inject device for TCP client
509            if let Some(ref device) = rns_config.reticulum.device {
510                if !params.contains_key("device") {
511                    params.insert("device".to_string(), device.clone());
512                }
513            }
514
515            let config_data = match factory.parse_config(&iface.name, iface_id, &params) {
516                Ok(data) => data,
517                Err(e) => {
518                    log::warn!("Failed to parse config for '{}': {}", iface.name, e);
519                    continue;
520                }
521            };
522
523            interface_configs.push(InterfaceConfig {
524                name: iface.name.clone(),
525                type_name: iface.interface_type.clone(),
526                config_data,
527                mode: iface_mode,
528                ifac: ifac_config,
529                discovery: discovery_config,
530            });
531        }
532
533        // Parse management config
534        let mut mgmt_allowed = Vec::new();
535        for hex_hash in &rns_config.reticulum.remote_management_allowed {
536            if hex_hash.len() == 32 {
537                if let Ok(bytes) = (0..hex_hash.len())
538                    .step_by(2)
539                    .map(|i| u8::from_str_radix(&hex_hash[i..i + 2], 16))
540                    .collect::<Result<Vec<u8>, _>>()
541                {
542                    if bytes.len() == 16 {
543                        let mut h = [0u8; 16];
544                        h.copy_from_slice(&bytes);
545                        mgmt_allowed.push(h);
546                    }
547                } else {
548                    log::warn!("Invalid hex in remote_management_allowed: {}", hex_hash);
549                }
550            } else {
551                log::warn!(
552                    "Invalid entry in remote_management_allowed (expected 32 hex chars, got {}): {}",
553                    hex_hash.len(), hex_hash,
554                );
555            }
556        }
557
558        // Parse probe_addr (comma-separated list of SocketAddr)
559        let probe_addrs: Vec<std::net::SocketAddr> = rns_config
560            .reticulum
561            .probe_addr
562            .as_ref()
563            .map(|s| {
564                s.split(',')
565                    .filter_map(|entry| {
566                        let trimmed = entry.trim();
567                        if trimmed.is_empty() {
568                            return None;
569                        }
570                        trimmed
571                            .parse::<std::net::SocketAddr>()
572                            .map_err(|e| {
573                                log::warn!("Invalid probe_addr entry '{}': {}", trimmed, e);
574                                e
575                            })
576                            .ok()
577                    })
578                    .collect()
579            })
580            .unwrap_or_default();
581
582        // Parse probe_protocol (default: rnsp)
583        let probe_protocol = match rns_config
584            .reticulum
585            .probe_protocol
586            .as_deref()
587            .map(|s| s.to_lowercase())
588        {
589            Some(ref s) if s == "stun" => rns_core::holepunch::ProbeProtocol::Stun,
590            _ => rns_core::holepunch::ProbeProtocol::Rnsp,
591        };
592
593        let node_config = NodeConfig {
594            transport_enabled: rns_config.reticulum.enable_transport,
595            identity: Some(identity),
596            share_instance: rns_config.reticulum.share_instance,
597            instance_name: rns_config.reticulum.instance_name.clone(),
598            shared_instance_port: rns_config.reticulum.shared_instance_port,
599            rpc_port: rns_config.reticulum.instance_control_port,
600            cache_dir: Some(paths.cache),
601            management: crate::management::ManagementConfig {
602                enable_remote_management: rns_config.reticulum.enable_remote_management,
603                remote_management_allowed: mgmt_allowed,
604                publish_blackhole: rns_config.reticulum.publish_blackhole,
605            },
606            probe_port: rns_config.reticulum.probe_port,
607            probe_addrs,
608            probe_protocol,
609            device: rns_config.reticulum.device.clone(),
610            hooks: rns_config.hooks.clone(),
611            discover_interfaces: rns_config.reticulum.discover_interfaces,
612            discovery_required_value: rns_config.reticulum.required_discovery_value,
613            respond_to_probes: rns_config.reticulum.respond_to_probes,
614            prefer_shorter_path: rns_config.reticulum.prefer_shorter_path,
615            max_paths_per_destination: rns_config.reticulum.max_paths_per_destination,
616            packet_hashlist_max_entries: rns_config.reticulum.packet_hashlist_max_entries,
617            max_discovery_pr_tags: rns_config.reticulum.max_discovery_pr_tags,
618            max_path_destinations: rns_config.reticulum.max_path_destinations,
619            max_tunnel_destinations_total: rns_config.reticulum.max_tunnel_destinations_total,
620            known_destinations_ttl: Duration::from_secs(
621                rns_config.reticulum.known_destinations_ttl,
622            ),
623            known_destinations_max_entries: rns_config.reticulum.known_destinations_max_entries,
624            announce_table_ttl: Duration::from_secs(rns_config.reticulum.announce_table_ttl),
625            announce_table_max_bytes: rns_config.reticulum.announce_table_max_bytes,
626            driver_event_queue_capacity: rns_config.reticulum.driver_event_queue_capacity,
627            interface_writer_queue_capacity: rns_config.reticulum.interface_writer_queue_capacity,
628            announce_sig_cache_enabled: rns_config.reticulum.announce_sig_cache_enabled,
629            announce_sig_cache_max_entries: rns_config.reticulum.announce_sig_cache_max_entries,
630            announce_sig_cache_ttl: Duration::from_secs(
631                rns_config.reticulum.announce_sig_cache_ttl,
632            ),
633            interfaces: interface_configs,
634            registry: None,
635            panic_on_interface_error: rns_config.reticulum.panic_on_interface_error,
636            #[cfg(feature = "rns-hooks")]
637            provider_bridge: if rns_config.reticulum.provider_bridge {
638                Some(crate::provider_bridge::ProviderBridgeConfig {
639                    enabled: true,
640                    socket_path: rns_config
641                        .reticulum
642                        .provider_socket_path
643                        .as_ref()
644                        .map(std::path::PathBuf::from)
645                        .unwrap_or_else(|| config_dir.join("provider.sock")),
646                    queue_max_events: rns_config.reticulum.provider_queue_max_events,
647                    queue_max_bytes: rns_config.reticulum.provider_queue_max_bytes,
648                    overflow_policy: match rns_config.reticulum.provider_overflow_policy.as_str() {
649                        "drop_oldest" => crate::provider_bridge::OverflowPolicy::DropOldest,
650                        _ => crate::provider_bridge::OverflowPolicy::DropNewest,
651                    },
652                    node_instance: rns_config.reticulum.instance_name.clone(),
653                })
654            } else {
655                None
656            },
657        };
658
659        Self::start_with_announce_queue_max_entries(
660            node_config,
661            callbacks,
662            rns_config.reticulum.announce_queue_max_entries,
663            rns_config.reticulum.announce_queue_max_interfaces,
664            rns_config.reticulum.announce_queue_max_bytes,
665            rns_config.reticulum.announce_queue_ttl as f64,
666            match rns_config.reticulum.announce_queue_overflow_policy.as_str() {
667                "drop_newest" => AnnounceQueueOverflowPolicy::DropNewest,
668                "drop_oldest" => AnnounceQueueOverflowPolicy::DropOldest,
669                _ => AnnounceQueueOverflowPolicy::DropWorst,
670            },
671        )
672    }
673
674    /// Start the node. Connects all interfaces, starts driver and timer threads.
675    pub fn start(config: NodeConfig, callbacks: Box<dyn Callbacks>) -> io::Result<Self> {
676        Self::start_with_announce_queue_max_entries(
677            config,
678            callbacks,
679            256,
680            1024,
681            256 * 1024,
682            30.0,
683            AnnounceQueueOverflowPolicy::DropWorst,
684        )
685    }
686
687    fn start_with_announce_queue_max_entries(
688        config: NodeConfig,
689        callbacks: Box<dyn Callbacks>,
690        announce_queue_max_entries: usize,
691        announce_queue_max_interfaces: usize,
692        announce_queue_max_bytes: usize,
693        announce_queue_ttl_secs: f64,
694        announce_queue_overflow_policy: AnnounceQueueOverflowPolicy,
695    ) -> io::Result<Self> {
696        let identity = config.identity.unwrap_or_else(|| Identity::new(&mut OsRng));
697
698        let transport_config = TransportConfig {
699            transport_enabled: config.transport_enabled,
700            identity_hash: Some(*identity.hash()),
701            prefer_shorter_path: config.prefer_shorter_path,
702            max_paths_per_destination: config.max_paths_per_destination,
703            packet_hashlist_max_entries: config.packet_hashlist_max_entries,
704            max_discovery_pr_tags: config.max_discovery_pr_tags,
705            max_path_destinations: config.max_path_destinations,
706            max_tunnel_destinations_total: config.max_tunnel_destinations_total,
707            destination_timeout_secs: config.known_destinations_ttl.as_secs_f64(),
708            announce_table_ttl_secs: config.announce_table_ttl.as_secs_f64(),
709            announce_table_max_bytes: config.announce_table_max_bytes,
710            announce_sig_cache_enabled: config.announce_sig_cache_enabled,
711            announce_sig_cache_max_entries: config.announce_sig_cache_max_entries,
712            announce_sig_cache_ttl_secs: config.announce_sig_cache_ttl.as_secs_f64(),
713            announce_queue_max_entries,
714            announce_queue_max_interfaces,
715        };
716
717        let (tx, rx) = event::channel_with_capacity(config.driver_event_queue_capacity);
718        let tick_interval_ms = Arc::new(AtomicU64::new(1000));
719        let mut driver = Driver::new(transport_config, rx, tx.clone(), callbacks);
720        driver.set_announce_verify_queue_config(
721            announce_queue_max_entries,
722            announce_queue_max_bytes,
723            announce_queue_ttl_secs,
724            announce_queue_overflow_policy,
725        );
726        driver.async_announce_verification = true;
727        driver.set_tick_interval_handle(Arc::clone(&tick_interval_ms));
728        driver.set_packet_hashlist_max_entries(config.packet_hashlist_max_entries);
729        driver.known_destinations_ttl = config.known_destinations_ttl.as_secs_f64();
730        driver.known_destinations_max_entries = config.known_destinations_max_entries;
731        driver.interface_writer_queue_capacity = config.interface_writer_queue_capacity;
732        driver.runtime_config_defaults.known_destinations_ttl =
733            config.known_destinations_ttl.as_secs_f64();
734
735        #[cfg(feature = "rns-hooks")]
736        if let Some(provider_config) = config.provider_bridge.clone() {
737            driver.runtime_config_defaults.provider_queue_max_events =
738                provider_config.queue_max_events;
739            driver.runtime_config_defaults.provider_queue_max_bytes =
740                provider_config.queue_max_bytes;
741            if provider_config.enabled {
742                match crate::provider_bridge::ProviderBridge::start(provider_config) {
743                    Ok(bridge) => driver.provider_bridge = Some(bridge),
744                    Err(err) => log::warn!("failed to start provider bridge: {}", err),
745                }
746            }
747        }
748
749        // Set up announce cache if cache directory is configured
750        if let Some(ref cache_dir) = config.cache_dir {
751            let announces_dir = cache_dir.join("announces");
752            let _ = std::fs::create_dir_all(&announces_dir);
753            driver.announce_cache = Some(crate::announce_cache::AnnounceCache::new(announces_dir));
754        }
755
756        // Configure probe addresses and device for hole punching
757        if !config.probe_addrs.is_empty() || config.device.is_some() {
758            driver.set_probe_config(
759                config.probe_addrs.clone(),
760                config.probe_protocol,
761                config.device.clone(),
762            );
763        }
764
765        // Start probe server if configured
766        let probe_server = if let Some(port) = config.probe_port {
767            let listen_addr: std::net::SocketAddr = ([0, 0, 0, 0], port).into();
768            match crate::holepunch::probe::start_probe_server(listen_addr) {
769                Ok(handle) => {
770                    log::info!("Probe server started on 0.0.0.0:{}", port);
771                    Some(handle)
772                }
773                Err(e) => {
774                    log::error!("Failed to start probe server on port {}: {}", port, e);
775                    None
776                }
777            }
778        } else {
779            None
780        };
781
782        // Store management config on driver for ACL enforcement
783        driver.management_config = config.management.clone();
784
785        // Store transport identity for tunnel synthesis
786        if let Some(prv_key) = identity.get_private_key() {
787            driver.transport_identity = Some(Identity::from_private_key(&prv_key));
788        }
789
790        // Load hooks from config
791        #[cfg(feature = "rns-hooks")]
792        {
793            for hook_cfg in &config.hooks {
794                if !hook_cfg.enabled {
795                    continue;
796                }
797                let point_idx = match config::parse_hook_point(&hook_cfg.attach_point) {
798                    Some(idx) => idx,
799                    None => {
800                        log::warn!(
801                            "Unknown hook point '{}' for hook '{}'",
802                            hook_cfg.attach_point,
803                            hook_cfg.name,
804                        );
805                        continue;
806                    }
807                };
808                let mgr = match driver.hook_manager.as_ref() {
809                    Some(m) => m,
810                    None => {
811                        log::warn!(
812                            "Hook manager not available, skipping hook '{}'",
813                            hook_cfg.name
814                        );
815                        continue;
816                    }
817                };
818                match mgr.load_file(
819                    hook_cfg.name.clone(),
820                    std::path::Path::new(&hook_cfg.path),
821                    hook_cfg.priority,
822                ) {
823                    Ok(program) => {
824                        driver.hook_slots[point_idx].attach(program);
825                        log::info!(
826                            "Loaded hook '{}' at point {} (priority {})",
827                            hook_cfg.name,
828                            hook_cfg.attach_point,
829                            hook_cfg.priority,
830                        );
831                    }
832                    Err(e) => {
833                        log::error!(
834                            "Failed to load hook '{}' from '{}': {}",
835                            hook_cfg.name,
836                            hook_cfg.path,
837                            e,
838                        );
839                    }
840                }
841            }
842        }
843
844        // Configure discovery
845        driver.discover_interfaces = config.discover_interfaces;
846        if let Some(val) = config.discovery_required_value {
847            driver.discovery_required_value = val;
848        }
849
850        // Shared counter for dynamic interface IDs
851        let next_dynamic_id = Arc::new(AtomicU64::new(10000));
852
853        // Collect discoverable interface configs for the announcer
854        let mut discoverable_interfaces = Vec::new();
855
856        // --- Registry-based startup for interfaces ---
857        let registry = config
858            .registry
859            .unwrap_or_else(crate::interface::registry::InterfaceRegistry::with_builtins);
860        for iface_config in config.interfaces {
861            #[cfg(feature = "iface-backbone")]
862            if iface_config.type_name == "BackboneInterface" {
863                if let Some(mode) = iface_config
864                    .config_data
865                    .as_any()
866                    .downcast_ref::<BackboneMode>()
867                {
868                    if let Some(handle) = runtime_handle_from_mode(mode) {
869                        driver.register_backbone_runtime(handle);
870                    }
871                    if let Some(handle) = peer_state_handle_from_mode(mode) {
872                        driver.register_backbone_peer_state(handle);
873                    }
874                    if let Some(handle) = client_runtime_handle_from_mode(mode) {
875                        driver.register_backbone_client_runtime(handle);
876                    }
877                    if let Some(handle) = backbone_discovery_runtime_from_interface(
878                        &iface_config.name,
879                        mode,
880                        iface_config.discovery.as_ref(),
881                        config.transport_enabled,
882                        iface_config.ifac.as_ref(),
883                    ) {
884                        driver.register_backbone_discovery_runtime(handle);
885                    }
886                }
887            }
888            #[cfg(feature = "iface-tcp")]
889            if iface_config.type_name == "TCPClientInterface" {
890                if let Some(tcp_config) = iface_config
891                    .config_data
892                    .as_any()
893                    .downcast_ref::<TcpClientConfig>()
894                {
895                    driver.register_tcp_client_runtime(tcp_client_runtime_handle_from_config(
896                        tcp_config,
897                    ));
898                }
899            }
900            #[cfg(feature = "iface-tcp")]
901            if iface_config.type_name == "TCPServerInterface" {
902                if let Some(tcp_config) = iface_config
903                    .config_data
904                    .as_any()
905                    .downcast_ref::<TcpServerConfig>()
906                {
907                    driver.register_tcp_server_runtime(tcp_runtime_handle_from_config(tcp_config));
908                    driver.register_tcp_server_discovery_runtime(
909                        tcp_server_discovery_runtime_from_interface(
910                            &iface_config.name,
911                            tcp_config,
912                            iface_config.discovery.as_ref(),
913                            config.transport_enabled,
914                            iface_config.ifac.as_ref(),
915                        ),
916                    );
917                }
918            }
919            #[cfg(feature = "iface-udp")]
920            if iface_config.type_name == "UDPInterface" {
921                if let Some(udp_config) = iface_config
922                    .config_data
923                    .as_any()
924                    .downcast_ref::<UdpConfig>()
925                {
926                    driver.register_udp_runtime(udp_runtime_handle_from_config(udp_config));
927                }
928            }
929            #[cfg(feature = "iface-auto")]
930            if iface_config.type_name == "AutoInterface" {
931                if let Some(auto_config) = iface_config
932                    .config_data
933                    .as_any()
934                    .downcast_ref::<AutoConfig>()
935                {
936                    driver.register_auto_runtime(auto_runtime_handle_from_config(auto_config));
937                }
938            }
939            #[cfg(feature = "iface-i2p")]
940            if iface_config.type_name == "I2PInterface" {
941                if let Some(i2p_config) = iface_config
942                    .config_data
943                    .as_any()
944                    .downcast_ref::<I2pConfig>()
945                {
946                    driver.register_i2p_runtime(i2p_runtime_handle_from_config(i2p_config));
947                }
948            }
949            #[cfg(feature = "iface-pipe")]
950            if iface_config.type_name == "PipeInterface" {
951                if let Some(pipe_config) = iface_config
952                    .config_data
953                    .as_any()
954                    .downcast_ref::<PipeConfig>()
955                {
956                    driver.register_pipe_runtime(pipe_runtime_handle_from_config(pipe_config));
957                }
958            }
959            #[cfg(feature = "iface-rnode")]
960            if iface_config.type_name == "RNodeInterface" {
961                if let Some(rnode_config) = iface_config
962                    .config_data
963                    .as_any()
964                    .downcast_ref::<RNodeConfig>()
965                {
966                    driver.register_rnode_runtime(rnode_runtime_handle_from_config(rnode_config));
967                }
968            }
969
970            let factory = match registry.get(&iface_config.type_name) {
971                Some(f) => f,
972                None => {
973                    log::warn!(
974                        "No factory registered for interface type '{}'",
975                        iface_config.type_name
976                    );
977                    continue;
978                }
979            };
980
981            let mut ifac_state = iface_config.ifac.as_ref().and_then(|ic| {
982                if ic.netname.is_some() || ic.netkey.is_some() {
983                    Some(ifac::derive_ifac(
984                        ic.netname.as_deref(),
985                        ic.netkey.as_deref(),
986                        ic.size,
987                    ))
988                } else {
989                    None
990                }
991            });
992            let ifac_runtime = crate::driver::IfacRuntimeConfig {
993                netname: iface_config.ifac.as_ref().and_then(|ic| ic.netname.clone()),
994                netkey: iface_config.ifac.as_ref().and_then(|ic| ic.netkey.clone()),
995                size: iface_config
996                    .ifac
997                    .as_ref()
998                    .map(|ic| ic.size)
999                    .unwrap_or(factory.default_ifac_size()),
1000            };
1001
1002            let ctx = crate::interface::StartContext {
1003                tx: tx.clone(),
1004                next_dynamic_id: next_dynamic_id.clone(),
1005                mode: iface_config.mode,
1006            };
1007
1008            let result = match factory.start(iface_config.config_data, ctx) {
1009                Ok(r) => r,
1010                Err(e) => {
1011                    if config.panic_on_interface_error {
1012                        return Err(e);
1013                    }
1014                    log::error!(
1015                        "Interface '{}' ({}) failed to start: {}",
1016                        iface_config.name,
1017                        iface_config.type_name,
1018                        e
1019                    );
1020                    continue;
1021                }
1022            };
1023
1024            if let Some(ref disc) = iface_config.discovery {
1025                discoverable_interfaces.push(crate::discovery::DiscoverableInterface {
1026                    interface_name: iface_config.name.clone(),
1027                    config: disc.clone(),
1028                    transport_enabled: config.transport_enabled,
1029                    ifac_netname: iface_config.ifac.as_ref().and_then(|ic| ic.netname.clone()),
1030                    ifac_netkey: iface_config.ifac.as_ref().and_then(|ic| ic.netkey.clone()),
1031                });
1032            }
1033
1034            match result {
1035                crate::interface::StartResult::Simple {
1036                    id,
1037                    info,
1038                    writer,
1039                    interface_type_name,
1040                } => {
1041                    let (writer, async_writer_metrics) = crate::interface::wrap_async_writer(
1042                        writer,
1043                        id,
1044                        &info.name,
1045                        tx.clone(),
1046                        config.interface_writer_queue_capacity,
1047                    );
1048                    driver.register_interface_runtime_defaults(&info);
1049                    driver.register_interface_ifac_runtime(&info.name, ifac_runtime.clone());
1050                    driver.engine.register_interface(info.clone());
1051                    driver.interfaces.insert(
1052                        id,
1053                        InterfaceEntry {
1054                            id,
1055                            info,
1056                            writer,
1057                            async_writer_metrics: Some(async_writer_metrics),
1058                            enabled: true,
1059                            online: false,
1060                            dynamic: false,
1061                            ifac: ifac_state,
1062                            stats: InterfaceStats {
1063                                started: time::now(),
1064                                ..Default::default()
1065                            },
1066                            interface_type: interface_type_name,
1067                            send_retry_at: None,
1068                            send_retry_backoff: Duration::ZERO,
1069                        },
1070                    );
1071                }
1072                crate::interface::StartResult::Listener { control } => {
1073                    // Listener-type interface (TcpServer, Auto, I2P, etc.)
1074                    // registers dynamic interfaces via InterfaceUp events.
1075                    if let Some(control) = control {
1076                        driver.register_listener_control(control);
1077                    }
1078                }
1079                crate::interface::StartResult::Multi(subs) => {
1080                    let ifac_cfg = &iface_config.ifac;
1081                    let mut first = true;
1082                    for sub in subs {
1083                        let (writer, async_writer_metrics) = crate::interface::wrap_async_writer(
1084                            sub.writer,
1085                            sub.id,
1086                            &sub.info.name,
1087                            tx.clone(),
1088                            config.interface_writer_queue_capacity,
1089                        );
1090                        let sub_ifac = if first {
1091                            first = false;
1092                            ifac_state.take()
1093                        } else if let Some(ref ic) = ifac_cfg {
1094                            Some(ifac::derive_ifac(
1095                                ic.netname.as_deref(),
1096                                ic.netkey.as_deref(),
1097                                ic.size,
1098                            ))
1099                        } else {
1100                            None
1101                        };
1102
1103                        driver.register_interface_runtime_defaults(&sub.info);
1104                        driver
1105                            .register_interface_ifac_runtime(&sub.info.name, ifac_runtime.clone());
1106                        driver.engine.register_interface(sub.info.clone());
1107                        driver.interfaces.insert(
1108                            sub.id,
1109                            InterfaceEntry {
1110                                id: sub.id,
1111                                info: sub.info,
1112                                writer,
1113                                async_writer_metrics: Some(async_writer_metrics),
1114                                enabled: true,
1115                                online: false,
1116                                dynamic: false,
1117                                ifac: sub_ifac,
1118                                stats: InterfaceStats {
1119                                    started: time::now(),
1120                                    ..Default::default()
1121                                },
1122                                interface_type: sub.interface_type_name,
1123                                send_retry_at: None,
1124                                send_retry_backoff: Duration::ZERO,
1125                            },
1126                        );
1127                    }
1128                }
1129            }
1130        }
1131
1132        // Set up interface announcer if we have discoverable interfaces
1133        if !discoverable_interfaces.is_empty() {
1134            let transport_id = *identity.hash();
1135            let announcer =
1136                crate::discovery::InterfaceAnnouncer::new(transport_id, discoverable_interfaces);
1137            log::info!("Interface discovery announcer initialized");
1138            driver.interface_announcer = Some(announcer);
1139        }
1140
1141        // Set up discovered interfaces storage path
1142        if let Some(ref cache_dir) = config.cache_dir {
1143            let disc_path = std::path::PathBuf::from(cache_dir)
1144                .parent()
1145                .unwrap_or(std::path::Path::new("."))
1146                .join("storage")
1147                .join("discovery")
1148                .join("interfaces");
1149            let _ = std::fs::create_dir_all(&disc_path);
1150            driver.discovered_interfaces =
1151                crate::discovery::DiscoveredInterfaceStorage::new(disc_path);
1152        }
1153
1154        // Set up management destinations if enabled
1155        if config.management.enable_remote_management {
1156            if let Some(prv_key) = identity.get_private_key() {
1157                let identity_hash = *identity.hash();
1158                let mgmt_dest = crate::management::management_dest_hash(&identity_hash);
1159
1160                // Extract Ed25519 signing keys from the identity
1161                let sig_prv = rns_crypto::ed25519::Ed25519PrivateKey::from_bytes(
1162                    &prv_key[32..64].try_into().unwrap(),
1163                );
1164                let sig_pub_bytes: [u8; 32] = identity.get_public_key().unwrap()[32..64]
1165                    .try_into()
1166                    .unwrap();
1167
1168                // Register as SINGLE destination in transport engine
1169                driver
1170                    .engine
1171                    .register_destination(mgmt_dest, rns_core::constants::DESTINATION_SINGLE);
1172                driver
1173                    .local_destinations
1174                    .insert(mgmt_dest, rns_core::constants::DESTINATION_SINGLE);
1175
1176                // Register as link destination in link manager
1177                driver.link_manager.register_link_destination(
1178                    mgmt_dest,
1179                    sig_prv,
1180                    sig_pub_bytes,
1181                    crate::link_manager::ResourceStrategy::AcceptNone,
1182                );
1183
1184                // Register management path hashes
1185                driver
1186                    .link_manager
1187                    .register_management_path(crate::management::status_path_hash());
1188                driver
1189                    .link_manager
1190                    .register_management_path(crate::management::path_path_hash());
1191
1192                log::info!("Remote management enabled on {:02x?}", &mgmt_dest[..4],);
1193
1194                // Set up allowed list
1195                if !config.management.remote_management_allowed.is_empty() {
1196                    log::info!(
1197                        "Remote management allowed for {} identities",
1198                        config.management.remote_management_allowed.len(),
1199                    );
1200                }
1201            }
1202        }
1203
1204        if config.management.publish_blackhole {
1205            if let Some(prv_key) = identity.get_private_key() {
1206                let identity_hash = *identity.hash();
1207                let bh_dest = crate::management::blackhole_dest_hash(&identity_hash);
1208
1209                let sig_prv = rns_crypto::ed25519::Ed25519PrivateKey::from_bytes(
1210                    &prv_key[32..64].try_into().unwrap(),
1211                );
1212                let sig_pub_bytes: [u8; 32] = identity.get_public_key().unwrap()[32..64]
1213                    .try_into()
1214                    .unwrap();
1215
1216                driver
1217                    .engine
1218                    .register_destination(bh_dest, rns_core::constants::DESTINATION_SINGLE);
1219                driver.link_manager.register_link_destination(
1220                    bh_dest,
1221                    sig_prv,
1222                    sig_pub_bytes,
1223                    crate::link_manager::ResourceStrategy::AcceptNone,
1224                );
1225                driver
1226                    .link_manager
1227                    .register_management_path(crate::management::list_path_hash());
1228
1229                log::info!(
1230                    "Blackhole list publishing enabled on {:02x?}",
1231                    &bh_dest[..4],
1232                );
1233            }
1234        }
1235
1236        // Set up probe responder if enabled
1237        if config.respond_to_probes && config.transport_enabled {
1238            let identity_hash = *identity.hash();
1239            let probe_dest = crate::management::probe_dest_hash(&identity_hash);
1240
1241            // Register as SINGLE destination in transport engine
1242            driver
1243                .engine
1244                .register_destination(probe_dest, rns_core::constants::DESTINATION_SINGLE);
1245            driver
1246                .local_destinations
1247                .insert(probe_dest, rns_core::constants::DESTINATION_SINGLE);
1248
1249            // Register PROVE_ALL proof strategy with transport identity
1250            let probe_identity = rns_crypto::identity::Identity::from_private_key(
1251                &identity.get_private_key().unwrap(),
1252            );
1253            driver.proof_strategies.insert(
1254                probe_dest,
1255                (
1256                    rns_core::types::ProofStrategy::ProveAll,
1257                    Some(probe_identity),
1258                ),
1259            );
1260
1261            driver.probe_responder_hash = Some(probe_dest);
1262
1263            log::info!("Probe responder enabled on {:02x?}", &probe_dest[..4],);
1264        }
1265
1266        // Spawn timer thread with configurable tick interval
1267        let timer_tx = tx.clone();
1268        let timer_interval = Arc::clone(&tick_interval_ms);
1269        thread::Builder::new()
1270            .name("rns-timer".into())
1271            .spawn(move || {
1272                loop {
1273                    let ms = timer_interval.load(Ordering::Relaxed);
1274                    thread::sleep(Duration::from_millis(ms));
1275                    if timer_tx.send(Event::Tick).is_err() {
1276                        break; // receiver dropped
1277                    }
1278                }
1279            })?;
1280
1281        // Start LocalServer for shared instance clients if share_instance is enabled
1282        #[cfg(feature = "iface-local")]
1283        if config.share_instance {
1284            let local_server_config = LocalServerConfig {
1285                instance_name: config.instance_name.clone(),
1286                port: config.shared_instance_port,
1287                interface_id: rns_core::transport::types::InterfaceId(0), // Not used for server
1288            };
1289            match crate::interface::local::start_server(
1290                local_server_config,
1291                tx.clone(),
1292                next_dynamic_id.clone(),
1293            ) {
1294                Ok(control) => {
1295                    driver.register_listener_control(control);
1296                    log::info!(
1297                        "Local shared instance server started (instance={}, port={})",
1298                        config.instance_name,
1299                        config.shared_instance_port
1300                    );
1301                }
1302                Err(e) => {
1303                    log::error!("Failed to start local shared instance server: {}", e);
1304                }
1305            }
1306        }
1307
1308        // Start RPC server if share_instance is enabled
1309        let rpc_server = if config.share_instance {
1310            let auth_key =
1311                crate::rpc::derive_auth_key(&identity.get_private_key().unwrap_or([0u8; 64]));
1312            let rpc_addr = crate::rpc::RpcAddr::Tcp("127.0.0.1".into(), config.rpc_port);
1313            match crate::rpc::RpcServer::start(&rpc_addr, auth_key, tx.clone()) {
1314                Ok(server) => {
1315                    log::info!("RPC server started on 127.0.0.1:{}", config.rpc_port);
1316                    Some(server)
1317                }
1318                Err(e) => {
1319                    log::error!("Failed to start RPC server: {}", e);
1320                    None
1321                }
1322            }
1323        } else {
1324            None
1325        };
1326
1327        let announce_verify_queue = Arc::clone(&driver.announce_verify_queue);
1328        let verify_shutdown = Arc::new(AtomicBool::new(false));
1329        let verify_shutdown_thread = Arc::clone(&verify_shutdown);
1330        let verify_tx = tx.clone();
1331        let verify_handle = thread::Builder::new()
1332            .name("rns-verify".into())
1333            .spawn(move || {
1334                #[cfg(target_family = "unix")]
1335                {
1336                    unsafe {
1337                        libc::nice(5);
1338                    }
1339                }
1340
1341                while !verify_shutdown_thread.load(Ordering::Relaxed) {
1342                    let batch = {
1343                        let mut queue = announce_verify_queue
1344                            .lock()
1345                            .unwrap_or_else(|poisoned| poisoned.into_inner());
1346                        queue.take_pending(time::now())
1347                    };
1348
1349                    if batch.is_empty() {
1350                        thread::sleep(Duration::from_millis(50));
1351                        continue;
1352                    }
1353
1354                    for (key, pending) in batch {
1355                        if verify_shutdown_thread.load(Ordering::Relaxed) {
1356                            break;
1357                        }
1358                        let has_ratchet =
1359                            pending.packet.flags.context_flag == rns_core::constants::FLAG_SET;
1360                        let announce = match rns_core::announce::AnnounceData::unpack(
1361                            &pending.packet.data,
1362                            has_ratchet,
1363                        ) {
1364                            Ok(announce) => announce,
1365                            Err(_) => {
1366                                let signature = [0u8; 64];
1367                                let sig_cache_key = {
1368                                    let mut material = [0u8; 80];
1369                                    material[..16]
1370                                        .copy_from_slice(&pending.packet.destination_hash);
1371                                    material[16..].copy_from_slice(&signature);
1372                                    rns_core::hash::full_hash(&material)
1373                                };
1374                                if verify_tx
1375                                    .send(Event::AnnounceVerifyFailed { key, sig_cache_key })
1376                                    .is_err()
1377                                {
1378                                    return;
1379                                }
1380                                continue;
1381                            }
1382                        };
1383                        let mut material = [0u8; 80];
1384                        material[..16].copy_from_slice(&pending.packet.destination_hash);
1385                        material[16..].copy_from_slice(&announce.signature);
1386                        let sig_cache_key = rns_core::hash::full_hash(&material);
1387                        match announce.validate(&pending.packet.destination_hash) {
1388                            Ok(validated) => {
1389                                if verify_tx
1390                                    .send(Event::AnnounceVerified {
1391                                        key,
1392                                        validated,
1393                                        sig_cache_key,
1394                                    })
1395                                    .is_err()
1396                                {
1397                                    return;
1398                                }
1399                            }
1400                            Err(_) => {
1401                                if verify_tx
1402                                    .send(Event::AnnounceVerifyFailed { key, sig_cache_key })
1403                                    .is_err()
1404                                {
1405                                    return;
1406                                }
1407                            }
1408                        }
1409                    }
1410                }
1411            })?;
1412
1413        // Spawn driver thread
1414        let driver_handle = thread::Builder::new()
1415            .name("rns-driver".into())
1416            .spawn(move || {
1417                driver.run();
1418            })?;
1419
1420        Ok(RnsNode {
1421            tx,
1422            driver_handle: Some(driver_handle),
1423            verify_handle: Some(verify_handle),
1424            verify_shutdown,
1425            rpc_server,
1426            tick_interval_ms,
1427            probe_server,
1428        })
1429    }
1430
1431    /// Query the driver for state information.
1432    pub fn query(&self, request: QueryRequest) -> Result<QueryResponse, SendError> {
1433        let (resp_tx, resp_rx) = std::sync::mpsc::channel();
1434        self.tx
1435            .send(Event::Query(request, resp_tx))
1436            .map_err(|_| SendError)?;
1437        resp_rx.recv().map_err(|_| SendError)
1438    }
1439
1440    /// Enter drain mode and stop admitting new work.
1441    pub fn begin_drain(&self, timeout: Duration) -> Result<(), SendError> {
1442        self.tx
1443            .send(Event::BeginDrain { timeout })
1444            .map_err(|_| SendError)
1445    }
1446
1447    /// Query current drain/lifecycle status.
1448    pub fn drain_status(&self) -> Result<crate::event::DrainStatus, SendError> {
1449        match self.query(QueryRequest::DrainStatus)? {
1450            QueryResponse::DrainStatus(status) => Ok(status),
1451            _ => Err(SendError),
1452        }
1453    }
1454
1455    fn reject_new_work_if_draining(&self) -> Result<(), SendError> {
1456        let status = self.drain_status()?;
1457        if matches!(status.state, crate::event::LifecycleState::Active) {
1458            Ok(())
1459        } else {
1460            Err(SendError)
1461        }
1462    }
1463
1464    /// Send a raw outbound packet.
1465    pub fn send_raw(
1466        &self,
1467        raw: Vec<u8>,
1468        dest_type: u8,
1469        attached_interface: Option<rns_core::transport::types::InterfaceId>,
1470    ) -> Result<(), SendError> {
1471        self.tx
1472            .send(Event::SendOutbound {
1473                raw,
1474                dest_type,
1475                attached_interface,
1476            })
1477            .map_err(|_| SendError)
1478    }
1479
1480    /// Register a local destination with the transport engine.
1481    pub fn register_destination(
1482        &self,
1483        dest_hash: [u8; 16],
1484        dest_type: u8,
1485    ) -> Result<(), SendError> {
1486        self.tx
1487            .send(Event::RegisterDestination {
1488                dest_hash,
1489                dest_type,
1490            })
1491            .map_err(|_| SendError)
1492    }
1493
1494    /// Deregister a local destination.
1495    pub fn deregister_destination(&self, dest_hash: [u8; 16]) -> Result<(), SendError> {
1496        self.tx
1497            .send(Event::DeregisterDestination { dest_hash })
1498            .map_err(|_| SendError)
1499    }
1500
1501    /// Deregister a link destination (stop accepting incoming links).
1502    pub fn deregister_link_destination(&self, dest_hash: [u8; 16]) -> Result<(), SendError> {
1503        self.tx
1504            .send(Event::DeregisterLinkDestination { dest_hash })
1505            .map_err(|_| SendError)
1506    }
1507
1508    /// Register a link destination that can accept incoming links.
1509    ///
1510    /// `dest_hash`: the destination hash
1511    /// `sig_prv_bytes`: Ed25519 private signing key (32 bytes)
1512    /// `sig_pub_bytes`: Ed25519 public signing key (32 bytes)
1513    pub fn register_link_destination(
1514        &self,
1515        dest_hash: [u8; 16],
1516        sig_prv_bytes: [u8; 32],
1517        sig_pub_bytes: [u8; 32],
1518        resource_strategy: u8,
1519    ) -> Result<(), SendError> {
1520        self.tx
1521            .send(Event::RegisterLinkDestination {
1522                dest_hash,
1523                sig_prv_bytes,
1524                sig_pub_bytes,
1525                resource_strategy,
1526            })
1527            .map_err(|_| SendError)
1528    }
1529
1530    /// Register a request handler for a given path on established links.
1531    pub fn register_request_handler<F>(
1532        &self,
1533        path: &str,
1534        allowed_list: Option<Vec<[u8; 16]>>,
1535        handler: F,
1536    ) -> Result<(), SendError>
1537    where
1538        F: Fn([u8; 16], &str, &[u8], Option<&([u8; 16], [u8; 64])>) -> Option<Vec<u8>>
1539            + Send
1540            + 'static,
1541    {
1542        self.tx
1543            .send(Event::RegisterRequestHandler {
1544                path: path.to_string(),
1545                allowed_list,
1546                handler: Box::new(handler),
1547            })
1548            .map_err(|_| SendError)
1549    }
1550
1551    /// Create an outbound link to a destination.
1552    ///
1553    /// Returns the link_id on success.
1554    pub fn create_link(
1555        &self,
1556        dest_hash: [u8; 16],
1557        dest_sig_pub_bytes: [u8; 32],
1558    ) -> Result<[u8; 16], SendError> {
1559        self.reject_new_work_if_draining()?;
1560        let (response_tx, response_rx) = std::sync::mpsc::channel();
1561        self.tx
1562            .send(Event::CreateLink {
1563                dest_hash,
1564                dest_sig_pub_bytes,
1565                response_tx,
1566            })
1567            .map_err(|_| SendError)?;
1568        let link_id = response_rx.recv().map_err(|_| SendError)?;
1569        if link_id == [0u8; 16] {
1570            Err(SendError)
1571        } else {
1572            Ok(link_id)
1573        }
1574    }
1575
1576    /// Send a request on an established link.
1577    pub fn send_request(
1578        &self,
1579        link_id: [u8; 16],
1580        path: &str,
1581        data: &[u8],
1582    ) -> Result<(), SendError> {
1583        self.reject_new_work_if_draining()?;
1584        self.tx
1585            .send(Event::SendRequest {
1586                link_id,
1587                path: path.to_string(),
1588                data: data.to_vec(),
1589            })
1590            .map_err(|_| SendError)
1591    }
1592
1593    /// Identify on a link (reveal identity to remote peer).
1594    pub fn identify_on_link(
1595        &self,
1596        link_id: [u8; 16],
1597        identity_prv_key: [u8; 64],
1598    ) -> Result<(), SendError> {
1599        self.reject_new_work_if_draining()?;
1600        self.tx
1601            .send(Event::IdentifyOnLink {
1602                link_id,
1603                identity_prv_key,
1604            })
1605            .map_err(|_| SendError)
1606    }
1607
1608    /// Tear down a link.
1609    pub fn teardown_link(&self, link_id: [u8; 16]) -> Result<(), SendError> {
1610        self.tx
1611            .send(Event::TeardownLink { link_id })
1612            .map_err(|_| SendError)
1613    }
1614
1615    /// Send a resource on an established link.
1616    pub fn send_resource(
1617        &self,
1618        link_id: [u8; 16],
1619        data: Vec<u8>,
1620        metadata: Option<Vec<u8>>,
1621    ) -> Result<(), SendError> {
1622        self.reject_new_work_if_draining()?;
1623        self.tx
1624            .send(Event::SendResource {
1625                link_id,
1626                data,
1627                metadata,
1628            })
1629            .map_err(|_| SendError)
1630    }
1631
1632    /// Set the resource acceptance strategy for a link.
1633    ///
1634    /// 0 = AcceptNone, 1 = AcceptAll, 2 = AcceptApp
1635    pub fn set_resource_strategy(&self, link_id: [u8; 16], strategy: u8) -> Result<(), SendError> {
1636        self.tx
1637            .send(Event::SetResourceStrategy { link_id, strategy })
1638            .map_err(|_| SendError)
1639    }
1640
1641    /// Accept or reject a pending resource (for AcceptApp strategy).
1642    pub fn accept_resource(
1643        &self,
1644        link_id: [u8; 16],
1645        resource_hash: Vec<u8>,
1646        accept: bool,
1647    ) -> Result<(), SendError> {
1648        if accept {
1649            self.reject_new_work_if_draining()?;
1650        }
1651        self.tx
1652            .send(Event::AcceptResource {
1653                link_id,
1654                resource_hash,
1655                accept,
1656            })
1657            .map_err(|_| SendError)
1658    }
1659
1660    /// Send a channel message on a link.
1661    pub fn send_channel_message(
1662        &self,
1663        link_id: [u8; 16],
1664        msgtype: u16,
1665        payload: Vec<u8>,
1666    ) -> Result<(), SendError> {
1667        self.reject_new_work_if_draining()?;
1668        let (response_tx, response_rx) = std::sync::mpsc::channel();
1669        self.tx
1670            .send(Event::SendChannelMessage {
1671                link_id,
1672                msgtype,
1673                payload,
1674                response_tx,
1675            })
1676            .map_err(|_| SendError)?;
1677        response_rx
1678            .recv()
1679            .map_err(|_| SendError)?
1680            .map_err(|_| SendError)
1681    }
1682
1683    /// Propose a direct P2P connection to a peer via NAT hole punching.
1684    ///
1685    /// The link must be active and connected through a backbone node.
1686    /// If successful, a direct UDP connection will be established, bypassing the backbone.
1687    pub fn propose_direct_connect(&self, link_id: [u8; 16]) -> Result<(), SendError> {
1688        self.reject_new_work_if_draining()?;
1689        self.tx
1690            .send(Event::ProposeDirectConnect { link_id })
1691            .map_err(|_| SendError)
1692    }
1693
1694    /// Set the policy for handling incoming direct-connect proposals.
1695    pub fn set_direct_connect_policy(
1696        &self,
1697        policy: crate::holepunch::orchestrator::HolePunchPolicy,
1698    ) -> Result<(), SendError> {
1699        self.tx
1700            .send(Event::SetDirectConnectPolicy { policy })
1701            .map_err(|_| SendError)
1702    }
1703
1704    /// Send data on a link with a given context.
1705    pub fn send_on_link(
1706        &self,
1707        link_id: [u8; 16],
1708        data: Vec<u8>,
1709        context: u8,
1710    ) -> Result<(), SendError> {
1711        self.reject_new_work_if_draining()?;
1712        self.tx
1713            .send(Event::SendOnLink {
1714                link_id,
1715                data,
1716                context,
1717            })
1718            .map_err(|_| SendError)
1719    }
1720
1721    /// Build and broadcast an announce for a destination.
1722    ///
1723    /// The identity is used to sign the announce. Must be the identity that
1724    /// owns the destination (i.e. `identity.hash()` matches `dest.identity_hash`).
1725    pub fn announce(
1726        &self,
1727        dest: &crate::destination::Destination,
1728        identity: &Identity,
1729        app_data: Option<&[u8]>,
1730    ) -> Result<(), SendError> {
1731        self.reject_new_work_if_draining()?;
1732        let name_hash = rns_core::destination::name_hash(
1733            &dest.app_name,
1734            &dest.aspects.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
1735        );
1736
1737        let mut random_hash = [0u8; 10];
1738        OsRng.fill_bytes(&mut random_hash[..5]);
1739        // Bytes [5:10] must be the emission timestamp (seconds since epoch,
1740        // big-endian, truncated to 5 bytes) so that path table dedup can
1741        // compare announce freshness.  Matches Python: int(time.time()).to_bytes(5, "big")
1742        let now_secs = std::time::SystemTime::now()
1743            .duration_since(std::time::UNIX_EPOCH)
1744            .unwrap_or_default()
1745            .as_secs();
1746        random_hash[5..10].copy_from_slice(&now_secs.to_be_bytes()[3..8]);
1747
1748        let (announce_data, _has_ratchet) = rns_core::announce::AnnounceData::pack(
1749            identity,
1750            &dest.hash.0,
1751            &name_hash,
1752            &random_hash,
1753            None, // no ratchet
1754            app_data,
1755        )
1756        .map_err(|_| SendError)?;
1757
1758        let context_flag = rns_core::constants::FLAG_UNSET;
1759
1760        let flags = rns_core::packet::PacketFlags {
1761            header_type: rns_core::constants::HEADER_1,
1762            context_flag,
1763            transport_type: rns_core::constants::TRANSPORT_BROADCAST,
1764            destination_type: rns_core::constants::DESTINATION_SINGLE,
1765            packet_type: rns_core::constants::PACKET_TYPE_ANNOUNCE,
1766        };
1767
1768        let packet = rns_core::packet::RawPacket::pack(
1769            flags,
1770            0,
1771            &dest.hash.0,
1772            None,
1773            rns_core::constants::CONTEXT_NONE,
1774            &announce_data,
1775        )
1776        .map_err(|_| SendError)?;
1777
1778        if dest.dest_type == rns_core::types::DestinationType::Single {
1779            if let Some(identity_prv_key) = identity.get_private_key() {
1780                self.tx
1781                    .send(Event::StoreSharedAnnounce {
1782                        dest_hash: dest.hash.0,
1783                        name_hash,
1784                        identity_prv_key,
1785                        app_data: app_data.map(|d| d.to_vec()),
1786                    })
1787                    .map_err(|_| SendError)?;
1788            }
1789        }
1790
1791        self.send_raw(packet.raw, dest.dest_type.to_wire_constant(), None)
1792    }
1793
1794    /// Send an encrypted (SINGLE) or plaintext (PLAIN) packet to a destination.
1795    ///
1796    /// For SINGLE destinations, `dest.public_key` must be set (OUT direction).
1797    /// Returns the packet hash for proof tracking.
1798    pub fn send_packet(
1799        &self,
1800        dest: &crate::destination::Destination,
1801        data: &[u8],
1802    ) -> Result<rns_core::types::PacketHash, SendError> {
1803        self.reject_new_work_if_draining()?;
1804        use rns_core::types::DestinationType;
1805
1806        let payload = match dest.dest_type {
1807            DestinationType::Single => {
1808                let pub_key = dest.public_key.ok_or(SendError)?;
1809                let remote_id = rns_crypto::identity::Identity::from_public_key(&pub_key);
1810                remote_id.encrypt(data, &mut OsRng).map_err(|_| SendError)?
1811            }
1812            DestinationType::Plain => data.to_vec(),
1813            DestinationType::Group => dest.encrypt(data).map_err(|_| SendError)?,
1814        };
1815
1816        let flags = rns_core::packet::PacketFlags {
1817            header_type: rns_core::constants::HEADER_1,
1818            context_flag: rns_core::constants::FLAG_UNSET,
1819            transport_type: rns_core::constants::TRANSPORT_BROADCAST,
1820            destination_type: dest.dest_type.to_wire_constant(),
1821            packet_type: rns_core::constants::PACKET_TYPE_DATA,
1822        };
1823
1824        let packet = rns_core::packet::RawPacket::pack(
1825            flags,
1826            0,
1827            &dest.hash.0,
1828            None,
1829            rns_core::constants::CONTEXT_NONE,
1830            &payload,
1831        )
1832        .map_err(|_| SendError)?;
1833
1834        let packet_hash = rns_core::types::PacketHash(packet.packet_hash);
1835
1836        self.tx
1837            .send(Event::SendOutbound {
1838                raw: packet.raw,
1839                dest_type: dest.dest_type.to_wire_constant(),
1840                attached_interface: None,
1841            })
1842            .map_err(|_| SendError)?;
1843
1844        Ok(packet_hash)
1845    }
1846
1847    /// Register a destination with the transport engine and set its proof strategy.
1848    ///
1849    /// `signing_key` is the full 64-byte identity private key (X25519 32 bytes +
1850    /// Ed25519 32 bytes), needed for ProveAll/ProveApp to sign proof packets.
1851    pub fn register_destination_with_proof(
1852        &self,
1853        dest: &crate::destination::Destination,
1854        signing_key: Option<[u8; 64]>,
1855    ) -> Result<(), SendError> {
1856        // Register with transport engine
1857        self.register_destination(dest.hash.0, dest.dest_type.to_wire_constant())?;
1858
1859        // Register proof strategy if not ProveNone
1860        if dest.proof_strategy != rns_core::types::ProofStrategy::ProveNone {
1861            self.tx
1862                .send(Event::RegisterProofStrategy {
1863                    dest_hash: dest.hash.0,
1864                    strategy: dest.proof_strategy,
1865                    signing_key,
1866                })
1867                .map_err(|_| SendError)?;
1868        }
1869
1870        Ok(())
1871    }
1872
1873    /// Request a path to a destination from the network.
1874    pub fn request_path(&self, dest_hash: &rns_core::types::DestHash) -> Result<(), SendError> {
1875        self.reject_new_work_if_draining()?;
1876        self.tx
1877            .send(Event::RequestPath {
1878                dest_hash: dest_hash.0,
1879            })
1880            .map_err(|_| SendError)
1881    }
1882
1883    /// Check if a path exists to a destination (synchronous query).
1884    pub fn has_path(&self, dest_hash: &rns_core::types::DestHash) -> Result<bool, SendError> {
1885        match self.query(QueryRequest::HasPath {
1886            dest_hash: dest_hash.0,
1887        })? {
1888            QueryResponse::HasPath(v) => Ok(v),
1889            _ => Ok(false),
1890        }
1891    }
1892
1893    /// Get hop count to a destination (synchronous query).
1894    pub fn hops_to(&self, dest_hash: &rns_core::types::DestHash) -> Result<Option<u8>, SendError> {
1895        match self.query(QueryRequest::HopsTo {
1896            dest_hash: dest_hash.0,
1897        })? {
1898            QueryResponse::HopsTo(v) => Ok(v),
1899            _ => Ok(None),
1900        }
1901    }
1902
1903    /// Recall the identity information for a previously announced destination.
1904    pub fn recall_identity(
1905        &self,
1906        dest_hash: &rns_core::types::DestHash,
1907    ) -> Result<Option<crate::destination::AnnouncedIdentity>, SendError> {
1908        match self.query(QueryRequest::RecallIdentity {
1909            dest_hash: dest_hash.0,
1910        })? {
1911            QueryResponse::RecallIdentity(v) => Ok(v),
1912            _ => Ok(None),
1913        }
1914    }
1915
1916    /// Load a WASM hook at runtime.
1917    pub fn load_hook(
1918        &self,
1919        name: String,
1920        wasm_bytes: Vec<u8>,
1921        attach_point: String,
1922        priority: i32,
1923    ) -> Result<Result<(), String>, SendError> {
1924        let (response_tx, response_rx) = std::sync::mpsc::channel();
1925        self.tx
1926            .send(Event::LoadHook {
1927                name,
1928                wasm_bytes,
1929                attach_point,
1930                priority,
1931                response_tx,
1932            })
1933            .map_err(|_| SendError)?;
1934        response_rx.recv().map_err(|_| SendError)
1935    }
1936
1937    /// Unload a WASM hook at runtime.
1938    pub fn unload_hook(
1939        &self,
1940        name: String,
1941        attach_point: String,
1942    ) -> Result<Result<(), String>, SendError> {
1943        let (response_tx, response_rx) = std::sync::mpsc::channel();
1944        self.tx
1945            .send(Event::UnloadHook {
1946                name,
1947                attach_point,
1948                response_tx,
1949            })
1950            .map_err(|_| SendError)?;
1951        response_rx.recv().map_err(|_| SendError)
1952    }
1953
1954    /// Reload a WASM hook at runtime (detach + recompile + reattach with same priority).
1955    pub fn reload_hook(
1956        &self,
1957        name: String,
1958        attach_point: String,
1959        wasm_bytes: Vec<u8>,
1960    ) -> Result<Result<(), String>, SendError> {
1961        let (response_tx, response_rx) = std::sync::mpsc::channel();
1962        self.tx
1963            .send(Event::ReloadHook {
1964                name,
1965                attach_point,
1966                wasm_bytes,
1967                response_tx,
1968            })
1969            .map_err(|_| SendError)?;
1970        response_rx.recv().map_err(|_| SendError)
1971    }
1972
1973    /// Enable or disable a loaded WASM hook at runtime.
1974    pub fn set_hook_enabled(
1975        &self,
1976        name: String,
1977        attach_point: String,
1978        enabled: bool,
1979    ) -> Result<Result<(), String>, SendError> {
1980        let (response_tx, response_rx) = std::sync::mpsc::channel();
1981        self.tx
1982            .send(Event::SetHookEnabled {
1983                name,
1984                attach_point,
1985                enabled,
1986                response_tx,
1987            })
1988            .map_err(|_| SendError)?;
1989        response_rx.recv().map_err(|_| SendError)
1990    }
1991
1992    /// Update the priority of a loaded WASM hook at runtime.
1993    pub fn set_hook_priority(
1994        &self,
1995        name: String,
1996        attach_point: String,
1997        priority: i32,
1998    ) -> Result<Result<(), String>, SendError> {
1999        let (response_tx, response_rx) = std::sync::mpsc::channel();
2000        self.tx
2001            .send(Event::SetHookPriority {
2002                name,
2003                attach_point,
2004                priority,
2005                response_tx,
2006            })
2007            .map_err(|_| SendError)?;
2008        response_rx.recv().map_err(|_| SendError)
2009    }
2010
2011    /// List all loaded hooks.
2012    pub fn list_hooks(&self) -> Result<Vec<crate::event::HookInfo>, SendError> {
2013        let (response_tx, response_rx) = std::sync::mpsc::channel();
2014        self.tx
2015            .send(Event::ListHooks { response_tx })
2016            .map_err(|_| SendError)?;
2017        response_rx.recv().map_err(|_| SendError)
2018    }
2019
2020    /// Construct an RnsNode from its constituent parts.
2021    /// Used by `shared_client` to build a client-mode node.
2022    pub(crate) fn from_parts(
2023        tx: EventSender,
2024        driver_handle: thread::JoinHandle<()>,
2025        rpc_server: Option<crate::rpc::RpcServer>,
2026        tick_interval_ms: Arc<AtomicU64>,
2027    ) -> Self {
2028        RnsNode {
2029            tx,
2030            driver_handle: Some(driver_handle),
2031            verify_handle: None,
2032            verify_shutdown: Arc::new(AtomicBool::new(false)),
2033            rpc_server,
2034            tick_interval_ms,
2035            probe_server: None,
2036        }
2037    }
2038
2039    /// Get the event sender for direct event injection.
2040    pub fn event_sender(&self) -> &EventSender {
2041        &self.tx
2042    }
2043
2044    /// Set the tick interval in milliseconds.
2045    /// Default is 1000 (1 second). Changes take effect on the next tick cycle.
2046    /// Values are clamped to the range 100..=10000.
2047    /// Returns the actual stored value (which may differ from `ms` if clamped).
2048    pub fn set_tick_interval(&self, ms: u64) -> u64 {
2049        let clamped = ms.clamp(100, 10_000);
2050        if clamped != ms {
2051            log::warn!(
2052                "tick interval {}ms out of range, clamped to {}ms",
2053                ms,
2054                clamped
2055            );
2056        }
2057        self.tick_interval_ms.store(clamped, Ordering::Relaxed);
2058        clamped
2059    }
2060
2061    /// Get the current tick interval in milliseconds.
2062    pub fn tick_interval(&self) -> u64 {
2063        self.tick_interval_ms.load(Ordering::Relaxed)
2064    }
2065
2066    /// Shut down the node. Blocks until the driver thread exits.
2067    pub fn shutdown(mut self) {
2068        // Stop RPC server first
2069        if let Some(mut rpc) = self.rpc_server.take() {
2070            rpc.stop();
2071        }
2072        self.verify_shutdown.store(true, Ordering::Relaxed);
2073        let _ = self.tx.send(Event::Shutdown);
2074        if let Some(handle) = self.driver_handle.take() {
2075            let _ = handle.join();
2076        }
2077        if let Some(handle) = self.verify_handle.take() {
2078            let _ = handle.join();
2079        }
2080    }
2081}
2082
2083#[cfg(test)]
2084mod tests {
2085    use super::*;
2086    use std::fs;
2087
2088    struct NoopCallbacks;
2089
2090    impl Callbacks for NoopCallbacks {
2091        fn on_announce(&mut self, _: crate::destination::AnnouncedIdentity) {}
2092        fn on_path_updated(&mut self, _: rns_core::types::DestHash, _: u8) {}
2093        fn on_local_delivery(
2094            &mut self,
2095            _: rns_core::types::DestHash,
2096            _: Vec<u8>,
2097            _: rns_core::types::PacketHash,
2098        ) {
2099        }
2100    }
2101
2102    #[test]
2103    fn start_and_shutdown() {
2104        let node = RnsNode::start(
2105            NodeConfig {
2106                panic_on_interface_error: false,
2107                transport_enabled: false,
2108                identity: None,
2109                interfaces: vec![],
2110                share_instance: false,
2111                instance_name: "default".into(),
2112                shared_instance_port: 37428,
2113                rpc_port: 0,
2114                cache_dir: None,
2115                management: Default::default(),
2116                probe_port: None,
2117                probe_addrs: vec![],
2118                probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
2119                device: None,
2120                hooks: Vec::new(),
2121                discover_interfaces: false,
2122                discovery_required_value: None,
2123                respond_to_probes: false,
2124                prefer_shorter_path: false,
2125                max_paths_per_destination: 1,
2126                packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
2127                max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
2128                max_path_destinations: usize::MAX,
2129                max_tunnel_destinations_total: usize::MAX,
2130                known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
2131                known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
2132                announce_table_ttl: Duration::from_secs(
2133                    rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
2134                ),
2135                announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
2136                driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
2137                interface_writer_queue_capacity:
2138                    crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
2139                announce_sig_cache_enabled: true,
2140                announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
2141                announce_sig_cache_ttl: Duration::from_secs(
2142                    rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
2143                ),
2144                registry: None,
2145                #[cfg(feature = "rns-hooks")]
2146                provider_bridge: None,
2147            },
2148            Box::new(NoopCallbacks),
2149        )
2150        .unwrap();
2151        node.shutdown();
2152    }
2153
2154    #[test]
2155    fn start_with_identity() {
2156        let identity = Identity::new(&mut OsRng);
2157        let hash = *identity.hash();
2158        let node = RnsNode::start(
2159            NodeConfig {
2160                panic_on_interface_error: false,
2161                transport_enabled: true,
2162                identity: Some(identity),
2163                interfaces: vec![],
2164                share_instance: false,
2165                instance_name: "default".into(),
2166                shared_instance_port: 37428,
2167                rpc_port: 0,
2168                cache_dir: None,
2169                management: Default::default(),
2170                probe_port: None,
2171                probe_addrs: vec![],
2172                probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
2173                device: None,
2174                hooks: Vec::new(),
2175                discover_interfaces: false,
2176                discovery_required_value: None,
2177                respond_to_probes: false,
2178                prefer_shorter_path: false,
2179                max_paths_per_destination: 1,
2180                packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
2181                max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
2182                max_path_destinations: usize::MAX,
2183                max_tunnel_destinations_total: usize::MAX,
2184                known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
2185                known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
2186                announce_table_ttl: Duration::from_secs(
2187                    rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
2188                ),
2189                announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
2190                driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
2191                interface_writer_queue_capacity:
2192                    crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
2193                announce_sig_cache_enabled: true,
2194                announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
2195                announce_sig_cache_ttl: Duration::from_secs(
2196                    rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
2197                ),
2198                registry: None,
2199                #[cfg(feature = "rns-hooks")]
2200                provider_bridge: None,
2201            },
2202            Box::new(NoopCallbacks),
2203        )
2204        .unwrap();
2205        // The identity hash should have been used
2206        let _ = hash;
2207        node.shutdown();
2208    }
2209
2210    #[test]
2211    fn start_generates_identity() {
2212        let node = RnsNode::start(
2213            NodeConfig {
2214                panic_on_interface_error: false,
2215                transport_enabled: false,
2216                identity: None,
2217                interfaces: vec![],
2218                share_instance: false,
2219                instance_name: "default".into(),
2220                shared_instance_port: 37428,
2221                rpc_port: 0,
2222                cache_dir: None,
2223                management: Default::default(),
2224                probe_port: None,
2225                probe_addrs: vec![],
2226                probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
2227                device: None,
2228                hooks: Vec::new(),
2229                discover_interfaces: false,
2230                discovery_required_value: None,
2231                respond_to_probes: false,
2232                prefer_shorter_path: false,
2233                max_paths_per_destination: 1,
2234                packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
2235                max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
2236                max_path_destinations: usize::MAX,
2237                max_tunnel_destinations_total: usize::MAX,
2238                known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
2239                known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
2240                announce_table_ttl: Duration::from_secs(
2241                    rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
2242                ),
2243                announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
2244                driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
2245                interface_writer_queue_capacity:
2246                    crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
2247                announce_sig_cache_enabled: true,
2248                announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
2249                announce_sig_cache_ttl: Duration::from_secs(
2250                    rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
2251                ),
2252                registry: None,
2253                #[cfg(feature = "rns-hooks")]
2254                provider_bridge: None,
2255            },
2256            Box::new(NoopCallbacks),
2257        )
2258        .unwrap();
2259        // Should not panic - identity was auto-generated
2260        node.shutdown();
2261    }
2262
2263    #[test]
2264    fn from_config_creates_identity() {
2265        let dir = std::env::temp_dir().join(format!("rns-test-fc-{}", std::process::id()));
2266        let _ = fs::remove_dir_all(&dir);
2267        fs::create_dir_all(&dir).unwrap();
2268
2269        // Write a minimal config file
2270        fs::write(
2271            dir.join("config"),
2272            "[reticulum]\nenable_transport = False\n",
2273        )
2274        .unwrap();
2275
2276        let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
2277
2278        // Identity file should have been created
2279        assert!(dir.join("storage/identities/identity").exists());
2280
2281        node.shutdown();
2282        let _ = fs::remove_dir_all(&dir);
2283    }
2284
2285    #[test]
2286    fn from_config_loads_identity() {
2287        let dir = std::env::temp_dir().join(format!("rns-test-fl-{}", std::process::id()));
2288        let _ = fs::remove_dir_all(&dir);
2289        fs::create_dir_all(dir.join("storage/identities")).unwrap();
2290
2291        // Pre-create an identity
2292        let identity = Identity::new(&mut OsRng);
2293        let hash = *identity.hash();
2294        storage::save_identity(&identity, &dir.join("storage/identities/identity")).unwrap();
2295
2296        fs::write(
2297            dir.join("config"),
2298            "[reticulum]\nenable_transport = False\n",
2299        )
2300        .unwrap();
2301
2302        let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
2303
2304        // Verify the same identity was loaded (hash matches)
2305        let loaded = storage::load_identity(&dir.join("storage/identities/identity")).unwrap();
2306        assert_eq!(*loaded.hash(), hash);
2307
2308        node.shutdown();
2309        let _ = fs::remove_dir_all(&dir);
2310    }
2311
2312    #[test]
2313    fn from_config_tcp_server() {
2314        let dir = std::env::temp_dir().join(format!("rns-test-fts-{}", std::process::id()));
2315        let _ = fs::remove_dir_all(&dir);
2316        fs::create_dir_all(&dir).unwrap();
2317
2318        // Find a free port
2319        let port = std::net::TcpListener::bind("127.0.0.1:0")
2320            .unwrap()
2321            .local_addr()
2322            .unwrap()
2323            .port();
2324
2325        let config = format!(
2326            r#"
2327[reticulum]
2328enable_transport = False
2329
2330[interfaces]
2331  [[Test TCP Server]]
2332    type = TCPServerInterface
2333    listen_ip = 127.0.0.1
2334    listen_port = {}
2335"#,
2336            port
2337        );
2338
2339        fs::write(dir.join("config"), config).unwrap();
2340
2341        let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
2342
2343        // Give server time to start
2344        thread::sleep(Duration::from_millis(100));
2345
2346        // Should be able to connect
2347        let _client = std::net::TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2348
2349        node.shutdown();
2350        let _ = fs::remove_dir_all(&dir);
2351    }
2352
2353    #[test]
2354    fn from_config_starts_rpc_when_share_instance_enabled() {
2355        let dir = std::env::temp_dir().join(format!("rns-test-rpc-{}", std::process::id()));
2356        let _ = fs::remove_dir_all(&dir);
2357        fs::create_dir_all(&dir).unwrap();
2358
2359        let rpc_port = std::net::TcpListener::bind("127.0.0.1:0")
2360            .unwrap()
2361            .local_addr()
2362            .unwrap()
2363            .port();
2364
2365        let config = format!(
2366            r#"
2367[reticulum]
2368enable_transport = False
2369share_instance = Yes
2370instance_control_port = {}
2371
2372[interfaces]
2373"#,
2374            rpc_port
2375        );
2376
2377        fs::write(dir.join("config"), config).unwrap();
2378
2379        let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
2380
2381        thread::sleep(Duration::from_millis(100));
2382
2383        let _client = std::net::TcpStream::connect(format!("127.0.0.1:{}", rpc_port)).unwrap();
2384
2385        node.shutdown();
2386        let _ = fs::remove_dir_all(&dir);
2387    }
2388
2389    #[test]
2390    fn from_config_starts_rpc_when_transport_enabled() {
2391        let dir =
2392            std::env::temp_dir().join(format!("rns-test-rpc-transport-{}", std::process::id()));
2393        let _ = fs::remove_dir_all(&dir);
2394        fs::create_dir_all(&dir).unwrap();
2395
2396        let rpc_port = std::net::TcpListener::bind("127.0.0.1:0")
2397            .unwrap()
2398            .local_addr()
2399            .unwrap()
2400            .port();
2401
2402        let config = format!(
2403            r#"
2404[reticulum]
2405enable_transport = True
2406share_instance = Yes
2407instance_control_port = {}
2408
2409[interfaces]
2410"#,
2411            rpc_port
2412        );
2413
2414        fs::write(dir.join("config"), config).unwrap();
2415
2416        let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
2417
2418        thread::sleep(Duration::from_millis(100));
2419
2420        let _client = std::net::TcpStream::connect(format!("127.0.0.1:{}", rpc_port)).unwrap();
2421
2422        node.shutdown();
2423        let _ = fs::remove_dir_all(&dir);
2424    }
2425
2426    #[test]
2427    fn from_config_starts_rpc_when_tcp_client_is_unreachable() {
2428        let dir =
2429            std::env::temp_dir().join(format!("rns-test-rpc-unreachable-{}", std::process::id()));
2430        let _ = fs::remove_dir_all(&dir);
2431        fs::create_dir_all(&dir).unwrap();
2432
2433        let rpc_port = std::net::TcpListener::bind("127.0.0.1:0")
2434            .unwrap()
2435            .local_addr()
2436            .unwrap()
2437            .port();
2438        let unreachable_port = std::net::TcpListener::bind("127.0.0.1:0")
2439            .unwrap()
2440            .local_addr()
2441            .unwrap()
2442            .port();
2443
2444        let config = format!(
2445            r#"
2446[reticulum]
2447enable_transport = True
2448share_instance = Yes
2449instance_control_port = {}
2450
2451[interfaces]
2452  [[Unreachable Upstream]]
2453    type = TCPClientInterface
2454    target_host = 127.0.0.1
2455    target_port = {}
2456"#,
2457            rpc_port, unreachable_port
2458        );
2459
2460        fs::write(dir.join("config"), config).unwrap();
2461
2462        let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
2463
2464        thread::sleep(Duration::from_millis(100));
2465
2466        let _client = std::net::TcpStream::connect(format!("127.0.0.1:{}", rpc_port)).unwrap();
2467
2468        node.shutdown();
2469        let _ = fs::remove_dir_all(&dir);
2470    }
2471
2472    #[test]
2473    fn test_parse_interface_mode() {
2474        use rns_core::constants::*;
2475
2476        assert_eq!(parse_interface_mode("full"), MODE_FULL);
2477        assert_eq!(parse_interface_mode("Full"), MODE_FULL);
2478        assert_eq!(parse_interface_mode("access_point"), MODE_ACCESS_POINT);
2479        assert_eq!(parse_interface_mode("accesspoint"), MODE_ACCESS_POINT);
2480        assert_eq!(parse_interface_mode("ap"), MODE_ACCESS_POINT);
2481        assert_eq!(parse_interface_mode("AP"), MODE_ACCESS_POINT);
2482        assert_eq!(parse_interface_mode("pointtopoint"), MODE_POINT_TO_POINT);
2483        assert_eq!(parse_interface_mode("ptp"), MODE_POINT_TO_POINT);
2484        assert_eq!(parse_interface_mode("roaming"), MODE_ROAMING);
2485        assert_eq!(parse_interface_mode("boundary"), MODE_BOUNDARY);
2486        assert_eq!(parse_interface_mode("gateway"), MODE_GATEWAY);
2487        assert_eq!(parse_interface_mode("gw"), MODE_GATEWAY);
2488        // Unknown defaults to FULL
2489        assert_eq!(parse_interface_mode("invalid"), MODE_FULL);
2490    }
2491
2492    #[test]
2493    fn to_node_config_serial() {
2494        // Verify from_config parses SerialInterface correctly.
2495        // The serial port won't exist, so start() will fail, but the config
2496        // parsing path is exercised. We verify via the error (not a config error).
2497        let dir = std::env::temp_dir().join(format!("rns-test-serial-{}", std::process::id()));
2498        let _ = fs::remove_dir_all(&dir);
2499        fs::create_dir_all(&dir).unwrap();
2500
2501        let config = r#"
2502[reticulum]
2503enable_transport = False
2504
2505[interfaces]
2506  [[Test Serial Port]]
2507    type = SerialInterface
2508    port = /dev/nonexistent_rns_test_serial
2509    speed = 115200
2510    databits = 8
2511    parity = E
2512    stopbits = 1
2513    interface_mode = ptp
2514    networkname = testnet
2515"#;
2516        fs::write(dir.join("config"), config).unwrap();
2517
2518        // Interface error is non-fatal: the node starts but logs the error.
2519        let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks))
2520            .expect("Config should parse; interface failure is non-fatal");
2521        node.shutdown();
2522
2523        let _ = fs::remove_dir_all(&dir);
2524    }
2525
2526    #[test]
2527    fn to_node_config_kiss() {
2528        // Verify from_config parses KISSInterface correctly.
2529        let dir = std::env::temp_dir().join(format!("rns-test-kiss-{}", std::process::id()));
2530        let _ = fs::remove_dir_all(&dir);
2531        fs::create_dir_all(&dir).unwrap();
2532
2533        let config = r#"
2534[reticulum]
2535enable_transport = False
2536
2537[interfaces]
2538  [[Test KISS TNC]]
2539    type = KISSInterface
2540    port = /dev/nonexistent_rns_test_kiss
2541    speed = 9600
2542    preamble = 500
2543    txtail = 30
2544    persistence = 128
2545    slottime = 40
2546    flow_control = True
2547    id_interval = 600
2548    id_callsign = TEST0
2549    interface_mode = full
2550    passphrase = secretkey
2551"#;
2552        fs::write(dir.join("config"), config).unwrap();
2553
2554        // Interface error is non-fatal: the node starts but logs the error.
2555        let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks))
2556            .expect("Config should parse; interface failure is non-fatal");
2557        node.shutdown();
2558
2559        let _ = fs::remove_dir_all(&dir);
2560    }
2561
2562    #[test]
2563    fn test_extract_ifac_config() {
2564        use std::collections::HashMap;
2565
2566        // No IFAC params → None
2567        let params: HashMap<String, String> = HashMap::new();
2568        assert!(extract_ifac_config(&params, 16).is_none());
2569
2570        // networkname only
2571        let mut params = HashMap::new();
2572        params.insert("networkname".into(), "testnet".into());
2573        let ifac = extract_ifac_config(&params, 16).unwrap();
2574        assert_eq!(ifac.netname.as_deref(), Some("testnet"));
2575        assert!(ifac.netkey.is_none());
2576        assert_eq!(ifac.size, 16);
2577
2578        // passphrase only with custom size (in bits)
2579        let mut params = HashMap::new();
2580        params.insert("passphrase".into(), "secret".into());
2581        params.insert("ifac_size".into(), "64".into()); // 64 bits = 8 bytes
2582        let ifac = extract_ifac_config(&params, 16).unwrap();
2583        assert!(ifac.netname.is_none());
2584        assert_eq!(ifac.netkey.as_deref(), Some("secret"));
2585        assert_eq!(ifac.size, 8);
2586
2587        // Both with alternate key names
2588        let mut params = HashMap::new();
2589        params.insert("network_name".into(), "mynet".into());
2590        params.insert("pass_phrase".into(), "mykey".into());
2591        let ifac = extract_ifac_config(&params, 8).unwrap();
2592        assert_eq!(ifac.netname.as_deref(), Some("mynet"));
2593        assert_eq!(ifac.netkey.as_deref(), Some("mykey"));
2594        assert_eq!(ifac.size, 8);
2595    }
2596
2597    #[test]
2598    fn to_node_config_rnode() {
2599        // Verify from_config parses RNodeInterface correctly.
2600        // The serial port won't exist, so start() will fail at open time.
2601        let dir = std::env::temp_dir().join(format!("rns-test-rnode-{}", std::process::id()));
2602        let _ = fs::remove_dir_all(&dir);
2603        fs::create_dir_all(&dir).unwrap();
2604
2605        let config = r#"
2606[reticulum]
2607enable_transport = False
2608
2609[interfaces]
2610  [[Test RNode]]
2611    type = RNodeInterface
2612    port = /dev/nonexistent_rns_test_rnode
2613    frequency = 867200000
2614    bandwidth = 125000
2615    txpower = 7
2616    spreadingfactor = 8
2617    codingrate = 5
2618    flow_control = True
2619    st_alock = 5.0
2620    lt_alock = 2.5
2621    interface_mode = full
2622    networkname = testnet
2623"#;
2624        fs::write(dir.join("config"), config).unwrap();
2625
2626        // Interface error is non-fatal: the node starts but logs the error.
2627        let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks))
2628            .expect("Config should parse; interface failure is non-fatal");
2629        node.shutdown();
2630
2631        let _ = fs::remove_dir_all(&dir);
2632    }
2633
2634    #[test]
2635    fn to_node_config_pipe() {
2636        // Verify from_config parses PipeInterface correctly.
2637        // Use `cat` as a real command so it actually starts.
2638        let dir = std::env::temp_dir().join(format!("rns-test-pipe-{}", std::process::id()));
2639        let _ = fs::remove_dir_all(&dir);
2640        fs::create_dir_all(&dir).unwrap();
2641
2642        let config = r#"
2643[reticulum]
2644enable_transport = False
2645
2646[interfaces]
2647  [[Test Pipe]]
2648    type = PipeInterface
2649    command = cat
2650    respawn_delay = 5000
2651    interface_mode = full
2652"#;
2653        fs::write(dir.join("config"), config).unwrap();
2654
2655        let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
2656        // If we got here, config parsing and start() succeeded
2657        node.shutdown();
2658
2659        let _ = fs::remove_dir_all(&dir);
2660    }
2661
2662    #[test]
2663    fn to_node_config_backbone() {
2664        // Verify from_config parses BackboneInterface correctly.
2665        let dir = std::env::temp_dir().join(format!("rns-test-backbone-{}", std::process::id()));
2666        let _ = fs::remove_dir_all(&dir);
2667        fs::create_dir_all(&dir).unwrap();
2668
2669        let port = std::net::TcpListener::bind("127.0.0.1:0")
2670            .unwrap()
2671            .local_addr()
2672            .unwrap()
2673            .port();
2674
2675        let config = format!(
2676            r#"
2677[reticulum]
2678enable_transport = False
2679
2680[interfaces]
2681  [[Test Backbone]]
2682    type = BackboneInterface
2683    listen_ip = 127.0.0.1
2684    listen_port = {}
2685    interface_mode = full
2686"#,
2687            port
2688        );
2689
2690        fs::write(dir.join("config"), config).unwrap();
2691
2692        let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
2693
2694        // Give server time to start
2695        thread::sleep(Duration::from_millis(100));
2696
2697        // Should be able to connect
2698        {
2699            let _client = std::net::TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2700            // client drops here, closing the connection cleanly
2701        }
2702
2703        // Small delay to let epoll process the disconnect
2704        thread::sleep(Duration::from_millis(50));
2705
2706        node.shutdown();
2707        let _ = fs::remove_dir_all(&dir);
2708    }
2709
2710    #[test]
2711    fn rnode_config_defaults() {
2712        use crate::interface::rnode::{RNodeConfig, RNodeSubConfig};
2713
2714        let config = RNodeConfig::default();
2715        assert_eq!(config.speed, 115200);
2716        assert!(config.subinterfaces.is_empty());
2717        assert!(config.id_interval.is_none());
2718        assert!(config.id_callsign.is_none());
2719
2720        let sub = RNodeSubConfig {
2721            name: "test".into(),
2722            frequency: 868_000_000,
2723            bandwidth: 125_000,
2724            txpower: 7,
2725            spreading_factor: 8,
2726            coding_rate: 5,
2727            flow_control: false,
2728            st_alock: None,
2729            lt_alock: None,
2730        };
2731        assert_eq!(sub.frequency, 868_000_000);
2732        assert_eq!(sub.bandwidth, 125_000);
2733        assert!(!sub.flow_control);
2734    }
2735
2736    // =========================================================================
2737    // Phase 9c: Announce + Discovery node-level tests
2738    // =========================================================================
2739
2740    #[test]
2741    fn announce_builds_valid_packet() {
2742        let identity = Identity::new(&mut OsRng);
2743        let identity_hash = rns_core::types::IdentityHash(*identity.hash());
2744
2745        let node = RnsNode::start(
2746            NodeConfig {
2747                panic_on_interface_error: false,
2748                transport_enabled: false,
2749                identity: None,
2750                interfaces: vec![],
2751                share_instance: false,
2752                instance_name: "default".into(),
2753                shared_instance_port: 37428,
2754                rpc_port: 0,
2755                cache_dir: None,
2756                management: Default::default(),
2757                probe_port: None,
2758                probe_addrs: vec![],
2759                probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
2760                device: None,
2761                hooks: Vec::new(),
2762                discover_interfaces: false,
2763                discovery_required_value: None,
2764                respond_to_probes: false,
2765                prefer_shorter_path: false,
2766                max_paths_per_destination: 1,
2767                packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
2768                max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
2769                max_path_destinations: usize::MAX,
2770                max_tunnel_destinations_total: usize::MAX,
2771                known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
2772                known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
2773                announce_table_ttl: Duration::from_secs(
2774                    rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
2775                ),
2776                announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
2777                driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
2778                interface_writer_queue_capacity:
2779                    crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
2780                announce_sig_cache_enabled: true,
2781                announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
2782                announce_sig_cache_ttl: Duration::from_secs(
2783                    rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
2784                ),
2785                registry: None,
2786                #[cfg(feature = "rns-hooks")]
2787                provider_bridge: None,
2788            },
2789            Box::new(NoopCallbacks),
2790        )
2791        .unwrap();
2792
2793        let dest = crate::destination::Destination::single_in("test", &["echo"], identity_hash);
2794
2795        // Register destination first
2796        node.register_destination(dest.hash.0, dest.dest_type.to_wire_constant())
2797            .unwrap();
2798
2799        // Announce should succeed (though no interfaces to send on)
2800        let result = node.announce(&dest, &identity, Some(b"hello"));
2801        assert!(result.is_ok());
2802
2803        node.shutdown();
2804    }
2805
2806    #[test]
2807    fn has_path_and_hops_to() {
2808        let node = RnsNode::start(
2809            NodeConfig {
2810                panic_on_interface_error: false,
2811                transport_enabled: false,
2812                identity: None,
2813                interfaces: vec![],
2814                share_instance: false,
2815                instance_name: "default".into(),
2816                shared_instance_port: 37428,
2817                rpc_port: 0,
2818                cache_dir: None,
2819                management: Default::default(),
2820                probe_port: None,
2821                probe_addrs: vec![],
2822                probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
2823                device: None,
2824                hooks: Vec::new(),
2825                discover_interfaces: false,
2826                discovery_required_value: None,
2827                respond_to_probes: false,
2828                prefer_shorter_path: false,
2829                max_paths_per_destination: 1,
2830                packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
2831                max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
2832                max_path_destinations: usize::MAX,
2833                max_tunnel_destinations_total: usize::MAX,
2834                known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
2835                known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
2836                announce_table_ttl: Duration::from_secs(
2837                    rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
2838                ),
2839                announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
2840                driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
2841                interface_writer_queue_capacity:
2842                    crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
2843                announce_sig_cache_enabled: true,
2844                announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
2845                announce_sig_cache_ttl: Duration::from_secs(
2846                    rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
2847                ),
2848                registry: None,
2849                #[cfg(feature = "rns-hooks")]
2850                provider_bridge: None,
2851            },
2852            Box::new(NoopCallbacks),
2853        )
2854        .unwrap();
2855
2856        let dh = rns_core::types::DestHash([0xAA; 16]);
2857
2858        // No path should exist
2859        assert_eq!(node.has_path(&dh).unwrap(), false);
2860        assert_eq!(node.hops_to(&dh).unwrap(), None);
2861
2862        node.shutdown();
2863    }
2864
2865    #[test]
2866    fn recall_identity_none_when_unknown() {
2867        let node = RnsNode::start(
2868            NodeConfig {
2869                panic_on_interface_error: false,
2870                transport_enabled: false,
2871                identity: None,
2872                interfaces: vec![],
2873                share_instance: false,
2874                instance_name: "default".into(),
2875                shared_instance_port: 37428,
2876                rpc_port: 0,
2877                cache_dir: None,
2878                management: Default::default(),
2879                probe_port: None,
2880                probe_addrs: vec![],
2881                probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
2882                device: None,
2883                hooks: Vec::new(),
2884                discover_interfaces: false,
2885                discovery_required_value: None,
2886                respond_to_probes: false,
2887                prefer_shorter_path: false,
2888                max_paths_per_destination: 1,
2889                packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
2890                max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
2891                max_path_destinations: usize::MAX,
2892                max_tunnel_destinations_total: usize::MAX,
2893                known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
2894                known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
2895                announce_table_ttl: Duration::from_secs(
2896                    rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
2897                ),
2898                announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
2899                driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
2900                interface_writer_queue_capacity:
2901                    crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
2902                announce_sig_cache_enabled: true,
2903                announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
2904                announce_sig_cache_ttl: Duration::from_secs(
2905                    rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
2906                ),
2907                registry: None,
2908                #[cfg(feature = "rns-hooks")]
2909                provider_bridge: None,
2910            },
2911            Box::new(NoopCallbacks),
2912        )
2913        .unwrap();
2914
2915        let dh = rns_core::types::DestHash([0xBB; 16]);
2916        assert!(node.recall_identity(&dh).unwrap().is_none());
2917
2918        node.shutdown();
2919    }
2920
2921    #[test]
2922    fn request_path_does_not_crash() {
2923        let node = RnsNode::start(
2924            NodeConfig {
2925                panic_on_interface_error: false,
2926                transport_enabled: false,
2927                identity: None,
2928                interfaces: vec![],
2929                share_instance: false,
2930                instance_name: "default".into(),
2931                shared_instance_port: 37428,
2932                rpc_port: 0,
2933                cache_dir: None,
2934                management: Default::default(),
2935                probe_port: None,
2936                probe_addrs: vec![],
2937                probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
2938                device: None,
2939                hooks: Vec::new(),
2940                discover_interfaces: false,
2941                discovery_required_value: None,
2942                respond_to_probes: false,
2943                prefer_shorter_path: false,
2944                max_paths_per_destination: 1,
2945                packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
2946                max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
2947                max_path_destinations: usize::MAX,
2948                max_tunnel_destinations_total: usize::MAX,
2949                known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
2950                known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
2951                announce_table_ttl: Duration::from_secs(
2952                    rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
2953                ),
2954                announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
2955                driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
2956                interface_writer_queue_capacity:
2957                    crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
2958                announce_sig_cache_enabled: true,
2959                announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
2960                announce_sig_cache_ttl: Duration::from_secs(
2961                    rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
2962                ),
2963                registry: None,
2964                #[cfg(feature = "rns-hooks")]
2965                provider_bridge: None,
2966            },
2967            Box::new(NoopCallbacks),
2968        )
2969        .unwrap();
2970
2971        let dh = rns_core::types::DestHash([0xCC; 16]);
2972        assert!(node.request_path(&dh).is_ok());
2973
2974        // Small wait for the event to be processed
2975        thread::sleep(Duration::from_millis(50));
2976
2977        node.shutdown();
2978    }
2979
2980    #[test]
2981    fn create_link_returns_error_while_draining() {
2982        let node = RnsNode::start(NodeConfig::default(), Box::new(NoopCallbacks)).unwrap();
2983
2984        node.begin_drain(Duration::from_secs(1)).unwrap();
2985        assert!(node.create_link([0xAB; 16], [0xCD; 32]).is_err());
2986
2987        node.shutdown();
2988    }
2989
2990    #[test]
2991    fn request_path_returns_error_while_draining() {
2992        let node = RnsNode::start(NodeConfig::default(), Box::new(NoopCallbacks)).unwrap();
2993
2994        node.begin_drain(Duration::from_secs(1)).unwrap();
2995        assert!(node.request_path(&rns_core::types::DestHash([0xAB; 16])).is_err());
2996
2997        node.shutdown();
2998    }
2999
3000    // =========================================================================
3001    // Phase 9d: send_packet + register_destination_with_proof tests
3002    // =========================================================================
3003
3004    #[test]
3005    fn send_packet_returns_error_while_draining() {
3006        let node = RnsNode::start(NodeConfig::default(), Box::new(NoopCallbacks)).unwrap();
3007        let dest = crate::destination::Destination::plain("drain-test", &["send"]);
3008
3009        node.begin_drain(Duration::from_secs(1)).unwrap();
3010        assert!(node.send_packet(&dest, b"hello").is_err());
3011
3012        node.shutdown();
3013    }
3014
3015    #[test]
3016    fn send_packet_plain() {
3017        let node = RnsNode::start(
3018            NodeConfig {
3019                panic_on_interface_error: false,
3020                transport_enabled: false,
3021                identity: None,
3022                interfaces: vec![],
3023                share_instance: false,
3024                instance_name: "default".into(),
3025                shared_instance_port: 37428,
3026                rpc_port: 0,
3027                cache_dir: None,
3028                management: Default::default(),
3029                probe_port: None,
3030                probe_addrs: vec![],
3031                probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
3032                device: None,
3033                hooks: Vec::new(),
3034                discover_interfaces: false,
3035                discovery_required_value: None,
3036                respond_to_probes: false,
3037                prefer_shorter_path: false,
3038                max_paths_per_destination: 1,
3039                packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
3040                max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
3041                max_path_destinations: usize::MAX,
3042                max_tunnel_destinations_total: usize::MAX,
3043                known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
3044                known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
3045                announce_table_ttl: Duration::from_secs(
3046                    rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
3047                ),
3048                announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
3049                driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
3050                interface_writer_queue_capacity:
3051                    crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
3052                announce_sig_cache_enabled: true,
3053                announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
3054                announce_sig_cache_ttl: Duration::from_secs(
3055                    rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
3056                ),
3057                registry: None,
3058                #[cfg(feature = "rns-hooks")]
3059                provider_bridge: None,
3060            },
3061            Box::new(NoopCallbacks),
3062        )
3063        .unwrap();
3064
3065        let dest = crate::destination::Destination::plain("test", &["echo"]);
3066        let result = node.send_packet(&dest, b"hello world");
3067        assert!(result.is_ok());
3068
3069        let packet_hash = result.unwrap();
3070        // Packet hash should be non-zero
3071        assert_ne!(packet_hash.0, [0u8; 32]);
3072
3073        // Small wait for the event to be processed
3074        thread::sleep(Duration::from_millis(50));
3075
3076        node.shutdown();
3077    }
3078
3079    #[test]
3080    fn send_packet_single_requires_public_key() {
3081        let node = RnsNode::start(
3082            NodeConfig {
3083                panic_on_interface_error: false,
3084                transport_enabled: false,
3085                identity: None,
3086                interfaces: vec![],
3087                share_instance: false,
3088                instance_name: "default".into(),
3089                shared_instance_port: 37428,
3090                rpc_port: 0,
3091                cache_dir: None,
3092                management: Default::default(),
3093                probe_port: None,
3094                probe_addrs: vec![],
3095                probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
3096                device: None,
3097                hooks: Vec::new(),
3098                discover_interfaces: false,
3099                discovery_required_value: None,
3100                respond_to_probes: false,
3101                prefer_shorter_path: false,
3102                max_paths_per_destination: 1,
3103                packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
3104                max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
3105                max_path_destinations: usize::MAX,
3106                max_tunnel_destinations_total: usize::MAX,
3107                known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
3108                known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
3109                announce_table_ttl: Duration::from_secs(
3110                    rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
3111                ),
3112                announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
3113                driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
3114                interface_writer_queue_capacity:
3115                    crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
3116                announce_sig_cache_enabled: true,
3117                announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
3118                announce_sig_cache_ttl: Duration::from_secs(
3119                    rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
3120                ),
3121                registry: None,
3122                #[cfg(feature = "rns-hooks")]
3123                provider_bridge: None,
3124            },
3125            Box::new(NoopCallbacks),
3126        )
3127        .unwrap();
3128
3129        // single_in has no public_key — sending should fail
3130        let dest = crate::destination::Destination::single_in(
3131            "test",
3132            &["echo"],
3133            rns_core::types::IdentityHash([0x42; 16]),
3134        );
3135        let result = node.send_packet(&dest, b"hello");
3136        assert!(result.is_err(), "single_in has no public_key, should fail");
3137
3138        node.shutdown();
3139    }
3140
3141    #[test]
3142    fn send_packet_single_encrypts() {
3143        let node = RnsNode::start(
3144            NodeConfig {
3145                panic_on_interface_error: false,
3146                transport_enabled: false,
3147                identity: None,
3148                interfaces: vec![],
3149                share_instance: false,
3150                instance_name: "default".into(),
3151                shared_instance_port: 37428,
3152                rpc_port: 0,
3153                cache_dir: None,
3154                management: Default::default(),
3155                probe_port: None,
3156                probe_addrs: vec![],
3157                probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
3158                device: None,
3159                hooks: Vec::new(),
3160                discover_interfaces: false,
3161                discovery_required_value: None,
3162                respond_to_probes: false,
3163                prefer_shorter_path: false,
3164                max_paths_per_destination: 1,
3165                packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
3166                max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
3167                max_path_destinations: usize::MAX,
3168                max_tunnel_destinations_total: usize::MAX,
3169                known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
3170                known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
3171                announce_table_ttl: Duration::from_secs(
3172                    rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
3173                ),
3174                announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
3175                driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
3176                interface_writer_queue_capacity:
3177                    crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
3178                announce_sig_cache_enabled: true,
3179                announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
3180                announce_sig_cache_ttl: Duration::from_secs(
3181                    rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
3182                ),
3183                registry: None,
3184                #[cfg(feature = "rns-hooks")]
3185                provider_bridge: None,
3186            },
3187            Box::new(NoopCallbacks),
3188        )
3189        .unwrap();
3190
3191        // Create a proper OUT SINGLE destination with a real identity's public key
3192        let remote_identity = Identity::new(&mut OsRng);
3193        let recalled = crate::destination::AnnouncedIdentity {
3194            dest_hash: rns_core::types::DestHash([0xAA; 16]),
3195            identity_hash: rns_core::types::IdentityHash(*remote_identity.hash()),
3196            public_key: remote_identity.get_public_key().unwrap(),
3197            app_data: None,
3198            hops: 1,
3199            received_at: 0.0,
3200            receiving_interface: rns_core::transport::types::InterfaceId(0),
3201        };
3202        let dest = crate::destination::Destination::single_out("test", &["echo"], &recalled);
3203
3204        let result = node.send_packet(&dest, b"secret message");
3205        assert!(result.is_ok());
3206
3207        let packet_hash = result.unwrap();
3208        assert_ne!(packet_hash.0, [0u8; 32]);
3209
3210        thread::sleep(Duration::from_millis(50));
3211        node.shutdown();
3212    }
3213
3214    #[test]
3215    fn register_destination_with_proof_prove_all() {
3216        let node = RnsNode::start(
3217            NodeConfig {
3218                panic_on_interface_error: false,
3219                transport_enabled: false,
3220                identity: None,
3221                interfaces: vec![],
3222                share_instance: false,
3223                instance_name: "default".into(),
3224                shared_instance_port: 37428,
3225                rpc_port: 0,
3226                cache_dir: None,
3227                management: Default::default(),
3228                probe_port: None,
3229                probe_addrs: vec![],
3230                probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
3231                device: None,
3232                hooks: Vec::new(),
3233                discover_interfaces: false,
3234                discovery_required_value: None,
3235                respond_to_probes: false,
3236                prefer_shorter_path: false,
3237                max_paths_per_destination: 1,
3238                packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
3239                max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
3240                max_path_destinations: usize::MAX,
3241                max_tunnel_destinations_total: usize::MAX,
3242                known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
3243                known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
3244                announce_table_ttl: Duration::from_secs(
3245                    rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
3246                ),
3247                announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
3248                driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
3249                interface_writer_queue_capacity:
3250                    crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
3251                announce_sig_cache_enabled: true,
3252                announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
3253                announce_sig_cache_ttl: Duration::from_secs(
3254                    rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
3255                ),
3256                registry: None,
3257                #[cfg(feature = "rns-hooks")]
3258                provider_bridge: None,
3259            },
3260            Box::new(NoopCallbacks),
3261        )
3262        .unwrap();
3263
3264        let identity = Identity::new(&mut OsRng);
3265        let ih = rns_core::types::IdentityHash(*identity.hash());
3266        let dest = crate::destination::Destination::single_in("echo", &["request"], ih)
3267            .set_proof_strategy(rns_core::types::ProofStrategy::ProveAll);
3268        let prv_key = identity.get_private_key().unwrap();
3269
3270        let result = node.register_destination_with_proof(&dest, Some(prv_key));
3271        assert!(result.is_ok());
3272
3273        // Small wait for the events to be processed
3274        thread::sleep(Duration::from_millis(50));
3275
3276        node.shutdown();
3277    }
3278
3279    #[test]
3280    fn register_destination_with_proof_prove_none() {
3281        let node = RnsNode::start(
3282            NodeConfig {
3283                panic_on_interface_error: false,
3284                transport_enabled: false,
3285                identity: None,
3286                interfaces: vec![],
3287                share_instance: false,
3288                instance_name: "default".into(),
3289                shared_instance_port: 37428,
3290                rpc_port: 0,
3291                cache_dir: None,
3292                management: Default::default(),
3293                probe_port: None,
3294                probe_addrs: vec![],
3295                probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
3296                device: None,
3297                hooks: Vec::new(),
3298                discover_interfaces: false,
3299                discovery_required_value: None,
3300                respond_to_probes: false,
3301                prefer_shorter_path: false,
3302                max_paths_per_destination: 1,
3303                packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
3304                max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
3305                max_path_destinations: usize::MAX,
3306                max_tunnel_destinations_total: usize::MAX,
3307                known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
3308                known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
3309                announce_table_ttl: Duration::from_secs(
3310                    rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
3311                ),
3312                announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
3313                driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
3314                interface_writer_queue_capacity:
3315                    crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
3316                announce_sig_cache_enabled: true,
3317                announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
3318                announce_sig_cache_ttl: Duration::from_secs(
3319                    rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
3320                ),
3321                registry: None,
3322                #[cfg(feature = "rns-hooks")]
3323                provider_bridge: None,
3324            },
3325            Box::new(NoopCallbacks),
3326        )
3327        .unwrap();
3328
3329        // ProveNone should not send RegisterProofStrategy event
3330        let dest = crate::destination::Destination::plain("test", &["data"])
3331            .set_proof_strategy(rns_core::types::ProofStrategy::ProveNone);
3332
3333        let result = node.register_destination_with_proof(&dest, None);
3334        assert!(result.is_ok());
3335
3336        thread::sleep(Duration::from_millis(50));
3337        node.shutdown();
3338    }
3339}