1use std::path::Path;
7use std::sync::mpsc;
8use std::sync::{Arc, Mutex};
9use std::time::Duration;
10
11use rns_net::shared_client::SharedClientConfig;
12use rns_net::{Callbacks, RnsNode};
13
14fn lock_response_data<'a>(
15 response_data: &'a Arc<Mutex<Option<Vec<u8>>>>,
16) -> std::sync::MutexGuard<'a, Option<Vec<u8>>> {
17 match response_data.lock() {
18 Ok(guard) => guard,
19 Err(poisoned) => {
20 log::error!("recovering from poisoned remote response buffer");
21 poisoned.into_inner()
22 }
23 }
24}
25
26pub fn parse_hex_hash(s: &str) -> Option<[u8; 16]> {
28 let s = s.trim();
29 if s.len() != 32 {
30 return None;
31 }
32 let bytes: Vec<u8> = (0..s.len())
33 .step_by(2)
34 .filter_map(|i| u8::from_str_radix(&s[i..i + 2], 16).ok())
35 .collect();
36 if bytes.len() != 16 {
37 return None;
38 }
39 let mut result = [0u8; 16];
40 result.copy_from_slice(&bytes);
41 Some(result)
42}
43
44pub struct RemoteQueryResult {
46 pub data: Vec<u8>,
48}
49
50struct RemoteCallbacks {
52 link_established_tx: mpsc::Sender<rns_net::LinkId>,
53 response_data: Arc<Mutex<Option<Vec<u8>>>>,
54 response_tx: mpsc::Sender<()>,
55}
56
57impl Callbacks for RemoteCallbacks {
58 fn on_announce(&mut self, _announced: rns_net::AnnouncedIdentity) {}
59
60 fn on_path_updated(&mut self, _dest_hash: rns_net::DestHash, _hops: u8) {}
61
62 fn on_local_delivery(
63 &mut self,
64 _dest_hash: rns_net::DestHash,
65 _raw: Vec<u8>,
66 _packet_hash: rns_net::PacketHash,
67 ) {
68 }
69
70 fn on_link_established(
71 &mut self,
72 link_id: rns_net::LinkId,
73 _dest_hash: rns_net::DestHash,
74 _rtt: f64,
75 _is_initiator: bool,
76 ) {
77 let _ = self.link_established_tx.send(link_id);
78 }
79
80 fn on_response(&mut self, _link_id: rns_net::LinkId, _request_id: [u8; 16], data: Vec<u8>) {
81 *lock_response_data(&self.response_data) = Some(data);
82 let _ = self.response_tx.send(());
83 }
84}
85
86pub fn remote_query(
96 dest_hash: [u8; 16],
97 dest_sig_pub: [u8; 32],
98 identity_prv_key: [u8; 64],
99 path: &str,
100 data: &[u8],
101 config_path: Option<&Path>,
102 timeout: Duration,
103) -> Option<RemoteQueryResult> {
104 let (link_tx, link_rx) = mpsc::channel();
105 let (resp_tx, resp_rx) = mpsc::channel();
106 let response_data = Arc::new(Mutex::new(None));
107
108 let callbacks = RemoteCallbacks {
109 link_established_tx: link_tx,
110 response_data: response_data.clone(),
111 response_tx: resp_tx,
112 };
113
114 let config_dir = rns_net::storage::resolve_config_dir(config_path);
116 let config_file = config_dir.join("config");
117 let rns_config = if config_file.exists() {
118 rns_net::config::parse_file(&config_file).ok()?
119 } else {
120 rns_net::config::parse("").ok()?
121 };
122
123 let shared_config = SharedClientConfig {
124 instance_name: rns_config.reticulum.instance_name.clone(),
125 port: rns_config.reticulum.shared_instance_port,
126 rpc_port: rns_config.reticulum.instance_control_port,
127 };
128
129 let node = RnsNode::connect_shared(shared_config, Box::new(callbacks)).ok()?;
130
131 std::thread::sleep(Duration::from_millis(500));
133
134 let link_id = node.create_link(dest_hash, dest_sig_pub).ok()?;
136
137 let _established_link_id = link_rx.recv_timeout(timeout).ok()?;
139
140 node.identify_on_link(link_id, identity_prv_key).ok()?;
142 std::thread::sleep(Duration::from_millis(200));
143
144 node.send_request(link_id, path, data).ok()?;
146
147 resp_rx.recv_timeout(timeout).ok()?;
149
150 let data = lock_response_data(&response_data).take()?;
151 node.shutdown();
152
153 Some(RemoteQueryResult { data })
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 #[test]
161 fn parse_hex_hash_valid() {
162 let hash = parse_hex_hash("0123456789abcdef0123456789abcdef").unwrap();
163 assert_eq!(hash[0], 0x01);
164 assert_eq!(hash[15], 0xef);
165 }
166
167 #[test]
168 fn parse_hex_hash_invalid() {
169 assert!(parse_hex_hash("short").is_none());
170 assert!(parse_hex_hash("0123456789abcdef0123456789abcdef00").is_none());
171 assert!(parse_hex_hash("xyz3456789abcdef0123456789abcdef").is_none());
172 }
173
174 #[test]
175 fn parse_hex_hash_trimmed() {
176 let hash = parse_hex_hash(" 0123456789abcdef0123456789abcdef ").unwrap();
177 assert_eq!(hash[0], 0x01);
178 }
179}