Skip to main content

deaddrop_net/discovery/
mod.rs

1pub mod lan;
2use async_trait::async_trait;
3use deaddrop_core::store::Store;
4use deaddrop_core::{PROTOCOL_VERSION, PeerId, Result, hex_encode};
5use std::net::SocketAddr;
6
7pub use lan::{LanDiscovery, dedupe_endpoints, scan_lan};
8
9pub const BEACON_MAGIC: &[u8; 4] = b"DDP2";
10pub const LAST_LOCATOR_KEY: &str = "lan-locator:last";
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct Endpoint {
14    pub peer: Option<PeerId>,
15    pub locator: String,
16}
17
18pub fn locator_key(peer: PeerId) -> String {
19    format!("lan-locator:{}", hex_encode(peer.as_bytes()))
20}
21
22pub fn remember_locator(store: &Store, peer: Option<PeerId>, locator: &str) -> Result<()> {
23    if let Some(p) = peer {
24        store.kv_set(&locator_key(p), locator)?;
25    }
26    store.kv_set(LAST_LOCATOR_KEY, locator)
27}
28
29pub fn remembered_locator(store: &Store, peer: PeerId) -> Result<Option<String>> {
30    store.kv_get(&locator_key(peer))
31}
32
33pub fn last_locator(store: &Store) -> Result<Option<String>> {
34    store.kv_get(LAST_LOCATOR_KEY)
35}
36
37/// Discovery MUST NOT imply trust. A beacon is a reachability claim only.
38#[async_trait]
39pub trait DiscoveryProvider: Send + Sync {
40    fn name(&self) -> &'static str;
41    async fn advertise(&self, self_id: PeerId, stream_port: u16) -> Result<()>;
42    async fn scan(&self, timeout_ms: u64) -> Result<Vec<Endpoint>>;
43}
44
45#[derive(Debug, Clone, Copy)]
46pub struct Beacon {
47    pub version: u16,
48    pub stream_port: u16,
49    pub node_id: PeerId,
50}
51
52impl Beacon {
53    pub fn encode(self) -> [u8; 4 + 2 + 2 + 32] {
54        let mut buf = [0u8; 40];
55        buf[0..4].copy_from_slice(BEACON_MAGIC);
56        buf[4..6].copy_from_slice(&self.version.to_be_bytes());
57        buf[6..8].copy_from_slice(&self.stream_port.to_be_bytes());
58        buf[8..].copy_from_slice(self.node_id.as_bytes());
59        buf
60    }
61
62    pub fn decode(bytes: &[u8]) -> Result<Self> {
63        if bytes.len() < 40 || &bytes[0..4] != BEACON_MAGIC {
64            return Err(deaddrop_core::DdError::invalid_frame("beacon"));
65        }
66        let version = u16::from_be_bytes([bytes[4], bytes[5]]);
67        if version != PROTOCOL_VERSION {
68            return Err(deaddrop_core::DdError::protocol(
69                deaddrop_core::ErrorCode::Ddp1002UnsupportedVersion,
70                "beacon",
71            ));
72        }
73        let stream_port = u16::from_be_bytes([bytes[6], bytes[7]]);
74        let mut id = [0u8; 32];
75        id.copy_from_slice(&bytes[8..40]);
76        Ok(Self {
77            version,
78            stream_port,
79            node_id: PeerId::from_digest(id),
80        })
81    }
82}
83
84pub struct StaticPeers {
85    pub endpoints: Vec<SocketAddr>,
86}
87
88#[async_trait]
89impl DiscoveryProvider for StaticPeers {
90    fn name(&self) -> &'static str {
91        "static"
92    }
93    async fn advertise(&self, _self_id: PeerId, _stream_port: u16) -> Result<()> {
94        Ok(())
95    }
96    async fn scan(&self, _timeout_ms: u64) -> Result<Vec<Endpoint>> {
97        Ok(self
98            .endpoints
99            .iter()
100            .map(|a| Endpoint {
101                peer: None,
102                locator: a.to_string(),
103            })
104            .collect())
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111    use deaddrop_core::store::Store;
112
113    fn tmp(name: &str) -> std::path::PathBuf {
114        let p = std::env::temp_dir().join(format!(
115            "dd-lan-{}-{}",
116            name,
117            std::time::SystemTime::now()
118                .duration_since(std::time::UNIX_EPOCH)
119                .unwrap()
120                .as_nanos()
121        ));
122        let _ = std::fs::remove_dir_all(&p);
123        std::fs::create_dir_all(&p).unwrap();
124        p
125    }
126
127    #[test]
128    fn remembers_locator_by_peer() {
129        let dir = tmp("kv");
130        let store = Store::open(&dir, Default::default()).unwrap();
131        let peer = PeerId::from_digest([9u8; 32]);
132        remember_locator(&store, Some(peer), "192.168.1.20:7947").unwrap();
133        assert_eq!(
134            remembered_locator(&store, peer).unwrap().as_deref(),
135            Some("192.168.1.20:7947")
136        );
137        assert_eq!(
138            last_locator(&store).unwrap().as_deref(),
139            Some("192.168.1.20:7947")
140        );
141    }
142}