1use std::future::Future;
2use std::pin::Pin;
3use std::sync::Arc;
4
5use unb_client::{Endpoint, EndpointSet};
6use unb_runtime::WsError;
7
8use crate::connection::{ConnectError, PeerConnection};
9use crate::node::{Node, PeerLink};
10use crate::session::{CandidateFailure, CandidateOutcome, CandidateSession};
11
12#[doc(hidden)]
17pub trait EndpointDialer: Send + Sync {
18 fn supports(&self, kind: unb_client::TransportKind) -> bool;
19
20 fn dial(
21 &self,
22 endpoint: Endpoint,
23 ) -> Pin<Box<dyn Future<Output = Result<unb_runtime::Pipe, WsError>> + Send + 'static>>;
24}
25
26pub(crate) struct ReconnectCandidate {
27 pub(crate) identity: unb_core::NodeIdentity,
28 pub(crate) selected: PeerLink,
29 pub(crate) candidate_wire: Arc<unb_runtime::Wire>,
30}
31
32impl Node {
33 pub async fn connect(
39 self: &Arc<Self>,
40 endpoints: impl Into<EndpointSet>,
41 ) -> Result<PeerConnection, ConnectError> {
42 self.connect_using(endpoints.into(), None).await
43 }
44
45 #[doc(hidden)]
51 pub async fn connect_with_dialer(
52 self: &Arc<Self>,
53 endpoints: impl Into<EndpointSet>,
54 dialer: Arc<dyn EndpointDialer>,
55 ) -> Result<PeerConnection, ConnectError> {
56 self.connect_using(endpoints.into(), Some(dialer)).await
57 }
58
59 async fn connect_using(
60 self: &Arc<Self>,
61 set: EndpointSet,
62 dialer: Option<Arc<dyn EndpointDialer>>,
63 ) -> Result<PeerConnection, ConnectError> {
64 let key = set.cache_key();
65 let ordered = self
66 .dial_policy
67 .ordered_candidates(&key, &set)
68 .into_iter()
69 .filter(|endpoint| {
70 dialer
71 .as_ref()
72 .is_none_or(|dialer| dialer.supports(endpoint.kind))
73 })
74 .collect::<Vec<_>>();
75 if ordered.is_empty() {
76 return Err(ConnectError::NoSupportedEndpoint);
77 }
78 let mut last_error = ConnectError::NoSupportedEndpoint;
79 for endpoint in ordered {
80 match self.try_candidate(&endpoint, None, dialer.as_ref()).await {
81 Ok((candidate, outcome)) => {
82 let identity = match outcome {
83 CandidateOutcome::Promoted(identity)
84 | CandidateOutcome::Duplicate(identity) => identity,
85 };
86 let Some(link) = self.peer(&identity.node_id).await else {
87 candidate.wire.shutdown();
88 let _ = candidate.cleaned.await;
89 last_error = ConnectError::Establishment {
90 message: format!(
91 "verified peer {:?} has no selected live session",
92 identity.node_id
93 ),
94 };
95 continue;
96 };
97 let connection = {
98 let mut connections = self
99 .connections
100 .write()
101 .unwrap_or_else(|poisoned| poisoned.into_inner());
102 if let Some(connection) = connections.get(&identity.node_id).cloned() {
103 connection.bind(identity, link.session_id.clone(), link.wire.clone());
104 connection.replace_endpoints(set.clone());
105 connection.replace_dialer(dialer.clone());
106 connection
107 } else {
108 let connection = PeerConnection::new(
109 Arc::downgrade(self),
110 identity.clone(),
111 set.clone(),
112 link.session_id.clone(),
113 link.wire.clone(),
114 dialer.clone(),
115 );
116 connections.insert(identity.node_id, connection.clone());
117 connection
118 }
119 };
120 self.dial_policy.record_winner(&key, endpoint.kind);
121 return Ok(connection);
122 }
123 Err(error) => last_error = error,
124 }
125 }
126 Err(last_error)
127 }
128
129 async fn try_candidate(
130 self: &Arc<Self>,
131 endpoint: &Endpoint,
132 expected_peer: Option<&str>,
133 dialer: Option<&Arc<dyn EndpointDialer>>,
134 ) -> Result<(CandidateSession, CandidateOutcome), ConnectError> {
135 let deadline = self.dial_policy.attempt_timeout();
136 let candidate_dial = async {
137 match dialer {
138 Some(dialer) => dialer.dial(endpoint.clone()).await,
139 None => self.dial_policy.dial_candidate(endpoint).await,
140 }
141 };
142 let pipe = match n0_future::time::timeout(deadline, candidate_dial).await {
143 Ok(Ok(pipe)) => pipe,
144 Ok(Err(error)) => {
145 return Err(ConnectError::Dial {
146 transport: endpoint.kind,
147 message: error.to_string(),
148 })
149 }
150 Err(_) => {
151 return Err(ConnectError::DialTimedOut {
152 transport: endpoint.kind,
153 })
154 }
155 };
156 let candidate = self.establish(pipe, expected_peer.map(str::to_owned)).await;
157 let outcome = candidate.observed_outcome().await;
158 match outcome {
159 Ok(outcome) => Ok((candidate, outcome)),
160 Err(failure) => {
161 candidate.wire.shutdown();
162 let _ = candidate.cleaned.await;
163 Err(match (expected_peer, failure) {
164 (
165 Some(expected),
166 CandidateFailure::Retired {
167 reason: unb_core::RetirementReason::UnexpectedPeer,
168 identity,
169 },
170 ) => ConnectError::IdentityMismatch {
171 expected: expected.to_string(),
172 actual: identity.map(|identity| identity.node_id),
173 },
174 (_, CandidateFailure::Session(error)) => ConnectError::Establishment {
175 message: error.to_string(),
176 },
177 (_, CandidateFailure::Retired { reason, .. }) => ConnectError::Establishment {
178 message: format!("session retired during establishment: {reason:?}"),
179 },
180 (_, CandidateFailure::MissingIdentity) => ConnectError::Establishment {
181 message: "session completed without an admitted identity".into(),
182 },
183 })
184 }
185 }
186 }
187
188 pub(crate) async fn reconnect_peer(
189 self: &Arc<Self>,
190 peer: &str,
191 set: &EndpointSet,
192 dialer: Option<Arc<dyn EndpointDialer>>,
193 ) -> Result<ReconnectCandidate, ConnectError> {
194 let key = set.cache_key();
195 let ordered = self
196 .dial_policy
197 .ordered_candidates(&key, set)
198 .into_iter()
199 .filter(|endpoint| {
200 dialer
201 .as_ref()
202 .is_none_or(|dialer| dialer.supports(endpoint.kind))
203 })
204 .collect::<Vec<_>>();
205 if ordered.is_empty() {
206 return Err(ConnectError::NoSupportedEndpoint);
207 }
208 let mut last_error = ConnectError::NoSupportedEndpoint;
209 for endpoint in ordered {
210 match self
211 .try_candidate(&endpoint, Some(peer), dialer.as_ref())
212 .await
213 {
214 Ok((candidate, outcome)) => {
215 let identity = match outcome {
216 CandidateOutcome::Promoted(identity)
217 | CandidateOutcome::Duplicate(identity) => identity,
218 };
219 let Some(selected) = self.peer(&identity.node_id).await else {
220 candidate.wire.shutdown();
221 let _ = candidate.cleaned.await;
222 last_error = ConnectError::Establishment {
223 message: format!(
224 "verified peer {:?} has no selected live session",
225 identity.node_id
226 ),
227 };
228 continue;
229 };
230 self.dial_policy.record_winner(&key, endpoint.kind);
231 return Ok(ReconnectCandidate {
232 identity,
233 selected,
234 candidate_wire: candidate.wire,
235 });
236 }
237 Err(error) => last_error = error,
238 }
239 }
240 Err(last_error)
241 }
242
243 pub async fn link(self: &Arc<Self>, other: &Arc<Node>) -> Result<(), WsError> {
244 if Arc::ptr_eq(self, other) || self.identity.node_id == other.identity.node_id {
245 return Err(WsError::Connect("a node cannot link to itself".into()));
246 }
247 let (dial_side, accept_side) = unb_client::pair();
248 let left = self
249 .establish(dial_side, Some(other.identity.node_id.clone()))
250 .await;
251 let right = other
252 .establish(accept_side, Some(self.identity.node_id.clone()))
253 .await;
254 let result = match tokio::join!(
255 left.outcome(&other.identity.node_id),
256 right.outcome(&self.identity.node_id)
257 ) {
258 (Ok(CandidateOutcome::Promoted(_)), Ok(CandidateOutcome::Promoted(_))) => {
259 let _ = n0_future::time::timeout(crate::session::ROUTE_SYNC_TIMEOUT, async {
260 tokio::join!(left.wire.routes_acked(), right.wire.routes_acked())
261 })
262 .await;
263 Ok(())
264 }
265 (Err(error), _) | (_, Err(error)) => Err(error),
266 _ => Err(WsError::Connect("link closed during establishment".into())),
267 };
268 if result.is_err() {
269 left.wire.shutdown();
270 right.wire.shutdown();
271 let _ = tokio::join!(left.cleaned, right.cleaned);
272 }
273 result
274 }
275
276 pub async fn connect_transport_unchecked(
279 self: &Arc<Self>,
280 transport: unb_runtime::Pipe,
281 ) -> Result<(), WsError> {
282 let candidate = self.establish(transport, None).await;
283 match candidate.outcome("candidate").await {
284 Ok(CandidateOutcome::Promoted(_) | CandidateOutcome::Duplicate(_)) => {
285 let _ = n0_future::time::timeout(
286 crate::session::ROUTE_SYNC_TIMEOUT,
287 candidate.wire.routes_acked(),
288 )
289 .await;
290 Ok(())
291 }
292 Err(error) => {
293 candidate.wire.shutdown();
294 let _ = candidate.cleaned.await;
295 Err(error)
296 }
297 }
298 }
299}