Skip to main content

rns_cli/
remote.rs

1//! Remote management query helper.
2//!
3//! Connects as a shared client, creates a link to a remote management
4//! destination, sends a request, and returns the response data.
5
6use std::path::Path;
7use std::sync::mpsc;
8use std::sync::{Arc, Mutex};
9use std::time::Duration;
10
11use rns_net::{Callbacks, RnsNode};
12use rns_net::shared_client::SharedClientConfig;
13
14/// Parse a 32-hex-char destination hash.
15pub fn parse_hex_hash(s: &str) -> Option<[u8; 16]> {
16    let s = s.trim();
17    if s.len() != 32 {
18        return None;
19    }
20    let bytes: Vec<u8> = (0..s.len())
21        .step_by(2)
22        .filter_map(|i| u8::from_str_radix(&s[i..i + 2], 16).ok())
23        .collect();
24    if bytes.len() != 16 {
25        return None;
26    }
27    let mut result = [0u8; 16];
28    result.copy_from_slice(&bytes);
29    Some(result)
30}
31
32/// Result from a remote management query.
33pub struct RemoteQueryResult {
34    /// Raw response data from the management request.
35    pub data: Vec<u8>,
36}
37
38/// Callbacks that capture link establishment and response data.
39struct RemoteCallbacks {
40    link_established_tx: mpsc::Sender<rns_net::LinkId>,
41    response_data: Arc<Mutex<Option<Vec<u8>>>>,
42    response_tx: mpsc::Sender<()>,
43}
44
45impl Callbacks for RemoteCallbacks {
46    fn on_announce(&mut self, _announced: rns_net::AnnouncedIdentity) {}
47
48    fn on_path_updated(&mut self, _dest_hash: rns_net::DestHash, _hops: u8) {}
49
50    fn on_local_delivery(&mut self, _dest_hash: rns_net::DestHash, _raw: Vec<u8>, _packet_hash: rns_net::PacketHash) {}
51
52    fn on_link_established(&mut self, link_id: rns_net::LinkId, _dest_hash: rns_net::DestHash, _rtt: f64, _is_initiator: bool) {
53        let _ = self.link_established_tx.send(link_id);
54    }
55
56    fn on_response(&mut self, _link_id: rns_net::LinkId, _request_id: [u8; 16], data: Vec<u8>) {
57        *self.response_data.lock().unwrap() = Some(data);
58        let _ = self.response_tx.send(());
59    }
60}
61
62/// Perform a remote management query.
63///
64/// 1. Connects as a shared client
65/// 2. Creates a link to the management destination
66/// 3. Identifies on the link
67/// 4. Sends a request to the specified path
68/// 5. Returns the response data
69///
70/// Returns `None` if the query fails or times out.
71pub fn remote_query(
72    dest_hash: [u8; 16],
73    dest_sig_pub: [u8; 32],
74    identity_prv_key: [u8; 64],
75    path: &str,
76    data: &[u8],
77    config_path: Option<&Path>,
78    timeout: Duration,
79) -> Option<RemoteQueryResult> {
80    let (link_tx, link_rx) = mpsc::channel();
81    let (resp_tx, resp_rx) = mpsc::channel();
82    let response_data = Arc::new(Mutex::new(None));
83
84    let callbacks = RemoteCallbacks {
85        link_established_tx: link_tx,
86        response_data: response_data.clone(),
87        response_tx: resp_tx,
88    };
89
90    // Load config for shared instance connection
91    let config_dir = rns_net::storage::resolve_config_dir(config_path);
92    let config_file = config_dir.join("config");
93    let rns_config = if config_file.exists() {
94        rns_net::config::parse_file(&config_file).ok()?
95    } else {
96        rns_net::config::parse("").ok()?
97    };
98
99    let shared_config = SharedClientConfig {
100        instance_name: rns_config.reticulum.instance_name.clone(),
101        port: rns_config.reticulum.shared_instance_port,
102        rpc_port: rns_config.reticulum.instance_control_port,
103    };
104
105    let node = RnsNode::connect_shared(shared_config, Box::new(callbacks)).ok()?;
106
107    // Wait briefly for connection
108    std::thread::sleep(Duration::from_millis(500));
109
110    // Create link to management destination
111    let link_id = node.create_link(dest_hash, dest_sig_pub).ok()?;
112
113    // Wait for link establishment
114    let _established_link_id = link_rx.recv_timeout(timeout).ok()?;
115
116    // Identify on the link
117    node.identify_on_link(link_id, identity_prv_key).ok()?;
118    std::thread::sleep(Duration::from_millis(200));
119
120    // Send the request
121    node.send_request(link_id, path, data).ok()?;
122
123    // Wait for response
124    resp_rx.recv_timeout(timeout).ok()?;
125
126    let data = response_data.lock().unwrap().take()?;
127    node.shutdown();
128
129    Some(RemoteQueryResult { data })
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn parse_hex_hash_valid() {
138        let hash = parse_hex_hash("0123456789abcdef0123456789abcdef").unwrap();
139        assert_eq!(hash[0], 0x01);
140        assert_eq!(hash[15], 0xef);
141    }
142
143    #[test]
144    fn parse_hex_hash_invalid() {
145        assert!(parse_hex_hash("short").is_none());
146        assert!(parse_hex_hash("0123456789abcdef0123456789abcdef00").is_none());
147        assert!(parse_hex_hash("xyz3456789abcdef0123456789abcdef").is_none());
148    }
149
150    #[test]
151    fn parse_hex_hash_trimmed() {
152        let hash = parse_hex_hash("  0123456789abcdef0123456789abcdef  ").unwrap();
153        assert_eq!(hash[0], 0x01);
154    }
155}