1#![cfg(not(target_arch = "wasm32"))]
27
28use std::collections::HashMap;
29use std::net::{IpAddr, SocketAddr};
30use std::time::Duration;
31
32use super::wireguard_runtime::{TunnelEvent, WgTunnel};
33use super::wireguard_userspace::{public_key_from_hex, WgKeypair};
34
35#[derive(Debug)]
37pub struct MeshPacket {
38 pub peer_id: String,
40 pub inner: Vec<u8>,
42}
43
44pub struct SocialWebNet {
46 keys: WgKeypair,
48 bind_ip: IpAddr,
50 tunnels: HashMap<String, WgTunnel>,
52 next_index: u32,
54 read_timeout: Option<Duration>,
56}
57
58impl SocialWebNet {
59 pub fn new(keys: WgKeypair, bind_ip: IpAddr, read_timeout: Option<Duration>) -> SocialWebNet {
63 SocialWebNet {
64 keys,
65 bind_ip,
66 tunnels: HashMap::new(),
67 next_index: 1,
68 read_timeout,
69 }
70 }
71
72 pub fn public_key_hex(&self) -> String {
74 self.keys.public_hex()
75 }
76
77 pub fn peers(&self) -> Vec<String> {
79 self.tunnels.keys().cloned().collect()
80 }
81
82 pub fn add_peer(
91 &mut self,
92 peer_id: &str,
93 peer_pubkey_hex: &str,
94 endpoint: Option<SocketAddr>,
95 ) -> Result<SocketAddr, String> {
96 let peer_public = public_key_from_hex(peer_pubkey_hex)?;
97 let index = self.next_index;
98 self.next_index = self.next_index.wrapping_add(1);
99
100 let bind_addr = SocketAddr::new(self.bind_ip, 0);
101 let mut tunnel = WgTunnel::bind(&self.keys, peer_public, bind_addr, index)?;
102 tunnel.set_read_timeout(self.read_timeout)?;
103 if let Some(ep) = endpoint {
104 tunnel.set_peer_endpoint(ep);
105 }
106 let local = tunnel.local_addr()?;
107 self.tunnels.insert(peer_id.to_string(), tunnel);
108 Ok(local)
109 }
110
111 pub fn remove_peer(&mut self, peer_id: &str) -> bool {
113 self.tunnels.remove(peer_id).is_some()
114 }
115
116 fn tunnel_mut(&mut self, peer_id: &str) -> Result<&mut WgTunnel, String> {
117 self.tunnels
118 .get_mut(peer_id)
119 .ok_or_else(|| format!("unknown peer '{peer_id}'"))
120 }
121
122 pub fn local_addr(&self, peer_id: &str) -> Option<SocketAddr> {
124 self.tunnels.get(peer_id).and_then(|t| t.local_addr().ok())
125 }
126
127 pub fn set_peer_endpoint(&mut self, peer_id: &str, addr: SocketAddr) -> Result<(), String> {
129 self.tunnel_mut(peer_id)?.set_peer_endpoint(addr);
130 Ok(())
131 }
132
133 pub fn has_session(&self, peer_id: &str) -> bool {
135 self.tunnels.get(peer_id).is_some_and(|t| t.has_session())
136 }
137
138 pub fn initiate_handshake(&mut self, peer_id: &str) -> Result<(), String> {
140 self.tunnel_mut(peer_id)?.initiate_handshake()
141 }
142
143 pub fn send_to(&mut self, peer_id: &str, inner: &[u8]) -> Result<bool, String> {
146 self.tunnel_mut(peer_id)?.send_packet(inner)
147 }
148
149 pub fn send_datagram(
155 &mut self,
156 peer_id: &str,
157 src_port: u16,
158 dst_port: u16,
159 payload: &[u8],
160 ) -> Result<bool, String> {
161 let pkt = super::mesh_datagram::encode_datagram(src_port, dst_port, payload);
162 self.send_to(peer_id, &pkt)
163 }
164
165 pub fn pump(&mut self, peer_id: &str) -> Result<TunnelEvent, String> {
167 self.tunnel_mut(peer_id)?.pump()
168 }
169
170 pub fn pump_all(&mut self) -> Vec<Result<MeshPacket, (String, String)>> {
174 let ids: Vec<String> = self.tunnels.keys().cloned().collect();
175 let mut out = Vec::new();
176 for id in ids {
177 match self.pump(&id) {
178 Ok(TunnelEvent::InnerPacket(inner)) => {
179 out.push(Ok(MeshPacket { peer_id: id, inner }))
180 }
181 Ok(_) => {}
182 Err(e) => out.push(Err((id, e))),
183 }
184 }
185 out
186 }
187
188 pub fn tick_all(&mut self) -> Vec<(String, String)> {
191 let ids: Vec<String> = self.tunnels.keys().cloned().collect();
192 let mut errs = Vec::new();
193 for id in ids {
194 if let Ok(t) = self.tunnel_mut(&id) {
195 if let Err(e) = t.tick() {
196 errs.push((id, e));
197 }
198 }
199 }
200 errs
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207 use crate::p2p::wireguard_userspace::generate_keypair;
208
209 fn v6(payload: &[u8]) -> Vec<u8> {
210 let total = 40 + payload.len();
211 let mut p = vec![0u8; total];
212 p[0] = 0x60;
213 p[4..6].copy_from_slice(&(payload.len() as u16).to_be_bytes());
214 p[6] = 17;
215 p[7] = 64;
216 p[8] = 0xfd;
217 p[23] = 0x01;
218 p[24] = 0xfd;
219 p[39] = 0x02;
220 p[40..].copy_from_slice(payload);
221 p
222 }
223
224 #[test]
227 fn two_meshes_peer_and_exchange_by_id() {
228 let a_keys = generate_keypair();
229 let b_keys = generate_keypair();
230 let a_pub = a_keys.public_hex();
231 let b_pub = b_keys.public_hex();
232
233 let to = Some(Duration::from_millis(300));
234 let mut a = SocialWebNet::new(a_keys, "127.0.0.1".parse().unwrap(), to);
235 let mut b = SocialWebNet::new(b_keys, "127.0.0.1".parse().unwrap(), to);
236
237 let a_local = a.add_peer("did:wf:bob", &b_pub, None).expect("A adds B");
239 let b_local = b.add_peer("did:wf:alice", &a_pub, None).expect("B adds A");
240
241 a.set_peer_endpoint("did:wf:bob", b_local).unwrap();
243 b.set_peer_endpoint("did:wf:alice", a_local).unwrap();
244
245 a.initiate_handshake("did:wf:bob").expect("A initiates");
247 for _ in 0..20 {
248 if a.has_session("did:wf:bob") {
249 break;
250 }
251 let _ = b.pump_all();
252 let _ = a.pump_all();
253 }
254 assert!(
255 a.has_session("did:wf:bob"),
256 "A established a session with B"
257 );
258 let _ = b.pump_all(); assert!(
260 b.has_session("did:wf:alice"),
261 "B established a session with A"
262 );
263
264 let payload = v6(b"mesh packet by peer id");
266 assert!(a.send_to("did:wf:bob", &payload).expect("A sends"));
267
268 let mut got = None;
269 for _ in 0..10 {
270 for evt in b.pump_all() {
271 if let Ok(pkt) = evt {
272 assert_eq!(pkt.peer_id, "did:wf:alice", "tagged with the sending peer");
273 got = Some(pkt.inner);
274 }
275 }
276 if got.is_some() {
277 break;
278 }
279 }
280 assert_eq!(got.expect("B received the inner packet"), payload);
281 }
282
283 #[test]
286 fn two_meshes_exchange_an_application_datagram() {
287 use super::super::mesh_datagram::{decode_datagram, ports};
288
289 let a_keys = generate_keypair();
290 let b_keys = generate_keypair();
291 let (a_pub, b_pub) = (a_keys.public_hex(), b_keys.public_hex());
292
293 let to = Some(Duration::from_millis(300));
294 let mut a = SocialWebNet::new(a_keys, "127.0.0.1".parse().unwrap(), to);
295 let mut b = SocialWebNet::new(b_keys, "127.0.0.1".parse().unwrap(), to);
296
297 let a_local = a.add_peer("b", &b_pub, None).unwrap();
298 let b_local = b.add_peer("a", &a_pub, None).unwrap();
299 a.set_peer_endpoint("b", b_local).unwrap();
300 b.set_peer_endpoint("a", a_local).unwrap();
301
302 a.initiate_handshake("b").unwrap();
303 for _ in 0..20 {
304 if a.has_session("b") {
305 break;
306 }
307 let _ = b.pump_all();
308 let _ = a.pump_all();
309 }
310 assert!(a.has_session("b"));
311 let _ = b.pump_all();
312
313 assert!(a
315 .send_datagram("b", ports::CHAT, ports::CHAT, b"hi over the app layer")
316 .unwrap());
317
318 let mut got = None;
319 for _ in 0..10 {
320 for evt in b.pump_all() {
321 if let Ok(pkt) = evt {
322 got = decode_datagram(&pkt.inner);
323 }
324 }
325 if got.is_some() {
326 break;
327 }
328 }
329 let d = got.expect("B decoded the datagram");
330 assert_eq!(d.dst_port, ports::CHAT, "demuxes on the chat port");
331 assert_eq!(d.payload, b"hi over the app layer");
332 }
333
334 #[test]
335 fn add_and_remove_peer_tracks_membership() {
336 let keys = generate_keypair();
337 let peer = generate_keypair();
338 let mut mesh = SocialWebNet::new(keys, "127.0.0.1".parse().unwrap(), None);
339
340 assert!(mesh.peers().is_empty());
341 mesh.add_peer("did:wf:x", &peer.public_hex(), None).unwrap();
342 assert_eq!(mesh.peers(), vec!["did:wf:x".to_string()]);
343 assert!(mesh.local_addr("did:wf:x").is_some());
344 assert!(!mesh.has_session("did:wf:x"), "no handshake yet");
345
346 assert!(mesh.remove_peer("did:wf:x"));
347 assert!(!mesh.remove_peer("did:wf:x"), "second remove is a no-op");
348 assert!(mesh.peers().is_empty());
349 }
350
351 #[test]
352 fn bad_peer_pubkey_is_rejected() {
353 let keys = generate_keypair();
354 let mut mesh = SocialWebNet::new(keys, "127.0.0.1".parse().unwrap(), None);
355 let err = mesh.add_peer("did:wf:x", "not-hex", None).unwrap_err();
356 assert!(err.contains("hex"), "surfaces the key-parse error: {err}");
357 }
358}