1pub use crate::common::discovery::*;
13
14use std::fs;
15use std::io;
16use std::path::PathBuf;
17
18use rns_core::msgpack::{self, Value};
19use rns_core::stamp::{stamp_valid, stamp_workblock};
20use rns_crypto::sha256::sha256;
21
22use crate::time;
23
24pub struct DiscoveredInterfaceStorage {
30 base_path: PathBuf,
31}
32
33impl DiscoveredInterfaceStorage {
34 pub fn new(base_path: PathBuf) -> Self {
36 Self { base_path }
37 }
38
39 pub fn store(&self, iface: &DiscoveredInterface) -> io::Result<()> {
41 let filename = hex_encode(&iface.discovery_hash);
42 let filepath = self.base_path.join(filename);
43
44 let data = self.serialize_interface(iface)?;
45 fs::write(&filepath, &data)
46 }
47
48 pub fn load(&self, discovery_hash: &[u8; 32]) -> io::Result<Option<DiscoveredInterface>> {
50 let filename = hex_encode(discovery_hash);
51 let filepath = self.base_path.join(filename);
52
53 if !filepath.exists() {
54 return Ok(None);
55 }
56
57 let data = fs::read(&filepath)?;
58 self.deserialize_interface(&data).map(Some)
59 }
60
61 pub fn list(&self) -> io::Result<Vec<DiscoveredInterface>> {
63 let mut interfaces = Vec::new();
64
65 let entries = match fs::read_dir(&self.base_path) {
66 Ok(e) => e,
67 Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(interfaces),
68 Err(e) => return Err(e),
69 };
70
71 for entry in entries {
72 let entry = entry?;
73 let path = entry.path();
74
75 if !path.is_file() {
76 continue;
77 }
78
79 match fs::read(&path) {
80 Ok(data) => {
81 if let Ok(iface) = self.deserialize_interface(&data) {
82 interfaces.push(iface);
83 }
84 }
85 Err(_) => continue,
86 }
87 }
88
89 Ok(interfaces)
90 }
91
92 pub fn remove(&self, discovery_hash: &[u8; 32]) -> io::Result<()> {
94 let filename = hex_encode(discovery_hash);
95 let filepath = self.base_path.join(filename);
96
97 if filepath.exists() {
98 fs::remove_file(&filepath)?;
99 }
100 Ok(())
101 }
102
103 pub fn cleanup(&self) -> io::Result<usize> {
106 let mut removed = 0;
107 let now = time::now();
108
109 let interfaces = self.list()?;
110 for iface in interfaces {
111 let invalid_reachable_on = iface
112 .reachable_on
113 .as_ref()
114 .map(|reachable_on| !(is_ip_address(reachable_on) || is_hostname(reachable_on)))
115 .unwrap_or(false);
116
117 if !is_discoverable_type(&iface.interface_type)
118 || invalid_reachable_on
119 || now - iface.last_heard > THRESHOLD_REMOVE
120 {
121 self.remove(&iface.discovery_hash)?;
122 removed += 1;
123 }
124 }
125
126 Ok(removed)
127 }
128
129 fn serialize_interface(&self, iface: &DiscoveredInterface) -> io::Result<Vec<u8>> {
131 let mut entries: Vec<(Value, Value)> = Vec::new();
132
133 entries.push((
134 Value::Str("type".into()),
135 Value::Str(iface.interface_type.clone()),
136 ));
137 entries.push((Value::Str("transport".into()), Value::Bool(iface.transport)));
138 entries.push((Value::Str("name".into()), Value::Str(iface.name.clone())));
139 entries.push((
140 Value::Str("discovered".into()),
141 Value::Float(iface.discovered),
142 ));
143 entries.push((
144 Value::Str("last_heard".into()),
145 Value::Float(iface.last_heard),
146 ));
147 entries.push((
148 Value::Str("heard_count".into()),
149 Value::UInt(iface.heard_count as u64),
150 ));
151 entries.push((
152 Value::Str("status".into()),
153 Value::Str(iface.status.as_str().into()),
154 ));
155 entries.push((Value::Str("stamp".into()), Value::Bin(iface.stamp.clone())));
156 entries.push((
157 Value::Str("value".into()),
158 Value::UInt(iface.stamp_value as u64),
159 ));
160 entries.push((
161 Value::Str("transport_id".into()),
162 Value::Bin(iface.transport_id.to_vec()),
163 ));
164 entries.push((
165 Value::Str("network_id".into()),
166 Value::Bin(iface.network_id.to_vec()),
167 ));
168 entries.push((Value::Str("hops".into()), Value::UInt(iface.hops as u64)));
169
170 if let Some(v) = iface.latitude {
171 entries.push((Value::Str("latitude".into()), Value::Float(v)));
172 }
173 if let Some(v) = iface.longitude {
174 entries.push((Value::Str("longitude".into()), Value::Float(v)));
175 }
176 if let Some(v) = iface.height {
177 entries.push((Value::Str("height".into()), Value::Float(v)));
178 }
179 if let Some(ref v) = iface.reachable_on {
180 entries.push((Value::Str("reachable_on".into()), Value::Str(v.clone())));
181 }
182 if let Some(v) = iface.port {
183 entries.push((Value::Str("port".into()), Value::UInt(v as u64)));
184 }
185 if let Some(v) = iface.frequency {
186 entries.push((Value::Str("frequency".into()), Value::UInt(v as u64)));
187 }
188 if let Some(v) = iface.bandwidth {
189 entries.push((Value::Str("bandwidth".into()), Value::UInt(v as u64)));
190 }
191 if let Some(v) = iface.spreading_factor {
192 entries.push((Value::Str("sf".into()), Value::UInt(v as u64)));
193 }
194 if let Some(v) = iface.coding_rate {
195 entries.push((Value::Str("cr".into()), Value::UInt(v as u64)));
196 }
197 if let Some(ref v) = iface.modulation {
198 entries.push((Value::Str("modulation".into()), Value::Str(v.clone())));
199 }
200 if let Some(v) = iface.channel {
201 entries.push((Value::Str("channel".into()), Value::UInt(v as u64)));
202 }
203 if let Some(ref v) = iface.ifac_netname {
204 entries.push((Value::Str("ifac_netname".into()), Value::Str(v.clone())));
205 }
206 if let Some(ref v) = iface.ifac_netkey {
207 entries.push((Value::Str("ifac_netkey".into()), Value::Str(v.clone())));
208 }
209 if let Some(ref v) = iface.config_entry {
210 entries.push((Value::Str("config_entry".into()), Value::Str(v.clone())));
211 }
212
213 entries.push((
214 Value::Str("discovery_hash".into()),
215 Value::Bin(iface.discovery_hash.to_vec()),
216 ));
217
218 Ok(msgpack::pack(&Value::Map(entries)))
219 }
220
221 fn deserialize_interface(&self, data: &[u8]) -> io::Result<DiscoveredInterface> {
223 let (value, _) = msgpack::unpack(data).map_err(|e| {
224 io::Error::new(io::ErrorKind::InvalidData, format!("msgpack error: {}", e))
225 })?;
226
227 let get_str = |v: &Value, key: &str| -> io::Result<String> {
229 v.map_get(key)
230 .and_then(|val| val.as_str())
231 .map(|s| s.to_string())
232 .ok_or_else(|| {
233 io::Error::new(io::ErrorKind::InvalidData, format!("{} not a string", key))
234 })
235 };
236
237 let get_opt_str = |v: &Value, key: &str| -> Option<String> {
238 v.map_get(key)
239 .and_then(|val| val.as_str().map(|s| s.to_string()))
240 };
241
242 let get_bool = |v: &Value, key: &str| -> io::Result<bool> {
243 v.map_get(key).and_then(|val| val.as_bool()).ok_or_else(|| {
244 io::Error::new(io::ErrorKind::InvalidData, format!("{} not a bool", key))
245 })
246 };
247
248 let get_float = |v: &Value, key: &str| -> io::Result<f64> {
249 v.map_get(key)
250 .and_then(|val| val.as_float())
251 .ok_or_else(|| {
252 io::Error::new(io::ErrorKind::InvalidData, format!("{} not a float", key))
253 })
254 };
255
256 let get_opt_float =
257 |v: &Value, key: &str| -> Option<f64> { v.map_get(key).and_then(|val| val.as_float()) };
258
259 let get_uint = |v: &Value, key: &str| -> io::Result<u64> {
260 v.map_get(key).and_then(|val| val.as_uint()).ok_or_else(|| {
261 io::Error::new(io::ErrorKind::InvalidData, format!("{} not a uint", key))
262 })
263 };
264
265 let get_opt_uint =
266 |v: &Value, key: &str| -> Option<u64> { v.map_get(key).and_then(|val| val.as_uint()) };
267
268 let get_bytes = |v: &Value, key: &str| -> io::Result<Vec<u8>> {
269 v.map_get(key)
270 .and_then(|val| val.as_bin())
271 .map(|b| b.to_vec())
272 .ok_or_else(|| {
273 io::Error::new(io::ErrorKind::InvalidData, format!("{} not bytes", key))
274 })
275 };
276
277 let transport_id_bytes = get_bytes(&value, "transport_id")?;
278 let mut transport_id = [0u8; 16];
279 if transport_id_bytes.len() == 16 {
280 transport_id.copy_from_slice(&transport_id_bytes);
281 }
282
283 let network_id_bytes = get_bytes(&value, "network_id")?;
284 let mut network_id = [0u8; 16];
285 if network_id_bytes.len() == 16 {
286 network_id.copy_from_slice(&network_id_bytes);
287 }
288
289 let discovery_hash_bytes = get_bytes(&value, "discovery_hash")?;
290 let mut discovery_hash = [0u8; 32];
291 if discovery_hash_bytes.len() == 32 {
292 discovery_hash.copy_from_slice(&discovery_hash_bytes);
293 }
294
295 let status_str = get_str(&value, "status")?;
296 let status = match status_str.as_str() {
297 "available" => DiscoveredStatus::Available,
298 "unknown" => DiscoveredStatus::Unknown,
299 "stale" => DiscoveredStatus::Stale,
300 _ => DiscoveredStatus::Unknown,
301 };
302
303 Ok(DiscoveredInterface {
304 interface_type: get_str(&value, "type")?,
305 transport: get_bool(&value, "transport")?,
306 name: get_str(&value, "name")?,
307 discovered: get_float(&value, "discovered")?,
308 last_heard: get_float(&value, "last_heard")?,
309 heard_count: get_uint(&value, "heard_count")? as u32,
310 status,
311 stamp: get_bytes(&value, "stamp")?,
312 stamp_value: get_uint(&value, "value")? as u32,
313 transport_id,
314 network_id,
315 hops: get_uint(&value, "hops")? as u8,
316 latitude: get_opt_float(&value, "latitude"),
317 longitude: get_opt_float(&value, "longitude"),
318 height: get_opt_float(&value, "height"),
319 reachable_on: get_opt_str(&value, "reachable_on"),
320 port: get_opt_uint(&value, "port").map(|v| v as u16),
321 frequency: get_opt_uint(&value, "frequency").map(|v| v as u32),
322 bandwidth: get_opt_uint(&value, "bandwidth").map(|v| v as u32),
323 spreading_factor: get_opt_uint(&value, "sf").map(|v| v as u8),
324 coding_rate: get_opt_uint(&value, "cr").map(|v| v as u8),
325 modulation: get_opt_str(&value, "modulation"),
326 channel: get_opt_uint(&value, "channel").map(|v| v as u8),
327 ifac_netname: get_opt_str(&value, "ifac_netname"),
328 ifac_netkey: get_opt_str(&value, "ifac_netkey"),
329 config_entry: get_opt_str(&value, "config_entry"),
330 discovery_hash,
331 })
332 }
333}
334
335pub fn generate_discovery_stamp(packed_data: &[u8], stamp_cost: u8) -> ([u8; STAMP_SIZE], u32) {
343 use rns_crypto::{OsRng, Rng};
344 use std::sync::atomic::{AtomicBool, Ordering};
345 use std::sync::{Arc, Mutex};
346
347 let infohash = sha256(packed_data);
348 let workblock = stamp_workblock(&infohash, WORKBLOCK_EXPAND_ROUNDS);
349
350 let found: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));
351 let result: Arc<Mutex<Option<[u8; STAMP_SIZE]>>> = Arc::new(Mutex::new(None));
352
353 let num_threads = rayon::current_num_threads();
354
355 rayon::scope(|s| {
356 for _ in 0..num_threads {
357 let found = found.clone();
358 let result = result.clone();
359 let workblock = &workblock;
360 s.spawn(move |_| {
361 let mut rng = OsRng;
362 let mut nonce = [0u8; STAMP_SIZE];
363 loop {
364 if found.load(Ordering::Relaxed) {
365 return;
366 }
367 rng.fill_bytes(&mut nonce);
368 if stamp_valid(&nonce, stamp_cost, workblock) {
369 let mut r = match result.lock() {
370 Ok(guard) => guard,
371 Err(poisoned) => {
372 log::error!(
373 "recovering from poisoned discovery stamp result buffer"
374 );
375 poisoned.into_inner()
376 }
377 };
378 if r.is_none() {
379 *r = Some(nonce);
380 }
381 found.store(true, Ordering::Relaxed);
382 return;
383 }
384 }
385 });
386 }
387 });
388
389 let stamp = match result.lock() {
390 Ok(mut guard) => guard.take(),
391 Err(poisoned) => {
392 log::error!("recovering from poisoned discovery stamp result buffer");
393 poisoned.into_inner().take()
394 }
395 }
396 .unwrap_or_else(|| {
397 log::error!("parallel discovery stamp search returned no result; retrying synchronously");
398 let mut rng = OsRng;
399 let mut nonce = [0u8; STAMP_SIZE];
400 loop {
401 rng.fill_bytes(&mut nonce);
402 if stamp_valid(&nonce, stamp_cost, &workblock) {
403 return nonce;
404 }
405 }
406 });
407 let value = rns_core::stamp::stamp_value(&workblock, &stamp);
408 (stamp, value)
409}
410
411#[derive(Debug, Clone)]
417pub struct DiscoverableInterface {
418 pub interface_name: String,
420 pub config: DiscoveryConfig,
421 pub transport_enabled: bool,
423 pub ifac_netname: Option<String>,
425 pub ifac_netkey: Option<String>,
427}
428
429pub struct StampResult {
431 pub interface_name: String,
433 pub app_data: Vec<u8>,
435}
436
437pub struct InterfaceAnnouncer {
443 transport_id: [u8; 16],
445 interfaces: Vec<DiscoverableInterface>,
447 last_announced: Vec<f64>,
449 stamp_rx: std::sync::mpsc::Receiver<StampResult>,
451 stamp_tx: std::sync::mpsc::Sender<StampResult>,
453 stamp_pending: bool,
455}
456
457impl InterfaceAnnouncer {
458 pub fn new(transport_id: [u8; 16], interfaces: Vec<DiscoverableInterface>) -> Self {
460 let n = interfaces.len();
461 let (stamp_tx, stamp_rx) = std::sync::mpsc::channel();
462 InterfaceAnnouncer {
463 transport_id,
464 interfaces,
465 last_announced: vec![0.0; n],
466 stamp_rx,
467 stamp_tx,
468 stamp_pending: false,
469 }
470 }
471
472 pub fn maybe_start(&mut self, now: f64) {
476 if self.stamp_pending {
477 return;
478 }
479 let due_index = self.interfaces.iter().enumerate().find_map(|(i, iface)| {
480 let elapsed = now - self.last_announced[i];
481 if elapsed >= iface.config.announce_interval as f64 {
482 Some(i)
483 } else {
484 None
485 }
486 });
487
488 if let Some(idx) = due_index {
489 let packed = self.pack_interface_info(idx);
490 let stamp_cost = self.interfaces[idx].config.stamp_value;
491 let name = self.interfaces[idx].config.discovery_name.clone();
492 let interface_name = self.interfaces[idx].interface_name.clone();
493 let tx = self.stamp_tx.clone();
494
495 log::info!(
496 "Spawning discovery stamp generation (cost={}) for '{}'...",
497 stamp_cost,
498 name,
499 );
500
501 self.stamp_pending = true;
502 self.last_announced[idx] = now;
503
504 std::thread::spawn(move || {
505 let (stamp, value) = generate_discovery_stamp(&packed, stamp_cost);
506 log::info!("Discovery stamp generated (value={}) for '{}'", value, name,);
507
508 let flags: u8 = 0x00; let mut app_data = Vec::with_capacity(1 + packed.len() + STAMP_SIZE);
510 app_data.push(flags);
511 app_data.extend_from_slice(&packed);
512 app_data.extend_from_slice(&stamp);
513
514 let _ = tx.send(StampResult {
515 interface_name,
516 app_data,
517 });
518 });
519 }
520 }
521
522 pub fn poll_ready(&mut self) -> Option<StampResult> {
525 match self.stamp_rx.try_recv() {
526 Ok(result) => {
527 self.stamp_pending = false;
528 Some(result)
529 }
530 Err(_) => None,
531 }
532 }
533
534 pub fn contains_interface(&self, interface_name: &str) -> bool {
536 self.interfaces
537 .iter()
538 .any(|iface| iface.interface_name == interface_name)
539 }
540
541 pub fn upsert_interface(&mut self, iface: DiscoverableInterface) {
543 if let Some(index) = self
544 .interfaces
545 .iter()
546 .position(|existing| existing.interface_name == iface.interface_name)
547 {
548 self.interfaces[index] = iface;
549 return;
550 }
551 self.interfaces.push(iface);
552 self.last_announced.push(0.0);
553 }
554
555 pub fn remove_interface(&mut self, interface_name: &str) -> bool {
557 if let Some(index) = self
558 .interfaces
559 .iter()
560 .position(|iface| iface.interface_name == interface_name)
561 {
562 self.interfaces.remove(index);
563 self.last_announced.remove(index);
564 true
565 } else {
566 false
567 }
568 }
569
570 pub fn is_empty(&self) -> bool {
572 self.interfaces.is_empty()
573 }
574
575 fn pack_interface_info(&self, index: usize) -> Vec<u8> {
577 let iface = &self.interfaces[index];
578 let mut entries: Vec<(msgpack::Value, msgpack::Value)> = Vec::new();
579
580 entries.push((
581 msgpack::Value::UInt(INTERFACE_TYPE as u64),
582 msgpack::Value::Str(iface.config.interface_type.clone()),
583 ));
584 entries.push((
585 msgpack::Value::UInt(TRANSPORT as u64),
586 msgpack::Value::Bool(iface.transport_enabled),
587 ));
588 entries.push((
589 msgpack::Value::UInt(NAME as u64),
590 msgpack::Value::Str(iface.config.discovery_name.clone()),
591 ));
592 entries.push((
593 msgpack::Value::UInt(TRANSPORT_ID as u64),
594 msgpack::Value::Bin(self.transport_id.to_vec()),
595 ));
596 if let Some(ref reachable) = iface.config.reachable_on {
597 entries.push((
598 msgpack::Value::UInt(REACHABLE_ON as u64),
599 msgpack::Value::Str(reachable.clone()),
600 ));
601 }
602 if let Some(port) = iface.config.listen_port {
603 entries.push((
604 msgpack::Value::UInt(PORT as u64),
605 msgpack::Value::UInt(port as u64),
606 ));
607 }
608 if let Some(lat) = iface.config.latitude {
609 entries.push((
610 msgpack::Value::UInt(LATITUDE as u64),
611 msgpack::Value::Float(lat),
612 ));
613 }
614 if let Some(lon) = iface.config.longitude {
615 entries.push((
616 msgpack::Value::UInt(LONGITUDE as u64),
617 msgpack::Value::Float(lon),
618 ));
619 }
620 if let Some(h) = iface.config.height {
621 entries.push((
622 msgpack::Value::UInt(HEIGHT as u64),
623 msgpack::Value::Float(h),
624 ));
625 }
626 if let Some(ref netname) = iface.ifac_netname {
627 entries.push((
628 msgpack::Value::UInt(IFAC_NETNAME as u64),
629 msgpack::Value::Str(netname.clone()),
630 ));
631 }
632 if let Some(ref netkey) = iface.ifac_netkey {
633 entries.push((
634 msgpack::Value::UInt(IFAC_NETKEY as u64),
635 msgpack::Value::Str(netkey.clone()),
636 ));
637 }
638
639 msgpack::pack(&msgpack::Value::Map(entries))
640 }
641}
642
643#[cfg(test)]
648mod tests {
649 use super::*;
650
651 #[test]
652 fn test_hex_encode() {
653 assert_eq!(hex_encode(&[0x00, 0xff, 0x12]), "00ff12");
654 assert_eq!(hex_encode(&[]), "");
655 }
656
657 #[test]
658 fn test_compute_discovery_hash() {
659 let transport_id = [0x42u8; 16];
660 let name = "TestInterface";
661 let hash = compute_discovery_hash(&transport_id, name);
662
663 let hash2 = compute_discovery_hash(&transport_id, name);
665 assert_eq!(hash, hash2);
666
667 let hash3 = compute_discovery_hash(&transport_id, "OtherInterface");
669 assert_ne!(hash, hash3);
670 }
671
672 #[test]
673 fn test_is_ip_address() {
674 assert!(is_ip_address("192.168.1.1"));
675 assert!(is_ip_address("::1"));
676 assert!(is_ip_address("2001:db8::1"));
677 assert!(!is_ip_address("not-an-ip"));
678 assert!(!is_ip_address("hostname.example.com"));
679 }
680
681 #[test]
682 fn test_is_hostname() {
683 assert!(is_hostname("example.com"));
684 assert!(is_hostname("sub.example.com"));
685 assert!(is_hostname("my-node"));
686 assert!(is_hostname("my-node.example.com"));
687 assert!(!is_hostname(""));
688 assert!(!is_hostname("-invalid"));
689 assert!(!is_hostname("invalid-"));
690 assert!(!is_hostname("a".repeat(300).as_str()));
691 }
692
693 #[test]
694 fn test_discovered_status() {
695 let now = time::now();
696
697 let mut iface = DiscoveredInterface {
698 interface_type: "TestInterface".into(),
699 transport: true,
700 name: "Test".into(),
701 discovered: now,
702 last_heard: now,
703 heard_count: 0,
704 status: DiscoveredStatus::Available,
705 stamp: vec![],
706 stamp_value: 14,
707 transport_id: [0u8; 16],
708 network_id: [0u8; 16],
709 hops: 0,
710 latitude: None,
711 longitude: None,
712 height: None,
713 reachable_on: None,
714 port: None,
715 frequency: None,
716 bandwidth: None,
717 spreading_factor: None,
718 coding_rate: None,
719 modulation: None,
720 channel: None,
721 ifac_netname: None,
722 ifac_netkey: None,
723 config_entry: None,
724 discovery_hash: [0u8; 32],
725 };
726
727 assert_eq!(iface.compute_status(), DiscoveredStatus::Available);
729
730 iface.last_heard = now - THRESHOLD_UNKNOWN - 3600.0;
732 assert_eq!(iface.compute_status(), DiscoveredStatus::Unknown);
733
734 iface.last_heard = now - THRESHOLD_STALE - 3600.0;
736 assert_eq!(iface.compute_status(), DiscoveredStatus::Stale);
737 }
738
739 #[test]
740 fn test_storage_roundtrip() {
741 use std::sync::atomic::{AtomicU64, Ordering};
742 static TEST_COUNTER: AtomicU64 = AtomicU64::new(0);
743
744 let id = TEST_COUNTER.fetch_add(1, Ordering::Relaxed);
745 let dir =
746 std::env::temp_dir().join(format!("rns-discovery-test-{}-{}", std::process::id(), id));
747 let _ = fs::remove_dir_all(&dir);
748 fs::create_dir_all(&dir).unwrap();
749
750 let storage = DiscoveredInterfaceStorage::new(dir.clone());
751
752 let iface = DiscoveredInterface {
753 interface_type: "BackboneInterface".into(),
754 transport: true,
755 name: "TestNode".into(),
756 discovered: 1700000000.0,
757 last_heard: 1700001000.0,
758 heard_count: 5,
759 status: DiscoveredStatus::Available,
760 stamp: vec![0x42u8; 64],
761 stamp_value: 18,
762 transport_id: [0x01u8; 16],
763 network_id: [0x02u8; 16],
764 hops: 2,
765 latitude: Some(45.0),
766 longitude: Some(9.0),
767 height: Some(100.0),
768 reachable_on: Some("example.com".into()),
769 port: Some(4242),
770 frequency: None,
771 bandwidth: None,
772 spreading_factor: None,
773 coding_rate: None,
774 modulation: None,
775 channel: None,
776 ifac_netname: Some("mynetwork".into()),
777 ifac_netkey: Some("secretkey".into()),
778 config_entry: Some("test config".into()),
779 discovery_hash: compute_discovery_hash(&[0x01u8; 16], "TestNode"),
780 };
781
782 storage.store(&iface).unwrap();
784
785 let loaded = storage.load(&iface.discovery_hash).unwrap().unwrap();
787
788 assert_eq!(loaded.interface_type, iface.interface_type);
789 assert_eq!(loaded.name, iface.name);
790 assert_eq!(loaded.stamp_value, iface.stamp_value);
791 assert_eq!(loaded.transport_id, iface.transport_id);
792 assert_eq!(loaded.hops, iface.hops);
793 assert_eq!(loaded.latitude, iface.latitude);
794 assert_eq!(loaded.reachable_on, iface.reachable_on);
795 assert_eq!(loaded.port, iface.port);
796
797 let list = storage.list().unwrap();
799 assert_eq!(list.len(), 1);
800
801 storage.remove(&iface.discovery_hash).unwrap();
803 let list = storage.list().unwrap();
804 assert!(list.is_empty());
805
806 let _ = fs::remove_dir_all(&dir);
807 }
808
809 #[test]
810 fn test_filter_and_sort() {
811 let now = time::now();
812
813 let ifaces = vec![
814 DiscoveredInterface {
815 interface_type: "BackboneInterface".into(),
816 transport: true,
817 name: "high-value-stale".into(),
818 discovered: now,
819 last_heard: now - THRESHOLD_STALE - 100.0, heard_count: 0,
821 status: DiscoveredStatus::Stale,
822 stamp: vec![],
823 stamp_value: 20,
824 transport_id: [0u8; 16],
825 network_id: [0u8; 16],
826 hops: 0,
827 latitude: None,
828 longitude: None,
829 height: None,
830 reachable_on: None,
831 port: None,
832 frequency: None,
833 bandwidth: None,
834 spreading_factor: None,
835 coding_rate: None,
836 modulation: None,
837 channel: None,
838 ifac_netname: None,
839 ifac_netkey: None,
840 config_entry: None,
841 discovery_hash: [0u8; 32],
842 },
843 DiscoveredInterface {
844 interface_type: "TCPServerInterface".into(),
845 transport: true,
846 name: "low-value-available".into(),
847 discovered: now,
848 last_heard: now - 10.0, heard_count: 0,
850 status: DiscoveredStatus::Available,
851 stamp: vec![],
852 stamp_value: 10,
853 transport_id: [0u8; 16],
854 network_id: [0u8; 16],
855 hops: 0,
856 latitude: None,
857 longitude: None,
858 height: None,
859 reachable_on: None,
860 port: None,
861 frequency: None,
862 bandwidth: None,
863 spreading_factor: None,
864 coding_rate: None,
865 modulation: None,
866 channel: None,
867 ifac_netname: None,
868 ifac_netkey: None,
869 config_entry: None,
870 discovery_hash: [1u8; 32],
871 },
872 DiscoveredInterface {
873 interface_type: "I2PInterface".into(),
874 transport: false,
875 name: "high-value-available".into(),
876 discovered: now,
877 last_heard: now - 10.0, heard_count: 0,
879 status: DiscoveredStatus::Available,
880 stamp: vec![],
881 stamp_value: 20,
882 transport_id: [0u8; 16],
883 network_id: [0u8; 16],
884 hops: 0,
885 latitude: None,
886 longitude: None,
887 height: None,
888 reachable_on: None,
889 port: None,
890 frequency: None,
891 bandwidth: None,
892 spreading_factor: None,
893 coding_rate: None,
894 modulation: None,
895 channel: None,
896 ifac_netname: None,
897 ifac_netkey: None,
898 config_entry: None,
899 discovery_hash: [2u8; 32],
900 },
901 ];
902
903 let mut result = ifaces.clone();
905 filter_and_sort_interfaces(&mut result, false, false);
906 assert_eq!(result.len(), 3);
907 assert_eq!(result[0].name, "high-value-available");
909 assert_eq!(result[1].name, "low-value-available");
910 assert_eq!(result[2].name, "high-value-stale");
911
912 let mut result = ifaces.clone();
914 filter_and_sort_interfaces(&mut result, true, false);
915 assert_eq!(result.len(), 2); let mut result = ifaces.clone();
919 filter_and_sort_interfaces(&mut result, false, true);
920 assert_eq!(result.len(), 2); }
922
923 #[test]
924 fn test_discovery_name_hash_deterministic() {
925 let h1 = discovery_name_hash();
926 let h2 = discovery_name_hash();
927 assert_eq!(h1, h2);
928 assert_ne!(h1, [0u8; 10]); }
930}