clawft_kernel/
mesh_bootstrap.rs1use crate::mesh_discovery::{DiscoveredPeer, DiscoveryBackend, DiscoveryError, DiscoverySource};
22use async_trait::async_trait;
23
24pub struct BootstrapDiscovery {
26 seed_addresses: Vec<String>,
28 discovered: Vec<DiscoveredPeer>,
30 bootstrapped: bool,
32}
33
34impl BootstrapDiscovery {
35 pub fn new(seed_addresses: Vec<String>) -> Self {
37 Self {
38 seed_addresses,
39 discovered: Vec::new(),
40 bootstrapped: false,
41 }
42 }
43
44 pub fn seed_addresses(&self) -> &[String] {
46 &self.seed_addresses
47 }
48
49 pub fn add_seed(&mut self, address: String) {
51 if !self.seed_addresses.contains(&address) {
52 self.seed_addresses.push(address);
53 }
54 }
55}
56
57#[async_trait]
58impl DiscoveryBackend for BootstrapDiscovery {
59 fn name(&self) -> &str {
60 "bootstrap"
61 }
62
63 async fn start(&mut self) -> Result<(), DiscoveryError> {
64 if !self.bootstrapped {
68 for (i, addr) in self.seed_addresses.iter().enumerate() {
69 self.discovered.push(DiscoveredPeer {
70 node_id: format!("seed-{i}"),
71 address: addr.clone(),
72 platform: "unknown".into(),
73 source: DiscoverySource::SeedPeer,
74 });
75 }
76 self.bootstrapped = true;
77 }
78 Ok(())
79 }
80
81 async fn poll(&mut self) -> Vec<DiscoveredPeer> {
82 std::mem::take(&mut self.discovered)
84 }
85
86 async fn stop(&mut self) -> Result<(), DiscoveryError> {
87 self.discovered.clear();
88 Ok(())
89 }
90}
91
92pub struct PeerExchangeDiscovery {
94 pending: Vec<DiscoveredPeer>,
96}
97
98impl PeerExchangeDiscovery {
99 pub fn new() -> Self {
100 Self {
101 pending: Vec::new(),
102 }
103 }
104
105 pub fn add_peers_from_exchange(&mut self, peers: Vec<DiscoveredPeer>) {
107 for mut peer in peers {
108 peer.source = DiscoverySource::PeerExchange;
109 self.pending.push(peer);
110 }
111 }
112}
113
114impl Default for PeerExchangeDiscovery {
115 fn default() -> Self {
116 Self::new()
117 }
118}
119
120#[async_trait]
121impl DiscoveryBackend for PeerExchangeDiscovery {
122 fn name(&self) -> &str {
123 "peer-exchange"
124 }
125
126 async fn start(&mut self) -> Result<(), DiscoveryError> {
127 Ok(())
128 }
129
130 async fn poll(&mut self) -> Vec<DiscoveredPeer> {
131 std::mem::take(&mut self.pending)
132 }
133
134 async fn stop(&mut self) -> Result<(), DiscoveryError> {
135 self.pending.clear();
136 Ok(())
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[tokio::test]
145 async fn bootstrap_start_produces_peers() {
146 let seeds = vec![
147 "quic://10.0.0.1:9489".to_string(),
148 "quic://10.0.0.2:9489".to_string(),
149 ];
150 let mut disc = BootstrapDiscovery::new(seeds);
151 disc.start().await.unwrap();
152
153 let peers = disc.poll().await;
154 assert_eq!(peers.len(), 2);
155 assert_eq!(peers[0].node_id, "seed-0");
156 assert_eq!(peers[0].address, "quic://10.0.0.1:9489");
157 assert_eq!(peers[0].source, DiscoverySource::SeedPeer);
158 assert_eq!(peers[1].node_id, "seed-1");
159 }
160
161 #[tokio::test]
162 async fn bootstrap_poll_drains() {
163 let mut disc = BootstrapDiscovery::new(vec!["addr".into()]);
164 disc.start().await.unwrap();
165
166 let first = disc.poll().await;
167 assert_eq!(first.len(), 1);
168
169 let second = disc.poll().await;
170 assert!(second.is_empty(), "second poll should be empty");
171 }
172
173 #[tokio::test]
174 async fn bootstrap_double_start_no_duplicate() {
175 let mut disc = BootstrapDiscovery::new(vec!["a".into()]);
176 disc.start().await.unwrap();
177 disc.start().await.unwrap(); let peers = disc.poll().await;
180 assert_eq!(peers.len(), 1, "should not duplicate on second start");
181 }
182
183 #[tokio::test]
184 async fn bootstrap_add_seed() {
185 let mut disc = BootstrapDiscovery::new(vec!["a".into()]);
186 disc.add_seed("b".into());
187 disc.add_seed("a".into()); assert_eq!(disc.seed_addresses().len(), 2);
189 }
190
191 #[tokio::test]
192 async fn bootstrap_stop_clears() {
193 let mut disc = BootstrapDiscovery::new(vec!["a".into()]);
194 disc.start().await.unwrap();
195 disc.stop().await.unwrap();
196
197 let peers = disc.poll().await;
198 assert!(peers.is_empty());
199 }
200
201 #[tokio::test]
202 async fn peer_exchange_add_and_poll() {
203 let mut pex = PeerExchangeDiscovery::new();
204 assert_eq!(pex.name(), "peer-exchange");
205
206 pex.start().await.unwrap();
207
208 let incoming = vec![DiscoveredPeer {
209 node_id: "remote-1".into(),
210 address: "quic://192.168.1.50:9489".into(),
211 platform: "darwin".into(),
212 source: DiscoverySource::Manual, }];
214 pex.add_peers_from_exchange(incoming);
215
216 let peers = pex.poll().await;
217 assert_eq!(peers.len(), 1);
218 assert_eq!(peers[0].source, DiscoverySource::PeerExchange);
219 assert_eq!(peers[0].node_id, "remote-1");
220
221 let second = pex.poll().await;
222 assert!(second.is_empty());
223 }
224
225 #[tokio::test]
226 async fn peer_exchange_stop() {
227 let mut pex = PeerExchangeDiscovery::new();
228 pex.add_peers_from_exchange(vec![DiscoveredPeer {
229 node_id: "n".into(),
230 address: "a".into(),
231 platform: "p".into(),
232 source: DiscoverySource::Manual,
233 }]);
234 pex.stop().await.unwrap();
235 let peers = pex.poll().await;
236 assert!(peers.is_empty());
237 }
238}