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, Header;
91}
92
93use void::ResultVoidExt as _;
94
95#[deprecated]
96#[cfg(feature = "ns_consensus")]
97pub use PlainConsensus as NsConsensus;
98#[deprecated]
99#[cfg(feature = "ns_consensus")]
100pub use PlainRouterStatus as NsRouterStatus;
101#[deprecated]
102#[cfg(feature = "ns_consensus")]
103pub use UncheckedPlainConsensus as UncheckedNsConsensus;
104#[deprecated]
105#[cfg(feature = "ns_consensus")]
106pub use UnvalidatedPlainConsensus as UnvalidatedNsConsensus;
107
108#[derive(Clone, Debug)]
114pub struct Lifetime {
115 valid_after: time::SystemTime,
117 fresh_until: time::SystemTime,
120 valid_until: time::SystemTime,
125}
126
127impl Lifetime {
128 pub fn new(
130 valid_after: time::SystemTime,
131 fresh_until: time::SystemTime,
132 valid_until: time::SystemTime,
133 ) -> Result<Self> {
134 if valid_after < fresh_until && fresh_until < valid_until {
135 Ok(Lifetime {
136 valid_after,
137 fresh_until,
138 valid_until,
139 })
140 } else {
141 Err(EK::InvalidLifetime.err())
142 }
143 }
144 pub fn valid_after(&self) -> time::SystemTime {
149 self.valid_after
150 }
151 pub fn fresh_until(&self) -> time::SystemTime {
156 self.fresh_until
157 }
158 pub fn valid_until(&self) -> time::SystemTime {
164 self.valid_until
165 }
166 pub fn valid_at(&self, when: time::SystemTime) -> bool {
168 self.valid_after <= when && when <= self.valid_until
169 }
170
171 pub fn voting_period(&self) -> time::Duration {
176 let valid_after = self.valid_after();
177 let fresh_until = self.fresh_until();
178 fresh_until
179 .duration_since(valid_after)
180 .expect("Mis-formed lifetime")
181 }
182}
183
184#[derive(Debug, Clone, Default, Eq, PartialEq)]
193pub struct NetParams<T> {
194 params: HashMap<String, T>,
196}
197
198impl<T> NetParams<T> {
199 #[allow(unused)]
201 pub fn new() -> Self {
202 NetParams {
203 params: HashMap::new(),
204 }
205 }
206 pub fn get<A: AsRef<str>>(&self, v: A) -> Option<&T> {
208 self.params.get(v.as_ref())
209 }
210 pub fn iter(&self) -> impl Iterator<Item = (&String, &T)> {
212 self.params.iter()
213 }
214 pub fn set(&mut self, k: String, v: T) {
216 self.params.insert(k, v);
217 }
218}
219
220impl<K: Into<String>, T> FromIterator<(K, T)> for NetParams<T> {
221 fn from_iter<I: IntoIterator<Item = (K, T)>>(i: I) -> Self {
222 NetParams {
223 params: i.into_iter().map(|(k, v)| (k.into(), v)).collect(),
224 }
225 }
226}
227
228impl<T> std::iter::Extend<(String, T)> for NetParams<T> {
229 fn extend<I: IntoIterator<Item = (String, T)>>(&mut self, iter: I) {
230 self.params.extend(iter);
231 }
232}
233
234impl<'de, T> Deserialize<'de> for NetParams<T>
235where
236 T: Deserialize<'de>,
237{
238 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
239 where
240 D: Deserializer<'de>,
241 {
242 let params = HashMap::deserialize(deserializer)?;
243 Ok(NetParams { params })
244 }
245}
246
247#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
251pub struct ProtoStatus {
252 recommended: Protocols,
255 required: Protocols,
258}
259
260impl ProtoStatus {
261 pub fn check_protocols(
271 &self,
272 supported_protocols: &Protocols,
273 ) -> StdResult<(), ProtocolSupportError> {
274 let missing_required = self.required.difference(supported_protocols);
276 if !missing_required.is_empty() {
277 return Err(ProtocolSupportError::MissingRequired(missing_required));
278 }
279 let missing_recommended = self.recommended.difference(supported_protocols);
280 if !missing_recommended.is_empty() {
281 return Err(ProtocolSupportError::MissingRecommended(
282 missing_recommended,
283 ));
284 }
285
286 Ok(())
287 }
288}
289
290#[derive(Clone, Debug, thiserror::Error)]
292#[cfg_attr(test, derive(PartialEq))]
293#[non_exhaustive]
294pub enum ProtocolSupportError {
295 #[error("Required protocols are not implemented: {0}")]
297 MissingRequired(Protocols),
298
299 #[error("Recommended protocols are not implemented: {0}")]
303 MissingRecommended(Protocols),
304}
305
306impl ProtocolSupportError {
307 pub fn should_shutdown(&self) -> bool {
309 matches!(self, Self::MissingRequired(_))
310 }
311}
312
313impl HasKind for ProtocolSupportError {
314 fn kind(&self) -> tor_error::ErrorKind {
315 tor_error::ErrorKind::SoftwareDeprecated
316 }
317}
318
319#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
322pub struct ProtoStatuses {
323 client: ProtoStatus,
325 relay: ProtoStatus,
327}
328
329impl ProtoStatuses {
330 pub fn client(&self) -> &ProtoStatus {
332 &self.client
333 }
334
335 pub fn relay(&self) -> &ProtoStatus {
337 &self.relay
338 }
339}
340
341#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
343#[non_exhaustive]
344pub enum ConsensusFlavor {
345 Microdesc,
348 Plain,
353}
354
355impl ConsensusFlavor {
356 pub fn name(&self) -> &'static str {
358 match self {
359 ConsensusFlavor::Plain => "ns", ConsensusFlavor::Microdesc => "microdesc",
361 }
362 }
363 pub fn from_opt_name(name: Option<&str>) -> Result<Self> {
368 match name {
369 Some("microdesc") => Ok(ConsensusFlavor::Microdesc),
370 Some("ns") | None => Ok(ConsensusFlavor::Plain),
371 Some(other) => {
372 Err(EK::BadDocumentType.with_msg(format!("unrecognized flavor {:?}", other)))
373 }
374 }
375 }
376}
377
378#[derive(Debug, Clone)]
380#[non_exhaustive]
381pub struct Signature {
382 pub digestname: String,
387 pub key_ids: AuthCertKeyIds,
390 pub signature: Vec<u8>,
392}
393
394#[derive(Debug, Clone)]
396#[non_exhaustive]
397pub struct SignatureGroup {
398 pub sha256: Option<[u8; 32]>,
400 pub sha1: Option<[u8; 20]>,
402 pub signatures: Vec<Signature>,
404}
405
406#[derive(
408 Debug, Clone, Copy, Eq, PartialEq, derive_more::From, derive_more::Into, derive_more::AsRef,
409)]
410pub struct SharedRandVal([u8; 32]);
412
413#[derive(Debug, Clone)]
416#[non_exhaustive]
417pub struct SharedRandStatus {
418 pub n_reveals: u8,
420 pub value: SharedRandVal,
427
428 pub timestamp: Option<time::SystemTime>,
432}
433
434#[derive(Debug, Clone)]
438#[non_exhaustive]
439pub struct DirSource {
440 pub nickname: String,
442 pub identity: RsaIdentity,
448 pub ip: net::IpAddr,
450 pub dir_port: u16,
452 pub or_port: u16,
454}
455
456bitflags! {
457 #[derive(Clone, Copy, Debug)]
468 pub struct RelayFlags: u16 {
469 const AUTHORITY = (1<<0);
471 const BAD_EXIT = (1<<1);
476 const EXIT = (1<<2);
478 const FAST = (1<<3);
480 const GUARD = (1<<4);
485 const HSDIR = (1<<5);
488 const MIDDLE_ONLY = (1<<6);
495 const NO_ED_CONSENSUS = (1<<7);
497 const STABLE = (1<<8);
499 const STALE_DESC = (1<<9);
502 const RUNNING = (1<<10);
507 const VALID = (1<<11);
513 const V2DIR = (1<<12);
516 }
517}
518
519#[non_exhaustive]
521#[derive(Debug, Clone, Copy)]
522pub enum RelayWeight {
523 Unmeasured(u32),
525 Measured(u32),
527}
528
529impl RelayWeight {
530 pub fn is_measured(&self) -> bool {
532 matches!(self, RelayWeight::Measured(_))
533 }
534 pub fn is_nonzero(&self) -> bool {
536 !matches!(self, RelayWeight::Unmeasured(0) | RelayWeight::Measured(0))
537 }
538}
539
540#[derive(Debug, Clone)]
542#[non_exhaustive]
543pub struct ConsensusVoterInfo {
544 pub dir_source: DirSource,
546 pub contact: String,
548 pub vote_digest: Vec<u8>,
551}
552
553#[derive(Debug, Clone)]
555#[non_exhaustive]
556pub struct Footer {
557 pub weights: NetParams<i32>,
563}
564
565pub type MdConsensus = md::Consensus;
568
569pub type UnvalidatedMdConsensus = md::UnvalidatedConsensus;
572
573pub type UncheckedMdConsensus = md::UncheckedConsensus;
576
577#[cfg(feature = "plain-consensus")]
578pub type PlainConsensus = plain::Consensus;
581
582#[cfg(feature = "plain-consensus")]
583pub type UnvalidatedPlainConsensus = plain::UnvalidatedConsensus;
586
587#[cfg(feature = "plain-consensus")]
588pub type UncheckedPlainConsensus = plain::UncheckedConsensus;
591
592decl_keyword! {
593 #[non_exhaustive]
598 #[allow(missing_docs)]
599 pub NetstatusKwd {
600 "network-status-version" => NETWORK_STATUS_VERSION,
602 "vote-status" => VOTE_STATUS,
603 "consensus-methods" => CONSENSUS_METHODS,
604 "consensus-method" => CONSENSUS_METHOD,
605 "published" => PUBLISHED,
606 "valid-after" => VALID_AFTER,
607 "fresh-until" => FRESH_UNTIL,
608 "valid-until" => VALID_UNTIL,
609 "voting-delay" => VOTING_DELAY,
610 "client-versions" => CLIENT_VERSIONS,
611 "server-versions" => SERVER_VERSIONS,
612 "known-flags" => KNOWN_FLAGS,
613 "flag-thresholds" => FLAG_THRESHOLDS,
614 "recommended-client-protocols" => RECOMMENDED_CLIENT_PROTOCOLS,
615 "required-client-protocols" => REQUIRED_CLIENT_PROTOCOLS,
616 "recommended-relay-protocols" => RECOMMENDED_RELAY_PROTOCOLS,
617 "required-relay-protocols" => REQUIRED_RELAY_PROTOCOLS,
618 "params" => PARAMS,
619 "bandwidth-file-headers" => BANDWIDTH_FILE_HEADERS,
620 "bandwidth-file-digest" => BANDWIDTH_FILE_DIGEST,
621 "shared-rand-previous-value" => SHARED_RAND_PREVIOUS_VALUE,
625 "shared-rand-current-value" => SHARED_RAND_CURRENT_VALUE,
626
627 "dir-source" => DIR_SOURCE,
629 "contact" => CONTACT,
630
631 "legacy-dir-key" => LEGACY_DIR_KEY,
633 "shared-rand-participate" => SHARED_RAND_PARTICIPATE,
634 "shared-rand-commit" => SHARED_RAND_COMMIT,
635
636 "vote-digest" => VOTE_DIGEST,
638
639 "dir-key-certificate-version" => DIR_KEY_CERTIFICATE_VERSION,
641
642 "r" => RS_R,
644 "a" => RS_A,
645 "s" => RS_S,
646 "v" => RS_V,
647 "pr" => RS_PR,
648 "w" => RS_W,
649 "p" => RS_P,
650 "m" => RS_M,
651 "id" => RS_ID,
652
653 "directory-footer" => DIRECTORY_FOOTER,
655 "bandwidth-weights" => BANDWIDTH_WEIGHTS,
656 "directory-signature" => DIRECTORY_SIGNATURE,
657 }
658}
659
660static NS_HEADER_RULES_COMMON_: LazyLock<SectionRulesBuilder<NetstatusKwd>> = LazyLock::new(|| {
662 use NetstatusKwd::*;
663 let mut rules = SectionRules::builder();
664 rules.add(NETWORK_STATUS_VERSION.rule().required().args(1..=2));
665 rules.add(VOTE_STATUS.rule().required().args(1..));
666 rules.add(VALID_AFTER.rule().required());
667 rules.add(FRESH_UNTIL.rule().required());
668 rules.add(VALID_UNTIL.rule().required());
669 rules.add(VOTING_DELAY.rule().args(2..));
670 rules.add(CLIENT_VERSIONS.rule());
671 rules.add(SERVER_VERSIONS.rule());
672 rules.add(KNOWN_FLAGS.rule().required());
673 rules.add(RECOMMENDED_CLIENT_PROTOCOLS.rule().args(1..));
674 rules.add(RECOMMENDED_RELAY_PROTOCOLS.rule().args(1..));
675 rules.add(REQUIRED_CLIENT_PROTOCOLS.rule().args(1..));
676 rules.add(REQUIRED_RELAY_PROTOCOLS.rule().args(1..));
677 rules.add(PARAMS.rule());
678 rules
679});
680static NS_HEADER_RULES_CONSENSUS: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
682 use NetstatusKwd::*;
683 let mut rules = NS_HEADER_RULES_COMMON_.clone();
684 rules.add(CONSENSUS_METHOD.rule().args(1..=1));
685 rules.add(SHARED_RAND_PREVIOUS_VALUE.rule().args(2..));
686 rules.add(SHARED_RAND_CURRENT_VALUE.rule().args(2..));
687 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
688 rules.build()
689});
690static NS_VOTERINFO_RULES_CONSENSUS: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
721 use NetstatusKwd::*;
722 let mut rules = SectionRules::builder();
723 rules.add(DIR_SOURCE.rule().required().args(6..));
724 rules.add(CONTACT.rule().required());
725 rules.add(VOTE_DIGEST.rule().required());
726 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
727 rules.build()
728});
729static NS_ROUTERSTATUS_RULES_COMMON_: LazyLock<SectionRulesBuilder<NetstatusKwd>> =
731 LazyLock::new(|| {
732 use NetstatusKwd::*;
733 let mut rules = SectionRules::builder();
734 rules.add(RS_A.rule().may_repeat().args(1..));
735 rules.add(RS_S.rule().required());
736 rules.add(RS_V.rule());
737 rules.add(RS_PR.rule().required());
738 rules.add(RS_W.rule());
739 rules.add(RS_P.rule().args(2..));
740 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
741 rules
742 });
743
744static NS_ROUTERSTATUS_RULES_PLAIN: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
746 use NetstatusKwd::*;
747 let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
748 rules.add(RS_R.rule().required().args(8..));
749 rules.build()
750});
751
752static NS_ROUTERSTATUS_RULES_MDCON: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
765 use NetstatusKwd::*;
766 let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
767 rules.add(RS_R.rule().required().args(6..));
768 rules.add(RS_M.rule().required().args(1..));
769 rules.build()
770});
771static NS_FOOTER_RULES: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
773 use NetstatusKwd::*;
774 let mut rules = SectionRules::builder();
775 rules.add(DIRECTORY_FOOTER.rule().required().no_args());
776 rules.add(BANDWIDTH_WEIGHTS.rule());
778 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
779 rules.build()
780});
781
782impl ProtoStatus {
783 fn from_section(
785 sec: &Section<'_, NetstatusKwd>,
786 recommend_token: NetstatusKwd,
787 required_token: NetstatusKwd,
788 ) -> Result<ProtoStatus> {
789 fn parse(t: Option<&Item<'_, NetstatusKwd>>) -> Result<Protocols> {
791 if let Some(item) = t {
792 item.args_as_str()
793 .parse::<Protocols>()
794 .map_err(|e| EK::BadArgument.at_pos(item.pos()).with_source(e))
795 } else {
796 Ok(Protocols::new())
797 }
798 }
799
800 let recommended = parse(sec.get(recommend_token))?;
801 let required = parse(sec.get(required_token))?;
802 Ok(ProtoStatus {
803 recommended,
804 required,
805 })
806 }
807
808 pub fn required_protocols(&self) -> &Protocols {
815 &self.required
816 }
817
818 pub fn recommended_protocols(&self) -> &Protocols {
823 &self.recommended
824 }
825}
826
827impl<T> std::str::FromStr for NetParams<T>
828where
829 T: std::str::FromStr,
830 T::Err: std::error::Error,
831{
832 type Err = Error;
833 fn from_str(s: &str) -> Result<Self> {
834 fn parse_pair<U>(p: &str) -> Result<(String, U)>
836 where
837 U: std::str::FromStr,
838 U::Err: std::error::Error,
839 {
840 let parts: Vec<_> = p.splitn(2, '=').collect();
841 if parts.len() != 2 {
842 return Err(EK::BadArgument
843 .at_pos(Pos::at(p))
844 .with_msg("Missing = in key=value list"));
845 }
846 let num = parts[1].parse::<U>().map_err(|e| {
847 EK::BadArgument
848 .at_pos(Pos::at(parts[1]))
849 .with_msg(e.to_string())
850 })?;
851 Ok((parts[0].to_string(), num))
852 }
853
854 let params = s
855 .split(' ')
856 .filter(|p| !p.is_empty())
857 .map(parse_pair)
858 .collect::<Result<HashMap<_, _>>>()?;
859 Ok(NetParams { params })
860 }
861}
862
863impl SharedRandStatus {
864 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
867 match item.kwd() {
868 NetstatusKwd::SHARED_RAND_PREVIOUS_VALUE | NetstatusKwd::SHARED_RAND_CURRENT_VALUE => {}
869 _ => {
870 return Err(Error::from(internal!(
871 "wrong keyword {:?} on shared-random value",
872 item.kwd()
873 ))
874 .at_pos(item.pos()));
875 }
876 }
877 let n_reveals: u8 = item.parse_arg(0)?;
878 let val: B64 = item.parse_arg(1)?;
879 let value = SharedRandVal(val.into_array()?);
880 let timestamp = item
882 .parse_optional_arg::<Iso8601TimeNoSp>(2)?
883 .map(Into::into);
884 Ok(SharedRandStatus {
885 n_reveals,
886 value,
887 timestamp,
888 })
889 }
890
891 pub fn value(&self) -> &SharedRandVal {
893 &self.value
894 }
895
896 pub fn timestamp(&self) -> Option<std::time::SystemTime> {
898 self.timestamp
899 }
900}
901
902impl DirSource {
903 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
905 if item.kwd() != NetstatusKwd::DIR_SOURCE {
906 return Err(
907 Error::from(internal!("Bad keyword {:?} on dir-source", item.kwd()))
908 .at_pos(item.pos()),
909 );
910 }
911 let nickname = item.required_arg(0)?.to_string();
912 let identity = item.parse_arg::<Fingerprint>(1)?.into();
913 let ip = item.parse_arg(3)?;
914 let dir_port = item.parse_arg(4)?;
915 let or_port = item.parse_arg(5)?;
916
917 Ok(DirSource {
918 nickname,
919 identity,
920 ip,
921 dir_port,
922 or_port,
923 })
924 }
925}
926
927impl ConsensusVoterInfo {
928 fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<ConsensusVoterInfo> {
930 use NetstatusKwd::*;
931 #[allow(clippy::unwrap_used)]
934 let first = sec.first_item().unwrap();
935 if first.kwd() != DIR_SOURCE {
936 return Err(Error::from(internal!(
937 "Wrong keyword {:?} at start of voter info",
938 first.kwd()
939 ))
940 .at_pos(first.pos()));
941 }
942 let dir_source = DirSource::from_item(sec.required(DIR_SOURCE)?)?;
943
944 let contact = sec.required(CONTACT)?.args_as_str().to_string();
945
946 let vote_digest = sec.required(VOTE_DIGEST)?.parse_arg::<B16>(0)?.into();
947
948 Ok(ConsensusVoterInfo {
949 dir_source,
950 contact,
951 vote_digest,
952 })
953 }
954}
955
956impl std::str::FromStr for RelayFlags {
957 type Err = void::Void;
958 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
959 Ok(match s {
960 "Authority" => RelayFlags::AUTHORITY,
961 "BadExit" => RelayFlags::BAD_EXIT,
962 "Exit" => RelayFlags::EXIT,
963 "Fast" => RelayFlags::FAST,
964 "Guard" => RelayFlags::GUARD,
965 "HSDir" => RelayFlags::HSDIR,
966 "MiddleOnly" => RelayFlags::MIDDLE_ONLY,
967 "NoEdConsensus" => RelayFlags::NO_ED_CONSENSUS,
968 "Stable" => RelayFlags::STABLE,
969 "StaleDesc" => RelayFlags::STALE_DESC,
970 "Running" => RelayFlags::RUNNING,
971 "Valid" => RelayFlags::VALID,
972 "V2Dir" => RelayFlags::V2DIR,
973 _ => RelayFlags::empty(),
974 })
975 }
976}
977
978impl RelayFlags {
979 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<RelayFlags> {
981 if item.kwd() != NetstatusKwd::RS_S {
982 return Err(
983 Error::from(internal!("Wrong keyword {:?} for S line", item.kwd()))
984 .at_pos(item.pos()),
985 );
986 }
987 let mut flags: RelayFlags = RelayFlags::RUNNING | RelayFlags::VALID;
989
990 let mut prev: Option<&str> = None;
991 for s in item.args() {
992 if let Some(p) = prev {
993 if p >= s {
994 return Err(EK::BadArgument
996 .at_pos(item.pos())
997 .with_msg("Flags out of order"));
998 }
999 }
1000 let fl = s.parse().void_unwrap();
1001 flags |= fl;
1002 prev = Some(s);
1003 }
1004
1005 Ok(flags)
1006 }
1007}
1008
1009impl Default for RelayWeight {
1010 fn default() -> RelayWeight {
1011 RelayWeight::Unmeasured(0)
1012 }
1013}
1014
1015impl RelayWeight {
1016 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<RelayWeight> {
1018 if item.kwd() != NetstatusKwd::RS_W {
1019 return Err(
1020 Error::from(internal!("Wrong keyword {:?} on W line", item.kwd()))
1021 .at_pos(item.pos()),
1022 );
1023 }
1024
1025 let params: NetParams<u32> = item.args_as_str().parse()?;
1026
1027 let bw = params.params.get("Bandwidth");
1028 let unmeas = params.params.get("Unmeasured");
1029
1030 let bw = match bw {
1031 None => return Ok(RelayWeight::Unmeasured(0)),
1032 Some(b) => *b,
1033 };
1034
1035 match unmeas {
1036 None | Some(0) => Ok(RelayWeight::Measured(bw)),
1037 Some(1) => Ok(RelayWeight::Unmeasured(bw)),
1038 _ => Err(EK::BadArgument
1039 .at_pos(item.pos())
1040 .with_msg("unmeasured value")),
1041 }
1042 }
1043}
1044
1045impl Footer {
1046 fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<Footer> {
1048 use NetstatusKwd::*;
1049 sec.required(DIRECTORY_FOOTER)?;
1050
1051 let weights = sec
1052 .maybe(BANDWIDTH_WEIGHTS)
1053 .args_as_str()
1054 .unwrap_or("")
1055 .parse()?;
1056
1057 Ok(Footer { weights })
1058 }
1059}
1060
1061enum SigCheckResult {
1063 Valid,
1065 Invalid,
1068 MissingCert,
1071}
1072
1073impl Signature {
1074 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Signature> {
1076 if item.kwd() != NetstatusKwd::DIRECTORY_SIGNATURE {
1077 return Err(Error::from(internal!(
1078 "Wrong keyword {:?} for directory signature",
1079 item.kwd()
1080 ))
1081 .at_pos(item.pos()));
1082 }
1083
1084 let (alg, id_fp, sk_fp) = if item.n_args() > 2 {
1085 (
1086 item.required_arg(0)?,
1087 item.required_arg(1)?,
1088 item.required_arg(2)?,
1089 )
1090 } else {
1091 ("sha1", item.required_arg(0)?, item.required_arg(1)?)
1092 };
1093
1094 let digestname = alg.to_string();
1095 let id_fingerprint = id_fp.parse::<Fingerprint>()?.into();
1096 let sk_fingerprint = sk_fp.parse::<Fingerprint>()?.into();
1097 let key_ids = AuthCertKeyIds {
1098 id_fingerprint,
1099 sk_fingerprint,
1100 };
1101 let signature = item.obj("SIGNATURE")?;
1102
1103 Ok(Signature {
1104 digestname,
1105 key_ids,
1106 signature,
1107 })
1108 }
1109
1110 fn matches_cert(&self, cert: &AuthCert) -> bool {
1113 cert.key_ids() == &self.key_ids
1114 }
1115
1116 fn find_cert<'a>(&self, certs: &'a [AuthCert]) -> Option<&'a AuthCert> {
1119 certs.iter().find(|&c| self.matches_cert(c))
1120 }
1121
1122 fn check_signature(&self, signed_digest: &[u8], certs: &[AuthCert]) -> SigCheckResult {
1126 match self.find_cert(certs) {
1127 None => SigCheckResult::MissingCert,
1128 Some(cert) => {
1129 let key = cert.signing_key();
1130 match key.verify(signed_digest, &self.signature[..]) {
1131 Ok(()) => SigCheckResult::Valid,
1132 Err(_) => SigCheckResult::Invalid,
1133 }
1134 }
1135 }
1136 }
1137}
1138
1139impl SignatureGroup {
1140 fn list_missing(&self, certs: &[AuthCert]) -> (usize, Vec<&Signature>) {
1147 let mut ok: HashSet<RsaIdentity> = HashSet::new();
1148 let mut missing = Vec::new();
1149 for sig in &self.signatures {
1150 let id_fingerprint = &sig.key_ids.id_fingerprint;
1151 if ok.contains(id_fingerprint) {
1152 continue;
1153 }
1154 if sig.find_cert(certs).is_some() {
1155 ok.insert(*id_fingerprint);
1156 continue;
1157 }
1158
1159 missing.push(sig);
1160 }
1161 (ok.len(), missing)
1162 }
1163
1164 fn could_validate(&self, authorities: &[&RsaIdentity]) -> bool {
1168 let mut signed_by: HashSet<RsaIdentity> = HashSet::new();
1169 for sig in &self.signatures {
1170 let id_fp = &sig.key_ids.id_fingerprint;
1171 if signed_by.contains(id_fp) {
1172 continue;
1174 }
1175 if authorities.contains(&id_fp) {
1176 signed_by.insert(*id_fp);
1177 }
1178 }
1179
1180 signed_by.len() > (authorities.len() / 2)
1181 }
1182
1183 fn validate(&self, n_authorities: u16, certs: &[AuthCert]) -> bool {
1190 let mut ok: HashSet<RsaIdentity> = HashSet::new();
1194
1195 for sig in &self.signatures {
1196 let id_fingerprint = &sig.key_ids.id_fingerprint;
1197 if ok.contains(id_fingerprint) {
1198 continue;
1201 }
1202
1203 let d: Option<&[u8]> = match sig.digestname.as_ref() {
1204 "sha256" => self.sha256.as_ref().map(|a| &a[..]),
1205 "sha1" => self.sha1.as_ref().map(|a| &a[..]),
1206 _ => None, };
1208 if d.is_none() {
1209 continue;
1212 }
1213
1214 #[allow(clippy::unwrap_used)]
1216 match sig.check_signature(d.as_ref().unwrap(), certs) {
1217 SigCheckResult::Valid => {
1218 ok.insert(*id_fingerprint);
1219 }
1220 _ => continue,
1221 }
1222 }
1223
1224 ok.len() > (n_authorities / 2) as usize
1225 }
1226}
1227
1228#[cfg(test)]
1229mod test {
1230 #![allow(clippy::bool_assert_comparison)]
1232 #![allow(clippy::clone_on_copy)]
1233 #![allow(clippy::dbg_macro)]
1234 #![allow(clippy::mixed_attributes_style)]
1235 #![allow(clippy::print_stderr)]
1236 #![allow(clippy::print_stdout)]
1237 #![allow(clippy::single_char_pattern)]
1238 #![allow(clippy::unwrap_used)]
1239 #![allow(clippy::unchecked_duration_subtraction)]
1240 #![allow(clippy::useless_vec)]
1241 #![allow(clippy::needless_pass_by_value)]
1242 use super::*;
1244 use hex_literal::hex;
1245
1246 const CERTS: &str = include_str!("../../testdata/authcerts2.txt");
1247 const CONSENSUS: &str = include_str!("../../testdata/mdconsensus1.txt");
1248
1249 #[cfg(feature = "plain-consensus")]
1250 const PLAIN_CERTS: &str = include_str!("../../testdata2/cached-certs");
1251 #[cfg(feature = "plain-consensus")]
1252 const PLAIN_CONSENSUS: &str = include_str!("../../testdata2/cached-consensus");
1253
1254 fn read_bad(fname: &str) -> String {
1255 use std::fs;
1256 use std::path::PathBuf;
1257 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1258 path.push("testdata");
1259 path.push("bad-mdconsensus");
1260 path.push(fname);
1261
1262 fs::read_to_string(path).unwrap()
1263 }
1264
1265 #[test]
1266 fn parse_and_validate_md() -> Result<()> {
1267 use std::net::SocketAddr;
1268 use tor_checkable::{SelfSigned, Timebound};
1269 let mut certs = Vec::new();
1270 for cert in AuthCert::parse_multiple(CERTS)? {
1271 let cert = cert?.check_signature()?.dangerously_assume_timely();
1272 certs.push(cert);
1273 }
1274 let auth_ids: Vec<_> = certs.iter().map(|c| &c.key_ids().id_fingerprint).collect();
1275
1276 assert_eq!(certs.len(), 3);
1277
1278 let (_, _, consensus) = MdConsensus::parse(CONSENSUS)?;
1279 let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
1280
1281 assert!(consensus.authorities_are_correct(&auth_ids));
1283 assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
1285 {
1286 let bad_auth_id = (*b"xxxxxxxxxxxxxxxxxxxx").into();
1289 assert!(!consensus.authorities_are_correct(&[&bad_auth_id]));
1290 }
1291
1292 let missing = consensus.key_is_correct(&[]).err().unwrap();
1293 assert_eq!(3, missing.len());
1294 assert!(consensus.key_is_correct(&certs).is_ok());
1295 let missing = consensus.key_is_correct(&certs[0..1]).err().unwrap();
1296 assert_eq!(2, missing.len());
1297
1298 let same_three_times = vec![certs[0].clone(), certs[0].clone(), certs[0].clone()];
1300 let missing = consensus.key_is_correct(&same_three_times).err().unwrap();
1301
1302 assert_eq!(2, missing.len());
1303 assert!(consensus.is_well_signed(&same_three_times).is_err());
1304
1305 assert!(consensus.key_is_correct(&certs).is_ok());
1306 let consensus = consensus.check_signature(&certs)?;
1307
1308 assert_eq!(6, consensus.relays().len());
1309 let r0 = &consensus.relays()[0];
1310 assert_eq!(
1311 r0.md_digest(),
1312 &hex!("73dabe0a0468f4f7a67810a18d11e36731bb1d2ec3634db459100609f3b3f535")
1313 );
1314 assert_eq!(
1315 r0.rsa_identity().as_bytes(),
1316 &hex!("0a3057af2910415794d8ea430309d9ac5f5d524b")
1317 );
1318 assert!(!r0.weight().is_measured());
1319 assert!(!r0.weight().is_nonzero());
1320 let pv = &r0.protovers();
1321 assert!(pv.supports_subver("HSDir", 2));
1322 assert!(!pv.supports_subver("HSDir", 3));
1323 let ip4 = "127.0.0.1:5002".parse::<SocketAddr>().unwrap();
1324 let ip6 = "[::1]:5002".parse::<SocketAddr>().unwrap();
1325 assert!(r0.orport_addrs().any(|a| a == &ip4));
1326 assert!(r0.orport_addrs().any(|a| a == &ip6));
1327
1328 Ok(())
1329 }
1330
1331 #[test]
1332 #[cfg(feature = "plain-consensus")]
1333 fn parse_and_validate_ns() -> Result<()> {
1334 use tor_checkable::{SelfSigned, Timebound};
1335 let mut certs = Vec::new();
1336 for cert in AuthCert::parse_multiple(PLAIN_CERTS)? {
1337 let cert = cert?.check_signature()?.dangerously_assume_timely();
1338 certs.push(cert);
1339 }
1340 let auth_ids: Vec<_> = certs.iter().map(|c| &c.key_ids().id_fingerprint).collect();
1341 assert_eq!(certs.len(), 4);
1342
1343 let (_, _, consensus) = PlainConsensus::parse(PLAIN_CONSENSUS)?;
1344 let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
1345 assert!(consensus.authorities_are_correct(&auth_ids));
1347 assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
1349
1350 assert!(consensus.key_is_correct(&certs).is_ok());
1351
1352 let _consensus = consensus.check_signature(&certs)?;
1353
1354 Ok(())
1355 }
1356
1357 #[test]
1358 fn test_bad() {
1359 use crate::Pos;
1360 fn check(fname: &str, e: &Error) {
1361 let content = read_bad(fname);
1362 let res = MdConsensus::parse(&content);
1363 assert!(res.is_err());
1364 assert_eq!(&res.err().unwrap(), e);
1365 }
1366
1367 check(
1368 "bad-flags",
1369 &EK::BadArgument
1370 .at_pos(Pos::from_line(27, 1))
1371 .with_msg("Flags out of order"),
1372 );
1373 check(
1374 "bad-md-digest",
1375 &EK::BadArgument
1376 .at_pos(Pos::from_line(40, 3))
1377 .with_msg("Invalid base64"),
1378 );
1379 check(
1380 "bad-weight",
1381 &EK::BadArgument
1382 .at_pos(Pos::from_line(67, 141))
1383 .with_msg("invalid digit found in string"),
1384 );
1385 check(
1386 "bad-weights",
1387 &EK::BadArgument
1388 .at_pos(Pos::from_line(51, 13))
1389 .with_msg("invalid digit found in string"),
1390 );
1391 check(
1392 "wrong-order",
1393 &EK::WrongSortOrder.at_pos(Pos::from_line(52, 1)),
1394 );
1395 check(
1396 "wrong-start",
1397 &EK::UnexpectedToken
1398 .with_msg("vote-status")
1399 .at_pos(Pos::from_line(1, 1)),
1400 );
1401 check("wrong-version", &EK::BadDocumentVersion.with_msg("10"));
1402 }
1403
1404 fn gettok(s: &str) -> Result<Item<'_, NetstatusKwd>> {
1405 let mut reader = NetDocReader::new(s)?;
1406 let tok = reader.next().unwrap();
1407 assert!(reader.next().is_none());
1408 tok
1409 }
1410
1411 #[test]
1412 fn test_weight() {
1413 let w = gettok("w Unmeasured=1 Bandwidth=6\n").unwrap();
1414 let w = RelayWeight::from_item(&w).unwrap();
1415 assert!(!w.is_measured());
1416 assert!(w.is_nonzero());
1417
1418 let w = gettok("w Bandwidth=10\n").unwrap();
1419 let w = RelayWeight::from_item(&w).unwrap();
1420 assert!(w.is_measured());
1421 assert!(w.is_nonzero());
1422
1423 let w = RelayWeight::default();
1424 assert!(!w.is_measured());
1425 assert!(!w.is_nonzero());
1426
1427 let w = gettok("w Mustelid=66 Cheato=7 Unmeasured=1\n").unwrap();
1428 let w = RelayWeight::from_item(&w).unwrap();
1429 assert!(!w.is_measured());
1430 assert!(!w.is_nonzero());
1431
1432 let w = gettok("r foo\n").unwrap();
1433 let w = RelayWeight::from_item(&w);
1434 assert!(w.is_err());
1435
1436 let w = gettok("r Bandwidth=6 Unmeasured=Frog\n").unwrap();
1437 let w = RelayWeight::from_item(&w);
1438 assert!(w.is_err());
1439
1440 let w = gettok("r Bandwidth=6 Unmeasured=3\n").unwrap();
1441 let w = RelayWeight::from_item(&w);
1442 assert!(w.is_err());
1443 }
1444
1445 #[test]
1446 fn test_netparam() {
1447 let p = "Hello=600 Goodbye=5 Fred=7"
1448 .parse::<NetParams<u32>>()
1449 .unwrap();
1450 assert_eq!(p.get("Hello"), Some(&600_u32));
1451
1452 let p = "Hello=Goodbye=5 Fred=7".parse::<NetParams<u32>>();
1453 assert!(p.is_err());
1454
1455 let p = "Hello=Goodbye Fred=7".parse::<NetParams<u32>>();
1456 assert!(p.is_err());
1457 }
1458
1459 #[test]
1460 fn test_sharedrand() {
1461 let sr =
1462 gettok("shared-rand-previous-value 9 5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4\n")
1463 .unwrap();
1464 let sr = SharedRandStatus::from_item(&sr).unwrap();
1465
1466 assert_eq!(sr.n_reveals, 9);
1467 assert_eq!(
1468 sr.value.0,
1469 hex!("e4ba1d638c96c458532adc6957dc0080d03d37c7e5854087d0da90bf5ff4e72e")
1470 );
1471 assert!(sr.timestamp.is_none());
1472
1473 let sr2 = gettok(
1474 "shared-rand-current-value 9 \
1475 5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4 2022-01-20T12:34:56\n",
1476 )
1477 .unwrap();
1478 let sr2 = SharedRandStatus::from_item(&sr2).unwrap();
1479 assert_eq!(sr2.n_reveals, sr.n_reveals);
1480 assert_eq!(sr2.value.0, sr.value.0);
1481 assert_eq!(
1482 sr2.timestamp.unwrap(),
1483 humantime::parse_rfc3339("2022-01-20T12:34:56Z").unwrap()
1484 );
1485
1486 let sr = gettok("foo bar\n").unwrap();
1487 let sr = SharedRandStatus::from_item(&sr);
1488 assert!(sr.is_err());
1489 }
1490
1491 #[test]
1492 fn test_protostatus() {
1493 let my_protocols: Protocols = "Link=7 Cons=1-5 Desc=3-10".parse().unwrap();
1494
1495 let outcome = ProtoStatus {
1496 recommended: "Link=7".parse().unwrap(),
1497 required: "Desc=5".parse().unwrap(),
1498 }
1499 .check_protocols(&my_protocols);
1500 assert!(outcome.is_ok());
1501
1502 let outcome = ProtoStatus {
1503 recommended: "Microdesc=4 Link=7".parse().unwrap(),
1504 required: "Desc=5".parse().unwrap(),
1505 }
1506 .check_protocols(&my_protocols);
1507 assert_eq!(
1508 outcome,
1509 Err(ProtocolSupportError::MissingRecommended(
1510 "Microdesc=4".parse().unwrap()
1511 ))
1512 );
1513
1514 let outcome = ProtoStatus {
1515 recommended: "Microdesc=4 Link=7".parse().unwrap(),
1516 required: "Desc=5 Cons=5-12 Wombat=15".parse().unwrap(),
1517 }
1518 .check_protocols(&my_protocols);
1519 assert_eq!(
1520 outcome,
1521 Err(ProtocolSupportError::MissingRequired(
1522 "Cons=6-12 Wombat=15".parse().unwrap()
1523 ))
1524 );
1525 }
1526
1527 #[test]
1528 fn serialize_protostatus() {
1529 let ps = ProtoStatuses {
1530 client: ProtoStatus {
1531 recommended: "Link=1-5 LinkAuth=2-5".parse().unwrap(),
1532 required: "Link=5 LinkAuth=3".parse().unwrap(),
1533 },
1534 relay: ProtoStatus {
1535 recommended: "Wombat=20-30 Knish=20-30".parse().unwrap(),
1536 required: "Wombat=20-22 Knish=25-27".parse().unwrap(),
1537 },
1538 };
1539 let json = serde_json::to_string(&ps).unwrap();
1540 let ps2 = serde_json::from_str(json.as_str()).unwrap();
1541 assert_eq!(ps, ps2);
1542
1543 let ps3: ProtoStatuses = serde_json::from_str(
1544 r#"{
1545 "client":{
1546 "required":"Link=5 LinkAuth=3",
1547 "recommended":"Link=1-5 LinkAuth=2-5"
1548 },
1549 "relay":{
1550 "required":"Wombat=20-22 Knish=25-27",
1551 "recommended":"Wombat=20-30 Knish=20-30"
1552 }
1553 }"#,
1554 )
1555 .unwrap();
1556 assert_eq!(ps, ps3);
1557 }
1558}