deaddrop-net 2.6.0

DeadDrop networking: transports, discovery, sync, routing, scheduler
Documentation
use crate::discovery::{Beacon, DiscoveryProvider, Endpoint};
use async_trait::async_trait;
use deaddrop_core::{PROTOCOL_VERSION, PeerId, Result};
use std::net::SocketAddr;
use std::time::Duration;
use tokio::net::UdpSocket;
use tokio::time::{interval, timeout};

pub struct LanDiscovery {
    pub port: u16,
}

pub async fn scan_lan(port: u16, timeout_ms: u64) -> Result<Vec<Endpoint>> {
    LanDiscovery { port }.scan(timeout_ms).await
}

fn bind_scan(port: u16) -> std::io::Result<UdpSocket> {
    let addr = SocketAddr::from(([0, 0, 0, 0], port));
    let socket = socket2::Socket::new(
        socket2::Domain::IPV4,
        socket2::Type::DGRAM,
        Some(socket2::Protocol::UDP),
    )?;
    socket.set_reuse_address(true)?;
    socket.bind(&addr.into())?;
    socket.set_nonblocking(true)?;
    UdpSocket::from_std(socket.into())
}

#[async_trait]
impl DiscoveryProvider for LanDiscovery {
    fn name(&self) -> &'static str {
        "lan"
    }
    async fn advertise(&self, self_id: PeerId, stream_port: u16) -> Result<()> {
        let sock = UdpSocket::bind("0.0.0.0:0").await?;
        sock.set_broadcast(true)?;
        let beacon = Beacon {
            version: PROTOCOL_VERSION,
            stream_port,
            node_id: self_id,
        };
        let bytes = beacon.encode();
        let mut tick = interval(Duration::from_secs(2));
        loop {
            tick.tick().await;
            let _ = sock
                .send_to(&bytes, SocketAddr::from(([255, 255, 255, 255], self.port)))
                .await;
        }
    }
    async fn scan(&self, timeout_ms: u64) -> Result<Vec<Endpoint>> {
        let sock = bind_scan(self.port)?;
        let mut buf = [0u8; 64];
        let mut out = Vec::new();
        let deadline = Duration::from_millis(timeout_ms.max(1));
        let start = tokio::time::Instant::now();
        while start.elapsed() < deadline {
            let remain = deadline.saturating_sub(start.elapsed());
            match timeout(remain, sock.recv_from(&mut buf)).await {
                Ok(Ok((n, from))) => {
                    if let Ok(b) = Beacon::decode(&buf[..n]) {
                        out.push(Endpoint {
                            peer: Some(b.node_id),
                            locator: format!("{}:{}", from.ip(), b.stream_port),
                        });
                    }
                }
                _ => break,
            }
        }
        Ok(dedupe_endpoints(out))
    }
}

pub fn dedupe_endpoints(eps: Vec<Endpoint>) -> Vec<Endpoint> {
    let mut seen = std::collections::BTreeSet::new();
    let mut out = Vec::new();
    for e in eps {
        let key = format!(
            "{}|{}",
            e.locator,
            e.peer.map(|p| p.to_string()).unwrap_or_default()
        );
        if seen.insert(key) {
            out.push(e);
        }
    }
    out
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn dedupe_keeps_one_locator() {
        let p = PeerId::from_digest([1u8; 32]);
        let a = Endpoint {
            peer: Some(p),
            locator: "10.0.0.2:7947".into(),
        };
        let out = dedupe_endpoints(vec![a.clone(), a]);
        assert_eq!(out.len(), 1);
    }
}