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(v.parse().map_err(|_| {
1765 Error::Protocol(format!("invalid DELIVERED_READ value: {}", v))
1766 })?);
1767 } else if line.is_next_mapping(Some("DELIVERED_WRITTEN"), false) {
1768 let (_, v) = line.pop_mapping(false, false)?;
1769 delivered_written = Some(v.parse().map_err(|_| {
1770 Error::Protocol(format!("invalid DELIVERED_WRITTEN value: {}", v))
1771 })?);
1772 } else if line.is_next_mapping(Some("OVERHEAD_READ"), false) {
1773 let (_, v) = line.pop_mapping(false, false)?;
1774 overhead_read =
1775 Some(v.parse().map_err(|_| {
1776 Error::Protocol(format!("invalid OVERHEAD_READ value: {}", v))
1777 })?);
1778 } else if line.is_next_mapping(Some("OVERHEAD_WRITTEN"), false) {
1779 let (_, v) = line.pop_mapping(false, false)?;
1780 overhead_written = Some(v.parse().map_err(|_| {
1781 Error::Protocol(format!("invalid OVERHEAD_WRITTEN value: {}", v))
1782 })?);
1783 } else if line.is_next_mapping(Some("TIME"), false) {
1784 let (_, v) = line.pop_mapping(false, false)?;
1785 time = parse_iso_timestamp(&v).ok();
1786 } else {
1787 let _ = line.pop(false, false)?;
1788 }
1789 }
1790
1791 Ok(Self {
1792 id: id.ok_or_else(|| Error::Protocol("missing ID in CIRC_BW".to_string()))?,
1793 read: read.ok_or_else(|| Error::Protocol("missing READ in CIRC_BW".to_string()))?,
1794 written: written
1795 .ok_or_else(|| Error::Protocol("missing WRITTEN in CIRC_BW".to_string()))?,
1796 delivered_read,
1797 delivered_written,
1798 overhead_read,
1799 overhead_written,
1800 time,
1801 raw_content: content.to_string(),
1802 arrived_at: Instant::now(),
1803 })
1804 }
1805}
1806
1807/// Event providing bandwidth information for a specific connection.
1808///
1809/// This event tracks bandwidth usage per connection, categorized by
1810/// connection type (OR, Dir, Exit). Useful for detailed bandwidth
1811/// analysis and monitoring.
1812///
1813/// # Connection Types
1814///
1815/// - [`ConnectionType::Or`] - Onion Router connections (relay-to-relay)
1816/// - [`ConnectionType::Dir`] - Directory connections
1817/// - [`ConnectionType::Exit`] - Exit connections to the internet
1818///
1819/// # Example
1820///
1821/// ```rust,ignore
1822/// use stem_rs::events::ConnectionBandwidthEvent;
1823/// use stem_rs::ConnectionType;
1824///
1825/// fn handle_conn_bw(event: &ConnectionBandwidthEvent) {
1826/// let type_str = match event.conn_type {
1827/// ConnectionType::Or => "OR",
1828/// ConnectionType::Dir => "Dir",
1829/// ConnectionType::Exit => "Exit",
1830/// };
1831/// println!("{} connection {}: {} read, {} written",
1832/// type_str, event.id, event.read, event.written);
1833/// }
1834/// ```
1835///
1836/// # See Also
1837///
1838/// - [`BandwidthEvent`] - Aggregate bandwidth
1839/// - [`CircuitBandwidthEvent`] - Per-circuit bandwidth
1840/// - [`ConnectionType`] - Connection types
1841#[derive(Debug, Clone)]
1842pub struct ConnectionBandwidthEvent {
1843 /// Connection identifier.
1844 pub id: String,
1845 /// Type of connection.
1846 pub conn_type: ConnectionType,
1847 /// Bytes read on this connection.
1848 pub read: u64,
1849 /// Bytes written on this connection.
1850 pub written: u64,
1851 raw_content: String,
1852 arrived_at: Instant,
1853}
1854
1855impl Event for ConnectionBandwidthEvent {
1856 fn event_type(&self) -> EventType {
1857 EventType::ConnBw
1858 }
1859 fn raw_content(&self) -> &str {
1860 &self.raw_content
1861 }
1862 fn arrived_at(&self) -> Instant {
1863 self.arrived_at
1864 }
1865}
1866
1867impl ConnectionBandwidthEvent {
1868 /// Parses a connection bandwidth event from raw control protocol content.
1869 ///
1870 /// # Arguments
1871 ///
1872 /// * `content` - The event content after the event type
1873 ///
1874 /// # Event Format
1875 ///
1876 /// ```text
1877 /// ID=ConnID TYPE=ConnType READ=bytes WRITTEN=bytes
1878 /// ```
1879 ///
1880 /// # Errors
1881 ///
1882 /// Returns [`Error::Protocol`] if:
1883 /// - Required fields (ID, TYPE, READ, WRITTEN) are missing
1884 /// - The connection type is unrecognized
1885 /// - Numeric values cannot be parsed
1886 pub fn parse(content: &str) -> Result<Self, Error> {
1887 let mut line = ControlLine::new(content);
1888 let mut id = None;
1889 let mut conn_type = None;
1890 let mut read = None;
1891 let mut written = None;
1892
1893 while !line.is_empty() {
1894 if line.is_next_mapping(Some("ID"), false) {
1895 let (_, v) = line.pop_mapping(false, false)?;
1896 id = Some(v);
1897 } else if line.is_next_mapping(Some("TYPE"), false) {
1898 let (_, v) = line.pop_mapping(false, false)?;
1899 conn_type = Some(parse_connection_type(&v)?);
1900 } else if line.is_next_mapping(Some("READ"), false) {
1901 let (_, v) = line.pop_mapping(false, false)?;
1902 read = Some(
1903 v.parse()
1904 .map_err(|_| Error::Protocol(format!("invalid READ value: {}", v)))?,
1905 );
1906 } else if line.is_next_mapping(Some("WRITTEN"), false) {
1907 let (_, v) = line.pop_mapping(false, false)?;
1908 written = Some(
1909 v.parse()
1910 .map_err(|_| Error::Protocol(format!("invalid WRITTEN value: {}", v)))?,
1911 );
1912 } else {
1913 let _ = line.pop(false, false)?;
1914 }
1915 }
1916
1917 Ok(Self {
1918 id: id.ok_or_else(|| Error::Protocol("missing ID in CONN_BW".to_string()))?,
1919 conn_type: conn_type
1920 .ok_or_else(|| Error::Protocol("missing TYPE in CONN_BW".to_string()))?,
1921 read: read.ok_or_else(|| Error::Protocol("missing READ in CONN_BW".to_string()))?,
1922 written: written
1923 .ok_or_else(|| Error::Protocol("missing WRITTEN in CONN_BW".to_string()))?,
1924 raw_content: content.to_string(),
1925 arrived_at: Instant::now(),
1926 })
1927 }
1928}
1929
1930/// Event triggered when fetching or uploading hidden service descriptors.
1931///
1932/// This event tracks the lifecycle of hidden service descriptor operations,
1933/// including requests, uploads, and failures. It's essential for monitoring
1934/// hidden service connectivity.
1935///
1936/// # Actions
1937///
1938/// The `action` field indicates the operation:
1939/// - [`HsDescAction::Requested`] - Descriptor fetch requested
1940/// - [`HsDescAction::Received`] - Descriptor successfully received
1941/// - [`HsDescAction::Uploaded`] - Descriptor successfully uploaded
1942/// - [`HsDescAction::Failed`] - Operation failed (check `reason`)
1943/// - [`HsDescAction::Created`] - New descriptor created
1944/// - [`HsDescAction::Ignore`] - Descriptor ignored
1945///
1946/// # Directory Information
1947///
1948/// The `directory` field contains the HSDir relay handling the request.
1949/// The fingerprint and nickname are extracted into separate fields for
1950/// convenience.
1951///
1952/// # Example
1953///
1954/// ```rust,ignore
1955/// use stem_rs::events::HsDescEvent;
1956/// use stem_rs::HsDescAction;
1957///
1958/// fn handle_hsdesc(event: &HsDescEvent) {
1959/// match event.action {
1960/// HsDescAction::Received => {
1961/// println!("Got descriptor for {} from {:?}",
1962/// event.address, event.directory_nickname);
1963/// }
1964/// HsDescAction::Failed => {
1965/// println!("Failed to get descriptor for {}: {:?}",
1966/// event.address, event.reason);
1967/// }
1968/// _ => {}
1969/// }
1970/// }
1971/// ```
1972///
1973/// # See Also
1974///
1975/// - [`HsDescAction`] - Descriptor actions
1976/// - [`HsDescReason`] - Failure reasons
1977/// - [`HsAuth`] - Authentication types
1978#[derive(Debug, Clone)]
1979pub struct HsDescEvent {
1980 /// Action being performed on the descriptor.
1981 pub action: HsDescAction,
1982 /// Hidden service address (onion address).
1983 pub address: String,
1984 /// Authentication type for the hidden service.
1985 pub authentication: Option<HsAuth>,
1986 /// Full directory relay string.
1987 pub directory: Option<String>,
1988 /// Directory relay fingerprint.
1989 pub directory_fingerprint: Option<String>,
1990 /// Directory relay nickname.
1991 pub directory_nickname: Option<String>,
1992 /// Descriptor identifier.
1993 pub descriptor_id: Option<String>,
1994 /// Reason for failure (if action is Failed).
1995 pub reason: Option<HsDescReason>,
1996 raw_content: String,
1997 arrived_at: Instant,
1998}
1999
2000impl Event for HsDescEvent {
2001 fn event_type(&self) -> EventType {
2002 EventType::HsDesc
2003 }
2004 fn raw_content(&self) -> &str {
2005 &self.raw_content
2006 }
2007 fn arrived_at(&self) -> Instant {
2008 self.arrived_at
2009 }
2010}
2011
2012impl HsDescEvent {
2013 /// Parses a hidden service descriptor event from raw control protocol content.
2014 ///
2015 /// # Arguments
2016 ///
2017 /// * `content` - The event content after the event type
2018 ///
2019 /// # Event Format
2020 ///
2021 /// ```text
2022 /// Action Address AuthType [Directory] [DescriptorID] [REASON=...]
2023 /// ```
2024 ///
2025 /// # Errors
2026 ///
2027 /// Returns [`Error::Protocol`] if:
2028 /// - Required fields are missing
2029 /// - The action is unrecognized
2030 pub fn parse(content: &str) -> Result<Self, Error> {
2031 let mut line = ControlLine::new(content);
2032 let action_str = line.pop(false, false)?;
2033 let address = line.pop(false, false)?;
2034 let auth_str = line.pop(false, false)?;
2035
2036 let action = parse_hs_desc_action(&action_str)?;
2037 let authentication = parse_hs_auth(&auth_str).ok();
2038
2039 let mut directory = None;
2040 let mut directory_fingerprint = None;
2041 let mut directory_nickname = None;
2042 let mut descriptor_id = None;
2043 let mut reason = None;
2044
2045 if !line.is_empty() {
2046 let dir_token = line.pop(false, false)?;
2047 if dir_token != "UNKNOWN" {
2048 directory = Some(dir_token.clone());
2049 let (fp, nick) = parse_relay_endpoint(&dir_token);
2050 directory_fingerprint = Some(fp);
2051 directory_nickname = nick;
2052 }
2053 }
2054
2055 if !line.is_empty() && line.peek_key().is_none_or(|k| k != "REASON") {
2056 descriptor_id = Some(line.pop(false, false)?);
2057 }
2058
2059 while !line.is_empty() {
2060 if line.is_next_mapping(Some("REASON"), false) {
2061 let (_, r) = line.pop_mapping(false, false)?;
2062 reason = parse_hs_desc_reason(&r).ok();
2063 } else {
2064 let _ = line.pop(false, false)?;
2065 }
2066 }
2067
2068 Ok(Self {
2069 action,
2070 address,
2071 authentication,
2072 directory,
2073 directory_fingerprint,
2074 directory_nickname,
2075 descriptor_id,
2076 reason,
2077 raw_content: content.to_string(),
2078 arrived_at: Instant::now(),
2079 })
2080 }
2081}
2082
2083/// Parses a circuit status string into a [`CircStatus`] enum variant.
2084///
2085/// Converts a case-insensitive string representation of a circuit status
2086/// from the Tor control protocol into the corresponding enum variant.
2087///
2088/// # Arguments
2089///
2090/// * `s` - The circuit status string to parse (e.g., "LAUNCHED", "BUILT")
2091///
2092/// # Returns
2093///
2094/// * `Ok(CircStatus)` - The parsed circuit status variant
2095/// * `Err(Error::Protocol)` - If the string doesn't match any known status
2096///
2097/// # Supported Values
2098///
2099/// - `LAUNCHED` - Circuit construction has begun
2100/// - `BUILT` - Circuit is fully constructed and ready for use
2101/// - `GUARD_WAIT` - Waiting for guard node selection
2102/// - `EXTENDED` - Circuit has been extended by one hop
2103/// - `FAILED` - Circuit construction failed
2104/// - `CLOSED` - Circuit has been closed
2105fn parse_circ_status(s: &str) -> Result<CircStatus, Error> {
2106 match s.to_uppercase().as_str() {
2107 "LAUNCHED" => Ok(CircStatus::Launched),
2108 "BUILT" => Ok(CircStatus::Built),
2109 "GUARD_WAIT" => Ok(CircStatus::GuardWait),
2110 "EXTENDED" => Ok(CircStatus::Extended),
2111 "FAILED" => Ok(CircStatus::Failed),
2112 "CLOSED" => Ok(CircStatus::Closed),
2113 _ => Err(Error::Protocol(format!("unknown circuit status: {}", s))),
2114 }
2115}
2116
2117/// Parses a stream status string into a [`StreamStatus`] enum variant.
2118///
2119/// Converts a case-insensitive string representation of a stream status
2120/// from the Tor control protocol into the corresponding enum variant.
2121///
2122/// # Arguments
2123///
2124/// * `s` - The stream status string to parse (e.g., "NEW", "SUCCEEDED")
2125///
2126/// # Returns
2127///
2128/// * `Ok(StreamStatus)` - The parsed stream status variant
2129/// * `Err(Error::Protocol)` - If the string doesn't match any known status
2130///
2131/// # Supported Values
2132///
2133/// - `NEW` - New stream awaiting connection
2134/// - `NEWRESOLVE` - New stream awaiting DNS resolution
2135/// - `REMAP` - Address has been remapped
2136/// - `SENTCONNECT` - Connect request sent to exit
2137/// - `SENTRESOLVE` - Resolve request sent to exit
2138/// - `SUCCEEDED` - Stream connection succeeded
2139/// - `FAILED` - Stream connection failed
2140/// - `DETACHED` - Stream detached from circuit
2141/// - `CONTROLLER_WAIT` - Waiting for controller attachment
2142/// - `CLOSED` - Stream has been closed
2143fn parse_stream_status(s: &str) -> Result<StreamStatus, Error> {
2144 match s.to_uppercase().as_str() {
2145 "NEW" => Ok(StreamStatus::New),
2146 "NEWRESOLVE" => Ok(StreamStatus::NewResolve),
2147 "REMAP" => Ok(StreamStatus::Remap),
2148 "SENTCONNECT" => Ok(StreamStatus::SentConnect),
2149 "SENTRESOLVE" => Ok(StreamStatus::SentResolve),
2150 "SUCCEEDED" => Ok(StreamStatus::Succeeded),
2151 "FAILED" => Ok(StreamStatus::Failed),
2152 "DETACHED" => Ok(StreamStatus::Detached),
2153 "CONTROLLER_WAIT" => Ok(StreamStatus::ControllerWait),
2154 "CLOSED" => Ok(StreamStatus::Closed),
2155 _ => Err(Error::Protocol(format!("unknown stream status: {}", s))),
2156 }
2157}
2158
2159/// Parses an OR (Onion Router) connection status string into an [`OrStatus`] enum variant.
2160///
2161/// Converts a case-insensitive string representation of an OR connection status
2162/// from the Tor control protocol into the corresponding enum variant.
2163///
2164/// # Arguments
2165///
2166/// * `s` - The OR status string to parse (e.g., "NEW", "CONNECTED")
2167///
2168/// # Returns
2169///
2170/// * `Ok(OrStatus)` - The parsed OR connection status variant
2171/// * `Err(Error::Protocol)` - If the string doesn't match any known status
2172///
2173/// # Supported Values
2174///
2175/// - `NEW` - New OR connection initiated
2176/// - `LAUNCHED` - Connection attempt launched
2177/// - `CONNECTED` - Successfully connected to OR
2178/// - `FAILED` - Connection attempt failed
2179/// - `CLOSED` - Connection has been closed
2180fn parse_or_status(s: &str) -> Result<OrStatus, Error> {
2181 match s.to_uppercase().as_str() {
2182 "NEW" => Ok(OrStatus::New),
2183 "LAUNCHED" => Ok(OrStatus::Launched),
2184 "CONNECTED" => Ok(OrStatus::Connected),
2185 "FAILED" => Ok(OrStatus::Failed),
2186 "CLOSED" => Ok(OrStatus::Closed),
2187 _ => Err(Error::Protocol(format!("unknown OR status: {}", s))),
2188 }
2189}
2190
2191/// Parses a guard type string into a [`GuardType`] enum variant.
2192///
2193/// Converts a case-insensitive string representation of a guard node type
2194/// from the Tor control protocol into the corresponding enum variant.
2195///
2196/// # Arguments
2197///
2198/// * `s` - The guard type string to parse (currently only "ENTRY")
2199///
2200/// # Returns
2201///
2202/// * `Ok(GuardType)` - The parsed guard type variant
2203/// * `Err(Error::Protocol)` - If the string doesn't match any known type
2204///
2205/// # Supported Values
2206///
2207/// - `ENTRY` - Entry guard node
2208fn parse_guard_type(s: &str) -> Result<GuardType, Error> {
2209 match s.to_uppercase().as_str() {
2210 "ENTRY" => Ok(GuardType::Entry),
2211 _ => Err(Error::Protocol(format!("unknown guard type: {}", s))),
2212 }
2213}
2214
2215/// Parses a guard status string into a [`GuardStatus`] enum variant.
2216///
2217/// Converts a case-insensitive string representation of a guard node status
2218/// from the Tor control protocol into the corresponding enum variant.
2219///
2220/// # Arguments
2221///
2222/// * `s` - The guard status string to parse (e.g., "NEW", "UP", "DOWN")
2223///
2224/// # Returns
2225///
2226/// * `Ok(GuardStatus)` - The parsed guard status variant
2227/// * `Err(Error::Protocol)` - If the string doesn't match any known status
2228///
2229/// # Supported Values
2230///
2231/// - `NEW` - Guard node newly selected
2232/// - `DROPPED` - Guard node dropped from selection
2233/// - `UP` - Guard node is reachable
2234/// - `DOWN` - Guard node is unreachable
2235/// - `BAD` - Guard node marked as bad
2236/// - `GOOD` - Guard node marked as good
2237fn parse_guard_status(s: &str) -> Result<GuardStatus, Error> {
2238 match s.to_uppercase().as_str() {
2239 "NEW" => Ok(GuardStatus::New),
2240 "DROPPED" => Ok(GuardStatus::Dropped),
2241 "UP" => Ok(GuardStatus::Up),
2242 "DOWN" => Ok(GuardStatus::Down),
2243 "BAD" => Ok(GuardStatus::Bad),
2244 "GOOD" => Ok(GuardStatus::Good),
2245 _ => Err(Error::Protocol(format!("unknown guard status: {}", s))),
2246 }
2247}
2248
2249/// Parses a timeout set type string into a [`TimeoutSetType`] enum variant.
2250///
2251/// Converts a case-insensitive string representation of a circuit build
2252/// timeout set type from the Tor control protocol into the corresponding enum variant.
2253///
2254/// # Arguments
2255///
2256/// * `s` - The timeout set type string to parse (e.g., "COMPUTED", "RESET")
2257///
2258/// # Returns
2259///
2260/// * `Ok(TimeoutSetType)` - The parsed timeout set type variant
2261/// * `Err(Error::Protocol)` - If the string doesn't match any known type
2262///
2263/// # Supported Values
2264///
2265/// - `COMPUTED` - Timeout computed from circuit build times
2266/// - `RESET` - Timeout values have been reset
2267/// - `SUSPENDED` - Timeout learning suspended
2268/// - `DISCARD` - Timeout values discarded
2269/// - `RESUME` - Timeout learning resumed
2270fn parse_timeout_set_type(s: &str) -> Result<TimeoutSetType, Error> {
2271 match s.to_uppercase().as_str() {
2272 "COMPUTED" => Ok(TimeoutSetType::Computed),
2273 "RESET" => Ok(TimeoutSetType::Reset),
2274 "SUSPENDED" => Ok(TimeoutSetType::Suspended),
2275 "DISCARD" => Ok(TimeoutSetType::Discard),
2276 "RESUME" => Ok(TimeoutSetType::Resume),
2277 _ => Err(Error::Protocol(format!("unknown timeout set type: {}", s))),
2278 }
2279}
2280
2281/// Parses a log runlevel string into a [`Runlevel`] enum variant.
2282///
2283/// Converts a case-insensitive string representation of a Tor log severity
2284/// level from the Tor control protocol into the corresponding enum variant.
2285///
2286/// # Arguments
2287///
2288/// * `s` - The runlevel string to parse (e.g., "DEBUG", "INFO", "WARN")
2289///
2290/// # Returns
2291///
2292/// * `Ok(Runlevel)` - The parsed runlevel variant
2293/// * `Err(Error::Protocol)` - If the string doesn't match any known level
2294///
2295/// # Supported Values
2296///
2297/// - `DEBUG` - Debug-level messages (most verbose)
2298/// - `INFO` - Informational messages
2299/// - `NOTICE` - Normal operational messages
2300/// - `WARN` - Warning messages
2301/// - `ERR` - Error messages (most severe)
2302fn parse_runlevel(s: &str) -> Result<Runlevel, Error> {
2303 match s.to_uppercase().as_str() {
2304 "DEBUG" => Ok(Runlevel::Debug),
2305 "INFO" => Ok(Runlevel::Info),
2306 "NOTICE" => Ok(Runlevel::Notice),
2307 "WARN" => Ok(Runlevel::Warn),
2308 "ERR" => Ok(Runlevel::Err),
2309 _ => Err(Error::Protocol(format!("unknown runlevel: {}", s))),
2310 }
2311}
2312
2313/// Parses a signal string into a [`Signal`] enum variant.
2314///
2315/// Converts a case-insensitive string representation of a Tor signal
2316/// from the Tor control protocol into the corresponding enum variant.
2317/// Supports both signal names and their Unix signal equivalents.
2318///
2319/// # Arguments
2320///
2321/// * `s` - The signal string to parse (e.g., "RELOAD", "HUP", "NEWNYM")
2322///
2323/// # Returns
2324///
2325/// * `Ok(Signal)` - The parsed signal variant
2326/// * `Err(Error::Protocol)` - If the string doesn't match any known signal
2327///
2328/// # Supported Values
2329///
2330/// - `RELOAD` or `HUP` - Reload configuration
2331/// - `SHUTDOWN` or `INT` - Controlled shutdown
2332/// - `DUMP` or `USR1` - Dump statistics
2333/// - `DEBUG` or `USR2` - Switch to debug logging
2334/// - `HALT` or `TERM` - Immediate shutdown
2335/// - `NEWNYM` - Request new circuits
2336/// - `CLEARDNSCACHE` - Clear DNS cache
2337/// - `HEARTBEAT` - Trigger heartbeat log
2338/// - `ACTIVE` - Wake from dormant mode
2339/// - `DORMANT` - Enter dormant mode
2340fn parse_signal(s: &str) -> Result<Signal, Error> {
2341 match s.to_uppercase().as_str() {
2342 "RELOAD" | "HUP" => Ok(Signal::Reload),
2343 "SHUTDOWN" | "INT" => Ok(Signal::Shutdown),
2344 "DUMP" | "USR1" => Ok(Signal::Dump),
2345 "DEBUG" | "USR2" => Ok(Signal::Debug),
2346 "HALT" | "TERM" => Ok(Signal::Halt),
2347 "NEWNYM" => Ok(Signal::Newnym),
2348 "CLEARDNSCACHE" => Ok(Signal::ClearDnsCache),
2349 "HEARTBEAT" => Ok(Signal::Heartbeat),
2350 "ACTIVE" => Ok(Signal::Active),
2351 "DORMANT" => Ok(Signal::Dormant),
2352 _ => Err(Error::Protocol(format!("unknown signal: {}", s))),
2353 }
2354}
2355
2356/// Parses a connection type string into a [`ConnectionType`] enum variant.
2357///
2358/// Converts a case-insensitive string representation of a Tor connection type
2359/// from the Tor control protocol into the corresponding enum variant.
2360///
2361/// # Arguments
2362///
2363/// * `s` - The connection type string to parse (e.g., "OR", "DIR", "EXIT")
2364///
2365/// # Returns
2366///
2367/// * `Ok(ConnectionType)` - The parsed connection type variant
2368/// * `Err(Error::Protocol)` - If the string doesn't match any known type
2369///
2370/// # Supported Values
2371///
2372/// - `OR` - Onion Router connection (relay-to-relay)
2373/// - `DIR` - Directory connection
2374/// - `EXIT` - Exit connection to destination
2375fn parse_connection_type(s: &str) -> Result<ConnectionType, Error> {
2376 match s.to_uppercase().as_str() {
2377 "OR" => Ok(ConnectionType::Or),
2378 "DIR" => Ok(ConnectionType::Dir),
2379 "EXIT" => Ok(ConnectionType::Exit),
2380 _ => Err(Error::Protocol(format!("unknown connection type: {}", s))),
2381 }
2382}
2383
2384/// Parses a hidden service descriptor action string into an [`HsDescAction`] enum variant.
2385///
2386/// Converts a case-insensitive string representation of a hidden service
2387/// descriptor action from the Tor control protocol into the corresponding enum variant.
2388///
2389/// # Arguments
2390///
2391/// * `s` - The HS_DESC action string to parse (e.g., "REQUESTED", "RECEIVED")
2392///
2393/// # Returns
2394///
2395/// * `Ok(HsDescAction)` - The parsed action variant
2396/// * `Err(Error::Protocol)` - If the string doesn't match any known action
2397///
2398/// # Supported Values
2399///
2400/// - `REQUESTED` - Descriptor fetch requested
2401/// - `UPLOAD` - Descriptor upload initiated
2402/// - `RECEIVED` - Descriptor successfully received
2403/// - `UPLOADED` - Descriptor successfully uploaded
2404/// - `IGNORE` - Descriptor ignored
2405/// - `FAILED` - Descriptor operation failed
2406/// - `CREATED` - Descriptor created locally
2407fn parse_hs_desc_action(s: &str) -> Result<HsDescAction, Error> {
2408 match s.to_uppercase().as_str() {
2409 "REQUESTED" => Ok(HsDescAction::Requested),
2410 "UPLOAD" => Ok(HsDescAction::Upload),
2411 "RECEIVED" => Ok(HsDescAction::Received),
2412 "UPLOADED" => Ok(HsDescAction::Uploaded),
2413 "IGNORE" => Ok(HsDescAction::Ignore),
2414 "FAILED" => Ok(HsDescAction::Failed),
2415 "CREATED" => Ok(HsDescAction::Created),
2416 _ => Err(Error::Protocol(format!("unknown HS_DESC action: {}", s))),
2417 }
2418}
2419
2420/// Parses a hidden service authentication type string into an [`HsAuth`] enum variant.
2421///
2422/// Converts a case-insensitive string representation of a hidden service
2423/// authentication type from the Tor control protocol into the corresponding enum variant.
2424///
2425/// # Arguments
2426///
2427/// * `s` - The HS auth type string to parse (e.g., "NO_AUTH", "BASIC_AUTH")
2428///
2429/// # Returns
2430///
2431/// * `Ok(HsAuth)` - The parsed authentication type variant
2432/// * `Err(Error::Protocol)` - If the string doesn't match any known type
2433///
2434/// # Supported Values
2435///
2436/// - `NO_AUTH` - No authentication required
2437/// - `BASIC_AUTH` - Basic authentication
2438/// - `STEALTH_AUTH` - Stealth authentication (more private)
2439/// - `UNKNOWN` - Unknown authentication type
2440fn parse_hs_auth(s: &str) -> Result<HsAuth, Error> {
2441 match s.to_uppercase().as_str() {
2442 "NO_AUTH" => Ok(HsAuth::NoAuth),
2443 "BASIC_AUTH" => Ok(HsAuth::BasicAuth),
2444 "STEALTH_AUTH" => Ok(HsAuth::StealthAuth),
2445 "UNKNOWN" => Ok(HsAuth::Unknown),
2446 _ => Err(Error::Protocol(format!("unknown HS auth type: {}", s))),
2447 }
2448}
2449
2450/// Parses a hidden service descriptor failure reason string into an [`HsDescReason`] enum variant.
2451///
2452/// Converts a case-insensitive string representation of a hidden service
2453/// descriptor failure reason from the Tor control protocol into the corresponding enum variant.
2454///
2455/// # Arguments
2456///
2457/// * `s` - The HS_DESC reason string to parse (e.g., "NOT_FOUND", "BAD_DESC")
2458///
2459/// # Returns
2460///
2461/// * `Ok(HsDescReason)` - The parsed reason variant
2462/// * `Err(Error::Protocol)` - If the string doesn't match any known reason
2463///
2464/// # Supported Values
2465///
2466/// - `BAD_DESC` - Descriptor was malformed or invalid
2467/// - `QUERY_REJECTED` - Query was rejected by HSDir
2468/// - `UPLOAD_REJECTED` - Upload was rejected by HSDir
2469/// - `NOT_FOUND` - Descriptor not found
2470/// - `QUERY_NO_HSDIR` - No HSDir available for query
2471/// - `QUERY_RATE_LIMITED` - Query rate limited
2472/// - `UNEXPECTED` - Unexpected error occurred
2473fn parse_hs_desc_reason(s: &str) -> Result<HsDescReason, Error> {
2474 match s.to_uppercase().as_str() {
2475 "BAD_DESC" => Ok(HsDescReason::BadDesc),
2476 "QUERY_REJECTED" => Ok(HsDescReason::QueryRejected),
2477 "UPLOAD_REJECTED" => Ok(HsDescReason::UploadRejected),
2478 "NOT_FOUND" => Ok(HsDescReason::NotFound),
2479 "QUERY_NO_HSDIR" => Ok(HsDescReason::QueryNoHsDir),
2480 "QUERY_RATE_LIMITED" => Ok(HsDescReason::QueryRateLimited),
2481 "UNEXPECTED" => Ok(HsDescReason::Unexpected),
2482 _ => Err(Error::Protocol(format!("unknown HS_DESC reason: {}", s))),
2483 }
2484}
2485
2486/// Parses a circuit purpose string into a [`CircPurpose`] enum variant.
2487///
2488/// Converts a case-insensitive string representation of a circuit purpose
2489/// from the Tor control protocol into the corresponding enum variant.
2490///
2491/// # Arguments
2492///
2493/// * `s` - The circuit purpose string to parse (e.g., "GENERAL", "HS_CLIENT_REND")
2494///
2495/// # Returns
2496///
2497/// * `Ok(CircPurpose)` - The parsed circuit purpose variant
2498/// * `Err(Error::Protocol)` - If the string doesn't match any known purpose
2499///
2500/// # Supported Values
2501///
2502/// - `GENERAL` - General-purpose circuit for user traffic
2503/// - `HS_CLIENT_INTRO` - Hidden service client introduction circuit
2504/// - `HS_CLIENT_REND` - Hidden service client rendezvous circuit
2505/// - `HS_SERVICE_INTRO` - Hidden service introduction point circuit
2506/// - `HS_SERVICE_REND` - Hidden service rendezvous circuit
2507/// - `TESTING` - Circuit for testing purposes
2508/// - `CONTROLLER` - Circuit created by controller
2509/// - `MEASURE_TIMEOUT` - Circuit for measuring build timeouts
2510/// - `HS_VANGUARDS` - Vanguard circuit for hidden services
2511/// - `PATH_BIAS_TESTING` - Circuit for path bias testing
2512/// - `CIRCUIT_PADDING` - Circuit for padding purposes
2513fn parse_circ_purpose(s: &str) -> Result<CircPurpose, Error> {
2514 match s.to_uppercase().as_str() {
2515 "GENERAL" => Ok(CircPurpose::General),
2516 "HS_CLIENT_INTRO" => Ok(CircPurpose::HsClientIntro),
2517 "HS_CLIENT_REND" => Ok(CircPurpose::HsClientRend),
2518 "HS_SERVICE_INTRO" => Ok(CircPurpose::HsServiceIntro),
2519 "HS_SERVICE_REND" => Ok(CircPurpose::HsServiceRend),
2520 "TESTING" => Ok(CircPurpose::Testing),
2521 "CONTROLLER" => Ok(CircPurpose::Controller),
2522 "MEASURE_TIMEOUT" => Ok(CircPurpose::MeasureTimeout),
2523 "HS_VANGUARDS" => Ok(CircPurpose::HsVanguards),
2524 "PATH_BIAS_TESTING" => Ok(CircPurpose::PathBiasTesting),
2525 "CIRCUIT_PADDING" => Ok(CircPurpose::CircuitPadding),
2526 _ => Err(Error::Protocol(format!("unknown circuit purpose: {}", s))),
2527 }
2528}
2529
2530/// Parses a hidden service state string into a [`HiddenServiceState`] enum variant.
2531///
2532/// Converts a case-insensitive string representation of a hidden service
2533/// circuit state from the Tor control protocol into the corresponding enum variant.
2534///
2535/// # Arguments
2536///
2537/// * `s` - The HS state string to parse (e.g., "HSCI_CONNECTING", "HSCR_JOINED")
2538///
2539/// # Returns
2540///
2541/// * `Ok(HiddenServiceState)` - The parsed hidden service state variant
2542/// * `Err(Error::Protocol)` - If the string doesn't match any known state
2543///
2544/// # Supported Values
2545///
2546/// Client Introduction (HSCI):
2547/// - `HSCI_CONNECTING` - Connecting to introduction point
2548/// - `HSCI_INTRO_SENT` - Introduction sent to service
2549/// - `HSCI_DONE` - Introduction complete
2550///
2551/// Client Rendezvous (HSCR):
2552/// - `HSCR_CONNECTING` - Connecting to rendezvous point
2553/// - `HSCR_ESTABLISHED_IDLE` - Rendezvous established, idle
2554/// - `HSCR_ESTABLISHED_WAITING` - Rendezvous established, waiting
2555/// - `HSCR_JOINED` - Rendezvous joined with service
2556///
2557/// Service Introduction (HSSI):
2558/// - `HSSI_CONNECTING` - Service connecting to intro point
2559/// - `HSSI_ESTABLISHED` - Service intro point established
2560///
2561/// Service Rendezvous (HSSR):
2562/// - `HSSR_CONNECTING` - Service connecting to rendezvous
2563/// - `HSSR_JOINED` - Service joined rendezvous
2564fn parse_hs_state(s: &str) -> Result<HiddenServiceState, Error> {
2565 match s.to_uppercase().as_str() {
2566 "HSCI_CONNECTING" => Ok(HiddenServiceState::HsciConnecting),
2567 "HSCI_INTRO_SENT" => Ok(HiddenServiceState::HsciIntroSent),
2568 "HSCI_DONE" => Ok(HiddenServiceState::HsciDone),
2569 "HSCR_CONNECTING" => Ok(HiddenServiceState::HscrConnecting),
2570 "HSCR_ESTABLISHED_IDLE" => Ok(HiddenServiceState::HscrEstablishedIdle),
2571 "HSCR_ESTABLISHED_WAITING" => Ok(HiddenServiceState::HscrEstablishedWaiting),
2572 "HSCR_JOINED" => Ok(HiddenServiceState::HscrJoined),
2573 "HSSI_CONNECTING" => Ok(HiddenServiceState::HssiConnecting),
2574 "HSSI_ESTABLISHED" => Ok(HiddenServiceState::HssiEstablished),
2575 "HSSR_CONNECTING" => Ok(HiddenServiceState::HssrConnecting),
2576 "HSSR_JOINED" => Ok(HiddenServiceState::HssrJoined),
2577 _ => Err(Error::Protocol(format!("unknown HS state: {}", s))),
2578 }
2579}
2580
2581/// Parses a circuit closure reason string into a [`CircClosureReason`] enum variant.
2582///
2583/// Converts a case-insensitive string representation of a circuit closure reason
2584/// from the Tor control protocol into the corresponding enum variant.
2585///
2586/// # Arguments
2587///
2588/// * `s` - The circuit closure reason string to parse (e.g., "FINISHED", "TIMEOUT")
2589///
2590/// # Returns
2591///
2592/// * `Ok(CircClosureReason)` - The parsed closure reason variant
2593/// * `Err(Error::Protocol)` - If the string doesn't match any known reason
2594///
2595/// # Supported Values
2596///
2597/// - `NONE` - No reason given
2598/// - `TORPROTOCOL` - Tor protocol violation
2599/// - `INTERNAL` - Internal error
2600/// - `REQUESTED` - Closure requested by client
2601/// - `HIBERNATING` - Relay is hibernating
2602/// - `RESOURCELIMIT` - Resource limit reached
2603/// - `CONNECTFAILED` - Connection to relay failed
2604/// - `OR_IDENTITY` - OR identity mismatch
2605/// - `OR_CONN_CLOSED` - OR connection closed
2606/// - `FINISHED` - Circuit finished normally
2607/// - `TIMEOUT` - Circuit timed out
2608/// - `DESTROYED` - Circuit was destroyed
2609/// - `NOPATH` - No path available
2610/// - `NOSUCHSERVICE` - Hidden service not found
2611/// - `MEASUREMENT_EXPIRED` - Measurement circuit expired
2612/// - `IP_NOW_REDUNDANT` - Introduction point now redundant
2613fn parse_circ_closure_reason(s: &str) -> Result<CircClosureReason, Error> {
2614 match s.to_uppercase().as_str() {
2615 "NONE" => Ok(CircClosureReason::None),
2616 "TORPROTOCOL" => Ok(CircClosureReason::TorProtocol),
2617 "INTERNAL" => Ok(CircClosureReason::Internal),
2618 "REQUESTED" => Ok(CircClosureReason::Requested),
2619 "HIBERNATING" => Ok(CircClosureReason::Hibernating),
2620 "RESOURCELIMIT" => Ok(CircClosureReason::ResourceLimit),
2621 "CONNECTFAILED" => Ok(CircClosureReason::ConnectFailed),
2622 "OR_IDENTITY" => Ok(CircClosureReason::OrIdentity),
2623 "OR_CONN_CLOSED" => Ok(CircClosureReason::OrConnClosed),
2624 "FINISHED" => Ok(CircClosureReason::Finished),
2625 "TIMEOUT" => Ok(CircClosureReason::Timeout),
2626 "DESTROYED" => Ok(CircClosureReason::Destroyed),
2627 "NOPATH" => Ok(CircClosureReason::NoPath),
2628 "NOSUCHSERVICE" => Ok(CircClosureReason::NoSuchService),
2629 "MEASUREMENT_EXPIRED" => Ok(CircClosureReason::MeasurementExpired),
2630 "IP_NOW_REDUNDANT" => Ok(CircClosureReason::IpNowRedundant),
2631 _ => Err(Error::Protocol(format!(
2632 "unknown circuit closure reason: {}",
2633 s
2634 ))),
2635 }
2636}
2637
2638/// Parses a stream closure reason string into a [`StreamClosureReason`] enum variant.
2639///
2640/// Converts a case-insensitive string representation of a stream closure reason
2641/// from the Tor control protocol into the corresponding enum variant.
2642///
2643/// # Arguments
2644///
2645/// * `s` - The stream closure reason string to parse (e.g., "DONE", "TIMEOUT")
2646///
2647/// # Returns
2648///
2649/// * `Ok(StreamClosureReason)` - The parsed closure reason variant
2650/// * `Err(Error::Protocol)` - If the string doesn't match any known reason
2651///
2652/// # Supported Values
2653///
2654/// - `MISC` - Miscellaneous error
2655/// - `RESOLVEFAILED` - DNS resolution failed
2656/// - `CONNECTREFUSED` - Connection refused by destination
2657/// - `EXITPOLICY` - Exit policy rejected connection
2658/// - `DESTROY` - Circuit was destroyed
2659/// - `DONE` - Stream completed normally
2660/// - `TIMEOUT` - Stream timed out
2661/// - `NOROUTE` - No route to destination
2662/// - `HIBERNATING` - Relay is hibernating
2663/// - `INTERNAL` - Internal error
2664/// - `RESOURCELIMIT` - Resource limit reached
2665/// - `CONNRESET` - Connection reset
2666/// - `TORPROTOCOL` - Tor protocol violation
2667/// - `NOTDIRECTORY` - Not a directory server
2668/// - `END` - Stream ended
2669/// - `PRIVATE_ADDR` - Private address rejected
2670fn parse_stream_closure_reason(s: &str) -> Result<StreamClosureReason, Error> {
2671 match s.to_uppercase().as_str() {
2672 "MISC" => Ok(StreamClosureReason::Misc),
2673 "RESOLVEFAILED" => Ok(StreamClosureReason::ResolveFailed),
2674 "CONNECTREFUSED" => Ok(StreamClosureReason::ConnectRefused),
2675 "EXITPOLICY" => Ok(StreamClosureReason::ExitPolicy),
2676 "DESTROY" => Ok(StreamClosureReason::Destroy),
2677 "DONE" => Ok(StreamClosureReason::Done),
2678 "TIMEOUT" => Ok(StreamClosureReason::Timeout),
2679 "NOROUTE" => Ok(StreamClosureReason::NoRoute),
2680 "HIBERNATING" => Ok(StreamClosureReason::Hibernating),
2681 "INTERNAL" => Ok(StreamClosureReason::Internal),
2682 "RESOURCELIMIT" => Ok(StreamClosureReason::ResourceLimit),
2683 "CONNRESET" => Ok(StreamClosureReason::ConnReset),
2684 "TORPROTOCOL" => Ok(StreamClosureReason::TorProtocol),
2685 "NOTDIRECTORY" => Ok(StreamClosureReason::NotDirectory),
2686 "END" => Ok(StreamClosureReason::End),
2687 "PRIVATE_ADDR" => Ok(StreamClosureReason::PrivateAddr),
2688 _ => Err(Error::Protocol(format!(
2689 "unknown stream closure reason: {}",
2690 s
2691 ))),
2692 }
2693}
2694
2695/// Parses a stream source string into a [`StreamSource`] enum variant.
2696///
2697/// Converts a case-insensitive string representation of a stream source
2698/// from the Tor control protocol into the corresponding enum variant.
2699///
2700/// # Arguments
2701///
2702/// * `s` - The stream source string to parse (e.g., "CACHE", "EXIT")
2703///
2704/// # Returns
2705///
2706/// * `Ok(StreamSource)` - The parsed stream source variant
2707/// * `Err(Error::Protocol)` - If the string doesn't match any known source
2708///
2709/// # Supported Values
2710///
2711/// - `CACHE` - Data from cache
2712/// - `EXIT` - Data from exit node
2713fn parse_stream_source(s: &str) -> Result<StreamSource, Error> {
2714 match s.to_uppercase().as_str() {
2715 "CACHE" => Ok(StreamSource::Cache),
2716 "EXIT" => Ok(StreamSource::Exit),
2717 _ => Err(Error::Protocol(format!("unknown stream source: {}", s))),
2718 }
2719}
2720
2721/// Parses a stream purpose string into a [`StreamPurpose`] enum variant.
2722///
2723/// Converts a case-insensitive string representation of a stream purpose
2724/// from the Tor control protocol into the corresponding enum variant.
2725///
2726/// # Arguments
2727///
2728/// * `s` - The stream purpose string to parse (e.g., "USER", "DIR_FETCH")
2729///
2730/// # Returns
2731///
2732/// * `Ok(StreamPurpose)` - The parsed stream purpose variant
2733/// * `Err(Error::Protocol)` - If the string doesn't match any known purpose
2734///
2735/// # Supported Values
2736///
2737/// - `DIR_FETCH` - Directory fetch operation
2738/// - `DIR_UPLOAD` - Directory upload operation
2739/// - `DNS_REQUEST` - DNS resolution request
2740/// - `DIRPORT_TEST` - Directory port testing
2741/// - `USER` - User-initiated stream
2742fn parse_stream_purpose(s: &str) -> Result<StreamPurpose, Error> {
2743 match s.to_uppercase().as_str() {
2744 "DIR_FETCH" => Ok(StreamPurpose::DirFetch),
2745 "DIR_UPLOAD" => Ok(StreamPurpose::DirUpload),
2746 "DNS_REQUEST" => Ok(StreamPurpose::DnsRequest),
2747 "DIRPORT_TEST" => Ok(StreamPurpose::DirportTest),
2748 "USER" => Ok(StreamPurpose::User),
2749 _ => Err(Error::Protocol(format!("unknown stream purpose: {}", s))),
2750 }
2751}
2752
2753/// Parses an OR connection closure reason string into an [`OrClosureReason`] enum variant.
2754///
2755/// Converts a case-insensitive string representation of an OR connection
2756/// closure reason from the Tor control protocol into the corresponding enum variant.
2757///
2758/// # Arguments
2759///
2760/// * `s` - The OR closure reason string to parse (e.g., "DONE", "TIMEOUT")
2761///
2762/// # Returns
2763///
2764/// * `Ok(OrClosureReason)` - The parsed closure reason variant
2765/// * `Err(Error::Protocol)` - If the string doesn't match any known reason
2766///
2767/// # Supported Values
2768///
2769/// - `DONE` - Connection completed normally
2770/// - `CONNECTREFUSED` - Connection refused
2771/// - `IDENTITY` - Identity verification failed
2772/// - `CONNECTRESET` - Connection reset
2773/// - `TIMEOUT` - Connection timed out
2774/// - `NOROUTE` - No route to relay
2775/// - `IOERROR` - I/O error occurred
2776/// - `RESOURCELIMIT` - Resource limit reached
2777/// - `MISC` - Miscellaneous error
2778/// - `PT_MISSING` - Pluggable transport missing
2779fn parse_or_closure_reason(s: &str) -> Result<OrClosureReason, Error> {
2780 match s.to_uppercase().as_str() {
2781 "DONE" => Ok(OrClosureReason::Done),
2782 "CONNECTREFUSED" => Ok(OrClosureReason::ConnectRefused),
2783 "IDENTITY" => Ok(OrClosureReason::Identity),
2784 "CONNECTRESET" => Ok(OrClosureReason::ConnectReset),
2785 "TIMEOUT" => Ok(OrClosureReason::Timeout),
2786 "NOROUTE" => Ok(OrClosureReason::NoRoute),
2787 "IOERROR" => Ok(OrClosureReason::IoError),
2788 "RESOURCELIMIT" => Ok(OrClosureReason::ResourceLimit),
2789 "MISC" => Ok(OrClosureReason::Misc),
2790 "PT_MISSING" => Ok(OrClosureReason::PtMissing),
2791 _ => Err(Error::Protocol(format!("unknown OR closure reason: {}", s))),
2792 }
2793}
2794
2795/// Parses a comma-separated string of circuit build flags into a vector of [`CircBuildFlag`].
2796///
2797/// Converts a comma-separated string of circuit build flags from the Tor
2798/// control protocol into a vector of enum variants. Unknown flags are silently ignored.
2799///
2800/// # Arguments
2801///
2802/// * `s` - The comma-separated build flags string (e.g., "ONEHOP_TUNNEL,IS_INTERNAL")
2803///
2804/// # Returns
2805///
2806/// A vector of recognized [`CircBuildFlag`] variants. Unknown flags are filtered out.
2807///
2808/// # Supported Values
2809///
2810/// - `ONEHOP_TUNNEL` - Single-hop circuit (for directory connections)
2811/// - `IS_INTERNAL` - Internal circuit (not for user traffic)
2812/// - `NEED_CAPACITY` - Circuit needs high-capacity relays
2813/// - `NEED_UPTIME` - Circuit needs high-uptime relays
2814fn parse_build_flags(s: &str) -> Vec<CircBuildFlag> {
2815 s.split(',')
2816 .filter_map(|f| match f.to_uppercase().as_str() {
2817 "ONEHOP_TUNNEL" => Some(CircBuildFlag::OneHopTunnel),
2818 "IS_INTERNAL" => Some(CircBuildFlag::IsInternal),
2819 "NEED_CAPACITY" => Some(CircBuildFlag::NeedCapacity),
2820 "NEED_UPTIME" => Some(CircBuildFlag::NeedUptime),
2821 _ => None,
2822 })
2823 .collect()
2824}
2825
2826/// Parses a circuit path string into a vector of relay fingerprint and nickname pairs.
2827///
2828/// Converts a comma-separated circuit path string from the Tor control protocol
2829/// into a vector of tuples containing relay fingerprints and optional nicknames.
2830///
2831/// # Arguments
2832///
2833/// * `s` - The circuit path string (e.g., "$FP1~nick1,$FP2=nick2,$FP3")
2834///
2835/// # Returns
2836///
2837/// A vector of tuples where each tuple contains:
2838/// - The relay fingerprint (with leading `$` stripped)
2839/// - An optional nickname (if present after `~` or `=`)
2840///
2841/// # Format
2842///
2843/// Each relay in the path can be specified as:
2844/// - `$FINGERPRINT~nickname` - Fingerprint with nickname (tilde separator)
2845/// - `$FINGERPRINT=nickname` - Fingerprint with nickname (equals separator)
2846/// - `$FINGERPRINT` - Fingerprint only
2847/// - `FINGERPRINT` - Fingerprint without `$` prefix
2848fn parse_circuit_path(s: &str) -> Vec<(String, Option<String>)> {
2849 s.split(',')
2850 .map(|relay| {
2851 let relay = relay.trim_start_matches('$');
2852 if let Some((fp, nick)) = relay.split_once('~') {
2853 (fp.to_string(), Some(nick.to_string()))
2854 } else if let Some((fp, nick)) = relay.split_once('=') {
2855 (fp.to_string(), Some(nick.to_string()))
2856 } else {
2857 (relay.to_string(), None)
2858 }
2859 })
2860 .collect()
2861}
2862
2863/// Parses a relay endpoint string into a fingerprint and optional nickname.
2864///
2865/// Converts a relay endpoint string from the Tor control protocol into a tuple
2866/// containing the relay fingerprint and optional nickname.
2867///
2868/// # Arguments
2869///
2870/// * `s` - The relay endpoint string (e.g., "$FP~nickname" or "$FP=nickname")
2871///
2872/// # Returns
2873///
2874/// A tuple containing:
2875/// - The relay fingerprint (with leading `$` stripped)
2876/// - An optional nickname (if present after `~` or `=`)
2877///
2878/// # Format
2879///
2880/// The relay can be specified as:
2881/// - `$FINGERPRINT~nickname` - Fingerprint with nickname (tilde separator)
2882/// - `$FINGERPRINT=nickname` - Fingerprint with nickname (equals separator)
2883/// - `$FINGERPRINT` - Fingerprint only
2884/// - `FINGERPRINT` - Fingerprint without `$` prefix
2885fn parse_relay_endpoint(s: &str) -> (String, Option<String>) {
2886 let s = s.trim_start_matches('$');
2887 if let Some((fp, nick)) = s.split_once('~') {
2888 (fp.to_string(), Some(nick.to_string()))
2889 } else if let Some((fp, nick)) = s.split_once('=') {
2890 (fp.to_string(), Some(nick.to_string()))
2891 } else {
2892 (s.to_string(), None)
2893 }
2894}
2895
2896/// Parses a target address string into a host and port tuple.
2897///
2898/// Converts a target address string (host:port format) from the Tor control
2899/// protocol into a tuple containing the host and port number.
2900///
2901/// # Arguments
2902///
2903/// * `target` - The target address string (e.g., "example.com:80" or "[::1]:443")
2904///
2905/// # Returns
2906///
2907/// * `Ok((host, port))` - The parsed host string and port number
2908/// * `Err(Error::Protocol)` - If the port cannot be parsed as a valid u16
2909///
2910/// # Format
2911///
2912/// Supports both IPv4 and IPv6 addresses:
2913/// - `hostname:port` - Standard hostname with port
2914/// - `ip:port` - IPv4 address with port
2915/// - `[ipv6]:port` - IPv6 address with port (brackets preserved in host)
2916///
2917/// If no port is specified, returns port 0.
2918fn parse_target(target: &str) -> Result<(String, u16), Error> {
2919 if let Some(colon_pos) = target.rfind(':') {
2920 let host = target[..colon_pos].to_string();
2921 let port_str = &target[colon_pos + 1..];
2922 let port: u16 = port_str
2923 .parse()
2924 .map_err(|_| Error::Protocol(format!("invalid port: {}", port_str)))?;
2925 Ok((host, port))
2926 } else {
2927 Ok((target.to_string(), 0))
2928 }
2929}
2930
2931/// Parses an ISO 8601 timestamp string into a UTC [`DateTime`].
2932///
2933/// Converts a timestamp string in ISO 8601 format from the Tor control
2934/// protocol into a [`DateTime<Utc>`] value.
2935///
2936/// # Arguments
2937///
2938/// * `s` - The timestamp string (e.g., "2024-01-15 12:30:45" or "2024-01-15T12:30:45.123")
2939///
2940/// # Returns
2941///
2942/// * `Ok(DateTime<Utc>)` - The parsed UTC datetime
2943/// * `Err(Error::Protocol)` - If the timestamp format is invalid
2944///
2945/// # Supported Formats
2946///
2947/// - `YYYY-MM-DD HH:MM:SS` - Standard format
2948/// - `YYYY-MM-DDTHH:MM:SS` - ISO 8601 with T separator
2949/// - `YYYY-MM-DD HH:MM:SS.fff` - With fractional seconds
2950/// - `YYYY-MM-DDTHH:MM:SS.fff` - ISO 8601 with fractional seconds
2951fn parse_iso_timestamp(s: &str) -> Result<DateTime<Utc>, Error> {
2952 let s = s.replace('T', " ");
2953 let formats = ["%Y-%m-%d %H:%M:%S%.f", "%Y-%m-%d %H:%M:%S"];
2954 for fmt in &formats {
2955 if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(&s, fmt) {
2956 return Ok(DateTime::from_naive_utc_and_offset(dt, Utc));
2957 }
2958 }
2959 Err(Error::Protocol(format!("invalid timestamp: {}", s)))
2960}
2961
2962/// Parses a local timestamp string into a local [`DateTime`].
2963///
2964/// Converts a timestamp string from the Tor control protocol into a
2965/// [`DateTime<Local>`] value using the system's local timezone offset.
2966///
2967/// # Arguments
2968///
2969/// * `s` - The timestamp string (e.g., "2024-01-15 12:30:45")
2970///
2971/// # Returns
2972///
2973/// * `Ok(DateTime<Local>)` - The parsed local datetime
2974/// * `Err(Error::Protocol)` - If the timestamp format is invalid
2975///
2976/// # Supported Formats
2977///
2978/// - `YYYY-MM-DD HH:MM:SS` - Standard format
2979/// - `YYYY-MM-DD HH:MM:SS.fff` - With fractional seconds
2980///
2981/// # Note
2982///
2983/// The timestamp is interpreted as being in the local timezone at the
2984/// time of parsing. The current local timezone offset is applied.
2985fn parse_local_timestamp(s: &str) -> Result<DateTime<Local>, Error> {
2986 let formats = ["%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M:%S%.f"];
2987 for fmt in &formats {
2988 if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(s, fmt) {
2989 return Ok(DateTime::from_naive_utc_and_offset(
2990 dt,
2991 *Local::now().offset(),
2992 ));
2993 }
2994 }
2995 Err(Error::Protocol(format!("invalid local timestamp: {}", s)))
2996}
2997
2998/// Parses a UTC timestamp string into a UTC [`DateTime`].
2999///
3000/// This is an alias for [`parse_iso_timestamp`] that explicitly indicates
3001/// the timestamp should be interpreted as UTC.
3002///
3003/// # Arguments
3004///
3005/// * `s` - The timestamp string in ISO 8601 format
3006///
3007/// # Returns
3008///
3009/// * `Ok(DateTime<Utc>)` - The parsed UTC datetime
3010/// * `Err(Error::Protocol)` - If the timestamp format is invalid
3011///
3012/// # See Also
3013///
3014/// - [`parse_iso_timestamp`] - The underlying implementation
3015fn parse_utc_timestamp(s: &str) -> Result<DateTime<Utc>, Error> {
3016 parse_iso_timestamp(s)
3017}
3018
3019/// Enumeration of all parsed event types.
3020///
3021/// This enum provides a unified way to handle different event types
3022/// through pattern matching. Use [`ParsedEvent::parse`] to convert
3023/// raw event data into the appropriate variant.
3024///
3025/// # Parsing Events
3026///
3027/// Events are parsed from raw control protocol messages:
3028///
3029/// ```rust,ignore
3030/// use stem_rs::events::ParsedEvent;
3031///
3032/// let event = ParsedEvent::parse("BW", "1024 2048", None)?;
3033/// match event {
3034/// ParsedEvent::Bandwidth(bw) => {
3035/// println!("Read: {}, Written: {}", bw.read, bw.written);
3036/// }
3037/// _ => {}
3038/// }
3039/// ```
3040///
3041/// # Unknown Events
3042///
3043/// Events that don't match a known type are captured as
3044/// [`ParsedEvent::Unknown`], preserving the raw content for
3045/// debugging or custom handling.
3046///
3047/// # Display
3048///
3049/// All variants implement [`Display`](std::fmt::Display) to reconstruct
3050/// a human-readable representation of the event.
3051#[derive(Debug, Clone)]
3052pub enum ParsedEvent {
3053 /// Aggregate bandwidth event (BW).
3054 Bandwidth(BandwidthEvent),
3055 /// Log message event (DEBUG, INFO, NOTICE, WARN, ERR).
3056 Log(LogEvent),
3057 /// Circuit status change event (CIRC).
3058 Circuit(CircuitEvent),
3059 /// Stream status change event (STREAM).
3060 Stream(StreamEvent),
3061 /// OR connection status change event (ORCONN).
3062 OrConn(OrConnEvent),
3063 /// Address mapping event (ADDRMAP).
3064 AddrMap(AddrMapEvent),
3065 /// Circuit build timeout change event (BUILDTIMEOUT_SET).
3066 BuildTimeoutSet(BuildTimeoutSetEvent),
3067 /// Guard relay status change event (GUARD).
3068 Guard(GuardEvent),
3069 /// New descriptor available event (NEWDESC).
3070 NewDesc(NewDescEvent),
3071 /// Signal received event (SIGNAL).
3072 Signal(SignalEvent),
3073 /// Status event (STATUS_GENERAL, STATUS_CLIENT, STATUS_SERVER).
3074 Status(StatusEvent),
3075 /// Configuration changed event (CONF_CHANGED).
3076 ConfChanged(ConfChangedEvent),
3077 /// Network liveness event (NETWORK_LIVENESS).
3078 NetworkLiveness(NetworkLivenessEvent),
3079 /// Per-circuit bandwidth event (CIRC_BW).
3080 CircuitBandwidth(CircuitBandwidthEvent),
3081 /// Per-connection bandwidth event (CONN_BW).
3082 ConnectionBandwidth(ConnectionBandwidthEvent),
3083 /// Hidden service descriptor event (HS_DESC).
3084 HsDesc(HsDescEvent),
3085 /// Unknown or unrecognized event type.
3086 Unknown {
3087 /// The event type string.
3088 event_type: String,
3089 /// The raw event content.
3090 content: String,
3091 },
3092}
3093
3094impl ParsedEvent {
3095 /// Parses raw event data into a typed event.
3096 ///
3097 /// # Arguments
3098 ///
3099 /// * `event_type` - The event type keyword (e.g., "BW", "CIRC")
3100 /// * `content` - The event content after the type
3101 /// * `lines` - Optional multi-line content for events like CONF_CHANGED
3102 ///
3103 /// # Supported Event Types
3104 ///
3105 /// - `BW` - Bandwidth events
3106 /// - `DEBUG`, `INFO`, `NOTICE`, `WARN`, `ERR` - Log events
3107 /// - `CIRC` - Circuit events
3108 /// - `STREAM` - Stream events
3109 /// - `ORCONN` - OR connection events
3110 /// - `ADDRMAP` - Address map events
3111 /// - `BUILDTIMEOUT_SET` - Build timeout events
3112 /// - `GUARD` - Guard events
3113 /// - `NEWDESC` - New descriptor events
3114 /// - `SIGNAL` - Signal events
3115 /// - `STATUS_GENERAL`, `STATUS_CLIENT`, `STATUS_SERVER` - Status events
3116 /// - `CONF_CHANGED` - Configuration change events
3117 /// - `NETWORK_LIVENESS` - Network liveness events
3118 /// - `CIRC_BW` - Circuit bandwidth events
3119 /// - `CONN_BW` - Connection bandwidth events
3120 /// - `HS_DESC` - Hidden service descriptor events
3121 ///
3122 /// # Errors
3123 ///
3124 /// Returns [`Error::Protocol`] if the event content is malformed.
3125 /// Unknown event types are returned as [`ParsedEvent::Unknown`]
3126 /// rather than causing an error.
3127 ///
3128 /// # Example
3129 ///
3130 /// ```rust,ignore
3131 /// use stem_rs::events::ParsedEvent;
3132 ///
3133 /// // Parse a bandwidth event
3134 /// let event = ParsedEvent::parse("BW", "100 200", None)?;
3135 ///
3136 /// // Parse a circuit event
3137 /// let event = ParsedEvent::parse("CIRC", "1 BUILT $ABC...=relay", None)?;
3138 ///
3139 /// // Unknown events are captured, not rejected
3140 /// let event = ParsedEvent::parse("FUTURE_EVENT", "data", None)?;
3141 /// assert!(matches!(event, ParsedEvent::Unknown { .. }));
3142 /// ```
3143 pub fn parse(event_type: &str, content: &str, lines: Option<&[String]>) -> Result<Self, Error> {
3144 match event_type.to_uppercase().as_str() {
3145 "BW" => Ok(ParsedEvent::Bandwidth(BandwidthEvent::parse(content)?)),
3146 "DEBUG" => Ok(ParsedEvent::Log(LogEvent::parse(Runlevel::Debug, content)?)),
3147 "INFO" => Ok(ParsedEvent::Log(LogEvent::parse(Runlevel::Info, content)?)),
3148 "NOTICE" => Ok(ParsedEvent::Log(LogEvent::parse(
3149 Runlevel::Notice,
3150 content,
3151 )?)),
3152 "WARN" => Ok(ParsedEvent::Log(LogEvent::parse(Runlevel::Warn, content)?)),
3153 "ERR" => Ok(ParsedEvent::Log(LogEvent::parse(Runlevel::Err, content)?)),
3154 "CIRC" => Ok(ParsedEvent::Circuit(CircuitEvent::parse(content)?)),
3155 "STREAM" => Ok(ParsedEvent::Stream(StreamEvent::parse(content)?)),
3156 "ORCONN" => Ok(ParsedEvent::OrConn(OrConnEvent::parse(content)?)),
3157 "ADDRMAP" => Ok(ParsedEvent::AddrMap(AddrMapEvent::parse(content)?)),
3158 "BUILDTIMEOUT_SET" => Ok(ParsedEvent::BuildTimeoutSet(BuildTimeoutSetEvent::parse(
3159 content,
3160 )?)),
3161 "GUARD" => Ok(ParsedEvent::Guard(GuardEvent::parse(content)?)),
3162 "NEWDESC" => Ok(ParsedEvent::NewDesc(NewDescEvent::parse(content)?)),
3163 "SIGNAL" => Ok(ParsedEvent::Signal(SignalEvent::parse(content)?)),
3164 "STATUS_GENERAL" => Ok(ParsedEvent::Status(StatusEvent::parse(
3165 StatusType::General,
3166 content,
3167 )?)),
3168 "STATUS_CLIENT" => Ok(ParsedEvent::Status(StatusEvent::parse(
3169 StatusType::Client,
3170 content,
3171 )?)),
3172 "STATUS_SERVER" => Ok(ParsedEvent::Status(StatusEvent::parse(
3173 StatusType::Server,
3174 content,
3175 )?)),
3176 "CONF_CHANGED" => {
3177 let lines = lines.unwrap_or(&[]);
3178 Ok(ParsedEvent::ConfChanged(ConfChangedEvent::parse(lines)?))
3179 }
3180 "NETWORK_LIVENESS" => Ok(ParsedEvent::NetworkLiveness(NetworkLivenessEvent::parse(
3181 content,
3182 )?)),
3183 "CIRC_BW" => Ok(ParsedEvent::CircuitBandwidth(CircuitBandwidthEvent::parse(
3184 content,
3185 )?)),
3186 "CONN_BW" => Ok(ParsedEvent::ConnectionBandwidth(
3187 ConnectionBandwidthEvent::parse(content)?,
3188 )),
3189 "HS_DESC" => Ok(ParsedEvent::HsDesc(HsDescEvent::parse(content)?)),
3190 _ => Ok(ParsedEvent::Unknown {
3191 event_type: event_type.to_string(),
3192 content: content.to_string(),
3193 }),
3194 }
3195 }
3196
3197 /// Returns the event type string for this event.
3198 ///
3199 /// This returns the canonical event type keyword as used in
3200 /// `SETEVENTS` commands and event responses.
3201 ///
3202 /// # Example
3203 ///
3204 /// ```rust,ignore
3205 /// let event = ParsedEvent::parse("BW", "100 200", None)?;
3206 /// assert_eq!(event.event_type(), "BW");
3207 /// ```
3208 pub fn event_type(&self) -> &str {
3209 match self {
3210 ParsedEvent::Bandwidth(_) => "BW",
3211 ParsedEvent::Log(e) => match e.runlevel {
3212 Runlevel::Debug => "DEBUG",
3213 Runlevel::Info => "INFO",
3214 Runlevel::Notice => "NOTICE",
3215 Runlevel::Warn => "WARN",
3216 Runlevel::Err => "ERR",
3217 },
3218 ParsedEvent::Circuit(_) => "CIRC",
3219 ParsedEvent::Stream(_) => "STREAM",
3220 ParsedEvent::OrConn(_) => "ORCONN",
3221 ParsedEvent::AddrMap(_) => "ADDRMAP",
3222 ParsedEvent::BuildTimeoutSet(_) => "BUILDTIMEOUT_SET",
3223 ParsedEvent::Guard(_) => "GUARD",
3224 ParsedEvent::NewDesc(_) => "NEWDESC",
3225 ParsedEvent::Signal(_) => "SIGNAL",
3226 ParsedEvent::Status(e) => match e.status_type {
3227 StatusType::General => "STATUS_GENERAL",
3228 StatusType::Client => "STATUS_CLIENT",
3229 StatusType::Server => "STATUS_SERVER",
3230 },
3231 ParsedEvent::ConfChanged(_) => "CONF_CHANGED",
3232 ParsedEvent::NetworkLiveness(_) => "NETWORK_LIVENESS",
3233 ParsedEvent::CircuitBandwidth(_) => "CIRC_BW",
3234 ParsedEvent::ConnectionBandwidth(_) => "CONN_BW",
3235 ParsedEvent::HsDesc(_) => "HS_DESC",
3236 ParsedEvent::Unknown { event_type, .. } => event_type,
3237 }
3238 }
3239}
3240
3241impl std::fmt::Display for ParsedEvent {
3242 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3243 match self {
3244 ParsedEvent::Bandwidth(e) => write!(f, "650 BW {} {}", e.read, e.written),
3245 ParsedEvent::Log(e) => write!(f, "650 {} {}", e.runlevel, e.message),
3246 ParsedEvent::Circuit(e) => write!(f, "650 CIRC {} {}", e.id, e.status),
3247 ParsedEvent::Stream(e) => write!(f, "650 STREAM {} {}", e.id, e.status),
3248 ParsedEvent::OrConn(e) => write!(f, "650 ORCONN {} {}", e.target, e.status),
3249 ParsedEvent::AddrMap(e) => {
3250 write!(
3251 f,
3252 "650 ADDRMAP {} {}",
3253 e.hostname,
3254 e.destination.as_deref().unwrap_or("<error>")
3255 )
3256 }
3257 ParsedEvent::BuildTimeoutSet(e) => write!(f, "650 BUILDTIMEOUT_SET {:?}", e.set_type),
3258 ParsedEvent::Guard(e) => {
3259 write!(f, "650 GUARD {} {} {}", e.guard_type, e.endpoint, e.status)
3260 }
3261 ParsedEvent::NewDesc(e) => {
3262 let relays: Vec<String> = e
3263 .relays
3264 .iter()
3265 .map(|(fp, nick)| match nick {
3266 Some(n) => format!("{}~{}", fp, n),
3267 None => fp.clone(),
3268 })
3269 .collect();
3270 write!(f, "650 NEWDESC {}", relays.join(" "))
3271 }
3272 ParsedEvent::Signal(e) => write!(f, "650 SIGNAL {}", e.signal),
3273 ParsedEvent::Status(e) => write!(
3274 f,
3275 "650 STATUS_{} {} {}",
3276 e.status_type, e.runlevel, e.action
3277 ),
3278 ParsedEvent::ConfChanged(e) => {
3279 let changes: Vec<String> = e
3280 .changed
3281 .iter()
3282 .map(|(k, v)| format!("{}={}", k, v.join(",")))
3283 .collect();
3284 write!(f, "650 CONF_CHANGED {}", changes.join(" "))
3285 }
3286 ParsedEvent::NetworkLiveness(e) => write!(f, "650 NETWORK_LIVENESS {}", e.status),
3287 ParsedEvent::CircuitBandwidth(e) => {
3288 write!(f, "650 CIRC_BW {} {} {}", e.id, e.read, e.written)
3289 }
3290 ParsedEvent::ConnectionBandwidth(e) => write!(
3291 f,
3292 "650 CONN_BW {} {} {} {}",
3293 e.id, e.conn_type, e.read, e.written
3294 ),
3295 ParsedEvent::HsDesc(e) => write!(f, "650 HS_DESC {} {}", e.action, e.address),
3296 ParsedEvent::Unknown {
3297 event_type,
3298 content,
3299 } => write!(f, "650 {} {}", event_type, content),
3300 }
3301 }
3302}
3303
3304#[cfg(test)]
3305mod tests {
3306 use super::*;
3307 use chrono::{Datelike, Timelike};
3308
3309 #[test]
3310 fn test_bandwidth_event() {
3311 let event = BandwidthEvent::parse("15 25").unwrap();
3312 assert_eq!(event.read, 15);
3313 assert_eq!(event.written, 25);
3314 }
3315
3316 #[test]
3317 fn test_bandwidth_event_zero() {
3318 let event = BandwidthEvent::parse("0 0").unwrap();
3319 assert_eq!(event.read, 0);
3320 assert_eq!(event.written, 0);
3321 }
3322
3323 #[test]
3324 fn test_bandwidth_event_invalid_missing_values() {
3325 assert!(BandwidthEvent::parse("").is_err());
3326 assert!(BandwidthEvent::parse("15").is_err());
3327 }
3328
3329 #[test]
3330 fn test_bandwidth_event_invalid_non_numeric() {
3331 assert!(BandwidthEvent::parse("x 25").is_err());
3332 assert!(BandwidthEvent::parse("15 y").is_err());
3333 }
3334
3335 #[test]
3336 fn test_log_event() {
3337 let event = LogEvent::parse(Runlevel::Debug, "test message").unwrap();
3338 assert_eq!(event.runlevel, Runlevel::Debug);
3339 assert_eq!(event.message, "test message");
3340 }
3341
3342 #[test]
3343 fn test_log_event_debug() {
3344 let event = LogEvent::parse(
3345 Runlevel::Debug,
3346 "connection_edge_process_relay_cell(): Got an extended cell! Yay.",
3347 )
3348 .unwrap();
3349 assert_eq!(event.runlevel, Runlevel::Debug);
3350 assert_eq!(
3351 event.message,
3352 "connection_edge_process_relay_cell(): Got an extended cell! Yay."
3353 );
3354 }
3355
3356 #[test]
3357 fn test_log_event_info() {
3358 let event = LogEvent::parse(
3359 Runlevel::Info,
3360 "circuit_finish_handshake(): Finished building circuit hop:",
3361 )
3362 .unwrap();
3363 assert_eq!(event.runlevel, Runlevel::Info);
3364 }
3365
3366 #[test]
3367 fn test_log_event_warn() {
3368 let event = LogEvent::parse(Runlevel::Warn, "a multi-line\nwarning message").unwrap();
3369 assert_eq!(event.runlevel, Runlevel::Warn);
3370 assert_eq!(event.message, "a multi-line\nwarning message");
3371 }
3372
3373 #[test]
3374 fn test_circuit_event_launched() {
3375 let content = "7 LAUNCHED BUILD_FLAGS=NEED_CAPACITY PURPOSE=GENERAL TIME_CREATED=2012-11-08T16:48:38.417238";
3376 let event = CircuitEvent::parse(content).unwrap();
3377 assert_eq!(event.id.0, "7");
3378 assert_eq!(event.status, CircStatus::Launched);
3379 assert!(event.path.is_empty());
3380 assert_eq!(event.build_flags, Some(vec![CircBuildFlag::NeedCapacity]));
3381 assert_eq!(event.purpose, Some(CircPurpose::General));
3382 assert!(event.created.is_some());
3383 assert_eq!(event.reason, None);
3384 assert_eq!(event.remote_reason, None);
3385 assert_eq!(event.socks_username, None);
3386 assert_eq!(event.socks_password, None);
3387 }
3388
3389 #[test]
3390 fn test_circuit_event_extended() {
3391 let content = "7 EXTENDED $999A226EBED397F331B612FE1E4CFAE5C1F201BA=piyaz BUILD_FLAGS=NEED_CAPACITY PURPOSE=GENERAL";
3392 let event = CircuitEvent::parse(content).unwrap();
3393 assert_eq!(event.id.0, "7");
3394 assert_eq!(event.status, CircStatus::Extended);
3395 assert_eq!(event.path.len(), 1);
3396 assert_eq!(event.path[0].0, "999A226EBED397F331B612FE1E4CFAE5C1F201BA");
3397 assert_eq!(event.path[0].1, Some("piyaz".to_string()));
3398 }
3399
3400 #[test]
3401 fn test_circuit_event_failed() {
3402 let content = "5 FAILED $E57A476CD4DFBD99B4EE52A100A58610AD6E80B9=ergebnisoffen BUILD_FLAGS=NEED_CAPACITY PURPOSE=GENERAL REASON=DESTROYED REMOTE_REASON=OR_CONN_CLOSED";
3403 let event = CircuitEvent::parse(content).unwrap();
3404 assert_eq!(event.id.0, "5");
3405 assert_eq!(event.status, CircStatus::Failed);
3406 assert_eq!(event.reason, Some(CircClosureReason::Destroyed));
3407 assert_eq!(event.remote_reason, Some(CircClosureReason::OrConnClosed));
3408 }
3409
3410 #[test]
3411 fn test_circuit_event_with_credentials() {
3412 let content = r#"7 LAUNCHED SOCKS_USERNAME="It's a me, Mario!" SOCKS_PASSWORD="your princess is in another castle""#;
3413 let event = CircuitEvent::parse(content).unwrap();
3414 assert_eq!(event.id.0, "7");
3415 assert_eq!(event.status, CircStatus::Launched);
3416 assert_eq!(event.socks_username, Some("It's a me, Mario!".to_string()));
3417 assert_eq!(
3418 event.socks_password,
3419 Some("your princess is in another castle".to_string())
3420 );
3421 }
3422
3423 #[test]
3424 fn test_circuit_event_launched_old_format() {
3425 let content = "4 LAUNCHED";
3426 let event = CircuitEvent::parse(content).unwrap();
3427 assert_eq!(event.id.0, "4");
3428 assert_eq!(event.status, CircStatus::Launched);
3429 assert!(event.path.is_empty());
3430 assert_eq!(event.build_flags, None);
3431 assert_eq!(event.purpose, None);
3432 }
3433
3434 #[test]
3435 fn test_circuit_event_extended_old_format() {
3436 let content = "$E57A476CD4DFBD99B4EE52A100A58610AD6E80B9,hamburgerphone";
3437 let event = CircuitEvent::parse(&format!("1 EXTENDED {}", content)).unwrap();
3438 assert_eq!(event.id.0, "1");
3439 assert_eq!(event.status, CircStatus::Extended);
3440 }
3441
3442 #[test]
3443 fn test_circuit_event_built_old_format() {
3444 let content =
3445 "1 BUILT $E57A476CD4DFBD99B4EE52A100A58610AD6E80B9,hamburgerphone,PrivacyRepublic14";
3446 let event = CircuitEvent::parse(content).unwrap();
3447 assert_eq!(event.id.0, "1");
3448 assert_eq!(event.status, CircStatus::Built);
3449 }
3450
3451 #[test]
3452 fn test_stream_event_new() {
3453 let content = "18 NEW 0 encrypted.google.com:443 SOURCE_ADDR=127.0.0.1:47849 PURPOSE=USER";
3454 let event = StreamEvent::parse(content).unwrap();
3455 assert_eq!(event.id.0, "18");
3456 assert_eq!(event.status, StreamStatus::New);
3457 assert_eq!(event.circuit_id, None);
3458 assert_eq!(event.target_host, "encrypted.google.com");
3459 assert_eq!(event.target_port, 443);
3460 assert_eq!(event.source_addr, Some("127.0.0.1:47849".to_string()));
3461 assert_eq!(event.purpose, Some(StreamPurpose::User));
3462 }
3463
3464 #[test]
3465 fn test_stream_event_sentconnect() {
3466 let content = "18 SENTCONNECT 26 encrypted.google.com:443";
3467 let event = StreamEvent::parse(content).unwrap();
3468 assert_eq!(event.id.0, "18");
3469 assert_eq!(event.status, StreamStatus::SentConnect);
3470 assert_eq!(event.circuit_id, Some(CircuitId::new("26")));
3471 assert_eq!(event.target_host, "encrypted.google.com");
3472 assert_eq!(event.target_port, 443);
3473 assert_eq!(event.source_addr, None);
3474 assert_eq!(event.purpose, None);
3475 }
3476
3477 #[test]
3478 fn test_stream_event_remap() {
3479 let content = "18 REMAP 26 74.125.227.129:443 SOURCE=EXIT";
3480 let event = StreamEvent::parse(content).unwrap();
3481 assert_eq!(event.id.0, "18");
3482 assert_eq!(event.status, StreamStatus::Remap);
3483 assert_eq!(event.circuit_id, Some(CircuitId::new("26")));
3484 assert_eq!(event.target_host, "74.125.227.129");
3485 assert_eq!(event.target_port, 443);
3486 assert_eq!(event.source, Some(StreamSource::Exit));
3487 }
3488
3489 #[test]
3490 fn test_stream_event_succeeded() {
3491 let content = "18 SUCCEEDED 26 74.125.227.129:443";
3492 let event = StreamEvent::parse(content).unwrap();
3493 assert_eq!(event.id.0, "18");
3494 assert_eq!(event.status, StreamStatus::Succeeded);
3495 assert_eq!(event.circuit_id, Some(CircuitId::new("26")));
3496 assert_eq!(event.target_host, "74.125.227.129");
3497 assert_eq!(event.target_port, 443);
3498 }
3499
3500 #[test]
3501 fn test_stream_event_closed() {
3502 let content = "21 CLOSED 26 74.125.227.129:443 REASON=CONNRESET";
3503 let event = StreamEvent::parse(content).unwrap();
3504 assert_eq!(event.status, StreamStatus::Closed);
3505 assert_eq!(event.reason, Some(StreamClosureReason::ConnReset));
3506 }
3507
3508 #[test]
3509 fn test_stream_event_closed_done() {
3510 let content = "25 CLOSED 26 199.7.52.72:80 REASON=DONE";
3511 let event = StreamEvent::parse(content).unwrap();
3512 assert_eq!(event.id.0, "25");
3513 assert_eq!(event.status, StreamStatus::Closed);
3514 assert_eq!(event.reason, Some(StreamClosureReason::Done));
3515 }
3516
3517 #[test]
3518 fn test_stream_event_dir_fetch() {
3519 let content = "14 NEW 0 176.28.51.238.$649F2D0ACF418F7CFC6539AB2257EB2D5297BAFA.exit:443 SOURCE_ADDR=(Tor_internal):0 PURPOSE=DIR_FETCH";
3520 let event = StreamEvent::parse(content).unwrap();
3521 assert_eq!(event.id.0, "14");
3522 assert_eq!(event.status, StreamStatus::New);
3523 assert_eq!(event.circuit_id, None);
3524 assert_eq!(
3525 event.target_host,
3526 "176.28.51.238.$649F2D0ACF418F7CFC6539AB2257EB2D5297BAFA.exit"
3527 );
3528 assert_eq!(event.target_port, 443);
3529 assert_eq!(event.source_addr, Some("(Tor_internal):0".to_string()));
3530 assert_eq!(event.purpose, Some(StreamPurpose::DirFetch));
3531 }
3532
3533 #[test]
3534 fn test_stream_event_dns_request() {
3535 let content = "1113 NEW 0 www.google.com:0 SOURCE_ADDR=127.0.0.1:15297 PURPOSE=DNS_REQUEST";
3536 let event = StreamEvent::parse(content).unwrap();
3537 assert_eq!(event.id.0, "1113");
3538 assert_eq!(event.status, StreamStatus::New);
3539 assert_eq!(event.target_host, "www.google.com");
3540 assert_eq!(event.target_port, 0);
3541 assert_eq!(event.purpose, Some(StreamPurpose::DnsRequest));
3542 }
3543
3544 #[test]
3545 fn test_orconn_event_closed() {
3546 let content = "$A1130635A0CDA6F60C276FBF6994EFBD4ECADAB1~tama CLOSED REASON=DONE";
3547 let event = OrConnEvent::parse(content).unwrap();
3548 assert_eq!(
3549 event.target,
3550 "$A1130635A0CDA6F60C276FBF6994EFBD4ECADAB1~tama"
3551 );
3552 assert_eq!(event.status, OrStatus::Closed);
3553 assert_eq!(event.reason, Some(OrClosureReason::Done));
3554 assert_eq!(event.num_circuits, None);
3555 assert_eq!(event.id, None);
3556 }
3557
3558 #[test]
3559 fn test_orconn_event_connected() {
3560 let content = "127.0.0.1:9000 CONNECTED NCIRCS=20 ID=18";
3561 let event = OrConnEvent::parse(content).unwrap();
3562 assert_eq!(event.target, "127.0.0.1:9000");
3563 assert_eq!(event.status, OrStatus::Connected);
3564 assert_eq!(event.num_circuits, Some(20));
3565 assert_eq!(event.id, Some("18".to_string()));
3566 assert_eq!(event.reason, None);
3567 }
3568
3569 #[test]
3570 fn test_orconn_event_launched() {
3571 let content = "$7ED90E2833EE38A75795BA9237B0A4560E51E1A0=GreenDragon LAUNCHED";
3572 let event = OrConnEvent::parse(content).unwrap();
3573 assert_eq!(
3574 event.target,
3575 "$7ED90E2833EE38A75795BA9237B0A4560E51E1A0=GreenDragon"
3576 );
3577 assert_eq!(event.status, OrStatus::Launched);
3578 assert_eq!(event.reason, None);
3579 assert_eq!(event.num_circuits, None);
3580 }
3581
3582 #[test]
3583 fn test_addrmap_event() {
3584 let content =
3585 r#"www.atagar.com 75.119.206.243 "2012-11-19 00:50:13" EXPIRES="2012-11-19 08:50:13""#;
3586 let event = AddrMapEvent::parse(content).unwrap();
3587 assert_eq!(event.hostname, "www.atagar.com");
3588 assert_eq!(event.destination, Some("75.119.206.243".to_string()));
3589 assert!(event.expiry.is_some());
3590 assert_eq!(event.error, None);
3591 assert!(event.utc_expiry.is_some());
3592 }
3593
3594 #[test]
3595 fn test_addrmap_event_no_expiration() {
3596 let content = "www.atagar.com 75.119.206.243 NEVER";
3597 let event = AddrMapEvent::parse(content).unwrap();
3598 assert_eq!(event.hostname, "www.atagar.com");
3599 assert_eq!(event.destination, Some("75.119.206.243".to_string()));
3600 assert_eq!(event.expiry, None);
3601 assert_eq!(event.utc_expiry, None);
3602 }
3603
3604 #[test]
3605 fn test_addrmap_event_error() {
3606 let content = r#"www.atagar.com <error> "2012-11-19 00:50:13" error=yes EXPIRES="2012-11-19 08:50:13""#;
3607 let event = AddrMapEvent::parse(content).unwrap();
3608 assert_eq!(event.hostname, "www.atagar.com");
3609 assert_eq!(event.destination, None);
3610 assert_eq!(event.error, Some("yes".to_string()));
3611 }
3612
3613 #[test]
3614 fn test_addrmap_event_cached_yes() {
3615 let content = r#"example.com 192.0.43.10 "2013-04-03 22:31:22" EXPIRES="2013-04-03 20:31:22" CACHED="YES""#;
3616 let event = AddrMapEvent::parse(content).unwrap();
3617 assert_eq!(event.hostname, "example.com");
3618 assert_eq!(event.cached, Some(true));
3619 }
3620
3621 #[test]
3622 fn test_addrmap_event_cached_no() {
3623 let content = r#"example.com 192.0.43.10 "2013-04-03 22:29:11" EXPIRES="2013-04-03 20:29:11" CACHED="NO""#;
3624 let event = AddrMapEvent::parse(content).unwrap();
3625 assert_eq!(event.hostname, "example.com");
3626 assert_eq!(event.cached, Some(false));
3627 }
3628
3629 #[test]
3630 fn test_build_timeout_set_event() {
3631 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";
3632 let event = BuildTimeoutSetEvent::parse(content).unwrap();
3633 assert_eq!(event.set_type, TimeoutSetType::Computed);
3634 assert_eq!(event.total_times, Some(124));
3635 assert_eq!(event.timeout, Some(9019));
3636 assert_eq!(event.xm, Some(1375));
3637 assert!((event.alpha.unwrap() - 0.855662).abs() < 0.0001);
3638 assert!((event.quantile.unwrap() - 0.8).abs() < 0.0001);
3639 assert!((event.timeout_rate.unwrap() - 0.137097).abs() < 0.0001);
3640 assert_eq!(event.close_timeout, Some(21850));
3641 assert!((event.close_rate.unwrap() - 0.072581).abs() < 0.0001);
3642 }
3643
3644 #[test]
3645 fn test_build_timeout_set_event_invalid_total_times() {
3646 let content = "COMPUTED TOTAL_TIMES=one_twenty_four TIMEOUT_MS=9019";
3647 assert!(BuildTimeoutSetEvent::parse(content).is_err());
3648 }
3649
3650 #[test]
3651 fn test_build_timeout_set_event_invalid_quantile() {
3652 let content = "COMPUTED TOTAL_TIMES=124 CUTOFF_QUANTILE=zero_point_eight";
3653 assert!(BuildTimeoutSetEvent::parse(content).is_err());
3654 }
3655
3656 #[test]
3657 fn test_guard_event_new() {
3658 let content = "ENTRY $36B5DBA788246E8369DBAF58577C6BC044A9A374 NEW";
3659 let event = GuardEvent::parse(content).unwrap();
3660 assert_eq!(event.guard_type, GuardType::Entry);
3661 assert_eq!(event.endpoint, "$36B5DBA788246E8369DBAF58577C6BC044A9A374");
3662 assert_eq!(
3663 event.endpoint_fingerprint,
3664 "36B5DBA788246E8369DBAF58577C6BC044A9A374"
3665 );
3666 assert_eq!(event.endpoint_nickname, None);
3667 assert_eq!(event.status, GuardStatus::New);
3668 }
3669
3670 #[test]
3671 fn test_guard_event_good() {
3672 let content = "ENTRY $5D0034A368E0ABAF663D21847E1C9B6CFA09752A GOOD";
3673 let event = GuardEvent::parse(content).unwrap();
3674 assert_eq!(event.guard_type, GuardType::Entry);
3675 assert_eq!(
3676 event.endpoint_fingerprint,
3677 "5D0034A368E0ABAF663D21847E1C9B6CFA09752A"
3678 );
3679 assert_eq!(event.endpoint_nickname, None);
3680 assert_eq!(event.status, GuardStatus::Good);
3681 }
3682
3683 #[test]
3684 fn test_guard_event_bad() {
3685 let content = "ENTRY $5D0034A368E0ABAF663D21847E1C9B6CFA09752A=caerSidi BAD";
3686 let event = GuardEvent::parse(content).unwrap();
3687 assert_eq!(
3688 event.endpoint_fingerprint,
3689 "5D0034A368E0ABAF663D21847E1C9B6CFA09752A"
3690 );
3691 assert_eq!(event.endpoint_nickname, Some("caerSidi".to_string()));
3692 assert_eq!(event.status, GuardStatus::Bad);
3693 }
3694
3695 #[test]
3696 fn test_newdesc_event_single() {
3697 let content = "$B3FA3110CC6F42443F039220C134CBD2FC4F0493=Sakura";
3698 let event = NewDescEvent::parse(content).unwrap();
3699 assert_eq!(event.relays.len(), 1);
3700 assert_eq!(
3701 event.relays[0].0,
3702 "B3FA3110CC6F42443F039220C134CBD2FC4F0493"
3703 );
3704 assert_eq!(event.relays[0].1, Some("Sakura".to_string()));
3705 }
3706
3707 #[test]
3708 fn test_newdesc_event_multiple() {
3709 let content = "$BE938957B2CA5F804B3AFC2C1EE6673170CDBBF8=Moonshine $B4BE08B22D4D2923EDC3970FD1B93D0448C6D8FF~Unnamed";
3710 let event = NewDescEvent::parse(content).unwrap();
3711 assert_eq!(event.relays.len(), 2);
3712 assert_eq!(
3713 event.relays[0].0,
3714 "BE938957B2CA5F804B3AFC2C1EE6673170CDBBF8"
3715 );
3716 assert_eq!(event.relays[0].1, Some("Moonshine".to_string()));
3717 assert_eq!(
3718 event.relays[1].0,
3719 "B4BE08B22D4D2923EDC3970FD1B93D0448C6D8FF"
3720 );
3721 assert_eq!(event.relays[1].1, Some("Unnamed".to_string()));
3722 }
3723
3724 #[test]
3725 fn test_signal_event() {
3726 let event = SignalEvent::parse("DEBUG").unwrap();
3727 assert_eq!(event.signal, Signal::Debug);
3728
3729 let event = SignalEvent::parse("DUMP").unwrap();
3730 assert_eq!(event.signal, Signal::Dump);
3731 }
3732
3733 #[test]
3734 fn test_signal_event_all_signals() {
3735 assert_eq!(SignalEvent::parse("RELOAD").unwrap().signal, Signal::Reload);
3736 assert_eq!(SignalEvent::parse("HUP").unwrap().signal, Signal::Reload);
3737 assert_eq!(
3738 SignalEvent::parse("SHUTDOWN").unwrap().signal,
3739 Signal::Shutdown
3740 );
3741 assert_eq!(SignalEvent::parse("INT").unwrap().signal, Signal::Shutdown);
3742 assert_eq!(SignalEvent::parse("DUMP").unwrap().signal, Signal::Dump);
3743 assert_eq!(SignalEvent::parse("USR1").unwrap().signal, Signal::Dump);
3744 assert_eq!(SignalEvent::parse("DEBUG").unwrap().signal, Signal::Debug);
3745 assert_eq!(SignalEvent::parse("USR2").unwrap().signal, Signal::Debug);
3746 assert_eq!(SignalEvent::parse("HALT").unwrap().signal, Signal::Halt);
3747 assert_eq!(SignalEvent::parse("TERM").unwrap().signal, Signal::Halt);
3748 assert_eq!(SignalEvent::parse("NEWNYM").unwrap().signal, Signal::Newnym);
3749 assert_eq!(
3750 SignalEvent::parse("CLEARDNSCACHE").unwrap().signal,
3751 Signal::ClearDnsCache
3752 );
3753 assert_eq!(
3754 SignalEvent::parse("HEARTBEAT").unwrap().signal,
3755 Signal::Heartbeat
3756 );
3757 assert_eq!(SignalEvent::parse("ACTIVE").unwrap().signal, Signal::Active);
3758 assert_eq!(
3759 SignalEvent::parse("DORMANT").unwrap().signal,
3760 Signal::Dormant
3761 );
3762 }
3763
3764 #[test]
3765 fn test_status_event() {
3766 let content = "NOTICE CONSENSUS_ARRIVED";
3767 let event = StatusEvent::parse(StatusType::General, content).unwrap();
3768 assert_eq!(event.status_type, StatusType::General);
3769 assert_eq!(event.runlevel, Runlevel::Notice);
3770 assert_eq!(event.action, "CONSENSUS_ARRIVED");
3771 }
3772
3773 #[test]
3774 fn test_status_event_enough_dir_info() {
3775 let content = "NOTICE ENOUGH_DIR_INFO";
3776 let event = StatusEvent::parse(StatusType::Client, content).unwrap();
3777 assert_eq!(event.status_type, StatusType::Client);
3778 assert_eq!(event.runlevel, Runlevel::Notice);
3779 assert_eq!(event.action, "ENOUGH_DIR_INFO");
3780 }
3781
3782 #[test]
3783 fn test_status_event_circuit_established() {
3784 let content = "NOTICE CIRCUIT_ESTABLISHED";
3785 let event = StatusEvent::parse(StatusType::Client, content).unwrap();
3786 assert_eq!(event.status_type, StatusType::Client);
3787 assert_eq!(event.runlevel, Runlevel::Notice);
3788 assert_eq!(event.action, "CIRCUIT_ESTABLISHED");
3789 }
3790
3791 #[test]
3792 fn test_status_event_with_args() {
3793 let content = "NOTICE BOOTSTRAP PROGRESS=53 TAG=loading_descriptors SUMMARY=\"Loading relay descriptors\"";
3794 let event = StatusEvent::parse(StatusType::Client, content).unwrap();
3795 assert_eq!(event.status_type, StatusType::Client);
3796 assert_eq!(event.action, "BOOTSTRAP");
3797 assert_eq!(event.arguments.get("PROGRESS"), Some(&"53".to_string()));
3798 assert_eq!(
3799 event.arguments.get("TAG"),
3800 Some(&"loading_descriptors".to_string())
3801 );
3802 assert_eq!(
3803 event.arguments.get("SUMMARY"),
3804 Some(&"Loading relay descriptors".to_string())
3805 );
3806 }
3807
3808 #[test]
3809 fn test_status_event_bootstrap_stuck() {
3810 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";
3811 let event = StatusEvent::parse(StatusType::Client, content).unwrap();
3812 assert_eq!(event.status_type, StatusType::Client);
3813 assert_eq!(event.runlevel, Runlevel::Warn);
3814 assert_eq!(event.action, "BOOTSTRAP");
3815 assert_eq!(event.arguments.get("PROGRESS"), Some(&"80".to_string()));
3816 assert_eq!(event.arguments.get("TAG"), Some(&"conn_or".to_string()));
3817 assert_eq!(
3818 event.arguments.get("WARNING"),
3819 Some(&"Network is unreachable".to_string())
3820 );
3821 assert_eq!(event.arguments.get("REASON"), Some(&"NOROUTE".to_string()));
3822 assert_eq!(event.arguments.get("COUNT"), Some(&"5".to_string()));
3823 assert_eq!(
3824 event.arguments.get("RECOMMENDATION"),
3825 Some(&"warn".to_string())
3826 );
3827 }
3828
3829 #[test]
3830 fn test_status_event_bootstrap_done() {
3831 let content = "NOTICE BOOTSTRAP PROGRESS=100 TAG=done SUMMARY=\"Done\"";
3832 let event = StatusEvent::parse(StatusType::Client, content).unwrap();
3833 assert_eq!(event.arguments.get("PROGRESS"), Some(&"100".to_string()));
3834 assert_eq!(event.arguments.get("TAG"), Some(&"done".to_string()));
3835 assert_eq!(event.arguments.get("SUMMARY"), Some(&"Done".to_string()));
3836 }
3837
3838 #[test]
3839 fn test_status_event_server_check_reachability() {
3840 let content = "NOTICE CHECKING_REACHABILITY ORADDRESS=71.35.143.230:9050";
3841 let event = StatusEvent::parse(StatusType::Server, content).unwrap();
3842 assert_eq!(event.status_type, StatusType::Server);
3843 assert_eq!(event.runlevel, Runlevel::Notice);
3844 assert_eq!(event.action, "CHECKING_REACHABILITY");
3845 assert_eq!(
3846 event.arguments.get("ORADDRESS"),
3847 Some(&"71.35.143.230:9050".to_string())
3848 );
3849 }
3850
3851 #[test]
3852 fn test_status_event_dns_timeout() {
3853 let content =
3854 "NOTICE NAMESERVER_STATUS NS=205.171.3.25 STATUS=DOWN ERR=\"request timed out.\"";
3855 let event = StatusEvent::parse(StatusType::Server, content).unwrap();
3856 assert_eq!(event.action, "NAMESERVER_STATUS");
3857 assert_eq!(event.arguments.get("NS"), Some(&"205.171.3.25".to_string()));
3858 assert_eq!(event.arguments.get("STATUS"), Some(&"DOWN".to_string()));
3859 assert_eq!(
3860 event.arguments.get("ERR"),
3861 Some(&"request timed out.".to_string())
3862 );
3863 }
3864
3865 #[test]
3866 fn test_status_event_dns_down() {
3867 let content = "WARN NAMESERVER_ALL_DOWN";
3868 let event = StatusEvent::parse(StatusType::Server, content).unwrap();
3869 assert_eq!(event.status_type, StatusType::Server);
3870 assert_eq!(event.runlevel, Runlevel::Warn);
3871 assert_eq!(event.action, "NAMESERVER_ALL_DOWN");
3872 }
3873
3874 #[test]
3875 fn test_status_event_dns_up() {
3876 let content = "NOTICE NAMESERVER_STATUS NS=205.171.3.25 STATUS=UP";
3877 let event = StatusEvent::parse(StatusType::Server, content).unwrap();
3878 assert_eq!(event.action, "NAMESERVER_STATUS");
3879 assert_eq!(event.arguments.get("STATUS"), Some(&"UP".to_string()));
3880 }
3881
3882 #[test]
3883 fn test_conf_changed_event() {
3884 let lines = vec![
3885 "ExitNodes=caerSidi".to_string(),
3886 "ExitPolicy".to_string(),
3887 "MaxCircuitDirtiness=20".to_string(),
3888 ];
3889 let event = ConfChangedEvent::parse(&lines).unwrap();
3890 assert_eq!(
3891 event.changed.get("ExitNodes"),
3892 Some(&vec!["caerSidi".to_string()])
3893 );
3894 assert_eq!(
3895 event.changed.get("MaxCircuitDirtiness"),
3896 Some(&vec!["20".to_string()])
3897 );
3898 assert_eq!(event.unset, vec!["ExitPolicy".to_string()]);
3899 }
3900
3901 #[test]
3902 fn test_conf_changed_event_multiple_values() {
3903 let lines = vec![
3904 "ExitPolicy=accept 34.3.4.5".to_string(),
3905 "ExitPolicy=accept 3.4.53.3".to_string(),
3906 "MaxCircuitDirtiness=20".to_string(),
3907 ];
3908 let event = ConfChangedEvent::parse(&lines).unwrap();
3909 assert_eq!(
3910 event.changed.get("ExitPolicy"),
3911 Some(&vec![
3912 "accept 34.3.4.5".to_string(),
3913 "accept 3.4.53.3".to_string()
3914 ])
3915 );
3916 assert_eq!(
3917 event.changed.get("MaxCircuitDirtiness"),
3918 Some(&vec!["20".to_string()])
3919 );
3920 assert!(event.unset.is_empty());
3921 }
3922
3923 #[test]
3924 fn test_network_liveness_event() {
3925 let event = NetworkLivenessEvent::parse("UP").unwrap();
3926 assert_eq!(event.status, "UP");
3927
3928 let event = NetworkLivenessEvent::parse("DOWN").unwrap();
3929 assert_eq!(event.status, "DOWN");
3930 }
3931
3932 #[test]
3933 fn test_network_liveness_event_other_status() {
3934 let event = NetworkLivenessEvent::parse("OTHER_STATUS key=value").unwrap();
3935 assert_eq!(event.status, "OTHER_STATUS");
3936 }
3937
3938 #[test]
3939 fn test_circuit_bandwidth_event() {
3940 let content = "ID=11 READ=272 WRITTEN=817";
3941 let event = CircuitBandwidthEvent::parse(content).unwrap();
3942 assert_eq!(event.id.0, "11");
3943 assert_eq!(event.read, 272);
3944 assert_eq!(event.written, 817);
3945 assert_eq!(event.time, None);
3946 }
3947
3948 #[test]
3949 fn test_circuit_bandwidth_event_with_time() {
3950 let content = "ID=11 READ=272 WRITTEN=817 TIME=2012-12-06T13:51:11.433755";
3951 let event = CircuitBandwidthEvent::parse(content).unwrap();
3952 assert_eq!(event.id.0, "11");
3953 assert!(event.time.is_some());
3954 }
3955
3956 #[test]
3957 fn test_circuit_bandwidth_event_invalid_written() {
3958 let content = "ID=11 READ=272 WRITTEN=817.7";
3959 assert!(CircuitBandwidthEvent::parse(content).is_err());
3960 }
3961
3962 #[test]
3963 fn test_circuit_bandwidth_event_missing_id() {
3964 let content = "READ=272 WRITTEN=817";
3965 assert!(CircuitBandwidthEvent::parse(content).is_err());
3966 }
3967
3968 #[test]
3969 fn test_connection_bandwidth_event() {
3970 let content = "ID=11 TYPE=DIR READ=272 WRITTEN=817";
3971 let event = ConnectionBandwidthEvent::parse(content).unwrap();
3972 assert_eq!(event.id, "11");
3973 assert_eq!(event.conn_type, ConnectionType::Dir);
3974 assert_eq!(event.read, 272);
3975 assert_eq!(event.written, 817);
3976 }
3977
3978 #[test]
3979 fn test_connection_bandwidth_event_invalid_written() {
3980 let content = "ID=11 TYPE=DIR READ=272 WRITTEN=817.7";
3981 assert!(ConnectionBandwidthEvent::parse(content).is_err());
3982 }
3983
3984 #[test]
3985 fn test_connection_bandwidth_event_missing_id() {
3986 let content = "TYPE=DIR READ=272 WRITTEN=817";
3987 assert!(ConnectionBandwidthEvent::parse(content).is_err());
3988 }
3989
3990 #[test]
3991 fn test_hs_desc_event() {
3992 let content = "REQUESTED ajhb7kljbiru65qo NO_AUTH $67B2BDA4264D8A189D9270E28B1D30A262838243=europa1 b3oeducbhjmbqmgw2i3jtz4fekkrinwj";
3993 let event = HsDescEvent::parse(content).unwrap();
3994 assert_eq!(event.action, HsDescAction::Requested);
3995 assert_eq!(event.address, "ajhb7kljbiru65qo");
3996 assert_eq!(event.authentication, Some(HsAuth::NoAuth));
3997 assert_eq!(
3998 event.directory,
3999 Some("$67B2BDA4264D8A189D9270E28B1D30A262838243=europa1".to_string())
4000 );
4001 assert_eq!(
4002 event.directory_fingerprint,
4003 Some("67B2BDA4264D8A189D9270E28B1D30A262838243".to_string())
4004 );
4005 assert_eq!(event.directory_nickname, Some("europa1".to_string()));
4006 assert_eq!(
4007 event.descriptor_id,
4008 Some("b3oeducbhjmbqmgw2i3jtz4fekkrinwj".to_string())
4009 );
4010 assert_eq!(event.reason, None);
4011 }
4012
4013 #[test]
4014 fn test_hs_desc_event_no_desc_id() {
4015 let content =
4016 "REQUESTED ajhb7kljbiru65qo NO_AUTH $67B2BDA4264D8A189D9270E28B1D30A262838243";
4017 let event = HsDescEvent::parse(content).unwrap();
4018 assert_eq!(
4019 event.directory,
4020 Some("$67B2BDA4264D8A189D9270E28B1D30A262838243".to_string())
4021 );
4022 assert_eq!(
4023 event.directory_fingerprint,
4024 Some("67B2BDA4264D8A189D9270E28B1D30A262838243".to_string())
4025 );
4026 assert_eq!(event.directory_nickname, None);
4027 assert_eq!(event.descriptor_id, None);
4028 assert_eq!(event.reason, None);
4029 }
4030
4031 #[test]
4032 fn test_hs_desc_event_not_found() {
4033 let content = "REQUESTED ajhb7kljbiru65qo NO_AUTH UNKNOWN";
4034 let event = HsDescEvent::parse(content).unwrap();
4035 assert_eq!(event.directory, None);
4036 assert_eq!(event.directory_fingerprint, None);
4037 assert_eq!(event.directory_nickname, None);
4038 assert_eq!(event.descriptor_id, None);
4039 assert_eq!(event.reason, None);
4040 }
4041
4042 #[test]
4043 fn test_hs_desc_event_failed() {
4044 let content = "FAILED ajhb7kljbiru65qo NO_AUTH $67B2BDA4264D8A189D9270E28B1D30A262838243 b3oeducbhjmbqmgw2i3jtz4fekkrinwj REASON=NOT_FOUND";
4045 let event = HsDescEvent::parse(content).unwrap();
4046 assert_eq!(event.action, HsDescAction::Failed);
4047 assert_eq!(event.address, "ajhb7kljbiru65qo");
4048 assert_eq!(event.authentication, Some(HsAuth::NoAuth));
4049 assert_eq!(
4050 event.directory,
4051 Some("$67B2BDA4264D8A189D9270E28B1D30A262838243".to_string())
4052 );
4053 assert_eq!(
4054 event.directory_fingerprint,
4055 Some("67B2BDA4264D8A189D9270E28B1D30A262838243".to_string())
4056 );
4057 assert_eq!(event.directory_nickname, None);
4058 assert_eq!(
4059 event.descriptor_id,
4060 Some("b3oeducbhjmbqmgw2i3jtz4fekkrinwj".to_string())
4061 );
4062 assert_eq!(event.reason, Some(HsDescReason::NotFound));
4063 }
4064
4065 #[test]
4066 fn test_parsed_event_dispatch() {
4067 let event = ParsedEvent::parse("BW", "100 200", None).unwrap();
4068 match event {
4069 ParsedEvent::Bandwidth(bw) => {
4070 assert_eq!(bw.read, 100);
4071 assert_eq!(bw.written, 200);
4072 }
4073 _ => panic!("expected bandwidth event"),
4074 }
4075
4076 let event = ParsedEvent::parse("CIRC", "1 BUILT", None).unwrap();
4077 match event {
4078 ParsedEvent::Circuit(circ) => {
4079 assert_eq!(circ.id.0, "1");
4080 assert_eq!(circ.status, CircStatus::Built);
4081 }
4082 _ => panic!("expected circuit event"),
4083 }
4084 }
4085
4086 #[test]
4087 fn test_parsed_event_log_events() {
4088 let event = ParsedEvent::parse("DEBUG", "test debug message", None).unwrap();
4089 match event {
4090 ParsedEvent::Log(log) => {
4091 assert_eq!(log.runlevel, Runlevel::Debug);
4092 assert_eq!(log.message, "test debug message");
4093 }
4094 _ => panic!("expected log event"),
4095 }
4096
4097 let event = ParsedEvent::parse("INFO", "test info message", None).unwrap();
4098 match event {
4099 ParsedEvent::Log(log) => {
4100 assert_eq!(log.runlevel, Runlevel::Info);
4101 }
4102 _ => panic!("expected log event"),
4103 }
4104
4105 let event = ParsedEvent::parse("NOTICE", "test notice message", None).unwrap();
4106 match event {
4107 ParsedEvent::Log(log) => {
4108 assert_eq!(log.runlevel, Runlevel::Notice);
4109 }
4110 _ => panic!("expected log event"),
4111 }
4112
4113 let event = ParsedEvent::parse("WARN", "test warn message", None).unwrap();
4114 match event {
4115 ParsedEvent::Log(log) => {
4116 assert_eq!(log.runlevel, Runlevel::Warn);
4117 }
4118 _ => panic!("expected log event"),
4119 }
4120
4121 let event = ParsedEvent::parse("ERR", "test error message", None).unwrap();
4122 match event {
4123 ParsedEvent::Log(log) => {
4124 assert_eq!(log.runlevel, Runlevel::Err);
4125 }
4126 _ => panic!("expected log event"),
4127 }
4128 }
4129
4130 #[test]
4131 fn test_parsed_event_status_events() {
4132 let event = ParsedEvent::parse("STATUS_GENERAL", "NOTICE CONSENSUS_ARRIVED", None).unwrap();
4133 match event {
4134 ParsedEvent::Status(status) => {
4135 assert_eq!(status.status_type, StatusType::General);
4136 assert_eq!(status.action, "CONSENSUS_ARRIVED");
4137 }
4138 _ => panic!("expected status event"),
4139 }
4140
4141 let event = ParsedEvent::parse("STATUS_CLIENT", "NOTICE ENOUGH_DIR_INFO", None).unwrap();
4142 match event {
4143 ParsedEvent::Status(status) => {
4144 assert_eq!(status.status_type, StatusType::Client);
4145 }
4146 _ => panic!("expected status event"),
4147 }
4148
4149 let event = ParsedEvent::parse(
4150 "STATUS_SERVER",
4151 "NOTICE CHECKING_REACHABILITY ORADDRESS=127.0.0.1:9050",
4152 None,
4153 )
4154 .unwrap();
4155 match event {
4156 ParsedEvent::Status(status) => {
4157 assert_eq!(status.status_type, StatusType::Server);
4158 }
4159 _ => panic!("expected status event"),
4160 }
4161 }
4162
4163 #[test]
4164 fn test_parsed_event_unknown() {
4165 let event = ParsedEvent::parse("UNKNOWN_EVENT", "some content", None).unwrap();
4166 match event {
4167 ParsedEvent::Unknown {
4168 event_type,
4169 content,
4170 } => {
4171 assert_eq!(event_type, "UNKNOWN_EVENT");
4172 assert_eq!(content, "some content");
4173 }
4174 _ => panic!("expected unknown event"),
4175 }
4176 }
4177
4178 #[test]
4179 fn test_parse_circuit_path() {
4180 let path = parse_circuit_path("$999A226EBED397F331B612FE1E4CFAE5C1F201BA=piyaz");
4181 assert_eq!(path.len(), 1);
4182 assert_eq!(path[0].0, "999A226EBED397F331B612FE1E4CFAE5C1F201BA");
4183 assert_eq!(path[0].1, Some("piyaz".to_string()));
4184
4185 let path = parse_circuit_path(
4186 "$E57A476CD4DFBD99B4EE52A100A58610AD6E80B9,hamburgerphone,PrivacyRepublic14",
4187 );
4188 assert_eq!(path.len(), 3);
4189 }
4190
4191 #[test]
4192 fn test_parse_relay_endpoint() {
4193 let (fp, nick) = parse_relay_endpoint("$36B5DBA788246E8369DBAF58577C6BC044A9A374");
4194 assert_eq!(fp, "36B5DBA788246E8369DBAF58577C6BC044A9A374");
4195 assert_eq!(nick, None);
4196
4197 let (fp, nick) = parse_relay_endpoint("$5D0034A368E0ABAF663D21847E1C9B6CFA09752A=caerSidi");
4198 assert_eq!(fp, "5D0034A368E0ABAF663D21847E1C9B6CFA09752A");
4199 assert_eq!(nick, Some("caerSidi".to_string()));
4200
4201 let (fp, nick) = parse_relay_endpoint("$B4BE08B22D4D2923EDC3970FD1B93D0448C6D8FF~Unnamed");
4202 assert_eq!(fp, "B4BE08B22D4D2923EDC3970FD1B93D0448C6D8FF");
4203 assert_eq!(nick, Some("Unnamed".to_string()));
4204 }
4205
4206 #[test]
4207 fn test_parse_target() {
4208 let (host, port) = parse_target("encrypted.google.com:443").unwrap();
4209 assert_eq!(host, "encrypted.google.com");
4210 assert_eq!(port, 443);
4211
4212 let (host, port) = parse_target("74.125.227.129:443").unwrap();
4213 assert_eq!(host, "74.125.227.129");
4214 assert_eq!(port, 443);
4215
4216 let (host, port) = parse_target("www.google.com:0").unwrap();
4217 assert_eq!(host, "www.google.com");
4218 assert_eq!(port, 0);
4219 }
4220
4221 #[test]
4222 fn test_parse_iso_timestamp() {
4223 let dt = parse_iso_timestamp("2012-11-08T16:48:38.417238").unwrap();
4224 assert_eq!(dt.year(), 2012);
4225 assert_eq!(dt.month(), 11);
4226 assert_eq!(dt.day(), 8);
4227 assert_eq!(dt.hour(), 16);
4228 assert_eq!(dt.minute(), 48);
4229 assert_eq!(dt.second(), 38);
4230
4231 let dt = parse_iso_timestamp("2012-12-06T13:51:11.433755").unwrap();
4232 assert_eq!(dt.year(), 2012);
4233 assert_eq!(dt.month(), 12);
4234 assert_eq!(dt.day(), 6);
4235 }
4236
4237 #[test]
4238 fn test_parse_build_flags() {
4239 let flags = parse_build_flags("NEED_CAPACITY");
4240 assert_eq!(flags, vec![CircBuildFlag::NeedCapacity]);
4241
4242 let flags = parse_build_flags("IS_INTERNAL,NEED_CAPACITY");
4243 assert_eq!(
4244 flags,
4245 vec![CircBuildFlag::IsInternal, CircBuildFlag::NeedCapacity]
4246 );
4247
4248 let flags = parse_build_flags("ONEHOP_TUNNEL,IS_INTERNAL,NEED_CAPACITY,NEED_UPTIME");
4249 assert_eq!(
4250 flags,
4251 vec![
4252 CircBuildFlag::OneHopTunnel,
4253 CircBuildFlag::IsInternal,
4254 CircBuildFlag::NeedCapacity,
4255 CircBuildFlag::NeedUptime
4256 ]
4257 );
4258 }
4259}