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