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 std::sync::LazyLock;
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_: LazyLock<SectionRulesBuilder<NetstatusKwd>> = LazyLock::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: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::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: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::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_: LazyLock<SectionRulesBuilder<NetstatusKwd>> =
938 LazyLock::new(|| {
939 use NetstatusKwd::*;
940 let mut rules = SectionRules::builder();
941 rules.add(RS_A.rule().may_repeat().args(1..));
942 rules.add(RS_S.rule().required());
943 rules.add(RS_V.rule());
944 rules.add(RS_PR.rule().required());
945 rules.add(RS_W.rule());
946 rules.add(RS_P.rule().args(2..));
947 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
948 rules
949 });
950
951static NS_ROUTERSTATUS_RULES_NSCON: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
953 use NetstatusKwd::*;
954 let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
955 rules.add(RS_R.rule().required().args(8..));
956 rules.build()
957});
958
959static NS_ROUTERSTATUS_RULES_MDCON: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
972 use NetstatusKwd::*;
973 let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
974 rules.add(RS_R.rule().required().args(6..));
975 rules.add(RS_M.rule().required().args(1..));
976 rules.build()
977});
978static NS_FOOTER_RULES: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
980 use NetstatusKwd::*;
981 let mut rules = SectionRules::builder();
982 rules.add(DIRECTORY_FOOTER.rule().required().no_args());
983 rules.add(BANDWIDTH_WEIGHTS.rule());
985 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
986 rules.build()
987});
988
989impl ProtoStatus {
990 fn from_section(
992 sec: &Section<'_, NetstatusKwd>,
993 recommend_token: NetstatusKwd,
994 required_token: NetstatusKwd,
995 ) -> Result<ProtoStatus> {
996 fn parse(t: Option<&Item<'_, NetstatusKwd>>) -> Result<Protocols> {
998 if let Some(item) = t {
999 item.args_as_str()
1000 .parse::<Protocols>()
1001 .map_err(|e| EK::BadArgument.at_pos(item.pos()).with_source(e))
1002 } else {
1003 Ok(Protocols::new())
1004 }
1005 }
1006
1007 let recommended = parse(sec.get(recommend_token))?;
1008 let required = parse(sec.get(required_token))?;
1009 Ok(ProtoStatus {
1010 recommended,
1011 required,
1012 })
1013 }
1014
1015 pub fn required_protocols(&self) -> &Protocols {
1022 &self.required
1023 }
1024
1025 pub fn recommended_protocols(&self) -> &Protocols {
1030 &self.recommended
1031 }
1032}
1033
1034impl<T> std::str::FromStr for NetParams<T>
1035where
1036 T: std::str::FromStr,
1037 T::Err: std::error::Error,
1038{
1039 type Err = Error;
1040 fn from_str(s: &str) -> Result<Self> {
1041 fn parse_pair<U>(p: &str) -> Result<(String, U)>
1043 where
1044 U: std::str::FromStr,
1045 U::Err: std::error::Error,
1046 {
1047 let parts: Vec<_> = p.splitn(2, '=').collect();
1048 if parts.len() != 2 {
1049 return Err(EK::BadArgument
1050 .at_pos(Pos::at(p))
1051 .with_msg("Missing = in key=value list"));
1052 }
1053 let num = parts[1].parse::<U>().map_err(|e| {
1054 EK::BadArgument
1055 .at_pos(Pos::at(parts[1]))
1056 .with_msg(e.to_string())
1057 })?;
1058 Ok((parts[0].to_string(), num))
1059 }
1060
1061 let params = s
1062 .split(' ')
1063 .filter(|p| !p.is_empty())
1064 .map(parse_pair)
1065 .collect::<Result<HashMap<_, _>>>()?;
1066 Ok(NetParams { params })
1067 }
1068}
1069
1070impl CommonHeader {
1071 fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<CommonHeader> {
1073 use NetstatusKwd::*;
1074
1075 {
1076 #[allow(clippy::unwrap_used)]
1079 let first = sec.first_item().unwrap();
1080 if first.kwd() != NETWORK_STATUS_VERSION {
1081 return Err(EK::UnexpectedToken
1082 .with_msg(first.kwd().to_str())
1083 .at_pos(first.pos()));
1084 }
1085 }
1086
1087 let ver_item = sec.required(NETWORK_STATUS_VERSION)?;
1088
1089 let version: u32 = ver_item.parse_arg(0)?;
1090 if version != 3 {
1091 return Err(EK::BadDocumentVersion.with_msg(version.to_string()));
1092 }
1093 let flavor = ConsensusFlavor::from_opt_name(ver_item.arg(1))?;
1094
1095 let valid_after = sec
1096 .required(VALID_AFTER)?
1097 .args_as_str()
1098 .parse::<Iso8601TimeSp>()?
1099 .into();
1100 let fresh_until = sec
1101 .required(FRESH_UNTIL)?
1102 .args_as_str()
1103 .parse::<Iso8601TimeSp>()?
1104 .into();
1105 let valid_until = sec
1106 .required(VALID_UNTIL)?
1107 .args_as_str()
1108 .parse::<Iso8601TimeSp>()?
1109 .into();
1110 let lifetime = Lifetime::new(valid_after, fresh_until, valid_until)?;
1111
1112 let client_versions = sec
1113 .maybe(CLIENT_VERSIONS)
1114 .args_as_str()
1115 .unwrap_or("")
1116 .split(',')
1117 .map(str::to_string)
1118 .collect();
1119 let relay_versions = sec
1120 .maybe(SERVER_VERSIONS)
1121 .args_as_str()
1122 .unwrap_or("")
1123 .split(',')
1124 .map(str::to_string)
1125 .collect();
1126
1127 let proto_statuses = {
1128 let client = ProtoStatus::from_section(
1129 sec,
1130 RECOMMENDED_CLIENT_PROTOCOLS,
1131 REQUIRED_CLIENT_PROTOCOLS,
1132 )?;
1133 let relay = ProtoStatus::from_section(
1134 sec,
1135 RECOMMENDED_RELAY_PROTOCOLS,
1136 REQUIRED_RELAY_PROTOCOLS,
1137 )?;
1138 Arc::new(ProtoStatuses { client, relay })
1139 };
1140
1141 let params = sec.maybe(PARAMS).args_as_str().unwrap_or("").parse()?;
1142
1143 let voting_delay = if let Some(tok) = sec.get(VOTING_DELAY) {
1144 let n1 = tok.parse_arg(0)?;
1145 let n2 = tok.parse_arg(1)?;
1146 Some((n1, n2))
1147 } else {
1148 None
1149 };
1150
1151 Ok(CommonHeader {
1152 flavor,
1153 lifetime,
1154 client_versions,
1155 relay_versions,
1156 proto_statuses,
1157 params,
1158 voting_delay,
1159 })
1160 }
1161}
1162
1163impl SharedRandStatus {
1164 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
1167 match item.kwd() {
1168 NetstatusKwd::SHARED_RAND_PREVIOUS_VALUE | NetstatusKwd::SHARED_RAND_CURRENT_VALUE => {}
1169 _ => {
1170 return Err(Error::from(internal!(
1171 "wrong keyword {:?} on shared-random value",
1172 item.kwd()
1173 ))
1174 .at_pos(item.pos()))
1175 }
1176 }
1177 let n_reveals: u8 = item.parse_arg(0)?;
1178 let val: B64 = item.parse_arg(1)?;
1179 let value = SharedRandVal(val.into_array()?);
1180 let timestamp = item
1182 .parse_optional_arg::<Iso8601TimeNoSp>(2)?
1183 .map(Into::into);
1184 Ok(SharedRandStatus {
1185 n_reveals,
1186 value,
1187 timestamp,
1188 })
1189 }
1190
1191 pub fn value(&self) -> &SharedRandVal {
1193 &self.value
1194 }
1195
1196 pub fn timestamp(&self) -> Option<std::time::SystemTime> {
1198 self.timestamp
1199 }
1200}
1201
1202impl ConsensusHeader {
1203 fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<ConsensusHeader> {
1205 use NetstatusKwd::*;
1206
1207 let status: &str = sec.required(VOTE_STATUS)?.arg(0).unwrap_or("");
1208 if status != "consensus" {
1209 return Err(EK::BadDocumentType.err());
1210 }
1211
1212 let hdr = CommonHeader::from_section(sec)?;
1215
1216 let consensus_method: u32 = sec.required(CONSENSUS_METHOD)?.parse_arg(0)?;
1217
1218 let shared_rand_prev = sec
1219 .get(SHARED_RAND_PREVIOUS_VALUE)
1220 .map(SharedRandStatus::from_item)
1221 .transpose()?;
1222
1223 let shared_rand_cur = sec
1224 .get(SHARED_RAND_CURRENT_VALUE)
1225 .map(SharedRandStatus::from_item)
1226 .transpose()?;
1227
1228 Ok(ConsensusHeader {
1229 hdr,
1230 consensus_method,
1231 shared_rand_prev,
1232 shared_rand_cur,
1233 })
1234 }
1235}
1236
1237impl DirSource {
1238 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
1240 if item.kwd() != NetstatusKwd::DIR_SOURCE {
1241 return Err(
1242 Error::from(internal!("Bad keyword {:?} on dir-source", item.kwd()))
1243 .at_pos(item.pos()),
1244 );
1245 }
1246 let nickname = item.required_arg(0)?.to_string();
1247 let identity = item.parse_arg::<Fingerprint>(1)?.into();
1248 let ip = item.parse_arg(3)?;
1249 let dir_port = item.parse_arg(4)?;
1250 let or_port = item.parse_arg(5)?;
1251
1252 Ok(DirSource {
1253 nickname,
1254 identity,
1255 ip,
1256 dir_port,
1257 or_port,
1258 })
1259 }
1260}
1261
1262impl ConsensusVoterInfo {
1263 fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<ConsensusVoterInfo> {
1265 use NetstatusKwd::*;
1266 #[allow(clippy::unwrap_used)]
1269 let first = sec.first_item().unwrap();
1270 if first.kwd() != DIR_SOURCE {
1271 return Err(Error::from(internal!(
1272 "Wrong keyword {:?} at start of voter info",
1273 first.kwd()
1274 ))
1275 .at_pos(first.pos()));
1276 }
1277 let dir_source = DirSource::from_item(sec.required(DIR_SOURCE)?)?;
1278
1279 let contact = sec.required(CONTACT)?.args_as_str().to_string();
1280
1281 let vote_digest = sec.required(VOTE_DIGEST)?.parse_arg::<B16>(0)?.into();
1282
1283 Ok(ConsensusVoterInfo {
1284 dir_source,
1285 contact,
1286 vote_digest,
1287 })
1288 }
1289}
1290
1291impl std::str::FromStr for RelayFlags {
1292 type Err = void::Void;
1293 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
1294 Ok(match s {
1295 "Authority" => RelayFlags::AUTHORITY,
1296 "BadExit" => RelayFlags::BAD_EXIT,
1297 "Exit" => RelayFlags::EXIT,
1298 "Fast" => RelayFlags::FAST,
1299 "Guard" => RelayFlags::GUARD,
1300 "HSDir" => RelayFlags::HSDIR,
1301 "MiddleOnly" => RelayFlags::MIDDLE_ONLY,
1302 "NoEdConsensus" => RelayFlags::NO_ED_CONSENSUS,
1303 "Stable" => RelayFlags::STABLE,
1304 "StaleDesc" => RelayFlags::STALE_DESC,
1305 "Running" => RelayFlags::RUNNING,
1306 "Valid" => RelayFlags::VALID,
1307 "V2Dir" => RelayFlags::V2DIR,
1308 _ => RelayFlags::empty(),
1309 })
1310 }
1311}
1312
1313impl RelayFlags {
1314 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<RelayFlags> {
1316 if item.kwd() != NetstatusKwd::RS_S {
1317 return Err(
1318 Error::from(internal!("Wrong keyword {:?} for S line", item.kwd()))
1319 .at_pos(item.pos()),
1320 );
1321 }
1322 let mut flags: RelayFlags = RelayFlags::RUNNING | RelayFlags::VALID;
1324
1325 let mut prev: Option<&str> = None;
1326 for s in item.args() {
1327 if let Some(p) = prev {
1328 if p >= s {
1329 return Err(EK::BadArgument
1331 .at_pos(item.pos())
1332 .with_msg("Flags out of order"));
1333 }
1334 }
1335 let fl = s.parse().void_unwrap();
1336 flags |= fl;
1337 prev = Some(s);
1338 }
1339
1340 Ok(flags)
1341 }
1342}
1343
1344impl Default for RelayWeight {
1345 fn default() -> RelayWeight {
1346 RelayWeight::Unmeasured(0)
1347 }
1348}
1349
1350impl RelayWeight {
1351 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<RelayWeight> {
1353 if item.kwd() != NetstatusKwd::RS_W {
1354 return Err(
1355 Error::from(internal!("Wrong keyword {:?} on W line", item.kwd()))
1356 .at_pos(item.pos()),
1357 );
1358 }
1359
1360 let params: NetParams<u32> = item.args_as_str().parse()?;
1361
1362 let bw = params.params.get("Bandwidth");
1363 let unmeas = params.params.get("Unmeasured");
1364
1365 let bw = match bw {
1366 None => return Ok(RelayWeight::Unmeasured(0)),
1367 Some(b) => *b,
1368 };
1369
1370 match unmeas {
1371 None | Some(0) => Ok(RelayWeight::Measured(bw)),
1372 Some(1) => Ok(RelayWeight::Unmeasured(bw)),
1373 _ => Err(EK::BadArgument
1374 .at_pos(item.pos())
1375 .with_msg("unmeasured value")),
1376 }
1377 }
1378}
1379
1380impl Footer {
1381 fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<Footer> {
1383 use NetstatusKwd::*;
1384 sec.required(DIRECTORY_FOOTER)?;
1385
1386 let weights = sec
1387 .maybe(BANDWIDTH_WEIGHTS)
1388 .args_as_str()
1389 .unwrap_or("")
1390 .parse()?;
1391
1392 Ok(Footer { weights })
1393 }
1394}
1395
1396enum SigCheckResult {
1398 Valid,
1400 Invalid,
1403 MissingCert,
1406}
1407
1408impl Signature {
1409 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Signature> {
1411 if item.kwd() != NetstatusKwd::DIRECTORY_SIGNATURE {
1412 return Err(Error::from(internal!(
1413 "Wrong keyword {:?} for directory signature",
1414 item.kwd()
1415 ))
1416 .at_pos(item.pos()));
1417 }
1418
1419 let (alg, id_fp, sk_fp) = if item.n_args() > 2 {
1420 (
1421 item.required_arg(0)?,
1422 item.required_arg(1)?,
1423 item.required_arg(2)?,
1424 )
1425 } else {
1426 ("sha1", item.required_arg(0)?, item.required_arg(1)?)
1427 };
1428
1429 let digestname = alg.to_string();
1430 let id_fingerprint = id_fp.parse::<Fingerprint>()?.into();
1431 let sk_fingerprint = sk_fp.parse::<Fingerprint>()?.into();
1432 let key_ids = AuthCertKeyIds {
1433 id_fingerprint,
1434 sk_fingerprint,
1435 };
1436 let signature = item.obj("SIGNATURE")?;
1437
1438 Ok(Signature {
1439 digestname,
1440 key_ids,
1441 signature,
1442 })
1443 }
1444
1445 fn matches_cert(&self, cert: &AuthCert) -> bool {
1448 cert.key_ids() == &self.key_ids
1449 }
1450
1451 fn find_cert<'a>(&self, certs: &'a [AuthCert]) -> Option<&'a AuthCert> {
1454 certs.iter().find(|&c| self.matches_cert(c))
1455 }
1456
1457 fn check_signature(&self, signed_digest: &[u8], certs: &[AuthCert]) -> SigCheckResult {
1461 match self.find_cert(certs) {
1462 None => SigCheckResult::MissingCert,
1463 Some(cert) => {
1464 let key = cert.signing_key();
1465 match key.verify(signed_digest, &self.signature[..]) {
1466 Ok(()) => SigCheckResult::Valid,
1467 Err(_) => SigCheckResult::Invalid,
1468 }
1469 }
1470 }
1471 }
1472}
1473
1474pub type UncheckedConsensus<RS> = TimerangeBound<UnvalidatedConsensus<RS>>;
1477
1478impl<RS: RouterStatus + ParseRouterStatus> Consensus<RS> {
1479 #[cfg(feature = "build_docs")]
1484 pub fn builder() -> ConsensusBuilder<RS> {
1485 ConsensusBuilder::new(RS::flavor())
1486 }
1487
1488 pub fn parse(s: &str) -> Result<(&str, &str, UncheckedConsensus<RS>)> {
1490 let mut reader = NetDocReader::new(s)?;
1491 Self::parse_from_reader(&mut reader).map_err(|e| e.within(s))
1492 }
1493 fn take_voterinfo(
1496 r: &mut NetDocReader<'_, NetstatusKwd>,
1497 ) -> Result<Option<ConsensusVoterInfo>> {
1498 use NetstatusKwd::*;
1499
1500 match r.peek() {
1501 None => return Ok(None),
1502 Some(e) if e.is_ok_with_kwd_in(&[RS_R, DIRECTORY_FOOTER]) => return Ok(None),
1503 _ => (),
1504 };
1505
1506 let mut first_dir_source = true;
1507 let mut p = r.pause_at(|i| match i {
1510 Err(_) => false,
1511 Ok(item) => {
1512 item.kwd() == RS_R
1513 || if item.kwd() == DIR_SOURCE {
1514 let was_first = first_dir_source;
1515 first_dir_source = false;
1516 !was_first
1517 } else {
1518 false
1519 }
1520 }
1521 });
1522
1523 let voter_sec = NS_VOTERINFO_RULES_CONSENSUS.parse(&mut p)?;
1524 let voter = ConsensusVoterInfo::from_section(&voter_sec)?;
1525
1526 Ok(Some(voter))
1527 }
1528
1529 fn take_footer(r: &mut NetDocReader<'_, NetstatusKwd>) -> Result<Footer> {
1531 use NetstatusKwd::*;
1532 let mut p = r.pause_at(|i| i.is_ok_with_kwd_in(&[DIRECTORY_SIGNATURE]));
1533 let footer_sec = NS_FOOTER_RULES.parse(&mut p)?;
1534 let footer = Footer::from_section(&footer_sec)?;
1535 Ok(footer)
1536 }
1537
1538 fn take_routerstatus(r: &mut NetDocReader<'_, NetstatusKwd>) -> Result<Option<(Pos, RS)>> {
1541 use NetstatusKwd::*;
1542 match r.peek() {
1543 None => return Ok(None),
1544 Some(e) if e.is_ok_with_kwd_in(&[DIRECTORY_FOOTER]) => return Ok(None),
1545 _ => (),
1546 };
1547
1548 let pos = r.pos();
1549
1550 let mut first_r = true;
1551 let mut p = r.pause_at(|i| match i {
1552 Err(_) => false,
1553 Ok(item) => {
1554 item.kwd() == DIRECTORY_FOOTER
1555 || if item.kwd() == RS_R {
1556 let was_first = first_r;
1557 first_r = false;
1558 !was_first
1559 } else {
1560 false
1561 }
1562 }
1563 });
1564
1565 let rules = match RS::flavor() {
1566 ConsensusFlavor::Microdesc => &NS_ROUTERSTATUS_RULES_MDCON,
1567 ConsensusFlavor::Ns => &NS_ROUTERSTATUS_RULES_NSCON,
1568 };
1569
1570 let rs_sec = rules.parse(&mut p)?;
1571 let rs = RS::from_section(&rs_sec)?;
1572 Ok(Some((pos, rs)))
1573 }
1574
1575 fn parse_from_reader<'a>(
1580 r: &mut NetDocReader<'a, NetstatusKwd>,
1581 ) -> Result<(&'a str, &'a str, UncheckedConsensus<RS>)> {
1582 use NetstatusKwd::*;
1583 let (header, start_pos) = {
1584 let mut h = r.pause_at(|i| i.is_ok_with_kwd_in(&[DIR_SOURCE]));
1585 let header_sec = NS_HEADER_RULES_CONSENSUS.parse(&mut h)?;
1586 #[allow(clippy::unwrap_used)]
1589 let pos = header_sec.first_item().unwrap().offset_in(r.str()).unwrap();
1590 (ConsensusHeader::from_section(&header_sec)?, pos)
1591 };
1592 if RS::flavor() != header.hdr.flavor {
1593 return Err(EK::BadDocumentType.with_msg(format!(
1594 "Expected {:?}, got {:?}",
1595 RS::flavor(),
1596 header.hdr.flavor
1597 )));
1598 }
1599
1600 let mut voters = Vec::new();
1601
1602 while let Some(voter) = Self::take_voterinfo(r)? {
1603 voters.push(voter);
1604 }
1605
1606 let mut relays: Vec<RS> = Vec::new();
1607 while let Some((pos, routerstatus)) = Self::take_routerstatus(r)? {
1608 if let Some(prev) = relays.last() {
1609 if prev.rsa_identity() >= routerstatus.rsa_identity() {
1610 return Err(EK::WrongSortOrder.at_pos(pos));
1611 }
1612 }
1613 relays.push(routerstatus);
1614 }
1615 relays.shrink_to_fit();
1616
1617 let footer = Self::take_footer(r)?;
1618
1619 let consensus = Consensus {
1620 header,
1621 voters,
1622 relays,
1623 footer,
1624 };
1625
1626 let mut first_sig: Option<Item<'_, NetstatusKwd>> = None;
1628 let mut signatures = Vec::new();
1629 for item in &mut *r {
1630 let item = item?;
1631 if item.kwd() != DIRECTORY_SIGNATURE {
1632 return Err(EK::UnexpectedToken
1633 .with_msg(item.kwd().to_str())
1634 .at_pos(item.pos()));
1635 }
1636
1637 let sig = Signature::from_item(&item)?;
1638 if first_sig.is_none() {
1639 first_sig = Some(item);
1640 }
1641 signatures.push(sig);
1642 }
1643
1644 let end_pos = match first_sig {
1645 None => return Err(EK::MissingToken.with_msg("directory-signature")),
1646 #[allow(clippy::unwrap_used)]
1648 Some(sig) => sig.offset_in(r.str()).unwrap() + "directory-signature ".len(),
1649 };
1650
1651 let signed_str = &r.str()[start_pos..end_pos];
1653 let remainder = &r.str()[end_pos..];
1654 let (sha256, sha1) = match RS::flavor() {
1655 ConsensusFlavor::Ns => (
1656 None,
1657 Some(ll::d::Sha1::digest(signed_str.as_bytes()).into()),
1658 ),
1659 ConsensusFlavor::Microdesc => (
1660 Some(ll::d::Sha256::digest(signed_str.as_bytes()).into()),
1661 None,
1662 ),
1663 };
1664 let siggroup = SignatureGroup {
1665 sha256,
1666 sha1,
1667 signatures,
1668 };
1669
1670 let unval = UnvalidatedConsensus {
1671 consensus,
1672 siggroup,
1673 n_authorities: None,
1674 };
1675 let lifetime = unval.consensus.header.hdr.lifetime.clone();
1676 let delay = unval.consensus.header.hdr.voting_delay.unwrap_or((0, 0));
1677 let dist_interval = time::Duration::from_secs(delay.1.into());
1678 let starting_time = lifetime.valid_after - dist_interval;
1679 let timebound = TimerangeBound::new(unval, starting_time..lifetime.valid_until);
1680 Ok((signed_str, remainder, timebound))
1681 }
1682}
1683
1684#[cfg_attr(
1691 feature = "dangerous-expose-struct-fields",
1692 visible::StructFields(pub),
1693 non_exhaustive
1694)]
1695#[derive(Debug, Clone)]
1696pub struct UnvalidatedConsensus<RS> {
1697 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
1700 consensus: Consensus<RS>,
1701 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
1704 siggroup: SignatureGroup,
1705 #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
1709 n_authorities: Option<u16>,
1710}
1711
1712impl<RS> UnvalidatedConsensus<RS> {
1713 #[must_use]
1717 pub fn set_n_authorities(self, n_authorities: u16) -> Self {
1718 UnvalidatedConsensus {
1719 n_authorities: Some(n_authorities),
1720 ..self
1721 }
1722 }
1723
1724 pub fn signing_cert_ids(&self) -> impl Iterator<Item = AuthCertKeyIds> {
1727 match self.key_is_correct(&[]) {
1728 Ok(()) => Vec::new(),
1729 Err(missing) => missing,
1730 }
1731 .into_iter()
1732 }
1733
1734 pub fn peek_lifetime(&self) -> &Lifetime {
1736 self.consensus.lifetime()
1737 }
1738
1739 pub fn authorities_are_correct(&self, authorities: &[&RsaIdentity]) -> bool {
1746 self.siggroup.could_validate(authorities)
1747 }
1748
1749 #[cfg(feature = "experimental-api")]
1754 pub fn n_relays(&self) -> usize {
1755 self.consensus.relays.len()
1756 }
1757
1758 #[cfg(feature = "experimental-api")]
1767 pub fn modify_relays<F>(&mut self, func: F)
1768 where
1769 F: FnOnce(&mut Vec<RS>),
1770 {
1771 func(&mut self.consensus.relays);
1772 }
1773}
1774
1775impl<RS> ExternallySigned<Consensus<RS>> for UnvalidatedConsensus<RS> {
1776 type Key = [AuthCert];
1777 type KeyHint = Vec<AuthCertKeyIds>;
1778 type Error = Error;
1779
1780 fn key_is_correct(&self, k: &Self::Key) -> result::Result<(), Self::KeyHint> {
1781 let (n_ok, missing) = self.siggroup.list_missing(k);
1782 match self.n_authorities {
1783 Some(n) if n_ok > (n / 2) as usize => Ok(()),
1784 _ => Err(missing.iter().map(|cert| cert.key_ids).collect()),
1785 }
1786 }
1787 fn is_well_signed(&self, k: &Self::Key) -> result::Result<(), Self::Error> {
1788 match self.n_authorities {
1789 None => Err(Error::from(internal!(
1790 "Didn't set authorities on consensus"
1791 ))),
1792 Some(authority) => {
1793 if self.siggroup.validate(authority, k) {
1794 Ok(())
1795 } else {
1796 Err(EK::BadSignature.err())
1797 }
1798 }
1799 }
1800 }
1801 fn dangerously_assume_wellsigned(self) -> Consensus<RS> {
1802 self.consensus
1803 }
1804}
1805
1806impl SignatureGroup {
1807 fn list_missing(&self, certs: &[AuthCert]) -> (usize, Vec<&Signature>) {
1814 let mut ok: HashSet<RsaIdentity> = HashSet::new();
1815 let mut missing = Vec::new();
1816 for sig in &self.signatures {
1817 let id_fingerprint = &sig.key_ids.id_fingerprint;
1818 if ok.contains(id_fingerprint) {
1819 continue;
1820 }
1821 if sig.find_cert(certs).is_some() {
1822 ok.insert(*id_fingerprint);
1823 continue;
1824 }
1825
1826 missing.push(sig);
1827 }
1828 (ok.len(), missing)
1829 }
1830
1831 fn could_validate(&self, authorities: &[&RsaIdentity]) -> bool {
1835 let mut signed_by: HashSet<RsaIdentity> = HashSet::new();
1836 for sig in &self.signatures {
1837 let id_fp = &sig.key_ids.id_fingerprint;
1838 if signed_by.contains(id_fp) {
1839 continue;
1841 }
1842 if authorities.contains(&id_fp) {
1843 signed_by.insert(*id_fp);
1844 }
1845 }
1846
1847 signed_by.len() > (authorities.len() / 2)
1848 }
1849
1850 fn validate(&self, n_authorities: u16, certs: &[AuthCert]) -> bool {
1857 let mut ok: HashSet<RsaIdentity> = HashSet::new();
1861
1862 for sig in &self.signatures {
1863 let id_fingerprint = &sig.key_ids.id_fingerprint;
1864 if ok.contains(id_fingerprint) {
1865 continue;
1868 }
1869
1870 let d: Option<&[u8]> = match sig.digestname.as_ref() {
1871 "sha256" => self.sha256.as_ref().map(|a| &a[..]),
1872 "sha1" => self.sha1.as_ref().map(|a| &a[..]),
1873 _ => None, };
1875 if d.is_none() {
1876 continue;
1879 }
1880
1881 #[allow(clippy::unwrap_used)]
1883 match sig.check_signature(d.as_ref().unwrap(), certs) {
1884 SigCheckResult::Valid => {
1885 ok.insert(*id_fingerprint);
1886 }
1887 _ => continue,
1888 }
1889 }
1890
1891 ok.len() > (n_authorities / 2) as usize
1892 }
1893}
1894
1895#[cfg(test)]
1896mod test {
1897 #![allow(clippy::bool_assert_comparison)]
1899 #![allow(clippy::clone_on_copy)]
1900 #![allow(clippy::dbg_macro)]
1901 #![allow(clippy::mixed_attributes_style)]
1902 #![allow(clippy::print_stderr)]
1903 #![allow(clippy::print_stdout)]
1904 #![allow(clippy::single_char_pattern)]
1905 #![allow(clippy::unwrap_used)]
1906 #![allow(clippy::unchecked_duration_subtraction)]
1907 #![allow(clippy::useless_vec)]
1908 #![allow(clippy::needless_pass_by_value)]
1909 use super::*;
1911 use hex_literal::hex;
1912
1913 const CERTS: &str = include_str!("../../testdata/authcerts2.txt");
1914 const CONSENSUS: &str = include_str!("../../testdata/mdconsensus1.txt");
1915
1916 #[cfg(feature = "ns_consensus")]
1917 const NS_CERTS: &str = include_str!("../../testdata/authcerts3.txt");
1918 #[cfg(feature = "ns_consensus")]
1919 const NS_CONSENSUS: &str = include_str!("../../testdata/nsconsensus1.txt");
1920
1921 fn read_bad(fname: &str) -> String {
1922 use std::fs;
1923 use std::path::PathBuf;
1924 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1925 path.push("testdata");
1926 path.push("bad-mdconsensus");
1927 path.push(fname);
1928
1929 fs::read_to_string(path).unwrap()
1930 }
1931
1932 #[test]
1933 fn parse_and_validate_md() -> Result<()> {
1934 use std::net::SocketAddr;
1935 use tor_checkable::{SelfSigned, Timebound};
1936 let mut certs = Vec::new();
1937 for cert in AuthCert::parse_multiple(CERTS)? {
1938 let cert = cert?.check_signature()?.dangerously_assume_timely();
1939 certs.push(cert);
1940 }
1941 let auth_ids: Vec<_> = certs.iter().map(|c| &c.key_ids().id_fingerprint).collect();
1942
1943 assert_eq!(certs.len(), 3);
1944
1945 let (_, _, consensus) = MdConsensus::parse(CONSENSUS)?;
1946 let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
1947
1948 assert!(consensus.authorities_are_correct(&auth_ids));
1950 assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
1952 {
1953 let bad_auth_id = (*b"xxxxxxxxxxxxxxxxxxxx").into();
1956 assert!(!consensus.authorities_are_correct(&[&bad_auth_id]));
1957 }
1958
1959 let missing = consensus.key_is_correct(&[]).err().unwrap();
1960 assert_eq!(3, missing.len());
1961 assert!(consensus.key_is_correct(&certs).is_ok());
1962 let missing = consensus.key_is_correct(&certs[0..1]).err().unwrap();
1963 assert_eq!(2, missing.len());
1964
1965 let same_three_times = vec![certs[0].clone(), certs[0].clone(), certs[0].clone()];
1967 let missing = consensus.key_is_correct(&same_three_times).err().unwrap();
1968
1969 assert_eq!(2, missing.len());
1970 assert!(consensus.is_well_signed(&same_three_times).is_err());
1971
1972 assert!(consensus.key_is_correct(&certs).is_ok());
1973 let consensus = consensus.check_signature(&certs)?;
1974
1975 assert_eq!(6, consensus.relays().len());
1976 let r0 = &consensus.relays()[0];
1977 assert_eq!(
1978 r0.md_digest(),
1979 &hex!("73dabe0a0468f4f7a67810a18d11e36731bb1d2ec3634db459100609f3b3f535")
1980 );
1981 assert_eq!(
1982 r0.rsa_identity().as_bytes(),
1983 &hex!("0a3057af2910415794d8ea430309d9ac5f5d524b")
1984 );
1985 assert!(!r0.weight().is_measured());
1986 assert!(!r0.weight().is_nonzero());
1987 let pv = &r0.protovers();
1988 assert!(pv.supports_subver("HSDir", 2));
1989 assert!(!pv.supports_subver("HSDir", 3));
1990 let ip4 = "127.0.0.1:5002".parse::<SocketAddr>().unwrap();
1991 let ip6 = "[::1]:5002".parse::<SocketAddr>().unwrap();
1992 assert!(r0.orport_addrs().any(|a| a == &ip4));
1993 assert!(r0.orport_addrs().any(|a| a == &ip6));
1994
1995 Ok(())
1996 }
1997
1998 #[test]
1999 #[cfg(feature = "ns_consensus")]
2000 fn parse_and_validate_ns() -> Result<()> {
2001 use tor_checkable::{SelfSigned, Timebound};
2002 let mut certs = Vec::new();
2003 for cert in AuthCert::parse_multiple(NS_CERTS)? {
2004 let cert = cert?.check_signature()?.dangerously_assume_timely();
2005 certs.push(cert);
2006 }
2007 let auth_ids: Vec<_> = certs.iter().map(|c| &c.key_ids().id_fingerprint).collect();
2008 assert_eq!(certs.len(), 3);
2009
2010 let (_, _, consensus) = NsConsensus::parse(NS_CONSENSUS)?;
2011 let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
2012 assert!(consensus.authorities_are_correct(&auth_ids));
2014 assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
2016
2017 assert!(consensus.key_is_correct(&certs).is_ok());
2018
2019 let _consensus = consensus.check_signature(&certs)?;
2020
2021 Ok(())
2022 }
2023
2024 #[test]
2025 fn test_bad() {
2026 use crate::Pos;
2027 fn check(fname: &str, e: &Error) {
2028 let content = read_bad(fname);
2029 let res = MdConsensus::parse(&content);
2030 assert!(res.is_err());
2031 assert_eq!(&res.err().unwrap(), e);
2032 }
2033
2034 check(
2035 "bad-flags",
2036 &EK::BadArgument
2037 .at_pos(Pos::from_line(27, 1))
2038 .with_msg("Flags out of order"),
2039 );
2040 check(
2041 "bad-md-digest",
2042 &EK::BadArgument
2043 .at_pos(Pos::from_line(40, 3))
2044 .with_msg("Invalid base64"),
2045 );
2046 check(
2047 "bad-weight",
2048 &EK::BadArgument
2049 .at_pos(Pos::from_line(67, 141))
2050 .with_msg("invalid digit found in string"),
2051 );
2052 check(
2053 "bad-weights",
2054 &EK::BadArgument
2055 .at_pos(Pos::from_line(51, 13))
2056 .with_msg("invalid digit found in string"),
2057 );
2058 check(
2059 "wrong-order",
2060 &EK::WrongSortOrder.at_pos(Pos::from_line(52, 1)),
2061 );
2062 check(
2063 "wrong-start",
2064 &EK::UnexpectedToken
2065 .with_msg("vote-status")
2066 .at_pos(Pos::from_line(1, 1)),
2067 );
2068 check("wrong-version", &EK::BadDocumentVersion.with_msg("10"));
2069 }
2070
2071 fn gettok(s: &str) -> Result<Item<'_, NetstatusKwd>> {
2072 let mut reader = NetDocReader::new(s)?;
2073 let tok = reader.next().unwrap();
2074 assert!(reader.next().is_none());
2075 tok
2076 }
2077
2078 #[test]
2079 fn test_weight() {
2080 let w = gettok("w Unmeasured=1 Bandwidth=6\n").unwrap();
2081 let w = RelayWeight::from_item(&w).unwrap();
2082 assert!(!w.is_measured());
2083 assert!(w.is_nonzero());
2084
2085 let w = gettok("w Bandwidth=10\n").unwrap();
2086 let w = RelayWeight::from_item(&w).unwrap();
2087 assert!(w.is_measured());
2088 assert!(w.is_nonzero());
2089
2090 let w = RelayWeight::default();
2091 assert!(!w.is_measured());
2092 assert!(!w.is_nonzero());
2093
2094 let w = gettok("w Mustelid=66 Cheato=7 Unmeasured=1\n").unwrap();
2095 let w = RelayWeight::from_item(&w).unwrap();
2096 assert!(!w.is_measured());
2097 assert!(!w.is_nonzero());
2098
2099 let w = gettok("r foo\n").unwrap();
2100 let w = RelayWeight::from_item(&w);
2101 assert!(w.is_err());
2102
2103 let w = gettok("r Bandwidth=6 Unmeasured=Frog\n").unwrap();
2104 let w = RelayWeight::from_item(&w);
2105 assert!(w.is_err());
2106
2107 let w = gettok("r Bandwidth=6 Unmeasured=3\n").unwrap();
2108 let w = RelayWeight::from_item(&w);
2109 assert!(w.is_err());
2110 }
2111
2112 #[test]
2113 fn test_netparam() {
2114 let p = "Hello=600 Goodbye=5 Fred=7"
2115 .parse::<NetParams<u32>>()
2116 .unwrap();
2117 assert_eq!(p.get("Hello"), Some(&600_u32));
2118
2119 let p = "Hello=Goodbye=5 Fred=7".parse::<NetParams<u32>>();
2120 assert!(p.is_err());
2121
2122 let p = "Hello=Goodbye Fred=7".parse::<NetParams<u32>>();
2123 assert!(p.is_err());
2124 }
2125
2126 #[test]
2127 fn test_sharedrand() {
2128 let sr =
2129 gettok("shared-rand-previous-value 9 5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4\n")
2130 .unwrap();
2131 let sr = SharedRandStatus::from_item(&sr).unwrap();
2132
2133 assert_eq!(sr.n_reveals, 9);
2134 assert_eq!(
2135 sr.value.0,
2136 hex!("e4ba1d638c96c458532adc6957dc0080d03d37c7e5854087d0da90bf5ff4e72e")
2137 );
2138 assert!(sr.timestamp.is_none());
2139
2140 let sr2 = gettok(
2141 "shared-rand-current-value 9 \
2142 5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4 2022-01-20T12:34:56\n",
2143 )
2144 .unwrap();
2145 let sr2 = SharedRandStatus::from_item(&sr2).unwrap();
2146 assert_eq!(sr2.n_reveals, sr.n_reveals);
2147 assert_eq!(sr2.value.0, sr.value.0);
2148 assert_eq!(
2149 sr2.timestamp.unwrap(),
2150 humantime::parse_rfc3339("2022-01-20T12:34:56Z").unwrap()
2151 );
2152
2153 let sr = gettok("foo bar\n").unwrap();
2154 let sr = SharedRandStatus::from_item(&sr);
2155 assert!(sr.is_err());
2156 }
2157
2158 #[test]
2159 fn test_protostatus() {
2160 let my_protocols: Protocols = "Link=7 Cons=1-5 Desc=3-10".parse().unwrap();
2161
2162 let outcome = ProtoStatus {
2163 recommended: "Link=7".parse().unwrap(),
2164 required: "Desc=5".parse().unwrap(),
2165 }
2166 .check_protocols(&my_protocols);
2167 assert!(outcome.is_ok());
2168
2169 let outcome = ProtoStatus {
2170 recommended: "Microdesc=4 Link=7".parse().unwrap(),
2171 required: "Desc=5".parse().unwrap(),
2172 }
2173 .check_protocols(&my_protocols);
2174 assert_eq!(
2175 outcome,
2176 Err(ProtocolSupportError::MissingRecommended(
2177 "Microdesc=4".parse().unwrap()
2178 ))
2179 );
2180
2181 let outcome = ProtoStatus {
2182 recommended: "Microdesc=4 Link=7".parse().unwrap(),
2183 required: "Desc=5 Cons=5-12 Wombat=15".parse().unwrap(),
2184 }
2185 .check_protocols(&my_protocols);
2186 assert_eq!(
2187 outcome,
2188 Err(ProtocolSupportError::MissingRequired(
2189 "Cons=6-12 Wombat=15".parse().unwrap()
2190 ))
2191 );
2192 }
2193
2194 #[test]
2195 fn serialize_protostatus() {
2196 let ps = ProtoStatuses {
2197 client: ProtoStatus {
2198 recommended: "Link=1-5 LinkAuth=2-5".parse().unwrap(),
2199 required: "Link=5 LinkAuth=3".parse().unwrap(),
2200 },
2201 relay: ProtoStatus {
2202 recommended: "Wombat=20-30 Knish=20-30".parse().unwrap(),
2203 required: "Wombat=20-22 Knish=25-27".parse().unwrap(),
2204 },
2205 };
2206 let json = serde_json::to_string(&ps).unwrap();
2207 let ps2 = serde_json::from_str(json.as_str()).unwrap();
2208 assert_eq!(ps, ps2);
2209
2210 let ps3: ProtoStatuses = serde_json::from_str(
2211 r#"{
2212 "client":{
2213 "required":"Link=5 LinkAuth=3",
2214 "recommended":"Link=1-5 LinkAuth=2-5"
2215 },
2216 "relay":{
2217 "required":"Wombat=20-22 Knish=25-27",
2218 "recommended":"Wombat=20-30 Knish=20-30"
2219 }
2220 }"#,
2221 )
2222 .unwrap();
2223 assert_eq!(ps, ps3);
2224 }
2225}