1use std::io;
14use std::io::Read;
15use std::path::Path;
16use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
17use std::sync::Arc;
18use std::thread;
19use std::time::Duration;
20
21use rns_core::destination::{destination_hash, name_hash};
22use rns_core::packet::RawPacket;
23use rns_core::transport::types::TransportConfig;
24use rns_crypto::identity::Identity;
25
26use crate::driver::{Callbacks, Driver};
27use crate::event;
28use crate::event::{Event, QueryRequest, QueryResponse};
29use crate::interface::local::LocalClientConfig;
30use crate::interface::{InterfaceEntry, InterfaceStats};
31use crate::node::RnsNode;
32use crate::pickle::PickleValue;
33use crate::rpc::{RpcAddr, RpcClient};
34use crate::storage;
35use crate::time;
36
37pub struct SharedClientConfig {
39 pub instance_name: String,
41 pub port: u16,
43 pub rpc_port: u16,
45}
46
47impl Default for SharedClientConfig {
48 fn default() -> Self {
49 SharedClientConfig {
50 instance_name: "default".into(),
51 port: 37428,
52 rpc_port: 37429,
53 }
54 }
55}
56
57pub(crate) struct DestinationTypeRequester {
61 requester: [u8; 16],
64 shared_client: bool,
66 shared_rpc: Option<(RpcAddr, [u8; 32])>,
68 registered: AtomicBool,
71}
72
73impl Default for DestinationTypeRequester {
74 fn default() -> Self {
75 DestinationTypeRequester {
76 requester: *Identity::new(&mut rns_crypto::OsRng).hash(),
77 shared_client: false,
78 shared_rpc: None,
79 registered: AtomicBool::new(false),
80 }
81 }
82}
83
84impl DestinationTypeRequester {
85 fn call_shared_instance(&self, key: &str, value: PickleValue) -> bool {
88 let Some((addr, auth_key)) = &self.shared_rpc else {
89 return false;
90 };
91 let request = PickleValue::Dict(vec![
92 (PickleValue::String(key.into()), value),
93 (
94 PickleValue::String("requester".into()),
95 PickleValue::Bytes(self.requester.to_vec()),
96 ),
97 (PickleValue::String("signature".into()), PickleValue::None),
98 ]);
99 match RpcClient::connect(addr, auth_key).and_then(|mut client| client.call(&request)) {
100 Ok(response) => response.as_bool() == Some(true),
101 Err(e) => {
105 log::debug!(
106 "Shared instance RPC failed while setting destination type filters: {e}"
107 );
108 false
109 }
110 }
111 }
112
113 pub(crate) fn withdraw_from_shared_instance(&self) {
116 if self.registered.swap(false, Ordering::Relaxed) {
117 self.call_shared_instance("unwants_all_destination_types", PickleValue::Bool(true));
118 }
119 }
120}
121
122impl RnsNode {
123 pub fn wants_destination_type(&self, full_name: &str) -> bool {
130 let local = self.destination_type_query(QueryRequest::WantsDestinationType {
131 full_name: full_name.into(),
132 requester: self.destination_types.requester.to_vec(),
133 });
134 if !self.destination_types.shared_client || self.destination_types.shared_rpc.is_none() {
135 return local;
136 }
137 let registered = self.destination_types.call_shared_instance(
138 "wants_destination_type",
139 PickleValue::String(full_name.into()),
140 );
141 if registered {
142 self.destination_types
143 .registered
144 .store(true, Ordering::Relaxed);
145 }
146 registered
147 }
148
149 pub fn unwants_destination_type(&self, full_name: &str) -> bool {
152 let local = self.destination_type_query(QueryRequest::UnwantsDestinationType {
153 full_name: full_name.into(),
154 requester: self.destination_types.requester.to_vec(),
155 });
156 if !self.destination_types.shared_client || self.destination_types.shared_rpc.is_none() {
157 return local;
158 }
159 self.destination_types.call_shared_instance(
160 "unwants_destination_type",
161 PickleValue::String(full_name.into()),
162 )
163 }
164
165 pub fn unwants_all_destination_types(&self) -> bool {
168 let local = self.destination_type_query(QueryRequest::UnwantsAllDestinationTypes {
169 requester: self.destination_types.requester.to_vec(),
170 });
171 if !self.destination_types.shared_client || self.destination_types.shared_rpc.is_none() {
172 return local;
173 }
174 self.destination_types
175 .registered
176 .store(false, Ordering::Relaxed);
177 self.destination_types
178 .call_shared_instance("unwants_all_destination_types", PickleValue::Bool(true))
179 }
180
181 pub fn required_announces_only(&self, enabled: bool) -> bool {
186 if !self.destination_types.shared_client {
187 return false;
188 }
189 self.destination_type_query(QueryRequest::SetRequiredDestinationsOnly { enabled })
190 }
191
192 fn destination_type_query(&self, request: QueryRequest) -> bool {
193 matches!(
194 self.query(request),
195 Ok(QueryResponse::DestinationTypeRegistration(true))
196 )
197 }
198
199 pub fn connect_shared(
205 config: SharedClientConfig,
206 callbacks: Box<dyn Callbacks>,
207 ) -> io::Result<Self> {
208 Self::connect_shared_with_reconnect_wait(config, callbacks, Duration::from_secs(8))
209 }
210
211 fn connect_shared_with_reconnect_wait(
212 config: SharedClientConfig,
213 callbacks: Box<dyn Callbacks>,
214 reconnect_wait: Duration,
215 ) -> io::Result<Self> {
216 let transport_config = TransportConfig {
217 transport_enabled: false,
218 identity_hash: None,
219 local_hops_delta: 0,
220 prefer_shorter_path: false,
221 max_paths_per_destination: 1,
222 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
223 packet_hashlist_allocation: rns_core::transport::types::PacketHashlistAllocation::Lazy,
224 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
225 max_path_destinations: rns_core::transport::types::DEFAULT_MAX_PATH_DESTINATIONS,
226 max_tunnel_destinations_total: usize::MAX,
227 destination_timeout_secs: rns_core::constants::DESTINATION_TIMEOUT,
228 announce_table_ttl_secs: rns_core::constants::ANNOUNCE_TABLE_TTL,
229 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
230 announce_sig_cache_enabled: true,
231 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
232 announce_sig_cache_ttl_secs: rns_core::constants::ANNOUNCE_SIG_CACHE_TTL,
233 announce_queue_max_entries: 256,
234 announce_queue_max_interfaces: 1024,
235 };
236
237 let (tx, rx) = event::channel();
238 let tick_interval_ms = Arc::new(AtomicU64::new(1000));
239 let mut driver = Driver::new(transport_config, rx, tx.clone(), callbacks);
240 driver.connected_to_shared_instance = true;
241 driver.set_tick_interval_handle(Arc::clone(&tick_interval_ms));
242 let accepting_work = driver.accepting_work_handle();
243
244 let local_config = LocalClientConfig {
246 name: "Local shared instance".into(),
247 instance_name: config.instance_name.clone(),
248 port: config.port,
249 interface_id: rns_core::transport::types::InterfaceId(1),
250 reconnect_wait,
251 };
252
253 let id = local_config.interface_id;
254 let info = rns_core::transport::types::InterfaceInfo {
255 stamp_costs: Default::default(),
256 id,
257 name: "LocalInterface".into(),
258 mode: rns_core::constants::MODE_FULL,
259 gravity: 0,
260 recursive_prs: false,
261 announces_from_internal: true,
262 announces_to_internal: None,
263 out_capable: true,
264 in_capable: true,
265 bitrate: Some(crate::interface::local::BITRATE),
266 airtime_profile: None,
267 announce_rate_target: None,
268 announce_rate_grace: 0,
269 announce_rate_penalty: 0.0,
270 announce_cap: rns_core::constants::ANNOUNCE_CAP,
271 is_local_client: true,
272 wants_tunnel: false,
273 tunnel_id: None,
274 mtu: crate::interface::local::HW_MTU,
275 ia_freq: 0.0,
276 ip_freq: 0.0,
277 op_freq: 0.0,
278 op_samples: 0,
279 started: time::now(),
280 ingress_control: rns_core::transport::types::IngressControlConfig::disabled(),
281 };
282
283 let writer = crate::interface::local::start_client(local_config, tx.clone())?;
284 let (writer, async_writer_metrics) = crate::interface::wrap_async_writer(
285 writer,
286 id,
287 &info.name,
288 tx.clone(),
289 driver.interface_writer_queue_capacity,
290 );
291
292 driver.engine.register_interface(info.clone());
293 driver.interfaces.insert(
294 id,
295 InterfaceEntry {
296 id,
297 info,
298 writer,
299 async_writer_metrics: Some(async_writer_metrics),
300 enabled: true,
301 online: false,
302 dynamic: false,
303 ifac: None,
304 stats: InterfaceStats {
305 started: time::now(),
306 ..Default::default()
307 },
308 interface_type: "LocalClientInterface".to_string(),
309 send_retry_at: None,
310 send_retry_backoff: Duration::ZERO,
311 },
312 );
313
314 if !crate::engine::FOLD_TIMERS {
317 let timer_tx = tx.clone();
318 let timer_interval = Arc::clone(&tick_interval_ms);
319 thread::Builder::new()
320 .name("rns-timer-client".into())
321 .spawn(move || loop {
322 let ms = timer_interval.load(Ordering::Relaxed);
323 thread::sleep(Duration::from_millis(ms));
324 if timer_tx.send(event::Event::Tick).is_err() {
325 break;
326 }
327 })?;
328 }
329
330 let driver_handle = thread::Builder::new()
332 .name("rns-driver-client".into())
333 .spawn(move || {
334 driver.run();
335 })?;
336
337 let mut node =
338 RnsNode::from_parts(tx, driver_handle, None, tick_interval_ms, accepting_work);
339 node.destination_types.shared_client = true;
340 Ok(node)
341 }
342
343 pub fn connect_shared_from_config(
347 config_path: Option<&Path>,
348 callbacks: Box<dyn Callbacks>,
349 ) -> io::Result<Self> {
350 let config_dir = storage::resolve_config_dir(config_path);
351
352 let config_file = config_dir.join("config");
354 let rns_config = if config_file.exists() {
355 crate::config::parse_file(&config_file)
356 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("{}", e)))?
357 } else {
358 crate::config::parse("")
359 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("{}", e)))?
360 };
361
362 let shared_config = SharedClientConfig {
363 instance_name: rns_config.reticulum.instance_name.clone(),
364 port: rns_config.reticulum.shared_instance_port,
365 rpc_port: rns_config.reticulum.instance_control_port,
366 };
367 let rpc_port = shared_config.rpc_port;
368
369 let mut node = Self::connect_shared(shared_config, callbacks)?;
370 node.use_shared_instance_rpc(&config_dir, rpc_port);
371 Ok(node)
372 }
373
374 pub(crate) fn use_shared_instance_rpc(&mut self, config_dir: &Path, rpc_port: u16) {
379 match storage::load_identity(&config_dir.join("storage").join("transport_identity")) {
380 Ok(identity) => {
381 let auth_key =
382 crate::rpc::derive_auth_key(&identity.get_private_key().unwrap_or([0u8; 64]));
383 self.destination_types.shared_rpc =
384 Some((RpcAddr::Tcp("127.0.0.1".into(), rpc_port), auth_key));
385 }
386 Err(e) => log::debug!("No shared instance RPC key for destination type filters: {e}"),
387 }
388 }
389}
390
391#[doc(hidden)]
392pub fn bench_shared_client_replay_once(
393 announce_count: usize,
394 reconnect_wait: Duration,
395) -> io::Result<usize> {
396 struct BenchNoopCallbacks;
397 impl Callbacks for BenchNoopCallbacks {
398 fn on_announce(&mut self, _: crate::destination::AnnouncedIdentity) {}
399 fn on_path_updated(&mut self, _: rns_core::types::DestHash, _: u8) {}
400 fn on_local_delivery(
401 &mut self,
402 _: rns_core::types::DestHash,
403 _: Vec<u8>,
404 _: rns_core::types::PacketHash,
405 ) {
406 }
407 }
408
409 fn build_shared_announce_raw(
410 dest_hash: &[u8; 16],
411 name_hash: &[u8; 10],
412 identity_prv_key: &[u8; 64],
413 app_data: Option<&[u8]>,
414 path_response: bool,
415 ) -> Vec<u8> {
416 let identity = Identity::from_private_key(identity_prv_key);
417 let mut random_hash = [0u8; 10];
418 random_hash[..5].copy_from_slice(&[0xA5; 5]);
419 random_hash[5..10].copy_from_slice(&[0, 0, 0, 0, 1]);
420
421 let (announce_data, _) = rns_core::announce::AnnounceData::pack(
422 &identity,
423 dest_hash,
424 name_hash,
425 &random_hash,
426 None,
427 app_data,
428 )
429 .unwrap();
430
431 let flags = rns_core::packet::PacketFlags {
432 header_type: rns_core::constants::HEADER_1,
433 context_flag: rns_core::constants::FLAG_UNSET,
434 transport_type: rns_core::constants::TRANSPORT_BROADCAST,
435 destination_type: rns_core::constants::DESTINATION_SINGLE,
436 packet_type: rns_core::constants::PACKET_TYPE_ANNOUNCE,
437 };
438 let context = if path_response {
439 rns_core::constants::CONTEXT_PATH_RESPONSE
440 } else {
441 rns_core::constants::CONTEXT_NONE
442 };
443
444 rns_core::packet::RawPacket::pack(flags, 0, dest_hash, None, context, &announce_data)
445 .unwrap()
446 .raw
447 }
448
449 fn read_until_frames(
450 stream: &mut std::net::TcpStream,
451 expected: usize,
452 expected_context: u8,
453 ) -> io::Result<Vec<Vec<u8>>> {
454 let mut decoder = crate::interface::local::frame_decoder(0);
455 let mut buf = [0u8; 4096];
456 let mut frames = Vec::new();
457 let deadline = std::time::Instant::now() + Duration::from_secs(2);
458 while frames.len() < expected {
459 let n = match stream.read(&mut buf) {
460 Ok(n) => n,
461 Err(e)
462 if e.kind() == io::ErrorKind::WouldBlock
463 || e.kind() == io::ErrorKind::TimedOut =>
464 {
465 if std::time::Instant::now() >= deadline {
466 return Err(io::Error::new(
467 io::ErrorKind::TimedOut,
468 format!(
469 "timed out waiting for {} frames, got {}",
470 expected,
471 frames.len()
472 ),
473 ));
474 }
475 continue;
476 }
477 Err(e) => return Err(e),
478 };
479 for frame in decoder.feed(&buf[..n]) {
480 let Ok(packet) = RawPacket::unpack(&frame) else {
481 log::debug!("skipping malformed shared-instance packet");
482 continue;
483 };
484 if packet.context == expected_context {
485 frames.push(frame);
486 }
487 }
488 }
489 Ok(frames)
490 }
491
492 let listener1 = std::net::TcpListener::bind("127.0.0.1:0")?;
493 let port = listener1.local_addr()?.port();
494 let instance_name = format!("bench-shared-replay-{port}");
495
496 let (accepted1_tx, accepted1_rx) = std::sync::mpsc::channel();
497 thread::spawn(move || {
498 let (stream, _) = listener1.accept().unwrap();
499 accepted1_tx.send(stream).unwrap();
500 });
501
502 let transport_config = TransportConfig {
503 transport_enabled: false,
504 identity_hash: None,
505 local_hops_delta: 0,
506 prefer_shorter_path: false,
507 max_paths_per_destination: 1,
508 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
509 packet_hashlist_allocation: rns_core::transport::types::PacketHashlistAllocation::Lazy,
510 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
511 max_path_destinations: rns_core::transport::types::DEFAULT_MAX_PATH_DESTINATIONS,
512 max_tunnel_destinations_total: usize::MAX,
513 destination_timeout_secs: rns_core::constants::DESTINATION_TIMEOUT,
514 announce_table_ttl_secs: rns_core::constants::ANNOUNCE_TABLE_TTL,
515 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
516 announce_sig_cache_enabled: true,
517 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
518 announce_sig_cache_ttl_secs: rns_core::constants::ANNOUNCE_SIG_CACHE_TTL,
519 announce_queue_max_entries: 256,
520 announce_queue_max_interfaces: 1024,
521 };
522
523 let (tx, rx) = event::channel();
524 let tick_interval_ms = Arc::new(AtomicU64::new(1000));
525 let mut driver = Driver::new(
526 transport_config,
527 rx,
528 tx.clone(),
529 Box::new(BenchNoopCallbacks),
530 );
531 driver.set_tick_interval_handle(Arc::clone(&tick_interval_ms));
532
533 let local_config = LocalClientConfig {
534 name: "Shared replay bench".into(),
535 instance_name: instance_name.clone(),
536 port,
537 interface_id: rns_core::transport::types::InterfaceId(1),
538 reconnect_wait,
539 };
540
541 let id = local_config.interface_id;
542 let info = rns_core::transport::types::InterfaceInfo {
543 stamp_costs: Default::default(),
544 id,
545 name: "LocalInterface".into(),
546 mode: rns_core::constants::MODE_FULL,
547 gravity: 0,
548 recursive_prs: false,
549 announces_from_internal: true,
550 announces_to_internal: None,
551 out_capable: true,
552 in_capable: true,
553 bitrate: Some(crate::interface::local::BITRATE),
554 airtime_profile: None,
555 announce_rate_target: None,
556 announce_rate_grace: 0,
557 announce_rate_penalty: 0.0,
558 announce_cap: rns_core::constants::ANNOUNCE_CAP,
559 is_local_client: true,
560 wants_tunnel: false,
561 tunnel_id: None,
562 mtu: crate::interface::local::HW_MTU,
563 ia_freq: 0.0,
564 ip_freq: 0.0,
565 op_freq: 0.0,
566 op_samples: 0,
567 started: time::now(),
568 ingress_control: rns_core::transport::types::IngressControlConfig::disabled(),
569 };
570
571 let writer = crate::interface::local::start_client(local_config, tx.clone())?;
572 let (writer, async_writer_metrics) = crate::interface::wrap_async_writer(
573 writer,
574 id,
575 &info.name,
576 tx.clone(),
577 driver.interface_writer_queue_capacity,
578 );
579 driver.engine.register_interface(info.clone());
580 driver.interfaces.insert(
581 id,
582 InterfaceEntry {
583 id,
584 info,
585 writer,
586 async_writer_metrics: Some(async_writer_metrics),
587 enabled: true,
588 online: false,
589 dynamic: false,
590 ifac: None,
591 stats: InterfaceStats {
592 started: time::now(),
593 ..Default::default()
594 },
595 interface_type: "LocalClientInterface".to_string(),
596 send_retry_at: None,
597 send_retry_backoff: Duration::ZERO,
598 },
599 );
600
601 let driver_handle = thread::Builder::new()
602 .name("rns-driver-bench-client".into())
603 .spawn(move || {
604 driver.run();
605 })?;
606
607 let mut stream1 = accepted1_rx
608 .recv_timeout(Duration::from_secs(2))
609 .map_err(|e| {
610 io::Error::new(
611 io::ErrorKind::TimedOut,
612 format!("shared bench initial accept failed: {e}"),
613 )
614 })?;
615 stream1.set_read_timeout(Some(Duration::from_secs(2)))?;
616
617 let mut records = Vec::new();
618 for i in 0..announce_count {
619 let mut prv_key = [0u8; 64];
620 for (j, byte) in prv_key.iter_mut().enumerate() {
621 *byte = (i as u8)
622 .wrapping_mul(23)
623 .wrapping_add(j as u8)
624 .wrapping_add(5);
625 }
626 let identity = Identity::from_private_key(&prv_key);
627 let aspect = format!("echo-{i}");
628 let name_hash = name_hash("shared-bench", &[&aspect]);
629 let dest_hash = destination_hash("shared-bench", &[&aspect], Some(identity.hash()));
630 let app_data = format!("hello-{i}").into_bytes();
631 records.push((dest_hash, name_hash, prv_key, app_data));
632 }
633
634 for (dest_hash, name_hash, prv_key, app_data) in &records {
635 let raw = build_shared_announce_raw(dest_hash, name_hash, prv_key, Some(app_data), false);
636 tx.send(Event::RegisterDestination {
637 dest_hash: *dest_hash,
638 dest_type: rns_core::constants::DESTINATION_SINGLE,
639 })
640 .map_err(|e| io::Error::new(io::ErrorKind::BrokenPipe, format!("{e}")))?;
641 tx.send(Event::StoreSharedAnnounce {
642 dest_hash: *dest_hash,
643 name_hash: *name_hash,
644 identity_prv_key: *prv_key,
645 app_data: Some(app_data.clone()),
646 })
647 .map_err(|e| io::Error::new(io::ErrorKind::BrokenPipe, format!("{e}")))?;
648 tx.send(Event::SendOutbound {
649 raw,
650 dest_type: rns_core::constants::DESTINATION_SINGLE,
651 attached_interface: None,
652 })
653 .map_err(|e| io::Error::new(io::ErrorKind::BrokenPipe, format!("{e}")))?;
654 }
655
656 let _ = read_until_frames(
657 &mut stream1,
658 announce_count,
659 rns_core::constants::CONTEXT_NONE,
660 )
661 .map_err(|e| io::Error::new(e.kind(), format!("initial announce delivery failed: {e}")))?;
662 drop(stream1);
663
664 let listener2 = std::net::TcpListener::bind(format!("127.0.0.1:{port}"))?;
665 let (accepted2_tx, accepted2_rx) = std::sync::mpsc::channel();
666 thread::spawn(move || {
667 let (stream, _) = listener2.accept().unwrap();
668 accepted2_tx.send(stream).unwrap();
669 });
670
671 let mut stream2 = accepted2_rx
672 .recv_timeout(Duration::from_secs(2))
673 .map_err(|e| {
674 io::Error::new(
675 io::ErrorKind::TimedOut,
676 format!("shared bench reconnect accept failed: {e}"),
677 )
678 })?;
679 stream2.set_read_timeout(Some(Duration::from_secs(2)))?;
680 let frames = read_until_frames(
681 &mut stream2,
682 announce_count,
683 rns_core::constants::CONTEXT_PATH_RESPONSE,
684 )
685 .map_err(|e| io::Error::new(e.kind(), format!("replayed announce delivery failed: {e}")))?;
686
687 tx.send(Event::Shutdown)
688 .map_err(|e| io::Error::new(io::ErrorKind::BrokenPipe, format!("{e}")))?;
689 let _ = driver_handle.join();
690
691 Ok(frames.len())
692}
693
694#[cfg(test)]
695mod tests {
696 use super::*;
697 use rns_core::packet::RawPacket;
698 use rns_core::types::IdentityHash;
699 use rns_crypto::identity::Identity;
700 use rns_crypto::OsRng;
701 use std::io::Read;
702 use std::sync::atomic::AtomicU64;
703 use std::sync::mpsc;
704 use std::sync::Arc;
705
706 use crate::interface::local::LocalServerConfig;
707
708 struct NoopCallbacks;
709 impl Callbacks for NoopCallbacks {
710 fn on_announce(&mut self, _: crate::destination::AnnouncedIdentity) {}
711 fn on_path_updated(&mut self, _: rns_core::types::DestHash, _: u8) {}
712 fn on_local_delivery(
713 &mut self,
714 _: rns_core::types::DestHash,
715 _: Vec<u8>,
716 _: rns_core::types::PacketHash,
717 ) {
718 }
719 }
720
721 fn find_free_port() -> u16 {
722 crate::test_support::port()
723 }
724
725 #[test]
726 fn shared_client_registers_destination_types_with_the_shared_instance() {
727 let dir = tempfile::tempdir().unwrap();
730 let rpc_port = find_free_port();
731 let shared_port = find_free_port();
732 std::fs::write(
733 dir.path().join("config"),
734 format!(
735 "[reticulum]\nshare_instance = yes\ninstance_control_port = {rpc_port}\n\
736 shared_instance_port = {shared_port}\n\n[filter]\n\
737 required_only = yes\nignored_types = nomadnetwork.node\n"
738 ),
739 )
740 .unwrap();
741 let daemon = RnsNode::from_config(Some(dir.path()), Box::new(NoopCallbacks)).unwrap();
742 let client =
743 RnsNode::connect_shared_from_config(Some(dir.path()), Box::new(NoopCallbacks)).unwrap();
744
745 assert!(client.wants_destination_type("rncp.receive"));
748 assert!(!client.wants_destination_type("nomadnetwork.node"));
749 assert!(!client.unwants_destination_type("rnx.execute"));
750 assert!(client.unwants_destination_type("rncp.receive"));
751 assert!(client.wants_destination_type("rnx.execute"));
752
753 assert!(client.required_announces_only(true));
754 assert!(!daemon.required_announces_only(true));
755
756 let auth_key = client.destination_types.shared_rpc.as_ref().unwrap().1;
758 let requester = client.destination_types.requester;
759 client.shutdown();
760 let mut rpc =
761 RpcClient::connect(&RpcAddr::Tcp("127.0.0.1".into(), rpc_port), &auth_key).unwrap();
762 let response = rpc
763 .call(&PickleValue::Dict(vec![
764 (
765 PickleValue::String("unwants_destination_type".into()),
766 PickleValue::String("rnx.execute".into()),
767 ),
768 (
769 PickleValue::String("requester".into()),
770 PickleValue::Bytes(requester.to_vec()),
771 ),
772 ]))
773 .unwrap();
774 assert_eq!(response, PickleValue::Bool(false));
775 daemon.shutdown();
776 }
777
778 #[test]
779 fn connect_shared_to_tcp_server() {
780 let port = find_free_port();
781 let next_id = Arc::new(AtomicU64::new(50000));
782 let (server_tx, server_rx) = crate::event::channel();
783
784 let server_config = LocalServerConfig {
786 instance_name: crate::test_support::instance_name("test-shared-connect"),
787 port,
788 interface_id: rns_core::transport::types::InterfaceId(99),
789 };
790
791 crate::interface::local::start_server(server_config, server_tx, next_id).unwrap();
792 thread::sleep(Duration::from_millis(50));
793
794 let config = SharedClientConfig {
796 instance_name: crate::test_support::instance_name("test-shared-connect"),
797 port,
798 rpc_port: 0,
799 };
800
801 let node = RnsNode::connect_shared(config, Box::new(NoopCallbacks)).unwrap();
802
803 let event = server_rx.recv_timeout(Duration::from_secs(2)).unwrap();
805 assert!(matches!(event, crate::event::Event::InterfaceUp(_, _, _)));
806
807 node.shutdown();
808 }
809
810 #[test]
811 fn shared_client_register_destination() {
812 let port = find_free_port();
813 let next_id = Arc::new(AtomicU64::new(51000));
814 let (server_tx, _server_rx) = crate::event::channel();
815
816 let server_config = LocalServerConfig {
817 instance_name: crate::test_support::instance_name("test-shared-reg"),
818 port,
819 interface_id: rns_core::transport::types::InterfaceId(98),
820 };
821
822 crate::interface::local::start_server(server_config, server_tx, next_id).unwrap();
823 thread::sleep(Duration::from_millis(50));
824
825 let config = SharedClientConfig {
826 instance_name: crate::test_support::instance_name("test-shared-reg"),
827 port,
828 rpc_port: 0,
829 };
830
831 let node = RnsNode::connect_shared(config, Box::new(NoopCallbacks)).unwrap();
832
833 let dest_hash = [0xAA; 16];
835 node.register_destination(dest_hash, rns_core::constants::DESTINATION_SINGLE)
836 .unwrap();
837
838 thread::sleep(Duration::from_millis(100));
840
841 node.shutdown();
842 }
843
844 #[test]
845 fn shared_client_send_packet() {
846 let port = find_free_port();
847 let next_id = Arc::new(AtomicU64::new(52000));
848 let (server_tx, server_rx) = crate::event::channel();
849
850 let server_config = LocalServerConfig {
851 instance_name: crate::test_support::instance_name("test-shared-send"),
852 port,
853 interface_id: rns_core::transport::types::InterfaceId(97),
854 };
855
856 crate::interface::local::start_server(server_config, server_tx, next_id).unwrap();
857 thread::sleep(Duration::from_millis(50));
858
859 let config = SharedClientConfig {
860 instance_name: crate::test_support::instance_name("test-shared-send"),
861 port,
862 rpc_port: 0,
863 };
864
865 let node = RnsNode::connect_shared(config, Box::new(NoopCallbacks)).unwrap();
866
867 let raw = vec![0x00, 0x00, 0xAA, 0xBB, 0xCC, 0xDD]; node.send_raw(raw, rns_core::constants::DESTINATION_PLAIN, None)
870 .unwrap();
871
872 for _ in 0..10 {
875 match server_rx.recv_timeout(Duration::from_secs(1)) {
876 Ok(crate::event::Event::Frame { .. }) => {
877 break;
878 }
879 Ok(_) => continue,
880 Err(_) => break,
881 }
882 }
883 node.shutdown();
887 }
888
889 #[test]
890 fn shared_client_replays_single_announces_after_reconnect() {
891 let port = find_free_port();
892 let addr = format!("127.0.0.1:{}", port);
893 let instance_name = format!("test-shared-replay-{}", port);
894
895 let listener1 = std::net::TcpListener::bind(&addr).unwrap();
896 let (accepted1_tx, accepted1_rx) = mpsc::channel();
897 thread::spawn(move || {
898 let (stream, _) = listener1.accept().unwrap();
899 accepted1_tx.send(stream).unwrap();
900 });
901
902 let node = RnsNode::connect_shared(
903 SharedClientConfig {
904 instance_name,
905 port,
906 rpc_port: 0,
907 },
908 Box::new(NoopCallbacks),
909 )
910 .unwrap();
911
912 let identity = Identity::new(&mut OsRng);
913 let dest = crate::destination::Destination::single_in(
914 "shared-replay",
915 &["echo"],
916 IdentityHash(*identity.hash()),
917 );
918 node.register_destination(dest.hash.0, dest.dest_type.to_wire_constant())
919 .unwrap();
920 node.announce_queued(&dest, &identity, Some(b"hello"))
921 .unwrap();
922
923 let mut stream1 = accepted1_rx.recv_timeout(Duration::from_secs(2)).unwrap();
924 stream1
925 .set_read_timeout(Some(Duration::from_secs(2)))
926 .unwrap();
927
928 let mut decoder = crate::interface::local::frame_decoder(0);
929 let mut buf = [0u8; 4096];
930 let n = stream1.read(&mut buf).unwrap();
931 let frames = decoder.feed(&buf[..n]);
932 assert!(!frames.is_empty(), "expected initial announce frame");
933 let packet1 = RawPacket::unpack(&frames[0]).unwrap();
934 assert_eq!(packet1.destination_hash, dest.hash.0);
935 assert_eq!(packet1.context, rns_core::constants::CONTEXT_NONE);
936
937 drop(stream1);
938
939 let listener2 = std::net::TcpListener::bind(&addr).unwrap();
940 let (accepted2_tx, accepted2_rx) = mpsc::channel();
941 thread::spawn(move || {
942 let (stream, _) = listener2.accept().unwrap();
943 accepted2_tx.send(stream).unwrap();
944 });
945
946 let mut stream2 = accepted2_rx.recv_timeout(Duration::from_secs(15)).unwrap();
947 stream2
948 .set_read_timeout(Some(Duration::from_secs(15)))
949 .unwrap();
950
951 let mut decoder = crate::interface::local::frame_decoder(0);
952 let n = stream2.read(&mut buf).unwrap();
953 let frames = decoder.feed(&buf[..n]);
954 assert!(!frames.is_empty(), "expected replayed announce frame");
955 let packet2 = RawPacket::unpack(&frames[0]).unwrap();
956 assert_eq!(packet2.destination_hash, dest.hash.0);
957 assert_eq!(packet2.context, rns_core::constants::CONTEXT_PATH_RESPONSE);
958
959 node.shutdown();
960 }
961
962 #[test]
963 fn connect_shared_fails_no_server() {
964 let port = find_free_port();
965
966 let config = SharedClientConfig {
967 instance_name: "nonexistent-instance-12345".into(),
968 port,
969 rpc_port: 0,
970 };
971
972 let result = RnsNode::connect_shared(config, Box::new(NoopCallbacks));
974 assert!(result.is_err());
975 }
976
977 #[test]
978 fn shared_config_defaults() {
979 let config = SharedClientConfig::default();
980 assert_eq!(config.instance_name, "default");
981 assert_eq!(config.port, 37428);
982 assert_eq!(config.rpc_port, 37429);
983 }
984}