stem_rs/response/
mod.rs

1//! Response parsing for Tor control protocol messages.
2//!
3//! This module provides types for parsing and handling responses from Tor's control
4//! protocol. The control protocol uses a text-based format where responses consist
5//! of status codes, dividers, and content.
6//!
7//! # Overview
8//!
9//! The Tor control protocol response format follows this structure:
10//!
11//! - Single-line responses: `STATUS DIVIDER CONTENT\r\n`
12//! - Multi-line responses: Multiple lines with `-` or `+` dividers, ending with ` `
13//!
14//! Where:
15//! - `STATUS` is a 3-digit code (e.g., `250` for success, `5xx` for errors)
16//! - `DIVIDER` is one of:
17//!   - ` ` (space): End of response
18//!   - `-`: More lines follow
19//!   - `+`: Data section follows (terminated by `.\r\n`)
20//! - `CONTENT` is the payload of the line
21//!
22//! # Primary Types
23//!
24//! - [`ControlMessage`]: Represents a complete control protocol response
25//! - [`ControlLine`]: A single line with parsing utilities for extracting values
26//! - [`SingleLineResponse`]: A simple response containing only one line
27//!
28//! # Response-Specific Types
29//!
30//! Each submodule provides specialized parsing for specific command responses:
31//!
32//! - [`add_onion`]: ADD_ONION response parsing
33//! - [`authchallenge`]: AUTHCHALLENGE response for SAFECOOKIE authentication
34//! - [`events`]: Asynchronous event parsing
35//! - [`getconf`]: GETCONF response parsing
36//! - [`getinfo`]: GETINFO response parsing
37//! - [`mapaddress`]: MAPADDRESS response parsing
38//! - [`onion_client_auth`]: ONION_CLIENT_AUTH_VIEW response parsing
39//! - [`protocolinfo`]: PROTOCOLINFO response parsing
40//!
41//! # Example
42//!
43//! ```rust
44//! use stem_rs::response::{ControlMessage, ControlLine};
45//!
46//! // Parse a simple OK response
47//! let msg = ControlMessage::from_str("250 OK\r\n", None, false).unwrap();
48//! assert!(msg.is_ok());
49//!
50//! // Parse a multi-line GETINFO response
51//! let response = "250-version=0.4.7.8\r\n250 OK\r\n";
52//! let msg = ControlMessage::from_str(response, None, false).unwrap();
53//! assert_eq!(msg.len(), 2);
54//!
55//! // Iterate over response lines
56//! for line in msg.iter() {
57//!     println!("Line: {}", line);
58//! }
59//! ```
60//!
61//! # Thread Safety
62//!
63//! [`ControlMessage`] is `Send` and `Sync`. [`ControlLine`] uses internal
64//! synchronization for its mutable parsing state, making it safe to share
65//! across threads.
66//!
67//! # See Also
68//!
69//! - [`crate::Controller`]: High-level API that uses these response types
70//! - [`crate::socket::ControlSocket`]: Low-level socket that produces these messages
71//! - [Tor Control Protocol Specification](https://spec.torproject.org/control-spec)
72
73pub mod add_onion;
74pub mod authchallenge;
75pub mod events;
76pub mod getconf;
77pub mod getinfo;
78pub mod mapaddress;
79pub mod onion_client_auth;
80pub mod protocolinfo;
81
82use std::hash::{Hash, Hasher};
83use std::sync::Mutex;
84
85use crate::Error;
86
87pub use add_onion::AddOnionResponse;
88pub use authchallenge::AuthChallengeResponse;
89pub use getconf::GetConfResponse;
90pub use getinfo::GetInfoResponse;
91pub use mapaddress::MapAddressResponse;
92pub use onion_client_auth::OnionClientAuthViewResponse;
93pub use protocolinfo::{AuthMethod, ProtocolInfoResponse};
94
95/// A parsed control protocol message from Tor.
96///
97/// `ControlMessage` represents a complete response from Tor's control interface.
98/// It handles both single-line and multi-line responses, parsing the status codes,
99/// dividers, and content according to the control protocol specification.
100///
101/// # Response Format
102///
103/// Each line in a control message has the format:
104/// ```text
105/// STATUS DIVIDER CONTENT
106/// ```
107///
108/// Where STATUS is a 3-digit code, DIVIDER indicates continuation, and CONTENT
109/// is the payload. The parsed content is stored as tuples of `(status_code, divider, content)`.
110///
111/// # Status Codes
112///
113/// - `2xx`: Success (e.g., `250 OK`)
114/// - `4xx`: Temporary failure
115/// - `5xx`: Permanent failure (e.g., `552 Unrecognized key`)
116/// - `6xx`: Asynchronous event notification
117///
118/// # Invariants
119///
120/// - A `ControlMessage` is never empty; construction fails if no valid lines exist
121/// - The `arrived_at` timestamp is set at construction time
122/// - Raw content preserves the original bytes for hashing and equality
123///
124/// # Thread Safety
125///
126/// `ControlMessage` is `Send` and `Sync`. It is immutable after construction.
127///
128/// # Example
129///
130/// ```rust
131/// use stem_rs::response::ControlMessage;
132///
133/// // Parse a GETINFO response
134/// let response = "250-version=0.4.7.8\r\n250 OK\r\n";
135/// let msg = ControlMessage::from_str(response, None, false).unwrap();
136///
137/// // Check if successful
138/// assert!(msg.is_ok());
139///
140/// // Access parsed content
141/// let content = msg.content();
142/// assert_eq!(content[0].0, "250"); // status code
143/// assert_eq!(content[0].1, '-');   // divider (more lines follow)
144///
145/// // Iterate over lines
146/// for line in msg.iter() {
147///     if let Ok((key, value)) = line.clone().pop_mapping(false, false) {
148///         println!("{} = {}", key, value);
149///     }
150/// }
151/// ```
152#[derive(Debug, Clone)]
153pub struct ControlMessage {
154    /// Parsed content as tuples of (status_code, divider, content_bytes).
155    parsed_content: Vec<(String, char, Vec<u8>)>,
156    /// Original raw bytes received from the socket.
157    raw_content: Vec<u8>,
158    /// Unix timestamp (seconds since epoch) when this message arrived.
159    pub arrived_at: i64,
160}
161
162impl ControlMessage {
163    /// Creates a new `ControlMessage` from pre-parsed content.
164    ///
165    /// This is a low-level constructor typically used internally. Most users
166    /// should use [`from_str`](Self::from_str) instead.
167    ///
168    /// # Arguments
169    ///
170    /// * `parsed_content` - Vector of (status_code, divider, content) tuples
171    /// * `raw_content` - Original raw bytes of the message
172    /// * `arrived_at` - Optional Unix timestamp; defaults to current time if `None`
173    ///
174    /// # Errors
175    ///
176    /// Returns [`Error::Protocol`](crate::Error::Protocol) if `parsed_content` is empty,
177    /// as control messages must contain at least one line.
178    ///
179    /// # Example
180    ///
181    /// ```rust
182    /// use stem_rs::response::ControlMessage;
183    ///
184    /// let parsed = vec![("250".to_string(), ' ', b"OK".to_vec())];
185    /// let raw = b"250 OK\r\n".to_vec();
186    /// let msg = ControlMessage::new(parsed, raw, None).unwrap();
187    /// assert!(msg.is_ok());
188    /// ```
189    pub fn new(
190        parsed_content: Vec<(String, char, Vec<u8>)>,
191        raw_content: Vec<u8>,
192        arrived_at: Option<i64>,
193    ) -> Result<Self, Error> {
194        if parsed_content.is_empty() {
195            return Err(Error::Protocol(
196                "ControlMessages can't be empty".to_string(),
197            ));
198        }
199        Ok(Self {
200            parsed_content,
201            raw_content,
202            arrived_at: arrived_at.unwrap_or_else(|| {
203                std::time::SystemTime::now()
204                    .duration_since(std::time::UNIX_EPOCH)
205                    .map(|d| d.as_secs() as i64)
206                    .unwrap_or(0)
207            }),
208        })
209    }
210
211    /// Parses a control message from a string.
212    ///
213    /// This is the primary way to create a `ControlMessage` from raw protocol data.
214    /// It handles both single-line and multi-line responses, including data sections.
215    ///
216    /// # Arguments
217    ///
218    /// * `content` - The raw message string to parse
219    /// * `msg_type` - Optional response type for validation (e.g., "SINGLELINE", "GETINFO")
220    /// * `normalize` - If `true`, ensures proper `\r\n` line endings
221    ///
222    /// # Normalization
223    ///
224    /// When `normalize` is `true`:
225    /// - Adds a trailing newline if missing
226    /// - Converts `\n` to `\r\n` (CRLF) as required by the protocol
227    ///
228    /// # Errors
229    ///
230    /// Returns [`Error::Protocol`](crate::Error::Protocol) if:
231    /// - The content contains no valid control protocol lines
232    /// - The specified `msg_type` validation fails
233    ///
234    /// # Example
235    ///
236    /// ```rust
237    /// use stem_rs::response::ControlMessage;
238    ///
239    /// // Parse without normalization (content already has \r\n)
240    /// let msg = ControlMessage::from_str("250 OK\r\n", None, false).unwrap();
241    ///
242    /// // Parse with normalization (adds \r\n if needed)
243    /// let msg = ControlMessage::from_str("250 OK", None, true).unwrap();
244    ///
245    /// // Parse and validate as single-line response
246    /// let msg = ControlMessage::from_str("250 OK\r\n", Some("SINGLELINE"), false).unwrap();
247    /// ```
248    pub fn from_str(content: &str, msg_type: Option<&str>, normalize: bool) -> Result<Self, Error> {
249        let mut content_bytes = content.as_bytes().to_vec();
250
251        if normalize {
252            if !content_bytes.ends_with(b"\n") {
253                content_bytes.push(b'\n');
254            }
255            let mut normalized = Vec::with_capacity(content_bytes.len() * 2);
256            let mut i = 0;
257            while i < content_bytes.len() {
258                if content_bytes[i] == b'\n' {
259                    if i == 0 || content_bytes[i - 1] != b'\r' {
260                        normalized.push(b'\r');
261                    }
262                    normalized.push(b'\n');
263                } else {
264                    normalized.push(content_bytes[i]);
265                }
266                i += 1;
267            }
268            content_bytes = normalized;
269        }
270
271        let raw_content = content_bytes.clone();
272        let parsed_content = Self::parse_content(&content_bytes)?;
273
274        let mut msg = Self::new(parsed_content, raw_content, None)?;
275
276        if let Some(response_type) = msg_type {
277            msg = convert(response_type, msg)?;
278        }
279
280        Ok(msg)
281    }
282
283    fn parse_content(content: &[u8]) -> Result<Vec<(String, char, Vec<u8>)>, Error> {
284        let mut result = Vec::new();
285        let content_str = String::from_utf8_lossy(content);
286
287        for line in content_str.lines() {
288            let line = line.trim_end_matches(['\r', '\n']);
289            if line.len() < 3 {
290                continue;
291            }
292
293            let status_code = &line[..3];
294            let divider = line.chars().nth(3).unwrap_or(' ');
295            let line_content = if line.len() > 4 { &line[4..] } else { "" };
296
297            if divider == '+' {
298                let mut data_content = Vec::new();
299                let mut in_data = false;
300                let mut data_lines = Vec::new();
301
302                for data_line in content_str
303                    .lines()
304                    .skip_while(|l| !l.starts_with(&format!("{}+", status_code)))
305                {
306                    if data_line.starts_with(&format!("{}+", status_code)) {
307                        in_data = true;
308                        let key = if data_line.len() > 4 {
309                            &data_line[4..]
310                        } else {
311                            ""
312                        };
313                        if let Some(eq_pos) = key.find('=') {
314                            data_content.extend_from_slice(&key.as_bytes()[..=eq_pos]);
315                            data_content.push(b'\n');
316                        }
317                        continue;
318                    }
319                    if in_data {
320                        if data_line == "." {
321                            break;
322                        }
323                        let unescaped = data_line.strip_prefix('.').unwrap_or(data_line);
324                        data_lines.push(unescaped.to_string());
325                    }
326                }
327
328                if !data_lines.is_empty() {
329                    data_content.extend_from_slice(data_lines.join("\n").as_bytes());
330                }
331
332                result.push((status_code.to_string(), divider, data_content));
333            } else {
334                result.push((
335                    status_code.to_string(),
336                    divider,
337                    line_content.as_bytes().to_vec(),
338                ));
339            }
340        }
341
342        if result.is_empty() {
343            return Err(Error::Protocol(
344                "No valid lines in control message".to_string(),
345            ));
346        }
347
348        Ok(result)
349    }
350
351    /// Checks if the response indicates success.
352    ///
353    /// A response is considered successful if any of its lines has a status code
354    /// in the 2xx range (200-299). This is the standard success range in the
355    /// Tor control protocol.
356    ///
357    /// # Returns
358    ///
359    /// `true` if at least one line has a 2xx status code, `false` otherwise.
360    ///
361    /// # Example
362    ///
363    /// ```rust
364    /// use stem_rs::response::ControlMessage;
365    ///
366    /// let ok = ControlMessage::from_str("250 OK\r\n", None, false).unwrap();
367    /// assert!(ok.is_ok());
368    ///
369    /// let err = ControlMessage::from_str("552 Unrecognized key\r\n", None, false).unwrap();
370    /// assert!(!err.is_ok());
371    /// ```
372    pub fn is_ok(&self) -> bool {
373        for (code, _, _) in &self.parsed_content {
374            if let Ok(code_num) = code.parse::<u16>() {
375                if (200..300).contains(&code_num) {
376                    return true;
377                }
378            }
379        }
380        false
381    }
382
383    /// Returns the parsed content as string tuples.
384    ///
385    /// Each tuple contains:
386    /// - Status code (e.g., "250", "552", "650")
387    /// - Divider character (` `, `-`, or `+`)
388    /// - Content as a UTF-8 string (lossy conversion from bytes)
389    ///
390    /// # Returns
391    ///
392    /// A vector of `(status_code, divider, content)` tuples.
393    ///
394    /// # Example
395    ///
396    /// ```rust
397    /// use stem_rs::response::ControlMessage;
398    ///
399    /// let msg = ControlMessage::from_str("250-version=0.4.7.8\r\n250 OK\r\n", None, false).unwrap();
400    /// let content = msg.content();
401    ///
402    /// assert_eq!(content[0].0, "250");
403    /// assert_eq!(content[0].1, '-');
404    /// assert!(content[0].2.starts_with("version="));
405    ///
406    /// assert_eq!(content[1].0, "250");
407    /// assert_eq!(content[1].1, ' ');
408    /// assert_eq!(content[1].2, "OK");
409    /// ```
410    pub fn content(&self) -> Vec<(String, char, String)> {
411        self.parsed_content
412            .iter()
413            .map(|(code, div, content)| {
414                (
415                    code.clone(),
416                    *div,
417                    String::from_utf8_lossy(content).to_string(),
418                )
419            })
420            .collect()
421    }
422
423    /// Returns the parsed content with raw bytes.
424    ///
425    /// Similar to [`content`](Self::content), but preserves the original bytes
426    /// without UTF-8 conversion. Useful when handling binary data or when
427    /// exact byte preservation is required.
428    ///
429    /// # Returns
430    ///
431    /// A slice of `(status_code, divider, content_bytes)` tuples.
432    pub fn content_bytes(&self) -> &[(String, char, Vec<u8>)] {
433        &self.parsed_content
434    }
435
436    /// Returns the original raw bytes of the message.
437    ///
438    /// This is the unmodified data as received from the control socket,
439    /// including all protocol formatting (`\r\n`, status codes, etc.).
440    ///
441    /// # Returns
442    ///
443    /// A byte slice of the original message data.
444    pub fn raw_content(&self) -> &[u8] {
445        &self.raw_content
446    }
447
448    /// Returns the raw content as a UTF-8 string.
449    ///
450    /// Performs lossy UTF-8 conversion, replacing invalid sequences with
451    /// the Unicode replacement character (U+FFFD).
452    ///
453    /// # Returns
454    ///
455    /// The raw message content as a string.
456    pub fn raw_content_str(&self) -> String {
457        String::from_utf8_lossy(&self.raw_content).to_string()
458    }
459
460    /// Returns an iterator over the message lines as [`ControlLine`] instances.
461    ///
462    /// Each [`ControlLine`] provides parsing utilities for extracting values,
463    /// key-value mappings, and quoted strings from the line content.
464    ///
465    /// # Example
466    ///
467    /// ```rust
468    /// use stem_rs::response::ControlMessage;
469    ///
470    /// let msg = ControlMessage::from_str(
471    ///     "250-version=0.4.7.8\r\n250 OK\r\n",
472    ///     None,
473    ///     false
474    /// ).unwrap();
475    ///
476    /// for line in msg.iter() {
477    ///     println!("Line: {}", line);
478    /// }
479    /// ```
480    pub fn iter(&self) -> impl Iterator<Item = ControlLine> + '_ {
481        self.parsed_content
482            .iter()
483            .map(|(_, _, content)| ControlLine::new(&String::from_utf8_lossy(content)))
484    }
485
486    /// Returns the number of lines in the message.
487    ///
488    /// # Returns
489    ///
490    /// The count of parsed lines. Always at least 1 for valid messages.
491    pub fn len(&self) -> usize {
492        self.parsed_content.len()
493    }
494
495    /// Checks if the message has no lines.
496    ///
497    /// # Returns
498    ///
499    /// `true` if the message is empty, `false` otherwise.
500    ///
501    /// # Note
502    ///
503    /// Valid `ControlMessage` instances are never empty; this method exists
504    /// for API completeness with `len()`.
505    pub fn is_empty(&self) -> bool {
506        self.parsed_content.is_empty()
507    }
508
509    /// Returns the line at the specified index as a [`ControlLine`].
510    ///
511    /// # Arguments
512    ///
513    /// * `index` - Zero-based index of the line to retrieve
514    ///
515    /// # Returns
516    ///
517    /// `Some(ControlLine)` if the index is valid, `None` otherwise.
518    ///
519    /// # Example
520    ///
521    /// ```rust
522    /// use stem_rs::response::ControlMessage;
523    ///
524    /// let msg = ControlMessage::from_str("250-first\r\n250 second\r\n", None, false).unwrap();
525    ///
526    /// assert!(msg.get(0).is_some());
527    /// assert!(msg.get(1).is_some());
528    /// assert!(msg.get(2).is_none());
529    /// ```
530    pub fn get(&self, index: usize) -> Option<ControlLine> {
531        self.parsed_content
532            .get(index)
533            .map(|(_, _, content)| ControlLine::new(&String::from_utf8_lossy(content)))
534    }
535}
536
537impl PartialEq for ControlMessage {
538    fn eq(&self, other: &Self) -> bool {
539        self.raw_content == other.raw_content
540    }
541}
542
543impl Eq for ControlMessage {}
544
545impl Hash for ControlMessage {
546    fn hash<H: Hasher>(&self, state: &mut H) {
547        self.raw_content.hash(state);
548    }
549}
550
551impl std::fmt::Display for ControlMessage {
552    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
553        let lines: Vec<String> = self.iter().map(|line| line.to_string()).collect();
554        write!(f, "{}", lines.join("\n"))
555    }
556}
557
558impl std::ops::Index<usize> for ControlMessage {
559    type Output = str;
560
561    fn index(&self, _index: usize) -> &Self::Output {
562        panic!("Use get() method instead for safe access")
563    }
564}
565
566/// A single line from a control protocol response with parsing utilities.
567///
568/// `ControlLine` provides methods for parsing space-delimited entries from
569/// control protocol response lines. It maintains internal state to track
570/// the unparsed remainder, allowing sequential extraction of values.
571///
572/// # Parsing Model
573///
574/// The line is treated as a sequence of space-separated entries that can be
575/// "popped" from left to right. Each pop operation:
576/// 1. Extracts the next entry (optionally handling quotes and escapes)
577/// 2. Updates the internal remainder to exclude the extracted entry
578/// 3. Returns the extracted value
579///
580/// # Entry Types
581///
582/// - **Simple values**: Space-separated tokens (e.g., `PROTOCOLINFO 1`)
583/// - **Quoted values**: Values enclosed in double quotes (e.g., `"hello world"`)
584/// - **Key-value mappings**: `KEY=VALUE` pairs (e.g., `Tor="0.4.7.8"`)
585/// - **Escaped values**: Values with backslash escapes (e.g., `"path\\to\\file"`)
586///
587/// # Thread Safety
588///
589/// `ControlLine` uses internal synchronization (`Mutex`) for its mutable
590/// parsing state, making it safe to share across threads. However, concurrent
591/// pops from multiple threads will produce unpredictable results.
592///
593/// # Example
594///
595/// ```rust
596/// use stem_rs::response::ControlLine;
597///
598/// let mut line = ControlLine::new("AUTH METHODS=COOKIE,PASSWORD VERSION=\"1.0\"");
599///
600/// // Pop simple value
601/// assert_eq!(line.pop(false, false).unwrap(), "AUTH");
602///
603/// // Pop key-value mapping
604/// let (key, value) = line.pop_mapping(false, false).unwrap();
605/// assert_eq!(key, "METHODS");
606/// assert_eq!(value, "COOKIE,PASSWORD");
607///
608/// // Pop quoted key-value mapping
609/// let (key, value) = line.pop_mapping(true, false).unwrap();
610/// assert_eq!(key, "VERSION");
611/// assert_eq!(value, "1.0");
612///
613/// assert!(line.is_empty());
614/// ```
615#[derive(Debug)]
616pub struct ControlLine {
617    /// The original content of the line (immutable).
618    content: String,
619    /// The unparsed remainder (mutable via Mutex).
620    remainder: Mutex<String>,
621}
622
623impl Clone for ControlLine {
624    fn clone(&self) -> Self {
625        Self {
626            content: self.content.clone(),
627            remainder: Mutex::new(self.remainder.lock().unwrap().clone()),
628        }
629    }
630}
631
632impl ControlLine {
633    /// Creates a new `ControlLine` from the given content.
634    ///
635    /// The entire content is initially available as the remainder for parsing.
636    ///
637    /// # Arguments
638    ///
639    /// * `content` - The line content to parse
640    ///
641    /// # Example
642    ///
643    /// ```rust
644    /// use stem_rs::response::ControlLine;
645    ///
646    /// let line = ControlLine::new("KEY=value more data");
647    /// assert_eq!(line.remainder(), "KEY=value more data");
648    /// ```
649    pub fn new(content: &str) -> Self {
650        Self {
651            content: content.to_string(),
652            remainder: Mutex::new(content.to_string()),
653        }
654    }
655
656    /// Returns the unparsed remainder of the line.
657    ///
658    /// This is the portion of the line that hasn't been consumed by
659    /// [`pop`](Self::pop) or [`pop_mapping`](Self::pop_mapping) calls.
660    ///
661    /// # Returns
662    ///
663    /// The remaining unparsed content as a string.
664    ///
665    /// # Example
666    ///
667    /// ```rust
668    /// use stem_rs::response::ControlLine;
669    ///
670    /// let mut line = ControlLine::new("first second third");
671    /// assert_eq!(line.remainder(), "first second third");
672    ///
673    /// line.pop(false, false).unwrap();
674    /// assert_eq!(line.remainder(), "second third");
675    /// ```
676    pub fn remainder(&self) -> String {
677        self.remainder.lock().unwrap().clone()
678    }
679
680    /// Checks if there is no remaining content to parse.
681    ///
682    /// # Returns
683    ///
684    /// `true` if the remainder is empty, `false` otherwise.
685    ///
686    /// # Example
687    ///
688    /// ```rust
689    /// use stem_rs::response::ControlLine;
690    ///
691    /// let mut line = ControlLine::new("single");
692    /// assert!(!line.is_empty());
693    ///
694    /// line.pop(false, false).unwrap();
695    /// assert!(line.is_empty());
696    /// ```
697    pub fn is_empty(&self) -> bool {
698        self.remainder.lock().unwrap().is_empty()
699    }
700
701    /// Checks if the next entry is a quoted value.
702    ///
703    /// A quoted value starts with a double quote (`"`) and has a matching
704    /// closing quote.
705    ///
706    /// # Arguments
707    ///
708    /// * `escaped` - If `true`, handles backslash-escaped quotes within the value
709    ///
710    /// # Returns
711    ///
712    /// `true` if the next entry can be parsed as a quoted value, `false` otherwise.
713    ///
714    /// # Example
715    ///
716    /// ```rust
717    /// use stem_rs::response::ControlLine;
718    ///
719    /// let line = ControlLine::new("\"quoted value\" unquoted");
720    /// assert!(line.is_next_quoted(false));
721    ///
722    /// let line = ControlLine::new("unquoted \"quoted\"");
723    /// assert!(!line.is_next_quoted(false));
724    /// ```
725    pub fn is_next_quoted(&self, escaped: bool) -> bool {
726        let remainder = self.remainder.lock().unwrap();
727        let trimmed = remainder.trim_start();
728        if !trimmed.starts_with('"') {
729            return false;
730        }
731        let (start, end) = get_quote_indices(trimmed, escaped);
732        start == 0 && end != -1
733    }
734
735    /// Checks if the next entry is a KEY=VALUE mapping.
736    ///
737    /// # Arguments
738    ///
739    /// * `key` - If `Some`, checks that the key matches this specific value
740    /// * `quoted` - If `true`, checks that the value is quoted
741    /// * `escaped` - If `true`, handles backslash escapes in quoted values
742    ///
743    /// # Returns
744    ///
745    /// `true` if the next entry is a valid KEY=VALUE mapping matching the criteria.
746    ///
747    /// # Example
748    ///
749    /// ```rust
750    /// use stem_rs::response::ControlLine;
751    ///
752    /// let line = ControlLine::new("KEY=value OTHER=stuff");
753    ///
754    /// // Check for any mapping
755    /// assert!(line.is_next_mapping(None, false, false));
756    ///
757    /// // Check for specific key
758    /// assert!(line.is_next_mapping(Some("KEY"), false, false));
759    /// assert!(!line.is_next_mapping(Some("OTHER"), false, false));
760    ///
761    /// // Check for quoted value
762    /// let line = ControlLine::new("KEY=\"quoted\"");
763    /// assert!(line.is_next_mapping(Some("KEY"), true, false));
764    /// ```
765    pub fn is_next_mapping(&self, key: Option<&str>, quoted: bool, escaped: bool) -> bool {
766        let remainder = self.remainder.lock().unwrap();
767        let trimmed = remainder.trim_start();
768
769        if let Some(eq_pos) = trimmed.find('=') {
770            let actual_key = &trimmed[..eq_pos];
771            if let Some(expected_key) = key {
772                if actual_key != expected_key {
773                    return false;
774                }
775            }
776            if quoted {
777                let after_eq = &trimmed[eq_pos + 1..];
778                let (start, end) = get_quote_indices(after_eq, escaped);
779                start == 0 && end != -1
780            } else {
781                true
782            }
783        } else {
784            false
785        }
786    }
787
788    /// Returns the key of the next entry without consuming it.
789    ///
790    /// If the next entry is a KEY=VALUE mapping, returns the key portion.
791    /// Otherwise returns `None`.
792    ///
793    /// # Returns
794    ///
795    /// `Some(key)` if the next entry is a mapping, `None` otherwise.
796    ///
797    /// # Example
798    ///
799    /// ```rust
800    /// use stem_rs::response::ControlLine;
801    ///
802    /// let line = ControlLine::new("MYKEY=myvalue");
803    /// assert_eq!(line.peek_key(), Some("MYKEY".to_string()));
804    ///
805    /// let line = ControlLine::new("no_equals_here");
806    /// assert!(line.peek_key().is_none());
807    /// ```
808    pub fn peek_key(&self) -> Option<String> {
809        let remainder = self.remainder.lock().unwrap();
810        let trimmed = remainder.trim_start();
811        trimmed.find('=').map(|pos| trimmed[..pos].to_string())
812    }
813
814    /// Removes and returns the next space-separated entry.
815    ///
816    /// This method extracts the next entry from the remainder, handling
817    /// optional quoting and escape sequences.
818    ///
819    /// # Arguments
820    ///
821    /// * `quoted` - If `true`, parses the entry as a quoted value (removes quotes)
822    /// * `escaped` - If `true`, processes backslash escape sequences
823    ///
824    /// # Escape Sequences
825    ///
826    /// When `escaped` is `true`, the following sequences are processed:
827    /// - `\\n` → newline
828    /// - `\\r` → carriage return
829    /// - `\\t` → tab
830    /// - `\\\\` → backslash
831    /// - `\\"` → double quote
832    ///
833    /// # Errors
834    ///
835    /// Returns [`Error::Protocol`](crate::Error::Protocol) if:
836    /// - No remaining content to parse
837    /// - `quoted` is `true` but the next entry isn't quoted
838    ///
839    /// # Example
840    ///
841    /// ```rust
842    /// use stem_rs::response::ControlLine;
843    ///
844    /// let mut line = ControlLine::new("\"We're all mad here.\" says the cat.");
845    ///
846    /// // Pop quoted value
847    /// assert_eq!(line.pop(true, false).unwrap(), "We're all mad here.");
848    ///
849    /// // Pop unquoted value
850    /// assert_eq!(line.pop(false, false).unwrap(), "says");
851    ///
852    /// // Check remainder
853    /// assert_eq!(line.remainder(), "the cat.");
854    /// ```
855    pub fn pop(&mut self, quoted: bool, escaped: bool) -> Result<String, Error> {
856        let mut remainder = self.remainder.lock().unwrap();
857        let trimmed = remainder.trim_start();
858
859        if trimmed.is_empty() {
860            return Err(Error::Protocol("no remaining content to parse".to_string()));
861        }
862
863        let (entry, new_remainder) = parse_entry(trimmed, quoted, escaped, false)?;
864        *remainder = new_remainder;
865        Ok(entry)
866    }
867
868    /// Removes and returns the next entry as a KEY=VALUE mapping.
869    ///
870    /// Parses the next entry expecting a `KEY=VALUE` format and returns
871    /// both the key and value as separate strings.
872    ///
873    /// # Arguments
874    ///
875    /// * `quoted` - If `true`, the value is expected to be quoted
876    /// * `escaped` - If `true`, processes backslash escape sequences in the value
877    ///
878    /// # Errors
879    ///
880    /// Returns [`Error::Protocol`](crate::Error::Protocol) if:
881    /// - No remaining content to parse
882    /// - The next entry isn't a KEY=VALUE mapping
883    /// - `quoted` is `true` but the value isn't quoted
884    ///
885    /// # Example
886    ///
887    /// ```rust
888    /// use stem_rs::response::ControlLine;
889    ///
890    /// let mut line = ControlLine::new("Tor=\"0.4.7.8\" other=value");
891    ///
892    /// // Pop quoted mapping
893    /// let (key, value) = line.pop_mapping(true, false).unwrap();
894    /// assert_eq!(key, "Tor");
895    /// assert_eq!(value, "0.4.7.8");
896    ///
897    /// // Pop unquoted mapping
898    /// let (key, value) = line.pop_mapping(false, false).unwrap();
899    /// assert_eq!(key, "other");
900    /// assert_eq!(value, "value");
901    /// ```
902    pub fn pop_mapping(&mut self, quoted: bool, escaped: bool) -> Result<(String, String), Error> {
903        self.pop_mapping_impl(quoted, escaped, false)
904    }
905
906    /// Removes and returns the next entry as a KEY=VALUE mapping with raw bytes.
907    ///
908    /// Similar to [`pop_mapping`](Self::pop_mapping), but returns the value as
909    /// raw bytes instead of a string. Useful when the value may contain binary
910    /// data or when exact byte preservation is required.
911    ///
912    /// # Arguments
913    ///
914    /// * `quoted` - If `true`, the value is expected to be quoted
915    /// * `escaped` - If `true`, processes backslash escape sequences in the value
916    ///
917    /// # Errors
918    ///
919    /// Returns [`Error::Protocol`](crate::Error::Protocol) if:
920    /// - No remaining content to parse
921    /// - The next entry isn't a KEY=VALUE mapping
922    /// - `quoted` is `true` but the value isn't quoted
923    ///
924    /// # Example
925    ///
926    /// ```rust
927    /// use stem_rs::response::ControlLine;
928    ///
929    /// let mut line = ControlLine::new("DATA=binary_content");
930    /// let (key, value_bytes) = line.pop_mapping_bytes(false, false).unwrap();
931    /// assert_eq!(key, "DATA");
932    /// assert_eq!(value_bytes, b"binary_content");
933    /// ```
934    pub fn pop_mapping_bytes(
935        &mut self,
936        quoted: bool,
937        escaped: bool,
938    ) -> Result<(String, Vec<u8>), Error> {
939        let mut remainder = self.remainder.lock().unwrap();
940        let trimmed = remainder.trim_start();
941
942        if trimmed.is_empty() {
943            return Err(Error::Protocol("no remaining content to parse".to_string()));
944        }
945
946        let eq_pos = trimmed.find('=').ok_or_else(|| {
947            Error::Protocol(format!(
948                "the next entry isn't a KEY=VALUE mapping: {}",
949                trimmed
950            ))
951        })?;
952
953        let key = trimmed[..eq_pos].to_string();
954        let after_eq = &trimmed[eq_pos + 1..];
955
956        let (entry, new_remainder) = parse_entry(after_eq, quoted, escaped, true)?;
957        *remainder = new_remainder;
958        Ok((key, entry.into_bytes()))
959    }
960
961    /// Internal implementation for pop_mapping variants.
962    fn pop_mapping_impl(
963        &mut self,
964        quoted: bool,
965        escaped: bool,
966        get_bytes: bool,
967    ) -> Result<(String, String), Error> {
968        let mut remainder = self.remainder.lock().unwrap();
969        let trimmed = remainder.trim_start();
970
971        if trimmed.is_empty() {
972            return Err(Error::Protocol("no remaining content to parse".to_string()));
973        }
974
975        let eq_pos = trimmed.find('=').ok_or_else(|| {
976            Error::Protocol(format!(
977                "the next entry isn't a KEY=VALUE mapping: {}",
978                trimmed
979            ))
980        })?;
981
982        let key = trimmed[..eq_pos].to_string();
983        let after_eq = &trimmed[eq_pos + 1..];
984
985        let (entry, new_remainder) = parse_entry(after_eq, quoted, escaped, get_bytes)?;
986        *remainder = new_remainder;
987        Ok((key, entry))
988    }
989}
990
991impl std::fmt::Display for ControlLine {
992    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
993        write!(f, "{}", self.content)
994    }
995}
996
997impl AsRef<str> for ControlLine {
998    fn as_ref(&self) -> &str {
999        &self.content
1000    }
1001}
1002
1003/// Parses an entry from a line, handling quoting and escaping.
1004fn parse_entry(
1005    line: &str,
1006    quoted: bool,
1007    escaped: bool,
1008    _get_bytes: bool,
1009) -> Result<(String, String), Error> {
1010    if line.is_empty() {
1011        return Err(Error::Protocol("no remaining content to parse".to_string()));
1012    }
1013
1014    let (next_entry, remainder) = if quoted {
1015        let (start, end) = get_quote_indices(line, escaped);
1016        if start != 0 || end == -1 {
1017            return Err(Error::Protocol(format!(
1018                "the next entry isn't a quoted value: {}",
1019                line
1020            )));
1021        }
1022        let end = end as usize;
1023        let entry = &line[1..end];
1024        let rest = &line[end + 1..];
1025        (entry.to_string(), rest.trim_start().to_string())
1026    } else if let Some(space_pos) = line.find(' ') {
1027        (
1028            line[..space_pos].to_string(),
1029            line[space_pos + 1..].to_string(),
1030        )
1031    } else {
1032        (line.to_string(), String::new())
1033    };
1034
1035    let result = if escaped {
1036        unescape_string(&next_entry)
1037    } else {
1038        next_entry
1039    };
1040
1041    Ok((result, remainder))
1042}
1043
1044/// Finds the indices of the next two quote characters in a line.
1045///
1046/// Returns (-1, -1) if quotes are not found. When `escaped` is true,
1047/// skips over backslash-escaped quotes.
1048fn get_quote_indices(line: &str, escaped: bool) -> (i32, i32) {
1049    let bytes = line.as_bytes();
1050    let mut indices = [-1i32, -1i32];
1051    let mut quote_index: i32 = -1;
1052
1053    for index in indices.iter_mut().take(2) {
1054        let start = (quote_index + 1) as usize;
1055        if start >= bytes.len() {
1056            break;
1057        }
1058
1059        let mut pos = start;
1060        while pos < bytes.len() {
1061            if bytes[pos] == b'"' {
1062                if escaped && pos > 0 && bytes[pos - 1] == b'\\' {
1063                    pos += 1;
1064                    continue;
1065                }
1066                quote_index = pos as i32;
1067                break;
1068            }
1069            pos += 1;
1070        }
1071
1072        if pos >= bytes.len() {
1073            break;
1074        }
1075
1076        *index = quote_index;
1077    }
1078
1079    (indices[0], indices[1])
1080}
1081
1082/// Processes backslash escape sequences in a string.
1083///
1084/// Handles: `\n`, `\r`, `\t`, `\\`, `\"`
1085fn unescape_string(s: &str) -> String {
1086    let mut result = String::with_capacity(s.len());
1087    let mut chars = s.chars().peekable();
1088
1089    while let Some(c) = chars.next() {
1090        if c == '\\' {
1091            if let Some(&next) = chars.peek() {
1092                chars.next();
1093                match next {
1094                    'n' => result.push('\n'),
1095                    'r' => result.push('\r'),
1096                    't' => result.push('\t'),
1097                    '\\' => result.push('\\'),
1098                    '"' => result.push('"'),
1099                    _ => {
1100                        result.push('\\');
1101                        result.push(next);
1102                    }
1103                }
1104            } else {
1105                result.push('\\');
1106            }
1107        } else {
1108            result.push(c);
1109        }
1110    }
1111
1112    result
1113}
1114
1115/// A simple single-line response from Tor.
1116///
1117/// `SingleLineResponse` represents responses that contain exactly one line,
1118/// typically used for commands that perform actions rather than query data.
1119/// These responses are usually "250 OK" on success or an error code with
1120/// a description on failure.
1121///
1122/// # Response Format
1123///
1124/// Single-line responses have the format:
1125/// ```text
1126/// STATUS CONTENT
1127/// ```
1128///
1129/// For example:
1130/// - `250 OK` - Success
1131/// - `552 Unrecognized key "foo"` - Error
1132///
1133/// # Example
1134///
1135/// ```rust
1136/// use stem_rs::response::{ControlMessage, SingleLineResponse};
1137///
1138/// let msg = ControlMessage::from_str("250 OK\r\n", None, false).unwrap();
1139/// let response = SingleLineResponse::from_message(msg).unwrap();
1140///
1141/// assert!(response.is_ok(false));
1142/// assert!(response.is_ok(true)); // Strict check for "250 OK"
1143/// assert_eq!(response.code, "250");
1144/// assert_eq!(response.message_text, "OK");
1145/// ```
1146#[derive(Debug, Clone)]
1147pub struct SingleLineResponse {
1148    /// The underlying control message.
1149    pub message: ControlMessage,
1150    /// The 3-digit status code (e.g., "250", "552").
1151    pub code: String,
1152    /// The message content after the status code.
1153    pub message_text: String,
1154}
1155
1156impl SingleLineResponse {
1157    /// Creates a `SingleLineResponse` from a control message.
1158    ///
1159    /// Validates that the message contains exactly one line and extracts
1160    /// the status code and message text.
1161    ///
1162    /// # Errors
1163    ///
1164    /// Returns [`Error::Protocol`](crate::Error::Protocol) if:
1165    /// - The message contains more than one line
1166    /// - The message is empty
1167    ///
1168    /// # Example
1169    ///
1170    /// ```rust
1171    /// use stem_rs::response::{ControlMessage, SingleLineResponse};
1172    ///
1173    /// // Valid single-line response
1174    /// let msg = ControlMessage::from_str("250 OK\r\n", None, false).unwrap();
1175    /// let response = SingleLineResponse::from_message(msg).unwrap();
1176    ///
1177    /// // Multi-line response fails
1178    /// let msg = ControlMessage::from_str("250-line1\r\n250 line2\r\n", None, false).unwrap();
1179    /// assert!(SingleLineResponse::from_message(msg).is_err());
1180    /// ```
1181    pub fn from_message(message: ControlMessage) -> Result<Self, Error> {
1182        let content = message.content();
1183
1184        if content.len() > 1 {
1185            return Err(Error::Protocol("Received multi-line response".to_string()));
1186        }
1187        if content.is_empty() {
1188            return Err(Error::Protocol("Received empty response".to_string()));
1189        }
1190
1191        let (code, _, msg) = &content[0];
1192
1193        Ok(Self {
1194            code: code.clone(),
1195            message_text: msg.clone(),
1196            message,
1197        })
1198    }
1199
1200    /// Checks if the response indicates success.
1201    ///
1202    /// # Arguments
1203    ///
1204    /// * `strict` - If `true`, requires exactly "250 OK"; if `false`, only checks
1205    ///   for status code "250"
1206    ///
1207    /// # Returns
1208    ///
1209    /// - Non-strict: `true` if status code is "250"
1210    /// - Strict: `true` if response is exactly "250 OK"
1211    ///
1212    /// # Example
1213    ///
1214    /// ```rust
1215    /// use stem_rs::response::{ControlMessage, SingleLineResponse};
1216    ///
1217    /// let msg = ControlMessage::from_str("250 OK\r\n", None, false).unwrap();
1218    /// let response = SingleLineResponse::from_message(msg).unwrap();
1219    /// assert!(response.is_ok(false)); // Non-strict
1220    /// assert!(response.is_ok(true));  // Strict
1221    ///
1222    /// let msg = ControlMessage::from_str("250 Done\r\n", None, false).unwrap();
1223    /// let response = SingleLineResponse::from_message(msg).unwrap();
1224    /// assert!(response.is_ok(false));  // Non-strict passes
1225    /// assert!(!response.is_ok(true));  // Strict fails (not "OK")
1226    /// ```
1227    pub fn is_ok(&self, strict: bool) -> bool {
1228        if strict {
1229            let content = self.message.content();
1230            if let Some((code, div, msg)) = content.first() {
1231                return code == "250" && *div == ' ' && msg == "OK";
1232            }
1233            false
1234        } else {
1235            self.code == "250"
1236        }
1237    }
1238}
1239
1240/// Converts a control message to a specific response type.
1241///
1242/// This function validates that a [`ControlMessage`] conforms to the expected
1243/// format for a specific response type. It performs type-specific validation
1244/// without modifying the message.
1245///
1246/// # Supported Response Types
1247///
1248/// | Type | Description |
1249/// |------|-------------|
1250/// | `SINGLELINE` | Simple single-line response |
1251/// | `ADD_ONION` | ADD_ONION command response |
1252/// | `AUTHCHALLENGE` | SAFECOOKIE authentication challenge |
1253/// | `EVENT` | Asynchronous event notification |
1254/// | `GETCONF` | GETCONF command response |
1255/// | `GETINFO` | GETINFO command response |
1256/// | `MAPADDRESS` | MAPADDRESS command response |
1257/// | `ONION_CLIENT_AUTH_VIEW` | Onion client auth view response |
1258/// | `PROTOCOLINFO` | PROTOCOLINFO command response |
1259///
1260/// # Arguments
1261///
1262/// * `response_type` - The type of response to validate (case-insensitive)
1263/// * `message` - The control message to validate
1264///
1265/// # Errors
1266///
1267/// Returns [`Error::Protocol`](crate::Error::Protocol) if:
1268/// - The response type is not supported
1269/// - The message doesn't conform to the expected format (for `SINGLELINE`)
1270///
1271/// # Example
1272///
1273/// ```rust
1274/// use stem_rs::response::{ControlMessage, convert};
1275///
1276/// let msg = ControlMessage::from_str("250 OK\r\n", None, false).unwrap();
1277///
1278/// // Validate as single-line response
1279/// let validated = convert("SINGLELINE", msg.clone()).unwrap();
1280///
1281/// // Multi-line fails SINGLELINE validation
1282/// let multi = ControlMessage::from_str("250-line1\r\n250 line2\r\n", None, false).unwrap();
1283/// assert!(convert("SINGLELINE", multi).is_err());
1284/// ```
1285pub fn convert(response_type: &str, message: ControlMessage) -> Result<ControlMessage, Error> {
1286    match response_type.to_uppercase().as_str() {
1287        "SINGLELINE" => {
1288            SingleLineResponse::from_message(message.clone())?;
1289            Ok(message)
1290        }
1291        "ADD_ONION"
1292        | "AUTHCHALLENGE"
1293        | "EVENT"
1294        | "GETCONF"
1295        | "GETINFO"
1296        | "MAPADDRESS"
1297        | "ONION_CLIENT_AUTH_VIEW"
1298        | "PROTOCOLINFO" => Ok(message),
1299        _ => Err(Error::Protocol(format!(
1300            "Unsupported response type: {}",
1301            response_type
1302        ))),
1303    }
1304}
1305
1306#[cfg(test)]
1307mod tests {
1308    use super::*;
1309    use std::collections::hash_map::DefaultHasher;
1310    use std::hash::{Hash, Hasher};
1311
1312    const OK_REPLY: &str = "250 OK\r\n";
1313    const EVENT_BW: &str = "650 BW 32326 2856\r\n";
1314    const EVENT_CIRC_TIMEOUT: &str = "650 CIRC 5 FAILED PURPOSE=GENERAL REASON=TIMEOUT\r\n";
1315    const EVENT_CIRC_LAUNCHED: &str = "650 CIRC 9 LAUNCHED PURPOSE=GENERAL\r\n";
1316
1317    const GETINFO_VERSION: &str = "250-version=0.2.2.23-alpha (git-b85eb949b528f4d7)\r\n250 OK\r\n";
1318
1319    #[test]
1320    fn test_from_str_basic() {
1321        let msg = ControlMessage::from_str(GETINFO_VERSION, None, false).unwrap();
1322        assert!(msg.is_ok());
1323        assert_eq!(msg.len(), 2);
1324    }
1325
1326    #[test]
1327    fn test_from_str_with_normalize() {
1328        let msg = ControlMessage::from_str("250 OK", None, true).unwrap();
1329        assert!(msg.is_ok());
1330    }
1331
1332    #[test]
1333    fn test_ok_response() {
1334        let msg = ControlMessage::from_str(OK_REPLY, None, false).unwrap();
1335        assert!(msg.is_ok());
1336        let content = msg.content();
1337        assert_eq!(content.len(), 1);
1338        assert_eq!(content[0], ("250".to_string(), ' ', "OK".to_string()));
1339    }
1340
1341    #[test]
1342    fn test_event_response_bw() {
1343        let msg = ControlMessage::from_str(EVENT_BW, None, false).unwrap();
1344        let content = msg.content();
1345        assert_eq!(content.len(), 1);
1346        assert_eq!(content[0].0, "650");
1347        assert_eq!(content[0].1, ' ');
1348        assert!(content[0].2.contains("BW 32326 2856"));
1349    }
1350
1351    #[test]
1352    fn test_event_response_circ() {
1353        for circ_content in [EVENT_CIRC_TIMEOUT, EVENT_CIRC_LAUNCHED] {
1354            let msg = ControlMessage::from_str(circ_content, None, false).unwrap();
1355            let content = msg.content();
1356            assert_eq!(content.len(), 1);
1357            assert_eq!(content[0].0, "650");
1358        }
1359    }
1360
1361    #[test]
1362    fn test_getinfo_response_version() {
1363        let msg = ControlMessage::from_str(GETINFO_VERSION, None, false).unwrap();
1364        assert_eq!(msg.len(), 2);
1365        let content = msg.content();
1366        assert_eq!(content[0].0, "250");
1367        assert_eq!(content[0].1, '-');
1368        assert!(content[0].2.starts_with("version="));
1369        assert_eq!(content[1], ("250".to_string(), ' ', "OK".to_string()));
1370    }
1371
1372    #[test]
1373    fn test_is_ok_various_codes() {
1374        let msg_250 = ControlMessage::from_str("250 OK\r\n", None, false).unwrap();
1375        assert!(msg_250.is_ok());
1376
1377        let msg_552 = ControlMessage::from_str("552 Unrecognized key\r\n", None, false).unwrap();
1378        assert!(!msg_552.is_ok());
1379
1380        let msg_451 = ControlMessage::from_str("451 Resource exhausted\r\n", None, false).unwrap();
1381        assert!(!msg_451.is_ok());
1382    }
1383
1384    #[test]
1385    fn test_raw_content() {
1386        let msg = ControlMessage::from_str(OK_REPLY, None, false).unwrap();
1387        assert_eq!(msg.raw_content(), OK_REPLY.as_bytes());
1388    }
1389
1390    #[test]
1391    fn test_iteration() {
1392        let msg = ControlMessage::from_str(GETINFO_VERSION, None, false).unwrap();
1393        let lines: Vec<_> = msg.iter().collect();
1394        assert_eq!(lines.len(), 2);
1395    }
1396
1397    #[test]
1398    fn test_indexing_get() {
1399        let msg = ControlMessage::from_str(GETINFO_VERSION, None, false).unwrap();
1400        let line = msg.get(0).unwrap();
1401        assert!(line.to_string().starts_with("version="));
1402        assert!(msg.get(10).is_none());
1403    }
1404
1405    #[test]
1406    fn test_equality() {
1407        let msg1 = ControlMessage::from_str(EVENT_BW, None, false).unwrap();
1408        let msg2 = ControlMessage::from_str(EVENT_BW, None, false).unwrap();
1409        let msg3 = ControlMessage::from_str(EVENT_CIRC_TIMEOUT, None, false).unwrap();
1410
1411        assert_eq!(msg1, msg2);
1412        assert_ne!(msg1, msg3);
1413    }
1414
1415    #[test]
1416    fn test_hashing() {
1417        let msg1 = ControlMessage::from_str(EVENT_BW, None, false).unwrap();
1418        let msg2 = ControlMessage::from_str(EVENT_BW, None, false).unwrap();
1419
1420        let mut hasher1 = DefaultHasher::new();
1421        let mut hasher2 = DefaultHasher::new();
1422        msg1.hash(&mut hasher1);
1423        msg2.hash(&mut hasher2);
1424
1425        assert_eq!(hasher1.finish(), hasher2.finish());
1426    }
1427
1428    #[test]
1429    fn test_empty_message_error() {
1430        let result = ControlMessage::new(vec![], vec![], None);
1431        assert!(result.is_err());
1432    }
1433
1434    #[test]
1435    fn test_control_line_pop_examples() {
1436        let mut line = ControlLine::new("\"We're all mad here.\" says the grinning cat.");
1437        assert_eq!(line.pop(true, false).unwrap(), "We're all mad here.");
1438        assert_eq!(line.pop(false, false).unwrap(), "says");
1439        assert_eq!(line.remainder(), "the grinning cat.");
1440    }
1441
1442    #[test]
1443    fn test_control_line_pop_escaped() {
1444        let mut line = ControlLine::new("\"this has a \\\" and \\\\ in it\" foo=bar more_data");
1445        assert_eq!(line.pop(true, true).unwrap(), "this has a \" and \\ in it");
1446    }
1447
1448    #[test]
1449    fn test_control_line_string_behavior() {
1450        let mut line = ControlLine::new("PROTOCOLINFO 1");
1451        assert_eq!(line.to_string(), "PROTOCOLINFO 1");
1452        assert!(line.to_string().starts_with("PROTOCOLINFO "));
1453
1454        line.pop(false, false).unwrap();
1455        assert_eq!(line.to_string(), "PROTOCOLINFO 1");
1456    }
1457
1458    #[test]
1459    fn test_control_line_general_usage() {
1460        let mut line = ControlLine::new("PROTOCOLINFO 1");
1461        assert_eq!(line.remainder(), "PROTOCOLINFO 1");
1462        assert!(!line.is_empty());
1463        assert!(!line.is_next_quoted(false));
1464        assert!(!line.is_next_mapping(None, false, false));
1465        assert!(line.peek_key().is_none());
1466
1467        assert!(line.pop_mapping(false, false).is_err());
1468        assert_eq!(line.pop(false, false).unwrap(), "PROTOCOLINFO");
1469        assert_eq!(line.remainder(), "1");
1470        assert!(!line.is_empty());
1471
1472        assert!(line.pop_mapping(false, false).is_err());
1473        assert_eq!(line.pop(false, false).unwrap(), "1");
1474        assert_eq!(line.remainder(), "");
1475        assert!(line.is_empty());
1476
1477        assert!(line.pop_mapping(false, false).is_err());
1478        assert!(line.pop(false, false).is_err());
1479    }
1480
1481    #[test]
1482    fn test_control_line_pop_mapping() {
1483        let version_entry = "Tor=\"0.2.1.30 (0a083b0188cacd2f07838ff0446113bd5211a024)\"";
1484
1485        let mut line = ControlLine::new(version_entry);
1486        assert_eq!(line.remainder(), version_entry);
1487        assert!(!line.is_empty());
1488        assert!(!line.is_next_quoted(false));
1489        assert!(line.is_next_mapping(None, false, false));
1490        assert!(line.is_next_mapping(Some("Tor"), false, false));
1491        assert!(line.is_next_mapping(Some("Tor"), true, false));
1492        assert!(line.is_next_mapping(None, true, false));
1493        assert_eq!(line.peek_key(), Some("Tor".to_string()));
1494
1495        let (key, value) = line.pop_mapping(false, false).unwrap();
1496        assert_eq!(key, "Tor");
1497        assert_eq!(value, "\"0.2.1.30");
1498
1499        let mut line = ControlLine::new(version_entry);
1500        let (key, value) = line.pop_mapping(true, false).unwrap();
1501        assert_eq!(key, "Tor");
1502        assert_eq!(value, "0.2.1.30 (0a083b0188cacd2f07838ff0446113bd5211a024)");
1503        assert!(line.is_empty());
1504    }
1505
1506    #[test]
1507    fn test_control_line_escapes() {
1508        let auth_line =
1509            r#"AUTH METHODS=COOKIE COOKIEFILE="/tmp/my data\\\"dir//control_auth_cookie""#;
1510        let mut line = ControlLine::new(auth_line);
1511
1512        assert_eq!(line.pop(false, false).unwrap(), "AUTH");
1513        let (key, value) = line.pop_mapping(false, false).unwrap();
1514        assert_eq!(key, "METHODS");
1515        assert_eq!(value, "COOKIE");
1516
1517        let cookie_entry = r#"COOKIEFILE="/tmp/my data\\\"dir//control_auth_cookie""#;
1518        let mut line = ControlLine::new(cookie_entry);
1519        let (key, value) = line.pop_mapping(true, true).unwrap();
1520        assert_eq!(key, "COOKIEFILE");
1521        assert_eq!(value, r#"/tmp/my data\"dir//control_auth_cookie"#);
1522        assert!(line.is_empty());
1523    }
1524
1525    #[test]
1526    fn test_control_line_windows_path_escapes() {
1527        let mut line = ControlLine::new(
1528            r#"COOKIEFILE="C:\\Users\\Atagar\\AppData\\tor\\control_auth_cookie""#,
1529        );
1530        let (key, value) = line.pop_mapping(true, true).unwrap();
1531        assert_eq!(key, "COOKIEFILE");
1532        assert_eq!(value, r#"C:\Users\Atagar\AppData\tor\control_auth_cookie"#);
1533        assert!(line.is_empty());
1534    }
1535
1536    #[test]
1537    fn test_is_next_quoted() {
1538        let line = ControlLine::new("\"quoted value\" unquoted");
1539        assert!(line.is_next_quoted(false));
1540
1541        let line = ControlLine::new("unquoted \"quoted\"");
1542        assert!(!line.is_next_quoted(false));
1543    }
1544
1545    #[test]
1546    fn test_is_next_mapping_with_key() {
1547        let line = ControlLine::new("KEY=value OTHER=stuff");
1548        assert!(line.is_next_mapping(Some("KEY"), false, false));
1549        assert!(!line.is_next_mapping(Some("OTHER"), false, false));
1550        assert!(line.is_next_mapping(None, false, false));
1551    }
1552
1553    #[test]
1554    fn test_peek_key() {
1555        let line = ControlLine::new("MYKEY=myvalue");
1556        assert_eq!(line.peek_key(), Some("MYKEY".to_string()));
1557
1558        let line = ControlLine::new("no_equals_here");
1559        assert!(line.peek_key().is_none());
1560    }
1561
1562    #[test]
1563    fn test_single_line_response_not_ok() {
1564        let msg = ControlMessage::from_str("552 NOTOK\r\n", None, false).unwrap();
1565        let response = SingleLineResponse::from_message(msg).unwrap();
1566        assert!(!response.is_ok(false));
1567    }
1568
1569    #[test]
1570    fn test_single_line_response_ok_non_strict() {
1571        let msg = ControlMessage::from_str("250 KK\r\n", None, false).unwrap();
1572        let response = SingleLineResponse::from_message(msg).unwrap();
1573        assert!(response.is_ok(false));
1574    }
1575
1576    #[test]
1577    fn test_single_line_response_ok_strict() {
1578        let msg = ControlMessage::from_str("250 OK\r\n", None, false).unwrap();
1579        let response = SingleLineResponse::from_message(msg).unwrap();
1580        assert!(response.is_ok(true));
1581    }
1582
1583    #[test]
1584    fn test_single_line_response_ok_strict_fails() {
1585        let msg = ControlMessage::from_str("250 HMM\r\n", None, false).unwrap();
1586        let response = SingleLineResponse::from_message(msg).unwrap();
1587        assert!(!response.is_ok(true));
1588    }
1589
1590    #[test]
1591    fn test_single_line_response_multi_line_error() {
1592        let msg = ControlMessage::from_str("250-MULTI\r\n250 LINE\r\n", None, false).unwrap();
1593        let result = SingleLineResponse::from_message(msg);
1594        assert!(result.is_err());
1595    }
1596
1597    #[test]
1598    fn test_convert_singleline() {
1599        let msg = ControlMessage::from_str("250 OK\r\n", None, false).unwrap();
1600        let result = convert("SINGLELINE", msg);
1601        assert!(result.is_ok());
1602    }
1603
1604    #[test]
1605    fn test_convert_singleline_multi_line_error() {
1606        let msg = ControlMessage::from_str("250-MULTI\r\n250 LINE\r\n", None, false).unwrap();
1607        let result = convert("SINGLELINE", msg);
1608        assert!(result.is_err());
1609    }
1610
1611    #[test]
1612    fn test_convert_unsupported_type() {
1613        let msg = ControlMessage::from_str("250 OK\r\n", None, false).unwrap();
1614        let result = convert("UNKNOWN_TYPE", msg);
1615        assert!(result.is_err());
1616    }
1617
1618    #[test]
1619    fn test_convert_known_types() {
1620        let msg = ControlMessage::from_str("250 OK\r\n", None, false).unwrap();
1621        for response_type in [
1622            "ADD_ONION",
1623            "AUTHCHALLENGE",
1624            "EVENT",
1625            "GETCONF",
1626            "GETINFO",
1627            "MAPADDRESS",
1628            "PROTOCOLINFO",
1629        ] {
1630            let result = convert(response_type, msg.clone());
1631            assert!(result.is_ok(), "Failed for type: {}", response_type);
1632        }
1633    }
1634
1635    #[test]
1636    fn test_unescape_string() {
1637        assert_eq!(unescape_string(r#"hello\nworld"#), "hello\nworld");
1638        assert_eq!(unescape_string(r#"tab\there"#), "tab\there");
1639        assert_eq!(unescape_string(r#"quote\"here"#), "quote\"here");
1640        assert_eq!(unescape_string(r#"backslash\\here"#), "backslash\\here");
1641        assert_eq!(unescape_string(r#"carriage\rreturn"#), "carriage\rreturn");
1642        assert_eq!(unescape_string(r#"unknown\xescape"#), "unknown\\xescape");
1643    }
1644
1645    #[test]
1646    fn test_get_quote_indices() {
1647        assert_eq!(get_quote_indices("\"hello\"", false), (0, 6));
1648        assert_eq!(get_quote_indices("no quotes", false), (-1, -1));
1649        assert_eq!(get_quote_indices("\"only one", false), (0, -1));
1650        assert_eq!(get_quote_indices("\"escaped\\\"quote\"", true), (0, 15));
1651    }
1652}