1use std::collections::{HashMap, HashSet};
6use std::io::{self, Read};
7use std::path::Path;
8use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
9use std::sync::Arc;
10use std::thread::{self, JoinHandle};
11use std::time::Duration;
12
13use rns_core::transport::announce_verify_queue::OverflowPolicy as AnnounceQueueOverflowPolicy;
14use rns_core::transport::types::TransportConfig;
15use rns_crypto::identity::Identity;
16use rns_crypto::{OsRng, Rng};
17
18use crate::config;
19use crate::driver::{AnnounceRateDefaults, Callbacks, Driver};
20#[cfg(feature = "iface-backbone")]
21use crate::driver::{BackbonePeerPoolCandidateConfig, BackbonePeerPoolSettings};
22use crate::event::{self, Event, EventSender};
23use crate::ifac;
24#[cfg(feature = "iface-auto")]
25use crate::interface::auto::{auto_runtime_handle_from_config, AutoConfig};
26#[cfg(feature = "iface-backbone")]
27use crate::interface::backbone::{
28 client_config_from_mode, client_priority_from_mode, client_runtime_handle_from_mode,
29 peer_state_handle_from_mode, runtime_handle_from_mode, BackboneMode,
30};
31#[cfg(feature = "iface-i2p")]
32use crate::interface::i2p::{i2p_runtime_handle_from_config, I2pConfig};
33#[cfg(feature = "iface-local")]
34use crate::interface::local::LocalServerConfig;
35#[cfg(feature = "iface-pipe")]
36use crate::interface::pipe::{pipe_runtime_handle_from_config, PipeConfig};
37#[cfg(feature = "iface-rnode")]
38use crate::interface::rnode::{rnode_runtime_handle_from_config, RNodeConfig};
39#[cfg(feature = "iface-tcp")]
40use crate::interface::tcp::{tcp_client_runtime_handle_from_config, TcpClientConfig};
41#[cfg(feature = "iface-tcp")]
42use crate::interface::tcp_server::{
43 runtime_handle_from_config as tcp_runtime_handle_from_config, TcpServerConfig,
44};
45#[cfg(feature = "iface-udp")]
46use crate::interface::udp::{udp_runtime_handle_from_config, UdpConfig};
47use crate::interface::{InterfaceEntry, InterfaceStats};
48use crate::storage;
49use crate::time;
50
51static NEXT_RESOURCE_TRANSFER_ID: AtomicU64 = AtomicU64::new(1);
52
53#[cfg(test)]
54const DEFAULT_KNOWN_DESTINATIONS_TTL: Duration = Duration::from_secs(48 * 60 * 60);
55const DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES: usize = 8192;
56
57fn parse_interface_mode(mode: &str) -> u8 {
60 match mode.to_lowercase().as_str() {
61 "full" => rns_core::constants::MODE_FULL,
62 "access_point" | "accesspoint" | "ap" => rns_core::constants::MODE_ACCESS_POINT,
63 "pointtopoint" | "ptp" => rns_core::constants::MODE_POINT_TO_POINT,
64 "roaming" => rns_core::constants::MODE_ROAMING,
65 "boundary" => rns_core::constants::MODE_BOUNDARY,
66 "gateway" | "gw" => rns_core::constants::MODE_GATEWAY,
67 "internal" => rns_core::constants::MODE_INTERNAL,
68 _ => rns_core::constants::MODE_FULL,
69 }
70}
71
72fn parse_optional_interface_mode(mode: &str) -> Option<u8> {
73 match mode.to_lowercase().as_str() {
74 "full" => Some(rns_core::constants::MODE_FULL),
75 "access_point" | "accesspoint" | "ap" => Some(rns_core::constants::MODE_ACCESS_POINT),
76 "pointtopoint" | "ptp" => Some(rns_core::constants::MODE_POINT_TO_POINT),
77 "roaming" => Some(rns_core::constants::MODE_ROAMING),
78 "boundary" => Some(rns_core::constants::MODE_BOUNDARY),
79 "gateway" | "gw" => Some(rns_core::constants::MODE_GATEWAY),
80 "internal" => Some(rns_core::constants::MODE_INTERNAL),
81 _ => None,
82 }
83}
84
85fn parse_interface_gravity(value: Option<&str>, default_gravity: i64) -> i64 {
86 value
87 .and_then(|value| value.parse().ok())
88 .unwrap_or(default_gravity)
89}
90
91fn normalize_discovery_mode(
93 interface_type: &str,
94 configured_mode: u8,
95 discoverable: bool,
96 ignore_config_warnings: bool,
97) -> u8 {
98 if !discoverable
99 || ignore_config_warnings
100 || matches!(
101 configured_mode,
102 rns_core::constants::MODE_ACCESS_POINT
103 | rns_core::constants::MODE_GATEWAY
104 | rns_core::constants::MODE_INTERNAL
105 )
106 {
107 return configured_mode;
108 }
109
110 if matches!(interface_type, "RNodeInterface" | "RNodeMultiInterface") {
111 rns_core::constants::MODE_ACCESS_POINT
112 } else {
113 rns_core::constants::MODE_GATEWAY
114 }
115}
116
117fn default_ingress_control_for_type(
118 iface_type: &str,
119) -> rns_core::transport::types::IngressControlConfig {
120 match iface_type {
121 "AutoInterface" | "BackboneInterface" | "TCPClientInterface" | "TCPServerInterface"
122 | "UDPInterface" | "I2PInterface" => {
123 rns_core::transport::types::IngressControlConfig::enabled()
124 }
125 _ => rns_core::transport::types::IngressControlConfig::disabled(),
126 }
127}
128
129fn parse_ingress_control_config(
130 iface_type: &str,
131 params: &std::collections::HashMap<String, String>,
132) -> Result<rns_core::transport::types::IngressControlConfig, String> {
133 let mut config = default_ingress_control_for_type(iface_type);
134
135 if let Some(v) = params.get("ingress_control") {
136 config.enabled = config::parse_bool_pub(v)
137 .ok_or_else(|| format!("ingress_control must be a boolean, got '{}'", v))?;
138 }
139 if let Some(v) = params.get("ic_max_held_announces") {
140 config.max_held_announces = v
141 .parse::<usize>()
142 .map_err(|_| format!("ic_max_held_announces must be an integer, got '{}'", v))?;
143 }
144 if let Some(v) = params.get("ic_burst_hold") {
145 config.burst_hold = parse_nonnegative_f64("ic_burst_hold", v)?;
146 }
147 if let Some(v) = params.get("ic_burst_freq_new") {
148 config.burst_freq_new = parse_nonnegative_f64("ic_burst_freq_new", v)?;
149 }
150 if let Some(v) = params.get("ic_burst_freq") {
151 config.burst_freq = parse_nonnegative_f64("ic_burst_freq", v)?;
152 }
153 if let Some(v) = params.get("ic_pr_burst_freq_new") {
154 config.pr_burst_freq_new = parse_nonnegative_f64("ic_pr_burst_freq_new", v)?;
155 }
156 if let Some(v) = params.get("ic_pr_burst_freq") {
157 config.pr_burst_freq = parse_nonnegative_f64("ic_pr_burst_freq", v)?;
158 }
159 if let Some(v) = params.get("egress_control") {
160 config.egress_enabled = config::parse_bool_pub(v)
161 .ok_or_else(|| format!("egress_control must be a boolean, got '{}'", v))?;
162 }
163 if let Some(v) = params.get("ec_pr_freq") {
164 config.egress_pr_freq = parse_nonnegative_f64("ec_pr_freq", v)?;
165 }
166 if let Some(v) = params.get("ic_new_time") {
167 config.new_time = parse_nonnegative_f64("ic_new_time", v)?;
168 }
169 if let Some(v) = params.get("ic_burst_penalty") {
170 config.burst_penalty = parse_nonnegative_f64("ic_burst_penalty", v)?;
171 }
172 if let Some(v) = params.get("ic_held_release_interval") {
173 config.held_release_interval = parse_nonnegative_f64("ic_held_release_interval", v)?;
174 }
175
176 Ok(config)
177}
178
179fn parse_nonnegative_f64(key: &str, value: &str) -> Result<f64, String> {
180 let parsed = value
181 .parse::<f64>()
182 .map_err(|_| format!("{} must be numeric, got '{}'", key, value))?;
183 if parsed < 0.0 {
184 return Err(format!("{} must be >= 0, got '{}'", key, value));
185 }
186 Ok(parsed)
187}
188
189fn sanitize_ifac_config_string(kind: &str, value: &str) -> Option<String> {
195 if value == "None" {
196 log::warn!(
197 "Ambiguous IFAC {} \"None\", this value is ignored and an IFAC {} has NOT been set",
198 kind,
199 kind
200 );
201 return None;
202 }
203 if value.is_empty() {
204 return None;
205 }
206 Some(value.to_string())
207}
208
209fn extract_ifac_config(
212 params: &std::collections::HashMap<String, String>,
213 default_size: usize,
214) -> Option<IfacConfig> {
215 let netname = params
216 .get("networkname")
217 .or_else(|| params.get("network_name"))
218 .cloned()
219 .and_then(|value| sanitize_ifac_config_string("network name", &value));
220 let netkey = params
221 .get("passphrase")
222 .or_else(|| params.get("pass_phrase"))
223 .cloned()
224 .and_then(|value| sanitize_ifac_config_string("passphrase", &value));
225
226 if netname.is_none() && netkey.is_none() {
227 return None;
228 }
229
230 let size = params
232 .get("ifac_size")
233 .and_then(|v| v.parse::<usize>().ok())
234 .map(|bits| (bits / 8).max(1))
235 .unwrap_or(default_size);
236
237 Some(IfacConfig {
238 netname,
239 netkey,
240 size,
241 })
242}
243
244fn effective_static_transport_identity(
248 configured: bool,
249 transport_enabled: bool,
250 has_discoverable_interface: bool,
251) -> bool {
252 configured || (!transport_enabled && has_discoverable_interface)
253}
254
255fn extract_discovery_config(
257 iface_name: &str,
258 iface_type: &str,
259 params: &std::collections::HashMap<String, String>,
260) -> Option<crate::discovery::DiscoveryConfig> {
261 let discoverable = params
262 .get("discoverable")
263 .and_then(|v| config::parse_bool_pub(v))
264 .unwrap_or(false);
265 if !discoverable {
266 return None;
267 }
268
269 if iface_type == "TCPClientInterface" {
270 log::error!(
271 "Invalid interface discovery configuration for {}, aborting discovery announce",
272 iface_name
273 );
274 return None;
275 }
276
277 let discovery_name = params
278 .get("discovery_name")
279 .cloned()
280 .unwrap_or_else(|| iface_name.to_string());
281
282 let announce_interval = params
284 .get("announce_interval")
285 .and_then(|v| v.parse::<u64>().ok())
286 .map(|secs| secs.max(300))
287 .unwrap_or(21600);
288
289 let stamp_value = params
291 .get("discovery_stamp_value")
292 .and_then(|v| config::parse_config_float(v))
293 .map(|v| rns_core::stamp::clamp_cost(v, rns_core::constants::STAMP_COST_CAP))
294 .unwrap_or(crate::discovery::DEFAULT_STAMP_VALUE);
295
296 let reachable_on = params.get("reachable_on").cloned();
297
298 let listen_port = params
299 .get("listen_port")
300 .or_else(|| params.get("port"))
301 .and_then(|v| v.parse().ok());
302
303 let location_cmd = params.get("location_cmd").cloned();
304
305 let latitude = params
306 .get("latitude")
307 .or_else(|| params.get("lat"))
308 .and_then(|v| v.parse().ok());
309 let longitude = params
310 .get("longitude")
311 .or_else(|| params.get("lon"))
312 .and_then(|v| v.parse().ok());
313 let height = params.get("height").and_then(|v| v.parse().ok());
314 let operator_lxmf_address = params.get("discovery_lxmf_address").and_then(|value| {
315 let parsed = parse_discovery_lxmf_address(value);
316 if parsed.is_none() {
317 log::error!(
318 "Invalid interface discovery LXMF address for '{}': {}",
319 iface_name,
320 value
321 );
322 }
323 parsed
324 });
325
326 Some(crate::discovery::DiscoveryConfig {
327 discovery_name,
328 announce_interval,
329 stamp_value,
330 reachable_on,
331 interface_type: iface_type.to_string(),
332 listen_port,
333 location_cmd,
334 latitude,
335 longitude,
336 height,
337 operator_lxmf_address,
338 stamp_costs: parse_interface_stamp_costs(params).unwrap_or_default(),
340 })
341}
342
343fn default_discovery_runtime_config(
344 interface_name: &str,
345 interface_type: &str,
346 listen_port: Option<u16>,
347) -> crate::discovery::DiscoveryConfig {
348 crate::discovery::DiscoveryConfig {
349 stamp_costs: Default::default(),
350 discovery_name: interface_name.to_string(),
351 announce_interval: 21600,
352 stamp_value: crate::discovery::DEFAULT_STAMP_VALUE,
353 reachable_on: None,
354 interface_type: interface_type.to_string(),
355 listen_port,
356 location_cmd: None,
357 latitude: None,
358 longitude: None,
359 height: None,
360 operator_lxmf_address: None,
361 }
362}
363
364fn parse_discovery_lxmf_address(value: &str) -> Option<[u8; 16]> {
365 if value.len() != 32 || !value.bytes().all(|byte| byte.is_ascii_hexdigit()) {
366 return None;
367 }
368 let mut address = [0u8; 16];
369 for (index, byte) in address.iter_mut().enumerate() {
370 *byte = u8::from_str_radix(&value[index * 2..index * 2 + 2], 16).ok()?;
371 }
372 Some(address)
373}
374
375fn discovery_runtime_ifac_fields(ifac: Option<&IfacConfig>) -> (Option<String>, Option<String>) {
376 (
377 ifac.and_then(|cfg| cfg.netname.clone()),
378 ifac.and_then(|cfg| cfg.netkey.clone()),
379 )
380}
381
382#[cfg(feature = "iface-backbone")]
383pub(crate) fn backbone_discovery_runtime_from_interface(
384 interface_name: &str,
385 mode: &BackboneMode,
386 discovery: Option<&crate::discovery::DiscoveryConfig>,
387 transport_enabled: bool,
388 ifac: Option<&IfacConfig>,
389) -> Option<crate::driver::BackboneDiscoveryRuntimeHandle> {
390 let config = match mode {
391 BackboneMode::Server(config) => config,
392 BackboneMode::Client(_, _) => return None,
393 };
394
395 let startup_config = discovery.cloned().unwrap_or_else(|| {
396 default_discovery_runtime_config(
397 interface_name,
398 "BackboneInterface",
399 Some(config.listen_port),
400 )
401 });
402 let (ifac_netname, ifac_netkey) = discovery_runtime_ifac_fields(ifac);
403
404 Some(crate::driver::BackboneDiscoveryRuntimeHandle::from_parts(
405 config.name.clone(),
406 startup_config,
407 transport_enabled,
408 ifac_netname,
409 ifac_netkey,
410 discovery.is_some(),
411 ))
412}
413
414#[cfg(feature = "iface-tcp")]
415pub(crate) fn tcp_server_discovery_runtime_from_interface(
416 interface_name: &str,
417 config: &crate::interface::tcp_server::TcpServerConfig,
418 discovery: Option<&crate::discovery::DiscoveryConfig>,
419 transport_enabled: bool,
420 ifac: Option<&IfacConfig>,
421) -> crate::driver::TcpServerDiscoveryRuntimeHandle {
422 let startup_config = discovery.cloned().unwrap_or_else(|| {
423 default_discovery_runtime_config(
424 interface_name,
425 "TCPServerInterface",
426 Some(config.listen_port),
427 )
428 });
429 let (ifac_netname, ifac_netkey) = discovery_runtime_ifac_fields(ifac);
430
431 crate::driver::TcpServerDiscoveryRuntimeHandle::from_parts(
432 config.name.clone(),
433 startup_config,
434 transport_enabled,
435 ifac_netname,
436 ifac_netkey,
437 discovery.is_some(),
438 )
439}
440
441pub(crate) fn ifac_runtime_from_config(
442 ifac: Option<&IfacConfig>,
443 default_size: usize,
444) -> crate::driver::IfacRuntimeConfig {
445 crate::driver::IfacRuntimeConfig::from_parts(
446 ifac.and_then(|cfg| cfg.netname.clone()),
447 ifac.and_then(|cfg| cfg.netkey.clone()),
448 ifac.map(|cfg| cfg.size).unwrap_or(default_size),
449 )
450}
451
452pub(crate) fn discoverable_interface_from_config(
453 interface_name: &str,
454 discovery: &crate::discovery::DiscoveryConfig,
455 transport_enabled: bool,
456 ifac: Option<&IfacConfig>,
457) -> crate::discovery::DiscoverableInterface {
458 crate::discovery::DiscoverableInterface {
459 interface_name: interface_name.to_string(),
460 config: discovery.clone(),
461 transport_enabled,
462 ifac_netname: ifac.and_then(|cfg| cfg.netname.clone()),
463 ifac_netkey: ifac.and_then(|cfg| cfg.netkey.clone()),
464 }
465}
466
467pub(crate) fn derive_ifac_state(
468 ifac: Option<&IfacConfig>,
469 interface_name: &str,
470) -> io::Result<Option<crate::ifac::IfacState>> {
471 let Some(ifac) = ifac else {
472 return Ok(None);
473 };
474 if ifac.netname.is_none() && ifac.netkey.is_none() {
475 return Ok(None);
476 }
477
478 ifac::derive_ifac(ifac.netname.as_deref(), ifac.netkey.as_deref(), ifac.size)
479 .map(Some)
480 .map_err(|err| {
481 io::Error::other(format!(
482 "failed to derive IFAC for {}: {}",
483 interface_name, err
484 ))
485 })
486}
487
488pub(crate) struct StartedInterface<'a> {
489 pub(crate) driver: &'a mut Driver,
490 pub(crate) tx: &'a EventSender,
491 pub(crate) queue_capacity: usize,
492 pub(crate) id: rns_core::transport::types::InterfaceId,
493 pub(crate) info: rns_core::transport::types::InterfaceInfo,
494 pub(crate) writer: Box<dyn crate::interface::Writer>,
495 pub(crate) interface_type_name: String,
496 pub(crate) ifac_state: Option<crate::ifac::IfacState>,
497 pub(crate) ifac_runtime: &'a crate::driver::IfacRuntimeConfig,
498 pub(crate) stamp_costs: rns_core::transport::types::InterfaceStampCosts,
499}
500
501pub(crate) fn register_started_interface(params: StartedInterface<'_>) {
502 let StartedInterface {
503 driver,
504 tx,
505 queue_capacity,
506 id,
507 mut info,
508 writer,
509 interface_type_name,
510 ifac_state,
511 ifac_runtime,
512 stamp_costs,
513 } = params;
514 info.stamp_costs = stamp_costs;
515 driver.apply_announce_rate_defaults(&mut info);
516 driver.apply_ingress_control_defaults(&mut info);
517 let (writer, async_writer_metrics) =
518 crate::interface::wrap_async_writer(writer, id, &info.name, tx.clone(), queue_capacity);
519 driver.register_interface_runtime_defaults(&info);
520 driver.register_interface_ifac_runtime(&info.name, ifac_runtime.clone());
521 driver.engine.register_interface(info.clone());
522 driver.interfaces.insert(
523 id,
524 InterfaceEntry {
525 id,
526 info,
527 writer,
528 async_writer_metrics: Some(async_writer_metrics),
529 enabled: true,
530 online: false,
531 dynamic: false,
532 ifac: ifac_state,
533 stats: InterfaceStats {
534 started: time::now(),
535 ..Default::default()
536 },
537 interface_type: interface_type_name,
538 send_retry_at: None,
539 send_retry_backoff: Duration::ZERO,
540 },
541 );
542}
543
544pub(crate) struct InterfaceManagementConfig {
546 pub(crate) config_file: std::path::PathBuf,
547 pub(crate) storage_dir: std::path::PathBuf,
548 pub(crate) initial_ids: HashMap<String, rns_core::transport::types::InterfaceId>,
549 pub(crate) queue_capacity: usize,
550 pub(crate) transport_enabled: bool,
551 pub(crate) underlay_mark: Option<u32>,
552}
553
554pub struct NodeConfig {
556 pub transport_enabled: bool,
557 pub static_transport_identity: bool,
558 pub local_hops_delta: bool,
559 pub identity: Option<Identity>,
560 pub interfaces: Vec<InterfaceConfig>,
562 pub share_instance: bool,
564 pub instance_name: String,
566 pub shared_instance_port: u16,
568 pub rpc_port: u16,
570 pub cache_dir: Option<std::path::PathBuf>,
572 pub ratchet_store: Option<Arc<dyn storage::RatchetStore>>,
574 pub ratchet_expiry: Duration,
576 pub management: crate::management::ManagementConfig,
578 pub probe_port: Option<u16>,
580 pub probe_addrs: Vec<std::net::SocketAddr>,
582 pub probe_protocol: rns_core::holepunch::ProbeProtocol,
584 pub direct_connect_policy: crate::event::HolePunchPolicy,
586 pub direct_connect_allow_private_endpoints: bool,
589 pub device: Option<String>,
591 pub underlay_mark: Option<u32>,
593 pub hooks: Vec<config::ParsedHook>,
595 pub discover_interfaces: bool,
597 pub autoconnect_interface_mode: Option<u8>,
599 pub autoconnect_interface_gravity: i64,
601 pub autoconnect_announces_to_internal: bool,
603 pub autoconnect_unverified_implementations: bool,
605 pub discovery_required_value: Option<rns_core::stamp::StampCost>,
607 pub respond_to_probes: bool,
609 pub prefer_shorter_path: bool,
613 pub max_paths_per_destination: usize,
616 pub packet_hashlist_max_entries: usize,
618 pub packet_hashlist_allocation: rns_core::transport::types::PacketHashlistAllocation,
620 pub max_discovery_pr_tags: usize,
622 pub max_path_destinations: usize,
624 pub max_tunnel_destinations_total: usize,
626 pub known_destinations_ttl: Duration,
628 pub known_destinations_max_entries: usize,
630 pub announce_table_ttl: Duration,
632 pub announce_table_max_bytes: usize,
634 pub driver_event_queue_capacity: usize,
636 pub interface_writer_queue_capacity: usize,
638 pub announce_rate_defaults: AnnounceRateDefaults,
640 pub ingress_control_defaults: rns_core::transport::types::IngressControlConfig,
642 #[cfg(feature = "iface-backbone")]
644 pub backbone_peer_pool: Option<BackbonePeerPoolSettings>,
645 pub announce_sig_cache_enabled: bool,
647 pub announce_sig_cache_max_entries: usize,
649 pub announce_sig_cache_ttl: Duration,
651 pub registry: Option<crate::interface::registry::InterfaceRegistry>,
653 pub panic_on_interface_error: bool,
656 pub link_proof_verify_threads: crate::ProofVerifyThreads,
659 pub stamps: config::StampsSection,
665 #[cfg(feature = "hooks")]
667 pub provider_bridge: Option<crate::provider_bridge::ProviderBridgeConfig>,
668}
669
670struct NodeQueueConfig {
671 rpc_auth_key: Option<[u8; 32]>,
672 announce_max_entries: usize,
673 announce_max_interfaces: usize,
674 announce_max_bytes: usize,
675 announce_ttl_secs: f64,
676 announce_overflow_policy: AnnounceQueueOverflowPolicy,
677 inbound_capacities: crate::event::InboundQueueCapacities,
678 link_mtu_discovery: bool,
679 filter: config::FilterSection,
681 hop_limiter: u8,
683}
684
685impl Default for NodeQueueConfig {
686 fn default() -> Self {
687 Self {
688 rpc_auth_key: None,
689 announce_max_entries: 256,
690 announce_max_interfaces: 1024,
691 announce_max_bytes: 256 * 1024,
692 announce_ttl_secs: 30.0,
693 announce_overflow_policy: AnnounceQueueOverflowPolicy::DropWorst,
694 inbound_capacities: crate::event::InboundQueueCapacities::default(),
695 link_mtu_discovery: true,
696 filter: config::FilterSection::default(),
697 hop_limiter: rns_core::constants::HOP_LIMITER,
698 }
699 }
700}
701
702impl Default for NodeConfig {
703 fn default() -> Self {
704 Self {
705 transport_enabled: false,
706 static_transport_identity: false,
707 local_hops_delta: false,
708 identity: None,
709 interfaces: Vec::new(),
710 share_instance: false,
711 instance_name: "default".into(),
712 shared_instance_port: 37428,
713 rpc_port: 0,
714 cache_dir: None,
715 ratchet_store: None,
716 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
717 management: Default::default(),
718 probe_port: None,
719 probe_addrs: vec![],
720 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
721 direct_connect_policy: crate::event::HolePunchPolicy::IdentifiedOnly,
722 direct_connect_allow_private_endpoints: false,
723 device: None,
724 underlay_mark: None,
725 hooks: Vec::new(),
726 discover_interfaces: false,
727 autoconnect_interface_mode: None,
728 autoconnect_interface_gravity: 0,
729 autoconnect_announces_to_internal: false,
730 autoconnect_unverified_implementations: false,
731 discovery_required_value: None,
732 respond_to_probes: false,
733 prefer_shorter_path: false,
734 max_paths_per_destination: 1,
735 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
736 packet_hashlist_allocation: rns_core::transport::types::PacketHashlistAllocation::Lazy,
737 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
738 max_path_destinations: rns_core::transport::types::DEFAULT_MAX_PATH_DESTINATIONS,
739 max_tunnel_destinations_total: usize::MAX,
740 known_destinations_ttl: Duration::from_secs(48 * 60 * 60),
741 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
742 announce_table_ttl: Duration::from_secs(rns_core::constants::ANNOUNCE_TABLE_TTL as u64),
743 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
744 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
745 interface_writer_queue_capacity: crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
746 announce_rate_defaults: AnnounceRateDefaults::default(),
747 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(),
748 #[cfg(feature = "iface-backbone")]
749 backbone_peer_pool: None,
750 announce_sig_cache_enabled: true,
751 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
752 announce_sig_cache_ttl: Duration::from_secs(
753 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
754 ),
755 registry: None,
756 panic_on_interface_error: false,
757 link_proof_verify_threads: Default::default(),
758 stamps: config::StampsSection::default(),
759 #[cfg(feature = "hooks")]
760 provider_bridge: None,
761 }
762 }
763}
764
765pub struct IfacConfig {
767 pub netname: Option<String>,
768 pub netkey: Option<String>,
769 pub size: usize,
770}
771
772pub struct InterfaceConfig {
774 pub name: String,
775 pub type_name: String,
776 pub config_data: Box<dyn crate::interface::InterfaceConfigData>,
777 pub mode: u8,
778 pub gravity: i64,
779 pub recursive_prs: bool,
780 pub announces_from_internal: bool,
781 pub announces_to_internal: Option<bool>,
782 pub ingress_control: rns_core::transport::types::IngressControlConfig,
783 pub stamp_costs: rns_core::transport::types::InterfaceStampCosts,
785 pub ifac: Option<IfacConfig>,
786 pub discovery: Option<crate::discovery::DiscoveryConfig>,
787}
788
789pub(crate) fn parse_interface_stamp_costs(
792 params: &HashMap<String, String>,
793) -> Result<rns_core::transport::types::InterfaceStampCosts, String> {
794 let cost = |key: &str| -> Result<Option<rns_core::stamp::StampCost>, String> {
795 let Some(value) = params.get(key) else {
796 return Ok(None);
797 };
798 let value = config::parse_config_float(value)
799 .ok_or_else(|| format!("{key} must be a number, got '{value}'"))?;
800 Ok(Some(rns_core::stamp::clamp_cost(
801 value,
802 rns_core::constants::STAMP_COST_CAP,
803 )))
804 };
805 Ok(rns_core::transport::types::InterfaceStampCosts {
806 destination_cost: cost("destination_cost")?,
807 pr_cost: cost("pr_cost")?,
808 })
809}
810
811pub(crate) fn parse_managed_interface(
812 iface: &config::ParsedInterface,
813 parsed: &config::RnsConfig,
814 storage_dir: &Path,
815 id: rns_core::transport::types::InterfaceId,
816 registry: &crate::interface::registry::InterfaceRegistry,
817) -> io::Result<InterfaceConfig> {
818 let factory = registry.get(&iface.interface_type).ok_or_else(|| {
819 io::Error::new(
820 io::ErrorKind::InvalidInput,
821 format!("unsupported interface type '{}'", iface.interface_type),
822 )
823 })?;
824 let discoverable = iface
825 .params
826 .get("discoverable")
827 .and_then(|value| config::parse_bool_pub(value))
828 .unwrap_or(false);
829 let ignore_warnings = iface
830 .params
831 .get("ignore_config_warnings")
832 .and_then(|value| config::parse_bool_pub(value))
833 .unwrap_or(false);
834 let mode = normalize_discovery_mode(
835 &iface.interface_type,
836 parse_interface_mode(&iface.mode),
837 discoverable,
838 ignore_warnings,
839 );
840 let ifac = extract_ifac_config(&iface.params, factory.default_ifac_size());
841 let discovery = extract_discovery_config(&iface.name, &iface.interface_type, &iface.params);
842 let ingress_control = parse_ingress_control_config(&iface.interface_type, &iface.params)
843 .map_err(|error| io::Error::new(io::ErrorKind::InvalidInput, error))?;
844 let stamp_costs = parse_interface_stamp_costs(&iface.params)
845 .map_err(|error| io::Error::new(io::ErrorKind::InvalidInput, error))?;
846 let recursive_prs = iface
847 .params
848 .get("recursive_prs")
849 .and_then(|value| config::parse_bool_pub(value))
850 .unwrap_or(false);
851 let announces_from_internal = iface
852 .params
853 .get("announces_from_internal")
854 .and_then(|value| config::parse_bool_pub(value))
855 .unwrap_or(true);
856 let announces_to_internal = iface
857 .params
858 .get("announces_to_internal")
859 .and_then(|value| config::parse_bool_pub(value));
860 let gravity = parse_interface_gravity(
861 iface.params.get("gravity").map(String::as_str),
862 parsed.reticulum.default_gravity,
863 );
864 let mut params = iface.params.clone();
865 params
866 .entry("storage_dir".to_string())
867 .or_insert_with(|| storage_dir.to_string_lossy().to_string());
868 if let Some(device) = &parsed.reticulum.device {
869 params
870 .entry("device".to_string())
871 .or_insert_with(|| device.clone());
872 }
873 let config_data = factory
874 .parse_config_section(
875 &iface.name,
876 id,
877 crate::interface::ConfigSection {
878 params: ¶ms,
879 children: &iface.subinterfaces,
880 },
881 )
882 .map_err(|error| io::Error::new(io::ErrorKind::InvalidInput, error))?;
883 Ok(InterfaceConfig {
884 name: iface.name.clone(),
885 type_name: iface.interface_type.clone(),
886 config_data,
887 mode,
888 gravity,
889 recursive_prs,
890 announces_from_internal,
891 announces_to_internal,
892 ingress_control,
893 stamp_costs,
894 ifac,
895 discovery,
896 })
897}
898
899use crate::event::{QueryRequest, QueryResponse};
900
901#[derive(Debug)]
903pub struct SendError;
904
905impl std::fmt::Display for SendError {
906 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
907 write!(f, "driver shut down")
908 }
909}
910
911impl std::error::Error for SendError {}
912
913#[derive(Debug, Clone, PartialEq, Eq)]
915pub enum ChannelSendError {
916 NotReady,
917 Rejected(String),
918 Unavailable,
919}
920
921impl From<String> for ChannelSendError {
922 fn from(error: String) -> Self {
923 if error == rns_core::channel::ChannelError::NotReady.to_string() {
924 Self::NotReady
925 } else {
926 Self::Rejected(error)
927 }
928 }
929}
930
931impl std::fmt::Display for ChannelSendError {
932 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
933 match self {
934 Self::NotReady => write!(f, "channel is not ready to send"),
935 Self::Rejected(error) => write!(f, "{error}"),
936 Self::Unavailable => write!(f, "driver is stopped or draining"),
937 }
938 }
939}
940
941impl std::error::Error for ChannelSendError {}
942
943pub struct RnsNode {
945 tx: EventSender,
946 driver_handle: Option<JoinHandle<()>>,
947 verify_handle: Option<JoinHandle<()>>,
948 verify_shutdown: Arc<crate::driver::WorkSignal>,
949 persist_handle: Option<JoinHandle<()>>,
950 persist_stop: Option<std::sync::mpsc::Sender<()>>,
951 rpc_server: Option<crate::rpc::RpcServer>,
952 #[cfg(target_os = "linux")]
953 rpc_unix_server: Option<crate::rpc::RpcServer>,
954 tick_interval_ms: Arc<AtomicU64>,
955 next_send_ticket: AtomicU64,
957 encrypt_deferred_inline: bool,
960 accepting_work: Arc<AtomicBool>,
962 #[allow(dead_code)]
963 probe_server: Option<crate::holepunch::probe::ProbeServerHandle>,
964 known_destinations_path: Option<std::path::PathBuf>,
965 known_destinations_store: Option<Arc<crate::storage_sqlite::KnownDestinationsStore>>,
966 stamp_store: Option<crate::stamps::DestinationStampStore>,
967 stamp_destinations: Arc<AtomicBool>,
972 stamp_targets: crate::stamper::StampTargets,
973 stamper: Option<Arc<crate::stamper::Stamper>>,
975 transport_state_dir: Option<std::path::PathBuf>,
976 ratchet_store: Option<Arc<dyn storage::RatchetStore>>,
977 ratchet_expiry: Duration,
978 #[cfg(feature = "iface-local")]
980 pub(crate) destination_types: crate::shared_client::DestinationTypeRequester,
981}
982
983impl RnsNode {
984 pub fn from_config(
987 config_path: Option<&Path>,
988 callbacks: Box<dyn Callbacks>,
989 ) -> io::Result<Self> {
990 let config_dir = storage::resolve_config_dir(config_path);
991 Self::from_config_dirs(&config_dir, &config_dir, None, false, callbacks)
992 }
993
994 pub fn from_private_config(
1002 config_dir: &Path,
1003 state_dir: &Path,
1004 underlay_mark: Option<u32>,
1005 callbacks: Box<dyn Callbacks>,
1006 ) -> io::Result<Self> {
1007 Self::from_config_dirs(config_dir, state_dir, underlay_mark, true, callbacks)
1008 }
1009
1010 fn from_config_dirs(
1011 config_dir: &Path,
1012 state_dir: &Path,
1013 underlay_mark: Option<u32>,
1014 private: bool,
1015 callbacks: Box<dyn Callbacks>,
1016 ) -> io::Result<Self> {
1017 let paths = storage::ensure_storage_dirs(state_dir)?;
1018 let known_destinations_path = paths.storage.join("known_destinations");
1019
1020 let config_file = config_dir.join("config");
1022 let config_content = if config_file.exists() {
1023 std::fs::read_to_string(&config_file)?
1024 } else {
1025 String::new()
1026 };
1027 let rns_config = config::parse(&config_content)
1028 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("{}", e)))?;
1029 let link_mtu_discovery = config::parse_link_mtu_discovery(&config_content)
1030 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("{}", e)))?;
1031
1032 let identity = if let Some(ref id_path_str) = rns_config.reticulum.network_identity {
1034 let id_path = std::path::PathBuf::from(id_path_str);
1035 if id_path.exists() {
1036 storage::load_identity(&id_path)?
1037 } else {
1038 let id = Identity::new(&mut OsRng);
1039 storage::save_identity(&id, &id_path)?;
1040 id
1041 }
1042 } else {
1043 storage::load_or_create_identity(&paths.identities)?
1044 };
1045 let rpc_auth_key = if !private && rns_config.reticulum.share_instance {
1048 let transport_identity_path = storage::transport_identity_path(&paths);
1049 let rpc_identity = if transport_identity_path.exists() {
1050 storage::load_identity(&transport_identity_path)?
1051 } else {
1052 storage::save_identity(&identity, &transport_identity_path)?;
1053 Identity::from_private_key(
1054 &identity
1055 .get_private_key()
1056 .expect("node identity has private key"),
1057 )
1058 };
1059 Some(crate::rpc::derive_auth_key(
1060 &rpc_identity
1061 .get_private_key()
1062 .expect("RPC identity has private key"),
1063 ))
1064 } else {
1065 None
1066 };
1067
1068 let registry = crate::interface::registry::InterfaceRegistry::with_builtins();
1070 let mut interface_configs = Vec::new();
1071 let mut initial_ids = HashMap::new();
1072 let mut next_id_val = 1u64;
1073
1074 for iface in &rns_config.interfaces {
1075 if !iface.enabled {
1076 continue;
1077 }
1078
1079 let iface_id = rns_core::transport::types::InterfaceId(next_id_val);
1080 next_id_val += 1;
1081
1082 let factory = match registry.get(&iface.interface_type) {
1083 Some(f) => f,
1084 None => {
1085 if private {
1086 return Err(io::Error::new(
1087 io::ErrorKind::InvalidData,
1088 format!(
1089 "unsupported enabled interface type '{}' for '{}'",
1090 iface.interface_type, iface.name
1091 ),
1092 ));
1093 }
1094 log::warn!(
1095 "Unsupported interface type '{}' for '{}'",
1096 iface.interface_type,
1097 iface.name,
1098 );
1099 continue;
1100 }
1101 };
1102
1103 let mut iface_mode = parse_interface_mode(&iface.mode);
1104
1105 let discoverable = iface
1106 .params
1107 .get("discoverable")
1108 .and_then(|value| config::parse_bool_pub(value))
1109 .unwrap_or(false);
1110 let ignore_config_warnings = iface
1111 .params
1112 .get("ignore_config_warnings")
1113 .and_then(|value| config::parse_bool_pub(value))
1114 .unwrap_or(false);
1115 let normalized_mode = normalize_discovery_mode(
1116 &iface.interface_type,
1117 iface_mode,
1118 discoverable,
1119 ignore_config_warnings,
1120 );
1121 if normalized_mode != iface_mode {
1122 log::info!(
1123 "Interface '{}' has discovery enabled, auto-configuring mode to {}",
1124 iface.name,
1125 if normalized_mode == rns_core::constants::MODE_ACCESS_POINT {
1126 "ACCESS_POINT"
1127 } else {
1128 "GATEWAY"
1129 }
1130 );
1131 iface_mode = normalized_mode;
1132 }
1133
1134 let default_ifac_size = factory.default_ifac_size();
1135 let ifac_config = extract_ifac_config(&iface.params, default_ifac_size);
1136 let discovery_config =
1137 extract_discovery_config(&iface.name, &iface.interface_type, &iface.params);
1138 let ingress_control =
1139 match parse_ingress_control_config(&iface.interface_type, &iface.params) {
1140 Ok(config) => config,
1141 Err(e) => {
1142 if private {
1143 return Err(io::Error::new(
1144 io::ErrorKind::InvalidData,
1145 format!("invalid ingress control for '{}': {e}", iface.name),
1146 ));
1147 }
1148 log::warn!(
1149 "Failed to parse ingress control config for '{}': {}",
1150 iface.name,
1151 e
1152 );
1153 continue;
1154 }
1155 };
1156 let stamp_costs = match parse_interface_stamp_costs(&iface.params) {
1157 Ok(costs) => costs,
1158 Err(e) => {
1159 if private {
1160 return Err(io::Error::new(
1161 io::ErrorKind::InvalidData,
1162 format!("invalid stamp cost for '{}': {e}", iface.name),
1163 ));
1164 }
1165 log::warn!("Failed to parse stamp costs for '{}': {}", iface.name, e);
1166 continue;
1167 }
1168 };
1169 let recursive_prs = iface
1170 .params
1171 .get("recursive_prs")
1172 .and_then(|v| config::parse_bool_pub(v))
1173 .unwrap_or(false);
1174 let announces_from_internal = iface
1175 .params
1176 .get("announces_from_internal")
1177 .and_then(|v| config::parse_bool_pub(v))
1178 .unwrap_or(true);
1179 let gravity = parse_interface_gravity(
1180 iface.params.get("gravity").map(String::as_str),
1181 rns_config.reticulum.default_gravity,
1182 );
1183 let announces_to_internal = iface
1184 .params
1185 .get("announces_to_internal")
1186 .and_then(|v| config::parse_bool_pub(v));
1187
1188 let mut params = iface.params.clone();
1190 if !params.contains_key("storage_dir") {
1191 params.insert(
1192 "storage_dir".to_string(),
1193 paths.storage.to_string_lossy().to_string(),
1194 );
1195 }
1196 if let Some(ref device) = rns_config.reticulum.device {
1198 if !params.contains_key("device") {
1199 params.insert("device".to_string(), device.clone());
1200 }
1201 }
1202
1203 let config_data = match factory.parse_config_section(
1204 &iface.name,
1205 iface_id,
1206 crate::interface::ConfigSection {
1207 params: ¶ms,
1208 children: &iface.subinterfaces,
1209 },
1210 ) {
1211 Ok(data) => data,
1212 Err(e) => {
1213 if private {
1214 return Err(io::Error::new(
1215 io::ErrorKind::InvalidData,
1216 format!("invalid interface '{}': {e}", iface.name),
1217 ));
1218 }
1219 log::warn!("Failed to parse config for '{}': {}", iface.name, e);
1220 continue;
1221 }
1222 };
1223
1224 interface_configs.push(InterfaceConfig {
1225 name: iface.name.clone(),
1226 type_name: iface.interface_type.clone(),
1227 config_data,
1228 mode: iface_mode,
1229 gravity,
1230 recursive_prs,
1231 announces_from_internal,
1232 announces_to_internal,
1233 ingress_control,
1234 stamp_costs,
1235 ifac: ifac_config,
1236 discovery: discovery_config,
1237 });
1238 initial_ids.insert(iface.name.clone(), iface_id);
1239 }
1240
1241 let mut mgmt_allowed = Vec::new();
1243 for hex_hash in &rns_config.reticulum.remote_management_allowed {
1244 if hex_hash.len() == 32 {
1245 if let Ok(bytes) = (0..hex_hash.len())
1246 .step_by(2)
1247 .map(|i| u8::from_str_radix(&hex_hash[i..i + 2], 16))
1248 .collect::<Result<Vec<u8>, _>>()
1249 {
1250 if bytes.len() == 16 {
1251 let mut h = [0u8; 16];
1252 h.copy_from_slice(&bytes);
1253 mgmt_allowed.push(h);
1254 }
1255 } else {
1256 log::warn!("Invalid hex in remote_management_allowed: {}", hex_hash);
1257 }
1258 } else {
1259 log::warn!(
1260 "Invalid entry in remote_management_allowed (expected 32 hex chars, got {}): {}",
1261 hex_hash.len(), hex_hash,
1262 );
1263 }
1264 }
1265
1266 let probe_addrs: Vec<std::net::SocketAddr> = rns_config
1268 .reticulum
1269 .probe_addr
1270 .as_ref()
1271 .map(|s| {
1272 s.split(',')
1273 .filter_map(|entry| {
1274 let trimmed = entry.trim();
1275 if trimmed.is_empty() {
1276 return None;
1277 }
1278 trimmed
1279 .parse::<std::net::SocketAddr>()
1280 .map_err(|e| {
1281 log::warn!("Invalid probe_addr entry '{}': {}", trimmed, e);
1282 e
1283 })
1284 .ok()
1285 })
1286 .collect()
1287 })
1288 .unwrap_or_default();
1289
1290 let probe_protocol = match rns_config
1292 .reticulum
1293 .probe_protocol
1294 .as_deref()
1295 .map(|s| s.to_lowercase())
1296 {
1297 Some(ref s) if s == "stun" => rns_core::holepunch::ProbeProtocol::Stun,
1298 _ => rns_core::holepunch::ProbeProtocol::Rnsp,
1299 };
1300
1301 let known_destinations_db = crate::storage_sqlite::default_path(&paths.storage);
1302 let mut known_destinations_store: Option<
1303 Arc<crate::storage_sqlite::KnownDestinationsStore>,
1304 > = None;
1305 let (known_destinations, ratchet_store): (
1306 std::collections::HashMap<[u8; 16], storage::KnownDestination>,
1307 Arc<dyn storage::RatchetStore>,
1308 ) = match crate::storage_sqlite::KnownDestinationsStore::open(&known_destinations_db, 16) {
1309 Ok(store) => {
1310 let store = Arc::new(store);
1311 let mut destinations: std::collections::HashMap<
1312 [u8; 16],
1313 storage::KnownDestination,
1314 > = store
1315 .load_all()
1316 .map(|entries| entries.into_iter().collect())
1317 .unwrap_or_else(|err| {
1318 log::warn!("failed to load known destinations from database: {}", err);
1319 Default::default()
1320 });
1321 if destinations.is_empty() && known_destinations_path.is_file() {
1322 match store.import_legacy(&known_destinations_path) {
1323 Ok(imported) if imported > 0 => {
1324 log::info!(
1325 "imported {} known destinations from legacy storage",
1326 imported
1327 );
1328 let _ = std::fs::rename(
1329 &known_destinations_path,
1330 known_destinations_path.with_extension("imported"),
1331 );
1332 destinations = store
1333 .load_all()
1334 .map(|entries| entries.into_iter().collect())
1335 .unwrap_or_default();
1336 }
1337 Ok(_) => {}
1338 Err(err) => {
1339 log::warn!("could not import legacy known destinations: {}", err)
1340 }
1341 }
1342 }
1343 let known_destination_hashes: HashSet<[u8; 16]> =
1344 destinations.keys().copied().collect();
1345 if let Err(err) = store.import_legacy_ratchets(
1346 &paths.ratchets,
1347 time::now() - rns_config.reticulum.ratchet_expiry as f64,
1348 &known_destination_hashes,
1349 ) {
1350 log::warn!("could not import legacy ratchets: {}", err);
1351 }
1352 known_destinations_store = Some(Arc::clone(&store));
1353 (destinations, store)
1354 }
1355 Err(err) => {
1356 log::warn!(
1357 "could not initialise known destinations database, falling back to legacy storage: {}",
1358 err
1359 );
1360 let destinations = match storage::load_known_destinations(&known_destinations_path)
1361 {
1362 Ok(destinations) => destinations,
1363 Err(err) if err.kind() == io::ErrorKind::NotFound => Default::default(),
1364 Err(err) => {
1365 log::warn!("failed to load known destinations: {}", err);
1366 Default::default()
1367 }
1368 };
1369 (
1370 destinations,
1371 Arc::new(storage::FsRatchetStore::new(paths.ratchets.clone())),
1372 )
1373 }
1374 };
1375 let known_destination_hashes: HashSet<[u8; 16]> =
1376 known_destinations.keys().copied().collect();
1377 match ratchet_store.cleanup(
1378 &known_destination_hashes,
1379 time::now(),
1380 rns_config.reticulum.ratchet_expiry as f64,
1381 ) {
1382 Ok(stats) if stats.processed > 0 => log::debug!(
1383 "Processed {} ratchets, not in use {}, removed {}",
1384 stats.processed,
1385 stats.not_known,
1386 stats.removed
1387 ),
1388 Ok(_) => {}
1389 Err(err) => log::warn!("failed to clean ratchets: {}", err),
1390 }
1391
1392 let interface_management = if private || !rns_config.reticulum.enable_interface_management {
1393 None
1394 } else {
1395 Some(InterfaceManagementConfig {
1396 config_file: config_file.clone(),
1397 storage_dir: paths.storage.clone(),
1398 initial_ids,
1399 queue_capacity: rns_config.reticulum.interface_writer_queue_capacity,
1400 transport_enabled: rns_config.reticulum.enable_transport,
1401 underlay_mark: underlay_mark.or(rns_config.reticulum.underlay_mark),
1402 })
1403 };
1404 let node_config = NodeConfig {
1405 transport_enabled: rns_config.reticulum.enable_transport,
1406 static_transport_identity: rns_config.reticulum.static_transport_identity,
1407 local_hops_delta: rns_config.reticulum.local_hops_delta,
1408 identity: Some(identity),
1409 share_instance: !private && rns_config.reticulum.share_instance,
1410 instance_name: rns_config.reticulum.instance_name.clone(),
1411 shared_instance_port: rns_config.reticulum.shared_instance_port,
1412 rpc_port: rns_config.reticulum.instance_control_port,
1413 cache_dir: Some(paths.cache),
1414 ratchet_store: Some(Arc::clone(&ratchet_store)),
1415 ratchet_expiry: Duration::from_secs(rns_config.reticulum.ratchet_expiry),
1416 management: if private {
1417 crate::management::ManagementConfig::default()
1418 } else {
1419 crate::management::ManagementConfig {
1420 enable_remote_management: rns_config.reticulum.enable_remote_management,
1421 remote_management_allowed: mgmt_allowed,
1422 publish_blackhole: rns_config.reticulum.publish_blackhole,
1423 }
1424 },
1425 probe_port: rns_config.reticulum.probe_port,
1426 probe_addrs,
1427 probe_protocol,
1428 direct_connect_policy: rns_config.reticulum.direct_connect_policy,
1429 direct_connect_allow_private_endpoints: rns_config
1430 .reticulum
1431 .direct_connect_allow_private_endpoints,
1432 device: rns_config.reticulum.device.clone(),
1433 underlay_mark: underlay_mark.or(rns_config.reticulum.underlay_mark),
1434 hooks: if private {
1435 Vec::new()
1436 } else {
1437 rns_config.hooks.clone()
1438 },
1439 discover_interfaces: rns_config.reticulum.discover_interfaces,
1440 autoconnect_interface_mode: rns_config
1441 .reticulum
1442 .autoconnect_interface_mode
1443 .as_deref()
1444 .and_then(parse_optional_interface_mode),
1445 autoconnect_interface_gravity: rns_config.reticulum.autoconnect_interface_gravity,
1446 autoconnect_announces_to_internal: rns_config
1447 .reticulum
1448 .autoconnect_announces_to_internal,
1449 autoconnect_unverified_implementations: rns_config
1450 .reticulum
1451 .autoconnect_unverified_implementations,
1452 discovery_required_value: rns_config.reticulum.required_discovery_value,
1453 respond_to_probes: rns_config.reticulum.respond_to_probes,
1454 prefer_shorter_path: rns_config.reticulum.prefer_shorter_path,
1455 max_paths_per_destination: rns_config.reticulum.max_paths_per_destination,
1456 packet_hashlist_max_entries: rns_config.reticulum.packet_hashlist_max_entries,
1457 packet_hashlist_allocation: rns_config.reticulum.packet_hashlist_allocation,
1458 max_discovery_pr_tags: rns_config.reticulum.max_discovery_pr_tags,
1459 max_path_destinations: rns_config.reticulum.max_path_destinations,
1460 max_tunnel_destinations_total: rns_config.reticulum.max_tunnel_destinations_total,
1461 known_destinations_ttl: Duration::from_secs(
1462 rns_config.reticulum.known_destinations_ttl,
1463 ),
1464 known_destinations_max_entries: rns_config.reticulum.known_destinations_max_entries,
1465 announce_table_ttl: Duration::from_secs(rns_config.reticulum.announce_table_ttl),
1466 announce_table_max_bytes: rns_config.reticulum.announce_table_max_bytes,
1467 driver_event_queue_capacity: rns_config.reticulum.driver_event_queue_capacity,
1468 interface_writer_queue_capacity: rns_config.reticulum.interface_writer_queue_capacity,
1469 announce_rate_defaults: AnnounceRateDefaults {
1470 target: rns_config.reticulum.default_ar_target,
1471 penalty: rns_config.reticulum.default_ar_penalty,
1472 grace: rns_config.reticulum.default_ar_grace,
1473 },
1474 ingress_control_defaults: rns_core::transport::types::IngressControlConfig {
1475 enabled: true,
1476 egress_enabled: rns_config.reticulum.default_egress_control,
1477 max_held_announces: rns_config.reticulum.default_ic_max_held_announces,
1478 burst_freq_new: rns_config.reticulum.default_ic_burst_freq_new,
1479 burst_freq: rns_config.reticulum.default_ic_burst_freq,
1480 pr_burst_freq_new: rns_config.reticulum.default_ic_pr_burst_freq_new,
1481 pr_burst_freq: rns_config.reticulum.default_ic_pr_burst_freq,
1482 egress_pr_freq: rns_config.reticulum.default_ec_pr_freq,
1483 new_time: rns_config.reticulum.default_ic_new_time,
1484 burst_hold: rns_config.reticulum.default_ic_burst_hold,
1485 burst_penalty: rns_config.reticulum.default_ic_burst_penalty,
1486 held_release_interval: rns_config.reticulum.default_ic_held_release_interval,
1487 },
1488 #[cfg(feature = "iface-backbone")]
1489 backbone_peer_pool: if rns_config.reticulum.backbone_peer_pool_max_connected > 0 {
1490 Some(BackbonePeerPoolSettings {
1491 max_connected: rns_config.reticulum.backbone_peer_pool_max_connected,
1492 failure_threshold: rns_config.reticulum.backbone_peer_pool_failure_threshold,
1493 failure_window: Duration::from_secs(
1494 rns_config.reticulum.backbone_peer_pool_failure_window,
1495 ),
1496 cooldown: Duration::from_secs(rns_config.reticulum.backbone_peer_pool_cooldown),
1497 })
1498 } else {
1499 None
1500 },
1501 announce_sig_cache_enabled: rns_config.reticulum.announce_sig_cache_enabled,
1502 announce_sig_cache_max_entries: rns_config.reticulum.announce_sig_cache_max_entries,
1503 announce_sig_cache_ttl: Duration::from_secs(
1504 rns_config.reticulum.announce_sig_cache_ttl,
1505 ),
1506 interfaces: interface_configs,
1507 registry: None,
1508 panic_on_interface_error: private || rns_config.reticulum.panic_on_interface_error,
1509 link_proof_verify_threads: Default::default(),
1510 stamps: rns_config.stamps.clone(),
1511 #[cfg(feature = "hooks")]
1512 provider_bridge: if !private && rns_config.reticulum.provider_bridge {
1513 Some(crate::provider_bridge::ProviderBridgeConfig {
1514 enabled: true,
1515 socket_path: rns_config
1516 .reticulum
1517 .provider_socket_path
1518 .as_ref()
1519 .map(std::path::PathBuf::from)
1520 .unwrap_or_else(|| config_dir.join("provider.sock")),
1521 queue_max_events: rns_config.reticulum.provider_queue_max_events,
1522 queue_max_bytes: rns_config.reticulum.provider_queue_max_bytes,
1523 overflow_policy: match rns_config.reticulum.provider_overflow_policy.as_str() {
1524 "drop_oldest" => crate::provider_bridge::OverflowPolicy::DropOldest,
1525 _ => crate::provider_bridge::OverflowPolicy::DropNewest,
1526 },
1527 node_instance: rns_config.reticulum.instance_name.clone(),
1528 })
1529 } else {
1530 None
1531 },
1532 };
1533
1534 let mut node = Self::start_with_queue_config(
1535 node_config,
1536 callbacks,
1537 NodeQueueConfig {
1538 rpc_auth_key,
1539 announce_max_entries: rns_config.reticulum.announce_queue_max_entries,
1540 announce_max_interfaces: rns_config.reticulum.announce_queue_max_interfaces,
1541 announce_max_bytes: rns_config.reticulum.announce_queue_max_bytes,
1542 announce_ttl_secs: rns_config.reticulum.announce_queue_ttl as f64,
1543 announce_overflow_policy: match rns_config
1544 .reticulum
1545 .announce_queue_overflow_policy
1546 .as_str()
1547 {
1548 "drop_newest" => AnnounceQueueOverflowPolicy::DropNewest,
1549 "drop_oldest" => AnnounceQueueOverflowPolicy::DropOldest,
1550 _ => AnnounceQueueOverflowPolicy::DropWorst,
1551 },
1552 inbound_capacities: rns_config.reticulum.inbound_queue_capacities,
1553 link_mtu_discovery,
1554 filter: rns_config.filter.clone(),
1555 hop_limiter: rns_config.reticulum.hop_limiter,
1556 },
1557 interface_management,
1558 )?;
1559
1560 node.known_destinations_path = Some(known_destinations_path.clone());
1561 node.known_destinations_store = known_destinations_store;
1562 node.set_stamp_store(crate::stamps::DestinationStampStore::new(&paths.storage));
1563 for (dest_hash, known) in known_destinations {
1564 let _ = node.query(QueryRequest::RestoreKnownDestination(
1565 crate::event::KnownDestinationEntry {
1566 dest_hash,
1567 identity_hash: known.identity_hash,
1568 public_key: known.public_key,
1569 app_data: known.app_data,
1570 hops: known.hops,
1571 received_at: known.received_at,
1572 receiving_interface: rns_core::transport::types::InterfaceId(
1573 known.receiving_interface,
1574 ),
1575 was_used: known.was_used,
1576 last_used_at: known.last_used_at,
1577 retained: known.retained,
1578 stamp: known
1579 .stamp
1580 .as_deref()
1581 .and_then(|stamp| stamp.try_into().ok()),
1582 stamp_value: known.stamp_value,
1583 lr_cost: known.lr_cost,
1584 },
1585 ));
1586 }
1587
1588 Ok(node)
1589 }
1590
1591 pub fn start(config: NodeConfig, callbacks: Box<dyn Callbacks>) -> io::Result<Self> {
1593 crate::interface::validate_underlay_mark(config.underlay_mark)?;
1594 let inbound_queue_capacities = crate::event::InboundQueueCapacities::from_shared_capacity(
1595 config.driver_event_queue_capacity,
1596 );
1597 Self::start_with_queue_config(
1598 config,
1599 callbacks,
1600 NodeQueueConfig {
1601 inbound_capacities: inbound_queue_capacities,
1602 ..NodeQueueConfig::default()
1603 },
1604 None,
1605 )
1606 }
1607
1608 fn start_with_queue_config(
1609 config: NodeConfig,
1610 callbacks: Box<dyn Callbacks>,
1611 queue_config: NodeQueueConfig,
1612 interface_management: Option<InterfaceManagementConfig>,
1613 ) -> io::Result<Self> {
1614 let storage_dir = config.cache_dir.as_ref().map(|cache_dir| {
1615 cache_dir
1616 .parent()
1617 .unwrap_or_else(|| std::path::Path::new("."))
1618 .join("storage")
1619 });
1620 let transport_state_dir = if config.transport_enabled {
1621 storage_dir.clone()
1622 } else {
1623 None
1624 };
1625 let identity = config.identity.unwrap_or_else(|| Identity::new(&mut OsRng));
1626 let rpc_auth_key = queue_config.rpc_auth_key.unwrap_or_else(|| {
1627 crate::rpc::derive_auth_key(
1628 &identity
1629 .get_private_key()
1630 .expect("node identity has private key"),
1631 )
1632 });
1633 let has_discoverable_interface = config
1634 .interfaces
1635 .iter()
1636 .any(|iface| iface.discovery.is_some());
1637 let static_transport_identity = effective_static_transport_identity(
1638 config.static_transport_identity,
1639 config.transport_enabled,
1640 has_discoverable_interface,
1641 );
1642 if static_transport_identity && !config.static_transport_identity {
1643 log::warn!(
1644 "Discoverable interface was configured, enabling static transport identity on non-transport instance"
1645 );
1646 }
1647 let transport_identity = if config.transport_enabled || static_transport_identity {
1648 Identity::from_private_key(
1649 &identity
1650 .get_private_key()
1651 .expect("node identity must expose a private key"),
1652 )
1653 } else {
1654 Identity::new(&mut OsRng)
1655 };
1656
1657 let local_hops_delta = if config.local_hops_delta {
1658 let mut byte = [0u8; 1];
1659 OsRng.fill_bytes(&mut byte);
1660 byte[0] % 6 + 2
1661 } else {
1662 0
1663 };
1664
1665 let transport_config = TransportConfig {
1666 transport_enabled: config.transport_enabled,
1667 identity_hash: Some(*transport_identity.hash()),
1668 local_hops_delta,
1669 prefer_shorter_path: config.prefer_shorter_path,
1670 max_paths_per_destination: config.max_paths_per_destination,
1671 packet_hashlist_max_entries: config.packet_hashlist_max_entries,
1672 packet_hashlist_allocation: config.packet_hashlist_allocation,
1673 max_discovery_pr_tags: config.max_discovery_pr_tags,
1674 max_path_destinations: config.max_path_destinations,
1675 max_tunnel_destinations_total: config.max_tunnel_destinations_total,
1676 destination_timeout_secs: config.known_destinations_ttl.as_secs_f64(),
1677 announce_table_ttl_secs: config.announce_table_ttl.as_secs_f64(),
1678 announce_table_max_bytes: config.announce_table_max_bytes,
1679 announce_sig_cache_enabled: config.announce_sig_cache_enabled,
1680 announce_sig_cache_max_entries: config.announce_sig_cache_max_entries,
1681 announce_sig_cache_ttl_secs: config.announce_sig_cache_ttl.as_secs_f64(),
1682 announce_queue_max_entries: queue_config.announce_max_entries,
1683 announce_queue_max_interfaces: queue_config.announce_max_interfaces,
1684 };
1685
1686 let (tx, rx) = event::channel_with_queue_capacities(
1687 config.driver_event_queue_capacity,
1688 queue_config.inbound_capacities,
1689 );
1690 let tick_interval_ms = Arc::new(AtomicU64::new(1000));
1691 let mut driver = Driver::new(transport_config, rx, tx.clone(), callbacks);
1692 let stamper = Arc::new(crate::stamper::Stamper::new(tx.clone(), None));
1693 driver.set_stamper(Arc::clone(&stamper));
1694 let stamp_destinations = Arc::new(AtomicBool::new(rns_core::constants::STAMP_DESTINATIONS));
1695 driver.set_stamp_destinations_flag(Arc::clone(&stamp_destinations));
1696 driver.apply_stamp_config(&config.stamps, queue_config.hop_limiter);
1697 *driver.engine.destination_filter_mut() = queue_config.filter.destination_filter();
1698 let stamp_targets = crate::stamper::StampTargets::from(&config.stamps);
1699 driver.underlay_mark = config.underlay_mark;
1700 driver
1701 .link_manager
1702 .set_link_mtu_discovery(queue_config.link_mtu_discovery);
1703 driver
1704 .link_manager
1705 .set_proof_verify_threads(config.link_proof_verify_threads);
1706 driver.set_announce_verify_queue_config(
1707 queue_config.announce_max_entries,
1708 queue_config.announce_max_bytes,
1709 queue_config.announce_ttl_secs,
1710 queue_config.announce_overflow_policy,
1711 );
1712 driver.async_announce_verification = true;
1713 driver.set_tick_interval_handle(Arc::clone(&tick_interval_ms));
1714 let accepting_work = driver.accepting_work_handle();
1715 driver.known_destinations_ttl = config.known_destinations_ttl.as_secs_f64();
1716 driver.known_destinations_max_entries = config.known_destinations_max_entries;
1717 driver.ratchet_store = config.ratchet_store.clone();
1718 driver.ratchet_expiry_secs = config.ratchet_expiry.as_secs_f64();
1719 driver.interface_writer_queue_capacity = config.interface_writer_queue_capacity;
1720 driver.set_announce_rate_defaults(config.announce_rate_defaults);
1721 driver.set_ingress_control_defaults(config.ingress_control_defaults);
1722 driver.runtime_config_defaults.known_destinations_ttl =
1723 config.known_destinations_ttl.as_secs_f64();
1724 driver
1725 .holepunch_manager
1726 .set_policy(config.direct_connect_policy);
1727 driver
1728 .holepunch_manager
1729 .set_allow_private_endpoints(config.direct_connect_allow_private_endpoints);
1730 driver.runtime_config_defaults.direct_connect_policy = config.direct_connect_policy;
1731 #[cfg(feature = "hooks")]
1732 if let Some(provider_config) = config.provider_bridge.clone() {
1733 driver.runtime_config_defaults.provider_queue_max_events =
1734 provider_config.queue_max_events;
1735 driver.runtime_config_defaults.provider_queue_max_bytes =
1736 provider_config.queue_max_bytes;
1737 if provider_config.enabled {
1738 let started = if crate::engine::FOLD_SERVICES {
1739 crate::provider_bridge::ProviderBridge::start_folded(
1740 provider_config,
1741 Arc::clone(tx.poller()),
1742 )
1743 } else {
1744 crate::provider_bridge::ProviderBridge::start(provider_config)
1745 };
1746 match started {
1747 Ok(bridge) => driver.provider_bridge = Some(bridge),
1748 Err(err) => log::warn!("failed to start provider bridge: {}", err),
1749 }
1750 }
1751 }
1752
1753 if let Some(ref cache_dir) = config.cache_dir {
1755 let announces_dir = cache_dir.join("announces");
1756 let _ = std::fs::create_dir_all(&announces_dir);
1757 driver.announce_cache = Some(crate::announce_cache::AnnounceCache::new(announces_dir));
1758 }
1759
1760 if !config.probe_addrs.is_empty()
1762 || config.device.is_some()
1763 || config.underlay_mark.is_some()
1764 {
1765 driver.set_probe_config(
1766 config.probe_addrs.clone(),
1767 config.probe_protocol,
1768 config.device.clone(),
1769 );
1770 }
1771
1772 let probe_server = if let Some(port) = config.probe_port {
1774 let listen_addr: std::net::SocketAddr = ([0, 0, 0, 0], port).into();
1775 if crate::engine::FOLD_SERVICES {
1776 match crate::holepunch::probe::FoldedProbeServer::bind(
1777 listen_addr,
1778 config.underlay_mark,
1779 Arc::clone(tx.poller()),
1780 ) {
1781 Ok(server) => {
1782 log::info!("Probe server started on 0.0.0.0:{}", port);
1783 driver.probe_server = Some(server);
1784 }
1785 Err(e) => log::error!("Failed to start probe server on port {}: {}", port, e),
1786 }
1787 None
1788 } else {
1789 match crate::holepunch::probe::start_probe_server_with_mark(
1790 listen_addr,
1791 config.underlay_mark,
1792 ) {
1793 Ok(handle) => {
1794 log::info!("Probe server started on 0.0.0.0:{}", port);
1795 Some(handle)
1796 }
1797 Err(e) => {
1798 log::error!("Failed to start probe server on port {}: {}", port, e);
1799 None
1800 }
1801 }
1802 }
1803 } else {
1804 None
1805 };
1806
1807 driver.management_config = config.management.clone();
1809
1810 driver.transport_identity = Some(transport_identity);
1812
1813 #[cfg(feature = "hooks")]
1815 {
1816 for hook_cfg in &config.hooks {
1817 if !hook_cfg.enabled {
1818 continue;
1819 }
1820 let point_idx = match config::parse_hook_point(&hook_cfg.attach_point) {
1821 Some(idx) => idx,
1822 None => {
1823 log::warn!(
1824 "Unknown hook point '{}' for hook '{}'",
1825 hook_cfg.attach_point,
1826 hook_cfg.name,
1827 );
1828 continue;
1829 }
1830 };
1831 let mgr = match driver.hook_manager.as_ref() {
1832 Some(m) => m,
1833 None => {
1834 log::warn!(
1835 "Hook manager not available, skipping hook '{}'",
1836 hook_cfg.name
1837 );
1838 continue;
1839 }
1840 };
1841 let hook_backend = match config::parse_hook_backend(&hook_cfg.hook_type) {
1842 Ok(backend) => backend,
1843 Err(e) => {
1844 log::warn!(
1845 "Invalid hook type '{}' for hook '{}': {}",
1846 hook_cfg.hook_type,
1847 hook_cfg.name,
1848 e,
1849 );
1850 continue;
1851 }
1852 };
1853 let load_result = if hook_backend == rns_hooks::HookBackend::Builtin {
1854 let builtin_id = hook_cfg
1855 .builtin_id
1856 .as_deref()
1857 .filter(|id| !id.is_empty())
1858 .or_else(|| (!hook_cfg.path.is_empty()).then_some(hook_cfg.path.as_str()));
1859 match builtin_id {
1860 Some(id) => mgr.load_builtin(hook_cfg.name.clone(), id, hook_cfg.priority),
1861 None => Err(rns_hooks::HookError::CompileError(
1862 "built-in hook requires builtin/id or path".to_string(),
1863 )),
1864 }
1865 } else {
1866 mgr.load_file_backend(
1867 hook_cfg.name.clone(),
1868 std::path::Path::new(&hook_cfg.path),
1869 hook_cfg.priority,
1870 hook_backend,
1871 )
1872 };
1873 match load_result {
1874 Ok(program) => {
1875 driver.hook_slots[point_idx].attach(program);
1876 log::info!(
1877 "Loaded hook '{}' at point {} (priority {})",
1878 hook_cfg.name,
1879 hook_cfg.attach_point,
1880 hook_cfg.priority,
1881 );
1882 }
1883 Err(e) => {
1884 log::error!(
1885 "Failed to load hook '{}' from '{}': {}",
1886 hook_cfg.name,
1887 hook_cfg.path,
1888 e,
1889 );
1890 }
1891 }
1892 }
1893 }
1894
1895 driver.discover_interfaces = config.discover_interfaces;
1897 driver.autoconnect_interface_mode = config.autoconnect_interface_mode;
1898 driver.autoconnect_interface_gravity = config.autoconnect_interface_gravity;
1899 driver.autoconnect_announces_to_internal =
1900 config.autoconnect_announces_to_internal.then_some(true);
1901 driver.autoconnect_unverified_implementations =
1902 config.autoconnect_unverified_implementations;
1903 if let Some(val) = config.discovery_required_value {
1904 driver.discovery_required_value = val;
1905 }
1906
1907 let next_dynamic_id = Arc::new(AtomicU64::new(10000));
1909 driver.next_dynamic_interface_id = Arc::clone(&next_dynamic_id);
1910
1911 let mut discoverable_interfaces = Vec::new();
1913 #[cfg(feature = "iface-backbone")]
1914 let mut backbone_peer_pool_candidates = Vec::new();
1915
1916 let initial_managed_ids = interface_management
1917 .as_ref()
1918 .map(|management| management.initial_ids.clone())
1919 .unwrap_or_default();
1920 driver.interface_management = interface_management;
1921
1922 let registry = config
1924 .registry
1925 .unwrap_or_else(crate::interface::registry::InterfaceRegistry::with_builtins);
1926 for iface_config in config.interfaces {
1927 let managed_id = initial_managed_ids.get(&iface_config.name).copied();
1928 #[cfg(feature = "iface-backbone")]
1929 if iface_config.type_name == "BackboneInterface" {
1930 if let Some(mode) = iface_config
1931 .config_data
1932 .as_any()
1933 .downcast_ref::<BackboneMode>()
1934 {
1935 if let Some(handle) = runtime_handle_from_mode(mode) {
1936 driver.register_backbone_runtime(handle);
1937 }
1938 if let Some(handle) = peer_state_handle_from_mode(mode) {
1939 driver.register_backbone_peer_state(handle);
1940 }
1941 if let Some(handle) = client_runtime_handle_from_mode(mode) {
1942 driver.register_backbone_client_runtime(handle);
1943 }
1944 if let Some(handle) = backbone_discovery_runtime_from_interface(
1945 &iface_config.name,
1946 mode,
1947 iface_config.discovery.as_ref(),
1948 config.transport_enabled,
1949 iface_config.ifac.as_ref(),
1950 ) {
1951 driver.register_backbone_discovery_runtime(handle);
1952 }
1953 }
1954 }
1955 #[cfg(feature = "iface-tcp")]
1956 if iface_config.type_name == "TCPClientInterface" {
1957 if let Some(tcp_config) = iface_config
1958 .config_data
1959 .as_any()
1960 .downcast_ref::<TcpClientConfig>()
1961 {
1962 driver.register_tcp_client_runtime(tcp_client_runtime_handle_from_config(
1963 tcp_config,
1964 ));
1965 }
1966 }
1967 #[cfg(feature = "iface-tcp")]
1968 if iface_config.type_name == "TCPServerInterface" {
1969 if let Some(tcp_config) = iface_config
1970 .config_data
1971 .as_any()
1972 .downcast_ref::<TcpServerConfig>()
1973 {
1974 driver.register_tcp_server_runtime(tcp_runtime_handle_from_config(tcp_config));
1975 driver.register_tcp_server_discovery_runtime(
1976 tcp_server_discovery_runtime_from_interface(
1977 &iface_config.name,
1978 tcp_config,
1979 iface_config.discovery.as_ref(),
1980 config.transport_enabled,
1981 iface_config.ifac.as_ref(),
1982 ),
1983 );
1984 }
1985 }
1986 #[cfg(feature = "iface-udp")]
1987 if iface_config.type_name == "UDPInterface" {
1988 if let Some(udp_config) = iface_config
1989 .config_data
1990 .as_any()
1991 .downcast_ref::<UdpConfig>()
1992 {
1993 driver.register_udp_runtime(udp_runtime_handle_from_config(udp_config));
1994 }
1995 }
1996 #[cfg(feature = "iface-auto")]
1997 if iface_config.type_name == "AutoInterface" {
1998 if let Some(auto_config) = iface_config
1999 .config_data
2000 .as_any()
2001 .downcast_ref::<AutoConfig>()
2002 {
2003 driver.register_auto_runtime(auto_runtime_handle_from_config(auto_config));
2004 }
2005 }
2006 #[cfg(feature = "iface-i2p")]
2007 if iface_config.type_name == "I2PInterface" {
2008 if let Some(i2p_config) = iface_config
2009 .config_data
2010 .as_any()
2011 .downcast_ref::<I2pConfig>()
2012 {
2013 driver.register_i2p_runtime(i2p_runtime_handle_from_config(i2p_config));
2014 }
2015 }
2016 #[cfg(feature = "iface-pipe")]
2017 if iface_config.type_name == "PipeInterface" {
2018 if let Some(pipe_config) = iface_config
2019 .config_data
2020 .as_any()
2021 .downcast_ref::<PipeConfig>()
2022 {
2023 driver.register_pipe_runtime(pipe_runtime_handle_from_config(pipe_config));
2024 }
2025 }
2026 #[cfg(feature = "iface-rnode")]
2027 if iface_config.type_name == "RNodeInterface" {
2028 if let Some(rnode_config) = iface_config
2029 .config_data
2030 .as_any()
2031 .downcast_ref::<RNodeConfig>()
2032 {
2033 driver.register_rnode_runtime(rnode_runtime_handle_from_config(rnode_config));
2034 }
2035 }
2036
2037 let factory = match registry.get(&iface_config.type_name) {
2038 Some(f) => f,
2039 None => {
2040 log::warn!(
2041 "No factory registered for interface type '{}'",
2042 iface_config.type_name
2043 );
2044 continue;
2045 }
2046 };
2047
2048 let mut ifac_state = derive_ifac_state(iface_config.ifac.as_ref(), &iface_config.name)?;
2049 let ifac_runtime =
2050 ifac_runtime_from_config(iface_config.ifac.as_ref(), factory.default_ifac_size());
2051
2052 #[cfg(feature = "iface-backbone")]
2053 if config.backbone_peer_pool.is_some() && iface_config.type_name == "BackboneInterface"
2054 {
2055 if let Some(mode) = iface_config
2056 .config_data
2057 .as_any()
2058 .downcast_ref::<BackboneMode>()
2059 {
2060 if let Some(client) = client_config_from_mode(mode) {
2061 let priority = client_priority_from_mode(mode).unwrap_or(
2062 crate::driver::BACKBONE_PEER_POOL_CONFIGURED_DEFAULT_PRIORITY,
2063 );
2064 backbone_peer_pool_candidates.push(BackbonePeerPoolCandidateConfig {
2065 client,
2066 mode: iface_config.mode,
2067 gravity: iface_config.gravity,
2068 recursive_prs: iface_config.recursive_prs,
2069 announces_from_internal: iface_config.announces_from_internal,
2070 announces_to_internal: iface_config.announces_to_internal,
2071 ingress_control: iface_config.ingress_control,
2072 stamp_costs: iface_config.stamp_costs,
2073 ifac_runtime: ifac_runtime.clone(),
2074 ifac_enabled: ifac_state.is_some(),
2075 interface_type_name: iface_config.type_name.clone(),
2076 source: crate::driver::BackbonePeerPoolCandidateSource::Configured,
2077 priority,
2078 discovery: None,
2079 });
2080 if let Some(ref disc) = iface_config.discovery {
2081 discoverable_interfaces.push(discoverable_interface_from_config(
2082 &iface_config.name,
2083 disc,
2084 config.transport_enabled,
2085 iface_config.ifac.as_ref(),
2086 ));
2087 }
2088 continue;
2089 }
2090 }
2091 }
2092
2093 let ctx = crate::interface::StartContext {
2094 tx: tx.clone(),
2095 next_dynamic_id: next_dynamic_id.clone(),
2096 mode: iface_config.mode,
2097 gravity: iface_config.gravity,
2098 recursive_prs: iface_config.recursive_prs,
2099 announces_from_internal: iface_config.announces_from_internal,
2100 announces_to_internal: iface_config.announces_to_internal,
2101 ingress_control: iface_config.ingress_control,
2102 stamp_costs: iface_config.stamp_costs,
2103 ifac: ifac_state.clone(),
2104 underlay_mark: config.underlay_mark,
2105 };
2106
2107 let result = match factory.start(iface_config.config_data, ctx) {
2108 Ok(r) => r,
2109 Err(e) => {
2110 if config.panic_on_interface_error {
2111 return Err(e);
2112 }
2113 log::error!(
2114 "Interface '{}' ({}) failed to start: {}",
2115 iface_config.name,
2116 iface_config.type_name,
2117 e
2118 );
2119 continue;
2120 }
2121 };
2122
2123 if let Some(ref disc) = iface_config.discovery {
2124 discoverable_interfaces.push(discoverable_interface_from_config(
2125 &iface_config.name,
2126 disc,
2127 config.transport_enabled,
2128 iface_config.ifac.as_ref(),
2129 ));
2130 }
2131
2132 match result {
2133 crate::interface::StartResult::Simple {
2134 id,
2135 info,
2136 writer,
2137 interface_type_name,
2138 control,
2139 } => {
2140 let managed_control = control.clone();
2141 if let Some(control) = control {
2142 driver.register_listener_control(control);
2143 }
2144 if let Some(parent_id) = managed_id {
2145 driver.managed_interfaces.insert(
2146 iface_config.name.clone(),
2147 crate::driver::ManagedInterface {
2148 parent_id,
2149 type_name: iface_config.type_name.clone(),
2150 control: managed_control,
2151 static_ids: vec![id],
2152 },
2153 );
2154 }
2155 register_started_interface(StartedInterface {
2156 driver: &mut driver,
2157 tx: &tx,
2158 queue_capacity: config.interface_writer_queue_capacity,
2159 id,
2160 info,
2161 writer,
2162 interface_type_name,
2163 ifac_state,
2164 ifac_runtime: &ifac_runtime,
2165 stamp_costs: iface_config.stamp_costs,
2166 });
2167 }
2168 crate::interface::StartResult::Listener { control } => {
2169 let managed_control = control.clone();
2172 if let Some(control) = control {
2173 driver.register_listener_control(control);
2174 }
2175 if let Some(parent_id) = managed_id {
2176 driver.managed_interfaces.insert(
2177 iface_config.name.clone(),
2178 crate::driver::ManagedInterface {
2179 parent_id,
2180 type_name: iface_config.type_name.clone(),
2181 control: managed_control,
2182 static_ids: Vec::new(),
2183 },
2184 );
2185 }
2186 }
2187 crate::interface::StartResult::Multi {
2188 subinterfaces: subs,
2189 control,
2190 } => {
2191 let managed_control = control.clone();
2192 if let Some(control) = control {
2193 driver.register_listener_control(control);
2194 }
2195 let static_ids = subs.iter().map(|sub| sub.id).collect();
2196 if let Some(parent_id) = managed_id {
2197 driver.managed_interfaces.insert(
2198 iface_config.name.clone(),
2199 crate::driver::ManagedInterface {
2200 parent_id,
2201 type_name: iface_config.type_name.clone(),
2202 control: managed_control,
2203 static_ids,
2204 },
2205 );
2206 }
2207 let ifac_cfg = &iface_config.ifac;
2208 let mut first = true;
2209 for sub in subs {
2210 let sub_ifac = if first {
2211 first = false;
2212 ifac_state.take()
2213 } else {
2214 derive_ifac_state(ifac_cfg.as_ref(), &sub.info.name)?
2215 };
2216 register_started_interface(StartedInterface {
2217 driver: &mut driver,
2218 tx: &tx,
2219 queue_capacity: config.interface_writer_queue_capacity,
2220 id: sub.id,
2221 info: sub.info,
2222 writer: sub.writer,
2223 interface_type_name: sub.interface_type_name,
2224 ifac_state: sub_ifac,
2225 ifac_runtime: &ifac_runtime,
2226 stamp_costs: iface_config.stamp_costs,
2227 });
2228 }
2229 }
2230 }
2231 }
2232
2233 if !discoverable_interfaces.is_empty() {
2235 let transport_id = *identity.hash();
2236 let announcer =
2237 crate::discovery::InterfaceAnnouncer::new(transport_id, discoverable_interfaces);
2238 log::info!("Interface discovery announcer initialized");
2239 driver.interface_announcer = Some(announcer);
2240 }
2241
2242 if let Some(ref cache_dir) = config.cache_dir {
2244 let disc_path = std::path::PathBuf::from(cache_dir)
2245 .parent()
2246 .unwrap_or(std::path::Path::new("."))
2247 .join("storage")
2248 .join("discovery")
2249 .join("interfaces");
2250 let _ = std::fs::create_dir_all(&disc_path);
2251 driver.discovered_interfaces =
2252 crate::discovery::DiscoveredInterfaceStorage::new(disc_path);
2253 }
2254
2255 if let Some(ref storage_dir) = transport_state_dir {
2258 match storage::load_transport_state(storage_dir) {
2259 Ok(snapshot) => {
2260 let announce_cache = driver.announce_cache.as_ref();
2261 let stats = driver.engine.restore_persistence_snapshot(
2262 snapshot,
2263 time::now(),
2264 |packet_hash| {
2265 announce_cache
2266 .and_then(|cache| cache.get(packet_hash).ok().flatten())
2267 .map(|(raw, _)| raw)
2268 },
2269 );
2270 log::debug!(
2271 "restored transport state: {} packet hashes, {} paths, {} tunnels ({} paths skipped)",
2272 stats.packet_hashes,
2273 stats.paths,
2274 stats.tunnels,
2275 stats.skipped_paths,
2276 );
2277 }
2278 Err(err) => log::warn!("failed to restore transport state: {}", err),
2279 }
2280 }
2281
2282 #[cfg(feature = "iface-backbone")]
2283 if let Some(settings) = config.backbone_peer_pool.clone() {
2284 driver.configure_backbone_peer_pool(settings, backbone_peer_pool_candidates);
2285 driver.seed_backbone_peer_pool_from_discovery_cache();
2286 }
2287
2288 if config.management.enable_remote_management {
2290 if let Some(prv_key) = identity.get_private_key() {
2291 let identity_hash = *identity.hash();
2292 let mgmt_dest = crate::management::management_dest_hash(&identity_hash);
2293
2294 let sig_prv = rns_crypto::ed25519::Ed25519PrivateKey::from_bytes(
2296 &prv_key[32..64].try_into().unwrap(),
2297 );
2298 let sig_pub_bytes: [u8; 32] = identity.get_public_key().unwrap()[32..64]
2299 .try_into()
2300 .unwrap();
2301
2302 driver
2304 .engine
2305 .register_destination(mgmt_dest, rns_core::constants::DESTINATION_SINGLE);
2306 driver
2307 .local_destinations
2308 .insert(mgmt_dest, rns_core::constants::DESTINATION_SINGLE);
2309
2310 driver.link_manager.register_link_destination(
2312 mgmt_dest,
2313 sig_prv,
2314 sig_pub_bytes,
2315 crate::link_manager::ResourceStrategy::AcceptNone,
2316 );
2317
2318 driver
2320 .link_manager
2321 .register_management_path(crate::management::status_path_hash());
2322 driver
2323 .link_manager
2324 .register_management_path(crate::management::path_path_hash());
2325
2326 log::info!("Remote management enabled on {:02x?}", &mgmt_dest[..4],);
2327
2328 if !config.management.remote_management_allowed.is_empty() {
2330 log::info!(
2331 "Remote management allowed for {} identities",
2332 config.management.remote_management_allowed.len(),
2333 );
2334 }
2335 }
2336 }
2337
2338 if config.management.publish_blackhole {
2339 if let Some(prv_key) = identity.get_private_key() {
2340 let identity_hash = *identity.hash();
2341 let bh_dest = crate::management::blackhole_dest_hash(&identity_hash);
2342
2343 let sig_prv = rns_crypto::ed25519::Ed25519PrivateKey::from_bytes(
2344 &prv_key[32..64].try_into().unwrap(),
2345 );
2346 let sig_pub_bytes: [u8; 32] = identity.get_public_key().unwrap()[32..64]
2347 .try_into()
2348 .unwrap();
2349
2350 driver
2351 .engine
2352 .register_destination(bh_dest, rns_core::constants::DESTINATION_SINGLE);
2353 driver.link_manager.register_link_destination(
2354 bh_dest,
2355 sig_prv,
2356 sig_pub_bytes,
2357 crate::link_manager::ResourceStrategy::AcceptNone,
2358 );
2359 driver
2360 .link_manager
2361 .register_management_path(crate::management::list_path_hash());
2362 driver
2365 .link_manager
2366 .link_request_costs()
2367 .set(bh_dest, rns_core::constants::BLACKHOLE_LINK_COST.as_f64());
2368
2369 log::info!(
2370 "Blackhole list publishing enabled on {:02x?}",
2371 &bh_dest[..4],
2372 );
2373 }
2374 }
2375
2376 if config.respond_to_probes && config.transport_enabled {
2378 let identity_hash = *identity.hash();
2379 let probe_dest = crate::management::probe_dest_hash(&identity_hash);
2380
2381 driver
2383 .engine
2384 .register_destination(probe_dest, rns_core::constants::DESTINATION_SINGLE);
2385 driver
2386 .local_destinations
2387 .insert(probe_dest, rns_core::constants::DESTINATION_SINGLE);
2388
2389 let probe_identity = rns_crypto::identity::Identity::from_private_key(
2391 &identity.get_private_key().unwrap(),
2392 );
2393 driver.proof_strategies.insert(
2394 probe_dest,
2395 (
2396 rns_core::types::ProofStrategy::ProveAll,
2397 Some(probe_identity),
2398 ),
2399 );
2400
2401 driver.probe_responder_hash = Some(probe_dest);
2402
2403 log::info!("Probe responder enabled on {:02x?}", &probe_dest[..4],);
2404 }
2405
2406 if !crate::engine::FOLD_TIMERS {
2409 let timer_tx = tx.clone();
2410 let timer_interval = Arc::clone(&tick_interval_ms);
2411 thread::Builder::new()
2412 .name("rns-timer".into())
2413 .spawn(move || loop {
2414 let ms = timer_interval.load(Ordering::Relaxed);
2415 thread::sleep(Duration::from_millis(ms));
2416 if timer_tx.send(Event::Tick).is_err() {
2417 break; }
2419 })?;
2420 }
2421
2422 #[cfg(feature = "iface-local")]
2424 if config.share_instance {
2425 let local_server_config = LocalServerConfig {
2426 instance_name: config.instance_name.clone(),
2427 port: config.shared_instance_port,
2428 interface_id: rns_core::transport::types::InterfaceId(
2431 next_dynamic_id.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
2432 ),
2433 };
2434 match crate::interface::local::start_server(
2435 local_server_config,
2436 tx.clone(),
2437 next_dynamic_id.clone(),
2438 ) {
2439 Ok(control) => {
2440 driver.register_listener_control(control);
2441 log::info!(
2442 "Local shared instance server started (instance={}, port={})",
2443 config.instance_name,
2444 config.shared_instance_port
2445 );
2446 }
2447 Err(e) => {
2448 log::error!("Failed to start local shared instance server: {}", e);
2449 }
2450 }
2451 }
2452
2453 let serve_rpc_threads = config.share_instance && !crate::engine::FOLD_SERVICES;
2456 if config.share_instance && crate::engine::FOLD_SERVICES {
2457 let mut service = crate::driver::RpcService::new(rpc_auth_key, Arc::clone(tx.poller()));
2458 match std::net::TcpListener::bind(("127.0.0.1", config.rpc_port)).and_then(|listener| {
2459 service.add_listener(crate::driver::RpcListener::Tcp(listener))
2460 }) {
2461 Ok(()) => log::info!("RPC server started on 127.0.0.1:{}", config.rpc_port),
2462 Err(e) => log::error!("Failed to start RPC server: {}", e),
2463 }
2464 #[cfg(target_os = "linux")]
2465 match crate::rpc::bind_abstract_unix(&config.instance_name).and_then(|listener| {
2466 service.add_listener(crate::driver::RpcListener::Unix(listener))
2467 }) {
2468 Ok(()) => log::info!(
2469 "RPC server started on abstract Unix socket for {}",
2470 config.instance_name
2471 ),
2472 Err(e) => log::error!("Failed to start abstract Unix RPC server: {e}"),
2473 }
2474 driver.rpc_service = Some(service);
2475 }
2476 let rpc_server = if serve_rpc_threads {
2477 let rpc_addr = crate::rpc::RpcAddr::Tcp("127.0.0.1".into(), config.rpc_port);
2478 match crate::rpc::RpcServer::start(&rpc_addr, rpc_auth_key, tx.clone()) {
2479 Ok(server) => {
2480 log::info!("RPC server started on 127.0.0.1:{}", config.rpc_port);
2481 Some(server)
2482 }
2483 Err(e) => {
2484 log::error!("Failed to start RPC server: {}", e);
2485 None
2486 }
2487 }
2488 } else {
2489 None
2490 };
2491 #[cfg(target_os = "linux")]
2492 let rpc_unix_server = if serve_rpc_threads {
2493 match crate::rpc::RpcServer::start_abstract_unix(
2494 &config.instance_name,
2495 rpc_auth_key,
2496 tx.clone(),
2497 ) {
2498 Ok(server) => {
2499 log::info!(
2500 "RPC server started on abstract Unix socket for {}",
2501 config.instance_name
2502 );
2503 Some(server)
2504 }
2505 Err(e) => {
2506 log::error!("Failed to start abstract Unix RPC server: {e}");
2507 None
2508 }
2509 }
2510 } else {
2511 None
2512 };
2513
2514 let announce_verify_queue = Arc::clone(&driver.announce_verify_queue);
2515 let verify_shutdown = Arc::clone(&driver.announce_verify_signal);
2516 let verify_shutdown_thread = Arc::clone(&verify_shutdown);
2517 let verify_tx = tx.clone();
2518 let verify_handle = if crate::engine::FOLD_WORK {
2521 None
2522 } else {
2523 Some(
2524 thread::Builder::new()
2525 .name("rns-verify".into())
2526 .spawn(move || {
2527 #[cfg(target_family = "unix")]
2528 {
2529 unsafe {
2530 libc::nice(5);
2531 }
2532 }
2533
2534 while !verify_shutdown_thread.stopped() {
2535 let batch = {
2536 let mut queue = announce_verify_queue
2537 .lock()
2538 .unwrap_or_else(|poisoned| poisoned.into_inner());
2539 queue.take_pending(time::now())
2540 };
2541
2542 if batch.is_empty() {
2543 verify_shutdown_thread.wait();
2545 continue;
2546 }
2547
2548 for (key, pending) in batch {
2549 if verify_shutdown_thread.stopped() {
2550 break;
2551 }
2552 let event = match crate::driver::check_pending_announce(&pending) {
2553 crate::driver::AnnounceCheck::Valid {
2554 validated,
2555 sig_cache_key,
2556 } => Event::AnnounceVerified {
2557 key,
2558 validated,
2559 sig_cache_key,
2560 },
2561 crate::driver::AnnounceCheck::Invalid { sig_cache_key } => {
2562 Event::AnnounceVerifyFailed { key, sig_cache_key }
2563 }
2564 };
2565 if verify_tx.send(event).is_err() {
2566 return;
2567 }
2568 }
2569 }
2570 })?,
2571 )
2572 };
2573
2574 log::info!("rns-net {} engine", crate::engine::ENGINE);
2578 if crate::engine::FOLD_TIMERS {
2580 driver.folded.persist_dir = transport_state_dir.clone();
2581 }
2582 let driver_handle = thread::Builder::new()
2583 .name("rns-driver".into())
2584 .spawn(move || {
2585 driver.run();
2586 })?;
2587
2588 let (persist_handle, persist_stop) = if let Some(storage_dir) = transport_state_dir
2592 .clone()
2593 .filter(|_| !crate::engine::FOLD_TIMERS)
2594 {
2595 let persist_tx = tx.clone();
2596 let (stop_tx, stop_rx) = std::sync::mpsc::channel();
2597 let handle = thread::Builder::new()
2598 .name("rns-persist".into())
2599 .spawn(move || {
2600 #[cfg(target_family = "unix")]
2601 unsafe {
2602 libc::nice(5);
2603 }
2604
2605 loop {
2606 match stop_rx.recv_timeout(Duration::from_secs(12 * 60 * 60)) {
2607 Ok(()) | Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => break,
2608 Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {}
2609 }
2610 let (response_tx, response_rx) = std::sync::mpsc::channel();
2611 if persist_tx
2612 .send(Event::query(
2613 QueryRequest::TransportStateSnapshot,
2614 response_tx,
2615 ))
2616 .is_err()
2617 {
2618 break;
2619 }
2620 let Ok(QueryResponse::TransportStateSnapshot(snapshot)) =
2621 response_rx.recv()
2622 else {
2623 continue;
2624 };
2625 if let Err(err) =
2626 storage::save_transport_state(&snapshot, &storage_dir, true)
2627 {
2628 log::warn!("failed to persist transport state: {}", err);
2629 }
2630 }
2631 })?;
2632 (Some(handle), Some(stop_tx))
2633 } else {
2634 (None, None)
2635 };
2636
2637 let mut node = RnsNode {
2638 tx,
2639 driver_handle: Some(driver_handle),
2640 verify_handle,
2641 verify_shutdown,
2642 persist_handle,
2643 persist_stop,
2644 rpc_server,
2645 #[cfg(target_os = "linux")]
2646 rpc_unix_server,
2647 tick_interval_ms,
2648 next_send_ticket: AtomicU64::new(0),
2649 encrypt_deferred_inline: std::thread::available_parallelism()
2650 .map_or(true, |cpus| cpus.get() == 1),
2651 accepting_work,
2652 probe_server,
2653 known_destinations_path: None,
2654 known_destinations_store: None,
2655 stamp_store: None,
2656 stamp_destinations,
2657 stamp_targets,
2658 stamper: Some(stamper),
2659 transport_state_dir,
2660 ratchet_store: config.ratchet_store,
2661 ratchet_expiry: config.ratchet_expiry,
2662 #[cfg(feature = "iface-local")]
2663 destination_types: Default::default(),
2664 };
2665 if let Some(storage_dir) = storage_dir {
2669 node.set_stamp_store(crate::stamps::DestinationStampStore::new(&storage_dir));
2670 }
2671 Ok(node)
2672 }
2673
2674 pub fn query(&self, request: QueryRequest) -> Result<QueryResponse, SendError> {
2676 let (resp_tx, resp_rx) = std::sync::mpsc::channel();
2677 self.tx
2678 .send(Event::query(request, resp_tx))
2679 .map_err(|_| SendError)?;
2680 resp_rx.recv().map_err(|_| SendError)
2681 }
2682
2683 pub fn attach_interface(&self, name: &str) -> Result<Option<bool>, SendError> {
2685 self.manage_interface(crate::event::InterfaceManagementOperation::Attach, name)
2686 }
2687
2688 pub fn detach_interface(&self, name: &str) -> Result<Option<bool>, SendError> {
2690 self.manage_interface(crate::event::InterfaceManagementOperation::Detach, name)
2691 }
2692
2693 pub fn reload_interface(&self, name: &str) -> Result<Option<bool>, SendError> {
2695 self.manage_interface(crate::event::InterfaceManagementOperation::Reload, name)
2696 }
2697
2698 fn manage_interface(
2699 &self,
2700 operation: crate::event::InterfaceManagementOperation,
2701 name: &str,
2702 ) -> Result<Option<bool>, SendError> {
2703 match self.query(QueryRequest::ManageInterface {
2704 operation,
2705 name: name.to_string(),
2706 })? {
2707 QueryResponse::InterfaceManagement(result) => Ok(result),
2708 _ => Err(SendError),
2709 }
2710 }
2711
2712 pub fn links(&self) -> Result<Vec<crate::event::LinkInfoEntry>, SendError> {
2714 match self.query(QueryRequest::Links)? {
2715 QueryResponse::Links(links) => Ok(links),
2716 _ => Err(SendError),
2717 }
2718 }
2719
2720 pub fn query_link(
2722 &self,
2723 link_id: [u8; 16],
2724 ) -> Result<Option<crate::event::LinkInfoEntry>, SendError> {
2725 Ok(self
2726 .links()?
2727 .into_iter()
2728 .find(|link| link.link_id == link_id))
2729 }
2730
2731 pub fn state_snapshot(&self) -> Arc<crate::state_snapshot::StateSnapshot> {
2746 self.tx.state_snapshot().load()
2747 }
2748
2749 pub fn snapshot_links(&self) -> Vec<crate::event::LinkInfoEntry> {
2754 self.state_snapshot().links.clone()
2755 }
2756
2757 pub fn snapshot_link(&self, link_id: [u8; 16]) -> Option<crate::event::LinkInfoEntry> {
2764 self.state_snapshot().link(&link_id).cloned()
2765 }
2766
2767 pub fn snapshot_interfaces(&self) -> crate::event::InterfaceStatsResponse {
2773 self.state_snapshot().interfaces.clone()
2774 }
2775
2776 pub fn lowest_interface_bitrate(&self) -> Result<Option<u64>, SendError> {
2781 match self.query(QueryRequest::LowestInterfaceBitrate)? {
2782 QueryResponse::LowestInterfaceBitrate(bitrate) => Ok(bitrate),
2783 _ => Err(SendError),
2784 }
2785 }
2786
2787 pub fn medium_path_timeout(&self) -> Result<f64, SendError> {
2793 match self.query(QueryRequest::MediumPathTimeout)? {
2794 QueryResponse::MediumPathTimeout(timeout) => Ok(timeout),
2795 _ => Err(SendError),
2796 }
2797 }
2798
2799 pub fn begin_drain(&self, timeout: Duration) -> Result<(), SendError> {
2801 self.accepting_work.store(false, Ordering::Release);
2804 self.tx
2805 .send(Event::BeginDrain { timeout })
2806 .map_err(|_| SendError)
2807 }
2808
2809 pub fn drain_status(&self) -> Result<crate::event::DrainStatus, SendError> {
2811 match self.query(QueryRequest::DrainStatus)? {
2812 QueryResponse::DrainStatus(status) => Ok(status),
2813 _ => Err(SendError),
2814 }
2815 }
2816
2817 fn reject_new_work_if_draining(&self) -> Result<(), SendError> {
2818 if self.accepting_work.load(Ordering::Acquire) {
2821 Ok(())
2822 } else {
2823 Err(SendError)
2824 }
2825 }
2826
2827 pub fn send_raw(
2829 &self,
2830 raw: Vec<u8>,
2831 dest_type: u8,
2832 attached_interface: Option<rns_core::transport::types::InterfaceId>,
2833 ) -> Result<(), SendError> {
2834 self.tx
2835 .send(Event::SendOutbound {
2836 raw,
2837 dest_type,
2838 attached_interface,
2839 })
2840 .map_err(|_| SendError)
2841 }
2842
2843 pub fn register_destination(
2845 &self,
2846 dest_hash: [u8; 16],
2847 dest_type: u8,
2848 ) -> Result<(), SendError> {
2849 self.tx
2850 .send(Event::RegisterDestination {
2851 dest_hash,
2852 dest_type,
2853 })
2854 .map_err(|_| SendError)
2855 }
2856
2857 pub fn deregister_destination(&self, dest_hash: [u8; 16]) -> Result<(), SendError> {
2859 self.tx
2860 .send(Event::DeregisterDestination { dest_hash })
2861 .map_err(|_| SendError)
2862 }
2863
2864 pub fn deregister_link_destination(&self, dest_hash: [u8; 16]) -> Result<(), SendError> {
2866 self.tx
2867 .send(Event::DeregisterLinkDestination { dest_hash })
2868 .map_err(|_| SendError)
2869 }
2870
2871 pub fn register_link_destination(
2877 &self,
2878 dest_hash: [u8; 16],
2879 sig_prv_bytes: [u8; 32],
2880 sig_pub_bytes: [u8; 32],
2881 resource_strategy: u8,
2882 ) -> Result<(), SendError> {
2883 self.register_link_destination_with_max_request_size(
2884 dest_hash,
2885 sig_prv_bytes,
2886 sig_pub_bytes,
2887 resource_strategy,
2888 None,
2889 )
2890 }
2891
2892 pub fn register_link_destination_with_max_request_size(
2894 &self,
2895 dest_hash: [u8; 16],
2896 sig_prv_bytes: [u8; 32],
2897 sig_pub_bytes: [u8; 32],
2898 resource_strategy: u8,
2899 max_request_size: Option<usize>,
2900 ) -> Result<(), SendError> {
2901 self.tx
2902 .send(Event::RegisterLinkDestination {
2903 dest_hash,
2904 sig_prv_bytes,
2905 sig_pub_bytes,
2906 resource_strategy,
2907 max_request_size,
2908 })
2909 .map_err(|_| SendError)
2910 }
2911
2912 pub fn set_link_cost(&self, dest_hash: [u8; 16], cost: f64) -> f64 {
2921 self.tx.link_request_costs().set(dest_hash, cost).as_f64()
2922 }
2923
2924 pub fn link_cost(&self, dest_hash: &[u8; 16]) -> f64 {
2932 self.tx.link_request_costs().get(dest_hash).as_f64()
2933 }
2934
2935 pub fn set_link_request_stamps_enforced(&self, enforced: bool) {
2941 self.tx.link_request_costs().set_enforced(enforced);
2942 }
2943
2944 pub fn link_request_stamps_enforced(&self) -> bool {
2946 self.tx.link_request_costs().enforced()
2947 }
2948
2949 pub fn register_request_handler<F>(
2951 &self,
2952 path: &str,
2953 allowed_list: Option<Vec<[u8; 16]>>,
2954 handler: F,
2955 ) -> Result<(), SendError>
2956 where
2957 F: Fn([u8; 16], &str, &[u8], Option<&([u8; 16], [u8; 64])>) -> Option<Vec<u8>>
2958 + Send
2959 + 'static,
2960 {
2961 self.tx
2962 .send(Event::RegisterRequestHandler {
2963 path: path.to_string(),
2964 allowed_list,
2965 handler: Box::new(handler),
2966 })
2967 .map_err(|_| SendError)
2968 }
2969
2970 pub fn register_request_handler_response<F>(
2972 &self,
2973 path: &str,
2974 allowed_list: Option<Vec<[u8; 16]>>,
2975 handler: F,
2976 ) -> Result<(), SendError>
2977 where
2978 F: Fn(
2979 [u8; 16],
2980 &str,
2981 &[u8],
2982 Option<&([u8; 16], [u8; 64])>,
2983 ) -> Option<crate::link_manager::RequestResponse>
2984 + Send
2985 + 'static,
2986 {
2987 self.tx
2988 .send(Event::RegisterRequestHandlerResponse {
2989 path: path.to_string(),
2990 allowed_list,
2991 handler: Box::new(handler),
2992 })
2993 .map_err(|_| SendError)
2994 }
2995
2996 pub fn register_deferred_request_handler<F>(
2999 &self,
3000 path: &str,
3001 allowed_list: Option<Vec<[u8; 16]>>,
3002 handler: F,
3003 ) -> Result<(), SendError>
3004 where
3005 F: Fn([u8; 16], &str, [u8; 16], &[u8], Option<&([u8; 16], [u8; 64])>) + Send + 'static,
3006 {
3007 self.tx
3008 .send(Event::RegisterDeferredRequestHandler {
3009 path: path.to_string(),
3010 allowed_list,
3011 handler: Box::new(handler),
3012 })
3013 .map_err(|_| SendError)
3014 }
3015
3016 pub fn send_deferred_response(
3018 &self,
3019 link_id: [u8; 16],
3020 request_id: [u8; 16],
3021 data: Vec<u8>,
3022 ) -> Result<(), SendError> {
3023 self.tx
3024 .send(Event::SendDeferredResponse {
3025 link_id,
3026 request_id,
3027 data,
3028 })
3029 .map_err(|_| SendError)
3030 }
3031
3032 pub fn create_link(
3036 &self,
3037 dest_hash: [u8; 16],
3038 dest_sig_pub_bytes: [u8; 32],
3039 ) -> Result<[u8; 16], SendError> {
3040 self.reject_new_work_if_draining()?;
3041 let (response_tx, response_rx) = std::sync::mpsc::channel();
3042 self.tx
3043 .send(Event::CreateLink {
3044 dest_hash,
3045 dest_sig_pub_bytes,
3046 response_tx,
3047 })
3048 .map_err(|_| SendError)?;
3049 let link_id = response_rx.recv().map_err(|_| SendError)?;
3050 if link_id == [0u8; 16] {
3051 Err(SendError)
3052 } else {
3053 Ok(link_id)
3054 }
3055 }
3056
3057 pub fn send_request(
3059 &self,
3060 link_id: [u8; 16],
3061 path: &str,
3062 data: &[u8],
3063 ) -> Result<(), SendError> {
3064 self.send_request_with_max_response_size(link_id, path, data, None)
3065 }
3066
3067 pub fn send_request_with_max_response_size(
3069 &self,
3070 link_id: [u8; 16],
3071 path: &str,
3072 data: &[u8],
3073 max_response_size: Option<usize>,
3074 ) -> Result<(), SendError> {
3075 self.reject_new_work_if_draining()?;
3076 self.tx
3077 .send(Event::SendRequest {
3078 link_id,
3079 path: path.to_string(),
3080 data: data.to_vec(),
3081 max_response_size,
3082 })
3083 .map_err(|_| SendError)
3084 }
3085
3086 pub fn identify_on_link(
3088 &self,
3089 link_id: [u8; 16],
3090 identity_prv_key: [u8; 64],
3091 ) -> Result<(), SendError> {
3092 self.reject_new_work_if_draining()?;
3093 self.tx
3094 .send(Event::IdentifyOnLink {
3095 link_id,
3096 identity_prv_key,
3097 })
3098 .map_err(|_| SendError)
3099 }
3100
3101 pub fn teardown_link(&self, link_id: [u8; 16]) -> Result<(), SendError> {
3103 self.tx
3104 .send(Event::TeardownLink { link_id })
3105 .map_err(|_| SendError)
3106 }
3107
3108 pub fn send_resource(
3110 &self,
3111 link_id: [u8; 16],
3112 data: Vec<u8>,
3113 metadata: Option<Vec<u8>>,
3114 ) -> Result<(), SendError> {
3115 self.send_resource_with_auto_compress(link_id, data, metadata, true)
3116 }
3117
3118 pub fn send_resource_with_auto_compress(
3137 &self,
3138 link_id: [u8; 16],
3139 data: Vec<u8>,
3140 metadata: Option<Vec<u8>>,
3141 auto_compress: bool,
3142 ) -> Result<(), SendError> {
3143 self.send_resource_with_options(
3144 link_id,
3145 data,
3146 metadata,
3147 crate::resource::ResourceSendOptions {
3148 auto_compress,
3149 ..Default::default()
3150 },
3151 )
3152 }
3153
3154 pub fn send_resource_with_options(
3157 &self,
3158 link_id: [u8; 16],
3159 data: Vec<u8>,
3160 metadata: Option<Vec<u8>>,
3161 options: crate::resource::ResourceSendOptions,
3162 ) -> Result<(), SendError> {
3163 self.reject_new_work_if_draining()?;
3164 self.tx
3165 .send(Event::SendResource {
3166 link_id,
3167 data,
3168 metadata,
3169 options,
3170 })
3171 .map_err(|_| SendError)
3172 }
3173
3174 pub fn send_resource_reader<R: Read + Send + 'static>(
3177 &self,
3178 link_id: [u8; 16],
3179 reader: R,
3180 declared_length: u64,
3181 metadata: Option<Vec<u8>>,
3182 auto_compress: bool,
3183 ) -> Result<crate::resource::ResourceTransferId, SendError> {
3184 self.send_resource_reader_with_options(
3185 link_id,
3186 reader,
3187 declared_length,
3188 metadata,
3189 crate::resource::ResourceSendOptions {
3190 auto_compress,
3191 ..Default::default()
3192 },
3193 )
3194 }
3195
3196 pub fn send_resource_reader_with_options<R: Read + Send + 'static>(
3198 &self,
3199 link_id: [u8; 16],
3200 reader: R,
3201 declared_length: u64,
3202 metadata: Option<Vec<u8>>,
3203 options: crate::resource::ResourceSendOptions,
3204 ) -> Result<crate::resource::ResourceTransferId, SendError> {
3205 self.reject_new_work_if_draining()?;
3206 let transfer_id = crate::resource::ResourceTransferId(
3207 NEXT_RESOURCE_TRANSFER_ID.fetch_add(1, Ordering::Relaxed),
3208 );
3209 self.tx
3210 .send(Event::SendResourceStream {
3211 link_id,
3212 transfer_id,
3213 reader: Box::new(reader),
3214 declared_length,
3215 metadata,
3216 options,
3217 })
3218 .map_err(|_| SendError)?;
3219 Ok(transfer_id)
3220 }
3221
3222 pub fn send_resource_file(
3224 &self,
3225 link_id: [u8; 16],
3226 path: impl AsRef<Path>,
3227 metadata: Option<Vec<u8>>,
3228 auto_compress: bool,
3229 ) -> io::Result<crate::resource::ResourceTransferId> {
3230 let file = std::fs::File::open(path.as_ref())?;
3231 let length = file.metadata()?.len();
3232 self.send_resource_reader(link_id, file, length, metadata, auto_compress)
3233 .map_err(|_| io::Error::new(io::ErrorKind::BrokenPipe, "RNS driver shut down"))
3234 }
3235
3236 pub fn set_resource_receive_mode(
3238 &self,
3239 link_id: [u8; 16],
3240 mode: crate::resource::ResourceReceiveMode,
3241 ) -> Result<(), SendError> {
3242 self.tx
3243 .send(Event::SetResourceReceiveMode { link_id, mode })
3244 .map_err(|_| SendError)
3245 }
3246
3247 pub fn set_resource_strategy(&self, link_id: [u8; 16], strategy: u8) -> Result<(), SendError> {
3251 self.tx
3252 .send(Event::SetResourceStrategy { link_id, strategy })
3253 .map_err(|_| SendError)
3254 }
3255
3256 pub fn accept_resource(
3258 &self,
3259 link_id: [u8; 16],
3260 resource_hash: Vec<u8>,
3261 accept: bool,
3262 ) -> Result<(), SendError> {
3263 if accept {
3264 self.reject_new_work_if_draining()?;
3265 }
3266 self.tx
3267 .send(Event::AcceptResource {
3268 link_id,
3269 resource_hash,
3270 accept,
3271 })
3272 .map_err(|_| SendError)
3273 }
3274
3275 pub fn send_channel_message(
3277 &self,
3278 link_id: [u8; 16],
3279 msgtype: u16,
3280 payload: Vec<u8>,
3281 ) -> Result<(), SendError> {
3282 self.try_send_channel_message(link_id, msgtype, payload)
3283 .map_err(|_| SendError)
3284 }
3285
3286 pub fn try_send_channel_message(
3290 &self,
3291 link_id: [u8; 16],
3292 msgtype: u16,
3293 payload: Vec<u8>,
3294 ) -> Result<(), ChannelSendError> {
3295 self.reject_new_work_if_draining()
3296 .map_err(|_| ChannelSendError::Unavailable)?;
3297 let (response_tx, response_rx) = std::sync::mpsc::channel();
3298 self.tx
3299 .send(Event::SendChannelMessage {
3300 link_id,
3301 msgtype,
3302 payload,
3303 response_tx,
3304 })
3305 .map_err(|_| ChannelSendError::Unavailable)?;
3306 response_rx
3307 .recv()
3308 .map_err(|_| ChannelSendError::Unavailable)?
3309 .map_err(ChannelSendError::from)
3310 }
3311
3312 pub fn propose_direct_connect(&self, link_id: [u8; 16]) -> Result<(), SendError> {
3317 self.reject_new_work_if_draining()?;
3318 self.tx
3319 .send(Event::ProposeDirectConnect { link_id })
3320 .map_err(|_| SendError)
3321 }
3322
3323 pub fn set_direct_connect_policy(
3325 &self,
3326 policy: crate::holepunch::orchestrator::HolePunchPolicy,
3327 ) -> Result<(), SendError> {
3328 self.tx
3329 .send(Event::SetDirectConnectPolicy { policy })
3330 .map_err(|_| SendError)
3331 }
3332
3333 pub async fn send_on_link(
3345 &self,
3346 link_id: [u8; 16],
3347 data: Vec<u8>,
3348 context: u8,
3349 ) -> Result<(), crate::link_send::LinkSendError> {
3350 use crate::link_send::Completion;
3351 let tracking = self.tx.link_send_pool().track(link_id, true);
3352 let permit = self.tx.link_send_pool().acquire().await?;
3353 let (completion, receipt) = Completion::new_tracked(permit, tracking);
3354 self.tx
3355 .send_async(Event::SendLinkTracked {
3356 link_id,
3357 data,
3358 context,
3359 completion,
3360 })
3361 .await?;
3362 receipt.await
3363 }
3364
3365 pub fn try_send_on_link(
3371 &self,
3372 link_id: [u8; 16],
3373 data: Vec<u8>,
3374 context: u8,
3375 ) -> Result<crate::link_send::LinkSendReceipt, crate::link_send::LinkSendError> {
3376 use crate::link_send::{Completion, LinkSendError};
3377 let permit = self.tx.link_send_pool().try_acquire()?;
3378 let tracking = self.tx.link_send_pool().track(link_id, false);
3379 let (completion, receipt) = Completion::new_tracked(permit, tracking);
3380 self.tx
3381 .try_send(Event::SendLinkTracked {
3382 link_id,
3383 data,
3384 context,
3385 completion,
3386 })
3387 .map_err(|error| match error {
3388 std::sync::mpsc::TrySendError::Full(_) => LinkSendError::QueueFull,
3389 std::sync::mpsc::TrySendError::Disconnected(_) => LinkSendError::DriverStopped,
3390 })?;
3391 Ok(receipt)
3392 }
3393
3394 pub fn try_send_link_datagram(
3399 &self,
3400 link_id: [u8; 16],
3401 data: Vec<u8>,
3402 context: u8,
3403 timeout: Duration,
3404 ) -> Result<(), crate::event::LinkDatagramError> {
3405 use crate::event::LinkDatagramError;
3406 if self.reject_new_work_if_draining().is_err() {
3407 return Err(LinkDatagramError::DriverStopped);
3408 }
3409 let (response_tx, response_rx) = std::sync::mpsc::channel();
3410 self.tx
3411 .try_send(Event::SendOnLink {
3412 link_id,
3413 data,
3414 context,
3415 response_tx: Some(response_tx),
3416 })
3417 .map_err(|error| match error {
3418 std::sync::mpsc::TrySendError::Full(_) => LinkDatagramError::DriverQueueFull,
3419 std::sync::mpsc::TrySendError::Disconnected(_) => LinkDatagramError::DriverStopped,
3420 })?;
3421 response_rx
3422 .recv_timeout(timeout)
3423 .map_err(|error| match error {
3424 std::sync::mpsc::RecvTimeoutError::Timeout => LinkDatagramError::AdmissionTimedOut,
3425 std::sync::mpsc::RecvTimeoutError::Disconnected => LinkDatagramError::DriverStopped,
3426 })?
3427 }
3428
3429 pub fn attach_local_ratchets(&self, owner: Arc<crate::LocalRatchets>) -> io::Result<()> {
3433 let mut owners = self.tx.local_ratchets().lock().unwrap();
3434 if let Some(existing) = owners.get(&owner.destination_hash()) {
3435 if !Arc::ptr_eq(existing, &owner) {
3436 return Err(io::Error::new(
3437 io::ErrorKind::AlreadyExists,
3438 "destination already has a ratchet owner",
3439 ));
3440 }
3441 }
3442 owners.insert(owner.destination_hash(), owner);
3443 Ok(())
3444 }
3445
3446 pub async fn announce(
3455 &self,
3456 dest: &crate::destination::Destination,
3457 identity: &Identity,
3458 app_data: Option<&[u8]>,
3459 ) -> Result<(), crate::TransmissionError> {
3460 let permit = self.tx.link_send_pool().acquire().await?;
3461 if self.announce_needs_stamp(dest, identity) {
3462 let (completion, receipt) = crate::link_send::Completion::new(permit);
3466 self.enqueue_stamped_announce(dest, identity, app_data, Some(completion))
3467 .map_err(|_| crate::TransmissionError::QueueFull)?;
3468 return receipt.await;
3469 }
3470 let (packet, replay) = self
3471 .build_announce(dest, identity, app_data)
3472 .map_err(|_| crate::TransmissionError::PacketBuildFailed)?;
3473 let (completion, receipt) = crate::link_send::Completion::new(permit);
3474 self.tx
3475 .send_async(Event::SendOutboundTracked {
3476 raw: packet.raw,
3477 dest_type: dest.dest_type.to_wire_constant(),
3478 replay,
3479 completion,
3480 })
3481 .await?;
3482 receipt.await
3483 }
3484
3485 pub fn try_announce(
3488 &self,
3489 dest: &crate::destination::Destination,
3490 identity: &Identity,
3491 app_data: Option<&[u8]>,
3492 ) -> Result<crate::TransmissionReceipt, crate::TransmissionError> {
3493 let permit = self.tx.link_send_pool().try_acquire()?;
3494 if self.announce_needs_stamp(dest, identity) {
3495 let (completion, receipt) = crate::link_send::Completion::new(permit);
3500 self.enqueue_stamped_announce(dest, identity, app_data, Some(completion))
3501 .map_err(|_| crate::TransmissionError::QueueFull)?;
3502 return Ok(receipt);
3503 }
3504 let (packet, replay) = self
3505 .build_announce(dest, identity, app_data)
3506 .map_err(|_| crate::TransmissionError::PacketBuildFailed)?;
3507 let (completion, receipt) = crate::link_send::Completion::new(permit);
3508 self.try_confirmed_event(Event::SendOutboundTracked {
3509 raw: packet.raw,
3510 dest_type: dest.dest_type.to_wire_constant(),
3511 replay,
3512 completion,
3513 })?;
3514 Ok(receipt)
3515 }
3516
3517 pub async fn send_packet(
3524 &self,
3525 dest: &crate::destination::Destination,
3526 data: &[u8],
3527 ) -> Result<rns_core::types::PacketHash, crate::TransmissionError> {
3528 let permit = self.tx.link_send_pool().acquire().await?;
3529 let packet = self
3530 .build_packet(dest, data)
3531 .map_err(|_| crate::TransmissionError::PacketBuildFailed)?;
3532 let hash = rns_core::types::PacketHash(packet.packet_hash);
3533 let (completion, receipt) = crate::link_send::Completion::new(permit);
3534 self.tx
3535 .send_async(Event::SendOutboundTracked {
3536 raw: packet.raw,
3537 dest_type: dest.dest_type.to_wire_constant(),
3538 replay: None,
3539 completion,
3540 })
3541 .await?;
3542 receipt.await?;
3543 Ok(hash)
3544 }
3545
3546 pub fn try_send_packet(
3550 &self,
3551 dest: &crate::destination::Destination,
3552 data: &[u8],
3553 ) -> Result<crate::PacketSendReceipt, crate::TransmissionError> {
3554 let permit = self.tx.link_send_pool().try_acquire()?;
3555 let packet = self
3556 .build_packet(dest, data)
3557 .map_err(|_| crate::TransmissionError::PacketBuildFailed)?;
3558 let hash = rns_core::types::PacketHash(packet.packet_hash);
3559 let (completion, receipt) = crate::link_send::Completion::new(permit);
3560 self.try_confirmed_event(Event::SendOutboundTracked {
3561 raw: packet.raw,
3562 dest_type: dest.dest_type.to_wire_constant(),
3563 replay: None,
3564 completion,
3565 })?;
3566 Ok(crate::PacketSendReceipt {
3567 completion: receipt,
3568 hash,
3569 })
3570 }
3571
3572 fn try_confirmed_event(&self, event: Event) -> Result<(), crate::TransmissionError> {
3573 self.tx.try_send(event).map_err(|error| match error {
3574 std::sync::mpsc::TrySendError::Full(_) => crate::TransmissionError::QueueFull,
3575 std::sync::mpsc::TrySendError::Disconnected(_) => {
3576 crate::TransmissionError::DriverStopped
3577 }
3578 })
3579 }
3580
3581 pub fn announce_queued(
3589 &self,
3590 dest: &crate::destination::Destination,
3591 identity: &Identity,
3592 app_data: Option<&[u8]>,
3593 ) -> Result<(), SendError> {
3594 self.reject_new_work_if_draining()?;
3595 if self.announce_needs_stamp(dest, identity) {
3596 return self
3599 .enqueue_stamped_announce(dest, identity, app_data, None)
3600 .map_err(|_| SendError);
3601 }
3602 let (packet, replay) = self.build_announce(dest, identity, app_data)?;
3603 if let Some(replay) = replay {
3604 self.tx
3605 .send(Event::StoreSharedAnnounce {
3606 dest_hash: replay.dest_hash,
3607 name_hash: replay.name_hash,
3608 identity_prv_key: replay.identity_prv_key,
3609 app_data: replay.app_data,
3610 })
3611 .map_err(|_| SendError)?;
3612 }
3613 self.send_raw(packet.raw, dest.dest_type.to_wire_constant(), None)
3614 }
3615
3616 fn build_announce(
3617 &self,
3618 dest: &crate::destination::Destination,
3619 identity: &Identity,
3620 app_data: Option<&[u8]>,
3621 ) -> Result<
3622 (
3623 rns_core::packet::RawPacket,
3624 Option<Box<crate::common::event::AnnounceReplay>>,
3625 ),
3626 SendError,
3627 > {
3628 build_announce_packet(
3629 &self.tx,
3630 self.stamp_store.as_ref(),
3631 self.stamp_destinations.load(Ordering::Relaxed),
3632 self.stamp_targets,
3633 dest,
3634 identity,
3635 app_data,
3636 )
3637 }
3638
3639 fn announce_needs_stamp(
3643 &self,
3644 dest: &crate::destination::Destination,
3645 identity: &Identity,
3646 ) -> bool {
3647 if !self.stamp_destinations.load(Ordering::Relaxed) || self.stamper.is_none() {
3648 return false;
3649 }
3650 let (Some(store), Some(public_key)) =
3651 (self.stamp_store.as_ref(), identity.get_public_key())
3652 else {
3653 return false;
3654 };
3655 let name_hash = rns_core::destination::name_hash(
3656 &dest.app_name,
3657 &dest.aspects.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
3658 );
3659 let material =
3660 crate::stamps::destination_stamp_material(&dest.hash.0, &public_key, &name_hash);
3661 store
3662 .load(
3663 &dest.hash.0,
3664 &material,
3665 self.stamp_targets.destination_target(dest),
3666 )
3667 .is_none()
3668 }
3669
3670 fn enqueue_stamped_announce(
3673 &self,
3674 dest: &crate::destination::Destination,
3675 identity: &Identity,
3676 app_data: Option<&[u8]>,
3677 completion: Option<crate::link_send::Completion>,
3678 ) -> Result<(), crate::stamper::QueueFull> {
3679 let stamper = self.stamper.as_ref().ok_or(crate::stamper::QueueFull)?;
3680 let identity_prv_key = identity
3681 .get_private_key()
3682 .ok_or(crate::stamper::QueueFull)?;
3683 let result = stamper.enqueue_announce(crate::stamper::AnnounceStampJob {
3684 dest: dest.clone(),
3685 identity_prv_key,
3686 app_data: app_data.map(|data| data.to_vec()),
3687 completion,
3688 });
3689 if result.is_err() {
3690 log::debug!(
3691 "Stamp queue full, dropping announce for {:02x?}",
3692 &dest.hash.0[..4]
3693 );
3694 }
3695 result
3696 }
3697
3698 pub(crate) fn set_stamp_store(&mut self, store: crate::stamps::DestinationStampStore) {
3700 if let Some(stamper) = self.stamper.as_ref() {
3701 stamper.set_stamp_store(Some(store.clone()));
3702 }
3703 self.stamp_store = Some(store);
3704 }
3705
3706 #[doc(hidden)]
3709 pub fn set_destination_stamping(
3710 &mut self,
3711 enabled: bool,
3712 targets: crate::stamper::StampTargets,
3713 ) {
3714 self.stamp_destinations.store(enabled, Ordering::Relaxed);
3715 self.stamp_targets = targets;
3716 if let Some(stamper) = self.stamper.as_ref() {
3717 stamper.set_targets(targets);
3718 }
3719 }
3720
3721 #[doc(hidden)]
3723 pub fn stamper(&self) -> Option<&Arc<crate::stamper::Stamper>> {
3724 self.stamper.as_ref()
3725 }
3726
3727 pub fn send_packet_queued(
3735 &self,
3736 dest: &crate::destination::Destination,
3737 data: &[u8],
3738 ) -> Result<rns_core::types::PacketHash, SendError> {
3739 self.reject_new_work_if_draining()?;
3740 let packet = self.build_packet(dest, data)?;
3741 let packet_hash = rns_core::types::PacketHash(packet.packet_hash);
3742 self.tx
3743 .send(Event::SendOutbound {
3744 raw: packet.raw,
3745 dest_type: dest.dest_type.to_wire_constant(),
3746 attached_interface: None,
3747 })
3748 .map_err(|_| SendError)?;
3749 Ok(packet_hash)
3750 }
3751
3752 pub fn deliver_decrypted(
3762 &self,
3763 dest: &crate::destination::Destination,
3764 identity: &rns_crypto::identity::Identity,
3765 ) -> Result<(), SendError> {
3766 let private = identity.get_private_key().ok_or(SendError)?;
3767 self.tx
3768 .send(Event::DeliverDecrypted {
3769 dest_hash: dest.hash.0,
3770 identity: Box::new(rns_crypto::identity::Identity::from_private_key(&private)),
3771 })
3772 .map_err(|_| SendError)
3773 }
3774
3775 pub fn send_packet_deferred(
3787 &self,
3788 dest: &crate::destination::Destination,
3789 data: &[u8],
3790 ) -> Result<crate::SendTicket, SendError> {
3791 self.reject_new_work_if_draining()?;
3792 if dest.dest_type != rns_core::types::DestinationType::Single {
3793 return Err(SendError);
3794 }
3795 let public_key = dest.public_key.ok_or(SendError)?;
3796 let ticket = self.next_send_ticket.fetch_add(1, Ordering::Relaxed);
3797 let ratchet = self.single_ratchet(dest);
3798 let (data, encrypted) = if self.encrypt_deferred_inline {
3799 let payload = rns_crypto::identity::Identity::encrypt_for_public_key(
3800 &public_key,
3801 data,
3802 ratchet.as_ref(),
3803 &mut OsRng,
3804 )
3805 .map_err(|_| SendError)?;
3806 (payload, true)
3807 } else {
3808 (data.to_vec(), false)
3809 };
3810 self.tx
3811 .send(Event::SendDeferred(Box::new(
3812 crate::common::event::DeferredSend {
3813 ticket,
3814 destination: dest.hash.0,
3815 public_key,
3816 ratchet,
3817 data,
3818 encrypted,
3819 },
3820 )))
3821 .map_err(|_| SendError)?;
3822 Ok(crate::SendTicket(ticket))
3823 }
3824
3825 fn build_packet(
3826 &self,
3827 dest: &crate::destination::Destination,
3828 data: &[u8],
3829 ) -> Result<rns_core::packet::RawPacket, SendError> {
3830 use rns_core::types::DestinationType;
3831
3832 let payload = match dest.dest_type {
3833 DestinationType::Single => self.encrypt_single_payload(dest, data)?,
3834 DestinationType::Plain => data.to_vec(),
3835 DestinationType::Group => dest.encrypt(data).map_err(|_| SendError)?,
3836 };
3837
3838 let flags = rns_core::packet::PacketFlags {
3839 header_type: rns_core::constants::HEADER_1,
3840 context_flag: rns_core::constants::FLAG_UNSET,
3841 transport_type: rns_core::constants::TRANSPORT_BROADCAST,
3842 destination_type: dest.dest_type.to_wire_constant(),
3843 packet_type: rns_core::constants::PACKET_TYPE_DATA,
3844 };
3845
3846 let packet = rns_core::packet::RawPacket::pack(
3847 flags,
3848 0,
3849 &dest.hash.0,
3850 None,
3851 rns_core::constants::CONTEXT_NONE,
3852 &payload,
3853 )
3854 .map_err(|_| SendError)?;
3855
3856 Ok(packet)
3857 }
3858
3859 fn encrypt_single_payload(
3860 &self,
3861 dest: &crate::destination::Destination,
3862 data: &[u8],
3863 ) -> Result<Vec<u8>, SendError> {
3864 let pub_key = dest.public_key.ok_or(SendError)?;
3865 let ratchet = self.single_ratchet(dest);
3866 rns_crypto::identity::Identity::encrypt_for_public_key(
3870 &pub_key,
3871 data,
3872 ratchet.as_ref(),
3873 &mut OsRng,
3874 )
3875 .map_err(|_| SendError)
3876 }
3877
3878 fn single_ratchet(&self, dest: &crate::destination::Destination) -> Option<[u8; 32]> {
3881 let local_ratchet = self
3882 .tx
3883 .local_ratchets()
3884 .lock()
3885 .unwrap()
3886 .get(&dest.hash.0)
3887 .and_then(|owner| owner.current_public());
3888 local_ratchet.or_else(|| {
3889 self.ratchet_store.as_ref().and_then(|store| {
3890 match store.current(&dest.hash.0, time::now(), self.ratchet_expiry.as_secs_f64()) {
3891 Ok(entry) => entry.map(|entry| entry.ratchet),
3892 Err(err) => {
3893 log::warn!(
3894 "failed to load ratchet for {:02x}{:02x}{:02x}{:02x}..: {}",
3895 dest.hash.0[0],
3896 dest.hash.0[1],
3897 dest.hash.0[2],
3898 dest.hash.0[3],
3899 err
3900 );
3901 None
3902 }
3903 }
3904 })
3905 })
3906 }
3907
3908 pub fn register_destination_with_proof(
3913 &self,
3914 dest: &crate::destination::Destination,
3915 signing_key: Option<[u8; 64]>,
3916 ) -> Result<(), SendError> {
3917 self.register_destination(dest.hash.0, dest.dest_type.to_wire_constant())?;
3919
3920 if dest.proof_strategy != rns_core::types::ProofStrategy::ProveNone {
3922 self.tx
3923 .send(Event::RegisterProofStrategy {
3924 dest_hash: dest.hash.0,
3925 strategy: dest.proof_strategy,
3926 signing_key,
3927 })
3928 .map_err(|_| SendError)?;
3929 }
3930
3931 Ok(())
3932 }
3933
3934 pub fn request_path(&self, dest_hash: &rns_core::types::DestHash) -> Result<(), SendError> {
3936 self.reject_new_work_if_draining()?;
3937 self.tx
3938 .send(Event::RequestPath {
3939 dest_hash: dest_hash.0,
3940 })
3941 .map_err(|_| SendError)
3942 }
3943
3944 pub fn has_path(&self, dest_hash: &rns_core::types::DestHash) -> Result<bool, SendError> {
3946 match self.query(QueryRequest::HasPath {
3947 dest_hash: dest_hash.0,
3948 })? {
3949 QueryResponse::HasPath(v) => Ok(v),
3950 _ => Ok(false),
3951 }
3952 }
3953
3954 pub fn hops_to(&self, dest_hash: &rns_core::types::DestHash) -> Result<Option<u8>, SendError> {
3956 match self.query(QueryRequest::HopsTo {
3957 dest_hash: dest_hash.0,
3958 })? {
3959 QueryResponse::HopsTo(v) => Ok(v),
3960 _ => Ok(None),
3961 }
3962 }
3963
3964 pub fn recall_identity(
3966 &self,
3967 dest_hash: &rns_core::types::DestHash,
3968 ) -> Result<Option<crate::destination::AnnouncedIdentity>, SendError> {
3969 match self.query(QueryRequest::RecallIdentity {
3970 dest_hash: dest_hash.0,
3971 })? {
3972 QueryResponse::RecallIdentity(v) => Ok(v),
3973 _ => Ok(None),
3974 }
3975 }
3976
3977 pub fn recall_destination_stamp(
3980 &self,
3981 dest_hash: &rns_core::types::DestHash,
3982 ) -> Result<Option<crate::event::DestinationStampInfo>, SendError> {
3983 match self.query(QueryRequest::RecallDestinationStamp {
3984 dest_hash: dest_hash.0,
3985 })? {
3986 QueryResponse::RecallDestinationStamp(v) => Ok(v),
3987 _ => Ok(None),
3988 }
3989 }
3990
3991 pub fn known_destinations(
3993 &self,
3994 ) -> Result<Vec<crate::event::KnownDestinationEntry>, SendError> {
3995 match self.query(QueryRequest::KnownDestinations)? {
3996 QueryResponse::KnownDestinations(entries) => Ok(entries),
3997 _ => Ok(Vec::new()),
3998 }
3999 }
4000
4001 pub fn retain_known_destination(
4003 &self,
4004 dest_hash: &rns_core::types::DestHash,
4005 ) -> Result<bool, SendError> {
4006 match self.query(QueryRequest::RetainKnownDestination {
4007 dest_hash: dest_hash.0,
4008 })? {
4009 QueryResponse::RetainKnownDestination(ok) => Ok(ok),
4010 _ => Ok(false),
4011 }
4012 }
4013
4014 pub fn retain_identity(
4016 &self,
4017 identity_hash: &rns_core::types::IdentityHash,
4018 ) -> Result<bool, SendError> {
4019 match self.query(QueryRequest::RetainIdentity {
4020 identity_hash: identity_hash.0,
4021 })? {
4022 QueryResponse::RetainIdentity(ok) => Ok(ok),
4023 _ => Ok(false),
4024 }
4025 }
4026
4027 pub fn unretain_known_destination(
4029 &self,
4030 dest_hash: &rns_core::types::DestHash,
4031 ) -> Result<bool, SendError> {
4032 match self.query(QueryRequest::UnretainKnownDestination {
4033 dest_hash: dest_hash.0,
4034 })? {
4035 QueryResponse::UnretainKnownDestination(ok) => Ok(ok),
4036 _ => Ok(false),
4037 }
4038 }
4039
4040 pub fn mark_known_destination_used(
4042 &self,
4043 dest_hash: &rns_core::types::DestHash,
4044 ) -> Result<bool, SendError> {
4045 match self.query(QueryRequest::MarkKnownDestinationUsed {
4046 dest_hash: dest_hash.0,
4047 })? {
4048 QueryResponse::MarkKnownDestinationUsed(ok) => Ok(ok),
4049 _ => Ok(false),
4050 }
4051 }
4052
4053 fn persist_known_destinations(&self) {
4054 if self.known_destinations_store.is_none() && self.known_destinations_path.is_none() {
4055 return;
4056 }
4057
4058 let Ok(entries) = self.known_destinations() else {
4059 return;
4060 };
4061
4062 let destinations: std::collections::HashMap<[u8; 16], storage::KnownDestination> = entries
4063 .into_iter()
4064 .map(|entry| {
4065 (
4066 entry.dest_hash,
4067 storage::KnownDestination {
4068 identity_hash: entry.identity_hash,
4069 public_key: entry.public_key,
4070 app_data: entry.app_data,
4071 hops: entry.hops,
4072 received_at: entry.received_at,
4073 receiving_interface: entry.receiving_interface.0,
4074 was_used: entry.was_used,
4075 last_used_at: entry.last_used_at,
4076 retained: entry.retained,
4077 stamp: entry.stamp.map(|stamp| stamp.to_vec()),
4078 stamp_value: entry.stamp_value,
4079 lr_cost: entry.lr_cost,
4080 },
4081 )
4082 })
4083 .collect();
4084
4085 if let Some(store) = self.known_destinations_store.as_ref() {
4086 if let Err(err) = store.save_all(&destinations) {
4087 log::warn!("failed to persist known destinations: {}", err);
4088 }
4089 } else if let Some(path) = self.known_destinations_path.as_ref() {
4090 if let Err(err) = storage::save_known_destinations(&destinations, path) {
4091 log::warn!("failed to persist known destinations: {}", err);
4092 }
4093 }
4094 }
4095
4096 fn persist_transport_state(&self) {
4097 let Some(storage_dir) = self.transport_state_dir.as_ref() else {
4098 return;
4099 };
4100 let Ok(QueryResponse::TransportStateSnapshot(snapshot)) =
4101 self.query(QueryRequest::TransportStateSnapshot)
4102 else {
4103 return;
4104 };
4105 if let Err(err) = storage::save_transport_state(&snapshot, storage_dir, true) {
4106 log::warn!("failed to persist transport state: {}", err);
4107 }
4108 }
4109
4110 pub fn load_hook(
4112 &self,
4113 name: String,
4114 wasm_bytes: Vec<u8>,
4115 attach_point: String,
4116 priority: i32,
4117 ) -> Result<Result<(), String>, SendError> {
4118 let (response_tx, response_rx) = std::sync::mpsc::channel();
4119 self.tx
4120 .send(Event::LoadHook {
4121 name,
4122 wasm_bytes,
4123 attach_point,
4124 priority,
4125 response_tx,
4126 })
4127 .map_err(|_| SendError)?;
4128 response_rx.recv().map_err(|_| SendError)
4129 }
4130
4131 pub fn load_hook_file(
4133 &self,
4134 name: String,
4135 path: String,
4136 hook_type: String,
4137 attach_point: String,
4138 priority: i32,
4139 ) -> Result<Result<(), String>, SendError> {
4140 let (response_tx, response_rx) = std::sync::mpsc::channel();
4141 self.tx
4142 .send(Event::LoadHookFile {
4143 name,
4144 path,
4145 hook_type,
4146 attach_point,
4147 priority,
4148 response_tx,
4149 })
4150 .map_err(|_| SendError)?;
4151 response_rx.recv().map_err(|_| SendError)
4152 }
4153
4154 pub fn load_builtin_hook(
4156 &self,
4157 name: String,
4158 builtin_id: String,
4159 attach_point: String,
4160 priority: i32,
4161 ) -> Result<Result<(), String>, SendError> {
4162 let (response_tx, response_rx) = std::sync::mpsc::channel();
4163 self.tx
4164 .send(Event::LoadBuiltinHook {
4165 name,
4166 builtin_id,
4167 attach_point,
4168 priority,
4169 response_tx,
4170 })
4171 .map_err(|_| SendError)?;
4172 response_rx.recv().map_err(|_| SendError)
4173 }
4174
4175 pub fn unload_hook(
4177 &self,
4178 name: String,
4179 attach_point: String,
4180 ) -> Result<Result<(), String>, SendError> {
4181 let (response_tx, response_rx) = std::sync::mpsc::channel();
4182 self.tx
4183 .send(Event::UnloadHook {
4184 name,
4185 attach_point,
4186 response_tx,
4187 })
4188 .map_err(|_| SendError)?;
4189 response_rx.recv().map_err(|_| SendError)
4190 }
4191
4192 pub fn reload_hook(
4194 &self,
4195 name: String,
4196 attach_point: String,
4197 wasm_bytes: Vec<u8>,
4198 ) -> Result<Result<(), String>, SendError> {
4199 let (response_tx, response_rx) = std::sync::mpsc::channel();
4200 self.tx
4201 .send(Event::ReloadHook {
4202 name,
4203 attach_point,
4204 wasm_bytes,
4205 response_tx,
4206 })
4207 .map_err(|_| SendError)?;
4208 response_rx.recv().map_err(|_| SendError)
4209 }
4210
4211 pub fn reload_hook_file(
4213 &self,
4214 name: String,
4215 attach_point: String,
4216 path: String,
4217 hook_type: String,
4218 ) -> Result<Result<(), String>, SendError> {
4219 let (response_tx, response_rx) = std::sync::mpsc::channel();
4220 self.tx
4221 .send(Event::ReloadHookFile {
4222 name,
4223 attach_point,
4224 path,
4225 hook_type,
4226 response_tx,
4227 })
4228 .map_err(|_| SendError)?;
4229 response_rx.recv().map_err(|_| SendError)
4230 }
4231
4232 pub fn reload_builtin_hook(
4234 &self,
4235 name: String,
4236 attach_point: String,
4237 builtin_id: String,
4238 ) -> Result<Result<(), String>, SendError> {
4239 let (response_tx, response_rx) = std::sync::mpsc::channel();
4240 self.tx
4241 .send(Event::ReloadBuiltinHook {
4242 name,
4243 attach_point,
4244 builtin_id,
4245 response_tx,
4246 })
4247 .map_err(|_| SendError)?;
4248 response_rx.recv().map_err(|_| SendError)
4249 }
4250
4251 pub fn set_hook_enabled(
4253 &self,
4254 name: String,
4255 attach_point: String,
4256 enabled: bool,
4257 ) -> Result<Result<(), String>, SendError> {
4258 let (response_tx, response_rx) = std::sync::mpsc::channel();
4259 self.tx
4260 .send(Event::SetHookEnabled {
4261 name,
4262 attach_point,
4263 enabled,
4264 response_tx,
4265 })
4266 .map_err(|_| SendError)?;
4267 response_rx.recv().map_err(|_| SendError)
4268 }
4269
4270 pub fn set_hook_priority(
4272 &self,
4273 name: String,
4274 attach_point: String,
4275 priority: i32,
4276 ) -> Result<Result<(), String>, SendError> {
4277 let (response_tx, response_rx) = std::sync::mpsc::channel();
4278 self.tx
4279 .send(Event::SetHookPriority {
4280 name,
4281 attach_point,
4282 priority,
4283 response_tx,
4284 })
4285 .map_err(|_| SendError)?;
4286 response_rx.recv().map_err(|_| SendError)
4287 }
4288
4289 pub fn list_hooks(&self) -> Result<Vec<crate::event::HookInfo>, SendError> {
4291 let (response_tx, response_rx) = std::sync::mpsc::channel();
4292 self.tx
4293 .send(Event::ListHooks { response_tx })
4294 .map_err(|_| SendError)?;
4295 response_rx.recv().map_err(|_| SendError)
4296 }
4297
4298 #[cfg(any(feature = "iface-local", test))]
4301 pub(crate) fn from_parts(
4302 tx: EventSender,
4303 driver_handle: thread::JoinHandle<()>,
4304 rpc_server: Option<crate::rpc::RpcServer>,
4305 tick_interval_ms: Arc<AtomicU64>,
4306 accepting_work: Arc<AtomicBool>,
4307 ) -> Self {
4308 RnsNode {
4309 tx,
4310 driver_handle: Some(driver_handle),
4311 verify_handle: None,
4312 verify_shutdown: Arc::default(),
4313 persist_handle: None,
4314 persist_stop: None,
4315 rpc_server,
4316 #[cfg(target_os = "linux")]
4317 rpc_unix_server: None,
4318 tick_interval_ms,
4319 next_send_ticket: AtomicU64::new(0),
4320 encrypt_deferred_inline: std::thread::available_parallelism()
4321 .map_or(true, |cpus| cpus.get() == 1),
4322 accepting_work,
4323 probe_server: None,
4324 known_destinations_path: None,
4325 known_destinations_store: None,
4326 stamp_store: None,
4327 stamp_destinations: Arc::new(AtomicBool::new(rns_core::constants::STAMP_DESTINATIONS)),
4328 stamp_targets: crate::stamper::StampTargets::default(),
4329 stamper: None,
4330 transport_state_dir: None,
4331 ratchet_store: None,
4332 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
4333 #[cfg(feature = "iface-local")]
4334 destination_types: Default::default(),
4335 }
4336 }
4337
4338 pub fn event_sender(&self) -> &EventSender {
4340 &self.tx
4341 }
4342
4343 pub fn set_tick_interval(&self, ms: u64) -> u64 {
4348 let clamped = ms.clamp(100, 10_000);
4349 if clamped != ms {
4350 log::warn!(
4351 "tick interval {}ms out of range, clamped to {}ms",
4352 ms,
4353 clamped
4354 );
4355 }
4356 self.tick_interval_ms.store(clamped, Ordering::Relaxed);
4357 clamped
4358 }
4359
4360 pub fn tick_interval(&self) -> u64 {
4362 self.tick_interval_ms.load(Ordering::Relaxed)
4363 }
4364
4365 pub fn shutdown(mut self) {
4367 self.shutdown_inner();
4368 }
4369
4370 fn shutdown_inner(&mut self) {
4371 #[cfg(feature = "iface-local")]
4372 self.destination_types.withdraw_from_shared_instance();
4373 if self.driver_handle.is_none()
4374 && self.verify_handle.is_none()
4375 && self.persist_handle.is_none()
4376 && self.rpc_server.is_none()
4377 && {
4378 #[cfg(target_os = "linux")]
4379 {
4380 self.rpc_unix_server.is_none()
4381 }
4382 #[cfg(not(target_os = "linux"))]
4383 {
4384 true
4385 }
4386 }
4387 {
4388 return;
4389 }
4390 if let Some(mut rpc) = self.rpc_server.take() {
4392 rpc.stop();
4393 }
4394 #[cfg(target_os = "linux")]
4395 if let Some(mut rpc) = self.rpc_unix_server.take() {
4396 rpc.stop();
4397 }
4398 self.persist_known_destinations();
4399 self.persist_transport_state();
4400 if let Some(stop) = self.persist_stop.take() {
4401 let _ = stop.send(());
4402 }
4403 if let Some(handle) = self.persist_handle.take() {
4404 let _ = handle.join();
4405 }
4406 self.verify_shutdown.stop();
4407 let _ = self.tx.send(Event::Shutdown);
4408 if let Some(handle) = self.driver_handle.take() {
4409 let _ = handle.join();
4410 }
4411 self.tx.local_ratchets().lock().unwrap().clear();
4414 if let Some(handle) = self.verify_handle.take() {
4415 let _ = handle.join();
4416 }
4417 }
4418}
4419
4420pub(crate) fn ensure_destination_stamp(
4431 store: &crate::stamps::DestinationStampStore,
4432 targets: crate::stamper::StampTargets,
4433 dest: &crate::destination::Destination,
4434 identity: &Identity,
4435) -> Result<(), String> {
4436 let public_key = identity
4437 .get_public_key()
4438 .ok_or_else(|| "identity has no public key".to_string())?;
4439 let name_hash = rns_core::destination::name_hash(
4440 &dest.app_name,
4441 &dest.aspects.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
4442 );
4443 let material = crate::stamps::destination_stamp_material(&dest.hash.0, &public_key, &name_hash);
4444 let target = targets.destination_target(dest);
4445 if store.load(&dest.hash.0, &material, target).is_some() {
4446 return Ok(());
4447 }
4448 let started = std::time::Instant::now();
4449 let (stamp, value) =
4450 crate::stamps::generate_stamp(&material, target, rns_core::constants::STAMP_LENGTH, 0)
4451 .ok_or_else(|| "stamp cost is above the protocol cap".to_string())?;
4452 if let Err(error) = store.store(&dest.hash.0, &stamp, value) {
4453 log::warn!("failed to persist destination stamp: {error}");
4454 }
4455 log::debug!(
4456 "Generated and persisted stamp with value {value} for {:02x?} in {:?}",
4457 &dest.hash.0[..4],
4458 started.elapsed()
4459 );
4460 Ok(())
4461}
4462
4463pub(crate) fn build_announce_packet(
4471 tx: &EventSender,
4472 stamp_store: Option<&crate::stamps::DestinationStampStore>,
4473 stamp_destinations: bool,
4474 targets: crate::stamper::StampTargets,
4475 dest: &crate::destination::Destination,
4476 identity: &Identity,
4477 app_data: Option<&[u8]>,
4478) -> Result<
4479 (
4480 rns_core::packet::RawPacket,
4481 Option<Box<crate::common::event::AnnounceReplay>>,
4482 ),
4483 SendError,
4484> {
4485 let name_hash = rns_core::destination::name_hash(
4486 &dest.app_name,
4487 &dest.aspects.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
4488 );
4489
4490 let mut random_hash = [0u8; 10];
4491 OsRng.fill_bytes(&mut random_hash[..5]);
4492 let now_secs = std::time::SystemTime::now()
4496 .duration_since(std::time::UNIX_EPOCH)
4497 .unwrap_or_default()
4498 .as_secs();
4499 random_hash[5..10].copy_from_slice(&now_secs.to_be_bytes()[3..8]);
4500
4501 let owner = tx
4502 .local_ratchets()
4503 .lock()
4504 .unwrap()
4505 .get(&dest.hash.0)
4506 .cloned();
4507 let ratchet = if let Some(owner) = owner {
4508 if dest.direction != rns_core::types::Direction::In
4509 || owner.identity_hash() != *identity.hash()
4510 {
4511 return Err(SendError);
4512 }
4513 Some(owner.public_for_announce(now_secs).map_err(|error| {
4514 log::error!("local ratchet announcement failed: {error}");
4515 SendError
4516 })?)
4517 } else {
4518 None
4519 };
4520 let wire_app_data: Option<Vec<u8>> = if stamp_destinations {
4521 let public_key = identity.get_public_key().ok_or(SendError)?;
4522 let material =
4523 crate::stamps::destination_stamp_material(&dest.hash.0, &public_key, &name_hash);
4524 let store = stamp_store.ok_or(SendError)?;
4525 let target = targets.destination_target(dest);
4526 let (stamp, _value) = match store.load(&dest.hash.0, &material, target) {
4527 Some(entry) => entry,
4528 None => {
4529 let (stamp, value) = crate::stamps::generate_stamp(
4530 &material,
4531 target,
4532 rns_core::constants::STAMP_LENGTH,
4533 0,
4534 )
4535 .ok_or(SendError)?;
4536 if let Err(error) = store.store(&dest.hash.0, &stamp, value) {
4537 log::warn!("failed to persist destination stamp: {error}");
4538 }
4539 (stamp, value)
4540 }
4541 };
4542 let mut wire = app_data.map(|data| data.to_vec()).unwrap_or_default();
4543 wire.extend_from_slice(&crate::stamps::announce_ext_payload(
4546 &stamp,
4547 0,
4548 tx.link_request_costs().get(&dest.hash.0),
4549 ));
4550 Some(wire)
4551 } else {
4552 app_data.map(|data| data.to_vec())
4553 };
4554
4555 let (announce_data, has_ratchet) = rns_core::announce::AnnounceData::pack(
4556 identity,
4557 &dest.hash.0,
4558 &name_hash,
4559 &random_hash,
4560 ratchet.as_ref(),
4561 wire_app_data.as_deref(),
4562 )
4563 .map_err(|_| SendError)?;
4564
4565 let context_flag = if has_ratchet {
4566 rns_core::constants::FLAG_SET
4567 } else {
4568 rns_core::constants::FLAG_UNSET
4569 };
4570
4571 let flags = rns_core::packet::PacketFlags {
4572 header_type: rns_core::constants::HEADER_1,
4573 context_flag,
4574 transport_type: rns_core::constants::TRANSPORT_BROADCAST,
4575 destination_type: rns_core::constants::DESTINATION_SINGLE,
4576 packet_type: rns_core::constants::PACKET_TYPE_ANNOUNCE,
4577 };
4578
4579 let packet = rns_core::packet::RawPacket::pack(
4580 flags,
4581 0,
4582 &dest.hash.0,
4583 None,
4584 rns_core::constants::CONTEXT_NONE,
4585 &announce_data,
4586 )
4587 .map_err(|_| SendError)?;
4588
4589 let replay = if dest.dest_type == rns_core::types::DestinationType::Single {
4590 identity.get_private_key().map(|identity_prv_key| {
4591 Box::new(crate::common::event::AnnounceReplay {
4592 dest_hash: dest.hash.0,
4593 name_hash,
4594 identity_prv_key,
4595 app_data: wire_app_data,
4596 })
4597 })
4598 } else {
4599 None
4600 };
4601 Ok((packet, replay))
4602}
4603
4604impl Drop for RnsNode {
4605 fn drop(&mut self) {
4606 self.shutdown_inner();
4607 }
4608}
4609
4610#[cfg(test)]
4611mod tests {
4612 use super::*;
4613 use crate::driver::IfacRuntimeConfig;
4614 use crate::storage::RatchetStore;
4615 use rns_core::stamp::StampCost;
4616 use std::fs;
4617 use tempfile::tempdir;
4618
4619 #[test]
4620 fn local_ratchet_announcements_and_self_send() {
4621 let node = RnsNode::start(NodeConfig::default(), Box::new(NoopCallbacks)).unwrap();
4622 let identity = Identity::from_private_key(&[42; 64]);
4623 let dest = crate::Destination::single_in(
4624 "test",
4625 &["ratchet"],
4626 rns_core::types::IdentityHash(*identity.hash()),
4627 );
4628 let owner = crate::LocalRatchets::with_store(
4629 &dest,
4630 Identity::from_private_key(&[42; 64]),
4631 Arc::new(crate::MemoryRatchetStore::default()),
4632 )
4633 .unwrap();
4634 owner.enforce();
4635 node.attach_local_ratchets(owner.clone()).unwrap();
4636 node.attach_local_ratchets(owner.clone()).unwrap();
4637 let conflicting = crate::LocalRatchets::with_store(
4638 &dest,
4639 Identity::from_private_key(&[42; 64]),
4640 Arc::new(crate::MemoryRatchetStore::default()),
4641 )
4642 .unwrap();
4643 assert!(node.attach_local_ratchets(conflicting).is_err());
4644 let (packet, _) = node.build_announce(&dest, &identity, Some(b"app")).unwrap();
4645 assert_eq!(packet.flags.context_flag, rns_core::constants::FLAG_SET);
4646 let announce = rns_core::announce::AnnounceData::unpack(packet.data(), true).unwrap();
4647 announce.validate(&dest.hash.0).unwrap();
4648 assert_eq!(announce.ratchet, owner.current_public());
4649 assert!(node
4650 .build_announce(&dest, &Identity::from_private_key(&[43; 64]), None)
4651 .is_err());
4652 assert!(node
4653 .build_announce(&dest, &identity, Some(&vec![0; 500]))
4654 .is_err());
4655 let mut outgoing = dest.clone();
4656 outgoing.direction = rns_core::types::Direction::Out;
4657 outgoing.public_key = identity.get_public_key();
4658 let encrypted = node.encrypt_single_payload(&outgoing, b"self").unwrap();
4659 assert_eq!(owner.decrypt(&encrypted).unwrap().plaintext, b"self");
4660 assert!(identity.decrypt(&encrypted).is_err());
4661 }
4662
4663 struct NoopCallbacks;
4664
4665 impl Callbacks for NoopCallbacks {
4666 fn on_announce(&mut self, _: crate::destination::AnnouncedIdentity) {}
4667 fn on_path_updated(&mut self, _: rns_core::types::DestHash, _: u8) {}
4668 fn on_local_delivery(
4669 &mut self,
4670 _: rns_core::types::DestHash,
4671 _: Vec<u8>,
4672 _: rns_core::types::PacketHash,
4673 ) {
4674 }
4675 }
4676
4677 #[test]
4678 fn managed_interface_attaches_disabled_section_and_reloads_from_disk() {
4679 use crate::event::InterfaceManagementOperation as Operation;
4680
4681 let dir = tempdir().unwrap();
4682 let config_file = dir.path().join("config");
4683 let write_config = |port| {
4684 fs::write(&config_file, format!("[reticulum]\nshare_instance = no\n\n[interfaces]\n [[Managed UDP]]\n type = UDPInterface\n enabled = no\n forward_ip = 127.0.0.1\n forward_port = {port}\n"))
4685 .unwrap();
4686 };
4687 write_config(9);
4688 let node = RnsNode::from_config(Some(dir.path()), Box::new(NoopCallbacks)).unwrap();
4689 let manage = |operation| {
4690 node.query(QueryRequest::ManageInterface {
4691 operation,
4692 name: "Managed UDP".into(),
4693 })
4694 .unwrap()
4695 };
4696 assert!(matches!(
4697 manage(Operation::Detach),
4698 QueryResponse::InterfaceManagement(None)
4699 ));
4700 assert!(matches!(
4701 manage(Operation::Attach),
4702 QueryResponse::InterfaceManagement(Some(true))
4703 ));
4704 assert!(matches!(
4705 manage(Operation::Attach),
4706 QueryResponse::InterfaceManagement(Some(false))
4707 ));
4708 write_config(10);
4709 assert!(matches!(
4710 manage(Operation::Reload),
4711 QueryResponse::InterfaceManagement(Some(true))
4712 ));
4713 assert!(matches!(
4714 manage(Operation::Detach),
4715 QueryResponse::InterfaceManagement(Some(true))
4716 ));
4717 assert!(matches!(
4718 manage(Operation::Detach),
4719 QueryResponse::InterfaceManagement(None)
4720 ));
4721 node.shutdown();
4722 }
4723
4724 #[test]
4725 fn interface_management_can_be_disabled() {
4726 use crate::event::InterfaceManagementOperation as Operation;
4727
4728 let dir = tempdir().unwrap();
4729 fs::write(
4730 dir.path().join("config"),
4731 "[reticulum]\nshare_instance = no\nenable_interface_management = no\n",
4732 )
4733 .unwrap();
4734 let node = RnsNode::from_config(Some(dir.path()), Box::new(NoopCallbacks)).unwrap();
4735 assert!(matches!(
4736 node.query(QueryRequest::ManageInterface {
4737 operation: Operation::Attach,
4738 name: "missing".into(),
4739 })
4740 .unwrap(),
4741 QueryResponse::InterfaceManagement(Some(false))
4742 ));
4743 node.shutdown();
4744 }
4745
4746 #[cfg(feature = "iface-tcp")]
4747 #[test]
4748 fn managed_tcp_listener_releases_port_on_detach() {
4749 use crate::event::InterfaceManagementOperation as Operation;
4750 use std::io::Read;
4751
4752 let dir = tempdir().unwrap();
4753 let port = crate::test_support::port();
4754 fs::write(dir.path().join("config"), format!(
4755 "[reticulum]\nshare_instance = no\n\n[interfaces]\n [[Managed TCP]]\n type = TCPServerInterface\n enabled = no\n listen_ip = 127.0.0.1\n listen_port = {port}\n"
4756 )).unwrap();
4757 let node = RnsNode::from_config(Some(dir.path()), Box::new(NoopCallbacks)).unwrap();
4758 let manage = |operation| {
4759 node.query(QueryRequest::ManageInterface {
4760 operation,
4761 name: "Managed TCP".into(),
4762 })
4763 .unwrap()
4764 };
4765 assert!(matches!(
4766 manage(Operation::Attach),
4767 QueryResponse::InterfaceManagement(Some(true))
4768 ));
4769 let mut client = std::net::TcpStream::connect(("127.0.0.1", port)).unwrap();
4770 client
4771 .set_read_timeout(Some(Duration::from_secs(2)))
4772 .unwrap();
4773 thread::sleep(Duration::from_millis(100));
4774 assert!(matches!(
4775 manage(Operation::Detach),
4776 QueryResponse::InterfaceManagement(Some(true))
4777 ));
4778 let deadline = std::time::Instant::now() + Duration::from_secs(2);
4779 while std::net::TcpStream::connect(("127.0.0.1", port)).is_ok()
4780 && std::time::Instant::now() < deadline
4781 {
4782 thread::sleep(Duration::from_millis(25));
4783 }
4784 assert!(std::net::TcpStream::connect(("127.0.0.1", port)).is_err());
4785 assert_eq!(client.read(&mut [0u8; 1]).unwrap(), 0);
4786 assert!(matches!(
4787 manage(Operation::Attach),
4788 QueryResponse::InterfaceManagement(Some(true))
4789 ));
4790 assert!(std::net::TcpStream::connect(("127.0.0.1", port)).is_ok());
4791 node.shutdown();
4792 }
4793
4794 #[test]
4795 fn shared_instance_rpc_manages_interface_by_name() {
4796 use crate::pickle::PickleValue;
4797 use crate::rpc::{derive_auth_key, RpcAddr, RpcClient};
4798
4799 let dir = tempdir().unwrap();
4800 let rpc_port = crate::test_support::port();
4801 let shared_port = crate::test_support::port();
4802 fs::write(dir.path().join("config"), format!(
4803 "[reticulum]\nshare_instance = yes\ninstance_control_port = {rpc_port}\nshared_instance_port = {shared_port}\n\n[interfaces]\n [[Managed UDP]]\n type = UDPInterface\n enabled = no\n forward_ip = 127.0.0.1\n forward_port = 9\n"
4804 )).unwrap();
4805 let node = RnsNode::from_config(Some(dir.path()), Box::new(NoopCallbacks)).unwrap();
4806 let paths = storage::ensure_storage_dirs(dir.path()).unwrap();
4807 let identity = storage::load_or_create_identity(&paths.identities).unwrap();
4808 let key = derive_auth_key(&identity.get_private_key().unwrap());
4809 let call = |action: &str| {
4810 let mut client =
4811 RpcClient::connect(&RpcAddr::Tcp("127.0.0.1".into(), rpc_port), &key).unwrap();
4812 client
4813 .call(&PickleValue::Dict(vec![
4814 (
4815 PickleValue::String("manage".into()),
4816 PickleValue::String(action.into()),
4817 ),
4818 (
4819 PickleValue::String("name".into()),
4820 PickleValue::String("Managed UDP".into()),
4821 ),
4822 ]))
4823 .unwrap()
4824 };
4825 assert_eq!(call("attach_interface"), PickleValue::Bool(true));
4826 assert_eq!(call("reload_interface"), PickleValue::Bool(true));
4827 assert_eq!(call("detach_interface"), PickleValue::Bool(true));
4828 assert_eq!(call("detach_interface"), PickleValue::None);
4829 node.shutdown();
4830 }
4831
4832 struct TestWriter;
4833
4834 impl crate::interface::Writer for TestWriter {
4835 fn send_frame(&mut self, _data: &[u8]) -> io::Result<()> {
4836 Ok(())
4837 }
4838 }
4839
4840 fn test_transport_config(transport_enabled: bool) -> TransportConfig {
4841 TransportConfig {
4842 transport_enabled,
4843 identity_hash: None,
4844 local_hops_delta: 0,
4845 prefer_shorter_path: false,
4846 max_paths_per_destination: 1,
4847 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
4848 packet_hashlist_allocation: rns_core::transport::types::PacketHashlistAllocation::Eager,
4849 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
4850 max_path_destinations: usize::MAX,
4851 max_tunnel_destinations_total: usize::MAX,
4852 destination_timeout_secs: rns_core::constants::DESTINATION_TIMEOUT,
4853 announce_table_ttl_secs: rns_core::constants::ANNOUNCE_TABLE_TTL,
4854 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
4855 announce_sig_cache_enabled: true,
4856 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
4857 announce_sig_cache_ttl_secs: rns_core::constants::ANNOUNCE_SIG_CACHE_TTL,
4858 announce_queue_max_entries: 256,
4859 announce_queue_max_interfaces: 1024,
4860 }
4861 }
4862
4863 fn test_interface_info(id: u64) -> rns_core::transport::types::InterfaceInfo {
4864 rns_core::transport::types::InterfaceInfo {
4865 stamp_costs: Default::default(),
4866 id: rns_core::transport::types::InterfaceId(id),
4867 name: format!("test-{id}"),
4868 mode: rns_core::constants::MODE_FULL,
4869 gravity: 0,
4870 recursive_prs: false,
4871 announces_from_internal: true,
4872 announces_to_internal: None,
4873 out_capable: true,
4874 in_capable: true,
4875 bitrate: None,
4876 airtime_profile: None,
4877 announce_rate_target: None,
4878 announce_rate_grace: 0,
4879 announce_rate_penalty: 0.0,
4880 announce_cap: rns_core::constants::ANNOUNCE_CAP,
4881 is_local_client: false,
4882 wants_tunnel: false,
4883 tunnel_id: None,
4884 mtu: rns_core::constants::MTU as u32,
4885 ingress_control: rns_core::transport::types::IngressControlConfig::disabled(),
4886 ia_freq: 0.0,
4887 ip_freq: 0.0,
4888 op_freq: 0.0,
4889 op_samples: 0,
4890 started: 0.0,
4891 }
4892 }
4893
4894 #[test]
4895 fn static_interface_registration_applies_transport_announce_rate_defaults() {
4896 let (tx, rx) = crate::event::channel();
4897 let mut driver = Driver::new(
4898 test_transport_config(true),
4899 rx,
4900 tx.clone(),
4901 Box::new(NoopCallbacks),
4902 );
4903 driver.set_announce_rate_defaults(AnnounceRateDefaults {
4904 target: Some(7200.0),
4905 penalty: 15.0,
4906 grace: 7,
4907 });
4908
4909 let id = rns_core::transport::types::InterfaceId(7);
4910 register_started_interface(StartedInterface {
4911 driver: &mut driver,
4912 tx: &tx,
4913 queue_capacity: crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
4914 id,
4915 info: test_interface_info(id.0),
4916 writer: Box::new(TestWriter),
4917 interface_type_name: "TestInterface".to_string(),
4918 ifac_state: None,
4919 ifac_runtime: &IfacRuntimeConfig {
4920 netname: None,
4921 netkey: None,
4922 size: 16,
4923 },
4924 stamp_costs: Default::default(),
4925 });
4926
4927 let info = &driver.interfaces[&id].info;
4928 assert_eq!(info.announce_rate_target, Some(7200.0));
4929 assert_eq!(info.announce_rate_penalty, 15.0);
4930 assert_eq!(info.announce_rate_grace, 7);
4931 }
4932
4933 #[test]
4934 fn static_interface_registration_applies_transport_ingress_control_defaults() {
4935 let (tx, rx) = crate::event::channel();
4936 let mut driver = Driver::new(
4937 test_transport_config(true),
4938 rx,
4939 tx.clone(),
4940 Box::new(NoopCallbacks),
4941 );
4942 let mut defaults = rns_core::transport::types::IngressControlConfig::enabled();
4943 defaults.burst_hold = 1.5;
4944 defaults.pr_burst_freq = 5.5;
4945 defaults.egress_enabled = true;
4946 defaults.egress_pr_freq = 9.5;
4947 driver.set_ingress_control_defaults(defaults);
4948
4949 let id = rns_core::transport::types::InterfaceId(8);
4950 register_started_interface(StartedInterface {
4951 driver: &mut driver,
4952 tx: &tx,
4953 queue_capacity: crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
4954 id,
4955 info: test_interface_info(id.0),
4956 writer: Box::new(TestWriter),
4957 interface_type_name: "TestInterface".to_string(),
4958 ifac_state: None,
4959 ifac_runtime: &IfacRuntimeConfig {
4960 netname: None,
4961 netkey: None,
4962 size: 16,
4963 },
4964 stamp_costs: rns_core::transport::types::InterfaceStampCosts {
4965 destination_cost: None,
4966 pr_cost: Some(rns_core::stamp::StampCost::from_f64(12.25)),
4967 },
4968 });
4969
4970 let ic = driver.interfaces[&id].info.ingress_control;
4971 assert_eq!(ic.burst_hold, 1.5);
4972 assert_eq!(ic.pr_burst_freq, 5.5);
4973 assert!(ic.egress_enabled);
4974 assert_eq!(ic.egress_pr_freq, 9.5);
4975 assert_eq!(
4977 driver
4978 .engine
4979 .interface_info(&id)
4980 .unwrap()
4981 .stamp_costs
4982 .pr_cost,
4983 Some(rns_core::stamp::StampCost::from_f64(12.25))
4984 );
4985 }
4986
4987 struct TestNodeRatchetStore {
4988 entry: storage::RatchetEntry,
4989 current_calls: std::sync::Mutex<Vec<[u8; 16]>>,
4990 }
4991
4992 impl storage::RatchetStore for TestNodeRatchetStore {
4993 fn remember(&self, _dest_hash: [u8; 16], _entry: storage::RatchetEntry) -> io::Result<()> {
4994 Ok(())
4995 }
4996
4997 fn current(
4998 &self,
4999 dest_hash: &[u8; 16],
5000 _now: f64,
5001 _expiry_secs: f64,
5002 ) -> io::Result<Option<storage::RatchetEntry>> {
5003 self.current_calls.lock().unwrap().push(*dest_hash);
5004 Ok(Some(self.entry))
5005 }
5006
5007 fn cleanup(
5008 &self,
5009 _known_destinations: &HashSet<[u8; 16]>,
5010 _now: f64,
5011 _expiry_secs: f64,
5012 ) -> io::Result<storage::RatchetCleanupStats> {
5013 Ok(Default::default())
5014 }
5015 }
5016
5017 #[test]
5018 fn send_packet_checks_ratchet_store_for_single_destinations() {
5019 let store = Arc::new(TestNodeRatchetStore {
5020 entry: storage::RatchetEntry {
5021 ratchet: [0x55; 32],
5022 received_at: time::now(),
5023 },
5024 current_calls: std::sync::Mutex::new(Vec::new()),
5025 });
5026 let ratchet_store: Arc<dyn storage::RatchetStore> = store.clone();
5027 let node = RnsNode::start(
5028 NodeConfig {
5029 ratchet_store: Some(ratchet_store),
5030 ..Default::default()
5031 },
5032 Box::new(NoopCallbacks),
5033 )
5034 .unwrap();
5035
5036 let mut rng = OsRng;
5037 let remote_identity = Identity::new(&mut rng);
5038 let public_key = remote_identity.get_public_key().unwrap();
5039 let announced = crate::destination::AnnouncedIdentity {
5040 dest_hash: rns_core::types::DestHash([0x22; 16]),
5041 identity_hash: rns_core::types::IdentityHash(*remote_identity.hash()),
5042 public_key,
5043 app_data: None,
5044 hops: 1,
5045 received_at: time::now(),
5046 receiving_interface: rns_core::transport::types::InterfaceId(0),
5047 rssi: Some(-100),
5048 snr: Some(10.5),
5049 };
5050 let dest = crate::destination::Destination::single_out("test", &["ratchet"], &announced);
5051
5052 node.send_packet_queued(&dest, b"hello").unwrap();
5053 assert_eq!(
5054 store.current_calls.lock().unwrap().as_slice(),
5055 &[dest.hash.0]
5056 );
5057
5058 node.shutdown();
5059 }
5060
5061 #[test]
5062 fn single_payload_uses_stored_ratchet_key_material() {
5063 let ratchet_prv = rns_crypto::x25519::X25519PrivateKey::from_bytes(&[0x42; 32]);
5064 let ratchet_pub = ratchet_prv.public_key().public_bytes();
5065 let store = Arc::new(TestNodeRatchetStore {
5066 entry: storage::RatchetEntry {
5067 ratchet: ratchet_pub,
5068 received_at: time::now(),
5069 },
5070 current_calls: std::sync::Mutex::new(Vec::new()),
5071 });
5072 let ratchet_store: Arc<dyn storage::RatchetStore> = store.clone();
5073 let (tx, _rx) = crate::event::channel();
5074 let node = RnsNode {
5075 tx,
5076 driver_handle: None,
5077 verify_handle: None,
5078 verify_shutdown: Arc::default(),
5079 persist_handle: None,
5080 persist_stop: None,
5081 rpc_server: None,
5082 #[cfg(target_os = "linux")]
5083 rpc_unix_server: None,
5084 tick_interval_ms: Arc::new(AtomicU64::new(1000)),
5085 next_send_ticket: AtomicU64::new(0),
5086 encrypt_deferred_inline: std::thread::available_parallelism()
5087 .map_or(true, |cpus| cpus.get() == 1),
5088 accepting_work: Arc::new(AtomicBool::new(true)),
5089 probe_server: None,
5090 known_destinations_path: None,
5091 known_destinations_store: None,
5092 stamp_store: None,
5093 stamp_destinations: Arc::new(AtomicBool::new(rns_core::constants::STAMP_DESTINATIONS)),
5094 stamp_targets: crate::stamper::StampTargets::default(),
5095 stamper: None,
5096 transport_state_dir: None,
5097 ratchet_store: Some(ratchet_store),
5098 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
5099 #[cfg(feature = "iface-local")]
5100 destination_types: Default::default(),
5101 };
5102
5103 let mut rng = OsRng;
5104 let remote_identity = Identity::new(&mut rng);
5105 let public_key = remote_identity.get_public_key().unwrap();
5106 let announced = crate::destination::AnnouncedIdentity {
5107 dest_hash: rns_core::types::DestHash([0x33; 16]),
5108 identity_hash: rns_core::types::IdentityHash(*remote_identity.hash()),
5109 public_key,
5110 app_data: None,
5111 hops: 1,
5112 received_at: time::now(),
5113 receiving_interface: rns_core::transport::types::InterfaceId(0),
5114 rssi: Some(-100),
5115 snr: Some(10.5),
5116 };
5117 let dest = crate::destination::Destination::single_out("test", &["ratchet"], &announced);
5118 let payload = node
5119 .encrypt_single_payload(&dest, b"ratchet message")
5120 .unwrap();
5121
5122 assert_eq!(
5123 store.current_calls.lock().unwrap().as_slice(),
5124 &[dest.hash.0]
5125 );
5126 assert!(remote_identity.decrypt(&payload).is_err());
5127
5128 let peer_pub_bytes: [u8; 32] = payload[..32].try_into().unwrap();
5129 let peer_pub = rns_crypto::x25519::X25519PublicKey::from_bytes(&peer_pub_bytes);
5130 let shared_key = ratchet_prv.exchange(&peer_pub);
5131 let derived_key = rns_crypto::hkdf::hkdf(
5132 rns_crypto::identity::DERIVED_KEY_LENGTH,
5133 &shared_key,
5134 Some(remote_identity.hash()),
5135 None,
5136 )
5137 .unwrap();
5138 let token = rns_crypto::token::Token::new(&derived_key).unwrap();
5139 let plaintext = token.decrypt(&payload[32..]).unwrap();
5140 assert_eq!(plaintext, b"ratchet message");
5141 }
5142
5143 #[test]
5144 fn tcp_client_interface_is_not_discoverable_without_kiss_framing() {
5145 let mut params = std::collections::HashMap::new();
5146 params.insert("discoverable".to_string(), "yes".to_string());
5147 params.insert(
5148 "discovery_name".to_string(),
5149 "invalid-tcp-client".to_string(),
5150 );
5151 params.insert("reachable_on".to_string(), "example.com".to_string());
5152 params.insert("target_port".to_string(), "4242".to_string());
5153
5154 let discovery =
5155 super::extract_discovery_config("tcp-client", "TCPClientInterface", ¶ms);
5156
5157 assert!(
5158 discovery.is_none(),
5159 "TCPClientInterface discovery must be rejected unless KISS framing is supported"
5160 );
5161 }
5162
5163 #[test]
5164 fn discovery_config_parses_location_command() {
5165 let mut params = std::collections::HashMap::new();
5166 params.insert("discoverable".into(), "yes".into());
5167 params.insert("location_cmd".into(), "~/bin/reticulum-location".into());
5168
5169 let discovery =
5170 super::extract_discovery_config("public", "BackboneInterface", ¶ms).unwrap();
5171 assert_eq!(
5172 discovery.location_cmd.as_deref(),
5173 Some("~/bin/reticulum-location")
5174 );
5175 }
5176
5177 #[test]
5178 fn discovery_config_parses_valid_operator_lxmf_address() {
5179 let mut params = std::collections::HashMap::new();
5180 params.insert("discoverable".into(), "yes".into());
5181 params.insert("discovery_lxmf_address".into(), "ab".repeat(16));
5182
5183 let discovery =
5184 super::extract_discovery_config("public", "BackboneInterface", ¶ms).unwrap();
5185
5186 assert_eq!(discovery.operator_lxmf_address, Some([0xab; 16]));
5187 }
5188
5189 #[test]
5190 fn discovery_config_ignores_invalid_operator_lxmf_addresses() {
5191 for address in ["ab".repeat(15), "zz".repeat(16)] {
5192 let mut params = std::collections::HashMap::new();
5193 params.insert("discoverable".into(), "yes".into());
5194 params.insert("discovery_lxmf_address".into(), address);
5195
5196 let discovery =
5197 super::extract_discovery_config("public", "BackboneInterface", ¶ms).unwrap();
5198
5199 assert_eq!(discovery.operator_lxmf_address, None);
5200 }
5201 }
5202
5203 #[test]
5204 fn ingress_control_config_defaults_by_interface_type() {
5205 let params = std::collections::HashMap::new();
5206
5207 let tcp = super::parse_ingress_control_config("TCPServerInterface", ¶ms).unwrap();
5208 assert!(tcp.enabled);
5209 assert_eq!(
5210 tcp.max_held_announces,
5211 rns_core::constants::IC_MAX_HELD_ANNOUNCES
5212 );
5213 assert_eq!(tcp.burst_hold, rns_core::constants::IC_BURST_HOLD);
5214
5215 let pipe = super::parse_ingress_control_config("PipeInterface", ¶ms).unwrap();
5216 assert!(!pipe.enabled);
5217 assert_eq!(
5218 pipe.held_release_interval,
5219 rns_core::constants::IC_HELD_RELEASE_INTERVAL
5220 );
5221 }
5222
5223 #[test]
5224 fn ingress_control_config_parses_python_ic_keys() {
5225 let mut params = std::collections::HashMap::new();
5226 params.insert("ingress_control".to_string(), "No".to_string());
5227 params.insert("ic_max_held_announces".to_string(), "17".to_string());
5228 params.insert("ic_burst_hold".to_string(), "1.5".to_string());
5229 params.insert("ic_burst_freq_new".to_string(), "2.5".to_string());
5230 params.insert("ic_burst_freq".to_string(), "3.5".to_string());
5231 params.insert("ic_pr_burst_freq_new".to_string(), "3.25".to_string());
5232 params.insert("ic_pr_burst_freq".to_string(), "8.25".to_string());
5233 params.insert("egress_control".to_string(), "Yes".to_string());
5234 params.insert("ec_pr_freq".to_string(), "5.25".to_string());
5235 params.insert("ic_new_time".to_string(), "4.5".to_string());
5236 params.insert("ic_burst_penalty".to_string(), "5.5".to_string());
5237 params.insert("ic_held_release_interval".to_string(), "6.5".to_string());
5238
5239 let config = super::parse_ingress_control_config("TCPServerInterface", ¶ms).unwrap();
5240
5241 assert!(!config.enabled);
5242 assert_eq!(config.max_held_announces, 17);
5243 assert_eq!(config.burst_hold, 1.5);
5244 assert_eq!(config.burst_freq_new, 2.5);
5245 assert_eq!(config.burst_freq, 3.5);
5246 assert_eq!(config.pr_burst_freq_new, 3.25);
5247 assert_eq!(config.pr_burst_freq, 8.25);
5248 assert!(config.egress_enabled);
5249 assert_eq!(config.egress_pr_freq, 5.25);
5250 assert_eq!(config.new_time, 4.5);
5251 assert_eq!(config.burst_penalty, 5.5);
5252 assert_eq!(config.held_release_interval, 6.5);
5253 }
5254
5255 #[test]
5256 fn ingress_control_config_rejects_invalid_values() {
5257 let mut params = std::collections::HashMap::new();
5258 params.insert("ic_burst_hold".to_string(), "-1".to_string());
5259
5260 let err = super::parse_ingress_control_config("TCPServerInterface", ¶ms).unwrap_err();
5261
5262 assert!(err.contains("ic_burst_hold"));
5263 }
5264
5265 #[test]
5266 fn interface_stamp_costs_are_fractional_and_clamped() {
5267 use rns_core::stamp::StampCost;
5269 let mut params = std::collections::HashMap::new();
5270 let none = super::parse_interface_stamp_costs(¶ms).unwrap();
5271 assert_eq!(none, Default::default());
5272
5273 params.insert("destination_cost".to_string(), "17.5".to_string());
5274 params.insert("pr_cost".to_string(), "250".to_string());
5275 let costs = super::parse_interface_stamp_costs(¶ms).unwrap();
5276 assert_eq!(costs.destination_cost, Some(StampCost::from_f64(17.5)));
5277 assert_eq!(costs.pr_cost, Some(StampCost::from_whole(100)));
5278
5279 params.insert("pr_cost".to_string(), "-3".to_string());
5280 let costs = super::parse_interface_stamp_costs(¶ms).unwrap();
5281 assert_eq!(costs.pr_cost, Some(StampCost::ZERO));
5282
5283 params.insert("destination_cost".to_string(), "steep".to_string());
5284 let err = super::parse_interface_stamp_costs(¶ms).unwrap_err();
5285 assert!(err.contains("destination_cost"));
5286 }
5287
5288 #[test]
5289 fn discovery_stamp_default_is_16_but_explicit_override_is_preserved() {
5290 let mut params = std::collections::HashMap::new();
5291 params.insert("discoverable".to_string(), "yes".to_string());
5292 let defaults =
5293 super::extract_discovery_config("Discovery Test", "BackboneInterface", ¶ms)
5294 .unwrap();
5295 assert_eq!(
5296 defaults.stamp_value,
5297 rns_core::stamp::StampCost::from_whole(16)
5298 );
5299
5300 params.insert("discovery_stamp_value".to_string(), "14".to_string());
5301 let overridden =
5302 super::extract_discovery_config("Discovery Test", "BackboneInterface", ¶ms)
5303 .unwrap();
5304 assert_eq!(
5305 overridden.stamp_value,
5306 rns_core::stamp::StampCost::from_whole(14)
5307 );
5308 }
5309
5310 #[test]
5311 fn start_and_shutdown() {
5312 let node = RnsNode::start(
5313 NodeConfig {
5314 panic_on_interface_error: false,
5315 link_proof_verify_threads: Default::default(),
5316 stamps: Default::default(),
5317 transport_enabled: false,
5318 static_transport_identity: false,
5319 local_hops_delta: false,
5320 identity: None,
5321 interfaces: vec![],
5322 share_instance: false,
5323 instance_name: "default".into(),
5324 shared_instance_port: 37428,
5325 rpc_port: 0,
5326 cache_dir: None,
5327 ratchet_store: None,
5328 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
5329 management: Default::default(),
5330 probe_port: None,
5331 probe_addrs: vec![],
5332 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
5333 direct_connect_policy: Default::default(),
5334 direct_connect_allow_private_endpoints: false,
5335 device: None,
5336 underlay_mark: None,
5337 hooks: Vec::new(),
5338 discover_interfaces: false,
5339 autoconnect_interface_mode: None,
5340 autoconnect_interface_gravity: 0,
5341 autoconnect_announces_to_internal: false,
5342 autoconnect_unverified_implementations: false,
5343 discovery_required_value: None,
5344 respond_to_probes: false,
5345 prefer_shorter_path: false,
5346 max_paths_per_destination: 1,
5347 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
5348 packet_hashlist_allocation:
5349 rns_core::transport::types::PacketHashlistAllocation::Eager,
5350 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
5351 max_path_destinations: usize::MAX,
5352 max_tunnel_destinations_total: usize::MAX,
5353 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
5354 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
5355 announce_table_ttl: Duration::from_secs(
5356 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
5357 ),
5358 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
5359 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
5360 interface_writer_queue_capacity:
5361 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
5362 announce_rate_defaults: AnnounceRateDefaults::default(),
5363 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
5364 ),
5365 #[cfg(feature = "iface-backbone")]
5366 backbone_peer_pool: None,
5367 announce_sig_cache_enabled: true,
5368 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
5369 announce_sig_cache_ttl: Duration::from_secs(
5370 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
5371 ),
5372 registry: None,
5373 #[cfg(feature = "hooks")]
5374 provider_bridge: None,
5375 },
5376 Box::new(NoopCallbacks),
5377 )
5378 .unwrap();
5379 node.shutdown();
5380 }
5381
5382 #[test]
5383 fn known_destinations_persist_across_restart() {
5384 let dir = tempdir().unwrap();
5385 let dest_hash = [0x91; 16];
5386 let identity = Identity::new(&mut OsRng);
5387 let last_used_at = 77.0;
5388 let receiving_interface = rns_core::transport::types::InterfaceId(42);
5389
5390 let node = RnsNode::from_config(Some(dir.path()), Box::new(NoopCallbacks)).unwrap();
5391 let (response_tx, response_rx) = std::sync::mpsc::channel();
5392 node.event_sender()
5393 .send(crate::event::Event::query(
5394 QueryRequest::RestoreKnownDestination(crate::event::KnownDestinationEntry {
5395 dest_hash,
5396 identity_hash: *identity.hash(),
5397 public_key: identity.get_public_key().unwrap(),
5398 app_data: Some(b"persisted".to_vec()),
5399 hops: 2,
5400 received_at: 55.0,
5401 receiving_interface,
5402 was_used: true,
5403 last_used_at: Some(last_used_at),
5404 retained: true,
5405 stamp: None,
5406 stamp_value: rns_core::stamp::StampValue::ZERO,
5407 lr_cost: None,
5408 }),
5409 response_tx,
5410 ))
5411 .unwrap();
5412 assert!(matches!(
5413 response_rx.recv().unwrap(),
5414 QueryResponse::RestoreKnownDestination(true)
5415 ));
5416 node.shutdown();
5417
5418 let restarted = RnsNode::from_config(Some(dir.path()), Box::new(NoopCallbacks)).unwrap();
5419 let entries = restarted.known_destinations().unwrap();
5420 let entry = entries
5421 .iter()
5422 .find(|entry| entry.dest_hash == dest_hash)
5423 .expect("reloaded destination should appear in lifecycle listing");
5424 assert!(entry.retained);
5425 assert!(entry.was_used);
5426 assert_eq!(entry.hops, 2);
5427 assert_eq!(entry.receiving_interface, receiving_interface);
5428 assert_eq!(entry.last_used_at, Some(last_used_at));
5429
5430 let recalled = restarted
5431 .recall_identity(&rns_core::types::DestHash(dest_hash))
5432 .unwrap()
5433 .expect("known destination should reload from storage");
5434 assert_eq!(recalled.identity_hash.0, *identity.hash());
5435 assert_eq!(recalled.app_data, Some(b"persisted".to_vec()));
5436 restarted.shutdown();
5437 }
5438
5439 #[test]
5440 fn transport_paths_hashes_and_detached_tunnels_restore_from_upstream_files() {
5441 use rns_core::packet::{PacketFlags, RawPacket};
5442 use rns_core::transport::persistence::{
5443 PersistedPath, PersistedTunnel, TransportStateSnapshot,
5444 };
5445
5446 let dir = tempdir().unwrap();
5447 fs::write(
5448 dir.path().join("config"),
5449 r#"[reticulum]
5450enable_transport = True
5451share_instance = False
5452
5453[interfaces]
5454 [[Persistence Test]]
5455 type = UDPInterface
5456 forward_ip = 127.0.0.1
5457 forward_port = 9
5458 interface_mode = full
5459"#,
5460 )
5461 .unwrap();
5462 let paths = storage::ensure_storage_dirs(dir.path()).unwrap();
5463 let destination_hash = [0x31; 16];
5464 let packet = RawPacket::pack(
5465 PacketFlags {
5466 header_type: rns_core::constants::HEADER_1,
5467 context_flag: rns_core::constants::FLAG_UNSET,
5468 transport_type: rns_core::constants::TRANSPORT_BROADCAST,
5469 destination_type: rns_core::constants::DESTINATION_SINGLE,
5470 packet_type: rns_core::constants::PACKET_TYPE_ANNOUNCE,
5471 },
5472 0,
5473 &destination_hash,
5474 None,
5475 rns_core::constants::CONTEXT_NONE,
5476 &[0xA5; 32],
5477 )
5478 .unwrap();
5479 crate::announce_cache::AnnounceCache::new(paths.cache.join("announces"))
5480 .store(&packet.packet_hash, &packet.raw, None)
5481 .unwrap();
5482 let now = time::now();
5483 let route = PersistedPath {
5484 destination_hash,
5485 timestamp: now,
5486 received_from: [0x41; 16],
5487 hops: 2,
5488 expires: now + 600.0,
5489 random_blobs: vec![[0x51; 10]],
5490 interface_hash: Some(rns_core::hash::full_hash(b"Persistence Test")),
5491 packet_hash: packet.packet_hash,
5492 used: 0.0,
5493 };
5494 storage::save_transport_state(
5495 &TransportStateSnapshot {
5496 packet_hashes: vec![[0x61; 32], [0x62; 32]],
5497 paths: vec![route.clone()],
5498 tunnels: vec![PersistedTunnel {
5499 tunnel_id: [0x71; 32],
5500 interface_hash: None,
5501 paths: vec![PersistedPath {
5502 interface_hash: None,
5503 ..route
5504 }],
5505 expires: now + 1_200.0,
5506 }],
5507 },
5508 &paths.storage,
5509 false,
5510 )
5511 .unwrap();
5512
5513 let node = RnsNode::from_config(Some(dir.path()), Box::new(NoopCallbacks)).unwrap();
5514 let QueryResponse::TransportStateSnapshot(restored) =
5515 node.query(QueryRequest::TransportStateSnapshot).unwrap()
5516 else {
5517 panic!("unexpected transport snapshot response");
5518 };
5519 let stored = |byte: u8| {
5521 let mut hash = [0u8; 32];
5522 hash[..16].fill(byte);
5523 hash
5524 };
5525 assert_eq!(restored.packet_hashes, vec![stored(0x61), stored(0x62)]);
5526 assert_eq!(restored.paths.len(), 1);
5527 assert_eq!(restored.paths[0].destination_hash, destination_hash);
5528 assert_eq!(restored.paths[0].received_from, [0x41; 16]);
5529 assert_eq!(restored.tunnels.len(), 1);
5530 assert_eq!(restored.tunnels[0].tunnel_id, [0x71; 32]);
5531 assert_eq!(restored.tunnels[0].interface_hash, None);
5532 node.shutdown();
5533 }
5534
5535 #[test]
5536 fn from_config_cleans_persistent_ratchets_after_loading_known_destinations() {
5537 fn hex(bytes: &[u8]) -> String {
5538 bytes.iter().map(|byte| format!("{:02x}", byte)).collect()
5539 }
5540
5541 let dir = tempdir().unwrap();
5542 fs::write(
5543 dir.path().join("config"),
5544 "[reticulum]\nenable_transport = False\nshare_instance = False\nratchet_expiry = 300\n",
5545 )
5546 .unwrap();
5547 let paths = storage::ensure_storage_dirs(dir.path()).unwrap();
5548 let known_live = [0x01; 16];
5549 let known_expired = [0x02; 16];
5550 let unknown = [0x03; 16];
5551 let known_corrupt = [0x04; 16];
5552 let now = time::now();
5553
5554 let identity = Identity::new(&mut OsRng);
5555 let known = std::collections::HashMap::from([
5556 (
5557 known_live,
5558 storage::KnownDestination {
5559 identity_hash: *identity.hash(),
5560 public_key: identity.get_public_key().unwrap(),
5561 app_data: None,
5562 hops: 1,
5563 received_at: now,
5564 receiving_interface: 0,
5565 was_used: false,
5566 last_used_at: None,
5567 retained: false,
5568 stamp: None,
5569 stamp_value: rns_core::stamp::StampValue::ZERO,
5570 lr_cost: None,
5571 },
5572 ),
5573 (
5574 known_expired,
5575 storage::KnownDestination {
5576 identity_hash: *identity.hash(),
5577 public_key: identity.get_public_key().unwrap(),
5578 app_data: None,
5579 hops: 1,
5580 received_at: now,
5581 receiving_interface: 0,
5582 was_used: false,
5583 last_used_at: None,
5584 retained: false,
5585 stamp: None,
5586 stamp_value: rns_core::stamp::StampValue::ZERO,
5587 lr_cost: None,
5588 },
5589 ),
5590 (
5591 known_corrupt,
5592 storage::KnownDestination {
5593 identity_hash: *identity.hash(),
5594 public_key: identity.get_public_key().unwrap(),
5595 app_data: None,
5596 hops: 1,
5597 received_at: now,
5598 receiving_interface: 0,
5599 was_used: false,
5600 last_used_at: None,
5601 retained: false,
5602 stamp: None,
5603 stamp_value: rns_core::stamp::StampValue::ZERO,
5604 lr_cost: None,
5605 },
5606 ),
5607 ]);
5608 storage::save_known_destinations(&known, &paths.storage.join("known_destinations"))
5609 .unwrap();
5610
5611 let store = storage::FsRatchetStore::new(paths.ratchets.clone());
5612 store
5613 .remember(
5614 known_live,
5615 storage::RatchetEntry {
5616 ratchet: [0x11; 32],
5617 received_at: now,
5618 },
5619 )
5620 .unwrap();
5621 store
5622 .remember(
5623 known_expired,
5624 storage::RatchetEntry {
5625 ratchet: [0x22; 32],
5626 received_at: now - 1000.0,
5627 },
5628 )
5629 .unwrap();
5630 store
5631 .remember(
5632 unknown,
5633 storage::RatchetEntry {
5634 ratchet: [0x33; 32],
5635 received_at: now,
5636 },
5637 )
5638 .unwrap();
5639 fs::write(paths.ratchets.join(hex(&known_corrupt)), b"not msgpack").unwrap();
5640 fs::write(paths.ratchets.join("0102.out"), b"temp").unwrap();
5641
5642 let node = RnsNode::from_config(Some(dir.path()), Box::new(NoopCallbacks)).unwrap();
5643
5644 assert!(!paths.ratchets.join(hex(&known_live)).exists());
5645 assert!(!paths.ratchets.join(hex(&known_expired)).exists());
5646 assert!(!paths.ratchets.join(hex(&unknown)).exists());
5647 assert!(!paths.ratchets.join(hex(&known_corrupt)).exists());
5648 assert!(!paths.ratchets.join("0102.out").exists());
5649 assert!(paths.storage.join("known_destinations.imported").exists());
5650
5651 let store = crate::storage_sqlite::KnownDestinationsStore::open_read_only(
5652 &crate::storage_sqlite::default_path(&paths.storage),
5653 16,
5654 )
5655 .unwrap();
5656 assert_eq!(store.load_all().unwrap().len(), 3);
5657 assert!(store.current(&known_live, now, 300.0).unwrap().is_some());
5658 assert!(store.current(&known_expired, now, 300.0).unwrap().is_none());
5659 assert!(store.current(&unknown, now, 300.0).unwrap().is_none());
5660 assert!(store.current(&known_corrupt, now, 300.0).unwrap().is_none());
5661
5662 node.shutdown();
5663 }
5664
5665 #[test]
5666 fn start_with_identity() {
5667 let identity = Identity::new(&mut OsRng);
5668 let hash = *identity.hash();
5669 let node = RnsNode::start(
5670 NodeConfig {
5671 panic_on_interface_error: false,
5672 link_proof_verify_threads: Default::default(),
5673 stamps: Default::default(),
5674 transport_enabled: true,
5675 static_transport_identity: false,
5676 local_hops_delta: false,
5677 identity: Some(identity),
5678 interfaces: vec![],
5679 share_instance: false,
5680 instance_name: "default".into(),
5681 shared_instance_port: 37428,
5682 rpc_port: 0,
5683 cache_dir: None,
5684 ratchet_store: None,
5685 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
5686 management: Default::default(),
5687 probe_port: None,
5688 probe_addrs: vec![],
5689 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
5690 direct_connect_policy: Default::default(),
5691 direct_connect_allow_private_endpoints: false,
5692 device: None,
5693 underlay_mark: None,
5694 hooks: Vec::new(),
5695 discover_interfaces: false,
5696 autoconnect_interface_mode: None,
5697 autoconnect_interface_gravity: 0,
5698 autoconnect_announces_to_internal: false,
5699 autoconnect_unverified_implementations: false,
5700 discovery_required_value: None,
5701 respond_to_probes: false,
5702 prefer_shorter_path: false,
5703 max_paths_per_destination: 1,
5704 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
5705 packet_hashlist_allocation:
5706 rns_core::transport::types::PacketHashlistAllocation::Eager,
5707 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
5708 max_path_destinations: usize::MAX,
5709 max_tunnel_destinations_total: usize::MAX,
5710 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
5711 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
5712 announce_table_ttl: Duration::from_secs(
5713 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
5714 ),
5715 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
5716 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
5717 interface_writer_queue_capacity:
5718 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
5719 announce_rate_defaults: AnnounceRateDefaults::default(),
5720 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
5721 ),
5722 #[cfg(feature = "iface-backbone")]
5723 backbone_peer_pool: None,
5724 announce_sig_cache_enabled: true,
5725 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
5726 announce_sig_cache_ttl: Duration::from_secs(
5727 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
5728 ),
5729 registry: None,
5730 #[cfg(feature = "hooks")]
5731 provider_bridge: None,
5732 },
5733 Box::new(NoopCallbacks),
5734 )
5735 .unwrap();
5736 let _ = hash;
5738 node.shutdown();
5739 }
5740
5741 #[test]
5742 fn start_generates_identity() {
5743 let node = RnsNode::start(
5744 NodeConfig {
5745 panic_on_interface_error: false,
5746 link_proof_verify_threads: Default::default(),
5747 stamps: Default::default(),
5748 transport_enabled: false,
5749 static_transport_identity: false,
5750 local_hops_delta: false,
5751 identity: None,
5752 interfaces: vec![],
5753 share_instance: false,
5754 instance_name: "default".into(),
5755 shared_instance_port: 37428,
5756 rpc_port: 0,
5757 cache_dir: None,
5758 ratchet_store: None,
5759 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
5760 management: Default::default(),
5761 probe_port: None,
5762 probe_addrs: vec![],
5763 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
5764 direct_connect_policy: Default::default(),
5765 direct_connect_allow_private_endpoints: false,
5766 device: None,
5767 underlay_mark: None,
5768 hooks: Vec::new(),
5769 discover_interfaces: false,
5770 autoconnect_interface_mode: None,
5771 autoconnect_interface_gravity: 0,
5772 autoconnect_announces_to_internal: false,
5773 autoconnect_unverified_implementations: false,
5774 discovery_required_value: None,
5775 respond_to_probes: false,
5776 prefer_shorter_path: false,
5777 max_paths_per_destination: 1,
5778 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
5779 packet_hashlist_allocation:
5780 rns_core::transport::types::PacketHashlistAllocation::Eager,
5781 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
5782 max_path_destinations: usize::MAX,
5783 max_tunnel_destinations_total: usize::MAX,
5784 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
5785 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
5786 announce_table_ttl: Duration::from_secs(
5787 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
5788 ),
5789 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
5790 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
5791 interface_writer_queue_capacity:
5792 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
5793 announce_rate_defaults: AnnounceRateDefaults::default(),
5794 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
5795 ),
5796 #[cfg(feature = "iface-backbone")]
5797 backbone_peer_pool: None,
5798 announce_sig_cache_enabled: true,
5799 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
5800 announce_sig_cache_ttl: Duration::from_secs(
5801 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
5802 ),
5803 registry: None,
5804 #[cfg(feature = "hooks")]
5805 provider_bridge: None,
5806 },
5807 Box::new(NoopCallbacks),
5808 )
5809 .unwrap();
5810 node.shutdown();
5812 }
5813
5814 #[test]
5815 fn from_config_creates_identity() {
5816 let dir = std::env::temp_dir().join(format!("rns-test-fc-{}", std::process::id()));
5817 let _ = fs::remove_dir_all(&dir);
5818 fs::create_dir_all(&dir).unwrap();
5819
5820 fs::write(
5822 dir.join("config"),
5823 "[reticulum]\nenable_transport = False\n",
5824 )
5825 .unwrap();
5826
5827 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
5828
5829 assert!(dir.join("storage/identities/identity").exists());
5831
5832 node.shutdown();
5833 let _ = fs::remove_dir_all(&dir);
5834 }
5835
5836 #[test]
5837 fn private_config_keeps_state_out_of_config_directory() {
5838 let root = tempfile::tempdir().unwrap();
5839 let config_dir = root.path().join("config");
5840 let state_dir = root.path().join("state");
5841 fs::create_dir_all(&config_dir).unwrap();
5842 fs::write(
5843 config_dir.join("config"),
5844 "[reticulum]\nenable_transport = False\nshare_instance = Yes\n",
5845 )
5846 .unwrap();
5847 let node =
5848 RnsNode::from_private_config(&config_dir, &state_dir, None, Box::new(NoopCallbacks))
5849 .unwrap();
5850 assert!(state_dir.join("storage/identities/identity").exists());
5851 assert!(!config_dir.join("storage").exists());
5852 node.shutdown();
5853 }
5854
5855 #[test]
5856 fn from_config_loads_identity() {
5857 let dir = std::env::temp_dir().join(format!("rns-test-fl-{}", std::process::id()));
5858 let _ = fs::remove_dir_all(&dir);
5859 fs::create_dir_all(dir.join("storage/identities")).unwrap();
5860
5861 let identity = Identity::new(&mut OsRng);
5863 let hash = *identity.hash();
5864 storage::save_identity(&identity, &dir.join("storage/identities/identity")).unwrap();
5865
5866 fs::write(
5867 dir.join("config"),
5868 "[reticulum]\nenable_transport = False\n",
5869 )
5870 .unwrap();
5871
5872 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
5873
5874 let loaded = storage::load_identity(&dir.join("storage/identities/identity")).unwrap();
5876 assert_eq!(*loaded.hash(), hash);
5877
5878 node.shutdown();
5879 let _ = fs::remove_dir_all(&dir);
5880 }
5881
5882 #[test]
5883 fn from_config_tcp_server() {
5884 let dir = std::env::temp_dir().join(format!("rns-test-fts-{}", std::process::id()));
5885 let _ = fs::remove_dir_all(&dir);
5886 fs::create_dir_all(&dir).unwrap();
5887
5888 let port = crate::test_support::port();
5889
5890 let config = format!(
5891 r#"
5892[reticulum]
5893enable_transport = False
5894
5895[interfaces]
5896 [[Test TCP Server]]
5897 type = TCPServerInterface
5898 listen_ip = 127.0.0.1
5899 listen_port = {}
5900"#,
5901 port
5902 );
5903
5904 fs::write(dir.join("config"), config).unwrap();
5905
5906 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
5907
5908 thread::sleep(Duration::from_millis(100));
5910
5911 let _client = std::net::TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
5913
5914 node.shutdown();
5915 let _ = fs::remove_dir_all(&dir);
5916 }
5917
5918 #[test]
5919 fn from_config_starts_rpc_when_share_instance_enabled() {
5920 let dir = std::env::temp_dir().join(format!("rns-test-rpc-{}", std::process::id()));
5921 let _ = fs::remove_dir_all(&dir);
5922 fs::create_dir_all(&dir).unwrap();
5923
5924 let rpc_port = crate::test_support::port();
5925
5926 let config = format!(
5927 r#"
5928[reticulum]
5929enable_transport = False
5930share_instance = Yes
5931instance_control_port = {}
5932
5933[interfaces]
5934"#,
5935 rpc_port
5936 );
5937
5938 fs::write(dir.join("config"), config).unwrap();
5939
5940 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
5941
5942 thread::sleep(Duration::from_millis(100));
5943
5944 let _client = std::net::TcpStream::connect(format!("127.0.0.1:{}", rpc_port)).unwrap();
5945
5946 node.shutdown();
5947 let _ = fs::remove_dir_all(&dir);
5948 }
5949
5950 #[test]
5951 fn from_config_starts_rpc_when_transport_enabled() {
5952 let dir =
5953 std::env::temp_dir().join(format!("rns-test-rpc-transport-{}", std::process::id()));
5954 let _ = fs::remove_dir_all(&dir);
5955 fs::create_dir_all(&dir).unwrap();
5956
5957 let rpc_port = crate::test_support::port();
5958
5959 let config = format!(
5960 r#"
5961[reticulum]
5962enable_transport = True
5963share_instance = Yes
5964instance_control_port = {}
5965
5966[interfaces]
5967"#,
5968 rpc_port
5969 );
5970
5971 fs::write(dir.join("config"), config).unwrap();
5972
5973 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
5974
5975 thread::sleep(Duration::from_millis(100));
5976
5977 let _client = std::net::TcpStream::connect(format!("127.0.0.1:{}", rpc_port)).unwrap();
5978
5979 node.shutdown();
5980 let _ = fs::remove_dir_all(&dir);
5981 }
5982
5983 #[test]
5984 fn from_config_starts_rpc_when_tcp_client_is_unreachable() {
5985 let dir =
5986 std::env::temp_dir().join(format!("rns-test-rpc-unreachable-{}", std::process::id()));
5987 let _ = fs::remove_dir_all(&dir);
5988 fs::create_dir_all(&dir).unwrap();
5989
5990 let rpc_port = crate::test_support::port();
5991 let unreachable_port = crate::test_support::port();
5992
5993 let config = format!(
5994 r#"
5995[reticulum]
5996enable_transport = True
5997share_instance = Yes
5998instance_control_port = {}
5999
6000[interfaces]
6001 [[Unreachable Upstream]]
6002 type = TCPClientInterface
6003 target_host = 127.0.0.1
6004 target_port = {}
6005"#,
6006 rpc_port, unreachable_port
6007 );
6008
6009 fs::write(dir.join("config"), config).unwrap();
6010
6011 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
6012
6013 thread::sleep(Duration::from_millis(100));
6014
6015 let _client = std::net::TcpStream::connect(format!("127.0.0.1:{}", rpc_port)).unwrap();
6016
6017 node.shutdown();
6018 let _ = fs::remove_dir_all(&dir);
6019 }
6020
6021 #[test]
6022 fn test_parse_interface_mode() {
6023 use rns_core::constants::*;
6024
6025 assert_eq!(parse_interface_mode("full"), MODE_FULL);
6026 assert_eq!(parse_interface_mode("Full"), MODE_FULL);
6027 assert_eq!(parse_interface_mode("access_point"), MODE_ACCESS_POINT);
6028 assert_eq!(parse_interface_mode("accesspoint"), MODE_ACCESS_POINT);
6029 assert_eq!(parse_interface_mode("ap"), MODE_ACCESS_POINT);
6030 assert_eq!(parse_interface_mode("AP"), MODE_ACCESS_POINT);
6031 assert_eq!(parse_interface_mode("pointtopoint"), MODE_POINT_TO_POINT);
6032 assert_eq!(parse_interface_mode("ptp"), MODE_POINT_TO_POINT);
6033 assert_eq!(parse_interface_mode("roaming"), MODE_ROAMING);
6034 assert_eq!(parse_interface_mode("boundary"), MODE_BOUNDARY);
6035 assert_eq!(parse_interface_mode("gateway"), MODE_GATEWAY);
6036 assert_eq!(parse_interface_mode("gw"), MODE_GATEWAY);
6037 assert_eq!(parse_interface_mode("internal"), MODE_INTERNAL);
6038 assert_eq!(parse_interface_mode("Internal"), MODE_INTERNAL);
6039 assert_eq!(parse_interface_mode("invalid"), MODE_FULL);
6041 }
6042
6043 #[test]
6044 fn discovery_mode_normalization_matrix() {
6045 use rns_core::constants::*;
6046
6047 let interface_types = [
6048 "RNodeInterface",
6049 "RNodeMultiInterface",
6050 "BackboneInterface",
6051 "TCPServerInterface",
6052 "AutoInterface",
6053 ];
6054 let modes = [
6055 MODE_FULL,
6056 MODE_ACCESS_POINT,
6057 MODE_POINT_TO_POINT,
6058 MODE_ROAMING,
6059 MODE_BOUNDARY,
6060 MODE_GATEWAY,
6061 MODE_INTERNAL,
6062 ];
6063
6064 for interface_type in interface_types {
6065 for configured_mode in modes {
6066 for discoverable in [false, true] {
6067 for ignore_warnings in [false, true] {
6068 let expected = if !discoverable
6069 || ignore_warnings
6070 || matches!(
6071 configured_mode,
6072 MODE_ACCESS_POINT | MODE_GATEWAY | MODE_INTERNAL
6073 ) {
6074 configured_mode
6075 } else if matches!(interface_type, "RNodeInterface" | "RNodeMultiInterface")
6076 {
6077 MODE_ACCESS_POINT
6078 } else {
6079 MODE_GATEWAY
6080 };
6081 assert_eq!(
6082 normalize_discovery_mode(
6083 interface_type,
6084 configured_mode,
6085 discoverable,
6086 ignore_warnings,
6087 ),
6088 expected,
6089 "type={interface_type}, mode={configured_mode}, discoverable={discoverable}, ignore={ignore_warnings}",
6090 );
6091 }
6092 }
6093 }
6094 }
6095 }
6096
6097 #[test]
6098 fn to_node_config_serial() {
6099 let dir = std::env::temp_dir().join(format!("rns-test-serial-{}", std::process::id()));
6103 let _ = fs::remove_dir_all(&dir);
6104 fs::create_dir_all(&dir).unwrap();
6105
6106 let config = r#"
6107[reticulum]
6108enable_transport = False
6109
6110[interfaces]
6111 [[Test Serial Port]]
6112 type = SerialInterface
6113 port = /dev/nonexistent_rns_test_serial
6114 speed = 115200
6115 databits = 8
6116 parity = E
6117 stopbits = 1
6118 interface_mode = ptp
6119 networkname = testnet
6120"#;
6121 fs::write(dir.join("config"), config).unwrap();
6122
6123 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks))
6125 .expect("Config should parse; interface failure is non-fatal");
6126 node.shutdown();
6127
6128 let _ = fs::remove_dir_all(&dir);
6129 }
6130
6131 #[test]
6132 fn to_node_config_kiss() {
6133 let dir = std::env::temp_dir().join(format!("rns-test-kiss-{}", std::process::id()));
6135 let _ = fs::remove_dir_all(&dir);
6136 fs::create_dir_all(&dir).unwrap();
6137
6138 let config = r#"
6139[reticulum]
6140enable_transport = False
6141
6142[interfaces]
6143 [[Test KISS TNC]]
6144 type = KISSInterface
6145 port = /dev/nonexistent_rns_test_kiss
6146 speed = 9600
6147 preamble = 500
6148 txtail = 30
6149 persistence = 128
6150 slottime = 40
6151 flow_control = True
6152 id_interval = 600
6153 id_callsign = TEST0
6154 interface_mode = full
6155 passphrase = secretkey
6156"#;
6157 fs::write(dir.join("config"), config).unwrap();
6158
6159 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks))
6161 .expect("Config should parse; interface failure is non-fatal");
6162 node.shutdown();
6163
6164 let _ = fs::remove_dir_all(&dir);
6165 }
6166
6167 #[test]
6168 fn config_parser_accepts_recursive_prs_interface_option() {
6169 let config = r#"
6170[reticulum]
6171enable_transport = True
6172
6173[interfaces]
6174 [[Recursive Client]]
6175 type = TCPClientInterface
6176 target_host = 127.0.0.1
6177 target_port = 4242
6178 interface_mode = full
6179 recursive_prs = yes
6180
6181 [[Default Client]]
6182 type = TCPClientInterface
6183 target_host = 127.0.0.1
6184 target_port = 4243
6185 interface_mode = gateway
6186"#;
6187
6188 let parsed = config::parse(config).unwrap();
6189
6190 assert_eq!(parsed.interfaces.len(), 2);
6191 assert_eq!(parsed.interfaces[0].name, "Recursive Client");
6192 assert_eq!(parsed.interfaces[0].mode, "full");
6193 assert_eq!(
6194 parsed.interfaces[0]
6195 .params
6196 .get("recursive_prs")
6197 .map(String::as_str),
6198 Some("yes")
6199 );
6200 assert_eq!(parsed.interfaces[1].name, "Default Client");
6201 assert_eq!(parsed.interfaces[1].mode, "gateway");
6202 assert!(!parsed.interfaces[1].params.contains_key("recursive_prs"));
6203 }
6204
6205 #[test]
6206 fn config_parser_accepts_announces_from_internal_interface_option() {
6207 let config = r#"
6208[reticulum]
6209enable_transport = True
6210
6211[interfaces]
6212 [[Filtered Client]]
6213 type = TCPClientInterface
6214 target_host = 127.0.0.1
6215 target_port = 4242
6216 interface_mode = gateway
6217 announces_from_internal = no
6218
6219 [[Default Client]]
6220 type = TCPClientInterface
6221 target_host = 127.0.0.1
6222 target_port = 4243
6223 interface_mode = full
6224"#;
6225
6226 let parsed = config::parse(config).unwrap();
6227
6228 assert_eq!(parsed.interfaces.len(), 2);
6229 assert_eq!(parsed.interfaces[0].name, "Filtered Client");
6230 assert_eq!(parsed.interfaces[0].mode, "gateway");
6231 assert_eq!(
6232 parsed.interfaces[0]
6233 .params
6234 .get("announces_from_internal")
6235 .map(String::as_str),
6236 Some("no")
6237 );
6238 assert_eq!(parsed.interfaces[1].name, "Default Client");
6239 assert_eq!(parsed.interfaces[1].mode, "full");
6240 assert!(!parsed.interfaces[1]
6241 .params
6242 .contains_key("announces_from_internal"));
6243 }
6244
6245 #[test]
6246 fn config_parser_preserves_announces_to_internal_tristate() {
6247 let config = r#"
6248[interfaces]
6249 [[Allowed Boundary]]
6250 type = TCPClientInterface
6251 target_host = 127.0.0.1
6252 target_port = 4242
6253 announces_to_internal = yes
6254
6255 [[Blocked Boundary]]
6256 type = TCPClientInterface
6257 target_host = 127.0.0.1
6258 target_port = 4243
6259 announces_to_internal = no
6260
6261 [[Default Boundary]]
6262 type = TCPClientInterface
6263 target_host = 127.0.0.1
6264 target_port = 4244
6265"#;
6266
6267 let parsed = config::parse(config).unwrap();
6268 let parsed_value = |index: usize| {
6269 parsed.interfaces[index]
6270 .params
6271 .get("announces_to_internal")
6272 .and_then(|value| config::parse_bool_pub(value))
6273 };
6274
6275 assert_eq!(parsed_value(0), Some(true));
6276 assert_eq!(parsed_value(1), Some(false));
6277 assert_eq!(parsed_value(2), None);
6278 }
6279
6280 #[test]
6281 fn interface_gravity_parser_accepts_signed_values_and_uses_global_default() {
6282 assert_eq!(parse_interface_gravity(Some("7"), -2), 7);
6283 assert_eq!(parse_interface_gravity(Some("-4"), 2), -4);
6284 assert_eq!(parse_interface_gravity(None, -6), -6);
6285 }
6286
6287 #[test]
6288 fn config_parser_accepts_local_hops_delta_option() {
6289 let cfg = r#"
6290[reticulum]
6291local_hops_delta = yes
6292"#;
6293 let parsed = config::parse(cfg).unwrap();
6294 assert!(parsed.reticulum.local_hops_delta);
6295 }
6296
6297 #[test]
6298 fn config_parser_accepts_static_transport_identity_option() {
6299 let config = r#"
6300[reticulum]
6301enable_transport = false
6302static_transport_identity = yes
6303"#;
6304
6305 let parsed = config::parse(config).unwrap();
6306
6307 assert!(!parsed.reticulum.enable_transport);
6308 assert!(parsed.reticulum.static_transport_identity);
6309 }
6310
6311 #[test]
6312 fn non_transport_node_uses_ephemeral_transport_identity_by_default() {
6313 let identity = Identity::new(&mut rns_crypto::FixedRng::new(&[0x41; 32]));
6314 let stored_hash = *identity.hash();
6315 let node = RnsNode::start(
6316 NodeConfig {
6317 transport_enabled: false,
6318 static_transport_identity: false,
6319 local_hops_delta: false,
6320 identity: Some(identity),
6321 share_instance: false,
6322 ..Default::default()
6323 },
6324 Box::new(NoopCallbacks),
6325 )
6326 .unwrap();
6327
6328 let response = node.query(QueryRequest::TransportIdentity).unwrap();
6329 let QueryResponse::TransportIdentity(Some(hash)) = response else {
6330 panic!("transport identity query returned {response:?}");
6331 };
6332 assert_ne!(hash, stored_hash);
6333
6334 node.shutdown();
6335 }
6336
6337 #[test]
6338 fn non_transport_node_can_use_static_transport_identity() {
6339 let identity = Identity::new(&mut rns_crypto::FixedRng::new(&[0x42; 32]));
6340 let stored_hash = *identity.hash();
6341 let node = RnsNode::start(
6342 NodeConfig {
6343 transport_enabled: false,
6344 static_transport_identity: true,
6345 local_hops_delta: false,
6346 identity: Some(identity),
6347 share_instance: false,
6348 ..Default::default()
6349 },
6350 Box::new(NoopCallbacks),
6351 )
6352 .unwrap();
6353
6354 let response = node.query(QueryRequest::TransportIdentity).unwrap();
6355 let QueryResponse::TransportIdentity(Some(hash)) = response else {
6356 panic!("transport identity query returned {response:?}");
6357 };
6358 assert_eq!(hash, stored_hash);
6359
6360 node.shutdown();
6361 }
6362
6363 #[test]
6364 fn discoverable_interface_forces_static_transport_identity_on_non_transport() {
6365 assert!(!effective_static_transport_identity(false, false, false));
6366 assert!(effective_static_transport_identity(true, false, false));
6367 assert!(!effective_static_transport_identity(false, true, false));
6368 assert!(effective_static_transport_identity(false, false, true));
6369 assert!(effective_static_transport_identity(true, false, true));
6370 }
6371
6372 #[test]
6373 fn test_extract_ifac_config() {
6374 use std::collections::HashMap;
6375
6376 let params: HashMap<String, String> = HashMap::new();
6378 assert!(extract_ifac_config(¶ms, 16).is_none());
6379
6380 let mut params = HashMap::new();
6382 params.insert("networkname".into(), "testnet".into());
6383 let ifac = extract_ifac_config(¶ms, 16).unwrap();
6384 assert_eq!(ifac.netname.as_deref(), Some("testnet"));
6385 assert!(ifac.netkey.is_none());
6386 assert_eq!(ifac.size, 16);
6387
6388 let mut params = HashMap::new();
6390 params.insert("passphrase".into(), "secret".into());
6391 params.insert("ifac_size".into(), "64".into()); let ifac = extract_ifac_config(¶ms, 16).unwrap();
6393 assert!(ifac.netname.is_none());
6394 assert_eq!(ifac.netkey.as_deref(), Some("secret"));
6395 assert_eq!(ifac.size, 8);
6396
6397 let mut params = HashMap::new();
6399 params.insert("network_name".into(), "mynet".into());
6400 params.insert("pass_phrase".into(), "mykey".into());
6401 let ifac = extract_ifac_config(¶ms, 8).unwrap();
6402 assert_eq!(ifac.netname.as_deref(), Some("mynet"));
6403 assert_eq!(ifac.netkey.as_deref(), Some("mykey"));
6404 assert_eq!(ifac.size, 8);
6405
6406 let mut params = HashMap::new();
6408 params.insert("networkname".into(), "None".into());
6409 params.insert("passphrase".into(), "None".into());
6410 assert!(extract_ifac_config(¶ms, 16).is_none());
6411
6412 let mut params = HashMap::new();
6414 params.insert("networkname".into(), String::new());
6415 params.insert("passphrase".into(), "secret".into());
6416 let ifac = extract_ifac_config(¶ms, 16).unwrap();
6417 assert!(ifac.netname.is_none());
6418 assert_eq!(ifac.netkey.as_deref(), Some("secret"));
6419 }
6420
6421 #[test]
6422 fn to_node_config_rnode() {
6423 let dir = std::env::temp_dir().join(format!("rns-test-rnode-{}", std::process::id()));
6426 let _ = fs::remove_dir_all(&dir);
6427 fs::create_dir_all(&dir).unwrap();
6428
6429 let config = r#"
6430[reticulum]
6431enable_transport = False
6432
6433[interfaces]
6434 [[Test RNode]]
6435 type = RNodeInterface
6436 port = /dev/nonexistent_rns_test_rnode
6437 frequency = 867200000
6438 bandwidth = 125000
6439 txpower = 7
6440 spreadingfactor = 8
6441 codingrate = 5
6442 flow_control = True
6443 st_alock = 5.0
6444 lt_alock = 2.5
6445 interface_mode = full
6446 networkname = testnet
6447"#;
6448 fs::write(dir.join("config"), config).unwrap();
6449
6450 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks))
6452 .expect("Config should parse; interface failure is non-fatal");
6453 node.shutdown();
6454
6455 let _ = fs::remove_dir_all(&dir);
6456 }
6457
6458 #[test]
6459 fn to_node_config_pipe() {
6460 let dir = std::env::temp_dir().join(format!("rns-test-pipe-{}", std::process::id()));
6463 let _ = fs::remove_dir_all(&dir);
6464 fs::create_dir_all(&dir).unwrap();
6465
6466 let config = r#"
6467[reticulum]
6468enable_transport = False
6469
6470[interfaces]
6471 [[Test Pipe]]
6472 type = PipeInterface
6473 command = cat
6474 respawn_delay = 5000
6475 interface_mode = full
6476"#;
6477 fs::write(dir.join("config"), config).unwrap();
6478
6479 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
6480 node.shutdown();
6482
6483 let _ = fs::remove_dir_all(&dir);
6484 }
6485
6486 #[test]
6487 fn to_node_config_backbone() {
6488 let dir = std::env::temp_dir().join(format!("rns-test-backbone-{}", std::process::id()));
6490 let _ = fs::remove_dir_all(&dir);
6491 fs::create_dir_all(&dir).unwrap();
6492
6493 let port = crate::test_support::port();
6494
6495 let config = format!(
6496 r#"
6497[reticulum]
6498enable_transport = False
6499
6500[interfaces]
6501 [[Test Backbone]]
6502 type = BackboneInterface
6503 listen_ip = 127.0.0.1
6504 listen_port = {}
6505 interface_mode = full
6506"#,
6507 port
6508 );
6509
6510 fs::write(dir.join("config"), config).unwrap();
6511
6512 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
6513
6514 thread::sleep(Duration::from_millis(100));
6516
6517 {
6519 let _client = std::net::TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
6520 }
6522
6523 thread::sleep(Duration::from_millis(50));
6525
6526 node.shutdown();
6527 let _ = fs::remove_dir_all(&dir);
6528 }
6529
6530 #[test]
6531 fn rnode_config_defaults() {
6532 use crate::interface::rnode::{RNodeConfig, RNodeSubConfig};
6533
6534 let config = RNodeConfig::default();
6535 assert_eq!(config.speed, 115200);
6536 assert!(config.subinterfaces.is_empty());
6537 assert!(config.id_interval.is_none());
6538 assert!(config.id_callsign.is_none());
6539
6540 let sub = RNodeSubConfig {
6541 name: "test".into(),
6542 vport: 0,
6543 outgoing: true,
6544 frequency: 868_000_000,
6545 bandwidth: 125_000,
6546 txpower: 7,
6547 spreading_factor: 8,
6548 coding_rate: 5,
6549 flow_control: false,
6550 st_alock: None,
6551 lt_alock: None,
6552 };
6553 assert_eq!(sub.frequency, 868_000_000);
6554 assert_eq!(sub.bandwidth, 125_000);
6555 assert!(!sub.flow_control);
6556 }
6557
6558 #[test]
6563 fn announce_without_persisted_stamp_is_deferred_to_the_stamper() {
6564 let dir = tempdir().unwrap();
6565 let store = crate::stamps::DestinationStampStore::new(dir.path());
6566 let targets = crate::stamper::StampTargets {
6567 destination_value: StampCost::from_whole(6),
6568 path_request_value: StampCost::from_whole(6),
6569 };
6570 let mut node = RnsNode::start(NodeConfig::default(), Box::new(NoopCallbacks)).unwrap();
6571 node.set_stamp_store(store.clone());
6572 node.set_destination_stamping(true, targets);
6573 let stamper = Arc::clone(node.stamper().expect("started nodes share a stamper"));
6574 assert_eq!(
6575 stamper.targets(),
6576 targets,
6577 "the stamper grinds at the node's targets"
6578 );
6579
6580 let identity = Identity::from_private_key(&[0x51; 64]);
6581 let dest = crate::Destination::single_in(
6582 "stamper",
6583 &["node"],
6584 rns_core::types::IdentityHash(*identity.hash()),
6585 );
6586 node.register_destination(dest.hash.0, dest.dest_type.to_wire_constant())
6587 .unwrap();
6588 assert!(node.announce_needs_stamp(&dest, &identity));
6589
6590 let started = std::time::Instant::now();
6593 node.announce_queued(&dest, &identity, Some(b"first"))
6594 .unwrap();
6595 assert!(started.elapsed() < Duration::from_secs(1));
6596
6597 let name_hash = rns_core::destination::name_hash("stamper", &["node"]);
6598 let material = crate::stamps::destination_stamp_material(
6599 &dest.hash.0,
6600 &identity.get_public_key().unwrap(),
6601 &name_hash,
6602 );
6603 let deadline = std::time::Instant::now() + Duration::from_secs(30);
6604 while store
6605 .load(&dest.hash.0, &material, targets.destination_value)
6606 .is_none()
6607 {
6608 assert!(
6609 std::time::Instant::now() < deadline,
6610 "stamp was never persisted"
6611 );
6612 thread::sleep(Duration::from_millis(20));
6613 }
6614
6615 assert!(!node.announce_needs_stamp(&dest, &identity));
6617 node.announce_queued(&dest, &identity, Some(b"second"))
6618 .unwrap();
6619 assert_eq!(stamper.queued(), 0);
6620 let receipt = node
6621 .try_announce(&dest, &identity, None)
6622 .expect("synchronous stamped announce is admitted");
6623 let _ = futures::executor::block_on(receipt);
6625
6626 let other_identity = Identity::from_private_key(&[0x52; 64]);
6629 let other = crate::Destination::single_in(
6630 "stamper",
6631 &["node", "other"],
6632 rns_core::types::IdentityHash(*other_identity.hash()),
6633 );
6634 node.register_destination(other.hash.0, other.dest_type.to_wire_constant())
6635 .unwrap();
6636 let started = std::time::Instant::now();
6637 let receipt = node
6638 .try_announce(&other, &other_identity, Some(b"deferred"))
6639 .expect("deferred announce is admitted");
6640 assert!(started.elapsed() < Duration::from_secs(1));
6641 let _ = futures::executor::block_on(receipt);
6642 assert!(!node.announce_needs_stamp(&other, &other_identity));
6643
6644 node.shutdown();
6645 }
6646
6647 #[test]
6648 fn announce_builds_valid_packet() {
6649 let identity = Identity::new(&mut OsRng);
6650 let identity_hash = rns_core::types::IdentityHash(*identity.hash());
6651
6652 let node = RnsNode::start(
6653 NodeConfig {
6654 panic_on_interface_error: false,
6655 link_proof_verify_threads: Default::default(),
6656 stamps: Default::default(),
6657 transport_enabled: false,
6658 static_transport_identity: false,
6659 local_hops_delta: false,
6660 identity: None,
6661 interfaces: vec![],
6662 share_instance: false,
6663 instance_name: "default".into(),
6664 shared_instance_port: 37428,
6665 rpc_port: 0,
6666 cache_dir: None,
6667 ratchet_store: None,
6668 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
6669 management: Default::default(),
6670 probe_port: None,
6671 probe_addrs: vec![],
6672 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
6673 direct_connect_policy: Default::default(),
6674 direct_connect_allow_private_endpoints: false,
6675 device: None,
6676 underlay_mark: None,
6677 hooks: Vec::new(),
6678 discover_interfaces: false,
6679 autoconnect_interface_mode: None,
6680 autoconnect_interface_gravity: 0,
6681 autoconnect_announces_to_internal: false,
6682 autoconnect_unverified_implementations: false,
6683 discovery_required_value: None,
6684 respond_to_probes: false,
6685 prefer_shorter_path: false,
6686 max_paths_per_destination: 1,
6687 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
6688 packet_hashlist_allocation:
6689 rns_core::transport::types::PacketHashlistAllocation::Eager,
6690 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
6691 max_path_destinations: usize::MAX,
6692 max_tunnel_destinations_total: usize::MAX,
6693 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
6694 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
6695 announce_table_ttl: Duration::from_secs(
6696 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
6697 ),
6698 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
6699 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
6700 interface_writer_queue_capacity:
6701 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
6702 announce_rate_defaults: AnnounceRateDefaults::default(),
6703 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
6704 ),
6705 #[cfg(feature = "iface-backbone")]
6706 backbone_peer_pool: None,
6707 announce_sig_cache_enabled: true,
6708 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
6709 announce_sig_cache_ttl: Duration::from_secs(
6710 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
6711 ),
6712 registry: None,
6713 #[cfg(feature = "hooks")]
6714 provider_bridge: None,
6715 },
6716 Box::new(NoopCallbacks),
6717 )
6718 .unwrap();
6719
6720 let dest = crate::destination::Destination::single_in("test", &["echo"], identity_hash);
6721
6722 node.register_destination(dest.hash.0, dest.dest_type.to_wire_constant())
6724 .unwrap();
6725
6726 let result = node.announce_queued(&dest, &identity, Some(b"hello"));
6728 assert!(result.is_ok());
6729
6730 node.shutdown();
6731 }
6732
6733 #[test]
6734 fn has_path_and_hops_to() {
6735 let node = RnsNode::start(
6736 NodeConfig {
6737 panic_on_interface_error: false,
6738 link_proof_verify_threads: Default::default(),
6739 stamps: Default::default(),
6740 transport_enabled: false,
6741 static_transport_identity: false,
6742 local_hops_delta: false,
6743 identity: None,
6744 interfaces: vec![],
6745 share_instance: false,
6746 instance_name: "default".into(),
6747 shared_instance_port: 37428,
6748 rpc_port: 0,
6749 cache_dir: None,
6750 ratchet_store: None,
6751 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
6752 management: Default::default(),
6753 probe_port: None,
6754 probe_addrs: vec![],
6755 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
6756 direct_connect_policy: Default::default(),
6757 direct_connect_allow_private_endpoints: false,
6758 device: None,
6759 underlay_mark: None,
6760 hooks: Vec::new(),
6761 discover_interfaces: false,
6762 autoconnect_interface_mode: None,
6763 autoconnect_interface_gravity: 0,
6764 autoconnect_announces_to_internal: false,
6765 autoconnect_unverified_implementations: false,
6766 discovery_required_value: None,
6767 respond_to_probes: false,
6768 prefer_shorter_path: false,
6769 max_paths_per_destination: 1,
6770 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
6771 packet_hashlist_allocation:
6772 rns_core::transport::types::PacketHashlistAllocation::Eager,
6773 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
6774 max_path_destinations: usize::MAX,
6775 max_tunnel_destinations_total: usize::MAX,
6776 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
6777 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
6778 announce_table_ttl: Duration::from_secs(
6779 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
6780 ),
6781 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
6782 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
6783 interface_writer_queue_capacity:
6784 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
6785 announce_rate_defaults: AnnounceRateDefaults::default(),
6786 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
6787 ),
6788 #[cfg(feature = "iface-backbone")]
6789 backbone_peer_pool: None,
6790 announce_sig_cache_enabled: true,
6791 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
6792 announce_sig_cache_ttl: Duration::from_secs(
6793 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
6794 ),
6795 registry: None,
6796 #[cfg(feature = "hooks")]
6797 provider_bridge: None,
6798 },
6799 Box::new(NoopCallbacks),
6800 )
6801 .unwrap();
6802
6803 let dh = rns_core::types::DestHash([0xAA; 16]);
6804
6805 assert!(!node.has_path(&dh).unwrap());
6807 assert_eq!(node.hops_to(&dh).unwrap(), None);
6808
6809 node.shutdown();
6810 }
6811
6812 #[test]
6813 fn recall_identity_none_when_unknown() {
6814 let node = RnsNode::start(
6815 NodeConfig {
6816 panic_on_interface_error: false,
6817 link_proof_verify_threads: Default::default(),
6818 stamps: Default::default(),
6819 transport_enabled: false,
6820 static_transport_identity: false,
6821 local_hops_delta: false,
6822 identity: None,
6823 interfaces: vec![],
6824 share_instance: false,
6825 instance_name: "default".into(),
6826 shared_instance_port: 37428,
6827 rpc_port: 0,
6828 cache_dir: None,
6829 ratchet_store: None,
6830 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
6831 management: Default::default(),
6832 probe_port: None,
6833 probe_addrs: vec![],
6834 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
6835 direct_connect_policy: Default::default(),
6836 direct_connect_allow_private_endpoints: false,
6837 device: None,
6838 underlay_mark: None,
6839 hooks: Vec::new(),
6840 discover_interfaces: false,
6841 autoconnect_interface_mode: None,
6842 autoconnect_interface_gravity: 0,
6843 autoconnect_announces_to_internal: false,
6844 autoconnect_unverified_implementations: false,
6845 discovery_required_value: None,
6846 respond_to_probes: false,
6847 prefer_shorter_path: false,
6848 max_paths_per_destination: 1,
6849 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
6850 packet_hashlist_allocation:
6851 rns_core::transport::types::PacketHashlistAllocation::Eager,
6852 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
6853 max_path_destinations: usize::MAX,
6854 max_tunnel_destinations_total: usize::MAX,
6855 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
6856 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
6857 announce_table_ttl: Duration::from_secs(
6858 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
6859 ),
6860 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
6861 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
6862 interface_writer_queue_capacity:
6863 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
6864 announce_rate_defaults: AnnounceRateDefaults::default(),
6865 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
6866 ),
6867 #[cfg(feature = "iface-backbone")]
6868 backbone_peer_pool: None,
6869 announce_sig_cache_enabled: true,
6870 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
6871 announce_sig_cache_ttl: Duration::from_secs(
6872 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
6873 ),
6874 registry: None,
6875 #[cfg(feature = "hooks")]
6876 provider_bridge: None,
6877 },
6878 Box::new(NoopCallbacks),
6879 )
6880 .unwrap();
6881
6882 let dh = rns_core::types::DestHash([0xBB; 16]);
6883 assert!(node.recall_identity(&dh).unwrap().is_none());
6884
6885 node.shutdown();
6886 }
6887
6888 #[test]
6889 fn request_path_does_not_crash() {
6890 let node = RnsNode::start(
6891 NodeConfig {
6892 panic_on_interface_error: false,
6893 link_proof_verify_threads: Default::default(),
6894 stamps: Default::default(),
6895 transport_enabled: false,
6896 static_transport_identity: false,
6897 local_hops_delta: false,
6898 identity: None,
6899 interfaces: vec![],
6900 share_instance: false,
6901 instance_name: "default".into(),
6902 shared_instance_port: 37428,
6903 rpc_port: 0,
6904 cache_dir: None,
6905 ratchet_store: None,
6906 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
6907 management: Default::default(),
6908 probe_port: None,
6909 probe_addrs: vec![],
6910 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
6911 direct_connect_policy: Default::default(),
6912 direct_connect_allow_private_endpoints: false,
6913 device: None,
6914 underlay_mark: None,
6915 hooks: Vec::new(),
6916 discover_interfaces: false,
6917 autoconnect_interface_mode: None,
6918 autoconnect_interface_gravity: 0,
6919 autoconnect_announces_to_internal: false,
6920 autoconnect_unverified_implementations: false,
6921 discovery_required_value: None,
6922 respond_to_probes: false,
6923 prefer_shorter_path: false,
6924 max_paths_per_destination: 1,
6925 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
6926 packet_hashlist_allocation:
6927 rns_core::transport::types::PacketHashlistAllocation::Eager,
6928 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
6929 max_path_destinations: usize::MAX,
6930 max_tunnel_destinations_total: usize::MAX,
6931 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
6932 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
6933 announce_table_ttl: Duration::from_secs(
6934 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
6935 ),
6936 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
6937 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
6938 interface_writer_queue_capacity:
6939 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
6940 announce_rate_defaults: AnnounceRateDefaults::default(),
6941 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
6942 ),
6943 #[cfg(feature = "iface-backbone")]
6944 backbone_peer_pool: None,
6945 announce_sig_cache_enabled: true,
6946 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
6947 announce_sig_cache_ttl: Duration::from_secs(
6948 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
6949 ),
6950 registry: None,
6951 #[cfg(feature = "hooks")]
6952 provider_bridge: None,
6953 },
6954 Box::new(NoopCallbacks),
6955 )
6956 .unwrap();
6957
6958 let dh = rns_core::types::DestHash([0xCC; 16]);
6959 assert!(node.request_path(&dh).is_ok());
6960
6961 thread::sleep(Duration::from_millis(50));
6963
6964 node.shutdown();
6965 }
6966
6967 #[test]
6968 fn create_link_returns_error_while_draining() {
6969 let node = RnsNode::start(NodeConfig::default(), Box::new(NoopCallbacks)).unwrap();
6970
6971 node.begin_drain(Duration::from_secs(1)).unwrap();
6972 assert!(node.create_link([0xAB; 16], [0xCD; 32]).is_err());
6973
6974 node.shutdown();
6975 }
6976
6977 #[test]
6978 fn request_path_returns_error_while_draining() {
6979 let node = RnsNode::start(NodeConfig::default(), Box::new(NoopCallbacks)).unwrap();
6980
6981 node.begin_drain(Duration::from_secs(1)).unwrap();
6982 assert!(node
6983 .request_path(&rns_core::types::DestHash([0xAB; 16]))
6984 .is_err());
6985
6986 node.shutdown();
6987 }
6988
6989 #[test]
6994 fn send_packet_returns_error_while_draining() {
6995 let node = RnsNode::start(NodeConfig::default(), Box::new(NoopCallbacks)).unwrap();
6996 let dest = crate::destination::Destination::plain("drain-test", &["send"]);
6997
6998 node.begin_drain(Duration::from_secs(1)).unwrap();
6999 assert!(node.send_packet_queued(&dest, b"hello").is_err());
7000
7001 node.shutdown();
7002 }
7003
7004 #[test]
7005 fn send_packet_plain() {
7006 let node = RnsNode::start(
7007 NodeConfig {
7008 panic_on_interface_error: false,
7009 link_proof_verify_threads: Default::default(),
7010 stamps: Default::default(),
7011 transport_enabled: false,
7012 static_transport_identity: false,
7013 local_hops_delta: false,
7014 identity: None,
7015 interfaces: vec![],
7016 share_instance: false,
7017 instance_name: "default".into(),
7018 shared_instance_port: 37428,
7019 rpc_port: 0,
7020 cache_dir: None,
7021 ratchet_store: None,
7022 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
7023 management: Default::default(),
7024 probe_port: None,
7025 probe_addrs: vec![],
7026 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
7027 direct_connect_policy: Default::default(),
7028 direct_connect_allow_private_endpoints: false,
7029 device: None,
7030 underlay_mark: None,
7031 hooks: Vec::new(),
7032 discover_interfaces: false,
7033 autoconnect_interface_mode: None,
7034 autoconnect_interface_gravity: 0,
7035 autoconnect_announces_to_internal: false,
7036 autoconnect_unverified_implementations: false,
7037 discovery_required_value: None,
7038 respond_to_probes: false,
7039 prefer_shorter_path: false,
7040 max_paths_per_destination: 1,
7041 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
7042 packet_hashlist_allocation:
7043 rns_core::transport::types::PacketHashlistAllocation::Eager,
7044 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
7045 max_path_destinations: usize::MAX,
7046 max_tunnel_destinations_total: usize::MAX,
7047 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
7048 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
7049 announce_table_ttl: Duration::from_secs(
7050 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
7051 ),
7052 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
7053 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
7054 interface_writer_queue_capacity:
7055 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
7056 announce_rate_defaults: AnnounceRateDefaults::default(),
7057 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
7058 ),
7059 #[cfg(feature = "iface-backbone")]
7060 backbone_peer_pool: None,
7061 announce_sig_cache_enabled: true,
7062 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
7063 announce_sig_cache_ttl: Duration::from_secs(
7064 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
7065 ),
7066 registry: None,
7067 #[cfg(feature = "hooks")]
7068 provider_bridge: None,
7069 },
7070 Box::new(NoopCallbacks),
7071 )
7072 .unwrap();
7073
7074 let dest = crate::destination::Destination::plain("test", &["echo"]);
7075 let result = node.send_packet_queued(&dest, b"hello world");
7076 assert!(result.is_ok());
7077
7078 let packet_hash = result.unwrap();
7079 assert_ne!(packet_hash.0, [0u8; 32]);
7081
7082 thread::sleep(Duration::from_millis(50));
7084
7085 node.shutdown();
7086 }
7087
7088 #[test]
7089 fn send_packet_single_requires_public_key() {
7090 let node = RnsNode::start(
7091 NodeConfig {
7092 panic_on_interface_error: false,
7093 link_proof_verify_threads: Default::default(),
7094 stamps: Default::default(),
7095 transport_enabled: false,
7096 static_transport_identity: false,
7097 local_hops_delta: false,
7098 identity: None,
7099 interfaces: vec![],
7100 share_instance: false,
7101 instance_name: "default".into(),
7102 shared_instance_port: 37428,
7103 rpc_port: 0,
7104 cache_dir: None,
7105 ratchet_store: None,
7106 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
7107 management: Default::default(),
7108 probe_port: None,
7109 probe_addrs: vec![],
7110 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
7111 direct_connect_policy: Default::default(),
7112 direct_connect_allow_private_endpoints: false,
7113 device: None,
7114 underlay_mark: None,
7115 hooks: Vec::new(),
7116 discover_interfaces: false,
7117 autoconnect_interface_mode: None,
7118 autoconnect_interface_gravity: 0,
7119 autoconnect_announces_to_internal: false,
7120 autoconnect_unverified_implementations: false,
7121 discovery_required_value: None,
7122 respond_to_probes: false,
7123 prefer_shorter_path: false,
7124 max_paths_per_destination: 1,
7125 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
7126 packet_hashlist_allocation:
7127 rns_core::transport::types::PacketHashlistAllocation::Eager,
7128 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
7129 max_path_destinations: usize::MAX,
7130 max_tunnel_destinations_total: usize::MAX,
7131 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
7132 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
7133 announce_table_ttl: Duration::from_secs(
7134 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
7135 ),
7136 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
7137 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
7138 interface_writer_queue_capacity:
7139 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
7140 announce_rate_defaults: AnnounceRateDefaults::default(),
7141 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
7142 ),
7143 #[cfg(feature = "iface-backbone")]
7144 backbone_peer_pool: None,
7145 announce_sig_cache_enabled: true,
7146 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
7147 announce_sig_cache_ttl: Duration::from_secs(
7148 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
7149 ),
7150 registry: None,
7151 #[cfg(feature = "hooks")]
7152 provider_bridge: None,
7153 },
7154 Box::new(NoopCallbacks),
7155 )
7156 .unwrap();
7157
7158 let dest = crate::destination::Destination::single_in(
7160 "test",
7161 &["echo"],
7162 rns_core::types::IdentityHash([0x42; 16]),
7163 );
7164 let result = node.send_packet_queued(&dest, b"hello");
7165 assert!(result.is_err(), "single_in has no public_key, should fail");
7166
7167 node.shutdown();
7168 }
7169
7170 #[test]
7171 fn send_packet_single_encrypts() {
7172 let node = RnsNode::start(
7173 NodeConfig {
7174 panic_on_interface_error: false,
7175 link_proof_verify_threads: Default::default(),
7176 stamps: Default::default(),
7177 transport_enabled: false,
7178 static_transport_identity: false,
7179 local_hops_delta: false,
7180 identity: None,
7181 interfaces: vec![],
7182 share_instance: false,
7183 instance_name: "default".into(),
7184 shared_instance_port: 37428,
7185 rpc_port: 0,
7186 cache_dir: None,
7187 ratchet_store: None,
7188 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
7189 management: Default::default(),
7190 probe_port: None,
7191 probe_addrs: vec![],
7192 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
7193 direct_connect_policy: Default::default(),
7194 direct_connect_allow_private_endpoints: false,
7195 device: None,
7196 underlay_mark: None,
7197 hooks: Vec::new(),
7198 discover_interfaces: false,
7199 autoconnect_interface_mode: None,
7200 autoconnect_interface_gravity: 0,
7201 autoconnect_announces_to_internal: false,
7202 autoconnect_unverified_implementations: false,
7203 discovery_required_value: None,
7204 respond_to_probes: false,
7205 prefer_shorter_path: false,
7206 max_paths_per_destination: 1,
7207 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
7208 packet_hashlist_allocation:
7209 rns_core::transport::types::PacketHashlistAllocation::Eager,
7210 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
7211 max_path_destinations: usize::MAX,
7212 max_tunnel_destinations_total: usize::MAX,
7213 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
7214 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
7215 announce_table_ttl: Duration::from_secs(
7216 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
7217 ),
7218 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
7219 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
7220 interface_writer_queue_capacity:
7221 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
7222 announce_rate_defaults: AnnounceRateDefaults::default(),
7223 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
7224 ),
7225 #[cfg(feature = "iface-backbone")]
7226 backbone_peer_pool: None,
7227 announce_sig_cache_enabled: true,
7228 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
7229 announce_sig_cache_ttl: Duration::from_secs(
7230 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
7231 ),
7232 registry: None,
7233 #[cfg(feature = "hooks")]
7234 provider_bridge: None,
7235 },
7236 Box::new(NoopCallbacks),
7237 )
7238 .unwrap();
7239
7240 let remote_identity = Identity::new(&mut OsRng);
7242 let recalled = crate::destination::AnnouncedIdentity {
7243 dest_hash: rns_core::types::DestHash([0xAA; 16]),
7244 identity_hash: rns_core::types::IdentityHash(*remote_identity.hash()),
7245 public_key: remote_identity.get_public_key().unwrap(),
7246 app_data: None,
7247 hops: 1,
7248 received_at: 0.0,
7249 receiving_interface: rns_core::transport::types::InterfaceId(0),
7250 rssi: Some(-100),
7251 snr: Some(10.5),
7252 };
7253 let dest = crate::destination::Destination::single_out("test", &["echo"], &recalled);
7254
7255 let result = node.send_packet_queued(&dest, b"secret message");
7256 assert!(result.is_ok());
7257
7258 let packet_hash = result.unwrap();
7259 assert_ne!(packet_hash.0, [0u8; 32]);
7260
7261 thread::sleep(Duration::from_millis(50));
7262 node.shutdown();
7263 }
7264
7265 #[test]
7266 fn register_destination_with_proof_prove_all() {
7267 let node = RnsNode::start(
7268 NodeConfig {
7269 panic_on_interface_error: false,
7270 link_proof_verify_threads: Default::default(),
7271 stamps: Default::default(),
7272 transport_enabled: false,
7273 static_transport_identity: false,
7274 local_hops_delta: false,
7275 identity: None,
7276 interfaces: vec![],
7277 share_instance: false,
7278 instance_name: "default".into(),
7279 shared_instance_port: 37428,
7280 rpc_port: 0,
7281 cache_dir: None,
7282 ratchet_store: None,
7283 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
7284 management: Default::default(),
7285 probe_port: None,
7286 probe_addrs: vec![],
7287 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
7288 direct_connect_policy: Default::default(),
7289 direct_connect_allow_private_endpoints: false,
7290 device: None,
7291 underlay_mark: None,
7292 hooks: Vec::new(),
7293 discover_interfaces: false,
7294 autoconnect_interface_mode: None,
7295 autoconnect_interface_gravity: 0,
7296 autoconnect_announces_to_internal: false,
7297 autoconnect_unverified_implementations: false,
7298 discovery_required_value: None,
7299 respond_to_probes: false,
7300 prefer_shorter_path: false,
7301 max_paths_per_destination: 1,
7302 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
7303 packet_hashlist_allocation:
7304 rns_core::transport::types::PacketHashlistAllocation::Eager,
7305 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
7306 max_path_destinations: usize::MAX,
7307 max_tunnel_destinations_total: usize::MAX,
7308 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
7309 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
7310 announce_table_ttl: Duration::from_secs(
7311 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
7312 ),
7313 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
7314 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
7315 interface_writer_queue_capacity:
7316 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
7317 announce_rate_defaults: AnnounceRateDefaults::default(),
7318 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
7319 ),
7320 #[cfg(feature = "iface-backbone")]
7321 backbone_peer_pool: None,
7322 announce_sig_cache_enabled: true,
7323 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
7324 announce_sig_cache_ttl: Duration::from_secs(
7325 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
7326 ),
7327 registry: None,
7328 #[cfg(feature = "hooks")]
7329 provider_bridge: None,
7330 },
7331 Box::new(NoopCallbacks),
7332 )
7333 .unwrap();
7334
7335 let identity = Identity::new(&mut OsRng);
7336 let ih = rns_core::types::IdentityHash(*identity.hash());
7337 let dest = crate::destination::Destination::single_in("echo", &["request"], ih)
7338 .set_proof_strategy(rns_core::types::ProofStrategy::ProveAll);
7339 let prv_key = identity.get_private_key().unwrap();
7340
7341 let result = node.register_destination_with_proof(&dest, Some(prv_key));
7342 assert!(result.is_ok());
7343
7344 thread::sleep(Duration::from_millis(50));
7346
7347 node.shutdown();
7348 }
7349
7350 #[test]
7351 fn register_destination_with_proof_prove_none() {
7352 let node = RnsNode::start(
7353 NodeConfig {
7354 panic_on_interface_error: false,
7355 link_proof_verify_threads: Default::default(),
7356 stamps: Default::default(),
7357 transport_enabled: false,
7358 static_transport_identity: false,
7359 local_hops_delta: false,
7360 identity: None,
7361 interfaces: vec![],
7362 share_instance: false,
7363 instance_name: "default".into(),
7364 shared_instance_port: 37428,
7365 rpc_port: 0,
7366 cache_dir: None,
7367 ratchet_store: None,
7368 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
7369 management: Default::default(),
7370 probe_port: None,
7371 probe_addrs: vec![],
7372 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
7373 direct_connect_policy: Default::default(),
7374 direct_connect_allow_private_endpoints: false,
7375 device: None,
7376 underlay_mark: None,
7377 hooks: Vec::new(),
7378 discover_interfaces: false,
7379 autoconnect_interface_mode: None,
7380 autoconnect_interface_gravity: 0,
7381 autoconnect_announces_to_internal: false,
7382 autoconnect_unverified_implementations: false,
7383 discovery_required_value: None,
7384 respond_to_probes: false,
7385 prefer_shorter_path: false,
7386 max_paths_per_destination: 1,
7387 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
7388 packet_hashlist_allocation:
7389 rns_core::transport::types::PacketHashlistAllocation::Eager,
7390 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
7391 max_path_destinations: usize::MAX,
7392 max_tunnel_destinations_total: usize::MAX,
7393 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
7394 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
7395 announce_table_ttl: Duration::from_secs(
7396 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
7397 ),
7398 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
7399 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
7400 interface_writer_queue_capacity:
7401 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
7402 announce_rate_defaults: AnnounceRateDefaults::default(),
7403 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
7404 ),
7405 #[cfg(feature = "iface-backbone")]
7406 backbone_peer_pool: None,
7407 announce_sig_cache_enabled: true,
7408 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
7409 announce_sig_cache_ttl: Duration::from_secs(
7410 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
7411 ),
7412 registry: None,
7413 #[cfg(feature = "hooks")]
7414 provider_bridge: None,
7415 },
7416 Box::new(NoopCallbacks),
7417 )
7418 .unwrap();
7419
7420 let dest = crate::destination::Destination::plain("test", &["data"])
7422 .set_proof_strategy(rns_core::types::ProofStrategy::ProveNone);
7423
7424 let result = node.register_destination_with_proof(&dest, None);
7425 assert!(result.is_ok());
7426
7427 thread::sleep(Duration::from_millis(50));
7428 node.shutdown();
7429 }
7430
7431 #[test]
7432 fn destination_stamp_below_target_value_is_regenerated() {
7433 let dir = tempdir().unwrap();
7437 let store = crate::stamps::DestinationStampStore::new(dir.path());
7438 let targets = crate::stamper::StampTargets {
7439 destination_value: StampCost::from_whole(4),
7440 path_request_value: StampCost::from_whole(4),
7441 };
7442 let identity = Identity::from_private_key(&[0x52; 64]);
7443 let mut dest = crate::Destination::single_in(
7444 "stamper",
7445 &["target"],
7446 rns_core::types::IdentityHash(*identity.hash()),
7447 );
7448 let name_hash = rns_core::destination::name_hash("stamper", &["target"]);
7449 let material = crate::stamps::destination_stamp_material(
7450 &dest.hash.0,
7451 &identity.get_public_key().unwrap(),
7452 &name_hash,
7453 );
7454
7455 ensure_destination_stamp(&store, targets, &dest, &identity).unwrap();
7456 let (first, first_value) = store
7457 .load(&dest.hash.0, &material, StampCost::ZERO)
7458 .expect("stamp persisted at the default target");
7459 assert!(first_value.meets(StampCost::from_whole(4)));
7460
7461 dest.set_value(f64::from(first_value.whole()));
7463 ensure_destination_stamp(&store, targets, &dest, &identity).unwrap();
7464 assert_eq!(
7465 store
7466 .load(&dest.hash.0, &material, StampCost::ZERO)
7467 .unwrap()
7468 .0,
7469 first
7470 );
7471
7472 let raised = first_value.whole() as u8 + 1;
7475 dest.set_value(f64::from(raised));
7476 ensure_destination_stamp(&store, targets, &dest, &identity).unwrap();
7477 let (second, second_value) = store
7478 .load(&dest.hash.0, &material, StampCost::ZERO)
7479 .unwrap();
7480 assert_ne!(second, first);
7481 assert!(second_value.meets(StampCost::from_whole(u32::from(raised))));
7482
7483 let (tx, _rx) = crate::event::channel();
7486 tx.link_request_costs()
7487 .set_default_cost(StampCost::from_whole(7));
7488 let (packet, _) =
7489 build_announce_packet(&tx, Some(&store), true, targets, &dest, &identity, None)
7490 .unwrap();
7491 let announce = rns_core::announce::AnnounceData::unpack(packet.data(), false).unwrap();
7492 let app_data = announce
7493 .app_data
7494 .expect("stamped announce carries app data");
7495 let ext = &app_data[app_data.len() - rns_core::constants::EXT_PAYLOAD_LENGTH..];
7496 assert_eq!(ext[0], rns_core::constants::EXT_PAYLOAD_MARKER);
7497 assert_eq!(
7498 &ext[rns_core::constants::EXT_HEADER_LENGTH
7499 ..rns_core::constants::EXT_HEADER_LENGTH + rns_core::constants::STAMP_LENGTH],
7500 &second[..]
7501 );
7502 assert_eq!(ext[rns_core::constants::EXT_PAYLOAD_LENGTH - 1], 7);
7503 }
7504
7505 #[test]
7506 fn from_config_applies_the_stamps_section_and_hop_limiter() {
7507 let dir = tempdir().unwrap();
7508 std::fs::write(
7509 dir.path().join("config"),
7510 "[reticulum]\n\
7511 hop_limiter = 12\n\
7512 [stamps]\n\
7513 enforce_stamps = yes\n\
7514 stamp_destinations = yes\n\
7515 stamp_prs = yes\n\
7516 always_stamp_lrs = yes\n\
7517 destination_cost = 18\n\
7518 destination_value = 27\n\
7519 pr_cost = 11\n\
7520 pr_value = 13\n\
7521 lr_cost = 9\n\
7522 pr_stamp_timeout = 3\n",
7523 )
7524 .unwrap();
7525 let node =
7526 RnsNode::from_private_config(dir.path(), dir.path(), None, Box::new(NoopCallbacks))
7527 .unwrap();
7528
7529 assert!(node.stamp_destinations.load(Ordering::Relaxed));
7530 assert_eq!(
7531 node.stamp_targets,
7532 crate::stamper::StampTargets {
7533 destination_value: StampCost::from_whole(27),
7534 path_request_value: StampCost::from_whole(13),
7535 }
7536 );
7537 assert_eq!(node.link_cost(&[0u8; 16]), 9.0);
7539 assert!(node.link_request_stamps_enforced());
7540 let stamper = node.stamper().unwrap();
7541 assert_eq!(stamper.targets(), node.stamp_targets);
7542 assert_eq!(stamper.path_request_timeout(), Duration::from_secs(3));
7543
7544 for key in [
7548 "global.stamp_destinations",
7549 "global.stamp_path_requests",
7550 "global.stamps_enforced",
7551 "global.stamp_link_requests",
7552 "global.always_stamp_link_requests",
7553 ] {
7554 let QueryResponse::RuntimeConfigEntry(Some(entry)) = node
7555 .query(QueryRequest::GetRuntimeConfig { key: key.into() })
7556 .unwrap()
7557 else {
7558 panic!("expected runtime config entry for {key}");
7559 };
7560 assert_eq!(
7561 entry.value,
7562 crate::event::RuntimeConfigValue::Bool(true),
7563 "{key}"
7564 );
7565 assert_eq!(
7566 entry.default,
7567 crate::event::RuntimeConfigValue::Bool(true),
7568 "{key}"
7569 );
7570 }
7571 node.shutdown();
7572 }
7573
7574 #[test]
7575 fn start_applies_the_node_config_stamps() {
7576 let node = RnsNode::start(
7579 NodeConfig {
7580 stamps: config::StampsSection {
7581 stamp_destinations: true,
7582 stamp_prs: true,
7583 stamps_enforced: true,
7584 lr_stamps_enforced: true,
7585 lr_cost: StampCost::from_whole(9),
7586 ..Default::default()
7587 },
7588 ..NodeConfig::default()
7589 },
7590 Box::new(NoopCallbacks),
7591 )
7592 .unwrap();
7593
7594 assert!(node.stamp_destinations.load(Ordering::Relaxed));
7595 assert_eq!(node.link_cost(&[0u8; 16]), 9.0);
7596 assert!(node.link_request_stamps_enforced());
7597 for key in [
7598 "global.stamp_destinations",
7599 "global.stamp_path_requests",
7600 "global.stamps_enforced",
7601 "global.lr_stamps_enforced",
7602 ] {
7603 let QueryResponse::RuntimeConfigEntry(Some(entry)) = node
7604 .query(QueryRequest::GetRuntimeConfig { key: key.into() })
7605 .unwrap()
7606 else {
7607 panic!("expected runtime config entry for {key}");
7608 };
7609 assert_eq!(
7610 entry.value,
7611 crate::event::RuntimeConfigValue::Bool(true),
7612 "{key}"
7613 );
7614 assert_eq!(
7615 entry.default,
7616 crate::event::RuntimeConfigValue::Bool(true),
7617 "{key}"
7618 );
7619 }
7620 node.shutdown();
7621 }
7622
7623 #[test]
7624 fn published_blackhole_destination_requires_its_link_cost() {
7625 let identity = Identity::new(&mut OsRng);
7626 let blackhole = crate::management::blackhole_dest_hash(identity.hash());
7627 let node = RnsNode::start(
7628 NodeConfig {
7629 identity: Some(Identity::from_private_key(
7630 &identity.get_private_key().unwrap(),
7631 )),
7632 management: crate::ManagementConfig {
7633 publish_blackhole: true,
7634 ..Default::default()
7635 },
7636 ..NodeConfig::default()
7637 },
7638 Box::new(NoopCallbacks),
7639 )
7640 .unwrap();
7641
7642 assert_eq!(node.link_cost(&blackhole), 5.25);
7643 assert_eq!(node.link_cost(&[0u8; 16]), 12.5);
7645 node.shutdown();
7646 }
7647}