1use rns_core::msgpack::{self, Value};
9use rns_core::stamp::{stamp_valid, stamp_value, stamp_workblock};
10use rns_crypto::sha256::sha256;
11
12use super::time;
13
14pub const NAME: u8 = 0xFF;
20pub const TRANSPORT_ID: u8 = 0xFE;
21pub const INTERFACE_TYPE: u8 = 0x00;
22pub const TRANSPORT: u8 = 0x01;
23pub const REACHABLE_ON: u8 = 0x02;
24pub const LATITUDE: u8 = 0x03;
25pub const LONGITUDE: u8 = 0x04;
26pub const HEIGHT: u8 = 0x05;
27pub const PORT: u8 = 0x06;
28pub const IFAC_NETNAME: u8 = 0x07;
29pub const IFAC_NETKEY: u8 = 0x08;
30pub const FREQUENCY: u8 = 0x09;
31pub const BANDWIDTH: u8 = 0x0A;
32pub const SPREADINGFACTOR: u8 = 0x0B;
33pub const CODINGRATE: u8 = 0x0C;
34pub const MODULATION: u8 = 0x0D;
35pub const CHANNEL: u8 = 0x0E;
36
37pub const APP_NAME: &str = "rnstransport";
39
40pub const DEFAULT_STAMP_VALUE: u8 = 14;
42
43pub const WORKBLOCK_EXPAND_ROUNDS: u32 = 20;
45
46pub const STAMP_SIZE: usize = 32;
48
49pub const THRESHOLD_UNKNOWN: f64 = 24.0 * 60.0 * 60.0;
52pub const THRESHOLD_STALE: f64 = 3.0 * 24.0 * 60.0 * 60.0;
54pub const THRESHOLD_REMOVE: f64 = 7.0 * 24.0 * 60.0 * 60.0;
56
57const STATUS_STALE: i32 = 0;
59const STATUS_UNKNOWN: i32 = 100;
60const STATUS_AVAILABLE: i32 = 1000;
61
62#[derive(Debug, Clone)]
68pub struct DiscoveryConfig {
69 pub discovery_name: String,
71 pub announce_interval: u64,
73 pub stamp_value: u8,
75 pub reachable_on: Option<String>,
77 pub interface_type: String,
79 pub listen_port: Option<u16>,
81 pub latitude: Option<f64>,
83 pub longitude: Option<f64>,
85 pub height: Option<f64>,
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub enum DiscoveredStatus {
96 Available,
97 Unknown,
98 Stale,
99}
100
101impl DiscoveredStatus {
102 pub fn code(&self) -> i32 {
104 match self {
105 DiscoveredStatus::Available => STATUS_AVAILABLE,
106 DiscoveredStatus::Unknown => STATUS_UNKNOWN,
107 DiscoveredStatus::Stale => STATUS_STALE,
108 }
109 }
110
111 pub fn as_str(&self) -> &'static str {
113 match self {
114 DiscoveredStatus::Available => "available",
115 DiscoveredStatus::Unknown => "unknown",
116 DiscoveredStatus::Stale => "stale",
117 }
118 }
119}
120
121#[derive(Debug, Clone)]
123pub struct DiscoveredInterface {
124 pub interface_type: String,
126 pub transport: bool,
128 pub name: String,
130 pub discovered: f64,
132 pub last_heard: f64,
134 pub heard_count: u32,
136 pub status: DiscoveredStatus,
138 pub stamp: Vec<u8>,
140 pub stamp_value: u32,
142 pub transport_id: [u8; 16],
144 pub network_id: [u8; 16],
146 pub hops: u8,
148
149 pub latitude: Option<f64>,
151 pub longitude: Option<f64>,
152 pub height: Option<f64>,
153
154 pub reachable_on: Option<String>,
156 pub port: Option<u16>,
157
158 pub frequency: Option<u32>,
160 pub bandwidth: Option<u32>,
161 pub spreading_factor: Option<u8>,
162 pub coding_rate: Option<u8>,
163 pub modulation: Option<String>,
164 pub channel: Option<u8>,
165
166 pub ifac_netname: Option<String>,
168 pub ifac_netkey: Option<String>,
169
170 pub config_entry: Option<String>,
172
173 pub discovery_hash: [u8; 32],
175}
176
177impl DiscoveredInterface {
178 pub fn compute_status(&self) -> DiscoveredStatus {
180 let delta = time::now() - self.last_heard;
181 if delta > THRESHOLD_STALE {
182 DiscoveredStatus::Stale
183 } else if delta > THRESHOLD_UNKNOWN {
184 DiscoveredStatus::Unknown
185 } else {
186 DiscoveredStatus::Available
187 }
188 }
189}
190
191pub fn parse_interface_announce(
202 app_data: &[u8],
203 announced_identity_hash: &[u8; 16],
204 hops: u8,
205 required_stamp_value: u8,
206) -> Option<DiscoveredInterface> {
207 if app_data.len() <= STAMP_SIZE + 1 {
209 return None;
210 }
211
212 let flags = app_data[0];
214 let payload = &app_data[1..];
215
216 let encrypted = (flags & 0x02) != 0;
218 if encrypted {
219 log::debug!("Ignoring encrypted discovered interface (not supported)");
220 return None;
221 }
222
223 let stamp = &payload[payload.len() - STAMP_SIZE..];
225 let packed = &payload[..payload.len() - STAMP_SIZE];
226
227 let infohash = sha256(packed);
229 let workblock = stamp_workblock(&infohash, WORKBLOCK_EXPAND_ROUNDS);
230
231 if !stamp_valid(stamp, required_stamp_value, &workblock) {
233 log::debug!("Ignoring discovered interface with invalid stamp");
234 return None;
235 }
236
237 let stamp_value = stamp_value(&workblock, stamp);
239
240 let (value, _) = msgpack::unpack(packed).ok()?;
242 let map = value.as_map()?;
243
244 let get_u8_val = |key: u8| -> Option<Value> {
246 for (k, v) in map {
247 if k.as_uint()? as u8 == key {
248 return Some(v.clone());
249 }
250 }
251 None
252 };
253
254 let interface_type = get_u8_val(INTERFACE_TYPE)?.as_str()?.to_string();
256 let transport = get_u8_val(TRANSPORT)?.as_bool()?;
257 let name = get_u8_val(NAME)?
258 .as_str()
259 .unwrap_or(&format!("Discovered {}", interface_type))
260 .to_string();
261
262 let transport_id_val = get_u8_val(TRANSPORT_ID)?;
263 let transport_id_bytes = transport_id_val.as_bin()?;
264 let mut transport_id = [0u8; 16];
265 if transport_id_bytes.len() >= 16 {
266 transport_id.copy_from_slice(&transport_id_bytes[..16]);
267 }
268
269 let latitude = get_u8_val(LATITUDE).and_then(|v| v.as_float());
271 let longitude = get_u8_val(LONGITUDE).and_then(|v| v.as_float());
272 let height = get_u8_val(HEIGHT).and_then(|v| v.as_float());
273 let reachable_on = get_u8_val(REACHABLE_ON).and_then(|v| v.as_str().map(|s| s.to_string()));
274 let port = get_u8_val(PORT).and_then(|v| v.as_uint().map(|n| n as u16));
275 let frequency = get_u8_val(FREQUENCY).and_then(|v| v.as_uint().map(|n| n as u32));
276 let bandwidth = get_u8_val(BANDWIDTH).and_then(|v| v.as_uint().map(|n| n as u32));
277 let spreading_factor = get_u8_val(SPREADINGFACTOR).and_then(|v| v.as_uint().map(|n| n as u8));
278 let coding_rate = get_u8_val(CODINGRATE).and_then(|v| v.as_uint().map(|n| n as u8));
279 let modulation = get_u8_val(MODULATION).and_then(|v| v.as_str().map(|s| s.to_string()));
280 let channel = get_u8_val(CHANNEL).and_then(|v| v.as_uint().map(|n| n as u8));
281 let ifac_netname = get_u8_val(IFAC_NETNAME).and_then(|v| v.as_str().map(|s| s.to_string()));
282 let ifac_netkey = get_u8_val(IFAC_NETKEY).and_then(|v| v.as_str().map(|s| s.to_string()));
283
284 let discovery_hash = compute_discovery_hash(&transport_id, &name);
286
287 let config_entry = generate_config_entry(
289 &interface_type,
290 &name,
291 &transport_id,
292 reachable_on.as_deref(),
293 port,
294 frequency,
295 bandwidth,
296 spreading_factor,
297 coding_rate,
298 modulation.as_deref(),
299 ifac_netname.as_deref(),
300 ifac_netkey.as_deref(),
301 );
302
303 let now = time::now();
304
305 Some(DiscoveredInterface {
306 interface_type,
307 transport,
308 name,
309 discovered: now,
310 last_heard: now,
311 heard_count: 0,
312 status: DiscoveredStatus::Available,
313 stamp: stamp.to_vec(),
314 stamp_value,
315 transport_id,
316 network_id: *announced_identity_hash,
317 hops,
318 latitude,
319 longitude,
320 height,
321 reachable_on,
322 port,
323 frequency,
324 bandwidth,
325 spreading_factor,
326 coding_rate,
327 modulation,
328 channel,
329 ifac_netname,
330 ifac_netkey,
331 config_entry,
332 discovery_hash,
333 })
334}
335
336pub fn compute_discovery_hash(transport_id: &[u8; 16], name: &str) -> [u8; 32] {
338 let mut material = Vec::with_capacity(16 + name.len());
339 material.extend_from_slice(transport_id);
340 material.extend_from_slice(name.as_bytes());
341 sha256(&material)
342}
343
344fn generate_config_entry(
346 interface_type: &str,
347 name: &str,
348 transport_id: &[u8; 16],
349 reachable_on: Option<&str>,
350 port: Option<u16>,
351 frequency: Option<u32>,
352 bandwidth: Option<u32>,
353 spreading_factor: Option<u8>,
354 coding_rate: Option<u8>,
355 modulation: Option<&str>,
356 ifac_netname: Option<&str>,
357 ifac_netkey: Option<&str>,
358) -> Option<String> {
359 let transport_id_hex = hex_encode(transport_id);
360 let netname_str = ifac_netname.map(|n| format!("\n network_name = {}", n)).unwrap_or_default();
361 let netkey_str = ifac_netkey.map(|k| format!("\n passphrase = {}", k)).unwrap_or_default();
362 let identity_str = format!("\n transport_identity = {}", transport_id_hex);
363
364 match interface_type {
365 "BackboneInterface" | "TCPServerInterface" => {
366 let reachable = reachable_on.unwrap_or("unknown");
367 let port_val = port.unwrap_or(4242);
368 Some(format!(
369 "[[{}]]\n type = BackboneInterface\n enabled = yes\n remote = {}\n target_port = {}{}{}{}",
370 name, reachable, port_val, identity_str, netname_str, netkey_str
371 ))
372 }
373 "I2PInterface" => {
374 let reachable = reachable_on.unwrap_or("unknown");
375 Some(format!(
376 "[[{}]]\n type = I2PInterface\n enabled = yes\n peers = {}{}{}{}",
377 name, reachable, identity_str, netname_str, netkey_str
378 ))
379 }
380 "RNodeInterface" => {
381 let freq_str = frequency.map(|f| format!("\n frequency = {}", f)).unwrap_or_default();
382 let bw_str = bandwidth.map(|b| format!("\n bandwidth = {}", b)).unwrap_or_default();
383 let sf_str = spreading_factor.map(|s| format!("\n spreadingfactor = {}", s)).unwrap_or_default();
384 let cr_str = coding_rate.map(|c| format!("\n codingrate = {}", c)).unwrap_or_default();
385 Some(format!(
386 "[[{}]]\n type = RNodeInterface\n enabled = yes\n port = {}{}{}{}{}{}{}{}",
387 name, "", freq_str, bw_str, sf_str, cr_str, identity_str, netname_str, netkey_str
388 ))
389 }
390 "KISSInterface" => {
391 let freq_str = frequency.map(|f| format!("\n # Frequency: {}", f)).unwrap_or_default();
392 let bw_str = bandwidth.map(|b| format!("\n # Bandwidth: {}", b)).unwrap_or_default();
393 let mod_str = modulation.map(|m| format!("\n # Modulation: {}", m)).unwrap_or_default();
394 Some(format!(
395 "[[{}]]\n type = KISSInterface\n enabled = yes\n port = {}{}{}{}{}{}{}",
396 name, "", freq_str, bw_str, mod_str, identity_str, netname_str, netkey_str
397 ))
398 }
399 "WeaveInterface" => {
400 Some(format!(
401 "[[{}]]\n type = WeaveInterface\n enabled = yes\n port = {}{}{}{}",
402 name, "", identity_str, netname_str, netkey_str
403 ))
404 }
405 _ => None,
406 }
407}
408
409pub fn hex_encode(bytes: &[u8]) -> String {
415 bytes.iter().map(|b| format!("{:02x}", b)).collect()
416}
417
418pub fn is_ip_address(s: &str) -> bool {
420 s.parse::<std::net::IpAddr>().is_ok()
421}
422
423pub fn is_hostname(s: &str) -> bool {
425 let s = s.strip_suffix('.').unwrap_or(s);
426 if s.len() > 253 {
427 return false;
428 }
429 let components: Vec<&str> = s.split('.').collect();
430 if components.is_empty() {
431 return false;
432 }
433 if components.last().map(|c| c.chars().all(|ch| ch.is_ascii_digit())).unwrap_or(false) {
435 return false;
436 }
437 components.iter().all(|c| {
438 !c.is_empty()
439 && c.len() <= 63
440 && !c.starts_with('-')
441 && !c.ends_with('-')
442 && c.chars().all(|ch| ch.is_ascii_alphanumeric() || ch == '-')
443 })
444}
445
446pub fn filter_and_sort_interfaces(
448 interfaces: &mut Vec<DiscoveredInterface>,
449 only_available: bool,
450 only_transport: bool,
451) {
452 let now = time::now();
453
454 interfaces.retain(|iface| {
456 let delta = now - iface.last_heard;
457
458 if delta > THRESHOLD_REMOVE {
460 return false;
461 }
462
463 let status = iface.compute_status();
465
466 if only_available && status != DiscoveredStatus::Available {
468 return false;
469 }
470 if only_transport && !iface.transport {
471 return false;
472 }
473
474 true
475 });
476
477 interfaces.sort_by(|a, b| {
479 let status_cmp = b.compute_status().code().cmp(&a.compute_status().code());
480 if status_cmp != std::cmp::Ordering::Equal {
481 return status_cmp;
482 }
483 let value_cmp = b.stamp_value.cmp(&a.stamp_value);
484 if value_cmp != std::cmp::Ordering::Equal {
485 return value_cmp;
486 }
487 b.last_heard.partial_cmp(&a.last_heard).unwrap_or(std::cmp::Ordering::Equal)
488 });
489}
490
491pub fn discovery_name_hash() -> [u8; 10] {
496 rns_core::destination::name_hash(APP_NAME, &["discovery", "interface"])
497}