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
62use crate::doc::authcert::{AuthCert, AuthCertKeyIds};
63use crate::parse::keyword::Keyword;
64use crate::parse::parser::{Section, SectionRules, SectionRulesBuilder};
65use crate::parse::tokenize::{Item, ItemResult, NetDocReader};
66use crate::types::misc::*;
67use crate::util::PeekableIterator;
68use crate::{Error, KeywordEncodable, NetdocErrorKind as EK, NormalItemArgument, Pos, Result};
69use std::collections::{BTreeSet, HashMap, HashSet};
70use std::fmt::{self, Display};
71use std::result::Result as StdResult;
72use std::str::FromStr;
73use std::sync::Arc;
74use std::{net, result, time};
75use tor_error::{HasKind, internal};
76use tor_protover::Protocols;
77
78use derive_deftly::{Deftly, define_derive_deftly};
79use digest::Digest;
80use std::sync::LazyLock;
81use tor_checkable::{ExternallySigned, timed::TimerangeBound};
82use tor_llcrypto as ll;
83use tor_llcrypto::pk::rsa::RsaIdentity;
84
85use serde::{Deserialize, Deserializer};
86
87#[cfg(feature = "build_docs")]
88pub use build::MdConsensusBuilder;
89#[cfg(all(feature = "build_docs", feature = "plain-consensus"))]
90pub use build::PlainConsensusBuilder;
91#[cfg(feature = "build_docs")]
92ns_export_each_flavor! {
93 ty: RouterStatusBuilder;
94}
95
96ns_export_each_variety! {
97 ty: RouterStatus, Preamble;
98}
99
100#[deprecated]
101#[cfg(feature = "ns_consensus")]
102pub use PlainConsensus as NsConsensus;
103#[deprecated]
104#[cfg(feature = "ns_consensus")]
105pub use PlainRouterStatus as NsRouterStatus;
106#[deprecated]
107#[cfg(feature = "ns_consensus")]
108pub use UncheckedPlainConsensus as UncheckedNsConsensus;
109#[deprecated]
110#[cfg(feature = "ns_consensus")]
111pub use UnvalidatedPlainConsensus as UnvalidatedNsConsensus;
112
113#[cfg(feature = "ns-vote")]
114pub use rs::RouterStatusMdDigestsVote;
115
116#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Default)]
130#[allow(clippy::exhaustive_structs)]
131pub struct IgnoredPublicationTimeSp;
132
133#[derive(Clone, Debug, Deftly)]
141#[derive_deftly(Lifetime)]
142#[cfg_attr(feature = "parse2", derive_deftly(NetdocParseableFields))]
143pub struct Lifetime {
144 #[cfg_attr(feature = "parse2", deftly(netdoc(single_arg)))]
151 valid_after: Iso8601TimeSp,
152 #[cfg_attr(feature = "parse2", deftly(netdoc(single_arg)))]
160 fresh_until: Iso8601TimeSp,
161 #[cfg_attr(feature = "parse2", deftly(netdoc(single_arg)))]
169 valid_until: Iso8601TimeSp,
170}
171
172define_derive_deftly! {
173 Lifetime:
175
176 impl Lifetime {
177 pub fn new(
179 $( $fname: time::SystemTime, )
180 ) -> Result<Self> {
181 let self_ = Lifetime {
185 $( $fname: $fname.into(), )
186 };
187 if self_.valid_after < self_.fresh_until && self_.fresh_until < self_.valid_until {
188 Ok(self_)
189 } else {
190 Err(EK::InvalidLifetime.err())
191 }
192 }
193 $(
194 ${fattrs doc}
195 pub fn $fname(&self) -> time::SystemTime {
196 *self.$fname
197 }
198 )
199 pub fn valid_at(&self, when: time::SystemTime) -> bool {
201 *self.valid_after <= when && when <= *self.valid_until
202 }
203
204 pub fn voting_period(&self) -> time::Duration {
209 let valid_after = self.valid_after();
210 let fresh_until = self.fresh_until();
211 fresh_until
212 .duration_since(valid_after)
213 .expect("Mis-formed lifetime")
214 }
215 }
216}
217use derive_deftly_template_Lifetime;
218
219#[derive(Debug, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Copy)] #[derive(derive_more::From, derive_more::Into, derive_more::Display, derive_more::FromStr)]
231pub struct ConsensusMethod(u32);
232impl NormalItemArgument for ConsensusMethod {}
233
234#[derive(Debug, Clone, Default, Eq, PartialEq)]
241#[cfg_attr(feature = "parse2", derive(Deftly), derive_deftly(ItemValueParseable))]
242#[non_exhaustive]
243pub struct ConsensusMethods {
244 pub methods: BTreeSet<ConsensusMethod>,
246}
247
248#[cfg(feature = "parse2")]
253pub mod consensus_methods_comma_separated {
254 use super::*;
255 use parse2::ArgumentError as AE;
256 use std::result::Result;
257
258 pub fn from_args<'s>(args: &mut ArgumentStream<'s>) -> Result<ConsensusMethods, AE> {
260 let mut methods = BTreeSet::new();
261 for ent in args.next().ok_or(AE::Missing)?.split(',') {
262 let ent = ent.parse().map_err(|_| AE::Invalid)?;
263 if !methods.insert(ent) {
264 return Err(AE::Invalid);
265 }
266 }
267 Ok(ConsensusMethods { methods })
268 }
269}
270
271#[derive(Debug, Clone, Default, Eq, PartialEq)]
287pub struct NetParams<T> {
288 params: HashMap<String, T>,
290}
291
292impl<T> NetParams<T> {
293 #[allow(unused)]
295 pub fn new() -> Self {
296 NetParams {
297 params: HashMap::new(),
298 }
299 }
300 pub fn get<A: AsRef<str>>(&self, v: A) -> Option<&T> {
302 self.params.get(v.as_ref())
303 }
304 pub fn iter(&self) -> impl Iterator<Item = (&String, &T)> {
306 self.params.iter()
307 }
308 pub fn set(&mut self, k: String, v: T) {
310 self.params.insert(k, v);
311 }
312}
313
314impl<K: Into<String>, T> FromIterator<(K, T)> for NetParams<T> {
315 fn from_iter<I: IntoIterator<Item = (K, T)>>(i: I) -> Self {
316 NetParams {
317 params: i.into_iter().map(|(k, v)| (k.into(), v)).collect(),
318 }
319 }
320}
321
322impl<T> std::iter::Extend<(String, T)> for NetParams<T> {
323 fn extend<I: IntoIterator<Item = (String, T)>>(&mut self, iter: I) {
324 self.params.extend(iter);
325 }
326}
327
328impl<'de, T> Deserialize<'de> for NetParams<T>
329where
330 T: Deserialize<'de>,
331{
332 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
333 where
334 D: Deserializer<'de>,
335 {
336 let params = HashMap::deserialize(deserializer)?;
337 Ok(NetParams { params })
338 }
339}
340
341#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
350pub struct ProtoStatus {
351 recommended: Protocols,
356 required: Protocols,
361}
362
363impl ProtoStatus {
364 pub fn check_protocols(
374 &self,
375 supported_protocols: &Protocols,
376 ) -> StdResult<(), ProtocolSupportError> {
377 let missing_required = self.required.difference(supported_protocols);
379 if !missing_required.is_empty() {
380 return Err(ProtocolSupportError::MissingRequired(missing_required));
381 }
382 let missing_recommended = self.recommended.difference(supported_protocols);
383 if !missing_recommended.is_empty() {
384 return Err(ProtocolSupportError::MissingRecommended(
385 missing_recommended,
386 ));
387 }
388
389 Ok(())
390 }
391}
392
393#[derive(Clone, Debug, thiserror::Error)]
395#[cfg_attr(test, derive(PartialEq))]
396#[non_exhaustive]
397pub enum ProtocolSupportError {
398 #[error("Required protocols are not implemented: {0}")]
400 MissingRequired(Protocols),
401
402 #[error("Recommended protocols are not implemented: {0}")]
406 MissingRecommended(Protocols),
407}
408
409impl ProtocolSupportError {
410 pub fn should_shutdown(&self) -> bool {
412 matches!(self, Self::MissingRequired(_))
413 }
414}
415
416impl HasKind for ProtocolSupportError {
417 fn kind(&self) -> tor_error::ErrorKind {
418 tor_error::ErrorKind::SoftwareDeprecated
419 }
420}
421
422#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
429pub struct ProtoStatuses {
430 client: ProtoStatus,
432 relay: ProtoStatus,
434}
435
436impl ProtoStatuses {
437 pub fn client(&self) -> &ProtoStatus {
439 &self.client
440 }
441
442 pub fn relay(&self) -> &ProtoStatus {
444 &self.relay
445 }
446}
447
448#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
452#[non_exhaustive]
453pub enum ConsensusFlavor {
454 Microdesc,
457 Plain,
462}
463
464impl ConsensusFlavor {
465 pub fn name(&self) -> &'static str {
467 match self {
468 ConsensusFlavor::Plain => "ns", ConsensusFlavor::Microdesc => "microdesc",
470 }
471 }
472 pub fn from_opt_name(name: Option<&str>) -> Result<Self> {
477 match name {
478 Some("microdesc") => Ok(ConsensusFlavor::Microdesc),
479 Some("ns") | None => Ok(ConsensusFlavor::Plain),
480 Some(other) => {
481 Err(EK::BadDocumentType.with_msg(format!("unrecognized flavor {:?}", other)))
482 }
483 }
484 }
485}
486
487#[derive(Debug, Clone)]
489#[non_exhaustive]
490pub struct Signature {
491 pub digestname: String,
496 pub key_ids: AuthCertKeyIds,
499 pub signature: Vec<u8>,
501}
502
503#[derive(Debug, Clone)]
505#[non_exhaustive]
506pub struct SignatureGroup {
507 pub sha256: Option<[u8; 32]>,
509 pub sha1: Option<[u8; 20]>,
511 pub signatures: Vec<Signature>,
513}
514
515#[derive(
517 Debug, Clone, Copy, Eq, PartialEq, derive_more::From, derive_more::Into, derive_more::AsRef,
518)]
519pub struct SharedRandVal([u8; 32]);
521
522#[derive(Debug, Clone)]
525#[non_exhaustive]
526#[cfg_attr(feature = "parse2", derive(Deftly), derive_deftly(ItemValueParseable))]
527pub struct SharedRandStatus {
528 pub n_reveals: u8,
530 pub value: SharedRandVal,
537
538 pub timestamp: Option<Iso8601TimeNoSp>,
542}
543
544#[derive(Debug, Clone)]
548#[non_exhaustive]
549pub struct DirSource {
550 pub nickname: String,
552 pub identity: RsaIdentity,
558 pub ip: net::IpAddr,
560 pub dir_port: u16,
562 pub or_port: u16,
564}
565
566#[non_exhaustive]
568#[derive(Debug, Clone, Copy)]
569pub enum RelayWeight {
570 Unmeasured(u32),
572 Measured(u32),
574}
575
576impl RelayWeight {
577 pub fn is_measured(&self) -> bool {
579 matches!(self, RelayWeight::Measured(_))
580 }
581 pub fn is_nonzero(&self) -> bool {
583 !matches!(self, RelayWeight::Unmeasured(0) | RelayWeight::Measured(0))
584 }
585}
586
587#[derive(Debug, Clone)]
589#[non_exhaustive]
590pub struct ConsensusVoterInfo {
591 pub dir_source: DirSource,
593 pub contact: String,
595 pub vote_digest: Vec<u8>,
598}
599
600#[derive(Debug, Clone)]
602#[non_exhaustive]
603pub struct Footer {
604 pub weights: NetParams<i32>,
610}
611
612pub type MdConsensus = md::Consensus;
615
616pub type UnvalidatedMdConsensus = md::UnvalidatedConsensus;
619
620pub type UncheckedMdConsensus = md::UncheckedConsensus;
623
624#[cfg(feature = "plain-consensus")]
625pub type PlainConsensus = plain::Consensus;
628
629#[cfg(feature = "plain-consensus")]
630pub type UnvalidatedPlainConsensus = plain::UnvalidatedConsensus;
633
634#[cfg(feature = "plain-consensus")]
635pub type UncheckedPlainConsensus = plain::UncheckedConsensus;
638
639decl_keyword! {
640 #[non_exhaustive]
645 #[allow(missing_docs)]
646 pub NetstatusKwd {
647 "network-status-version" => NETWORK_STATUS_VERSION,
649 "vote-status" => VOTE_STATUS,
650 "consensus-methods" => CONSENSUS_METHODS,
651 "consensus-method" => CONSENSUS_METHOD,
652 "published" => PUBLISHED,
653 "valid-after" => VALID_AFTER,
654 "fresh-until" => FRESH_UNTIL,
655 "valid-until" => VALID_UNTIL,
656 "voting-delay" => VOTING_DELAY,
657 "client-versions" => CLIENT_VERSIONS,
658 "server-versions" => SERVER_VERSIONS,
659 "known-flags" => KNOWN_FLAGS,
660 "flag-thresholds" => FLAG_THRESHOLDS,
661 "recommended-client-protocols" => RECOMMENDED_CLIENT_PROTOCOLS,
662 "required-client-protocols" => REQUIRED_CLIENT_PROTOCOLS,
663 "recommended-relay-protocols" => RECOMMENDED_RELAY_PROTOCOLS,
664 "required-relay-protocols" => REQUIRED_RELAY_PROTOCOLS,
665 "params" => PARAMS,
666 "bandwidth-file-headers" => BANDWIDTH_FILE_HEADERS,
667 "bandwidth-file-digest" => BANDWIDTH_FILE_DIGEST,
668 "shared-rand-previous-value" => SHARED_RAND_PREVIOUS_VALUE,
672 "shared-rand-current-value" => SHARED_RAND_CURRENT_VALUE,
673
674 "dir-source" => DIR_SOURCE,
676 "contact" => CONTACT,
677
678 "legacy-dir-key" => LEGACY_DIR_KEY,
680 "shared-rand-participate" => SHARED_RAND_PARTICIPATE,
681 "shared-rand-commit" => SHARED_RAND_COMMIT,
682
683 "vote-digest" => VOTE_DIGEST,
685
686 "dir-key-certificate-version" => DIR_KEY_CERTIFICATE_VERSION,
688
689 "r" => RS_R,
691 "a" => RS_A,
692 "s" => RS_S,
693 "v" => RS_V,
694 "pr" => RS_PR,
695 "w" => RS_W,
696 "p" => RS_P,
697 "m" => RS_M,
698 "id" => RS_ID,
699
700 "directory-footer" => DIRECTORY_FOOTER,
702 "bandwidth-weights" => BANDWIDTH_WEIGHTS,
703 "directory-signature" => DIRECTORY_SIGNATURE,
704 }
705}
706
707static NS_HEADER_RULES_COMMON_: LazyLock<SectionRulesBuilder<NetstatusKwd>> = LazyLock::new(|| {
709 use NetstatusKwd::*;
710 let mut rules = SectionRules::builder();
711 rules.add(NETWORK_STATUS_VERSION.rule().required().args(1..=2));
712 rules.add(VOTE_STATUS.rule().required().args(1..));
713 rules.add(VALID_AFTER.rule().required());
714 rules.add(FRESH_UNTIL.rule().required());
715 rules.add(VALID_UNTIL.rule().required());
716 rules.add(VOTING_DELAY.rule().args(2..));
717 rules.add(CLIENT_VERSIONS.rule());
718 rules.add(SERVER_VERSIONS.rule());
719 rules.add(KNOWN_FLAGS.rule().required());
720 rules.add(RECOMMENDED_CLIENT_PROTOCOLS.rule().args(1..));
721 rules.add(RECOMMENDED_RELAY_PROTOCOLS.rule().args(1..));
722 rules.add(REQUIRED_CLIENT_PROTOCOLS.rule().args(1..));
723 rules.add(REQUIRED_RELAY_PROTOCOLS.rule().args(1..));
724 rules.add(PARAMS.rule());
725 rules
726});
727static NS_HEADER_RULES_CONSENSUS: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
729 use NetstatusKwd::*;
730 let mut rules = NS_HEADER_RULES_COMMON_.clone();
731 rules.add(CONSENSUS_METHOD.rule().args(1..=1));
732 rules.add(SHARED_RAND_PREVIOUS_VALUE.rule().args(2..));
733 rules.add(SHARED_RAND_CURRENT_VALUE.rule().args(2..));
734 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
735 rules.build()
736});
737static NS_VOTERINFO_RULES_CONSENSUS: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
768 use NetstatusKwd::*;
769 let mut rules = SectionRules::builder();
770 rules.add(DIR_SOURCE.rule().required().args(6..));
771 rules.add(CONTACT.rule().required());
772 rules.add(VOTE_DIGEST.rule().required());
773 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
774 rules.build()
775});
776static NS_ROUTERSTATUS_RULES_COMMON_: LazyLock<SectionRulesBuilder<NetstatusKwd>> =
778 LazyLock::new(|| {
779 use NetstatusKwd::*;
780 let mut rules = SectionRules::builder();
781 rules.add(RS_A.rule().may_repeat().args(1..));
782 rules.add(RS_S.rule().required());
783 rules.add(RS_V.rule());
784 rules.add(RS_PR.rule().required());
785 rules.add(RS_W.rule());
786 rules.add(RS_P.rule().args(2..));
787 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
788 rules
789 });
790
791static NS_ROUTERSTATUS_RULES_PLAIN: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
793 use NetstatusKwd::*;
794 let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
795 rules.add(RS_R.rule().required().args(8..));
796 rules.build()
797});
798
799static NS_ROUTERSTATUS_RULES_MDCON: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
812 use NetstatusKwd::*;
813 let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
814 rules.add(RS_R.rule().required().args(6..));
815 rules.add(RS_M.rule().required().args(1..));
816 rules.build()
817});
818static NS_FOOTER_RULES: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
820 use NetstatusKwd::*;
821 let mut rules = SectionRules::builder();
822 rules.add(DIRECTORY_FOOTER.rule().required().no_args());
823 rules.add(BANDWIDTH_WEIGHTS.rule());
825 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
826 rules.build()
827});
828
829impl ProtoStatus {
830 fn from_section(
832 sec: &Section<'_, NetstatusKwd>,
833 recommend_token: NetstatusKwd,
834 required_token: NetstatusKwd,
835 ) -> Result<ProtoStatus> {
836 fn parse(t: Option<&Item<'_, NetstatusKwd>>) -> Result<Protocols> {
838 if let Some(item) = t {
839 item.args_as_str()
840 .parse::<Protocols>()
841 .map_err(|e| EK::BadArgument.at_pos(item.pos()).with_source(e))
842 } else {
843 Ok(Protocols::new())
844 }
845 }
846
847 let recommended = parse(sec.get(recommend_token))?;
848 let required = parse(sec.get(required_token))?;
849 Ok(ProtoStatus {
850 recommended,
851 required,
852 })
853 }
854
855 pub fn required_protocols(&self) -> &Protocols {
862 &self.required
863 }
864
865 pub fn recommended_protocols(&self) -> &Protocols {
870 &self.recommended
871 }
872}
873
874impl<T> std::str::FromStr for NetParams<T>
875where
876 T: std::str::FromStr,
877 T::Err: std::error::Error,
878{
879 type Err = Error;
880 fn from_str(s: &str) -> Result<Self> {
881 fn parse_pair<U>(p: &str) -> Result<(String, U)>
883 where
884 U: std::str::FromStr,
885 U::Err: std::error::Error,
886 {
887 let parts: Vec<_> = p.splitn(2, '=').collect();
888 if parts.len() != 2 {
889 return Err(EK::BadArgument
890 .at_pos(Pos::at(p))
891 .with_msg("Missing = in key=value list"));
892 }
893 let num = parts[1].parse::<U>().map_err(|e| {
894 EK::BadArgument
895 .at_pos(Pos::at(parts[1]))
896 .with_msg(e.to_string())
897 })?;
898 Ok((parts[0].to_string(), num))
899 }
900
901 let params = s
902 .split(' ')
903 .filter(|p| !p.is_empty())
904 .map(parse_pair)
905 .collect::<Result<HashMap<_, _>>>()?;
906 Ok(NetParams { params })
907 }
908}
909
910impl FromStr for SharedRandVal {
911 type Err = Error;
912 fn from_str(s: &str) -> Result<Self> {
913 let val: B64 = s.parse()?;
914 let val = SharedRandVal(val.into_array()?);
915 Ok(val)
916 }
917}
918impl Display for SharedRandVal {
919 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
920 Display::fmt(&B64::from(Vec::from(self.0)), f)
921 }
922}
923impl NormalItemArgument for SharedRandVal {}
924
925impl SharedRandStatus {
926 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
929 match item.kwd() {
930 NetstatusKwd::SHARED_RAND_PREVIOUS_VALUE | NetstatusKwd::SHARED_RAND_CURRENT_VALUE => {}
931 _ => {
932 return Err(Error::from(internal!(
933 "wrong keyword {:?} on shared-random value",
934 item.kwd()
935 ))
936 .at_pos(item.pos()));
937 }
938 }
939 let n_reveals: u8 = item.parse_arg(0)?;
940 let value: SharedRandVal = item.parse_arg(1)?;
941 let timestamp = item.parse_optional_arg::<Iso8601TimeNoSp>(2)?;
943 Ok(SharedRandStatus {
944 n_reveals,
945 value,
946 timestamp,
947 })
948 }
949
950 pub fn value(&self) -> &SharedRandVal {
952 &self.value
953 }
954
955 pub fn timestamp(&self) -> Option<std::time::SystemTime> {
957 self.timestamp.map(|t| t.0)
958 }
959}
960
961impl DirSource {
962 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
964 if item.kwd() != NetstatusKwd::DIR_SOURCE {
965 return Err(
966 Error::from(internal!("Bad keyword {:?} on dir-source", item.kwd()))
967 .at_pos(item.pos()),
968 );
969 }
970 let nickname = item.required_arg(0)?.to_string();
971 let identity = item.parse_arg::<Fingerprint>(1)?.into();
972 let ip = item.parse_arg(3)?;
973 let dir_port = item.parse_arg(4)?;
974 let or_port = item.parse_arg(5)?;
975
976 Ok(DirSource {
977 nickname,
978 identity,
979 ip,
980 dir_port,
981 or_port,
982 })
983 }
984}
985
986impl ConsensusVoterInfo {
987 fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<ConsensusVoterInfo> {
989 use NetstatusKwd::*;
990 #[allow(clippy::unwrap_used)]
993 let first = sec.first_item().unwrap();
994 if first.kwd() != DIR_SOURCE {
995 return Err(Error::from(internal!(
996 "Wrong keyword {:?} at start of voter info",
997 first.kwd()
998 ))
999 .at_pos(first.pos()));
1000 }
1001 let dir_source = DirSource::from_item(sec.required(DIR_SOURCE)?)?;
1002
1003 let contact = sec.required(CONTACT)?.args_as_str().to_string();
1004
1005 let vote_digest = sec.required(VOTE_DIGEST)?.parse_arg::<B16>(0)?.into();
1006
1007 Ok(ConsensusVoterInfo {
1008 dir_source,
1009 contact,
1010 vote_digest,
1011 })
1012 }
1013}
1014
1015impl Default for RelayWeight {
1016 fn default() -> RelayWeight {
1017 RelayWeight::Unmeasured(0)
1018 }
1019}
1020
1021impl RelayWeight {
1022 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<RelayWeight> {
1024 if item.kwd() != NetstatusKwd::RS_W {
1025 return Err(
1026 Error::from(internal!("Wrong keyword {:?} on W line", item.kwd()))
1027 .at_pos(item.pos()),
1028 );
1029 }
1030
1031 let params = item.args_as_str().parse()?;
1032
1033 Self::from_net_params(¶ms).map_err(|e| e.at_pos(item.pos()))
1034 }
1035
1036 fn from_net_params(params: &NetParams<u32>) -> Result<RelayWeight> {
1040 let bw = params.params.get("Bandwidth");
1041 let unmeas = params.params.get("Unmeasured");
1042
1043 let bw = match bw {
1044 None => return Ok(RelayWeight::Unmeasured(0)),
1045 Some(b) => *b,
1046 };
1047
1048 match unmeas {
1049 None | Some(0) => Ok(RelayWeight::Measured(bw)),
1050 Some(1) => Ok(RelayWeight::Unmeasured(bw)),
1051 _ => Err(EK::BadArgument.with_msg("unmeasured value")),
1052 }
1053 }
1054}
1055
1056#[cfg(feature = "parse2")]
1060mod parse2_impls {
1061 use super::*;
1062 use parse2::ArgumentError as AE;
1063 use parse2::ErrorProblem as EP;
1064 use parse2::{ArgumentStream, ItemArgumentParseable, ItemValueParseable};
1065 use std::result::Result;
1066
1067 impl ItemValueParseable for RelayWeight {
1068 fn from_unparsed(item: parse2::UnparsedItem<'_>) -> Result<Self, EP> {
1069 item.check_no_object()?;
1070 (|| {
1071 let params = item.args_copy().into_remaining().parse()?;
1072 Self::from_net_params(¶ms)
1073 })()
1074 .map_err(item.invalid_argument_handler("weights"))
1075 }
1076 }
1077
1078 impl ItemValueParseable for rs::Version {
1079 fn from_unparsed(mut item: parse2::UnparsedItem<'_>) -> Result<Self, EP> {
1080 item.check_no_object()?;
1081 item.args_mut()
1082 .into_remaining()
1083 .parse()
1084 .map_err(item.invalid_argument_handler("version"))
1085 }
1086 }
1087
1088 impl ItemArgumentParseable for IgnoredPublicationTimeSp {
1089 fn from_args(a: &mut ArgumentStream) -> Result<IgnoredPublicationTimeSp, AE> {
1090 let mut next_arg = || a.next().ok_or(AE::Missing);
1091 let _: &str = next_arg()?;
1092 let _: &str = next_arg()?;
1093 Ok(IgnoredPublicationTimeSp)
1094 }
1095 }
1096}
1097
1098impl Footer {
1099 fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<Footer> {
1101 use NetstatusKwd::*;
1102 sec.required(DIRECTORY_FOOTER)?;
1103
1104 let weights = sec
1105 .maybe(BANDWIDTH_WEIGHTS)
1106 .args_as_str()
1107 .unwrap_or("")
1108 .parse()?;
1109
1110 Ok(Footer { weights })
1111 }
1112}
1113
1114enum SigCheckResult {
1116 Valid,
1118 Invalid,
1121 MissingCert,
1124}
1125
1126impl Signature {
1127 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Signature> {
1129 if item.kwd() != NetstatusKwd::DIRECTORY_SIGNATURE {
1130 return Err(Error::from(internal!(
1131 "Wrong keyword {:?} for directory signature",
1132 item.kwd()
1133 ))
1134 .at_pos(item.pos()));
1135 }
1136
1137 let (alg, id_fp, sk_fp) = if item.n_args() > 2 {
1138 (
1139 item.required_arg(0)?,
1140 item.required_arg(1)?,
1141 item.required_arg(2)?,
1142 )
1143 } else {
1144 ("sha1", item.required_arg(0)?, item.required_arg(1)?)
1145 };
1146
1147 let digestname = alg.to_string();
1148 let id_fingerprint = id_fp.parse::<Fingerprint>()?.into();
1149 let sk_fingerprint = sk_fp.parse::<Fingerprint>()?.into();
1150 let key_ids = AuthCertKeyIds {
1151 id_fingerprint,
1152 sk_fingerprint,
1153 };
1154 let signature = item.obj("SIGNATURE")?;
1155
1156 Ok(Signature {
1157 digestname,
1158 key_ids,
1159 signature,
1160 })
1161 }
1162
1163 fn matches_cert(&self, cert: &AuthCert) -> bool {
1166 cert.key_ids() == &self.key_ids
1167 }
1168
1169 fn find_cert<'a>(&self, certs: &'a [AuthCert]) -> Option<&'a AuthCert> {
1172 certs.iter().find(|&c| self.matches_cert(c))
1173 }
1174
1175 fn check_signature(&self, signed_digest: &[u8], certs: &[AuthCert]) -> SigCheckResult {
1179 match self.find_cert(certs) {
1180 None => SigCheckResult::MissingCert,
1181 Some(cert) => {
1182 let key = cert.signing_key();
1183 match key.verify(signed_digest, &self.signature[..]) {
1184 Ok(()) => SigCheckResult::Valid,
1185 Err(_) => SigCheckResult::Invalid,
1186 }
1187 }
1188 }
1189 }
1190}
1191
1192impl SignatureGroup {
1193 fn list_missing(&self, certs: &[AuthCert]) -> (usize, Vec<&Signature>) {
1200 let mut ok: HashSet<RsaIdentity> = HashSet::new();
1201 let mut missing = Vec::new();
1202 for sig in &self.signatures {
1203 let id_fingerprint = &sig.key_ids.id_fingerprint;
1204 if ok.contains(id_fingerprint) {
1205 continue;
1206 }
1207 if sig.find_cert(certs).is_some() {
1208 ok.insert(*id_fingerprint);
1209 continue;
1210 }
1211
1212 missing.push(sig);
1213 }
1214 (ok.len(), missing)
1215 }
1216
1217 fn could_validate(&self, authorities: &[&RsaIdentity]) -> bool {
1221 let mut signed_by: HashSet<RsaIdentity> = HashSet::new();
1222 for sig in &self.signatures {
1223 let id_fp = &sig.key_ids.id_fingerprint;
1224 if signed_by.contains(id_fp) {
1225 continue;
1227 }
1228 if authorities.contains(&id_fp) {
1229 signed_by.insert(*id_fp);
1230 }
1231 }
1232
1233 signed_by.len() > (authorities.len() / 2)
1234 }
1235
1236 fn validate(&self, n_authorities: u16, certs: &[AuthCert]) -> bool {
1243 let mut ok: HashSet<RsaIdentity> = HashSet::new();
1247
1248 for sig in &self.signatures {
1249 let id_fingerprint = &sig.key_ids.id_fingerprint;
1250 if ok.contains(id_fingerprint) {
1251 continue;
1254 }
1255
1256 let d: Option<&[u8]> = match sig.digestname.as_ref() {
1257 "sha256" => self.sha256.as_ref().map(|a| &a[..]),
1258 "sha1" => self.sha1.as_ref().map(|a| &a[..]),
1259 _ => None, };
1261 if d.is_none() {
1262 continue;
1265 }
1266
1267 #[allow(clippy::unwrap_used)]
1269 match sig.check_signature(d.as_ref().unwrap(), certs) {
1270 SigCheckResult::Valid => {
1271 ok.insert(*id_fingerprint);
1272 }
1273 _ => continue,
1274 }
1275 }
1276
1277 ok.len() > (n_authorities / 2) as usize
1278 }
1279}
1280
1281#[cfg(test)]
1282mod test {
1283 #![allow(clippy::bool_assert_comparison)]
1285 #![allow(clippy::clone_on_copy)]
1286 #![allow(clippy::dbg_macro)]
1287 #![allow(clippy::mixed_attributes_style)]
1288 #![allow(clippy::print_stderr)]
1289 #![allow(clippy::print_stdout)]
1290 #![allow(clippy::single_char_pattern)]
1291 #![allow(clippy::unwrap_used)]
1292 #![allow(clippy::unchecked_time_subtraction)]
1293 #![allow(clippy::useless_vec)]
1294 #![allow(clippy::needless_pass_by_value)]
1295 use super::*;
1297 use hex_literal::hex;
1298 #[cfg(all(feature = "ns-vote", feature = "parse2"))]
1299 use {
1300 crate::parse2::{NetdocSigned as _, ParseInput, parse_netdoc},
1301 std::fs,
1302 };
1303
1304 const CERTS: &str = include_str!("../../testdata/authcerts2.txt");
1305 const CONSENSUS: &str = include_str!("../../testdata/mdconsensus1.txt");
1306
1307 #[cfg(feature = "plain-consensus")]
1308 const PLAIN_CERTS: &str = include_str!("../../testdata2/cached-certs");
1309 #[cfg(feature = "plain-consensus")]
1310 const PLAIN_CONSENSUS: &str = include_str!("../../testdata2/cached-consensus");
1311
1312 fn read_bad(fname: &str) -> String {
1313 use std::fs;
1314 use std::path::PathBuf;
1315 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1316 path.push("testdata");
1317 path.push("bad-mdconsensus");
1318 path.push(fname);
1319
1320 fs::read_to_string(path).unwrap()
1321 }
1322
1323 #[test]
1324 fn parse_and_validate_md() -> Result<()> {
1325 use std::net::SocketAddr;
1326 use tor_checkable::{SelfSigned, Timebound};
1327 let mut certs = Vec::new();
1328 for cert in AuthCert::parse_multiple(CERTS)? {
1329 let cert = cert?.check_signature()?.dangerously_assume_timely();
1330 certs.push(cert);
1331 }
1332 let auth_ids: Vec<_> = certs.iter().map(|c| &c.key_ids().id_fingerprint).collect();
1333
1334 assert_eq!(certs.len(), 3);
1335
1336 let (_, _, consensus) = MdConsensus::parse(CONSENSUS)?;
1337 let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
1338
1339 assert!(consensus.authorities_are_correct(&auth_ids));
1341 assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
1343 {
1344 let bad_auth_id = (*b"xxxxxxxxxxxxxxxxxxxx").into();
1347 assert!(!consensus.authorities_are_correct(&[&bad_auth_id]));
1348 }
1349
1350 let missing = consensus.key_is_correct(&[]).err().unwrap();
1351 assert_eq!(3, missing.len());
1352 assert!(consensus.key_is_correct(&certs).is_ok());
1353 let missing = consensus.key_is_correct(&certs[0..1]).err().unwrap();
1354 assert_eq!(2, missing.len());
1355
1356 let same_three_times = vec![certs[0].clone(), certs[0].clone(), certs[0].clone()];
1358 let missing = consensus.key_is_correct(&same_three_times).err().unwrap();
1359
1360 assert_eq!(2, missing.len());
1361 assert!(consensus.is_well_signed(&same_three_times).is_err());
1362
1363 assert!(consensus.key_is_correct(&certs).is_ok());
1364 let consensus = consensus.check_signature(&certs)?;
1365
1366 assert_eq!(6, consensus.relays().len());
1367 let r0 = &consensus.relays()[0];
1368 assert_eq!(
1369 r0.md_digest(),
1370 &hex!("73dabe0a0468f4f7a67810a18d11e36731bb1d2ec3634db459100609f3b3f535")
1371 );
1372 assert_eq!(
1373 r0.rsa_identity().as_bytes(),
1374 &hex!("0a3057af2910415794d8ea430309d9ac5f5d524b")
1375 );
1376 assert!(!r0.weight().is_measured());
1377 assert!(!r0.weight().is_nonzero());
1378 let pv = &r0.protovers();
1379 assert!(pv.supports_subver("HSDir", 2));
1380 assert!(!pv.supports_subver("HSDir", 3));
1381 let ip4 = "127.0.0.1:5002".parse::<SocketAddr>().unwrap();
1382 let ip6 = "[::1]:5002".parse::<SocketAddr>().unwrap();
1383 assert!(r0.addrs().any(|a| a == ip4));
1384 assert!(r0.addrs().any(|a| a == ip6));
1385
1386 Ok(())
1387 }
1388
1389 #[test]
1390 #[cfg(feature = "plain-consensus")]
1391 fn parse_and_validate_ns() -> Result<()> {
1392 use tor_checkable::{SelfSigned, Timebound};
1393 let mut certs = Vec::new();
1394 for cert in AuthCert::parse_multiple(PLAIN_CERTS)? {
1395 let cert = cert?.check_signature()?.dangerously_assume_timely();
1396 certs.push(cert);
1397 }
1398 let auth_ids: Vec<_> = certs.iter().map(|c| &c.key_ids().id_fingerprint).collect();
1399 assert_eq!(certs.len(), 4);
1400
1401 let (_, _, consensus) = PlainConsensus::parse(PLAIN_CONSENSUS)?;
1402 let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
1403 assert!(consensus.authorities_are_correct(&auth_ids));
1405 assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
1407
1408 assert!(consensus.key_is_correct(&certs).is_ok());
1409
1410 let _consensus = consensus.check_signature(&certs)?;
1411
1412 Ok(())
1413 }
1414
1415 #[test]
1416 #[cfg(all(feature = "ns-vote", feature = "parse2"))]
1417 fn parse2_vote() -> anyhow::Result<()> {
1418 let file = "testdata2/v3-status-votes--1";
1419 let text = fs::read_to_string(file)?;
1420
1421 use crate::parse2::poc::netstatus::NetworkStatusSignedVote;
1423
1424 let input = ParseInput::new(&text, file);
1425 let doc: NetworkStatusSignedVote = parse_netdoc(&input)?;
1426
1427 println!("{doc:?}");
1428 println!("{:#?}", doc.inspect_unverified().0.r[0]);
1429
1430 Ok(())
1431 }
1432
1433 #[test]
1434 fn test_bad() {
1435 use crate::Pos;
1436 fn check(fname: &str, e: &Error) {
1437 let content = read_bad(fname);
1438 let res = MdConsensus::parse(&content);
1439 assert!(res.is_err());
1440 assert_eq!(&res.err().unwrap(), e);
1441 }
1442
1443 check(
1444 "bad-flags",
1445 &EK::BadArgument
1446 .at_pos(Pos::from_line(27, 1))
1447 .with_msg("Flags out of order"),
1448 );
1449 check(
1450 "bad-md-digest",
1451 &EK::BadArgument
1452 .at_pos(Pos::from_line(40, 3))
1453 .with_msg("Invalid base64"),
1454 );
1455 check(
1456 "bad-weight",
1457 &EK::BadArgument
1458 .at_pos(Pos::from_line(67, 141))
1459 .with_msg("invalid digit found in string"),
1460 );
1461 check(
1462 "bad-weights",
1463 &EK::BadArgument
1464 .at_pos(Pos::from_line(51, 13))
1465 .with_msg("invalid digit found in string"),
1466 );
1467 check(
1468 "wrong-order",
1469 &EK::WrongSortOrder.at_pos(Pos::from_line(52, 1)),
1470 );
1471 check(
1472 "wrong-start",
1473 &EK::UnexpectedToken
1474 .with_msg("vote-status")
1475 .at_pos(Pos::from_line(1, 1)),
1476 );
1477 check("wrong-version", &EK::BadDocumentVersion.with_msg("10"));
1478 }
1479
1480 fn gettok(s: &str) -> Result<Item<'_, NetstatusKwd>> {
1481 let mut reader = NetDocReader::new(s)?;
1482 let tok = reader.next().unwrap();
1483 assert!(reader.next().is_none());
1484 tok
1485 }
1486
1487 #[test]
1488 fn test_weight() {
1489 let w = gettok("w Unmeasured=1 Bandwidth=6\n").unwrap();
1490 let w = RelayWeight::from_item(&w).unwrap();
1491 assert!(!w.is_measured());
1492 assert!(w.is_nonzero());
1493
1494 let w = gettok("w Bandwidth=10\n").unwrap();
1495 let w = RelayWeight::from_item(&w).unwrap();
1496 assert!(w.is_measured());
1497 assert!(w.is_nonzero());
1498
1499 let w = RelayWeight::default();
1500 assert!(!w.is_measured());
1501 assert!(!w.is_nonzero());
1502
1503 let w = gettok("w Mustelid=66 Cheato=7 Unmeasured=1\n").unwrap();
1504 let w = RelayWeight::from_item(&w).unwrap();
1505 assert!(!w.is_measured());
1506 assert!(!w.is_nonzero());
1507
1508 let w = gettok("r foo\n").unwrap();
1509 let w = RelayWeight::from_item(&w);
1510 assert!(w.is_err());
1511
1512 let w = gettok("r Bandwidth=6 Unmeasured=Frog\n").unwrap();
1513 let w = RelayWeight::from_item(&w);
1514 assert!(w.is_err());
1515
1516 let w = gettok("r Bandwidth=6 Unmeasured=3\n").unwrap();
1517 let w = RelayWeight::from_item(&w);
1518 assert!(w.is_err());
1519 }
1520
1521 #[test]
1522 fn test_netparam() {
1523 let p = "Hello=600 Goodbye=5 Fred=7"
1524 .parse::<NetParams<u32>>()
1525 .unwrap();
1526 assert_eq!(p.get("Hello"), Some(&600_u32));
1527
1528 let p = "Hello=Goodbye=5 Fred=7".parse::<NetParams<u32>>();
1529 assert!(p.is_err());
1530
1531 let p = "Hello=Goodbye Fred=7".parse::<NetParams<u32>>();
1532 assert!(p.is_err());
1533 }
1534
1535 #[test]
1536 fn test_sharedrand() {
1537 let sr =
1538 gettok("shared-rand-previous-value 9 5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4\n")
1539 .unwrap();
1540 let sr = SharedRandStatus::from_item(&sr).unwrap();
1541
1542 assert_eq!(sr.n_reveals, 9);
1543 assert_eq!(
1544 sr.value.0,
1545 hex!("e4ba1d638c96c458532adc6957dc0080d03d37c7e5854087d0da90bf5ff4e72e")
1546 );
1547 assert!(sr.timestamp.is_none());
1548
1549 let sr2 = gettok(
1550 "shared-rand-current-value 9 \
1551 5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4 2022-01-20T12:34:56\n",
1552 )
1553 .unwrap();
1554 let sr2 = SharedRandStatus::from_item(&sr2).unwrap();
1555 assert_eq!(sr2.n_reveals, sr.n_reveals);
1556 assert_eq!(sr2.value.0, sr.value.0);
1557 assert_eq!(
1558 sr2.timestamp.unwrap().0,
1559 humantime::parse_rfc3339("2022-01-20T12:34:56Z").unwrap()
1560 );
1561
1562 let sr = gettok("foo bar\n").unwrap();
1563 let sr = SharedRandStatus::from_item(&sr);
1564 assert!(sr.is_err());
1565 }
1566
1567 #[test]
1568 fn test_protostatus() {
1569 let my_protocols: Protocols = "Link=7 Cons=1-5 Desc=3-10".parse().unwrap();
1570
1571 let outcome = ProtoStatus {
1572 recommended: "Link=7".parse().unwrap(),
1573 required: "Desc=5".parse().unwrap(),
1574 }
1575 .check_protocols(&my_protocols);
1576 assert!(outcome.is_ok());
1577
1578 let outcome = ProtoStatus {
1579 recommended: "Microdesc=4 Link=7".parse().unwrap(),
1580 required: "Desc=5".parse().unwrap(),
1581 }
1582 .check_protocols(&my_protocols);
1583 assert_eq!(
1584 outcome,
1585 Err(ProtocolSupportError::MissingRecommended(
1586 "Microdesc=4".parse().unwrap()
1587 ))
1588 );
1589
1590 let outcome = ProtoStatus {
1591 recommended: "Microdesc=4 Link=7".parse().unwrap(),
1592 required: "Desc=5 Cons=5-12 Wombat=15".parse().unwrap(),
1593 }
1594 .check_protocols(&my_protocols);
1595 assert_eq!(
1596 outcome,
1597 Err(ProtocolSupportError::MissingRequired(
1598 "Cons=6-12 Wombat=15".parse().unwrap()
1599 ))
1600 );
1601 }
1602
1603 #[test]
1604 fn serialize_protostatus() {
1605 let ps = ProtoStatuses {
1606 client: ProtoStatus {
1607 recommended: "Link=1-5 LinkAuth=2-5".parse().unwrap(),
1608 required: "Link=5 LinkAuth=3".parse().unwrap(),
1609 },
1610 relay: ProtoStatus {
1611 recommended: "Wombat=20-30 Knish=20-30".parse().unwrap(),
1612 required: "Wombat=20-22 Knish=25-27".parse().unwrap(),
1613 },
1614 };
1615 let json = serde_json::to_string(&ps).unwrap();
1616 let ps2 = serde_json::from_str(json.as_str()).unwrap();
1617 assert_eq!(ps, ps2);
1618
1619 let ps3: ProtoStatuses = serde_json::from_str(
1620 r#"{
1621 "client":{
1622 "required":"Link=5 LinkAuth=3",
1623 "recommended":"Link=1-5 LinkAuth=2-5"
1624 },
1625 "relay":{
1626 "required":"Wombat=20-22 Knish=25-27",
1627 "recommended":"Wombat=20-30 Knish=20-30"
1628 }
1629 }"#,
1630 )
1631 .unwrap();
1632 assert_eq!(ps, ps3);
1633 }
1634}