1use std::fs;
9use std::io;
10use std::path::PathBuf;
11
12use rns_core::msgpack::{self, Value};
13use rns_core::stamp::{stamp_valid, stamp_value, stamp_workblock};
14use rns_crypto::sha256::sha256;
15
16use crate::time;
17
18pub const NAME: u8 = 0xFF;
24pub const TRANSPORT_ID: u8 = 0xFE;
25pub const INTERFACE_TYPE: u8 = 0x00;
26pub const TRANSPORT: u8 = 0x01;
27pub const REACHABLE_ON: u8 = 0x02;
28pub const LATITUDE: u8 = 0x03;
29pub const LONGITUDE: u8 = 0x04;
30pub const HEIGHT: u8 = 0x05;
31pub const PORT: u8 = 0x06;
32pub const IFAC_NETNAME: u8 = 0x07;
33pub const IFAC_NETKEY: u8 = 0x08;
34pub const FREQUENCY: u8 = 0x09;
35pub const BANDWIDTH: u8 = 0x0A;
36pub const SPREADINGFACTOR: u8 = 0x0B;
37pub const CODINGRATE: u8 = 0x0C;
38pub const MODULATION: u8 = 0x0D;
39pub const CHANNEL: u8 = 0x0E;
40
41pub const APP_NAME: &str = "rnstransport";
43
44pub const DEFAULT_STAMP_VALUE: u8 = 14;
46
47pub const WORKBLOCK_EXPAND_ROUNDS: u32 = 20;
49
50pub const STAMP_SIZE: usize = 32;
52
53pub const THRESHOLD_UNKNOWN: f64 = 24.0 * 60.0 * 60.0;
56pub const THRESHOLD_STALE: f64 = 3.0 * 24.0 * 60.0 * 60.0;
58pub const THRESHOLD_REMOVE: f64 = 7.0 * 24.0 * 60.0 * 60.0;
60
61const STATUS_STALE: i32 = 0;
63const STATUS_UNKNOWN: i32 = 100;
64const STATUS_AVAILABLE: i32 = 1000;
65
66#[derive(Debug, Clone)]
72pub struct DiscoveryConfig {
73 pub discovery_name: String,
75 pub announce_interval: u64,
77 pub stamp_value: u8,
79 pub reachable_on: Option<String>,
81 pub interface_type: String,
83 pub listen_port: Option<u16>,
85 pub latitude: Option<f64>,
87 pub longitude: Option<f64>,
89 pub height: Option<f64>,
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99pub enum DiscoveredStatus {
100 Available,
101 Unknown,
102 Stale,
103}
104
105impl DiscoveredStatus {
106 pub fn code(&self) -> i32 {
108 match self {
109 DiscoveredStatus::Available => STATUS_AVAILABLE,
110 DiscoveredStatus::Unknown => STATUS_UNKNOWN,
111 DiscoveredStatus::Stale => STATUS_STALE,
112 }
113 }
114
115 pub fn as_str(&self) -> &'static str {
117 match self {
118 DiscoveredStatus::Available => "available",
119 DiscoveredStatus::Unknown => "unknown",
120 DiscoveredStatus::Stale => "stale",
121 }
122 }
123}
124
125#[derive(Debug, Clone)]
127pub struct DiscoveredInterface {
128 pub interface_type: String,
130 pub transport: bool,
132 pub name: String,
134 pub discovered: f64,
136 pub last_heard: f64,
138 pub heard_count: u32,
140 pub status: DiscoveredStatus,
142 pub stamp: Vec<u8>,
144 pub stamp_value: u32,
146 pub transport_id: [u8; 16],
148 pub network_id: [u8; 16],
150 pub hops: u8,
152
153 pub latitude: Option<f64>,
155 pub longitude: Option<f64>,
156 pub height: Option<f64>,
157
158 pub reachable_on: Option<String>,
160 pub port: Option<u16>,
161
162 pub frequency: Option<u32>,
164 pub bandwidth: Option<u32>,
165 pub spreading_factor: Option<u8>,
166 pub coding_rate: Option<u8>,
167 pub modulation: Option<String>,
168 pub channel: Option<u8>,
169
170 pub ifac_netname: Option<String>,
172 pub ifac_netkey: Option<String>,
173
174 pub config_entry: Option<String>,
176
177 pub discovery_hash: [u8; 32],
179}
180
181impl DiscoveredInterface {
182 pub fn compute_status(&self) -> DiscoveredStatus {
184 let delta = time::now() - self.last_heard;
185 if delta > THRESHOLD_STALE {
186 DiscoveredStatus::Stale
187 } else if delta > THRESHOLD_UNKNOWN {
188 DiscoveredStatus::Unknown
189 } else {
190 DiscoveredStatus::Available
191 }
192 }
193}
194
195pub struct DiscoveredInterfaceStorage {
201 base_path: PathBuf,
202}
203
204impl DiscoveredInterfaceStorage {
205 pub fn new(base_path: PathBuf) -> Self {
207 Self { base_path }
208 }
209
210 pub fn store(&self, iface: &DiscoveredInterface) -> io::Result<()> {
212 let filename = hex_encode(&iface.discovery_hash);
213 let filepath = self.base_path.join(filename);
214
215 let data = self.serialize_interface(iface)?;
216 fs::write(&filepath, &data)
217 }
218
219 pub fn load(&self, discovery_hash: &[u8; 32]) -> io::Result<Option<DiscoveredInterface>> {
221 let filename = hex_encode(discovery_hash);
222 let filepath = self.base_path.join(filename);
223
224 if !filepath.exists() {
225 return Ok(None);
226 }
227
228 let data = fs::read(&filepath)?;
229 self.deserialize_interface(&data).map(Some)
230 }
231
232 pub fn list(&self) -> io::Result<Vec<DiscoveredInterface>> {
234 let mut interfaces = Vec::new();
235
236 let entries = match fs::read_dir(&self.base_path) {
237 Ok(e) => e,
238 Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(interfaces),
239 Err(e) => return Err(e),
240 };
241
242 for entry in entries {
243 let entry = entry?;
244 let path = entry.path();
245
246 if !path.is_file() {
247 continue;
248 }
249
250 match fs::read(&path) {
251 Ok(data) => {
252 if let Ok(iface) = self.deserialize_interface(&data) {
253 interfaces.push(iface);
254 }
255 }
256 Err(_) => continue,
257 }
258 }
259
260 Ok(interfaces)
261 }
262
263 pub fn remove(&self, discovery_hash: &[u8; 32]) -> io::Result<()> {
265 let filename = hex_encode(discovery_hash);
266 let filepath = self.base_path.join(filename);
267
268 if filepath.exists() {
269 fs::remove_file(&filepath)?;
270 }
271 Ok(())
272 }
273
274 pub fn cleanup(&self) -> io::Result<usize> {
277 let mut removed = 0;
278 let now = time::now();
279
280 let interfaces = self.list()?;
281 for iface in interfaces {
282 if now - iface.last_heard > THRESHOLD_REMOVE {
283 self.remove(&iface.discovery_hash)?;
284 removed += 1;
285 }
286 }
287
288 Ok(removed)
289 }
290
291 fn serialize_interface(&self, iface: &DiscoveredInterface) -> io::Result<Vec<u8>> {
293 let mut entries: Vec<(Value, Value)> = Vec::new();
294
295 entries.push((Value::Str("type".into()), Value::Str(iface.interface_type.clone())));
296 entries.push((Value::Str("transport".into()), Value::Bool(iface.transport)));
297 entries.push((Value::Str("name".into()), Value::Str(iface.name.clone())));
298 entries.push((Value::Str("discovered".into()), Value::Float(iface.discovered)));
299 entries.push((Value::Str("last_heard".into()), Value::Float(iface.last_heard)));
300 entries.push((Value::Str("heard_count".into()), Value::UInt(iface.heard_count as u64)));
301 entries.push((Value::Str("status".into()), Value::Str(iface.status.as_str().into())));
302 entries.push((Value::Str("stamp".into()), Value::Bin(iface.stamp.clone())));
303 entries.push((Value::Str("value".into()), Value::UInt(iface.stamp_value as u64)));
304 entries.push((Value::Str("transport_id".into()), Value::Bin(iface.transport_id.to_vec())));
305 entries.push((Value::Str("network_id".into()), Value::Bin(iface.network_id.to_vec())));
306 entries.push((Value::Str("hops".into()), Value::UInt(iface.hops as u64)));
307
308 if let Some(v) = iface.latitude {
309 entries.push((Value::Str("latitude".into()), Value::Float(v)));
310 }
311 if let Some(v) = iface.longitude {
312 entries.push((Value::Str("longitude".into()), Value::Float(v)));
313 }
314 if let Some(v) = iface.height {
315 entries.push((Value::Str("height".into()), Value::Float(v)));
316 }
317 if let Some(ref v) = iface.reachable_on {
318 entries.push((Value::Str("reachable_on".into()), Value::Str(v.clone())));
319 }
320 if let Some(v) = iface.port {
321 entries.push((Value::Str("port".into()), Value::UInt(v as u64)));
322 }
323 if let Some(v) = iface.frequency {
324 entries.push((Value::Str("frequency".into()), Value::UInt(v as u64)));
325 }
326 if let Some(v) = iface.bandwidth {
327 entries.push((Value::Str("bandwidth".into()), Value::UInt(v as u64)));
328 }
329 if let Some(v) = iface.spreading_factor {
330 entries.push((Value::Str("sf".into()), Value::UInt(v as u64)));
331 }
332 if let Some(v) = iface.coding_rate {
333 entries.push((Value::Str("cr".into()), Value::UInt(v as u64)));
334 }
335 if let Some(ref v) = iface.modulation {
336 entries.push((Value::Str("modulation".into()), Value::Str(v.clone())));
337 }
338 if let Some(v) = iface.channel {
339 entries.push((Value::Str("channel".into()), Value::UInt(v as u64)));
340 }
341 if let Some(ref v) = iface.ifac_netname {
342 entries.push((Value::Str("ifac_netname".into()), Value::Str(v.clone())));
343 }
344 if let Some(ref v) = iface.ifac_netkey {
345 entries.push((Value::Str("ifac_netkey".into()), Value::Str(v.clone())));
346 }
347 if let Some(ref v) = iface.config_entry {
348 entries.push((Value::Str("config_entry".into()), Value::Str(v.clone())));
349 }
350
351 entries.push((Value::Str("discovery_hash".into()), Value::Bin(iface.discovery_hash.to_vec())));
352
353 Ok(msgpack::pack(&Value::Map(entries)))
354 }
355
356 fn deserialize_interface(&self, data: &[u8]) -> io::Result<DiscoveredInterface> {
358 let (value, _) = msgpack::unpack(data).map_err(|e| {
359 io::Error::new(io::ErrorKind::InvalidData, format!("msgpack error: {}", e))
360 })?;
361
362 let get_str = |v: &Value, key: &str| -> io::Result<String> {
364 v.map_get(key)
365 .and_then(|val| val.as_str())
366 .map(|s| s.to_string())
367 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, format!("{} not a string", key)))
368 };
369
370 let get_opt_str = |v: &Value, key: &str| -> Option<String> {
371 v.map_get(key).and_then(|val| val.as_str().map(|s| s.to_string()))
372 };
373
374 let get_bool = |v: &Value, key: &str| -> io::Result<bool> {
375 v.map_get(key)
376 .and_then(|val| val.as_bool())
377 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, format!("{} not a bool", key)))
378 };
379
380 let get_float = |v: &Value, key: &str| -> io::Result<f64> {
381 v.map_get(key)
382 .and_then(|val| val.as_float())
383 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, format!("{} not a float", key)))
384 };
385
386 let get_opt_float = |v: &Value, key: &str| -> Option<f64> {
387 v.map_get(key).and_then(|val| val.as_float())
388 };
389
390 let get_uint = |v: &Value, key: &str| -> io::Result<u64> {
391 v.map_get(key)
392 .and_then(|val| val.as_uint())
393 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, format!("{} not a uint", key)))
394 };
395
396 let get_opt_uint = |v: &Value, key: &str| -> Option<u64> {
397 v.map_get(key).and_then(|val| val.as_uint())
398 };
399
400 let get_bytes = |v: &Value, key: &str| -> io::Result<Vec<u8>> {
401 v.map_get(key)
402 .and_then(|val| val.as_bin())
403 .map(|b| b.to_vec())
404 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, format!("{} not bytes", key)))
405 };
406
407 let transport_id_bytes = get_bytes(&value, "transport_id")?;
408 let mut transport_id = [0u8; 16];
409 if transport_id_bytes.len() == 16 {
410 transport_id.copy_from_slice(&transport_id_bytes);
411 }
412
413 let network_id_bytes = get_bytes(&value, "network_id")?;
414 let mut network_id = [0u8; 16];
415 if network_id_bytes.len() == 16 {
416 network_id.copy_from_slice(&network_id_bytes);
417 }
418
419 let discovery_hash_bytes = get_bytes(&value, "discovery_hash")?;
420 let mut discovery_hash = [0u8; 32];
421 if discovery_hash_bytes.len() == 32 {
422 discovery_hash.copy_from_slice(&discovery_hash_bytes);
423 }
424
425 let status_str = get_str(&value, "status")?;
426 let status = match status_str.as_str() {
427 "available" => DiscoveredStatus::Available,
428 "unknown" => DiscoveredStatus::Unknown,
429 "stale" => DiscoveredStatus::Stale,
430 _ => DiscoveredStatus::Unknown,
431 };
432
433 Ok(DiscoveredInterface {
434 interface_type: get_str(&value, "type")?,
435 transport: get_bool(&value, "transport")?,
436 name: get_str(&value, "name")?,
437 discovered: get_float(&value, "discovered")?,
438 last_heard: get_float(&value, "last_heard")?,
439 heard_count: get_uint(&value, "heard_count")? as u32,
440 status,
441 stamp: get_bytes(&value, "stamp")?,
442 stamp_value: get_uint(&value, "value")? as u32,
443 transport_id,
444 network_id,
445 hops: get_uint(&value, "hops")? as u8,
446 latitude: get_opt_float(&value, "latitude"),
447 longitude: get_opt_float(&value, "longitude"),
448 height: get_opt_float(&value, "height"),
449 reachable_on: get_opt_str(&value, "reachable_on"),
450 port: get_opt_uint(&value, "port").map(|v| v as u16),
451 frequency: get_opt_uint(&value, "frequency").map(|v| v as u32),
452 bandwidth: get_opt_uint(&value, "bandwidth").map(|v| v as u32),
453 spreading_factor: get_opt_uint(&value, "sf").map(|v| v as u8),
454 coding_rate: get_opt_uint(&value, "cr").map(|v| v as u8),
455 modulation: get_opt_str(&value, "modulation"),
456 channel: get_opt_uint(&value, "channel").map(|v| v as u8),
457 ifac_netname: get_opt_str(&value, "ifac_netname"),
458 ifac_netkey: get_opt_str(&value, "ifac_netkey"),
459 config_entry: get_opt_str(&value, "config_entry"),
460 discovery_hash,
461 })
462 }
463}
464
465pub fn parse_interface_announce(
476 app_data: &[u8],
477 announced_identity_hash: &[u8; 16],
478 hops: u8,
479 required_stamp_value: u8,
480) -> Option<DiscoveredInterface> {
481 if app_data.len() <= STAMP_SIZE + 1 {
483 return None;
484 }
485
486 let flags = app_data[0];
488 let payload = &app_data[1..];
489
490 let encrypted = (flags & 0x02) != 0;
492 if encrypted {
493 log::debug!("Ignoring encrypted discovered interface (not supported)");
494 return None;
495 }
496
497 let stamp = &payload[payload.len() - STAMP_SIZE..];
499 let packed = &payload[..payload.len() - STAMP_SIZE];
500
501 let infohash = sha256(packed);
503 let workblock = stamp_workblock(&infohash, WORKBLOCK_EXPAND_ROUNDS);
504
505 if !stamp_valid(stamp, required_stamp_value, &workblock) {
507 log::debug!("Ignoring discovered interface with invalid stamp");
508 return None;
509 }
510
511 let stamp_value = stamp_value(&workblock, stamp);
513
514 let (value, _) = msgpack::unpack(packed).ok()?;
516 let map = value.as_map()?;
517
518 let get_u8_val = |key: u8| -> Option<Value> {
520 for (k, v) in map {
521 if k.as_uint()? as u8 == key {
522 return Some(v.clone());
523 }
524 }
525 None
526 };
527
528 let interface_type = get_u8_val(INTERFACE_TYPE)?.as_str()?.to_string();
530 let transport = get_u8_val(TRANSPORT)?.as_bool()?;
531 let name = get_u8_val(NAME)?
532 .as_str()
533 .unwrap_or(&format!("Discovered {}", interface_type))
534 .to_string();
535
536 let transport_id_val = get_u8_val(TRANSPORT_ID)?;
537 let transport_id_bytes = transport_id_val.as_bin()?;
538 let mut transport_id = [0u8; 16];
539 if transport_id_bytes.len() >= 16 {
540 transport_id.copy_from_slice(&transport_id_bytes[..16]);
541 }
542
543 let latitude = get_u8_val(LATITUDE).and_then(|v| v.as_float());
545 let longitude = get_u8_val(LONGITUDE).and_then(|v| v.as_float());
546 let height = get_u8_val(HEIGHT).and_then(|v| v.as_float());
547 let reachable_on = get_u8_val(REACHABLE_ON).and_then(|v| v.as_str().map(|s| s.to_string()));
548 let port = get_u8_val(PORT).and_then(|v| v.as_uint().map(|n| n as u16));
549 let frequency = get_u8_val(FREQUENCY).and_then(|v| v.as_uint().map(|n| n as u32));
550 let bandwidth = get_u8_val(BANDWIDTH).and_then(|v| v.as_uint().map(|n| n as u32));
551 let spreading_factor = get_u8_val(SPREADINGFACTOR).and_then(|v| v.as_uint().map(|n| n as u8));
552 let coding_rate = get_u8_val(CODINGRATE).and_then(|v| v.as_uint().map(|n| n as u8));
553 let modulation = get_u8_val(MODULATION).and_then(|v| v.as_str().map(|s| s.to_string()));
554 let channel = get_u8_val(CHANNEL).and_then(|v| v.as_uint().map(|n| n as u8));
555 let ifac_netname = get_u8_val(IFAC_NETNAME).and_then(|v| v.as_str().map(|s| s.to_string()));
556 let ifac_netkey = get_u8_val(IFAC_NETKEY).and_then(|v| v.as_str().map(|s| s.to_string()));
557
558 let discovery_hash = compute_discovery_hash(&transport_id, &name);
560
561 let config_entry = generate_config_entry(
563 &interface_type,
564 &name,
565 &transport_id,
566 reachable_on.as_deref(),
567 port,
568 frequency,
569 bandwidth,
570 spreading_factor,
571 coding_rate,
572 modulation.as_deref(),
573 ifac_netname.as_deref(),
574 ifac_netkey.as_deref(),
575 );
576
577 let now = time::now();
578
579 Some(DiscoveredInterface {
580 interface_type,
581 transport,
582 name,
583 discovered: now,
584 last_heard: now,
585 heard_count: 0,
586 status: DiscoveredStatus::Available,
587 stamp: stamp.to_vec(),
588 stamp_value,
589 transport_id,
590 network_id: *announced_identity_hash,
591 hops,
592 latitude,
593 longitude,
594 height,
595 reachable_on,
596 port,
597 frequency,
598 bandwidth,
599 spreading_factor,
600 coding_rate,
601 modulation,
602 channel,
603 ifac_netname,
604 ifac_netkey,
605 config_entry,
606 discovery_hash,
607 })
608}
609
610pub fn compute_discovery_hash(transport_id: &[u8; 16], name: &str) -> [u8; 32] {
612 let mut material = Vec::with_capacity(16 + name.len());
613 material.extend_from_slice(transport_id);
614 material.extend_from_slice(name.as_bytes());
615 sha256(&material)
616}
617
618fn generate_config_entry(
620 interface_type: &str,
621 name: &str,
622 transport_id: &[u8; 16],
623 reachable_on: Option<&str>,
624 port: Option<u16>,
625 frequency: Option<u32>,
626 bandwidth: Option<u32>,
627 spreading_factor: Option<u8>,
628 coding_rate: Option<u8>,
629 modulation: Option<&str>,
630 ifac_netname: Option<&str>,
631 ifac_netkey: Option<&str>,
632) -> Option<String> {
633 let transport_id_hex = hex_encode(transport_id);
634 let netname_str = ifac_netname.map(|n| format!("\n network_name = {}", n)).unwrap_or_default();
635 let netkey_str = ifac_netkey.map(|k| format!("\n passphrase = {}", k)).unwrap_or_default();
636 let identity_str = format!("\n transport_identity = {}", transport_id_hex);
637
638 match interface_type {
639 "BackboneInterface" | "TCPServerInterface" => {
640 let reachable = reachable_on.unwrap_or("unknown");
641 let port_val = port.unwrap_or(4242);
642 Some(format!(
643 "[[{}]]\n type = BackboneInterface\n enabled = yes\n remote = {}\n target_port = {}{}{}{}",
644 name, reachable, port_val, identity_str, netname_str, netkey_str
645 ))
646 }
647 "I2PInterface" => {
648 let reachable = reachable_on.unwrap_or("unknown");
649 Some(format!(
650 "[[{}]]\n type = I2PInterface\n enabled = yes\n peers = {}{}{}{}",
651 name, reachable, identity_str, netname_str, netkey_str
652 ))
653 }
654 "RNodeInterface" => {
655 let freq_str = frequency.map(|f| format!("\n frequency = {}", f)).unwrap_or_default();
656 let bw_str = bandwidth.map(|b| format!("\n bandwidth = {}", b)).unwrap_or_default();
657 let sf_str = spreading_factor.map(|s| format!("\n spreadingfactor = {}", s)).unwrap_or_default();
658 let cr_str = coding_rate.map(|c| format!("\n codingrate = {}", c)).unwrap_or_default();
659 Some(format!(
660 "[[{}]]\n type = RNodeInterface\n enabled = yes\n port = {}{}{}{}{}{}{}{}",
661 name, "", freq_str, bw_str, sf_str, cr_str, identity_str, netname_str, netkey_str
662 ))
663 }
664 "KISSInterface" => {
665 let freq_str = frequency.map(|f| format!("\n # Frequency: {}", f)).unwrap_or_default();
666 let bw_str = bandwidth.map(|b| format!("\n # Bandwidth: {}", b)).unwrap_or_default();
667 let mod_str = modulation.map(|m| format!("\n # Modulation: {}", m)).unwrap_or_default();
668 Some(format!(
669 "[[{}]]\n type = KISSInterface\n enabled = yes\n port = {}{}{}{}{}{}{}",
670 name, "", freq_str, bw_str, mod_str, identity_str, netname_str, netkey_str
671 ))
672 }
673 "WeaveInterface" => {
674 Some(format!(
675 "[[{}]]\n type = WeaveInterface\n enabled = yes\n port = {}{}{}{}",
676 name, "", identity_str, netname_str, netkey_str
677 ))
678 }
679 _ => None,
680 }
681}
682
683fn hex_encode(bytes: &[u8]) -> String {
689 bytes.iter().map(|b| format!("{:02x}", b)).collect()
690}
691
692pub fn is_ip_address(s: &str) -> bool {
694 s.parse::<std::net::IpAddr>().is_ok()
695}
696
697pub fn is_hostname(s: &str) -> bool {
699 let s = s.strip_suffix('.').unwrap_or(s);
700 if s.len() > 253 {
701 return false;
702 }
703 let components: Vec<&str> = s.split('.').collect();
704 if components.is_empty() {
705 return false;
706 }
707 if components.last().map(|c| c.chars().all(|ch| ch.is_ascii_digit())).unwrap_or(false) {
709 return false;
710 }
711 components.iter().all(|c| {
712 !c.is_empty()
713 && c.len() <= 63
714 && !c.starts_with('-')
715 && !c.ends_with('-')
716 && c.chars().all(|ch| ch.is_ascii_alphanumeric() || ch == '-')
717 })
718}
719
720pub fn filter_and_sort_interfaces(
722 interfaces: &mut Vec<DiscoveredInterface>,
723 only_available: bool,
724 only_transport: bool,
725) {
726 let now = time::now();
727
728 interfaces.retain(|iface| {
730 let delta = now - iface.last_heard;
731
732 if delta > THRESHOLD_REMOVE {
734 return false;
735 }
736
737 let status = iface.compute_status();
739
740 if only_available && status != DiscoveredStatus::Available {
742 return false;
743 }
744 if only_transport && !iface.transport {
745 return false;
746 }
747
748 true
749 });
750
751 interfaces.sort_by(|a, b| {
753 let status_cmp = b.compute_status().code().cmp(&a.compute_status().code());
754 if status_cmp != std::cmp::Ordering::Equal {
755 return status_cmp;
756 }
757 let value_cmp = b.stamp_value.cmp(&a.stamp_value);
758 if value_cmp != std::cmp::Ordering::Equal {
759 return value_cmp;
760 }
761 b.last_heard.partial_cmp(&a.last_heard).unwrap_or(std::cmp::Ordering::Equal)
762 });
763}
764
765pub fn generate_discovery_stamp(
773 packed_data: &[u8],
774 stamp_cost: u8,
775) -> ([u8; STAMP_SIZE], u32) {
776 use std::sync::atomic::{AtomicBool, Ordering};
777 use std::sync::{Arc, Mutex};
778 use rns_crypto::{OsRng, Rng};
779
780 let infohash = sha256(packed_data);
781 let workblock = stamp_workblock(&infohash, WORKBLOCK_EXPAND_ROUNDS);
782
783 let found: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));
784 let result: Arc<Mutex<Option<[u8; STAMP_SIZE]>>> = Arc::new(Mutex::new(None));
785
786 let num_threads = rayon::current_num_threads();
787
788 rayon::scope(|s| {
789 for _ in 0..num_threads {
790 let found = found.clone();
791 let result = result.clone();
792 let workblock = &workblock;
793 s.spawn(move |_| {
794 let mut rng = OsRng;
795 let mut nonce = [0u8; STAMP_SIZE];
796 loop {
797 if found.load(Ordering::Relaxed) {
798 return;
799 }
800 rng.fill_bytes(&mut nonce);
801 if stamp_valid(&nonce, stamp_cost, workblock) {
802 let mut r = result.lock().unwrap();
803 if r.is_none() {
804 *r = Some(nonce);
805 }
806 found.store(true, Ordering::Relaxed);
807 return;
808 }
809 }
810 });
811 }
812 });
813
814 let stamp = result.lock().unwrap().take().expect("stamp search must find result");
815 let value = stamp_value(&workblock, &stamp);
816 (stamp, value)
817}
818
819#[derive(Debug, Clone)]
825pub struct DiscoverableInterface {
826 pub config: DiscoveryConfig,
827 pub transport_enabled: bool,
829 pub ifac_netname: Option<String>,
831 pub ifac_netkey: Option<String>,
833}
834
835pub struct StampResult {
837 pub index: usize,
839 pub app_data: Vec<u8>,
841}
842
843pub struct InterfaceAnnouncer {
849 transport_id: [u8; 16],
851 interfaces: Vec<DiscoverableInterface>,
853 last_announced: Vec<f64>,
855 stamp_rx: std::sync::mpsc::Receiver<StampResult>,
857 stamp_tx: std::sync::mpsc::Sender<StampResult>,
859 stamp_pending: bool,
861}
862
863impl InterfaceAnnouncer {
864 pub fn new(transport_id: [u8; 16], interfaces: Vec<DiscoverableInterface>) -> Self {
866 let n = interfaces.len();
867 let (stamp_tx, stamp_rx) = std::sync::mpsc::channel();
868 InterfaceAnnouncer {
869 transport_id,
870 interfaces,
871 last_announced: vec![0.0; n],
872 stamp_rx,
873 stamp_tx,
874 stamp_pending: false,
875 }
876 }
877
878 pub fn maybe_start(&mut self, now: f64) {
882 if self.stamp_pending {
883 return;
884 }
885 let due_index = self.interfaces.iter().enumerate().find_map(|(i, iface)| {
886 let elapsed = now - self.last_announced[i];
887 if elapsed >= iface.config.announce_interval as f64 {
888 Some(i)
889 } else {
890 None
891 }
892 });
893
894 if let Some(idx) = due_index {
895 let packed = self.pack_interface_info(idx);
896 let stamp_cost = self.interfaces[idx].config.stamp_value;
897 let name = self.interfaces[idx].config.discovery_name.clone();
898 let tx = self.stamp_tx.clone();
899
900 log::info!(
901 "Spawning discovery stamp generation (cost={}) for '{}'...",
902 stamp_cost,
903 name,
904 );
905
906 self.stamp_pending = true;
907 self.last_announced[idx] = now;
908
909 std::thread::spawn(move || {
910 let (stamp, value) = generate_discovery_stamp(&packed, stamp_cost);
911 log::info!(
912 "Discovery stamp generated (value={}) for '{}'",
913 value,
914 name,
915 );
916
917 let flags: u8 = 0x00; let mut app_data = Vec::with_capacity(1 + packed.len() + STAMP_SIZE);
919 app_data.push(flags);
920 app_data.extend_from_slice(&packed);
921 app_data.extend_from_slice(&stamp);
922
923 let _ = tx.send(StampResult {
924 index: idx,
925 app_data,
926 });
927 });
928 }
929 }
930
931 pub fn poll_ready(&mut self) -> Option<StampResult> {
934 match self.stamp_rx.try_recv() {
935 Ok(result) => {
936 self.stamp_pending = false;
937 Some(result)
938 }
939 Err(_) => None,
940 }
941 }
942
943 fn pack_interface_info(&self, index: usize) -> Vec<u8> {
945 let iface = &self.interfaces[index];
946 let mut entries: Vec<(msgpack::Value, msgpack::Value)> = Vec::new();
947
948 entries.push((
949 msgpack::Value::UInt(INTERFACE_TYPE as u64),
950 msgpack::Value::Str(iface.config.interface_type.clone()),
951 ));
952 entries.push((
953 msgpack::Value::UInt(TRANSPORT as u64),
954 msgpack::Value::Bool(iface.transport_enabled),
955 ));
956 entries.push((
957 msgpack::Value::UInt(NAME as u64),
958 msgpack::Value::Str(iface.config.discovery_name.clone()),
959 ));
960 entries.push((
961 msgpack::Value::UInt(TRANSPORT_ID as u64),
962 msgpack::Value::Bin(self.transport_id.to_vec()),
963 ));
964 if let Some(ref reachable) = iface.config.reachable_on {
965 entries.push((
966 msgpack::Value::UInt(REACHABLE_ON as u64),
967 msgpack::Value::Str(reachable.clone()),
968 ));
969 }
970 if let Some(port) = iface.config.listen_port {
971 entries.push((
972 msgpack::Value::UInt(PORT as u64),
973 msgpack::Value::UInt(port as u64),
974 ));
975 }
976 if let Some(lat) = iface.config.latitude {
977 entries.push((
978 msgpack::Value::UInt(LATITUDE as u64),
979 msgpack::Value::Float(lat),
980 ));
981 }
982 if let Some(lon) = iface.config.longitude {
983 entries.push((
984 msgpack::Value::UInt(LONGITUDE as u64),
985 msgpack::Value::Float(lon),
986 ));
987 }
988 if let Some(h) = iface.config.height {
989 entries.push((
990 msgpack::Value::UInt(HEIGHT as u64),
991 msgpack::Value::Float(h),
992 ));
993 }
994 if let Some(ref netname) = iface.ifac_netname {
995 entries.push((
996 msgpack::Value::UInt(IFAC_NETNAME as u64),
997 msgpack::Value::Str(netname.clone()),
998 ));
999 }
1000 if let Some(ref netkey) = iface.ifac_netkey {
1001 entries.push((
1002 msgpack::Value::UInt(IFAC_NETKEY as u64),
1003 msgpack::Value::Str(netkey.clone()),
1004 ));
1005 }
1006
1007 msgpack::pack(&msgpack::Value::Map(entries))
1008 }
1009
1010}
1011
1012pub fn discovery_name_hash() -> [u8; 10] {
1017 rns_core::destination::name_hash(APP_NAME, &["discovery", "interface"])
1018}
1019
1020#[cfg(test)]
1025mod tests {
1026 use super::*;
1027
1028 #[test]
1029 fn test_hex_encode() {
1030 assert_eq!(hex_encode(&[0x00, 0xff, 0x12]), "00ff12");
1031 assert_eq!(hex_encode(&[]), "");
1032 }
1033
1034 #[test]
1035 fn test_compute_discovery_hash() {
1036 let transport_id = [0x42u8; 16];
1037 let name = "TestInterface";
1038 let hash = compute_discovery_hash(&transport_id, name);
1039
1040 let hash2 = compute_discovery_hash(&transport_id, name);
1042 assert_eq!(hash, hash2);
1043
1044 let hash3 = compute_discovery_hash(&transport_id, "OtherInterface");
1046 assert_ne!(hash, hash3);
1047 }
1048
1049 #[test]
1050 fn test_is_ip_address() {
1051 assert!(is_ip_address("192.168.1.1"));
1052 assert!(is_ip_address("::1"));
1053 assert!(is_ip_address("2001:db8::1"));
1054 assert!(!is_ip_address("not-an-ip"));
1055 assert!(!is_ip_address("hostname.example.com"));
1056 }
1057
1058 #[test]
1059 fn test_is_hostname() {
1060 assert!(is_hostname("example.com"));
1061 assert!(is_hostname("sub.example.com"));
1062 assert!(is_hostname("my-node"));
1063 assert!(is_hostname("my-node.example.com"));
1064 assert!(!is_hostname(""));
1065 assert!(!is_hostname("-invalid"));
1066 assert!(!is_hostname("invalid-"));
1067 assert!(!is_hostname("a".repeat(300).as_str()));
1068 }
1069
1070 #[test]
1071 fn test_discovered_status() {
1072 let now = time::now();
1073
1074 let mut iface = DiscoveredInterface {
1075 interface_type: "TestInterface".into(),
1076 transport: true,
1077 name: "Test".into(),
1078 discovered: now,
1079 last_heard: now,
1080 heard_count: 0,
1081 status: DiscoveredStatus::Available,
1082 stamp: vec![],
1083 stamp_value: 14,
1084 transport_id: [0u8; 16],
1085 network_id: [0u8; 16],
1086 hops: 0,
1087 latitude: None,
1088 longitude: None,
1089 height: None,
1090 reachable_on: None,
1091 port: None,
1092 frequency: None,
1093 bandwidth: None,
1094 spreading_factor: None,
1095 coding_rate: None,
1096 modulation: None,
1097 channel: None,
1098 ifac_netname: None,
1099 ifac_netkey: None,
1100 config_entry: None,
1101 discovery_hash: [0u8; 32],
1102 };
1103
1104 assert_eq!(iface.compute_status(), DiscoveredStatus::Available);
1106
1107 iface.last_heard = now - THRESHOLD_UNKNOWN - 3600.0;
1109 assert_eq!(iface.compute_status(), DiscoveredStatus::Unknown);
1110
1111 iface.last_heard = now - THRESHOLD_STALE - 3600.0;
1113 assert_eq!(iface.compute_status(), DiscoveredStatus::Stale);
1114 }
1115
1116 #[test]
1117 fn test_storage_roundtrip() {
1118 use std::sync::atomic::{AtomicU64, Ordering};
1119 static TEST_COUNTER: AtomicU64 = AtomicU64::new(0);
1120
1121 let id = TEST_COUNTER.fetch_add(1, Ordering::Relaxed);
1122 let dir = std::env::temp_dir().join(format!("rns-discovery-test-{}-{}", std::process::id(), id));
1123 let _ = fs::remove_dir_all(&dir);
1124 fs::create_dir_all(&dir).unwrap();
1125
1126 let storage = DiscoveredInterfaceStorage::new(dir.clone());
1127
1128 let mut iface = DiscoveredInterface {
1129 interface_type: "BackboneInterface".into(),
1130 transport: true,
1131 name: "TestNode".into(),
1132 discovered: 1700000000.0,
1133 last_heard: 1700001000.0,
1134 heard_count: 5,
1135 status: DiscoveredStatus::Available,
1136 stamp: vec![0x42u8; 64],
1137 stamp_value: 18,
1138 transport_id: [0x01u8; 16],
1139 network_id: [0x02u8; 16],
1140 hops: 2,
1141 latitude: Some(45.0),
1142 longitude: Some(9.0),
1143 height: Some(100.0),
1144 reachable_on: Some("example.com".into()),
1145 port: Some(4242),
1146 frequency: None,
1147 bandwidth: None,
1148 spreading_factor: None,
1149 coding_rate: None,
1150 modulation: None,
1151 channel: None,
1152 ifac_netname: Some("mynetwork".into()),
1153 ifac_netkey: Some("secretkey".into()),
1154 config_entry: Some("test config".into()),
1155 discovery_hash: compute_discovery_hash(&[0x01u8; 16], "TestNode"),
1156 };
1157
1158 storage.store(&iface).unwrap();
1160
1161 let loaded = storage.load(&iface.discovery_hash).unwrap().unwrap();
1163
1164 assert_eq!(loaded.interface_type, iface.interface_type);
1165 assert_eq!(loaded.name, iface.name);
1166 assert_eq!(loaded.stamp_value, iface.stamp_value);
1167 assert_eq!(loaded.transport_id, iface.transport_id);
1168 assert_eq!(loaded.hops, iface.hops);
1169 assert_eq!(loaded.latitude, iface.latitude);
1170 assert_eq!(loaded.reachable_on, iface.reachable_on);
1171 assert_eq!(loaded.port, iface.port);
1172
1173 let list = storage.list().unwrap();
1175 assert_eq!(list.len(), 1);
1176
1177 storage.remove(&iface.discovery_hash).unwrap();
1179 let list = storage.list().unwrap();
1180 assert!(list.is_empty());
1181
1182 let _ = fs::remove_dir_all(&dir);
1183 }
1184
1185 #[test]
1186 fn test_filter_and_sort() {
1187 let now = time::now();
1188
1189 let ifaces = vec![
1190 DiscoveredInterface {
1191 interface_type: "A".into(),
1192 transport: true,
1193 name: "high-value-stale".into(),
1194 discovered: now,
1195 last_heard: now - THRESHOLD_STALE - 100.0, heard_count: 0,
1197 status: DiscoveredStatus::Stale,
1198 stamp: vec![],
1199 stamp_value: 20,
1200 transport_id: [0u8; 16],
1201 network_id: [0u8; 16],
1202 hops: 0,
1203 latitude: None,
1204 longitude: None,
1205 height: None,
1206 reachable_on: None,
1207 port: None,
1208 frequency: None,
1209 bandwidth: None,
1210 spreading_factor: None,
1211 coding_rate: None,
1212 modulation: None,
1213 channel: None,
1214 ifac_netname: None,
1215 ifac_netkey: None,
1216 config_entry: None,
1217 discovery_hash: [0u8; 32],
1218 },
1219 DiscoveredInterface {
1220 interface_type: "B".into(),
1221 transport: true,
1222 name: "low-value-available".into(),
1223 discovered: now,
1224 last_heard: now,
1225 heard_count: 0,
1226 status: DiscoveredStatus::Available,
1227 stamp: vec![],
1228 stamp_value: 10,
1229 transport_id: [0u8; 16],
1230 network_id: [0u8; 16],
1231 hops: 0,
1232 latitude: None,
1233 longitude: None,
1234 height: None,
1235 reachable_on: None,
1236 port: None,
1237 frequency: None,
1238 bandwidth: None,
1239 spreading_factor: None,
1240 coding_rate: None,
1241 modulation: None,
1242 channel: None,
1243 ifac_netname: None,
1244 ifac_netkey: None,
1245 config_entry: None,
1246 discovery_hash: [0u8; 32],
1247 },
1248 DiscoveredInterface {
1249 interface_type: "C".into(),
1250 transport: false,
1251 name: "no-transport".into(),
1252 discovered: now,
1253 last_heard: now,
1254 heard_count: 0,
1255 status: DiscoveredStatus::Available,
1256 stamp: vec![],
1257 stamp_value: 15,
1258 transport_id: [0u8; 16],
1259 network_id: [0u8; 16],
1260 hops: 0,
1261 latitude: None,
1262 longitude: None,
1263 height: None,
1264 reachable_on: None,
1265 port: None,
1266 frequency: None,
1267 bandwidth: None,
1268 spreading_factor: None,
1269 coding_rate: None,
1270 modulation: None,
1271 channel: None,
1272 ifac_netname: None,
1273 ifac_netkey: None,
1274 config_entry: None,
1275 discovery_hash: [0u8; 32],
1276 },
1277 ];
1278
1279 let mut filtered = ifaces.clone();
1281 filter_and_sort_interfaces(&mut filtered, true, false);
1282 assert_eq!(filtered.len(), 2);
1284
1285 let mut filtered = ifaces.clone();
1287 filter_and_sort_interfaces(&mut filtered, false, true);
1288 assert_eq!(filtered.len(), 2);
1290
1291 let mut filtered = ifaces.clone();
1293 filter_and_sort_interfaces(&mut filtered, true, true);
1294 assert_eq!(filtered.len(), 1);
1296 assert_eq!(filtered[0].name, "low-value-available");
1297 }
1298
1299 #[test]
1300 fn test_discovery_name_hash_is_deterministic() {
1301 let h1 = discovery_name_hash();
1302 let h2 = discovery_name_hash();
1303 assert_eq!(h1, h2);
1304 assert_eq!(h1.len(), 10);
1306 let expected = rns_core::destination::name_hash(
1308 APP_NAME,
1309 &["discovery", "interface"],
1310 );
1311 assert_eq!(h1, expected);
1312 }
1313
1314 #[test]
1315 fn test_announcer_next_due_and_maybe_start() {
1316 let transport_id = [0xABu8; 16];
1317 let iface = DiscoverableInterface {
1318 config: DiscoveryConfig {
1319 discovery_name: "TestBB".into(),
1320 announce_interval: 600, stamp_value: 8, reachable_on: Some("10.0.0.1".into()),
1323 interface_type: "BackboneInterface".into(),
1324 listen_port: Some(4242),
1325 latitude: None,
1326 longitude: None,
1327 height: None,
1328 },
1329 transport_enabled: true,
1330 ifac_netname: None,
1331 ifac_netkey: None,
1332 };
1333 let mut announcer = InterfaceAnnouncer::new(transport_id, vec![iface]);
1334
1335 announcer.maybe_start(1000.0);
1338 assert!(announcer.stamp_pending);
1339
1340 announcer.maybe_start(1000.0);
1342 assert!(announcer.stamp_pending);
1343
1344 let result = announcer.stamp_rx.recv_timeout(std::time::Duration::from_secs(30))
1346 .expect("stamp generation timed out");
1347 assert_eq!(result.index, 0);
1348 assert!(!result.app_data.is_empty());
1349 assert_eq!(result.app_data[0], 0x00);
1351 assert!(result.app_data.len() > STAMP_SIZE + 1);
1353
1354 announcer.stamp_pending = false;
1356
1357 assert!(announcer.poll_ready().is_none()); }
1360
1361 #[test]
1362 fn test_pack_parse_roundtrip() {
1363 let transport_id = [0x42u8; 16];
1365 let identity_hash = [0xFFu8; 16];
1366 let iface = DiscoverableInterface {
1367 config: DiscoveryConfig {
1368 discovery_name: "RoundtripNode".into(),
1369 announce_interval: 300,
1370 stamp_value: 8, reachable_on: Some("192.168.1.100".into()),
1372 interface_type: "TCPServerInterface".into(),
1373 listen_port: Some(5555),
1374 latitude: Some(45.464),
1375 longitude: Some(9.190),
1376 height: Some(120.0),
1377 },
1378 transport_enabled: true,
1379 ifac_netname: Some("testnet".into()),
1380 ifac_netkey: Some("secretkey".into()),
1381 };
1382 let mut announcer = InterfaceAnnouncer::new(transport_id, vec![iface]);
1383
1384 announcer.maybe_start(1000.0);
1386 let result = announcer.stamp_rx.recv_timeout(std::time::Duration::from_secs(30))
1387 .expect("stamp generation timed out");
1388
1389 let discovered = parse_interface_announce(
1391 &result.app_data,
1392 &identity_hash,
1393 0, 8, ).expect("parse_interface_announce should succeed on our own data");
1396
1397 assert_eq!(discovered.interface_type, "TCPServerInterface");
1398 assert_eq!(discovered.name, "RoundtripNode");
1399 assert_eq!(discovered.transport, true);
1400 assert_eq!(discovered.transport_id, transport_id);
1401 assert_eq!(discovered.network_id, identity_hash);
1402 assert_eq!(discovered.reachable_on.as_deref(), Some("192.168.1.100"));
1403 assert_eq!(discovered.port, Some(5555));
1404 assert_eq!(discovered.ifac_netname.as_deref(), Some("testnet"));
1405 assert_eq!(discovered.ifac_netkey.as_deref(), Some("secretkey"));
1406 assert!(discovered.stamp_value >= 8);
1407 assert_eq!(discovered.hops, 0);
1408 assert!((discovered.latitude.unwrap() - 45.464).abs() < 0.001);
1409 assert!((discovered.longitude.unwrap() - 9.190).abs() < 0.001);
1410 assert!((discovered.height.unwrap() - 120.0).abs() < 0.1);
1411 }
1412}