1use std::{future::Future, result};
2
3use iroh::{
4 endpoint::{ConnectOptions, Connection},
5 Endpoint, NodeId,
6};
7use n0_future::{BufferedStreamExt, Stream, StreamExt};
8use snafu::prelude::*;
9use tracing::trace;
10
11use crate::protocol::{
12 Query, QueryResponse, Request, Response, SignedAnnounce, ALPN, REQUEST_SIZE_LIMIT,
13};
14
15#[derive(Debug, Snafu)]
16pub enum Error {
17 #[snafu(display("Failed to connect to tracker: {}", source))]
18 Connect {
19 source: iroh::endpoint::ConnectWithOptsError,
20 backtrace: snafu::Backtrace,
21 },
22
23 #[snafu(display("Failed connect to tracker using 1-rtt: {}", source))]
24 Connect1Rtt {
25 source: iroh::endpoint::ConnectionError,
26 backtrace: snafu::Backtrace,
27 },
28
29 #[snafu(display("Failed to open bidi stream to tracker: {}", source))]
30 OpenStream {
31 source: iroh::endpoint::ConnectionError,
32 backtrace: snafu::Backtrace,
33 },
34
35 #[snafu(display("Failed to serialize request: {}", source))]
36 SerializeRequest {
37 source: postcard::Error,
38 backtrace: snafu::Backtrace,
39 },
40
41 #[snafu(display("Failed to write data: {}", source))]
42 WriteRequest {
43 source: iroh::endpoint::WriteError,
44 backtrace: snafu::Backtrace,
45 },
46
47 #[snafu(display("Failed to finish: {}", source))]
48 FinishWrite {
49 source: iroh::endpoint::ClosedStream,
50 backtrace: snafu::Backtrace,
51 },
52
53 #[snafu(display("Failed to read response: {}", source))]
54 ReadResponse {
55 source: iroh::endpoint::ReadToEndError,
56 backtrace: snafu::Backtrace,
57 },
58
59 #[snafu(display("Failed to deserialize response: {}", source))]
60 DeserializeResponse {
61 source: postcard::Error,
62 backtrace: snafu::Backtrace,
63 },
64
65 #[snafu(display("Failed to get remote node id: {}", source))]
66 RemoteNodeId {
67 source: iroh::endpoint::RemoteNodeIdError,
68 backtrace: snafu::Backtrace,
69 },
70}
71
72pub type Result<T> = result::Result<T, Error>;
73
74pub fn announce_all(
76 endpoint: Endpoint,
77 trackers: impl IntoIterator<Item = NodeId>,
78 signed_announce: SignedAnnounce,
79 announce_parallelism: usize,
80) -> impl Stream<Item = (NodeId, Result<()>)> {
81 n0_future::stream::iter(trackers)
82 .map(move |tracker| {
83 let endpoint = endpoint.clone();
84 async move {
85 let res = announce(&endpoint, tracker, signed_announce).await;
86 (tracker, res)
87 }
88 })
89 .buffered_unordered(announce_parallelism)
90}
91
92pub async fn announce(
101 endpoint: &Endpoint,
102 node_id: NodeId,
103 signed_announce: SignedAnnounce,
104) -> Result<()> {
105 let connecting = endpoint
106 .connect_with_opts(node_id, ALPN, ConnectOptions::default())
107 .await
108 .context(ConnectSnafu)?;
109 match connecting.into_0rtt() {
110 Ok((connection, zero_rtt_accepted)) => {
111 trace!("connected to tracker using possibly 0-rtt: {node_id}");
112 announce_conn(&connection, signed_announce, zero_rtt_accepted).await?;
113 wait_for_session_ticket(connection);
114 Ok(())
115 }
116 Err(connecting) => {
117 let connection = connecting.await.context(Connect1RttSnafu)?;
118 trace!("connected to tracker using 1-rtt: {node_id}");
119 announce_conn(&connection, signed_announce, async { true }).await?;
120 connection.close(0u32.into(), b"");
121 Ok(())
122 }
123 }
124}
125
126pub async fn announce_conn(
132 connection: &Connection,
133 signed_announce: SignedAnnounce,
134 proceed: impl Future<Output = bool>,
135) -> Result<()> {
136 let (mut send, recv) = connection.open_bi().await.context(OpenStreamSnafu)?;
137 let request = Request::Announce(signed_announce);
138 let request = postcard::to_stdvec(&request).context(SerializeRequestSnafu)?;
139 trace!("sending announce");
140 send.write_all(&request).await.context(WriteRequestSnafu)?;
141 send.finish().context(FinishWriteSnafu)?;
142 let mut recv = if proceed.await {
143 recv
144 } else {
145 let (mut send, recv) = connection.open_bi().await.context(OpenStreamSnafu)?;
146 trace!("re-sending announce using 1-rtt");
147 send.write_all(&request).await.context(WriteRequestSnafu)?;
148 send.finish().context(FinishWriteSnafu)?;
149 recv
150 };
151 let _response = recv
152 .read_to_end(REQUEST_SIZE_LIMIT)
153 .await
154 .context(ReadResponseSnafu)?;
155 trace!("got response");
156 Ok(())
157}
158
159pub async fn query(
161 endpoint: &Endpoint,
162 node_id: NodeId,
163 args: Query,
164) -> Result<Vec<SignedAnnounce>> {
165 let connecting = endpoint
166 .connect_with_opts(node_id, ALPN, ConnectOptions::default())
167 .await
168 .context(ConnectSnafu)?;
169 let result = match connecting.into_0rtt() {
170 Ok((connection, zero_rtt_accepted)) => {
171 trace!("connected to tracker using possibly 0-rtt: {node_id}");
172 let res = query_conn(&connection, args, zero_rtt_accepted).await?;
173 wait_for_session_ticket(connection);
174 res
175 }
176 Err(connecting) => {
177 let connection = connecting.await.context(Connect1RttSnafu)?;
178 trace!("connected to tracker using 1-rtt: {node_id}");
179 let res = query_conn(&connection, args, async { true }).await?;
180 connection.close(0u32.into(), b"");
181 res
182 }
183 };
184 Ok(result.hosts)
185}
186
187pub fn query_all(
192 endpoint: Endpoint,
193 trackers: impl IntoIterator<Item = NodeId>,
194 args: Query,
195 query_parallelism: usize,
196) -> impl Stream<Item = Result<SignedAnnounce>> {
197 n0_future::stream::iter(trackers)
198 .map(move |tracker| {
199 let endpoint = endpoint.clone();
200 async move {
201 let hosts = match query(&endpoint, tracker, args).await {
202 Ok(hosts) => hosts.into_iter().map(Ok).collect(),
203 Err(cause) => vec![Err(cause)],
204 };
205 n0_future::stream::iter(hosts)
206 }
207 })
208 .buffered_unordered(query_parallelism)
209 .flatten()
210}
211
212pub async fn query_conn(
218 connection: &Connection,
219 args: Query,
220 proceed: impl Future<Output = bool>,
221) -> Result<QueryResponse> {
222 let request = Request::Query(args);
223 let request = postcard::to_stdvec(&request).context(SerializeRequestSnafu)?;
224 trace!(
225 "connected to {:?}",
226 connection.remote_node_id().context(RemoteNodeIdSnafu)?
227 );
228 trace!("opened bi stream");
229 let (mut send, recv) = connection.open_bi().await.context(OpenStreamSnafu)?;
230 trace!("sending query");
231 send.write_all(&request).await.context(WriteRequestSnafu)?;
232 send.finish().context(FinishWriteSnafu)?;
233 let mut recv = if proceed.await {
234 recv
235 } else {
236 let (mut send, recv) = connection.open_bi().await.context(OpenStreamSnafu)?;
237 trace!("sending query again using 1-rtt");
238 send.write_all(&request).await.context(WriteRequestSnafu)?;
239 send.finish().context(FinishWriteSnafu)?;
240 recv
241 };
242 let response = recv
243 .read_to_end(REQUEST_SIZE_LIMIT)
244 .await
245 .context(ReadResponseSnafu)?;
246 let response = postcard::from_bytes::<Response>(&response).context(DeserializeResponseSnafu)?;
247 Ok(match response {
248 Response::QueryResponse(response) => response,
249 })
250}
251
252fn wait_for_session_ticket(connection: Connection) {
253 tokio::spawn(async move {
254 tokio::time::sleep(connection.rtt() * 2).await;
257 connection.close(0u32.into(), b"");
258 });
259}