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}