1use 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#[derive(Debug, Clone, PartialEq)]
166pub struct ServerDescriptor {
167 pub nickname: String,
169 pub fingerprint: Option<String>,
171 pub address: IpAddr,
173 pub or_port: u16,
175 pub socks_port: Option<u16>,
177 pub dir_port: Option<u16>,
179 pub or_addresses: Vec<(IpAddr, u16, bool)>,
182 pub platform: Option<Vec<u8>>,
184 pub tor_version: Option<Version>,
186 pub operating_system: Option<String>,
188 pub published: DateTime<Utc>,
190 pub uptime: Option<u64>,
192 pub contact: Option<Vec<u8>>,
194 pub link_protocols: Option<Vec<String>>,
196 pub circuit_protocols: Option<Vec<String>>,
198 pub bandwidth_avg: u64,
200 pub bandwidth_burst: u64,
202 pub bandwidth_observed: u64,
204 pub exit_policy: ExitPolicy,
206 pub exit_policy_v6: Option<String>,
208 pub bridge_distribution: BridgeDistribution,
210 pub family: HashSet<String>,
212 pub hibernating: bool,
214 pub allow_single_hop_exits: bool,
216 pub allow_tunneled_dir_requests: bool,
218 pub extra_info_cache: bool,
220 pub extra_info_digest: Option<String>,
222 pub extra_info_sha256_digest: Option<String>,
224 pub is_hidden_service_dir: bool,
226 pub protocols: HashMap<String, Vec<u32>>,
229 pub onion_key: Option<String>,
231 pub onion_key_crosscert: Option<String>,
233 pub ntor_onion_key: Option<String>,
235 pub ntor_onion_key_crosscert: Option<String>,
237 pub ntor_onion_key_crosscert_sign: Option<String>,
239 pub signing_key: Option<String>,
241 pub ed25519_certificate: Option<String>,
243 pub ed25519_master_key: Option<String>,
245 pub ed25519_signature: Option<String>,
247 pub signature: String,
249 raw_content: Vec<u8>,
251 unrecognized_lines: Vec<String>,
253}
254
255impl ServerDescriptor {
256 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 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 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
565fn 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}