Skip to main content

connexa/handle/
dht.rs

1use crate::error::{ConnexaResult, Error};
2use crate::handle::Connexa;
3use crate::types::{DHTCommand, DHTEvent};
4use bytes::Bytes;
5use futures::StreamExt;
6use futures::channel::oneshot;
7use futures::stream::BoxStream;
8use libp2p::kad::{Mode, PeerInfo, PeerRecord, Quorum, RecordKey};
9use libp2p::{Multiaddr, PeerId};
10use std::collections::HashSet;
11
12pub struct ConnexaDht<'a, T, K> {
13    connexa: &'a Connexa<T, K>,
14}
15
16impl<'a, T, K> Copy for ConnexaDht<'a, T, K> {}
17
18impl<'a, T, K> Clone for ConnexaDht<'a, T, K> {
19    fn clone(&self) -> Self {
20        *self
21    }
22}
23
24impl<'a, T, K> ConnexaDht<'a, T, K>
25where
26    T: Send + Sync + 'static,
27{
28    pub(crate) fn new(connexa: &'a Connexa<T, K>) -> Self {
29        Self { connexa }
30    }
31
32    /// Queries the DHT for information about a specific peer by its PeerID
33    pub async fn find_peer(&self, peer_id: PeerId) -> ConnexaResult<Vec<PeerInfo>> {
34        let (tx, rx) = oneshot::channel();
35        self.connexa
36            .to_task
37            .clone()
38            .send(DHTCommand::FindPeer { peer_id, resp: tx }.into())
39            .await
40            .map_err(|_| Error::ChannelClosed)?;
41        rx.await.map_err(|_| Error::ChannelClosed)?
42    }
43
44    /// Announces to the DHT that this peer can provide data for a given key
45    pub async fn provide(&self, key: impl ToRecordKey) -> ConnexaResult<()> {
46        let key = key.to_record_key();
47        let (tx, rx) = oneshot::channel();
48        self.connexa
49            .to_task
50            .clone()
51            .send(DHTCommand::Provide { key, resp: tx }.into())
52            .await
53            .map_err(|_| Error::ChannelClosed)?;
54        rx.await.map_err(|_| Error::ChannelClosed)?
55    }
56
57    /// Stop announcing that this peer can provide data for a given key
58    pub async fn stop_provide(&self, key: impl ToRecordKey) -> ConnexaResult<()> {
59        let key = key.to_record_key();
60        let (tx, rx) = oneshot::channel();
61        self.connexa
62            .to_task
63            .clone()
64            .send(DHTCommand::StopProviding { key, resp: tx }.into())
65            .await
66            .map_err(|_| Error::ChannelClosed)?;
67        rx.await.map_err(|_| Error::ChannelClosed)?
68    }
69
70    /// Queries the DHT for peers that can provide data for a given key
71    pub async fn get_providers(
72        &self,
73        key: impl ToRecordKey,
74    ) -> ConnexaResult<BoxStream<'static, ConnexaResult<HashSet<PeerId>>>> {
75        let key = key.to_record_key();
76        let (tx, rx) = oneshot::channel();
77        self.connexa
78            .to_task
79            .clone()
80            .send(DHTCommand::GetProviders { key, resp: tx }.into())
81            .await
82            .map_err(|_| Error::ChannelClosed)?;
83        rx.await
84            .map_err(|_| Error::ChannelClosed)?
85            .map(|s| s.boxed())
86    }
87
88    /// Bootstraps the DHT node.
89    /// Note that this will continue to wait until bootstrapping completes
90    pub async fn bootstrap(&self) -> ConnexaResult<()> {
91        let (tx, rx) = oneshot::channel();
92        self.connexa
93            .to_task
94            .clone()
95            .send(
96                DHTCommand::Bootstrap {
97                    lazy: false,
98                    resp: tx,
99                }
100                .into(),
101            )
102            .await
103            .map_err(|_| Error::ChannelClosed)?;
104        rx.await.map_err(|_| Error::ChannelClosed)?
105    }
106
107    /// Lazily bootstraps the DHT node.
108    /// Note that this will handle bootstrapping in the background
109    pub async fn bootstrap_lazy(&self) -> ConnexaResult<()> {
110        let (tx, rx) = oneshot::channel();
111        self.connexa
112            .to_task
113            .clone()
114            .send(
115                DHTCommand::Bootstrap {
116                    lazy: true,
117                    resp: tx,
118                }
119                .into(),
120            )
121            .await
122            .map_err(|_| Error::ChannelClosed)?;
123        rx.await.map_err(|_| Error::ChannelClosed)?
124    }
125
126    /// Creates a listener for DHT events related to a specific key
127    pub async fn listener(
128        &self,
129        key: impl ToOptionalRecordKey,
130    ) -> ConnexaResult<BoxStream<'static, DHTEvent>> {
131        let key = key.to_record_key();
132        let (tx, rx) = oneshot::channel();
133        self.connexa
134            .to_task
135            .clone()
136            .send(DHTCommand::Listener { key, resp: tx }.into())
137            .await
138            .map_err(|_| Error::ChannelClosed)?;
139        rx.await
140            .map_err(|_| Error::ChannelClosed)?
141            .map(|s| s.boxed())
142    }
143
144    /// Retrieves data from the DHT for a given key
145    pub async fn get(
146        &self,
147        key: impl ToRecordKey,
148    ) -> ConnexaResult<BoxStream<'static, ConnexaResult<PeerRecord>>> {
149        let key = key.to_record_key();
150        let (tx, rx) = oneshot::channel();
151        self.connexa
152            .to_task
153            .clone()
154            .send(DHTCommand::Get { key, resp: tx }.into())
155            .await
156            .map_err(|_| Error::ChannelClosed)?;
157        rx.await
158            .map_err(|_| Error::ChannelClosed)?
159            .map(|s| s.boxed())
160    }
161
162    /// Stores data in the DHT under a given key with a specified quorum
163    pub async fn put(
164        &self,
165        key: impl ToRecordKey,
166        data: impl Into<Bytes>,
167        quorum: Quorum,
168    ) -> ConnexaResult<()> {
169        let key = key.to_record_key();
170        let data = data.into();
171        let (tx, rx) = oneshot::channel();
172        self.connexa
173            .to_task
174            .clone()
175            .send(
176                DHTCommand::Put {
177                    key,
178                    data,
179                    quorum,
180                    resp: tx,
181                }
182                .into(),
183            )
184            .await
185            .map_err(|_| Error::ChannelClosed)?;
186        rx.await.map_err(|_| Error::ChannelClosed)?
187    }
188
189    /// Stores a record in the DHT targeting specific peers with a specified quorum.
190    /// Note that this operation does not store a record in the record store and will
191    /// require that the record exist before using this method.
192    ///
193    /// See [Behaviour::put_record_to](libp2p::kad::Behaviour::put_record_to) for more information
194    pub async fn put_to(
195        &self,
196        target: impl ExactSizeIterator<Item = PeerId>,
197        key: impl ToRecordKey,
198        data: impl Into<Bytes>,
199        quorum: Quorum,
200    ) -> ConnexaResult<()> {
201        let key = key.to_record_key();
202        let data = data.into();
203        let target = target.collect::<Vec<_>>();
204
205        let (tx, rx) = oneshot::channel();
206
207        self.connexa
208            .to_task
209            .clone()
210            .send(
211                DHTCommand::PutTo {
212                    target,
213                    key,
214                    data,
215                    quorum,
216                    resp: tx,
217                }
218                .into(),
219            )
220            .await
221            .map_err(|_| Error::ChannelClosed)?;
222        rx.await.map_err(|_| Error::ChannelClosed)?
223    }
224
225    /// Removes a record from the local record store
226    pub async fn remove_record(&self, key: impl ToRecordKey) -> ConnexaResult<()> {
227        let key = key.to_record_key();
228        let (tx, rx) = oneshot::channel();
229
230        self.connexa
231            .to_task
232            .clone()
233            .send(DHTCommand::Remove { key, resp: tx }.into())
234            .await
235            .map_err(|_| Error::ChannelClosed)?;
236        rx.await.map_err(|_| Error::ChannelClosed)?
237    }
238
239    /// Sets the DHT mode (Client/Server)
240    /// Mode can be None to automatically switch between client and server based on reachability.
241    pub async fn set_mode(&self, mode: impl Into<Option<Mode>>) -> ConnexaResult<()> {
242        let mode = mode.into();
243        let (tx, rx) = oneshot::channel();
244        self.connexa
245            .to_task
246            .clone()
247            .send(DHTCommand::SetDHTMode { mode, resp: tx }.into())
248            .await
249            .map_err(|_| Error::ChannelClosed)?;
250        rx.await.map_err(|_| Error::ChannelClosed)?
251    }
252
253    /// Gets the current DHT mode
254    pub async fn mode(&self) -> ConnexaResult<Mode> {
255        let (tx, rx) = oneshot::channel();
256        self.connexa
257            .to_task
258            .clone()
259            .send(DHTCommand::DHTMode { resp: tx }.into())
260            .await
261            .map_err(|_| Error::ChannelClosed)?;
262        rx.await.map_err(|_| Error::ChannelClosed)?
263    }
264
265    /// Adds an address to the routing table for a specific peer
266    pub async fn add_address(&self, peer_id: PeerId, addr: Multiaddr) -> ConnexaResult<()> {
267        let (tx, rx) = oneshot::channel();
268        self.connexa
269            .to_task
270            .clone()
271            .send(
272                DHTCommand::AddAddress {
273                    peer_id,
274                    addr,
275                    resp: tx,
276                }
277                .into(),
278            )
279            .await
280            .map_err(|_| Error::ChannelClosed)?;
281        rx.await.map_err(|_| Error::ChannelClosed)?
282    }
283
284    /// Removes an address from the routing table for a specific peer
285    pub async fn remove_address(&self, peer_id: PeerId, addr: Multiaddr) -> ConnexaResult<()> {
286        let (tx, rx) = oneshot::channel();
287        self.connexa
288            .to_task
289            .clone()
290            .send(
291                DHTCommand::RemoveAddress {
292                    peer_id,
293                    addr,
294                    resp: tx,
295                }
296                .into(),
297            )
298            .await
299            .map_err(|_| Error::ChannelClosed)?;
300        rx.await.map_err(|_| Error::ChannelClosed)?
301    }
302
303    /// Removes a peer from the routing table
304    pub async fn remove_peer(&self, peer_id: PeerId) -> ConnexaResult<()> {
305        let (tx, rx) = oneshot::channel();
306        self.connexa
307            .to_task
308            .clone()
309            .send(DHTCommand::RemovePeer { peer_id, resp: tx }.into())
310            .await
311            .map_err(|_| Error::ChannelClosed)?;
312        rx.await.map_err(|_| Error::ChannelClosed)?
313    }
314}
315
316pub trait ToRecordKey {
317    fn to_record_key(self) -> RecordKey;
318}
319
320pub trait ToOptionalRecordKey {
321    fn to_record_key(self) -> Option<RecordKey>;
322}
323
324#[cfg(feature = "cid")]
325impl ToRecordKey for cid::Cid {
326    fn to_record_key(self) -> RecordKey {
327        self.hash().to_bytes().into()
328    }
329}
330
331impl ToRecordKey for RecordKey {
332    fn to_record_key(self) -> RecordKey {
333        self
334    }
335}
336
337impl ToRecordKey for &RecordKey {
338    fn to_record_key(self) -> RecordKey {
339        self.clone()
340    }
341}
342
343impl ToRecordKey for String {
344    fn to_record_key(self) -> RecordKey {
345        self.into_bytes().into()
346    }
347}
348
349impl ToRecordKey for &String {
350    fn to_record_key(self) -> RecordKey {
351        self.as_bytes().to_vec().into()
352    }
353}
354
355impl ToRecordKey for &str {
356    fn to_record_key(self) -> RecordKey {
357        self.as_bytes().to_vec().into()
358    }
359}
360
361impl ToRecordKey for Vec<u8> {
362    fn to_record_key(self) -> RecordKey {
363        self.into()
364    }
365}
366
367impl ToRecordKey for &[u8] {
368    fn to_record_key(self) -> RecordKey {
369        self.to_vec().into()
370    }
371}
372
373impl<const N: usize> ToRecordKey for [u8; N] {
374    fn to_record_key(self) -> RecordKey {
375        self.to_vec().into()
376    }
377}
378
379impl ToRecordKey for Bytes {
380    fn to_record_key(self) -> RecordKey {
381        self.to_vec().into()
382    }
383}
384
385impl<R: ToRecordKey> ToOptionalRecordKey for R {
386    fn to_record_key(self) -> Option<RecordKey> {
387        Some(self.to_record_key())
388    }
389}
390
391impl<R: ToRecordKey> ToOptionalRecordKey for Option<R> {
392    fn to_record_key(self) -> Option<RecordKey> {
393        self.map(|r| r.to_record_key())
394    }
395}
396
397impl ToOptionalRecordKey for () {
398    fn to_record_key(self) -> Option<RecordKey> {
399        None
400    }
401}