stem_rs/descriptor/
server.rs

1//! Server descriptor parsing for Tor relay descriptors.
2//!
3//! This module provides parsing for server descriptors, which are the primary
4//! documents that Tor relays publish to describe themselves to the network.
5//! Server descriptors contain comprehensive metadata about a relay including
6//! its identity, network addresses, bandwidth capabilities, exit policy,
7//! and cryptographic keys.
8//!
9//! # Overview
10//!
11//! Server descriptors are published by relays to directory authorities and
12//! cached by clients. They contain:
13//!
14//! - **Identity information**: Nickname, fingerprint, contact info
15//! - **Network addresses**: IPv4/IPv6 addresses and ports (OR, SOCKS, Dir)
16//! - **Bandwidth**: Advertised and observed bandwidth values
17//! - **Exit policy**: Rules for what traffic the relay will exit
18//! - **Cryptographic keys**: Onion keys, signing keys, Ed25519 certificates
19//! - **Protocol versions**: Supported link and circuit protocol versions
20//! - **Family**: Related relays operated by the same entity
21//!
22//! # Descriptor Format
23//!
24//! Server descriptors follow a text-based format defined in the
25//! [Tor directory protocol specification](https://spec.torproject.org/dir-spec).
26//! The format consists of keyword-value lines, with some values spanning
27//! multiple lines (like PEM-encoded keys).
28//!
29//! ```text
30//! router <nickname> <address> <ORPort> <SOCKSPort> <DirPort>
31//! platform Tor <version> on <OS>
32//! published <YYYY-MM-DD HH:MM:SS>
33//! fingerprint <40 hex chars with spaces>
34//! bandwidth <avg> <burst> <observed>
35//! onion-key
36//! -----BEGIN RSA PUBLIC KEY-----
37//! <base64 encoded key>
38//! -----END RSA PUBLIC KEY-----
39//! signing-key
40//! -----BEGIN RSA PUBLIC KEY-----
41//! <base64 encoded key>
42//! -----END RSA PUBLIC KEY-----
43//! accept|reject <exit policy rule>
44//! router-signature
45//! -----BEGIN SIGNATURE-----
46//! <base64 encoded signature>
47//! -----END SIGNATURE-----
48//! ```
49//!
50//! # Example
51//!
52//! ```rust,no_run
53//! use stem_rs::descriptor::{ServerDescriptor, Descriptor, DigestHash, DigestEncoding};
54//!
55//! let content = r#"router example 192.168.1.1 9001 0 0
56//! published 2023-01-01 00:00:00
57//! bandwidth 1000000 2000000 500000
58//! accept *:80
59//! accept *:443
60//! reject *:*
61//! router-signature
62//! -----BEGIN SIGNATURE-----
63//! dGVzdA==
64//! -----END SIGNATURE-----
65//! "#;
66//!
67//! let descriptor = ServerDescriptor::parse(content).unwrap();
68//! println!("Relay: {} at {}", descriptor.nickname, descriptor.address);
69//! println!("Bandwidth: {} bytes/sec observed", descriptor.bandwidth_observed);
70//!
71//! // Check exit policy
72//! if descriptor.exit_policy.can_exit_to("10.0.0.1".parse().unwrap(), 80) {
73//!     println!("Allows HTTP traffic");
74//! }
75//! ```
76//!
77//! # Digest Computation
78//!
79//! Server descriptor digests are computed over the content from the
80//! `router` line through the `router-signature` line (inclusive of the
81//! newline after `router-signature`). This is the signed portion of
82//! the descriptor.
83//!
84//! # Bridge Descriptors
85//!
86//! Bridge descriptors are similar to server descriptors but have some
87//! fields redacted for privacy. They use the `bridge-server-descriptor`
88//! type annotation and may have different `bridge-distribution-request`
89//! values.
90//!
91//! # See Also
92//!
93//! - [`Microdescriptor`](super::Microdescriptor) - Compact client-side descriptors
94//! - [`ExtraInfoDescriptor`](super::ExtraInfoDescriptor) - Additional relay statistics
95//! - [Python Stem ServerDescriptor](https://stem.torproject.org/api/descriptor/server_descriptor.html)
96
97use std::collections::{HashMap, HashSet};
98use std::fmt;
99use std::net::IpAddr;
100use std::str::FromStr;
101
102use chrono::{DateTime, NaiveDateTime, Utc};
103use derive_builder::Builder;
104
105use crate::exit_policy::ExitPolicy;
106use crate::version::Version;
107use crate::{BridgeDistribution, Error};
108
109use super::{compute_digest, Descriptor, DigestEncoding, DigestHash};
110
111type RouterLineResult = (String, IpAddr, u16, Option<u16>, Option<u16>);
112
113/// Validates a relay fingerprint.
114///
115/// A valid fingerprint is exactly 40 hexadecimal characters (case-insensitive).
116fn is_valid_fingerprint(fingerprint: &str) -> bool {
117    fingerprint.len() == 40 && fingerprint.chars().all(|c| c.is_ascii_hexdigit())
118}
119
120/// Validates a relay nickname.
121///
122/// A valid nickname is 1-19 characters, all alphanumeric (a-z, A-Z, 0-9).
123fn is_valid_nickname(nickname: &str) -> bool {
124    if nickname.is_empty() || nickname.len() > 19 {
125        return false;
126    }
127    nickname.chars().all(|c| c.is_ascii_alphanumeric())
128}
129
130/// A server descriptor containing metadata about a Tor relay.
131///
132/// Server descriptors are the primary documents that relays publish to
133/// describe themselves. They contain identity information, network addresses,
134/// bandwidth capabilities, exit policies, and cryptographic keys needed
135/// for circuit construction.
136///
137/// # Fields Overview
138///
139/// | Category | Fields |
140/// |----------|--------|
141/// | Identity | `nickname`, `fingerprint`, `contact` |
142/// | Network | `address`, `or_port`, `dir_port`, `or_addresses` |
143/// | Bandwidth | `bandwidth_avg`, `bandwidth_burst`, `bandwidth_observed` |
144/// | Policy | `exit_policy`, `exit_policy_v6` |
145/// | Keys | `onion_key`, `signing_key`, `ntor_onion_key`, Ed25519 keys |
146/// | Protocols | `protocols`, `link_protocols`, `circuit_protocols` |
147/// | Metadata | `platform`, `tor_version`, `published`, `uptime` |
148///
149/// # Invariants
150///
151/// - `nickname` is 1-19 alphanumeric characters
152/// - `fingerprint` is 40 uppercase hex characters (if present)
153/// - `or_port` is non-zero
154/// - `published` is a valid UTC timestamp
155/// - `signature` is always present and non-empty
156///
157/// # Example
158///
159/// ```rust,no_run
160/// use stem_rs::descriptor::{ServerDescriptor, Descriptor};
161///
162/// let content = std::fs::read_to_string("server-descriptor").unwrap();
163/// let desc = ServerDescriptor::parse(&content).unwrap();
164///
165/// println!("Nickname: {}", desc.nickname);
166/// println!("Address: {}:{}", desc.address, desc.or_port);
167/// println!("Published: {}", desc.published);
168/// println!("Bandwidth: {} B/s avg, {} B/s observed",
169///          desc.bandwidth_avg, desc.bandwidth_observed);
170///
171/// if let Some(ref fp) = desc.fingerprint {
172///     println!("Fingerprint: {}", fp);
173/// }
174///
175/// if let Some(ref version) = desc.tor_version {
176///     println!("Tor version: {}", version);
177/// }
178/// ```
179///
180/// # Thread Safety
181///
182/// `ServerDescriptor` is `Send` and `Sync` as it contains only owned data.
183#[derive(Debug, Clone, PartialEq, Builder)]
184#[builder(setter(into, strip_option))]
185pub struct ServerDescriptor {
186    /// The relay's nickname (1-19 alphanumeric characters).
187    pub nickname: String,
188    /// The relay's fingerprint (40 hex characters), derived from identity key.
189    #[builder(default)]
190    pub fingerprint: Option<String>,
191    /// The relay's primary IPv4 address.
192    pub address: IpAddr,
193    /// The relay's onion routing port (always non-zero).
194    pub or_port: u16,
195    /// The relay's SOCKS port (deprecated, usually None).
196    #[builder(default)]
197    pub socks_port: Option<u16>,
198    /// The relay's directory port for serving cached descriptors.
199    #[builder(default)]
200    pub dir_port: Option<u16>,
201    /// Additional addresses (IPv4 or IPv6) the relay listens on.
202    /// Each tuple is (address, port, is_ipv6).
203    pub or_addresses: Vec<(IpAddr, u16, bool)>,
204    /// Raw platform string (e.g., "Tor 0.4.7.10 on Linux").
205    #[builder(default)]
206    pub platform: Option<Vec<u8>>,
207    /// Parsed Tor version from the platform string.
208    #[builder(default)]
209    pub tor_version: Option<Version>,
210    /// Operating system from the platform string.
211    #[builder(default)]
212    pub operating_system: Option<String>,
213    /// When this descriptor was published (UTC).
214    pub published: DateTime<Utc>,
215    /// Seconds the relay has been running.
216    #[builder(default)]
217    pub uptime: Option<u64>,
218    /// Contact information for the relay operator.
219    #[builder(default)]
220    pub contact: Option<Vec<u8>>,
221    /// Supported link protocol versions (legacy).
222    #[builder(default)]
223    pub link_protocols: Option<Vec<String>>,
224    /// Supported circuit protocol versions (legacy).
225    #[builder(default)]
226    pub circuit_protocols: Option<Vec<String>>,
227    /// Average bandwidth in bytes per second the relay is willing to sustain.
228    pub bandwidth_avg: u64,
229    /// Maximum bandwidth in bytes per second for short bursts.
230    pub bandwidth_burst: u64,
231    /// Bandwidth in bytes per second the relay has actually observed.
232    pub bandwidth_observed: u64,
233    /// The relay's exit policy (rules for what traffic it will exit).
234    pub exit_policy: ExitPolicy,
235    /// IPv6 exit policy summary (e.g., "accept 80,443" or "reject 1-65535").
236    #[builder(default)]
237    pub exit_policy_v6: Option<String>,
238    /// How this bridge wants to be distributed (bridges only).
239    pub bridge_distribution: BridgeDistribution,
240    /// Fingerprints of related relays (same operator).
241    pub family: HashSet<String>,
242    /// Whether the relay is currently hibernating (reduced service).
243    pub hibernating: bool,
244    /// Whether the relay allows single-hop exits (security risk).
245    pub allow_single_hop_exits: bool,
246    /// Whether the relay accepts tunneled directory requests.
247    pub allow_tunneled_dir_requests: bool,
248    /// Whether the relay caches extra-info descriptors.
249    pub extra_info_cache: bool,
250    /// SHA-1 digest of the relay's extra-info descriptor.
251    #[builder(default)]
252    pub extra_info_digest: Option<String>,
253    /// SHA-256 digest of the relay's extra-info descriptor.
254    #[builder(default)]
255    pub extra_info_sha256_digest: Option<String>,
256    /// Whether the relay serves as a hidden service directory.
257    pub is_hidden_service_dir: bool,
258    /// Supported protocol versions (modern format).
259    /// Maps protocol name to list of supported versions.
260    pub protocols: HashMap<String, Vec<u32>>,
261    /// RSA onion key for circuit creation (PEM format).
262    #[builder(default)]
263    pub onion_key: Option<String>,
264    /// Cross-certification of onion key by identity key.
265    #[builder(default)]
266    pub onion_key_crosscert: Option<String>,
267    /// Curve25519 onion key for ntor handshake (base64).
268    #[builder(default)]
269    pub ntor_onion_key: Option<String>,
270    /// Cross-certification of ntor key.
271    #[builder(default)]
272    pub ntor_onion_key_crosscert: Option<String>,
273    /// Sign bit for ntor key cross-certification.
274    #[builder(default)]
275    pub ntor_onion_key_crosscert_sign: Option<String>,
276    /// RSA signing key (PEM format).
277    #[builder(default)]
278    pub signing_key: Option<String>,
279    /// Ed25519 identity certificate (PEM format).
280    #[builder(default)]
281    pub ed25519_certificate: Option<String>,
282    /// Ed25519 master key (base64).
283    #[builder(default)]
284    pub ed25519_master_key: Option<String>,
285    /// Ed25519 signature over the descriptor.
286    #[builder(default)]
287    pub ed25519_signature: Option<String>,
288    /// RSA signature over the descriptor (PEM format).
289    pub signature: String,
290    /// Raw bytes of the original descriptor content.
291    raw_content: Vec<u8>,
292    /// Lines that were not recognized during parsing.
293    unrecognized_lines: Vec<String>,
294}
295
296impl ServerDescriptor {
297    /// Validates the server descriptor for correctness and consistency.
298    ///
299    /// Performs comprehensive validation including:
300    /// - Nickname format (1-19 alphanumeric characters)
301    /// - Fingerprint format (40 hex characters, if present)
302    /// - IP address validity
303    /// - Port ranges (1-65535)
304    /// - Bandwidth values (non-negative)
305    /// - Timestamp validity
306    ///
307    /// # Returns
308    ///
309    /// `Ok(())` if validation passes, otherwise returns a descriptive error.
310    ///
311    /// # Example
312    ///
313    /// ```rust,no_run
314    /// use stem_rs::descriptor::{ServerDescriptor, Descriptor};
315    ///
316    /// let content = std::fs::read_to_string("server-descriptor").unwrap();
317    /// let descriptor = ServerDescriptor::parse(&content).unwrap();
318    ///
319    /// match descriptor.validate() {
320    ///     Ok(()) => println!("Descriptor is valid"),
321    ///     Err(e) => eprintln!("Validation failed: {}", e),
322    /// }
323    /// ```
324    pub fn validate(&self) -> Result<(), Error> {
325        use crate::descriptor::ServerDescriptorError;
326
327        if !is_valid_nickname(&self.nickname) {
328            return Err(Error::Descriptor(
329                crate::descriptor::DescriptorError::ServerDescriptor(
330                    ServerDescriptorError::InvalidNickname(self.nickname.clone()),
331                ),
332            ));
333        }
334
335        if let Some(ref fp) = self.fingerprint {
336            if !is_valid_fingerprint(fp) {
337                return Err(Error::Descriptor(
338                    crate::descriptor::DescriptorError::ServerDescriptor(
339                        ServerDescriptorError::InvalidFingerprint(fp.clone()),
340                    ),
341                ));
342            }
343        }
344
345        if self.or_port == 0 {
346            return Err(Error::Descriptor(
347                crate::descriptor::DescriptorError::ServerDescriptor(
348                    ServerDescriptorError::MissingRequiredField(
349                        "or_port must be non-zero".to_string(),
350                    ),
351                ),
352            ));
353        }
354
355        if self.bandwidth_burst < self.bandwidth_avg {
356            return Err(Error::Descriptor(
357                crate::descriptor::DescriptorError::ServerDescriptor(
358                    ServerDescriptorError::InvalidBandwidth(format!(
359                        "burst bandwidth ({}) must be >= average bandwidth ({})",
360                        self.bandwidth_burst, self.bandwidth_avg
361                    )),
362                ),
363            ));
364        }
365
366        if self.signature.is_empty() {
367            return Err(Error::Descriptor(
368                crate::descriptor::DescriptorError::ServerDescriptor(
369                    ServerDescriptorError::MissingRequiredField("signature".to_string()),
370                ),
371            ));
372        }
373
374        for (addr, port, _) in &self.or_addresses {
375            if *port == 0 {
376                return Err(Error::Descriptor(
377                    crate::descriptor::DescriptorError::ServerDescriptor(
378                        ServerDescriptorError::MissingRequiredField(format!(
379                            "or-address {} has invalid port 0",
380                            addr
381                        )),
382                    ),
383                ));
384            }
385        }
386
387        for fp in &self.family {
388            let clean_fp = fp.trim_start_matches('$');
389            if !clean_fp.is_empty() && !is_valid_fingerprint(clean_fp) {
390                return Err(Error::Descriptor(
391                    crate::descriptor::DescriptorError::ServerDescriptor(
392                        ServerDescriptorError::InvalidFingerprint(fp.clone()),
393                    ),
394                ));
395            }
396        }
397
398        Ok(())
399    }
400
401    /// Creates a new server descriptor with minimal required fields.
402    ///
403    /// This creates a descriptor with default values for optional fields.
404    /// Use this for testing or when constructing descriptors programmatically.
405    ///
406    /// # Arguments
407    ///
408    /// * `nickname` - The relay's nickname (1-19 alphanumeric characters)
409    /// * `address` - The relay's primary IP address
410    /// * `or_port` - The relay's onion routing port
411    /// * `published` - When this descriptor was published
412    /// * `signature` - The RSA signature (PEM format)
413    ///
414    /// # Example
415    ///
416    /// ```rust
417    /// use stem_rs::descriptor::ServerDescriptor;
418    /// use chrono::Utc;
419    /// use std::net::IpAddr;
420    ///
421    /// let desc = ServerDescriptor::new(
422    ///     "MyRelay".to_string(),
423    ///     "192.168.1.1".parse().unwrap(),
424    ///     9001,
425    ///     Utc::now(),
426    ///     "-----BEGIN SIGNATURE-----\ntest\n-----END SIGNATURE-----".to_string(),
427    /// );
428    ///
429    /// assert_eq!(desc.nickname, "MyRelay");
430    /// assert_eq!(desc.or_port, 9001);
431    /// ```
432    pub fn new(
433        nickname: String,
434        address: IpAddr,
435        or_port: u16,
436        published: DateTime<Utc>,
437        signature: String,
438    ) -> Self {
439        Self {
440            nickname,
441            fingerprint: None,
442            address,
443            or_port,
444            socks_port: None,
445            dir_port: None,
446            or_addresses: Vec::new(),
447            platform: None,
448            tor_version: None,
449            operating_system: None,
450            published,
451            uptime: None,
452            contact: None,
453            link_protocols: None,
454            circuit_protocols: None,
455            bandwidth_avg: 0,
456            bandwidth_burst: 0,
457            bandwidth_observed: 0,
458            exit_policy: ExitPolicy::new(Vec::new()),
459            exit_policy_v6: None,
460            bridge_distribution: BridgeDistribution::Any,
461            family: HashSet::new(),
462            hibernating: false,
463            allow_single_hop_exits: false,
464            allow_tunneled_dir_requests: false,
465            extra_info_cache: false,
466            extra_info_digest: None,
467            extra_info_sha256_digest: None,
468            is_hidden_service_dir: false,
469            protocols: HashMap::new(),
470            onion_key: None,
471            onion_key_crosscert: None,
472            ntor_onion_key: None,
473            ntor_onion_key_crosscert: None,
474            ntor_onion_key_crosscert_sign: None,
475            signing_key: None,
476            ed25519_certificate: None,
477            ed25519_master_key: None,
478            ed25519_signature: None,
479            signature,
480            raw_content: Vec::new(),
481            unrecognized_lines: Vec::new(),
482        }
483    }
484
485    /// Parses the `router` line of a server descriptor.
486    ///
487    /// Format: `router <nickname> <address> <ORPort> <SOCKSPort> <DirPort>`
488    fn parse_router_line(line: &str) -> Result<RouterLineResult, Error> {
489        let parts: Vec<&str> = line.split_whitespace().collect();
490        if parts.len() < 5 {
491            return Err(Error::Parse {
492                location: "router".to_string(),
493                reason: "router line requires 5 fields".to_string(),
494            });
495        }
496
497        let nickname = parts[0].to_string();
498        if !is_valid_nickname(&nickname) {
499            return Err(Error::Parse {
500                location: "router".to_string(),
501                reason: format!("invalid nickname: {}", nickname),
502            });
503        }
504
505        let address: IpAddr = parts[1].parse().map_err(|_| Error::Parse {
506            location: "router".to_string(),
507            reason: format!("invalid address: {}", parts[1]),
508        })?;
509
510        let or_port: u16 = parts[2].parse().map_err(|_| Error::Parse {
511            location: "router".to_string(),
512            reason: format!("invalid or_port: {}", parts[2]),
513        })?;
514
515        let socks_port: Option<u16> = {
516            let port: u16 = parts[3].parse().map_err(|_| Error::Parse {
517                location: "router".to_string(),
518                reason: format!("invalid socks_port: {}", parts[3]),
519            })?;
520            if port == 0 {
521                None
522            } else {
523                Some(port)
524            }
525        };
526
527        let dir_port: Option<u16> = {
528            let port: u16 = parts[4].parse().map_err(|_| Error::Parse {
529                location: "router".to_string(),
530                reason: format!("invalid dir_port: {}", parts[4]),
531            })?;
532            if port == 0 {
533                None
534            } else {
535                Some(port)
536            }
537        };
538
539        Ok((nickname, address, or_port, socks_port, dir_port))
540    }
541
542    fn parse_bandwidth_line(line: &str) -> Result<(u64, u64, u64), Error> {
543        let parts: Vec<&str> = line.split_whitespace().collect();
544        if parts.len() < 3 {
545            return Err(Error::Parse {
546                location: "bandwidth".to_string(),
547                reason: "bandwidth line requires 3 values".to_string(),
548            });
549        }
550
551        let avg: u64 = parts[0].parse().map_err(|_| Error::Parse {
552            location: "bandwidth".to_string(),
553            reason: format!("invalid average bandwidth: {}", parts[0]),
554        })?;
555
556        let burst: u64 = parts[1].parse().map_err(|_| Error::Parse {
557            location: "bandwidth".to_string(),
558            reason: format!("invalid burst bandwidth: {}", parts[1]),
559        })?;
560
561        let observed: u64 = parts[2].parse().map_err(|_| Error::Parse {
562            location: "bandwidth".to_string(),
563            reason: format!("invalid observed bandwidth: {}", parts[2]),
564        })?;
565
566        Ok((avg, burst, observed))
567    }
568
569    fn parse_published_line(line: &str) -> Result<DateTime<Utc>, Error> {
570        let datetime =
571            NaiveDateTime::parse_from_str(line.trim(), "%Y-%m-%d %H:%M:%S").map_err(|e| {
572                Error::Parse {
573                    location: "published".to_string(),
574                    reason: format!("invalid datetime: {} - {}", line, e),
575                }
576            })?;
577        Ok(datetime.and_utc())
578    }
579
580    fn parse_fingerprint_line(line: &str) -> String {
581        line.replace(' ', "")
582    }
583
584    fn parse_platform_line(line: &str) -> (Option<Vec<u8>>, Option<Version>, Option<String>) {
585        let platform = line.as_bytes().to_vec();
586        let mut tor_version = None;
587        let mut operating_system = None;
588
589        if let Some(on_pos) = line.find(" on ") {
590            let version_part = &line[..on_pos];
591            operating_system = Some(line[on_pos + 4..].to_string());
592
593            if let Some(ver_start) = version_part.find(char::is_whitespace) {
594                let ver_str = version_part[ver_start..].trim();
595                if let Ok(v) = Version::parse(ver_str) {
596                    tor_version = Some(v);
597                }
598            }
599        }
600
601        (Some(platform), tor_version, operating_system)
602    }
603
604    fn parse_protocols_line(line: &str) -> (Option<Vec<String>>, Option<Vec<String>>) {
605        let mut link_protocols = None;
606        let mut circuit_protocols = None;
607        let parts: Vec<&str> = line.split_whitespace().collect();
608        let mut i = 0;
609        while i < parts.len() {
610            if parts[i] == "Link" {
611                let mut protos = Vec::new();
612                i += 1;
613                while i < parts.len() && parts[i] != "Circuit" {
614                    protos.push(parts[i].to_string());
615                    i += 1;
616                }
617                link_protocols = Some(protos);
618            } else if parts[i] == "Circuit" {
619                let mut protos = Vec::new();
620                i += 1;
621                while i < parts.len() && parts[i] != "Link" {
622                    protos.push(parts[i].to_string());
623                    i += 1;
624                }
625                circuit_protocols = Some(protos);
626            } else {
627                i += 1;
628            }
629        }
630        (link_protocols, circuit_protocols)
631    }
632
633    fn parse_family_line(line: &str) -> HashSet<String> {
634        line.split_whitespace().map(|s| s.to_string()).collect()
635    }
636
637    fn parse_or_address(line: &str) -> Result<(IpAddr, u16, bool), Error> {
638        let line = line.trim();
639        if line.starts_with('[') {
640            if let Some(bracket_end) = line.find(']') {
641                let ipv6_str = &line[1..bracket_end];
642                let port_str = &line[bracket_end + 2..];
643                let addr: IpAddr = ipv6_str.parse().map_err(|_| Error::Parse {
644                    location: "or-address".to_string(),
645                    reason: format!("invalid IPv6 address: {}", ipv6_str),
646                })?;
647                let port: u16 = port_str.parse().map_err(|_| Error::Parse {
648                    location: "or-address".to_string(),
649                    reason: format!("invalid port: {}", port_str),
650                })?;
651                return Ok((addr, port, true));
652            }
653        }
654
655        if let Some(colon_pos) = line.rfind(':') {
656            let addr_str = &line[..colon_pos];
657            let port_str = &line[colon_pos + 1..];
658            let addr: IpAddr = addr_str.parse().map_err(|_| Error::Parse {
659                location: "or-address".to_string(),
660                reason: format!("invalid address: {}", addr_str),
661            })?;
662            let port: u16 = port_str.parse().map_err(|_| Error::Parse {
663                location: "or-address".to_string(),
664                reason: format!("invalid port: {}", port_str),
665            })?;
666            let is_ipv6 = addr.is_ipv6();
667            return Ok((addr, port, is_ipv6));
668        }
669
670        Err(Error::Parse {
671            location: "or-address".to_string(),
672            reason: format!("invalid or-address format: {}", line),
673        })
674    }
675
676    fn parse_extra_info_digest(line: &str) -> (Option<String>, Option<String>) {
677        let parts: Vec<&str> = line.split_whitespace().collect();
678        let sha1_digest = parts.first().map(|s| s.to_string());
679        let sha256_digest = parts.get(1).map(|s| s.to_string());
680        (sha1_digest, sha256_digest)
681    }
682
683    fn extract_pem_block(lines: &[&str], start_idx: usize) -> (String, usize) {
684        let mut block = String::new();
685        let mut idx = start_idx;
686        while idx < lines.len() {
687            let line = lines[idx];
688            block.push_str(line);
689            block.push('\n');
690            if line.starts_with("-----END ") {
691                break;
692            }
693            idx += 1;
694        }
695        (block.trim_end().to_string(), idx)
696    }
697
698    /// Finds the content range used for digest computation.
699    ///
700    /// The digest is computed from `router ` through `router-signature\n`.
701    fn find_digest_content(content: &str) -> Option<&str> {
702        let start_marker = "router ";
703        let end_marker = "\nrouter-signature\n";
704        let start = content.find(start_marker)?;
705        let end = content.find(end_marker)?;
706        Some(&content[start..end + end_marker.len()])
707    }
708}
709
710impl Descriptor for ServerDescriptor {
711    fn parse(content: &str) -> Result<Self, Error> {
712        let raw_content = content.as_bytes().to_vec();
713        let lines: Vec<&str> = content.lines().collect();
714
715        let mut nickname = String::new();
716        let mut address: Option<IpAddr> = None;
717        let mut or_port: u16 = 0;
718        let mut socks_port: Option<u16> = None;
719        let mut dir_port: Option<u16> = None;
720        let mut fingerprint: Option<String> = None;
721        let mut or_addresses: Vec<(IpAddr, u16, bool)> = Vec::new();
722        let mut platform: Option<Vec<u8>> = None;
723        let mut tor_version: Option<Version> = None;
724        let mut operating_system: Option<String> = None;
725        let mut published: Option<DateTime<Utc>> = None;
726        let mut uptime: Option<u64> = None;
727        let mut contact: Option<Vec<u8>> = None;
728        let mut link_protocols: Option<Vec<String>> = None;
729        let mut circuit_protocols: Option<Vec<String>> = None;
730        let mut bandwidth_avg: u64 = 0;
731        let mut bandwidth_burst: u64 = 0;
732        let mut bandwidth_observed: u64 = 0;
733        let mut exit_policy_rules: Vec<String> = Vec::new();
734        let mut exit_policy_v6: Option<String> = None;
735        let mut bridge_distribution = BridgeDistribution::Any;
736        let mut family: HashSet<String> = HashSet::new();
737        let mut hibernating = false;
738        let mut allow_single_hop_exits = false;
739        let mut allow_tunneled_dir_requests = false;
740        let mut extra_info_cache = false;
741        let mut extra_info_digest: Option<String> = None;
742        let mut extra_info_sha256_digest: Option<String> = None;
743        let mut is_hidden_service_dir = false;
744        let mut protocols: HashMap<String, Vec<u32>> = HashMap::new();
745        let mut onion_key: Option<String> = None;
746        let mut onion_key_crosscert: Option<String> = None;
747        let mut ntor_onion_key: Option<String> = None;
748        let mut ntor_onion_key_crosscert: Option<String> = None;
749        let mut ntor_onion_key_crosscert_sign: Option<String> = None;
750        let mut signing_key: Option<String> = None;
751        let mut ed25519_certificate: Option<String> = None;
752        let mut ed25519_master_key: Option<String> = None;
753        let mut ed25519_signature: Option<String> = None;
754        let mut signature = String::new();
755        let mut unrecognized_lines: Vec<String> = Vec::new();
756
757        let mut idx = 0;
758        while idx < lines.len() {
759            let line = lines[idx];
760
761            if line.starts_with("@type ") {
762                idx += 1;
763                continue;
764            }
765
766            let line = line.strip_prefix("opt ").unwrap_or(line);
767
768            let (keyword, value) = if let Some(space_pos) = line.find(' ') {
769                (&line[..space_pos], line[space_pos + 1..].trim())
770            } else {
771                (line, "")
772            };
773
774            match keyword {
775                "router" => {
776                    let (n, a, op, sp, dp) = Self::parse_router_line(value)?;
777                    nickname = n;
778                    address = Some(a);
779                    or_port = op;
780                    socks_port = sp;
781                    dir_port = dp;
782                }
783                "identity-ed25519" => {
784                    let (block, end_idx) = Self::extract_pem_block(&lines, idx + 1);
785                    ed25519_certificate = Some(block);
786                    idx = end_idx;
787                }
788                "master-key-ed25519" => {
789                    ed25519_master_key = Some(value.to_string());
790                }
791                "bandwidth" => {
792                    let (avg, burst, obs) = Self::parse_bandwidth_line(value)?;
793                    bandwidth_avg = avg;
794                    bandwidth_burst = burst;
795                    bandwidth_observed = obs;
796                }
797                "platform" => {
798                    let (p, v, os) = Self::parse_platform_line(value);
799                    platform = p;
800                    tor_version = v;
801                    operating_system = os;
802                }
803                "published" => {
804                    published = Some(Self::parse_published_line(value)?);
805                }
806                "fingerprint" => {
807                    fingerprint = Some(Self::parse_fingerprint_line(value));
808                }
809                "uptime" => {
810                    uptime = value.parse().ok();
811                }
812                "contact" => {
813                    contact = Some(value.as_bytes().to_vec());
814                }
815                "protocols" => {
816                    let (lp, cp) = Self::parse_protocols_line(value);
817                    link_protocols = lp;
818                    circuit_protocols = cp;
819                }
820                "proto" => {
821                    for entry in value.split_whitespace() {
822                        if let Some(eq_pos) = entry.find('=') {
823                            let proto_name = &entry[..eq_pos];
824                            let versions_str = &entry[eq_pos + 1..];
825                            let versions: Vec<u32> = versions_str
826                                .split(',')
827                                .filter_map(|v| {
828                                    if let Some(dash) = v.find('-') {
829                                        let start: u32 = v[..dash].parse().ok()?;
830                                        let end: u32 = v[dash + 1..].parse().ok()?;
831                                        Some((start..=end).collect::<Vec<_>>())
832                                    } else {
833                                        v.parse().ok().map(|n| vec![n])
834                                    }
835                                })
836                                .flatten()
837                                .collect();
838                            protocols.insert(proto_name.to_string(), versions);
839                        }
840                    }
841                }
842                "family" => {
843                    family = Self::parse_family_line(value);
844                }
845                "or-address" => {
846                    if let Ok(addr) = Self::parse_or_address(value) {
847                        or_addresses.push(addr);
848                    }
849                }
850                "extra-info-digest" => {
851                    let (sha1, sha256) = Self::parse_extra_info_digest(value);
852                    extra_info_digest = sha1;
853                    extra_info_sha256_digest = sha256;
854                }
855                "hidden-service-dir" => {
856                    is_hidden_service_dir = true;
857                }
858                "caches-extra-info" => {
859                    extra_info_cache = true;
860                }
861                "hibernating" => {
862                    hibernating = value == "1";
863                }
864                "allow-single-hop-exits" => {
865                    allow_single_hop_exits = true;
866                }
867                "tunnelled-dir-server" => {
868                    allow_tunneled_dir_requests = true;
869                }
870                "bridge-distribution-request" => {
871                    bridge_distribution = match value.to_lowercase().as_str() {
872                        "https" => BridgeDistribution::Https,
873                        "email" => BridgeDistribution::Email,
874                        "moat" => BridgeDistribution::Moat,
875                        "hyphae" => BridgeDistribution::Hyphae,
876                        _ => BridgeDistribution::Any,
877                    };
878                }
879                "accept" | "reject" => {
880                    exit_policy_rules.push(line.to_string());
881                }
882                "ipv6-policy" => {
883                    exit_policy_v6 = Some(value.to_string());
884                }
885                "onion-key" => {
886                    let (block, end_idx) = Self::extract_pem_block(&lines, idx + 1);
887                    onion_key = Some(block);
888                    idx = end_idx;
889                }
890                "onion-key-crosscert" => {
891                    let (block, end_idx) = Self::extract_pem_block(&lines, idx + 1);
892                    onion_key_crosscert = Some(block);
893                    idx = end_idx;
894                }
895                "ntor-onion-key" => {
896                    ntor_onion_key = Some(value.to_string());
897                }
898                "ntor-onion-key-crosscert" => {
899                    ntor_onion_key_crosscert_sign = Some(value.to_string());
900                    let (block, end_idx) = Self::extract_pem_block(&lines, idx + 1);
901                    ntor_onion_key_crosscert = Some(block);
902                    idx = end_idx;
903                }
904                "signing-key" => {
905                    let (block, end_idx) = Self::extract_pem_block(&lines, idx + 1);
906                    signing_key = Some(block);
907                    idx = end_idx;
908                }
909                "router-sig-ed25519" => {
910                    ed25519_signature = Some(value.to_string());
911                }
912                "router-signature" => {
913                    let (block, end_idx) = Self::extract_pem_block(&lines, idx + 1);
914                    signature = block;
915                    idx = end_idx;
916                }
917                _ => {
918                    if !line.is_empty() && !line.starts_with("-----") {
919                        unrecognized_lines.push(line.to_string());
920                    }
921                }
922            }
923            idx += 1;
924        }
925
926        let address = address.ok_or_else(|| Error::Parse {
927            location: "router".to_string(),
928            reason: "missing router line".to_string(),
929        })?;
930
931        let published = published.ok_or_else(|| Error::Parse {
932            location: "published".to_string(),
933            reason: "missing published line".to_string(),
934        })?;
935
936        let exit_policy = if exit_policy_rules.is_empty() {
937            ExitPolicy::new(Vec::new())
938        } else {
939            ExitPolicy::from_rules(&exit_policy_rules)?
940        };
941
942        Ok(Self {
943            nickname,
944            fingerprint,
945            address,
946            or_port,
947            socks_port,
948            dir_port,
949            or_addresses,
950            platform,
951            tor_version,
952            operating_system,
953            published,
954            uptime,
955            contact,
956            link_protocols,
957            circuit_protocols,
958            bandwidth_avg,
959            bandwidth_burst,
960            bandwidth_observed,
961            exit_policy,
962            exit_policy_v6,
963            bridge_distribution,
964            family,
965            hibernating,
966            allow_single_hop_exits,
967            allow_tunneled_dir_requests,
968            extra_info_cache,
969            extra_info_digest,
970            extra_info_sha256_digest,
971            is_hidden_service_dir,
972            protocols,
973            onion_key,
974            onion_key_crosscert,
975            ntor_onion_key,
976            ntor_onion_key_crosscert,
977            ntor_onion_key_crosscert_sign,
978            signing_key,
979            ed25519_certificate,
980            ed25519_master_key,
981            ed25519_signature,
982            signature,
983            raw_content,
984            unrecognized_lines,
985        })
986    }
987
988    fn to_descriptor_string(&self) -> String {
989        let mut result = String::new();
990
991        result.push_str(&format!(
992            "router {} {} {} {} {}\n",
993            self.nickname,
994            self.address,
995            self.or_port,
996            self.socks_port.unwrap_or(0),
997            self.dir_port.unwrap_or(0)
998        ));
999
1000        if let Some(ref platform) = self.platform {
1001            if let Ok(s) = std::str::from_utf8(platform) {
1002                result.push_str(&format!("platform {}\n", s));
1003            }
1004        }
1005
1006        if let Some(ref link) = self.link_protocols {
1007            if let Some(ref circuit) = self.circuit_protocols {
1008                result.push_str(&format!(
1009                    "protocols Link {} Circuit {}\n",
1010                    link.join(" "),
1011                    circuit.join(" ")
1012                ));
1013            }
1014        }
1015
1016        result.push_str(&format!(
1017            "published {}\n",
1018            self.published.format("%Y-%m-%d %H:%M:%S")
1019        ));
1020
1021        if let Some(ref fp) = self.fingerprint {
1022            let formatted: String = fp
1023                .chars()
1024                .collect::<Vec<_>>()
1025                .chunks(4)
1026                .map(|c| c.iter().collect::<String>())
1027                .collect::<Vec<_>>()
1028                .join(" ");
1029            result.push_str(&format!("fingerprint {}\n", formatted));
1030        }
1031
1032        if let Some(uptime) = self.uptime {
1033            result.push_str(&format!("uptime {}\n", uptime));
1034        }
1035
1036        result.push_str(&format!(
1037            "bandwidth {} {} {}\n",
1038            self.bandwidth_avg, self.bandwidth_burst, self.bandwidth_observed
1039        ));
1040
1041        if let Some(ref digest) = self.extra_info_digest {
1042            if let Some(ref sha256) = self.extra_info_sha256_digest {
1043                result.push_str(&format!("extra-info-digest {} {}\n", digest, sha256));
1044            } else {
1045                result.push_str(&format!("extra-info-digest {}\n", digest));
1046            }
1047        }
1048
1049        if let Some(ref key) = self.onion_key {
1050            result.push_str("onion-key\n");
1051            result.push_str(key);
1052            result.push('\n');
1053        }
1054
1055        if let Some(ref key) = self.signing_key {
1056            result.push_str("signing-key\n");
1057            result.push_str(key);
1058            result.push('\n');
1059        }
1060
1061        if !self.family.is_empty() {
1062            let family_str: Vec<&str> = self.family.iter().map(|s| s.as_str()).collect();
1063            result.push_str(&format!("family {}\n", family_str.join(" ")));
1064        }
1065
1066        if self.is_hidden_service_dir {
1067            result.push_str("hidden-service-dir\n");
1068        }
1069
1070        if let Some(ref contact) = self.contact {
1071            if let Ok(s) = std::str::from_utf8(contact) {
1072                result.push_str(&format!("contact {}\n", s));
1073            }
1074        }
1075
1076        for rule in self.exit_policy.iter() {
1077            result.push_str(&format!("{}\n", rule));
1078        }
1079
1080        result.push_str("router-signature\n");
1081        result.push_str(&self.signature);
1082        result.push('\n');
1083
1084        result
1085    }
1086
1087    fn digest(&self, hash: DigestHash, encoding: DigestEncoding) -> Result<String, Error> {
1088        let content_str = std::str::from_utf8(&self.raw_content).map_err(|_| Error::Parse {
1089            location: "digest".to_string(),
1090            reason: "invalid UTF-8 in raw content".to_string(),
1091        })?;
1092
1093        let digest_content =
1094            Self::find_digest_content(content_str).ok_or_else(|| Error::Parse {
1095                location: "digest".to_string(),
1096                reason: "could not find digest content boundaries".to_string(),
1097            })?;
1098
1099        Ok(compute_digest(digest_content.as_bytes(), hash, encoding))
1100    }
1101
1102    fn raw_content(&self) -> &[u8] {
1103        &self.raw_content
1104    }
1105
1106    fn unrecognized_lines(&self) -> &[String] {
1107        &self.unrecognized_lines
1108    }
1109}
1110
1111impl FromStr for ServerDescriptor {
1112    type Err = Error;
1113
1114    fn from_str(s: &str) -> Result<Self, Self::Err> {
1115        Self::parse(s)
1116    }
1117}
1118
1119impl fmt::Display for ServerDescriptor {
1120    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1121        write!(f, "{}", self.to_descriptor_string())
1122    }
1123}
1124
1125#[cfg(test)]
1126mod tests {
1127    use super::*;
1128    use chrono::{Datelike, Timelike};
1129
1130    const EXAMPLE_DESCRIPTOR: &str = r#"@type server-descriptor 1.0
1131router caerSidi 71.35.133.197 9001 0 0
1132platform Tor 0.2.1.30 on Linux x86_64
1133opt protocols Link 1 2 Circuit 1
1134published 2012-03-01 17:15:27
1135opt fingerprint A756 9A83 B570 6AB1 B1A9 CB52 EFF7 D2D3 2E45 53EB
1136uptime 588217
1137bandwidth 153600 256000 104590
1138opt extra-info-digest D225B728768D7EA4B5587C13A7A9D22EBBEE6E66
1139onion-key
1140-----BEGIN RSA PUBLIC KEY-----
1141MIGJAoGBAJv5IIWQ+WDWYUdyA/0L8qbIkEVH/cwryZWoIaPAzINfrw1WfNZGtBmg
1142skFtXhOHHqTRN4GPPrZsAIUOQGzQtGb66IQgT4tO/pj+P6QmSCCdTfhvGfgTCsC+
1143WPi4Fl2qryzTb3QO5r5x7T8OsG2IBUET1bLQzmtbC560SYR49IvVAgMBAAE=
1144-----END RSA PUBLIC KEY-----
1145signing-key
1146-----BEGIN RSA PUBLIC KEY-----
1147MIGJAoGBAKwvOXyztVKnuYvpTKt+nS3XIKeO8dVungi8qGoeS+6gkR6lDtGfBTjd
1148uE9UIkdAl9zi8/1Ic2wsUNHE9jiS0VgeupITGZY8YOyMJJ/xtV1cqgiWhq1dUYaq
114951TOtUogtAPgXPh4J+V8HbFFIcCzIh3qCO/xXo+DSHhv7SSif1VpAgMBAAE=
1150-----END RSA PUBLIC KEY-----
1151family $0CE3CFB1E9CC47B63EA8869813BF6FAB7D4540C1 $1FD187E8F69A9B74C9202DC16A25B9E7744AB9F6 $74FB5EFA6A46DE4060431D515DC9A790E6AD9A7C $77001D8DA9BF445B0F81AA427A675F570D222E6A $B6D83EC2D9E18B0A7A33428F8CFA9C536769E209 $D2F37F46182C23AB747787FD657E680B34EAF892 $E0BD57A11F00041A9789577C53A1B784473669E4 $E5E3E9A472EAF7BE9682B86E92305DB4C71048EF
1152opt hidden-service-dir
1153contact www.atagar.com/contact
1154reject *:*
1155router-signature
1156-----BEGIN SIGNATURE-----
1157dskLSPz8beUW7bzwDjR6EVNGpyoZde83Ejvau+5F2c6cGnlu91fiZN3suE88iE6e
1158758b9ldq5eh5mapb8vuuV3uO+0Xsud7IEOqfxdkmk0GKnUX8ouru7DSIUzUL0zqq
1159Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
1160-----END SIGNATURE-----
1161"#;
1162
1163    #[test]
1164    fn test_parse_example_descriptor() {
1165        let desc = ServerDescriptor::parse(EXAMPLE_DESCRIPTOR).unwrap();
1166
1167        assert_eq!(desc.nickname, "caerSidi");
1168        assert_eq!(
1169            desc.fingerprint,
1170            Some("A7569A83B5706AB1B1A9CB52EFF7D2D32E4553EB".to_string())
1171        );
1172        assert_eq!(desc.address.to_string(), "71.35.133.197");
1173        assert_eq!(desc.or_port, 9001);
1174        assert_eq!(desc.socks_port, None);
1175        assert_eq!(desc.dir_port, None);
1176        assert_eq!(
1177            desc.platform,
1178            Some(b"Tor 0.2.1.30 on Linux x86_64".to_vec())
1179        );
1180        assert_eq!(desc.tor_version, Some(Version::parse("0.2.1.30").unwrap()));
1181        assert_eq!(desc.operating_system, Some("Linux x86_64".to_string()));
1182        assert_eq!(desc.uptime, Some(588217));
1183        assert_eq!(
1184            desc.published,
1185            NaiveDateTime::parse_from_str("2012-03-01 17:15:27", "%Y-%m-%d %H:%M:%S")
1186                .unwrap()
1187                .and_utc()
1188        );
1189        assert_eq!(desc.contact, Some(b"www.atagar.com/contact".to_vec()));
1190        assert_eq!(
1191            desc.link_protocols,
1192            Some(vec!["1".to_string(), "2".to_string()])
1193        );
1194        assert_eq!(desc.circuit_protocols, Some(vec!["1".to_string()]));
1195        assert!(desc.is_hidden_service_dir);
1196        assert!(!desc.hibernating);
1197        assert!(!desc.allow_single_hop_exits);
1198        assert!(!desc.allow_tunneled_dir_requests);
1199        assert!(!desc.extra_info_cache);
1200        assert_eq!(
1201            desc.extra_info_digest,
1202            Some("D225B728768D7EA4B5587C13A7A9D22EBBEE6E66".to_string())
1203        );
1204        assert_eq!(desc.extra_info_sha256_digest, None);
1205        assert_eq!(desc.bridge_distribution, BridgeDistribution::Any);
1206        assert_eq!(desc.family.len(), 8);
1207        assert!(desc
1208            .family
1209            .contains("$0CE3CFB1E9CC47B63EA8869813BF6FAB7D4540C1"));
1210        assert_eq!(desc.bandwidth_avg, 153600);
1211        assert_eq!(desc.bandwidth_burst, 256000);
1212        assert_eq!(desc.bandwidth_observed, 104590);
1213        assert!(desc.onion_key.is_some());
1214        assert!(desc.signing_key.is_some());
1215        assert!(desc.signature.contains("BEGIN SIGNATURE"));
1216        assert!(desc.unrecognized_lines.is_empty());
1217    }
1218
1219    #[test]
1220    fn test_parse_minimal_descriptor() {
1221        let minimal = r#"router TestRelay 192.168.1.1 9001 0 0
1222published 2023-01-01 00:00:00
1223bandwidth 1000 2000 500
1224router-signature
1225-----BEGIN SIGNATURE-----
1226test
1227-----END SIGNATURE-----
1228"#;
1229        let desc = ServerDescriptor::parse(minimal).unwrap();
1230        assert_eq!(desc.nickname, "TestRelay");
1231        assert_eq!(desc.address.to_string(), "192.168.1.1");
1232        assert_eq!(desc.or_port, 9001);
1233        assert_eq!(desc.bandwidth_avg, 1000);
1234        assert_eq!(desc.bandwidth_burst, 2000);
1235        assert_eq!(desc.bandwidth_observed, 500);
1236    }
1237
1238    #[test]
1239    fn test_parse_router_line() {
1240        let (nickname, address, or_port, socks_port, dir_port) =
1241            ServerDescriptor::parse_router_line("caerSidi 71.35.133.197 9001 0 0").unwrap();
1242        assert_eq!(nickname, "caerSidi");
1243        assert_eq!(address.to_string(), "71.35.133.197");
1244        assert_eq!(or_port, 9001);
1245        assert_eq!(socks_port, None);
1246        assert_eq!(dir_port, None);
1247    }
1248
1249    #[test]
1250    fn test_parse_router_line_with_ports() {
1251        let (nickname, address, or_port, socks_port, dir_port) =
1252            ServerDescriptor::parse_router_line("TestRelay 10.0.0.1 9001 9050 9030").unwrap();
1253        assert_eq!(nickname, "TestRelay");
1254        assert_eq!(address.to_string(), "10.0.0.1");
1255        assert_eq!(or_port, 9001);
1256        assert_eq!(socks_port, Some(9050));
1257        assert_eq!(dir_port, Some(9030));
1258    }
1259
1260    #[test]
1261    fn test_parse_bandwidth_line() {
1262        let (avg, burst, observed) =
1263            ServerDescriptor::parse_bandwidth_line("153600 256000 104590").unwrap();
1264        assert_eq!(avg, 153600);
1265        assert_eq!(burst, 256000);
1266        assert_eq!(observed, 104590);
1267    }
1268
1269    #[test]
1270    fn test_parse_published_line() {
1271        let dt = ServerDescriptor::parse_published_line("2012-03-01 17:15:27").unwrap();
1272        assert_eq!(dt.year(), 2012);
1273        assert_eq!(dt.month(), 3);
1274        assert_eq!(dt.day(), 1);
1275        assert_eq!(dt.hour(), 17);
1276        assert_eq!(dt.minute(), 15);
1277        assert_eq!(dt.second(), 27);
1278    }
1279
1280    #[test]
1281    fn test_parse_fingerprint_line() {
1282        let fp = ServerDescriptor::parse_fingerprint_line(
1283            "A756 9A83 B570 6AB1 B1A9 CB52 EFF7 D2D3 2E45 53EB",
1284        );
1285        assert_eq!(fp, "A7569A83B5706AB1B1A9CB52EFF7D2D32E4553EB");
1286    }
1287
1288    #[test]
1289    fn test_parse_platform_line() {
1290        let (platform, version, os) =
1291            ServerDescriptor::parse_platform_line("Tor 0.2.1.30 on Linux x86_64");
1292        assert_eq!(platform, Some(b"Tor 0.2.1.30 on Linux x86_64".to_vec()));
1293        assert_eq!(version, Some(Version::parse("0.2.1.30").unwrap()));
1294        assert_eq!(os, Some("Linux x86_64".to_string()));
1295    }
1296
1297    #[test]
1298    fn test_parse_protocols_line() {
1299        let (link, circuit) = ServerDescriptor::parse_protocols_line("Link 1 2 Circuit 1");
1300        assert_eq!(link, Some(vec!["1".to_string(), "2".to_string()]));
1301        assert_eq!(circuit, Some(vec!["1".to_string()]));
1302    }
1303
1304    #[test]
1305    fn test_parse_family_line() {
1306        let family = ServerDescriptor::parse_family_line("$ABC123 $DEF456 $GHI789");
1307        assert_eq!(family.len(), 3);
1308        assert!(family.contains("$ABC123"));
1309        assert!(family.contains("$DEF456"));
1310        assert!(family.contains("$GHI789"));
1311    }
1312
1313    #[test]
1314    fn test_parse_or_address_ipv4() {
1315        let (addr, port, is_ipv6) = ServerDescriptor::parse_or_address("192.168.1.1:9001").unwrap();
1316        assert_eq!(addr.to_string(), "192.168.1.1");
1317        assert_eq!(port, 9001);
1318        assert!(!is_ipv6);
1319    }
1320
1321    #[test]
1322    fn test_parse_or_address_ipv6() {
1323        let (addr, port, is_ipv6) =
1324            ServerDescriptor::parse_or_address("[2001:db8::1]:9001").unwrap();
1325        assert_eq!(addr.to_string(), "2001:db8::1");
1326        assert_eq!(port, 9001);
1327        assert!(is_ipv6);
1328    }
1329
1330    #[test]
1331    fn test_invalid_nickname_too_long() {
1332        let result = ServerDescriptor::parse_router_line(
1333            "ThisNicknameIsWayTooLongToBeValid 192.168.1.1 9001 0 0",
1334        );
1335        assert!(result.is_err());
1336    }
1337
1338    #[test]
1339    fn test_invalid_nickname_special_chars() {
1340        let result = ServerDescriptor::parse_router_line("Invalid$Name 192.168.1.1 9001 0 0");
1341        assert!(result.is_err());
1342    }
1343
1344    #[test]
1345    fn test_invalid_address() {
1346        let result = ServerDescriptor::parse_router_line("TestRelay 999.999.999.999 9001 0 0");
1347        assert!(result.is_err());
1348    }
1349
1350    #[test]
1351    fn test_invalid_port() {
1352        let result = ServerDescriptor::parse_router_line("TestRelay 192.168.1.1 99999 0 0");
1353        assert!(result.is_err());
1354    }
1355
1356    #[test]
1357    fn test_digest_sha1() {
1358        let desc = ServerDescriptor::parse(EXAMPLE_DESCRIPTOR).unwrap();
1359        let digest = desc.digest(DigestHash::Sha1, DigestEncoding::Hex).unwrap();
1360        assert_eq!(digest.len(), 40);
1361        assert!(digest.chars().all(|c| c.is_ascii_hexdigit()));
1362    }
1363
1364    #[test]
1365    fn test_digest_sha256() {
1366        let desc = ServerDescriptor::parse(EXAMPLE_DESCRIPTOR).unwrap();
1367        let digest = desc
1368            .digest(DigestHash::Sha256, DigestEncoding::Hex)
1369            .unwrap();
1370        assert_eq!(digest.len(), 64);
1371        assert!(digest.chars().all(|c| c.is_ascii_hexdigit()));
1372    }
1373
1374    #[test]
1375    fn test_to_descriptor_string() {
1376        let desc = ServerDescriptor::parse(EXAMPLE_DESCRIPTOR).unwrap();
1377        let output = desc.to_descriptor_string();
1378        assert!(output.contains("router caerSidi 71.35.133.197 9001 0 0"));
1379        assert!(output.contains("bandwidth 153600 256000 104590"));
1380        assert!(output.contains("router-signature"));
1381    }
1382
1383    #[test]
1384    fn test_descriptor_with_exit_policy() {
1385        let content = r#"router TestRelay 192.168.1.1 9001 0 0
1386published 2023-01-01 00:00:00
1387bandwidth 1000 2000 500
1388accept *:80
1389accept *:443
1390reject *:*
1391router-signature
1392-----BEGIN SIGNATURE-----
1393test
1394-----END SIGNATURE-----
1395"#;
1396        let desc = ServerDescriptor::parse(content).unwrap();
1397        assert!(desc
1398            .exit_policy
1399            .can_exit_to("10.0.0.1".parse().unwrap(), 80));
1400        assert!(desc
1401            .exit_policy
1402            .can_exit_to("10.0.0.1".parse().unwrap(), 443));
1403        assert!(!desc
1404            .exit_policy
1405            .can_exit_to("10.0.0.1".parse().unwrap(), 22));
1406    }
1407
1408    #[test]
1409    fn test_descriptor_with_hibernating() {
1410        let content = r#"router TestRelay 192.168.1.1 9001 0 0
1411published 2023-01-01 00:00:00
1412bandwidth 1000 2000 500
1413hibernating 1
1414router-signature
1415-----BEGIN SIGNATURE-----
1416test
1417-----END SIGNATURE-----
1418"#;
1419        let desc = ServerDescriptor::parse(content).unwrap();
1420        assert!(desc.hibernating);
1421    }
1422
1423    #[test]
1424    fn test_descriptor_with_proto() {
1425        let content = r#"router TestRelay 192.168.1.1 9001 0 0
1426published 2023-01-01 00:00:00
1427bandwidth 1000 2000 500
1428proto Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
1429router-signature
1430-----BEGIN SIGNATURE-----
1431test
1432-----END SIGNATURE-----
1433"#;
1434        let desc = ServerDescriptor::parse(content).unwrap();
1435        assert!(desc.protocols.contains_key("Cons"));
1436        assert!(desc.protocols.contains_key("Link"));
1437        assert_eq!(desc.protocols.get("Cons"), Some(&vec![1, 2]));
1438    }
1439
1440    #[test]
1441    fn test_is_valid_nickname() {
1442        assert!(is_valid_nickname("caerSidi"));
1443        assert!(is_valid_nickname("TestRelay123"));
1444        assert!(is_valid_nickname("A"));
1445        assert!(is_valid_nickname("ABCDEFGHIJKLMNOPQRS"));
1446        assert!(!is_valid_nickname(""));
1447        assert!(!is_valid_nickname("ABCDEFGHIJKLMNOPQRST"));
1448        assert!(!is_valid_nickname("Invalid$Name"));
1449        assert!(!is_valid_nickname("Invalid Name"));
1450    }
1451}
1452
1453#[cfg(test)]
1454mod proptests {
1455    use super::*;
1456    use proptest::prelude::*;
1457
1458    fn valid_nickname() -> impl Strategy<Value = String> {
1459        "[a-zA-Z][a-zA-Z0-9]{0,18}".prop_filter("must be valid nickname", |s| {
1460            !s.is_empty() && s.len() <= 19 && s.chars().all(|c| c.is_ascii_alphanumeric())
1461        })
1462    }
1463
1464    fn valid_ipv4() -> impl Strategy<Value = IpAddr> {
1465        (1u8..255, 0u8..255, 0u8..255, 1u8..255)
1466            .prop_map(|(a, b, c, d)| IpAddr::V4(std::net::Ipv4Addr::new(a, b, c, d)))
1467    }
1468
1469    fn valid_port() -> impl Strategy<Value = u16> {
1470        1u16..65535
1471    }
1472
1473    fn valid_bandwidth() -> impl Strategy<Value = u64> {
1474        1u64..1_000_000_000
1475    }
1476
1477    fn valid_datetime() -> impl Strategy<Value = DateTime<Utc>> {
1478        (
1479            2000i32..2030,
1480            1u32..13,
1481            1u32..29,
1482            0u32..24,
1483            0u32..60,
1484            0u32..60,
1485        )
1486            .prop_map(|(year, month, day, hour, min, sec)| {
1487                NaiveDateTime::new(
1488                    chrono::NaiveDate::from_ymd_opt(year, month, day).unwrap(),
1489                    chrono::NaiveTime::from_hms_opt(hour, min, sec).unwrap(),
1490                )
1491                .and_utc()
1492            })
1493    }
1494
1495    fn valid_fingerprint() -> impl Strategy<Value = String> {
1496        proptest::collection::vec(
1497            proptest::char::range('0', '9').prop_union(proptest::char::range('A', 'F')),
1498            40..=40,
1499        )
1500        .prop_map(|chars| chars.into_iter().collect())
1501    }
1502
1503    fn simple_server_descriptor() -> impl Strategy<Value = ServerDescriptor> {
1504        (
1505            valid_nickname(),
1506            valid_ipv4(),
1507            valid_port(),
1508            valid_datetime(),
1509            valid_bandwidth(),
1510            valid_bandwidth(),
1511            valid_bandwidth(),
1512            proptest::option::of(valid_fingerprint()),
1513        )
1514            .prop_map(
1515                |(nickname, address, or_port, published, bw_avg, bw_burst, bw_obs, fingerprint)| {
1516                    let mut desc = ServerDescriptor::new(
1517                        nickname,
1518                        address,
1519                        or_port,
1520                        published,
1521                        "-----BEGIN SIGNATURE-----\ntest\n-----END SIGNATURE-----".to_string(),
1522                    );
1523                    desc.bandwidth_avg = bw_avg;
1524                    desc.bandwidth_burst = bw_burst;
1525                    desc.bandwidth_observed = bw_obs;
1526                    desc.fingerprint = fingerprint;
1527                    desc
1528                },
1529            )
1530    }
1531
1532    proptest! {
1533        #![proptest_config(ProptestConfig::with_cases(100))]
1534
1535        #[test]
1536        fn prop_server_descriptor_roundtrip(desc in simple_server_descriptor()) {
1537            let serialized = desc.to_descriptor_string();
1538            let parsed = ServerDescriptor::parse(&serialized);
1539
1540            prop_assert!(parsed.is_ok(), "Failed to parse serialized descriptor: {:?}", parsed.err());
1541
1542            let parsed = parsed.unwrap();
1543
1544            prop_assert_eq!(&desc.nickname, &parsed.nickname, "nickname mismatch");
1545            prop_assert_eq!(desc.address, parsed.address, "address mismatch");
1546            prop_assert_eq!(desc.or_port, parsed.or_port, "or_port mismatch");
1547            prop_assert_eq!(desc.socks_port, parsed.socks_port, "socks_port mismatch");
1548            prop_assert_eq!(desc.dir_port, parsed.dir_port, "dir_port mismatch");
1549
1550            prop_assert_eq!(desc.bandwidth_avg, parsed.bandwidth_avg, "bandwidth_avg mismatch");
1551            prop_assert_eq!(desc.bandwidth_burst, parsed.bandwidth_burst, "bandwidth_burst mismatch");
1552            prop_assert_eq!(desc.bandwidth_observed, parsed.bandwidth_observed, "bandwidth_observed mismatch");
1553
1554            prop_assert_eq!(desc.published, parsed.published, "published mismatch");
1555
1556            prop_assert_eq!(desc.fingerprint, parsed.fingerprint, "fingerprint mismatch");
1557        }
1558
1559        #[test]
1560        fn prop_valid_nickname_parsing(nickname in valid_nickname()) {
1561            let content = format!(
1562                "router {} 192.168.1.1 9001 0 0\npublished 2023-01-01 00:00:00\nbandwidth 1000 2000 500\nrouter-signature\n-----BEGIN SIGNATURE-----\ntest\n-----END SIGNATURE-----\n",
1563                nickname
1564            );
1565            let result = ServerDescriptor::parse(&content);
1566            prop_assert!(result.is_ok(), "Failed to parse descriptor with nickname '{}': {:?}", nickname, result.err());
1567            prop_assert_eq!(result.unwrap().nickname, nickname);
1568        }
1569
1570        #[test]
1571        fn prop_bandwidth_preserved(
1572            avg in valid_bandwidth(),
1573            burst in valid_bandwidth(),
1574            observed in valid_bandwidth()
1575        ) {
1576            let content = format!(
1577                "router TestRelay 192.168.1.1 9001 0 0\npublished 2023-01-01 00:00:00\nbandwidth {} {} {}\nrouter-signature\n-----BEGIN SIGNATURE-----\ntest\n-----END SIGNATURE-----\n",
1578                avg, burst, observed
1579            );
1580            let result = ServerDescriptor::parse(&content);
1581            prop_assert!(result.is_ok());
1582            let desc = result.unwrap();
1583            prop_assert_eq!(desc.bandwidth_avg, avg);
1584            prop_assert_eq!(desc.bandwidth_burst, burst);
1585            prop_assert_eq!(desc.bandwidth_observed, observed);
1586        }
1587
1588        #[test]
1589        fn prop_ports_preserved(
1590            or_port in valid_port(),
1591            socks_port in proptest::option::of(valid_port()),
1592            dir_port in proptest::option::of(valid_port())
1593        ) {
1594            let socks = socks_port.unwrap_or(0);
1595            let dir = dir_port.unwrap_or(0);
1596            let content = format!(
1597                "router TestRelay 192.168.1.1 {} {} {}\npublished 2023-01-01 00:00:00\nbandwidth 1000 2000 500\nrouter-signature\n-----BEGIN SIGNATURE-----\ntest\n-----END SIGNATURE-----\n",
1598                or_port, socks, dir
1599            );
1600            let result = ServerDescriptor::parse(&content);
1601            prop_assert!(result.is_ok());
1602            let desc = result.unwrap();
1603            prop_assert_eq!(desc.or_port, or_port);
1604            prop_assert_eq!(desc.socks_port, if socks == 0 { None } else { Some(socks) });
1605            prop_assert_eq!(desc.dir_port, if dir == 0 { None } else { Some(dir) });
1606        }
1607
1608        #[test]
1609        fn prop_fingerprint_format_preserved(fp in valid_fingerprint()) {
1610            let formatted: String = fp
1611                .chars()
1612                .collect::<Vec<_>>()
1613                .chunks(4)
1614                .map(|c| c.iter().collect::<String>())
1615                .collect::<Vec<_>>()
1616                .join(" ");
1617
1618            let content = format!(
1619                "router TestRelay 192.168.1.1 9001 0 0\npublished 2023-01-01 00:00:00\nfingerprint {}\nbandwidth 1000 2000 500\nrouter-signature\n-----BEGIN SIGNATURE-----\ntest\n-----END SIGNATURE-----\n",
1620                formatted
1621            );
1622            let result = ServerDescriptor::parse(&content);
1623            prop_assert!(result.is_ok());
1624            let desc = result.unwrap();
1625            prop_assert_eq!(desc.fingerprint, Some(fp));
1626        }
1627    }
1628
1629    #[test]
1630    fn test_edge_case_empty_family() {
1631        let content = r#"router TestRelay 192.168.1.1 9001 0 0
1632published 2023-01-01 00:00:00
1633bandwidth 1000 2000 500
1634family
1635router-signature
1636-----BEGIN SIGNATURE-----
1637test
1638-----END SIGNATURE-----
1639"#;
1640        let desc = ServerDescriptor::parse(content).unwrap();
1641        assert!(desc.family.is_empty());
1642    }
1643
1644    #[test]
1645    fn test_edge_case_zero_bandwidth() {
1646        let content = r#"router TestRelay 192.168.1.1 9001 0 0
1647published 2023-01-01 00:00:00
1648bandwidth 0 0 0
1649router-signature
1650-----BEGIN SIGNATURE-----
1651test
1652-----END SIGNATURE-----
1653"#;
1654        let desc = ServerDescriptor::parse(content).unwrap();
1655        assert_eq!(desc.bandwidth_avg, 0);
1656        assert_eq!(desc.bandwidth_burst, 0);
1657        assert_eq!(desc.bandwidth_observed, 0);
1658    }
1659
1660    #[test]
1661    fn test_edge_case_large_bandwidth() {
1662        let content = r#"router TestRelay 192.168.1.1 9001 0 0
1663published 2023-01-01 00:00:00
1664bandwidth 9223372036854775807 9223372036854775807 9223372036854775807
1665router-signature
1666-----BEGIN SIGNATURE-----
1667test
1668-----END SIGNATURE-----
1669"#;
1670        let desc = ServerDescriptor::parse(content).unwrap();
1671        assert_eq!(desc.bandwidth_avg, i64::MAX as u64);
1672        assert_eq!(desc.bandwidth_burst, i64::MAX as u64);
1673        assert_eq!(desc.bandwidth_observed, i64::MAX as u64);
1674    }
1675
1676    #[test]
1677    fn test_edge_case_zero_uptime() {
1678        let content = r#"router TestRelay 192.168.1.1 9001 0 0
1679published 2023-01-01 00:00:00
1680bandwidth 1000 2000 500
1681uptime 0
1682router-signature
1683-----BEGIN SIGNATURE-----
1684test
1685-----END SIGNATURE-----
1686"#;
1687        let desc = ServerDescriptor::parse(content).unwrap();
1688        assert_eq!(desc.uptime, Some(0));
1689    }
1690
1691    #[test]
1692    fn test_edge_case_large_uptime() {
1693        let content = r#"router TestRelay 192.168.1.1 9001 0 0
1694published 2023-01-01 00:00:00
1695bandwidth 1000 2000 500
1696uptime 4294967295
1697router-signature
1698-----BEGIN SIGNATURE-----
1699test
1700-----END SIGNATURE-----
1701"#;
1702        let desc = ServerDescriptor::parse(content).unwrap();
1703        assert_eq!(desc.uptime, Some(4294967295));
1704    }
1705
1706    #[test]
1707    fn test_edge_case_empty_contact() {
1708        let content = r#"router TestRelay 192.168.1.1 9001 0 0
1709published 2023-01-01 00:00:00
1710bandwidth 1000 2000 500
1711contact
1712router-signature
1713-----BEGIN SIGNATURE-----
1714test
1715-----END SIGNATURE-----
1716"#;
1717        let desc = ServerDescriptor::parse(content).unwrap();
1718        assert_eq!(desc.contact, Some(Vec::new()));
1719    }
1720
1721    #[test]
1722    fn test_edge_case_multiple_or_addresses() {
1723        let content = r#"router TestRelay 192.168.1.1 9001 0 0
1724published 2023-01-01 00:00:00
1725bandwidth 1000 2000 500
1726or-address 10.0.0.1:9002
1727or-address [2001:db8::1]:9003
1728or-address 172.16.0.1:9004
1729router-signature
1730-----BEGIN SIGNATURE-----
1731test
1732-----END SIGNATURE-----
1733"#;
1734        let desc = ServerDescriptor::parse(content).unwrap();
1735        assert_eq!(desc.or_addresses.len(), 3);
1736        assert_eq!(desc.or_addresses[0].0.to_string(), "10.0.0.1");
1737        assert_eq!(desc.or_addresses[0].1, 9002);
1738        assert_eq!(desc.or_addresses[1].0.to_string(), "2001:db8::1");
1739        assert_eq!(desc.or_addresses[1].1, 9003);
1740        assert_eq!(desc.or_addresses[2].0.to_string(), "172.16.0.1");
1741        assert_eq!(desc.or_addresses[2].1, 9004);
1742    }
1743
1744    #[test]
1745    fn test_edge_case_all_bridge_distributions() {
1746        for (dist_str, expected) in [
1747            ("https", BridgeDistribution::Https),
1748            ("email", BridgeDistribution::Email),
1749            ("moat", BridgeDistribution::Moat),
1750            ("hyphae", BridgeDistribution::Hyphae),
1751            ("any", BridgeDistribution::Any),
1752            ("unknown", BridgeDistribution::Any),
1753        ] {
1754            let content = format!(
1755                r#"router TestRelay 192.168.1.1 9001 0 0
1756published 2023-01-01 00:00:00
1757bandwidth 1000 2000 500
1758bridge-distribution-request {}
1759router-signature
1760-----BEGIN SIGNATURE-----
1761test
1762-----END SIGNATURE-----
1763"#,
1764                dist_str
1765            );
1766            let desc = ServerDescriptor::parse(&content).unwrap();
1767            assert_eq!(desc.bridge_distribution, expected);
1768        }
1769    }
1770
1771    #[test]
1772    fn test_edge_case_proto_with_ranges() {
1773        let content = r#"router TestRelay 192.168.1.1 9001 0 0
1774published 2023-01-01 00:00:00
1775bandwidth 1000 2000 500
1776proto Link=1-5 Circuit=1-2 Relay=1-3
1777router-signature
1778-----BEGIN SIGNATURE-----
1779test
1780-----END SIGNATURE-----
1781"#;
1782        let desc = ServerDescriptor::parse(content).unwrap();
1783        assert_eq!(desc.protocols.get("Link"), Some(&vec![1, 2, 3, 4, 5]));
1784        assert_eq!(desc.protocols.get("Circuit"), Some(&vec![1, 2]));
1785        assert_eq!(desc.protocols.get("Relay"), Some(&vec![1, 2, 3]));
1786    }
1787
1788    #[test]
1789    fn test_edge_case_proto_with_mixed_ranges() {
1790        let content = r#"router TestRelay 192.168.1.1 9001 0 0
1791published 2023-01-01 00:00:00
1792bandwidth 1000 2000 500
1793proto Link=1-2,4,6-8
1794router-signature
1795-----BEGIN SIGNATURE-----
1796test
1797-----END SIGNATURE-----
1798"#;
1799        let desc = ServerDescriptor::parse(content).unwrap();
1800        assert_eq!(desc.protocols.get("Link"), Some(&vec![1, 2, 4, 6, 7, 8]));
1801    }
1802
1803    #[test]
1804    fn test_edge_case_unrecognized_lines() {
1805        let content = r#"router TestRelay 192.168.1.1 9001 0 0
1806published 2023-01-01 00:00:00
1807bandwidth 1000 2000 500
1808unknown-keyword some value
1809another-unknown-line with multiple words
1810router-signature
1811-----BEGIN SIGNATURE-----
1812test
1813-----END SIGNATURE-----
1814"#;
1815        let desc = ServerDescriptor::parse(content).unwrap();
1816        assert_eq!(desc.unrecognized_lines.len(), 2);
1817        assert!(desc
1818            .unrecognized_lines
1819            .contains(&"unknown-keyword some value".to_string()));
1820        assert!(desc
1821            .unrecognized_lines
1822            .contains(&"another-unknown-line with multiple words".to_string()));
1823    }
1824
1825    #[test]
1826    fn test_roundtrip_with_all_fields() {
1827        let content = r#"router TestRelay 192.168.1.1 9001 9050 9030
1828platform Tor 0.4.7.10 on Linux x86_64
1829protocols Link 1 2 Circuit 1
1830published 2023-01-01 12:00:00
1831fingerprint AAAA BBBB CCCC DDDD EEEE FFFF 0000 1111 2222 3333
1832uptime 86400
1833bandwidth 1000000 2000000 500000
1834extra-info-digest ABC123 DEF456
1835family $1111111111111111111111111111111111111111 $2222222222222222222222222222222222222222
1836hidden-service-dir
1837contact admin@example.com
1838accept *:80
1839accept *:443
1840reject *:*
1841router-signature
1842-----BEGIN SIGNATURE-----
1843test
1844-----END SIGNATURE-----
1845"#;
1846        let desc = ServerDescriptor::parse(content).unwrap();
1847        let serialized = desc.to_descriptor_string();
1848        let reparsed = ServerDescriptor::parse(&serialized).unwrap();
1849
1850        assert_eq!(desc.nickname, reparsed.nickname);
1851        assert_eq!(desc.address, reparsed.address);
1852        assert_eq!(desc.or_port, reparsed.or_port);
1853        assert_eq!(desc.socks_port, reparsed.socks_port);
1854        assert_eq!(desc.dir_port, reparsed.dir_port);
1855        assert_eq!(desc.published, reparsed.published);
1856        assert_eq!(desc.fingerprint, reparsed.fingerprint);
1857        assert_eq!(desc.uptime, reparsed.uptime);
1858        assert_eq!(desc.bandwidth_avg, reparsed.bandwidth_avg);
1859        assert_eq!(desc.bandwidth_burst, reparsed.bandwidth_burst);
1860        assert_eq!(desc.bandwidth_observed, reparsed.bandwidth_observed);
1861        assert_eq!(desc.family, reparsed.family);
1862        assert_eq!(desc.is_hidden_service_dir, reparsed.is_hidden_service_dir);
1863    }
1864
1865    #[test]
1866    fn test_validation_invalid_nickname_empty() {
1867        let desc = ServerDescriptor::new(
1868            String::new(),
1869            "192.168.1.1".parse().unwrap(),
1870            9001,
1871            Utc::now(),
1872            "test".to_string(),
1873        );
1874        let result = desc.validate();
1875        assert!(result.is_err());
1876    }
1877
1878    #[test]
1879    fn test_validation_invalid_nickname_too_long() {
1880        let desc = ServerDescriptor::new(
1881            "ThisNicknameIsWayTooLongToBeValid".to_string(),
1882            "192.168.1.1".parse().unwrap(),
1883            9001,
1884            Utc::now(),
1885            "test".to_string(),
1886        );
1887        let result = desc.validate();
1888        assert!(result.is_err());
1889    }
1890
1891    #[test]
1892    fn test_validation_invalid_nickname_special_chars() {
1893        let desc = ServerDescriptor::new(
1894            "Invalid$Name".to_string(),
1895            "192.168.1.1".parse().unwrap(),
1896            9001,
1897            Utc::now(),
1898            "test".to_string(),
1899        );
1900        let result = desc.validate();
1901        assert!(result.is_err());
1902    }
1903
1904    #[test]
1905    fn test_validation_invalid_fingerprint() {
1906        let mut desc = ServerDescriptor::new(
1907            "TestRelay".to_string(),
1908            "192.168.1.1".parse().unwrap(),
1909            9001,
1910            Utc::now(),
1911            "test".to_string(),
1912        );
1913        desc.fingerprint = Some("INVALID".to_string());
1914        let result = desc.validate();
1915        assert!(result.is_err());
1916    }
1917
1918    #[test]
1919    fn test_validation_zero_or_port() {
1920        let desc = ServerDescriptor::new(
1921            "TestRelay".to_string(),
1922            "192.168.1.1".parse().unwrap(),
1923            0,
1924            Utc::now(),
1925            "test".to_string(),
1926        );
1927        let result = desc.validate();
1928        assert!(result.is_err());
1929    }
1930
1931    #[test]
1932    fn test_validation_burst_less_than_avg() {
1933        let mut desc = ServerDescriptor::new(
1934            "TestRelay".to_string(),
1935            "192.168.1.1".parse().unwrap(),
1936            9001,
1937            Utc::now(),
1938            "test".to_string(),
1939        );
1940        desc.bandwidth_avg = 2000;
1941        desc.bandwidth_burst = 1000;
1942        let result = desc.validate();
1943        assert!(result.is_err());
1944    }
1945
1946    #[test]
1947    fn test_validation_empty_signature() {
1948        let desc = ServerDescriptor::new(
1949            "TestRelay".to_string(),
1950            "192.168.1.1".parse().unwrap(),
1951            9001,
1952            Utc::now(),
1953            String::new(),
1954        );
1955        let result = desc.validate();
1956        assert!(result.is_err());
1957    }
1958
1959    #[test]
1960    fn test_validation_zero_port_in_or_addresses() {
1961        let mut desc = ServerDescriptor::new(
1962            "TestRelay".to_string(),
1963            "192.168.1.1".parse().unwrap(),
1964            9001,
1965            Utc::now(),
1966            "test".to_string(),
1967        );
1968        desc.or_addresses
1969            .push(("10.0.0.1".parse().unwrap(), 0, false));
1970        let result = desc.validate();
1971        assert!(result.is_err());
1972    }
1973
1974    #[test]
1975    fn test_validation_invalid_family_fingerprint() {
1976        let mut desc = ServerDescriptor::new(
1977            "TestRelay".to_string(),
1978            "192.168.1.1".parse().unwrap(),
1979            9001,
1980            Utc::now(),
1981            "test".to_string(),
1982        );
1983        desc.family.insert("$INVALID".to_string());
1984        let result = desc.validate();
1985        assert!(result.is_err());
1986    }
1987
1988    #[test]
1989    fn test_validation_valid_descriptor() {
1990        let mut desc = ServerDescriptor::new(
1991            "TestRelay".to_string(),
1992            "192.168.1.1".parse().unwrap(),
1993            9001,
1994            Utc::now(),
1995            "test".to_string(),
1996        );
1997        desc.fingerprint = Some("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_string());
1998        desc.bandwidth_avg = 1000;
1999        desc.bandwidth_burst = 2000;
2000        desc.bandwidth_observed = 500;
2001        desc.family
2002            .insert("$1111111111111111111111111111111111111111".to_string());
2003        let result = desc.validate();
2004        assert!(result.is_ok());
2005    }
2006
2007    #[test]
2008    fn test_builder_pattern() {
2009        let desc = ServerDescriptorBuilder::default()
2010            .nickname("TestRelay")
2011            .address("192.168.1.1".parse::<IpAddr>().unwrap())
2012            .or_port(9001_u16)
2013            .or_addresses(vec![])
2014            .published(Utc::now())
2015            .bandwidth_avg(1000_u64)
2016            .bandwidth_burst(2000_u64)
2017            .bandwidth_observed(500_u64)
2018            .exit_policy(ExitPolicy::new(Vec::new()))
2019            .bridge_distribution(BridgeDistribution::Any)
2020            .family(HashSet::new())
2021            .hibernating(false)
2022            .allow_single_hop_exits(false)
2023            .allow_tunneled_dir_requests(false)
2024            .extra_info_cache(false)
2025            .is_hidden_service_dir(false)
2026            .protocols(HashMap::new())
2027            .signature("test")
2028            .raw_content(vec![])
2029            .unrecognized_lines(vec![])
2030            .build()
2031            .expect("Failed to build");
2032
2033        assert_eq!(desc.nickname, "TestRelay");
2034        assert_eq!(desc.or_port, 9001);
2035        assert_eq!(desc.bandwidth_avg, 1000);
2036    }
2037
2038    #[test]
2039    fn test_builder_with_optional_fields() {
2040        let desc = ServerDescriptorBuilder::default()
2041            .nickname("TestRelay")
2042            .address("192.168.1.1".parse::<IpAddr>().unwrap())
2043            .or_port(9001_u16)
2044            .or_addresses(vec![])
2045            .published(Utc::now())
2046            .bandwidth_avg(1000_u64)
2047            .bandwidth_burst(2000_u64)
2048            .bandwidth_observed(500_u64)
2049            .exit_policy(ExitPolicy::new(Vec::new()))
2050            .bridge_distribution(BridgeDistribution::Any)
2051            .family(HashSet::new())
2052            .hibernating(false)
2053            .allow_single_hop_exits(false)
2054            .allow_tunneled_dir_requests(false)
2055            .extra_info_cache(false)
2056            .is_hidden_service_dir(false)
2057            .protocols(HashMap::new())
2058            .signature("test")
2059            .raw_content(vec![])
2060            .unrecognized_lines(vec![])
2061            .fingerprint("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
2062            .dir_port(9030_u16)
2063            .uptime(86400_u64)
2064            .build()
2065            .expect("Failed to build");
2066
2067        assert_eq!(
2068            desc.fingerprint,
2069            Some("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_string())
2070        );
2071        assert_eq!(desc.dir_port, Some(9030));
2072        assert_eq!(desc.uptime, Some(86400));
2073    }
2074
2075    #[test]
2076    fn test_display_implementation() {
2077        let desc = ServerDescriptor::new(
2078            "TestRelay".to_string(),
2079            "192.168.1.1".parse().unwrap(),
2080            9001,
2081            Utc::now(),
2082            "test".to_string(),
2083        );
2084        let display_str = format!("{}", desc);
2085        assert!(display_str.contains("router TestRelay 192.168.1.1 9001"));
2086        assert!(display_str.contains("router-signature"));
2087    }
2088
2089    #[test]
2090    fn test_from_str_implementation() {
2091        let content = r#"router TestRelay 192.168.1.1 9001 0 0
2092published 2023-01-01 00:00:00
2093bandwidth 1000 2000 500
2094router-signature
2095-----BEGIN SIGNATURE-----
2096test
2097-----END SIGNATURE-----
2098"#;
2099        let desc: ServerDescriptor = content.parse().unwrap();
2100        assert_eq!(desc.nickname, "TestRelay");
2101        assert_eq!(desc.or_port, 9001);
2102    }
2103}