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
361 .map(|n| format!("\n network_name = {}", n))
362 .unwrap_or_default();
363 let netkey_str = ifac_netkey
364 .map(|k| format!("\n passphrase = {}", k))
365 .unwrap_or_default();
366 let identity_str = format!("\n transport_identity = {}", transport_id_hex);
367
368 match interface_type {
369 "BackboneInterface" | "TCPServerInterface" => {
370 let reachable = reachable_on.unwrap_or("unknown");
371 let port_val = port.unwrap_or(4242);
372 Some(format!(
373 "[[{}]]\n type = BackboneInterface\n enabled = yes\n remote = {}\n target_port = {}{}{}{}",
374 name, reachable, port_val, identity_str, netname_str, netkey_str
375 ))
376 }
377 "I2PInterface" => {
378 let reachable = reachable_on.unwrap_or("unknown");
379 Some(format!(
380 "[[{}]]\n type = I2PInterface\n enabled = yes\n peers = {}{}{}{}",
381 name, reachable, identity_str, netname_str, netkey_str
382 ))
383 }
384 "RNodeInterface" => {
385 let freq_str = frequency
386 .map(|f| format!("\n frequency = {}", f))
387 .unwrap_or_default();
388 let bw_str = bandwidth
389 .map(|b| format!("\n bandwidth = {}", b))
390 .unwrap_or_default();
391 let sf_str = spreading_factor
392 .map(|s| format!("\n spreadingfactor = {}", s))
393 .unwrap_or_default();
394 let cr_str = coding_rate
395 .map(|c| format!("\n codingrate = {}", c))
396 .unwrap_or_default();
397 Some(format!(
398 "[[{}]]\n type = RNodeInterface\n enabled = yes\n port = {}{}{}{}{}{}{}{}",
399 name, "", freq_str, bw_str, sf_str, cr_str, identity_str, netname_str, netkey_str
400 ))
401 }
402 "KISSInterface" => {
403 let freq_str = frequency
404 .map(|f| format!("\n # Frequency: {}", f))
405 .unwrap_or_default();
406 let bw_str = bandwidth
407 .map(|b| format!("\n # Bandwidth: {}", b))
408 .unwrap_or_default();
409 let mod_str = modulation
410 .map(|m| format!("\n # Modulation: {}", m))
411 .unwrap_or_default();
412 Some(format!(
413 "[[{}]]\n type = KISSInterface\n enabled = yes\n port = {}{}{}{}{}{}{}",
414 name, "", freq_str, bw_str, mod_str, identity_str, netname_str, netkey_str
415 ))
416 }
417 "WeaveInterface" => Some(format!(
418 "[[{}]]\n type = WeaveInterface\n enabled = yes\n port = {}{}{}{}",
419 name, "", identity_str, netname_str, netkey_str
420 )),
421 _ => None,
422 }
423}
424
425pub fn hex_encode(bytes: &[u8]) -> String {
431 bytes.iter().map(|b| format!("{:02x}", b)).collect()
432}
433
434pub fn is_ip_address(s: &str) -> bool {
436 s.parse::<std::net::IpAddr>().is_ok()
437}
438
439pub fn is_hostname(s: &str) -> bool {
441 let s = s.strip_suffix('.').unwrap_or(s);
442 if s.len() > 253 {
443 return false;
444 }
445 let components: Vec<&str> = s.split('.').collect();
446 if components.is_empty() {
447 return false;
448 }
449 if components
451 .last()
452 .map(|c| c.chars().all(|ch| ch.is_ascii_digit()))
453 .unwrap_or(false)
454 {
455 return false;
456 }
457 components.iter().all(|c| {
458 !c.is_empty()
459 && c.len() <= 63
460 && !c.starts_with('-')
461 && !c.ends_with('-')
462 && c.chars().all(|ch| ch.is_ascii_alphanumeric() || ch == '-')
463 })
464}
465
466pub fn filter_and_sort_interfaces(
468 interfaces: &mut Vec<DiscoveredInterface>,
469 only_available: bool,
470 only_transport: bool,
471) {
472 let now = time::now();
473
474 interfaces.retain(|iface| {
476 let delta = now - iface.last_heard;
477
478 if delta > THRESHOLD_REMOVE {
480 return false;
481 }
482
483 let status = iface.compute_status();
485
486 if only_available && status != DiscoveredStatus::Available {
488 return false;
489 }
490 if only_transport && !iface.transport {
491 return false;
492 }
493
494 true
495 });
496
497 interfaces.sort_by(|a, b| {
499 let status_cmp = b.compute_status().code().cmp(&a.compute_status().code());
500 if status_cmp != std::cmp::Ordering::Equal {
501 return status_cmp;
502 }
503 let value_cmp = b.stamp_value.cmp(&a.stamp_value);
504 if value_cmp != std::cmp::Ordering::Equal {
505 return value_cmp;
506 }
507 b.last_heard
508 .partial_cmp(&a.last_heard)
509 .unwrap_or(std::cmp::Ordering::Equal)
510 });
511}
512
513pub fn discovery_name_hash() -> [u8; 10] {
518 rns_core::destination::name_hash(APP_NAME, &["discovery", "interface"])
519}