1mod rs;
47
48pub mod md;
49#[cfg(feature = "plain-consensus")]
50pub mod plain;
51#[cfg(feature = "ns-vote")]
52pub mod vote;
53
54#[cfg(feature = "build_docs")]
55mod build;
56
57use crate::doc::authcert::{AuthCert, AuthCertKeyIds};
58use crate::parse::keyword::Keyword;
59use crate::parse::parser::{Section, SectionRules, SectionRulesBuilder};
60use crate::parse::tokenize::{Item, ItemResult, NetDocReader};
61use crate::types::misc::*;
62use crate::util::PeekableIterator;
63use crate::{Error, NetdocErrorKind as EK, Pos, Result};
64use std::collections::{HashMap, HashSet};
65use std::result::Result as StdResult;
66use std::sync::Arc;
67use std::{net, result, time};
68use tor_error::{HasKind, internal};
69use tor_protover::Protocols;
70
71use bitflags::bitflags;
72use digest::Digest;
73use std::sync::LazyLock;
74use tor_checkable::{ExternallySigned, timed::TimerangeBound};
75use tor_llcrypto as ll;
76use tor_llcrypto::pk::rsa::RsaIdentity;
77
78use serde::{Deserialize, Deserializer};
79
80#[cfg(feature = "build_docs")]
81pub use build::MdConsensusBuilder;
82#[cfg(all(feature = "build_docs", feature = "plain-consensus"))]
83pub use build::PlainConsensusBuilder;
84#[cfg(feature = "build_docs")]
85ns_export_each_flavor! {
86 ty: RouterStatusBuilder;
87}
88
89ns_export_each_variety! {
90 ty: RouterStatus;
91}
92#[cfg(feature = "dangerous-expose-struct-fields")]
93ns_export_each_variety! {
94 ty: Header;
95}
96
97use void::ResultVoidExt as _;
98
99#[deprecated]
100#[cfg(feature = "ns_consensus")]
101pub use PlainConsensus as NsConsensus;
102#[deprecated]
103#[cfg(feature = "ns_consensus")]
104pub use PlainRouterStatus as NsRouterStatus;
105#[deprecated]
106#[cfg(feature = "ns_consensus")]
107pub use UncheckedPlainConsensus as UncheckedNsConsensus;
108#[deprecated]
109#[cfg(feature = "ns_consensus")]
110pub use UnvalidatedPlainConsensus as UnvalidatedNsConsensus;
111
112#[derive(Clone, Debug)]
118pub struct Lifetime {
119 valid_after: time::SystemTime,
121 fresh_until: time::SystemTime,
124 valid_until: time::SystemTime,
129}
130
131impl Lifetime {
132 pub fn new(
134 valid_after: time::SystemTime,
135 fresh_until: time::SystemTime,
136 valid_until: time::SystemTime,
137 ) -> Result<Self> {
138 if valid_after < fresh_until && fresh_until < valid_until {
139 Ok(Lifetime {
140 valid_after,
141 fresh_until,
142 valid_until,
143 })
144 } else {
145 Err(EK::InvalidLifetime.err())
146 }
147 }
148 pub fn valid_after(&self) -> time::SystemTime {
153 self.valid_after
154 }
155 pub fn fresh_until(&self) -> time::SystemTime {
160 self.fresh_until
161 }
162 pub fn valid_until(&self) -> time::SystemTime {
168 self.valid_until
169 }
170 pub fn valid_at(&self, when: time::SystemTime) -> bool {
172 self.valid_after <= when && when <= self.valid_until
173 }
174
175 pub fn voting_period(&self) -> time::Duration {
180 let valid_after = self.valid_after();
181 let fresh_until = self.fresh_until();
182 fresh_until
183 .duration_since(valid_after)
184 .expect("Mis-formed lifetime")
185 }
186}
187
188#[derive(Debug, Clone, Default, Eq, PartialEq)]
197pub struct NetParams<T> {
198 params: HashMap<String, T>,
200}
201
202impl<T> NetParams<T> {
203 #[allow(unused)]
205 pub fn new() -> Self {
206 NetParams {
207 params: HashMap::new(),
208 }
209 }
210 pub fn get<A: AsRef<str>>(&self, v: A) -> Option<&T> {
212 self.params.get(v.as_ref())
213 }
214 pub fn iter(&self) -> impl Iterator<Item = (&String, &T)> {
216 self.params.iter()
217 }
218 pub fn set(&mut self, k: String, v: T) {
220 self.params.insert(k, v);
221 }
222}
223
224impl<K: Into<String>, T> FromIterator<(K, T)> for NetParams<T> {
225 fn from_iter<I: IntoIterator<Item = (K, T)>>(i: I) -> Self {
226 NetParams {
227 params: i.into_iter().map(|(k, v)| (k.into(), v)).collect(),
228 }
229 }
230}
231
232impl<T> std::iter::Extend<(String, T)> for NetParams<T> {
233 fn extend<I: IntoIterator<Item = (String, T)>>(&mut self, iter: I) {
234 self.params.extend(iter);
235 }
236}
237
238impl<'de, T> Deserialize<'de> for NetParams<T>
239where
240 T: Deserialize<'de>,
241{
242 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
243 where
244 D: Deserializer<'de>,
245 {
246 let params = HashMap::deserialize(deserializer)?;
247 Ok(NetParams { params })
248 }
249}
250
251#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
255pub struct ProtoStatus {
256 recommended: Protocols,
259 required: Protocols,
262}
263
264impl ProtoStatus {
265 pub fn check_protocols(
275 &self,
276 supported_protocols: &Protocols,
277 ) -> StdResult<(), ProtocolSupportError> {
278 let missing_required = self.required.difference(supported_protocols);
280 if !missing_required.is_empty() {
281 return Err(ProtocolSupportError::MissingRequired(missing_required));
282 }
283 let missing_recommended = self.recommended.difference(supported_protocols);
284 if !missing_recommended.is_empty() {
285 return Err(ProtocolSupportError::MissingRecommended(
286 missing_recommended,
287 ));
288 }
289
290 Ok(())
291 }
292}
293
294#[derive(Clone, Debug, thiserror::Error)]
296#[cfg_attr(test, derive(PartialEq))]
297#[non_exhaustive]
298pub enum ProtocolSupportError {
299 #[error("Required protocols are not implemented: {0}")]
301 MissingRequired(Protocols),
302
303 #[error("Recommended protocols are not implemented: {0}")]
307 MissingRecommended(Protocols),
308}
309
310impl ProtocolSupportError {
311 pub fn should_shutdown(&self) -> bool {
313 matches!(self, Self::MissingRequired(_))
314 }
315}
316
317impl HasKind for ProtocolSupportError {
318 fn kind(&self) -> tor_error::ErrorKind {
319 tor_error::ErrorKind::SoftwareDeprecated
320 }
321}
322
323#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
326pub struct ProtoStatuses {
327 client: ProtoStatus,
329 relay: ProtoStatus,
331}
332
333impl ProtoStatuses {
334 pub fn client(&self) -> &ProtoStatus {
336 &self.client
337 }
338
339 pub fn relay(&self) -> &ProtoStatus {
341 &self.relay
342 }
343}
344
345#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
347#[non_exhaustive]
348pub enum ConsensusFlavor {
349 Microdesc,
352 Plain,
357}
358
359impl ConsensusFlavor {
360 pub fn name(&self) -> &'static str {
362 match self {
363 ConsensusFlavor::Plain => "ns", ConsensusFlavor::Microdesc => "microdesc",
365 }
366 }
367 pub fn from_opt_name(name: Option<&str>) -> Result<Self> {
372 match name {
373 Some("microdesc") => Ok(ConsensusFlavor::Microdesc),
374 Some("ns") | None => Ok(ConsensusFlavor::Plain),
375 Some(other) => {
376 Err(EK::BadDocumentType.with_msg(format!("unrecognized flavor {:?}", other)))
377 }
378 }
379 }
380}
381
382#[allow(dead_code)]
384#[cfg_attr(
385 feature = "dangerous-expose-struct-fields",
386 visible::StructFields(pub),
387 non_exhaustive
388)]
389#[derive(Debug, Clone)]
390pub struct Signature {
391 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
396 digestname: String,
397 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
400 key_ids: AuthCertKeyIds,
401 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
403 signature: Vec<u8>,
404}
405
406#[allow(dead_code)]
408#[cfg_attr(
409 feature = "dangerous-expose-struct-fields",
410 visible::StructFields(pub),
411 non_exhaustive
412)]
413#[derive(Debug, Clone)]
414pub struct SignatureGroup {
415 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
417 sha256: Option<[u8; 32]>,
418 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
420 sha1: Option<[u8; 20]>,
421 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
423 signatures: Vec<Signature>,
424}
425
426#[derive(
428 Debug, Clone, Copy, Eq, PartialEq, derive_more::From, derive_more::Into, derive_more::AsRef,
429)]
430pub struct SharedRandVal([u8; 32]);
432
433#[allow(dead_code)]
436#[cfg_attr(
437 feature = "dangerous-expose-struct-fields",
438 visible::StructFields(pub),
439 visibility::make(pub),
440 non_exhaustive
441)]
442#[derive(Debug, Clone)]
443pub struct SharedRandStatus {
444 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
446 n_reveals: u8,
447 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
454 value: SharedRandVal,
455
456 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
460 timestamp: Option<time::SystemTime>,
461}
462
463#[allow(dead_code)]
467#[cfg_attr(
468 feature = "dangerous-expose-struct-fields",
469 visible::StructFields(pub),
470 visibility::make(pub),
471 non_exhaustive
472)]
473#[derive(Debug, Clone)]
474struct DirSource {
475 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
477 nickname: String,
478 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
484 identity: RsaIdentity,
485 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
487 ip: net::IpAddr,
488 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
490 dir_port: u16,
491 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
493 or_port: u16,
494}
495
496bitflags! {
497 #[derive(Clone, Copy, Debug)]
508 pub struct RelayFlags: u16 {
509 const AUTHORITY = (1<<0);
511 const BAD_EXIT = (1<<1);
516 const EXIT = (1<<2);
518 const FAST = (1<<3);
520 const GUARD = (1<<4);
525 const HSDIR = (1<<5);
528 const MIDDLE_ONLY = (1<<6);
535 const NO_ED_CONSENSUS = (1<<7);
537 const STABLE = (1<<8);
539 const STALE_DESC = (1<<9);
542 const RUNNING = (1<<10);
547 const VALID = (1<<11);
553 const V2DIR = (1<<12);
556 }
557}
558
559#[non_exhaustive]
561#[derive(Debug, Clone, Copy)]
562pub enum RelayWeight {
563 Unmeasured(u32),
565 Measured(u32),
567}
568
569impl RelayWeight {
570 pub fn is_measured(&self) -> bool {
572 matches!(self, RelayWeight::Measured(_))
573 }
574 pub fn is_nonzero(&self) -> bool {
576 !matches!(self, RelayWeight::Unmeasured(0) | RelayWeight::Measured(0))
577 }
578}
579
580#[allow(dead_code)]
582#[cfg_attr(
583 feature = "dangerous-expose-struct-fields",
584 visible::StructFields(pub),
585 visibility::make(pub),
586 non_exhaustive
587)]
588#[derive(Debug, Clone)]
589pub(crate) struct ConsensusVoterInfo {
590 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
592 dir_source: DirSource,
593 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
595 contact: String,
596 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
599 vote_digest: Vec<u8>,
600}
601
602#[allow(dead_code)]
604#[cfg_attr(
605 feature = "dangerous-expose-struct-fields",
606 visible::StructFields(pub),
607 visibility::make(pub),
608 non_exhaustive
609)]
610#[derive(Debug, Clone)]
611pub(crate) struct Footer {
612 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
618 weights: NetParams<i32>,
619}
620
621pub type MdConsensus = md::Consensus;
624
625pub type UnvalidatedMdConsensus = md::UnvalidatedConsensus;
628
629pub type UncheckedMdConsensus = md::UncheckedConsensus;
632
633#[cfg(feature = "plain-consensus")]
634pub type PlainConsensus = plain::Consensus;
637
638#[cfg(feature = "plain-consensus")]
639pub type UnvalidatedPlainConsensus = plain::UnvalidatedConsensus;
642
643#[cfg(feature = "plain-consensus")]
644pub type UncheckedPlainConsensus = plain::UncheckedConsensus;
647
648decl_keyword! {
649 #[non_exhaustive]
654 #[allow(missing_docs)]
655 pub NetstatusKwd {
656 "network-status-version" => NETWORK_STATUS_VERSION,
658 "vote-status" => VOTE_STATUS,
659 "consensus-methods" => CONSENSUS_METHODS,
660 "consensus-method" => CONSENSUS_METHOD,
661 "published" => PUBLISHED,
662 "valid-after" => VALID_AFTER,
663 "fresh-until" => FRESH_UNTIL,
664 "valid-until" => VALID_UNTIL,
665 "voting-delay" => VOTING_DELAY,
666 "client-versions" => CLIENT_VERSIONS,
667 "server-versions" => SERVER_VERSIONS,
668 "known-flags" => KNOWN_FLAGS,
669 "flag-thresholds" => FLAG_THRESHOLDS,
670 "recommended-client-protocols" => RECOMMENDED_CLIENT_PROTOCOLS,
671 "required-client-protocols" => REQUIRED_CLIENT_PROTOCOLS,
672 "recommended-relay-protocols" => RECOMMENDED_RELAY_PROTOCOLS,
673 "required-relay-protocols" => REQUIRED_RELAY_PROTOCOLS,
674 "params" => PARAMS,
675 "bandwidth-file-headers" => BANDWIDTH_FILE_HEADERS,
676 "bandwidth-file-digest" => BANDWIDTH_FILE_DIGEST,
677 "shared-rand-previous-value" => SHARED_RAND_PREVIOUS_VALUE,
681 "shared-rand-current-value" => SHARED_RAND_CURRENT_VALUE,
682
683 "dir-source" => DIR_SOURCE,
685 "contact" => CONTACT,
686
687 "legacy-dir-key" => LEGACY_DIR_KEY,
689 "shared-rand-participate" => SHARED_RAND_PARTICIPATE,
690 "shared-rand-commit" => SHARED_RAND_COMMIT,
691
692 "vote-digest" => VOTE_DIGEST,
694
695 "dir-key-certificate-version" => DIR_KEY_CERTIFICATE_VERSION,
697
698 "r" => RS_R,
700 "a" => RS_A,
701 "s" => RS_S,
702 "v" => RS_V,
703 "pr" => RS_PR,
704 "w" => RS_W,
705 "p" => RS_P,
706 "m" => RS_M,
707 "id" => RS_ID,
708
709 "directory-footer" => DIRECTORY_FOOTER,
711 "bandwidth-weights" => BANDWIDTH_WEIGHTS,
712 "directory-signature" => DIRECTORY_SIGNATURE,
713 }
714}
715
716static NS_HEADER_RULES_COMMON_: LazyLock<SectionRulesBuilder<NetstatusKwd>> = LazyLock::new(|| {
718 use NetstatusKwd::*;
719 let mut rules = SectionRules::builder();
720 rules.add(NETWORK_STATUS_VERSION.rule().required().args(1..=2));
721 rules.add(VOTE_STATUS.rule().required().args(1..));
722 rules.add(VALID_AFTER.rule().required());
723 rules.add(FRESH_UNTIL.rule().required());
724 rules.add(VALID_UNTIL.rule().required());
725 rules.add(VOTING_DELAY.rule().args(2..));
726 rules.add(CLIENT_VERSIONS.rule());
727 rules.add(SERVER_VERSIONS.rule());
728 rules.add(KNOWN_FLAGS.rule().required());
729 rules.add(RECOMMENDED_CLIENT_PROTOCOLS.rule().args(1..));
730 rules.add(RECOMMENDED_RELAY_PROTOCOLS.rule().args(1..));
731 rules.add(REQUIRED_CLIENT_PROTOCOLS.rule().args(1..));
732 rules.add(REQUIRED_RELAY_PROTOCOLS.rule().args(1..));
733 rules.add(PARAMS.rule());
734 rules
735});
736static NS_HEADER_RULES_CONSENSUS: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
738 use NetstatusKwd::*;
739 let mut rules = NS_HEADER_RULES_COMMON_.clone();
740 rules.add(CONSENSUS_METHOD.rule().args(1..=1));
741 rules.add(SHARED_RAND_PREVIOUS_VALUE.rule().args(2..));
742 rules.add(SHARED_RAND_CURRENT_VALUE.rule().args(2..));
743 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
744 rules.build()
745});
746static NS_VOTERINFO_RULES_CONSENSUS: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
777 use NetstatusKwd::*;
778 let mut rules = SectionRules::builder();
779 rules.add(DIR_SOURCE.rule().required().args(6..));
780 rules.add(CONTACT.rule().required());
781 rules.add(VOTE_DIGEST.rule().required());
782 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
783 rules.build()
784});
785static NS_ROUTERSTATUS_RULES_COMMON_: LazyLock<SectionRulesBuilder<NetstatusKwd>> =
787 LazyLock::new(|| {
788 use NetstatusKwd::*;
789 let mut rules = SectionRules::builder();
790 rules.add(RS_A.rule().may_repeat().args(1..));
791 rules.add(RS_S.rule().required());
792 rules.add(RS_V.rule());
793 rules.add(RS_PR.rule().required());
794 rules.add(RS_W.rule());
795 rules.add(RS_P.rule().args(2..));
796 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
797 rules
798 });
799
800static NS_ROUTERSTATUS_RULES_PLAIN: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
802 use NetstatusKwd::*;
803 let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
804 rules.add(RS_R.rule().required().args(8..));
805 rules.build()
806});
807
808static NS_ROUTERSTATUS_RULES_MDCON: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
821 use NetstatusKwd::*;
822 let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
823 rules.add(RS_R.rule().required().args(6..));
824 rules.add(RS_M.rule().required().args(1..));
825 rules.build()
826});
827static NS_FOOTER_RULES: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
829 use NetstatusKwd::*;
830 let mut rules = SectionRules::builder();
831 rules.add(DIRECTORY_FOOTER.rule().required().no_args());
832 rules.add(BANDWIDTH_WEIGHTS.rule());
834 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
835 rules.build()
836});
837
838impl ProtoStatus {
839 fn from_section(
841 sec: &Section<'_, NetstatusKwd>,
842 recommend_token: NetstatusKwd,
843 required_token: NetstatusKwd,
844 ) -> Result<ProtoStatus> {
845 fn parse(t: Option<&Item<'_, NetstatusKwd>>) -> Result<Protocols> {
847 if let Some(item) = t {
848 item.args_as_str()
849 .parse::<Protocols>()
850 .map_err(|e| EK::BadArgument.at_pos(item.pos()).with_source(e))
851 } else {
852 Ok(Protocols::new())
853 }
854 }
855
856 let recommended = parse(sec.get(recommend_token))?;
857 let required = parse(sec.get(required_token))?;
858 Ok(ProtoStatus {
859 recommended,
860 required,
861 })
862 }
863
864 pub fn required_protocols(&self) -> &Protocols {
871 &self.required
872 }
873
874 pub fn recommended_protocols(&self) -> &Protocols {
879 &self.recommended
880 }
881}
882
883impl<T> std::str::FromStr for NetParams<T>
884where
885 T: std::str::FromStr,
886 T::Err: std::error::Error,
887{
888 type Err = Error;
889 fn from_str(s: &str) -> Result<Self> {
890 fn parse_pair<U>(p: &str) -> Result<(String, U)>
892 where
893 U: std::str::FromStr,
894 U::Err: std::error::Error,
895 {
896 let parts: Vec<_> = p.splitn(2, '=').collect();
897 if parts.len() != 2 {
898 return Err(EK::BadArgument
899 .at_pos(Pos::at(p))
900 .with_msg("Missing = in key=value list"));
901 }
902 let num = parts[1].parse::<U>().map_err(|e| {
903 EK::BadArgument
904 .at_pos(Pos::at(parts[1]))
905 .with_msg(e.to_string())
906 })?;
907 Ok((parts[0].to_string(), num))
908 }
909
910 let params = s
911 .split(' ')
912 .filter(|p| !p.is_empty())
913 .map(parse_pair)
914 .collect::<Result<HashMap<_, _>>>()?;
915 Ok(NetParams { params })
916 }
917}
918
919impl SharedRandStatus {
920 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
923 match item.kwd() {
924 NetstatusKwd::SHARED_RAND_PREVIOUS_VALUE | NetstatusKwd::SHARED_RAND_CURRENT_VALUE => {}
925 _ => {
926 return Err(Error::from(internal!(
927 "wrong keyword {:?} on shared-random value",
928 item.kwd()
929 ))
930 .at_pos(item.pos()));
931 }
932 }
933 let n_reveals: u8 = item.parse_arg(0)?;
934 let val: B64 = item.parse_arg(1)?;
935 let value = SharedRandVal(val.into_array()?);
936 let timestamp = item
938 .parse_optional_arg::<Iso8601TimeNoSp>(2)?
939 .map(Into::into);
940 Ok(SharedRandStatus {
941 n_reveals,
942 value,
943 timestamp,
944 })
945 }
946
947 pub fn value(&self) -> &SharedRandVal {
949 &self.value
950 }
951
952 pub fn timestamp(&self) -> Option<std::time::SystemTime> {
954 self.timestamp
955 }
956}
957
958impl DirSource {
959 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
961 if item.kwd() != NetstatusKwd::DIR_SOURCE {
962 return Err(
963 Error::from(internal!("Bad keyword {:?} on dir-source", item.kwd()))
964 .at_pos(item.pos()),
965 );
966 }
967 let nickname = item.required_arg(0)?.to_string();
968 let identity = item.parse_arg::<Fingerprint>(1)?.into();
969 let ip = item.parse_arg(3)?;
970 let dir_port = item.parse_arg(4)?;
971 let or_port = item.parse_arg(5)?;
972
973 Ok(DirSource {
974 nickname,
975 identity,
976 ip,
977 dir_port,
978 or_port,
979 })
980 }
981}
982
983impl ConsensusVoterInfo {
984 fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<ConsensusVoterInfo> {
986 use NetstatusKwd::*;
987 #[allow(clippy::unwrap_used)]
990 let first = sec.first_item().unwrap();
991 if first.kwd() != DIR_SOURCE {
992 return Err(Error::from(internal!(
993 "Wrong keyword {:?} at start of voter info",
994 first.kwd()
995 ))
996 .at_pos(first.pos()));
997 }
998 let dir_source = DirSource::from_item(sec.required(DIR_SOURCE)?)?;
999
1000 let contact = sec.required(CONTACT)?.args_as_str().to_string();
1001
1002 let vote_digest = sec.required(VOTE_DIGEST)?.parse_arg::<B16>(0)?.into();
1003
1004 Ok(ConsensusVoterInfo {
1005 dir_source,
1006 contact,
1007 vote_digest,
1008 })
1009 }
1010}
1011
1012impl std::str::FromStr for RelayFlags {
1013 type Err = void::Void;
1014 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
1015 Ok(match s {
1016 "Authority" => RelayFlags::AUTHORITY,
1017 "BadExit" => RelayFlags::BAD_EXIT,
1018 "Exit" => RelayFlags::EXIT,
1019 "Fast" => RelayFlags::FAST,
1020 "Guard" => RelayFlags::GUARD,
1021 "HSDir" => RelayFlags::HSDIR,
1022 "MiddleOnly" => RelayFlags::MIDDLE_ONLY,
1023 "NoEdConsensus" => RelayFlags::NO_ED_CONSENSUS,
1024 "Stable" => RelayFlags::STABLE,
1025 "StaleDesc" => RelayFlags::STALE_DESC,
1026 "Running" => RelayFlags::RUNNING,
1027 "Valid" => RelayFlags::VALID,
1028 "V2Dir" => RelayFlags::V2DIR,
1029 _ => RelayFlags::empty(),
1030 })
1031 }
1032}
1033
1034impl RelayFlags {
1035 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<RelayFlags> {
1037 if item.kwd() != NetstatusKwd::RS_S {
1038 return Err(
1039 Error::from(internal!("Wrong keyword {:?} for S line", item.kwd()))
1040 .at_pos(item.pos()),
1041 );
1042 }
1043 let mut flags: RelayFlags = RelayFlags::RUNNING | RelayFlags::VALID;
1045
1046 let mut prev: Option<&str> = None;
1047 for s in item.args() {
1048 if let Some(p) = prev {
1049 if p >= s {
1050 return Err(EK::BadArgument
1052 .at_pos(item.pos())
1053 .with_msg("Flags out of order"));
1054 }
1055 }
1056 let fl = s.parse().void_unwrap();
1057 flags |= fl;
1058 prev = Some(s);
1059 }
1060
1061 Ok(flags)
1062 }
1063}
1064
1065impl Default for RelayWeight {
1066 fn default() -> RelayWeight {
1067 RelayWeight::Unmeasured(0)
1068 }
1069}
1070
1071impl RelayWeight {
1072 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<RelayWeight> {
1074 if item.kwd() != NetstatusKwd::RS_W {
1075 return Err(
1076 Error::from(internal!("Wrong keyword {:?} on W line", item.kwd()))
1077 .at_pos(item.pos()),
1078 );
1079 }
1080
1081 let params: NetParams<u32> = item.args_as_str().parse()?;
1082
1083 let bw = params.params.get("Bandwidth");
1084 let unmeas = params.params.get("Unmeasured");
1085
1086 let bw = match bw {
1087 None => return Ok(RelayWeight::Unmeasured(0)),
1088 Some(b) => *b,
1089 };
1090
1091 match unmeas {
1092 None | Some(0) => Ok(RelayWeight::Measured(bw)),
1093 Some(1) => Ok(RelayWeight::Unmeasured(bw)),
1094 _ => Err(EK::BadArgument
1095 .at_pos(item.pos())
1096 .with_msg("unmeasured value")),
1097 }
1098 }
1099}
1100
1101impl Footer {
1102 fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<Footer> {
1104 use NetstatusKwd::*;
1105 sec.required(DIRECTORY_FOOTER)?;
1106
1107 let weights = sec
1108 .maybe(BANDWIDTH_WEIGHTS)
1109 .args_as_str()
1110 .unwrap_or("")
1111 .parse()?;
1112
1113 Ok(Footer { weights })
1114 }
1115}
1116
1117enum SigCheckResult {
1119 Valid,
1121 Invalid,
1124 MissingCert,
1127}
1128
1129impl Signature {
1130 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Signature> {
1132 if item.kwd() != NetstatusKwd::DIRECTORY_SIGNATURE {
1133 return Err(Error::from(internal!(
1134 "Wrong keyword {:?} for directory signature",
1135 item.kwd()
1136 ))
1137 .at_pos(item.pos()));
1138 }
1139
1140 let (alg, id_fp, sk_fp) = if item.n_args() > 2 {
1141 (
1142 item.required_arg(0)?,
1143 item.required_arg(1)?,
1144 item.required_arg(2)?,
1145 )
1146 } else {
1147 ("sha1", item.required_arg(0)?, item.required_arg(1)?)
1148 };
1149
1150 let digestname = alg.to_string();
1151 let id_fingerprint = id_fp.parse::<Fingerprint>()?.into();
1152 let sk_fingerprint = sk_fp.parse::<Fingerprint>()?.into();
1153 let key_ids = AuthCertKeyIds {
1154 id_fingerprint,
1155 sk_fingerprint,
1156 };
1157 let signature = item.obj("SIGNATURE")?;
1158
1159 Ok(Signature {
1160 digestname,
1161 key_ids,
1162 signature,
1163 })
1164 }
1165
1166 fn matches_cert(&self, cert: &AuthCert) -> bool {
1169 cert.key_ids() == &self.key_ids
1170 }
1171
1172 fn find_cert<'a>(&self, certs: &'a [AuthCert]) -> Option<&'a AuthCert> {
1175 certs.iter().find(|&c| self.matches_cert(c))
1176 }
1177
1178 fn check_signature(&self, signed_digest: &[u8], certs: &[AuthCert]) -> SigCheckResult {
1182 match self.find_cert(certs) {
1183 None => SigCheckResult::MissingCert,
1184 Some(cert) => {
1185 let key = cert.signing_key();
1186 match key.verify(signed_digest, &self.signature[..]) {
1187 Ok(()) => SigCheckResult::Valid,
1188 Err(_) => SigCheckResult::Invalid,
1189 }
1190 }
1191 }
1192 }
1193}
1194
1195impl SignatureGroup {
1196 fn list_missing(&self, certs: &[AuthCert]) -> (usize, Vec<&Signature>) {
1203 let mut ok: HashSet<RsaIdentity> = HashSet::new();
1204 let mut missing = Vec::new();
1205 for sig in &self.signatures {
1206 let id_fingerprint = &sig.key_ids.id_fingerprint;
1207 if ok.contains(id_fingerprint) {
1208 continue;
1209 }
1210 if sig.find_cert(certs).is_some() {
1211 ok.insert(*id_fingerprint);
1212 continue;
1213 }
1214
1215 missing.push(sig);
1216 }
1217 (ok.len(), missing)
1218 }
1219
1220 fn could_validate(&self, authorities: &[&RsaIdentity]) -> bool {
1224 let mut signed_by: HashSet<RsaIdentity> = HashSet::new();
1225 for sig in &self.signatures {
1226 let id_fp = &sig.key_ids.id_fingerprint;
1227 if signed_by.contains(id_fp) {
1228 continue;
1230 }
1231 if authorities.contains(&id_fp) {
1232 signed_by.insert(*id_fp);
1233 }
1234 }
1235
1236 signed_by.len() > (authorities.len() / 2)
1237 }
1238
1239 fn validate(&self, n_authorities: u16, certs: &[AuthCert]) -> bool {
1246 let mut ok: HashSet<RsaIdentity> = HashSet::new();
1250
1251 for sig in &self.signatures {
1252 let id_fingerprint = &sig.key_ids.id_fingerprint;
1253 if ok.contains(id_fingerprint) {
1254 continue;
1257 }
1258
1259 let d: Option<&[u8]> = match sig.digestname.as_ref() {
1260 "sha256" => self.sha256.as_ref().map(|a| &a[..]),
1261 "sha1" => self.sha1.as_ref().map(|a| &a[..]),
1262 _ => None, };
1264 if d.is_none() {
1265 continue;
1268 }
1269
1270 #[allow(clippy::unwrap_used)]
1272 match sig.check_signature(d.as_ref().unwrap(), certs) {
1273 SigCheckResult::Valid => {
1274 ok.insert(*id_fingerprint);
1275 }
1276 _ => continue,
1277 }
1278 }
1279
1280 ok.len() > (n_authorities / 2) as usize
1281 }
1282}
1283
1284#[cfg(test)]
1285mod test {
1286 #![allow(clippy::bool_assert_comparison)]
1288 #![allow(clippy::clone_on_copy)]
1289 #![allow(clippy::dbg_macro)]
1290 #![allow(clippy::mixed_attributes_style)]
1291 #![allow(clippy::print_stderr)]
1292 #![allow(clippy::print_stdout)]
1293 #![allow(clippy::single_char_pattern)]
1294 #![allow(clippy::unwrap_used)]
1295 #![allow(clippy::unchecked_duration_subtraction)]
1296 #![allow(clippy::useless_vec)]
1297 #![allow(clippy::needless_pass_by_value)]
1298 use super::*;
1300 use hex_literal::hex;
1301
1302 const CERTS: &str = include_str!("../../testdata/authcerts2.txt");
1303 const CONSENSUS: &str = include_str!("../../testdata/mdconsensus1.txt");
1304
1305 #[cfg(feature = "plain-consensus")]
1306 const PLAIN_CERTS: &str = include_str!("../../testdata2/cached-certs");
1307 #[cfg(feature = "plain-consensus")]
1308 const PLAIN_CONSENSUS: &str = include_str!("../../testdata2/cached-consensus");
1309
1310 fn read_bad(fname: &str) -> String {
1311 use std::fs;
1312 use std::path::PathBuf;
1313 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1314 path.push("testdata");
1315 path.push("bad-mdconsensus");
1316 path.push(fname);
1317
1318 fs::read_to_string(path).unwrap()
1319 }
1320
1321 #[test]
1322 fn parse_and_validate_md() -> Result<()> {
1323 use std::net::SocketAddr;
1324 use tor_checkable::{SelfSigned, Timebound};
1325 let mut certs = Vec::new();
1326 for cert in AuthCert::parse_multiple(CERTS)? {
1327 let cert = cert?.check_signature()?.dangerously_assume_timely();
1328 certs.push(cert);
1329 }
1330 let auth_ids: Vec<_> = certs.iter().map(|c| &c.key_ids().id_fingerprint).collect();
1331
1332 assert_eq!(certs.len(), 3);
1333
1334 let (_, _, consensus) = MdConsensus::parse(CONSENSUS)?;
1335 let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
1336
1337 assert!(consensus.authorities_are_correct(&auth_ids));
1339 assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
1341 {
1342 let bad_auth_id = (*b"xxxxxxxxxxxxxxxxxxxx").into();
1345 assert!(!consensus.authorities_are_correct(&[&bad_auth_id]));
1346 }
1347
1348 let missing = consensus.key_is_correct(&[]).err().unwrap();
1349 assert_eq!(3, missing.len());
1350 assert!(consensus.key_is_correct(&certs).is_ok());
1351 let missing = consensus.key_is_correct(&certs[0..1]).err().unwrap();
1352 assert_eq!(2, missing.len());
1353
1354 let same_three_times = vec![certs[0].clone(), certs[0].clone(), certs[0].clone()];
1356 let missing = consensus.key_is_correct(&same_three_times).err().unwrap();
1357
1358 assert_eq!(2, missing.len());
1359 assert!(consensus.is_well_signed(&same_three_times).is_err());
1360
1361 assert!(consensus.key_is_correct(&certs).is_ok());
1362 let consensus = consensus.check_signature(&certs)?;
1363
1364 assert_eq!(6, consensus.relays().len());
1365 let r0 = &consensus.relays()[0];
1366 assert_eq!(
1367 r0.md_digest(),
1368 &hex!("73dabe0a0468f4f7a67810a18d11e36731bb1d2ec3634db459100609f3b3f535")
1369 );
1370 assert_eq!(
1371 r0.rsa_identity().as_bytes(),
1372 &hex!("0a3057af2910415794d8ea430309d9ac5f5d524b")
1373 );
1374 assert!(!r0.weight().is_measured());
1375 assert!(!r0.weight().is_nonzero());
1376 let pv = &r0.protovers();
1377 assert!(pv.supports_subver("HSDir", 2));
1378 assert!(!pv.supports_subver("HSDir", 3));
1379 let ip4 = "127.0.0.1:5002".parse::<SocketAddr>().unwrap();
1380 let ip6 = "[::1]:5002".parse::<SocketAddr>().unwrap();
1381 assert!(r0.orport_addrs().any(|a| a == &ip4));
1382 assert!(r0.orport_addrs().any(|a| a == &ip6));
1383
1384 Ok(())
1385 }
1386
1387 #[test]
1388 #[cfg(feature = "plain-consensus")]
1389 fn parse_and_validate_ns() -> Result<()> {
1390 use tor_checkable::{SelfSigned, Timebound};
1391 let mut certs = Vec::new();
1392 for cert in AuthCert::parse_multiple(PLAIN_CERTS)? {
1393 let cert = cert?.check_signature()?.dangerously_assume_timely();
1394 certs.push(cert);
1395 }
1396 let auth_ids: Vec<_> = certs.iter().map(|c| &c.key_ids().id_fingerprint).collect();
1397 assert_eq!(certs.len(), 4);
1398
1399 let (_, _, consensus) = PlainConsensus::parse(PLAIN_CONSENSUS)?;
1400 let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
1401 assert!(consensus.authorities_are_correct(&auth_ids));
1403 assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
1405
1406 assert!(consensus.key_is_correct(&certs).is_ok());
1407
1408 let _consensus = consensus.check_signature(&certs)?;
1409
1410 Ok(())
1411 }
1412
1413 #[test]
1414 fn test_bad() {
1415 use crate::Pos;
1416 fn check(fname: &str, e: &Error) {
1417 let content = read_bad(fname);
1418 let res = MdConsensus::parse(&content);
1419 assert!(res.is_err());
1420 assert_eq!(&res.err().unwrap(), e);
1421 }
1422
1423 check(
1424 "bad-flags",
1425 &EK::BadArgument
1426 .at_pos(Pos::from_line(27, 1))
1427 .with_msg("Flags out of order"),
1428 );
1429 check(
1430 "bad-md-digest",
1431 &EK::BadArgument
1432 .at_pos(Pos::from_line(40, 3))
1433 .with_msg("Invalid base64"),
1434 );
1435 check(
1436 "bad-weight",
1437 &EK::BadArgument
1438 .at_pos(Pos::from_line(67, 141))
1439 .with_msg("invalid digit found in string"),
1440 );
1441 check(
1442 "bad-weights",
1443 &EK::BadArgument
1444 .at_pos(Pos::from_line(51, 13))
1445 .with_msg("invalid digit found in string"),
1446 );
1447 check(
1448 "wrong-order",
1449 &EK::WrongSortOrder.at_pos(Pos::from_line(52, 1)),
1450 );
1451 check(
1452 "wrong-start",
1453 &EK::UnexpectedToken
1454 .with_msg("vote-status")
1455 .at_pos(Pos::from_line(1, 1)),
1456 );
1457 check("wrong-version", &EK::BadDocumentVersion.with_msg("10"));
1458 }
1459
1460 fn gettok(s: &str) -> Result<Item<'_, NetstatusKwd>> {
1461 let mut reader = NetDocReader::new(s)?;
1462 let tok = reader.next().unwrap();
1463 assert!(reader.next().is_none());
1464 tok
1465 }
1466
1467 #[test]
1468 fn test_weight() {
1469 let w = gettok("w Unmeasured=1 Bandwidth=6\n").unwrap();
1470 let w = RelayWeight::from_item(&w).unwrap();
1471 assert!(!w.is_measured());
1472 assert!(w.is_nonzero());
1473
1474 let w = gettok("w Bandwidth=10\n").unwrap();
1475 let w = RelayWeight::from_item(&w).unwrap();
1476 assert!(w.is_measured());
1477 assert!(w.is_nonzero());
1478
1479 let w = RelayWeight::default();
1480 assert!(!w.is_measured());
1481 assert!(!w.is_nonzero());
1482
1483 let w = gettok("w Mustelid=66 Cheato=7 Unmeasured=1\n").unwrap();
1484 let w = RelayWeight::from_item(&w).unwrap();
1485 assert!(!w.is_measured());
1486 assert!(!w.is_nonzero());
1487
1488 let w = gettok("r foo\n").unwrap();
1489 let w = RelayWeight::from_item(&w);
1490 assert!(w.is_err());
1491
1492 let w = gettok("r Bandwidth=6 Unmeasured=Frog\n").unwrap();
1493 let w = RelayWeight::from_item(&w);
1494 assert!(w.is_err());
1495
1496 let w = gettok("r Bandwidth=6 Unmeasured=3\n").unwrap();
1497 let w = RelayWeight::from_item(&w);
1498 assert!(w.is_err());
1499 }
1500
1501 #[test]
1502 fn test_netparam() {
1503 let p = "Hello=600 Goodbye=5 Fred=7"
1504 .parse::<NetParams<u32>>()
1505 .unwrap();
1506 assert_eq!(p.get("Hello"), Some(&600_u32));
1507
1508 let p = "Hello=Goodbye=5 Fred=7".parse::<NetParams<u32>>();
1509 assert!(p.is_err());
1510
1511 let p = "Hello=Goodbye Fred=7".parse::<NetParams<u32>>();
1512 assert!(p.is_err());
1513 }
1514
1515 #[test]
1516 fn test_sharedrand() {
1517 let sr =
1518 gettok("shared-rand-previous-value 9 5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4\n")
1519 .unwrap();
1520 let sr = SharedRandStatus::from_item(&sr).unwrap();
1521
1522 assert_eq!(sr.n_reveals, 9);
1523 assert_eq!(
1524 sr.value.0,
1525 hex!("e4ba1d638c96c458532adc6957dc0080d03d37c7e5854087d0da90bf5ff4e72e")
1526 );
1527 assert!(sr.timestamp.is_none());
1528
1529 let sr2 = gettok(
1530 "shared-rand-current-value 9 \
1531 5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4 2022-01-20T12:34:56\n",
1532 )
1533 .unwrap();
1534 let sr2 = SharedRandStatus::from_item(&sr2).unwrap();
1535 assert_eq!(sr2.n_reveals, sr.n_reveals);
1536 assert_eq!(sr2.value.0, sr.value.0);
1537 assert_eq!(
1538 sr2.timestamp.unwrap(),
1539 humantime::parse_rfc3339("2022-01-20T12:34:56Z").unwrap()
1540 );
1541
1542 let sr = gettok("foo bar\n").unwrap();
1543 let sr = SharedRandStatus::from_item(&sr);
1544 assert!(sr.is_err());
1545 }
1546
1547 #[test]
1548 fn test_protostatus() {
1549 let my_protocols: Protocols = "Link=7 Cons=1-5 Desc=3-10".parse().unwrap();
1550
1551 let outcome = ProtoStatus {
1552 recommended: "Link=7".parse().unwrap(),
1553 required: "Desc=5".parse().unwrap(),
1554 }
1555 .check_protocols(&my_protocols);
1556 assert!(outcome.is_ok());
1557
1558 let outcome = ProtoStatus {
1559 recommended: "Microdesc=4 Link=7".parse().unwrap(),
1560 required: "Desc=5".parse().unwrap(),
1561 }
1562 .check_protocols(&my_protocols);
1563 assert_eq!(
1564 outcome,
1565 Err(ProtocolSupportError::MissingRecommended(
1566 "Microdesc=4".parse().unwrap()
1567 ))
1568 );
1569
1570 let outcome = ProtoStatus {
1571 recommended: "Microdesc=4 Link=7".parse().unwrap(),
1572 required: "Desc=5 Cons=5-12 Wombat=15".parse().unwrap(),
1573 }
1574 .check_protocols(&my_protocols);
1575 assert_eq!(
1576 outcome,
1577 Err(ProtocolSupportError::MissingRequired(
1578 "Cons=6-12 Wombat=15".parse().unwrap()
1579 ))
1580 );
1581 }
1582
1583 #[test]
1584 fn serialize_protostatus() {
1585 let ps = ProtoStatuses {
1586 client: ProtoStatus {
1587 recommended: "Link=1-5 LinkAuth=2-5".parse().unwrap(),
1588 required: "Link=5 LinkAuth=3".parse().unwrap(),
1589 },
1590 relay: ProtoStatus {
1591 recommended: "Wombat=20-30 Knish=20-30".parse().unwrap(),
1592 required: "Wombat=20-22 Knish=25-27".parse().unwrap(),
1593 },
1594 };
1595 let json = serde_json::to_string(&ps).unwrap();
1596 let ps2 = serde_json::from_str(json.as_str()).unwrap();
1597 assert_eq!(ps, ps2);
1598
1599 let ps3: ProtoStatuses = serde_json::from_str(
1600 r#"{
1601 "client":{
1602 "required":"Link=5 LinkAuth=3",
1603 "recommended":"Link=1-5 LinkAuth=2-5"
1604 },
1605 "relay":{
1606 "required":"Wombat=20-22 Knish=25-27",
1607 "recommended":"Wombat=20-30 Knish=20-30"
1608 }
1609 }"#,
1610 )
1611 .unwrap();
1612 assert_eq!(ps, ps3);
1613 }
1614}