deaddrop_net/discovery/
mod.rs1pub 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#[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}