1use std::io;
14use std::io::Read;
15use std::path::Path;
16use std::sync::atomic::{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;
29use crate::interface::local::LocalClientConfig;
30use crate::interface::{InterfaceEntry, InterfaceStats};
31use crate::node::RnsNode;
32use crate::storage;
33use crate::time;
34
35pub struct SharedClientConfig {
37 pub instance_name: String,
39 pub port: u16,
41 pub rpc_port: u16,
43}
44
45impl Default for SharedClientConfig {
46 fn default() -> Self {
47 SharedClientConfig {
48 instance_name: "default".into(),
49 port: 37428,
50 rpc_port: 37429,
51 }
52 }
53}
54
55impl RnsNode {
56 pub fn connect_shared(
62 config: SharedClientConfig,
63 callbacks: Box<dyn Callbacks>,
64 ) -> io::Result<Self> {
65 Self::connect_shared_with_reconnect_wait(config, callbacks, Duration::from_secs(8))
66 }
67
68 fn connect_shared_with_reconnect_wait(
69 config: SharedClientConfig,
70 callbacks: Box<dyn Callbacks>,
71 reconnect_wait: Duration,
72 ) -> io::Result<Self> {
73 let transport_config = TransportConfig {
74 transport_enabled: false,
75 identity_hash: None,
76 local_hops_delta: 0,
77 prefer_shorter_path: false,
78 max_paths_per_destination: 1,
79 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
80 packet_hashlist_allocation: rns_core::transport::types::PacketHashlistAllocation::Eager,
81 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
82 max_path_destinations: rns_core::transport::types::DEFAULT_MAX_PATH_DESTINATIONS,
83 max_tunnel_destinations_total: usize::MAX,
84 destination_timeout_secs: rns_core::constants::DESTINATION_TIMEOUT,
85 announce_table_ttl_secs: rns_core::constants::ANNOUNCE_TABLE_TTL,
86 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
87 announce_sig_cache_enabled: true,
88 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
89 announce_sig_cache_ttl_secs: rns_core::constants::ANNOUNCE_SIG_CACHE_TTL,
90 announce_queue_max_entries: 256,
91 announce_queue_max_interfaces: 1024,
92 };
93
94 let (tx, rx) = event::channel();
95 let tick_interval_ms = Arc::new(AtomicU64::new(1000));
96 let mut driver = Driver::new(transport_config, rx, tx.clone(), callbacks);
97 driver.set_tick_interval_handle(Arc::clone(&tick_interval_ms));
98
99 let local_config = LocalClientConfig {
101 name: "Local shared instance".into(),
102 instance_name: config.instance_name.clone(),
103 port: config.port,
104 interface_id: rns_core::transport::types::InterfaceId(1),
105 reconnect_wait,
106 };
107
108 let id = local_config.interface_id;
109 let info = rns_core::transport::types::InterfaceInfo {
110 id,
111 name: "LocalInterface".into(),
112 mode: rns_core::constants::MODE_FULL,
113 gravity: 0,
114 recursive_prs: false,
115 announces_from_internal: true,
116 announces_to_internal: None,
117 out_capable: true,
118 in_capable: true,
119 bitrate: Some(crate::interface::local::BITRATE),
120 airtime_profile: None,
121 announce_rate_target: None,
122 announce_rate_grace: 0,
123 announce_rate_penalty: 0.0,
124 announce_cap: rns_core::constants::ANNOUNCE_CAP,
125 is_local_client: true,
126 wants_tunnel: false,
127 tunnel_id: None,
128 mtu: crate::interface::local::HW_MTU,
129 ia_freq: 0.0,
130 ip_freq: 0.0,
131 op_freq: 0.0,
132 op_samples: 0,
133 started: time::now(),
134 ingress_control: rns_core::transport::types::IngressControlConfig::disabled(),
135 };
136
137 let writer = crate::interface::local::start_client(local_config, tx.clone())?;
138 let (writer, async_writer_metrics) = crate::interface::wrap_async_writer(
139 writer,
140 id,
141 &info.name,
142 tx.clone(),
143 driver.interface_writer_queue_capacity,
144 );
145
146 driver.engine.register_interface(info.clone());
147 driver.interfaces.insert(
148 id,
149 InterfaceEntry {
150 id,
151 info,
152 writer,
153 async_writer_metrics: Some(async_writer_metrics),
154 enabled: true,
155 online: false,
156 dynamic: false,
157 ifac: None,
158 stats: InterfaceStats {
159 started: time::now(),
160 ..Default::default()
161 },
162 interface_type: "LocalClientInterface".to_string(),
163 send_retry_at: None,
164 send_retry_backoff: Duration::ZERO,
165 },
166 );
167
168 let timer_tx = tx.clone();
170 let timer_interval = Arc::clone(&tick_interval_ms);
171 thread::Builder::new()
172 .name("rns-timer-client".into())
173 .spawn(move || loop {
174 let ms = timer_interval.load(Ordering::Relaxed);
175 thread::sleep(Duration::from_millis(ms));
176 if timer_tx.send(event::Event::Tick).is_err() {
177 break;
178 }
179 })?;
180
181 let driver_handle = thread::Builder::new()
183 .name("rns-driver-client".into())
184 .spawn(move || {
185 driver.run();
186 })?;
187
188 Ok(RnsNode::from_parts(
189 tx,
190 driver_handle,
191 None,
192 tick_interval_ms,
193 ))
194 }
195
196 pub fn connect_shared_from_config(
200 config_path: Option<&Path>,
201 callbacks: Box<dyn Callbacks>,
202 ) -> io::Result<Self> {
203 let config_dir = storage::resolve_config_dir(config_path);
204
205 let config_file = config_dir.join("config");
207 let rns_config = if config_file.exists() {
208 crate::config::parse_file(&config_file)
209 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("{}", e)))?
210 } else {
211 crate::config::parse("")
212 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("{}", e)))?
213 };
214
215 let shared_config = SharedClientConfig {
216 instance_name: rns_config.reticulum.instance_name.clone(),
217 port: rns_config.reticulum.shared_instance_port,
218 rpc_port: rns_config.reticulum.instance_control_port,
219 };
220
221 Self::connect_shared(shared_config, callbacks)
222 }
223}
224
225#[doc(hidden)]
226pub fn bench_shared_client_replay_once(
227 announce_count: usize,
228 reconnect_wait: Duration,
229) -> io::Result<usize> {
230 struct BenchNoopCallbacks;
231 impl Callbacks for BenchNoopCallbacks {
232 fn on_announce(&mut self, _: crate::destination::AnnouncedIdentity) {}
233 fn on_path_updated(&mut self, _: rns_core::types::DestHash, _: u8) {}
234 fn on_local_delivery(
235 &mut self,
236 _: rns_core::types::DestHash,
237 _: Vec<u8>,
238 _: rns_core::types::PacketHash,
239 ) {
240 }
241 }
242
243 fn build_shared_announce_raw(
244 dest_hash: &[u8; 16],
245 name_hash: &[u8; 10],
246 identity_prv_key: &[u8; 64],
247 app_data: Option<&[u8]>,
248 path_response: bool,
249 ) -> Vec<u8> {
250 let identity = Identity::from_private_key(identity_prv_key);
251 let mut random_hash = [0u8; 10];
252 random_hash[..5].copy_from_slice(&[0xA5; 5]);
253 random_hash[5..10].copy_from_slice(&[0, 0, 0, 0, 1]);
254
255 let (announce_data, _) = rns_core::announce::AnnounceData::pack(
256 &identity,
257 dest_hash,
258 name_hash,
259 &random_hash,
260 None,
261 app_data,
262 )
263 .unwrap();
264
265 let flags = rns_core::packet::PacketFlags {
266 header_type: rns_core::constants::HEADER_1,
267 context_flag: rns_core::constants::FLAG_UNSET,
268 transport_type: rns_core::constants::TRANSPORT_BROADCAST,
269 destination_type: rns_core::constants::DESTINATION_SINGLE,
270 packet_type: rns_core::constants::PACKET_TYPE_ANNOUNCE,
271 };
272 let context = if path_response {
273 rns_core::constants::CONTEXT_PATH_RESPONSE
274 } else {
275 rns_core::constants::CONTEXT_NONE
276 };
277
278 rns_core::packet::RawPacket::pack(flags, 0, dest_hash, None, context, &announce_data)
279 .unwrap()
280 .raw
281 }
282
283 fn read_until_frames(
284 stream: &mut std::net::TcpStream,
285 expected: usize,
286 expected_context: u8,
287 ) -> io::Result<Vec<Vec<u8>>> {
288 let mut decoder = crate::interface::local::frame_decoder(0);
289 let mut buf = [0u8; 4096];
290 let mut frames = Vec::new();
291 let deadline = std::time::Instant::now() + Duration::from_secs(2);
292 while frames.len() < expected {
293 let n = match stream.read(&mut buf) {
294 Ok(n) => n,
295 Err(e)
296 if e.kind() == io::ErrorKind::WouldBlock
297 || e.kind() == io::ErrorKind::TimedOut =>
298 {
299 if std::time::Instant::now() >= deadline {
300 return Err(io::Error::new(
301 io::ErrorKind::TimedOut,
302 format!(
303 "timed out waiting for {} frames, got {}",
304 expected,
305 frames.len()
306 ),
307 ));
308 }
309 continue;
310 }
311 Err(e) => return Err(e),
312 };
313 for frame in decoder.feed(&buf[..n]) {
314 let Ok(packet) = RawPacket::unpack(&frame) else {
315 log::debug!("skipping malformed shared-instance packet");
316 continue;
317 };
318 if packet.context == expected_context {
319 frames.push(frame);
320 }
321 }
322 }
323 Ok(frames)
324 }
325
326 let listener1 = std::net::TcpListener::bind("127.0.0.1:0")?;
327 let port = listener1.local_addr()?.port();
328 let instance_name = format!("bench-shared-replay-{port}");
329
330 let (accepted1_tx, accepted1_rx) = std::sync::mpsc::channel();
331 thread::spawn(move || {
332 let (stream, _) = listener1.accept().unwrap();
333 accepted1_tx.send(stream).unwrap();
334 });
335
336 let transport_config = TransportConfig {
337 transport_enabled: false,
338 identity_hash: None,
339 local_hops_delta: 0,
340 prefer_shorter_path: false,
341 max_paths_per_destination: 1,
342 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
343 packet_hashlist_allocation: rns_core::transport::types::PacketHashlistAllocation::Eager,
344 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
345 max_path_destinations: rns_core::transport::types::DEFAULT_MAX_PATH_DESTINATIONS,
346 max_tunnel_destinations_total: usize::MAX,
347 destination_timeout_secs: rns_core::constants::DESTINATION_TIMEOUT,
348 announce_table_ttl_secs: rns_core::constants::ANNOUNCE_TABLE_TTL,
349 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
350 announce_sig_cache_enabled: true,
351 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
352 announce_sig_cache_ttl_secs: rns_core::constants::ANNOUNCE_SIG_CACHE_TTL,
353 announce_queue_max_entries: 256,
354 announce_queue_max_interfaces: 1024,
355 };
356
357 let (tx, rx) = event::channel();
358 let tick_interval_ms = Arc::new(AtomicU64::new(1000));
359 let mut driver = Driver::new(
360 transport_config,
361 rx,
362 tx.clone(),
363 Box::new(BenchNoopCallbacks),
364 );
365 driver.set_tick_interval_handle(Arc::clone(&tick_interval_ms));
366
367 let local_config = LocalClientConfig {
368 name: "Shared replay bench".into(),
369 instance_name: instance_name.clone(),
370 port,
371 interface_id: rns_core::transport::types::InterfaceId(1),
372 reconnect_wait,
373 };
374
375 let id = local_config.interface_id;
376 let info = rns_core::transport::types::InterfaceInfo {
377 id,
378 name: "LocalInterface".into(),
379 mode: rns_core::constants::MODE_FULL,
380 gravity: 0,
381 recursive_prs: false,
382 announces_from_internal: true,
383 announces_to_internal: None,
384 out_capable: true,
385 in_capable: true,
386 bitrate: Some(crate::interface::local::BITRATE),
387 airtime_profile: None,
388 announce_rate_target: None,
389 announce_rate_grace: 0,
390 announce_rate_penalty: 0.0,
391 announce_cap: rns_core::constants::ANNOUNCE_CAP,
392 is_local_client: true,
393 wants_tunnel: false,
394 tunnel_id: None,
395 mtu: crate::interface::local::HW_MTU,
396 ia_freq: 0.0,
397 ip_freq: 0.0,
398 op_freq: 0.0,
399 op_samples: 0,
400 started: time::now(),
401 ingress_control: rns_core::transport::types::IngressControlConfig::disabled(),
402 };
403
404 let writer = crate::interface::local::start_client(local_config, tx.clone())?;
405 let (writer, async_writer_metrics) = crate::interface::wrap_async_writer(
406 writer,
407 id,
408 &info.name,
409 tx.clone(),
410 driver.interface_writer_queue_capacity,
411 );
412 driver.engine.register_interface(info.clone());
413 driver.interfaces.insert(
414 id,
415 InterfaceEntry {
416 id,
417 info,
418 writer,
419 async_writer_metrics: Some(async_writer_metrics),
420 enabled: true,
421 online: false,
422 dynamic: false,
423 ifac: None,
424 stats: InterfaceStats {
425 started: time::now(),
426 ..Default::default()
427 },
428 interface_type: "LocalClientInterface".to_string(),
429 send_retry_at: None,
430 send_retry_backoff: Duration::ZERO,
431 },
432 );
433
434 let driver_handle = thread::Builder::new()
435 .name("rns-driver-bench-client".into())
436 .spawn(move || {
437 driver.run();
438 })?;
439
440 let mut stream1 = accepted1_rx
441 .recv_timeout(Duration::from_secs(2))
442 .map_err(|e| {
443 io::Error::new(
444 io::ErrorKind::TimedOut,
445 format!("shared bench initial accept failed: {e}"),
446 )
447 })?;
448 stream1.set_read_timeout(Some(Duration::from_secs(2)))?;
449
450 let mut records = Vec::new();
451 for i in 0..announce_count {
452 let mut prv_key = [0u8; 64];
453 for (j, byte) in prv_key.iter_mut().enumerate() {
454 *byte = (i as u8)
455 .wrapping_mul(23)
456 .wrapping_add(j as u8)
457 .wrapping_add(5);
458 }
459 let identity = Identity::from_private_key(&prv_key);
460 let aspect = format!("echo-{i}");
461 let name_hash = name_hash("shared-bench", &[&aspect]);
462 let dest_hash = destination_hash("shared-bench", &[&aspect], Some(identity.hash()));
463 let app_data = format!("hello-{i}").into_bytes();
464 records.push((dest_hash, name_hash, prv_key, app_data));
465 }
466
467 for (dest_hash, name_hash, prv_key, app_data) in &records {
468 let raw = build_shared_announce_raw(dest_hash, name_hash, prv_key, Some(app_data), false);
469 tx.send(Event::RegisterDestination {
470 dest_hash: *dest_hash,
471 dest_type: rns_core::constants::DESTINATION_SINGLE,
472 })
473 .map_err(|e| io::Error::new(io::ErrorKind::BrokenPipe, format!("{e}")))?;
474 tx.send(Event::StoreSharedAnnounce {
475 dest_hash: *dest_hash,
476 name_hash: *name_hash,
477 identity_prv_key: *prv_key,
478 app_data: Some(app_data.clone()),
479 })
480 .map_err(|e| io::Error::new(io::ErrorKind::BrokenPipe, format!("{e}")))?;
481 tx.send(Event::SendOutbound {
482 raw,
483 dest_type: rns_core::constants::DESTINATION_SINGLE,
484 attached_interface: None,
485 })
486 .map_err(|e| io::Error::new(io::ErrorKind::BrokenPipe, format!("{e}")))?;
487 }
488
489 let _ = read_until_frames(
490 &mut stream1,
491 announce_count,
492 rns_core::constants::CONTEXT_NONE,
493 )
494 .map_err(|e| io::Error::new(e.kind(), format!("initial announce delivery failed: {e}")))?;
495 drop(stream1);
496
497 let listener2 = std::net::TcpListener::bind(format!("127.0.0.1:{port}"))?;
498 let (accepted2_tx, accepted2_rx) = std::sync::mpsc::channel();
499 thread::spawn(move || {
500 let (stream, _) = listener2.accept().unwrap();
501 accepted2_tx.send(stream).unwrap();
502 });
503
504 let mut stream2 = accepted2_rx
505 .recv_timeout(Duration::from_secs(2))
506 .map_err(|e| {
507 io::Error::new(
508 io::ErrorKind::TimedOut,
509 format!("shared bench reconnect accept failed: {e}"),
510 )
511 })?;
512 stream2.set_read_timeout(Some(Duration::from_secs(2)))?;
513 let frames = read_until_frames(
514 &mut stream2,
515 announce_count,
516 rns_core::constants::CONTEXT_PATH_RESPONSE,
517 )
518 .map_err(|e| io::Error::new(e.kind(), format!("replayed announce delivery failed: {e}")))?;
519
520 tx.send(Event::Shutdown)
521 .map_err(|e| io::Error::new(io::ErrorKind::BrokenPipe, format!("{e}")))?;
522 let _ = driver_handle.join();
523
524 Ok(frames.len())
525}
526
527#[cfg(test)]
528mod tests {
529 use super::*;
530 use rns_core::packet::RawPacket;
531 use rns_core::types::IdentityHash;
532 use rns_crypto::identity::Identity;
533 use rns_crypto::OsRng;
534 use std::io::Read;
535 use std::sync::atomic::AtomicU64;
536 use std::sync::mpsc;
537 use std::sync::Arc;
538
539 use crate::interface::local::LocalServerConfig;
540
541 struct NoopCallbacks;
542 impl Callbacks for NoopCallbacks {
543 fn on_announce(&mut self, _: crate::destination::AnnouncedIdentity) {}
544 fn on_path_updated(&mut self, _: rns_core::types::DestHash, _: u8) {}
545 fn on_local_delivery(
546 &mut self,
547 _: rns_core::types::DestHash,
548 _: Vec<u8>,
549 _: rns_core::types::PacketHash,
550 ) {
551 }
552 }
553
554 fn find_free_port() -> u16 {
555 crate::test_support::port()
556 }
557
558 #[test]
559 fn connect_shared_to_tcp_server() {
560 let port = find_free_port();
561 let next_id = Arc::new(AtomicU64::new(50000));
562 let (server_tx, server_rx) = crate::event::channel();
563
564 let server_config = LocalServerConfig {
566 instance_name: "test-shared-connect".into(),
567 port,
568 interface_id: rns_core::transport::types::InterfaceId(99),
569 };
570
571 crate::interface::local::start_server(server_config, server_tx, next_id).unwrap();
572 thread::sleep(Duration::from_millis(50));
573
574 let config = SharedClientConfig {
576 instance_name: "test-shared-connect".into(),
577 port,
578 rpc_port: 0,
579 };
580
581 let node = RnsNode::connect_shared(config, Box::new(NoopCallbacks)).unwrap();
582
583 let event = server_rx.recv_timeout(Duration::from_secs(2)).unwrap();
585 assert!(matches!(event, crate::event::Event::InterfaceUp(_, _, _)));
586
587 node.shutdown();
588 }
589
590 #[test]
591 fn shared_client_register_destination() {
592 let port = find_free_port();
593 let next_id = Arc::new(AtomicU64::new(51000));
594 let (server_tx, _server_rx) = crate::event::channel();
595
596 let server_config = LocalServerConfig {
597 instance_name: "test-shared-reg".into(),
598 port,
599 interface_id: rns_core::transport::types::InterfaceId(98),
600 };
601
602 crate::interface::local::start_server(server_config, server_tx, next_id).unwrap();
603 thread::sleep(Duration::from_millis(50));
604
605 let config = SharedClientConfig {
606 instance_name: "test-shared-reg".into(),
607 port,
608 rpc_port: 0,
609 };
610
611 let node = RnsNode::connect_shared(config, Box::new(NoopCallbacks)).unwrap();
612
613 let dest_hash = [0xAA; 16];
615 node.register_destination(dest_hash, rns_core::constants::DESTINATION_SINGLE)
616 .unwrap();
617
618 thread::sleep(Duration::from_millis(100));
620
621 node.shutdown();
622 }
623
624 #[test]
625 fn shared_client_send_packet() {
626 let port = find_free_port();
627 let next_id = Arc::new(AtomicU64::new(52000));
628 let (server_tx, server_rx) = crate::event::channel();
629
630 let server_config = LocalServerConfig {
631 instance_name: "test-shared-send".into(),
632 port,
633 interface_id: rns_core::transport::types::InterfaceId(97),
634 };
635
636 crate::interface::local::start_server(server_config, server_tx, next_id).unwrap();
637 thread::sleep(Duration::from_millis(50));
638
639 let config = SharedClientConfig {
640 instance_name: "test-shared-send".into(),
641 port,
642 rpc_port: 0,
643 };
644
645 let node = RnsNode::connect_shared(config, Box::new(NoopCallbacks)).unwrap();
646
647 let raw = vec![0x00, 0x00, 0xAA, 0xBB, 0xCC, 0xDD]; node.send_raw(raw, rns_core::constants::DESTINATION_PLAIN, None)
650 .unwrap();
651
652 for _ in 0..10 {
655 match server_rx.recv_timeout(Duration::from_secs(1)) {
656 Ok(crate::event::Event::Frame { .. }) => {
657 break;
658 }
659 Ok(_) => continue,
660 Err(_) => break,
661 }
662 }
663 node.shutdown();
667 }
668
669 #[test]
670 fn shared_client_replays_single_announces_after_reconnect() {
671 let port = find_free_port();
672 let addr = format!("127.0.0.1:{}", port);
673 let instance_name = format!("test-shared-replay-{}", port);
674
675 let listener1 = std::net::TcpListener::bind(&addr).unwrap();
676 let (accepted1_tx, accepted1_rx) = mpsc::channel();
677 thread::spawn(move || {
678 let (stream, _) = listener1.accept().unwrap();
679 accepted1_tx.send(stream).unwrap();
680 });
681
682 let node = RnsNode::connect_shared(
683 SharedClientConfig {
684 instance_name,
685 port,
686 rpc_port: 0,
687 },
688 Box::new(NoopCallbacks),
689 )
690 .unwrap();
691
692 let identity = Identity::new(&mut OsRng);
693 let dest = crate::destination::Destination::single_in(
694 "shared-replay",
695 &["echo"],
696 IdentityHash(*identity.hash()),
697 );
698 node.register_destination(dest.hash.0, dest.dest_type.to_wire_constant())
699 .unwrap();
700 node.announce_queued(&dest, &identity, Some(b"hello"))
701 .unwrap();
702
703 let mut stream1 = accepted1_rx.recv_timeout(Duration::from_secs(2)).unwrap();
704 stream1
705 .set_read_timeout(Some(Duration::from_secs(2)))
706 .unwrap();
707
708 let mut decoder = crate::interface::local::frame_decoder(0);
709 let mut buf = [0u8; 4096];
710 let n = stream1.read(&mut buf).unwrap();
711 let frames = decoder.feed(&buf[..n]);
712 assert!(!frames.is_empty(), "expected initial announce frame");
713 let packet1 = RawPacket::unpack(&frames[0]).unwrap();
714 assert_eq!(packet1.destination_hash, dest.hash.0);
715 assert_eq!(packet1.context, rns_core::constants::CONTEXT_NONE);
716
717 drop(stream1);
718
719 let listener2 = std::net::TcpListener::bind(&addr).unwrap();
720 let (accepted2_tx, accepted2_rx) = mpsc::channel();
721 thread::spawn(move || {
722 let (stream, _) = listener2.accept().unwrap();
723 accepted2_tx.send(stream).unwrap();
724 });
725
726 let mut stream2 = accepted2_rx.recv_timeout(Duration::from_secs(15)).unwrap();
727 stream2
728 .set_read_timeout(Some(Duration::from_secs(15)))
729 .unwrap();
730
731 let mut decoder = crate::interface::local::frame_decoder(0);
732 let n = stream2.read(&mut buf).unwrap();
733 let frames = decoder.feed(&buf[..n]);
734 assert!(!frames.is_empty(), "expected replayed announce frame");
735 let packet2 = RawPacket::unpack(&frames[0]).unwrap();
736 assert_eq!(packet2.destination_hash, dest.hash.0);
737 assert_eq!(packet2.context, rns_core::constants::CONTEXT_PATH_RESPONSE);
738
739 node.shutdown();
740 }
741
742 #[test]
743 fn connect_shared_fails_no_server() {
744 let port = find_free_port();
745
746 let config = SharedClientConfig {
747 instance_name: "nonexistent-instance-12345".into(),
748 port,
749 rpc_port: 0,
750 };
751
752 let result = RnsNode::connect_shared(config, Box::new(NoopCallbacks));
754 assert!(result.is_err());
755 }
756
757 #[test]
758 fn shared_config_defaults() {
759 let config = SharedClientConfig::default();
760 assert_eq!(config.instance_name, "default");
761 assert_eq!(config.port, 37428);
762 assert_eq!(config.rpc_port, 37429);
763 }
764}