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