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