hyperswarm/
peer_discovery.rs1use std::{
2 net::SocketAddr,
3 pin::Pin,
4 task::{Context, Poll},
5 time::Duration,
6};
7
8use dht_rpc::{Commit, IdBytes};
9use futures::Stream;
10use hyperdht::{
11 Keypair,
12 adht::{Announce, Dht, Lookup, LookupResponse},
13};
14use tokio::time::Sleep;
15
16use crate::JoinOpts;
17
18const REFRESH_BASE_SECS: u64 = 600; const REFRESH_JITTER_SECS: u64 = 120; enum ActiveQuery {
22 Lookup(Lookup),
23 Announce(Announce),
24}
25
26impl Stream for ActiveQuery {
27 type Item = hyperdht::Result<Option<LookupResponse>>;
28
29 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
30 match self.get_mut() {
31 ActiveQuery::Lookup(lookup) => Pin::new(lookup).poll_next(cx),
32 ActiveQuery::Announce(announce) => Pin::new(announce).poll_next(cx),
33 }
34 }
35}
36
37enum PeerDiscoveryState {
38 Querying(Box<ActiveQuery>),
39 Sleeping(Pin<Box<Sleep>>),
40}
41
42pub struct PeerDiscovery {
43 topic: IdBytes,
44 opts: JoinOpts,
45 dht: Dht,
46 keypair: Keypair,
47 state: PeerDiscoveryState,
48 first_round_complete: bool,
49 relay_addresses: Vec<SocketAddr>,
50}
51
52impl PeerDiscovery {
53 pub fn new(
54 topic: IdBytes,
55 opts: JoinOpts,
56 dht: Dht,
57 keypair: Keypair,
58 relay_addresses: Vec<SocketAddr>,
59 ) -> Self {
60 let query = Self::create_query(&dht, topic, &opts, &keypair, &relay_addresses);
61 Self {
62 topic,
63 opts,
64 dht,
65 keypair,
66 state: PeerDiscoveryState::Querying(query),
67 first_round_complete: false,
68 relay_addresses,
69 }
70 }
71
72 pub fn topic(&self) -> IdBytes {
73 self.topic
74 }
75
76 pub fn is_first_round_complete(&self) -> bool {
77 self.first_round_complete
78 }
79
80 pub fn maybe_set_relay_addresses(&mut self, relay_addresses: &[SocketAddr]) {
82 if self.opts.server() && relay_addresses != self.relay_addresses {
83 self.relay_addresses = relay_addresses.to_vec();
84 }
85 }
86
87 pub fn refresh(&mut self) {
89 let query = Self::create_query(
90 &self.dht,
91 self.topic,
92 &self.opts,
93 &self.keypair,
94 &self.relay_addresses,
95 );
96 self.state = PeerDiscoveryState::Querying(query);
97 }
98
99 fn create_query(
100 dht: &Dht,
101 topic: IdBytes,
102 opts: &JoinOpts,
103 keypair: &Keypair,
104 relay_addresses: &[SocketAddr],
105 ) -> Box<ActiveQuery> {
106 Box::new(if opts.server() {
107 ActiveQuery::Announce(dht.announce(topic, keypair.clone(), relay_addresses.to_vec()))
108 } else {
109 ActiveQuery::Lookup(
110 dht.lookup(topic, Commit::No)
111 .expect("lookup creation failed"),
112 )
113 })
114 }
115
116 fn refresh_duration() -> Duration {
117 Duration::from_secs(REFRESH_BASE_SECS + rand::random::<u64>() % REFRESH_JITTER_SECS)
118 }
119}
120
121impl Stream for PeerDiscovery {
122 type Item = hyperdht::Result<Option<LookupResponse>>;
123
124 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
125 loop {
126 match &mut self.state {
127 PeerDiscoveryState::Querying(query) => match Pin::new(query.as_mut()).poll_next(cx)
128 {
129 Poll::Ready(Some(result)) => {
130 return Poll::Ready(Some(result));
131 }
132 Poll::Ready(None) => {
133 self.first_round_complete = true;
134 let duration = Self::refresh_duration();
135 self.state =
136 PeerDiscoveryState::Sleeping(Box::pin(tokio::time::sleep(duration)));
137 }
138 Poll::Pending => return Poll::Pending,
139 },
140 PeerDiscoveryState::Sleeping(timer) => match timer.as_mut().poll(cx) {
141 Poll::Ready(()) => {
142 let query = Self::create_query(
143 &self.dht,
144 self.topic,
145 &self.opts,
146 &self.keypair,
147 &self.relay_addresses,
148 );
149 self.state = PeerDiscoveryState::Querying(query);
150 }
151 Poll::Pending => {
152 return Poll::Pending;
153 }
154 },
155 }
156 }
157 }
158}