stem_rs/controller.rs
1//! High-level controller API for Tor control protocol interaction.
2//!
3//! This module provides the primary interface for interacting with Tor's control
4//! protocol. The [`Controller`] type wraps a [`ControlSocket`]
5//! and provides high-level methods for common operations like authentication,
6//! circuit management, stream handling, and event subscription.
7//!
8//! # Overview
9//!
10//! The Controller is the main entry point for most stem-rs users. It handles:
11//!
12//! - **Connection Management**: Connect via TCP port or Unix domain socket
13//! - **Authentication**: Automatic method detection and credential handling
14//! - **Information Queries**: GETINFO commands for version, PID, circuit status, etc.
15//! - **Configuration**: GETCONF/SETCONF/RESETCONF for Tor configuration
16//! - **Circuit Control**: Create, extend, and close circuits
17//! - **Stream Control**: Attach and close streams
18//! - **Event Handling**: Subscribe to and receive asynchronous events
19//! - **Hidden Services**: Create and manage ephemeral hidden services
20//! - **Address Mapping**: Map addresses for custom routing
21//!
22//! # Conceptual Role
23//!
24//! The Controller sits between your application and Tor's control socket:
25//!
26//! ```text
27//! ┌─────────────┐ ┌────────────┐ ┌─────────────┐
28//! │ Application │ ──▶│ Controller │ ──▶│ Tor Process │
29//! └─────────────┘ └────────────┘ └─────────────┘
30//! │
31//! Handles:
32//! • Protocol formatting
33//! • Response parsing
34//! • Event buffering
35//! • Error handling
36//! ```
37//!
38//! # What This Module Does NOT Do
39//!
40//! - **Direct relay communication**: Use [`client::Relay`](crate::client::Relay) for ORPort connections
41//! - **Descriptor parsing**: Use the [`descriptor`](crate::descriptor) module
42//! - **Exit policy evaluation**: Use [`ExitPolicy`](crate::exit_policy::ExitPolicy)
43//!
44//! # Thread Safety
45//!
46//! [`Controller`] is `Send` but not `Sync`. The controller maintains internal
47//! state (socket, event buffer) that requires exclusive access. For concurrent
48//! access from multiple tasks, wrap in `Arc<Mutex<Controller>>`:
49//!
50//! ```rust,no_run
51//! use std::sync::Arc;
52//! use tokio::sync::Mutex;
53//! use stem_rs::controller::Controller;
54//!
55//! # async fn example() -> Result<(), stem_rs::Error> {
56//! let controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
57//! let shared = Arc::new(Mutex::new(controller));
58//!
59//! // Clone Arc for each task
60//! let c1 = shared.clone();
61//! tokio::spawn(async move {
62//! let mut ctrl = c1.lock().await;
63//! // Use controller...
64//! });
65//! # Ok(())
66//! # }
67//! ```
68//!
69//! # Example
70//!
71//! Basic usage pattern:
72//!
73//! ```rust,no_run
74//! use stem_rs::controller::Controller;
75//! use stem_rs::Signal;
76//!
77//! # async fn example() -> Result<(), stem_rs::Error> {
78//! // Connect to Tor's control port
79//! let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
80//!
81//! // Authenticate (auto-detects method)
82//! controller.authenticate(None).await?;
83//!
84//! // Query information
85//! let version = controller.get_version().await?;
86//! println!("Connected to Tor {}", version);
87//!
88//! // Get active circuits
89//! let circuits = controller.get_circuits().await?;
90//! for circuit in circuits {
91//! println!("Circuit {}: {:?}", circuit.id, circuit.status);
92//! }
93//!
94//! // Request new identity
95//! controller.signal(Signal::Newnym).await?;
96//! # Ok(())
97//! # }
98//! ```
99//!
100//! # Security Considerations
101//!
102//! - Passwords are not stored after authentication
103//! - Cookie files are read with minimal permissions
104//! - SAFECOOKIE authentication uses secure random nonces
105//! - Input is validated to prevent protocol injection attacks
106//!
107//! # See Also
108//!
109//! - [`socket`](crate::socket): Low-level socket communication
110//! - [`auth`]: Authentication implementation details
111//! - [`events`](crate::events): Event types for subscription
112//! - Python Stem's `Controller` class for equivalent functionality
113
114use std::collections::{HashMap, HashSet};
115use std::net::SocketAddr;
116use std::path::Path;
117use std::time::{SystemTime, UNIX_EPOCH};
118
119use crate::auth;
120use crate::events::ParsedEvent;
121use crate::protocol::ControlLine;
122use crate::socket::{ControlMessage, ControlSocket};
123use crate::version::Version;
124use crate::{CircStatus, Error, EventType, Signal, StreamStatus};
125
126/// Types of listeners that Tor can have.
127///
128/// These correspond to the different types of connections that Tor handles,
129/// each configured via different torrc options.
130///
131/// # Example
132///
133/// ```rust
134/// use stem_rs::controller::ListenerType;
135///
136/// let listener = ListenerType::Socks;
137/// assert_eq!(listener.to_string(), "socks");
138/// ```
139#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
140pub enum ListenerType {
141 /// Traffic we're relaying as a member of the network (ORPort)
142 Or,
143 /// Mirroring for tor descriptor content (DirPort)
144 Dir,
145 /// Client traffic we're sending over Tor (SocksPort)
146 Socks,
147 /// Transparent proxy handling (TransPort)
148 Trans,
149 /// Forwarding for ipfw NATD connections (NatdPort)
150 Natd,
151 /// DNS lookups for our traffic (DNSPort)
152 Dns,
153 /// Controller applications (ControlPort)
154 Control,
155 /// Pluggable transport for Extended ORPorts (ExtORPort)
156 ExtOr,
157 /// HTTP tunneling proxy (HTTPTunnelPort)
158 HttpTunnel,
159}
160
161impl std::fmt::Display for ListenerType {
162 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163 match self {
164 ListenerType::Or => write!(f, "or"),
165 ListenerType::Dir => write!(f, "dir"),
166 ListenerType::Socks => write!(f, "socks"),
167 ListenerType::Trans => write!(f, "trans"),
168 ListenerType::Natd => write!(f, "natd"),
169 ListenerType::Dns => write!(f, "dns"),
170 ListenerType::Control => write!(f, "control"),
171 ListenerType::ExtOr => write!(f, "extor"),
172 ListenerType::HttpTunnel => write!(f, "httptunnel"),
173 }
174 }
175}
176
177/// Purpose for a circuit.
178///
179/// Circuits can be created for different purposes, which affects how Tor
180/// uses them.
181///
182/// # Example
183///
184/// ```rust
185/// use stem_rs::controller::CircuitPurpose;
186///
187/// let purpose = CircuitPurpose::General;
188/// assert_eq!(purpose.to_string(), "general");
189/// ```
190#[derive(Debug, Clone, Copy, PartialEq, Eq)]
191pub enum CircuitPurpose {
192 /// General purpose circuit for normal traffic
193 General,
194 /// Circuit created and managed by a controller
195 Controller,
196}
197
198impl std::fmt::Display for CircuitPurpose {
199 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
200 match self {
201 CircuitPurpose::General => write!(f, "general"),
202 CircuitPurpose::Controller => write!(f, "controller"),
203 }
204 }
205}
206
207/// Protocol information returned by PROTOCOLINFO command.
208///
209/// Contains information about the Tor control protocol version,
210/// the Tor version, and available authentication methods.
211///
212/// # Example
213///
214/// ```rust,no_run
215/// use stem_rs::controller::Controller;
216///
217/// # async fn example() -> Result<(), stem_rs::Error> {
218/// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
219/// let info = controller.get_protocolinfo().await?;
220/// println!("Tor version: {}", info.tor_version);
221/// println!("Auth methods: {:?}", info.auth_methods);
222/// # Ok(())
223/// # }
224/// ```
225#[derive(Debug, Clone)]
226pub struct ProtocolInfo {
227 /// Protocol version (typically 1)
228 pub protocol_version: u32,
229 /// Tor version string
230 pub tor_version: String,
231 /// Available authentication methods
232 pub auth_methods: Vec<String>,
233 /// Path to cookie file (if cookie auth available)
234 pub cookie_file: Option<String>,
235}
236
237/// Accounting statistics for bandwidth limiting.
238///
239/// Contains information about Tor's accounting status when AccountingMax
240/// is set in the torrc. This includes read/write limits and current usage.
241///
242/// # Example
243///
244/// ```rust,no_run
245/// use stem_rs::controller::Controller;
246///
247/// # async fn example() -> Result<(), stem_rs::Error> {
248/// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
249/// controller.authenticate(None).await?;
250/// let stats = controller.get_accounting_stats().await?;
251/// println!("Status: {}", stats.status);
252/// println!("Read: {} bytes", stats.read_bytes);
253/// # Ok(())
254/// # }
255/// ```
256#[derive(Debug, Clone)]
257pub struct AccountingStats {
258 /// Unix timestamp when this was retrieved
259 pub retrieved: f64,
260 /// Hibernation status: "awake", "soft", or "hard"
261 pub status: String,
262 /// Time when accounting interval ends (ISO format string)
263 pub interval_end: Option<String>,
264 /// Seconds until limits reset
265 pub time_until_reset: u64,
266 /// Bytes read during this interval
267 pub read_bytes: u64,
268 /// Bytes remaining before read limit
269 pub read_bytes_left: u64,
270 /// Read limit in bytes
271 pub read_limit: u64,
272 /// Bytes written during this interval
273 pub written_bytes: u64,
274 /// Bytes remaining before write limit
275 pub write_bytes_left: u64,
276 /// Write limit in bytes
277 pub write_limit: u64,
278}
279
280/// A unique identifier for a Tor circuit.
281///
282/// Circuit IDs are assigned by Tor when circuits are created and are used
283/// to reference specific circuits in control protocol commands. The ID is
284/// a string representation of a numeric identifier.
285///
286/// # Invariants
287///
288/// - Circuit IDs are unique within a Tor session
289/// - IDs are assigned sequentially by Tor
290/// - An ID remains valid until the circuit is closed
291///
292/// # Example
293///
294/// ```rust
295/// use stem_rs::controller::CircuitId;
296///
297/// let id = CircuitId::new("42");
298/// assert_eq!(id.to_string(), "42");
299///
300/// // CircuitIds can be compared for equality
301/// let id2 = CircuitId::new("42");
302/// assert_eq!(id, id2);
303/// ```
304///
305/// # See Also
306///
307/// - [`Controller::get_circuits`]: Retrieve active circuits
308/// - [`Controller::new_circuit`]: Create a new circuit
309/// - [`Controller::close_circuit`]: Close a circuit by ID
310#[derive(Debug, Clone, PartialEq, Eq, Hash)]
311pub struct CircuitId(pub String);
312
313impl CircuitId {
314 /// Creates a new circuit ID from any string-like value.
315 ///
316 /// # Arguments
317 ///
318 /// * `id` - The circuit identifier, typically a numeric string
319 ///
320 /// # Example
321 ///
322 /// ```rust
323 /// use stem_rs::controller::CircuitId;
324 ///
325 /// let id = CircuitId::new("123");
326 /// let id_from_string = CircuitId::new(String::from("123"));
327 /// assert_eq!(id, id_from_string);
328 /// ```
329 pub fn new(id: impl Into<String>) -> Self {
330 Self(id.into())
331 }
332}
333
334impl std::fmt::Display for CircuitId {
335 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
336 write!(f, "{}", self.0)
337 }
338}
339
340/// A unique identifier for a Tor stream.
341///
342/// Stream IDs are assigned by Tor when streams are created and are used
343/// to reference specific streams in control protocol commands. Streams
344/// represent individual TCP connections being routed through Tor circuits.
345///
346/// # Invariants
347///
348/// - Stream IDs are unique within a Tor session
349/// - IDs are assigned sequentially by Tor
350/// - An ID remains valid until the stream is closed
351///
352/// # Example
353///
354/// ```rust
355/// use stem_rs::controller::StreamId;
356///
357/// let id = StreamId::new("99");
358/// assert_eq!(id.to_string(), "99");
359///
360/// // StreamIds can be compared for equality
361/// let id2 = StreamId::new("99");
362/// assert_eq!(id, id2);
363/// ```
364///
365/// # See Also
366///
367/// - [`Controller::get_streams`]: Retrieve active streams
368/// - [`Controller::attach_stream`]: Attach a stream to a circuit
369/// - [`Controller::close_stream`]: Close a stream by ID
370#[derive(Debug, Clone, PartialEq, Eq, Hash)]
371pub struct StreamId(pub String);
372
373impl StreamId {
374 /// Creates a new stream ID from any string-like value.
375 ///
376 /// # Arguments
377 ///
378 /// * `id` - The stream identifier, typically a numeric string
379 ///
380 /// # Example
381 ///
382 /// ```rust
383 /// use stem_rs::controller::StreamId;
384 ///
385 /// let id = StreamId::new("456");
386 /// let id_from_string = StreamId::new(String::from("456"));
387 /// assert_eq!(id, id_from_string);
388 /// ```
389 pub fn new(id: impl Into<String>) -> Self {
390 Self(id.into())
391 }
392}
393
394impl std::fmt::Display for StreamId {
395 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
396 write!(f, "{}", self.0)
397 }
398}
399
400/// Information about a relay in a circuit path.
401///
402/// Each hop in a Tor circuit is represented by a `RelayInfo` containing
403/// the relay's fingerprint and optionally its nickname. The fingerprint
404/// is a 40-character hexadecimal string representing the SHA-1 hash of
405/// the relay's identity key.
406///
407/// # Fields
408///
409/// - `fingerprint`: The relay's identity fingerprint (40 hex characters)
410/// - `nickname`: The relay's optional human-readable nickname
411///
412/// # Example
413///
414/// ```rust
415/// use stem_rs::controller::RelayInfo;
416///
417/// let relay = RelayInfo {
418/// fingerprint: "9695DFC35FFEB861329B9F1AB04C46397020CE31".to_string(),
419/// nickname: Some("MyRelay".to_string()),
420/// };
421///
422/// println!("Relay: {} ({:?})", relay.fingerprint, relay.nickname);
423/// ```
424///
425/// # See Also
426///
427/// - [`Circuit`]: Contains a path of `RelayInfo` entries
428/// - [`util::is_valid_fingerprint`](crate::util::is_valid_fingerprint): Validate fingerprint format
429#[derive(Debug, Clone)]
430pub struct RelayInfo {
431 /// The relay's identity fingerprint (40 hexadecimal characters).
432 ///
433 /// This is the SHA-1 hash of the relay's identity key, used to uniquely
434 /// identify relays across the Tor network.
435 pub fingerprint: String,
436
437 /// The relay's optional human-readable nickname.
438 ///
439 /// Nicknames are chosen by relay operators and are not guaranteed to be
440 /// unique. May be `None` if the nickname was not provided in the circuit
441 /// status response.
442 pub nickname: Option<String>,
443}
444
445/// Information about an active Tor circuit.
446///
447/// A circuit is a path through the Tor network consisting of multiple
448/// relay hops. Circuits are used to route traffic anonymously by encrypting
449/// data in layers that are peeled off at each hop.
450///
451/// # Circuit Lifecycle
452///
453/// Circuits progress through several states:
454///
455/// 1. **Launched**: Circuit creation has begun
456/// 2. **Extended**: Circuit is being extended to additional hops
457/// 3. **Built**: Circuit is fully constructed and ready for use
458/// 4. **Failed**: Circuit construction failed
459/// 5. **Closed**: Circuit has been closed
460///
461/// # Fields
462///
463/// - `id`: Unique identifier for this circuit
464/// - `status`: Current state of the circuit
465/// - `path`: Ordered list of relays in the circuit (guard → middle → exit)
466///
467/// # Example
468///
469/// ```rust,no_run
470/// use stem_rs::controller::Controller;
471/// use stem_rs::CircStatus;
472///
473/// # async fn example() -> Result<(), stem_rs::Error> {
474/// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
475/// controller.authenticate(None).await?;
476///
477/// for circuit in controller.get_circuits().await? {
478/// if circuit.status == CircStatus::Built {
479/// println!("Circuit {} has {} hops:", circuit.id, circuit.path.len());
480/// for (i, relay) in circuit.path.iter().enumerate() {
481/// println!(" Hop {}: {} ({:?})", i + 1, relay.fingerprint, relay.nickname);
482/// }
483/// }
484/// }
485/// # Ok(())
486/// # }
487/// ```
488///
489/// # See Also
490///
491/// - [`Controller::get_circuits`]: Retrieve all active circuits
492/// - [`Controller::new_circuit`]: Create a new circuit
493/// - [`CircStatus`]: Circuit status enumeration
494#[derive(Debug, Clone)]
495pub struct Circuit {
496 /// Unique identifier for this circuit.
497 pub id: CircuitId,
498
499 /// Current status of the circuit.
500 ///
501 /// See [`CircStatus`] for possible values.
502 pub status: CircStatus,
503
504 /// Ordered list of relays in the circuit path.
505 ///
506 /// The first relay is the guard (entry) node, and the last relay is
507 /// typically the exit node. The path may be empty for newly launched
508 /// circuits that haven't yet established any hops.
509 pub path: Vec<RelayInfo>,
510}
511
512/// Information about an active Tor stream.
513///
514/// A stream represents a single TCP connection being routed through a Tor
515/// circuit. Streams are created when applications connect through Tor's
516/// SOCKS proxy and are attached to circuits for routing.
517///
518/// # Stream Lifecycle
519///
520/// Streams progress through several states:
521///
522/// 1. **New**: Stream created, awaiting circuit attachment
523/// 2. **SentConnect**: CONNECT command sent to exit relay
524/// 3. **Succeeded**: Connection established successfully
525/// 4. **Failed**: Connection attempt failed
526/// 5. **Closed**: Stream has been closed
527///
528/// # Fields
529///
530/// - `id`: Unique identifier for this stream
531/// - `status`: Current state of the stream
532/// - `circuit_id`: The circuit this stream is attached to (if any)
533/// - `target_host`: Destination hostname or IP address
534/// - `target_port`: Destination port number
535///
536/// # Example
537///
538/// ```rust,no_run
539/// use stem_rs::controller::Controller;
540/// use stem_rs::StreamStatus;
541///
542/// # async fn example() -> Result<(), stem_rs::Error> {
543/// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
544/// controller.authenticate(None).await?;
545///
546/// for stream in controller.get_streams().await? {
547/// println!("Stream {} -> {}:{} ({:?})",
548/// stream.id,
549/// stream.target_host,
550/// stream.target_port,
551/// stream.status
552/// );
553/// if let Some(ref circuit_id) = stream.circuit_id {
554/// println!(" Attached to circuit {}", circuit_id);
555/// }
556/// }
557/// # Ok(())
558/// # }
559/// ```
560///
561/// # See Also
562///
563/// - [`Controller::get_streams`]: Retrieve all active streams
564/// - [`Controller::attach_stream`]: Attach a stream to a circuit
565/// - [`StreamStatus`]: Stream status enumeration
566#[derive(Debug, Clone)]
567pub struct Stream {
568 /// Unique identifier for this stream.
569 pub id: StreamId,
570
571 /// Current status of the stream.
572 ///
573 /// See [`StreamStatus`] for possible values.
574 pub status: StreamStatus,
575
576 /// The circuit this stream is attached to, if any.
577 ///
578 /// Streams in the `New` or `Detached` state may not be attached to
579 /// any circuit. Once attached, this field contains the circuit ID.
580 pub circuit_id: Option<CircuitId>,
581
582 /// Destination hostname or IP address.
583 ///
584 /// This is the target the stream is connecting to through Tor.
585 pub target_host: String,
586
587 /// Destination port number.
588 ///
589 /// The TCP port on the target host. May be 0 if not specified.
590 pub target_port: u16,
591}
592
593/// A high-level interface for interacting with Tor's control protocol.
594///
595/// The `Controller` provides the primary API for controlling a Tor process.
596/// It wraps a [`ControlSocket`] and provides
597/// typed methods for common operations like authentication, circuit management,
598/// and event subscription.
599///
600/// # Conceptual Role
601///
602/// The Controller is the main entry point for most stem-rs users. It handles:
603///
604/// - Protocol message formatting and parsing
605/// - Response validation and error handling
606/// - Asynchronous event buffering
607/// - Connection lifecycle management
608///
609/// # What This Type Does NOT Do
610///
611/// - Direct relay communication (use [`client::Relay`](crate::client::Relay))
612/// - Descriptor parsing (use [`descriptor`](crate::descriptor) module)
613/// - Exit policy evaluation (use [`ExitPolicy`](crate::exit_policy::ExitPolicy))
614///
615/// # Invariants
616///
617/// - The underlying socket connection is valid while the Controller exists
618/// - After successful authentication, the controller is ready for commands
619/// - Events received during command execution are buffered for later retrieval
620///
621/// # Thread Safety
622///
623/// `Controller` is `Send` but not `Sync`. For concurrent access from multiple
624/// tasks, wrap in `Arc<Mutex<Controller>>`:
625///
626/// ```rust,no_run
627/// use std::sync::Arc;
628/// use tokio::sync::Mutex;
629/// use stem_rs::controller::Controller;
630///
631/// # async fn example() -> Result<(), stem_rs::Error> {
632/// let controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
633/// let shared = Arc::new(Mutex::new(controller));
634///
635/// // Clone Arc for each task
636/// let c1 = shared.clone();
637/// tokio::spawn(async move {
638/// let mut ctrl = c1.lock().await;
639/// // Use controller...
640/// });
641/// # Ok(())
642/// # }
643/// ```
644///
645/// # Example
646///
647/// ```rust,no_run
648/// use stem_rs::controller::Controller;
649/// use stem_rs::Signal;
650///
651/// # async fn example() -> Result<(), stem_rs::Error> {
652/// // Connect and authenticate
653/// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
654/// controller.authenticate(Some("my_password")).await?;
655///
656/// // Query information
657/// let version = controller.get_version().await?;
658/// let circuits = controller.get_circuits().await?;
659///
660/// // Send signal
661/// controller.signal(Signal::Newnym).await?;
662/// # Ok(())
663/// # }
664/// ```
665///
666/// # Security
667///
668/// - Passwords are not stored after authentication
669/// - Cookie files are read with minimal permissions
670/// - SAFECOOKIE uses secure random nonces
671///
672/// # See Also
673///
674/// - [`from_port`](Controller::from_port): Connect via TCP
675/// - [`from_socket_file`](Controller::from_socket_file): Connect via Unix socket
676/// - [`authenticate`](Controller::authenticate): Authenticate with Tor
677pub struct Controller {
678 /// The underlying control socket connection.
679 socket: ControlSocket,
680 /// Buffer for asynchronous events received during command execution.
681 event_buffer: Vec<ControlMessage>,
682}
683
684impl Controller {
685 /// Creates a new Controller connected to a TCP control port.
686 ///
687 /// Establishes a TCP connection to Tor's control port at the specified
688 /// address. The connection is unauthenticated; call [`authenticate`](Self::authenticate)
689 /// before issuing commands.
690 ///
691 /// # Arguments
692 ///
693 /// * `addr` - The socket address of Tor's control port (e.g., `127.0.0.1:9051`)
694 ///
695 /// # Errors
696 ///
697 /// Returns [`Error::Socket`] if:
698 /// - The connection is refused (Tor not running or port incorrect)
699 /// - Network is unreachable
700 /// - Connection times out
701 ///
702 /// # Example
703 ///
704 /// ```rust,no_run
705 /// use stem_rs::controller::Controller;
706 ///
707 /// # async fn example() -> Result<(), stem_rs::Error> {
708 /// // Connect to default control port
709 /// let controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
710 ///
711 /// // Connect to custom port
712 /// let controller = Controller::from_port("127.0.0.1:9151".parse()?).await?;
713 /// # Ok(())
714 /// # }
715 /// ```
716 ///
717 /// # See Also
718 ///
719 /// - [`from_socket_file`](Self::from_socket_file): Connect via Unix socket
720 /// - [`authenticate`](Self::authenticate): Authenticate after connecting
721 pub async fn from_port(addr: SocketAddr) -> Result<Self, Error> {
722 let socket = ControlSocket::connect_port(addr).await?;
723 Ok(Self {
724 socket,
725 event_buffer: Vec::new(),
726 })
727 }
728
729 /// Creates a new Controller connected to a Unix domain socket.
730 ///
731 /// Establishes a connection to Tor's control socket at the specified
732 /// file path. This is typically more secure than TCP as it doesn't
733 /// expose the control interface to the network.
734 ///
735 /// # Arguments
736 ///
737 /// * `path` - Path to Tor's control socket file (e.g., `/var/run/tor/control`)
738 ///
739 /// # Errors
740 ///
741 /// Returns [`Error::Socket`] if:
742 /// - The socket file doesn't exist
743 /// - Permission denied accessing the socket
744 /// - The socket is not a valid Unix domain socket
745 ///
746 /// # Example
747 ///
748 /// ```rust,no_run
749 /// use std::path::Path;
750 /// use stem_rs::controller::Controller;
751 ///
752 /// # async fn example() -> Result<(), stem_rs::Error> {
753 /// // Connect to Tor's Unix control socket
754 /// let controller = Controller::from_socket_file(
755 /// Path::new("/var/run/tor/control")
756 /// ).await?;
757 /// # Ok(())
758 /// # }
759 /// ```
760 ///
761 /// # Platform Support
762 ///
763 /// Unix domain sockets are only available on Unix-like systems (Linux, macOS, BSD).
764 /// On Windows, use [`from_port`](Self::from_port) instead.
765 ///
766 /// # See Also
767 ///
768 /// - [`from_port`](Self::from_port): Connect via TCP
769 /// - [`authenticate`](Self::authenticate): Authenticate after connecting
770 pub async fn from_socket_file(path: &Path) -> Result<Self, Error> {
771 let socket = ControlSocket::connect_unix(path).await?;
772 Ok(Self {
773 socket,
774 event_buffer: Vec::new(),
775 })
776 }
777
778 /// Receives a response, buffering any asynchronous events.
779 ///
780 /// This internal method reads responses from the socket, automatically
781 /// buffering any asynchronous events (status code 650) that arrive
782 /// while waiting for a command response.
783 async fn recv_response(&mut self) -> Result<ControlMessage, Error> {
784 loop {
785 let response = self.socket.recv().await?;
786 if response.status_code == 650 {
787 self.event_buffer.push(response);
788 } else {
789 return Ok(response);
790 }
791 }
792 }
793
794 /// Authenticates with the Tor control interface.
795 ///
796 /// Attempts authentication using the best available method. If `password`
797 /// is provided, PASSWORD authentication is attempted. Otherwise, the method
798 /// is auto-detected from PROTOCOLINFO.
799 ///
800 /// # Authentication Methods
801 ///
802 /// Methods are tried in this order:
803 /// 1. **NONE** - If control port is open (no auth required)
804 /// 2. **SAFECOOKIE** - Preferred for local connections
805 /// 3. **COOKIE** - Fallback for older Tor versions
806 /// 4. **PASSWORD** - If password is provided
807 ///
808 /// # Arguments
809 ///
810 /// * `password` - Optional password for PASSWORD authentication
811 ///
812 /// # Preconditions
813 ///
814 /// - Socket must be connected (not closed)
815 /// - No prior successful authentication on this connection
816 ///
817 /// # Postconditions
818 ///
819 /// - On success: Controller is authenticated and ready for commands
820 /// - On failure: Connection state is undefined; reconnect recommended
821 ///
822 /// # Errors
823 ///
824 /// Returns [`Error::Authentication`] with specific reason:
825 ///
826 /// - [`AuthError::NoMethods`](crate::AuthError::NoMethods) - No compatible auth methods available
827 /// - [`AuthError::IncorrectPassword`](crate::AuthError::IncorrectPassword) - PASSWORD auth failed
828 /// - [`AuthError::CookieUnreadable`](crate::AuthError::CookieUnreadable) - Cannot read cookie file
829 /// - [`AuthError::IncorrectCookie`](crate::AuthError::IncorrectCookie) - COOKIE auth failed
830 /// - [`AuthError::ChallengeFailed`](crate::AuthError::ChallengeFailed) - SAFECOOKIE challenge failed
831 ///
832 /// # Example
833 ///
834 /// ```rust,no_run
835 /// use stem_rs::controller::Controller;
836 ///
837 /// # async fn example() -> Result<(), stem_rs::Error> {
838 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
839 ///
840 /// // Auto-detect authentication method
841 /// controller.authenticate(None).await?;
842 ///
843 /// // Or use password authentication
844 /// controller.authenticate(Some("my_password")).await?;
845 /// # Ok(())
846 /// # }
847 /// ```
848 ///
849 /// # Security
850 ///
851 /// - Passwords are cleared from memory after use
852 /// - Cookie comparison uses constant-time algorithm
853 /// - SAFECOOKIE nonces are cryptographically random
854 ///
855 /// # See Also
856 ///
857 /// - [`auth`]: Authentication implementation details
858 /// - [`AuthError`](crate::AuthError): Authentication error types
859 pub async fn authenticate(&mut self, password: Option<&str>) -> Result<(), Error> {
860 auth::authenticate(&mut self.socket, password).await
861 }
862
863 /// Queries Tor for information using the GETINFO command.
864 ///
865 /// GETINFO retrieves various pieces of information from Tor. The available
866 /// keys depend on Tor's version and configuration.
867 ///
868 /// # Arguments
869 ///
870 /// * `key` - The information key to query (e.g., "version", "circuit-status")
871 ///
872 /// # Common Keys
873 ///
874 /// | Key | Description |
875 /// |-----|-------------|
876 /// | `version` | Tor version string |
877 /// | `process/pid` | Tor process ID |
878 /// | `circuit-status` | Active circuit information |
879 /// | `stream-status` | Active stream information |
880 /// | `address` | Best guess at external IP address |
881 /// | `fingerprint` | Relay fingerprint (if running as relay) |
882 /// | `config-file` | Path to torrc file |
883 ///
884 /// # Errors
885 ///
886 /// Returns [`Error::OperationFailed`] if:
887 /// - The key is unrecognized
888 /// - The information is not available
889 /// - Tor returns an error response
890 ///
891 /// # Example
892 ///
893 /// ```rust,no_run
894 /// use stem_rs::controller::Controller;
895 ///
896 /// # async fn example() -> Result<(), stem_rs::Error> {
897 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
898 /// controller.authenticate(None).await?;
899 ///
900 /// // Query Tor version
901 /// let version = controller.get_info("version").await?;
902 /// println!("Tor version: {}", version);
903 ///
904 /// // Query external IP address
905 /// let address = controller.get_info("address").await?;
906 /// println!("External IP: {}", address);
907 /// # Ok(())
908 /// # }
909 /// ```
910 ///
911 /// # See Also
912 ///
913 /// - [`get_version`](Self::get_version): Typed version query
914 /// - [`get_pid`](Self::get_pid): Typed PID query
915 pub async fn get_info(&mut self, key: &str) -> Result<String, Error> {
916 let command = format!("GETINFO {}", key);
917 self.socket.send(&command).await?;
918 let response = self.recv_response().await?;
919
920 if !response.is_ok() {
921 return Err(Error::OperationFailed {
922 code: response.status_code.to_string(),
923 message: response.content().to_string(),
924 });
925 }
926
927 for line in &response.lines {
928 if let Some(rest) = line.strip_prefix(&format!("{}=", key)) {
929 return Ok(rest.to_string());
930 }
931 if line.starts_with(&format!("{}\n", key)) {
932 return Ok(line
933 .strip_prefix(&format!("{}\n", key))
934 .unwrap_or("")
935 .to_string());
936 }
937 }
938
939 Ok(response.content().to_string())
940 }
941
942 /// Retrieves the Tor version as a parsed [`Version`] object.
943 ///
944 /// This is a convenience wrapper around [`get_info("version")`](Self::get_info)
945 /// that parses the version string into a structured [`Version`] type.
946 ///
947 /// # Errors
948 ///
949 /// Returns an error if:
950 /// - The GETINFO command fails
951 /// - The version string cannot be parsed
952 ///
953 /// # Example
954 ///
955 /// ```rust,no_run
956 /// use stem_rs::controller::Controller;
957 ///
958 /// # async fn example() -> Result<(), stem_rs::Error> {
959 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
960 /// controller.authenticate(None).await?;
961 ///
962 /// let version = controller.get_version().await?;
963 /// println!("Tor version: {}", version);
964 ///
965 /// // Version supports comparison
966 /// // if version >= Version::parse("0.4.0.0")? { ... }
967 /// # Ok(())
968 /// # }
969 /// ```
970 ///
971 /// # See Also
972 ///
973 /// - [`Version`]: Version type with comparison support
974 pub async fn get_version(&mut self) -> Result<Version, Error> {
975 let version_str = self.get_info("version").await?;
976 Version::parse(&version_str)
977 }
978
979 /// Retrieves the process ID of the Tor process.
980 ///
981 /// This is a convenience wrapper around [`get_info("process/pid")`](Self::get_info)
982 /// that parses the PID into a `u32`.
983 ///
984 /// # Errors
985 ///
986 /// Returns an error if:
987 /// - The GETINFO command fails
988 /// - The PID string cannot be parsed as a number
989 ///
990 /// # Example
991 ///
992 /// ```rust,no_run
993 /// use stem_rs::controller::Controller;
994 ///
995 /// # async fn example() -> Result<(), stem_rs::Error> {
996 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
997 /// controller.authenticate(None).await?;
998 ///
999 /// let pid = controller.get_pid().await?;
1000 /// println!("Tor PID: {}", pid);
1001 /// # Ok(())
1002 /// # }
1003 /// ```
1004 pub async fn get_pid(&mut self) -> Result<u32, Error> {
1005 let pid_str = self.get_info("process/pid").await?;
1006 pid_str.parse().map_err(|_| Error::Parse {
1007 location: "pid".to_string(),
1008 reason: format!("invalid pid: {}", pid_str),
1009 })
1010 }
1011
1012 /// Retrieves the value(s) of a Tor configuration option.
1013 ///
1014 /// Uses the GETCONF command to query Tor's current configuration.
1015 /// Some options can have multiple values, so this returns a `Vec<String>`.
1016 ///
1017 /// # Arguments
1018 ///
1019 /// * `key` - The configuration option name (e.g., "SocksPort", "ExitPolicy")
1020 ///
1021 /// # Errors
1022 ///
1023 /// Returns [`Error::OperationFailed`] if:
1024 /// - The configuration option is unrecognized
1025 /// - Tor returns an error response
1026 ///
1027 /// # Example
1028 ///
1029 /// ```rust,no_run
1030 /// use stem_rs::controller::Controller;
1031 ///
1032 /// # async fn example() -> Result<(), stem_rs::Error> {
1033 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
1034 /// controller.authenticate(None).await?;
1035 ///
1036 /// // Get SOCKS port configuration
1037 /// let socks_ports = controller.get_conf("SocksPort").await?;
1038 /// for port in socks_ports {
1039 /// println!("SOCKS port: {}", port);
1040 /// }
1041 /// # Ok(())
1042 /// # }
1043 /// ```
1044 ///
1045 /// # See Also
1046 ///
1047 /// - [`set_conf`](Self::set_conf): Set a configuration option
1048 /// - [`reset_conf`](Self::reset_conf): Reset to default value
1049 pub async fn get_conf(&mut self, key: &str) -> Result<Vec<String>, Error> {
1050 let command = format!("GETCONF {}", key);
1051 self.socket.send(&command).await?;
1052 let response = self.recv_response().await?;
1053
1054 if !response.is_ok() {
1055 return Err(Error::OperationFailed {
1056 code: response.status_code.to_string(),
1057 message: response.content().to_string(),
1058 });
1059 }
1060
1061 let mut values = Vec::new();
1062 for line in &response.lines {
1063 if let Some(rest) = line.strip_prefix(&format!("{}=", key)) {
1064 values.push(rest.to_string());
1065 } else if line
1066 .to_lowercase()
1067 .starts_with(&format!("{}=", key.to_lowercase()))
1068 {
1069 let eq_pos = line.find('=').unwrap_or(line.len());
1070 values.push(line[eq_pos + 1..].to_string());
1071 }
1072 }
1073
1074 if values.is_empty() && !response.lines.is_empty() {
1075 let first_line = &response.lines[0];
1076 if let Some(eq_pos) = first_line.find('=') {
1077 values.push(first_line[eq_pos + 1..].to_string());
1078 }
1079 }
1080
1081 Ok(values)
1082 }
1083
1084 /// Sets a Tor configuration option.
1085 ///
1086 /// Uses the SETCONF command to change Tor's configuration at runtime.
1087 /// The change takes effect immediately but is not persisted to the torrc
1088 /// file unless you call `save_conf`.
1089 ///
1090 /// # Arguments
1091 ///
1092 /// * `key` - The configuration option name
1093 /// * `value` - The new value for the option
1094 ///
1095 /// # Value Escaping
1096 ///
1097 /// Values containing spaces or quotes are automatically escaped. You don't
1098 /// need to handle quoting yourself.
1099 ///
1100 /// # Errors
1101 ///
1102 /// Returns [`Error::OperationFailed`] if:
1103 /// - The configuration option is unrecognized
1104 /// - The value is invalid for this option
1105 /// - The option cannot be changed at runtime
1106 /// - Tor returns an error response
1107 ///
1108 /// # Example
1109 ///
1110 /// ```rust,no_run
1111 /// use stem_rs::controller::Controller;
1112 ///
1113 /// # async fn example() -> Result<(), stem_rs::Error> {
1114 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
1115 /// controller.authenticate(None).await?;
1116 ///
1117 /// // Change bandwidth rate
1118 /// controller.set_conf("BandwidthRate", "1 MB").await?;
1119 ///
1120 /// // Enable a feature
1121 /// controller.set_conf("SafeLogging", "1").await?;
1122 /// # Ok(())
1123 /// # }
1124 /// ```
1125 ///
1126 /// # See Also
1127 ///
1128 /// - [`get_conf`](Self::get_conf): Get current configuration
1129 /// - [`reset_conf`](Self::reset_conf): Reset to default value
1130 pub async fn set_conf(&mut self, key: &str, value: &str) -> Result<(), Error> {
1131 let command = if value.contains(' ') || value.contains('"') {
1132 format!(
1133 "SETCONF {}=\"{}\"",
1134 key,
1135 value.replace('\\', "\\\\").replace('"', "\\\"")
1136 )
1137 } else {
1138 format!("SETCONF {}={}", key, value)
1139 };
1140 self.socket.send(&command).await?;
1141 let response = self.recv_response().await?;
1142
1143 if response.is_ok() {
1144 Ok(())
1145 } else {
1146 Err(Error::OperationFailed {
1147 code: response.status_code.to_string(),
1148 message: response.content().to_string(),
1149 })
1150 }
1151 }
1152
1153 /// Resets a Tor configuration option to its default value.
1154 ///
1155 /// Uses the RESETCONF command to restore a configuration option to its
1156 /// default value as if it were not set in the torrc file.
1157 ///
1158 /// # Arguments
1159 ///
1160 /// * `key` - The configuration option name to reset
1161 ///
1162 /// # Errors
1163 ///
1164 /// Returns [`Error::OperationFailed`] if:
1165 /// - The configuration option is unrecognized
1166 /// - The option cannot be reset at runtime
1167 /// - Tor returns an error response
1168 ///
1169 /// # Example
1170 ///
1171 /// ```rust,no_run
1172 /// use stem_rs::controller::Controller;
1173 ///
1174 /// # async fn example() -> Result<(), stem_rs::Error> {
1175 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
1176 /// controller.authenticate(None).await?;
1177 ///
1178 /// // Reset bandwidth rate to default
1179 /// controller.reset_conf("BandwidthRate").await?;
1180 /// # Ok(())
1181 /// # }
1182 /// ```
1183 ///
1184 /// # See Also
1185 ///
1186 /// - [`get_conf`](Self::get_conf): Get current configuration
1187 /// - [`set_conf`](Self::set_conf): Set a configuration option
1188 pub async fn reset_conf(&mut self, key: &str) -> Result<(), Error> {
1189 let command = format!("RESETCONF {}", key);
1190 self.socket.send(&command).await?;
1191 let response = self.recv_response().await?;
1192
1193 if response.is_ok() {
1194 Ok(())
1195 } else {
1196 Err(Error::OperationFailed {
1197 code: response.status_code.to_string(),
1198 message: response.content().to_string(),
1199 })
1200 }
1201 }
1202
1203 /// Saves the current configuration to the torrc file.
1204 ///
1205 /// This persists any configuration changes made via [`set_conf`](Self::set_conf)
1206 /// to Tor's configuration file, so they survive restarts.
1207 ///
1208 /// # Arguments
1209 ///
1210 /// * `force` - If `true`, overwrite the configuration even if it includes
1211 /// a `%include` clause. This is ignored if Tor doesn't support it.
1212 ///
1213 /// # Errors
1214 ///
1215 /// Returns [`Error::OperationFailed`] if:
1216 /// - Tor is unable to save the configuration file (e.g., permission denied)
1217 /// - The configuration file contains `%include` and `force` is `false`
1218 ///
1219 /// # Example
1220 ///
1221 /// ```rust,no_run
1222 /// use stem_rs::controller::Controller;
1223 ///
1224 /// # async fn example() -> Result<(), stem_rs::Error> {
1225 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
1226 /// controller.authenticate(None).await?;
1227 ///
1228 /// // Change a configuration option
1229 /// controller.set_conf("BandwidthRate", "1 MB").await?;
1230 ///
1231 /// // Save to torrc
1232 /// controller.save_conf(false).await?;
1233 ///
1234 /// // Force save even with %include
1235 /// controller.save_conf(true).await?;
1236 /// # Ok(())
1237 /// # }
1238 /// ```
1239 ///
1240 /// # See Also
1241 ///
1242 /// - [`set_conf`](Self::set_conf): Set a configuration option
1243 /// - [`get_conf`](Self::get_conf): Get current configuration
1244 pub async fn save_conf(&mut self, force: bool) -> Result<(), Error> {
1245 let command = if force { "SAVECONF FORCE" } else { "SAVECONF" };
1246 self.socket.send(command).await?;
1247 let response = self.recv_response().await?;
1248
1249 if response.is_ok() {
1250 Ok(())
1251 } else if response.status_code == 551 {
1252 Err(Error::OperationFailed {
1253 code: response.status_code.to_string(),
1254 message: response.content().to_string(),
1255 })
1256 } else {
1257 Err(Error::Protocol(format!(
1258 "SAVECONF returned unexpected response code: {}",
1259 response.status_code
1260 )))
1261 }
1262 }
1263
1264 /// Sends a signal to the Tor process.
1265 ///
1266 /// Signals control various aspects of Tor's behavior, from requesting
1267 /// new circuits to initiating shutdown.
1268 ///
1269 /// # Arguments
1270 ///
1271 /// * `signal` - The signal to send (see [`Signal`])
1272 ///
1273 /// # Available Signals
1274 ///
1275 /// | Signal | Description |
1276 /// |--------|-------------|
1277 /// | [`Reload`](crate::Signal::Reload) | Reload configuration (SIGHUP) |
1278 /// | [`Shutdown`](crate::Signal::Shutdown) | Controlled shutdown |
1279 /// | [`Dump`](crate::Signal::Dump) | Write statistics to disk |
1280 /// | [`Debug`](crate::Signal::Debug) | Switch to debug logging |
1281 /// | [`Halt`](crate::Signal::Halt) | Immediate shutdown (SIGTERM) |
1282 /// | [`Newnym`](crate::Signal::Newnym) | Request new circuits |
1283 /// | [`ClearDnsCache`](crate::Signal::ClearDnsCache) | Clear DNS cache |
1284 /// | [`Heartbeat`](crate::Signal::Heartbeat) | Trigger heartbeat log |
1285 /// | [`Active`](crate::Signal::Active) | Wake from dormant mode |
1286 /// | [`Dormant`](crate::Signal::Dormant) | Enter dormant mode |
1287 ///
1288 /// # Errors
1289 ///
1290 /// Returns [`Error::OperationFailed`] if:
1291 /// - The signal is not recognized
1292 /// - The signal cannot be sent (e.g., rate-limited NEWNYM)
1293 /// - Tor returns an error response
1294 ///
1295 /// # Example
1296 ///
1297 /// ```rust,no_run
1298 /// use stem_rs::controller::Controller;
1299 /// use stem_rs::Signal;
1300 ///
1301 /// # async fn example() -> Result<(), stem_rs::Error> {
1302 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
1303 /// controller.authenticate(None).await?;
1304 ///
1305 /// // Request new identity (new circuits)
1306 /// controller.signal(Signal::Newnym).await?;
1307 ///
1308 /// // Reload configuration
1309 /// controller.signal(Signal::Reload).await?;
1310 ///
1311 /// // Clear DNS cache
1312 /// controller.signal(Signal::ClearDnsCache).await?;
1313 /// # Ok(())
1314 /// # }
1315 /// ```
1316 ///
1317 /// # Rate Limiting
1318 ///
1319 /// The `Newnym` signal is rate-limited by Tor to prevent abuse. If called
1320 /// too frequently, Tor may delay the signal or return an error.
1321 ///
1322 /// # See Also
1323 ///
1324 /// - [`Signal`]: Signal enumeration
1325 pub async fn signal(&mut self, signal: Signal) -> Result<(), Error> {
1326 let command = format!("SIGNAL {}", signal);
1327 self.socket.send(&command).await?;
1328 let response = self.recv_response().await?;
1329
1330 if response.is_ok() {
1331 Ok(())
1332 } else {
1333 Err(Error::OperationFailed {
1334 code: response.status_code.to_string(),
1335 message: response.content().to_string(),
1336 })
1337 }
1338 }
1339
1340 /// Retrieves information about all active circuits.
1341 ///
1342 /// Returns a list of all circuits currently known to Tor, including
1343 /// their status and path information.
1344 ///
1345 /// # Errors
1346 ///
1347 /// Returns an error if:
1348 /// - The GETINFO command fails
1349 /// - The circuit status cannot be parsed
1350 ///
1351 /// # Example
1352 ///
1353 /// ```rust,no_run
1354 /// use stem_rs::controller::Controller;
1355 /// use stem_rs::CircStatus;
1356 ///
1357 /// # async fn example() -> Result<(), stem_rs::Error> {
1358 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
1359 /// controller.authenticate(None).await?;
1360 ///
1361 /// let circuits = controller.get_circuits().await?;
1362 /// for circuit in circuits {
1363 /// if circuit.status == CircStatus::Built {
1364 /// println!("Circuit {} is ready with {} hops",
1365 /// circuit.id, circuit.path.len());
1366 /// }
1367 /// }
1368 /// # Ok(())
1369 /// # }
1370 /// ```
1371 ///
1372 /// # See Also
1373 ///
1374 /// - [`Circuit`]: Circuit information structure
1375 /// - [`new_circuit`](Self::new_circuit): Create a new circuit
1376 /// - [`close_circuit`](Self::close_circuit): Close a circuit
1377 pub async fn get_circuits(&mut self) -> Result<Vec<Circuit>, Error> {
1378 let response_str = self.get_info("circuit-status").await?;
1379 parse_circuits(&response_str)
1380 }
1381
1382 /// Creates a new circuit, optionally with a specified path.
1383 ///
1384 /// If no path is specified, Tor will select relays automatically based
1385 /// on its path selection algorithm. If a path is provided, Tor will
1386 /// attempt to build a circuit through those specific relays.
1387 ///
1388 /// # Arguments
1389 ///
1390 /// * `path` - Optional list of relay fingerprints or nicknames for the circuit path
1391 ///
1392 /// # Path Specification
1393 ///
1394 /// Relays can be specified by:
1395 /// - Fingerprint: `$9695DFC35FFEB861329B9F1AB04C46397020CE31`
1396 /// - Nickname: `MyRelay`
1397 /// - Fingerprint with nickname: `$9695DFC35FFEB861329B9F1AB04C46397020CE31~MyRelay`
1398 ///
1399 /// # Errors
1400 ///
1401 /// Returns [`Error::OperationFailed`] if:
1402 /// - A specified relay is unknown or unavailable
1403 /// - The path is invalid (e.g., too short)
1404 /// - Circuit creation fails
1405 ///
1406 /// # Example
1407 ///
1408 /// ```rust,no_run
1409 /// use stem_rs::controller::Controller;
1410 ///
1411 /// # async fn example() -> Result<(), stem_rs::Error> {
1412 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
1413 /// controller.authenticate(None).await?;
1414 ///
1415 /// // Create circuit with automatic path selection
1416 /// let circuit_id = controller.new_circuit(None).await?;
1417 /// println!("Created circuit: {}", circuit_id);
1418 ///
1419 /// // Create circuit with specific path
1420 /// let path = &["$AAAA...", "$BBBB...", "$CCCC..."];
1421 /// let circuit_id = controller.new_circuit(Some(path)).await?;
1422 /// # Ok(())
1423 /// # }
1424 /// ```
1425 ///
1426 /// # See Also
1427 ///
1428 /// - [`extend_circuit`](Self::extend_circuit): Extend an existing circuit
1429 /// - [`close_circuit`](Self::close_circuit): Close a circuit
1430 /// - [`get_circuits`](Self::get_circuits): List active circuits
1431 pub async fn new_circuit(&mut self, path: Option<&[&str]>) -> Result<CircuitId, Error> {
1432 let command = match path {
1433 Some(relays) if !relays.is_empty() => {
1434 format!("EXTENDCIRCUIT 0 {}", relays.join(","))
1435 }
1436 _ => "EXTENDCIRCUIT 0".to_string(),
1437 };
1438 self.socket.send(&command).await?;
1439 let response = self.recv_response().await?;
1440
1441 if !response.is_ok() {
1442 return Err(Error::OperationFailed {
1443 code: response.status_code.to_string(),
1444 message: response.content().to_string(),
1445 });
1446 }
1447
1448 let content = response.content();
1449 let mut line = ControlLine::new(content);
1450 if line.is_next_mapping(Some("EXTENDED"), false) {
1451 let (_, circuit_id) = line.pop_mapping(false, false)?;
1452 return Ok(CircuitId::new(circuit_id));
1453 }
1454
1455 let circuit_id = line.pop(false, false)?;
1456 Ok(CircuitId::new(circuit_id))
1457 }
1458
1459 /// Extends an existing circuit by adding additional hops.
1460 ///
1461 /// Adds one or more relays to an existing circuit. The circuit must be
1462 /// in a state that allows extension (typically BUILT or EXTENDED).
1463 ///
1464 /// # Arguments
1465 ///
1466 /// * `id` - The circuit ID to extend
1467 /// * `path` - List of relay fingerprints or nicknames to add
1468 ///
1469 /// # Errors
1470 ///
1471 /// Returns [`Error::InvalidArguments`] if:
1472 /// - The path is empty
1473 ///
1474 /// Returns [`Error::CircuitExtensionFailed`] if:
1475 /// - The circuit doesn't exist
1476 /// - The circuit is in a state that doesn't allow extension
1477 /// - A specified relay is unknown or unavailable
1478 /// - The extension fails for any other reason
1479 ///
1480 /// # Example
1481 ///
1482 /// ```rust,no_run
1483 /// use stem_rs::controller::Controller;
1484 ///
1485 /// # async fn example() -> Result<(), stem_rs::Error> {
1486 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
1487 /// controller.authenticate(None).await?;
1488 ///
1489 /// // Create a circuit and extend it
1490 /// let circuit_id = controller.new_circuit(None).await?;
1491 /// controller.extend_circuit(&circuit_id, &["$DDDD..."]).await?;
1492 /// # Ok(())
1493 /// # }
1494 /// ```
1495 ///
1496 /// # See Also
1497 ///
1498 /// - [`new_circuit`](Self::new_circuit): Create a new circuit
1499 /// - [`close_circuit`](Self::close_circuit): Close a circuit
1500 pub async fn extend_circuit(&mut self, id: &CircuitId, path: &[&str]) -> Result<(), Error> {
1501 if path.is_empty() {
1502 return Err(Error::InvalidArguments("path cannot be empty".to_string()));
1503 }
1504 let command = format!("EXTENDCIRCUIT {} {}", id.0, path.join(","));
1505 self.socket.send(&command).await?;
1506 let response = self.recv_response().await?;
1507
1508 if response.is_ok() {
1509 Ok(())
1510 } else {
1511 Err(Error::CircuitExtensionFailed(
1512 response.content().to_string(),
1513 ))
1514 }
1515 }
1516
1517 /// Closes an existing circuit.
1518 ///
1519 /// Tears down the specified circuit, closing all streams attached to it.
1520 ///
1521 /// # Arguments
1522 ///
1523 /// * `id` - The circuit ID to close
1524 ///
1525 /// # Errors
1526 ///
1527 /// Returns [`Error::OperationFailed`] if:
1528 /// - The circuit doesn't exist
1529 /// - The circuit is already closed
1530 /// - Tor returns an error response
1531 ///
1532 /// # Example
1533 ///
1534 /// ```rust,no_run
1535 /// use stem_rs::controller::Controller;
1536 ///
1537 /// # async fn example() -> Result<(), stem_rs::Error> {
1538 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
1539 /// controller.authenticate(None).await?;
1540 ///
1541 /// // Create and then close a circuit
1542 /// let circuit_id = controller.new_circuit(None).await?;
1543 /// controller.close_circuit(&circuit_id).await?;
1544 /// # Ok(())
1545 /// # }
1546 /// ```
1547 ///
1548 /// # See Also
1549 ///
1550 /// - [`new_circuit`](Self::new_circuit): Create a new circuit
1551 /// - [`get_circuits`](Self::get_circuits): List active circuits
1552 pub async fn close_circuit(&mut self, id: &CircuitId) -> Result<(), Error> {
1553 let command = format!("CLOSECIRCUIT {}", id.0);
1554 self.socket.send(&command).await?;
1555 let response = self.recv_response().await?;
1556
1557 if response.is_ok() {
1558 Ok(())
1559 } else {
1560 Err(Error::OperationFailed {
1561 code: response.status_code.to_string(),
1562 message: response.content().to_string(),
1563 })
1564 }
1565 }
1566
1567 /// Retrieves information about all active streams.
1568 ///
1569 /// Returns a list of all streams currently known to Tor, including
1570 /// their status, target, and circuit attachment.
1571 ///
1572 /// # Errors
1573 ///
1574 /// Returns an error if:
1575 /// - The GETINFO command fails
1576 /// - The stream status cannot be parsed
1577 ///
1578 /// # Example
1579 ///
1580 /// ```rust,no_run
1581 /// use stem_rs::controller::Controller;
1582 /// use stem_rs::StreamStatus;
1583 ///
1584 /// # async fn example() -> Result<(), stem_rs::Error> {
1585 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
1586 /// controller.authenticate(None).await?;
1587 ///
1588 /// let streams = controller.get_streams().await?;
1589 /// for stream in streams {
1590 /// println!("Stream {} -> {}:{} ({:?})",
1591 /// stream.id, stream.target_host, stream.target_port, stream.status);
1592 /// }
1593 /// # Ok(())
1594 /// # }
1595 /// ```
1596 ///
1597 /// # See Also
1598 ///
1599 /// - [`Stream`]: Stream information structure
1600 /// - [`attach_stream`](Self::attach_stream): Attach a stream to a circuit
1601 /// - [`close_stream`](Self::close_stream): Close a stream
1602 pub async fn get_streams(&mut self) -> Result<Vec<Stream>, Error> {
1603 let response_str = self.get_info("stream-status").await?;
1604 parse_streams(&response_str)
1605 }
1606
1607 /// Attaches a stream to a specific circuit.
1608 ///
1609 /// Manually attaches a stream to a circuit. This is typically used when
1610 /// you want to control which circuit a stream uses, rather than letting
1611 /// Tor choose automatically.
1612 ///
1613 /// # Arguments
1614 ///
1615 /// * `stream_id` - The stream to attach
1616 /// * `circuit_id` - The circuit to attach the stream to
1617 ///
1618 /// # Preconditions
1619 ///
1620 /// - The stream must be in a state that allows attachment (typically NEW)
1621 /// - The circuit must be BUILT
1622 /// - The circuit's exit policy must allow the stream's target
1623 ///
1624 /// # Errors
1625 ///
1626 /// Returns [`Error::OperationFailed`] if:
1627 /// - The stream doesn't exist
1628 /// - The circuit doesn't exist
1629 /// - The stream is not in an attachable state
1630 /// - The circuit cannot handle the stream's target
1631 ///
1632 /// # Example
1633 ///
1634 /// ```rust,no_run
1635 /// use stem_rs::controller::{Controller, CircuitId, StreamId};
1636 ///
1637 /// # async fn example() -> Result<(), stem_rs::Error> {
1638 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
1639 /// controller.authenticate(None).await?;
1640 ///
1641 /// // Attach stream 1 to circuit 5
1642 /// let stream_id = StreamId::new("1");
1643 /// let circuit_id = CircuitId::new("5");
1644 /// controller.attach_stream(&stream_id, &circuit_id).await?;
1645 /// # Ok(())
1646 /// # }
1647 /// ```
1648 ///
1649 /// # See Also
1650 ///
1651 /// - [`get_streams`](Self::get_streams): List active streams
1652 /// - [`close_stream`](Self::close_stream): Close a stream
1653 pub async fn attach_stream(
1654 &mut self,
1655 stream_id: &StreamId,
1656 circuit_id: &CircuitId,
1657 ) -> Result<(), Error> {
1658 let command = format!("ATTACHSTREAM {} {}", stream_id.0, circuit_id.0);
1659 self.socket.send(&command).await?;
1660 let response = self.recv_response().await?;
1661
1662 if response.is_ok() {
1663 Ok(())
1664 } else {
1665 Err(Error::OperationFailed {
1666 code: response.status_code.to_string(),
1667 message: response.content().to_string(),
1668 })
1669 }
1670 }
1671
1672 /// Closes an existing stream.
1673 ///
1674 /// Terminates the specified stream with an optional reason code.
1675 ///
1676 /// # Arguments
1677 ///
1678 /// * `id` - The stream ID to close
1679 /// * `reason` - Optional reason code (defaults to 1 = MISC if not specified)
1680 ///
1681 /// # Reason Codes
1682 ///
1683 /// Common reason codes include:
1684 /// - 1: MISC (miscellaneous)
1685 /// - 2: RESOLVEFAILED (DNS resolution failed)
1686 /// - 3: CONNECTREFUSED (connection refused)
1687 /// - 4: EXITPOLICY (exit policy violation)
1688 /// - 5: DESTROY (circuit destroyed)
1689 /// - 6: DONE (stream finished normally)
1690 /// - 7: TIMEOUT (connection timeout)
1691 ///
1692 /// # Errors
1693 ///
1694 /// Returns [`Error::OperationFailed`] if:
1695 /// - The stream doesn't exist
1696 /// - The stream is already closed
1697 /// - Tor returns an error response
1698 ///
1699 /// # Example
1700 ///
1701 /// ```rust,no_run
1702 /// use stem_rs::controller::{Controller, StreamId};
1703 ///
1704 /// # async fn example() -> Result<(), stem_rs::Error> {
1705 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
1706 /// controller.authenticate(None).await?;
1707 ///
1708 /// // Close stream with default reason
1709 /// let stream_id = StreamId::new("1");
1710 /// controller.close_stream(&stream_id, None).await?;
1711 ///
1712 /// // Close stream with specific reason (DONE)
1713 /// controller.close_stream(&stream_id, Some(6)).await?;
1714 /// # Ok(())
1715 /// # }
1716 /// ```
1717 ///
1718 /// # See Also
1719 ///
1720 /// - [`get_streams`](Self::get_streams): List active streams
1721 /// - [`attach_stream`](Self::attach_stream): Attach a stream to a circuit
1722 pub async fn close_stream(&mut self, id: &StreamId, reason: Option<u8>) -> Result<(), Error> {
1723 let command = match reason {
1724 Some(r) => format!("CLOSESTREAM {} {}", id.0, r),
1725 None => format!("CLOSESTREAM {} 1", id.0),
1726 };
1727 self.socket.send(&command).await?;
1728 let response = self.recv_response().await?;
1729
1730 if response.is_ok() {
1731 Ok(())
1732 } else {
1733 Err(Error::OperationFailed {
1734 code: response.status_code.to_string(),
1735 message: response.content().to_string(),
1736 })
1737 }
1738 }
1739
1740 /// Maps one address to another for Tor connections.
1741 ///
1742 /// Creates an address mapping so that connections to the `from` address
1743 /// are redirected to the `to` address. This is useful for creating
1744 /// virtual addresses or redirecting traffic.
1745 ///
1746 /// # Arguments
1747 ///
1748 /// * `from` - The source address to map from
1749 /// * `to` - The destination address to map to
1750 ///
1751 /// # Returns
1752 ///
1753 /// Returns a `HashMap` containing the established mappings. The keys are
1754 /// the source addresses and values are the destination addresses.
1755 ///
1756 /// # Errors
1757 ///
1758 /// Returns [`Error::OperationFailed`] if:
1759 /// - The address format is invalid
1760 /// - The mapping cannot be created
1761 /// - Tor returns an error response
1762 ///
1763 /// # Example
1764 ///
1765 /// ```rust,no_run
1766 /// use stem_rs::controller::Controller;
1767 ///
1768 /// # async fn example() -> Result<(), stem_rs::Error> {
1769 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
1770 /// controller.authenticate(None).await?;
1771 ///
1772 /// // Map a hostname to a .onion address
1773 /// let mappings = controller.map_address(
1774 /// "www.example.com",
1775 /// "exampleonion.onion"
1776 /// ).await?;
1777 ///
1778 /// for (from, to) in mappings {
1779 /// println!("{} -> {}", from, to);
1780 /// }
1781 /// # Ok(())
1782 /// # }
1783 /// ```
1784 pub async fn map_address(
1785 &mut self,
1786 from: &str,
1787 to: &str,
1788 ) -> Result<HashMap<String, String>, Error> {
1789 let command = format!("MAPADDRESS {}={}", from, to);
1790 self.socket.send(&command).await?;
1791 let response = self.recv_response().await?;
1792
1793 if !response.is_ok() {
1794 return Err(Error::OperationFailed {
1795 code: response.status_code.to_string(),
1796 message: response.content().to_string(),
1797 });
1798 }
1799
1800 let mut mappings = HashMap::new();
1801 for line in &response.lines {
1802 if let Some(eq_pos) = line.find('=') {
1803 let key = line[..eq_pos].to_string();
1804 let value = line[eq_pos + 1..].to_string();
1805 mappings.insert(key, value);
1806 }
1807 }
1808 Ok(mappings)
1809 }
1810
1811 /// Subscribes to asynchronous events from Tor.
1812 ///
1813 /// Configures which event types Tor should send to this controller.
1814 /// Events are received via [`recv_event`](Self::recv_event).
1815 ///
1816 /// # Arguments
1817 ///
1818 /// * `events` - List of event types to subscribe to
1819 ///
1820 /// # Event Types
1821 ///
1822 /// Common event types include:
1823 /// - [`EventType::Circ`] - Circuit status changes
1824 /// - [`EventType::Stream`] - Stream status changes
1825 /// - [`EventType::Bw`] - Bandwidth usage
1826 /// - [`EventType::Notice`] - Notice-level log messages
1827 /// - [`EventType::Warn`] - Warning-level log messages
1828 ///
1829 /// # Errors
1830 ///
1831 /// Returns [`Error::OperationFailed`] if:
1832 /// - An event type is not recognized
1833 /// - Tor returns an error response
1834 ///
1835 /// # Example
1836 ///
1837 /// ```rust,no_run
1838 /// use stem_rs::controller::Controller;
1839 /// use stem_rs::EventType;
1840 ///
1841 /// # async fn example() -> Result<(), stem_rs::Error> {
1842 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
1843 /// controller.authenticate(None).await?;
1844 ///
1845 /// // Subscribe to circuit and bandwidth events
1846 /// controller.set_events(&[EventType::Circ, EventType::Bw]).await?;
1847 ///
1848 /// // Receive events
1849 /// loop {
1850 /// let event = controller.recv_event().await?;
1851 /// println!("Received event: {:?}", event);
1852 /// }
1853 /// # Ok(())
1854 /// # }
1855 /// ```
1856 ///
1857 /// # Clearing Subscriptions
1858 ///
1859 /// To stop receiving events, call with an empty slice:
1860 ///
1861 /// ```rust,no_run
1862 /// # use stem_rs::controller::Controller;
1863 /// # async fn example() -> Result<(), stem_rs::Error> {
1864 /// # let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
1865 /// controller.set_events(&[]).await?; // Clear all subscriptions
1866 /// # Ok(())
1867 /// # }
1868 /// ```
1869 ///
1870 /// # See Also
1871 ///
1872 /// - [`recv_event`](Self::recv_event): Receive subscribed events
1873 /// - [`EventType`]: Available event types
1874 /// - [`events`](crate::events): Event parsing module
1875 pub async fn set_events(&mut self, events: &[EventType]) -> Result<(), Error> {
1876 let event_names: Vec<String> = events.iter().map(|e| e.to_string()).collect();
1877 let command = if event_names.is_empty() {
1878 "SETEVENTS".to_string()
1879 } else {
1880 format!("SETEVENTS {}", event_names.join(" "))
1881 };
1882 self.socket.send(&command).await?;
1883 let response = self.recv_response().await?;
1884
1885 if response.is_ok() {
1886 Ok(())
1887 } else {
1888 Err(Error::OperationFailed {
1889 code: response.status_code.to_string(),
1890 message: response.content().to_string(),
1891 })
1892 }
1893 }
1894
1895 /// Receives the next asynchronous event from Tor.
1896 ///
1897 /// Blocks until an event is available. Events must first be subscribed
1898 /// to using [`set_events`](Self::set_events).
1899 ///
1900 /// # Event Buffering
1901 ///
1902 /// Events that arrive while waiting for command responses are automatically
1903 /// buffered and returned by subsequent calls to this method.
1904 ///
1905 /// # Errors
1906 ///
1907 /// Returns [`Error::Protocol`] if:
1908 /// - The received message is not an event (status code != 650)
1909 ///
1910 /// Returns [`Error::Socket`] if:
1911 /// - The connection is closed
1912 /// - A network error occurs
1913 ///
1914 /// # Example
1915 ///
1916 /// ```rust,no_run
1917 /// use stem_rs::controller::Controller;
1918 /// use stem_rs::EventType;
1919 /// use stem_rs::events::ParsedEvent;
1920 ///
1921 /// # async fn example() -> Result<(), stem_rs::Error> {
1922 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
1923 /// controller.authenticate(None).await?;
1924 ///
1925 /// // Subscribe to bandwidth events
1926 /// controller.set_events(&[EventType::Bw]).await?;
1927 ///
1928 /// // Receive and process events
1929 /// loop {
1930 /// match controller.recv_event().await? {
1931 /// ParsedEvent::Bandwidth(bw) => {
1932 /// println!("Bandwidth: {} read, {} written", bw.read, bw.written);
1933 /// }
1934 /// other => println!("Other event: {:?}", other),
1935 /// }
1936 /// }
1937 /// # Ok(())
1938 /// # }
1939 /// ```
1940 ///
1941 /// # See Also
1942 ///
1943 /// - [`set_events`](Self::set_events): Subscribe to events
1944 /// - [`ParsedEvent`]: Event types
1945 pub async fn recv_event(&mut self) -> Result<ParsedEvent, Error> {
1946 let response = if let Some(buffered) = self.event_buffer.pop() {
1947 buffered
1948 } else {
1949 self.socket.recv().await?
1950 };
1951
1952 if response.status_code != 650 {
1953 return Err(Error::Protocol(format!(
1954 "expected async event (650), got {}",
1955 response.status_code
1956 )));
1957 }
1958
1959 let content = response.content();
1960 let (event_type, event_content) = content.split_once(' ').unwrap_or((content, ""));
1961
1962 let lines: Vec<String> = response
1963 .lines
1964 .iter()
1965 .skip(1)
1966 .filter(|l| !l.is_empty() && *l != "OK")
1967 .cloned()
1968 .collect();
1969
1970 ParsedEvent::parse(event_type, event_content, Some(&lines))
1971 }
1972
1973 /// Sends a raw command to Tor and returns the response.
1974 ///
1975 /// This is a low-level method for sending arbitrary control protocol
1976 /// commands. For most use cases, prefer the typed methods like
1977 /// [`get_info`](Self::get_info), [`signal`](Self::signal), etc.
1978 ///
1979 /// # Arguments
1980 ///
1981 /// * `command` - The raw command string to send
1982 ///
1983 /// # Errors
1984 ///
1985 /// Returns [`Error::OperationFailed`] if:
1986 /// - Tor returns an error response
1987 ///
1988 /// Returns [`Error::Socket`] if:
1989 /// - The connection is closed
1990 /// - A network error occurs
1991 ///
1992 /// # Example
1993 ///
1994 /// ```rust,no_run
1995 /// use stem_rs::controller::Controller;
1996 ///
1997 /// # async fn example() -> Result<(), stem_rs::Error> {
1998 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
1999 /// controller.authenticate(None).await?;
2000 ///
2001 /// // Send a raw GETINFO command
2002 /// let response = controller.msg("GETINFO version").await?;
2003 /// println!("Raw response: {}", response);
2004 /// # Ok(())
2005 /// # }
2006 /// ```
2007 ///
2008 /// # See Also
2009 ///
2010 /// - [`get_info`](Self::get_info): Typed GETINFO wrapper
2011 /// - [`signal`](Self::signal): Typed SIGNAL wrapper
2012 pub async fn msg(&mut self, command: &str) -> Result<String, Error> {
2013 self.socket.send(command).await?;
2014 let response = self.recv_response().await?;
2015
2016 if !response.is_ok() {
2017 return Err(Error::OperationFailed {
2018 code: response.status_code.to_string(),
2019 message: response.content().to_string(),
2020 });
2021 }
2022
2023 Ok(response.raw_content())
2024 }
2025
2026 /// Creates an ephemeral hidden service.
2027 ///
2028 /// Unlike file-based hidden services, ephemeral services don't touch disk
2029 /// and are the recommended way to create hidden services programmatically.
2030 ///
2031 /// # Arguments
2032 ///
2033 /// * `ports` - Mapping of virtual ports to local targets (e.g., `[(80, "127.0.0.1:8080")]`)
2034 /// * `key_type` - Type of key: `"NEW"` to generate, `"RSA1024"`, or `"ED25519-V3"`
2035 /// * `key_content` - Key content or type to generate (`"BEST"`, `"RSA1024"`, `"ED25519-V3"`)
2036 /// * `flags` - Optional flags like `"Detach"`, `"DiscardPK"`, `"BasicAuth"`, `"MaxStreamsCloseCircuit"`
2037 ///
2038 /// # Returns
2039 ///
2040 /// Returns an [`AddOnionResponse`] containing:
2041 /// - `service_id`: The onion address (without `.onion` suffix)
2042 /// - `private_key`: The private key (unless `DiscardPK` flag was set)
2043 /// - `private_key_type`: The key type (e.g., `"ED25519-V3"`)
2044 ///
2045 /// # Example
2046 ///
2047 /// ```rust,no_run
2048 /// use stem_rs::controller::Controller;
2049 ///
2050 /// # async fn example() -> Result<(), stem_rs::Error> {
2051 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
2052 /// controller.authenticate(None).await?;
2053 ///
2054 /// // Create a v3 hidden service mapping port 80 to local port 8080
2055 /// let response = controller.create_ephemeral_hidden_service(
2056 /// &[(80, "127.0.0.1:8080")],
2057 /// "NEW",
2058 /// "ED25519-V3",
2059 /// &[],
2060 /// ).await?;
2061 ///
2062 /// println!("Hidden service: {}.onion", response.service_id);
2063 /// # Ok(())
2064 /// # }
2065 /// ```
2066 ///
2067 /// # See Also
2068 ///
2069 /// - [`remove_ephemeral_hidden_service`](Self::remove_ephemeral_hidden_service): Remove the service
2070 pub async fn create_ephemeral_hidden_service(
2071 &mut self,
2072 ports: &[(u16, &str)],
2073 key_type: &str,
2074 key_content: &str,
2075 flags: &[&str],
2076 ) -> Result<AddOnionResponse, Error> {
2077 let mut request = format!("ADD_ONION {}:{}", key_type, key_content);
2078
2079 if !flags.is_empty() {
2080 request.push_str(&format!(" Flags={}", flags.join(",")));
2081 }
2082
2083 for (virt_port, target) in ports {
2084 request.push_str(&format!(" Port={},{}", virt_port, target));
2085 }
2086
2087 self.socket.send(&request).await?;
2088 let response = self.recv_response().await?;
2089
2090 if !response.is_ok() {
2091 return Err(Error::OperationFailed {
2092 code: response.status_code.to_string(),
2093 message: response.content().to_string(),
2094 });
2095 }
2096
2097 parse_add_onion_response(&response.all_content())
2098 }
2099
2100 /// Removes an ephemeral hidden service.
2101 ///
2102 /// Discontinues a hidden service that was created with
2103 /// [`create_ephemeral_hidden_service`](Self::create_ephemeral_hidden_service).
2104 ///
2105 /// # Arguments
2106 ///
2107 /// * `service_id` - The onion address without the `.onion` suffix
2108 ///
2109 /// # Returns
2110 ///
2111 /// Returns `true` if the service was removed, `false` if it wasn't running.
2112 ///
2113 /// # Example
2114 ///
2115 /// ```rust,no_run
2116 /// use stem_rs::controller::Controller;
2117 ///
2118 /// # async fn example() -> Result<(), stem_rs::Error> {
2119 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
2120 /// controller.authenticate(None).await?;
2121 ///
2122 /// // Create and then remove a hidden service
2123 /// let response = controller.create_ephemeral_hidden_service(
2124 /// &[(80, "127.0.0.1:8080")],
2125 /// "NEW",
2126 /// "BEST",
2127 /// &[],
2128 /// ).await?;
2129 ///
2130 /// controller.remove_ephemeral_hidden_service(&response.service_id).await?;
2131 /// # Ok(())
2132 /// # }
2133 /// ```
2134 pub async fn remove_ephemeral_hidden_service(&mut self, service_id: &str) -> Result<bool, Error> {
2135 let command = format!("DEL_ONION {}", service_id);
2136 match self.msg(&command).await {
2137 Ok(_) => Ok(true),
2138 Err(Error::OperationFailed { code, message }) => {
2139 if message.contains("Unknown Onion Service") {
2140 Ok(false)
2141 } else {
2142 Err(Error::OperationFailed { code, message })
2143 }
2144 }
2145 Err(e) => Err(e),
2146 }
2147 }
2148
2149 /// Loads configuration text as if it were read from the torrc.
2150 ///
2151 /// This allows dynamically configuring Tor without modifying the torrc file.
2152 /// The configuration text is processed as if it were part of the torrc.
2153 ///
2154 /// # Arguments
2155 ///
2156 /// * `config_text` - The configuration text to load
2157 ///
2158 /// # Errors
2159 ///
2160 /// Returns [`Error::InvalidRequest`] if:
2161 /// - The configuration text contains invalid options
2162 /// - The configuration text has syntax errors
2163 ///
2164 /// Returns [`Error::InvalidArguments`] if:
2165 /// - An unknown configuration option is specified
2166 ///
2167 /// # Example
2168 ///
2169 /// ```rust,no_run
2170 /// use stem_rs::controller::Controller;
2171 ///
2172 /// # async fn example() -> Result<(), stem_rs::Error> {
2173 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
2174 /// controller.authenticate(None).await?;
2175 ///
2176 /// // Load configuration
2177 /// controller.load_conf("MaxCircuitDirtiness 600").await?;
2178 /// # Ok(())
2179 /// # }
2180 /// ```
2181 ///
2182 /// # See Also
2183 ///
2184 /// - [`set_conf`](Self::set_conf): Set individual configuration options
2185 /// - [`save_conf`](Self::save_conf): Save configuration to torrc
2186 pub async fn load_conf(&mut self, config_text: &str) -> Result<(), Error> {
2187 let command = format!("LOADCONF\n{}", config_text);
2188 self.socket.send(&command).await?;
2189 let response = self.recv_response().await?;
2190
2191 if response.is_ok() {
2192 Ok(())
2193 } else if response.status_code == 552 || response.status_code == 553 {
2194 let message = response.content().to_string();
2195 if response.status_code == 552 && message.contains("Unknown option") {
2196 // Extract the unknown option name
2197 Err(Error::InvalidArguments(message))
2198 } else {
2199 Err(Error::InvalidRequest(message))
2200 }
2201 } else {
2202 Err(Error::Protocol(format!(
2203 "LOADCONF received unexpected response: {}",
2204 response.status_code
2205 )))
2206 }
2207 }
2208
2209 /// Drops guard nodes and optionally resets circuit timeouts.
2210 ///
2211 /// This forces Tor to drop its current guard nodes and select new ones.
2212 /// Optionally, circuit build timeout counters can also be reset.
2213 ///
2214 /// # Arguments
2215 ///
2216 /// * `reset_timeouts` - If `true`, also reset circuit build timeout counters
2217 ///
2218 /// # Errors
2219 ///
2220 /// Returns [`Error::OperationFailed`] if:
2221 /// - Tor returns an error response
2222 /// - `reset_timeouts` is `true` but Tor version doesn't support DROPTIMEOUTS
2223 ///
2224 /// # Example
2225 ///
2226 /// ```rust,no_run
2227 /// use stem_rs::controller::Controller;
2228 ///
2229 /// # async fn example() -> Result<(), stem_rs::Error> {
2230 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
2231 /// controller.authenticate(None).await?;
2232 ///
2233 /// // Drop guards only
2234 /// controller.drop_guards(false).await?;
2235 ///
2236 /// // Drop guards and reset timeouts
2237 /// controller.drop_guards(true).await?;
2238 /// # Ok(())
2239 /// # }
2240 /// ```
2241 pub async fn drop_guards(&mut self, reset_timeouts: bool) -> Result<(), Error> {
2242 self.socket.send("DROPGUARDS").await?;
2243 let response = self.recv_response().await?;
2244
2245 if !response.is_ok() {
2246 return Err(Error::OperationFailed {
2247 code: response.status_code.to_string(),
2248 message: response.content().to_string(),
2249 });
2250 }
2251
2252 if reset_timeouts {
2253 self.socket.send("DROPTIMEOUTS").await?;
2254 let response = self.recv_response().await?;
2255
2256 if !response.is_ok() {
2257 return Err(Error::OperationFailed {
2258 code: response.status_code.to_string(),
2259 message: response.content().to_string(),
2260 });
2261 }
2262 }
2263
2264 Ok(())
2265 }
2266
2267 /// Changes a circuit's purpose.
2268 ///
2269 /// Currently, two purposes are recognized: "general" and "controller".
2270 ///
2271 /// # Arguments
2272 ///
2273 /// * `circuit_id` - The ID of the circuit to repurpose
2274 /// * `purpose` - The new purpose for the circuit
2275 ///
2276 /// # Errors
2277 ///
2278 /// Returns [`Error::InvalidRequest`] if:
2279 /// - The circuit doesn't exist
2280 /// - The purpose is invalid
2281 ///
2282 /// # Example
2283 ///
2284 /// ```rust,no_run
2285 /// use stem_rs::controller::{Controller, CircuitId, CircuitPurpose};
2286 ///
2287 /// # async fn example() -> Result<(), stem_rs::Error> {
2288 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
2289 /// controller.authenticate(None).await?;
2290 ///
2291 /// let circuit_id = CircuitId::new("5");
2292 /// controller.repurpose_circuit(&circuit_id, CircuitPurpose::Controller).await?;
2293 /// # Ok(())
2294 /// # }
2295 /// ```
2296 pub async fn repurpose_circuit(
2297 &mut self,
2298 circuit_id: &CircuitId,
2299 purpose: CircuitPurpose,
2300 ) -> Result<(), Error> {
2301 let command = format!("SETCIRCUITPURPOSE {} purpose={}", circuit_id.0, purpose);
2302 self.socket.send(&command).await?;
2303 let response = self.recv_response().await?;
2304
2305 if response.is_ok() {
2306 Ok(())
2307 } else if response.status_code == 552 {
2308 Err(Error::InvalidRequest(response.content().to_string()))
2309 } else {
2310 Err(Error::Protocol(format!(
2311 "SETCIRCUITPURPOSE returned unexpected response code: {}",
2312 response.status_code
2313 )))
2314 }
2315 }
2316
2317 /// Enables controller features that are disabled by default.
2318 ///
2319 /// Once enabled, a feature cannot be disabled and a new control connection
2320 /// must be opened to get a connection with the feature disabled.
2321 ///
2322 /// # Arguments
2323 ///
2324 /// * `features` - List of feature names to enable
2325 ///
2326 /// # Errors
2327 ///
2328 /// Returns [`Error::InvalidArguments`] if:
2329 /// - An unrecognized feature is specified
2330 ///
2331 /// # Example
2332 ///
2333 /// ```rust,no_run
2334 /// use stem_rs::controller::Controller;
2335 ///
2336 /// # async fn example() -> Result<(), stem_rs::Error> {
2337 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
2338 /// controller.authenticate(None).await?;
2339 ///
2340 /// controller.enable_feature(&["VERBOSE_NAMES"]).await?;
2341 /// # Ok(())
2342 /// # }
2343 /// ```
2344 pub async fn enable_feature(&mut self, features: &[&str]) -> Result<(), Error> {
2345 let command = format!("USEFEATURE {}", features.join(" "));
2346 self.socket.send(&command).await?;
2347 let response = self.recv_response().await?;
2348
2349 if response.is_ok() {
2350 Ok(())
2351 } else if response.status_code == 552 {
2352 let message = response.content().to_string();
2353 Err(Error::InvalidArguments(message))
2354 } else {
2355 Err(Error::Protocol(format!(
2356 "USEFEATURE provided an invalid response code: {}",
2357 response.status_code
2358 )))
2359 }
2360 }
2361
2362 /// Gets the addresses and ports where Tor is listening for connections.
2363 ///
2364 /// Returns a list of (address, port) tuples for the specified listener type.
2365 ///
2366 /// # Arguments
2367 ///
2368 /// * `listener_type` - The type of listener to query
2369 ///
2370 /// # Errors
2371 ///
2372 /// Returns [`Error::OperationFailed`] if:
2373 /// - The GETINFO command fails
2374 ///
2375 /// Returns [`Error::Protocol`] if:
2376 /// - The response format is unexpected
2377 ///
2378 /// # Example
2379 ///
2380 /// ```rust,no_run
2381 /// use stem_rs::controller::{Controller, ListenerType};
2382 ///
2383 /// # async fn example() -> Result<(), stem_rs::Error> {
2384 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
2385 /// controller.authenticate(None).await?;
2386 ///
2387 /// let listeners = controller.get_listeners(ListenerType::Socks).await?;
2388 /// for (addr, port) in listeners {
2389 /// println!("SOCKS listener: {}:{}", addr, port);
2390 /// }
2391 /// # Ok(())
2392 /// # }
2393 /// ```
2394 ///
2395 /// # See Also
2396 ///
2397 /// - [`get_ports`](Self::get_ports): Get just the port numbers
2398 pub async fn get_listeners(
2399 &mut self,
2400 listener_type: ListenerType,
2401 ) -> Result<Vec<(String, u16)>, Error> {
2402 let query = format!("net/listeners/{}", listener_type);
2403 let response = self.get_info(&query).await?;
2404
2405 parse_listeners(&response)
2406 }
2407
2408 /// Gets just the port numbers where Tor is listening for connections.
2409 ///
2410 /// Returns a set of unique port numbers for the specified listener type.
2411 /// This is a convenience method that extracts just the ports from
2412 /// [`get_listeners`](Self::get_listeners).
2413 ///
2414 /// # Arguments
2415 ///
2416 /// * `listener_type` - The type of listener to query
2417 ///
2418 /// # Example
2419 ///
2420 /// ```rust,no_run
2421 /// use stem_rs::controller::{Controller, ListenerType};
2422 ///
2423 /// # async fn example() -> Result<(), stem_rs::Error> {
2424 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
2425 /// controller.authenticate(None).await?;
2426 ///
2427 /// let ports = controller.get_ports(ListenerType::Socks).await?;
2428 /// for port in ports {
2429 /// println!("SOCKS port: {}", port);
2430 /// }
2431 /// # Ok(())
2432 /// # }
2433 /// ```
2434 ///
2435 /// # See Also
2436 ///
2437 /// - [`get_listeners`](Self::get_listeners): Get addresses and ports
2438 pub async fn get_ports(&mut self, listener_type: ListenerType) -> Result<HashSet<u16>, Error> {
2439 let listeners = self.get_listeners(listener_type).await?;
2440 Ok(listeners.into_iter().map(|(_, port)| port).collect())
2441 }
2442
2443 /// Gets the user Tor is running as.
2444 ///
2445 /// # Errors
2446 ///
2447 /// Returns [`Error::OperationFailed`] if:
2448 /// - The information is not available
2449 /// - Tor returns an error response
2450 ///
2451 /// # Example
2452 ///
2453 /// ```rust,no_run
2454 /// use stem_rs::controller::Controller;
2455 ///
2456 /// # async fn example() -> Result<(), stem_rs::Error> {
2457 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
2458 /// controller.authenticate(None).await?;
2459 ///
2460 /// let user = controller.get_user().await?;
2461 /// println!("Tor is running as: {}", user);
2462 /// # Ok(())
2463 /// # }
2464 /// ```
2465 pub async fn get_user(&mut self) -> Result<String, Error> {
2466 self.get_info("process/user").await
2467 }
2468
2469 /// Gets the Unix timestamp when Tor started.
2470 ///
2471 /// Calculates the start time by subtracting the uptime from the current time.
2472 ///
2473 /// # Errors
2474 ///
2475 /// Returns an error if:
2476 /// - The uptime cannot be determined
2477 /// - The uptime value is invalid
2478 ///
2479 /// # Example
2480 ///
2481 /// ```rust,no_run
2482 /// use stem_rs::controller::Controller;
2483 ///
2484 /// # async fn example() -> Result<(), stem_rs::Error> {
2485 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
2486 /// controller.authenticate(None).await?;
2487 ///
2488 /// let start_time = controller.get_start_time().await?;
2489 /// println!("Tor started at: {}", start_time);
2490 /// # Ok(())
2491 /// # }
2492 /// ```
2493 ///
2494 /// # See Also
2495 ///
2496 /// - [`get_uptime`](Self::get_uptime): Get how long Tor has been running
2497 pub async fn get_start_time(&mut self) -> Result<f64, Error> {
2498 let uptime = self.get_uptime().await?;
2499 let now = SystemTime::now()
2500 .duration_since(UNIX_EPOCH)
2501 .map_err(|e| Error::Parse {
2502 location: "system time".to_string(),
2503 reason: e.to_string(),
2504 })?
2505 .as_secs_f64();
2506 Ok(now - uptime)
2507 }
2508
2509 /// Gets how long Tor has been running in seconds.
2510 ///
2511 /// # Errors
2512 ///
2513 /// Returns an error if:
2514 /// - The GETINFO command fails
2515 /// - The uptime value cannot be parsed
2516 ///
2517 /// # Example
2518 ///
2519 /// ```rust,no_run
2520 /// use stem_rs::controller::Controller;
2521 ///
2522 /// # async fn example() -> Result<(), stem_rs::Error> {
2523 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
2524 /// controller.authenticate(None).await?;
2525 ///
2526 /// let uptime = controller.get_uptime().await?;
2527 /// println!("Tor has been running for {} seconds", uptime);
2528 /// # Ok(())
2529 /// # }
2530 /// ```
2531 ///
2532 /// # See Also
2533 ///
2534 /// - [`get_start_time`](Self::get_start_time): Get when Tor started
2535 pub async fn get_uptime(&mut self) -> Result<f64, Error> {
2536 let uptime_str = self.get_info("process/uptime").await?;
2537 uptime_str.parse().map_err(|_| Error::Parse {
2538 location: "uptime".to_string(),
2539 reason: format!("invalid uptime value: {}", uptime_str),
2540 })
2541 }
2542
2543 /// Gets protocol information including authentication methods.
2544 ///
2545 /// Returns information about the Tor control protocol version,
2546 /// the Tor version, and available authentication methods.
2547 ///
2548 /// # Errors
2549 ///
2550 /// Returns [`Error::Parse`] if:
2551 /// - The PROTOCOLINFO response cannot be parsed
2552 ///
2553 /// # Example
2554 ///
2555 /// ```rust,no_run
2556 /// use stem_rs::controller::Controller;
2557 ///
2558 /// # async fn example() -> Result<(), stem_rs::Error> {
2559 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
2560 ///
2561 /// let info = controller.get_protocolinfo().await?;
2562 /// println!("Protocol version: {}", info.protocol_version);
2563 /// println!("Tor version: {}", info.tor_version);
2564 /// println!("Auth methods: {:?}", info.auth_methods);
2565 /// # Ok(())
2566 /// # }
2567 /// ```
2568 pub async fn get_protocolinfo(&mut self) -> Result<ProtocolInfo, Error> {
2569 self.socket.send("PROTOCOLINFO 1").await?;
2570 let response = self.recv_response().await?;
2571
2572 if !response.is_ok() {
2573 return Err(Error::OperationFailed {
2574 code: response.status_code.to_string(),
2575 message: response.content().to_string(),
2576 });
2577 }
2578
2579 parse_protocolinfo(&response.all_content())
2580 }
2581
2582 /// Gets accounting statistics for bandwidth limiting.
2583 ///
2584 /// Returns statistics about Tor's accounting status when AccountingMax
2585 /// is set in the torrc.
2586 ///
2587 /// # Errors
2588 ///
2589 /// Returns [`Error::OperationFailed`] if:
2590 /// - Accounting is not enabled
2591 /// - The GETINFO commands fail
2592 ///
2593 /// # Example
2594 ///
2595 /// ```rust,no_run
2596 /// use stem_rs::controller::Controller;
2597 ///
2598 /// # async fn example() -> Result<(), stem_rs::Error> {
2599 /// let mut controller = Controller::from_port("127.0.0.1:9051".parse()?).await?;
2600 /// controller.authenticate(None).await?;
2601 ///
2602 /// let stats = controller.get_accounting_stats().await?;
2603 /// println!("Status: {}", stats.status);
2604 /// println!("Read: {} bytes", stats.read_bytes);
2605 /// println!("Written: {} bytes", stats.written_bytes);
2606 /// # Ok(())
2607 /// # }
2608 /// ```
2609 pub async fn get_accounting_stats(&mut self) -> Result<AccountingStats, Error> {
2610 // Check if accounting is enabled
2611 let enabled = self.get_info("accounting/enabled").await?;
2612 if enabled != "1" {
2613 return Err(Error::OperationFailed {
2614 code: "552".to_string(),
2615 message: "Accounting isn't enabled".to_string(),
2616 });
2617 }
2618
2619 let retrieved = SystemTime::now()
2620 .duration_since(UNIX_EPOCH)
2621 .map_err(|e| Error::Parse {
2622 location: "system time".to_string(),
2623 reason: e.to_string(),
2624 })?
2625 .as_secs_f64();
2626
2627 let status = self.get_info("accounting/hibernating").await?;
2628 let interval_end = self.get_info("accounting/interval-end").await.ok();
2629 let bytes = self.get_info("accounting/bytes").await?;
2630 let bytes_left = self.get_info("accounting/bytes-left").await?;
2631
2632 // Parse bytes: "read_bytes written_bytes"
2633 let (read_bytes, written_bytes) = parse_accounting_bytes(&bytes)?;
2634 let (read_bytes_left, write_bytes_left) = parse_accounting_bytes(&bytes_left)?;
2635
2636 // Calculate time until reset
2637 let time_until_reset = if let Some(ref end) = interval_end {
2638 parse_interval_end_to_seconds(end, retrieved).unwrap_or(0)
2639 } else {
2640 0
2641 };
2642
2643 Ok(AccountingStats {
2644 retrieved,
2645 status,
2646 interval_end,
2647 time_until_reset,
2648 read_bytes,
2649 read_bytes_left,
2650 read_limit: read_bytes + read_bytes_left,
2651 written_bytes,
2652 write_bytes_left,
2653 write_limit: written_bytes + write_bytes_left,
2654 })
2655 }
2656}
2657
2658/// Response from ADD_ONION command.
2659///
2660/// Contains the service ID and optionally the private key for the hidden service.
2661#[derive(Debug, Clone)]
2662pub struct AddOnionResponse {
2663 /// The onion address without the `.onion` suffix.
2664 pub service_id: String,
2665 /// The private key (base64 encoded), if not discarded.
2666 pub private_key: Option<String>,
2667 /// The type of private key (e.g., `"ED25519-V3"`, `"RSA1024"`).
2668 pub private_key_type: Option<String>,
2669}
2670
2671/// Parses the response from an ADD_ONION command.
2672fn parse_add_onion_response(content: &str) -> Result<AddOnionResponse, Error> {
2673 let mut service_id = None;
2674 let mut private_key = None;
2675 let mut private_key_type = None;
2676
2677 for line in content.lines() {
2678 let line = line.trim();
2679 if let Some(value) = line.strip_prefix("ServiceID=") {
2680 service_id = Some(value.to_string());
2681 } else if let Some(value) = line.strip_prefix("PrivateKey=") {
2682 if let Some((key_type, key_content)) = value.split_once(':') {
2683 private_key_type = Some(key_type.to_string());
2684 private_key = Some(key_content.to_string());
2685 }
2686 }
2687 }
2688
2689 let service_id = service_id.ok_or_else(|| Error::Parse {
2690 location: "ADD_ONION response".to_string(),
2691 reason: "missing ServiceID".to_string(),
2692 })?;
2693
2694 Ok(AddOnionResponse {
2695 service_id,
2696 private_key,
2697 private_key_type,
2698 })
2699}
2700
2701/// Parses circuit status output from GETINFO circuit-status.
2702///
2703/// Converts the multi-line circuit status response into a vector of [`Circuit`] structs.
2704fn parse_circuits(content: &str) -> Result<Vec<Circuit>, Error> {
2705 let mut circuits = Vec::new();
2706
2707 for line in content.lines() {
2708 let line = line.trim();
2709 if line.is_empty() {
2710 continue;
2711 }
2712
2713 let mut parts = line.split_whitespace();
2714 let id = parts.next().ok_or_else(|| Error::Parse {
2715 location: "circuit".to_string(),
2716 reason: "missing circuit id".to_string(),
2717 })?;
2718
2719 let status_str = parts.next().ok_or_else(|| Error::Parse {
2720 location: "circuit".to_string(),
2721 reason: "missing circuit status".to_string(),
2722 })?;
2723
2724 let status = parse_circ_status(status_str)?;
2725
2726 let mut path = Vec::new();
2727 if let Some(path_str) = parts.next() {
2728 if !path_str.starts_with("BUILD_FLAGS=")
2729 && !path_str.starts_with("PURPOSE=")
2730 && !path_str.starts_with("TIME_CREATED=")
2731 {
2732 for relay in path_str.split(',') {
2733 let relay_info = parse_relay_info(relay);
2734 path.push(relay_info);
2735 }
2736 }
2737 }
2738
2739 circuits.push(Circuit {
2740 id: CircuitId::new(id),
2741 status,
2742 path,
2743 });
2744 }
2745
2746 Ok(circuits)
2747}
2748
2749/// Parses a circuit status string into a [`CircStatus`] enum.
2750fn parse_circ_status(s: &str) -> Result<CircStatus, Error> {
2751 match s.to_uppercase().as_str() {
2752 "LAUNCHED" => Ok(CircStatus::Launched),
2753 "BUILT" => Ok(CircStatus::Built),
2754 "GUARD_WAIT" => Ok(CircStatus::GuardWait),
2755 "EXTENDED" => Ok(CircStatus::Extended),
2756 "FAILED" => Ok(CircStatus::Failed),
2757 "CLOSED" => Ok(CircStatus::Closed),
2758 _ => Err(Error::Parse {
2759 location: "circuit status".to_string(),
2760 reason: format!("unknown status: {}", s),
2761 }),
2762 }
2763}
2764
2765/// Parses a relay specification string into a [`RelayInfo`] struct.
2766///
2767/// Handles formats like `$FINGERPRINT~Nickname` or just `$FINGERPRINT`.
2768fn parse_relay_info(s: &str) -> RelayInfo {
2769 if let Some((fingerprint, nickname)) = s.split_once('~') {
2770 RelayInfo {
2771 fingerprint: fingerprint.trim_start_matches('$').to_string(),
2772 nickname: Some(nickname.to_string()),
2773 }
2774 } else {
2775 RelayInfo {
2776 fingerprint: s.trim_start_matches('$').to_string(),
2777 nickname: None,
2778 }
2779 }
2780}
2781
2782/// Parses stream status output from GETINFO stream-status.
2783///
2784/// Converts the multi-line stream status response into a vector of [`Stream`] structs.
2785fn parse_streams(content: &str) -> Result<Vec<Stream>, Error> {
2786 let mut streams = Vec::new();
2787
2788 for line in content.lines() {
2789 let line = line.trim();
2790 if line.is_empty() {
2791 continue;
2792 }
2793
2794 let mut parts = line.split_whitespace();
2795 let id = parts.next().ok_or_else(|| Error::Parse {
2796 location: "stream".to_string(),
2797 reason: "missing stream id".to_string(),
2798 })?;
2799
2800 let status_str = parts.next().ok_or_else(|| Error::Parse {
2801 location: "stream".to_string(),
2802 reason: "missing stream status".to_string(),
2803 })?;
2804
2805 let status = parse_stream_status(status_str)?;
2806
2807 let circuit_id_str = parts.next().ok_or_else(|| Error::Parse {
2808 location: "stream".to_string(),
2809 reason: "missing circuit id".to_string(),
2810 })?;
2811
2812 let circuit_id = if circuit_id_str == "0" {
2813 None
2814 } else {
2815 Some(CircuitId::new(circuit_id_str))
2816 };
2817
2818 let target = parts.next().ok_or_else(|| Error::Parse {
2819 location: "stream".to_string(),
2820 reason: "missing target".to_string(),
2821 })?;
2822
2823 let (target_host, target_port) = parse_target(target)?;
2824
2825 streams.push(Stream {
2826 id: StreamId::new(id),
2827 status,
2828 circuit_id,
2829 target_host,
2830 target_port,
2831 });
2832 }
2833
2834 Ok(streams)
2835}
2836
2837/// Parses a stream status string into a [`StreamStatus`] enum.
2838fn parse_stream_status(s: &str) -> Result<StreamStatus, Error> {
2839 match s.to_uppercase().as_str() {
2840 "NEW" => Ok(StreamStatus::New),
2841 "NEWRESOLVE" => Ok(StreamStatus::NewResolve),
2842 "REMAP" => Ok(StreamStatus::Remap),
2843 "SENTCONNECT" => Ok(StreamStatus::SentConnect),
2844 "SENTRESOLVE" => Ok(StreamStatus::SentResolve),
2845 "SUCCEEDED" => Ok(StreamStatus::Succeeded),
2846 "FAILED" => Ok(StreamStatus::Failed),
2847 "DETACHED" => Ok(StreamStatus::Detached),
2848 "CONTROLLER_WAIT" => Ok(StreamStatus::ControllerWait),
2849 "CLOSED" => Ok(StreamStatus::Closed),
2850 _ => Err(Error::Parse {
2851 location: "stream status".to_string(),
2852 reason: format!("unknown status: {}", s),
2853 }),
2854 }
2855}
2856
2857/// Parses a target address string into host and port components.
2858///
2859/// Handles formats like `host:port` or just `host` (port defaults to 0).
2860fn parse_target(target: &str) -> Result<(String, u16), Error> {
2861 if let Some(colon_pos) = target.rfind(':') {
2862 let host = target[..colon_pos].to_string();
2863 let port_str = &target[colon_pos + 1..];
2864 let port: u16 = port_str.parse().map_err(|_| Error::Parse {
2865 location: "stream target".to_string(),
2866 reason: format!("invalid port: {}", port_str),
2867 })?;
2868 Ok((host, port))
2869 } else {
2870 Ok((target.to_string(), 0))
2871 }
2872}
2873
2874/// Parses listener response from GETINFO net/listeners/*.
2875///
2876/// The response contains quoted "address:port" pairs separated by spaces.
2877fn parse_listeners(response: &str) -> Result<Vec<(String, u16)>, Error> {
2878 let mut listeners = Vec::new();
2879
2880 for listener in response.split_whitespace() {
2881 // Strip quotes
2882 let listener = listener.trim_matches('"');
2883 if listener.is_empty() {
2884 continue;
2885 }
2886
2887 // Skip unix sockets
2888 if listener.starts_with("unix:") {
2889 continue;
2890 }
2891
2892 // Find the last colon to split address and port
2893 if let Some(colon_pos) = listener.rfind(':') {
2894 let mut addr = listener[..colon_pos].to_string();
2895 let port_str = &listener[colon_pos + 1..];
2896
2897 // Handle IPv6 addresses in brackets
2898 if addr.starts_with('[') && addr.ends_with(']') {
2899 addr = addr[1..addr.len() - 1].to_string();
2900 }
2901
2902 let port: u16 = port_str.parse().map_err(|_| Error::Parse {
2903 location: "listener".to_string(),
2904 reason: format!("invalid port: {}", port_str),
2905 })?;
2906
2907 listeners.push((addr, port));
2908 }
2909 }
2910
2911 Ok(listeners)
2912}
2913
2914/// Parses PROTOCOLINFO response.
2915fn parse_protocolinfo(content: &str) -> Result<ProtocolInfo, Error> {
2916 let mut protocol_version = 1;
2917 let mut tor_version = String::new();
2918 let mut auth_methods = Vec::new();
2919 let mut cookie_file = None;
2920
2921 for line in content.lines() {
2922 let line = line.trim();
2923
2924 if let Some(stripped) = line.strip_prefix("PROTOCOLINFO ") {
2925 if let Ok(v) = stripped.trim().parse::<u32>() {
2926 protocol_version = v;
2927 }
2928 } else if let Some(rest) = line.strip_prefix("AUTH METHODS=") {
2929 // Parse: AUTH METHODS=COOKIE,SAFECOOKIE COOKIEFILE="/path/to/cookie"
2930
2931 // Find METHODS value
2932 if let Some(space_pos) = rest.find(' ') {
2933 let methods_str = &rest[..space_pos];
2934 auth_methods = methods_str.split(',').map(|s| s.to_string()).collect();
2935
2936 // Look for COOKIEFILE
2937 let remaining = &rest[space_pos..];
2938 if let Some(cookie_start) = remaining.find("COOKIEFILE=\"") {
2939 let cookie_path_start = cookie_start + 12;
2940 if let Some(cookie_end) = remaining[cookie_path_start..].find('"') {
2941 cookie_file =
2942 Some(remaining[cookie_path_start..cookie_path_start + cookie_end].to_string());
2943 }
2944 }
2945 } else {
2946 auth_methods = rest.split(',').map(|s| s.to_string()).collect();
2947 }
2948 } else if line.starts_with("VERSION Tor=\"") {
2949 // Parse: VERSION Tor="0.4.7.10"
2950 if let Some(start) = line.find("Tor=\"") {
2951 let version_start = start + 5;
2952 if let Some(end) = line[version_start..].find('"') {
2953 tor_version = line[version_start..version_start + end].to_string();
2954 }
2955 }
2956 }
2957 }
2958
2959 Ok(ProtocolInfo {
2960 protocol_version,
2961 tor_version,
2962 auth_methods,
2963 cookie_file,
2964 })
2965}
2966
2967/// Parses accounting bytes response: "read_bytes written_bytes"
2968fn parse_accounting_bytes(bytes_str: &str) -> Result<(u64, u64), Error> {
2969 let parts: Vec<&str> = bytes_str.split_whitespace().collect();
2970 if parts.len() != 2 {
2971 return Err(Error::Parse {
2972 location: "accounting bytes".to_string(),
2973 reason: format!("expected 'read written', got: {}", bytes_str),
2974 });
2975 }
2976
2977 let read: u64 = parts[0].parse().map_err(|_| Error::Parse {
2978 location: "accounting bytes".to_string(),
2979 reason: format!("invalid read bytes: {}", parts[0]),
2980 })?;
2981
2982 let written: u64 = parts[1].parse().map_err(|_| Error::Parse {
2983 location: "accounting bytes".to_string(),
2984 reason: format!("invalid written bytes: {}", parts[1]),
2985 })?;
2986
2987 Ok((read, written))
2988}
2989
2990/// Parses interval end timestamp and calculates seconds until reset.
2991fn parse_interval_end_to_seconds(interval_end: &str, current_time: f64) -> Option<u64> {
2992 // interval_end format: "YYYY-MM-DD HH:MM:SS"
2993 // This is a simplified parser - in production you'd use chrono
2994 let parts: Vec<&str> = interval_end.split_whitespace().collect();
2995 if parts.len() != 2 {
2996 return None;
2997 }
2998
2999 let date_parts: Vec<&str> = parts[0].split('-').collect();
3000 let time_parts: Vec<&str> = parts[1].split(':').collect();
3001
3002 if date_parts.len() != 3 || time_parts.len() != 3 {
3003 return None;
3004 }
3005
3006 let year: i32 = date_parts[0].parse().ok()?;
3007 let month: u32 = date_parts[1].parse().ok()?;
3008 let day: u32 = date_parts[2].parse().ok()?;
3009 let hour: u32 = time_parts[0].parse().ok()?;
3010 let minute: u32 = time_parts[1].parse().ok()?;
3011 let second: u32 = time_parts[2].parse().ok()?;
3012
3013 // Simple calculation - days since epoch approximation
3014 // This is a rough estimate; for production use chrono crate
3015 let days_since_epoch = (year - 1970) * 365 + (year - 1969) / 4 + day_of_year(month, day) as i32;
3016 let end_timestamp =
3017 days_since_epoch as f64 * 86400.0 + hour as f64 * 3600.0 + minute as f64 * 60.0 + second as f64;
3018
3019 if end_timestamp > current_time {
3020 Some((end_timestamp - current_time) as u64)
3021 } else {
3022 Some(0)
3023 }
3024}
3025
3026/// Helper to calculate day of year (approximate).
3027fn day_of_year(month: u32, day: u32) -> u32 {
3028 let days_before_month = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
3029 if (1..=12).contains(&month) {
3030 days_before_month[(month - 1) as usize] + day
3031 } else {
3032 day
3033 }
3034}
3035
3036#[cfg(test)]
3037mod tests {
3038 use super::*;
3039
3040 #[test]
3041 fn test_circuit_id_display() {
3042 let id = CircuitId::new("123");
3043 assert_eq!(id.to_string(), "123");
3044 }
3045
3046 #[test]
3047 fn test_stream_id_display() {
3048 let id = StreamId::new("456");
3049 assert_eq!(id.to_string(), "456");
3050 }
3051
3052 #[test]
3053 fn test_parse_circ_status() {
3054 assert_eq!(parse_circ_status("LAUNCHED").unwrap(), CircStatus::Launched);
3055 assert_eq!(parse_circ_status("BUILT").unwrap(), CircStatus::Built);
3056 assert_eq!(
3057 parse_circ_status("GUARD_WAIT").unwrap(),
3058 CircStatus::GuardWait
3059 );
3060 assert_eq!(parse_circ_status("EXTENDED").unwrap(), CircStatus::Extended);
3061 assert_eq!(parse_circ_status("FAILED").unwrap(), CircStatus::Failed);
3062 assert_eq!(parse_circ_status("CLOSED").unwrap(), CircStatus::Closed);
3063 assert_eq!(parse_circ_status("launched").unwrap(), CircStatus::Launched);
3064 assert!(parse_circ_status("UNKNOWN").is_err());
3065 }
3066
3067 #[test]
3068 fn test_parse_stream_status() {
3069 assert_eq!(parse_stream_status("NEW").unwrap(), StreamStatus::New);
3070 assert_eq!(
3071 parse_stream_status("NEWRESOLVE").unwrap(),
3072 StreamStatus::NewResolve
3073 );
3074 assert_eq!(parse_stream_status("REMAP").unwrap(), StreamStatus::Remap);
3075 assert_eq!(
3076 parse_stream_status("SENTCONNECT").unwrap(),
3077 StreamStatus::SentConnect
3078 );
3079 assert_eq!(
3080 parse_stream_status("SENTRESOLVE").unwrap(),
3081 StreamStatus::SentResolve
3082 );
3083 assert_eq!(
3084 parse_stream_status("SUCCEEDED").unwrap(),
3085 StreamStatus::Succeeded
3086 );
3087 assert_eq!(parse_stream_status("FAILED").unwrap(), StreamStatus::Failed);
3088 assert_eq!(
3089 parse_stream_status("DETACHED").unwrap(),
3090 StreamStatus::Detached
3091 );
3092 assert_eq!(
3093 parse_stream_status("CONTROLLER_WAIT").unwrap(),
3094 StreamStatus::ControllerWait
3095 );
3096 assert_eq!(parse_stream_status("CLOSED").unwrap(), StreamStatus::Closed);
3097 assert!(parse_stream_status("UNKNOWN").is_err());
3098 }
3099
3100 #[test]
3101 fn test_parse_relay_info_with_nickname() {
3102 let info = parse_relay_info("$ABCD1234~MyRelay");
3103 assert_eq!(info.fingerprint, "ABCD1234");
3104 assert_eq!(info.nickname, Some("MyRelay".to_string()));
3105 }
3106
3107 #[test]
3108 fn test_parse_relay_info_without_nickname() {
3109 let info = parse_relay_info("$ABCD1234");
3110 assert_eq!(info.fingerprint, "ABCD1234");
3111 assert_eq!(info.nickname, None);
3112 }
3113
3114 #[test]
3115 fn test_parse_relay_info_no_dollar() {
3116 let info = parse_relay_info("ABCD1234~MyRelay");
3117 assert_eq!(info.fingerprint, "ABCD1234");
3118 assert_eq!(info.nickname, Some("MyRelay".to_string()));
3119 }
3120
3121 #[test]
3122 fn test_parse_target_with_port() {
3123 let (host, port) = parse_target("example.com:443").unwrap();
3124 assert_eq!(host, "example.com");
3125 assert_eq!(port, 443);
3126 }
3127
3128 #[test]
3129 fn test_parse_target_ipv4_with_port() {
3130 let (host, port) = parse_target("192.168.1.1:80").unwrap();
3131 assert_eq!(host, "192.168.1.1");
3132 assert_eq!(port, 80);
3133 }
3134
3135 #[test]
3136 fn test_parse_target_without_port() {
3137 let (host, port) = parse_target("example.com").unwrap();
3138 assert_eq!(host, "example.com");
3139 assert_eq!(port, 0);
3140 }
3141
3142 #[test]
3143 fn test_parse_circuits_empty() {
3144 let circuits = parse_circuits("").unwrap();
3145 assert!(circuits.is_empty());
3146 }
3147
3148 #[test]
3149 fn test_parse_circuits_single() {
3150 let content = "1 BUILT $AAAA~Guard,$BBBB~Middle,$CCCC~Exit";
3151 let circuits = parse_circuits(content).unwrap();
3152 assert_eq!(circuits.len(), 1);
3153 assert_eq!(circuits[0].id.0, "1");
3154 assert_eq!(circuits[0].status, CircStatus::Built);
3155 assert_eq!(circuits[0].path.len(), 3);
3156 assert_eq!(circuits[0].path[0].fingerprint, "AAAA");
3157 assert_eq!(circuits[0].path[0].nickname, Some("Guard".to_string()));
3158 }
3159
3160 #[test]
3161 fn test_parse_circuits_multiple() {
3162 let content = "1 BUILT $AAAA~Guard,$BBBB~Exit\n2 LAUNCHED\n3 EXTENDED $CCCC~Relay";
3163 let circuits = parse_circuits(content).unwrap();
3164 assert_eq!(circuits.len(), 3);
3165 assert_eq!(circuits[0].status, CircStatus::Built);
3166 assert_eq!(circuits[1].status, CircStatus::Launched);
3167 assert_eq!(circuits[2].status, CircStatus::Extended);
3168 }
3169
3170 #[test]
3171 fn test_parse_circuits_with_flags() {
3172 let content = "1 BUILT $AAAA~Guard BUILD_FLAGS=IS_INTERNAL PURPOSE=GENERAL";
3173 let circuits = parse_circuits(content).unwrap();
3174 assert_eq!(circuits.len(), 1);
3175 assert_eq!(circuits[0].path.len(), 1);
3176 }
3177
3178 #[test]
3179 fn test_parse_streams_empty() {
3180 let streams = parse_streams("").unwrap();
3181 assert!(streams.is_empty());
3182 }
3183
3184 #[test]
3185 fn test_parse_streams_single() {
3186 let content = "1 SUCCEEDED 5 www.example.com:443";
3187 let streams = parse_streams(content).unwrap();
3188 assert_eq!(streams.len(), 1);
3189 assert_eq!(streams[0].id.0, "1");
3190 assert_eq!(streams[0].status, StreamStatus::Succeeded);
3191 assert_eq!(streams[0].circuit_id, Some(CircuitId::new("5")));
3192 assert_eq!(streams[0].target_host, "www.example.com");
3193 assert_eq!(streams[0].target_port, 443);
3194 }
3195
3196 #[test]
3197 fn test_parse_streams_no_circuit() {
3198 let content = "1 NEW 0 www.example.com:80";
3199 let streams = parse_streams(content).unwrap();
3200 assert_eq!(streams.len(), 1);
3201 assert_eq!(streams[0].circuit_id, None);
3202 }
3203
3204 #[test]
3205 fn test_parse_streams_multiple() {
3206 let content = "1 SUCCEEDED 5 www.example.com:443\n2 NEW 0 api.example.com:80";
3207 let streams = parse_streams(content).unwrap();
3208 assert_eq!(streams.len(), 2);
3209 }
3210
3211 #[test]
3212 fn test_circuit_id_equality() {
3213 let id1 = CircuitId::new("123");
3214 let id2 = CircuitId::new("123");
3215 let id3 = CircuitId::new("456");
3216 assert_eq!(id1, id2);
3217 assert_ne!(id1, id3);
3218 }
3219
3220 #[test]
3221 fn test_stream_id_equality() {
3222 let id1 = StreamId::new("123");
3223 let id2 = StreamId::new("123");
3224 let id3 = StreamId::new("456");
3225 assert_eq!(id1, id2);
3226 assert_ne!(id1, id3);
3227 }
3228}
3229
3230#[cfg(test)]
3231mod stem_tests {
3232 use super::*;
3233
3234 #[test]
3235 fn test_parse_circ_path_empty() {
3236 let circuits = parse_circuits("").unwrap();
3237 assert!(circuits.is_empty());
3238 }
3239
3240 #[test]
3241 fn test_parse_circ_path_with_fingerprint_and_nickname() {
3242 let content = "1 BUILT $999A226EBED397F331B612FE1E4CFAE5C1F201BA~piyaz";
3243 let circuits = parse_circuits(content).unwrap();
3244 assert_eq!(circuits.len(), 1);
3245 assert_eq!(circuits[0].path.len(), 1);
3246 assert_eq!(
3247 circuits[0].path[0].fingerprint,
3248 "999A226EBED397F331B612FE1E4CFAE5C1F201BA"
3249 );
3250 assert_eq!(circuits[0].path[0].nickname, Some("piyaz".to_string()));
3251 }
3252
3253 #[test]
3254 fn test_parse_circ_path_multiple_relays() {
3255 let content =
3256 "1 BUILT $E57A476CD4DFBD99B4EE52A100A58610AD6E80B9,$AAAA,$BBBB~PrivacyRepublic14";
3257 let circuits = parse_circuits(content).unwrap();
3258 assert_eq!(circuits.len(), 1);
3259 assert_eq!(circuits[0].path.len(), 3);
3260 assert_eq!(
3261 circuits[0].path[0].fingerprint,
3262 "E57A476CD4DFBD99B4EE52A100A58610AD6E80B9"
3263 );
3264 assert_eq!(circuits[0].path[0].nickname, None);
3265 assert_eq!(circuits[0].path[2].fingerprint, "BBBB");
3266 assert_eq!(
3267 circuits[0].path[2].nickname,
3268 Some("PrivacyRepublic14".to_string())
3269 );
3270 }
3271
3272 #[test]
3273 fn test_get_streams_parsing() {
3274 let content =
3275 "1 NEW 4 10.10.10.1:80\n2 SUCCEEDED 4 10.10.10.1:80\n3 SUCCEEDED 4 10.10.10.1:80";
3276 let streams = parse_streams(content).unwrap();
3277 assert_eq!(streams.len(), 3);
3278
3279 assert_eq!(streams[0].id.0, "1");
3280 assert_eq!(streams[0].status, StreamStatus::New);
3281 assert_eq!(streams[0].circuit_id, Some(CircuitId::new("4")));
3282 assert_eq!(streams[0].target_host, "10.10.10.1");
3283 assert_eq!(streams[0].target_port, 80);
3284
3285 assert_eq!(streams[1].id.0, "2");
3286 assert_eq!(streams[1].status, StreamStatus::Succeeded);
3287
3288 assert_eq!(streams[2].id.0, "3");
3289 assert_eq!(streams[2].status, StreamStatus::Succeeded);
3290 }
3291
3292 #[test]
3293 fn test_circuit_status_parsing() {
3294 let test_cases = [
3295 ("LAUNCHED", CircStatus::Launched),
3296 ("BUILT", CircStatus::Built),
3297 ("GUARD_WAIT", CircStatus::GuardWait),
3298 ("EXTENDED", CircStatus::Extended),
3299 ("FAILED", CircStatus::Failed),
3300 ("CLOSED", CircStatus::Closed),
3301 ];
3302
3303 for (input, expected) in test_cases {
3304 assert_eq!(parse_circ_status(input).unwrap(), expected);
3305 }
3306 }
3307
3308 #[test]
3309 fn test_stream_status_parsing() {
3310 let test_cases = [
3311 ("NEW", StreamStatus::New),
3312 ("NEWRESOLVE", StreamStatus::NewResolve),
3313 ("REMAP", StreamStatus::Remap),
3314 ("SENTCONNECT", StreamStatus::SentConnect),
3315 ("SENTRESOLVE", StreamStatus::SentResolve),
3316 ("SUCCEEDED", StreamStatus::Succeeded),
3317 ("FAILED", StreamStatus::Failed),
3318 ("DETACHED", StreamStatus::Detached),
3319 ("CONTROLLER_WAIT", StreamStatus::ControllerWait),
3320 ("CLOSED", StreamStatus::Closed),
3321 ];
3322
3323 for (input, expected) in test_cases {
3324 assert_eq!(parse_stream_status(input).unwrap(), expected);
3325 }
3326 }
3327
3328 #[test]
3329 fn test_parse_target_various() {
3330 let test_cases = [
3331 ("www.example.com:443", ("www.example.com", 443)),
3332 ("192.168.1.1:80", ("192.168.1.1", 80)),
3333 ("10.10.10.1:8080", ("10.10.10.1", 8080)),
3334 ("[::1]:443", ("[::1]", 443)),
3335 ];
3336
3337 for (input, (expected_host, expected_port)) in test_cases {
3338 let (host, port) = parse_target(input).unwrap();
3339 assert_eq!(host, expected_host);
3340 assert_eq!(port, expected_port);
3341 }
3342 }
3343
3344 #[test]
3345 fn test_parse_circuits_with_build_flags() {
3346 let content = "1 BUILT $AAAA~Guard,$BBBB~Exit BUILD_FLAGS=IS_INTERNAL,NEED_CAPACITY PURPOSE=GENERAL TIME_CREATED=2023-01-01T00:00:00";
3347 let circuits = parse_circuits(content).unwrap();
3348 assert_eq!(circuits.len(), 1);
3349 assert_eq!(circuits[0].status, CircStatus::Built);
3350 assert_eq!(circuits[0].path.len(), 2);
3351 }
3352
3353 #[test]
3354 fn test_parse_circuits_launched_no_path() {
3355 let content = "1 LAUNCHED BUILD_FLAGS=NEED_CAPACITY PURPOSE=GENERAL";
3356 let circuits = parse_circuits(content).unwrap();
3357 assert_eq!(circuits.len(), 1);
3358 assert_eq!(circuits[0].status, CircStatus::Launched);
3359 assert!(circuits[0].path.is_empty());
3360 }
3361
3362 #[test]
3363 fn test_parse_streams_detached() {
3364 let content = "1 DETACHED 0 www.example.com:443";
3365 let streams = parse_streams(content).unwrap();
3366 assert_eq!(streams.len(), 1);
3367 assert_eq!(streams[0].status, StreamStatus::Detached);
3368 assert_eq!(streams[0].circuit_id, None);
3369 }
3370
3371 #[test]
3372 fn test_relay_info_parsing_variations() {
3373 let test_cases = [
3374 ("$ABCD1234~MyRelay", "ABCD1234", Some("MyRelay")),
3375 ("$ABCD1234", "ABCD1234", None),
3376 ("ABCD1234~MyRelay", "ABCD1234", Some("MyRelay")),
3377 ("ABCD1234", "ABCD1234", None),
3378 ];
3379
3380 for (input, expected_fp, expected_nick) in test_cases {
3381 let info = parse_relay_info(input);
3382 assert_eq!(info.fingerprint, expected_fp);
3383 assert_eq!(info.nickname, expected_nick.map(|s| s.to_string()));
3384 }
3385 }
3386
3387 #[test]
3388 fn test_circuit_id_hash() {
3389 use std::collections::HashSet;
3390 let mut set = HashSet::new();
3391 set.insert(CircuitId::new("1"));
3392 set.insert(CircuitId::new("2"));
3393 set.insert(CircuitId::new("1"));
3394 assert_eq!(set.len(), 2);
3395 }
3396
3397 #[test]
3398 fn test_stream_id_hash() {
3399 use std::collections::HashSet;
3400 let mut set = HashSet::new();
3401 set.insert(StreamId::new("1"));
3402 set.insert(StreamId::new("2"));
3403 set.insert(StreamId::new("1"));
3404 assert_eq!(set.len(), 2);
3405 }
3406
3407 #[test]
3408 fn test_parse_circuits_real_world_example() {
3409 let content = r#"7 BUILT $5CECC5C30ACC4B3DE462792323967087CC53D947~Quetzalcoatl,$51E1CF613FD6F9F11FE24743C91D6F9981807D82~DigiGesTor4e3,$B06F093A3D4DFAD3E923F4F28A74901BD4F74EB1~torserversNet BUILD_FLAGS=IS_INTERNAL,NEED_CAPACITY,NEED_UPTIME PURPOSE=HS_CLIENT_HSDIR HS_STATE=HSCI_CONNECTING TIME_CREATED=2023-06-15T10:30:45.123456"#;
3410 let circuits = parse_circuits(content).unwrap();
3411 assert_eq!(circuits.len(), 1);
3412 assert_eq!(circuits[0].id.0, "7");
3413 assert_eq!(circuits[0].status, CircStatus::Built);
3414 assert_eq!(circuits[0].path.len(), 3);
3415 assert_eq!(
3416 circuits[0].path[0].nickname,
3417 Some("Quetzalcoatl".to_string())
3418 );
3419 assert_eq!(
3420 circuits[0].path[1].nickname,
3421 Some("DigiGesTor4e3".to_string())
3422 );
3423 assert_eq!(
3424 circuits[0].path[2].nickname,
3425 Some("torserversNet".to_string())
3426 );
3427 }
3428
3429 #[test]
3430 fn test_parse_streams_real_world_example() {
3431 let content =
3432 "42 SUCCEEDED 7 www.torproject.org:443 SOURCE_ADDR=127.0.0.1:12345 PURPOSE=USER";
3433 let streams = parse_streams(content).unwrap();
3434 assert_eq!(streams.len(), 1);
3435 assert_eq!(streams[0].id.0, "42");
3436 assert_eq!(streams[0].status, StreamStatus::Succeeded);
3437 assert_eq!(streams[0].circuit_id, Some(CircuitId::new("7")));
3438 assert_eq!(streams[0].target_host, "www.torproject.org");
3439 assert_eq!(streams[0].target_port, 443);
3440 }
3441
3442 #[test]
3443 fn test_parse_add_onion_response_v3() {
3444 let content = "ServiceID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\nPrivateKey=ED25519-V3:base64keydata==";
3445 let response = parse_add_onion_response(content).unwrap();
3446 assert_eq!(response.service_id, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
3447 assert_eq!(response.private_key_type, Some("ED25519-V3".to_string()));
3448 assert_eq!(response.private_key, Some("base64keydata==".to_string()));
3449 }
3450
3451 #[test]
3452 fn test_parse_add_onion_response_discarded_key() {
3453 let content = "ServiceID=abcdefghijklmnopqrstuvwxyz234567abcdefghijklmnopqrstuv";
3454 let response = parse_add_onion_response(content).unwrap();
3455 assert_eq!(response.service_id, "abcdefghijklmnopqrstuvwxyz234567abcdefghijklmnopqrstuv");
3456 assert!(response.private_key.is_none());
3457 assert!(response.private_key_type.is_none());
3458 }
3459
3460 #[test]
3461 fn test_parse_add_onion_response_missing_service_id() {
3462 let content = "PrivateKey=ED25519-V3:base64keydata==";
3463 let result = parse_add_onion_response(content);
3464 assert!(result.is_err());
3465 }
3466
3467 #[test]
3468 fn test_listener_type_display() {
3469 assert_eq!(ListenerType::Or.to_string(), "or");
3470 assert_eq!(ListenerType::Dir.to_string(), "dir");
3471 assert_eq!(ListenerType::Socks.to_string(), "socks");
3472 assert_eq!(ListenerType::Trans.to_string(), "trans");
3473 assert_eq!(ListenerType::Natd.to_string(), "natd");
3474 assert_eq!(ListenerType::Dns.to_string(), "dns");
3475 assert_eq!(ListenerType::Control.to_string(), "control");
3476 assert_eq!(ListenerType::ExtOr.to_string(), "extor");
3477 assert_eq!(ListenerType::HttpTunnel.to_string(), "httptunnel");
3478 }
3479
3480 #[test]
3481 fn test_circuit_purpose_display() {
3482 assert_eq!(CircuitPurpose::General.to_string(), "general");
3483 assert_eq!(CircuitPurpose::Controller.to_string(), "controller");
3484 }
3485
3486 #[test]
3487 fn test_parse_listeners_single() {
3488 let response = r#""127.0.0.1:9050""#;
3489 let listeners = parse_listeners(response).unwrap();
3490 assert_eq!(listeners.len(), 1);
3491 assert_eq!(listeners[0], ("127.0.0.1".to_string(), 9050));
3492 }
3493
3494 #[test]
3495 fn test_parse_listeners_multiple() {
3496 let response = r#""127.0.0.1:9050" "0.0.0.0:9051""#;
3497 let listeners = parse_listeners(response).unwrap();
3498 assert_eq!(listeners.len(), 2);
3499 assert_eq!(listeners[0], ("127.0.0.1".to_string(), 9050));
3500 assert_eq!(listeners[1], ("0.0.0.0".to_string(), 9051));
3501 }
3502
3503 #[test]
3504 fn test_parse_listeners_ipv6() {
3505 let response = r#""[::1]:9050""#;
3506 let listeners = parse_listeners(response).unwrap();
3507 assert_eq!(listeners.len(), 1);
3508 assert_eq!(listeners[0], ("::1".to_string(), 9050));
3509 }
3510
3511 #[test]
3512 fn test_parse_listeners_empty() {
3513 let response = "";
3514 let listeners = parse_listeners(response).unwrap();
3515 assert!(listeners.is_empty());
3516 }
3517
3518 #[test]
3519 fn test_parse_listeners_unix_socket_skipped() {
3520 let response = r#""unix:/tmp/tor/socket" "127.0.0.1:9050""#;
3521 let listeners = parse_listeners(response).unwrap();
3522 assert_eq!(listeners.len(), 1);
3523 assert_eq!(listeners[0], ("127.0.0.1".to_string(), 9050));
3524 }
3525
3526 #[test]
3527 fn test_parse_protocolinfo_basic() {
3528 let content = r#"PROTOCOLINFO 1
3529AUTH METHODS=COOKIE,SAFECOOKIE COOKIEFILE="/var/run/tor/control.authcookie"
3530VERSION Tor="0.4.7.10"
3531OK"#;
3532 let info = parse_protocolinfo(content).unwrap();
3533 assert_eq!(info.protocol_version, 1);
3534 assert_eq!(info.tor_version, "0.4.7.10");
3535 assert_eq!(info.auth_methods, vec!["COOKIE", "SAFECOOKIE"]);
3536 assert_eq!(
3537 info.cookie_file,
3538 Some("/var/run/tor/control.authcookie".to_string())
3539 );
3540 }
3541
3542 #[test]
3543 fn test_parse_protocolinfo_null_auth() {
3544 let content = r#"PROTOCOLINFO 1
3545AUTH METHODS=NULL
3546VERSION Tor="0.4.7.10"
3547OK"#;
3548 let info = parse_protocolinfo(content).unwrap();
3549 assert_eq!(info.auth_methods, vec!["NULL"]);
3550 assert!(info.cookie_file.is_none());
3551 }
3552
3553 #[test]
3554 fn test_parse_accounting_bytes() {
3555 let bytes_str = "1234567 7654321";
3556 let (read, written) = parse_accounting_bytes(bytes_str).unwrap();
3557 assert_eq!(read, 1234567);
3558 assert_eq!(written, 7654321);
3559 }
3560
3561 #[test]
3562 fn test_parse_accounting_bytes_zero() {
3563 let bytes_str = "0 0";
3564 let (read, written) = parse_accounting_bytes(bytes_str).unwrap();
3565 assert_eq!(read, 0);
3566 assert_eq!(written, 0);
3567 }
3568
3569 #[test]
3570 fn test_parse_accounting_bytes_invalid() {
3571 let bytes_str = "invalid";
3572 assert!(parse_accounting_bytes(bytes_str).is_err());
3573 }
3574
3575 #[test]
3576 fn test_listener_type_equality() {
3577 assert_eq!(ListenerType::Socks, ListenerType::Socks);
3578 assert_ne!(ListenerType::Socks, ListenerType::Control);
3579 }
3580
3581 #[test]
3582 fn test_circuit_purpose_equality() {
3583 assert_eq!(CircuitPurpose::General, CircuitPurpose::General);
3584 assert_ne!(CircuitPurpose::General, CircuitPurpose::Controller);
3585 }
3586
3587 #[test]
3588 fn test_listener_type_hash() {
3589 let mut set = HashSet::new();
3590 set.insert(ListenerType::Socks);
3591 set.insert(ListenerType::Control);
3592 set.insert(ListenerType::Socks);
3593 assert_eq!(set.len(), 2);
3594 }
3595}