bp_core/network/
kad_store.rs1use serde::{Deserialize, Serialize};
23use std::{collections::HashMap, path::Path};
24
25#[derive(Debug, Default, Serialize, Deserialize)]
32pub struct KadPeers(pub HashMap<String, Vec<String>>);
33
34impl KadPeers {
35 pub fn load(path: &Path) -> Self {
38 std::fs::read_to_string(path)
39 .ok()
40 .and_then(|s| serde_json::from_str(&s).ok())
41 .unwrap_or_default()
42 }
43
44 pub fn save(&self, path: &Path) {
47 match serde_json::to_string_pretty(self) {
48 Ok(s) => {
49 if let Err(e) = std::fs::write(path, s) {
50 tracing::warn!("kad_store: failed to save peers: {}", e);
51 }
52 }
53 Err(e) => {
54 tracing::warn!("kad_store: serialization error: {}", e);
55 }
56 }
57 }
58
59 pub fn add(&mut self, peer_id_str: String, addr_str: String) {
61 let entry = self.0.entry(peer_id_str).or_default();
62 if !entry.contains(&addr_str) {
63 entry.push(addr_str);
64 }
65 }
66}
67
68#[cfg(test)]
71mod tests {
72 use super::*;
73
74 fn tmp_path(name: &str) -> std::path::PathBuf {
75 std::env::temp_dir().join(format!("bp_kad_test_{name}.json"))
76 }
77
78 #[test]
79 fn round_trip_empty() {
80 let path = tmp_path("empty");
81 let peers = KadPeers::default();
82 peers.save(&path);
83 let loaded = KadPeers::load(&path);
84 let _ = std::fs::remove_file(&path);
85 assert!(loaded.0.is_empty());
86 }
87
88 #[test]
89 fn round_trip_with_entries() {
90 let path = tmp_path("entries");
91 let mut peers = KadPeers::default();
92 peers.add("peer-1".into(), "/ip4/1.2.3.4/tcp/4001".into());
93 peers.add("peer-1".into(), "/ip6/::1/tcp/4001".into());
94 peers.add("peer-2".into(), "/ip4/5.6.7.8/tcp/4002".into());
95 peers.save(&path);
96
97 let loaded = KadPeers::load(&path);
98 let _ = std::fs::remove_file(&path);
99 assert_eq!(loaded.0["peer-1"].len(), 2);
100 assert_eq!(loaded.0["peer-2"].len(), 1);
101 }
102
103 #[test]
104 fn add_deduplicates() {
105 let mut peers = KadPeers::default();
106 peers.add("p1".into(), "/ip4/1.2.3.4/tcp/4001".into());
107 peers.add("p1".into(), "/ip4/1.2.3.4/tcp/4001".into());
108 assert_eq!(peers.0["p1"].len(), 1);
109 }
110
111 #[test]
112 fn load_missing_file_returns_default() {
113 let peers = KadPeers::load(std::path::Path::new("/tmp/no_such_kad_file_xyz.json"));
114 assert!(peers.0.is_empty());
115 }
116
117 #[test]
118 fn load_corrupt_file_returns_default() {
119 let path = tmp_path("corrupt");
120 std::fs::write(&path, b"NOT VALID JSON {").unwrap();
121 let peers = KadPeers::load(&path);
122 let _ = std::fs::remove_file(&path);
123 assert!(peers.0.is_empty());
124 }
125}