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};
103
104use crate::exit_policy::ExitPolicy;
105use crate::version::Version;
106use crate::{BridgeDistribution, Error};
107
108use super::{compute_digest, Descriptor, DigestEncoding, DigestHash};
109
110type RouterLineResult = (String, IpAddr, u16, Option<u16>, Option<u16>);
111
112/// A server descriptor containing metadata about a Tor relay.
113///
114/// Server descriptors are the primary documents that relays publish to
115/// describe themselves. They contain identity information, network addresses,
116/// bandwidth capabilities, exit policies, and cryptographic keys needed
117/// for circuit construction.
118///
119/// # Fields Overview
120///
121/// | Category | Fields |
122/// |----------|--------|
123/// | Identity | `nickname`, `fingerprint`, `contact` |
124/// | Network | `address`, `or_port`, `dir_port`, `or_addresses` |
125/// | Bandwidth | `bandwidth_avg`, `bandwidth_burst`, `bandwidth_observed` |
126/// | Policy | `exit_policy`, `exit_policy_v6` |
127/// | Keys | `onion_key`, `signing_key`, `ntor_onion_key`, Ed25519 keys |
128/// | Protocols | `protocols`, `link_protocols`, `circuit_protocols` |
129/// | Metadata | `platform`, `tor_version`, `published`, `uptime` |
130///
131/// # Invariants
132///
133/// - `nickname` is 1-19 alphanumeric characters
134/// - `fingerprint` is 40 uppercase hex characters (if present)
135/// - `or_port` is non-zero
136/// - `published` is a valid UTC timestamp
137/// - `signature` is always present and non-empty
138///
139/// # Example
140///
141/// ```rust,no_run
142/// use stem_rs::descriptor::{ServerDescriptor, Descriptor};
143///
144/// let content = std::fs::read_to_string("server-descriptor").unwrap();
145/// let desc = ServerDescriptor::parse(&content).unwrap();
146///
147/// println!("Nickname: {}", desc.nickname);
148/// println!("Address: {}:{}", desc.address, desc.or_port);
149/// println!("Published: {}", desc.published);
150/// println!("Bandwidth: {} B/s avg, {} B/s observed",
151///          desc.bandwidth_avg, desc.bandwidth_observed);
152///
153/// if let Some(ref fp) = desc.fingerprint {
154///     println!("Fingerprint: {}", fp);
155/// }
156///
157/// if let Some(ref version) = desc.tor_version {
158///     println!("Tor version: {}", version);
159/// }
160/// ```
161///
162/// # Thread Safety
163///
164/// `ServerDescriptor` is `Send` and `Sync` as it contains only owned data.
165#[derive(Debug, Clone, PartialEq)]
166pub struct ServerDescriptor {
167    /// The relay's nickname (1-19 alphanumeric characters).
168    pub nickname: String,
169    /// The relay's fingerprint (40 hex characters), derived from identity key.
170    pub fingerprint: Option<String>,
171    /// The relay's primary IPv4 address.
172    pub address: IpAddr,
173    /// The relay's onion routing port (always non-zero).
174    pub or_port: u16,
175    /// The relay's SOCKS port (deprecated, usually None).
176    pub socks_port: Option<u16>,
177    /// The relay's directory port for serving cached descriptors.
178    pub dir_port: Option<u16>,
179    /// Additional addresses (IPv4 or IPv6) the relay listens on.
180    /// Each tuple is (address, port, is_ipv6).
181    pub or_addresses: Vec<(IpAddr, u16, bool)>,
182    /// Raw platform string (e.g., "Tor 0.4.7.10 on Linux").
183    pub platform: Option<Vec<u8>>,
184    /// Parsed Tor version from the platform string.
185    pub tor_version: Option<Version>,
186    /// Operating system from the platform string.
187    pub operating_system: Option<String>,
188    /// When this descriptor was published (UTC).
189    pub published: DateTime<Utc>,
190    /// Seconds the relay has been running.
191    pub uptime: Option<u64>,
192    /// Contact information for the relay operator.
193    pub contact: Option<Vec<u8>>,
194    /// Supported link protocol versions (legacy).
195    pub link_protocols: Option<Vec<String>>,
196    /// Supported circuit protocol versions (legacy).
197    pub circuit_protocols: Option<Vec<String>>,
198    /// Average bandwidth in bytes per second the relay is willing to sustain.
199    pub bandwidth_avg: u64,
200    /// Maximum bandwidth in bytes per second for short bursts.
201    pub bandwidth_burst: u64,
202    /// Bandwidth in bytes per second the relay has actually observed.
203    pub bandwidth_observed: u64,
204    /// The relay's exit policy (rules for what traffic it will exit).
205    pub exit_policy: ExitPolicy,
206    /// IPv6 exit policy summary (e.g., "accept 80,443" or "reject 1-65535").
207    pub exit_policy_v6: Option<String>,
208    /// How this bridge wants to be distributed (bridges only).
209    pub bridge_distribution: BridgeDistribution,
210    /// Fingerprints of related relays (same operator).
211    pub family: HashSet<String>,
212    /// Whether the relay is currently hibernating (reduced service).
213    pub hibernating: bool,
214    /// Whether the relay allows single-hop exits (security risk).
215    pub allow_single_hop_exits: bool,
216    /// Whether the relay accepts tunneled directory requests.
217    pub allow_tunneled_dir_requests: bool,
218    /// Whether the relay caches extra-info descriptors.
219    pub extra_info_cache: bool,
220    /// SHA-1 digest of the relay's extra-info descriptor.
221    pub extra_info_digest: Option<String>,
222    /// SHA-256 digest of the relay's extra-info descriptor.
223    pub extra_info_sha256_digest: Option<String>,
224    /// Whether the relay serves as a hidden service directory.
225    pub is_hidden_service_dir: bool,
226    /// Supported protocol versions (modern format).
227    /// Maps protocol name to list of supported versions.
228    pub protocols: HashMap<String, Vec<u32>>,
229    /// RSA onion key for circuit creation (PEM format).
230    pub onion_key: Option<String>,
231    /// Cross-certification of onion key by identity key.
232    pub onion_key_crosscert: Option<String>,
233    /// Curve25519 onion key for ntor handshake (base64).
234    pub ntor_onion_key: Option<String>,
235    /// Cross-certification of ntor key.
236    pub ntor_onion_key_crosscert: Option<String>,
237    /// Sign bit for ntor key cross-certification.
238    pub ntor_onion_key_crosscert_sign: Option<String>,
239    /// RSA signing key (PEM format).
240    pub signing_key: Option<String>,
241    /// Ed25519 identity certificate (PEM format).
242    pub ed25519_certificate: Option<String>,
243    /// Ed25519 master key (base64).
244    pub ed25519_master_key: Option<String>,
245    /// Ed25519 signature over the descriptor.
246    pub ed25519_signature: Option<String>,
247    /// RSA signature over the descriptor (PEM format).
248    pub signature: String,
249    /// Raw bytes of the original descriptor content.
250    raw_content: Vec<u8>,
251    /// Lines that were not recognized during parsing.
252    unrecognized_lines: Vec<String>,
253}
254
255impl ServerDescriptor {
256    /// Creates a new server descriptor with minimal required fields.
257    ///
258    /// This creates a descriptor with default values for optional fields.
259    /// Use this for testing or when constructing descriptors programmatically.
260    ///
261    /// # Arguments
262    ///
263    /// * `nickname` - The relay's nickname (1-19 alphanumeric characters)
264    /// * `address` - The relay's primary IP address
265    /// * `or_port` - The relay's onion routing port
266    /// * `published` - When this descriptor was published
267    /// * `signature` - The RSA signature (PEM format)
268    ///
269    /// # Example
270    ///
271    /// ```rust
272    /// use stem_rs::descriptor::ServerDescriptor;
273    /// use chrono::Utc;
274    /// use std::net::IpAddr;
275    ///
276    /// let desc = ServerDescriptor::new(
277    ///     "MyRelay".to_string(),
278    ///     "192.168.1.1".parse().unwrap(),
279    ///     9001,
280    ///     Utc::now(),
281    ///     "-----BEGIN SIGNATURE-----\ntest\n-----END SIGNATURE-----".to_string(),
282    /// );
283    ///
284    /// assert_eq!(desc.nickname, "MyRelay");
285    /// assert_eq!(desc.or_port, 9001);
286    /// ```
287    pub fn new(
288        nickname: String,
289        address: IpAddr,
290        or_port: u16,
291        published: DateTime<Utc>,
292        signature: String,
293    ) -> Self {
294        Self {
295            nickname,
296            fingerprint: None,
297            address,
298            or_port,
299            socks_port: None,
300            dir_port: None,
301            or_addresses: Vec::new(),
302            platform: None,
303            tor_version: None,
304            operating_system: None,
305            published,
306            uptime: None,
307            contact: None,
308            link_protocols: None,
309            circuit_protocols: None,
310            bandwidth_avg: 0,
311            bandwidth_burst: 0,
312            bandwidth_observed: 0,
313            exit_policy: ExitPolicy::new(Vec::new()),
314            exit_policy_v6: None,
315            bridge_distribution: BridgeDistribution::Any,
316            family: HashSet::new(),
317            hibernating: false,
318            allow_single_hop_exits: false,
319            allow_tunneled_dir_requests: false,
320            extra_info_cache: false,
321            extra_info_digest: None,
322            extra_info_sha256_digest: None,
323            is_hidden_service_dir: false,
324            protocols: HashMap::new(),
325            onion_key: None,
326            onion_key_crosscert: None,
327            ntor_onion_key: None,
328            ntor_onion_key_crosscert: None,
329            ntor_onion_key_crosscert_sign: None,
330            signing_key: None,
331            ed25519_certificate: None,
332            ed25519_master_key: None,
333            ed25519_signature: None,
334            signature,
335            raw_content: Vec::new(),
336            unrecognized_lines: Vec::new(),
337        }
338    }
339
340    /// Parses the `router` line of a server descriptor.
341    ///
342    /// Format: `router <nickname> <address> <ORPort> <SOCKSPort> <DirPort>`
343    fn parse_router_line(line: &str) -> Result<RouterLineResult, Error> {
344        let parts: Vec<&str> = line.split_whitespace().collect();
345        if parts.len() < 5 {
346            return Err(Error::Parse {
347                location: "router".to_string(),
348                reason: "router line requires 5 fields".to_string(),
349            });
350        }
351
352        let nickname = parts[0].to_string();
353        if !is_valid_nickname(&nickname) {
354            return Err(Error::Parse {
355                location: "router".to_string(),
356                reason: format!("invalid nickname: {}", nickname),
357            });
358        }
359
360        let address: IpAddr = parts[1].parse().map_err(|_| Error::Parse {
361            location: "router".to_string(),
362            reason: format!("invalid address: {}", parts[1]),
363        })?;
364
365        let or_port: u16 = parts[2].parse().map_err(|_| Error::Parse {
366            location: "router".to_string(),
367            reason: format!("invalid or_port: {}", parts[2]),
368        })?;
369
370        let socks_port: Option<u16> = {
371            let port: u16 = parts[3].parse().map_err(|_| Error::Parse {
372                location: "router".to_string(),
373                reason: format!("invalid socks_port: {}", parts[3]),
374            })?;
375            if port == 0 {
376                None
377            } else {
378                Some(port)
379            }
380        };
381
382        let dir_port: Option<u16> = {
383            let port: u16 = parts[4].parse().map_err(|_| Error::Parse {
384                location: "router".to_string(),
385                reason: format!("invalid dir_port: {}", parts[4]),
386            })?;
387            if port == 0 {
388                None
389            } else {
390                Some(port)
391            }
392        };
393
394        Ok((nickname, address, or_port, socks_port, dir_port))
395    }
396
397    fn parse_bandwidth_line(line: &str) -> Result<(u64, u64, u64), Error> {
398        let parts: Vec<&str> = line.split_whitespace().collect();
399        if parts.len() < 3 {
400            return Err(Error::Parse {
401                location: "bandwidth".to_string(),
402                reason: "bandwidth line requires 3 values".to_string(),
403            });
404        }
405
406        let avg: u64 = parts[0].parse().map_err(|_| Error::Parse {
407            location: "bandwidth".to_string(),
408            reason: format!("invalid average bandwidth: {}", parts[0]),
409        })?;
410
411        let burst: u64 = parts[1].parse().map_err(|_| Error::Parse {
412            location: "bandwidth".to_string(),
413            reason: format!("invalid burst bandwidth: {}", parts[1]),
414        })?;
415
416        let observed: u64 = parts[2].parse().map_err(|_| Error::Parse {
417            location: "bandwidth".to_string(),
418            reason: format!("invalid observed bandwidth: {}", parts[2]),
419        })?;
420
421        Ok((avg, burst, observed))
422    }
423
424    fn parse_published_line(line: &str) -> Result<DateTime<Utc>, Error> {
425        let datetime =
426            NaiveDateTime::parse_from_str(line.trim(), "%Y-%m-%d %H:%M:%S").map_err(|e| {
427                Error::Parse {
428                    location: "published".to_string(),
429                    reason: format!("invalid datetime: {} - {}", line, e),
430                }
431            })?;
432        Ok(datetime.and_utc())
433    }
434
435    fn parse_fingerprint_line(line: &str) -> String {
436        line.replace(' ', "")
437    }
438
439    fn parse_platform_line(line: &str) -> (Option<Vec<u8>>, Option<Version>, Option<String>) {
440        let platform = line.as_bytes().to_vec();
441        let mut tor_version = None;
442        let mut operating_system = None;
443
444        if let Some(on_pos) = line.find(" on ") {
445            let version_part = &line[..on_pos];
446            operating_system = Some(line[on_pos + 4..].to_string());
447
448            if let Some(ver_start) = version_part.find(char::is_whitespace) {
449                let ver_str = version_part[ver_start..].trim();
450                if let Ok(v) = Version::parse(ver_str) {
451                    tor_version = Some(v);
452                }
453            }
454        }
455
456        (Some(platform), tor_version, operating_system)
457    }
458
459    fn parse_protocols_line(line: &str) -> (Option<Vec<String>>, Option<Vec<String>>) {
460        let mut link_protocols = None;
461        let mut circuit_protocols = None;
462        let parts: Vec<&str> = line.split_whitespace().collect();
463        let mut i = 0;
464        while i < parts.len() {
465            if parts[i] == "Link" {
466                let mut protos = Vec::new();
467                i += 1;
468                while i < parts.len() && parts[i] != "Circuit" {
469                    protos.push(parts[i].to_string());
470                    i += 1;
471                }
472                link_protocols = Some(protos);
473            } else if parts[i] == "Circuit" {
474                let mut protos = Vec::new();
475                i += 1;
476                while i < parts.len() && parts[i] != "Link" {
477                    protos.push(parts[i].to_string());
478                    i += 1;
479                }
480                circuit_protocols = Some(protos);
481            } else {
482                i += 1;
483            }
484        }
485        (link_protocols, circuit_protocols)
486    }
487
488    fn parse_family_line(line: &str) -> HashSet<String> {
489        line.split_whitespace().map(|s| s.to_string()).collect()
490    }
491
492    fn parse_or_address(line: &str) -> Result<(IpAddr, u16, bool), Error> {
493        let line = line.trim();
494        if line.starts_with('[') {
495            if let Some(bracket_end) = line.find(']') {
496                let ipv6_str = &line[1..bracket_end];
497                let port_str = &line[bracket_end + 2..];
498                let addr: IpAddr = ipv6_str.parse().map_err(|_| Error::Parse {
499                    location: "or-address".to_string(),
500                    reason: format!("invalid IPv6 address: {}", ipv6_str),
501                })?;
502                let port: u16 = port_str.parse().map_err(|_| Error::Parse {
503                    location: "or-address".to_string(),
504                    reason: format!("invalid port: {}", port_str),
505                })?;
506                return Ok((addr, port, true));
507            }
508        }
509
510        if let Some(colon_pos) = line.rfind(':') {
511            let addr_str = &line[..colon_pos];
512            let port_str = &line[colon_pos + 1..];
513            let addr: IpAddr = addr_str.parse().map_err(|_| Error::Parse {
514                location: "or-address".to_string(),
515                reason: format!("invalid address: {}", addr_str),
516            })?;
517            let port: u16 = port_str.parse().map_err(|_| Error::Parse {
518                location: "or-address".to_string(),
519                reason: format!("invalid port: {}", port_str),
520            })?;
521            let is_ipv6 = addr.is_ipv6();
522            return Ok((addr, port, is_ipv6));
523        }
524
525        Err(Error::Parse {
526            location: "or-address".to_string(),
527            reason: format!("invalid or-address format: {}", line),
528        })
529    }
530
531    fn parse_extra_info_digest(line: &str) -> (Option<String>, Option<String>) {
532        let parts: Vec<&str> = line.split_whitespace().collect();
533        let sha1_digest = parts.first().map(|s| s.to_string());
534        let sha256_digest = parts.get(1).map(|s| s.to_string());
535        (sha1_digest, sha256_digest)
536    }
537
538    fn extract_pem_block(lines: &[&str], start_idx: usize) -> (String, usize) {
539        let mut block = String::new();
540        let mut idx = start_idx;
541        while idx < lines.len() {
542            let line = lines[idx];
543            block.push_str(line);
544            block.push('\n');
545            if line.starts_with("-----END ") {
546                break;
547            }
548            idx += 1;
549        }
550        (block.trim_end().to_string(), idx)
551    }
552
553    /// Finds the content range used for digest computation.
554    ///
555    /// The digest is computed from `router ` through `router-signature\n`.
556    fn find_digest_content(content: &str) -> Option<&str> {
557        let start_marker = "router ";
558        let end_marker = "\nrouter-signature\n";
559        let start = content.find(start_marker)?;
560        let end = content.find(end_marker)?;
561        Some(&content[start..end + end_marker.len()])
562    }
563}
564
565/// Validates a relay nickname.
566///
567/// A valid nickname is 1-19 characters, all alphanumeric (a-z, A-Z, 0-9).
568///
569/// # Arguments
570///
571/// * `nickname` - The nickname to validate
572///
573/// # Returns
574///
575/// `true` if the nickname is valid, `false` otherwise.
576fn is_valid_nickname(nickname: &str) -> bool {
577    if nickname.is_empty() || nickname.len() > 19 {
578        return false;
579    }
580    nickname.chars().all(|c| c.is_ascii_alphanumeric())
581}
582
583impl Descriptor for ServerDescriptor {
584    fn parse(content: &str) -> Result<Self, Error> {
585        let raw_content = content.as_bytes().to_vec();
586        let lines: Vec<&str> = content.lines().collect();
587
588        let mut nickname = String::new();
589        let mut address: Option<IpAddr> = None;
590        let mut or_port: u16 = 0;
591        let mut socks_port: Option<u16> = None;
592        let mut dir_port: Option<u16> = None;
593        let mut fingerprint: Option<String> = None;
594        let mut or_addresses: Vec<(IpAddr, u16, bool)> = Vec::new();
595        let mut platform: Option<Vec<u8>> = None;
596        let mut tor_version: Option<Version> = None;
597        let mut operating_system: Option<String> = None;
598        let mut published: Option<DateTime<Utc>> = None;
599        let mut uptime: Option<u64> = None;
600        let mut contact: Option<Vec<u8>> = None;
601        let mut link_protocols: Option<Vec<String>> = None;
602        let mut circuit_protocols: Option<Vec<String>> = None;
603        let mut bandwidth_avg: u64 = 0;
604        let mut bandwidth_burst: u64 = 0;
605        let mut bandwidth_observed: u64 = 0;
606        let mut exit_policy_rules: Vec<String> = Vec::new();
607        let mut exit_policy_v6: Option<String> = None;
608        let mut bridge_distribution = BridgeDistribution::Any;
609        let mut family: HashSet<String> = HashSet::new();
610        let mut hibernating = false;
611        let mut allow_single_hop_exits = false;
612        let mut allow_tunneled_dir_requests = false;
613        let mut extra_info_cache = false;
614        let mut extra_info_digest: Option<String> = None;
615        let mut extra_info_sha256_digest: Option<String> = None;
616        let mut is_hidden_service_dir = false;
617        let mut protocols: HashMap<String, Vec<u32>> = HashMap::new();
618        let mut onion_key: Option<String> = None;
619        let mut onion_key_crosscert: Option<String> = None;
620        let mut ntor_onion_key: Option<String> = None;
621        let mut ntor_onion_key_crosscert: Option<String> = None;
622        let mut ntor_onion_key_crosscert_sign: Option<String> = None;
623        let mut signing_key: Option<String> = None;
624        let mut ed25519_certificate: Option<String> = None;
625        let mut ed25519_master_key: Option<String> = None;
626        let mut ed25519_signature: Option<String> = None;
627        let mut signature = String::new();
628        let mut unrecognized_lines: Vec<String> = Vec::new();
629
630        let mut idx = 0;
631        while idx < lines.len() {
632            let line = lines[idx];
633
634            if line.starts_with("@type ") {
635                idx += 1;
636                continue;
637            }
638
639            let line = line.strip_prefix("opt ").unwrap_or(line);
640
641            let (keyword, value) = if let Some(space_pos) = line.find(' ') {
642                (&line[..space_pos], line[space_pos + 1..].trim())
643            } else {
644                (line, "")
645            };
646
647            match keyword {
648                "router" => {
649                    let (n, a, op, sp, dp) = Self::parse_router_line(value)?;
650                    nickname = n;
651                    address = Some(a);
652                    or_port = op;
653                    socks_port = sp;
654                    dir_port = dp;
655                }
656                "identity-ed25519" => {
657                    let (block, end_idx) = Self::extract_pem_block(&lines, idx + 1);
658                    ed25519_certificate = Some(block);
659                    idx = end_idx;
660                }
661                "master-key-ed25519" => {
662                    ed25519_master_key = Some(value.to_string());
663                }
664                "bandwidth" => {
665                    let (avg, burst, obs) = Self::parse_bandwidth_line(value)?;
666                    bandwidth_avg = avg;
667                    bandwidth_burst = burst;
668                    bandwidth_observed = obs;
669                }
670                "platform" => {
671                    let (p, v, os) = Self::parse_platform_line(value);
672                    platform = p;
673                    tor_version = v;
674                    operating_system = os;
675                }
676                "published" => {
677                    published = Some(Self::parse_published_line(value)?);
678                }
679                "fingerprint" => {
680                    fingerprint = Some(Self::parse_fingerprint_line(value));
681                }
682                "uptime" => {
683                    uptime = value.parse().ok();
684                }
685                "contact" => {
686                    contact = Some(value.as_bytes().to_vec());
687                }
688                "protocols" => {
689                    let (lp, cp) = Self::parse_protocols_line(value);
690                    link_protocols = lp;
691                    circuit_protocols = cp;
692                }
693                "proto" => {
694                    for entry in value.split_whitespace() {
695                        if let Some(eq_pos) = entry.find('=') {
696                            let proto_name = &entry[..eq_pos];
697                            let versions_str = &entry[eq_pos + 1..];
698                            let versions: Vec<u32> = versions_str
699                                .split(',')
700                                .filter_map(|v| {
701                                    if let Some(dash) = v.find('-') {
702                                        let start: u32 = v[..dash].parse().ok()?;
703                                        let end: u32 = v[dash + 1..].parse().ok()?;
704                                        Some((start..=end).collect::<Vec<_>>())
705                                    } else {
706                                        v.parse().ok().map(|n| vec![n])
707                                    }
708                                })
709                                .flatten()
710                                .collect();
711                            protocols.insert(proto_name.to_string(), versions);
712                        }
713                    }
714                }
715                "family" => {
716                    family = Self::parse_family_line(value);
717                }
718                "or-address" => {
719                    if let Ok(addr) = Self::parse_or_address(value) {
720                        or_addresses.push(addr);
721                    }
722                }
723                "extra-info-digest" => {
724                    let (sha1, sha256) = Self::parse_extra_info_digest(value);
725                    extra_info_digest = sha1;
726                    extra_info_sha256_digest = sha256;
727                }
728                "hidden-service-dir" => {
729                    is_hidden_service_dir = true;
730                }
731                "caches-extra-info" => {
732                    extra_info_cache = true;
733                }
734                "hibernating" => {
735                    hibernating = value == "1";
736                }
737                "allow-single-hop-exits" => {
738                    allow_single_hop_exits = true;
739                }
740                "tunnelled-dir-server" => {
741                    allow_tunneled_dir_requests = true;
742                }
743                "bridge-distribution-request" => {
744                    bridge_distribution = match value.to_lowercase().as_str() {
745                        "https" => BridgeDistribution::Https,
746                        "email" => BridgeDistribution::Email,
747                        "moat" => BridgeDistribution::Moat,
748                        "hyphae" => BridgeDistribution::Hyphae,
749                        _ => BridgeDistribution::Any,
750                    };
751                }
752                "accept" | "reject" => {
753                    exit_policy_rules.push(line.to_string());
754                }
755                "ipv6-policy" => {
756                    exit_policy_v6 = Some(value.to_string());
757                }
758                "onion-key" => {
759                    let (block, end_idx) = Self::extract_pem_block(&lines, idx + 1);
760                    onion_key = Some(block);
761                    idx = end_idx;
762                }
763                "onion-key-crosscert" => {
764                    let (block, end_idx) = Self::extract_pem_block(&lines, idx + 1);
765                    onion_key_crosscert = Some(block);
766                    idx = end_idx;
767                }
768                "ntor-onion-key" => {
769                    ntor_onion_key = Some(value.to_string());
770                }
771                "ntor-onion-key-crosscert" => {
772                    ntor_onion_key_crosscert_sign = Some(value.to_string());
773                    let (block, end_idx) = Self::extract_pem_block(&lines, idx + 1);
774                    ntor_onion_key_crosscert = Some(block);
775                    idx = end_idx;
776                }
777                "signing-key" => {
778                    let (block, end_idx) = Self::extract_pem_block(&lines, idx + 1);
779                    signing_key = Some(block);
780                    idx = end_idx;
781                }
782                "router-sig-ed25519" => {
783                    ed25519_signature = Some(value.to_string());
784                }
785                "router-signature" => {
786                    let (block, end_idx) = Self::extract_pem_block(&lines, idx + 1);
787                    signature = block;
788                    idx = end_idx;
789                }
790                _ => {
791                    if !line.is_empty() && !line.starts_with("-----") {
792                        unrecognized_lines.push(line.to_string());
793                    }
794                }
795            }
796            idx += 1;
797        }
798
799        let address = address.ok_or_else(|| Error::Parse {
800            location: "router".to_string(),
801            reason: "missing router line".to_string(),
802        })?;
803
804        let published = published.ok_or_else(|| Error::Parse {
805            location: "published".to_string(),
806            reason: "missing published line".to_string(),
807        })?;
808
809        let exit_policy = if exit_policy_rules.is_empty() {
810            ExitPolicy::new(Vec::new())
811        } else {
812            ExitPolicy::from_rules(&exit_policy_rules)?
813        };
814
815        Ok(Self {
816            nickname,
817            fingerprint,
818            address,
819            or_port,
820            socks_port,
821            dir_port,
822            or_addresses,
823            platform,
824            tor_version,
825            operating_system,
826            published,
827            uptime,
828            contact,
829            link_protocols,
830            circuit_protocols,
831            bandwidth_avg,
832            bandwidth_burst,
833            bandwidth_observed,
834            exit_policy,
835            exit_policy_v6,
836            bridge_distribution,
837            family,
838            hibernating,
839            allow_single_hop_exits,
840            allow_tunneled_dir_requests,
841            extra_info_cache,
842            extra_info_digest,
843            extra_info_sha256_digest,
844            is_hidden_service_dir,
845            protocols,
846            onion_key,
847            onion_key_crosscert,
848            ntor_onion_key,
849            ntor_onion_key_crosscert,
850            ntor_onion_key_crosscert_sign,
851            signing_key,
852            ed25519_certificate,
853            ed25519_master_key,
854            ed25519_signature,
855            signature,
856            raw_content,
857            unrecognized_lines,
858        })
859    }
860
861    fn to_descriptor_string(&self) -> String {
862        let mut result = String::new();
863
864        result.push_str(&format!(
865            "router {} {} {} {} {}\n",
866            self.nickname,
867            self.address,
868            self.or_port,
869            self.socks_port.unwrap_or(0),
870            self.dir_port.unwrap_or(0)
871        ));
872
873        if let Some(ref platform) = self.platform {
874            if let Ok(s) = std::str::from_utf8(platform) {
875                result.push_str(&format!("platform {}\n", s));
876            }
877        }
878
879        if let Some(ref link) = self.link_protocols {
880            if let Some(ref circuit) = self.circuit_protocols {
881                result.push_str(&format!(
882                    "protocols Link {} Circuit {}\n",
883                    link.join(" "),
884                    circuit.join(" ")
885                ));
886            }
887        }
888
889        result.push_str(&format!(
890            "published {}\n",
891            self.published.format("%Y-%m-%d %H:%M:%S")
892        ));
893
894        if let Some(ref fp) = self.fingerprint {
895            let formatted: String = fp
896                .chars()
897                .collect::<Vec<_>>()
898                .chunks(4)
899                .map(|c| c.iter().collect::<String>())
900                .collect::<Vec<_>>()
901                .join(" ");
902            result.push_str(&format!("fingerprint {}\n", formatted));
903        }
904
905        if let Some(uptime) = self.uptime {
906            result.push_str(&format!("uptime {}\n", uptime));
907        }
908
909        result.push_str(&format!(
910            "bandwidth {} {} {}\n",
911            self.bandwidth_avg, self.bandwidth_burst, self.bandwidth_observed
912        ));
913
914        if let Some(ref digest) = self.extra_info_digest {
915            if let Some(ref sha256) = self.extra_info_sha256_digest {
916                result.push_str(&format!("extra-info-digest {} {}\n", digest, sha256));
917            } else {
918                result.push_str(&format!("extra-info-digest {}\n", digest));
919            }
920        }
921
922        if let Some(ref key) = self.onion_key {
923            result.push_str("onion-key\n");
924            result.push_str(key);
925            result.push('\n');
926        }
927
928        if let Some(ref key) = self.signing_key {
929            result.push_str("signing-key\n");
930            result.push_str(key);
931            result.push('\n');
932        }
933
934        if !self.family.is_empty() {
935            let family_str: Vec<&str> = self.family.iter().map(|s| s.as_str()).collect();
936            result.push_str(&format!("family {}\n", family_str.join(" ")));
937        }
938
939        if self.is_hidden_service_dir {
940            result.push_str("hidden-service-dir\n");
941        }
942
943        if let Some(ref contact) = self.contact {
944            if let Ok(s) = std::str::from_utf8(contact) {
945                result.push_str(&format!("contact {}\n", s));
946            }
947        }
948
949        for rule in self.exit_policy.iter() {
950            result.push_str(&format!("{}\n", rule));
951        }
952
953        result.push_str("router-signature\n");
954        result.push_str(&self.signature);
955        result.push('\n');
956
957        result
958    }
959
960    fn digest(&self, hash: DigestHash, encoding: DigestEncoding) -> Result<String, Error> {
961        let content_str = std::str::from_utf8(&self.raw_content).map_err(|_| Error::Parse {
962            location: "digest".to_string(),
963            reason: "invalid UTF-8 in raw content".to_string(),
964        })?;
965
966        let digest_content =
967            Self::find_digest_content(content_str).ok_or_else(|| Error::Parse {
968                location: "digest".to_string(),
969                reason: "could not find digest content boundaries".to_string(),
970            })?;
971
972        Ok(compute_digest(digest_content.as_bytes(), hash, encoding))
973    }
974
975    fn raw_content(&self) -> &[u8] {
976        &self.raw_content
977    }
978
979    fn unrecognized_lines(&self) -> &[String] {
980        &self.unrecognized_lines
981    }
982}
983
984impl FromStr for ServerDescriptor {
985    type Err = Error;
986
987    fn from_str(s: &str) -> Result<Self, Self::Err> {
988        Self::parse(s)
989    }
990}
991
992impl fmt::Display for ServerDescriptor {
993    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
994        write!(f, "{}", self.to_descriptor_string())
995    }
996}
997
998#[cfg(test)]
999mod tests {
1000    use super::*;
1001    use chrono::{Datelike, Timelike};
1002
1003    const EXAMPLE_DESCRIPTOR: &str = r#"@type server-descriptor 1.0
1004router caerSidi 71.35.133.197 9001 0 0
1005platform Tor 0.2.1.30 on Linux x86_64
1006opt protocols Link 1 2 Circuit 1
1007published 2012-03-01 17:15:27
1008opt fingerprint A756 9A83 B570 6AB1 B1A9 CB52 EFF7 D2D3 2E45 53EB
1009uptime 588217
1010bandwidth 153600 256000 104590
1011opt extra-info-digest D225B728768D7EA4B5587C13A7A9D22EBBEE6E66
1012onion-key
1013-----BEGIN RSA PUBLIC KEY-----
1014MIGJAoGBAJv5IIWQ+WDWYUdyA/0L8qbIkEVH/cwryZWoIaPAzINfrw1WfNZGtBmg
1015skFtXhOHHqTRN4GPPrZsAIUOQGzQtGb66IQgT4tO/pj+P6QmSCCdTfhvGfgTCsC+
1016WPi4Fl2qryzTb3QO5r5x7T8OsG2IBUET1bLQzmtbC560SYR49IvVAgMBAAE=
1017-----END RSA PUBLIC KEY-----
1018signing-key
1019-----BEGIN RSA PUBLIC KEY-----
1020MIGJAoGBAKwvOXyztVKnuYvpTKt+nS3XIKeO8dVungi8qGoeS+6gkR6lDtGfBTjd
1021uE9UIkdAl9zi8/1Ic2wsUNHE9jiS0VgeupITGZY8YOyMJJ/xtV1cqgiWhq1dUYaq
102251TOtUogtAPgXPh4J+V8HbFFIcCzIh3qCO/xXo+DSHhv7SSif1VpAgMBAAE=
1023-----END RSA PUBLIC KEY-----
1024family $0CE3CFB1E9CC47B63EA8869813BF6FAB7D4540C1 $1FD187E8F69A9B74C9202DC16A25B9E7744AB9F6 $74FB5EFA6A46DE4060431D515DC9A790E6AD9A7C $77001D8DA9BF445B0F81AA427A675F570D222E6A $B6D83EC2D9E18B0A7A33428F8CFA9C536769E209 $D2F37F46182C23AB747787FD657E680B34EAF892 $E0BD57A11F00041A9789577C53A1B784473669E4 $E5E3E9A472EAF7BE9682B86E92305DB4C71048EF
1025opt hidden-service-dir
1026contact www.atagar.com/contact
1027reject *:*
1028router-signature
1029-----BEGIN SIGNATURE-----
1030dskLSPz8beUW7bzwDjR6EVNGpyoZde83Ejvau+5F2c6cGnlu91fiZN3suE88iE6e
1031758b9ldq5eh5mapb8vuuV3uO+0Xsud7IEOqfxdkmk0GKnUX8ouru7DSIUzUL0zqq
1032Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
1033-----END SIGNATURE-----
1034"#;
1035
1036    #[test]
1037    fn test_parse_example_descriptor() {
1038        let desc = ServerDescriptor::parse(EXAMPLE_DESCRIPTOR).unwrap();
1039
1040        assert_eq!(desc.nickname, "caerSidi");
1041        assert_eq!(
1042            desc.fingerprint,
1043            Some("A7569A83B5706AB1B1A9CB52EFF7D2D32E4553EB".to_string())
1044        );
1045        assert_eq!(desc.address.to_string(), "71.35.133.197");
1046        assert_eq!(desc.or_port, 9001);
1047        assert_eq!(desc.socks_port, None);
1048        assert_eq!(desc.dir_port, None);
1049        assert_eq!(
1050            desc.platform,
1051            Some(b"Tor 0.2.1.30 on Linux x86_64".to_vec())
1052        );
1053        assert_eq!(desc.tor_version, Some(Version::parse("0.2.1.30").unwrap()));
1054        assert_eq!(desc.operating_system, Some("Linux x86_64".to_string()));
1055        assert_eq!(desc.uptime, Some(588217));
1056        assert_eq!(
1057            desc.published,
1058            NaiveDateTime::parse_from_str("2012-03-01 17:15:27", "%Y-%m-%d %H:%M:%S")
1059                .unwrap()
1060                .and_utc()
1061        );
1062        assert_eq!(desc.contact, Some(b"www.atagar.com/contact".to_vec()));
1063        assert_eq!(
1064            desc.link_protocols,
1065            Some(vec!["1".to_string(), "2".to_string()])
1066        );
1067        assert_eq!(desc.circuit_protocols, Some(vec!["1".to_string()]));
1068        assert!(desc.is_hidden_service_dir);
1069        assert!(!desc.hibernating);
1070        assert!(!desc.allow_single_hop_exits);
1071        assert!(!desc.allow_tunneled_dir_requests);
1072        assert!(!desc.extra_info_cache);
1073        assert_eq!(
1074            desc.extra_info_digest,
1075            Some("D225B728768D7EA4B5587C13A7A9D22EBBEE6E66".to_string())
1076        );
1077        assert_eq!(desc.extra_info_sha256_digest, None);
1078        assert_eq!(desc.bridge_distribution, BridgeDistribution::Any);
1079        assert_eq!(desc.family.len(), 8);
1080        assert!(desc
1081            .family
1082            .contains("$0CE3CFB1E9CC47B63EA8869813BF6FAB7D4540C1"));
1083        assert_eq!(desc.bandwidth_avg, 153600);
1084        assert_eq!(desc.bandwidth_burst, 256000);
1085        assert_eq!(desc.bandwidth_observed, 104590);
1086        assert!(desc.onion_key.is_some());
1087        assert!(desc.signing_key.is_some());
1088        assert!(desc.signature.contains("BEGIN SIGNATURE"));
1089        assert!(desc.unrecognized_lines.is_empty());
1090    }
1091
1092    #[test]
1093    fn test_parse_minimal_descriptor() {
1094        let minimal = r#"router TestRelay 192.168.1.1 9001 0 0
1095published 2023-01-01 00:00:00
1096bandwidth 1000 2000 500
1097router-signature
1098-----BEGIN SIGNATURE-----
1099test
1100-----END SIGNATURE-----
1101"#;
1102        let desc = ServerDescriptor::parse(minimal).unwrap();
1103        assert_eq!(desc.nickname, "TestRelay");
1104        assert_eq!(desc.address.to_string(), "192.168.1.1");
1105        assert_eq!(desc.or_port, 9001);
1106        assert_eq!(desc.bandwidth_avg, 1000);
1107        assert_eq!(desc.bandwidth_burst, 2000);
1108        assert_eq!(desc.bandwidth_observed, 500);
1109    }
1110
1111    #[test]
1112    fn test_parse_router_line() {
1113        let (nickname, address, or_port, socks_port, dir_port) =
1114            ServerDescriptor::parse_router_line("caerSidi 71.35.133.197 9001 0 0").unwrap();
1115        assert_eq!(nickname, "caerSidi");
1116        assert_eq!(address.to_string(), "71.35.133.197");
1117        assert_eq!(or_port, 9001);
1118        assert_eq!(socks_port, None);
1119        assert_eq!(dir_port, None);
1120    }
1121
1122    #[test]
1123    fn test_parse_router_line_with_ports() {
1124        let (nickname, address, or_port, socks_port, dir_port) =
1125            ServerDescriptor::parse_router_line("TestRelay 10.0.0.1 9001 9050 9030").unwrap();
1126        assert_eq!(nickname, "TestRelay");
1127        assert_eq!(address.to_string(), "10.0.0.1");
1128        assert_eq!(or_port, 9001);
1129        assert_eq!(socks_port, Some(9050));
1130        assert_eq!(dir_port, Some(9030));
1131    }
1132
1133    #[test]
1134    fn test_parse_bandwidth_line() {
1135        let (avg, burst, observed) =
1136            ServerDescriptor::parse_bandwidth_line("153600 256000 104590").unwrap();
1137        assert_eq!(avg, 153600);
1138        assert_eq!(burst, 256000);
1139        assert_eq!(observed, 104590);
1140    }
1141
1142    #[test]
1143    fn test_parse_published_line() {
1144        let dt = ServerDescriptor::parse_published_line("2012-03-01 17:15:27").unwrap();
1145        assert_eq!(dt.year(), 2012);
1146        assert_eq!(dt.month(), 3);
1147        assert_eq!(dt.day(), 1);
1148        assert_eq!(dt.hour(), 17);
1149        assert_eq!(dt.minute(), 15);
1150        assert_eq!(dt.second(), 27);
1151    }
1152
1153    #[test]
1154    fn test_parse_fingerprint_line() {
1155        let fp = ServerDescriptor::parse_fingerprint_line(
1156            "A756 9A83 B570 6AB1 B1A9 CB52 EFF7 D2D3 2E45 53EB",
1157        );
1158        assert_eq!(fp, "A7569A83B5706AB1B1A9CB52EFF7D2D32E4553EB");
1159    }
1160
1161    #[test]
1162    fn test_parse_platform_line() {
1163        let (platform, version, os) =
1164            ServerDescriptor::parse_platform_line("Tor 0.2.1.30 on Linux x86_64");
1165        assert_eq!(platform, Some(b"Tor 0.2.1.30 on Linux x86_64".to_vec()));
1166        assert_eq!(version, Some(Version::parse("0.2.1.30").unwrap()));
1167        assert_eq!(os, Some("Linux x86_64".to_string()));
1168    }
1169
1170    #[test]
1171    fn test_parse_protocols_line() {
1172        let (link, circuit) = ServerDescriptor::parse_protocols_line("Link 1 2 Circuit 1");
1173        assert_eq!(link, Some(vec!["1".to_string(), "2".to_string()]));
1174        assert_eq!(circuit, Some(vec!["1".to_string()]));
1175    }
1176
1177    #[test]
1178    fn test_parse_family_line() {
1179        let family = ServerDescriptor::parse_family_line("$ABC123 $DEF456 $GHI789");
1180        assert_eq!(family.len(), 3);
1181        assert!(family.contains("$ABC123"));
1182        assert!(family.contains("$DEF456"));
1183        assert!(family.contains("$GHI789"));
1184    }
1185
1186    #[test]
1187    fn test_parse_or_address_ipv4() {
1188        let (addr, port, is_ipv6) = ServerDescriptor::parse_or_address("192.168.1.1:9001").unwrap();
1189        assert_eq!(addr.to_string(), "192.168.1.1");
1190        assert_eq!(port, 9001);
1191        assert!(!is_ipv6);
1192    }
1193
1194    #[test]
1195    fn test_parse_or_address_ipv6() {
1196        let (addr, port, is_ipv6) =
1197            ServerDescriptor::parse_or_address("[2001:db8::1]:9001").unwrap();
1198        assert_eq!(addr.to_string(), "2001:db8::1");
1199        assert_eq!(port, 9001);
1200        assert!(is_ipv6);
1201    }
1202
1203    #[test]
1204    fn test_invalid_nickname_too_long() {
1205        let result = ServerDescriptor::parse_router_line(
1206            "ThisNicknameIsWayTooLongToBeValid 192.168.1.1 9001 0 0",
1207        );
1208        assert!(result.is_err());
1209    }
1210
1211    #[test]
1212    fn test_invalid_nickname_special_chars() {
1213        let result = ServerDescriptor::parse_router_line("Invalid$Name 192.168.1.1 9001 0 0");
1214        assert!(result.is_err());
1215    }
1216
1217    #[test]
1218    fn test_invalid_address() {
1219        let result = ServerDescriptor::parse_router_line("TestRelay 999.999.999.999 9001 0 0");
1220        assert!(result.is_err());
1221    }
1222
1223    #[test]
1224    fn test_invalid_port() {
1225        let result = ServerDescriptor::parse_router_line("TestRelay 192.168.1.1 99999 0 0");
1226        assert!(result.is_err());
1227    }
1228
1229    #[test]
1230    fn test_digest_sha1() {
1231        let desc = ServerDescriptor::parse(EXAMPLE_DESCRIPTOR).unwrap();
1232        let digest = desc.digest(DigestHash::Sha1, DigestEncoding::Hex).unwrap();
1233        assert_eq!(digest.len(), 40);
1234        assert!(digest.chars().all(|c| c.is_ascii_hexdigit()));
1235    }
1236
1237    #[test]
1238    fn test_digest_sha256() {
1239        let desc = ServerDescriptor::parse(EXAMPLE_DESCRIPTOR).unwrap();
1240        let digest = desc
1241            .digest(DigestHash::Sha256, DigestEncoding::Hex)
1242            .unwrap();
1243        assert_eq!(digest.len(), 64);
1244        assert!(digest.chars().all(|c| c.is_ascii_hexdigit()));
1245    }
1246
1247    #[test]
1248    fn test_to_descriptor_string() {
1249        let desc = ServerDescriptor::parse(EXAMPLE_DESCRIPTOR).unwrap();
1250        let output = desc.to_descriptor_string();
1251        assert!(output.contains("router caerSidi 71.35.133.197 9001 0 0"));
1252        assert!(output.contains("bandwidth 153600 256000 104590"));
1253        assert!(output.contains("router-signature"));
1254    }
1255
1256    #[test]
1257    fn test_descriptor_with_exit_policy() {
1258        let content = r#"router TestRelay 192.168.1.1 9001 0 0
1259published 2023-01-01 00:00:00
1260bandwidth 1000 2000 500
1261accept *:80
1262accept *:443
1263reject *:*
1264router-signature
1265-----BEGIN SIGNATURE-----
1266test
1267-----END SIGNATURE-----
1268"#;
1269        let desc = ServerDescriptor::parse(content).unwrap();
1270        assert!(desc
1271            .exit_policy
1272            .can_exit_to("10.0.0.1".parse().unwrap(), 80));
1273        assert!(desc
1274            .exit_policy
1275            .can_exit_to("10.0.0.1".parse().unwrap(), 443));
1276        assert!(!desc
1277            .exit_policy
1278            .can_exit_to("10.0.0.1".parse().unwrap(), 22));
1279    }
1280
1281    #[test]
1282    fn test_descriptor_with_hibernating() {
1283        let content = r#"router TestRelay 192.168.1.1 9001 0 0
1284published 2023-01-01 00:00:00
1285bandwidth 1000 2000 500
1286hibernating 1
1287router-signature
1288-----BEGIN SIGNATURE-----
1289test
1290-----END SIGNATURE-----
1291"#;
1292        let desc = ServerDescriptor::parse(content).unwrap();
1293        assert!(desc.hibernating);
1294    }
1295
1296    #[test]
1297    fn test_descriptor_with_proto() {
1298        let content = r#"router TestRelay 192.168.1.1 9001 0 0
1299published 2023-01-01 00:00:00
1300bandwidth 1000 2000 500
1301proto 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
1302router-signature
1303-----BEGIN SIGNATURE-----
1304test
1305-----END SIGNATURE-----
1306"#;
1307        let desc = ServerDescriptor::parse(content).unwrap();
1308        assert!(desc.protocols.contains_key("Cons"));
1309        assert!(desc.protocols.contains_key("Link"));
1310        assert_eq!(desc.protocols.get("Cons"), Some(&vec![1, 2]));
1311    }
1312
1313    #[test]
1314    fn test_is_valid_nickname() {
1315        assert!(is_valid_nickname("caerSidi"));
1316        assert!(is_valid_nickname("TestRelay123"));
1317        assert!(is_valid_nickname("A"));
1318        assert!(is_valid_nickname("ABCDEFGHIJKLMNOPQRS"));
1319        assert!(!is_valid_nickname(""));
1320        assert!(!is_valid_nickname("ABCDEFGHIJKLMNOPQRST"));
1321        assert!(!is_valid_nickname("Invalid$Name"));
1322        assert!(!is_valid_nickname("Invalid Name"));
1323    }
1324}
1325
1326#[cfg(test)]
1327mod proptests {
1328    use super::*;
1329    use proptest::prelude::*;
1330
1331    fn valid_nickname() -> impl Strategy<Value = String> {
1332        "[a-zA-Z][a-zA-Z0-9]{0,18}".prop_filter("must be valid nickname", |s| {
1333            !s.is_empty() && s.len() <= 19 && s.chars().all(|c| c.is_ascii_alphanumeric())
1334        })
1335    }
1336
1337    fn valid_ipv4() -> impl Strategy<Value = IpAddr> {
1338        (1u8..255, 0u8..255, 0u8..255, 1u8..255)
1339            .prop_map(|(a, b, c, d)| IpAddr::V4(std::net::Ipv4Addr::new(a, b, c, d)))
1340    }
1341
1342    fn valid_port() -> impl Strategy<Value = u16> {
1343        1u16..65535
1344    }
1345
1346    fn valid_bandwidth() -> impl Strategy<Value = u64> {
1347        1u64..1_000_000_000
1348    }
1349
1350    fn valid_datetime() -> impl Strategy<Value = DateTime<Utc>> {
1351        (
1352            2000i32..2030,
1353            1u32..13,
1354            1u32..29,
1355            0u32..24,
1356            0u32..60,
1357            0u32..60,
1358        )
1359            .prop_map(|(year, month, day, hour, min, sec)| {
1360                NaiveDateTime::new(
1361                    chrono::NaiveDate::from_ymd_opt(year, month, day).unwrap(),
1362                    chrono::NaiveTime::from_hms_opt(hour, min, sec).unwrap(),
1363                )
1364                .and_utc()
1365            })
1366    }
1367
1368    fn valid_fingerprint() -> impl Strategy<Value = String> {
1369        proptest::collection::vec(
1370            proptest::char::range('0', '9').prop_union(proptest::char::range('A', 'F')),
1371            40..=40,
1372        )
1373        .prop_map(|chars| chars.into_iter().collect())
1374    }
1375
1376    fn simple_server_descriptor() -> impl Strategy<Value = ServerDescriptor> {
1377        (
1378            valid_nickname(),
1379            valid_ipv4(),
1380            valid_port(),
1381            valid_datetime(),
1382            valid_bandwidth(),
1383            valid_bandwidth(),
1384            valid_bandwidth(),
1385            proptest::option::of(valid_fingerprint()),
1386        )
1387            .prop_map(
1388                |(nickname, address, or_port, published, bw_avg, bw_burst, bw_obs, fingerprint)| {
1389                    let mut desc = ServerDescriptor::new(
1390                        nickname,
1391                        address,
1392                        or_port,
1393                        published,
1394                        "-----BEGIN SIGNATURE-----\ntest\n-----END SIGNATURE-----".to_string(),
1395                    );
1396                    desc.bandwidth_avg = bw_avg;
1397                    desc.bandwidth_burst = bw_burst;
1398                    desc.bandwidth_observed = bw_obs;
1399                    desc.fingerprint = fingerprint;
1400                    desc
1401                },
1402            )
1403    }
1404
1405    proptest! {
1406        #![proptest_config(ProptestConfig::with_cases(100))]
1407
1408        #[test]
1409        fn prop_server_descriptor_roundtrip(desc in simple_server_descriptor()) {
1410            let serialized = desc.to_descriptor_string();
1411            let parsed = ServerDescriptor::parse(&serialized);
1412
1413            prop_assert!(parsed.is_ok(), "Failed to parse serialized descriptor: {:?}", parsed.err());
1414
1415            let parsed = parsed.unwrap();
1416
1417            prop_assert_eq!(&desc.nickname, &parsed.nickname, "nickname mismatch");
1418            prop_assert_eq!(desc.address, parsed.address, "address mismatch");
1419            prop_assert_eq!(desc.or_port, parsed.or_port, "or_port mismatch");
1420            prop_assert_eq!(desc.socks_port, parsed.socks_port, "socks_port mismatch");
1421            prop_assert_eq!(desc.dir_port, parsed.dir_port, "dir_port mismatch");
1422
1423            prop_assert_eq!(desc.bandwidth_avg, parsed.bandwidth_avg, "bandwidth_avg mismatch");
1424            prop_assert_eq!(desc.bandwidth_burst, parsed.bandwidth_burst, "bandwidth_burst mismatch");
1425            prop_assert_eq!(desc.bandwidth_observed, parsed.bandwidth_observed, "bandwidth_observed mismatch");
1426
1427            prop_assert_eq!(desc.published, parsed.published, "published mismatch");
1428
1429            prop_assert_eq!(desc.fingerprint, parsed.fingerprint, "fingerprint mismatch");
1430        }
1431
1432        #[test]
1433        fn prop_valid_nickname_parsing(nickname in valid_nickname()) {
1434            let content = format!(
1435                "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",
1436                nickname
1437            );
1438            let result = ServerDescriptor::parse(&content);
1439            prop_assert!(result.is_ok(), "Failed to parse descriptor with nickname '{}': {:?}", nickname, result.err());
1440            prop_assert_eq!(result.unwrap().nickname, nickname);
1441        }
1442
1443        #[test]
1444        fn prop_bandwidth_preserved(
1445            avg in valid_bandwidth(),
1446            burst in valid_bandwidth(),
1447            observed in valid_bandwidth()
1448        ) {
1449            let content = format!(
1450                "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",
1451                avg, burst, observed
1452            );
1453            let result = ServerDescriptor::parse(&content);
1454            prop_assert!(result.is_ok());
1455            let desc = result.unwrap();
1456            prop_assert_eq!(desc.bandwidth_avg, avg);
1457            prop_assert_eq!(desc.bandwidth_burst, burst);
1458            prop_assert_eq!(desc.bandwidth_observed, observed);
1459        }
1460
1461        #[test]
1462        fn prop_ports_preserved(
1463            or_port in valid_port(),
1464            socks_port in proptest::option::of(valid_port()),
1465            dir_port in proptest::option::of(valid_port())
1466        ) {
1467            let socks = socks_port.unwrap_or(0);
1468            let dir = dir_port.unwrap_or(0);
1469            let content = format!(
1470                "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",
1471                or_port, socks, dir
1472            );
1473            let result = ServerDescriptor::parse(&content);
1474            prop_assert!(result.is_ok());
1475            let desc = result.unwrap();
1476            prop_assert_eq!(desc.or_port, or_port);
1477            prop_assert_eq!(desc.socks_port, if socks == 0 { None } else { Some(socks) });
1478            prop_assert_eq!(desc.dir_port, if dir == 0 { None } else { Some(dir) });
1479        }
1480
1481        #[test]
1482        fn prop_fingerprint_format_preserved(fp in valid_fingerprint()) {
1483            let formatted: String = fp
1484                .chars()
1485                .collect::<Vec<_>>()
1486                .chunks(4)
1487                .map(|c| c.iter().collect::<String>())
1488                .collect::<Vec<_>>()
1489                .join(" ");
1490
1491            let content = format!(
1492                "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",
1493                formatted
1494            );
1495            let result = ServerDescriptor::parse(&content);
1496            prop_assert!(result.is_ok());
1497            let desc = result.unwrap();
1498            prop_assert_eq!(desc.fingerprint, Some(fp));
1499        }
1500    }
1501}