1mod rs;
47
48#[cfg(feature = "build_docs")]
49mod build;
50
51use crate::doc::authcert::{AuthCert, AuthCertKeyIds};
52use crate::parse::keyword::Keyword;
53use crate::parse::parser::{Section, SectionRules, SectionRulesBuilder};
54use crate::parse::tokenize::{Item, ItemResult, NetDocReader};
55use crate::types::misc::*;
56use crate::util::private::Sealed;
57use crate::util::PeekableIterator;
58use crate::{Error, NetdocErrorKind as EK, Pos, Result};
59use std::collections::{HashMap, HashSet};
60use std::{net, result, time};
61use tor_error::internal;
62use tor_protover::Protocols;
63
64use bitflags::bitflags;
65use digest::Digest;
66use once_cell::sync::Lazy;
67use tor_checkable::{timed::TimerangeBound, ExternallySigned};
68use tor_llcrypto as ll;
69use tor_llcrypto::pk::rsa::RsaIdentity;
70
71use serde::{Deserialize, Deserializer};
72
73#[cfg(feature = "build_docs")]
74pub use build::ConsensusBuilder;
75#[cfg(feature = "build_docs")]
76pub use rs::build::RouterStatusBuilder;
77
78pub use rs::MdConsensusRouterStatus;
79#[cfg(feature = "ns_consensus")]
80pub use rs::NsConsensusRouterStatus;
81use void::ResultVoidExt as _;
82
83#[derive(Clone, Debug)]
89pub struct Lifetime {
90 valid_after: time::SystemTime,
92 fresh_until: time::SystemTime,
95 valid_until: time::SystemTime,
100}
101
102impl Lifetime {
103 pub fn new(
105 valid_after: time::SystemTime,
106 fresh_until: time::SystemTime,
107 valid_until: time::SystemTime,
108 ) -> Result<Self> {
109 if valid_after < fresh_until && fresh_until < valid_until {
110 Ok(Lifetime {
111 valid_after,
112 fresh_until,
113 valid_until,
114 })
115 } else {
116 Err(EK::InvalidLifetime.err())
117 }
118 }
119 pub fn valid_after(&self) -> time::SystemTime {
124 self.valid_after
125 }
126 pub fn fresh_until(&self) -> time::SystemTime {
131 self.fresh_until
132 }
133 pub fn valid_until(&self) -> time::SystemTime {
139 self.valid_until
140 }
141 pub fn valid_at(&self, when: time::SystemTime) -> bool {
143 self.valid_after <= when && when <= self.valid_until
144 }
145
146 pub fn voting_period(&self) -> time::Duration {
151 let valid_after = self.valid_after();
152 let fresh_until = self.fresh_until();
153 fresh_until
154 .duration_since(valid_after)
155 .expect("Mis-formed lifetime")
156 }
157}
158
159#[derive(Debug, Clone, Default, Eq, PartialEq)]
168pub struct NetParams<T> {
169 params: HashMap<String, T>,
171}
172
173impl<T> NetParams<T> {
174 #[allow(unused)]
176 pub fn new() -> Self {
177 NetParams {
178 params: HashMap::new(),
179 }
180 }
181 pub fn get<A: AsRef<str>>(&self, v: A) -> Option<&T> {
183 self.params.get(v.as_ref())
184 }
185 pub fn iter(&self) -> impl Iterator<Item = (&String, &T)> {
187 self.params.iter()
188 }
189 pub fn set(&mut self, k: String, v: T) {
191 self.params.insert(k, v);
192 }
193}
194
195impl<K: Into<String>, T> FromIterator<(K, T)> for NetParams<T> {
196 fn from_iter<I: IntoIterator<Item = (K, T)>>(i: I) -> Self {
197 NetParams {
198 params: i.into_iter().map(|(k, v)| (k.into(), v)).collect(),
199 }
200 }
201}
202
203impl<T> std::iter::Extend<(String, T)> for NetParams<T> {
204 fn extend<I: IntoIterator<Item = (String, T)>>(&mut self, iter: I) {
205 self.params.extend(iter);
206 }
207}
208
209impl<'de, T> Deserialize<'de> for NetParams<T>
210where
211 T: Deserialize<'de>,
212{
213 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
214 where
215 D: Deserializer<'de>,
216 {
217 let params = HashMap::deserialize(deserializer)?;
218 Ok(NetParams { params })
219 }
220}
221
222#[allow(dead_code)]
226#[derive(Debug, Clone, Default)]
227pub struct ProtoStatus {
228 recommended: Protocols,
231 required: Protocols,
234}
235
236#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
238#[non_exhaustive]
239pub enum ConsensusFlavor {
240 Microdesc,
243 Ns,
248}
249
250impl ConsensusFlavor {
251 pub fn name(&self) -> &'static str {
253 match self {
254 ConsensusFlavor::Ns => "ns",
255 ConsensusFlavor::Microdesc => "microdesc",
256 }
257 }
258 pub fn from_opt_name(name: Option<&str>) -> Result<Self> {
263 match name {
264 Some("microdesc") => Ok(ConsensusFlavor::Microdesc),
265 Some("ns") | None => Ok(ConsensusFlavor::Ns),
266 Some(other) => {
267 Err(EK::BadDocumentType.with_msg(format!("unrecognized flavor {:?}", other)))
268 }
269 }
270 }
271}
272
273#[allow(dead_code)]
275#[cfg_attr(
276 feature = "dangerous-expose-struct-fields",
277 visible::StructFields(pub),
278 non_exhaustive
279)]
280#[derive(Debug, Clone)]
281pub struct Signature {
282 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
287 digestname: String,
288 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
291 key_ids: AuthCertKeyIds,
292 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
294 signature: Vec<u8>,
295}
296
297#[allow(dead_code)]
299#[cfg_attr(
300 feature = "dangerous-expose-struct-fields",
301 visible::StructFields(pub),
302 non_exhaustive
303)]
304#[derive(Debug, Clone)]
305pub struct SignatureGroup {
306 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
308 sha256: Option<[u8; 32]>,
309 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
311 sha1: Option<[u8; 20]>,
312 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
314 signatures: Vec<Signature>,
315}
316
317#[derive(
319 Debug, Clone, Copy, Eq, PartialEq, derive_more::From, derive_more::Into, derive_more::AsRef,
320)]
321pub struct SharedRandVal([u8; 32]);
323
324#[allow(dead_code)]
327#[cfg_attr(
328 feature = "dangerous-expose-struct-fields",
329 visible::StructFields(pub),
330 visibility::make(pub),
331 non_exhaustive
332)]
333#[derive(Debug, Clone)]
334pub struct SharedRandStatus {
335 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
337 n_reveals: u8,
338 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
345 value: SharedRandVal,
346
347 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
351 timestamp: Option<time::SystemTime>,
352}
353
354#[allow(dead_code)]
359#[cfg_attr(
360 feature = "dangerous-expose-struct-fields",
361 visible::StructFields(pub),
362 visibility::make(pub),
363 non_exhaustive
364)]
365#[derive(Debug, Clone)]
366struct CommonHeader {
367 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
370 flavor: ConsensusFlavor,
371 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
374 lifetime: Lifetime,
375 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
377 client_versions: Vec<String>,
378 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
380 relay_versions: Vec<String>,
381 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
383 client_protos: ProtoStatus,
384 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
386 relay_protos: ProtoStatus,
387 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
391 params: NetParams<i32>,
392 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
395 voting_delay: Option<(u32, u32)>,
396}
397
398#[allow(dead_code)]
400#[cfg_attr(
401 feature = "dangerous-expose-struct-fields",
402 visible::StructFields(pub),
403 visibility::make(pub),
404 non_exhaustive
405)]
406#[derive(Debug, Clone)]
407struct ConsensusHeader {
408 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
410 hdr: CommonHeader,
411 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
415 consensus_method: u32,
416 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
418 shared_rand_prev: Option<SharedRandStatus>,
419 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
421 shared_rand_cur: Option<SharedRandStatus>,
422}
423
424#[allow(dead_code)]
428#[cfg_attr(
429 feature = "dangerous-expose-struct-fields",
430 visible::StructFields(pub),
431 visibility::make(pub),
432 non_exhaustive
433)]
434#[derive(Debug, Clone)]
435struct DirSource {
436 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
438 nickname: String,
439 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
445 identity: RsaIdentity,
446 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
448 ip: net::IpAddr,
449 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
451 dir_port: u16,
452 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
454 or_port: u16,
455}
456
457bitflags! {
458 #[derive(Clone, Copy, Debug)]
469 pub struct RelayFlags: u16 {
470 const AUTHORITY = (1<<0);
472 const BAD_EXIT = (1<<1);
477 const EXIT = (1<<2);
479 const FAST = (1<<3);
481 const GUARD = (1<<4);
486 const HSDIR = (1<<5);
489 const MIDDLE_ONLY = (1<<6);
496 const NO_ED_CONSENSUS = (1<<7);
498 const STABLE = (1<<8);
500 const STALE_DESC = (1<<9);
503 const RUNNING = (1<<10);
508 const VALID = (1<<11);
514 const V2DIR = (1<<12);
517 }
518}
519
520#[non_exhaustive]
522#[derive(Debug, Clone, Copy)]
523pub enum RelayWeight {
524 Unmeasured(u32),
526 Measured(u32),
528}
529
530impl RelayWeight {
531 pub fn is_measured(&self) -> bool {
533 matches!(self, RelayWeight::Measured(_))
534 }
535 pub fn is_nonzero(&self) -> bool {
537 !matches!(self, RelayWeight::Unmeasured(0) | RelayWeight::Measured(0))
538 }
539}
540
541#[allow(dead_code)]
543#[cfg_attr(
544 feature = "dangerous-expose-struct-fields",
545 visible::StructFields(pub),
546 visibility::make(pub),
547 non_exhaustive
548)]
549#[derive(Debug, Clone)]
550struct ConsensusVoterInfo {
551 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
553 dir_source: DirSource,
554 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
556 contact: String,
557 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
560 vote_digest: Vec<u8>,
561}
562
563#[allow(dead_code)]
565#[cfg_attr(
566 feature = "dangerous-expose-struct-fields",
567 visible::StructFields(pub),
568 visibility::make(pub),
569 non_exhaustive
570)]
571#[derive(Debug, Clone)]
572struct Footer {
573 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
579 weights: NetParams<i32>,
580}
581
582pub trait ParseRouterStatus: Sized + Sealed {
587 fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<Self>;
590
591 fn flavor() -> ConsensusFlavor;
594}
595
596pub trait RouterStatus: Sealed {
600 type DocumentDigest: Clone;
602
603 fn rsa_identity(&self) -> &RsaIdentity;
605
606 fn doc_digest(&self) -> &Self::DocumentDigest;
609}
610
611#[allow(dead_code)]
616#[cfg_attr(
617 feature = "dangerous-expose-struct-fields",
618 visible::StructFields(pub),
619 non_exhaustive
620)]
621#[derive(Debug, Clone)]
622pub struct Consensus<RS> {
623 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
625 header: ConsensusHeader,
626 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
628 voters: Vec<ConsensusVoterInfo>,
629 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
635 relays: Vec<RS>,
636 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
638 footer: Footer,
639}
640
641pub type MdConsensus = Consensus<MdConsensusRouterStatus>;
644
645pub type UnvalidatedMdConsensus = UnvalidatedConsensus<MdConsensusRouterStatus>;
648
649pub type UncheckedMdConsensus = UncheckedConsensus<MdConsensusRouterStatus>;
652
653#[cfg(feature = "ns_consensus")]
654pub type NsConsensus = Consensus<NsConsensusRouterStatus>;
657
658#[cfg(feature = "ns_consensus")]
659pub type UnvalidatedNsConsensus = UnvalidatedConsensus<NsConsensusRouterStatus>;
662
663#[cfg(feature = "ns_consensus")]
664pub type UncheckedNsConsensus = UncheckedConsensus<NsConsensusRouterStatus>;
667
668impl<RS> Consensus<RS> {
669 pub fn lifetime(&self) -> &Lifetime {
671 &self.header.hdr.lifetime
672 }
673
674 pub fn relays(&self) -> &[RS] {
676 &self.relays[..]
677 }
678
679 pub fn bandwidth_weights(&self) -> &NetParams<i32> {
682 &self.footer.weights
683 }
684
685 pub fn params(&self) -> &NetParams<i32> {
687 &self.header.hdr.params
688 }
689
690 pub fn shared_rand_cur(&self) -> Option<&SharedRandStatus> {
693 self.header.shared_rand_cur.as_ref()
694 }
695
696 pub fn shared_rand_prev(&self) -> Option<&SharedRandStatus> {
699 self.header.shared_rand_prev.as_ref()
700 }
701
702 pub fn relay_protocol_status(&self) -> &ProtoStatus {
705 &self.header.hdr.relay_protos
706 }
707
708 pub fn client_protocol_status(&self) -> &ProtoStatus {
711 &self.header.hdr.client_protos
712 }
713}
714
715decl_keyword! {
716 #[non_exhaustive]
721 #[allow(missing_docs)]
722 pub NetstatusKwd {
723 "network-status-version" => NETWORK_STATUS_VERSION,
725 "vote-status" => VOTE_STATUS,
726 "consensus-methods" => CONSENSUS_METHODS,
727 "consensus-method" => CONSENSUS_METHOD,
728 "published" => PUBLISHED,
729 "valid-after" => VALID_AFTER,
730 "fresh-until" => FRESH_UNTIL,
731 "valid-until" => VALID_UNTIL,
732 "voting-delay" => VOTING_DELAY,
733 "client-versions" => CLIENT_VERSIONS,
734 "server-versions" => SERVER_VERSIONS,
735 "known-flags" => KNOWN_FLAGS,
736 "flag-thresholds" => FLAG_THRESHOLDS,
737 "recommended-client-protocols" => RECOMMENDED_CLIENT_PROTOCOLS,
738 "required-client-protocols" => REQUIRED_CLIENT_PROTOCOLS,
739 "recommended-relay-protocols" => RECOMMENDED_RELAY_PROTOCOLS,
740 "required-relay-protocols" => REQUIRED_RELAY_PROTOCOLS,
741 "params" => PARAMS,
742 "bandwidth-file-headers" => BANDWIDTH_FILE_HEADERS,
743 "bandwidth-file-digest" => BANDWIDTH_FILE_DIGEST,
744 "shared-rand-previous-value" => SHARED_RAND_PREVIOUS_VALUE,
748 "shared-rand-current-value" => SHARED_RAND_CURRENT_VALUE,
749
750 "dir-source" => DIR_SOURCE,
752 "contact" => CONTACT,
753
754 "legacy-dir-key" => LEGACY_DIR_KEY,
756 "shared-rand-participate" => SHARED_RAND_PARTICIPATE,
757 "shared-rand-commit" => SHARED_RAND_COMMIT,
758
759 "vote-digest" => VOTE_DIGEST,
761
762 "dir-key-certificate-version" => DIR_KEY_CERTIFICATE_VERSION,
764
765 "r" => RS_R,
767 "a" => RS_A,
768 "s" => RS_S,
769 "v" => RS_V,
770 "pr" => RS_PR,
771 "w" => RS_W,
772 "p" => RS_P,
773 "m" => RS_M,
774 "id" => RS_ID,
775
776 "directory-footer" => DIRECTORY_FOOTER,
778 "bandwidth-weights" => BANDWIDTH_WEIGHTS,
779 "directory-signature" => DIRECTORY_SIGNATURE,
780 }
781}
782
783static NS_HEADER_RULES_COMMON_: Lazy<SectionRulesBuilder<NetstatusKwd>> = Lazy::new(|| {
785 use NetstatusKwd::*;
786 let mut rules = SectionRules::builder();
787 rules.add(NETWORK_STATUS_VERSION.rule().required().args(1..=2));
788 rules.add(VOTE_STATUS.rule().required().args(1..));
789 rules.add(VALID_AFTER.rule().required());
790 rules.add(FRESH_UNTIL.rule().required());
791 rules.add(VALID_UNTIL.rule().required());
792 rules.add(VOTING_DELAY.rule().args(2..));
793 rules.add(CLIENT_VERSIONS.rule());
794 rules.add(SERVER_VERSIONS.rule());
795 rules.add(KNOWN_FLAGS.rule().required());
796 rules.add(RECOMMENDED_CLIENT_PROTOCOLS.rule().args(1..));
797 rules.add(RECOMMENDED_RELAY_PROTOCOLS.rule().args(1..));
798 rules.add(REQUIRED_CLIENT_PROTOCOLS.rule().args(1..));
799 rules.add(REQUIRED_RELAY_PROTOCOLS.rule().args(1..));
800 rules.add(PARAMS.rule());
801 rules
802});
803static NS_HEADER_RULES_CONSENSUS: Lazy<SectionRules<NetstatusKwd>> = Lazy::new(|| {
805 use NetstatusKwd::*;
806 let mut rules = NS_HEADER_RULES_COMMON_.clone();
807 rules.add(CONSENSUS_METHOD.rule().args(1..=1));
808 rules.add(SHARED_RAND_PREVIOUS_VALUE.rule().args(2..));
809 rules.add(SHARED_RAND_CURRENT_VALUE.rule().args(2..));
810 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
811 rules.build()
812});
813static NS_VOTERINFO_RULES_CONSENSUS: Lazy<SectionRules<NetstatusKwd>> = Lazy::new(|| {
844 use NetstatusKwd::*;
845 let mut rules = SectionRules::builder();
846 rules.add(DIR_SOURCE.rule().required().args(6..));
847 rules.add(CONTACT.rule().required());
848 rules.add(VOTE_DIGEST.rule().required());
849 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
850 rules.build()
851});
852static NS_ROUTERSTATUS_RULES_COMMON_: Lazy<SectionRulesBuilder<NetstatusKwd>> = Lazy::new(|| {
854 use NetstatusKwd::*;
855 let mut rules = SectionRules::builder();
856 rules.add(RS_A.rule().may_repeat().args(1..));
857 rules.add(RS_S.rule().required());
858 rules.add(RS_V.rule());
859 rules.add(RS_PR.rule().required());
860 rules.add(RS_W.rule());
861 rules.add(RS_P.rule().args(2..));
862 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
863 rules
864});
865
866static NS_ROUTERSTATUS_RULES_NSCON: Lazy<SectionRules<NetstatusKwd>> = Lazy::new(|| {
868 use NetstatusKwd::*;
869 let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
870 rules.add(RS_R.rule().required().args(8..));
871 rules.build()
872});
873
874static NS_ROUTERSTATUS_RULES_MDCON: Lazy<SectionRules<NetstatusKwd>> = Lazy::new(|| {
887 use NetstatusKwd::*;
888 let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
889 rules.add(RS_R.rule().required().args(6..));
890 rules.add(RS_M.rule().required().args(1..));
891 rules.build()
892});
893static NS_FOOTER_RULES: Lazy<SectionRules<NetstatusKwd>> = Lazy::new(|| {
895 use NetstatusKwd::*;
896 let mut rules = SectionRules::builder();
897 rules.add(DIRECTORY_FOOTER.rule().required().no_args());
898 rules.add(BANDWIDTH_WEIGHTS.rule());
900 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
901 rules.build()
902});
903
904impl ProtoStatus {
905 fn from_section(
907 sec: &Section<'_, NetstatusKwd>,
908 recommend_token: NetstatusKwd,
909 required_token: NetstatusKwd,
910 ) -> Result<ProtoStatus> {
911 fn parse(t: Option<&Item<'_, NetstatusKwd>>) -> Result<Protocols> {
913 if let Some(item) = t {
914 item.args_as_str()
915 .parse::<Protocols>()
916 .map_err(|e| EK::BadArgument.at_pos(item.pos()).with_source(e))
917 } else {
918 Ok(Protocols::new())
919 }
920 }
921
922 let recommended = parse(sec.get(recommend_token))?;
923 let required = parse(sec.get(required_token))?;
924 Ok(ProtoStatus {
925 recommended,
926 required,
927 })
928 }
929
930 pub fn required_protocols(&self) -> &Protocols {
937 &self.required
938 }
939
940 pub fn recommended_protocols(&self) -> &Protocols {
945 &self.recommended
946 }
947}
948
949impl<T> std::str::FromStr for NetParams<T>
950where
951 T: std::str::FromStr,
952 T::Err: std::error::Error,
953{
954 type Err = Error;
955 fn from_str(s: &str) -> Result<Self> {
956 fn parse_pair<U>(p: &str) -> Result<(String, U)>
958 where
959 U: std::str::FromStr,
960 U::Err: std::error::Error,
961 {
962 let parts: Vec<_> = p.splitn(2, '=').collect();
963 if parts.len() != 2 {
964 return Err(EK::BadArgument
965 .at_pos(Pos::at(p))
966 .with_msg("Missing = in key=value list"));
967 }
968 let num = parts[1].parse::<U>().map_err(|e| {
969 EK::BadArgument
970 .at_pos(Pos::at(parts[1]))
971 .with_msg(e.to_string())
972 })?;
973 Ok((parts[0].to_string(), num))
974 }
975
976 let params = s
977 .split(' ')
978 .filter(|p| !p.is_empty())
979 .map(parse_pair)
980 .collect::<Result<HashMap<_, _>>>()?;
981 Ok(NetParams { params })
982 }
983}
984
985impl CommonHeader {
986 fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<CommonHeader> {
988 use NetstatusKwd::*;
989
990 {
991 #[allow(clippy::unwrap_used)]
994 let first = sec.first_item().unwrap();
995 if first.kwd() != NETWORK_STATUS_VERSION {
996 return Err(EK::UnexpectedToken
997 .with_msg(first.kwd().to_str())
998 .at_pos(first.pos()));
999 }
1000 }
1001
1002 let ver_item = sec.required(NETWORK_STATUS_VERSION)?;
1003
1004 let version: u32 = ver_item.parse_arg(0)?;
1005 if version != 3 {
1006 return Err(EK::BadDocumentVersion.with_msg(version.to_string()));
1007 }
1008 let flavor = ConsensusFlavor::from_opt_name(ver_item.arg(1))?;
1009
1010 let valid_after = sec
1011 .required(VALID_AFTER)?
1012 .args_as_str()
1013 .parse::<Iso8601TimeSp>()?
1014 .into();
1015 let fresh_until = sec
1016 .required(FRESH_UNTIL)?
1017 .args_as_str()
1018 .parse::<Iso8601TimeSp>()?
1019 .into();
1020 let valid_until = sec
1021 .required(VALID_UNTIL)?
1022 .args_as_str()
1023 .parse::<Iso8601TimeSp>()?
1024 .into();
1025 let lifetime = Lifetime::new(valid_after, fresh_until, valid_until)?;
1026
1027 let client_versions = sec
1028 .maybe(CLIENT_VERSIONS)
1029 .args_as_str()
1030 .unwrap_or("")
1031 .split(',')
1032 .map(str::to_string)
1033 .collect();
1034 let relay_versions = sec
1035 .maybe(SERVER_VERSIONS)
1036 .args_as_str()
1037 .unwrap_or("")
1038 .split(',')
1039 .map(str::to_string)
1040 .collect();
1041
1042 let client_protos = ProtoStatus::from_section(
1043 sec,
1044 RECOMMENDED_CLIENT_PROTOCOLS,
1045 REQUIRED_CLIENT_PROTOCOLS,
1046 )?;
1047 let relay_protos =
1048 ProtoStatus::from_section(sec, RECOMMENDED_RELAY_PROTOCOLS, REQUIRED_RELAY_PROTOCOLS)?;
1049
1050 let params = sec.maybe(PARAMS).args_as_str().unwrap_or("").parse()?;
1051
1052 let voting_delay = if let Some(tok) = sec.get(VOTING_DELAY) {
1053 let n1 = tok.parse_arg(0)?;
1054 let n2 = tok.parse_arg(1)?;
1055 Some((n1, n2))
1056 } else {
1057 None
1058 };
1059
1060 Ok(CommonHeader {
1061 flavor,
1062 lifetime,
1063 client_versions,
1064 relay_versions,
1065 client_protos,
1066 relay_protos,
1067 params,
1068 voting_delay,
1069 })
1070 }
1071}
1072
1073impl SharedRandStatus {
1074 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
1077 match item.kwd() {
1078 NetstatusKwd::SHARED_RAND_PREVIOUS_VALUE | NetstatusKwd::SHARED_RAND_CURRENT_VALUE => {}
1079 _ => {
1080 return Err(Error::from(internal!(
1081 "wrong keyword {:?} on shared-random value",
1082 item.kwd()
1083 ))
1084 .at_pos(item.pos()))
1085 }
1086 }
1087 let n_reveals: u8 = item.parse_arg(0)?;
1088 let val: B64 = item.parse_arg(1)?;
1089 let value = SharedRandVal(val.into_array()?);
1090 let timestamp = item
1092 .parse_optional_arg::<Iso8601TimeNoSp>(2)?
1093 .map(Into::into);
1094 Ok(SharedRandStatus {
1095 n_reveals,
1096 value,
1097 timestamp,
1098 })
1099 }
1100
1101 pub fn value(&self) -> &SharedRandVal {
1103 &self.value
1104 }
1105
1106 pub fn timestamp(&self) -> Option<std::time::SystemTime> {
1108 self.timestamp
1109 }
1110}
1111
1112impl ConsensusHeader {
1113 fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<ConsensusHeader> {
1115 use NetstatusKwd::*;
1116
1117 let status: &str = sec.required(VOTE_STATUS)?.arg(0).unwrap_or("");
1118 if status != "consensus" {
1119 return Err(EK::BadDocumentType.err());
1120 }
1121
1122 let hdr = CommonHeader::from_section(sec)?;
1125
1126 let consensus_method: u32 = sec.required(CONSENSUS_METHOD)?.parse_arg(0)?;
1127
1128 let shared_rand_prev = sec
1129 .get(SHARED_RAND_PREVIOUS_VALUE)
1130 .map(SharedRandStatus::from_item)
1131 .transpose()?;
1132
1133 let shared_rand_cur = sec
1134 .get(SHARED_RAND_CURRENT_VALUE)
1135 .map(SharedRandStatus::from_item)
1136 .transpose()?;
1137
1138 Ok(ConsensusHeader {
1139 hdr,
1140 consensus_method,
1141 shared_rand_prev,
1142 shared_rand_cur,
1143 })
1144 }
1145}
1146
1147impl DirSource {
1148 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
1150 if item.kwd() != NetstatusKwd::DIR_SOURCE {
1151 return Err(
1152 Error::from(internal!("Bad keyword {:?} on dir-source", item.kwd()))
1153 .at_pos(item.pos()),
1154 );
1155 }
1156 let nickname = item.required_arg(0)?.to_string();
1157 let identity = item.parse_arg::<Fingerprint>(1)?.into();
1158 let ip = item.parse_arg(3)?;
1159 let dir_port = item.parse_arg(4)?;
1160 let or_port = item.parse_arg(5)?;
1161
1162 Ok(DirSource {
1163 nickname,
1164 identity,
1165 ip,
1166 dir_port,
1167 or_port,
1168 })
1169 }
1170}
1171
1172impl ConsensusVoterInfo {
1173 fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<ConsensusVoterInfo> {
1175 use NetstatusKwd::*;
1176 #[allow(clippy::unwrap_used)]
1179 let first = sec.first_item().unwrap();
1180 if first.kwd() != DIR_SOURCE {
1181 return Err(Error::from(internal!(
1182 "Wrong keyword {:?} at start of voter info",
1183 first.kwd()
1184 ))
1185 .at_pos(first.pos()));
1186 }
1187 let dir_source = DirSource::from_item(sec.required(DIR_SOURCE)?)?;
1188
1189 let contact = sec.required(CONTACT)?.args_as_str().to_string();
1190
1191 let vote_digest = sec.required(VOTE_DIGEST)?.parse_arg::<B16>(0)?.into();
1192
1193 Ok(ConsensusVoterInfo {
1194 dir_source,
1195 contact,
1196 vote_digest,
1197 })
1198 }
1199}
1200
1201impl std::str::FromStr for RelayFlags {
1202 type Err = void::Void;
1203 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
1204 Ok(match s {
1205 "Authority" => RelayFlags::AUTHORITY,
1206 "BadExit" => RelayFlags::BAD_EXIT,
1207 "Exit" => RelayFlags::EXIT,
1208 "Fast" => RelayFlags::FAST,
1209 "Guard" => RelayFlags::GUARD,
1210 "HSDir" => RelayFlags::HSDIR,
1211 "MiddleOnly" => RelayFlags::MIDDLE_ONLY,
1212 "NoEdConsensus" => RelayFlags::NO_ED_CONSENSUS,
1213 "Stable" => RelayFlags::STABLE,
1214 "StaleDesc" => RelayFlags::STALE_DESC,
1215 "Running" => RelayFlags::RUNNING,
1216 "Valid" => RelayFlags::VALID,
1217 "V2Dir" => RelayFlags::V2DIR,
1218 _ => RelayFlags::empty(),
1219 })
1220 }
1221}
1222
1223impl RelayFlags {
1224 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<RelayFlags> {
1226 if item.kwd() != NetstatusKwd::RS_S {
1227 return Err(
1228 Error::from(internal!("Wrong keyword {:?} for S line", item.kwd()))
1229 .at_pos(item.pos()),
1230 );
1231 }
1232 let mut flags: RelayFlags = RelayFlags::RUNNING | RelayFlags::VALID;
1234
1235 let mut prev: Option<&str> = None;
1236 for s in item.args() {
1237 if let Some(p) = prev {
1238 if p >= s {
1239 return Err(EK::BadArgument
1241 .at_pos(item.pos())
1242 .with_msg("Flags out of order"));
1243 }
1244 }
1245 let fl = s.parse().void_unwrap();
1246 flags |= fl;
1247 prev = Some(s);
1248 }
1249
1250 Ok(flags)
1251 }
1252}
1253
1254impl Default for RelayWeight {
1255 fn default() -> RelayWeight {
1256 RelayWeight::Unmeasured(0)
1257 }
1258}
1259
1260impl RelayWeight {
1261 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<RelayWeight> {
1263 if item.kwd() != NetstatusKwd::RS_W {
1264 return Err(
1265 Error::from(internal!("Wrong keyword {:?} on W line", item.kwd()))
1266 .at_pos(item.pos()),
1267 );
1268 }
1269
1270 let params: NetParams<u32> = item.args_as_str().parse()?;
1271
1272 let bw = params.params.get("Bandwidth");
1273 let unmeas = params.params.get("Unmeasured");
1274
1275 let bw = match bw {
1276 None => return Ok(RelayWeight::Unmeasured(0)),
1277 Some(b) => *b,
1278 };
1279
1280 match unmeas {
1281 None | Some(0) => Ok(RelayWeight::Measured(bw)),
1282 Some(1) => Ok(RelayWeight::Unmeasured(bw)),
1283 _ => Err(EK::BadArgument
1284 .at_pos(item.pos())
1285 .with_msg("unmeasured value")),
1286 }
1287 }
1288}
1289
1290impl Footer {
1291 fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<Footer> {
1293 use NetstatusKwd::*;
1294 sec.required(DIRECTORY_FOOTER)?;
1295
1296 let weights = sec
1297 .maybe(BANDWIDTH_WEIGHTS)
1298 .args_as_str()
1299 .unwrap_or("")
1300 .parse()?;
1301
1302 Ok(Footer { weights })
1303 }
1304}
1305
1306enum SigCheckResult {
1308 Valid,
1310 Invalid,
1313 MissingCert,
1316}
1317
1318impl Signature {
1319 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Signature> {
1321 if item.kwd() != NetstatusKwd::DIRECTORY_SIGNATURE {
1322 return Err(Error::from(internal!(
1323 "Wrong keyword {:?} for directory signature",
1324 item.kwd()
1325 ))
1326 .at_pos(item.pos()));
1327 }
1328
1329 let (alg, id_fp, sk_fp) = if item.n_args() > 2 {
1330 (
1331 item.required_arg(0)?,
1332 item.required_arg(1)?,
1333 item.required_arg(2)?,
1334 )
1335 } else {
1336 ("sha1", item.required_arg(0)?, item.required_arg(1)?)
1337 };
1338
1339 let digestname = alg.to_string();
1340 let id_fingerprint = id_fp.parse::<Fingerprint>()?.into();
1341 let sk_fingerprint = sk_fp.parse::<Fingerprint>()?.into();
1342 let key_ids = AuthCertKeyIds {
1343 id_fingerprint,
1344 sk_fingerprint,
1345 };
1346 let signature = item.obj("SIGNATURE")?;
1347
1348 Ok(Signature {
1349 digestname,
1350 key_ids,
1351 signature,
1352 })
1353 }
1354
1355 fn matches_cert(&self, cert: &AuthCert) -> bool {
1358 cert.key_ids() == &self.key_ids
1359 }
1360
1361 fn find_cert<'a>(&self, certs: &'a [AuthCert]) -> Option<&'a AuthCert> {
1364 certs.iter().find(|&c| self.matches_cert(c))
1365 }
1366
1367 fn check_signature(&self, signed_digest: &[u8], certs: &[AuthCert]) -> SigCheckResult {
1371 match self.find_cert(certs) {
1372 None => SigCheckResult::MissingCert,
1373 Some(cert) => {
1374 let key = cert.signing_key();
1375 match key.verify(signed_digest, &self.signature[..]) {
1376 Ok(()) => SigCheckResult::Valid,
1377 Err(_) => SigCheckResult::Invalid,
1378 }
1379 }
1380 }
1381 }
1382}
1383
1384pub type UncheckedConsensus<RS> = TimerangeBound<UnvalidatedConsensus<RS>>;
1387
1388impl<RS: RouterStatus + ParseRouterStatus> Consensus<RS> {
1389 #[cfg(feature = "build_docs")]
1394 pub fn builder() -> ConsensusBuilder<RS> {
1395 ConsensusBuilder::new(RS::flavor())
1396 }
1397
1398 pub fn parse(s: &str) -> Result<(&str, &str, UncheckedConsensus<RS>)> {
1400 let mut reader = NetDocReader::new(s);
1401 Self::parse_from_reader(&mut reader).map_err(|e| e.within(s))
1402 }
1403 fn take_voterinfo(
1406 r: &mut NetDocReader<'_, NetstatusKwd>,
1407 ) -> Result<Option<ConsensusVoterInfo>> {
1408 use NetstatusKwd::*;
1409
1410 match r.peek() {
1411 None => return Ok(None),
1412 Some(e) if e.is_ok_with_kwd_in(&[RS_R, DIRECTORY_FOOTER]) => return Ok(None),
1413 _ => (),
1414 };
1415
1416 let mut first_dir_source = true;
1417 let mut p = r.pause_at(|i| match i {
1420 Err(_) => false,
1421 Ok(item) => {
1422 item.kwd() == RS_R
1423 || if item.kwd() == DIR_SOURCE {
1424 let was_first = first_dir_source;
1425 first_dir_source = false;
1426 !was_first
1427 } else {
1428 false
1429 }
1430 }
1431 });
1432
1433 let voter_sec = NS_VOTERINFO_RULES_CONSENSUS.parse(&mut p)?;
1434 let voter = ConsensusVoterInfo::from_section(&voter_sec)?;
1435
1436 Ok(Some(voter))
1437 }
1438
1439 fn take_footer(r: &mut NetDocReader<'_, NetstatusKwd>) -> Result<Footer> {
1441 use NetstatusKwd::*;
1442 let mut p = r.pause_at(|i| i.is_ok_with_kwd_in(&[DIRECTORY_SIGNATURE]));
1443 let footer_sec = NS_FOOTER_RULES.parse(&mut p)?;
1444 let footer = Footer::from_section(&footer_sec)?;
1445 Ok(footer)
1446 }
1447
1448 fn take_routerstatus(r: &mut NetDocReader<'_, NetstatusKwd>) -> Result<Option<(Pos, RS)>> {
1451 use NetstatusKwd::*;
1452 match r.peek() {
1453 None => return Ok(None),
1454 Some(e) if e.is_ok_with_kwd_in(&[DIRECTORY_FOOTER]) => return Ok(None),
1455 _ => (),
1456 };
1457
1458 let pos = r.pos();
1459
1460 let mut first_r = true;
1461 let mut p = r.pause_at(|i| match i {
1462 Err(_) => false,
1463 Ok(item) => {
1464 item.kwd() == DIRECTORY_FOOTER
1465 || if item.kwd() == RS_R {
1466 let was_first = first_r;
1467 first_r = false;
1468 !was_first
1469 } else {
1470 false
1471 }
1472 }
1473 });
1474
1475 let rules = match RS::flavor() {
1476 ConsensusFlavor::Microdesc => &NS_ROUTERSTATUS_RULES_MDCON,
1477 ConsensusFlavor::Ns => &NS_ROUTERSTATUS_RULES_NSCON,
1478 };
1479
1480 let rs_sec = rules.parse(&mut p)?;
1481 let rs = RS::from_section(&rs_sec)?;
1482 Ok(Some((pos, rs)))
1483 }
1484
1485 fn parse_from_reader<'a>(
1490 r: &mut NetDocReader<'a, NetstatusKwd>,
1491 ) -> Result<(&'a str, &'a str, UncheckedConsensus<RS>)> {
1492 use NetstatusKwd::*;
1493 let (header, start_pos) = {
1494 let mut h = r.pause_at(|i| i.is_ok_with_kwd_in(&[DIR_SOURCE]));
1495 let header_sec = NS_HEADER_RULES_CONSENSUS.parse(&mut h)?;
1496 #[allow(clippy::unwrap_used)]
1499 let pos = header_sec.first_item().unwrap().offset_in(r.str()).unwrap();
1500 (ConsensusHeader::from_section(&header_sec)?, pos)
1501 };
1502 if RS::flavor() != header.hdr.flavor {
1503 return Err(EK::BadDocumentType.with_msg(format!(
1504 "Expected {:?}, got {:?}",
1505 RS::flavor(),
1506 header.hdr.flavor
1507 )));
1508 }
1509
1510 let mut voters = Vec::new();
1511
1512 while let Some(voter) = Self::take_voterinfo(r)? {
1513 voters.push(voter);
1514 }
1515
1516 let mut relays: Vec<RS> = Vec::new();
1517 while let Some((pos, routerstatus)) = Self::take_routerstatus(r)? {
1518 if let Some(prev) = relays.last() {
1519 if prev.rsa_identity() >= routerstatus.rsa_identity() {
1520 return Err(EK::WrongSortOrder.at_pos(pos));
1521 }
1522 }
1523 relays.push(routerstatus);
1524 }
1525 relays.shrink_to_fit();
1526
1527 let footer = Self::take_footer(r)?;
1528
1529 let consensus = Consensus {
1530 header,
1531 voters,
1532 relays,
1533 footer,
1534 };
1535
1536 let mut first_sig: Option<Item<'_, NetstatusKwd>> = None;
1538 let mut signatures = Vec::new();
1539 for item in &mut *r {
1540 let item = item?;
1541 if item.kwd() != DIRECTORY_SIGNATURE {
1542 return Err(EK::UnexpectedToken
1543 .with_msg(item.kwd().to_str())
1544 .at_pos(item.pos()));
1545 }
1546
1547 let sig = Signature::from_item(&item)?;
1548 if first_sig.is_none() {
1549 first_sig = Some(item);
1550 }
1551 signatures.push(sig);
1552 }
1553
1554 let end_pos = match first_sig {
1555 None => return Err(EK::MissingToken.with_msg("directory-signature")),
1556 #[allow(clippy::unwrap_used)]
1558 Some(sig) => sig.offset_in(r.str()).unwrap() + "directory-signature ".len(),
1559 };
1560
1561 let signed_str = &r.str()[start_pos..end_pos];
1563 let remainder = &r.str()[end_pos..];
1564 let (sha256, sha1) = match RS::flavor() {
1565 ConsensusFlavor::Ns => (
1566 None,
1567 Some(ll::d::Sha1::digest(signed_str.as_bytes()).into()),
1568 ),
1569 ConsensusFlavor::Microdesc => (
1570 Some(ll::d::Sha256::digest(signed_str.as_bytes()).into()),
1571 None,
1572 ),
1573 };
1574 let siggroup = SignatureGroup {
1575 sha256,
1576 sha1,
1577 signatures,
1578 };
1579
1580 let unval = UnvalidatedConsensus {
1581 consensus,
1582 siggroup,
1583 n_authorities: None,
1584 };
1585 let lifetime = unval.consensus.header.hdr.lifetime.clone();
1586 let delay = unval.consensus.header.hdr.voting_delay.unwrap_or((0, 0));
1587 let dist_interval = time::Duration::from_secs(delay.1.into());
1588 let starting_time = lifetime.valid_after - dist_interval;
1589 let timebound = TimerangeBound::new(unval, starting_time..lifetime.valid_until);
1590 Ok((signed_str, remainder, timebound))
1591 }
1592}
1593
1594#[cfg_attr(
1601 feature = "dangerous-expose-struct-fields",
1602 visible::StructFields(pub),
1603 non_exhaustive
1604)]
1605#[derive(Debug, Clone)]
1606pub struct UnvalidatedConsensus<RS> {
1607 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
1610 consensus: Consensus<RS>,
1611 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
1614 siggroup: SignatureGroup,
1615 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
1619 n_authorities: Option<u16>,
1620}
1621
1622impl<RS> UnvalidatedConsensus<RS> {
1623 #[must_use]
1627 pub fn set_n_authorities(self, n_authorities: u16) -> Self {
1628 UnvalidatedConsensus {
1629 n_authorities: Some(n_authorities),
1630 ..self
1631 }
1632 }
1633
1634 pub fn signing_cert_ids(&self) -> impl Iterator<Item = AuthCertKeyIds> {
1637 match self.key_is_correct(&[]) {
1638 Ok(()) => Vec::new(),
1639 Err(missing) => missing,
1640 }
1641 .into_iter()
1642 }
1643
1644 pub fn peek_lifetime(&self) -> &Lifetime {
1646 self.consensus.lifetime()
1647 }
1648
1649 pub fn authorities_are_correct(&self, authorities: &[&RsaIdentity]) -> bool {
1656 self.siggroup.could_validate(authorities)
1657 }
1658
1659 #[cfg(feature = "experimental-api")]
1664 pub fn n_relays(&self) -> usize {
1665 self.consensus.relays.len()
1666 }
1667
1668 #[cfg(feature = "experimental-api")]
1677 pub fn modify_relays<F>(&mut self, func: F)
1678 where
1679 F: FnOnce(&mut Vec<RS>),
1680 {
1681 func(&mut self.consensus.relays);
1682 }
1683}
1684
1685impl<RS> ExternallySigned<Consensus<RS>> for UnvalidatedConsensus<RS> {
1686 type Key = [AuthCert];
1687 type KeyHint = Vec<AuthCertKeyIds>;
1688 type Error = Error;
1689
1690 fn key_is_correct(&self, k: &Self::Key) -> result::Result<(), Self::KeyHint> {
1691 let (n_ok, missing) = self.siggroup.list_missing(k);
1692 match self.n_authorities {
1693 Some(n) if n_ok > (n / 2) as usize => Ok(()),
1694 _ => Err(missing.iter().map(|cert| cert.key_ids).collect()),
1695 }
1696 }
1697 fn is_well_signed(&self, k: &Self::Key) -> result::Result<(), Self::Error> {
1698 match self.n_authorities {
1699 None => Err(Error::from(internal!(
1700 "Didn't set authorities on consensus"
1701 ))),
1702 Some(authority) => {
1703 if self.siggroup.validate(authority, k) {
1704 Ok(())
1705 } else {
1706 Err(EK::BadSignature.err())
1707 }
1708 }
1709 }
1710 }
1711 fn dangerously_assume_wellsigned(self) -> Consensus<RS> {
1712 self.consensus
1713 }
1714}
1715
1716impl SignatureGroup {
1717 fn list_missing(&self, certs: &[AuthCert]) -> (usize, Vec<&Signature>) {
1724 let mut ok: HashSet<RsaIdentity> = HashSet::new();
1725 let mut missing = Vec::new();
1726 for sig in &self.signatures {
1727 let id_fingerprint = &sig.key_ids.id_fingerprint;
1728 if ok.contains(id_fingerprint) {
1729 continue;
1730 }
1731 if sig.find_cert(certs).is_some() {
1732 ok.insert(*id_fingerprint);
1733 continue;
1734 }
1735
1736 missing.push(sig);
1737 }
1738 (ok.len(), missing)
1739 }
1740
1741 fn could_validate(&self, authorities: &[&RsaIdentity]) -> bool {
1745 let mut signed_by: HashSet<RsaIdentity> = HashSet::new();
1746 for sig in &self.signatures {
1747 let id_fp = &sig.key_ids.id_fingerprint;
1748 if signed_by.contains(id_fp) {
1749 continue;
1751 }
1752 if authorities.contains(&id_fp) {
1753 signed_by.insert(*id_fp);
1754 }
1755 }
1756
1757 signed_by.len() > (authorities.len() / 2)
1758 }
1759
1760 fn validate(&self, n_authorities: u16, certs: &[AuthCert]) -> bool {
1767 let mut ok: HashSet<RsaIdentity> = HashSet::new();
1771
1772 for sig in &self.signatures {
1773 let id_fingerprint = &sig.key_ids.id_fingerprint;
1774 if ok.contains(id_fingerprint) {
1775 continue;
1778 }
1779
1780 let d: Option<&[u8]> = match sig.digestname.as_ref() {
1781 "sha256" => self.sha256.as_ref().map(|a| &a[..]),
1782 "sha1" => self.sha1.as_ref().map(|a| &a[..]),
1783 _ => None, };
1785 if d.is_none() {
1786 continue;
1789 }
1790
1791 #[allow(clippy::unwrap_used)]
1793 match sig.check_signature(d.as_ref().unwrap(), certs) {
1794 SigCheckResult::Valid => {
1795 ok.insert(*id_fingerprint);
1796 }
1797 _ => continue,
1798 }
1799 }
1800
1801 ok.len() > (n_authorities / 2) as usize
1802 }
1803}
1804
1805#[cfg(test)]
1806mod test {
1807 #![allow(clippy::bool_assert_comparison)]
1809 #![allow(clippy::clone_on_copy)]
1810 #![allow(clippy::dbg_macro)]
1811 #![allow(clippy::mixed_attributes_style)]
1812 #![allow(clippy::print_stderr)]
1813 #![allow(clippy::print_stdout)]
1814 #![allow(clippy::single_char_pattern)]
1815 #![allow(clippy::unwrap_used)]
1816 #![allow(clippy::unchecked_duration_subtraction)]
1817 #![allow(clippy::useless_vec)]
1818 #![allow(clippy::needless_pass_by_value)]
1819 use super::*;
1821 use hex_literal::hex;
1822
1823 const CERTS: &str = include_str!("../../testdata/authcerts2.txt");
1824 const CONSENSUS: &str = include_str!("../../testdata/mdconsensus1.txt");
1825
1826 #[cfg(feature = "ns_consensus")]
1827 const NS_CERTS: &str = include_str!("../../testdata/authcerts3.txt");
1828 #[cfg(feature = "ns_consensus")]
1829 const NS_CONSENSUS: &str = include_str!("../../testdata/nsconsensus1.txt");
1830
1831 fn read_bad(fname: &str) -> String {
1832 use std::fs;
1833 use std::path::PathBuf;
1834 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1835 path.push("testdata");
1836 path.push("bad-mdconsensus");
1837 path.push(fname);
1838
1839 fs::read_to_string(path).unwrap()
1840 }
1841
1842 #[test]
1843 fn parse_and_validate_md() -> Result<()> {
1844 use std::net::SocketAddr;
1845 use tor_checkable::{SelfSigned, Timebound};
1846 let mut certs = Vec::new();
1847 for cert in AuthCert::parse_multiple(CERTS) {
1848 let cert = cert?.check_signature()?.dangerously_assume_timely();
1849 certs.push(cert);
1850 }
1851 let auth_ids: Vec<_> = certs.iter().map(|c| &c.key_ids().id_fingerprint).collect();
1852
1853 assert_eq!(certs.len(), 3);
1854
1855 let (_, _, consensus) = MdConsensus::parse(CONSENSUS)?;
1856 let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
1857
1858 assert!(consensus.authorities_are_correct(&auth_ids));
1860 assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
1862 {
1863 let bad_auth_id = (*b"xxxxxxxxxxxxxxxxxxxx").into();
1866 assert!(!consensus.authorities_are_correct(&[&bad_auth_id]));
1867 }
1868
1869 let missing = consensus.key_is_correct(&[]).err().unwrap();
1870 assert_eq!(3, missing.len());
1871 assert!(consensus.key_is_correct(&certs).is_ok());
1872 let missing = consensus.key_is_correct(&certs[0..1]).err().unwrap();
1873 assert_eq!(2, missing.len());
1874
1875 let same_three_times = vec![certs[0].clone(), certs[0].clone(), certs[0].clone()];
1877 let missing = consensus.key_is_correct(&same_three_times).err().unwrap();
1878
1879 assert_eq!(2, missing.len());
1880 assert!(consensus.is_well_signed(&same_three_times).is_err());
1881
1882 assert!(consensus.key_is_correct(&certs).is_ok());
1883 let consensus = consensus.check_signature(&certs)?;
1884
1885 assert_eq!(6, consensus.relays().len());
1886 let r0 = &consensus.relays()[0];
1887 assert_eq!(
1888 r0.md_digest(),
1889 &hex!("73dabe0a0468f4f7a67810a18d11e36731bb1d2ec3634db459100609f3b3f535")
1890 );
1891 assert_eq!(
1892 r0.rsa_identity().as_bytes(),
1893 &hex!("0a3057af2910415794d8ea430309d9ac5f5d524b")
1894 );
1895 assert!(!r0.weight().is_measured());
1896 assert!(!r0.weight().is_nonzero());
1897 let pv = &r0.protovers();
1898 assert!(pv.supports_subver("HSDir", 2));
1899 assert!(!pv.supports_subver("HSDir", 3));
1900 let ip4 = "127.0.0.1:5002".parse::<SocketAddr>().unwrap();
1901 let ip6 = "[::1]:5002".parse::<SocketAddr>().unwrap();
1902 assert!(r0.orport_addrs().any(|a| a == &ip4));
1903 assert!(r0.orport_addrs().any(|a| a == &ip6));
1904
1905 Ok(())
1906 }
1907
1908 #[test]
1909 #[cfg(feature = "ns_consensus")]
1910 fn parse_and_validate_ns() -> Result<()> {
1911 use tor_checkable::{SelfSigned, Timebound};
1912 let mut certs = Vec::new();
1913 for cert in AuthCert::parse_multiple(NS_CERTS) {
1914 let cert = cert?.check_signature()?.dangerously_assume_timely();
1915 certs.push(cert);
1916 }
1917 let auth_ids: Vec<_> = certs.iter().map(|c| &c.key_ids().id_fingerprint).collect();
1918 assert_eq!(certs.len(), 3);
1919
1920 let (_, _, consensus) = NsConsensus::parse(NS_CONSENSUS)?;
1921 let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
1922 assert!(consensus.authorities_are_correct(&auth_ids));
1924 assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
1926
1927 assert!(consensus.key_is_correct(&certs).is_ok());
1928
1929 let _consensus = consensus.check_signature(&certs)?;
1930
1931 Ok(())
1932 }
1933
1934 #[test]
1935 fn test_bad() {
1936 use crate::Pos;
1937 fn check(fname: &str, e: &Error) {
1938 let content = read_bad(fname);
1939 let res = MdConsensus::parse(&content);
1940 assert!(res.is_err());
1941 assert_eq!(&res.err().unwrap(), e);
1942 }
1943
1944 check(
1945 "bad-flags",
1946 &EK::BadArgument
1947 .at_pos(Pos::from_line(27, 1))
1948 .with_msg("Flags out of order"),
1949 );
1950 check(
1951 "bad-md-digest",
1952 &EK::BadArgument
1953 .at_pos(Pos::from_line(40, 3))
1954 .with_msg("Invalid base64"),
1955 );
1956 check(
1957 "bad-weight",
1958 &EK::BadArgument
1959 .at_pos(Pos::from_line(67, 141))
1960 .with_msg("invalid digit found in string"),
1961 );
1962 check(
1963 "bad-weights",
1964 &EK::BadArgument
1965 .at_pos(Pos::from_line(51, 13))
1966 .with_msg("invalid digit found in string"),
1967 );
1968 check(
1969 "wrong-order",
1970 &EK::WrongSortOrder.at_pos(Pos::from_line(52, 1)),
1971 );
1972 check(
1973 "wrong-start",
1974 &EK::UnexpectedToken
1975 .with_msg("vote-status")
1976 .at_pos(Pos::from_line(1, 1)),
1977 );
1978 check("wrong-version", &EK::BadDocumentVersion.with_msg("10"));
1979 }
1980
1981 fn gettok(s: &str) -> Result<Item<'_, NetstatusKwd>> {
1982 let mut reader = NetDocReader::new(s);
1983 let tok = reader.next().unwrap();
1984 assert!(reader.next().is_none());
1985 tok
1986 }
1987
1988 #[test]
1989 fn test_weight() {
1990 let w = gettok("w Unmeasured=1 Bandwidth=6\n").unwrap();
1991 let w = RelayWeight::from_item(&w).unwrap();
1992 assert!(!w.is_measured());
1993 assert!(w.is_nonzero());
1994
1995 let w = gettok("w Bandwidth=10\n").unwrap();
1996 let w = RelayWeight::from_item(&w).unwrap();
1997 assert!(w.is_measured());
1998 assert!(w.is_nonzero());
1999
2000 let w = RelayWeight::default();
2001 assert!(!w.is_measured());
2002 assert!(!w.is_nonzero());
2003
2004 let w = gettok("w Mustelid=66 Cheato=7 Unmeasured=1\n").unwrap();
2005 let w = RelayWeight::from_item(&w).unwrap();
2006 assert!(!w.is_measured());
2007 assert!(!w.is_nonzero());
2008
2009 let w = gettok("r foo\n").unwrap();
2010 let w = RelayWeight::from_item(&w);
2011 assert!(w.is_err());
2012
2013 let w = gettok("r Bandwidth=6 Unmeasured=Frog\n").unwrap();
2014 let w = RelayWeight::from_item(&w);
2015 assert!(w.is_err());
2016
2017 let w = gettok("r Bandwidth=6 Unmeasured=3\n").unwrap();
2018 let w = RelayWeight::from_item(&w);
2019 assert!(w.is_err());
2020 }
2021
2022 #[test]
2023 fn test_netparam() {
2024 let p = "Hello=600 Goodbye=5 Fred=7"
2025 .parse::<NetParams<u32>>()
2026 .unwrap();
2027 assert_eq!(p.get("Hello"), Some(&600_u32));
2028
2029 let p = "Hello=Goodbye=5 Fred=7".parse::<NetParams<u32>>();
2030 assert!(p.is_err());
2031
2032 let p = "Hello=Goodbye Fred=7".parse::<NetParams<u32>>();
2033 assert!(p.is_err());
2034 }
2035
2036 #[test]
2037 fn test_sharedrand() {
2038 let sr =
2039 gettok("shared-rand-previous-value 9 5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4\n")
2040 .unwrap();
2041 let sr = SharedRandStatus::from_item(&sr).unwrap();
2042
2043 assert_eq!(sr.n_reveals, 9);
2044 assert_eq!(
2045 sr.value.0,
2046 hex!("e4ba1d638c96c458532adc6957dc0080d03d37c7e5854087d0da90bf5ff4e72e")
2047 );
2048 assert!(sr.timestamp.is_none());
2049
2050 let sr2 = gettok(
2051 "shared-rand-current-value 9 \
2052 5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4 2022-01-20T12:34:56\n",
2053 )
2054 .unwrap();
2055 let sr2 = SharedRandStatus::from_item(&sr2).unwrap();
2056 assert_eq!(sr2.n_reveals, sr.n_reveals);
2057 assert_eq!(sr2.value.0, sr.value.0);
2058 assert_eq!(
2059 sr2.timestamp.unwrap(),
2060 humantime::parse_rfc3339("2022-01-20T12:34:56Z").unwrap()
2061 );
2062
2063 let sr = gettok("foo bar\n").unwrap();
2064 let sr = SharedRandStatus::from_item(&sr);
2065 assert!(sr.is_err());
2066 }
2067}