stem_rs/
events.rs

1//! Event types and handling for Tor control protocol async notifications.
2//!
3//! This module provides comprehensive event types for all Tor control protocol
4//! asynchronous events, as described in section 4.1 of the
5//! [control-spec](https://spec.torproject.org/control-spec/replies.html#asynchronous-events).
6//!
7//! # Overview
8//!
9//! Tor emits asynchronous events to notify controllers about state changes,
10//! bandwidth usage, circuit activity, and other important occurrences. Events
11//! are received after subscribing via the `SETEVENTS` command through the
12//! [`Controller`](crate::Controller).
13//!
14//! # Event Categories
15//!
16//! Events are organized into several categories:
17//!
18//! - **Bandwidth Events**: [`BandwidthEvent`], [`CircuitBandwidthEvent`],
19//!   [`ConnectionBandwidthEvent`] - Track data transfer rates
20//! - **Circuit Events**: [`CircuitEvent`] - Monitor circuit lifecycle
21//! - **Stream Events**: [`StreamEvent`] - Track stream connections
22//! - **Connection Events**: [`OrConnEvent`] - Monitor OR connections
23//! - **Log Events**: [`LogEvent`] - Receive Tor log messages
24//! - **Status Events**: [`StatusEvent`] - Bootstrap progress and status changes
25//! - **Guard Events**: [`GuardEvent`] - Guard relay changes
26//! - **Hidden Service Events**: [`HsDescEvent`] - Hidden service descriptor activity
27//! - **Configuration Events**: [`ConfChangedEvent`] - Configuration changes
28//! - **Network Events**: [`NetworkLivenessEvent`] - Network connectivity status
29//!
30//! # Event Subscription
31//!
32//! To receive events, subscribe using the controller's `set_events` method:
33//!
34//! ```rust,no_run
35//! use stem_rs::{controller::Controller, EventType};
36//!
37//! # async fn example() -> Result<(), stem_rs::Error> {
38//! let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
39//! controller.authenticate(None).await?;
40//!
41//! // Subscribe to bandwidth and circuit events
42//! controller.set_events(&[EventType::Bw, EventType::Circ]).await?;
43//!
44//! // Events will now be delivered asynchronously
45//! # Ok(())
46//! # }
47//! ```
48//!
49//! # Event Parsing
50//!
51//! Raw event data from Tor is parsed into strongly-typed event structs using
52//! [`ParsedEvent::parse`]. Each event type provides access to its specific
53//! fields while also preserving the raw content for debugging.
54//!
55//! # Thread Safety
56//!
57//! All event types implement [`Send`] and [`Sync`], allowing them to be safely
58//! shared across threads. The [`Event`] trait requires these bounds.
59//!
60//! # See Also
61//!
62//! - [`crate::controller`] - High-level controller API for event subscription
63//! - [`crate::protocol`] - Low-level protocol message handling
64
65use std::collections::HashMap;
66use std::time::Instant;
67
68use chrono::{DateTime, Local, Utc};
69
70use crate::controller::{CircuitId, StreamId};
71use crate::protocol::ControlLine;
72use crate::{
73    CircBuildFlag, CircClosureReason, CircPurpose, CircStatus, ConnectionType, Error, EventType,
74    GuardStatus, GuardType, HiddenServiceState, HsAuth, HsDescAction, HsDescReason,
75    OrClosureReason, OrStatus, Runlevel, Signal, StatusType, StreamClosureReason, StreamPurpose,
76    StreamSource, StreamStatus, TimeoutSetType,
77};
78
79/// Trait implemented by all Tor control protocol events.
80///
81/// This trait provides a common interface for accessing event metadata
82/// regardless of the specific event type. All event types must be thread-safe
83/// (`Send + Sync`) to support concurrent event handling.
84///
85/// # Implementors
86///
87/// All event structs in this module implement this trait:
88/// - [`BandwidthEvent`], [`LogEvent`], [`CircuitEvent`], [`StreamEvent`]
89/// - [`OrConnEvent`], [`AddrMapEvent`], [`BuildTimeoutSetEvent`]
90/// - [`GuardEvent`], [`NewDescEvent`], [`SignalEvent`], [`StatusEvent`]
91/// - [`ConfChangedEvent`], [`NetworkLivenessEvent`], [`CircuitBandwidthEvent`]
92/// - [`ConnectionBandwidthEvent`], [`HsDescEvent`]
93///
94/// # Example
95///
96/// ```rust,ignore
97/// fn handle_event(event: &dyn Event) {
98///     println!("Received {:?} event at {:?}", event.event_type(), event.arrived_at());
99///     println!("Raw content: {}", event.raw_content());
100/// }
101/// ```
102pub trait Event: Send + Sync {
103    /// Returns the type of this event.
104    ///
105    /// This corresponds to the event keyword used in `SETEVENTS` commands.
106    fn event_type(&self) -> EventType;
107
108    /// Returns the raw, unparsed content of the event.
109    ///
110    /// Useful for debugging or when additional parsing is needed beyond
111    /// what the typed event provides.
112    fn raw_content(&self) -> &str;
113
114    /// Returns the instant when this event was received.
115    ///
116    /// This is the local time when the event was parsed, not when Tor
117    /// generated it. Useful for measuring event latency or ordering events.
118    fn arrived_at(&self) -> Instant;
119}
120
121/// Event emitted every second with the bytes sent and received by Tor.
122///
123/// The BW event is one of the most commonly used events for monitoring
124/// Tor's bandwidth usage. It provides a snapshot of data transfer rates
125/// over the last second.
126///
127/// # Event Format
128///
129/// The raw event format is: `BW <bytes_read> <bytes_written>`
130///
131/// # Use Cases
132///
133/// - Monitoring bandwidth consumption
134/// - Building bandwidth graphs
135/// - Detecting network activity
136/// - Rate limiting applications
137///
138/// # Example
139///
140/// ```rust,ignore
141/// use stem_rs::events::BandwidthEvent;
142///
143/// fn handle_bandwidth(event: &BandwidthEvent) {
144///     let read_kbps = event.read as f64 / 1024.0;
145///     let written_kbps = event.written as f64 / 1024.0;
146///     println!("Bandwidth: {:.2} KB/s read, {:.2} KB/s written", read_kbps, written_kbps);
147/// }
148/// ```
149///
150/// # See Also
151///
152/// - [`CircuitBandwidthEvent`] - Per-circuit bandwidth tracking
153/// - [`ConnectionBandwidthEvent`] - Per-connection bandwidth tracking
154#[derive(Debug, Clone)]
155pub struct BandwidthEvent {
156    /// Bytes received by Tor in the last second.
157    pub read: u64,
158    /// Bytes sent by Tor in the last second.
159    pub written: u64,
160    raw_content: String,
161    arrived_at: Instant,
162}
163
164impl Event for BandwidthEvent {
165    fn event_type(&self) -> EventType {
166        EventType::Bw
167    }
168    fn raw_content(&self) -> &str {
169        &self.raw_content
170    }
171    fn arrived_at(&self) -> Instant {
172        self.arrived_at
173    }
174}
175
176impl BandwidthEvent {
177    /// Parses a bandwidth event from raw control protocol content.
178    ///
179    /// # Arguments
180    ///
181    /// * `content` - The event content after the event type, e.g., "15 25"
182    ///
183    /// # Errors
184    ///
185    /// Returns [`Error::Protocol`] if:
186    /// - The content is missing required values
187    /// - The read or written values are not valid integers
188    ///
189    /// # Example
190    ///
191    /// ```rust,ignore
192    /// let event = BandwidthEvent::parse("1024 2048")?;
193    /// assert_eq!(event.read, 1024);
194    /// assert_eq!(event.written, 2048);
195    /// ```
196    pub fn parse(content: &str) -> Result<Self, Error> {
197        let mut line = ControlLine::new(content);
198        let read_str = line.pop(false, false)?;
199        let written_str = line.pop(false, false)?;
200
201        let read: u64 = read_str.parse().map_err(|_| {
202            Error::Protocol(format!("invalid read value in BW event: {}", read_str))
203        })?;
204        let written: u64 = written_str.parse().map_err(|_| {
205            Error::Protocol(format!(
206                "invalid written value in BW event: {}",
207                written_str
208            ))
209        })?;
210
211        Ok(Self {
212            read,
213            written,
214            raw_content: content.to_string(),
215            arrived_at: Instant::now(),
216        })
217    }
218}
219
220/// Tor logging event for receiving log messages from the Tor process.
221///
222/// These are the most visible kind of event since, by default, Tor logs
223/// at the NOTICE [`Runlevel`] to stdout. Log events allow controllers to
224/// receive and process Tor's log output programmatically.
225///
226/// # Runlevels
227///
228/// Log events are categorized by severity:
229/// - [`Runlevel::Debug`] - Verbose debugging information
230/// - [`Runlevel::Info`] - Informational messages
231/// - [`Runlevel::Notice`] - Normal operational messages (default)
232/// - [`Runlevel::Warn`] - Warning conditions
233/// - [`Runlevel::Err`] - Error conditions
234///
235/// # Event Types
236///
237/// Each runlevel corresponds to a separate event type:
238/// - `DEBUG`, `INFO`, `NOTICE`, `WARN`, `ERR`
239///
240/// # Example
241///
242/// ```rust,ignore
243/// use stem_rs::{EventType, Runlevel};
244/// use stem_rs::events::LogEvent;
245///
246/// fn handle_log(event: &LogEvent) {
247///     match event.runlevel {
248///         Runlevel::Err | Runlevel::Warn => {
249///             eprintln!("[{}] {}", event.runlevel, event.message);
250///         }
251///         _ => {
252///             println!("[{}] {}", event.runlevel, event.message);
253///         }
254///     }
255/// }
256/// ```
257///
258/// # See Also
259///
260/// - [`Runlevel`] - Log severity levels
261/// - [`StatusEvent`] - Structured status messages
262#[derive(Debug, Clone)]
263pub struct LogEvent {
264    /// Severity level of the log message.
265    pub runlevel: Runlevel,
266    /// The log message content.
267    pub message: String,
268    raw_content: String,
269    arrived_at: Instant,
270}
271
272impl Event for LogEvent {
273    fn event_type(&self) -> EventType {
274        match self.runlevel {
275            Runlevel::Debug => EventType::Debug,
276            Runlevel::Info => EventType::Info,
277            Runlevel::Notice => EventType::Notice,
278            Runlevel::Warn => EventType::Warn,
279            Runlevel::Err => EventType::Err,
280        }
281    }
282    fn raw_content(&self) -> &str {
283        &self.raw_content
284    }
285    fn arrived_at(&self) -> Instant {
286        self.arrived_at
287    }
288}
289
290impl LogEvent {
291    /// Parses a log event from raw control protocol content.
292    ///
293    /// # Arguments
294    ///
295    /// * `runlevel` - The severity level of the log message
296    /// * `message` - The log message content
297    ///
298    /// # Errors
299    ///
300    /// This method currently does not return errors but returns `Result`
301    /// for API consistency with other event parsers.
302    pub fn parse(runlevel: Runlevel, message: &str) -> Result<Self, Error> {
303        Ok(Self {
304            runlevel,
305            message: message.to_string(),
306            raw_content: message.to_string(),
307            arrived_at: Instant::now(),
308        })
309    }
310}
311
312/// Event indicating that a circuit's status has changed.
313///
314/// Circuit events are fundamental to understanding Tor's operation. They
315/// track the lifecycle of circuits from creation through closure, including
316/// the relays involved and the purpose of each circuit.
317///
318/// # Circuit Lifecycle
319///
320/// Circuits progress through these states:
321/// 1. [`CircStatus::Launched`] - Circuit creation initiated
322/// 2. [`CircStatus::Extended`] - Circuit extended to additional hops
323/// 3. [`CircStatus::Built`] - Circuit fully constructed and ready
324/// 4. [`CircStatus::Failed`] or [`CircStatus::Closed`] - Circuit terminated
325///
326/// # Path Information
327///
328/// The `path` field contains the relays in the circuit as `(fingerprint, nickname)`
329/// tuples. The fingerprint is always present; the nickname may be `None` if
330/// the `VERBOSE_NAMES` feature isn't enabled (on by default since Tor 0.2.2.1).
331///
332/// # Hidden Service Circuits
333///
334/// For hidden service circuits, additional fields provide context:
335/// - `hs_state` - Current state in the hidden service protocol
336/// - `rend_query` - The rendezvous point address
337/// - `purpose` - Indicates the circuit's role (intro, rend, etc.)
338///
339/// # Example
340///
341/// ```rust,ignore
342/// use stem_rs::events::CircuitEvent;
343/// use stem_rs::CircStatus;
344///
345/// fn handle_circuit(event: &CircuitEvent) {
346///     match event.status {
347///         CircStatus::Built => {
348///             println!("Circuit {} built with {} hops", event.id, event.path.len());
349///             for (fingerprint, nickname) in &event.path {
350///                 println!("  - {} ({:?})", fingerprint, nickname);
351///             }
352///         }
353///         CircStatus::Failed => {
354///             println!("Circuit {} failed: {:?}", event.id, event.reason);
355///         }
356///         _ => {}
357///     }
358/// }
359/// ```
360///
361/// # See Also
362///
363/// - [`CircStatus`] - Circuit status values
364/// - [`CircPurpose`] - Circuit purpose types
365/// - [`CircClosureReason`] - Closure reasons
366#[derive(Debug, Clone)]
367pub struct CircuitEvent {
368    /// Unique identifier for this circuit.
369    pub id: CircuitId,
370    /// Current status of the circuit.
371    pub status: CircStatus,
372    /// Relays in the circuit path as `(fingerprint, nickname)` tuples.
373    pub path: Vec<(String, Option<String>)>,
374    /// Flags governing how the circuit was built.
375    pub build_flags: Option<Vec<CircBuildFlag>>,
376    /// Purpose that the circuit is intended for.
377    pub purpose: Option<CircPurpose>,
378    /// Hidden service state if this is an HS circuit.
379    pub hs_state: Option<HiddenServiceState>,
380    /// Rendezvous query if this is a hidden service circuit.
381    pub rend_query: Option<String>,
382    /// Time when the circuit was created or cannibalized.
383    pub created: Option<DateTime<Utc>>,
384    /// Reason for circuit closure (local).
385    pub reason: Option<CircClosureReason>,
386    /// Reason for circuit closure (from remote side).
387    pub remote_reason: Option<CircClosureReason>,
388    /// SOCKS username for stream isolation.
389    pub socks_username: Option<String>,
390    /// SOCKS password for stream isolation.
391    pub socks_password: Option<String>,
392    raw_content: String,
393    arrived_at: Instant,
394}
395
396impl Event for CircuitEvent {
397    fn event_type(&self) -> EventType {
398        EventType::Circ
399    }
400    fn raw_content(&self) -> &str {
401        &self.raw_content
402    }
403    fn arrived_at(&self) -> Instant {
404        self.arrived_at
405    }
406}
407
408impl CircuitEvent {
409    /// Parses a circuit event from raw control protocol content.
410    ///
411    /// # Arguments
412    ///
413    /// * `content` - The event content after the event type
414    ///
415    /// # Event Format
416    ///
417    /// ```text
418    /// CircuitID CircStatus [Path] [BUILD_FLAGS=...] [PURPOSE=...] [HS_STATE=...]
419    /// [REND_QUERY=...] [TIME_CREATED=...] [REASON=...] [REMOTE_REASON=...]
420    /// [SOCKS_USERNAME="..."] [SOCKS_PASSWORD="..."]
421    /// ```
422    ///
423    /// # Errors
424    ///
425    /// Returns [`Error::Protocol`] if:
426    /// - The circuit ID or status is missing
427    /// - The status is not a recognized value
428    pub fn parse(content: &str) -> Result<Self, Error> {
429        let mut line = ControlLine::new(content);
430        let id_str = line.pop(false, false)?;
431        let status_str = line.pop(false, false)?;
432        let status = parse_circ_status(&status_str)?;
433
434        let mut path = Vec::new();
435        let mut build_flags = None;
436        let mut purpose = None;
437        let mut hs_state = None;
438        let mut rend_query = None;
439        let mut created = None;
440        let mut reason = None;
441        let mut remote_reason = None;
442        let mut socks_username = None;
443        let mut socks_password = None;
444
445        while !line.is_empty() {
446            if line.is_next_mapping(Some("BUILD_FLAGS"), false) {
447                let (_, flags_str) = line.pop_mapping(false, false)?;
448                build_flags = Some(parse_build_flags(&flags_str));
449            } else if line.is_next_mapping(Some("PURPOSE"), false) {
450                let (_, p) = line.pop_mapping(false, false)?;
451                purpose = parse_circ_purpose(&p).ok();
452            } else if line.is_next_mapping(Some("HS_STATE"), false) {
453                let (_, s) = line.pop_mapping(false, false)?;
454                hs_state = parse_hs_state(&s).ok();
455            } else if line.is_next_mapping(Some("REND_QUERY"), false) {
456                let (_, q) = line.pop_mapping(false, false)?;
457                rend_query = Some(q);
458            } else if line.is_next_mapping(Some("TIME_CREATED"), false) {
459                let (_, t) = line.pop_mapping(false, false)?;
460                created = parse_iso_timestamp(&t).ok();
461            } else if line.is_next_mapping(Some("REASON"), false) {
462                let (_, r) = line.pop_mapping(false, false)?;
463                reason = parse_circ_closure_reason(&r).ok();
464            } else if line.is_next_mapping(Some("REMOTE_REASON"), false) {
465                let (_, r) = line.pop_mapping(false, false)?;
466                remote_reason = parse_circ_closure_reason(&r).ok();
467            } else if line.is_next_mapping(Some("SOCKS_USERNAME"), true) {
468                let (_, u) = line.pop_mapping(true, true)?;
469                socks_username = Some(u);
470            } else if line.is_next_mapping(Some("SOCKS_PASSWORD"), true) {
471                let (_, p) = line.pop_mapping(true, true)?;
472                socks_password = Some(p);
473            } else {
474                let token = line.pop(false, false)?;
475                if token.starts_with('$') || token.contains('~') || token.contains(',') {
476                    path = parse_circuit_path(&token);
477                }
478            }
479        }
480
481        Ok(Self {
482            id: CircuitId::new(id_str),
483            status,
484            path,
485            build_flags,
486            purpose,
487            hs_state,
488            rend_query,
489            created,
490            reason,
491            remote_reason,
492            socks_username,
493            socks_password,
494            raw_content: content.to_string(),
495            arrived_at: Instant::now(),
496        })
497    }
498}
499
500/// Event indicating that a stream's status has changed.
501///
502/// Stream events track the lifecycle of TCP connections made through Tor.
503/// Each stream is associated with a circuit and connects to a specific
504/// target host and port.
505///
506/// # Stream Lifecycle
507///
508/// Streams progress through these states:
509/// 1. [`StreamStatus::New`] - New stream request received
510/// 2. [`StreamStatus::SentConnect`] - CONNECT sent to exit relay
511/// 3. [`StreamStatus::Remap`] - Address remapped (e.g., DNS resolution)
512/// 4. [`StreamStatus::Succeeded`] - Connection established
513/// 5. [`StreamStatus::Closed`] or [`StreamStatus::Failed`] - Stream terminated
514///
515/// # Circuit Association
516///
517/// The `circuit_id` field indicates which circuit carries this stream.
518/// A value of `None` (circuit ID "0") means the stream is not yet
519/// attached to a circuit.
520///
521/// # Example
522///
523/// ```rust,ignore
524/// use stem_rs::events::StreamEvent;
525/// use stem_rs::StreamStatus;
526///
527/// fn handle_stream(event: &StreamEvent) {
528///     match event.status {
529///         StreamStatus::New => {
530///             println!("New stream {} to {}:{}",
531///                 event.id, event.target_host, event.target_port);
532///         }
533///         StreamStatus::Succeeded => {
534///             println!("Stream {} connected via circuit {:?}",
535///                 event.id, event.circuit_id);
536///         }
537///         StreamStatus::Closed | StreamStatus::Failed => {
538///             println!("Stream {} ended: {:?}", event.id, event.reason);
539///         }
540///         _ => {}
541///     }
542/// }
543/// ```
544///
545/// # See Also
546///
547/// - [`StreamStatus`] - Stream status values
548/// - [`StreamPurpose`] - Stream purpose types
549/// - [`StreamClosureReason`] - Closure reasons
550#[derive(Debug, Clone)]
551pub struct StreamEvent {
552    /// Unique identifier for this stream.
553    pub id: StreamId,
554    /// Current status of the stream.
555    pub status: StreamStatus,
556    /// Circuit carrying this stream, or `None` if unattached.
557    pub circuit_id: Option<CircuitId>,
558    /// Target hostname or IP address.
559    pub target_host: String,
560    /// Target port number.
561    pub target_port: u16,
562    /// Reason for stream closure (local).
563    pub reason: Option<StreamClosureReason>,
564    /// Reason for stream closure (from remote side).
565    pub remote_reason: Option<StreamClosureReason>,
566    /// Source of address resolution (cache or exit).
567    pub source: Option<StreamSource>,
568    /// Source address of the client connection.
569    pub source_addr: Option<String>,
570    /// Purpose of this stream.
571    pub purpose: Option<StreamPurpose>,
572    raw_content: String,
573    arrived_at: Instant,
574}
575
576impl Event for StreamEvent {
577    fn event_type(&self) -> EventType {
578        EventType::Stream
579    }
580    fn raw_content(&self) -> &str {
581        &self.raw_content
582    }
583    fn arrived_at(&self) -> Instant {
584        self.arrived_at
585    }
586}
587
588impl StreamEvent {
589    /// Parses a stream event from raw control protocol content.
590    ///
591    /// # Arguments
592    ///
593    /// * `content` - The event content after the event type
594    ///
595    /// # Event Format
596    ///
597    /// ```text
598    /// StreamID StreamStatus CircuitID Target [REASON=...] [REMOTE_REASON=...]
599    /// [SOURCE=...] [SOURCE_ADDR=...] [PURPOSE=...]
600    /// ```
601    ///
602    /// # Errors
603    ///
604    /// Returns [`Error::Protocol`] if:
605    /// - Required fields are missing
606    /// - The status is not a recognized value
607    /// - The target format is invalid
608    pub fn parse(content: &str) -> Result<Self, Error> {
609        let mut line = ControlLine::new(content);
610        let id_str = line.pop(false, false)?;
611        let status_str = line.pop(false, false)?;
612        let circuit_id_str = line.pop(false, false)?;
613        let target = line.pop(false, false)?;
614
615        let status = parse_stream_status(&status_str)?;
616        let circuit_id = if circuit_id_str == "0" {
617            None
618        } else {
619            Some(CircuitId::new(circuit_id_str))
620        };
621        let (target_host, target_port) = parse_target(&target)?;
622
623        let mut reason = None;
624        let mut remote_reason = None;
625        let mut source = None;
626        let mut source_addr = None;
627        let mut purpose = None;
628
629        while !line.is_empty() {
630            if line.is_next_mapping(Some("REASON"), false) {
631                let (_, r) = line.pop_mapping(false, false)?;
632                reason = parse_stream_closure_reason(&r).ok();
633            } else if line.is_next_mapping(Some("REMOTE_REASON"), false) {
634                let (_, r) = line.pop_mapping(false, false)?;
635                remote_reason = parse_stream_closure_reason(&r).ok();
636            } else if line.is_next_mapping(Some("SOURCE"), false) {
637                let (_, s) = line.pop_mapping(false, false)?;
638                source = parse_stream_source(&s).ok();
639            } else if line.is_next_mapping(Some("SOURCE_ADDR"), false) {
640                let (_, a) = line.pop_mapping(false, false)?;
641                source_addr = Some(a);
642            } else if line.is_next_mapping(Some("PURPOSE"), false) {
643                let (_, p) = line.pop_mapping(false, false)?;
644                purpose = parse_stream_purpose(&p).ok();
645            } else {
646                let _ = line.pop(false, false)?;
647            }
648        }
649
650        Ok(Self {
651            id: StreamId::new(id_str),
652            status,
653            circuit_id,
654            target_host,
655            target_port,
656            reason,
657            remote_reason,
658            source,
659            source_addr,
660            purpose,
661            raw_content: content.to_string(),
662            arrived_at: Instant::now(),
663        })
664    }
665}
666
667/// Event indicating that an OR (Onion Router) connection status has changed.
668///
669/// OR connection events track the status of connections between Tor relays.
670/// These are the TLS connections that carry circuit traffic between nodes
671/// in the Tor network.
672///
673/// # Connection Lifecycle
674///
675/// OR connections progress through these states:
676/// 1. [`OrStatus::New`] - Connection initiated
677/// 2. [`OrStatus::Launched`] - Connection attempt in progress
678/// 3. [`OrStatus::Connected`] - TLS handshake completed
679/// 4. [`OrStatus::Failed`] or [`OrStatus::Closed`] - Connection terminated
680///
681/// # Example
682///
683/// ```rust,ignore
684/// use stem_rs::events::OrConnEvent;
685/// use stem_rs::OrStatus;
686///
687/// fn handle_orconn(event: &OrConnEvent) {
688///     match event.status {
689///         OrStatus::Connected => {
690///             println!("Connected to relay: {}", event.target);
691///         }
692///         OrStatus::Failed | OrStatus::Closed => {
693///             println!("Connection to {} ended: {:?}", event.target, event.reason);
694///         }
695///         _ => {}
696///     }
697/// }
698/// ```
699///
700/// # See Also
701///
702/// - [`OrStatus`] - OR connection status values
703/// - [`OrClosureReason`] - Closure reasons
704#[derive(Debug, Clone)]
705pub struct OrConnEvent {
706    /// Connection identifier (may be `None` for older Tor versions).
707    pub id: Option<String>,
708    /// Current status of the OR connection.
709    pub status: OrStatus,
710    /// Target relay address (IP:port or fingerprint).
711    pub target: String,
712    /// Reason for connection closure.
713    pub reason: Option<OrClosureReason>,
714    /// Number of circuits using this connection.
715    pub num_circuits: Option<u32>,
716    raw_content: String,
717    arrived_at: Instant,
718}
719
720impl Event for OrConnEvent {
721    fn event_type(&self) -> EventType {
722        EventType::OrConn
723    }
724    fn raw_content(&self) -> &str {
725        &self.raw_content
726    }
727    fn arrived_at(&self) -> Instant {
728        self.arrived_at
729    }
730}
731
732impl OrConnEvent {
733    /// Parses an OR connection event from raw control protocol content.
734    ///
735    /// # Arguments
736    ///
737    /// * `content` - The event content after the event type
738    ///
739    /// # Event Format
740    ///
741    /// ```text
742    /// Target Status [REASON=...] [NCIRCS=...] [ID=...]
743    /// ```
744    ///
745    /// # Errors
746    ///
747    /// Returns [`Error::Protocol`] if:
748    /// - Required fields are missing
749    /// - The status is not a recognized value
750    pub fn parse(content: &str) -> Result<Self, Error> {
751        let mut line = ControlLine::new(content);
752        let target = line.pop(false, false)?;
753        let status_str = line.pop(false, false)?;
754        let status = parse_or_status(&status_str)?;
755
756        let mut id = None;
757        let mut reason = None;
758        let mut num_circuits = None;
759
760        while !line.is_empty() {
761            if line.is_next_mapping(Some("REASON"), false) {
762                let (_, r) = line.pop_mapping(false, false)?;
763                reason = parse_or_closure_reason(&r).ok();
764            } else if line.is_next_mapping(Some("NCIRCS"), false) {
765                let (_, n) = line.pop_mapping(false, false)?;
766                num_circuits = n.parse().ok();
767            } else if line.is_next_mapping(Some("ID"), false) {
768                let (_, i) = line.pop_mapping(false, false)?;
769                id = Some(i);
770            } else {
771                let _ = line.pop(false, false)?;
772            }
773        }
774
775        Ok(Self {
776            id,
777            status,
778            target,
779            reason,
780            num_circuits,
781            raw_content: content.to_string(),
782            arrived_at: Instant::now(),
783        })
784    }
785}
786
787/// Event indicating a new address mapping has been created.
788///
789/// Address map events are emitted when Tor creates a mapping between
790/// a hostname and its resolved address. This can occur due to DNS
791/// resolution, `MAPADDRESS` commands, or `TrackHostExits` configuration.
792///
793/// # Expiration
794///
795/// Address mappings have an expiration time after which they are no longer
796/// valid. The `expiry` field contains the local time, while `utc_expiry`
797/// contains the UTC time (if available).
798///
799/// # Caching
800///
801/// The `cached` field indicates whether the mapping will be kept until
802/// expiration (`true`) or may be evicted earlier (`false`).
803///
804/// # Error Mappings
805///
806/// When DNS resolution fails, `destination` will be `None` and the `error`
807/// field will contain the error code.
808///
809/// # Example
810///
811/// ```rust,ignore
812/// use stem_rs::events::AddrMapEvent;
813///
814/// fn handle_addrmap(event: &AddrMapEvent) {
815///     match &event.destination {
816///         Some(dest) => {
817///             println!("{} -> {} (expires: {:?})",
818///                 event.hostname, dest, event.expiry);
819///         }
820///         None => {
821///             println!("Resolution failed for {}: {:?}",
822///                 event.hostname, event.error);
823///         }
824///     }
825/// }
826/// ```
827#[derive(Debug, Clone)]
828pub struct AddrMapEvent {
829    /// The hostname being resolved.
830    pub hostname: String,
831    /// The resolved address, or `None` if resolution failed.
832    pub destination: Option<String>,
833    /// Expiration time in local time.
834    pub expiry: Option<DateTime<Local>>,
835    /// Error code if resolution failed.
836    pub error: Option<String>,
837    /// Expiration time in UTC.
838    pub utc_expiry: Option<DateTime<Utc>>,
839    /// Whether the mapping is cached until expiration.
840    pub cached: Option<bool>,
841    raw_content: String,
842    arrived_at: Instant,
843}
844
845impl Event for AddrMapEvent {
846    fn event_type(&self) -> EventType {
847        EventType::AddrMap
848    }
849    fn raw_content(&self) -> &str {
850        &self.raw_content
851    }
852    fn arrived_at(&self) -> Instant {
853        self.arrived_at
854    }
855}
856
857impl AddrMapEvent {
858    /// Parses an address map event from raw control protocol content.
859    ///
860    /// # Arguments
861    ///
862    /// * `content` - The event content after the event type
863    ///
864    /// # Event Format
865    ///
866    /// ```text
867    /// Hostname Destination Expiry [error=...] [EXPIRES="..."] [CACHED=YES|NO]
868    /// ```
869    ///
870    /// # Errors
871    ///
872    /// Returns [`Error::Protocol`] if required fields are missing.
873    pub fn parse(content: &str) -> Result<Self, Error> {
874        let mut line = ControlLine::new(content);
875        let hostname = line.pop(false, false)?;
876        let dest_str = line.pop(false, false)?;
877        let destination = if dest_str == "<error>" {
878            None
879        } else {
880            Some(dest_str)
881        };
882
883        let expiry_str = if line.is_next_quoted() {
884            Some(line.pop(true, false)?)
885        } else {
886            let token = line.pop(false, false)?;
887            if token == "NEVER" {
888                None
889            } else {
890                Some(token)
891            }
892        };
893
894        let expiry = expiry_str.and_then(|s| parse_local_timestamp(&s).ok());
895
896        let mut error = None;
897        let mut utc_expiry = None;
898        let mut cached = None;
899
900        while !line.is_empty() {
901            if line.is_next_mapping(Some("error"), false) {
902                let (_, e) = line.pop_mapping(false, false)?;
903                error = Some(e);
904            } else if line.is_next_mapping(Some("EXPIRES"), true) {
905                let (_, e) = line.pop_mapping(true, false)?;
906                utc_expiry = parse_utc_timestamp(&e).ok();
907            } else if line.is_next_mapping(Some("CACHED"), true) {
908                let (_, c) = line.pop_mapping(true, false)?;
909                cached = match c.as_str() {
910                    "YES" => Some(true),
911                    "NO" => Some(false),
912                    _ => None,
913                };
914            } else {
915                let _ = line.pop(false, false)?;
916            }
917        }
918
919        Ok(Self {
920            hostname,
921            destination,
922            expiry,
923            error,
924            utc_expiry,
925            cached,
926            raw_content: content.to_string(),
927            arrived_at: Instant::now(),
928        })
929    }
930}
931
932/// Event indicating that the circuit build timeout has changed.
933///
934/// Tor dynamically adjusts its circuit build timeout based on observed
935/// circuit construction times. This event is emitted when the timeout
936/// value changes, providing insight into network performance.
937///
938/// # Timeout Calculation
939///
940/// Tor uses a Pareto distribution to model circuit build times:
941/// - `xm` - The Pareto Xm parameter (minimum value)
942/// - `alpha` - The Pareto alpha parameter (shape)
943/// - `quantile` - The CDF cutoff quantile
944///
945/// # Set Types
946///
947/// The `set_type` indicates why the timeout changed:
948/// - [`TimeoutSetType::Computed`] - Calculated from observed times
949/// - [`TimeoutSetType::Reset`] - Reset to default values
950/// - [`TimeoutSetType::Suspended`] - Timeout learning suspended
951/// - [`TimeoutSetType::Discard`] - Discarding learned values
952/// - [`TimeoutSetType::Resume`] - Resuming timeout learning
953///
954/// # Example
955///
956/// ```rust,ignore
957/// use stem_rs::events::BuildTimeoutSetEvent;
958///
959/// fn handle_timeout(event: &BuildTimeoutSetEvent) {
960///     if let Some(timeout) = event.timeout {
961///         println!("Circuit timeout set to {}ms ({:?})", timeout, event.set_type);
962///     }
963///     if let Some(rate) = event.timeout_rate {
964///         println!("Timeout rate: {:.2}%", rate * 100.0);
965///     }
966/// }
967/// ```
968///
969/// # See Also
970///
971/// - [`TimeoutSetType`] - Timeout change reasons
972#[derive(Debug, Clone)]
973pub struct BuildTimeoutSetEvent {
974    /// Type of timeout change.
975    pub set_type: TimeoutSetType,
976    /// Number of circuit build times used to calculate timeout.
977    pub total_times: Option<u32>,
978    /// Circuit build timeout in milliseconds.
979    pub timeout: Option<u32>,
980    /// Pareto Xm parameter in milliseconds.
981    pub xm: Option<u32>,
982    /// Pareto alpha parameter.
983    pub alpha: Option<f64>,
984    /// CDF quantile cutoff point.
985    pub quantile: Option<f64>,
986    /// Ratio of circuits that timed out.
987    pub timeout_rate: Option<f64>,
988    /// Duration to keep measurement circuits in milliseconds.
989    pub close_timeout: Option<u32>,
990    /// Ratio of measurement circuits that were closed.
991    pub close_rate: Option<f64>,
992    raw_content: String,
993    arrived_at: Instant,
994}
995
996impl Event for BuildTimeoutSetEvent {
997    fn event_type(&self) -> EventType {
998        EventType::BuildTimeoutSet
999    }
1000    fn raw_content(&self) -> &str {
1001        &self.raw_content
1002    }
1003    fn arrived_at(&self) -> Instant {
1004        self.arrived_at
1005    }
1006}
1007
1008impl BuildTimeoutSetEvent {
1009    /// Parses a build timeout set event from raw control protocol content.
1010    ///
1011    /// # Arguments
1012    ///
1013    /// * `content` - The event content after the event type
1014    ///
1015    /// # Event Format
1016    ///
1017    /// ```text
1018    /// SetType [TOTAL_TIMES=...] [TIMEOUT_MS=...] [XM=...] [ALPHA=...]
1019    /// [CUTOFF_QUANTILE=...] [TIMEOUT_RATE=...] [CLOSE_MS=...] [CLOSE_RATE=...]
1020    /// ```
1021    ///
1022    /// # Errors
1023    ///
1024    /// Returns [`Error::Protocol`] if:
1025    /// - The set type is missing or unrecognized
1026    /// - Numeric values cannot be parsed
1027    pub fn parse(content: &str) -> Result<Self, Error> {
1028        let mut line = ControlLine::new(content);
1029        let set_type_str = line.pop(false, false)?;
1030        let set_type = parse_timeout_set_type(&set_type_str)?;
1031
1032        let mut total_times = None;
1033        let mut timeout = None;
1034        let mut xm = None;
1035        let mut alpha = None;
1036        let mut quantile = None;
1037        let mut timeout_rate = None;
1038        let mut close_timeout = None;
1039        let mut close_rate = None;
1040
1041        while !line.is_empty() {
1042            if line.is_next_mapping(Some("TOTAL_TIMES"), false) {
1043                let (_, v) = line.pop_mapping(false, false)?;
1044                total_times = Some(
1045                    v.parse()
1046                        .map_err(|_| Error::Protocol(format!("invalid TOTAL_TIMES: {}", v)))?,
1047                );
1048            } else if line.is_next_mapping(Some("TIMEOUT_MS"), false) {
1049                let (_, v) = line.pop_mapping(false, false)?;
1050                timeout = Some(
1051                    v.parse()
1052                        .map_err(|_| Error::Protocol(format!("invalid TIMEOUT_MS: {}", v)))?,
1053                );
1054            } else if line.is_next_mapping(Some("XM"), false) {
1055                let (_, v) = line.pop_mapping(false, false)?;
1056                xm = Some(
1057                    v.parse()
1058                        .map_err(|_| Error::Protocol(format!("invalid XM: {}", v)))?,
1059                );
1060            } else if line.is_next_mapping(Some("ALPHA"), false) {
1061                let (_, v) = line.pop_mapping(false, false)?;
1062                alpha = Some(
1063                    v.parse()
1064                        .map_err(|_| Error::Protocol(format!("invalid ALPHA: {}", v)))?,
1065                );
1066            } else if line.is_next_mapping(Some("CUTOFF_QUANTILE"), false) {
1067                let (_, v) = line.pop_mapping(false, false)?;
1068                quantile = Some(
1069                    v.parse()
1070                        .map_err(|_| Error::Protocol(format!("invalid CUTOFF_QUANTILE: {}", v)))?,
1071                );
1072            } else if line.is_next_mapping(Some("TIMEOUT_RATE"), false) {
1073                let (_, v) = line.pop_mapping(false, false)?;
1074                timeout_rate = Some(
1075                    v.parse()
1076                        .map_err(|_| Error::Protocol(format!("invalid TIMEOUT_RATE: {}", v)))?,
1077                );
1078            } else if line.is_next_mapping(Some("CLOSE_MS"), false) {
1079                let (_, v) = line.pop_mapping(false, false)?;
1080                close_timeout = Some(
1081                    v.parse()
1082                        .map_err(|_| Error::Protocol(format!("invalid CLOSE_MS: {}", v)))?,
1083                );
1084            } else if line.is_next_mapping(Some("CLOSE_RATE"), false) {
1085                let (_, v) = line.pop_mapping(false, false)?;
1086                close_rate = Some(
1087                    v.parse()
1088                        .map_err(|_| Error::Protocol(format!("invalid CLOSE_RATE: {}", v)))?,
1089                );
1090            } else {
1091                let _ = line.pop(false, false)?;
1092            }
1093        }
1094
1095        Ok(Self {
1096            set_type,
1097            total_times,
1098            timeout,
1099            xm,
1100            alpha,
1101            quantile,
1102            timeout_rate,
1103            close_timeout,
1104            close_rate,
1105            raw_content: content.to_string(),
1106            arrived_at: Instant::now(),
1107        })
1108    }
1109}
1110
1111/// Event indicating that guard relay status has changed.
1112///
1113/// Guard events track changes to the entry guards that Tor uses for the
1114/// first hop of circuits. Entry guards are a security feature that limits
1115/// the set of relays that can observe your traffic entering the Tor network.
1116///
1117/// # Guard Types
1118///
1119/// Currently, only [`GuardType::Entry`] is used, representing entry guards.
1120///
1121/// # Guard Status
1122///
1123/// Guards can have these statuses:
1124/// - [`GuardStatus::New`] - Newly selected as a guard
1125/// - [`GuardStatus::Up`] - Guard is reachable
1126/// - [`GuardStatus::Down`] - Guard is unreachable
1127/// - [`GuardStatus::Good`] - Guard confirmed as good
1128/// - [`GuardStatus::Bad`] - Guard marked as bad
1129/// - [`GuardStatus::Dropped`] - Guard removed from list
1130///
1131/// # Example
1132///
1133/// ```rust,ignore
1134/// use stem_rs::events::GuardEvent;
1135/// use stem_rs::GuardStatus;
1136///
1137/// fn handle_guard(event: &GuardEvent) {
1138///     match event.status {
1139///         GuardStatus::New => {
1140///             println!("New guard: {} ({:?})",
1141///                 event.endpoint_fingerprint, event.endpoint_nickname);
1142///         }
1143///         GuardStatus::Down => {
1144///             println!("Guard {} is down", event.endpoint_fingerprint);
1145///         }
1146///         GuardStatus::Dropped => {
1147///             println!("Guard {} dropped", event.endpoint_fingerprint);
1148///         }
1149///         _ => {}
1150///     }
1151/// }
1152/// ```
1153///
1154/// # See Also
1155///
1156/// - [`GuardType`] - Guard type values
1157/// - [`GuardStatus`] - Guard status values
1158#[derive(Debug, Clone)]
1159pub struct GuardEvent {
1160    /// Type of guard (currently only Entry).
1161    pub guard_type: GuardType,
1162    /// Full endpoint string (fingerprint with optional nickname).
1163    pub endpoint: String,
1164    /// Relay fingerprint (40 hex characters).
1165    pub endpoint_fingerprint: String,
1166    /// Relay nickname if available.
1167    pub endpoint_nickname: Option<String>,
1168    /// Current status of the guard.
1169    pub status: GuardStatus,
1170    raw_content: String,
1171    arrived_at: Instant,
1172}
1173
1174impl Event for GuardEvent {
1175    fn event_type(&self) -> EventType {
1176        EventType::Guard
1177    }
1178    fn raw_content(&self) -> &str {
1179        &self.raw_content
1180    }
1181    fn arrived_at(&self) -> Instant {
1182        self.arrived_at
1183    }
1184}
1185
1186impl GuardEvent {
1187    /// Parses a guard event from raw control protocol content.
1188    ///
1189    /// # Arguments
1190    ///
1191    /// * `content` - The event content after the event type
1192    ///
1193    /// # Event Format
1194    ///
1195    /// ```text
1196    /// GuardType Endpoint Status
1197    /// ```
1198    ///
1199    /// Where Endpoint is either a fingerprint or `fingerprint=nickname`.
1200    ///
1201    /// # Errors
1202    ///
1203    /// Returns [`Error::Protocol`] if:
1204    /// - Required fields are missing
1205    /// - The guard type or status is unrecognized
1206    pub fn parse(content: &str) -> Result<Self, Error> {
1207        let mut line = ControlLine::new(content);
1208        let guard_type_str = line.pop(false, false)?;
1209        let endpoint = line.pop(false, false)?;
1210        let status_str = line.pop(false, false)?;
1211
1212        let guard_type = parse_guard_type(&guard_type_str)?;
1213        let status = parse_guard_status(&status_str)?;
1214        let (fingerprint, nickname) = parse_relay_endpoint(&endpoint);
1215
1216        Ok(Self {
1217            guard_type,
1218            endpoint,
1219            endpoint_fingerprint: fingerprint,
1220            endpoint_nickname: nickname,
1221            status,
1222            raw_content: content.to_string(),
1223            arrived_at: Instant::now(),
1224        })
1225    }
1226}
1227
1228/// Event indicating that new relay descriptors are available.
1229///
1230/// This event is emitted when Tor receives new server descriptors for
1231/// relays in the network. It provides a list of relays whose descriptors
1232/// have been updated.
1233///
1234/// # Relay Identification
1235///
1236/// Each relay is identified by its fingerprint and optionally its nickname.
1237/// The `relays` field contains `(fingerprint, nickname)` tuples.
1238///
1239/// # Example
1240///
1241/// ```rust,ignore
1242/// use stem_rs::events::NewDescEvent;
1243///
1244/// fn handle_newdesc(event: &NewDescEvent) {
1245///     println!("Received {} new descriptors:", event.relays.len());
1246///     for (fingerprint, nickname) in &event.relays {
1247///         match nickname {
1248///             Some(nick) => println!("  {} ({})", fingerprint, nick),
1249///             None => println!("  {}", fingerprint),
1250///         }
1251///     }
1252/// }
1253/// ```
1254#[derive(Debug, Clone)]
1255pub struct NewDescEvent {
1256    /// List of relays with new descriptors as `(fingerprint, nickname)` tuples.
1257    pub relays: Vec<(String, Option<String>)>,
1258    raw_content: String,
1259    arrived_at: Instant,
1260}
1261
1262impl Event for NewDescEvent {
1263    fn event_type(&self) -> EventType {
1264        EventType::NewDesc
1265    }
1266    fn raw_content(&self) -> &str {
1267        &self.raw_content
1268    }
1269    fn arrived_at(&self) -> Instant {
1270        self.arrived_at
1271    }
1272}
1273
1274impl NewDescEvent {
1275    /// Parses a new descriptor event from raw control protocol content.
1276    ///
1277    /// # Arguments
1278    ///
1279    /// * `content` - The event content after the event type
1280    ///
1281    /// # Event Format
1282    ///
1283    /// ```text
1284    /// Relay1 [Relay2 ...]
1285    /// ```
1286    ///
1287    /// Where each relay is either a fingerprint or `fingerprint=nickname`.
1288    ///
1289    /// # Errors
1290    ///
1291    /// This method currently does not return errors but returns `Result`
1292    /// for API consistency.
1293    pub fn parse(content: &str) -> Result<Self, Error> {
1294        let mut relays = Vec::new();
1295        for token in content.split_whitespace() {
1296            let (fingerprint, nickname) = parse_relay_endpoint(token);
1297            relays.push((fingerprint, nickname));
1298        }
1299        Ok(Self {
1300            relays,
1301            raw_content: content.to_string(),
1302            arrived_at: Instant::now(),
1303        })
1304    }
1305}
1306
1307/// Event indicating that Tor received a signal.
1308///
1309/// This event is emitted when Tor receives a signal, either from the
1310/// operating system or via the control protocol's `SIGNAL` command.
1311///
1312/// # Signals
1313///
1314/// Common signals include:
1315/// - [`Signal::Newnym`] - Request new circuits
1316/// - [`Signal::Reload`] - Reload configuration
1317/// - [`Signal::Shutdown`] - Graceful shutdown
1318/// - [`Signal::Halt`] - Immediate shutdown
1319///
1320/// # Example
1321///
1322/// ```rust,ignore
1323/// use stem_rs::events::SignalEvent;
1324/// use stem_rs::Signal;
1325///
1326/// fn handle_signal(event: &SignalEvent) {
1327///     match event.signal {
1328///         Signal::Newnym => println!("New identity requested"),
1329///         Signal::Shutdown => println!("Tor is shutting down"),
1330///         _ => println!("Received signal: {:?}", event.signal),
1331///     }
1332/// }
1333/// ```
1334///
1335/// # See Also
1336///
1337/// - [`Signal`] - Signal types
1338#[derive(Debug, Clone)]
1339pub struct SignalEvent {
1340    /// The signal that was received.
1341    pub signal: Signal,
1342    raw_content: String,
1343    arrived_at: Instant,
1344}
1345
1346impl Event for SignalEvent {
1347    fn event_type(&self) -> EventType {
1348        EventType::Signal
1349    }
1350    fn raw_content(&self) -> &str {
1351        &self.raw_content
1352    }
1353    fn arrived_at(&self) -> Instant {
1354        self.arrived_at
1355    }
1356}
1357
1358impl SignalEvent {
1359    /// Parses a signal event from raw control protocol content.
1360    ///
1361    /// # Arguments
1362    ///
1363    /// * `content` - The event content after the event type
1364    ///
1365    /// # Errors
1366    ///
1367    /// Returns [`Error::Protocol`] if the signal is unrecognized.
1368    pub fn parse(content: &str) -> Result<Self, Error> {
1369        let signal = parse_signal(content.trim())?;
1370        Ok(Self {
1371            signal,
1372            raw_content: content.to_string(),
1373            arrived_at: Instant::now(),
1374        })
1375    }
1376}
1377
1378/// Event providing status information about Tor's operation.
1379///
1380/// Status events provide structured information about Tor's operational
1381/// state, including bootstrap progress, circuit establishment, and
1382/// various warnings or errors.
1383///
1384/// # Status Types
1385///
1386/// Events are categorized by type:
1387/// - [`StatusType::General`] - General status (e.g., consensus arrived)
1388/// - [`StatusType::Client`] - Client-specific status (e.g., bootstrap progress)
1389/// - [`StatusType::Server`] - Server-specific status (e.g., reachability checks)
1390///
1391/// # Bootstrap Progress
1392///
1393/// The most common use of status events is tracking bootstrap progress.
1394/// Look for `action == "BOOTSTRAP"` and check the `PROGRESS` argument.
1395///
1396/// # Example
1397///
1398/// ```rust,ignore
1399/// use stem_rs::events::StatusEvent;
1400/// use stem_rs::StatusType;
1401///
1402/// fn handle_status(event: &StatusEvent) {
1403///     if event.action == "BOOTSTRAP" {
1404///         if let Some(progress) = event.arguments.get("PROGRESS") {
1405///             println!("Bootstrap progress: {}%", progress);
1406///         }
1407///         if let Some(summary) = event.arguments.get("SUMMARY") {
1408///             println!("Status: {}", summary);
1409///         }
1410///     }
1411/// }
1412/// ```
1413///
1414/// # See Also
1415///
1416/// - [`StatusType`] - Status event types
1417/// - [`Runlevel`] - Severity levels
1418#[derive(Debug, Clone)]
1419pub struct StatusEvent {
1420    /// Type of status event (General, Client, or Server).
1421    pub status_type: StatusType,
1422    /// Severity level of the status message.
1423    pub runlevel: Runlevel,
1424    /// Action or event name (e.g., "BOOTSTRAP", "CIRCUIT_ESTABLISHED").
1425    pub action: String,
1426    /// Key-value arguments providing additional details.
1427    pub arguments: HashMap<String, String>,
1428    raw_content: String,
1429    arrived_at: Instant,
1430}
1431
1432impl Event for StatusEvent {
1433    fn event_type(&self) -> EventType {
1434        EventType::Status
1435    }
1436    fn raw_content(&self) -> &str {
1437        &self.raw_content
1438    }
1439    fn arrived_at(&self) -> Instant {
1440        self.arrived_at
1441    }
1442}
1443
1444impl StatusEvent {
1445    /// Parses a status event from raw control protocol content.
1446    ///
1447    /// # Arguments
1448    ///
1449    /// * `status_type` - The type of status event
1450    /// * `content` - The event content after the event type
1451    ///
1452    /// # Event Format
1453    ///
1454    /// ```text
1455    /// Runlevel Action [Key=Value ...]
1456    /// ```
1457    ///
1458    /// # Errors
1459    ///
1460    /// Returns [`Error::Protocol`] if:
1461    /// - Required fields are missing
1462    /// - The runlevel is unrecognized
1463    pub fn parse(status_type: StatusType, content: &str) -> Result<Self, Error> {
1464        let mut line = ControlLine::new(content);
1465        let runlevel_str = line.pop(false, false)?;
1466        let action = line.pop(false, false)?;
1467        let runlevel = parse_runlevel(&runlevel_str)?;
1468
1469        let mut arguments = HashMap::new();
1470        while !line.is_empty() {
1471            if line.peek_key().is_some() {
1472                let quoted = line.is_next_mapping(None, true);
1473                let (k, v) = line.pop_mapping(quoted, quoted)?;
1474                arguments.insert(k, v);
1475            } else {
1476                let _ = line.pop(false, false)?;
1477            }
1478        }
1479
1480        Ok(Self {
1481            status_type,
1482            runlevel,
1483            action,
1484            arguments,
1485            raw_content: content.to_string(),
1486            arrived_at: Instant::now(),
1487        })
1488    }
1489}
1490
1491/// Event indicating that Tor's configuration has changed.
1492///
1493/// This event is emitted when configuration options are modified, either
1494/// through `SETCONF` commands or by reloading the configuration file.
1495///
1496/// # Changed vs Unset
1497///
1498/// - `changed` - Options that were set to new values
1499/// - `unset` - Options that were reset to defaults
1500///
1501/// Options can have multiple values (e.g., `ExitPolicy`), so `changed`
1502/// maps option names to a list of values.
1503///
1504/// # Example
1505///
1506/// ```rust,ignore
1507/// use stem_rs::events::ConfChangedEvent;
1508///
1509/// fn handle_conf_changed(event: &ConfChangedEvent) {
1510///     for (option, values) in &event.changed {
1511///         println!("Changed: {} = {:?}", option, values);
1512///     }
1513///     for option in &event.unset {
1514///         println!("Unset: {}", option);
1515///     }
1516/// }
1517/// ```
1518#[derive(Debug, Clone)]
1519pub struct ConfChangedEvent {
1520    /// Options that were changed, mapped to their new values.
1521    pub changed: HashMap<String, Vec<String>>,
1522    /// Options that were unset (reset to defaults).
1523    pub unset: Vec<String>,
1524    raw_content: String,
1525    arrived_at: Instant,
1526}
1527
1528impl Event for ConfChangedEvent {
1529    fn event_type(&self) -> EventType {
1530        EventType::ConfChanged
1531    }
1532    fn raw_content(&self) -> &str {
1533        &self.raw_content
1534    }
1535    fn arrived_at(&self) -> Instant {
1536        self.arrived_at
1537    }
1538}
1539
1540impl ConfChangedEvent {
1541    /// Parses a configuration changed event from multi-line content.
1542    ///
1543    /// # Arguments
1544    ///
1545    /// * `lines` - The event content lines (excluding header/footer)
1546    ///
1547    /// # Event Format
1548    ///
1549    /// Each line is either:
1550    /// - `Key=Value` - Option set to a value
1551    /// - `Key` - Option unset
1552    ///
1553    /// # Errors
1554    ///
1555    /// This method currently does not return errors but returns `Result`
1556    /// for API consistency.
1557    pub fn parse(lines: &[String]) -> Result<Self, Error> {
1558        let mut changed: HashMap<String, Vec<String>> = HashMap::new();
1559        let mut unset = Vec::new();
1560
1561        for line in lines {
1562            if let Some(eq_pos) = line.find('=') {
1563                let key = line[..eq_pos].to_string();
1564                let value = line[eq_pos + 1..].to_string();
1565                changed.entry(key).or_default().push(value);
1566            } else if !line.is_empty() {
1567                unset.push(line.clone());
1568            }
1569        }
1570
1571        Ok(Self {
1572            changed,
1573            unset,
1574            raw_content: lines.join("\n"),
1575            arrived_at: Instant::now(),
1576        })
1577    }
1578}
1579
1580/// Event indicating network connectivity status.
1581///
1582/// This event is emitted when Tor's view of network liveness changes.
1583/// It indicates whether Tor believes the network is reachable.
1584///
1585/// # Status Values
1586///
1587/// - `"UP"` - Network is reachable
1588/// - `"DOWN"` - Network is unreachable
1589///
1590/// # Example
1591///
1592/// ```rust,ignore
1593/// use stem_rs::events::NetworkLivenessEvent;
1594///
1595/// fn handle_liveness(event: &NetworkLivenessEvent) {
1596///     match event.status.as_str() {
1597///         "UP" => println!("Network is up"),
1598///         "DOWN" => println!("Network is down"),
1599///         _ => println!("Unknown network status: {}", event.status),
1600///     }
1601/// }
1602/// ```
1603#[derive(Debug, Clone)]
1604pub struct NetworkLivenessEvent {
1605    /// Network status ("UP" or "DOWN").
1606    pub status: String,
1607    raw_content: String,
1608    arrived_at: Instant,
1609}
1610
1611impl Event for NetworkLivenessEvent {
1612    fn event_type(&self) -> EventType {
1613        EventType::NetworkLiveness
1614    }
1615    fn raw_content(&self) -> &str {
1616        &self.raw_content
1617    }
1618    fn arrived_at(&self) -> Instant {
1619        self.arrived_at
1620    }
1621}
1622
1623impl NetworkLivenessEvent {
1624    /// Parses a network liveness event from raw control protocol content.
1625    ///
1626    /// # Arguments
1627    ///
1628    /// * `content` - The event content after the event type
1629    ///
1630    /// # Errors
1631    ///
1632    /// This method currently does not return errors but returns `Result`
1633    /// for API consistency.
1634    pub fn parse(content: &str) -> Result<Self, Error> {
1635        let status = content.split_whitespace().next().unwrap_or("").to_string();
1636        Ok(Self {
1637            status,
1638            raw_content: content.to_string(),
1639            arrived_at: Instant::now(),
1640        })
1641    }
1642}
1643
1644/// Event providing bandwidth information for a specific circuit.
1645///
1646/// Unlike [`BandwidthEvent`] which provides aggregate bandwidth, this event
1647/// tracks bandwidth usage per circuit. This is useful for monitoring
1648/// individual connections or identifying high-bandwidth circuits.
1649///
1650/// # Example
1651///
1652/// ```rust,ignore
1653/// use stem_rs::events::CircuitBandwidthEvent;
1654///
1655/// fn handle_circ_bw(event: &CircuitBandwidthEvent) {
1656///     println!("Circuit {} bandwidth: {} read, {} written",
1657///         event.id, event.read, event.written);
1658///     if let Some(time) = &event.time {
1659///         println!("  at {}", time);
1660///     }
1661/// }
1662/// ```
1663///
1664/// # See Also
1665///
1666/// - [`BandwidthEvent`] - Aggregate bandwidth
1667/// - [`ConnectionBandwidthEvent`] - Per-connection bandwidth
1668#[derive(Debug, Clone)]
1669pub struct CircuitBandwidthEvent {
1670    /// Circuit identifier.
1671    pub id: CircuitId,
1672    /// Bytes read on this circuit.
1673    pub read: u64,
1674    /// Bytes written on this circuit.
1675    pub written: u64,
1676    /// User payload bytes received on this circuit (if available).
1677    ///
1678    /// This field was added in Tor 0.4.1.1-alpha and represents the actual
1679    /// user data received, excluding cell overhead.
1680    pub delivered_read: Option<u64>,
1681    /// User payload bytes sent on this circuit (if available).
1682    ///
1683    /// This field was added in Tor 0.4.1.1-alpha and represents the actual
1684    /// user data sent, excluding cell overhead.
1685    pub delivered_written: Option<u64>,
1686    /// Overhead bytes received on this circuit (if available).
1687    ///
1688    /// This field was added in Tor 0.4.1.1-alpha and represents padding
1689    /// added to make cells a fixed length.
1690    pub overhead_read: Option<u64>,
1691    /// Overhead bytes sent on this circuit (if available).
1692    ///
1693    /// This field was added in Tor 0.4.1.1-alpha and represents padding
1694    /// added to make cells a fixed length.
1695    pub overhead_written: Option<u64>,
1696    /// Timestamp of the measurement (if available).
1697    pub time: Option<DateTime<Utc>>,
1698    raw_content: String,
1699    arrived_at: Instant,
1700}
1701
1702impl Event for CircuitBandwidthEvent {
1703    fn event_type(&self) -> EventType {
1704        EventType::CircBw
1705    }
1706    fn raw_content(&self) -> &str {
1707        &self.raw_content
1708    }
1709    fn arrived_at(&self) -> Instant {
1710        self.arrived_at
1711    }
1712}
1713
1714impl CircuitBandwidthEvent {
1715    /// Parses a circuit bandwidth event from raw control protocol content.
1716    ///
1717    /// # Arguments
1718    ///
1719    /// * `content` - The event content after the event type
1720    ///
1721    /// # Event Format
1722    ///
1723    /// ```text
1724    /// ID=CircuitID READ=bytes WRITTEN=bytes [DELIVERED_READ=bytes] [DELIVERED_WRITTEN=bytes]
1725    /// [OVERHEAD_READ=bytes] [OVERHEAD_WRITTEN=bytes] [TIME=timestamp]
1726    /// ```
1727    ///
1728    /// The DELIVERED_* and OVERHEAD_* fields were added in Tor 0.4.1.1-alpha.
1729    ///
1730    /// # Errors
1731    ///
1732    /// Returns [`Error::Protocol`] if:
1733    /// - Required fields (ID, READ, WRITTEN) are missing
1734    /// - Numeric values cannot be parsed
1735    pub fn parse(content: &str) -> Result<Self, Error> {
1736        let mut line = ControlLine::new(content);
1737        let mut id = None;
1738        let mut read = None;
1739        let mut written = None;
1740        let mut delivered_read = None;
1741        let mut delivered_written = None;
1742        let mut overhead_read = None;
1743        let mut overhead_written = None;
1744        let mut time = None;
1745
1746        while !line.is_empty() {
1747            if line.is_next_mapping(Some("ID"), false) {
1748                let (_, v) = line.pop_mapping(false, false)?;
1749                id = Some(CircuitId::new(v));
1750            } else if line.is_next_mapping(Some("READ"), false) {
1751                let (_, v) = line.pop_mapping(false, false)?;
1752                read = Some(
1753                    v.parse()
1754                        .map_err(|_| Error::Protocol(format!("invalid READ value: {}", v)))?,
1755                );
1756            } else if line.is_next_mapping(Some("WRITTEN"), false) {
1757                let (_, v) = line.pop_mapping(false, false)?;
1758                written = Some(
1759                    v.parse()
1760                        .map_err(|_| Error::Protocol(format!("invalid WRITTEN value: {}", v)))?,
1761                );
1762            } else if line.is_next_mapping(Some("DELIVERED_READ"), false) {
1763                let (_, v) = line.pop_mapping(false, false)?;
1764                delivered_read = Some(
1765                    v.parse()
1766                        .map_err(|_| Error::Protocol(format!("invalid DELIVERED_READ value: {}", v)))?,
1767                );
1768            } else if line.is_next_mapping(Some("DELIVERED_WRITTEN"), false) {
1769                let (_, v) = line.pop_mapping(false, false)?;
1770                delivered_written = Some(
1771                    v.parse()
1772                        .map_err(|_| Error::Protocol(format!("invalid DELIVERED_WRITTEN value: {}", v)))?,
1773                );
1774            } else if line.is_next_mapping(Some("OVERHEAD_READ"), false) {
1775                let (_, v) = line.pop_mapping(false, false)?;
1776                overhead_read = Some(
1777                    v.parse()
1778                        .map_err(|_| Error::Protocol(format!("invalid OVERHEAD_READ value: {}", v)))?,
1779                );
1780            } else if line.is_next_mapping(Some("OVERHEAD_WRITTEN"), false) {
1781                let (_, v) = line.pop_mapping(false, false)?;
1782                overhead_written = Some(
1783                    v.parse()
1784                        .map_err(|_| Error::Protocol(format!("invalid OVERHEAD_WRITTEN value: {}", v)))?,
1785                );
1786            } else if line.is_next_mapping(Some("TIME"), false) {
1787                let (_, v) = line.pop_mapping(false, false)?;
1788                time = parse_iso_timestamp(&v).ok();
1789            } else {
1790                let _ = line.pop(false, false)?;
1791            }
1792        }
1793
1794        Ok(Self {
1795            id: id.ok_or_else(|| Error::Protocol("missing ID in CIRC_BW".to_string()))?,
1796            read: read.ok_or_else(|| Error::Protocol("missing READ in CIRC_BW".to_string()))?,
1797            written: written
1798                .ok_or_else(|| Error::Protocol("missing WRITTEN in CIRC_BW".to_string()))?,
1799            delivered_read,
1800            delivered_written,
1801            overhead_read,
1802            overhead_written,
1803            time,
1804            raw_content: content.to_string(),
1805            arrived_at: Instant::now(),
1806        })
1807    }
1808}
1809
1810/// Event providing bandwidth information for a specific connection.
1811///
1812/// This event tracks bandwidth usage per connection, categorized by
1813/// connection type (OR, Dir, Exit). Useful for detailed bandwidth
1814/// analysis and monitoring.
1815///
1816/// # Connection Types
1817///
1818/// - [`ConnectionType::Or`] - Onion Router connections (relay-to-relay)
1819/// - [`ConnectionType::Dir`] - Directory connections
1820/// - [`ConnectionType::Exit`] - Exit connections to the internet
1821///
1822/// # Example
1823///
1824/// ```rust,ignore
1825/// use stem_rs::events::ConnectionBandwidthEvent;
1826/// use stem_rs::ConnectionType;
1827///
1828/// fn handle_conn_bw(event: &ConnectionBandwidthEvent) {
1829///     let type_str = match event.conn_type {
1830///         ConnectionType::Or => "OR",
1831///         ConnectionType::Dir => "Dir",
1832///         ConnectionType::Exit => "Exit",
1833///     };
1834///     println!("{} connection {}: {} read, {} written",
1835///         type_str, event.id, event.read, event.written);
1836/// }
1837/// ```
1838///
1839/// # See Also
1840///
1841/// - [`BandwidthEvent`] - Aggregate bandwidth
1842/// - [`CircuitBandwidthEvent`] - Per-circuit bandwidth
1843/// - [`ConnectionType`] - Connection types
1844#[derive(Debug, Clone)]
1845pub struct ConnectionBandwidthEvent {
1846    /// Connection identifier.
1847    pub id: String,
1848    /// Type of connection.
1849    pub conn_type: ConnectionType,
1850    /// Bytes read on this connection.
1851    pub read: u64,
1852    /// Bytes written on this connection.
1853    pub written: u64,
1854    raw_content: String,
1855    arrived_at: Instant,
1856}
1857
1858impl Event for ConnectionBandwidthEvent {
1859    fn event_type(&self) -> EventType {
1860        EventType::ConnBw
1861    }
1862    fn raw_content(&self) -> &str {
1863        &self.raw_content
1864    }
1865    fn arrived_at(&self) -> Instant {
1866        self.arrived_at
1867    }
1868}
1869
1870impl ConnectionBandwidthEvent {
1871    /// Parses a connection bandwidth event from raw control protocol content.
1872    ///
1873    /// # Arguments
1874    ///
1875    /// * `content` - The event content after the event type
1876    ///
1877    /// # Event Format
1878    ///
1879    /// ```text
1880    /// ID=ConnID TYPE=ConnType READ=bytes WRITTEN=bytes
1881    /// ```
1882    ///
1883    /// # Errors
1884    ///
1885    /// Returns [`Error::Protocol`] if:
1886    /// - Required fields (ID, TYPE, READ, WRITTEN) are missing
1887    /// - The connection type is unrecognized
1888    /// - Numeric values cannot be parsed
1889    pub fn parse(content: &str) -> Result<Self, Error> {
1890        let mut line = ControlLine::new(content);
1891        let mut id = None;
1892        let mut conn_type = None;
1893        let mut read = None;
1894        let mut written = None;
1895
1896        while !line.is_empty() {
1897            if line.is_next_mapping(Some("ID"), false) {
1898                let (_, v) = line.pop_mapping(false, false)?;
1899                id = Some(v);
1900            } else if line.is_next_mapping(Some("TYPE"), false) {
1901                let (_, v) = line.pop_mapping(false, false)?;
1902                conn_type = Some(parse_connection_type(&v)?);
1903            } else if line.is_next_mapping(Some("READ"), false) {
1904                let (_, v) = line.pop_mapping(false, false)?;
1905                read = Some(
1906                    v.parse()
1907                        .map_err(|_| Error::Protocol(format!("invalid READ value: {}", v)))?,
1908                );
1909            } else if line.is_next_mapping(Some("WRITTEN"), false) {
1910                let (_, v) = line.pop_mapping(false, false)?;
1911                written = Some(
1912                    v.parse()
1913                        .map_err(|_| Error::Protocol(format!("invalid WRITTEN value: {}", v)))?,
1914                );
1915            } else {
1916                let _ = line.pop(false, false)?;
1917            }
1918        }
1919
1920        Ok(Self {
1921            id: id.ok_or_else(|| Error::Protocol("missing ID in CONN_BW".to_string()))?,
1922            conn_type: conn_type
1923                .ok_or_else(|| Error::Protocol("missing TYPE in CONN_BW".to_string()))?,
1924            read: read.ok_or_else(|| Error::Protocol("missing READ in CONN_BW".to_string()))?,
1925            written: written
1926                .ok_or_else(|| Error::Protocol("missing WRITTEN in CONN_BW".to_string()))?,
1927            raw_content: content.to_string(),
1928            arrived_at: Instant::now(),
1929        })
1930    }
1931}
1932
1933/// Event triggered when fetching or uploading hidden service descriptors.
1934///
1935/// This event tracks the lifecycle of hidden service descriptor operations,
1936/// including requests, uploads, and failures. It's essential for monitoring
1937/// hidden service connectivity.
1938///
1939/// # Actions
1940///
1941/// The `action` field indicates the operation:
1942/// - [`HsDescAction::Requested`] - Descriptor fetch requested
1943/// - [`HsDescAction::Received`] - Descriptor successfully received
1944/// - [`HsDescAction::Uploaded`] - Descriptor successfully uploaded
1945/// - [`HsDescAction::Failed`] - Operation failed (check `reason`)
1946/// - [`HsDescAction::Created`] - New descriptor created
1947/// - [`HsDescAction::Ignore`] - Descriptor ignored
1948///
1949/// # Directory Information
1950///
1951/// The `directory` field contains the HSDir relay handling the request.
1952/// The fingerprint and nickname are extracted into separate fields for
1953/// convenience.
1954///
1955/// # Example
1956///
1957/// ```rust,ignore
1958/// use stem_rs::events::HsDescEvent;
1959/// use stem_rs::HsDescAction;
1960///
1961/// fn handle_hsdesc(event: &HsDescEvent) {
1962///     match event.action {
1963///         HsDescAction::Received => {
1964///             println!("Got descriptor for {} from {:?}",
1965///                 event.address, event.directory_nickname);
1966///         }
1967///         HsDescAction::Failed => {
1968///             println!("Failed to get descriptor for {}: {:?}",
1969///                 event.address, event.reason);
1970///         }
1971///         _ => {}
1972///     }
1973/// }
1974/// ```
1975///
1976/// # See Also
1977///
1978/// - [`HsDescAction`] - Descriptor actions
1979/// - [`HsDescReason`] - Failure reasons
1980/// - [`HsAuth`] - Authentication types
1981#[derive(Debug, Clone)]
1982pub struct HsDescEvent {
1983    /// Action being performed on the descriptor.
1984    pub action: HsDescAction,
1985    /// Hidden service address (onion address).
1986    pub address: String,
1987    /// Authentication type for the hidden service.
1988    pub authentication: Option<HsAuth>,
1989    /// Full directory relay string.
1990    pub directory: Option<String>,
1991    /// Directory relay fingerprint.
1992    pub directory_fingerprint: Option<String>,
1993    /// Directory relay nickname.
1994    pub directory_nickname: Option<String>,
1995    /// Descriptor identifier.
1996    pub descriptor_id: Option<String>,
1997    /// Reason for failure (if action is Failed).
1998    pub reason: Option<HsDescReason>,
1999    raw_content: String,
2000    arrived_at: Instant,
2001}
2002
2003impl Event for HsDescEvent {
2004    fn event_type(&self) -> EventType {
2005        EventType::HsDesc
2006    }
2007    fn raw_content(&self) -> &str {
2008        &self.raw_content
2009    }
2010    fn arrived_at(&self) -> Instant {
2011        self.arrived_at
2012    }
2013}
2014
2015impl HsDescEvent {
2016    /// Parses a hidden service descriptor event from raw control protocol content.
2017    ///
2018    /// # Arguments
2019    ///
2020    /// * `content` - The event content after the event type
2021    ///
2022    /// # Event Format
2023    ///
2024    /// ```text
2025    /// Action Address AuthType [Directory] [DescriptorID] [REASON=...]
2026    /// ```
2027    ///
2028    /// # Errors
2029    ///
2030    /// Returns [`Error::Protocol`] if:
2031    /// - Required fields are missing
2032    /// - The action is unrecognized
2033    pub fn parse(content: &str) -> Result<Self, Error> {
2034        let mut line = ControlLine::new(content);
2035        let action_str = line.pop(false, false)?;
2036        let address = line.pop(false, false)?;
2037        let auth_str = line.pop(false, false)?;
2038
2039        let action = parse_hs_desc_action(&action_str)?;
2040        let authentication = parse_hs_auth(&auth_str).ok();
2041
2042        let mut directory = None;
2043        let mut directory_fingerprint = None;
2044        let mut directory_nickname = None;
2045        let mut descriptor_id = None;
2046        let mut reason = None;
2047
2048        if !line.is_empty() {
2049            let dir_token = line.pop(false, false)?;
2050            if dir_token != "UNKNOWN" {
2051                directory = Some(dir_token.clone());
2052                let (fp, nick) = parse_relay_endpoint(&dir_token);
2053                directory_fingerprint = Some(fp);
2054                directory_nickname = nick;
2055            }
2056        }
2057
2058        if !line.is_empty() && line.peek_key().is_none_or(|k| k != "REASON") {
2059            descriptor_id = Some(line.pop(false, false)?);
2060        }
2061
2062        while !line.is_empty() {
2063            if line.is_next_mapping(Some("REASON"), false) {
2064                let (_, r) = line.pop_mapping(false, false)?;
2065                reason = parse_hs_desc_reason(&r).ok();
2066            } else {
2067                let _ = line.pop(false, false)?;
2068            }
2069        }
2070
2071        Ok(Self {
2072            action,
2073            address,
2074            authentication,
2075            directory,
2076            directory_fingerprint,
2077            directory_nickname,
2078            descriptor_id,
2079            reason,
2080            raw_content: content.to_string(),
2081            arrived_at: Instant::now(),
2082        })
2083    }
2084}
2085
2086/// Parses a circuit status string into a [`CircStatus`] enum variant.
2087///
2088/// Converts a case-insensitive string representation of a circuit status
2089/// from the Tor control protocol into the corresponding enum variant.
2090///
2091/// # Arguments
2092///
2093/// * `s` - The circuit status string to parse (e.g., "LAUNCHED", "BUILT")
2094///
2095/// # Returns
2096///
2097/// * `Ok(CircStatus)` - The parsed circuit status variant
2098/// * `Err(Error::Protocol)` - If the string doesn't match any known status
2099///
2100/// # Supported Values
2101///
2102/// - `LAUNCHED` - Circuit construction has begun
2103/// - `BUILT` - Circuit is fully constructed and ready for use
2104/// - `GUARD_WAIT` - Waiting for guard node selection
2105/// - `EXTENDED` - Circuit has been extended by one hop
2106/// - `FAILED` - Circuit construction failed
2107/// - `CLOSED` - Circuit has been closed
2108fn parse_circ_status(s: &str) -> Result<CircStatus, Error> {
2109    match s.to_uppercase().as_str() {
2110        "LAUNCHED" => Ok(CircStatus::Launched),
2111        "BUILT" => Ok(CircStatus::Built),
2112        "GUARD_WAIT" => Ok(CircStatus::GuardWait),
2113        "EXTENDED" => Ok(CircStatus::Extended),
2114        "FAILED" => Ok(CircStatus::Failed),
2115        "CLOSED" => Ok(CircStatus::Closed),
2116        _ => Err(Error::Protocol(format!("unknown circuit status: {}", s))),
2117    }
2118}
2119
2120/// Parses a stream status string into a [`StreamStatus`] enum variant.
2121///
2122/// Converts a case-insensitive string representation of a stream status
2123/// from the Tor control protocol into the corresponding enum variant.
2124///
2125/// # Arguments
2126///
2127/// * `s` - The stream status string to parse (e.g., "NEW", "SUCCEEDED")
2128///
2129/// # Returns
2130///
2131/// * `Ok(StreamStatus)` - The parsed stream status variant
2132/// * `Err(Error::Protocol)` - If the string doesn't match any known status
2133///
2134/// # Supported Values
2135///
2136/// - `NEW` - New stream awaiting connection
2137/// - `NEWRESOLVE` - New stream awaiting DNS resolution
2138/// - `REMAP` - Address has been remapped
2139/// - `SENTCONNECT` - Connect request sent to exit
2140/// - `SENTRESOLVE` - Resolve request sent to exit
2141/// - `SUCCEEDED` - Stream connection succeeded
2142/// - `FAILED` - Stream connection failed
2143/// - `DETACHED` - Stream detached from circuit
2144/// - `CONTROLLER_WAIT` - Waiting for controller attachment
2145/// - `CLOSED` - Stream has been closed
2146fn parse_stream_status(s: &str) -> Result<StreamStatus, Error> {
2147    match s.to_uppercase().as_str() {
2148        "NEW" => Ok(StreamStatus::New),
2149        "NEWRESOLVE" => Ok(StreamStatus::NewResolve),
2150        "REMAP" => Ok(StreamStatus::Remap),
2151        "SENTCONNECT" => Ok(StreamStatus::SentConnect),
2152        "SENTRESOLVE" => Ok(StreamStatus::SentResolve),
2153        "SUCCEEDED" => Ok(StreamStatus::Succeeded),
2154        "FAILED" => Ok(StreamStatus::Failed),
2155        "DETACHED" => Ok(StreamStatus::Detached),
2156        "CONTROLLER_WAIT" => Ok(StreamStatus::ControllerWait),
2157        "CLOSED" => Ok(StreamStatus::Closed),
2158        _ => Err(Error::Protocol(format!("unknown stream status: {}", s))),
2159    }
2160}
2161
2162/// Parses an OR (Onion Router) connection status string into an [`OrStatus`] enum variant.
2163///
2164/// Converts a case-insensitive string representation of an OR connection status
2165/// from the Tor control protocol into the corresponding enum variant.
2166///
2167/// # Arguments
2168///
2169/// * `s` - The OR status string to parse (e.g., "NEW", "CONNECTED")
2170///
2171/// # Returns
2172///
2173/// * `Ok(OrStatus)` - The parsed OR connection status variant
2174/// * `Err(Error::Protocol)` - If the string doesn't match any known status
2175///
2176/// # Supported Values
2177///
2178/// - `NEW` - New OR connection initiated
2179/// - `LAUNCHED` - Connection attempt launched
2180/// - `CONNECTED` - Successfully connected to OR
2181/// - `FAILED` - Connection attempt failed
2182/// - `CLOSED` - Connection has been closed
2183fn parse_or_status(s: &str) -> Result<OrStatus, Error> {
2184    match s.to_uppercase().as_str() {
2185        "NEW" => Ok(OrStatus::New),
2186        "LAUNCHED" => Ok(OrStatus::Launched),
2187        "CONNECTED" => Ok(OrStatus::Connected),
2188        "FAILED" => Ok(OrStatus::Failed),
2189        "CLOSED" => Ok(OrStatus::Closed),
2190        _ => Err(Error::Protocol(format!("unknown OR status: {}", s))),
2191    }
2192}
2193
2194/// Parses a guard type string into a [`GuardType`] enum variant.
2195///
2196/// Converts a case-insensitive string representation of a guard node type
2197/// from the Tor control protocol into the corresponding enum variant.
2198///
2199/// # Arguments
2200///
2201/// * `s` - The guard type string to parse (currently only "ENTRY")
2202///
2203/// # Returns
2204///
2205/// * `Ok(GuardType)` - The parsed guard type variant
2206/// * `Err(Error::Protocol)` - If the string doesn't match any known type
2207///
2208/// # Supported Values
2209///
2210/// - `ENTRY` - Entry guard node
2211fn parse_guard_type(s: &str) -> Result<GuardType, Error> {
2212    match s.to_uppercase().as_str() {
2213        "ENTRY" => Ok(GuardType::Entry),
2214        _ => Err(Error::Protocol(format!("unknown guard type: {}", s))),
2215    }
2216}
2217
2218/// Parses a guard status string into a [`GuardStatus`] enum variant.
2219///
2220/// Converts a case-insensitive string representation of a guard node status
2221/// from the Tor control protocol into the corresponding enum variant.
2222///
2223/// # Arguments
2224///
2225/// * `s` - The guard status string to parse (e.g., "NEW", "UP", "DOWN")
2226///
2227/// # Returns
2228///
2229/// * `Ok(GuardStatus)` - The parsed guard status variant
2230/// * `Err(Error::Protocol)` - If the string doesn't match any known status
2231///
2232/// # Supported Values
2233///
2234/// - `NEW` - Guard node newly selected
2235/// - `DROPPED` - Guard node dropped from selection
2236/// - `UP` - Guard node is reachable
2237/// - `DOWN` - Guard node is unreachable
2238/// - `BAD` - Guard node marked as bad
2239/// - `GOOD` - Guard node marked as good
2240fn parse_guard_status(s: &str) -> Result<GuardStatus, Error> {
2241    match s.to_uppercase().as_str() {
2242        "NEW" => Ok(GuardStatus::New),
2243        "DROPPED" => Ok(GuardStatus::Dropped),
2244        "UP" => Ok(GuardStatus::Up),
2245        "DOWN" => Ok(GuardStatus::Down),
2246        "BAD" => Ok(GuardStatus::Bad),
2247        "GOOD" => Ok(GuardStatus::Good),
2248        _ => Err(Error::Protocol(format!("unknown guard status: {}", s))),
2249    }
2250}
2251
2252/// Parses a timeout set type string into a [`TimeoutSetType`] enum variant.
2253///
2254/// Converts a case-insensitive string representation of a circuit build
2255/// timeout set type from the Tor control protocol into the corresponding enum variant.
2256///
2257/// # Arguments
2258///
2259/// * `s` - The timeout set type string to parse (e.g., "COMPUTED", "RESET")
2260///
2261/// # Returns
2262///
2263/// * `Ok(TimeoutSetType)` - The parsed timeout set type variant
2264/// * `Err(Error::Protocol)` - If the string doesn't match any known type
2265///
2266/// # Supported Values
2267///
2268/// - `COMPUTED` - Timeout computed from circuit build times
2269/// - `RESET` - Timeout values have been reset
2270/// - `SUSPENDED` - Timeout learning suspended
2271/// - `DISCARD` - Timeout values discarded
2272/// - `RESUME` - Timeout learning resumed
2273fn parse_timeout_set_type(s: &str) -> Result<TimeoutSetType, Error> {
2274    match s.to_uppercase().as_str() {
2275        "COMPUTED" => Ok(TimeoutSetType::Computed),
2276        "RESET" => Ok(TimeoutSetType::Reset),
2277        "SUSPENDED" => Ok(TimeoutSetType::Suspended),
2278        "DISCARD" => Ok(TimeoutSetType::Discard),
2279        "RESUME" => Ok(TimeoutSetType::Resume),
2280        _ => Err(Error::Protocol(format!("unknown timeout set type: {}", s))),
2281    }
2282}
2283
2284/// Parses a log runlevel string into a [`Runlevel`] enum variant.
2285///
2286/// Converts a case-insensitive string representation of a Tor log severity
2287/// level from the Tor control protocol into the corresponding enum variant.
2288///
2289/// # Arguments
2290///
2291/// * `s` - The runlevel string to parse (e.g., "DEBUG", "INFO", "WARN")
2292///
2293/// # Returns
2294///
2295/// * `Ok(Runlevel)` - The parsed runlevel variant
2296/// * `Err(Error::Protocol)` - If the string doesn't match any known level
2297///
2298/// # Supported Values
2299///
2300/// - `DEBUG` - Debug-level messages (most verbose)
2301/// - `INFO` - Informational messages
2302/// - `NOTICE` - Normal operational messages
2303/// - `WARN` - Warning messages
2304/// - `ERR` - Error messages (most severe)
2305fn parse_runlevel(s: &str) -> Result<Runlevel, Error> {
2306    match s.to_uppercase().as_str() {
2307        "DEBUG" => Ok(Runlevel::Debug),
2308        "INFO" => Ok(Runlevel::Info),
2309        "NOTICE" => Ok(Runlevel::Notice),
2310        "WARN" => Ok(Runlevel::Warn),
2311        "ERR" => Ok(Runlevel::Err),
2312        _ => Err(Error::Protocol(format!("unknown runlevel: {}", s))),
2313    }
2314}
2315
2316/// Parses a signal string into a [`Signal`] enum variant.
2317///
2318/// Converts a case-insensitive string representation of a Tor signal
2319/// from the Tor control protocol into the corresponding enum variant.
2320/// Supports both signal names and their Unix signal equivalents.
2321///
2322/// # Arguments
2323///
2324/// * `s` - The signal string to parse (e.g., "RELOAD", "HUP", "NEWNYM")
2325///
2326/// # Returns
2327///
2328/// * `Ok(Signal)` - The parsed signal variant
2329/// * `Err(Error::Protocol)` - If the string doesn't match any known signal
2330///
2331/// # Supported Values
2332///
2333/// - `RELOAD` or `HUP` - Reload configuration
2334/// - `SHUTDOWN` or `INT` - Controlled shutdown
2335/// - `DUMP` or `USR1` - Dump statistics
2336/// - `DEBUG` or `USR2` - Switch to debug logging
2337/// - `HALT` or `TERM` - Immediate shutdown
2338/// - `NEWNYM` - Request new circuits
2339/// - `CLEARDNSCACHE` - Clear DNS cache
2340/// - `HEARTBEAT` - Trigger heartbeat log
2341/// - `ACTIVE` - Wake from dormant mode
2342/// - `DORMANT` - Enter dormant mode
2343fn parse_signal(s: &str) -> Result<Signal, Error> {
2344    match s.to_uppercase().as_str() {
2345        "RELOAD" | "HUP" => Ok(Signal::Reload),
2346        "SHUTDOWN" | "INT" => Ok(Signal::Shutdown),
2347        "DUMP" | "USR1" => Ok(Signal::Dump),
2348        "DEBUG" | "USR2" => Ok(Signal::Debug),
2349        "HALT" | "TERM" => Ok(Signal::Halt),
2350        "NEWNYM" => Ok(Signal::Newnym),
2351        "CLEARDNSCACHE" => Ok(Signal::ClearDnsCache),
2352        "HEARTBEAT" => Ok(Signal::Heartbeat),
2353        "ACTIVE" => Ok(Signal::Active),
2354        "DORMANT" => Ok(Signal::Dormant),
2355        _ => Err(Error::Protocol(format!("unknown signal: {}", s))),
2356    }
2357}
2358
2359/// Parses a connection type string into a [`ConnectionType`] enum variant.
2360///
2361/// Converts a case-insensitive string representation of a Tor connection type
2362/// from the Tor control protocol into the corresponding enum variant.
2363///
2364/// # Arguments
2365///
2366/// * `s` - The connection type string to parse (e.g., "OR", "DIR", "EXIT")
2367///
2368/// # Returns
2369///
2370/// * `Ok(ConnectionType)` - The parsed connection type variant
2371/// * `Err(Error::Protocol)` - If the string doesn't match any known type
2372///
2373/// # Supported Values
2374///
2375/// - `OR` - Onion Router connection (relay-to-relay)
2376/// - `DIR` - Directory connection
2377/// - `EXIT` - Exit connection to destination
2378fn parse_connection_type(s: &str) -> Result<ConnectionType, Error> {
2379    match s.to_uppercase().as_str() {
2380        "OR" => Ok(ConnectionType::Or),
2381        "DIR" => Ok(ConnectionType::Dir),
2382        "EXIT" => Ok(ConnectionType::Exit),
2383        _ => Err(Error::Protocol(format!("unknown connection type: {}", s))),
2384    }
2385}
2386
2387/// Parses a hidden service descriptor action string into an [`HsDescAction`] enum variant.
2388///
2389/// Converts a case-insensitive string representation of a hidden service
2390/// descriptor action from the Tor control protocol into the corresponding enum variant.
2391///
2392/// # Arguments
2393///
2394/// * `s` - The HS_DESC action string to parse (e.g., "REQUESTED", "RECEIVED")
2395///
2396/// # Returns
2397///
2398/// * `Ok(HsDescAction)` - The parsed action variant
2399/// * `Err(Error::Protocol)` - If the string doesn't match any known action
2400///
2401/// # Supported Values
2402///
2403/// - `REQUESTED` - Descriptor fetch requested
2404/// - `UPLOAD` - Descriptor upload initiated
2405/// - `RECEIVED` - Descriptor successfully received
2406/// - `UPLOADED` - Descriptor successfully uploaded
2407/// - `IGNORE` - Descriptor ignored
2408/// - `FAILED` - Descriptor operation failed
2409/// - `CREATED` - Descriptor created locally
2410fn parse_hs_desc_action(s: &str) -> Result<HsDescAction, Error> {
2411    match s.to_uppercase().as_str() {
2412        "REQUESTED" => Ok(HsDescAction::Requested),
2413        "UPLOAD" => Ok(HsDescAction::Upload),
2414        "RECEIVED" => Ok(HsDescAction::Received),
2415        "UPLOADED" => Ok(HsDescAction::Uploaded),
2416        "IGNORE" => Ok(HsDescAction::Ignore),
2417        "FAILED" => Ok(HsDescAction::Failed),
2418        "CREATED" => Ok(HsDescAction::Created),
2419        _ => Err(Error::Protocol(format!("unknown HS_DESC action: {}", s))),
2420    }
2421}
2422
2423/// Parses a hidden service authentication type string into an [`HsAuth`] enum variant.
2424///
2425/// Converts a case-insensitive string representation of a hidden service
2426/// authentication type from the Tor control protocol into the corresponding enum variant.
2427///
2428/// # Arguments
2429///
2430/// * `s` - The HS auth type string to parse (e.g., "NO_AUTH", "BASIC_AUTH")
2431///
2432/// # Returns
2433///
2434/// * `Ok(HsAuth)` - The parsed authentication type variant
2435/// * `Err(Error::Protocol)` - If the string doesn't match any known type
2436///
2437/// # Supported Values
2438///
2439/// - `NO_AUTH` - No authentication required
2440/// - `BASIC_AUTH` - Basic authentication
2441/// - `STEALTH_AUTH` - Stealth authentication (more private)
2442/// - `UNKNOWN` - Unknown authentication type
2443fn parse_hs_auth(s: &str) -> Result<HsAuth, Error> {
2444    match s.to_uppercase().as_str() {
2445        "NO_AUTH" => Ok(HsAuth::NoAuth),
2446        "BASIC_AUTH" => Ok(HsAuth::BasicAuth),
2447        "STEALTH_AUTH" => Ok(HsAuth::StealthAuth),
2448        "UNKNOWN" => Ok(HsAuth::Unknown),
2449        _ => Err(Error::Protocol(format!("unknown HS auth type: {}", s))),
2450    }
2451}
2452
2453/// Parses a hidden service descriptor failure reason string into an [`HsDescReason`] enum variant.
2454///
2455/// Converts a case-insensitive string representation of a hidden service
2456/// descriptor failure reason from the Tor control protocol into the corresponding enum variant.
2457///
2458/// # Arguments
2459///
2460/// * `s` - The HS_DESC reason string to parse (e.g., "NOT_FOUND", "BAD_DESC")
2461///
2462/// # Returns
2463///
2464/// * `Ok(HsDescReason)` - The parsed reason variant
2465/// * `Err(Error::Protocol)` - If the string doesn't match any known reason
2466///
2467/// # Supported Values
2468///
2469/// - `BAD_DESC` - Descriptor was malformed or invalid
2470/// - `QUERY_REJECTED` - Query was rejected by HSDir
2471/// - `UPLOAD_REJECTED` - Upload was rejected by HSDir
2472/// - `NOT_FOUND` - Descriptor not found
2473/// - `QUERY_NO_HSDIR` - No HSDir available for query
2474/// - `QUERY_RATE_LIMITED` - Query rate limited
2475/// - `UNEXPECTED` - Unexpected error occurred
2476fn parse_hs_desc_reason(s: &str) -> Result<HsDescReason, Error> {
2477    match s.to_uppercase().as_str() {
2478        "BAD_DESC" => Ok(HsDescReason::BadDesc),
2479        "QUERY_REJECTED" => Ok(HsDescReason::QueryRejected),
2480        "UPLOAD_REJECTED" => Ok(HsDescReason::UploadRejected),
2481        "NOT_FOUND" => Ok(HsDescReason::NotFound),
2482        "QUERY_NO_HSDIR" => Ok(HsDescReason::QueryNoHsDir),
2483        "QUERY_RATE_LIMITED" => Ok(HsDescReason::QueryRateLimited),
2484        "UNEXPECTED" => Ok(HsDescReason::Unexpected),
2485        _ => Err(Error::Protocol(format!("unknown HS_DESC reason: {}", s))),
2486    }
2487}
2488
2489/// Parses a circuit purpose string into a [`CircPurpose`] enum variant.
2490///
2491/// Converts a case-insensitive string representation of a circuit purpose
2492/// from the Tor control protocol into the corresponding enum variant.
2493///
2494/// # Arguments
2495///
2496/// * `s` - The circuit purpose string to parse (e.g., "GENERAL", "HS_CLIENT_REND")
2497///
2498/// # Returns
2499///
2500/// * `Ok(CircPurpose)` - The parsed circuit purpose variant
2501/// * `Err(Error::Protocol)` - If the string doesn't match any known purpose
2502///
2503/// # Supported Values
2504///
2505/// - `GENERAL` - General-purpose circuit for user traffic
2506/// - `HS_CLIENT_INTRO` - Hidden service client introduction circuit
2507/// - `HS_CLIENT_REND` - Hidden service client rendezvous circuit
2508/// - `HS_SERVICE_INTRO` - Hidden service introduction point circuit
2509/// - `HS_SERVICE_REND` - Hidden service rendezvous circuit
2510/// - `TESTING` - Circuit for testing purposes
2511/// - `CONTROLLER` - Circuit created by controller
2512/// - `MEASURE_TIMEOUT` - Circuit for measuring build timeouts
2513/// - `HS_VANGUARDS` - Vanguard circuit for hidden services
2514/// - `PATH_BIAS_TESTING` - Circuit for path bias testing
2515/// - `CIRCUIT_PADDING` - Circuit for padding purposes
2516fn parse_circ_purpose(s: &str) -> Result<CircPurpose, Error> {
2517    match s.to_uppercase().as_str() {
2518        "GENERAL" => Ok(CircPurpose::General),
2519        "HS_CLIENT_INTRO" => Ok(CircPurpose::HsClientIntro),
2520        "HS_CLIENT_REND" => Ok(CircPurpose::HsClientRend),
2521        "HS_SERVICE_INTRO" => Ok(CircPurpose::HsServiceIntro),
2522        "HS_SERVICE_REND" => Ok(CircPurpose::HsServiceRend),
2523        "TESTING" => Ok(CircPurpose::Testing),
2524        "CONTROLLER" => Ok(CircPurpose::Controller),
2525        "MEASURE_TIMEOUT" => Ok(CircPurpose::MeasureTimeout),
2526        "HS_VANGUARDS" => Ok(CircPurpose::HsVanguards),
2527        "PATH_BIAS_TESTING" => Ok(CircPurpose::PathBiasTesting),
2528        "CIRCUIT_PADDING" => Ok(CircPurpose::CircuitPadding),
2529        _ => Err(Error::Protocol(format!("unknown circuit purpose: {}", s))),
2530    }
2531}
2532
2533/// Parses a hidden service state string into a [`HiddenServiceState`] enum variant.
2534///
2535/// Converts a case-insensitive string representation of a hidden service
2536/// circuit state from the Tor control protocol into the corresponding enum variant.
2537///
2538/// # Arguments
2539///
2540/// * `s` - The HS state string to parse (e.g., "HSCI_CONNECTING", "HSCR_JOINED")
2541///
2542/// # Returns
2543///
2544/// * `Ok(HiddenServiceState)` - The parsed hidden service state variant
2545/// * `Err(Error::Protocol)` - If the string doesn't match any known state
2546///
2547/// # Supported Values
2548///
2549/// Client Introduction (HSCI):
2550/// - `HSCI_CONNECTING` - Connecting to introduction point
2551/// - `HSCI_INTRO_SENT` - Introduction sent to service
2552/// - `HSCI_DONE` - Introduction complete
2553///
2554/// Client Rendezvous (HSCR):
2555/// - `HSCR_CONNECTING` - Connecting to rendezvous point
2556/// - `HSCR_ESTABLISHED_IDLE` - Rendezvous established, idle
2557/// - `HSCR_ESTABLISHED_WAITING` - Rendezvous established, waiting
2558/// - `HSCR_JOINED` - Rendezvous joined with service
2559///
2560/// Service Introduction (HSSI):
2561/// - `HSSI_CONNECTING` - Service connecting to intro point
2562/// - `HSSI_ESTABLISHED` - Service intro point established
2563///
2564/// Service Rendezvous (HSSR):
2565/// - `HSSR_CONNECTING` - Service connecting to rendezvous
2566/// - `HSSR_JOINED` - Service joined rendezvous
2567fn parse_hs_state(s: &str) -> Result<HiddenServiceState, Error> {
2568    match s.to_uppercase().as_str() {
2569        "HSCI_CONNECTING" => Ok(HiddenServiceState::HsciConnecting),
2570        "HSCI_INTRO_SENT" => Ok(HiddenServiceState::HsciIntroSent),
2571        "HSCI_DONE" => Ok(HiddenServiceState::HsciDone),
2572        "HSCR_CONNECTING" => Ok(HiddenServiceState::HscrConnecting),
2573        "HSCR_ESTABLISHED_IDLE" => Ok(HiddenServiceState::HscrEstablishedIdle),
2574        "HSCR_ESTABLISHED_WAITING" => Ok(HiddenServiceState::HscrEstablishedWaiting),
2575        "HSCR_JOINED" => Ok(HiddenServiceState::HscrJoined),
2576        "HSSI_CONNECTING" => Ok(HiddenServiceState::HssiConnecting),
2577        "HSSI_ESTABLISHED" => Ok(HiddenServiceState::HssiEstablished),
2578        "HSSR_CONNECTING" => Ok(HiddenServiceState::HssrConnecting),
2579        "HSSR_JOINED" => Ok(HiddenServiceState::HssrJoined),
2580        _ => Err(Error::Protocol(format!("unknown HS state: {}", s))),
2581    }
2582}
2583
2584/// Parses a circuit closure reason string into a [`CircClosureReason`] enum variant.
2585///
2586/// Converts a case-insensitive string representation of a circuit closure reason
2587/// from the Tor control protocol into the corresponding enum variant.
2588///
2589/// # Arguments
2590///
2591/// * `s` - The circuit closure reason string to parse (e.g., "FINISHED", "TIMEOUT")
2592///
2593/// # Returns
2594///
2595/// * `Ok(CircClosureReason)` - The parsed closure reason variant
2596/// * `Err(Error::Protocol)` - If the string doesn't match any known reason
2597///
2598/// # Supported Values
2599///
2600/// - `NONE` - No reason given
2601/// - `TORPROTOCOL` - Tor protocol violation
2602/// - `INTERNAL` - Internal error
2603/// - `REQUESTED` - Closure requested by client
2604/// - `HIBERNATING` - Relay is hibernating
2605/// - `RESOURCELIMIT` - Resource limit reached
2606/// - `CONNECTFAILED` - Connection to relay failed
2607/// - `OR_IDENTITY` - OR identity mismatch
2608/// - `OR_CONN_CLOSED` - OR connection closed
2609/// - `FINISHED` - Circuit finished normally
2610/// - `TIMEOUT` - Circuit timed out
2611/// - `DESTROYED` - Circuit was destroyed
2612/// - `NOPATH` - No path available
2613/// - `NOSUCHSERVICE` - Hidden service not found
2614/// - `MEASUREMENT_EXPIRED` - Measurement circuit expired
2615/// - `IP_NOW_REDUNDANT` - Introduction point now redundant
2616fn parse_circ_closure_reason(s: &str) -> Result<CircClosureReason, Error> {
2617    match s.to_uppercase().as_str() {
2618        "NONE" => Ok(CircClosureReason::None),
2619        "TORPROTOCOL" => Ok(CircClosureReason::TorProtocol),
2620        "INTERNAL" => Ok(CircClosureReason::Internal),
2621        "REQUESTED" => Ok(CircClosureReason::Requested),
2622        "HIBERNATING" => Ok(CircClosureReason::Hibernating),
2623        "RESOURCELIMIT" => Ok(CircClosureReason::ResourceLimit),
2624        "CONNECTFAILED" => Ok(CircClosureReason::ConnectFailed),
2625        "OR_IDENTITY" => Ok(CircClosureReason::OrIdentity),
2626        "OR_CONN_CLOSED" => Ok(CircClosureReason::OrConnClosed),
2627        "FINISHED" => Ok(CircClosureReason::Finished),
2628        "TIMEOUT" => Ok(CircClosureReason::Timeout),
2629        "DESTROYED" => Ok(CircClosureReason::Destroyed),
2630        "NOPATH" => Ok(CircClosureReason::NoPath),
2631        "NOSUCHSERVICE" => Ok(CircClosureReason::NoSuchService),
2632        "MEASUREMENT_EXPIRED" => Ok(CircClosureReason::MeasurementExpired),
2633        "IP_NOW_REDUNDANT" => Ok(CircClosureReason::IpNowRedundant),
2634        _ => Err(Error::Protocol(format!(
2635            "unknown circuit closure reason: {}",
2636            s
2637        ))),
2638    }
2639}
2640
2641/// Parses a stream closure reason string into a [`StreamClosureReason`] enum variant.
2642///
2643/// Converts a case-insensitive string representation of a stream closure reason
2644/// from the Tor control protocol into the corresponding enum variant.
2645///
2646/// # Arguments
2647///
2648/// * `s` - The stream closure reason string to parse (e.g., "DONE", "TIMEOUT")
2649///
2650/// # Returns
2651///
2652/// * `Ok(StreamClosureReason)` - The parsed closure reason variant
2653/// * `Err(Error::Protocol)` - If the string doesn't match any known reason
2654///
2655/// # Supported Values
2656///
2657/// - `MISC` - Miscellaneous error
2658/// - `RESOLVEFAILED` - DNS resolution failed
2659/// - `CONNECTREFUSED` - Connection refused by destination
2660/// - `EXITPOLICY` - Exit policy rejected connection
2661/// - `DESTROY` - Circuit was destroyed
2662/// - `DONE` - Stream completed normally
2663/// - `TIMEOUT` - Stream timed out
2664/// - `NOROUTE` - No route to destination
2665/// - `HIBERNATING` - Relay is hibernating
2666/// - `INTERNAL` - Internal error
2667/// - `RESOURCELIMIT` - Resource limit reached
2668/// - `CONNRESET` - Connection reset
2669/// - `TORPROTOCOL` - Tor protocol violation
2670/// - `NOTDIRECTORY` - Not a directory server
2671/// - `END` - Stream ended
2672/// - `PRIVATE_ADDR` - Private address rejected
2673fn parse_stream_closure_reason(s: &str) -> Result<StreamClosureReason, Error> {
2674    match s.to_uppercase().as_str() {
2675        "MISC" => Ok(StreamClosureReason::Misc),
2676        "RESOLVEFAILED" => Ok(StreamClosureReason::ResolveFailed),
2677        "CONNECTREFUSED" => Ok(StreamClosureReason::ConnectRefused),
2678        "EXITPOLICY" => Ok(StreamClosureReason::ExitPolicy),
2679        "DESTROY" => Ok(StreamClosureReason::Destroy),
2680        "DONE" => Ok(StreamClosureReason::Done),
2681        "TIMEOUT" => Ok(StreamClosureReason::Timeout),
2682        "NOROUTE" => Ok(StreamClosureReason::NoRoute),
2683        "HIBERNATING" => Ok(StreamClosureReason::Hibernating),
2684        "INTERNAL" => Ok(StreamClosureReason::Internal),
2685        "RESOURCELIMIT" => Ok(StreamClosureReason::ResourceLimit),
2686        "CONNRESET" => Ok(StreamClosureReason::ConnReset),
2687        "TORPROTOCOL" => Ok(StreamClosureReason::TorProtocol),
2688        "NOTDIRECTORY" => Ok(StreamClosureReason::NotDirectory),
2689        "END" => Ok(StreamClosureReason::End),
2690        "PRIVATE_ADDR" => Ok(StreamClosureReason::PrivateAddr),
2691        _ => Err(Error::Protocol(format!(
2692            "unknown stream closure reason: {}",
2693            s
2694        ))),
2695    }
2696}
2697
2698/// Parses a stream source string into a [`StreamSource`] enum variant.
2699///
2700/// Converts a case-insensitive string representation of a stream source
2701/// from the Tor control protocol into the corresponding enum variant.
2702///
2703/// # Arguments
2704///
2705/// * `s` - The stream source string to parse (e.g., "CACHE", "EXIT")
2706///
2707/// # Returns
2708///
2709/// * `Ok(StreamSource)` - The parsed stream source variant
2710/// * `Err(Error::Protocol)` - If the string doesn't match any known source
2711///
2712/// # Supported Values
2713///
2714/// - `CACHE` - Data from cache
2715/// - `EXIT` - Data from exit node
2716fn parse_stream_source(s: &str) -> Result<StreamSource, Error> {
2717    match s.to_uppercase().as_str() {
2718        "CACHE" => Ok(StreamSource::Cache),
2719        "EXIT" => Ok(StreamSource::Exit),
2720        _ => Err(Error::Protocol(format!("unknown stream source: {}", s))),
2721    }
2722}
2723
2724/// Parses a stream purpose string into a [`StreamPurpose`] enum variant.
2725///
2726/// Converts a case-insensitive string representation of a stream purpose
2727/// from the Tor control protocol into the corresponding enum variant.
2728///
2729/// # Arguments
2730///
2731/// * `s` - The stream purpose string to parse (e.g., "USER", "DIR_FETCH")
2732///
2733/// # Returns
2734///
2735/// * `Ok(StreamPurpose)` - The parsed stream purpose variant
2736/// * `Err(Error::Protocol)` - If the string doesn't match any known purpose
2737///
2738/// # Supported Values
2739///
2740/// - `DIR_FETCH` - Directory fetch operation
2741/// - `DIR_UPLOAD` - Directory upload operation
2742/// - `DNS_REQUEST` - DNS resolution request
2743/// - `DIRPORT_TEST` - Directory port testing
2744/// - `USER` - User-initiated stream
2745fn parse_stream_purpose(s: &str) -> Result<StreamPurpose, Error> {
2746    match s.to_uppercase().as_str() {
2747        "DIR_FETCH" => Ok(StreamPurpose::DirFetch),
2748        "DIR_UPLOAD" => Ok(StreamPurpose::DirUpload),
2749        "DNS_REQUEST" => Ok(StreamPurpose::DnsRequest),
2750        "DIRPORT_TEST" => Ok(StreamPurpose::DirportTest),
2751        "USER" => Ok(StreamPurpose::User),
2752        _ => Err(Error::Protocol(format!("unknown stream purpose: {}", s))),
2753    }
2754}
2755
2756/// Parses an OR connection closure reason string into an [`OrClosureReason`] enum variant.
2757///
2758/// Converts a case-insensitive string representation of an OR connection
2759/// closure reason from the Tor control protocol into the corresponding enum variant.
2760///
2761/// # Arguments
2762///
2763/// * `s` - The OR closure reason string to parse (e.g., "DONE", "TIMEOUT")
2764///
2765/// # Returns
2766///
2767/// * `Ok(OrClosureReason)` - The parsed closure reason variant
2768/// * `Err(Error::Protocol)` - If the string doesn't match any known reason
2769///
2770/// # Supported Values
2771///
2772/// - `DONE` - Connection completed normally
2773/// - `CONNECTREFUSED` - Connection refused
2774/// - `IDENTITY` - Identity verification failed
2775/// - `CONNECTRESET` - Connection reset
2776/// - `TIMEOUT` - Connection timed out
2777/// - `NOROUTE` - No route to relay
2778/// - `IOERROR` - I/O error occurred
2779/// - `RESOURCELIMIT` - Resource limit reached
2780/// - `MISC` - Miscellaneous error
2781/// - `PT_MISSING` - Pluggable transport missing
2782fn parse_or_closure_reason(s: &str) -> Result<OrClosureReason, Error> {
2783    match s.to_uppercase().as_str() {
2784        "DONE" => Ok(OrClosureReason::Done),
2785        "CONNECTREFUSED" => Ok(OrClosureReason::ConnectRefused),
2786        "IDENTITY" => Ok(OrClosureReason::Identity),
2787        "CONNECTRESET" => Ok(OrClosureReason::ConnectReset),
2788        "TIMEOUT" => Ok(OrClosureReason::Timeout),
2789        "NOROUTE" => Ok(OrClosureReason::NoRoute),
2790        "IOERROR" => Ok(OrClosureReason::IoError),
2791        "RESOURCELIMIT" => Ok(OrClosureReason::ResourceLimit),
2792        "MISC" => Ok(OrClosureReason::Misc),
2793        "PT_MISSING" => Ok(OrClosureReason::PtMissing),
2794        _ => Err(Error::Protocol(format!("unknown OR closure reason: {}", s))),
2795    }
2796}
2797
2798/// Parses a comma-separated string of circuit build flags into a vector of [`CircBuildFlag`].
2799///
2800/// Converts a comma-separated string of circuit build flags from the Tor
2801/// control protocol into a vector of enum variants. Unknown flags are silently ignored.
2802///
2803/// # Arguments
2804///
2805/// * `s` - The comma-separated build flags string (e.g., "ONEHOP_TUNNEL,IS_INTERNAL")
2806///
2807/// # Returns
2808///
2809/// A vector of recognized [`CircBuildFlag`] variants. Unknown flags are filtered out.
2810///
2811/// # Supported Values
2812///
2813/// - `ONEHOP_TUNNEL` - Single-hop circuit (for directory connections)
2814/// - `IS_INTERNAL` - Internal circuit (not for user traffic)
2815/// - `NEED_CAPACITY` - Circuit needs high-capacity relays
2816/// - `NEED_UPTIME` - Circuit needs high-uptime relays
2817fn parse_build_flags(s: &str) -> Vec<CircBuildFlag> {
2818    s.split(',')
2819        .filter_map(|f| match f.to_uppercase().as_str() {
2820            "ONEHOP_TUNNEL" => Some(CircBuildFlag::OneHopTunnel),
2821            "IS_INTERNAL" => Some(CircBuildFlag::IsInternal),
2822            "NEED_CAPACITY" => Some(CircBuildFlag::NeedCapacity),
2823            "NEED_UPTIME" => Some(CircBuildFlag::NeedUptime),
2824            _ => None,
2825        })
2826        .collect()
2827}
2828
2829/// Parses a circuit path string into a vector of relay fingerprint and nickname pairs.
2830///
2831/// Converts a comma-separated circuit path string from the Tor control protocol
2832/// into a vector of tuples containing relay fingerprints and optional nicknames.
2833///
2834/// # Arguments
2835///
2836/// * `s` - The circuit path string (e.g., "$FP1~nick1,$FP2=nick2,$FP3")
2837///
2838/// # Returns
2839///
2840/// A vector of tuples where each tuple contains:
2841/// - The relay fingerprint (with leading `$` stripped)
2842/// - An optional nickname (if present after `~` or `=`)
2843///
2844/// # Format
2845///
2846/// Each relay in the path can be specified as:
2847/// - `$FINGERPRINT~nickname` - Fingerprint with nickname (tilde separator)
2848/// - `$FINGERPRINT=nickname` - Fingerprint with nickname (equals separator)
2849/// - `$FINGERPRINT` - Fingerprint only
2850/// - `FINGERPRINT` - Fingerprint without `$` prefix
2851fn parse_circuit_path(s: &str) -> Vec<(String, Option<String>)> {
2852    s.split(',')
2853        .map(|relay| {
2854            let relay = relay.trim_start_matches('$');
2855            if let Some((fp, nick)) = relay.split_once('~') {
2856                (fp.to_string(), Some(nick.to_string()))
2857            } else if let Some((fp, nick)) = relay.split_once('=') {
2858                (fp.to_string(), Some(nick.to_string()))
2859            } else {
2860                (relay.to_string(), None)
2861            }
2862        })
2863        .collect()
2864}
2865
2866/// Parses a relay endpoint string into a fingerprint and optional nickname.
2867///
2868/// Converts a relay endpoint string from the Tor control protocol into a tuple
2869/// containing the relay fingerprint and optional nickname.
2870///
2871/// # Arguments
2872///
2873/// * `s` - The relay endpoint string (e.g., "$FP~nickname" or "$FP=nickname")
2874///
2875/// # Returns
2876///
2877/// A tuple containing:
2878/// - The relay fingerprint (with leading `$` stripped)
2879/// - An optional nickname (if present after `~` or `=`)
2880///
2881/// # Format
2882///
2883/// The relay can be specified as:
2884/// - `$FINGERPRINT~nickname` - Fingerprint with nickname (tilde separator)
2885/// - `$FINGERPRINT=nickname` - Fingerprint with nickname (equals separator)
2886/// - `$FINGERPRINT` - Fingerprint only
2887/// - `FINGERPRINT` - Fingerprint without `$` prefix
2888fn parse_relay_endpoint(s: &str) -> (String, Option<String>) {
2889    let s = s.trim_start_matches('$');
2890    if let Some((fp, nick)) = s.split_once('~') {
2891        (fp.to_string(), Some(nick.to_string()))
2892    } else if let Some((fp, nick)) = s.split_once('=') {
2893        (fp.to_string(), Some(nick.to_string()))
2894    } else {
2895        (s.to_string(), None)
2896    }
2897}
2898
2899/// Parses a target address string into a host and port tuple.
2900///
2901/// Converts a target address string (host:port format) from the Tor control
2902/// protocol into a tuple containing the host and port number.
2903///
2904/// # Arguments
2905///
2906/// * `target` - The target address string (e.g., "example.com:80" or "[::1]:443")
2907///
2908/// # Returns
2909///
2910/// * `Ok((host, port))` - The parsed host string and port number
2911/// * `Err(Error::Protocol)` - If the port cannot be parsed as a valid u16
2912///
2913/// # Format
2914///
2915/// Supports both IPv4 and IPv6 addresses:
2916/// - `hostname:port` - Standard hostname with port
2917/// - `ip:port` - IPv4 address with port
2918/// - `[ipv6]:port` - IPv6 address with port (brackets preserved in host)
2919///
2920/// If no port is specified, returns port 0.
2921fn parse_target(target: &str) -> Result<(String, u16), Error> {
2922    if let Some(colon_pos) = target.rfind(':') {
2923        let host = target[..colon_pos].to_string();
2924        let port_str = &target[colon_pos + 1..];
2925        let port: u16 = port_str
2926            .parse()
2927            .map_err(|_| Error::Protocol(format!("invalid port: {}", port_str)))?;
2928        Ok((host, port))
2929    } else {
2930        Ok((target.to_string(), 0))
2931    }
2932}
2933
2934/// Parses an ISO 8601 timestamp string into a UTC [`DateTime`].
2935///
2936/// Converts a timestamp string in ISO 8601 format from the Tor control
2937/// protocol into a [`DateTime<Utc>`] value.
2938///
2939/// # Arguments
2940///
2941/// * `s` - The timestamp string (e.g., "2024-01-15 12:30:45" or "2024-01-15T12:30:45.123")
2942///
2943/// # Returns
2944///
2945/// * `Ok(DateTime<Utc>)` - The parsed UTC datetime
2946/// * `Err(Error::Protocol)` - If the timestamp format is invalid
2947///
2948/// # Supported Formats
2949///
2950/// - `YYYY-MM-DD HH:MM:SS` - Standard format
2951/// - `YYYY-MM-DDTHH:MM:SS` - ISO 8601 with T separator
2952/// - `YYYY-MM-DD HH:MM:SS.fff` - With fractional seconds
2953/// - `YYYY-MM-DDTHH:MM:SS.fff` - ISO 8601 with fractional seconds
2954fn parse_iso_timestamp(s: &str) -> Result<DateTime<Utc>, Error> {
2955    let s = s.replace('T', " ");
2956    let formats = ["%Y-%m-%d %H:%M:%S%.f", "%Y-%m-%d %H:%M:%S"];
2957    for fmt in &formats {
2958        if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(&s, fmt) {
2959            return Ok(DateTime::from_naive_utc_and_offset(dt, Utc));
2960        }
2961    }
2962    Err(Error::Protocol(format!("invalid timestamp: {}", s)))
2963}
2964
2965/// Parses a local timestamp string into a local [`DateTime`].
2966///
2967/// Converts a timestamp string from the Tor control protocol into a
2968/// [`DateTime<Local>`] value using the system's local timezone offset.
2969///
2970/// # Arguments
2971///
2972/// * `s` - The timestamp string (e.g., "2024-01-15 12:30:45")
2973///
2974/// # Returns
2975///
2976/// * `Ok(DateTime<Local>)` - The parsed local datetime
2977/// * `Err(Error::Protocol)` - If the timestamp format is invalid
2978///
2979/// # Supported Formats
2980///
2981/// - `YYYY-MM-DD HH:MM:SS` - Standard format
2982/// - `YYYY-MM-DD HH:MM:SS.fff` - With fractional seconds
2983///
2984/// # Note
2985///
2986/// The timestamp is interpreted as being in the local timezone at the
2987/// time of parsing. The current local timezone offset is applied.
2988fn parse_local_timestamp(s: &str) -> Result<DateTime<Local>, Error> {
2989    let formats = ["%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M:%S%.f"];
2990    for fmt in &formats {
2991        if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(s, fmt) {
2992            return Ok(DateTime::from_naive_utc_and_offset(
2993                dt,
2994                *Local::now().offset(),
2995            ));
2996        }
2997    }
2998    Err(Error::Protocol(format!("invalid local timestamp: {}", s)))
2999}
3000
3001/// Parses a UTC timestamp string into a UTC [`DateTime`].
3002///
3003/// This is an alias for [`parse_iso_timestamp`] that explicitly indicates
3004/// the timestamp should be interpreted as UTC.
3005///
3006/// # Arguments
3007///
3008/// * `s` - The timestamp string in ISO 8601 format
3009///
3010/// # Returns
3011///
3012/// * `Ok(DateTime<Utc>)` - The parsed UTC datetime
3013/// * `Err(Error::Protocol)` - If the timestamp format is invalid
3014///
3015/// # See Also
3016///
3017/// - [`parse_iso_timestamp`] - The underlying implementation
3018fn parse_utc_timestamp(s: &str) -> Result<DateTime<Utc>, Error> {
3019    parse_iso_timestamp(s)
3020}
3021
3022/// Enumeration of all parsed event types.
3023///
3024/// This enum provides a unified way to handle different event types
3025/// through pattern matching. Use [`ParsedEvent::parse`] to convert
3026/// raw event data into the appropriate variant.
3027///
3028/// # Parsing Events
3029///
3030/// Events are parsed from raw control protocol messages:
3031///
3032/// ```rust,ignore
3033/// use stem_rs::events::ParsedEvent;
3034///
3035/// let event = ParsedEvent::parse("BW", "1024 2048", None)?;
3036/// match event {
3037///     ParsedEvent::Bandwidth(bw) => {
3038///         println!("Read: {}, Written: {}", bw.read, bw.written);
3039///     }
3040///     _ => {}
3041/// }
3042/// ```
3043///
3044/// # Unknown Events
3045///
3046/// Events that don't match a known type are captured as
3047/// [`ParsedEvent::Unknown`], preserving the raw content for
3048/// debugging or custom handling.
3049///
3050/// # Display
3051///
3052/// All variants implement [`Display`](std::fmt::Display) to reconstruct
3053/// a human-readable representation of the event.
3054#[derive(Debug, Clone)]
3055pub enum ParsedEvent {
3056    /// Aggregate bandwidth event (BW).
3057    Bandwidth(BandwidthEvent),
3058    /// Log message event (DEBUG, INFO, NOTICE, WARN, ERR).
3059    Log(LogEvent),
3060    /// Circuit status change event (CIRC).
3061    Circuit(CircuitEvent),
3062    /// Stream status change event (STREAM).
3063    Stream(StreamEvent),
3064    /// OR connection status change event (ORCONN).
3065    OrConn(OrConnEvent),
3066    /// Address mapping event (ADDRMAP).
3067    AddrMap(AddrMapEvent),
3068    /// Circuit build timeout change event (BUILDTIMEOUT_SET).
3069    BuildTimeoutSet(BuildTimeoutSetEvent),
3070    /// Guard relay status change event (GUARD).
3071    Guard(GuardEvent),
3072    /// New descriptor available event (NEWDESC).
3073    NewDesc(NewDescEvent),
3074    /// Signal received event (SIGNAL).
3075    Signal(SignalEvent),
3076    /// Status event (STATUS_GENERAL, STATUS_CLIENT, STATUS_SERVER).
3077    Status(StatusEvent),
3078    /// Configuration changed event (CONF_CHANGED).
3079    ConfChanged(ConfChangedEvent),
3080    /// Network liveness event (NETWORK_LIVENESS).
3081    NetworkLiveness(NetworkLivenessEvent),
3082    /// Per-circuit bandwidth event (CIRC_BW).
3083    CircuitBandwidth(CircuitBandwidthEvent),
3084    /// Per-connection bandwidth event (CONN_BW).
3085    ConnectionBandwidth(ConnectionBandwidthEvent),
3086    /// Hidden service descriptor event (HS_DESC).
3087    HsDesc(HsDescEvent),
3088    /// Unknown or unrecognized event type.
3089    Unknown {
3090        /// The event type string.
3091        event_type: String,
3092        /// The raw event content.
3093        content: String,
3094    },
3095}
3096
3097impl ParsedEvent {
3098    /// Parses raw event data into a typed event.
3099    ///
3100    /// # Arguments
3101    ///
3102    /// * `event_type` - The event type keyword (e.g., "BW", "CIRC")
3103    /// * `content` - The event content after the type
3104    /// * `lines` - Optional multi-line content for events like CONF_CHANGED
3105    ///
3106    /// # Supported Event Types
3107    ///
3108    /// - `BW` - Bandwidth events
3109    /// - `DEBUG`, `INFO`, `NOTICE`, `WARN`, `ERR` - Log events
3110    /// - `CIRC` - Circuit events
3111    /// - `STREAM` - Stream events
3112    /// - `ORCONN` - OR connection events
3113    /// - `ADDRMAP` - Address map events
3114    /// - `BUILDTIMEOUT_SET` - Build timeout events
3115    /// - `GUARD` - Guard events
3116    /// - `NEWDESC` - New descriptor events
3117    /// - `SIGNAL` - Signal events
3118    /// - `STATUS_GENERAL`, `STATUS_CLIENT`, `STATUS_SERVER` - Status events
3119    /// - `CONF_CHANGED` - Configuration change events
3120    /// - `NETWORK_LIVENESS` - Network liveness events
3121    /// - `CIRC_BW` - Circuit bandwidth events
3122    /// - `CONN_BW` - Connection bandwidth events
3123    /// - `HS_DESC` - Hidden service descriptor events
3124    ///
3125    /// # Errors
3126    ///
3127    /// Returns [`Error::Protocol`] if the event content is malformed.
3128    /// Unknown event types are returned as [`ParsedEvent::Unknown`]
3129    /// rather than causing an error.
3130    ///
3131    /// # Example
3132    ///
3133    /// ```rust,ignore
3134    /// use stem_rs::events::ParsedEvent;
3135    ///
3136    /// // Parse a bandwidth event
3137    /// let event = ParsedEvent::parse("BW", "100 200", None)?;
3138    ///
3139    /// // Parse a circuit event
3140    /// let event = ParsedEvent::parse("CIRC", "1 BUILT $ABC...=relay", None)?;
3141    ///
3142    /// // Unknown events are captured, not rejected
3143    /// let event = ParsedEvent::parse("FUTURE_EVENT", "data", None)?;
3144    /// assert!(matches!(event, ParsedEvent::Unknown { .. }));
3145    /// ```
3146    pub fn parse(event_type: &str, content: &str, lines: Option<&[String]>) -> Result<Self, Error> {
3147        match event_type.to_uppercase().as_str() {
3148            "BW" => Ok(ParsedEvent::Bandwidth(BandwidthEvent::parse(content)?)),
3149            "DEBUG" => Ok(ParsedEvent::Log(LogEvent::parse(Runlevel::Debug, content)?)),
3150            "INFO" => Ok(ParsedEvent::Log(LogEvent::parse(Runlevel::Info, content)?)),
3151            "NOTICE" => Ok(ParsedEvent::Log(LogEvent::parse(
3152                Runlevel::Notice,
3153                content,
3154            )?)),
3155            "WARN" => Ok(ParsedEvent::Log(LogEvent::parse(Runlevel::Warn, content)?)),
3156            "ERR" => Ok(ParsedEvent::Log(LogEvent::parse(Runlevel::Err, content)?)),
3157            "CIRC" => Ok(ParsedEvent::Circuit(CircuitEvent::parse(content)?)),
3158            "STREAM" => Ok(ParsedEvent::Stream(StreamEvent::parse(content)?)),
3159            "ORCONN" => Ok(ParsedEvent::OrConn(OrConnEvent::parse(content)?)),
3160            "ADDRMAP" => Ok(ParsedEvent::AddrMap(AddrMapEvent::parse(content)?)),
3161            "BUILDTIMEOUT_SET" => Ok(ParsedEvent::BuildTimeoutSet(BuildTimeoutSetEvent::parse(
3162                content,
3163            )?)),
3164            "GUARD" => Ok(ParsedEvent::Guard(GuardEvent::parse(content)?)),
3165            "NEWDESC" => Ok(ParsedEvent::NewDesc(NewDescEvent::parse(content)?)),
3166            "SIGNAL" => Ok(ParsedEvent::Signal(SignalEvent::parse(content)?)),
3167            "STATUS_GENERAL" => Ok(ParsedEvent::Status(StatusEvent::parse(
3168                StatusType::General,
3169                content,
3170            )?)),
3171            "STATUS_CLIENT" => Ok(ParsedEvent::Status(StatusEvent::parse(
3172                StatusType::Client,
3173                content,
3174            )?)),
3175            "STATUS_SERVER" => Ok(ParsedEvent::Status(StatusEvent::parse(
3176                StatusType::Server,
3177                content,
3178            )?)),
3179            "CONF_CHANGED" => {
3180                let lines = lines.unwrap_or(&[]);
3181                Ok(ParsedEvent::ConfChanged(ConfChangedEvent::parse(lines)?))
3182            }
3183            "NETWORK_LIVENESS" => Ok(ParsedEvent::NetworkLiveness(NetworkLivenessEvent::parse(
3184                content,
3185            )?)),
3186            "CIRC_BW" => Ok(ParsedEvent::CircuitBandwidth(CircuitBandwidthEvent::parse(
3187                content,
3188            )?)),
3189            "CONN_BW" => Ok(ParsedEvent::ConnectionBandwidth(
3190                ConnectionBandwidthEvent::parse(content)?,
3191            )),
3192            "HS_DESC" => Ok(ParsedEvent::HsDesc(HsDescEvent::parse(content)?)),
3193            _ => Ok(ParsedEvent::Unknown {
3194                event_type: event_type.to_string(),
3195                content: content.to_string(),
3196            }),
3197        }
3198    }
3199
3200    /// Returns the event type string for this event.
3201    ///
3202    /// This returns the canonical event type keyword as used in
3203    /// `SETEVENTS` commands and event responses.
3204    ///
3205    /// # Example
3206    ///
3207    /// ```rust,ignore
3208    /// let event = ParsedEvent::parse("BW", "100 200", None)?;
3209    /// assert_eq!(event.event_type(), "BW");
3210    /// ```
3211    pub fn event_type(&self) -> &str {
3212        match self {
3213            ParsedEvent::Bandwidth(_) => "BW",
3214            ParsedEvent::Log(e) => match e.runlevel {
3215                Runlevel::Debug => "DEBUG",
3216                Runlevel::Info => "INFO",
3217                Runlevel::Notice => "NOTICE",
3218                Runlevel::Warn => "WARN",
3219                Runlevel::Err => "ERR",
3220            },
3221            ParsedEvent::Circuit(_) => "CIRC",
3222            ParsedEvent::Stream(_) => "STREAM",
3223            ParsedEvent::OrConn(_) => "ORCONN",
3224            ParsedEvent::AddrMap(_) => "ADDRMAP",
3225            ParsedEvent::BuildTimeoutSet(_) => "BUILDTIMEOUT_SET",
3226            ParsedEvent::Guard(_) => "GUARD",
3227            ParsedEvent::NewDesc(_) => "NEWDESC",
3228            ParsedEvent::Signal(_) => "SIGNAL",
3229            ParsedEvent::Status(e) => match e.status_type {
3230                StatusType::General => "STATUS_GENERAL",
3231                StatusType::Client => "STATUS_CLIENT",
3232                StatusType::Server => "STATUS_SERVER",
3233            },
3234            ParsedEvent::ConfChanged(_) => "CONF_CHANGED",
3235            ParsedEvent::NetworkLiveness(_) => "NETWORK_LIVENESS",
3236            ParsedEvent::CircuitBandwidth(_) => "CIRC_BW",
3237            ParsedEvent::ConnectionBandwidth(_) => "CONN_BW",
3238            ParsedEvent::HsDesc(_) => "HS_DESC",
3239            ParsedEvent::Unknown { event_type, .. } => event_type,
3240        }
3241    }
3242}
3243
3244impl std::fmt::Display for ParsedEvent {
3245    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3246        match self {
3247            ParsedEvent::Bandwidth(e) => write!(f, "650 BW {} {}", e.read, e.written),
3248            ParsedEvent::Log(e) => write!(f, "650 {} {}", e.runlevel, e.message),
3249            ParsedEvent::Circuit(e) => write!(f, "650 CIRC {} {}", e.id, e.status),
3250            ParsedEvent::Stream(e) => write!(f, "650 STREAM {} {}", e.id, e.status),
3251            ParsedEvent::OrConn(e) => write!(f, "650 ORCONN {} {}", e.target, e.status),
3252            ParsedEvent::AddrMap(e) => {
3253                write!(
3254                    f,
3255                    "650 ADDRMAP {} {}",
3256                    e.hostname,
3257                    e.destination.as_deref().unwrap_or("<error>")
3258                )
3259            }
3260            ParsedEvent::BuildTimeoutSet(e) => write!(f, "650 BUILDTIMEOUT_SET {:?}", e.set_type),
3261            ParsedEvent::Guard(e) => {
3262                write!(f, "650 GUARD {} {} {}", e.guard_type, e.endpoint, e.status)
3263            }
3264            ParsedEvent::NewDesc(e) => {
3265                let relays: Vec<String> = e
3266                    .relays
3267                    .iter()
3268                    .map(|(fp, nick)| match nick {
3269                        Some(n) => format!("{}~{}", fp, n),
3270                        None => fp.clone(),
3271                    })
3272                    .collect();
3273                write!(f, "650 NEWDESC {}", relays.join(" "))
3274            }
3275            ParsedEvent::Signal(e) => write!(f, "650 SIGNAL {}", e.signal),
3276            ParsedEvent::Status(e) => write!(
3277                f,
3278                "650 STATUS_{} {} {}",
3279                e.status_type, e.runlevel, e.action
3280            ),
3281            ParsedEvent::ConfChanged(e) => {
3282                let changes: Vec<String> = e
3283                    .changed
3284                    .iter()
3285                    .map(|(k, v)| format!("{}={}", k, v.join(",")))
3286                    .collect();
3287                write!(f, "650 CONF_CHANGED {}", changes.join(" "))
3288            }
3289            ParsedEvent::NetworkLiveness(e) => write!(f, "650 NETWORK_LIVENESS {}", e.status),
3290            ParsedEvent::CircuitBandwidth(e) => {
3291                write!(f, "650 CIRC_BW {} {} {}", e.id, e.read, e.written)
3292            }
3293            ParsedEvent::ConnectionBandwidth(e) => write!(
3294                f,
3295                "650 CONN_BW {} {} {} {}",
3296                e.id, e.conn_type, e.read, e.written
3297            ),
3298            ParsedEvent::HsDesc(e) => write!(f, "650 HS_DESC {} {}", e.action, e.address),
3299            ParsedEvent::Unknown {
3300                event_type,
3301                content,
3302            } => write!(f, "650 {} {}", event_type, content),
3303        }
3304    }
3305}
3306
3307#[cfg(test)]
3308mod tests {
3309    use super::*;
3310    use chrono::{Datelike, Timelike};
3311
3312    #[test]
3313    fn test_bandwidth_event() {
3314        let event = BandwidthEvent::parse("15 25").unwrap();
3315        assert_eq!(event.read, 15);
3316        assert_eq!(event.written, 25);
3317    }
3318
3319    #[test]
3320    fn test_bandwidth_event_zero() {
3321        let event = BandwidthEvent::parse("0 0").unwrap();
3322        assert_eq!(event.read, 0);
3323        assert_eq!(event.written, 0);
3324    }
3325
3326    #[test]
3327    fn test_bandwidth_event_invalid_missing_values() {
3328        assert!(BandwidthEvent::parse("").is_err());
3329        assert!(BandwidthEvent::parse("15").is_err());
3330    }
3331
3332    #[test]
3333    fn test_bandwidth_event_invalid_non_numeric() {
3334        assert!(BandwidthEvent::parse("x 25").is_err());
3335        assert!(BandwidthEvent::parse("15 y").is_err());
3336    }
3337
3338    #[test]
3339    fn test_log_event() {
3340        let event = LogEvent::parse(Runlevel::Debug, "test message").unwrap();
3341        assert_eq!(event.runlevel, Runlevel::Debug);
3342        assert_eq!(event.message, "test message");
3343    }
3344
3345    #[test]
3346    fn test_log_event_debug() {
3347        let event = LogEvent::parse(
3348            Runlevel::Debug,
3349            "connection_edge_process_relay_cell(): Got an extended cell! Yay.",
3350        )
3351        .unwrap();
3352        assert_eq!(event.runlevel, Runlevel::Debug);
3353        assert_eq!(
3354            event.message,
3355            "connection_edge_process_relay_cell(): Got an extended cell! Yay."
3356        );
3357    }
3358
3359    #[test]
3360    fn test_log_event_info() {
3361        let event = LogEvent::parse(
3362            Runlevel::Info,
3363            "circuit_finish_handshake(): Finished building circuit hop:",
3364        )
3365        .unwrap();
3366        assert_eq!(event.runlevel, Runlevel::Info);
3367    }
3368
3369    #[test]
3370    fn test_log_event_warn() {
3371        let event = LogEvent::parse(Runlevel::Warn, "a multi-line\nwarning message").unwrap();
3372        assert_eq!(event.runlevel, Runlevel::Warn);
3373        assert_eq!(event.message, "a multi-line\nwarning message");
3374    }
3375
3376    #[test]
3377    fn test_circuit_event_launched() {
3378        let content = "7 LAUNCHED BUILD_FLAGS=NEED_CAPACITY PURPOSE=GENERAL TIME_CREATED=2012-11-08T16:48:38.417238";
3379        let event = CircuitEvent::parse(content).unwrap();
3380        assert_eq!(event.id.0, "7");
3381        assert_eq!(event.status, CircStatus::Launched);
3382        assert!(event.path.is_empty());
3383        assert_eq!(event.build_flags, Some(vec![CircBuildFlag::NeedCapacity]));
3384        assert_eq!(event.purpose, Some(CircPurpose::General));
3385        assert!(event.created.is_some());
3386        assert_eq!(event.reason, None);
3387        assert_eq!(event.remote_reason, None);
3388        assert_eq!(event.socks_username, None);
3389        assert_eq!(event.socks_password, None);
3390    }
3391
3392    #[test]
3393    fn test_circuit_event_extended() {
3394        let content = "7 EXTENDED $999A226EBED397F331B612FE1E4CFAE5C1F201BA=piyaz BUILD_FLAGS=NEED_CAPACITY PURPOSE=GENERAL";
3395        let event = CircuitEvent::parse(content).unwrap();
3396        assert_eq!(event.id.0, "7");
3397        assert_eq!(event.status, CircStatus::Extended);
3398        assert_eq!(event.path.len(), 1);
3399        assert_eq!(event.path[0].0, "999A226EBED397F331B612FE1E4CFAE5C1F201BA");
3400        assert_eq!(event.path[0].1, Some("piyaz".to_string()));
3401    }
3402
3403    #[test]
3404    fn test_circuit_event_failed() {
3405        let content = "5 FAILED $E57A476CD4DFBD99B4EE52A100A58610AD6E80B9=ergebnisoffen BUILD_FLAGS=NEED_CAPACITY PURPOSE=GENERAL REASON=DESTROYED REMOTE_REASON=OR_CONN_CLOSED";
3406        let event = CircuitEvent::parse(content).unwrap();
3407        assert_eq!(event.id.0, "5");
3408        assert_eq!(event.status, CircStatus::Failed);
3409        assert_eq!(event.reason, Some(CircClosureReason::Destroyed));
3410        assert_eq!(event.remote_reason, Some(CircClosureReason::OrConnClosed));
3411    }
3412
3413    #[test]
3414    fn test_circuit_event_with_credentials() {
3415        let content = r#"7 LAUNCHED SOCKS_USERNAME="It's a me, Mario!" SOCKS_PASSWORD="your princess is in another castle""#;
3416        let event = CircuitEvent::parse(content).unwrap();
3417        assert_eq!(event.id.0, "7");
3418        assert_eq!(event.status, CircStatus::Launched);
3419        assert_eq!(event.socks_username, Some("It's a me, Mario!".to_string()));
3420        assert_eq!(
3421            event.socks_password,
3422            Some("your princess is in another castle".to_string())
3423        );
3424    }
3425
3426    #[test]
3427    fn test_circuit_event_launched_old_format() {
3428        let content = "4 LAUNCHED";
3429        let event = CircuitEvent::parse(content).unwrap();
3430        assert_eq!(event.id.0, "4");
3431        assert_eq!(event.status, CircStatus::Launched);
3432        assert!(event.path.is_empty());
3433        assert_eq!(event.build_flags, None);
3434        assert_eq!(event.purpose, None);
3435    }
3436
3437    #[test]
3438    fn test_circuit_event_extended_old_format() {
3439        let content = "$E57A476CD4DFBD99B4EE52A100A58610AD6E80B9,hamburgerphone";
3440        let event = CircuitEvent::parse(&format!("1 EXTENDED {}", content)).unwrap();
3441        assert_eq!(event.id.0, "1");
3442        assert_eq!(event.status, CircStatus::Extended);
3443    }
3444
3445    #[test]
3446    fn test_circuit_event_built_old_format() {
3447        let content =
3448            "1 BUILT $E57A476CD4DFBD99B4EE52A100A58610AD6E80B9,hamburgerphone,PrivacyRepublic14";
3449        let event = CircuitEvent::parse(content).unwrap();
3450        assert_eq!(event.id.0, "1");
3451        assert_eq!(event.status, CircStatus::Built);
3452    }
3453
3454    #[test]
3455    fn test_stream_event_new() {
3456        let content = "18 NEW 0 encrypted.google.com:443 SOURCE_ADDR=127.0.0.1:47849 PURPOSE=USER";
3457        let event = StreamEvent::parse(content).unwrap();
3458        assert_eq!(event.id.0, "18");
3459        assert_eq!(event.status, StreamStatus::New);
3460        assert_eq!(event.circuit_id, None);
3461        assert_eq!(event.target_host, "encrypted.google.com");
3462        assert_eq!(event.target_port, 443);
3463        assert_eq!(event.source_addr, Some("127.0.0.1:47849".to_string()));
3464        assert_eq!(event.purpose, Some(StreamPurpose::User));
3465    }
3466
3467    #[test]
3468    fn test_stream_event_sentconnect() {
3469        let content = "18 SENTCONNECT 26 encrypted.google.com:443";
3470        let event = StreamEvent::parse(content).unwrap();
3471        assert_eq!(event.id.0, "18");
3472        assert_eq!(event.status, StreamStatus::SentConnect);
3473        assert_eq!(event.circuit_id, Some(CircuitId::new("26")));
3474        assert_eq!(event.target_host, "encrypted.google.com");
3475        assert_eq!(event.target_port, 443);
3476        assert_eq!(event.source_addr, None);
3477        assert_eq!(event.purpose, None);
3478    }
3479
3480    #[test]
3481    fn test_stream_event_remap() {
3482        let content = "18 REMAP 26 74.125.227.129:443 SOURCE=EXIT";
3483        let event = StreamEvent::parse(content).unwrap();
3484        assert_eq!(event.id.0, "18");
3485        assert_eq!(event.status, StreamStatus::Remap);
3486        assert_eq!(event.circuit_id, Some(CircuitId::new("26")));
3487        assert_eq!(event.target_host, "74.125.227.129");
3488        assert_eq!(event.target_port, 443);
3489        assert_eq!(event.source, Some(StreamSource::Exit));
3490    }
3491
3492    #[test]
3493    fn test_stream_event_succeeded() {
3494        let content = "18 SUCCEEDED 26 74.125.227.129:443";
3495        let event = StreamEvent::parse(content).unwrap();
3496        assert_eq!(event.id.0, "18");
3497        assert_eq!(event.status, StreamStatus::Succeeded);
3498        assert_eq!(event.circuit_id, Some(CircuitId::new("26")));
3499        assert_eq!(event.target_host, "74.125.227.129");
3500        assert_eq!(event.target_port, 443);
3501    }
3502
3503    #[test]
3504    fn test_stream_event_closed() {
3505        let content = "21 CLOSED 26 74.125.227.129:443 REASON=CONNRESET";
3506        let event = StreamEvent::parse(content).unwrap();
3507        assert_eq!(event.status, StreamStatus::Closed);
3508        assert_eq!(event.reason, Some(StreamClosureReason::ConnReset));
3509    }
3510
3511    #[test]
3512    fn test_stream_event_closed_done() {
3513        let content = "25 CLOSED 26 199.7.52.72:80 REASON=DONE";
3514        let event = StreamEvent::parse(content).unwrap();
3515        assert_eq!(event.id.0, "25");
3516        assert_eq!(event.status, StreamStatus::Closed);
3517        assert_eq!(event.reason, Some(StreamClosureReason::Done));
3518    }
3519
3520    #[test]
3521    fn test_stream_event_dir_fetch() {
3522        let content = "14 NEW 0 176.28.51.238.$649F2D0ACF418F7CFC6539AB2257EB2D5297BAFA.exit:443 SOURCE_ADDR=(Tor_internal):0 PURPOSE=DIR_FETCH";
3523        let event = StreamEvent::parse(content).unwrap();
3524        assert_eq!(event.id.0, "14");
3525        assert_eq!(event.status, StreamStatus::New);
3526        assert_eq!(event.circuit_id, None);
3527        assert_eq!(
3528            event.target_host,
3529            "176.28.51.238.$649F2D0ACF418F7CFC6539AB2257EB2D5297BAFA.exit"
3530        );
3531        assert_eq!(event.target_port, 443);
3532        assert_eq!(event.source_addr, Some("(Tor_internal):0".to_string()));
3533        assert_eq!(event.purpose, Some(StreamPurpose::DirFetch));
3534    }
3535
3536    #[test]
3537    fn test_stream_event_dns_request() {
3538        let content = "1113 NEW 0 www.google.com:0 SOURCE_ADDR=127.0.0.1:15297 PURPOSE=DNS_REQUEST";
3539        let event = StreamEvent::parse(content).unwrap();
3540        assert_eq!(event.id.0, "1113");
3541        assert_eq!(event.status, StreamStatus::New);
3542        assert_eq!(event.target_host, "www.google.com");
3543        assert_eq!(event.target_port, 0);
3544        assert_eq!(event.purpose, Some(StreamPurpose::DnsRequest));
3545    }
3546
3547    #[test]
3548    fn test_orconn_event_closed() {
3549        let content = "$A1130635A0CDA6F60C276FBF6994EFBD4ECADAB1~tama CLOSED REASON=DONE";
3550        let event = OrConnEvent::parse(content).unwrap();
3551        assert_eq!(
3552            event.target,
3553            "$A1130635A0CDA6F60C276FBF6994EFBD4ECADAB1~tama"
3554        );
3555        assert_eq!(event.status, OrStatus::Closed);
3556        assert_eq!(event.reason, Some(OrClosureReason::Done));
3557        assert_eq!(event.num_circuits, None);
3558        assert_eq!(event.id, None);
3559    }
3560
3561    #[test]
3562    fn test_orconn_event_connected() {
3563        let content = "127.0.0.1:9000 CONNECTED NCIRCS=20 ID=18";
3564        let event = OrConnEvent::parse(content).unwrap();
3565        assert_eq!(event.target, "127.0.0.1:9000");
3566        assert_eq!(event.status, OrStatus::Connected);
3567        assert_eq!(event.num_circuits, Some(20));
3568        assert_eq!(event.id, Some("18".to_string()));
3569        assert_eq!(event.reason, None);
3570    }
3571
3572    #[test]
3573    fn test_orconn_event_launched() {
3574        let content = "$7ED90E2833EE38A75795BA9237B0A4560E51E1A0=GreenDragon LAUNCHED";
3575        let event = OrConnEvent::parse(content).unwrap();
3576        assert_eq!(
3577            event.target,
3578            "$7ED90E2833EE38A75795BA9237B0A4560E51E1A0=GreenDragon"
3579        );
3580        assert_eq!(event.status, OrStatus::Launched);
3581        assert_eq!(event.reason, None);
3582        assert_eq!(event.num_circuits, None);
3583    }
3584
3585    #[test]
3586    fn test_addrmap_event() {
3587        let content =
3588            r#"www.atagar.com 75.119.206.243 "2012-11-19 00:50:13" EXPIRES="2012-11-19 08:50:13""#;
3589        let event = AddrMapEvent::parse(content).unwrap();
3590        assert_eq!(event.hostname, "www.atagar.com");
3591        assert_eq!(event.destination, Some("75.119.206.243".to_string()));
3592        assert!(event.expiry.is_some());
3593        assert_eq!(event.error, None);
3594        assert!(event.utc_expiry.is_some());
3595    }
3596
3597    #[test]
3598    fn test_addrmap_event_no_expiration() {
3599        let content = "www.atagar.com 75.119.206.243 NEVER";
3600        let event = AddrMapEvent::parse(content).unwrap();
3601        assert_eq!(event.hostname, "www.atagar.com");
3602        assert_eq!(event.destination, Some("75.119.206.243".to_string()));
3603        assert_eq!(event.expiry, None);
3604        assert_eq!(event.utc_expiry, None);
3605    }
3606
3607    #[test]
3608    fn test_addrmap_event_error() {
3609        let content = r#"www.atagar.com <error> "2012-11-19 00:50:13" error=yes EXPIRES="2012-11-19 08:50:13""#;
3610        let event = AddrMapEvent::parse(content).unwrap();
3611        assert_eq!(event.hostname, "www.atagar.com");
3612        assert_eq!(event.destination, None);
3613        assert_eq!(event.error, Some("yes".to_string()));
3614    }
3615
3616    #[test]
3617    fn test_addrmap_event_cached_yes() {
3618        let content = r#"example.com 192.0.43.10 "2013-04-03 22:31:22" EXPIRES="2013-04-03 20:31:22" CACHED="YES""#;
3619        let event = AddrMapEvent::parse(content).unwrap();
3620        assert_eq!(event.hostname, "example.com");
3621        assert_eq!(event.cached, Some(true));
3622    }
3623
3624    #[test]
3625    fn test_addrmap_event_cached_no() {
3626        let content = r#"example.com 192.0.43.10 "2013-04-03 22:29:11" EXPIRES="2013-04-03 20:29:11" CACHED="NO""#;
3627        let event = AddrMapEvent::parse(content).unwrap();
3628        assert_eq!(event.hostname, "example.com");
3629        assert_eq!(event.cached, Some(false));
3630    }
3631
3632    #[test]
3633    fn test_build_timeout_set_event() {
3634        let content = "COMPUTED TOTAL_TIMES=124 TIMEOUT_MS=9019 XM=1375 ALPHA=0.855662 CUTOFF_QUANTILE=0.800000 TIMEOUT_RATE=0.137097 CLOSE_MS=21850 CLOSE_RATE=0.072581";
3635        let event = BuildTimeoutSetEvent::parse(content).unwrap();
3636        assert_eq!(event.set_type, TimeoutSetType::Computed);
3637        assert_eq!(event.total_times, Some(124));
3638        assert_eq!(event.timeout, Some(9019));
3639        assert_eq!(event.xm, Some(1375));
3640        assert!((event.alpha.unwrap() - 0.855662).abs() < 0.0001);
3641        assert!((event.quantile.unwrap() - 0.8).abs() < 0.0001);
3642        assert!((event.timeout_rate.unwrap() - 0.137097).abs() < 0.0001);
3643        assert_eq!(event.close_timeout, Some(21850));
3644        assert!((event.close_rate.unwrap() - 0.072581).abs() < 0.0001);
3645    }
3646
3647    #[test]
3648    fn test_build_timeout_set_event_invalid_total_times() {
3649        let content = "COMPUTED TOTAL_TIMES=one_twenty_four TIMEOUT_MS=9019";
3650        assert!(BuildTimeoutSetEvent::parse(content).is_err());
3651    }
3652
3653    #[test]
3654    fn test_build_timeout_set_event_invalid_quantile() {
3655        let content = "COMPUTED TOTAL_TIMES=124 CUTOFF_QUANTILE=zero_point_eight";
3656        assert!(BuildTimeoutSetEvent::parse(content).is_err());
3657    }
3658
3659    #[test]
3660    fn test_guard_event_new() {
3661        let content = "ENTRY $36B5DBA788246E8369DBAF58577C6BC044A9A374 NEW";
3662        let event = GuardEvent::parse(content).unwrap();
3663        assert_eq!(event.guard_type, GuardType::Entry);
3664        assert_eq!(event.endpoint, "$36B5DBA788246E8369DBAF58577C6BC044A9A374");
3665        assert_eq!(
3666            event.endpoint_fingerprint,
3667            "36B5DBA788246E8369DBAF58577C6BC044A9A374"
3668        );
3669        assert_eq!(event.endpoint_nickname, None);
3670        assert_eq!(event.status, GuardStatus::New);
3671    }
3672
3673    #[test]
3674    fn test_guard_event_good() {
3675        let content = "ENTRY $5D0034A368E0ABAF663D21847E1C9B6CFA09752A GOOD";
3676        let event = GuardEvent::parse(content).unwrap();
3677        assert_eq!(event.guard_type, GuardType::Entry);
3678        assert_eq!(
3679            event.endpoint_fingerprint,
3680            "5D0034A368E0ABAF663D21847E1C9B6CFA09752A"
3681        );
3682        assert_eq!(event.endpoint_nickname, None);
3683        assert_eq!(event.status, GuardStatus::Good);
3684    }
3685
3686    #[test]
3687    fn test_guard_event_bad() {
3688        let content = "ENTRY $5D0034A368E0ABAF663D21847E1C9B6CFA09752A=caerSidi BAD";
3689        let event = GuardEvent::parse(content).unwrap();
3690        assert_eq!(
3691            event.endpoint_fingerprint,
3692            "5D0034A368E0ABAF663D21847E1C9B6CFA09752A"
3693        );
3694        assert_eq!(event.endpoint_nickname, Some("caerSidi".to_string()));
3695        assert_eq!(event.status, GuardStatus::Bad);
3696    }
3697
3698    #[test]
3699    fn test_newdesc_event_single() {
3700        let content = "$B3FA3110CC6F42443F039220C134CBD2FC4F0493=Sakura";
3701        let event = NewDescEvent::parse(content).unwrap();
3702        assert_eq!(event.relays.len(), 1);
3703        assert_eq!(
3704            event.relays[0].0,
3705            "B3FA3110CC6F42443F039220C134CBD2FC4F0493"
3706        );
3707        assert_eq!(event.relays[0].1, Some("Sakura".to_string()));
3708    }
3709
3710    #[test]
3711    fn test_newdesc_event_multiple() {
3712        let content = "$BE938957B2CA5F804B3AFC2C1EE6673170CDBBF8=Moonshine $B4BE08B22D4D2923EDC3970FD1B93D0448C6D8FF~Unnamed";
3713        let event = NewDescEvent::parse(content).unwrap();
3714        assert_eq!(event.relays.len(), 2);
3715        assert_eq!(
3716            event.relays[0].0,
3717            "BE938957B2CA5F804B3AFC2C1EE6673170CDBBF8"
3718        );
3719        assert_eq!(event.relays[0].1, Some("Moonshine".to_string()));
3720        assert_eq!(
3721            event.relays[1].0,
3722            "B4BE08B22D4D2923EDC3970FD1B93D0448C6D8FF"
3723        );
3724        assert_eq!(event.relays[1].1, Some("Unnamed".to_string()));
3725    }
3726
3727    #[test]
3728    fn test_signal_event() {
3729        let event = SignalEvent::parse("DEBUG").unwrap();
3730        assert_eq!(event.signal, Signal::Debug);
3731
3732        let event = SignalEvent::parse("DUMP").unwrap();
3733        assert_eq!(event.signal, Signal::Dump);
3734    }
3735
3736    #[test]
3737    fn test_signal_event_all_signals() {
3738        assert_eq!(SignalEvent::parse("RELOAD").unwrap().signal, Signal::Reload);
3739        assert_eq!(SignalEvent::parse("HUP").unwrap().signal, Signal::Reload);
3740        assert_eq!(
3741            SignalEvent::parse("SHUTDOWN").unwrap().signal,
3742            Signal::Shutdown
3743        );
3744        assert_eq!(SignalEvent::parse("INT").unwrap().signal, Signal::Shutdown);
3745        assert_eq!(SignalEvent::parse("DUMP").unwrap().signal, Signal::Dump);
3746        assert_eq!(SignalEvent::parse("USR1").unwrap().signal, Signal::Dump);
3747        assert_eq!(SignalEvent::parse("DEBUG").unwrap().signal, Signal::Debug);
3748        assert_eq!(SignalEvent::parse("USR2").unwrap().signal, Signal::Debug);
3749        assert_eq!(SignalEvent::parse("HALT").unwrap().signal, Signal::Halt);
3750        assert_eq!(SignalEvent::parse("TERM").unwrap().signal, Signal::Halt);
3751        assert_eq!(SignalEvent::parse("NEWNYM").unwrap().signal, Signal::Newnym);
3752        assert_eq!(
3753            SignalEvent::parse("CLEARDNSCACHE").unwrap().signal,
3754            Signal::ClearDnsCache
3755        );
3756        assert_eq!(
3757            SignalEvent::parse("HEARTBEAT").unwrap().signal,
3758            Signal::Heartbeat
3759        );
3760        assert_eq!(SignalEvent::parse("ACTIVE").unwrap().signal, Signal::Active);
3761        assert_eq!(
3762            SignalEvent::parse("DORMANT").unwrap().signal,
3763            Signal::Dormant
3764        );
3765    }
3766
3767    #[test]
3768    fn test_status_event() {
3769        let content = "NOTICE CONSENSUS_ARRIVED";
3770        let event = StatusEvent::parse(StatusType::General, content).unwrap();
3771        assert_eq!(event.status_type, StatusType::General);
3772        assert_eq!(event.runlevel, Runlevel::Notice);
3773        assert_eq!(event.action, "CONSENSUS_ARRIVED");
3774    }
3775
3776    #[test]
3777    fn test_status_event_enough_dir_info() {
3778        let content = "NOTICE ENOUGH_DIR_INFO";
3779        let event = StatusEvent::parse(StatusType::Client, content).unwrap();
3780        assert_eq!(event.status_type, StatusType::Client);
3781        assert_eq!(event.runlevel, Runlevel::Notice);
3782        assert_eq!(event.action, "ENOUGH_DIR_INFO");
3783    }
3784
3785    #[test]
3786    fn test_status_event_circuit_established() {
3787        let content = "NOTICE CIRCUIT_ESTABLISHED";
3788        let event = StatusEvent::parse(StatusType::Client, content).unwrap();
3789        assert_eq!(event.status_type, StatusType::Client);
3790        assert_eq!(event.runlevel, Runlevel::Notice);
3791        assert_eq!(event.action, "CIRCUIT_ESTABLISHED");
3792    }
3793
3794    #[test]
3795    fn test_status_event_with_args() {
3796        let content = "NOTICE BOOTSTRAP PROGRESS=53 TAG=loading_descriptors SUMMARY=\"Loading relay descriptors\"";
3797        let event = StatusEvent::parse(StatusType::Client, content).unwrap();
3798        assert_eq!(event.status_type, StatusType::Client);
3799        assert_eq!(event.action, "BOOTSTRAP");
3800        assert_eq!(event.arguments.get("PROGRESS"), Some(&"53".to_string()));
3801        assert_eq!(
3802            event.arguments.get("TAG"),
3803            Some(&"loading_descriptors".to_string())
3804        );
3805        assert_eq!(
3806            event.arguments.get("SUMMARY"),
3807            Some(&"Loading relay descriptors".to_string())
3808        );
3809    }
3810
3811    #[test]
3812    fn test_status_event_bootstrap_stuck() {
3813        let content = "WARN BOOTSTRAP PROGRESS=80 TAG=conn_or SUMMARY=\"Connecting to the Tor network\" WARNING=\"Network is unreachable\" REASON=NOROUTE COUNT=5 RECOMMENDATION=warn";
3814        let event = StatusEvent::parse(StatusType::Client, content).unwrap();
3815        assert_eq!(event.status_type, StatusType::Client);
3816        assert_eq!(event.runlevel, Runlevel::Warn);
3817        assert_eq!(event.action, "BOOTSTRAP");
3818        assert_eq!(event.arguments.get("PROGRESS"), Some(&"80".to_string()));
3819        assert_eq!(event.arguments.get("TAG"), Some(&"conn_or".to_string()));
3820        assert_eq!(
3821            event.arguments.get("WARNING"),
3822            Some(&"Network is unreachable".to_string())
3823        );
3824        assert_eq!(event.arguments.get("REASON"), Some(&"NOROUTE".to_string()));
3825        assert_eq!(event.arguments.get("COUNT"), Some(&"5".to_string()));
3826        assert_eq!(
3827            event.arguments.get("RECOMMENDATION"),
3828            Some(&"warn".to_string())
3829        );
3830    }
3831
3832    #[test]
3833    fn test_status_event_bootstrap_done() {
3834        let content = "NOTICE BOOTSTRAP PROGRESS=100 TAG=done SUMMARY=\"Done\"";
3835        let event = StatusEvent::parse(StatusType::Client, content).unwrap();
3836        assert_eq!(event.arguments.get("PROGRESS"), Some(&"100".to_string()));
3837        assert_eq!(event.arguments.get("TAG"), Some(&"done".to_string()));
3838        assert_eq!(event.arguments.get("SUMMARY"), Some(&"Done".to_string()));
3839    }
3840
3841    #[test]
3842    fn test_status_event_server_check_reachability() {
3843        let content = "NOTICE CHECKING_REACHABILITY ORADDRESS=71.35.143.230:9050";
3844        let event = StatusEvent::parse(StatusType::Server, content).unwrap();
3845        assert_eq!(event.status_type, StatusType::Server);
3846        assert_eq!(event.runlevel, Runlevel::Notice);
3847        assert_eq!(event.action, "CHECKING_REACHABILITY");
3848        assert_eq!(
3849            event.arguments.get("ORADDRESS"),
3850            Some(&"71.35.143.230:9050".to_string())
3851        );
3852    }
3853
3854    #[test]
3855    fn test_status_event_dns_timeout() {
3856        let content =
3857            "NOTICE NAMESERVER_STATUS NS=205.171.3.25 STATUS=DOWN ERR=\"request timed out.\"";
3858        let event = StatusEvent::parse(StatusType::Server, content).unwrap();
3859        assert_eq!(event.action, "NAMESERVER_STATUS");
3860        assert_eq!(event.arguments.get("NS"), Some(&"205.171.3.25".to_string()));
3861        assert_eq!(event.arguments.get("STATUS"), Some(&"DOWN".to_string()));
3862        assert_eq!(
3863            event.arguments.get("ERR"),
3864            Some(&"request timed out.".to_string())
3865        );
3866    }
3867
3868    #[test]
3869    fn test_status_event_dns_down() {
3870        let content = "WARN NAMESERVER_ALL_DOWN";
3871        let event = StatusEvent::parse(StatusType::Server, content).unwrap();
3872        assert_eq!(event.status_type, StatusType::Server);
3873        assert_eq!(event.runlevel, Runlevel::Warn);
3874        assert_eq!(event.action, "NAMESERVER_ALL_DOWN");
3875    }
3876
3877    #[test]
3878    fn test_status_event_dns_up() {
3879        let content = "NOTICE NAMESERVER_STATUS NS=205.171.3.25 STATUS=UP";
3880        let event = StatusEvent::parse(StatusType::Server, content).unwrap();
3881        assert_eq!(event.action, "NAMESERVER_STATUS");
3882        assert_eq!(event.arguments.get("STATUS"), Some(&"UP".to_string()));
3883    }
3884
3885    #[test]
3886    fn test_conf_changed_event() {
3887        let lines = vec![
3888            "ExitNodes=caerSidi".to_string(),
3889            "ExitPolicy".to_string(),
3890            "MaxCircuitDirtiness=20".to_string(),
3891        ];
3892        let event = ConfChangedEvent::parse(&lines).unwrap();
3893        assert_eq!(
3894            event.changed.get("ExitNodes"),
3895            Some(&vec!["caerSidi".to_string()])
3896        );
3897        assert_eq!(
3898            event.changed.get("MaxCircuitDirtiness"),
3899            Some(&vec!["20".to_string()])
3900        );
3901        assert_eq!(event.unset, vec!["ExitPolicy".to_string()]);
3902    }
3903
3904    #[test]
3905    fn test_conf_changed_event_multiple_values() {
3906        let lines = vec![
3907            "ExitPolicy=accept 34.3.4.5".to_string(),
3908            "ExitPolicy=accept 3.4.53.3".to_string(),
3909            "MaxCircuitDirtiness=20".to_string(),
3910        ];
3911        let event = ConfChangedEvent::parse(&lines).unwrap();
3912        assert_eq!(
3913            event.changed.get("ExitPolicy"),
3914            Some(&vec![
3915                "accept 34.3.4.5".to_string(),
3916                "accept 3.4.53.3".to_string()
3917            ])
3918        );
3919        assert_eq!(
3920            event.changed.get("MaxCircuitDirtiness"),
3921            Some(&vec!["20".to_string()])
3922        );
3923        assert!(event.unset.is_empty());
3924    }
3925
3926    #[test]
3927    fn test_network_liveness_event() {
3928        let event = NetworkLivenessEvent::parse("UP").unwrap();
3929        assert_eq!(event.status, "UP");
3930
3931        let event = NetworkLivenessEvent::parse("DOWN").unwrap();
3932        assert_eq!(event.status, "DOWN");
3933    }
3934
3935    #[test]
3936    fn test_network_liveness_event_other_status() {
3937        let event = NetworkLivenessEvent::parse("OTHER_STATUS key=value").unwrap();
3938        assert_eq!(event.status, "OTHER_STATUS");
3939    }
3940
3941    #[test]
3942    fn test_circuit_bandwidth_event() {
3943        let content = "ID=11 READ=272 WRITTEN=817";
3944        let event = CircuitBandwidthEvent::parse(content).unwrap();
3945        assert_eq!(event.id.0, "11");
3946        assert_eq!(event.read, 272);
3947        assert_eq!(event.written, 817);
3948        assert_eq!(event.time, None);
3949    }
3950
3951    #[test]
3952    fn test_circuit_bandwidth_event_with_time() {
3953        let content = "ID=11 READ=272 WRITTEN=817 TIME=2012-12-06T13:51:11.433755";
3954        let event = CircuitBandwidthEvent::parse(content).unwrap();
3955        assert_eq!(event.id.0, "11");
3956        assert!(event.time.is_some());
3957    }
3958
3959    #[test]
3960    fn test_circuit_bandwidth_event_invalid_written() {
3961        let content = "ID=11 READ=272 WRITTEN=817.7";
3962        assert!(CircuitBandwidthEvent::parse(content).is_err());
3963    }
3964
3965    #[test]
3966    fn test_circuit_bandwidth_event_missing_id() {
3967        let content = "READ=272 WRITTEN=817";
3968        assert!(CircuitBandwidthEvent::parse(content).is_err());
3969    }
3970
3971    #[test]
3972    fn test_connection_bandwidth_event() {
3973        let content = "ID=11 TYPE=DIR READ=272 WRITTEN=817";
3974        let event = ConnectionBandwidthEvent::parse(content).unwrap();
3975        assert_eq!(event.id, "11");
3976        assert_eq!(event.conn_type, ConnectionType::Dir);
3977        assert_eq!(event.read, 272);
3978        assert_eq!(event.written, 817);
3979    }
3980
3981    #[test]
3982    fn test_connection_bandwidth_event_invalid_written() {
3983        let content = "ID=11 TYPE=DIR READ=272 WRITTEN=817.7";
3984        assert!(ConnectionBandwidthEvent::parse(content).is_err());
3985    }
3986
3987    #[test]
3988    fn test_connection_bandwidth_event_missing_id() {
3989        let content = "TYPE=DIR READ=272 WRITTEN=817";
3990        assert!(ConnectionBandwidthEvent::parse(content).is_err());
3991    }
3992
3993    #[test]
3994    fn test_hs_desc_event() {
3995        let content = "REQUESTED ajhb7kljbiru65qo NO_AUTH $67B2BDA4264D8A189D9270E28B1D30A262838243=europa1 b3oeducbhjmbqmgw2i3jtz4fekkrinwj";
3996        let event = HsDescEvent::parse(content).unwrap();
3997        assert_eq!(event.action, HsDescAction::Requested);
3998        assert_eq!(event.address, "ajhb7kljbiru65qo");
3999        assert_eq!(event.authentication, Some(HsAuth::NoAuth));
4000        assert_eq!(
4001            event.directory,
4002            Some("$67B2BDA4264D8A189D9270E28B1D30A262838243=europa1".to_string())
4003        );
4004        assert_eq!(
4005            event.directory_fingerprint,
4006            Some("67B2BDA4264D8A189D9270E28B1D30A262838243".to_string())
4007        );
4008        assert_eq!(event.directory_nickname, Some("europa1".to_string()));
4009        assert_eq!(
4010            event.descriptor_id,
4011            Some("b3oeducbhjmbqmgw2i3jtz4fekkrinwj".to_string())
4012        );
4013        assert_eq!(event.reason, None);
4014    }
4015
4016    #[test]
4017    fn test_hs_desc_event_no_desc_id() {
4018        let content =
4019            "REQUESTED ajhb7kljbiru65qo NO_AUTH $67B2BDA4264D8A189D9270E28B1D30A262838243";
4020        let event = HsDescEvent::parse(content).unwrap();
4021        assert_eq!(
4022            event.directory,
4023            Some("$67B2BDA4264D8A189D9270E28B1D30A262838243".to_string())
4024        );
4025        assert_eq!(
4026            event.directory_fingerprint,
4027            Some("67B2BDA4264D8A189D9270E28B1D30A262838243".to_string())
4028        );
4029        assert_eq!(event.directory_nickname, None);
4030        assert_eq!(event.descriptor_id, None);
4031        assert_eq!(event.reason, None);
4032    }
4033
4034    #[test]
4035    fn test_hs_desc_event_not_found() {
4036        let content = "REQUESTED ajhb7kljbiru65qo NO_AUTH UNKNOWN";
4037        let event = HsDescEvent::parse(content).unwrap();
4038        assert_eq!(event.directory, None);
4039        assert_eq!(event.directory_fingerprint, None);
4040        assert_eq!(event.directory_nickname, None);
4041        assert_eq!(event.descriptor_id, None);
4042        assert_eq!(event.reason, None);
4043    }
4044
4045    #[test]
4046    fn test_hs_desc_event_failed() {
4047        let content = "FAILED ajhb7kljbiru65qo NO_AUTH $67B2BDA4264D8A189D9270E28B1D30A262838243 b3oeducbhjmbqmgw2i3jtz4fekkrinwj REASON=NOT_FOUND";
4048        let event = HsDescEvent::parse(content).unwrap();
4049        assert_eq!(event.action, HsDescAction::Failed);
4050        assert_eq!(event.address, "ajhb7kljbiru65qo");
4051        assert_eq!(event.authentication, Some(HsAuth::NoAuth));
4052        assert_eq!(
4053            event.directory,
4054            Some("$67B2BDA4264D8A189D9270E28B1D30A262838243".to_string())
4055        );
4056        assert_eq!(
4057            event.directory_fingerprint,
4058            Some("67B2BDA4264D8A189D9270E28B1D30A262838243".to_string())
4059        );
4060        assert_eq!(event.directory_nickname, None);
4061        assert_eq!(
4062            event.descriptor_id,
4063            Some("b3oeducbhjmbqmgw2i3jtz4fekkrinwj".to_string())
4064        );
4065        assert_eq!(event.reason, Some(HsDescReason::NotFound));
4066    }
4067
4068    #[test]
4069    fn test_parsed_event_dispatch() {
4070        let event = ParsedEvent::parse("BW", "100 200", None).unwrap();
4071        match event {
4072            ParsedEvent::Bandwidth(bw) => {
4073                assert_eq!(bw.read, 100);
4074                assert_eq!(bw.written, 200);
4075            }
4076            _ => panic!("expected bandwidth event"),
4077        }
4078
4079        let event = ParsedEvent::parse("CIRC", "1 BUILT", None).unwrap();
4080        match event {
4081            ParsedEvent::Circuit(circ) => {
4082                assert_eq!(circ.id.0, "1");
4083                assert_eq!(circ.status, CircStatus::Built);
4084            }
4085            _ => panic!("expected circuit event"),
4086        }
4087    }
4088
4089    #[test]
4090    fn test_parsed_event_log_events() {
4091        let event = ParsedEvent::parse("DEBUG", "test debug message", None).unwrap();
4092        match event {
4093            ParsedEvent::Log(log) => {
4094                assert_eq!(log.runlevel, Runlevel::Debug);
4095                assert_eq!(log.message, "test debug message");
4096            }
4097            _ => panic!("expected log event"),
4098        }
4099
4100        let event = ParsedEvent::parse("INFO", "test info message", None).unwrap();
4101        match event {
4102            ParsedEvent::Log(log) => {
4103                assert_eq!(log.runlevel, Runlevel::Info);
4104            }
4105            _ => panic!("expected log event"),
4106        }
4107
4108        let event = ParsedEvent::parse("NOTICE", "test notice message", None).unwrap();
4109        match event {
4110            ParsedEvent::Log(log) => {
4111                assert_eq!(log.runlevel, Runlevel::Notice);
4112            }
4113            _ => panic!("expected log event"),
4114        }
4115
4116        let event = ParsedEvent::parse("WARN", "test warn message", None).unwrap();
4117        match event {
4118            ParsedEvent::Log(log) => {
4119                assert_eq!(log.runlevel, Runlevel::Warn);
4120            }
4121            _ => panic!("expected log event"),
4122        }
4123
4124        let event = ParsedEvent::parse("ERR", "test error message", None).unwrap();
4125        match event {
4126            ParsedEvent::Log(log) => {
4127                assert_eq!(log.runlevel, Runlevel::Err);
4128            }
4129            _ => panic!("expected log event"),
4130        }
4131    }
4132
4133    #[test]
4134    fn test_parsed_event_status_events() {
4135        let event = ParsedEvent::parse("STATUS_GENERAL", "NOTICE CONSENSUS_ARRIVED", None).unwrap();
4136        match event {
4137            ParsedEvent::Status(status) => {
4138                assert_eq!(status.status_type, StatusType::General);
4139                assert_eq!(status.action, "CONSENSUS_ARRIVED");
4140            }
4141            _ => panic!("expected status event"),
4142        }
4143
4144        let event = ParsedEvent::parse("STATUS_CLIENT", "NOTICE ENOUGH_DIR_INFO", None).unwrap();
4145        match event {
4146            ParsedEvent::Status(status) => {
4147                assert_eq!(status.status_type, StatusType::Client);
4148            }
4149            _ => panic!("expected status event"),
4150        }
4151
4152        let event = ParsedEvent::parse(
4153            "STATUS_SERVER",
4154            "NOTICE CHECKING_REACHABILITY ORADDRESS=127.0.0.1:9050",
4155            None,
4156        )
4157        .unwrap();
4158        match event {
4159            ParsedEvent::Status(status) => {
4160                assert_eq!(status.status_type, StatusType::Server);
4161            }
4162            _ => panic!("expected status event"),
4163        }
4164    }
4165
4166    #[test]
4167    fn test_parsed_event_unknown() {
4168        let event = ParsedEvent::parse("UNKNOWN_EVENT", "some content", None).unwrap();
4169        match event {
4170            ParsedEvent::Unknown {
4171                event_type,
4172                content,
4173            } => {
4174                assert_eq!(event_type, "UNKNOWN_EVENT");
4175                assert_eq!(content, "some content");
4176            }
4177            _ => panic!("expected unknown event"),
4178        }
4179    }
4180
4181    #[test]
4182    fn test_parse_circuit_path() {
4183        let path = parse_circuit_path("$999A226EBED397F331B612FE1E4CFAE5C1F201BA=piyaz");
4184        assert_eq!(path.len(), 1);
4185        assert_eq!(path[0].0, "999A226EBED397F331B612FE1E4CFAE5C1F201BA");
4186        assert_eq!(path[0].1, Some("piyaz".to_string()));
4187
4188        let path = parse_circuit_path(
4189            "$E57A476CD4DFBD99B4EE52A100A58610AD6E80B9,hamburgerphone,PrivacyRepublic14",
4190        );
4191        assert_eq!(path.len(), 3);
4192    }
4193
4194    #[test]
4195    fn test_parse_relay_endpoint() {
4196        let (fp, nick) = parse_relay_endpoint("$36B5DBA788246E8369DBAF58577C6BC044A9A374");
4197        assert_eq!(fp, "36B5DBA788246E8369DBAF58577C6BC044A9A374");
4198        assert_eq!(nick, None);
4199
4200        let (fp, nick) = parse_relay_endpoint("$5D0034A368E0ABAF663D21847E1C9B6CFA09752A=caerSidi");
4201        assert_eq!(fp, "5D0034A368E0ABAF663D21847E1C9B6CFA09752A");
4202        assert_eq!(nick, Some("caerSidi".to_string()));
4203
4204        let (fp, nick) = parse_relay_endpoint("$B4BE08B22D4D2923EDC3970FD1B93D0448C6D8FF~Unnamed");
4205        assert_eq!(fp, "B4BE08B22D4D2923EDC3970FD1B93D0448C6D8FF");
4206        assert_eq!(nick, Some("Unnamed".to_string()));
4207    }
4208
4209    #[test]
4210    fn test_parse_target() {
4211        let (host, port) = parse_target("encrypted.google.com:443").unwrap();
4212        assert_eq!(host, "encrypted.google.com");
4213        assert_eq!(port, 443);
4214
4215        let (host, port) = parse_target("74.125.227.129:443").unwrap();
4216        assert_eq!(host, "74.125.227.129");
4217        assert_eq!(port, 443);
4218
4219        let (host, port) = parse_target("www.google.com:0").unwrap();
4220        assert_eq!(host, "www.google.com");
4221        assert_eq!(port, 0);
4222    }
4223
4224    #[test]
4225    fn test_parse_iso_timestamp() {
4226        let dt = parse_iso_timestamp("2012-11-08T16:48:38.417238").unwrap();
4227        assert_eq!(dt.year(), 2012);
4228        assert_eq!(dt.month(), 11);
4229        assert_eq!(dt.day(), 8);
4230        assert_eq!(dt.hour(), 16);
4231        assert_eq!(dt.minute(), 48);
4232        assert_eq!(dt.second(), 38);
4233
4234        let dt = parse_iso_timestamp("2012-12-06T13:51:11.433755").unwrap();
4235        assert_eq!(dt.year(), 2012);
4236        assert_eq!(dt.month(), 12);
4237        assert_eq!(dt.day(), 6);
4238    }
4239
4240    #[test]
4241    fn test_parse_build_flags() {
4242        let flags = parse_build_flags("NEED_CAPACITY");
4243        assert_eq!(flags, vec![CircBuildFlag::NeedCapacity]);
4244
4245        let flags = parse_build_flags("IS_INTERNAL,NEED_CAPACITY");
4246        assert_eq!(
4247            flags,
4248            vec![CircBuildFlag::IsInternal, CircBuildFlag::NeedCapacity]
4249        );
4250
4251        let flags = parse_build_flags("ONEHOP_TUNNEL,IS_INTERNAL,NEED_CAPACITY,NEED_UPTIME");
4252        assert_eq!(
4253            flags,
4254            vec![
4255                CircBuildFlag::OneHopTunnel,
4256                CircBuildFlag::IsInternal,
4257                CircBuildFlag::NeedCapacity,
4258                CircBuildFlag::NeedUptime
4259            ]
4260        );
4261    }
4262}