1use std::io;
14use std::path::Path;
15use std::sync::atomic::{AtomicU64, Ordering};
16use std::sync::Arc;
17use std::thread;
18use std::time::Duration;
19
20use rns_core::transport::types::TransportConfig;
21
22use crate::driver::{Callbacks, Driver};
23use crate::event;
24use crate::interface::local::LocalClientConfig;
25use crate::interface::{InterfaceEntry, InterfaceStats};
26use crate::node::RnsNode;
27use crate::storage;
28use crate::time;
29
30pub struct SharedClientConfig {
32 pub instance_name: String,
34 pub port: u16,
36 pub rpc_port: u16,
38}
39
40impl Default for SharedClientConfig {
41 fn default() -> Self {
42 SharedClientConfig {
43 instance_name: "default".into(),
44 port: 37428,
45 rpc_port: 37429,
46 }
47 }
48}
49
50impl RnsNode {
51 pub fn connect_shared(
57 config: SharedClientConfig,
58 callbacks: Box<dyn Callbacks>,
59 ) -> io::Result<Self> {
60 let transport_config = TransportConfig {
61 transport_enabled: false,
62 identity_hash: None,
63 prefer_shorter_path: false,
64 max_paths_per_destination: 1,
65 };
66
67 let (tx, rx) = event::channel();
68 let mut driver = Driver::new(transport_config, rx, tx.clone(), callbacks);
69
70 let local_config = LocalClientConfig {
72 name: "Local shared instance".into(),
73 instance_name: config.instance_name.clone(),
74 port: config.port,
75 interface_id: rns_core::transport::types::InterfaceId(1),
76 reconnect_wait: Duration::from_secs(8),
77 };
78
79 let id = local_config.interface_id;
80 let info = rns_core::transport::types::InterfaceInfo {
81 id,
82 name: "LocalInterface".into(),
83 mode: rns_core::constants::MODE_FULL,
84 out_capable: true,
85 in_capable: true,
86 bitrate: Some(1_000_000_000),
87 announce_rate_target: None,
88 announce_rate_grace: 0,
89 announce_rate_penalty: 0.0,
90 announce_cap: rns_core::constants::ANNOUNCE_CAP,
91 is_local_client: true,
92 wants_tunnel: false,
93 tunnel_id: None,
94 mtu: 65535,
95 ia_freq: 0.0,
96 started: time::now(),
97 ingress_control: false,
98 };
99
100 let writer = crate::interface::local::start_client(local_config, tx.clone())?;
101
102 driver.engine.register_interface(info.clone());
103 driver.interfaces.insert(
104 id,
105 InterfaceEntry {
106 id,
107 info,
108 writer,
109 online: false,
110 dynamic: false,
111 ifac: None,
112 stats: InterfaceStats {
113 started: time::now(),
114 ..Default::default()
115 },
116 interface_type: "LocalClientInterface".to_string(),
117 },
118 );
119
120 let tick_interval_ms = Arc::new(AtomicU64::new(1000));
122 let timer_tx = tx.clone();
123 let timer_interval = Arc::clone(&tick_interval_ms);
124 thread::Builder::new()
125 .name("rns-timer-client".into())
126 .spawn(move || {
127 loop {
128 let ms = timer_interval.load(Ordering::Relaxed);
129 thread::sleep(Duration::from_millis(ms));
130 if timer_tx.send(event::Event::Tick).is_err() {
131 break;
132 }
133 }
134 })?;
135
136 let driver_handle = thread::Builder::new()
138 .name("rns-driver-client".into())
139 .spawn(move || {
140 driver.run();
141 })?;
142
143 Ok(RnsNode::from_parts(tx, driver_handle, None, tick_interval_ms))
144 }
145
146 pub fn connect_shared_from_config(
150 config_path: Option<&Path>,
151 callbacks: Box<dyn Callbacks>,
152 ) -> io::Result<Self> {
153 let config_dir = storage::resolve_config_dir(config_path);
154
155 let config_file = config_dir.join("config");
157 let rns_config = if config_file.exists() {
158 crate::config::parse_file(&config_file).map_err(|e| {
159 io::Error::new(io::ErrorKind::InvalidData, format!("{}", e))
160 })?
161 } else {
162 crate::config::parse("").map_err(|e| {
163 io::Error::new(io::ErrorKind::InvalidData, format!("{}", e))
164 })?
165 };
166
167 let shared_config = SharedClientConfig {
168 instance_name: rns_config.reticulum.instance_name.clone(),
169 port: rns_config.reticulum.shared_instance_port,
170 rpc_port: rns_config.reticulum.instance_control_port,
171 };
172
173 Self::connect_shared(shared_config, callbacks)
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180 use std::sync::atomic::{AtomicU64, Ordering};
181 use std::sync::mpsc;
182 use std::sync::Arc;
183
184 use crate::interface::local::LocalServerConfig;
185
186 struct NoopCallbacks;
187 impl Callbacks for NoopCallbacks {
188 fn on_announce(&mut self, _: crate::destination::AnnouncedIdentity) {}
189 fn on_path_updated(&mut self, _: rns_core::types::DestHash, _: u8) {}
190 fn on_local_delivery(&mut self, _: rns_core::types::DestHash, _: Vec<u8>, _: rns_core::types::PacketHash) {}
191 }
192
193 fn find_free_port() -> u16 {
194 std::net::TcpListener::bind("127.0.0.1:0")
195 .unwrap()
196 .local_addr()
197 .unwrap()
198 .port()
199 }
200
201 #[test]
202 fn connect_shared_to_tcp_server() {
203 let port = find_free_port();
204 let next_id = Arc::new(AtomicU64::new(50000));
205 let (server_tx, server_rx) = mpsc::channel();
206
207 let server_config = LocalServerConfig {
209 instance_name: "test-shared-connect".into(),
210 port,
211 interface_id: rns_core::transport::types::InterfaceId(99),
212 };
213
214 crate::interface::local::start_server(server_config, server_tx, next_id).unwrap();
215 thread::sleep(Duration::from_millis(50));
216
217 let config = SharedClientConfig {
219 instance_name: "test-shared-connect".into(),
220 port,
221 rpc_port: 0,
222 };
223
224 let node = RnsNode::connect_shared(config, Box::new(NoopCallbacks)).unwrap();
225
226 let event = server_rx.recv_timeout(Duration::from_secs(2)).unwrap();
228 assert!(matches!(event, crate::event::Event::InterfaceUp(_, _, _)));
229
230 node.shutdown();
231 }
232
233 #[test]
234 fn shared_client_register_destination() {
235 let port = find_free_port();
236 let next_id = Arc::new(AtomicU64::new(51000));
237 let (server_tx, _server_rx) = mpsc::channel();
238
239 let server_config = LocalServerConfig {
240 instance_name: "test-shared-reg".into(),
241 port,
242 interface_id: rns_core::transport::types::InterfaceId(98),
243 };
244
245 crate::interface::local::start_server(server_config, server_tx, next_id).unwrap();
246 thread::sleep(Duration::from_millis(50));
247
248 let config = SharedClientConfig {
249 instance_name: "test-shared-reg".into(),
250 port,
251 rpc_port: 0,
252 };
253
254 let node = RnsNode::connect_shared(config, Box::new(NoopCallbacks)).unwrap();
255
256 let dest_hash = [0xAA; 16];
258 node.register_destination(
259 dest_hash,
260 rns_core::constants::DESTINATION_SINGLE,
261 )
262 .unwrap();
263
264 thread::sleep(Duration::from_millis(100));
266
267 node.shutdown();
268 }
269
270 #[test]
271 fn shared_client_send_packet() {
272 let port = find_free_port();
273 let next_id = Arc::new(AtomicU64::new(52000));
274 let (server_tx, server_rx) = mpsc::channel();
275
276 let server_config = LocalServerConfig {
277 instance_name: "test-shared-send".into(),
278 port,
279 interface_id: rns_core::transport::types::InterfaceId(97),
280 };
281
282 crate::interface::local::start_server(server_config, server_tx, next_id).unwrap();
283 thread::sleep(Duration::from_millis(50));
284
285 let config = SharedClientConfig {
286 instance_name: "test-shared-send".into(),
287 port,
288 rpc_port: 0,
289 };
290
291 let node = RnsNode::connect_shared(config, Box::new(NoopCallbacks)).unwrap();
292
293 let raw = vec![0x00, 0x00, 0xAA, 0xBB, 0xCC, 0xDD]; node.send_raw(raw, rns_core::constants::DESTINATION_PLAIN, None)
296 .unwrap();
297
298 let mut saw_frame = false;
301 for _ in 0..10 {
302 match server_rx.recv_timeout(Duration::from_secs(1)) {
303 Ok(crate::event::Event::Frame { .. }) => {
304 saw_frame = true;
305 break;
306 }
307 Ok(_) => continue,
308 Err(_) => break,
309 }
310 }
311 node.shutdown();
315 }
316
317 #[test]
318 fn connect_shared_fails_no_server() {
319 let port = find_free_port();
320
321 let config = SharedClientConfig {
322 instance_name: "nonexistent-instance-12345".into(),
323 port,
324 rpc_port: 0,
325 };
326
327 let result = RnsNode::connect_shared(config, Box::new(NoopCallbacks));
329 assert!(result.is_err());
330 }
331
332 #[test]
333 fn shared_config_defaults() {
334 let config = SharedClientConfig::default();
335 assert_eq!(config.instance_name, "default");
336 assert_eq!(config.port, 37428);
337 assert_eq!(config.rpc_port, 37429);
338 }
339}