1mod rs;
47
48pub mod md;
49#[cfg(feature = "plain-consensus")]
50pub mod plain;
51#[cfg(feature = "ns-vote")]
52pub mod vote;
53
54#[cfg(feature = "build_docs")]
55mod build;
56
57#[cfg(feature = "parse2")]
58use {
59 crate::parse2::{self, ArgumentStream}, };
61
62#[cfg(feature = "parse2")]
63pub use {
64 parse2_impls::ProtoStatusesNetdocParseAccumulator, };
66
67use crate::doc::authcert::{AuthCert, AuthCertKeyIds};
68use crate::parse::keyword::Keyword;
69use crate::parse::parser::{Section, SectionRules, SectionRulesBuilder};
70use crate::parse::tokenize::{Item, ItemResult, NetDocReader};
71use crate::types::misc::*;
72use crate::util::PeekableIterator;
73use crate::{Error, KeywordEncodable, NetdocErrorKind as EK, NormalItemArgument, Pos, Result};
74use std::collections::{BTreeSet, HashMap, HashSet};
75use std::fmt::{self, Display};
76use std::result::Result as StdResult;
77use std::str::FromStr;
78use std::sync::Arc;
79use std::{net, result, time};
80use tor_error::{HasKind, internal};
81use tor_protover::Protocols;
82
83use derive_deftly::{Deftly, define_derive_deftly};
84use digest::Digest;
85use std::sync::LazyLock;
86use tor_checkable::{ExternallySigned, timed::TimerangeBound};
87use tor_llcrypto as ll;
88use tor_llcrypto::pk::rsa::RsaIdentity;
89
90use serde::{Deserialize, Deserializer};
91
92#[cfg(feature = "build_docs")]
93pub use build::MdConsensusBuilder;
94#[cfg(all(feature = "build_docs", feature = "plain-consensus"))]
95pub use build::PlainConsensusBuilder;
96#[cfg(feature = "build_docs")]
97ns_export_each_flavor! {
98 ty: RouterStatusBuilder;
99}
100
101ns_export_each_variety! {
102 ty: RouterStatus, Preamble;
103}
104
105#[deprecated]
106#[cfg(feature = "ns_consensus")]
107pub use PlainConsensus as NsConsensus;
108#[deprecated]
109#[cfg(feature = "ns_consensus")]
110pub use PlainRouterStatus as NsRouterStatus;
111#[deprecated]
112#[cfg(feature = "ns_consensus")]
113pub use UncheckedPlainConsensus as UncheckedNsConsensus;
114#[deprecated]
115#[cfg(feature = "ns_consensus")]
116pub use UnvalidatedPlainConsensus as UnvalidatedNsConsensus;
117
118#[cfg(feature = "ns-vote")]
119pub use rs::{RouterStatusMdDigestsVote, SoftwareVersion};
120
121#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Default)]
135#[allow(clippy::exhaustive_structs)]
136pub struct IgnoredPublicationTimeSp;
137
138#[derive(Clone, Debug, Deftly)]
146#[derive_deftly(Lifetime)]
147#[cfg_attr(feature = "parse2", derive_deftly(NetdocParseableFields))]
148pub struct Lifetime {
149 #[cfg_attr(feature = "parse2", deftly(netdoc(single_arg)))]
156 valid_after: Iso8601TimeSp,
157 #[cfg_attr(feature = "parse2", deftly(netdoc(single_arg)))]
165 fresh_until: Iso8601TimeSp,
166 #[cfg_attr(feature = "parse2", deftly(netdoc(single_arg)))]
174 valid_until: Iso8601TimeSp,
175}
176
177define_derive_deftly! {
178 Lifetime:
180
181 impl Lifetime {
182 pub fn new(
184 $( $fname: time::SystemTime, )
185 ) -> Result<Self> {
186 let self_ = Lifetime {
190 $( $fname: $fname.into(), )
191 };
192 if self_.valid_after < self_.fresh_until && self_.fresh_until < self_.valid_until {
193 Ok(self_)
194 } else {
195 Err(EK::InvalidLifetime.err())
196 }
197 }
198 $(
199 ${fattrs doc}
200 pub fn $fname(&self) -> time::SystemTime {
201 *self.$fname
202 }
203 )
204 pub fn valid_at(&self, when: time::SystemTime) -> bool {
206 *self.valid_after <= when && when <= *self.valid_until
207 }
208
209 pub fn voting_period(&self) -> time::Duration {
214 let valid_after = self.valid_after();
215 let fresh_until = self.fresh_until();
216 fresh_until
217 .duration_since(valid_after)
218 .expect("Mis-formed lifetime")
219 }
220 }
221}
222use derive_deftly_template_Lifetime;
223
224#[derive(Debug, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Copy)] #[derive(derive_more::From, derive_more::Into, derive_more::Display, derive_more::FromStr)]
236pub struct ConsensusMethod(u32);
237impl NormalItemArgument for ConsensusMethod {}
238
239#[derive(Debug, Clone, Default, Eq, PartialEq)]
246#[cfg_attr(feature = "parse2", derive(Deftly), derive_deftly(ItemValueParseable))]
247#[non_exhaustive]
248pub struct ConsensusMethods {
249 pub methods: BTreeSet<ConsensusMethod>,
251}
252
253#[cfg(feature = "parse2")]
258pub mod consensus_methods_comma_separated {
259 use super::*;
260 use parse2::ArgumentError as AE;
261 use std::result::Result;
262
263 pub fn from_args<'s>(args: &mut ArgumentStream<'s>) -> Result<ConsensusMethods, AE> {
265 let mut methods = BTreeSet::new();
266 for ent in args.next().ok_or(AE::Missing)?.split(',') {
267 let ent = ent.parse().map_err(|_| AE::Invalid)?;
268 if !methods.insert(ent) {
269 return Err(AE::Invalid);
270 }
271 }
272 Ok(ConsensusMethods { methods })
273 }
274}
275
276#[derive(Debug, Clone, Default, Eq, PartialEq)]
292pub struct NetParams<T> {
293 params: HashMap<String, T>,
295}
296
297impl<T> NetParams<T> {
298 #[allow(unused)]
300 pub fn new() -> Self {
301 NetParams {
302 params: HashMap::new(),
303 }
304 }
305 pub fn get<A: AsRef<str>>(&self, v: A) -> Option<&T> {
307 self.params.get(v.as_ref())
308 }
309 pub fn iter(&self) -> impl Iterator<Item = (&String, &T)> {
311 self.params.iter()
312 }
313 pub fn set(&mut self, k: String, v: T) {
315 self.params.insert(k, v);
316 }
317}
318
319impl<K: Into<String>, T> FromIterator<(K, T)> for NetParams<T> {
320 fn from_iter<I: IntoIterator<Item = (K, T)>>(i: I) -> Self {
321 NetParams {
322 params: i.into_iter().map(|(k, v)| (k.into(), v)).collect(),
323 }
324 }
325}
326
327impl<T> std::iter::Extend<(String, T)> for NetParams<T> {
328 fn extend<I: IntoIterator<Item = (String, T)>>(&mut self, iter: I) {
329 self.params.extend(iter);
330 }
331}
332
333impl<'de, T> Deserialize<'de> for NetParams<T>
334where
335 T: Deserialize<'de>,
336{
337 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
338 where
339 D: Deserializer<'de>,
340 {
341 let params = HashMap::deserialize(deserializer)?;
342 Ok(NetParams { params })
343 }
344}
345
346#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
355pub struct ProtoStatus {
356 recommended: Protocols,
361 required: Protocols,
366}
367
368impl ProtoStatus {
369 pub fn check_protocols(
379 &self,
380 supported_protocols: &Protocols,
381 ) -> StdResult<(), ProtocolSupportError> {
382 let missing_required = self.required.difference(supported_protocols);
384 if !missing_required.is_empty() {
385 return Err(ProtocolSupportError::MissingRequired(missing_required));
386 }
387 let missing_recommended = self.recommended.difference(supported_protocols);
388 if !missing_recommended.is_empty() {
389 return Err(ProtocolSupportError::MissingRecommended(
390 missing_recommended,
391 ));
392 }
393
394 Ok(())
395 }
396}
397
398#[derive(Clone, Debug, thiserror::Error)]
400#[cfg_attr(test, derive(PartialEq))]
401#[non_exhaustive]
402pub enum ProtocolSupportError {
403 #[error("Required protocols are not implemented: {0}")]
405 MissingRequired(Protocols),
406
407 #[error("Recommended protocols are not implemented: {0}")]
411 MissingRecommended(Protocols),
412}
413
414impl ProtocolSupportError {
415 pub fn should_shutdown(&self) -> bool {
417 matches!(self, Self::MissingRequired(_))
418 }
419}
420
421impl HasKind for ProtocolSupportError {
422 fn kind(&self) -> tor_error::ErrorKind {
423 tor_error::ErrorKind::SoftwareDeprecated
424 }
425}
426
427#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
434pub struct ProtoStatuses {
435 client: ProtoStatus,
437 relay: ProtoStatus,
439}
440
441impl ProtoStatuses {
442 pub fn client(&self) -> &ProtoStatus {
444 &self.client
445 }
446
447 pub fn relay(&self) -> &ProtoStatus {
449 &self.relay
450 }
451}
452
453#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
461#[allow(clippy::exhaustive_enums)]
462pub enum ConsensusFlavor {
463 Microdesc,
466 Plain,
471}
472
473impl ConsensusFlavor {
474 pub fn name(&self) -> &'static str {
476 match self {
477 ConsensusFlavor::Plain => "ns", ConsensusFlavor::Microdesc => "microdesc",
479 }
480 }
481 pub fn from_opt_name(name: Option<&str>) -> Result<Self> {
486 match name {
487 Some("microdesc") => Ok(ConsensusFlavor::Microdesc),
488 Some("ns") | None => Ok(ConsensusFlavor::Plain),
489 Some(other) => {
490 Err(EK::BadDocumentType.with_msg(format!("unrecognized flavor {:?}", other)))
491 }
492 }
493 }
494}
495
496#[derive(Debug, Clone)]
498#[non_exhaustive]
499pub struct Signature {
500 pub digestname: String,
505 pub key_ids: AuthCertKeyIds,
508 pub signature: Vec<u8>,
510}
511
512#[derive(Debug, Clone)]
514#[non_exhaustive]
515pub struct SignatureGroup {
516 pub sha256: Option<[u8; 32]>,
518 pub sha1: Option<[u8; 20]>,
520 pub signatures: Vec<Signature>,
522}
523
524#[derive(
526 Debug, Clone, Copy, Eq, PartialEq, derive_more::From, derive_more::Into, derive_more::AsRef,
527)]
528pub struct SharedRandVal([u8; 32]);
530
531#[derive(Debug, Clone, Deftly)]
534#[non_exhaustive]
535#[cfg_attr(feature = "parse2", derive_deftly(ItemValueParseable))]
536#[cfg_attr(feature = "encode", derive_deftly(ItemValueEncodable))]
537pub struct SharedRandStatus {
538 pub n_reveals: u8,
540 pub value: SharedRandVal,
547
548 pub timestamp: Option<Iso8601TimeNoSp>,
552}
553
554#[derive(Debug, Clone)]
558#[non_exhaustive]
559pub struct DirSource {
560 pub nickname: String,
562 pub identity: RsaIdentity,
568 pub ip: net::IpAddr,
570 pub dir_port: u16,
572 pub or_port: u16,
574}
575
576#[non_exhaustive]
578#[derive(Debug, Clone, Copy)]
579pub enum RelayWeight {
580 Unmeasured(u32),
582 Measured(u32),
584}
585
586impl RelayWeight {
587 pub fn is_measured(&self) -> bool {
589 matches!(self, RelayWeight::Measured(_))
590 }
591 pub fn is_nonzero(&self) -> bool {
593 !matches!(self, RelayWeight::Unmeasured(0) | RelayWeight::Measured(0))
594 }
595}
596
597#[derive(Debug, Clone)]
599#[non_exhaustive]
600pub struct ConsensusVoterInfo {
601 pub dir_source: DirSource,
603 pub contact: String,
605 pub vote_digest: Vec<u8>,
608}
609
610#[derive(Debug, Clone)]
612#[non_exhaustive]
613pub struct Footer {
614 pub weights: NetParams<i32>,
620}
621
622pub type MdConsensus = md::Consensus;
625
626pub type UnvalidatedMdConsensus = md::UnvalidatedConsensus;
629
630pub type UncheckedMdConsensus = md::UncheckedConsensus;
633
634#[cfg(feature = "plain-consensus")]
635pub type PlainConsensus = plain::Consensus;
638
639#[cfg(feature = "plain-consensus")]
640pub type UnvalidatedPlainConsensus = plain::UnvalidatedConsensus;
643
644#[cfg(feature = "plain-consensus")]
645pub type UncheckedPlainConsensus = plain::UncheckedConsensus;
648
649decl_keyword! {
650 #[non_exhaustive]
655 #[allow(missing_docs)]
656 pub NetstatusKwd {
657 "network-status-version" => NETWORK_STATUS_VERSION,
659 "vote-status" => VOTE_STATUS,
660 "consensus-methods" => CONSENSUS_METHODS,
661 "consensus-method" => CONSENSUS_METHOD,
662 "published" => PUBLISHED,
663 "valid-after" => VALID_AFTER,
664 "fresh-until" => FRESH_UNTIL,
665 "valid-until" => VALID_UNTIL,
666 "voting-delay" => VOTING_DELAY,
667 "client-versions" => CLIENT_VERSIONS,
668 "server-versions" => SERVER_VERSIONS,
669 "known-flags" => KNOWN_FLAGS,
670 "flag-thresholds" => FLAG_THRESHOLDS,
671 "recommended-client-protocols" => RECOMMENDED_CLIENT_PROTOCOLS,
672 "required-client-protocols" => REQUIRED_CLIENT_PROTOCOLS,
673 "recommended-relay-protocols" => RECOMMENDED_RELAY_PROTOCOLS,
674 "required-relay-protocols" => REQUIRED_RELAY_PROTOCOLS,
675 "params" => PARAMS,
676 "bandwidth-file-headers" => BANDWIDTH_FILE_HEADERS,
677 "bandwidth-file-digest" => BANDWIDTH_FILE_DIGEST,
678 "shared-rand-previous-value" => SHARED_RAND_PREVIOUS_VALUE,
682 "shared-rand-current-value" => SHARED_RAND_CURRENT_VALUE,
683
684 "dir-source" => DIR_SOURCE,
686 "contact" => CONTACT,
687
688 "legacy-dir-key" => LEGACY_DIR_KEY,
690 "shared-rand-participate" => SHARED_RAND_PARTICIPATE,
691 "shared-rand-commit" => SHARED_RAND_COMMIT,
692
693 "vote-digest" => VOTE_DIGEST,
695
696 "dir-key-certificate-version" => DIR_KEY_CERTIFICATE_VERSION,
698
699 "r" => RS_R,
701 "a" => RS_A,
702 "s" => RS_S,
703 "v" => RS_V,
704 "pr" => RS_PR,
705 "w" => RS_W,
706 "p" => RS_P,
707 "m" => RS_M,
708 "id" => RS_ID,
709
710 "directory-footer" => DIRECTORY_FOOTER,
712 "bandwidth-weights" => BANDWIDTH_WEIGHTS,
713 "directory-signature" => DIRECTORY_SIGNATURE,
714 }
715}
716
717static NS_HEADER_RULES_COMMON_: LazyLock<SectionRulesBuilder<NetstatusKwd>> = LazyLock::new(|| {
719 use NetstatusKwd::*;
720 let mut rules = SectionRules::builder();
721 rules.add(NETWORK_STATUS_VERSION.rule().required().args(1..=2));
722 rules.add(VOTE_STATUS.rule().required().args(1..));
723 rules.add(VALID_AFTER.rule().required());
724 rules.add(FRESH_UNTIL.rule().required());
725 rules.add(VALID_UNTIL.rule().required());
726 rules.add(VOTING_DELAY.rule().args(2..));
727 rules.add(CLIENT_VERSIONS.rule());
728 rules.add(SERVER_VERSIONS.rule());
729 rules.add(KNOWN_FLAGS.rule().required());
730 rules.add(RECOMMENDED_CLIENT_PROTOCOLS.rule().args(1..));
731 rules.add(RECOMMENDED_RELAY_PROTOCOLS.rule().args(1..));
732 rules.add(REQUIRED_CLIENT_PROTOCOLS.rule().args(1..));
733 rules.add(REQUIRED_RELAY_PROTOCOLS.rule().args(1..));
734 rules.add(PARAMS.rule());
735 rules
736});
737static NS_HEADER_RULES_CONSENSUS: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
739 use NetstatusKwd::*;
740 let mut rules = NS_HEADER_RULES_COMMON_.clone();
741 rules.add(CONSENSUS_METHOD.rule().args(1..=1));
742 rules.add(SHARED_RAND_PREVIOUS_VALUE.rule().args(2..));
743 rules.add(SHARED_RAND_CURRENT_VALUE.rule().args(2..));
744 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
745 rules.build()
746});
747static NS_VOTERINFO_RULES_CONSENSUS: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
778 use NetstatusKwd::*;
779 let mut rules = SectionRules::builder();
780 rules.add(DIR_SOURCE.rule().required().args(6..));
781 rules.add(CONTACT.rule().required());
782 rules.add(VOTE_DIGEST.rule().required());
783 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
784 rules.build()
785});
786static NS_ROUTERSTATUS_RULES_COMMON_: LazyLock<SectionRulesBuilder<NetstatusKwd>> =
788 LazyLock::new(|| {
789 use NetstatusKwd::*;
790 let mut rules = SectionRules::builder();
791 rules.add(RS_A.rule().may_repeat().args(1..));
792 rules.add(RS_S.rule().required());
793 rules.add(RS_V.rule());
794 rules.add(RS_PR.rule().required());
795 rules.add(RS_W.rule());
796 rules.add(RS_P.rule().args(2..));
797 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
798 rules
799 });
800
801static NS_ROUTERSTATUS_RULES_PLAIN: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
803 use NetstatusKwd::*;
804 let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
805 rules.add(RS_R.rule().required().args(8..));
806 rules.build()
807});
808
809static NS_ROUTERSTATUS_RULES_MDCON: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
822 use NetstatusKwd::*;
823 let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
824 rules.add(RS_R.rule().required().args(6..));
825 rules.add(RS_M.rule().required().args(1..));
826 rules.build()
827});
828static NS_FOOTER_RULES: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
830 use NetstatusKwd::*;
831 let mut rules = SectionRules::builder();
832 rules.add(DIRECTORY_FOOTER.rule().required().no_args());
833 rules.add(BANDWIDTH_WEIGHTS.rule());
835 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
836 rules.build()
837});
838
839impl ProtoStatus {
840 fn from_section(
842 sec: &Section<'_, NetstatusKwd>,
843 recommend_token: NetstatusKwd,
844 required_token: NetstatusKwd,
845 ) -> Result<ProtoStatus> {
846 fn parse(t: Option<&Item<'_, NetstatusKwd>>) -> Result<Protocols> {
848 if let Some(item) = t {
849 item.args_as_str()
850 .parse::<Protocols>()
851 .map_err(|e| EK::BadArgument.at_pos(item.pos()).with_source(e))
852 } else {
853 Ok(Protocols::new())
854 }
855 }
856
857 let recommended = parse(sec.get(recommend_token))?;
858 let required = parse(sec.get(required_token))?;
859 Ok(ProtoStatus {
860 recommended,
861 required,
862 })
863 }
864
865 pub fn required_protocols(&self) -> &Protocols {
872 &self.required
873 }
874
875 pub fn recommended_protocols(&self) -> &Protocols {
880 &self.recommended
881 }
882}
883
884impl<T> std::str::FromStr for NetParams<T>
885where
886 T: std::str::FromStr,
887 T::Err: std::error::Error,
888{
889 type Err = Error;
890 fn from_str(s: &str) -> Result<Self> {
891 fn parse_pair<U>(p: &str) -> Result<(String, U)>
893 where
894 U: std::str::FromStr,
895 U::Err: std::error::Error,
896 {
897 let parts: Vec<_> = p.splitn(2, '=').collect();
898 if parts.len() != 2 {
899 return Err(EK::BadArgument
900 .at_pos(Pos::at(p))
901 .with_msg("Missing = in key=value list"));
902 }
903 let num = parts[1].parse::<U>().map_err(|e| {
904 EK::BadArgument
905 .at_pos(Pos::at(parts[1]))
906 .with_msg(e.to_string())
907 })?;
908 Ok((parts[0].to_string(), num))
909 }
910
911 let params = s
912 .split(' ')
913 .filter(|p| !p.is_empty())
914 .map(parse_pair)
915 .collect::<Result<HashMap<_, _>>>()?;
916 Ok(NetParams { params })
917 }
918}
919
920impl FromStr for SharedRandVal {
921 type Err = Error;
922 fn from_str(s: &str) -> Result<Self> {
923 let val: B64 = s.parse()?;
924 let val = SharedRandVal(val.into_array()?);
925 Ok(val)
926 }
927}
928impl Display for SharedRandVal {
929 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
930 Display::fmt(&B64::from(Vec::from(self.0)), f)
931 }
932}
933impl NormalItemArgument for SharedRandVal {}
934
935impl SharedRandStatus {
936 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
939 match item.kwd() {
940 NetstatusKwd::SHARED_RAND_PREVIOUS_VALUE | NetstatusKwd::SHARED_RAND_CURRENT_VALUE => {}
941 _ => {
942 return Err(Error::from(internal!(
943 "wrong keyword {:?} on shared-random value",
944 item.kwd()
945 ))
946 .at_pos(item.pos()));
947 }
948 }
949 let n_reveals: u8 = item.parse_arg(0)?;
950 let value: SharedRandVal = item.parse_arg(1)?;
951 let timestamp = item.parse_optional_arg::<Iso8601TimeNoSp>(2)?;
953 Ok(SharedRandStatus {
954 n_reveals,
955 value,
956 timestamp,
957 })
958 }
959
960 pub fn value(&self) -> &SharedRandVal {
962 &self.value
963 }
964
965 pub fn timestamp(&self) -> Option<std::time::SystemTime> {
967 self.timestamp.map(|t| t.0)
968 }
969}
970
971impl DirSource {
972 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
974 if item.kwd() != NetstatusKwd::DIR_SOURCE {
975 return Err(
976 Error::from(internal!("Bad keyword {:?} on dir-source", item.kwd()))
977 .at_pos(item.pos()),
978 );
979 }
980 let nickname = item.required_arg(0)?.to_string();
981 let identity = item.parse_arg::<Fingerprint>(1)?.into();
982 let ip = item.parse_arg(3)?;
983 let dir_port = item.parse_arg(4)?;
984 let or_port = item.parse_arg(5)?;
985
986 Ok(DirSource {
987 nickname,
988 identity,
989 ip,
990 dir_port,
991 or_port,
992 })
993 }
994}
995
996impl ConsensusVoterInfo {
997 fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<ConsensusVoterInfo> {
999 use NetstatusKwd::*;
1000 #[allow(clippy::unwrap_used)]
1003 let first = sec.first_item().unwrap();
1004 if first.kwd() != DIR_SOURCE {
1005 return Err(Error::from(internal!(
1006 "Wrong keyword {:?} at start of voter info",
1007 first.kwd()
1008 ))
1009 .at_pos(first.pos()));
1010 }
1011 let dir_source = DirSource::from_item(sec.required(DIR_SOURCE)?)?;
1012
1013 let contact = sec.required(CONTACT)?.args_as_str().to_string();
1014
1015 let vote_digest = sec.required(VOTE_DIGEST)?.parse_arg::<B16>(0)?.into();
1016
1017 Ok(ConsensusVoterInfo {
1018 dir_source,
1019 contact,
1020 vote_digest,
1021 })
1022 }
1023}
1024
1025impl Default for RelayWeight {
1026 fn default() -> RelayWeight {
1027 RelayWeight::Unmeasured(0)
1028 }
1029}
1030
1031impl RelayWeight {
1032 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<RelayWeight> {
1034 if item.kwd() != NetstatusKwd::RS_W {
1035 return Err(
1036 Error::from(internal!("Wrong keyword {:?} on W line", item.kwd()))
1037 .at_pos(item.pos()),
1038 );
1039 }
1040
1041 let params = item.args_as_str().parse()?;
1042
1043 Self::from_net_params(¶ms).map_err(|e| e.at_pos(item.pos()))
1044 }
1045
1046 fn from_net_params(params: &NetParams<u32>) -> Result<RelayWeight> {
1050 let bw = params.params.get("Bandwidth");
1051 let unmeas = params.params.get("Unmeasured");
1052
1053 let bw = match bw {
1054 None => return Ok(RelayWeight::Unmeasured(0)),
1055 Some(b) => *b,
1056 };
1057
1058 match unmeas {
1059 None | Some(0) => Ok(RelayWeight::Measured(bw)),
1060 Some(1) => Ok(RelayWeight::Unmeasured(bw)),
1061 _ => Err(EK::BadArgument.with_msg("unmeasured value")),
1062 }
1063 }
1064}
1065
1066#[cfg(feature = "parse2")]
1070mod parse2_impls {
1071 use super::*;
1072 use parse2::ArgumentError as AE;
1073 use parse2::ErrorProblem as EP;
1074 use parse2::{ArgumentStream, ItemArgumentParseable, ItemValueParseable};
1075 use parse2::{KeywordRef, NetdocParseableFields, UnparsedItem};
1076 use paste::paste;
1077 use std::result::Result;
1078
1079 macro_rules! impl_proto_statuses { { $( $rr:ident $cr:ident; )* } => { paste! {
1093 #[derive(Deftly)]
1094 #[derive_deftly(NetdocParseableFields)]
1095 #[allow(unreachable_pub)]
1097 pub struct ProtoStatusesParseHelper {
1098 $(
1099 #[deftly(netdoc(default))]
1100 [<$rr _ $cr _protocols>]: Protocols,
1101 )*
1102 }
1103
1104 pub use ProtoStatusesParseHelperNetdocParseAccumulator
1106 as ProtoStatusesNetdocParseAccumulator;
1107
1108 impl NetdocParseableFields for ProtoStatuses {
1109 type Accumulator = ProtoStatusesNetdocParseAccumulator;
1110 fn is_item_keyword(kw: KeywordRef<'_>) -> bool {
1111 ProtoStatusesParseHelper::is_item_keyword(kw)
1112 }
1113 fn accumulate_item(
1114 acc: &mut Self::Accumulator,
1115 item: UnparsedItem<'_>,
1116 ) -> Result<(), EP> {
1117 ProtoStatusesParseHelper::accumulate_item(acc, item)
1118 }
1119 fn finish(acc: Self::Accumulator) -> Result<Self, EP> {
1120 let parse = ProtoStatusesParseHelper::finish(acc)?;
1121 let mut out = ProtoStatuses::default();
1122 $(
1123 out.$cr.$rr = parse.[< $rr _ $cr _protocols >];
1124 )*
1125 Ok(out)
1126 }
1127 }
1128 } } }
1129
1130 impl_proto_statuses! {
1131 required client;
1132 required relay;
1133 recommended client;
1134 recommended relay;
1135 }
1136
1137 impl ItemValueParseable for NetParams<i32> {
1138 fn from_unparsed(item: parse2::UnparsedItem<'_>) -> Result<Self, EP> {
1139 item.check_no_object()?;
1140 item.args_copy()
1141 .into_remaining()
1142 .parse()
1143 .map_err(item.invalid_argument_handler("parameters"))
1144 }
1145 }
1146
1147 impl ItemValueParseable for RelayWeight {
1148 fn from_unparsed(item: parse2::UnparsedItem<'_>) -> Result<Self, EP> {
1149 item.check_no_object()?;
1150 (|| {
1151 let params = item.args_copy().into_remaining().parse()?;
1152 Self::from_net_params(¶ms)
1153 })()
1154 .map_err(item.invalid_argument_handler("weights"))
1155 }
1156 }
1157
1158 impl ItemValueParseable for rs::SoftwareVersion {
1159 fn from_unparsed(mut item: parse2::UnparsedItem<'_>) -> Result<Self, EP> {
1160 item.check_no_object()?;
1161 item.args_mut()
1162 .into_remaining()
1163 .parse()
1164 .map_err(item.invalid_argument_handler("version"))
1165 }
1166 }
1167
1168 impl ItemArgumentParseable for IgnoredPublicationTimeSp {
1169 fn from_args(a: &mut ArgumentStream) -> Result<IgnoredPublicationTimeSp, AE> {
1170 let mut next_arg = || a.next().ok_or(AE::Missing);
1171 let _: &str = next_arg()?;
1172 let _: &str = next_arg()?;
1173 Ok(IgnoredPublicationTimeSp)
1174 }
1175 }
1176}
1177
1178impl Footer {
1179 fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<Footer> {
1181 use NetstatusKwd::*;
1182 sec.required(DIRECTORY_FOOTER)?;
1183
1184 let weights = sec
1185 .maybe(BANDWIDTH_WEIGHTS)
1186 .args_as_str()
1187 .unwrap_or("")
1188 .parse()?;
1189
1190 Ok(Footer { weights })
1191 }
1192}
1193
1194enum SigCheckResult {
1196 Valid,
1198 Invalid,
1201 MissingCert,
1204}
1205
1206impl Signature {
1207 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Signature> {
1209 if item.kwd() != NetstatusKwd::DIRECTORY_SIGNATURE {
1210 return Err(Error::from(internal!(
1211 "Wrong keyword {:?} for directory signature",
1212 item.kwd()
1213 ))
1214 .at_pos(item.pos()));
1215 }
1216
1217 let (alg, id_fp, sk_fp) = if item.n_args() > 2 {
1218 (
1219 item.required_arg(0)?,
1220 item.required_arg(1)?,
1221 item.required_arg(2)?,
1222 )
1223 } else {
1224 ("sha1", item.required_arg(0)?, item.required_arg(1)?)
1225 };
1226
1227 let digestname = alg.to_string();
1228 let id_fingerprint = id_fp.parse::<Fingerprint>()?.into();
1229 let sk_fingerprint = sk_fp.parse::<Fingerprint>()?.into();
1230 let key_ids = AuthCertKeyIds {
1231 id_fingerprint,
1232 sk_fingerprint,
1233 };
1234 let signature = item.obj("SIGNATURE")?;
1235
1236 Ok(Signature {
1237 digestname,
1238 key_ids,
1239 signature,
1240 })
1241 }
1242
1243 fn matches_cert(&self, cert: &AuthCert) -> bool {
1246 cert.key_ids() == self.key_ids
1247 }
1248
1249 fn find_cert<'a>(&self, certs: &'a [AuthCert]) -> Option<&'a AuthCert> {
1252 certs.iter().find(|&c| self.matches_cert(c))
1253 }
1254
1255 fn check_signature(&self, signed_digest: &[u8], certs: &[AuthCert]) -> SigCheckResult {
1259 match self.find_cert(certs) {
1260 None => SigCheckResult::MissingCert,
1261 Some(cert) => {
1262 let key = cert.signing_key();
1263 match key.verify(signed_digest, &self.signature[..]) {
1264 Ok(()) => SigCheckResult::Valid,
1265 Err(_) => SigCheckResult::Invalid,
1266 }
1267 }
1268 }
1269 }
1270}
1271
1272impl SignatureGroup {
1273 fn list_missing(&self, certs: &[AuthCert]) -> (usize, Vec<&Signature>) {
1280 let mut ok: HashSet<RsaIdentity> = HashSet::new();
1281 let mut missing = Vec::new();
1282 for sig in &self.signatures {
1283 let id_fingerprint = &sig.key_ids.id_fingerprint;
1284 if ok.contains(id_fingerprint) {
1285 continue;
1286 }
1287 if sig.find_cert(certs).is_some() {
1288 ok.insert(*id_fingerprint);
1289 continue;
1290 }
1291
1292 missing.push(sig);
1293 }
1294 (ok.len(), missing)
1295 }
1296
1297 fn could_validate(&self, authorities: &[&RsaIdentity]) -> bool {
1301 let mut signed_by: HashSet<RsaIdentity> = HashSet::new();
1302 for sig in &self.signatures {
1303 let id_fp = &sig.key_ids.id_fingerprint;
1304 if signed_by.contains(id_fp) {
1305 continue;
1307 }
1308 if authorities.contains(&id_fp) {
1309 signed_by.insert(*id_fp);
1310 }
1311 }
1312
1313 signed_by.len() > (authorities.len() / 2)
1314 }
1315
1316 fn validate(&self, n_authorities: usize, certs: &[AuthCert]) -> bool {
1323 let mut ok: HashSet<RsaIdentity> = HashSet::new();
1327
1328 for sig in &self.signatures {
1329 let id_fingerprint = &sig.key_ids.id_fingerprint;
1330 if ok.contains(id_fingerprint) {
1331 continue;
1334 }
1335
1336 let d: Option<&[u8]> = match sig.digestname.as_ref() {
1337 "sha256" => self.sha256.as_ref().map(|a| &a[..]),
1338 "sha1" => self.sha1.as_ref().map(|a| &a[..]),
1339 _ => None, };
1341 if d.is_none() {
1342 continue;
1345 }
1346
1347 #[allow(clippy::unwrap_used)]
1349 match sig.check_signature(d.as_ref().unwrap(), certs) {
1350 SigCheckResult::Valid => {
1351 ok.insert(*id_fingerprint);
1352 }
1353 _ => continue,
1354 }
1355 }
1356
1357 ok.len() > (n_authorities / 2)
1358 }
1359}
1360
1361#[cfg(test)]
1362mod test {
1363 #![allow(clippy::bool_assert_comparison)]
1365 #![allow(clippy::clone_on_copy)]
1366 #![allow(clippy::dbg_macro)]
1367 #![allow(clippy::mixed_attributes_style)]
1368 #![allow(clippy::print_stderr)]
1369 #![allow(clippy::print_stdout)]
1370 #![allow(clippy::single_char_pattern)]
1371 #![allow(clippy::unwrap_used)]
1372 #![allow(clippy::unchecked_time_subtraction)]
1373 #![allow(clippy::useless_vec)]
1374 #![allow(clippy::needless_pass_by_value)]
1375 use super::*;
1377 use hex_literal::hex;
1378 #[cfg(all(feature = "ns-vote", feature = "parse2"))]
1379 use {
1380 crate::parse2::{NetdocSigned as _, ParseInput, parse_netdoc},
1381 std::fs,
1382 };
1383
1384 const CERTS: &str = include_str!("../../testdata/authcerts2.txt");
1385 const CONSENSUS: &str = include_str!("../../testdata/mdconsensus1.txt");
1386
1387 #[cfg(feature = "plain-consensus")]
1388 const PLAIN_CERTS: &str = include_str!("../../testdata2/cached-certs");
1389 #[cfg(feature = "plain-consensus")]
1390 const PLAIN_CONSENSUS: &str = include_str!("../../testdata2/cached-consensus");
1391
1392 fn read_bad(fname: &str) -> String {
1393 use std::fs;
1394 use std::path::PathBuf;
1395 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1396 path.push("testdata");
1397 path.push("bad-mdconsensus");
1398 path.push(fname);
1399
1400 fs::read_to_string(path).unwrap()
1401 }
1402
1403 #[test]
1404 fn parse_and_validate_md() -> Result<()> {
1405 use std::net::SocketAddr;
1406 use tor_checkable::{SelfSigned, Timebound};
1407 let mut certs = Vec::new();
1408 for cert in AuthCert::parse_multiple(CERTS)? {
1409 let cert = cert?.check_signature()?.dangerously_assume_timely();
1410 certs.push(cert);
1411 }
1412 let auth_ids: Vec<_> = certs.iter().map(|c| c.id_fingerprint()).collect();
1413
1414 assert_eq!(certs.len(), 3);
1415
1416 let (_, _, consensus) = MdConsensus::parse(CONSENSUS)?;
1417 let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
1418
1419 assert!(consensus.authorities_are_correct(&auth_ids));
1421 assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
1423 {
1424 let bad_auth_id = (*b"xxxxxxxxxxxxxxxxxxxx").into();
1427 assert!(!consensus.authorities_are_correct(&[&bad_auth_id]));
1428 }
1429
1430 let missing = consensus.key_is_correct(&[]).err().unwrap();
1431 assert_eq!(3, missing.len());
1432 assert!(consensus.key_is_correct(&certs).is_ok());
1433 let missing = consensus.key_is_correct(&certs[0..1]).err().unwrap();
1434 assert_eq!(2, missing.len());
1435
1436 let same_three_times = vec![certs[0].clone(), certs[0].clone(), certs[0].clone()];
1438 let missing = consensus.key_is_correct(&same_three_times).err().unwrap();
1439
1440 assert_eq!(2, missing.len());
1441 assert!(consensus.is_well_signed(&same_three_times).is_err());
1442
1443 assert!(consensus.key_is_correct(&certs).is_ok());
1444 let consensus = consensus.check_signature(&certs)?;
1445
1446 assert_eq!(6, consensus.relays().len());
1447 let r0 = &consensus.relays()[0];
1448 assert_eq!(
1449 r0.md_digest(),
1450 &hex!("73dabe0a0468f4f7a67810a18d11e36731bb1d2ec3634db459100609f3b3f535")
1451 );
1452 assert_eq!(
1453 r0.rsa_identity().as_bytes(),
1454 &hex!("0a3057af2910415794d8ea430309d9ac5f5d524b")
1455 );
1456 assert!(!r0.weight().is_measured());
1457 assert!(!r0.weight().is_nonzero());
1458 let pv = &r0.protovers();
1459 assert!(pv.supports_subver("HSDir", 2));
1460 assert!(!pv.supports_subver("HSDir", 3));
1461 let ip4 = "127.0.0.1:5002".parse::<SocketAddr>().unwrap();
1462 let ip6 = "[::1]:5002".parse::<SocketAddr>().unwrap();
1463 assert!(r0.addrs().any(|a| a == ip4));
1464 assert!(r0.addrs().any(|a| a == ip6));
1465
1466 Ok(())
1467 }
1468
1469 #[test]
1470 #[cfg(feature = "plain-consensus")]
1471 fn parse_and_validate_ns() -> Result<()> {
1472 use tor_checkable::{SelfSigned, Timebound};
1473 let mut certs = Vec::new();
1474 for cert in AuthCert::parse_multiple(PLAIN_CERTS)? {
1475 let cert = cert?.check_signature()?.dangerously_assume_timely();
1476 certs.push(cert);
1477 }
1478 let auth_ids: Vec<_> = certs.iter().map(|c| c.id_fingerprint()).collect();
1479 assert_eq!(certs.len(), 4);
1480
1481 let (_, _, consensus) = PlainConsensus::parse(PLAIN_CONSENSUS)?;
1482 let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
1483 assert!(consensus.authorities_are_correct(&auth_ids));
1485 assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
1487
1488 assert!(consensus.key_is_correct(&certs).is_ok());
1489
1490 let _consensus = consensus.check_signature(&certs)?;
1491
1492 Ok(())
1493 }
1494
1495 #[test]
1496 #[cfg(all(feature = "ns-vote", feature = "parse2"))]
1497 fn parse2_vote() -> anyhow::Result<()> {
1498 let file = "testdata2/v3-status-votes--1";
1499 let text = fs::read_to_string(file)?;
1500
1501 use crate::parse2::poc::netstatus::NetworkStatusSignedVote;
1503
1504 let input = ParseInput::new(&text, file);
1505 let doc: NetworkStatusSignedVote = parse_netdoc(&input)?;
1506
1507 println!("{doc:?}");
1508 println!("{:#?}", doc.inspect_unverified().0.r[0]);
1509
1510 Ok(())
1511 }
1512
1513 #[test]
1514 fn test_bad() {
1515 use crate::Pos;
1516 fn check(fname: &str, e: &Error) {
1517 let content = read_bad(fname);
1518 let res = MdConsensus::parse(&content);
1519 assert!(res.is_err());
1520 assert_eq!(&res.err().unwrap(), e);
1521 }
1522
1523 check(
1524 "bad-flags",
1525 &EK::BadArgument
1526 .at_pos(Pos::from_line(27, 1))
1527 .with_msg("Flags out of order"),
1528 );
1529 check(
1530 "bad-md-digest",
1531 &EK::BadArgument
1532 .at_pos(Pos::from_line(40, 3))
1533 .with_msg("Invalid base64"),
1534 );
1535 check(
1536 "bad-weight",
1537 &EK::BadArgument
1538 .at_pos(Pos::from_line(67, 141))
1539 .with_msg("invalid digit found in string"),
1540 );
1541 check(
1542 "bad-weights",
1543 &EK::BadArgument
1544 .at_pos(Pos::from_line(51, 13))
1545 .with_msg("invalid digit found in string"),
1546 );
1547 check(
1548 "wrong-order",
1549 &EK::WrongSortOrder.at_pos(Pos::from_line(52, 1)),
1550 );
1551 check(
1552 "wrong-start",
1553 &EK::UnexpectedToken
1554 .with_msg("vote-status")
1555 .at_pos(Pos::from_line(1, 1)),
1556 );
1557 check("wrong-version", &EK::BadDocumentVersion.with_msg("10"));
1558 }
1559
1560 fn gettok(s: &str) -> Result<Item<'_, NetstatusKwd>> {
1561 let mut reader = NetDocReader::new(s)?;
1562 let tok = reader.next().unwrap();
1563 assert!(reader.next().is_none());
1564 tok
1565 }
1566
1567 #[test]
1568 fn test_weight() {
1569 let w = gettok("w Unmeasured=1 Bandwidth=6\n").unwrap();
1570 let w = RelayWeight::from_item(&w).unwrap();
1571 assert!(!w.is_measured());
1572 assert!(w.is_nonzero());
1573
1574 let w = gettok("w Bandwidth=10\n").unwrap();
1575 let w = RelayWeight::from_item(&w).unwrap();
1576 assert!(w.is_measured());
1577 assert!(w.is_nonzero());
1578
1579 let w = RelayWeight::default();
1580 assert!(!w.is_measured());
1581 assert!(!w.is_nonzero());
1582
1583 let w = gettok("w Mustelid=66 Cheato=7 Unmeasured=1\n").unwrap();
1584 let w = RelayWeight::from_item(&w).unwrap();
1585 assert!(!w.is_measured());
1586 assert!(!w.is_nonzero());
1587
1588 let w = gettok("r foo\n").unwrap();
1589 let w = RelayWeight::from_item(&w);
1590 assert!(w.is_err());
1591
1592 let w = gettok("r Bandwidth=6 Unmeasured=Frog\n").unwrap();
1593 let w = RelayWeight::from_item(&w);
1594 assert!(w.is_err());
1595
1596 let w = gettok("r Bandwidth=6 Unmeasured=3\n").unwrap();
1597 let w = RelayWeight::from_item(&w);
1598 assert!(w.is_err());
1599 }
1600
1601 #[test]
1602 fn test_netparam() {
1603 let p = "Hello=600 Goodbye=5 Fred=7"
1604 .parse::<NetParams<u32>>()
1605 .unwrap();
1606 assert_eq!(p.get("Hello"), Some(&600_u32));
1607
1608 let p = "Hello=Goodbye=5 Fred=7".parse::<NetParams<u32>>();
1609 assert!(p.is_err());
1610
1611 let p = "Hello=Goodbye Fred=7".parse::<NetParams<u32>>();
1612 assert!(p.is_err());
1613 }
1614
1615 #[test]
1616 fn test_sharedrand() {
1617 let sr =
1618 gettok("shared-rand-previous-value 9 5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4\n")
1619 .unwrap();
1620 let sr = SharedRandStatus::from_item(&sr).unwrap();
1621
1622 assert_eq!(sr.n_reveals, 9);
1623 assert_eq!(
1624 sr.value.0,
1625 hex!("e4ba1d638c96c458532adc6957dc0080d03d37c7e5854087d0da90bf5ff4e72e")
1626 );
1627 assert!(sr.timestamp.is_none());
1628
1629 let sr2 = gettok(
1630 "shared-rand-current-value 9 \
1631 5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4 2022-01-20T12:34:56\n",
1632 )
1633 .unwrap();
1634 let sr2 = SharedRandStatus::from_item(&sr2).unwrap();
1635 assert_eq!(sr2.n_reveals, sr.n_reveals);
1636 assert_eq!(sr2.value.0, sr.value.0);
1637 assert_eq!(
1638 sr2.timestamp.unwrap().0,
1639 humantime::parse_rfc3339("2022-01-20T12:34:56Z").unwrap()
1640 );
1641
1642 let sr = gettok("foo bar\n").unwrap();
1643 let sr = SharedRandStatus::from_item(&sr);
1644 assert!(sr.is_err());
1645 }
1646
1647 #[test]
1648 fn test_protostatus() {
1649 let my_protocols: Protocols = "Link=7 Cons=1-5 Desc=3-10".parse().unwrap();
1650
1651 let outcome = ProtoStatus {
1652 recommended: "Link=7".parse().unwrap(),
1653 required: "Desc=5".parse().unwrap(),
1654 }
1655 .check_protocols(&my_protocols);
1656 assert!(outcome.is_ok());
1657
1658 let outcome = ProtoStatus {
1659 recommended: "Microdesc=4 Link=7".parse().unwrap(),
1660 required: "Desc=5".parse().unwrap(),
1661 }
1662 .check_protocols(&my_protocols);
1663 assert_eq!(
1664 outcome,
1665 Err(ProtocolSupportError::MissingRecommended(
1666 "Microdesc=4".parse().unwrap()
1667 ))
1668 );
1669
1670 let outcome = ProtoStatus {
1671 recommended: "Microdesc=4 Link=7".parse().unwrap(),
1672 required: "Desc=5 Cons=5-12 Wombat=15".parse().unwrap(),
1673 }
1674 .check_protocols(&my_protocols);
1675 assert_eq!(
1676 outcome,
1677 Err(ProtocolSupportError::MissingRequired(
1678 "Cons=6-12 Wombat=15".parse().unwrap()
1679 ))
1680 );
1681 }
1682
1683 #[test]
1684 fn serialize_protostatus() {
1685 let ps = ProtoStatuses {
1686 client: ProtoStatus {
1687 recommended: "Link=1-5 LinkAuth=2-5".parse().unwrap(),
1688 required: "Link=5 LinkAuth=3".parse().unwrap(),
1689 },
1690 relay: ProtoStatus {
1691 recommended: "Wombat=20-30 Knish=20-30".parse().unwrap(),
1692 required: "Wombat=20-22 Knish=25-27".parse().unwrap(),
1693 },
1694 };
1695 let json = serde_json::to_string(&ps).unwrap();
1696 let ps2 = serde_json::from_str(json.as_str()).unwrap();
1697 assert_eq!(ps, ps2);
1698
1699 let ps3: ProtoStatuses = serde_json::from_str(
1700 r#"{
1701 "client":{
1702 "required":"Link=5 LinkAuth=3",
1703 "recommended":"Link=1-5 LinkAuth=2-5"
1704 },
1705 "relay":{
1706 "required":"Wombat=20-22 Knish=25-27",
1707 "recommended":"Wombat=20-30 Knish=20-30"
1708 }
1709 }"#,
1710 )
1711 .unwrap();
1712 assert_eq!(ps, ps3);
1713 }
1714}