stem_rs/
socket.rs

1//! Control socket communication with Tor's control interface.
2//!
3//! This module provides low-level async socket communication with Tor's control
4//! interface. It handles both TCP and Unix domain socket connections, with proper
5//! message framing according to the Tor control protocol specification.
6//!
7//! # Conceptual Role
8//!
9//! The [`ControlSocket`] is the transport layer for the Tor control protocol. It
10//! handles:
11//!
12//! - Establishing TCP connections to Tor's ControlPort (typically port 9051)
13//! - Establishing Unix domain socket connections to Tor's ControlSocket
14//! - Sending formatted control protocol messages with proper CRLF termination
15//! - Receiving and parsing control protocol responses (single-line, multi-line, and data)
16//! - Connection lifecycle management and status tracking
17//!
18//! Most users should use the high-level [`Controller`](crate::Controller) API instead
19//! of this module directly. This module is primarily useful for:
20//!
21//! - Implementing custom control protocol extensions
22//! - Low-level debugging of control protocol communication
23//! - Building alternative high-level abstractions
24//!
25//! # Protocol Format
26//!
27//! The Tor control protocol uses a text-based format with CRLF line endings:
28//!
29//! - **Requests**: `COMMAND [ARGS]\r\n`
30//! - **Single-line responses**: `STATUS MESSAGE\r\n` (space divider)
31//! - **Multi-line responses**: `STATUS-LINE1\r\n STATUS-LINE2\r\n ... STATUS FINAL\r\n` (hyphen divider)
32//! - **Data responses**: `STATUS+KEYWORD\r\n DATA...\r\n .\r\n` (plus divider, dot terminator)
33//!
34//! Status codes follow HTTP conventions:
35//! - 2xx: Success
36//! - 4xx: Client error (bad request)
37//! - 5xx: Server error (Tor rejected the command)
38//! - 6xx: Asynchronous event notification
39//!
40//! # Example
41//!
42//! ```rust,no_run
43//! use stem_rs::ControlSocket;
44//! use std::net::SocketAddr;
45//!
46//! # async fn example() -> Result<(), stem_rs::Error> {
47//! // Connect to Tor's control port
48//! let addr: SocketAddr = "127.0.0.1:9051".parse().unwrap();
49//! let mut socket = ControlSocket::connect_port(addr).await?;
50//!
51//! // Query protocol info (no authentication required)
52//! socket.send("PROTOCOLINFO 1").await?;
53//! let response = socket.recv().await?;
54//!
55//! if response.is_ok() {
56//!     println!("Protocol info: {}", response.content());
57//! }
58//! # Ok(())
59//! # }
60//! ```
61//!
62//! # Thread Safety
63//!
64//! [`ControlSocket`] is `Send` but not `Sync`. The socket maintains internal
65//! read/write buffers that require exclusive access. For concurrent access from
66//! multiple tasks, wrap in `Arc<Mutex<ControlSocket>>` or use separate connections.
67//!
68//! # Platform Support
69//!
70//! - **TCP sockets**: Supported on all platforms
71//! - **Unix domain sockets**: Supported on Unix-like systems only (Linux, macOS, BSD)
72//!
73//! On non-Unix platforms, [`connect_unix`](ControlSocket::connect_unix) returns
74//! [`Error::Socket`](crate::Error::Socket) with `ErrorKind::Unsupported`.
75//!
76//! # See Also
77//!
78//! - [`crate::Controller`]: High-level API built on top of this socket
79//! - [`crate::auth`]: Authentication methods for the control connection
80//! - [`crate::protocol`]: Protocol message parsing utilities
81
82use std::net::SocketAddr;
83use std::path::Path;
84use std::time::Instant;
85
86use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
87use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
88use tokio::net::TcpStream;
89
90use crate::Error;
91
92#[cfg(unix)]
93use tokio::net::UnixStream;
94
95/// A connection to Tor's control interface.
96///
97/// `ControlSocket` provides async communication with Tor's control port using
98/// either TCP or Unix domain sockets. It handles the low-level details of the
99/// control protocol including message framing and response parsing.
100///
101/// # Conceptual Role
102///
103/// This is the transport layer for all control protocol communication. It:
104///
105/// - Manages the underlying TCP or Unix socket connection
106/// - Formats outgoing messages with proper CRLF termination
107/// - Parses incoming responses according to the control protocol spec
108/// - Tracks connection state and timing
109///
110/// # What This Type Does NOT Do
111///
112/// - Authentication (see [`crate::auth`])
113/// - High-level command abstractions (see [`crate::Controller`])
114/// - Event handling or subscription management
115/// - Connection pooling or automatic reconnection
116///
117/// # Invariants
118///
119/// - After successful construction, the socket is connected and ready for I/O
120/// - The socket remains valid until explicitly dropped or an I/O error occurs
121/// - Messages sent are automatically terminated with CRLF if not already present
122///
123/// # Thread Safety
124///
125/// `ControlSocket` is `Send` but not `Sync`. The internal buffers require
126/// exclusive access for reading and writing. For concurrent access:
127///
128/// ```rust,no_run
129/// use std::sync::Arc;
130/// use tokio::sync::Mutex;
131/// use stem_rs::ControlSocket;
132///
133/// # async fn example() -> Result<(), stem_rs::Error> {
134/// let socket = ControlSocket::connect_port("127.0.0.1:9051".parse()?).await?;
135/// let shared = Arc::new(Mutex::new(socket));
136///
137/// // Clone Arc for each task
138/// let s1 = shared.clone();
139/// tokio::spawn(async move {
140///     let mut sock = s1.lock().await;
141///     // Use socket exclusively
142/// });
143/// # Ok(())
144/// # }
145/// ```
146///
147/// # Example
148///
149/// ```rust,no_run
150/// use stem_rs::ControlSocket;
151///
152/// # async fn example() -> Result<(), stem_rs::Error> {
153/// let mut socket = ControlSocket::connect_port("127.0.0.1:9051".parse()?).await?;
154///
155/// // Send a command
156/// socket.send("GETINFO version").await?;
157///
158/// // Receive the response
159/// let response = socket.recv().await?;
160/// if response.is_ok() {
161///     println!("Tor version: {}", response.content());
162/// }
163/// # Ok(())
164/// # }
165/// ```
166pub struct ControlSocket {
167    reader: SocketReader,
168    writer: SocketWriter,
169    connection_time: Instant,
170}
171
172enum SocketReader {
173    Tcp(BufReader<OwnedReadHalf>),
174    #[cfg(unix)]
175    Unix(BufReader<tokio::net::unix::OwnedReadHalf>),
176}
177
178enum SocketWriter {
179    Tcp(OwnedWriteHalf),
180    #[cfg(unix)]
181    Unix(tokio::net::unix::OwnedWriteHalf),
182}
183
184impl ControlSocket {
185    /// Connects to Tor's control port via TCP.
186    ///
187    /// Establishes a TCP connection to the specified address, which should be
188    /// Tor's ControlPort (typically `127.0.0.1:9051` or as configured in torrc).
189    ///
190    /// # Preconditions
191    ///
192    /// - Tor must be running with a ControlPort configured
193    /// - The address must be reachable from this host
194    /// - No firewall rules blocking the connection
195    ///
196    /// # Postconditions
197    ///
198    /// - On success: Returns a connected socket ready for I/O
199    /// - The connection time is recorded for later retrieval
200    ///
201    /// # Arguments
202    ///
203    /// * `addr` - The socket address to connect to (IP and port)
204    ///
205    /// # Errors
206    ///
207    /// Returns [`Error::Socket`](crate::Error::Socket) if:
208    ///
209    /// - The connection is refused (Tor not running or wrong port)
210    /// - The address is unreachable (network error)
211    /// - The connection times out
212    /// - Any other I/O error occurs
213    ///
214    /// # Example
215    ///
216    /// ```rust,no_run
217    /// use stem_rs::ControlSocket;
218    /// use std::net::SocketAddr;
219    ///
220    /// # async fn example() -> Result<(), stem_rs::Error> {
221    /// // Connect to default control port
222    /// let addr: SocketAddr = "127.0.0.1:9051".parse().unwrap();
223    /// let socket = ControlSocket::connect_port(addr).await?;
224    ///
225    /// // Or parse directly
226    /// let socket = ControlSocket::connect_port("127.0.0.1:9051".parse()?).await?;
227    /// # Ok(())
228    /// # }
229    /// ```
230    pub async fn connect_port(addr: SocketAddr) -> Result<Self, Error> {
231        let stream = TcpStream::connect(addr).await?;
232        let (read_half, write_half) = stream.into_split();
233        Ok(Self {
234            reader: SocketReader::Tcp(BufReader::new(read_half)),
235            writer: SocketWriter::Tcp(write_half),
236            connection_time: Instant::now(),
237        })
238    }
239
240    /// Connects to Tor's control socket via Unix domain socket.
241    ///
242    /// Establishes a Unix domain socket connection to the specified path, which
243    /// should be Tor's ControlSocket (typically `/var/run/tor/control` or as
244    /// configured in torrc).
245    ///
246    /// # Platform Support
247    ///
248    /// This method is only available on Unix-like systems (Linux, macOS, BSD).
249    /// On other platforms, it returns [`Error::Socket`](crate::Error::Socket)
250    /// with `ErrorKind::Unsupported`.
251    ///
252    /// # Preconditions
253    ///
254    /// - Tor must be running with a ControlSocket configured
255    /// - The socket file must exist and be accessible
256    /// - The current process must have permission to connect to the socket
257    ///
258    /// # Postconditions
259    ///
260    /// - On success: Returns a connected socket ready for I/O
261    /// - The connection time is recorded for later retrieval
262    ///
263    /// # Arguments
264    ///
265    /// * `path` - Path to the Unix domain socket file
266    ///
267    /// # Errors
268    ///
269    /// Returns [`Error::Socket`](crate::Error::Socket) if:
270    ///
271    /// - The socket file does not exist
272    /// - Permission denied (insufficient privileges)
273    /// - The path is not a socket
274    /// - Platform does not support Unix sockets
275    /// - Any other I/O error occurs
276    ///
277    /// # Example
278    ///
279    /// ```rust,no_run
280    /// use stem_rs::ControlSocket;
281    /// use std::path::Path;
282    ///
283    /// # async fn example() -> Result<(), stem_rs::Error> {
284    /// // Connect to default control socket
285    /// let socket = ControlSocket::connect_unix(Path::new("/var/run/tor/control")).await?;
286    ///
287    /// // Or a custom path
288    /// let socket = ControlSocket::connect_unix(Path::new("/tmp/tor/control")).await?;
289    /// # Ok(())
290    /// # }
291    /// ```
292    #[cfg(unix)]
293    pub async fn connect_unix(path: &Path) -> Result<Self, Error> {
294        let stream = UnixStream::connect(path).await?;
295        let (read_half, write_half) = stream.into_split();
296        Ok(Self {
297            reader: SocketReader::Unix(BufReader::new(read_half)),
298            writer: SocketWriter::Unix(write_half),
299            connection_time: Instant::now(),
300        })
301    }
302
303    /// Connects to Tor's control socket via Unix domain socket (non-Unix stub).
304    ///
305    /// This is a stub implementation for non-Unix platforms that always returns
306    /// an error indicating Unix sockets are not supported.
307    ///
308    /// # Errors
309    ///
310    /// Always returns [`Error::Socket`](crate::Error::Socket) with
311    /// `ErrorKind::Unsupported` on non-Unix platforms.
312    #[cfg(not(unix))]
313    pub async fn connect_unix(_path: &Path) -> Result<Self, Error> {
314        Err(Error::Socket(std::io::Error::new(
315            std::io::ErrorKind::Unsupported,
316            "Unix sockets not supported on this platform",
317        )))
318    }
319
320    /// Sends a message to the control socket.
321    ///
322    /// Formats and sends a control protocol message to Tor. The message is
323    /// automatically terminated with CRLF (`\r\n`) if not already present.
324    ///
325    /// # Protocol Format
326    ///
327    /// Messages are sent as: `MESSAGE\r\n`
328    ///
329    /// For multi-line data, use the `+` prefix format manually or use the
330    /// higher-level [`Controller`](crate::Controller) API.
331    ///
332    /// # Preconditions
333    ///
334    /// - The socket must be connected (not closed)
335    /// - For authenticated commands, authentication must have succeeded
336    ///
337    /// # Postconditions
338    ///
339    /// - On success: The message has been written and flushed to the socket
340    /// - The socket remains ready for subsequent operations
341    ///
342    /// # Arguments
343    ///
344    /// * `message` - The control protocol message to send (without CRLF)
345    ///
346    /// # Errors
347    ///
348    /// Returns [`Error::Socket`](crate::Error::Socket) if:
349    ///
350    /// - The socket has been closed or disconnected
351    /// - A write error occurs
352    /// - The flush operation fails
353    ///
354    /// # Example
355    ///
356    /// ```rust,no_run
357    /// use stem_rs::ControlSocket;
358    ///
359    /// # async fn example() -> Result<(), stem_rs::Error> {
360    /// let mut socket = ControlSocket::connect_port("127.0.0.1:9051".parse()?).await?;
361    ///
362    /// // Send PROTOCOLINFO (no auth required)
363    /// socket.send("PROTOCOLINFO 1").await?;
364    ///
365    /// // Send GETINFO (requires auth)
366    /// socket.send("GETINFO version").await?;
367    ///
368    /// // CRLF is added automatically, but explicit is also fine
369    /// socket.send("GETINFO config-file\r\n").await?;
370    /// # Ok(())
371    /// # }
372    /// ```
373    pub async fn send(&mut self, message: &str) -> Result<(), Error> {
374        let formatted = if message.ends_with("\r\n") {
375            message.to_string()
376        } else {
377            format!("{}\r\n", message)
378        };
379
380        match &mut self.writer {
381            SocketWriter::Tcp(w) => {
382                w.write_all(formatted.as_bytes()).await?;
383                w.flush().await?;
384            }
385            #[cfg(unix)]
386            SocketWriter::Unix(w) => {
387                w.write_all(formatted.as_bytes()).await?;
388                w.flush().await?;
389            }
390        }
391        Ok(())
392    }
393
394    /// Receives a message from the control socket.
395    ///
396    /// Reads and parses a complete control protocol response from Tor. This method
397    /// blocks until a complete message is received, handling single-line, multi-line,
398    /// and data responses according to the protocol specification.
399    ///
400    /// # Protocol Format
401    ///
402    /// Responses follow the format: `STATUS[DIVIDER]CONTENT\r\n`
403    ///
404    /// Where DIVIDER indicates the response type:
405    /// - ` ` (space): Final line of response
406    /// - `-` (hyphen): Continuation line (more lines follow)
407    /// - `+` (plus): Data block follows, terminated by `.\r\n`
408    ///
409    /// # Response Types
410    ///
411    /// **Single-line response:**
412    /// ```text
413    /// 250 OK\r\n
414    /// ```
415    ///
416    /// **Multi-line response:**
417    /// ```text
418    /// 250-version=0.4.7.1\r\n
419    /// 250-config-file=/etc/tor/torrc\r\n
420    /// 250 OK\r\n
421    /// ```
422    ///
423    /// **Data response:**
424    /// ```text
425    /// 250+getinfo/names=\r\n
426    /// accounting/bytes -- Number of bytes...\r\n
427    /// accounting/enabled -- Is accounting enabled?\r\n
428    /// .\r\n
429    /// 250 OK\r\n
430    /// ```
431    ///
432    /// # Postconditions
433    ///
434    /// - On success: Returns a complete [`ControlMessage`] with all response lines
435    /// - The socket remains ready for subsequent operations
436    ///
437    /// # Errors
438    ///
439    /// Returns an error if:
440    ///
441    /// - [`Error::SocketClosed`](crate::Error::SocketClosed): The socket was closed
442    ///   before a complete message was received
443    /// - [`Error::Protocol`](crate::Error::Protocol): The response is malformed:
444    ///   - Line too short (less than 4 characters)
445    ///   - Invalid status code (not a 3-digit number)
446    ///   - Inconsistent status codes across lines
447    ///   - Invalid divider character
448    ///
449    /// # Example
450    ///
451    /// ```rust,no_run
452    /// use stem_rs::ControlSocket;
453    ///
454    /// # async fn example() -> Result<(), stem_rs::Error> {
455    /// let mut socket = ControlSocket::connect_port("127.0.0.1:9051".parse()?).await?;
456    ///
457    /// socket.send("PROTOCOLINFO 1").await?;
458    /// let response = socket.recv().await?;
459    ///
460    /// if response.is_ok() {
461    ///     // Single-line content
462    ///     println!("First line: {}", response.content());
463    ///     
464    ///     // All lines joined
465    ///     println!("Full response: {}", response.all_content());
466    /// } else {
467    ///     println!("Error {}: {}", response.status_code, response.content());
468    /// }
469    /// # Ok(())
470    /// # }
471    /// ```
472    pub async fn recv(&mut self) -> Result<ControlMessage, Error> {
473        let mut lines = Vec::new();
474        let mut status_code: Option<u16> = None;
475
476        loop {
477            let mut line = String::new();
478            let bytes_read = match &mut self.reader {
479                SocketReader::Tcp(r) => r.read_line(&mut line).await?,
480                #[cfg(unix)]
481                SocketReader::Unix(r) => r.read_line(&mut line).await?,
482            };
483
484            if bytes_read == 0 {
485                return Err(Error::SocketClosed);
486            }
487
488            let line = line.trim_end_matches(['\r', '\n']);
489            if line.len() < 4 {
490                return Err(Error::Protocol(format!(
491                    "response line too short: {}",
492                    line
493                )));
494            }
495
496            let code: u16 = line[..3]
497                .parse()
498                .map_err(|_| Error::Protocol(format!("invalid status code: {}", &line[..3])))?;
499
500            match status_code {
501                None => status_code = Some(code),
502                Some(existing) if existing != code => {
503                    return Err(Error::Protocol(format!(
504                        "inconsistent status codes: {} vs {}",
505                        existing, code
506                    )));
507                }
508                _ => {}
509            }
510
511            let divider = line.chars().nth(3).unwrap_or(' ');
512            let content = &line[4..];
513
514            match divider {
515                ' ' => {
516                    lines.push(content.to_string());
517                    break;
518                }
519                '-' => {
520                    lines.push(content.to_string());
521                }
522                '+' => {
523                    let mut multi_line_content = content.to_string();
524                    loop {
525                        let mut data_line = String::new();
526                        let bytes = match &mut self.reader {
527                            SocketReader::Tcp(r) => r.read_line(&mut data_line).await?,
528                            #[cfg(unix)]
529                            SocketReader::Unix(r) => r.read_line(&mut data_line).await?,
530                        };
531                        if bytes == 0 {
532                            return Err(Error::SocketClosed);
533                        }
534                        let data_line = data_line.trim_end_matches(['\r', '\n']);
535                        if data_line == "." {
536                            break;
537                        }
538                        let unescaped = data_line.strip_prefix('.').unwrap_or(data_line);
539                        if !multi_line_content.is_empty() {
540                            multi_line_content.push('\n');
541                        }
542                        multi_line_content.push_str(unescaped);
543                    }
544                    lines.push(multi_line_content);
545                }
546                _ => {
547                    return Err(Error::Protocol(format!("invalid divider: {}", divider)));
548                }
549            }
550        }
551
552        Ok(ControlMessage {
553            status_code: status_code.unwrap_or(0),
554            lines,
555        })
556    }
557
558    /// Checks if the socket connection is alive.
559    ///
560    /// Returns whether the socket is believed to be connected. Note that this
561    /// is a best-effort check and may not detect all disconnection scenarios
562    /// until an actual I/O operation is attempted.
563    ///
564    /// # Limitations
565    ///
566    /// This method currently always returns `true` as the underlying socket
567    /// state is not actively monitored. A disconnection will only be detected
568    /// when [`send`](Self::send) or [`recv`](Self::recv) fails.
569    ///
570    /// For reliable disconnection detection, continuously poll with
571    /// [`recv`](Self::recv) or use the higher-level
572    /// [`Controller`](crate::Controller) which handles this automatically.
573    ///
574    /// # Returns
575    ///
576    /// `true` if the socket is believed to be connected, `false` otherwise.
577    ///
578    /// # Example
579    ///
580    /// ```rust,no_run
581    /// use stem_rs::ControlSocket;
582    ///
583    /// # async fn example() -> Result<(), stem_rs::Error> {
584    /// let socket = ControlSocket::connect_port("127.0.0.1:9051".parse()?).await?;
585    ///
586    /// if socket.is_alive() {
587    ///     println!("Socket is connected");
588    /// }
589    /// # Ok(())
590    /// # }
591    /// ```
592    pub fn is_alive(&self) -> bool {
593        true
594    }
595
596    /// Returns the time when the socket connection was established.
597    ///
598    /// This returns the [`Instant`] when the socket was successfully connected,
599    /// which can be used to calculate connection duration or implement
600    /// connection timeouts.
601    ///
602    /// # Returns
603    ///
604    /// The [`Instant`] when [`connect_port`](Self::connect_port) or
605    /// [`connect_unix`](Self::connect_unix) successfully completed.
606    ///
607    /// # Example
608    ///
609    /// ```rust,no_run
610    /// use stem_rs::ControlSocket;
611    /// use std::time::Duration;
612    ///
613    /// # async fn example() -> Result<(), stem_rs::Error> {
614    /// let socket = ControlSocket::connect_port("127.0.0.1:9051".parse()?).await?;
615    ///
616    /// // ... do some work ...
617    ///
618    /// let connected_for = socket.connection_time().elapsed();
619    /// println!("Connected for {:?}", connected_for);
620    ///
621    /// // Implement a connection timeout
622    /// if socket.connection_time().elapsed() > Duration::from_secs(3600) {
623    ///     println!("Connection has been open for over an hour");
624    /// }
625    /// # Ok(())
626    /// # }
627    /// ```
628    pub fn connection_time(&self) -> Instant {
629        self.connection_time
630    }
631}
632
633/// A parsed control protocol response message.
634///
635/// `ControlMessage` represents a complete response from Tor's control interface,
636/// containing the status code and all response lines. It provides methods to
637/// check success/failure and access the response content.
638///
639/// # Protocol Format
640///
641/// Control protocol responses consist of:
642/// - A 3-digit status code (similar to HTTP)
643/// - One or more content lines
644///
645/// Status code ranges:
646/// - **2xx**: Success (command accepted)
647/// - **4xx**: Temporary failure (try again later)
648/// - **5xx**: Permanent failure (command rejected)
649/// - **6xx**: Asynchronous event notification
650///
651/// # Example
652///
653/// ```rust,no_run
654/// use stem_rs::ControlSocket;
655///
656/// # async fn example() -> Result<(), stem_rs::Error> {
657/// let mut socket = ControlSocket::connect_port("127.0.0.1:9051".parse()?).await?;
658///
659/// socket.send("GETINFO version").await?;
660/// let msg = socket.recv().await?;
661///
662/// // Check if successful
663/// if msg.is_ok() {
664///     // Get first line content
665///     println!("Version: {}", msg.content());
666/// } else {
667///     // Handle error
668///     println!("Error {}: {}", msg.status_code, msg.content());
669/// }
670/// # Ok(())
671/// # }
672/// ```
673#[derive(Debug, Clone)]
674pub struct ControlMessage {
675    /// The 3-digit status code from the response.
676    ///
677    /// Common status codes:
678    /// - `250`: Command successful
679    /// - `251`: Resource exhausted
680    /// - `451`: Resource temporarily unavailable
681    /// - `500`: Syntax error
682    /// - `510`: Unrecognized command
683    /// - `515`: Authentication required/failed
684    /// - `550`: Unspecified error
685    /// - `650`: Asynchronous event
686    pub status_code: u16,
687
688    /// The content lines from the response.
689    ///
690    /// For single-line responses, this contains one element.
691    /// For multi-line responses, each line is a separate element.
692    /// For data responses, the data block content is included.
693    pub lines: Vec<String>,
694}
695
696impl ControlMessage {
697    /// Checks if the response indicates success.
698    ///
699    /// Returns `true` if the status code is in the 2xx range (200-299),
700    /// indicating the command was accepted and executed successfully.
701    ///
702    /// # Returns
703    ///
704    /// `true` if `status_code` is between 200 and 299 inclusive.
705    ///
706    /// # Example
707    ///
708    /// ```rust
709    /// use stem_rs::socket::ControlMessage;
710    ///
711    /// let success = ControlMessage {
712    ///     status_code: 250,
713    ///     lines: vec!["OK".to_string()],
714    /// };
715    /// assert!(success.is_ok());
716    ///
717    /// let error = ControlMessage {
718    ///     status_code: 515,
719    ///     lines: vec!["Authentication failed".to_string()],
720    /// };
721    /// assert!(!error.is_ok());
722    /// ```
723    pub fn is_ok(&self) -> bool {
724        self.status_code >= 200 && self.status_code < 300
725    }
726
727    /// Returns the first line of response content.
728    ///
729    /// For most responses, this is the primary content. For multi-line
730    /// responses, use [`all_content`](Self::all_content) to get all lines.
731    ///
732    /// # Returns
733    ///
734    /// The first line of content, or an empty string if there are no lines.
735    ///
736    /// # Example
737    ///
738    /// ```rust
739    /// use stem_rs::socket::ControlMessage;
740    ///
741    /// let msg = ControlMessage {
742    ///     status_code: 250,
743    ///     lines: vec!["version=0.4.7.1".to_string()],
744    /// };
745    /// assert_eq!(msg.content(), "version=0.4.7.1");
746    ///
747    /// let empty = ControlMessage {
748    ///     status_code: 250,
749    ///     lines: vec![],
750    /// };
751    /// assert_eq!(empty.content(), "");
752    /// ```
753    pub fn content(&self) -> &str {
754        self.lines.first().map(|s| s.as_str()).unwrap_or("")
755    }
756
757    /// Returns all response lines joined with newlines.
758    ///
759    /// Combines all content lines into a single string, separated by
760    /// newline characters (`\n`). Useful for multi-line responses.
761    ///
762    /// # Returns
763    ///
764    /// All lines joined with `\n` separators.
765    ///
766    /// # Example
767    ///
768    /// ```rust
769    /// use stem_rs::socket::ControlMessage;
770    ///
771    /// let msg = ControlMessage {
772    ///     status_code: 250,
773    ///     lines: vec![
774    ///         "version=0.4.7.1".to_string(),
775    ///         "config-file=/etc/tor/torrc".to_string(),
776    ///         "OK".to_string(),
777    ///     ],
778    /// };
779    /// assert_eq!(
780    ///     msg.all_content(),
781    ///     "version=0.4.7.1\nconfig-file=/etc/tor/torrc\nOK"
782    /// );
783    /// ```
784    pub fn all_content(&self) -> String {
785        self.lines.join("\n")
786    }
787
788    /// Returns the raw protocol representation of the response.
789    ///
790    /// Reconstructs the response in control protocol format with status
791    /// codes and CRLF line endings. Useful for debugging or logging.
792    ///
793    /// # Returns
794    ///
795    /// The response formatted as it would appear on the wire.
796    ///
797    /// # Example
798    ///
799    /// ```rust
800    /// use stem_rs::socket::ControlMessage;
801    ///
802    /// let msg = ControlMessage {
803    ///     status_code: 250,
804    ///     lines: vec![
805    ///         "version=0.4.7.1".to_string(),
806    ///         "OK".to_string(),
807    ///     ],
808    /// };
809    /// assert_eq!(msg.raw_content(), "250-version=0.4.7.1\r\n250 OK\r\n");
810    /// ```
811    pub fn raw_content(&self) -> String {
812        let mut result = String::new();
813        for (i, line) in self.lines.iter().enumerate() {
814            if i == self.lines.len() - 1 {
815                result.push_str(&format!("{} {}\r\n", self.status_code, line));
816            } else {
817                result.push_str(&format!("{}-{}\r\n", self.status_code, line));
818            }
819        }
820        result
821    }
822}
823
824#[cfg(test)]
825mod tests {
826    use super::*;
827
828    #[test]
829    fn test_control_message_is_ok() {
830        let msg = ControlMessage {
831            status_code: 250,
832            lines: vec!["OK".to_string()],
833        };
834        assert!(msg.is_ok());
835
836        let msg = ControlMessage {
837            status_code: 200,
838            lines: vec!["OK".to_string()],
839        };
840        assert!(msg.is_ok());
841
842        let msg = ControlMessage {
843            status_code: 299,
844            lines: vec!["OK".to_string()],
845        };
846        assert!(msg.is_ok());
847    }
848
849    #[test]
850    fn test_control_message_not_ok() {
851        let msg = ControlMessage {
852            status_code: 500,
853            lines: vec!["Error".to_string()],
854        };
855        assert!(!msg.is_ok());
856
857        let msg = ControlMessage {
858            status_code: 515,
859            lines: vec!["Authentication failed".to_string()],
860        };
861        assert!(!msg.is_ok());
862    }
863
864    #[test]
865    fn test_control_message_content() {
866        let msg = ControlMessage {
867            status_code: 250,
868            lines: vec!["version=0.4.7.1".to_string()],
869        };
870        assert_eq!(msg.content(), "version=0.4.7.1");
871    }
872
873    #[test]
874    fn test_control_message_empty_content() {
875        let msg = ControlMessage {
876            status_code: 250,
877            lines: vec![],
878        };
879        assert_eq!(msg.content(), "");
880    }
881
882    #[test]
883    fn test_control_message_all_content() {
884        let msg = ControlMessage {
885            status_code: 250,
886            lines: vec![
887                "line1".to_string(),
888                "line2".to_string(),
889                "line3".to_string(),
890            ],
891        };
892        assert_eq!(msg.all_content(), "line1\nline2\nline3");
893    }
894
895    #[test]
896    fn test_control_message_status_code_ranges() {
897        for code in [200, 250, 251, 299] {
898            let msg = ControlMessage {
899                status_code: code,
900                lines: vec!["OK".to_string()],
901            };
902            assert!(msg.is_ok(), "Status code {} should be OK", code);
903        }
904
905        for code in [400, 450, 499] {
906            let msg = ControlMessage {
907                status_code: code,
908                lines: vec!["Error".to_string()],
909            };
910            assert!(!msg.is_ok(), "Status code {} should not be OK", code);
911        }
912
913        for code in [500, 510, 515, 550, 599] {
914            let msg = ControlMessage {
915                status_code: code,
916                lines: vec!["Error".to_string()],
917            };
918            assert!(!msg.is_ok(), "Status code {} should not be OK", code);
919        }
920
921        for code in [650, 651] {
922            let msg = ControlMessage {
923                status_code: code,
924                lines: vec!["Event".to_string()],
925            };
926            assert!(!msg.is_ok(), "Status code {} should not be OK", code);
927        }
928    }
929
930    #[test]
931    fn test_control_message_multiline_content() {
932        let msg = ControlMessage {
933            status_code: 250,
934            lines: vec![
935                "First line".to_string(),
936                "Second line with data".to_string(),
937                "Third line".to_string(),
938            ],
939        };
940        assert_eq!(msg.content(), "First line");
941        assert_eq!(
942            msg.all_content(),
943            "First line\nSecond line with data\nThird line"
944        );
945    }
946
947    #[test]
948    fn test_control_message_single_line() {
949        let msg = ControlMessage {
950            status_code: 250,
951            lines: vec!["Single line response".to_string()],
952        };
953        assert_eq!(msg.content(), "Single line response");
954        assert_eq!(msg.all_content(), "Single line response");
955    }
956
957    #[test]
958    fn test_control_message_clone() {
959        let msg = ControlMessage {
960            status_code: 250,
961            lines: vec!["Test".to_string()],
962        };
963        let cloned = msg.clone();
964        assert_eq!(msg.status_code, cloned.status_code);
965        assert_eq!(msg.lines, cloned.lines);
966    }
967
968    #[test]
969    fn test_control_message_debug() {
970        let msg = ControlMessage {
971            status_code: 250,
972            lines: vec!["OK".to_string()],
973        };
974        let debug_str = format!("{:?}", msg);
975        assert!(debug_str.contains("250"));
976        assert!(debug_str.contains("OK"));
977    }
978}