1use crate::{Peer, ToolError, UI};
2use std::collections::HashMap;
3use std::net::{IpAddr, Ipv4Addr, SocketAddr};
4use std::sync::atomic::{AtomicBool, Ordering};
5use std::sync::Arc;
6use std::time::{Duration, Instant};
7use tokio::net::UdpSocket;
8
9const DISCOVERY_PORT: u16 = 58199;
10const BROADCAST_INTERVAL: Duration = Duration::from_secs(3);
11const PEER_TIMEOUT: Duration = Duration::from_secs(9);
12
13pub fn broadcast_targets() -> Vec<SocketAddr> {
17 let mut ips: Vec<Ipv4Addr> = Vec::new();
18
19 if let Ok(ifaces) = if_addrs::get_if_addrs() {
20 for iface in ifaces {
21 if iface.is_loopback() {
22 continue;
23 }
24 if let if_addrs::IfAddr::V4(v4) = iface.addr {
25 if let Some(bcast) = v4.broadcast {
26 ips.push(bcast);
27 }
28 let bcast = Ipv4Addr::from(u32::from(v4.ip) | !u32::from(v4.netmask));
30 if bcast != Ipv4Addr::BROADCAST {
31 ips.push(bcast);
32 }
33 }
34 }
35 }
36
37 ips.push(Ipv4Addr::BROADCAST);
38 ips.dedup();
39
40 ips.into_iter()
41 .map(|ip| SocketAddr::new(IpAddr::V4(ip), DISCOVERY_PORT))
42 .collect()
43}
44
45pub async fn start_discovery(
46 local_ip: String,
47 stop: Arc<AtomicBool>,
48 ui: Arc<dyn UI>,
49) -> Result<(), ToolError> {
50 let socket = Arc::new(UdpSocket::bind(format!("0.0.0.0:{DISCOVERY_PORT}")).await?);
51 socket.set_broadcast(true)?;
52 ui.log(&format!("Decouverte demarree sur le port {DISCOVERY_PORT}"));
53
54 let targets = broadcast_targets();
55 let me: IpAddr = local_ip.parse().unwrap_or(IpAddr::V4(Ipv4Addr::LOCALHOST));
56
57 let my_id = crate::utils::device_id();
58 let mut last_seen: HashMap<String, Instant> = HashMap::new();
59 let mut interval = tokio::time::interval(BROADCAST_INTERVAL);
60 let mut buf = [0u8; 1024];
61
62 loop {
63 if stop.load(Ordering::Relaxed) {
64 break;
65 }
66
67 let now = Instant::now();
68 let expired: Vec<String> = last_seen
69 .iter()
70 .filter(|(_, t)| now.duration_since(**t) > PEER_TIMEOUT)
71 .map(|(h, _)| h.clone())
72 .collect();
73 for h in expired {
74 last_seen.remove(&h);
75 ui.peer_lost(&h);
76 }
77
78 tokio::select! {
79 _ = interval.tick() => {
80 let msg = b"TOOLE_DISCOVERY";
81 for dest in &targets {
82 let _ = socket.send_to(msg, dest).await;
83 }
84 }
85 result = socket.recv_from(&mut buf) => {
86 if let Ok((len, addr)) = result {
87 if addr.ip() == me {
88 continue;
89 }
90 let msg = String::from_utf8_lossy(&buf[..len]);
91
92 if msg == "TOOLE_DISCOVERY" {
93 let response = format!("TOOLE_HERE:{}", my_id);
94 let _ = socket.send_to(response.as_bytes(), addr).await;
95 } else if let Some(id) = msg.strip_prefix("TOOLE_HERE:") {
96 if id != my_id {
97 let peer = Peer {
98 id: id.to_string(),
99 addr: addr.ip().to_string(),
100 };
101 let is_new = !last_seen.contains_key(id);
105 last_seen.insert(id.to_string(), Instant::now());
106 if is_new {
107 ui.peer_found(&peer);
108 }
109 }
110 }
111 }
112 }
113 _ = tokio::time::sleep(Duration::from_millis(200)) => {
114 }
119 }
120 }
121
122 Ok(())
123}