Skip to main content

ant_quic/
node_event.rs

1// Copyright 2024 Saorsa Labs Ltd.
2//
3// This Saorsa Network Software is licensed under the General Public License (GPL), version 3.
4// Please see the file LICENSE-GPL, or visit <http://www.gnu.org/licenses/> for the full text.
5//
6// Full details available at https://saorsalabs.com/licenses
7
8//! Unified events for P2P nodes
9//!
10//! This module provides [`NodeEvent`] - a single event type that covers
11//! all significant node activities including connections, NAT detection,
12//! relay sessions, and data transfer.
13//!
14//! # Example
15//!
16//! ```rust,ignore
17//! use ant_quic::Node;
18//!
19//! let node = Node::new().await?;
20//! let mut events = node.subscribe();
21//!
22//! tokio::spawn(async move {
23//!     while let Ok(event) = events.recv().await {
24//!         match event {
25//!             NodeEvent::PeerConnected { peer_id, .. } => {
26//!                 println!("Connected to: {:?}", peer_id);
27//!             }
28//!             NodeEvent::NatTypeDetected { nat_type } => {
29//!                 println!("NAT type: {:?}", nat_type);
30//!             }
31//!             _ => {}
32//!         }
33//!     }
34//! });
35//! ```
36
37use std::net::SocketAddr;
38
39use crate::nat_traversal_api::PeerId;
40use crate::node_status::NatType;
41use crate::transport::TransportAddr;
42
43/// Reason for peer disconnection
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub enum DisconnectReason {
46    /// Normal graceful shutdown
47    Graceful,
48    /// Connection timeout
49    Timeout,
50    /// Connection reset by peer
51    Reset,
52    /// Application-level close
53    ApplicationClose,
54    /// Idle timeout
55    Idle,
56    /// Transport error
57    TransportError(String),
58    /// Unknown reason
59    Unknown,
60}
61
62impl std::fmt::Display for DisconnectReason {
63    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64        match self {
65            Self::Graceful => write!(f, "graceful shutdown"),
66            Self::Timeout => write!(f, "connection timeout"),
67            Self::Reset => write!(f, "connection reset"),
68            Self::ApplicationClose => write!(f, "application close"),
69            Self::Idle => write!(f, "idle timeout"),
70            Self::TransportError(e) => write!(f, "transport error: {}", e),
71            Self::Unknown => write!(f, "unknown reason"),
72        }
73    }
74}
75
76/// Unified event type for all node activities
77///
78/// Subscribe to these events via `node.subscribe()` to monitor
79/// all significant node activities in real-time.
80#[derive(Debug, Clone)]
81pub enum NodeEvent {
82    // --- Peer Events ---
83    /// A peer connected successfully
84    PeerConnected {
85        /// The connected peer's ID
86        peer_id: PeerId,
87        /// The peer's address (supports all transport types)
88        addr: TransportAddr,
89        /// Whether this is a direct connection (vs relayed)
90        direct: bool,
91    },
92
93    /// A peer disconnected
94    PeerDisconnected {
95        /// The disconnected peer's ID
96        peer_id: PeerId,
97        /// Reason for disconnection
98        reason: DisconnectReason,
99    },
100
101    /// Connection attempt failed
102    ConnectionFailed {
103        /// Target address that failed
104        addr: SocketAddr,
105        /// Error message
106        error: String,
107    },
108
109    // --- NAT Events ---
110    /// External address discovered
111    ///
112    /// This is the address as seen by other peers.
113    ExternalAddressDiscovered {
114        /// The discovered external address (supports all transport types)
115        addr: TransportAddr,
116    },
117
118    /// NAT type detected
119    NatTypeDetected {
120        /// The detected NAT type
121        nat_type: NatType,
122    },
123
124    /// NAT traversal completed
125    NatTraversalComplete {
126        /// The peer we traversed to
127        peer_id: PeerId,
128        /// Whether traversal was successful
129        success: bool,
130        /// Connection method used
131        method: TraversalMethod,
132    },
133
134    // --- Relay Events ---
135    /// Started relaying for a peer
136    RelaySessionStarted {
137        /// The peer we're relaying for
138        peer_id: PeerId,
139    },
140
141    /// Stopped relaying for a peer
142    RelaySessionEnded {
143        /// The peer we were relaying for
144        peer_id: PeerId,
145        /// Total bytes forwarded during session
146        bytes_forwarded: u64,
147    },
148
149    // --- Coordination Events ---
150    /// Started coordinating NAT traversal for peers
151    CoordinationStarted {
152        /// Peer A in the coordination
153        peer_a: PeerId,
154        /// Peer B in the coordination
155        peer_b: PeerId,
156    },
157
158    /// NAT traversal coordination completed
159    CoordinationComplete {
160        /// Peer A in the coordination
161        peer_a: PeerId,
162        /// Peer B in the coordination
163        peer_b: PeerId,
164        /// Whether coordination was successful
165        success: bool,
166    },
167
168    // --- Data Events ---
169    /// Data received from a peer
170    DataReceived {
171        /// The peer that sent data
172        peer_id: PeerId,
173        /// Stream ID (for multiplexed connections)
174        stream_id: u64,
175        /// Number of bytes received
176        bytes: usize,
177    },
178
179    /// Data sent to a peer
180    DataSent {
181        /// The peer we sent data to
182        peer_id: PeerId,
183        /// Stream ID
184        stream_id: u64,
185        /// Number of bytes sent
186        bytes: usize,
187    },
188}
189
190/// Method used for NAT traversal
191#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
192pub enum TraversalMethod {
193    /// Direct connection (no NAT or easy NAT)
194    Direct,
195    /// Hole punching succeeded
196    HolePunch,
197    /// Connection via relay
198    Relay,
199    /// Port prediction for symmetric NAT
200    PortPrediction,
201}
202
203impl std::fmt::Display for TraversalMethod {
204    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205        match self {
206            Self::Direct => write!(f, "direct"),
207            Self::HolePunch => write!(f, "hole punch"),
208            Self::Relay => write!(f, "relay"),
209            Self::PortPrediction => write!(f, "port prediction"),
210        }
211    }
212}
213
214impl NodeEvent {
215    /// Check if this is a connection event
216    pub fn is_connection_event(&self) -> bool {
217        matches!(
218            self,
219            Self::PeerConnected { .. }
220                | Self::PeerDisconnected { .. }
221                | Self::ConnectionFailed { .. }
222        )
223    }
224
225    /// Check if this is a NAT-related event
226    pub fn is_nat_event(&self) -> bool {
227        matches!(
228            self,
229            Self::ExternalAddressDiscovered { .. }
230                | Self::NatTypeDetected { .. }
231                | Self::NatTraversalComplete { .. }
232        )
233    }
234
235    /// Check if this is a relay event
236    pub fn is_relay_event(&self) -> bool {
237        matches!(
238            self,
239            Self::RelaySessionStarted { .. } | Self::RelaySessionEnded { .. }
240        )
241    }
242
243    /// Check if this is a coordination event
244    pub fn is_coordination_event(&self) -> bool {
245        matches!(
246            self,
247            Self::CoordinationStarted { .. } | Self::CoordinationComplete { .. }
248        )
249    }
250
251    /// Check if this is a data event
252    pub fn is_data_event(&self) -> bool {
253        matches!(self, Self::DataReceived { .. } | Self::DataSent { .. })
254    }
255
256    /// Get the peer ID associated with this event (if any)
257    pub fn peer_id(&self) -> Option<&PeerId> {
258        match self {
259            Self::PeerConnected { peer_id, .. } => Some(peer_id),
260            Self::PeerDisconnected { peer_id, .. } => Some(peer_id),
261            Self::NatTraversalComplete { peer_id, .. } => Some(peer_id),
262            Self::RelaySessionStarted { peer_id } => Some(peer_id),
263            Self::RelaySessionEnded { peer_id, .. } => Some(peer_id),
264            Self::DataReceived { peer_id, .. } => Some(peer_id),
265            Self::DataSent { peer_id, .. } => Some(peer_id),
266            _ => None,
267        }
268    }
269}
270
271// Import P2pDisconnectReason for the From implementation
272use crate::p2p_endpoint::DisconnectReason as P2pDisconnectReason;
273
274/// Convert P2pDisconnectReason to NodeDisconnectReason (DisconnectReason in node_event)
275///
276/// This provides an idiomatic conversion between the two disconnect reason types
277/// used at different API layers.
278impl From<P2pDisconnectReason> for DisconnectReason {
279    fn from(reason: P2pDisconnectReason) -> Self {
280        match reason {
281            P2pDisconnectReason::Normal => Self::Graceful,
282            P2pDisconnectReason::Timeout => Self::Timeout,
283            P2pDisconnectReason::ProtocolError(e) => Self::TransportError(e),
284            P2pDisconnectReason::AuthenticationFailed => {
285                Self::TransportError("authentication failed".to_string())
286            }
287            P2pDisconnectReason::ConnectionLost => Self::Reset,
288            P2pDisconnectReason::RemoteClosed => Self::ApplicationClose,
289        }
290    }
291}
292
293#[cfg(test)]
294mod tests {
295    use super::*;
296
297    fn test_peer_id() -> PeerId {
298        PeerId([1u8; 32])
299    }
300
301    fn test_addr() -> SocketAddr {
302        "127.0.0.1:9000".parse().unwrap()
303    }
304
305    #[test]
306    fn test_peer_connected_event() {
307        let event = NodeEvent::PeerConnected {
308            peer_id: test_peer_id(),
309            addr: TransportAddr::Udp(test_addr()),
310            direct: true,
311        };
312
313        assert!(event.is_connection_event());
314        assert!(!event.is_nat_event());
315        assert_eq!(event.peer_id(), Some(&test_peer_id()));
316    }
317
318    #[test]
319    fn test_peer_disconnected_event() {
320        let event = NodeEvent::PeerDisconnected {
321            peer_id: test_peer_id(),
322            reason: DisconnectReason::Graceful,
323        };
324
325        assert!(event.is_connection_event());
326        assert_eq!(event.peer_id(), Some(&test_peer_id()));
327    }
328
329    #[test]
330    fn test_nat_type_detected_event() {
331        let event = NodeEvent::NatTypeDetected {
332            nat_type: NatType::FullCone,
333        };
334
335        assert!(event.is_nat_event());
336        assert!(!event.is_connection_event());
337        assert!(event.peer_id().is_none());
338    }
339
340    #[test]
341    fn test_relay_session_events() {
342        let start = NodeEvent::RelaySessionStarted {
343            peer_id: test_peer_id(),
344        };
345
346        let end = NodeEvent::RelaySessionEnded {
347            peer_id: test_peer_id(),
348            bytes_forwarded: 1024,
349        };
350
351        assert!(start.is_relay_event());
352        assert!(end.is_relay_event());
353        assert!(!start.is_connection_event());
354    }
355
356    #[test]
357    fn test_coordination_events() {
358        let peer_a = PeerId([1u8; 32]);
359        let peer_b = PeerId([2u8; 32]);
360
361        let start = NodeEvent::CoordinationStarted { peer_a, peer_b };
362
363        let complete = NodeEvent::CoordinationComplete {
364            peer_a,
365            peer_b,
366            success: true,
367        };
368
369        assert!(start.is_coordination_event());
370        assert!(complete.is_coordination_event());
371    }
372
373    #[test]
374    fn test_data_events() {
375        let recv = NodeEvent::DataReceived {
376            peer_id: test_peer_id(),
377            stream_id: 1,
378            bytes: 1024,
379        };
380
381        let send = NodeEvent::DataSent {
382            peer_id: test_peer_id(),
383            stream_id: 1,
384            bytes: 512,
385        };
386
387        assert!(recv.is_data_event());
388        assert!(send.is_data_event());
389        assert!(!recv.is_connection_event());
390    }
391
392    #[test]
393    fn test_disconnect_reason_display() {
394        assert_eq!(
395            format!("{}", DisconnectReason::Graceful),
396            "graceful shutdown"
397        );
398        assert_eq!(
399            format!("{}", DisconnectReason::Timeout),
400            "connection timeout"
401        );
402        assert_eq!(
403            format!("{}", DisconnectReason::TransportError("test".to_string())),
404            "transport error: test"
405        );
406    }
407
408    #[test]
409    fn test_traversal_method_display() {
410        assert_eq!(format!("{}", TraversalMethod::Direct), "direct");
411        assert_eq!(format!("{}", TraversalMethod::HolePunch), "hole punch");
412        assert_eq!(format!("{}", TraversalMethod::Relay), "relay");
413        assert_eq!(
414            format!("{}", TraversalMethod::PortPrediction),
415            "port prediction"
416        );
417    }
418
419    #[test]
420    fn test_events_are_clone() {
421        let event = NodeEvent::PeerConnected {
422            peer_id: test_peer_id(),
423            addr: TransportAddr::Udp(test_addr()),
424            direct: true,
425        };
426
427        let cloned = event.clone();
428        assert!(cloned.is_connection_event());
429    }
430
431    #[test]
432    fn test_events_are_debug() {
433        let event = NodeEvent::NatTypeDetected {
434            nat_type: NatType::Symmetric,
435        };
436
437        let debug_str = format!("{:?}", event);
438        assert!(debug_str.contains("NatTypeDetected"));
439        assert!(debug_str.contains("Symmetric"));
440    }
441
442    #[test]
443    fn test_connection_failed_event() {
444        let event = NodeEvent::ConnectionFailed {
445            addr: test_addr(),
446            error: "connection refused".to_string(),
447        };
448
449        assert!(event.is_connection_event());
450        assert!(event.peer_id().is_none());
451    }
452
453    #[test]
454    fn test_external_address_discovered() {
455        let event = NodeEvent::ExternalAddressDiscovered {
456            addr: TransportAddr::Udp("1.2.3.4:9000".parse().unwrap()),
457        };
458
459        assert!(event.is_nat_event());
460        assert!(event.peer_id().is_none());
461    }
462}