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