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::result::Result as StdResult;
61use std::sync::Arc;
62use std::{net, result, time};
63use tor_error::{internal, HasKind};
64use tor_protover::Protocols;
65
66use bitflags::bitflags;
67use digest::Digest;
68use once_cell::sync::Lazy;
69use tor_checkable::{timed::TimerangeBound, ExternallySigned};
70use tor_llcrypto as ll;
71use tor_llcrypto::pk::rsa::RsaIdentity;
72
73use serde::{Deserialize, Deserializer};
74
75#[cfg(feature = "build_docs")]
76pub use build::ConsensusBuilder;
77#[cfg(feature = "build_docs")]
78pub use rs::build::RouterStatusBuilder;
79
80pub use rs::MdConsensusRouterStatus;
81#[cfg(feature = "ns_consensus")]
82pub use rs::NsConsensusRouterStatus;
83use void::ResultVoidExt as _;
84
85#[derive(Clone, Debug)]
91pub struct Lifetime {
92 valid_after: time::SystemTime,
94 fresh_until: time::SystemTime,
97 valid_until: time::SystemTime,
102}
103
104impl Lifetime {
105 pub fn new(
107 valid_after: time::SystemTime,
108 fresh_until: time::SystemTime,
109 valid_until: time::SystemTime,
110 ) -> Result<Self> {
111 if valid_after < fresh_until && fresh_until < valid_until {
112 Ok(Lifetime {
113 valid_after,
114 fresh_until,
115 valid_until,
116 })
117 } else {
118 Err(EK::InvalidLifetime.err())
119 }
120 }
121 pub fn valid_after(&self) -> time::SystemTime {
126 self.valid_after
127 }
128 pub fn fresh_until(&self) -> time::SystemTime {
133 self.fresh_until
134 }
135 pub fn valid_until(&self) -> time::SystemTime {
141 self.valid_until
142 }
143 pub fn valid_at(&self, when: time::SystemTime) -> bool {
145 self.valid_after <= when && when <= self.valid_until
146 }
147
148 pub fn voting_period(&self) -> time::Duration {
153 let valid_after = self.valid_after();
154 let fresh_until = self.fresh_until();
155 fresh_until
156 .duration_since(valid_after)
157 .expect("Mis-formed lifetime")
158 }
159}
160
161#[derive(Debug, Clone, Default, Eq, PartialEq)]
170pub struct NetParams<T> {
171 params: HashMap<String, T>,
173}
174
175impl<T> NetParams<T> {
176 #[allow(unused)]
178 pub fn new() -> Self {
179 NetParams {
180 params: HashMap::new(),
181 }
182 }
183 pub fn get<A: AsRef<str>>(&self, v: A) -> Option<&T> {
185 self.params.get(v.as_ref())
186 }
187 pub fn iter(&self) -> impl Iterator<Item = (&String, &T)> {
189 self.params.iter()
190 }
191 pub fn set(&mut self, k: String, v: T) {
193 self.params.insert(k, v);
194 }
195}
196
197impl<K: Into<String>, T> FromIterator<(K, T)> for NetParams<T> {
198 fn from_iter<I: IntoIterator<Item = (K, T)>>(i: I) -> Self {
199 NetParams {
200 params: i.into_iter().map(|(k, v)| (k.into(), v)).collect(),
201 }
202 }
203}
204
205impl<T> std::iter::Extend<(String, T)> for NetParams<T> {
206 fn extend<I: IntoIterator<Item = (String, T)>>(&mut self, iter: I) {
207 self.params.extend(iter);
208 }
209}
210
211impl<'de, T> Deserialize<'de> for NetParams<T>
212where
213 T: Deserialize<'de>,
214{
215 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
216 where
217 D: Deserializer<'de>,
218 {
219 let params = HashMap::deserialize(deserializer)?;
220 Ok(NetParams { params })
221 }
222}
223
224#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
228pub struct ProtoStatus {
229 recommended: Protocols,
232 required: Protocols,
235}
236
237impl ProtoStatus {
238 pub fn check_protocols(
248 &self,
249 supported_protocols: &Protocols,
250 ) -> StdResult<(), ProtocolSupportError> {
251 let missing_required = self.required.difference(supported_protocols);
253 if !missing_required.is_empty() {
254 return Err(ProtocolSupportError::MissingRequired(missing_required));
255 }
256 let missing_recommended = self.recommended.difference(supported_protocols);
257 if !missing_recommended.is_empty() {
258 return Err(ProtocolSupportError::MissingRecommended(
259 missing_recommended,
260 ));
261 }
262
263 Ok(())
264 }
265}
266
267#[derive(Clone, Debug, thiserror::Error)]
269#[cfg_attr(test, derive(PartialEq))]
270#[non_exhaustive]
271pub enum ProtocolSupportError {
272 #[error("Required protocols are not implemented: {0}")]
274 MissingRequired(Protocols),
275
276 #[error("Recommended protocols are not implemented: {0}")]
280 MissingRecommended(Protocols),
281}
282
283impl ProtocolSupportError {
284 pub fn should_shutdown(&self) -> bool {
286 matches!(self, Self::MissingRequired(_))
287 }
288}
289
290impl HasKind for ProtocolSupportError {
291 fn kind(&self) -> tor_error::ErrorKind {
292 tor_error::ErrorKind::SoftwareDeprecated
293 }
294}
295
296#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
299pub struct ProtoStatuses {
300 client: ProtoStatus,
302 relay: ProtoStatus,
304}
305
306impl ProtoStatuses {
307 pub fn client(&self) -> &ProtoStatus {
309 &self.client
310 }
311
312 pub fn relay(&self) -> &ProtoStatus {
314 &self.relay
315 }
316}
317
318#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
320#[non_exhaustive]
321pub enum ConsensusFlavor {
322 Microdesc,
325 Ns,
330}
331
332impl ConsensusFlavor {
333 pub fn name(&self) -> &'static str {
335 match self {
336 ConsensusFlavor::Ns => "ns",
337 ConsensusFlavor::Microdesc => "microdesc",
338 }
339 }
340 pub fn from_opt_name(name: Option<&str>) -> Result<Self> {
345 match name {
346 Some("microdesc") => Ok(ConsensusFlavor::Microdesc),
347 Some("ns") | None => Ok(ConsensusFlavor::Ns),
348 Some(other) => {
349 Err(EK::BadDocumentType.with_msg(format!("unrecognized flavor {:?}", other)))
350 }
351 }
352 }
353}
354
355#[allow(dead_code)]
357#[cfg_attr(
358 feature = "dangerous-expose-struct-fields",
359 visible::StructFields(pub),
360 non_exhaustive
361)]
362#[derive(Debug, Clone)]
363pub struct Signature {
364 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
369 digestname: String,
370 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
373 key_ids: AuthCertKeyIds,
374 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
376 signature: Vec<u8>,
377}
378
379#[allow(dead_code)]
381#[cfg_attr(
382 feature = "dangerous-expose-struct-fields",
383 visible::StructFields(pub),
384 non_exhaustive
385)]
386#[derive(Debug, Clone)]
387pub struct SignatureGroup {
388 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
390 sha256: Option<[u8; 32]>,
391 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
393 sha1: Option<[u8; 20]>,
394 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
396 signatures: Vec<Signature>,
397}
398
399#[derive(
401 Debug, Clone, Copy, Eq, PartialEq, derive_more::From, derive_more::Into, derive_more::AsRef,
402)]
403pub struct SharedRandVal([u8; 32]);
405
406#[allow(dead_code)]
409#[cfg_attr(
410 feature = "dangerous-expose-struct-fields",
411 visible::StructFields(pub),
412 visibility::make(pub),
413 non_exhaustive
414)]
415#[derive(Debug, Clone)]
416pub struct SharedRandStatus {
417 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
419 n_reveals: u8,
420 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
427 value: SharedRandVal,
428
429 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
433 timestamp: Option<time::SystemTime>,
434}
435
436#[allow(dead_code)]
441#[cfg_attr(
442 feature = "dangerous-expose-struct-fields",
443 visible::StructFields(pub),
444 visibility::make(pub),
445 non_exhaustive
446)]
447#[derive(Debug, Clone)]
448struct CommonHeader {
449 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
452 flavor: ConsensusFlavor,
453 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
456 lifetime: Lifetime,
457 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
459 client_versions: Vec<String>,
460 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
462 relay_versions: Vec<String>,
463 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
465 proto_statuses: Arc<ProtoStatuses>,
466 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
470 params: NetParams<i32>,
471 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
474 voting_delay: Option<(u32, u32)>,
475}
476
477#[allow(dead_code)]
479#[cfg_attr(
480 feature = "dangerous-expose-struct-fields",
481 visible::StructFields(pub),
482 visibility::make(pub),
483 non_exhaustive
484)]
485#[derive(Debug, Clone)]
486struct ConsensusHeader {
487 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
489 hdr: CommonHeader,
490 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
494 consensus_method: u32,
495 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
497 shared_rand_prev: Option<SharedRandStatus>,
498 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
500 shared_rand_cur: Option<SharedRandStatus>,
501}
502
503#[allow(dead_code)]
507#[cfg_attr(
508 feature = "dangerous-expose-struct-fields",
509 visible::StructFields(pub),
510 visibility::make(pub),
511 non_exhaustive
512)]
513#[derive(Debug, Clone)]
514struct DirSource {
515 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
517 nickname: String,
518 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
524 identity: RsaIdentity,
525 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
527 ip: net::IpAddr,
528 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
530 dir_port: u16,
531 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
533 or_port: u16,
534}
535
536bitflags! {
537 #[derive(Clone, Copy, Debug)]
548 pub struct RelayFlags: u16 {
549 const AUTHORITY = (1<<0);
551 const BAD_EXIT = (1<<1);
556 const EXIT = (1<<2);
558 const FAST = (1<<3);
560 const GUARD = (1<<4);
565 const HSDIR = (1<<5);
568 const MIDDLE_ONLY = (1<<6);
575 const NO_ED_CONSENSUS = (1<<7);
577 const STABLE = (1<<8);
579 const STALE_DESC = (1<<9);
582 const RUNNING = (1<<10);
587 const VALID = (1<<11);
593 const V2DIR = (1<<12);
596 }
597}
598
599#[non_exhaustive]
601#[derive(Debug, Clone, Copy)]
602pub enum RelayWeight {
603 Unmeasured(u32),
605 Measured(u32),
607}
608
609impl RelayWeight {
610 pub fn is_measured(&self) -> bool {
612 matches!(self, RelayWeight::Measured(_))
613 }
614 pub fn is_nonzero(&self) -> bool {
616 !matches!(self, RelayWeight::Unmeasured(0) | RelayWeight::Measured(0))
617 }
618}
619
620#[allow(dead_code)]
622#[cfg_attr(
623 feature = "dangerous-expose-struct-fields",
624 visible::StructFields(pub),
625 visibility::make(pub),
626 non_exhaustive
627)]
628#[derive(Debug, Clone)]
629struct ConsensusVoterInfo {
630 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
632 dir_source: DirSource,
633 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
635 contact: String,
636 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
639 vote_digest: Vec<u8>,
640}
641
642#[allow(dead_code)]
644#[cfg_attr(
645 feature = "dangerous-expose-struct-fields",
646 visible::StructFields(pub),
647 visibility::make(pub),
648 non_exhaustive
649)]
650#[derive(Debug, Clone)]
651struct Footer {
652 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
658 weights: NetParams<i32>,
659}
660
661pub trait ParseRouterStatus: Sized + Sealed {
666 fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<Self>;
669
670 fn flavor() -> ConsensusFlavor;
673}
674
675pub trait RouterStatus: Sealed {
679 type DocumentDigest: Clone;
681
682 fn rsa_identity(&self) -> &RsaIdentity;
684
685 fn doc_digest(&self) -> &Self::DocumentDigest;
688}
689
690#[allow(dead_code)]
695#[cfg_attr(
696 feature = "dangerous-expose-struct-fields",
697 visible::StructFields(pub),
698 non_exhaustive
699)]
700#[derive(Debug, Clone)]
701pub struct Consensus<RS> {
702 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
704 header: ConsensusHeader,
705 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
707 voters: Vec<ConsensusVoterInfo>,
708 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
714 relays: Vec<RS>,
715 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
717 footer: Footer,
718}
719
720pub type MdConsensus = Consensus<MdConsensusRouterStatus>;
723
724pub type UnvalidatedMdConsensus = UnvalidatedConsensus<MdConsensusRouterStatus>;
727
728pub type UncheckedMdConsensus = UncheckedConsensus<MdConsensusRouterStatus>;
731
732#[cfg(feature = "ns_consensus")]
733pub type NsConsensus = Consensus<NsConsensusRouterStatus>;
736
737#[cfg(feature = "ns_consensus")]
738pub type UnvalidatedNsConsensus = UnvalidatedConsensus<NsConsensusRouterStatus>;
741
742#[cfg(feature = "ns_consensus")]
743pub type UncheckedNsConsensus = UncheckedConsensus<NsConsensusRouterStatus>;
746
747impl<RS> Consensus<RS> {
748 pub fn lifetime(&self) -> &Lifetime {
750 &self.header.hdr.lifetime
751 }
752
753 pub fn relays(&self) -> &[RS] {
755 &self.relays[..]
756 }
757
758 pub fn bandwidth_weights(&self) -> &NetParams<i32> {
761 &self.footer.weights
762 }
763
764 pub fn params(&self) -> &NetParams<i32> {
766 &self.header.hdr.params
767 }
768
769 pub fn shared_rand_cur(&self) -> Option<&SharedRandStatus> {
772 self.header.shared_rand_cur.as_ref()
773 }
774
775 pub fn shared_rand_prev(&self) -> Option<&SharedRandStatus> {
778 self.header.shared_rand_prev.as_ref()
779 }
780
781 pub fn relay_protocol_status(&self) -> &ProtoStatus {
784 &self.header.hdr.proto_statuses.relay
785 }
786
787 pub fn client_protocol_status(&self) -> &ProtoStatus {
790 &self.header.hdr.proto_statuses.client
791 }
792
793 pub fn protocol_statuses(&self) -> &Arc<ProtoStatuses> {
795 &self.header.hdr.proto_statuses
796 }
797}
798
799decl_keyword! {
800 #[non_exhaustive]
805 #[allow(missing_docs)]
806 pub NetstatusKwd {
807 "network-status-version" => NETWORK_STATUS_VERSION,
809 "vote-status" => VOTE_STATUS,
810 "consensus-methods" => CONSENSUS_METHODS,
811 "consensus-method" => CONSENSUS_METHOD,
812 "published" => PUBLISHED,
813 "valid-after" => VALID_AFTER,
814 "fresh-until" => FRESH_UNTIL,
815 "valid-until" => VALID_UNTIL,
816 "voting-delay" => VOTING_DELAY,
817 "client-versions" => CLIENT_VERSIONS,
818 "server-versions" => SERVER_VERSIONS,
819 "known-flags" => KNOWN_FLAGS,
820 "flag-thresholds" => FLAG_THRESHOLDS,
821 "recommended-client-protocols" => RECOMMENDED_CLIENT_PROTOCOLS,
822 "required-client-protocols" => REQUIRED_CLIENT_PROTOCOLS,
823 "recommended-relay-protocols" => RECOMMENDED_RELAY_PROTOCOLS,
824 "required-relay-protocols" => REQUIRED_RELAY_PROTOCOLS,
825 "params" => PARAMS,
826 "bandwidth-file-headers" => BANDWIDTH_FILE_HEADERS,
827 "bandwidth-file-digest" => BANDWIDTH_FILE_DIGEST,
828 "shared-rand-previous-value" => SHARED_RAND_PREVIOUS_VALUE,
832 "shared-rand-current-value" => SHARED_RAND_CURRENT_VALUE,
833
834 "dir-source" => DIR_SOURCE,
836 "contact" => CONTACT,
837
838 "legacy-dir-key" => LEGACY_DIR_KEY,
840 "shared-rand-participate" => SHARED_RAND_PARTICIPATE,
841 "shared-rand-commit" => SHARED_RAND_COMMIT,
842
843 "vote-digest" => VOTE_DIGEST,
845
846 "dir-key-certificate-version" => DIR_KEY_CERTIFICATE_VERSION,
848
849 "r" => RS_R,
851 "a" => RS_A,
852 "s" => RS_S,
853 "v" => RS_V,
854 "pr" => RS_PR,
855 "w" => RS_W,
856 "p" => RS_P,
857 "m" => RS_M,
858 "id" => RS_ID,
859
860 "directory-footer" => DIRECTORY_FOOTER,
862 "bandwidth-weights" => BANDWIDTH_WEIGHTS,
863 "directory-signature" => DIRECTORY_SIGNATURE,
864 }
865}
866
867static NS_HEADER_RULES_COMMON_: Lazy<SectionRulesBuilder<NetstatusKwd>> = Lazy::new(|| {
869 use NetstatusKwd::*;
870 let mut rules = SectionRules::builder();
871 rules.add(NETWORK_STATUS_VERSION.rule().required().args(1..=2));
872 rules.add(VOTE_STATUS.rule().required().args(1..));
873 rules.add(VALID_AFTER.rule().required());
874 rules.add(FRESH_UNTIL.rule().required());
875 rules.add(VALID_UNTIL.rule().required());
876 rules.add(VOTING_DELAY.rule().args(2..));
877 rules.add(CLIENT_VERSIONS.rule());
878 rules.add(SERVER_VERSIONS.rule());
879 rules.add(KNOWN_FLAGS.rule().required());
880 rules.add(RECOMMENDED_CLIENT_PROTOCOLS.rule().args(1..));
881 rules.add(RECOMMENDED_RELAY_PROTOCOLS.rule().args(1..));
882 rules.add(REQUIRED_CLIENT_PROTOCOLS.rule().args(1..));
883 rules.add(REQUIRED_RELAY_PROTOCOLS.rule().args(1..));
884 rules.add(PARAMS.rule());
885 rules
886});
887static NS_HEADER_RULES_CONSENSUS: Lazy<SectionRules<NetstatusKwd>> = Lazy::new(|| {
889 use NetstatusKwd::*;
890 let mut rules = NS_HEADER_RULES_COMMON_.clone();
891 rules.add(CONSENSUS_METHOD.rule().args(1..=1));
892 rules.add(SHARED_RAND_PREVIOUS_VALUE.rule().args(2..));
893 rules.add(SHARED_RAND_CURRENT_VALUE.rule().args(2..));
894 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
895 rules.build()
896});
897static NS_VOTERINFO_RULES_CONSENSUS: Lazy<SectionRules<NetstatusKwd>> = Lazy::new(|| {
928 use NetstatusKwd::*;
929 let mut rules = SectionRules::builder();
930 rules.add(DIR_SOURCE.rule().required().args(6..));
931 rules.add(CONTACT.rule().required());
932 rules.add(VOTE_DIGEST.rule().required());
933 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
934 rules.build()
935});
936static NS_ROUTERSTATUS_RULES_COMMON_: Lazy<SectionRulesBuilder<NetstatusKwd>> = Lazy::new(|| {
938 use NetstatusKwd::*;
939 let mut rules = SectionRules::builder();
940 rules.add(RS_A.rule().may_repeat().args(1..));
941 rules.add(RS_S.rule().required());
942 rules.add(RS_V.rule());
943 rules.add(RS_PR.rule().required());
944 rules.add(RS_W.rule());
945 rules.add(RS_P.rule().args(2..));
946 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
947 rules
948});
949
950static NS_ROUTERSTATUS_RULES_NSCON: Lazy<SectionRules<NetstatusKwd>> = Lazy::new(|| {
952 use NetstatusKwd::*;
953 let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
954 rules.add(RS_R.rule().required().args(8..));
955 rules.build()
956});
957
958static NS_ROUTERSTATUS_RULES_MDCON: Lazy<SectionRules<NetstatusKwd>> = Lazy::new(|| {
971 use NetstatusKwd::*;
972 let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
973 rules.add(RS_R.rule().required().args(6..));
974 rules.add(RS_M.rule().required().args(1..));
975 rules.build()
976});
977static NS_FOOTER_RULES: Lazy<SectionRules<NetstatusKwd>> = Lazy::new(|| {
979 use NetstatusKwd::*;
980 let mut rules = SectionRules::builder();
981 rules.add(DIRECTORY_FOOTER.rule().required().no_args());
982 rules.add(BANDWIDTH_WEIGHTS.rule());
984 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
985 rules.build()
986});
987
988impl ProtoStatus {
989 fn from_section(
991 sec: &Section<'_, NetstatusKwd>,
992 recommend_token: NetstatusKwd,
993 required_token: NetstatusKwd,
994 ) -> Result<ProtoStatus> {
995 fn parse(t: Option<&Item<'_, NetstatusKwd>>) -> Result<Protocols> {
997 if let Some(item) = t {
998 item.args_as_str()
999 .parse::<Protocols>()
1000 .map_err(|e| EK::BadArgument.at_pos(item.pos()).with_source(e))
1001 } else {
1002 Ok(Protocols::new())
1003 }
1004 }
1005
1006 let recommended = parse(sec.get(recommend_token))?;
1007 let required = parse(sec.get(required_token))?;
1008 Ok(ProtoStatus {
1009 recommended,
1010 required,
1011 })
1012 }
1013
1014 pub fn required_protocols(&self) -> &Protocols {
1021 &self.required
1022 }
1023
1024 pub fn recommended_protocols(&self) -> &Protocols {
1029 &self.recommended
1030 }
1031}
1032
1033impl<T> std::str::FromStr for NetParams<T>
1034where
1035 T: std::str::FromStr,
1036 T::Err: std::error::Error,
1037{
1038 type Err = Error;
1039 fn from_str(s: &str) -> Result<Self> {
1040 fn parse_pair<U>(p: &str) -> Result<(String, U)>
1042 where
1043 U: std::str::FromStr,
1044 U::Err: std::error::Error,
1045 {
1046 let parts: Vec<_> = p.splitn(2, '=').collect();
1047 if parts.len() != 2 {
1048 return Err(EK::BadArgument
1049 .at_pos(Pos::at(p))
1050 .with_msg("Missing = in key=value list"));
1051 }
1052 let num = parts[1].parse::<U>().map_err(|e| {
1053 EK::BadArgument
1054 .at_pos(Pos::at(parts[1]))
1055 .with_msg(e.to_string())
1056 })?;
1057 Ok((parts[0].to_string(), num))
1058 }
1059
1060 let params = s
1061 .split(' ')
1062 .filter(|p| !p.is_empty())
1063 .map(parse_pair)
1064 .collect::<Result<HashMap<_, _>>>()?;
1065 Ok(NetParams { params })
1066 }
1067}
1068
1069impl CommonHeader {
1070 fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<CommonHeader> {
1072 use NetstatusKwd::*;
1073
1074 {
1075 #[allow(clippy::unwrap_used)]
1078 let first = sec.first_item().unwrap();
1079 if first.kwd() != NETWORK_STATUS_VERSION {
1080 return Err(EK::UnexpectedToken
1081 .with_msg(first.kwd().to_str())
1082 .at_pos(first.pos()));
1083 }
1084 }
1085
1086 let ver_item = sec.required(NETWORK_STATUS_VERSION)?;
1087
1088 let version: u32 = ver_item.parse_arg(0)?;
1089 if version != 3 {
1090 return Err(EK::BadDocumentVersion.with_msg(version.to_string()));
1091 }
1092 let flavor = ConsensusFlavor::from_opt_name(ver_item.arg(1))?;
1093
1094 let valid_after = sec
1095 .required(VALID_AFTER)?
1096 .args_as_str()
1097 .parse::<Iso8601TimeSp>()?
1098 .into();
1099 let fresh_until = sec
1100 .required(FRESH_UNTIL)?
1101 .args_as_str()
1102 .parse::<Iso8601TimeSp>()?
1103 .into();
1104 let valid_until = sec
1105 .required(VALID_UNTIL)?
1106 .args_as_str()
1107 .parse::<Iso8601TimeSp>()?
1108 .into();
1109 let lifetime = Lifetime::new(valid_after, fresh_until, valid_until)?;
1110
1111 let client_versions = sec
1112 .maybe(CLIENT_VERSIONS)
1113 .args_as_str()
1114 .unwrap_or("")
1115 .split(',')
1116 .map(str::to_string)
1117 .collect();
1118 let relay_versions = sec
1119 .maybe(SERVER_VERSIONS)
1120 .args_as_str()
1121 .unwrap_or("")
1122 .split(',')
1123 .map(str::to_string)
1124 .collect();
1125
1126 let proto_statuses = {
1127 let client = ProtoStatus::from_section(
1128 sec,
1129 RECOMMENDED_CLIENT_PROTOCOLS,
1130 REQUIRED_CLIENT_PROTOCOLS,
1131 )?;
1132 let relay = ProtoStatus::from_section(
1133 sec,
1134 RECOMMENDED_RELAY_PROTOCOLS,
1135 REQUIRED_RELAY_PROTOCOLS,
1136 )?;
1137 Arc::new(ProtoStatuses { client, relay })
1138 };
1139
1140 let params = sec.maybe(PARAMS).args_as_str().unwrap_or("").parse()?;
1141
1142 let voting_delay = if let Some(tok) = sec.get(VOTING_DELAY) {
1143 let n1 = tok.parse_arg(0)?;
1144 let n2 = tok.parse_arg(1)?;
1145 Some((n1, n2))
1146 } else {
1147 None
1148 };
1149
1150 Ok(CommonHeader {
1151 flavor,
1152 lifetime,
1153 client_versions,
1154 relay_versions,
1155 proto_statuses,
1156 params,
1157 voting_delay,
1158 })
1159 }
1160}
1161
1162impl SharedRandStatus {
1163 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
1166 match item.kwd() {
1167 NetstatusKwd::SHARED_RAND_PREVIOUS_VALUE | NetstatusKwd::SHARED_RAND_CURRENT_VALUE => {}
1168 _ => {
1169 return Err(Error::from(internal!(
1170 "wrong keyword {:?} on shared-random value",
1171 item.kwd()
1172 ))
1173 .at_pos(item.pos()))
1174 }
1175 }
1176 let n_reveals: u8 = item.parse_arg(0)?;
1177 let val: B64 = item.parse_arg(1)?;
1178 let value = SharedRandVal(val.into_array()?);
1179 let timestamp = item
1181 .parse_optional_arg::<Iso8601TimeNoSp>(2)?
1182 .map(Into::into);
1183 Ok(SharedRandStatus {
1184 n_reveals,
1185 value,
1186 timestamp,
1187 })
1188 }
1189
1190 pub fn value(&self) -> &SharedRandVal {
1192 &self.value
1193 }
1194
1195 pub fn timestamp(&self) -> Option<std::time::SystemTime> {
1197 self.timestamp
1198 }
1199}
1200
1201impl ConsensusHeader {
1202 fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<ConsensusHeader> {
1204 use NetstatusKwd::*;
1205
1206 let status: &str = sec.required(VOTE_STATUS)?.arg(0).unwrap_or("");
1207 if status != "consensus" {
1208 return Err(EK::BadDocumentType.err());
1209 }
1210
1211 let hdr = CommonHeader::from_section(sec)?;
1214
1215 let consensus_method: u32 = sec.required(CONSENSUS_METHOD)?.parse_arg(0)?;
1216
1217 let shared_rand_prev = sec
1218 .get(SHARED_RAND_PREVIOUS_VALUE)
1219 .map(SharedRandStatus::from_item)
1220 .transpose()?;
1221
1222 let shared_rand_cur = sec
1223 .get(SHARED_RAND_CURRENT_VALUE)
1224 .map(SharedRandStatus::from_item)
1225 .transpose()?;
1226
1227 Ok(ConsensusHeader {
1228 hdr,
1229 consensus_method,
1230 shared_rand_prev,
1231 shared_rand_cur,
1232 })
1233 }
1234}
1235
1236impl DirSource {
1237 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
1239 if item.kwd() != NetstatusKwd::DIR_SOURCE {
1240 return Err(
1241 Error::from(internal!("Bad keyword {:?} on dir-source", item.kwd()))
1242 .at_pos(item.pos()),
1243 );
1244 }
1245 let nickname = item.required_arg(0)?.to_string();
1246 let identity = item.parse_arg::<Fingerprint>(1)?.into();
1247 let ip = item.parse_arg(3)?;
1248 let dir_port = item.parse_arg(4)?;
1249 let or_port = item.parse_arg(5)?;
1250
1251 Ok(DirSource {
1252 nickname,
1253 identity,
1254 ip,
1255 dir_port,
1256 or_port,
1257 })
1258 }
1259}
1260
1261impl ConsensusVoterInfo {
1262 fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<ConsensusVoterInfo> {
1264 use NetstatusKwd::*;
1265 #[allow(clippy::unwrap_used)]
1268 let first = sec.first_item().unwrap();
1269 if first.kwd() != DIR_SOURCE {
1270 return Err(Error::from(internal!(
1271 "Wrong keyword {:?} at start of voter info",
1272 first.kwd()
1273 ))
1274 .at_pos(first.pos()));
1275 }
1276 let dir_source = DirSource::from_item(sec.required(DIR_SOURCE)?)?;
1277
1278 let contact = sec.required(CONTACT)?.args_as_str().to_string();
1279
1280 let vote_digest = sec.required(VOTE_DIGEST)?.parse_arg::<B16>(0)?.into();
1281
1282 Ok(ConsensusVoterInfo {
1283 dir_source,
1284 contact,
1285 vote_digest,
1286 })
1287 }
1288}
1289
1290impl std::str::FromStr for RelayFlags {
1291 type Err = void::Void;
1292 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
1293 Ok(match s {
1294 "Authority" => RelayFlags::AUTHORITY,
1295 "BadExit" => RelayFlags::BAD_EXIT,
1296 "Exit" => RelayFlags::EXIT,
1297 "Fast" => RelayFlags::FAST,
1298 "Guard" => RelayFlags::GUARD,
1299 "HSDir" => RelayFlags::HSDIR,
1300 "MiddleOnly" => RelayFlags::MIDDLE_ONLY,
1301 "NoEdConsensus" => RelayFlags::NO_ED_CONSENSUS,
1302 "Stable" => RelayFlags::STABLE,
1303 "StaleDesc" => RelayFlags::STALE_DESC,
1304 "Running" => RelayFlags::RUNNING,
1305 "Valid" => RelayFlags::VALID,
1306 "V2Dir" => RelayFlags::V2DIR,
1307 _ => RelayFlags::empty(),
1308 })
1309 }
1310}
1311
1312impl RelayFlags {
1313 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<RelayFlags> {
1315 if item.kwd() != NetstatusKwd::RS_S {
1316 return Err(
1317 Error::from(internal!("Wrong keyword {:?} for S line", item.kwd()))
1318 .at_pos(item.pos()),
1319 );
1320 }
1321 let mut flags: RelayFlags = RelayFlags::RUNNING | RelayFlags::VALID;
1323
1324 let mut prev: Option<&str> = None;
1325 for s in item.args() {
1326 if let Some(p) = prev {
1327 if p >= s {
1328 return Err(EK::BadArgument
1330 .at_pos(item.pos())
1331 .with_msg("Flags out of order"));
1332 }
1333 }
1334 let fl = s.parse().void_unwrap();
1335 flags |= fl;
1336 prev = Some(s);
1337 }
1338
1339 Ok(flags)
1340 }
1341}
1342
1343impl Default for RelayWeight {
1344 fn default() -> RelayWeight {
1345 RelayWeight::Unmeasured(0)
1346 }
1347}
1348
1349impl RelayWeight {
1350 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<RelayWeight> {
1352 if item.kwd() != NetstatusKwd::RS_W {
1353 return Err(
1354 Error::from(internal!("Wrong keyword {:?} on W line", item.kwd()))
1355 .at_pos(item.pos()),
1356 );
1357 }
1358
1359 let params: NetParams<u32> = item.args_as_str().parse()?;
1360
1361 let bw = params.params.get("Bandwidth");
1362 let unmeas = params.params.get("Unmeasured");
1363
1364 let bw = match bw {
1365 None => return Ok(RelayWeight::Unmeasured(0)),
1366 Some(b) => *b,
1367 };
1368
1369 match unmeas {
1370 None | Some(0) => Ok(RelayWeight::Measured(bw)),
1371 Some(1) => Ok(RelayWeight::Unmeasured(bw)),
1372 _ => Err(EK::BadArgument
1373 .at_pos(item.pos())
1374 .with_msg("unmeasured value")),
1375 }
1376 }
1377}
1378
1379impl Footer {
1380 fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<Footer> {
1382 use NetstatusKwd::*;
1383 sec.required(DIRECTORY_FOOTER)?;
1384
1385 let weights = sec
1386 .maybe(BANDWIDTH_WEIGHTS)
1387 .args_as_str()
1388 .unwrap_or("")
1389 .parse()?;
1390
1391 Ok(Footer { weights })
1392 }
1393}
1394
1395enum SigCheckResult {
1397 Valid,
1399 Invalid,
1402 MissingCert,
1405}
1406
1407impl Signature {
1408 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Signature> {
1410 if item.kwd() != NetstatusKwd::DIRECTORY_SIGNATURE {
1411 return Err(Error::from(internal!(
1412 "Wrong keyword {:?} for directory signature",
1413 item.kwd()
1414 ))
1415 .at_pos(item.pos()));
1416 }
1417
1418 let (alg, id_fp, sk_fp) = if item.n_args() > 2 {
1419 (
1420 item.required_arg(0)?,
1421 item.required_arg(1)?,
1422 item.required_arg(2)?,
1423 )
1424 } else {
1425 ("sha1", item.required_arg(0)?, item.required_arg(1)?)
1426 };
1427
1428 let digestname = alg.to_string();
1429 let id_fingerprint = id_fp.parse::<Fingerprint>()?.into();
1430 let sk_fingerprint = sk_fp.parse::<Fingerprint>()?.into();
1431 let key_ids = AuthCertKeyIds {
1432 id_fingerprint,
1433 sk_fingerprint,
1434 };
1435 let signature = item.obj("SIGNATURE")?;
1436
1437 Ok(Signature {
1438 digestname,
1439 key_ids,
1440 signature,
1441 })
1442 }
1443
1444 fn matches_cert(&self, cert: &AuthCert) -> bool {
1447 cert.key_ids() == &self.key_ids
1448 }
1449
1450 fn find_cert<'a>(&self, certs: &'a [AuthCert]) -> Option<&'a AuthCert> {
1453 certs.iter().find(|&c| self.matches_cert(c))
1454 }
1455
1456 fn check_signature(&self, signed_digest: &[u8], certs: &[AuthCert]) -> SigCheckResult {
1460 match self.find_cert(certs) {
1461 None => SigCheckResult::MissingCert,
1462 Some(cert) => {
1463 let key = cert.signing_key();
1464 match key.verify(signed_digest, &self.signature[..]) {
1465 Ok(()) => SigCheckResult::Valid,
1466 Err(_) => SigCheckResult::Invalid,
1467 }
1468 }
1469 }
1470 }
1471}
1472
1473pub type UncheckedConsensus<RS> = TimerangeBound<UnvalidatedConsensus<RS>>;
1476
1477impl<RS: RouterStatus + ParseRouterStatus> Consensus<RS> {
1478 #[cfg(feature = "build_docs")]
1483 pub fn builder() -> ConsensusBuilder<RS> {
1484 ConsensusBuilder::new(RS::flavor())
1485 }
1486
1487 pub fn parse(s: &str) -> Result<(&str, &str, UncheckedConsensus<RS>)> {
1489 let mut reader = NetDocReader::new(s)?;
1490 Self::parse_from_reader(&mut reader).map_err(|e| e.within(s))
1491 }
1492 fn take_voterinfo(
1495 r: &mut NetDocReader<'_, NetstatusKwd>,
1496 ) -> Result<Option<ConsensusVoterInfo>> {
1497 use NetstatusKwd::*;
1498
1499 match r.peek() {
1500 None => return Ok(None),
1501 Some(e) if e.is_ok_with_kwd_in(&[RS_R, DIRECTORY_FOOTER]) => return Ok(None),
1502 _ => (),
1503 };
1504
1505 let mut first_dir_source = true;
1506 let mut p = r.pause_at(|i| match i {
1509 Err(_) => false,
1510 Ok(item) => {
1511 item.kwd() == RS_R
1512 || if item.kwd() == DIR_SOURCE {
1513 let was_first = first_dir_source;
1514 first_dir_source = false;
1515 !was_first
1516 } else {
1517 false
1518 }
1519 }
1520 });
1521
1522 let voter_sec = NS_VOTERINFO_RULES_CONSENSUS.parse(&mut p)?;
1523 let voter = ConsensusVoterInfo::from_section(&voter_sec)?;
1524
1525 Ok(Some(voter))
1526 }
1527
1528 fn take_footer(r: &mut NetDocReader<'_, NetstatusKwd>) -> Result<Footer> {
1530 use NetstatusKwd::*;
1531 let mut p = r.pause_at(|i| i.is_ok_with_kwd_in(&[DIRECTORY_SIGNATURE]));
1532 let footer_sec = NS_FOOTER_RULES.parse(&mut p)?;
1533 let footer = Footer::from_section(&footer_sec)?;
1534 Ok(footer)
1535 }
1536
1537 fn take_routerstatus(r: &mut NetDocReader<'_, NetstatusKwd>) -> Result<Option<(Pos, RS)>> {
1540 use NetstatusKwd::*;
1541 match r.peek() {
1542 None => return Ok(None),
1543 Some(e) if e.is_ok_with_kwd_in(&[DIRECTORY_FOOTER]) => return Ok(None),
1544 _ => (),
1545 };
1546
1547 let pos = r.pos();
1548
1549 let mut first_r = true;
1550 let mut p = r.pause_at(|i| match i {
1551 Err(_) => false,
1552 Ok(item) => {
1553 item.kwd() == DIRECTORY_FOOTER
1554 || if item.kwd() == RS_R {
1555 let was_first = first_r;
1556 first_r = false;
1557 !was_first
1558 } else {
1559 false
1560 }
1561 }
1562 });
1563
1564 let rules = match RS::flavor() {
1565 ConsensusFlavor::Microdesc => &NS_ROUTERSTATUS_RULES_MDCON,
1566 ConsensusFlavor::Ns => &NS_ROUTERSTATUS_RULES_NSCON,
1567 };
1568
1569 let rs_sec = rules.parse(&mut p)?;
1570 let rs = RS::from_section(&rs_sec)?;
1571 Ok(Some((pos, rs)))
1572 }
1573
1574 fn parse_from_reader<'a>(
1579 r: &mut NetDocReader<'a, NetstatusKwd>,
1580 ) -> Result<(&'a str, &'a str, UncheckedConsensus<RS>)> {
1581 use NetstatusKwd::*;
1582 let (header, start_pos) = {
1583 let mut h = r.pause_at(|i| i.is_ok_with_kwd_in(&[DIR_SOURCE]));
1584 let header_sec = NS_HEADER_RULES_CONSENSUS.parse(&mut h)?;
1585 #[allow(clippy::unwrap_used)]
1588 let pos = header_sec.first_item().unwrap().offset_in(r.str()).unwrap();
1589 (ConsensusHeader::from_section(&header_sec)?, pos)
1590 };
1591 if RS::flavor() != header.hdr.flavor {
1592 return Err(EK::BadDocumentType.with_msg(format!(
1593 "Expected {:?}, got {:?}",
1594 RS::flavor(),
1595 header.hdr.flavor
1596 )));
1597 }
1598
1599 let mut voters = Vec::new();
1600
1601 while let Some(voter) = Self::take_voterinfo(r)? {
1602 voters.push(voter);
1603 }
1604
1605 let mut relays: Vec<RS> = Vec::new();
1606 while let Some((pos, routerstatus)) = Self::take_routerstatus(r)? {
1607 if let Some(prev) = relays.last() {
1608 if prev.rsa_identity() >= routerstatus.rsa_identity() {
1609 return Err(EK::WrongSortOrder.at_pos(pos));
1610 }
1611 }
1612 relays.push(routerstatus);
1613 }
1614 relays.shrink_to_fit();
1615
1616 let footer = Self::take_footer(r)?;
1617
1618 let consensus = Consensus {
1619 header,
1620 voters,
1621 relays,
1622 footer,
1623 };
1624
1625 let mut first_sig: Option<Item<'_, NetstatusKwd>> = None;
1627 let mut signatures = Vec::new();
1628 for item in &mut *r {
1629 let item = item?;
1630 if item.kwd() != DIRECTORY_SIGNATURE {
1631 return Err(EK::UnexpectedToken
1632 .with_msg(item.kwd().to_str())
1633 .at_pos(item.pos()));
1634 }
1635
1636 let sig = Signature::from_item(&item)?;
1637 if first_sig.is_none() {
1638 first_sig = Some(item);
1639 }
1640 signatures.push(sig);
1641 }
1642
1643 let end_pos = match first_sig {
1644 None => return Err(EK::MissingToken.with_msg("directory-signature")),
1645 #[allow(clippy::unwrap_used)]
1647 Some(sig) => sig.offset_in(r.str()).unwrap() + "directory-signature ".len(),
1648 };
1649
1650 let signed_str = &r.str()[start_pos..end_pos];
1652 let remainder = &r.str()[end_pos..];
1653 let (sha256, sha1) = match RS::flavor() {
1654 ConsensusFlavor::Ns => (
1655 None,
1656 Some(ll::d::Sha1::digest(signed_str.as_bytes()).into()),
1657 ),
1658 ConsensusFlavor::Microdesc => (
1659 Some(ll::d::Sha256::digest(signed_str.as_bytes()).into()),
1660 None,
1661 ),
1662 };
1663 let siggroup = SignatureGroup {
1664 sha256,
1665 sha1,
1666 signatures,
1667 };
1668
1669 let unval = UnvalidatedConsensus {
1670 consensus,
1671 siggroup,
1672 n_authorities: None,
1673 };
1674 let lifetime = unval.consensus.header.hdr.lifetime.clone();
1675 let delay = unval.consensus.header.hdr.voting_delay.unwrap_or((0, 0));
1676 let dist_interval = time::Duration::from_secs(delay.1.into());
1677 let starting_time = lifetime.valid_after - dist_interval;
1678 let timebound = TimerangeBound::new(unval, starting_time..lifetime.valid_until);
1679 Ok((signed_str, remainder, timebound))
1680 }
1681}
1682
1683#[cfg_attr(
1690 feature = "dangerous-expose-struct-fields",
1691 visible::StructFields(pub),
1692 non_exhaustive
1693)]
1694#[derive(Debug, Clone)]
1695pub struct UnvalidatedConsensus<RS> {
1696 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
1699 consensus: Consensus<RS>,
1700 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
1703 siggroup: SignatureGroup,
1704 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
1708 n_authorities: Option<u16>,
1709}
1710
1711impl<RS> UnvalidatedConsensus<RS> {
1712 #[must_use]
1716 pub fn set_n_authorities(self, n_authorities: u16) -> Self {
1717 UnvalidatedConsensus {
1718 n_authorities: Some(n_authorities),
1719 ..self
1720 }
1721 }
1722
1723 pub fn signing_cert_ids(&self) -> impl Iterator<Item = AuthCertKeyIds> {
1726 match self.key_is_correct(&[]) {
1727 Ok(()) => Vec::new(),
1728 Err(missing) => missing,
1729 }
1730 .into_iter()
1731 }
1732
1733 pub fn peek_lifetime(&self) -> &Lifetime {
1735 self.consensus.lifetime()
1736 }
1737
1738 pub fn authorities_are_correct(&self, authorities: &[&RsaIdentity]) -> bool {
1745 self.siggroup.could_validate(authorities)
1746 }
1747
1748 #[cfg(feature = "experimental-api")]
1753 pub fn n_relays(&self) -> usize {
1754 self.consensus.relays.len()
1755 }
1756
1757 #[cfg(feature = "experimental-api")]
1766 pub fn modify_relays<F>(&mut self, func: F)
1767 where
1768 F: FnOnce(&mut Vec<RS>),
1769 {
1770 func(&mut self.consensus.relays);
1771 }
1772}
1773
1774impl<RS> ExternallySigned<Consensus<RS>> for UnvalidatedConsensus<RS> {
1775 type Key = [AuthCert];
1776 type KeyHint = Vec<AuthCertKeyIds>;
1777 type Error = Error;
1778
1779 fn key_is_correct(&self, k: &Self::Key) -> result::Result<(), Self::KeyHint> {
1780 let (n_ok, missing) = self.siggroup.list_missing(k);
1781 match self.n_authorities {
1782 Some(n) if n_ok > (n / 2) as usize => Ok(()),
1783 _ => Err(missing.iter().map(|cert| cert.key_ids).collect()),
1784 }
1785 }
1786 fn is_well_signed(&self, k: &Self::Key) -> result::Result<(), Self::Error> {
1787 match self.n_authorities {
1788 None => Err(Error::from(internal!(
1789 "Didn't set authorities on consensus"
1790 ))),
1791 Some(authority) => {
1792 if self.siggroup.validate(authority, k) {
1793 Ok(())
1794 } else {
1795 Err(EK::BadSignature.err())
1796 }
1797 }
1798 }
1799 }
1800 fn dangerously_assume_wellsigned(self) -> Consensus<RS> {
1801 self.consensus
1802 }
1803}
1804
1805impl SignatureGroup {
1806 fn list_missing(&self, certs: &[AuthCert]) -> (usize, Vec<&Signature>) {
1813 let mut ok: HashSet<RsaIdentity> = HashSet::new();
1814 let mut missing = Vec::new();
1815 for sig in &self.signatures {
1816 let id_fingerprint = &sig.key_ids.id_fingerprint;
1817 if ok.contains(id_fingerprint) {
1818 continue;
1819 }
1820 if sig.find_cert(certs).is_some() {
1821 ok.insert(*id_fingerprint);
1822 continue;
1823 }
1824
1825 missing.push(sig);
1826 }
1827 (ok.len(), missing)
1828 }
1829
1830 fn could_validate(&self, authorities: &[&RsaIdentity]) -> bool {
1834 let mut signed_by: HashSet<RsaIdentity> = HashSet::new();
1835 for sig in &self.signatures {
1836 let id_fp = &sig.key_ids.id_fingerprint;
1837 if signed_by.contains(id_fp) {
1838 continue;
1840 }
1841 if authorities.contains(&id_fp) {
1842 signed_by.insert(*id_fp);
1843 }
1844 }
1845
1846 signed_by.len() > (authorities.len() / 2)
1847 }
1848
1849 fn validate(&self, n_authorities: u16, certs: &[AuthCert]) -> bool {
1856 let mut ok: HashSet<RsaIdentity> = HashSet::new();
1860
1861 for sig in &self.signatures {
1862 let id_fingerprint = &sig.key_ids.id_fingerprint;
1863 if ok.contains(id_fingerprint) {
1864 continue;
1867 }
1868
1869 let d: Option<&[u8]> = match sig.digestname.as_ref() {
1870 "sha256" => self.sha256.as_ref().map(|a| &a[..]),
1871 "sha1" => self.sha1.as_ref().map(|a| &a[..]),
1872 _ => None, };
1874 if d.is_none() {
1875 continue;
1878 }
1879
1880 #[allow(clippy::unwrap_used)]
1882 match sig.check_signature(d.as_ref().unwrap(), certs) {
1883 SigCheckResult::Valid => {
1884 ok.insert(*id_fingerprint);
1885 }
1886 _ => continue,
1887 }
1888 }
1889
1890 ok.len() > (n_authorities / 2) as usize
1891 }
1892}
1893
1894#[cfg(test)]
1895mod test {
1896 #![allow(clippy::bool_assert_comparison)]
1898 #![allow(clippy::clone_on_copy)]
1899 #![allow(clippy::dbg_macro)]
1900 #![allow(clippy::mixed_attributes_style)]
1901 #![allow(clippy::print_stderr)]
1902 #![allow(clippy::print_stdout)]
1903 #![allow(clippy::single_char_pattern)]
1904 #![allow(clippy::unwrap_used)]
1905 #![allow(clippy::unchecked_duration_subtraction)]
1906 #![allow(clippy::useless_vec)]
1907 #![allow(clippy::needless_pass_by_value)]
1908 use super::*;
1910 use hex_literal::hex;
1911
1912 const CERTS: &str = include_str!("../../testdata/authcerts2.txt");
1913 const CONSENSUS: &str = include_str!("../../testdata/mdconsensus1.txt");
1914
1915 #[cfg(feature = "ns_consensus")]
1916 const NS_CERTS: &str = include_str!("../../testdata/authcerts3.txt");
1917 #[cfg(feature = "ns_consensus")]
1918 const NS_CONSENSUS: &str = include_str!("../../testdata/nsconsensus1.txt");
1919
1920 fn read_bad(fname: &str) -> String {
1921 use std::fs;
1922 use std::path::PathBuf;
1923 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1924 path.push("testdata");
1925 path.push("bad-mdconsensus");
1926 path.push(fname);
1927
1928 fs::read_to_string(path).unwrap()
1929 }
1930
1931 #[test]
1932 fn parse_and_validate_md() -> Result<()> {
1933 use std::net::SocketAddr;
1934 use tor_checkable::{SelfSigned, Timebound};
1935 let mut certs = Vec::new();
1936 for cert in AuthCert::parse_multiple(CERTS)? {
1937 let cert = cert?.check_signature()?.dangerously_assume_timely();
1938 certs.push(cert);
1939 }
1940 let auth_ids: Vec<_> = certs.iter().map(|c| &c.key_ids().id_fingerprint).collect();
1941
1942 assert_eq!(certs.len(), 3);
1943
1944 let (_, _, consensus) = MdConsensus::parse(CONSENSUS)?;
1945 let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
1946
1947 assert!(consensus.authorities_are_correct(&auth_ids));
1949 assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
1951 {
1952 let bad_auth_id = (*b"xxxxxxxxxxxxxxxxxxxx").into();
1955 assert!(!consensus.authorities_are_correct(&[&bad_auth_id]));
1956 }
1957
1958 let missing = consensus.key_is_correct(&[]).err().unwrap();
1959 assert_eq!(3, missing.len());
1960 assert!(consensus.key_is_correct(&certs).is_ok());
1961 let missing = consensus.key_is_correct(&certs[0..1]).err().unwrap();
1962 assert_eq!(2, missing.len());
1963
1964 let same_three_times = vec![certs[0].clone(), certs[0].clone(), certs[0].clone()];
1966 let missing = consensus.key_is_correct(&same_three_times).err().unwrap();
1967
1968 assert_eq!(2, missing.len());
1969 assert!(consensus.is_well_signed(&same_three_times).is_err());
1970
1971 assert!(consensus.key_is_correct(&certs).is_ok());
1972 let consensus = consensus.check_signature(&certs)?;
1973
1974 assert_eq!(6, consensus.relays().len());
1975 let r0 = &consensus.relays()[0];
1976 assert_eq!(
1977 r0.md_digest(),
1978 &hex!("73dabe0a0468f4f7a67810a18d11e36731bb1d2ec3634db459100609f3b3f535")
1979 );
1980 assert_eq!(
1981 r0.rsa_identity().as_bytes(),
1982 &hex!("0a3057af2910415794d8ea430309d9ac5f5d524b")
1983 );
1984 assert!(!r0.weight().is_measured());
1985 assert!(!r0.weight().is_nonzero());
1986 let pv = &r0.protovers();
1987 assert!(pv.supports_subver("HSDir", 2));
1988 assert!(!pv.supports_subver("HSDir", 3));
1989 let ip4 = "127.0.0.1:5002".parse::<SocketAddr>().unwrap();
1990 let ip6 = "[::1]:5002".parse::<SocketAddr>().unwrap();
1991 assert!(r0.orport_addrs().any(|a| a == &ip4));
1992 assert!(r0.orport_addrs().any(|a| a == &ip6));
1993
1994 Ok(())
1995 }
1996
1997 #[test]
1998 #[cfg(feature = "ns_consensus")]
1999 fn parse_and_validate_ns() -> Result<()> {
2000 use tor_checkable::{SelfSigned, Timebound};
2001 let mut certs = Vec::new();
2002 for cert in AuthCert::parse_multiple(NS_CERTS)? {
2003 let cert = cert?.check_signature()?.dangerously_assume_timely();
2004 certs.push(cert);
2005 }
2006 let auth_ids: Vec<_> = certs.iter().map(|c| &c.key_ids().id_fingerprint).collect();
2007 assert_eq!(certs.len(), 3);
2008
2009 let (_, _, consensus) = NsConsensus::parse(NS_CONSENSUS)?;
2010 let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
2011 assert!(consensus.authorities_are_correct(&auth_ids));
2013 assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
2015
2016 assert!(consensus.key_is_correct(&certs).is_ok());
2017
2018 let _consensus = consensus.check_signature(&certs)?;
2019
2020 Ok(())
2021 }
2022
2023 #[test]
2024 fn test_bad() {
2025 use crate::Pos;
2026 fn check(fname: &str, e: &Error) {
2027 let content = read_bad(fname);
2028 let res = MdConsensus::parse(&content);
2029 assert!(res.is_err());
2030 assert_eq!(&res.err().unwrap(), e);
2031 }
2032
2033 check(
2034 "bad-flags",
2035 &EK::BadArgument
2036 .at_pos(Pos::from_line(27, 1))
2037 .with_msg("Flags out of order"),
2038 );
2039 check(
2040 "bad-md-digest",
2041 &EK::BadArgument
2042 .at_pos(Pos::from_line(40, 3))
2043 .with_msg("Invalid base64"),
2044 );
2045 check(
2046 "bad-weight",
2047 &EK::BadArgument
2048 .at_pos(Pos::from_line(67, 141))
2049 .with_msg("invalid digit found in string"),
2050 );
2051 check(
2052 "bad-weights",
2053 &EK::BadArgument
2054 .at_pos(Pos::from_line(51, 13))
2055 .with_msg("invalid digit found in string"),
2056 );
2057 check(
2058 "wrong-order",
2059 &EK::WrongSortOrder.at_pos(Pos::from_line(52, 1)),
2060 );
2061 check(
2062 "wrong-start",
2063 &EK::UnexpectedToken
2064 .with_msg("vote-status")
2065 .at_pos(Pos::from_line(1, 1)),
2066 );
2067 check("wrong-version", &EK::BadDocumentVersion.with_msg("10"));
2068 }
2069
2070 fn gettok(s: &str) -> Result<Item<'_, NetstatusKwd>> {
2071 let mut reader = NetDocReader::new(s)?;
2072 let tok = reader.next().unwrap();
2073 assert!(reader.next().is_none());
2074 tok
2075 }
2076
2077 #[test]
2078 fn test_weight() {
2079 let w = gettok("w Unmeasured=1 Bandwidth=6\n").unwrap();
2080 let w = RelayWeight::from_item(&w).unwrap();
2081 assert!(!w.is_measured());
2082 assert!(w.is_nonzero());
2083
2084 let w = gettok("w Bandwidth=10\n").unwrap();
2085 let w = RelayWeight::from_item(&w).unwrap();
2086 assert!(w.is_measured());
2087 assert!(w.is_nonzero());
2088
2089 let w = RelayWeight::default();
2090 assert!(!w.is_measured());
2091 assert!(!w.is_nonzero());
2092
2093 let w = gettok("w Mustelid=66 Cheato=7 Unmeasured=1\n").unwrap();
2094 let w = RelayWeight::from_item(&w).unwrap();
2095 assert!(!w.is_measured());
2096 assert!(!w.is_nonzero());
2097
2098 let w = gettok("r foo\n").unwrap();
2099 let w = RelayWeight::from_item(&w);
2100 assert!(w.is_err());
2101
2102 let w = gettok("r Bandwidth=6 Unmeasured=Frog\n").unwrap();
2103 let w = RelayWeight::from_item(&w);
2104 assert!(w.is_err());
2105
2106 let w = gettok("r Bandwidth=6 Unmeasured=3\n").unwrap();
2107 let w = RelayWeight::from_item(&w);
2108 assert!(w.is_err());
2109 }
2110
2111 #[test]
2112 fn test_netparam() {
2113 let p = "Hello=600 Goodbye=5 Fred=7"
2114 .parse::<NetParams<u32>>()
2115 .unwrap();
2116 assert_eq!(p.get("Hello"), Some(&600_u32));
2117
2118 let p = "Hello=Goodbye=5 Fred=7".parse::<NetParams<u32>>();
2119 assert!(p.is_err());
2120
2121 let p = "Hello=Goodbye Fred=7".parse::<NetParams<u32>>();
2122 assert!(p.is_err());
2123 }
2124
2125 #[test]
2126 fn test_sharedrand() {
2127 let sr =
2128 gettok("shared-rand-previous-value 9 5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4\n")
2129 .unwrap();
2130 let sr = SharedRandStatus::from_item(&sr).unwrap();
2131
2132 assert_eq!(sr.n_reveals, 9);
2133 assert_eq!(
2134 sr.value.0,
2135 hex!("e4ba1d638c96c458532adc6957dc0080d03d37c7e5854087d0da90bf5ff4e72e")
2136 );
2137 assert!(sr.timestamp.is_none());
2138
2139 let sr2 = gettok(
2140 "shared-rand-current-value 9 \
2141 5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4 2022-01-20T12:34:56\n",
2142 )
2143 .unwrap();
2144 let sr2 = SharedRandStatus::from_item(&sr2).unwrap();
2145 assert_eq!(sr2.n_reveals, sr.n_reveals);
2146 assert_eq!(sr2.value.0, sr.value.0);
2147 assert_eq!(
2148 sr2.timestamp.unwrap(),
2149 humantime::parse_rfc3339("2022-01-20T12:34:56Z").unwrap()
2150 );
2151
2152 let sr = gettok("foo bar\n").unwrap();
2153 let sr = SharedRandStatus::from_item(&sr);
2154 assert!(sr.is_err());
2155 }
2156
2157 #[test]
2158 fn test_protostatus() {
2159 let my_protocols: Protocols = "Link=7 Cons=1-5 Desc=3-10".parse().unwrap();
2160
2161 let outcome = ProtoStatus {
2162 recommended: "Link=7".parse().unwrap(),
2163 required: "Desc=5".parse().unwrap(),
2164 }
2165 .check_protocols(&my_protocols);
2166 assert!(outcome.is_ok());
2167
2168 let outcome = ProtoStatus {
2169 recommended: "Microdesc=4 Link=7".parse().unwrap(),
2170 required: "Desc=5".parse().unwrap(),
2171 }
2172 .check_protocols(&my_protocols);
2173 assert_eq!(
2174 outcome,
2175 Err(ProtocolSupportError::MissingRecommended(
2176 "Microdesc=4".parse().unwrap()
2177 ))
2178 );
2179
2180 let outcome = ProtoStatus {
2181 recommended: "Microdesc=4 Link=7".parse().unwrap(),
2182 required: "Desc=5 Cons=5-12 Wombat=15".parse().unwrap(),
2183 }
2184 .check_protocols(&my_protocols);
2185 assert_eq!(
2186 outcome,
2187 Err(ProtocolSupportError::MissingRequired(
2188 "Cons=6-12 Wombat=15".parse().unwrap()
2189 ))
2190 );
2191 }
2192
2193 #[test]
2194 fn serialize_protostatus() {
2195 let ps = ProtoStatuses {
2196 client: ProtoStatus {
2197 recommended: "Link=1-5 LinkAuth=2-5".parse().unwrap(),
2198 required: "Link=5 LinkAuth=3".parse().unwrap(),
2199 },
2200 relay: ProtoStatus {
2201 recommended: "Wombat=20-30 Knish=20-30".parse().unwrap(),
2202 required: "Wombat=20-22 Knish=25-27".parse().unwrap(),
2203 },
2204 };
2205 let json = serde_json::to_string(&ps).unwrap();
2206 let ps2 = serde_json::from_str(json.as_str()).unwrap();
2207 assert_eq!(ps, ps2);
2208
2209 let ps3: ProtoStatuses = serde_json::from_str(
2210 r#"{
2211 "client":{
2212 "required":"Link=5 LinkAuth=3",
2213 "recommended":"Link=1-5 LinkAuth=2-5"
2214 },
2215 "relay":{
2216 "required":"Wombat=20-22 Knish=25-27",
2217 "recommended":"Wombat=20-30 Knish=20-30"
2218 }
2219 }"#,
2220 )
2221 .unwrap();
2222 assert_eq!(ps, ps3);
2223 }
2224}