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