1mod active;
11mod connection;
12
13pub use active::{ActivePeer, ConnectivityState};
14pub use connection::{HandshakeState, PeerConnection};
15
16use crate::NodeAddr;
17use crate::transport::LinkId;
18use std::fmt;
19use thiserror::Error;
20
21#[derive(Debug, Error)]
27pub enum PeerError {
28 #[error("peer not authenticated")]
29 NotAuthenticated,
30
31 #[error("peer not found: {0:?}")]
32 NotFound(NodeAddr),
33
34 #[error("connection not found: {0}")]
35 ConnectionNotFound(LinkId),
36
37 #[error("peer already exists: {0:?}")]
38 AlreadyExists(NodeAddr),
39
40 #[error("handshake failed: {0}")]
41 HandshakeFailed(String),
42
43 #[error("handshake timeout")]
44 HandshakeTimeout,
45
46 #[error("identity mismatch: expected {expected:?}, got {actual:?}")]
47 IdentityMismatch {
48 expected: NodeAddr,
49 actual: NodeAddr,
50 },
51
52 #[error("peer disconnected")]
53 Disconnected,
54
55 #[error("max connections exceeded: {max}")]
56 MaxConnectionsExceeded { max: usize },
57
58 #[error("max peers exceeded: {max}")]
59 MaxPeersExceeded { max: usize },
60}
61
62#[derive(Debug, Clone, Copy)]
76pub enum PromotionResult {
77 Promoted(NodeAddr),
79
80 CrossConnectionLost {
83 winner_link_id: LinkId,
85 },
86
87 CrossConnectionWon {
90 loser_link_id: LinkId,
92 node_addr: NodeAddr,
94 },
95}
96
97impl PromotionResult {
98 pub fn node_addr(&self) -> Option<NodeAddr> {
100 match self {
101 PromotionResult::Promoted(node_addr) => Some(*node_addr),
102 PromotionResult::CrossConnectionWon { node_addr, .. } => Some(*node_addr),
103 PromotionResult::CrossConnectionLost { .. } => None,
104 }
105 }
106
107 pub fn should_close_this_connection(&self) -> bool {
109 matches!(self, PromotionResult::CrossConnectionLost { .. })
110 }
111
112 pub fn link_to_close(&self) -> Option<LinkId> {
114 match self {
115 PromotionResult::CrossConnectionLost { .. } => None, PromotionResult::CrossConnectionWon { loser_link_id, .. } => Some(*loser_link_id),
117 PromotionResult::Promoted(_) => None,
118 }
119 }
120}
121
122pub fn cross_connection_winner(
135 our_node_addr: &NodeAddr,
136 their_node_addr: &NodeAddr,
137 this_is_outbound: bool,
138) -> bool {
139 let we_are_smaller = our_node_addr < their_node_addr;
140
141 if we_are_smaller {
145 this_is_outbound
146 } else {
147 !this_is_outbound
148 }
149}
150
151#[derive(Debug)]
157pub enum PeerSlot {
158 Connecting(Box<PeerConnection>),
160 Active(Box<ActivePeer>),
162}
163
164impl PeerSlot {
165 pub fn outbound(conn: PeerConnection) -> Self {
167 PeerSlot::Connecting(Box::new(conn))
168 }
169
170 pub fn inbound(conn: PeerConnection) -> Self {
172 PeerSlot::Connecting(Box::new(conn))
173 }
174
175 pub fn active(peer: ActivePeer) -> Self {
177 PeerSlot::Active(Box::new(peer))
178 }
179
180 pub fn is_connecting(&self) -> bool {
182 matches!(self, PeerSlot::Connecting(_))
183 }
184
185 pub fn is_active(&self) -> bool {
187 matches!(self, PeerSlot::Active(_))
188 }
189
190 pub fn link_id(&self) -> LinkId {
192 match self {
193 PeerSlot::Connecting(conn) => conn.link_id(),
194 PeerSlot::Active(peer) => peer.link_id(),
195 }
196 }
197
198 pub fn as_connection(&self) -> Option<&PeerConnection> {
200 match self {
201 PeerSlot::Connecting(conn) => Some(conn),
202 PeerSlot::Active(_) => None,
203 }
204 }
205
206 pub fn as_connection_mut(&mut self) -> Option<&mut PeerConnection> {
208 match self {
209 PeerSlot::Connecting(conn) => Some(conn),
210 PeerSlot::Active(_) => None,
211 }
212 }
213
214 pub fn as_active(&self) -> Option<&ActivePeer> {
216 match self {
217 PeerSlot::Active(peer) => Some(peer),
218 PeerSlot::Connecting(_) => None,
219 }
220 }
221
222 pub fn as_active_mut(&mut self) -> Option<&mut ActivePeer> {
224 match self {
225 PeerSlot::Active(peer) => Some(peer),
226 PeerSlot::Connecting(_) => None,
227 }
228 }
229
230 pub fn node_addr(&self) -> Option<&NodeAddr> {
235 match self {
236 PeerSlot::Connecting(conn) => conn.expected_identity().map(|id| id.node_addr()),
237 PeerSlot::Active(peer) => Some(peer.node_addr()),
238 }
239 }
240}
241
242impl fmt::Display for PeerSlot {
243 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244 match self {
245 PeerSlot::Connecting(conn) => {
246 write!(
247 f,
248 "connecting(link={}, state={})",
249 conn.link_id(),
250 conn.handshake_state()
251 )
252 }
253 PeerSlot::Active(peer) => {
254 write!(
255 f,
256 "active(node={:?}, link={})",
257 peer.node_addr(),
258 peer.link_id()
259 )
260 }
261 }
262 }
263}
264
265#[cfg(test)]
270mod tests {
271 use super::*;
272 use crate::transport::LinkId;
273 use crate::{Identity, PeerIdentity};
274
275 fn make_node_addr(val: u8) -> NodeAddr {
276 let mut bytes = [0u8; 16];
277 bytes[0] = val;
278 NodeAddr::from_bytes(bytes)
279 }
280
281 fn make_peer_identity() -> PeerIdentity {
282 let identity = Identity::generate();
283 PeerIdentity::from_pubkey(identity.pubkey())
284 }
285
286 #[test]
287 fn test_cross_connection_smaller_node_wins_outbound() {
288 let node_a = make_node_addr(1); let node_b = make_node_addr(2); assert!(cross_connection_winner(&node_a, &node_b, true)); assert!(!cross_connection_winner(&node_a, &node_b, false)); assert!(!cross_connection_winner(&node_b, &node_a, true)); assert!(cross_connection_winner(&node_b, &node_a, false)); }
299
300 #[test]
301 fn test_cross_connection_symmetric() {
302 let node_a = make_node_addr(1);
303 let node_b = make_node_addr(2);
304
305 let a_outbound_wins = cross_connection_winner(&node_a, &node_b, true);
307 let b_inbound_wins = cross_connection_winner(&node_b, &node_a, false);
308 assert_eq!(a_outbound_wins, b_inbound_wins);
309
310 let a_inbound_wins = cross_connection_winner(&node_a, &node_b, false);
312 let b_outbound_wins = cross_connection_winner(&node_b, &node_a, true);
313 assert_eq!(a_inbound_wins, b_outbound_wins);
314
315 assert!(a_outbound_wins != a_inbound_wins);
317 }
318
319 #[test]
320 fn test_peer_slot_connecting() {
321 let identity = make_peer_identity();
322 let conn = PeerConnection::outbound(LinkId::new(1), identity, 1000);
323 let slot = PeerSlot::Connecting(Box::new(conn));
324
325 assert!(slot.is_connecting());
326 assert!(!slot.is_active());
327 assert!(slot.as_connection().is_some());
328 assert!(slot.as_active().is_none());
329 assert_eq!(slot.link_id(), LinkId::new(1));
330 }
331
332 #[test]
333 fn test_peer_slot_active() {
334 let identity = make_peer_identity();
335 let peer = ActivePeer::new(identity, LinkId::new(2), 2000);
336 let slot = PeerSlot::Active(Box::new(peer));
337
338 assert!(!slot.is_connecting());
339 assert!(slot.is_active());
340 assert!(slot.as_connection().is_none());
341 assert!(slot.as_active().is_some());
342 assert_eq!(slot.link_id(), LinkId::new(2));
343 }
344
345 #[test]
346 fn test_promotion_result_promoted() {
347 let identity = make_peer_identity();
348 let node_addr = *identity.node_addr();
349 let result = PromotionResult::Promoted(node_addr);
350
351 assert!(result.node_addr().is_some());
352 assert_eq!(result.node_addr(), Some(node_addr));
353 assert!(!result.should_close_this_connection());
354 assert!(result.link_to_close().is_none());
355 }
356
357 #[test]
358 fn test_promotion_result_cross_lost() {
359 let result = PromotionResult::CrossConnectionLost {
360 winner_link_id: LinkId::new(1),
361 };
362
363 assert!(result.node_addr().is_none());
364 assert!(result.should_close_this_connection());
365 assert!(result.link_to_close().is_none()); }
367
368 #[test]
369 fn test_promotion_result_cross_won() {
370 let identity = make_peer_identity();
371 let node_addr = *identity.node_addr();
372 let result = PromotionResult::CrossConnectionWon {
373 loser_link_id: LinkId::new(1),
374 node_addr,
375 };
376
377 assert!(result.node_addr().is_some());
378 assert_eq!(result.node_addr(), Some(node_addr));
379 assert!(!result.should_close_this_connection());
380 assert_eq!(result.link_to_close(), Some(LinkId::new(1)));
381 }
382
383 #[test]
384 fn test_peer_slot_node_addr() {
385 let identity = make_peer_identity();
387 let expected_node_addr = *identity.node_addr();
388 let conn = PeerConnection::outbound(LinkId::new(1), identity, 1000);
389 let slot = PeerSlot::Connecting(Box::new(conn));
390 assert_eq!(slot.node_addr(), Some(&expected_node_addr));
391
392 let conn_inbound = PeerConnection::inbound(LinkId::new(2), 2000);
394 let slot_inbound = PeerSlot::Connecting(Box::new(conn_inbound));
395 assert!(slot_inbound.node_addr().is_none());
396
397 let identity2 = make_peer_identity();
399 let active_node_addr = *identity2.node_addr();
400 let peer = ActivePeer::new(identity2, LinkId::new(3), 3000);
401 let slot_active = PeerSlot::Active(Box::new(peer));
402 assert_eq!(slot_active.node_addr(), Some(&active_node_addr));
403 }
404}