1mod dir_source;
52mod rs;
53
54pub mod md;
55pub mod plain;
56#[cfg(feature = "incomplete")]
57pub mod vote;
58
59#[cfg(feature = "build_docs")]
60mod build;
61
62pub use proto_statuses_parse2_encode::ProtoStatusesNetdocParseAccumulator;
63
64#[cfg(feature = "incomplete")]
65use crate::doc::authcert::EncodedAuthCert;
66
67use crate::doc::authcert::{AuthCert, AuthCertKeyIds};
68use crate::encode::{ItemValueEncodable, NetdocEncodable, NetdocEncoder};
69use crate::parse::keyword::Keyword;
70use crate::parse::parser::{Section, SectionRules, SectionRulesBuilder};
71use crate::parse::tokenize::{Item, ItemResult, NetDocReader};
72use crate::parse2::{
73 self, ArgumentStream, ErrorProblem, IsStructural, ItemStream, ItemValueParseable, KeywordRef,
74 NetdocParseable, StopAt,
75};
76use crate::types::misc::*;
77use crate::types::relay_flags::{self, DocRelayFlags};
78use crate::util::PeekableIterator;
79use crate::{Error, KeywordEncodable, NetdocErrorKind as EK, NormalItemArgument, Pos, Result};
80use std::collections::{BTreeSet, HashMap, HashSet};
81use std::fmt::{self, Display};
82use std::result::Result as StdResult;
83use std::str::FromStr;
84use std::sync::Arc;
85use std::{net, result, time};
86use tor_error::{Bug, HasKind, bad_api_usage, internal};
87use tor_protover::Protocols;
88use void::ResultVoidExt as _;
89
90use derive_deftly::{Deftly, define_derive_deftly};
91use digest::Digest;
92use std::sync::LazyLock;
93use tor_checkable::{ExternallySigned, timed::TimerangeBound};
94use tor_llcrypto as ll;
95use tor_llcrypto::pk::rsa::RsaIdentity;
96
97use serde::{Deserialize, Deserializer};
98
99#[cfg(feature = "build_docs")]
100pub use build::MdConsensusBuilder;
101#[cfg(feature = "build_docs")]
102pub use build::PlainConsensusBuilder;
103#[cfg(feature = "build_docs")]
104ns_export_each_flavor! {
105 ty: RouterStatusBuilder;
106}
107
108ns_export_each_variety! {
109 ty: RouterStatus, Preamble;
110}
111
112#[deprecated]
113pub use PlainConsensus as NsConsensus;
114#[deprecated]
115pub use PlainRouterStatus as NsRouterStatus;
116#[deprecated]
117pub use UncheckedPlainConsensus as UncheckedNsConsensus;
118#[deprecated]
119pub use UnvalidatedPlainConsensus as UnvalidatedNsConsensus;
120
121pub use rs::{RouterStatusMdDigestsVote, SoftwareVersion};
122
123pub use dir_source::{ConsensusAuthoritySection, DirSource, SupersededAuthorityKey};
124
125#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Default)]
139#[allow(clippy::exhaustive_structs)]
140pub struct IgnoredPublicationTimeSp;
141
142#[derive(Clone, Debug, Deftly)]
150#[derive_deftly(Constructor, NetdocEncodableFields, NetdocParseableFields)]
151#[derive_deftly(Lifetime)]
152#[allow(clippy::exhaustive_structs)]
153pub struct Lifetime {
154 #[deftly(constructor)]
161 #[deftly(netdoc(single_arg))]
162 pub valid_after: Iso8601TimeSp,
163 #[deftly(constructor)]
171 #[deftly(netdoc(single_arg))]
172 pub fresh_until: Iso8601TimeSp,
173 #[deftly(constructor)]
181 #[deftly(netdoc(single_arg))]
182 pub valid_until: Iso8601TimeSp,
183
184 #[doc(hidden)]
185 #[deftly(netdoc(skip))]
186 pub __non_exhaustive: (),
187}
188
189define_derive_deftly! {
190 Lifetime:
192
193 ${defcond FIELD not(approx_equal($fname, __non_exhaustive))}
194
195 impl Lifetime {
196 pub fn new(
198 $( ${when FIELD} $fname: time::SystemTime, )
199 ) -> Result<Self> {
200 let self_ = Lifetime {
204 $( ${when FIELD} $fname: $fname.into(), )
205 __non_exhaustive: (),
206 };
207 if self_.valid_after < self_.fresh_until && self_.fresh_until < self_.valid_until {
208 Ok(self_)
209 } else {
210 Err(EK::InvalidLifetime.err())
211 }
212 }
213 $(
214 ${when FIELD}
215
216 ${fattrs doc}
217 pub fn $fname(&self) -> time::SystemTime {
218 *self.$fname
219 }
220 )
221 pub fn valid_at(&self, when: time::SystemTime) -> bool {
223 *self.valid_after <= when && when <= *self.valid_until
224 }
225
226 pub fn voting_period(&self) -> time::Duration {
231 let valid_after = self.valid_after();
232 let fresh_until = self.fresh_until();
233 fresh_until
234 .duration_since(valid_after)
235 .expect("Mis-formed lifetime")
236 }
237 }
238}
239use derive_deftly_template_Lifetime;
240
241#[derive(Debug, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Copy)] #[derive(derive_more::From, derive_more::Into, derive_more::Display, derive_more::FromStr)]
253pub struct ConsensusMethod(u32);
254impl NormalItemArgument for ConsensusMethod {}
255
256#[derive(Debug, Clone, Default, Eq, PartialEq, Deftly)]
263#[derive_deftly(ItemValueEncodable, ItemValueParseable)]
264#[non_exhaustive]
265pub struct ConsensusMethods {
266 pub methods: BTreeSet<ConsensusMethod>,
268}
269
270pub mod consensus_methods_comma_separated {
275 use super::*;
276 use parse2::ArgumentError as AE;
277 use std::result::Result;
278
279 pub fn from_args<'s>(args: &mut ArgumentStream<'s>) -> Result<ConsensusMethods, AE> {
281 let mut methods = BTreeSet::new();
282 for ent in args.next().ok_or(AE::Missing)?.split(',') {
283 let ent = ent.parse().map_err(|_| AE::Invalid)?;
284 if !methods.insert(ent) {
285 return Err(AE::Invalid);
286 }
287 }
288 Ok(ConsensusMethods { methods })
289 }
290}
291
292#[derive(Debug, Clone, Default, Eq, PartialEq)]
318pub struct NetParams<T> {
319 params: HashMap<String, T>,
321}
322
323impl<T> NetParams<T> {
324 #[allow(unused)]
326 pub fn new() -> Self {
327 NetParams {
328 params: HashMap::new(),
329 }
330 }
331 pub fn get<A: AsRef<str>>(&self, v: A) -> Option<&T> {
333 self.params.get(v.as_ref())
334 }
335 pub fn iter(&self) -> impl Iterator<Item = (&String, &T)> {
337 self.params.iter()
338 }
339 pub fn set(&mut self, k: String, v: T) {
341 self.params.insert(k, v);
342 }
343}
344
345impl<K: Into<String>, T> FromIterator<(K, T)> for NetParams<T> {
346 fn from_iter<I: IntoIterator<Item = (K, T)>>(i: I) -> Self {
347 NetParams {
348 params: i.into_iter().map(|(k, v)| (k.into(), v)).collect(),
349 }
350 }
351}
352
353impl<T> std::iter::Extend<(String, T)> for NetParams<T> {
354 fn extend<I: IntoIterator<Item = (String, T)>>(&mut self, iter: I) {
355 self.params.extend(iter);
356 }
357}
358
359impl<'de, T> Deserialize<'de> for NetParams<T>
360where
361 T: Deserialize<'de>,
362{
363 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
364 where
365 D: Deserializer<'de>,
366 {
367 let params = HashMap::deserialize(deserializer)?;
368 Ok(NetParams { params })
369 }
370}
371
372#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
381pub struct ProtoStatus {
382 recommended: Protocols,
387 required: Protocols,
392}
393
394impl ProtoStatus {
395 pub fn check_protocols(
405 &self,
406 supported_protocols: &Protocols,
407 ) -> StdResult<(), ProtocolSupportError> {
408 let missing_required = self.required.difference(supported_protocols);
410 if !missing_required.is_empty() {
411 return Err(ProtocolSupportError::MissingRequired(missing_required));
412 }
413 let missing_recommended = self.recommended.difference(supported_protocols);
414 if !missing_recommended.is_empty() {
415 return Err(ProtocolSupportError::MissingRecommended(
416 missing_recommended,
417 ));
418 }
419
420 Ok(())
421 }
422}
423
424#[derive(Clone, Debug, thiserror::Error)]
426#[cfg_attr(test, derive(PartialEq))]
427#[non_exhaustive]
428pub enum ProtocolSupportError {
429 #[error("Required protocols are not implemented: {0}")]
431 MissingRequired(Protocols),
432
433 #[error("Recommended protocols are not implemented: {0}")]
437 MissingRecommended(Protocols),
438}
439
440impl ProtocolSupportError {
441 pub fn should_shutdown(&self) -> bool {
443 matches!(self, Self::MissingRequired(_))
444 }
445}
446
447impl HasKind for ProtocolSupportError {
448 fn kind(&self) -> tor_error::ErrorKind {
449 tor_error::ErrorKind::SoftwareDeprecated
450 }
451}
452
453#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
460pub struct ProtoStatuses {
461 client: ProtoStatus,
463 relay: ProtoStatus,
465}
466
467impl ProtoStatuses {
468 pub fn client(&self) -> &ProtoStatus {
470 &self.client
471 }
472
473 pub fn relay(&self) -> &ProtoStatus {
475 &self.relay
476 }
477}
478
479#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
487#[allow(clippy::exhaustive_enums)]
488pub enum ConsensusFlavor {
489 Microdesc,
492 Plain,
497}
498
499impl ConsensusFlavor {
500 pub fn name(&self) -> &'static str {
502 match self {
503 ConsensusFlavor::Plain => "ns", ConsensusFlavor::Microdesc => "microdesc",
505 }
506 }
507 pub fn from_opt_name(name: Option<&str>) -> Result<Self> {
512 match name {
513 Some("microdesc") => Ok(ConsensusFlavor::Microdesc),
514 Some("ns") | None => Ok(ConsensusFlavor::Plain),
515 Some(other) => {
516 Err(EK::BadDocumentType.with_msg(format!("unrecognized flavor {:?}", other)))
517 }
518 }
519 }
520}
521
522define_directory_signature_hash_algo! {
523 #[derive_deftly_adhoc] }
525
526#[derive(Debug, Clone)]
528#[non_exhaustive]
529pub struct Signature {
530 pub digest_algo: KeywordOrString<DirectorySignatureHashAlgo>,
535 pub key_ids: AuthCertKeyIds,
538 pub signature: Vec<u8>,
540}
541
542#[derive(Debug, Clone)]
544#[non_exhaustive]
545pub struct SignatureGroup {
546 pub sha256: Option<[u8; 32]>,
548 pub sha1: Option<[u8; 20]>,
550 pub signatures: Vec<Signature>,
552}
553
554#[derive(
556 Debug, Clone, Copy, Eq, PartialEq, derive_more::From, derive_more::Into, derive_more::AsRef,
557)]
558pub struct SharedRandVal([u8; 32]);
560
561#[derive(Debug, Clone, Deftly)]
564#[non_exhaustive]
565#[derive_deftly(ItemValueEncodable, ItemValueParseable)]
566pub struct SharedRandStatus {
567 pub n_reveals: u8,
569 pub value: SharedRandVal,
576
577 pub timestamp: Option<Iso8601TimeNoSp>,
581}
582
583#[non_exhaustive]
585#[derive(Debug, Clone, Copy)]
586pub enum RelayWeight {
587 Unmeasured(u32),
589 Measured(u32),
591}
592
593impl RelayWeight {
594 pub fn is_measured(&self) -> bool {
596 matches!(self, RelayWeight::Measured(_))
597 }
598 pub fn is_nonzero(&self) -> bool {
600 !matches!(self, RelayWeight::Unmeasured(0) | RelayWeight::Measured(0))
601 }
602}
603
604#[deprecated = "renamed to ConsensusAuthorityEntry"]
606pub type ConsensusVoterInfo = ConsensusAuthorityEntry;
607
608pub type PlainAuthorityEntry = ConsensusAuthorityEntry;
610pub type MdAuthorityEntry = ConsensusAuthorityEntry;
612
613#[derive(Debug, Clone, Deftly)]
623#[derive_deftly(Constructor, NetdocEncodable, NetdocParseable)]
624#[allow(clippy::exhaustive_structs)]
625pub struct ConsensusAuthorityEntry {
626 #[deftly(constructor)]
628 pub dir_source: DirSource,
629
630 #[deftly(constructor)]
636 pub contact: ContactInfo,
637
638 #[deftly(netdoc(single_arg))]
645 #[deftly(constructor)]
646 pub vote_digest: B16U,
647
648 #[doc(hidden)]
649 #[deftly(netdoc(skip))]
650 pub __non_exhaustive: (),
651}
652
653#[derive(Debug, Clone, Deftly)]
662#[derive_deftly(Constructor, NetdocEncodable, NetdocParseable)]
663#[allow(clippy::exhaustive_structs)]
664pub struct VoteAuthorityEntry {
665 #[deftly(constructor)]
667 pub dir_source: DirSource,
668
669 #[deftly(constructor)]
671 pub contact: ContactInfo,
672
673 #[doc(hidden)]
680 #[deftly(netdoc(skip))]
681 pub __non_exhaustive: (),
682}
683
684define_derive_deftly! {
687 VoteAuthoritySection:
699
700 ${defcond F_NORMAL not(fmeta(netdoc(skip)))}
701
702 #[cfg(feature = "incomplete")] impl NetdocParseable for VoteAuthoritySection {
704 fn doctype_for_error() -> &'static str {
705 "vote.authority.section"
706 }
707 fn is_intro_item_keyword(kw: KeywordRef<'_>) -> bool {
708 VoteAuthorityEntry::is_intro_item_keyword(kw)
709 }
710 fn is_structural_keyword(kw: KeywordRef<'_>) -> Option<IsStructural> {
711 $(
712 ${when F_NORMAL}
713 if let y @ Some(_) = $ftype::is_structural_keyword(kw) {
714 return y;
715 }
716 )
717 None
718 }
719 fn from_items<'s>(
720 input: &mut ItemStream<'s>,
721 stop_outer: stop_at!(),
722 ) -> StdResult<Self, ErrorProblem> {
723 let stop_inner = stop_outer
724 $(
725 ${when F_NORMAL}
726 | StopAt($ftype::is_intro_item_keyword)
727 )
728 ;
729 Ok(VoteAuthoritySection { $(
730 ${when F_NORMAL}
731 $fname: NetdocParseable::from_items(input, stop_inner)?,
732 )
733 __non_exhaustive: (),
734 })
735 }
736 }
737
738 #[cfg(feature = "incomplete")]
739 impl NetdocEncodable for VoteAuthoritySection {
740 fn encode_unsigned(&self, out: &mut NetdocEncoder) -> StdResult<(), Bug> {
741 $(
742 ${when F_NORMAL}
743 self.$fname.encode_unsigned(out)?;
744 )
745 Ok(())
746 }
747 }
748}
749
750#[derive(Deftly, Clone, Debug)]
757#[derive_deftly(VoteAuthoritySection, Constructor)]
758#[allow(clippy::exhaustive_structs)]
759#[cfg(feature = "incomplete")] pub struct VoteAuthoritySection {
761 #[deftly(constructor)]
763 pub authority: VoteAuthorityEntry,
764
765 #[deftly(constructor)]
767 pub cert: EncodedAuthCert,
768
769 #[doc(hidden)]
770 #[deftly(netdoc(skip))]
771 pub __non_exhaustive: (),
772}
773
774#[derive(Debug, Clone)]
776#[non_exhaustive]
777pub struct Footer {
778 pub weights: NetParams<i32>,
784}
785
786pub type MdConsensus = md::Consensus;
789
790pub type UnvalidatedMdConsensus = md::UnvalidatedConsensus;
793
794pub type UncheckedMdConsensus = md::UncheckedConsensus;
797
798pub type PlainConsensus = plain::Consensus;
801
802pub type UnvalidatedPlainConsensus = plain::UnvalidatedConsensus;
805
806pub type UncheckedPlainConsensus = plain::UncheckedConsensus;
809
810decl_keyword! {
811 #[non_exhaustive]
816 #[allow(missing_docs)]
817 pub NetstatusKwd {
818 "network-status-version" => NETWORK_STATUS_VERSION,
820 "vote-status" => VOTE_STATUS,
821 "consensus-methods" => CONSENSUS_METHODS,
822 "consensus-method" => CONSENSUS_METHOD,
823 "published" => PUBLISHED,
824 "valid-after" => VALID_AFTER,
825 "fresh-until" => FRESH_UNTIL,
826 "valid-until" => VALID_UNTIL,
827 "voting-delay" => VOTING_DELAY,
828 "client-versions" => CLIENT_VERSIONS,
829 "server-versions" => SERVER_VERSIONS,
830 "known-flags" => KNOWN_FLAGS,
831 "flag-thresholds" => FLAG_THRESHOLDS,
832 "recommended-client-protocols" => RECOMMENDED_CLIENT_PROTOCOLS,
833 "required-client-protocols" => REQUIRED_CLIENT_PROTOCOLS,
834 "recommended-relay-protocols" => RECOMMENDED_RELAY_PROTOCOLS,
835 "required-relay-protocols" => REQUIRED_RELAY_PROTOCOLS,
836 "params" => PARAMS,
837 "bandwidth-file-headers" => BANDWIDTH_FILE_HEADERS,
838 "bandwidth-file-digest" => BANDWIDTH_FILE_DIGEST,
839 "shared-rand-previous-value" => SHARED_RAND_PREVIOUS_VALUE,
843 "shared-rand-current-value" => SHARED_RAND_CURRENT_VALUE,
844
845 "dir-source" => DIR_SOURCE,
847 "contact" => CONTACT,
848
849 "legacy-dir-key" => LEGACY_DIR_KEY,
851 "shared-rand-participate" => SHARED_RAND_PARTICIPATE,
852 "shared-rand-commit" => SHARED_RAND_COMMIT,
853
854 "vote-digest" => VOTE_DIGEST,
856
857 "dir-key-certificate-version" => DIR_KEY_CERTIFICATE_VERSION,
859
860 "r" => RS_R,
862 "a" => RS_A,
863 "s" => RS_S,
864 "v" => RS_V,
865 "pr" => RS_PR,
866 "w" => RS_W,
867 "p" => RS_P,
868 "m" => RS_M,
869 "id" => RS_ID,
870
871 "directory-footer" => DIRECTORY_FOOTER,
873 "bandwidth-weights" => BANDWIDTH_WEIGHTS,
874 "directory-signature" => DIRECTORY_SIGNATURE,
875 }
876}
877
878static NS_HEADER_RULES_COMMON_: LazyLock<SectionRulesBuilder<NetstatusKwd>> = LazyLock::new(|| {
880 use NetstatusKwd::*;
881 let mut rules = SectionRules::builder();
882 rules.add(NETWORK_STATUS_VERSION.rule().required().args(1..=2));
883 rules.add(VOTE_STATUS.rule().required().args(1..));
884 rules.add(VALID_AFTER.rule().required());
885 rules.add(FRESH_UNTIL.rule().required());
886 rules.add(VALID_UNTIL.rule().required());
887 rules.add(VOTING_DELAY.rule().args(2..));
888 rules.add(CLIENT_VERSIONS.rule());
889 rules.add(SERVER_VERSIONS.rule());
890 rules.add(KNOWN_FLAGS.rule().required());
891 rules.add(RECOMMENDED_CLIENT_PROTOCOLS.rule().args(1..));
892 rules.add(RECOMMENDED_RELAY_PROTOCOLS.rule().args(1..));
893 rules.add(REQUIRED_CLIENT_PROTOCOLS.rule().args(1..));
894 rules.add(REQUIRED_RELAY_PROTOCOLS.rule().args(1..));
895 rules.add(PARAMS.rule());
896 rules
897});
898static NS_HEADER_RULES_CONSENSUS: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
900 use NetstatusKwd::*;
901 let mut rules = NS_HEADER_RULES_COMMON_.clone();
902 rules.add(CONSENSUS_METHOD.rule().args(1..=1));
903 rules.add(SHARED_RAND_PREVIOUS_VALUE.rule().args(2..));
904 rules.add(SHARED_RAND_CURRENT_VALUE.rule().args(2..));
905 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
906 rules.build()
907});
908static NS_VOTERINFO_RULES_CONSENSUS: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
939 use NetstatusKwd::*;
940 let mut rules = SectionRules::builder();
941 rules.add(DIR_SOURCE.rule().required().args(6..));
942 rules.add(CONTACT.rule().required());
943 rules.add(VOTE_DIGEST.rule().required());
944 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
945 rules.build()
946});
947static NS_ROUTERSTATUS_RULES_COMMON_: LazyLock<SectionRulesBuilder<NetstatusKwd>> =
949 LazyLock::new(|| {
950 use NetstatusKwd::*;
951 let mut rules = SectionRules::builder();
952 rules.add(RS_A.rule().may_repeat().args(1..));
953 rules.add(RS_S.rule().required());
954 rules.add(RS_V.rule());
955 rules.add(RS_PR.rule().required());
956 rules.add(RS_W.rule());
957 rules.add(RS_P.rule().args(2..));
958 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
959 rules
960 });
961
962static NS_ROUTERSTATUS_RULES_PLAIN: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
964 use NetstatusKwd::*;
965 let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
966 rules.add(RS_R.rule().required().args(8..));
967 rules.build()
968});
969
970static NS_ROUTERSTATUS_RULES_MDCON: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
983 use NetstatusKwd::*;
984 let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
985 rules.add(RS_R.rule().required().args(6..));
986 rules.add(RS_M.rule().required().args(1..));
987 rules.build()
988});
989static NS_FOOTER_RULES: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
991 use NetstatusKwd::*;
992 let mut rules = SectionRules::builder();
993 rules.add(DIRECTORY_FOOTER.rule().required().no_args());
994 rules.add(BANDWIDTH_WEIGHTS.rule());
996 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
997 rules.build()
998});
999
1000impl ProtoStatus {
1001 fn from_section(
1003 sec: &Section<'_, NetstatusKwd>,
1004 recommend_token: NetstatusKwd,
1005 required_token: NetstatusKwd,
1006 ) -> Result<ProtoStatus> {
1007 fn parse(t: Option<&Item<'_, NetstatusKwd>>) -> Result<Protocols> {
1009 if let Some(item) = t {
1010 item.args_as_str()
1011 .parse::<Protocols>()
1012 .map_err(|e| EK::BadArgument.at_pos(item.pos()).with_source(e))
1013 } else {
1014 Ok(Protocols::new())
1015 }
1016 }
1017
1018 let recommended = parse(sec.get(recommend_token))?;
1019 let required = parse(sec.get(required_token))?;
1020 Ok(ProtoStatus {
1021 recommended,
1022 required,
1023 })
1024 }
1025
1026 pub fn required_protocols(&self) -> &Protocols {
1033 &self.required
1034 }
1035
1036 pub fn recommended_protocols(&self) -> &Protocols {
1041 &self.recommended
1042 }
1043}
1044
1045impl<T> std::str::FromStr for NetParams<T>
1046where
1047 T: std::str::FromStr,
1048 T::Err: std::error::Error,
1049{
1050 type Err = Error;
1051 fn from_str(s: &str) -> Result<Self> {
1052 fn parse_pair<U>(p: &str) -> Result<(String, U)>
1054 where
1055 U: std::str::FromStr,
1056 U::Err: std::error::Error,
1057 {
1058 let parts: Vec<_> = p.splitn(2, '=').collect();
1059 if parts.len() != 2 {
1060 return Err(EK::BadArgument
1061 .at_pos(Pos::at(p))
1062 .with_msg("Missing = in key=value list"));
1063 }
1064 let num = parts[1].parse::<U>().map_err(|e| {
1065 EK::BadArgument
1066 .at_pos(Pos::at(parts[1]))
1067 .with_msg(e.to_string())
1068 })?;
1069 Ok((parts[0].to_string(), num))
1070 }
1071
1072 let params = s
1073 .split(' ')
1074 .filter(|p| !p.is_empty())
1075 .map(parse_pair)
1076 .collect::<Result<HashMap<_, _>>>()?;
1077 Ok(NetParams { params })
1078 }
1079}
1080
1081impl FromStr for SharedRandVal {
1082 type Err = Error;
1083 fn from_str(s: &str) -> Result<Self> {
1084 let val: B64 = s.parse()?;
1085 let val = SharedRandVal(val.into_array()?);
1086 Ok(val)
1087 }
1088}
1089impl Display for SharedRandVal {
1090 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1091 Display::fmt(&B64::from(Vec::from(self.0)), f)
1092 }
1093}
1094impl NormalItemArgument for SharedRandVal {}
1095
1096impl SharedRandStatus {
1097 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
1100 match item.kwd() {
1101 NetstatusKwd::SHARED_RAND_PREVIOUS_VALUE | NetstatusKwd::SHARED_RAND_CURRENT_VALUE => {}
1102 _ => {
1103 return Err(Error::from(internal!(
1104 "wrong keyword {:?} on shared-random value",
1105 item.kwd()
1106 ))
1107 .at_pos(item.pos()));
1108 }
1109 }
1110 let n_reveals: u8 = item.parse_arg(0)?;
1111 let value: SharedRandVal = item.parse_arg(1)?;
1112 let timestamp = item.parse_optional_arg::<Iso8601TimeNoSp>(2)?;
1114 Ok(SharedRandStatus {
1115 n_reveals,
1116 value,
1117 timestamp,
1118 })
1119 }
1120
1121 pub fn value(&self) -> &SharedRandVal {
1123 &self.value
1124 }
1125
1126 pub fn timestamp(&self) -> Option<std::time::SystemTime> {
1128 self.timestamp.map(|t| t.0)
1129 }
1130}
1131
1132impl DirSource {
1133 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
1135 if item.kwd() != NetstatusKwd::DIR_SOURCE {
1136 return Err(
1137 Error::from(internal!("Bad keyword {:?} on dir-source", item.kwd()))
1138 .at_pos(item.pos()),
1139 );
1140 }
1141 let nickname = item
1142 .required_arg(0)?
1143 .parse()
1144 .map_err(|e: InvalidNickname| {
1145 EK::BadArgument.at_pos(item.pos()).with_msg(e.to_string())
1146 })?;
1147 let identity = item.parse_arg(1)?;
1148 let hostname = item
1149 .required_arg(2)?
1150 .parse()
1151 .map_err(|e: InvalidInternetHost| {
1152 EK::BadArgument.at_pos(item.pos()).with_msg(e.to_string())
1153 })?;
1154 let ip = item.parse_arg(3)?;
1155 let dir_port = item.parse_arg(4)?;
1156 let or_port = item.parse_arg(5)?;
1157
1158 Ok(DirSource {
1159 nickname,
1160 identity,
1161 hostname,
1162 ip,
1163 dir_port,
1164 or_port,
1165 __non_exhaustive: (),
1166 })
1167 }
1168}
1169
1170impl ConsensusAuthorityEntry {
1171 fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<ConsensusAuthorityEntry> {
1173 use NetstatusKwd::*;
1174 #[allow(clippy::unwrap_used)]
1177 let first = sec.first_item().unwrap();
1178 if first.kwd() != DIR_SOURCE {
1179 return Err(Error::from(internal!(
1180 "Wrong keyword {:?} at start of voter info",
1181 first.kwd()
1182 ))
1183 .at_pos(first.pos()));
1184 }
1185 let dir_source = DirSource::from_item(sec.required(DIR_SOURCE)?)?;
1186
1187 let contact = sec.required(CONTACT)?;
1188 let contact = contact
1194 .args_as_str()
1195 .parse()
1196 .map_err(|err: InvalidContactInfo| {
1197 EK::BadArgument
1198 .with_msg(err.to_string())
1199 .at_pos(contact.pos())
1200 })?;
1201
1202 let vote_digest = sec.required(VOTE_DIGEST)?.parse_arg::<B16U>(0)?;
1203
1204 Ok(ConsensusAuthorityEntry {
1205 dir_source,
1206 contact,
1207 vote_digest,
1208 __non_exhaustive: (),
1209 })
1210 }
1211}
1212
1213impl Default for RelayWeight {
1214 fn default() -> RelayWeight {
1215 RelayWeight::Unmeasured(0)
1216 }
1217}
1218
1219impl RelayWeight {
1220 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<RelayWeight> {
1222 if item.kwd() != NetstatusKwd::RS_W {
1223 return Err(
1224 Error::from(internal!("Wrong keyword {:?} on W line", item.kwd()))
1225 .at_pos(item.pos()),
1226 );
1227 }
1228
1229 let params = item.args_as_str().parse()?;
1230
1231 Self::from_net_params(¶ms).map_err(|e| e.at_pos(item.pos()))
1232 }
1233
1234 fn from_net_params(params: &NetParams<u32>) -> Result<RelayWeight> {
1238 let bw = params.params.get("Bandwidth");
1239 let unmeas = params.params.get("Unmeasured");
1240
1241 let bw = match bw {
1242 None => return Ok(RelayWeight::Unmeasured(0)),
1243 Some(b) => *b,
1244 };
1245
1246 match unmeas {
1247 None | Some(0) => Ok(RelayWeight::Measured(bw)),
1248 Some(1) => Ok(RelayWeight::Unmeasured(bw)),
1249 _ => Err(EK::BadArgument.with_msg("unmeasured value")),
1250 }
1251 }
1252}
1253
1254mod parse2_impls {
1258 use super::*;
1259 pub(super) use parse2::{
1260 ArgumentError as AE, ArgumentStream, ErrorProblem as EP, ItemArgumentParseable,
1261 ItemValueParseable, NetdocParseableFields, UnparsedItem,
1262 };
1263 use std::result::Result;
1264
1265 impl ItemValueParseable for NetParams<i32> {
1266 fn from_unparsed(item: parse2::UnparsedItem<'_>) -> Result<Self, EP> {
1267 item.check_no_object()?;
1268 item.args_copy()
1269 .into_remaining()
1270 .parse()
1271 .map_err(item.invalid_argument_handler("parameters"))
1272 }
1273 }
1274
1275 impl ItemValueParseable for RelayWeight {
1276 fn from_unparsed(item: parse2::UnparsedItem<'_>) -> Result<Self, EP> {
1277 item.check_no_object()?;
1278 (|| {
1279 let params = item.args_copy().into_remaining().parse()?;
1280 Self::from_net_params(¶ms)
1281 })()
1282 .map_err(item.invalid_argument_handler("weights"))
1283 }
1284 }
1285
1286 impl ItemValueParseable for rs::SoftwareVersion {
1287 fn from_unparsed(mut item: parse2::UnparsedItem<'_>) -> Result<Self, EP> {
1288 item.check_no_object()?;
1289 item.args_mut()
1290 .into_remaining()
1291 .parse()
1292 .map_err(item.invalid_argument_handler("version"))
1293 }
1294 }
1295
1296 impl ItemArgumentParseable for IgnoredPublicationTimeSp {
1297 fn from_args(a: &mut ArgumentStream) -> Result<IgnoredPublicationTimeSp, AE> {
1298 let mut next_arg = || a.next().ok_or(AE::Missing);
1299 let _: &str = next_arg()?;
1300 let _: &str = next_arg()?;
1301 Ok(IgnoredPublicationTimeSp)
1302 }
1303 }
1304}
1305
1306mod encode_impls {
1310 use super::*;
1311 use std::result::Result;
1312 pub(crate) use {
1313 crate::encode::{ItemEncoder, ItemValueEncodable, NetdocEncodableFields},
1314 tor_error::Bug,
1315 };
1316
1317 impl ItemValueEncodable for NetParams<i32> {
1318 fn write_item_value_onto(&self, mut out: ItemEncoder) -> Result<(), Bug> {
1319 for (k, v) in self.iter().collect::<BTreeSet<_>>() {
1320 if k.is_empty()
1321 || k.chars()
1322 .any(|c| c.is_whitespace() || c.is_control() || c == '=')
1323 {
1324 return Err(bad_api_usage!(
1326 "tried to encode NetParms with unreasonable keyword {k:?}"
1327 ));
1328 }
1329 out.args_raw_string(&format_args!("{k}={v}"));
1330 }
1331 Ok(())
1332 }
1333 }
1334}
1335
1336impl Footer {
1337 fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<Footer> {
1339 use NetstatusKwd::*;
1340 sec.required(DIRECTORY_FOOTER)?;
1341
1342 let weights = sec
1343 .maybe(BANDWIDTH_WEIGHTS)
1344 .args_as_str()
1345 .unwrap_or("")
1346 .parse()?;
1347
1348 Ok(Footer { weights })
1349 }
1350}
1351
1352mod proto_statuses_parse2_encode {
1356 use super::encode_impls::*;
1357 use super::parse2_impls::*;
1358 use super::*;
1359 use paste::paste;
1360 use std::result::Result;
1361
1362 macro_rules! impl_proto_statuses { { $( $rr:ident $cr:ident; )* } => { paste! {
1376 #[derive(Deftly)]
1377 #[derive_deftly(NetdocParseableFields)]
1378 #[allow(unreachable_pub)]
1380 pub struct ProtoStatusesParseHelper {
1381 $(
1382 #[deftly(netdoc(default))]
1383 [<$rr _ $cr _protocols>]: Protocols,
1384 )*
1385 }
1386
1387 pub use ProtoStatusesParseHelperNetdocParseAccumulator
1389 as ProtoStatusesNetdocParseAccumulator;
1390
1391 impl NetdocParseableFields for ProtoStatuses {
1392 type Accumulator = ProtoStatusesNetdocParseAccumulator;
1393 fn is_item_keyword(kw: KeywordRef<'_>) -> bool {
1394 ProtoStatusesParseHelper::is_item_keyword(kw)
1395 }
1396 fn accumulate_item(
1397 acc: &mut Self::Accumulator,
1398 item: UnparsedItem<'_>,
1399 ) -> Result<(), EP> {
1400 ProtoStatusesParseHelper::accumulate_item(acc, item)
1401 }
1402 fn finish(acc: Self::Accumulator) -> Result<Self, EP> {
1403 let parse = ProtoStatusesParseHelper::finish(acc)?;
1404 let mut out = ProtoStatuses::default();
1405 $(
1406 out.$cr.$rr = parse.[< $rr _ $cr _protocols >];
1407 )*
1408 Ok(out)
1409 }
1410 }
1411
1412 impl NetdocEncodableFields for ProtoStatuses {
1413 fn encode_fields(&self, out: &mut NetdocEncoder) -> Result<(), Bug> {
1414 $(
1415 self.$cr.$rr.write_item_value_onto(
1416 out.item(stringify!([<$rr _ $cr _protocols>]))
1417 )?;
1418 )*
1419 Ok(())
1420 }
1421 }
1422 } } }
1423
1424 impl_proto_statuses! {
1425 required client;
1426 required relay;
1427 recommended client;
1428 recommended relay;
1429 }
1430}
1431
1432enum SigCheckResult {
1434 Valid,
1436 Invalid,
1439 MissingCert,
1442}
1443
1444impl Signature {
1445 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Signature> {
1447 if item.kwd() != NetstatusKwd::DIRECTORY_SIGNATURE {
1448 return Err(Error::from(internal!(
1449 "Wrong keyword {:?} for directory signature",
1450 item.kwd()
1451 ))
1452 .at_pos(item.pos()));
1453 }
1454
1455 let (digest_algo, id_fp, sk_fp) = if item.n_args() > 2 {
1456 (
1457 item.required_arg(0)?,
1458 item.required_arg(1)?,
1459 item.required_arg(2)?,
1460 )
1461 } else {
1462 ("sha1", item.required_arg(0)?, item.required_arg(1)?)
1463 };
1464
1465 let digest_algo = digest_algo.to_string().parse().void_unwrap();
1466 let id_fingerprint = id_fp.parse::<Fingerprint>()?.into();
1467 let sk_fingerprint = sk_fp.parse::<Fingerprint>()?.into();
1468 let key_ids = AuthCertKeyIds {
1469 id_fingerprint,
1470 sk_fingerprint,
1471 };
1472 let signature = item.obj("SIGNATURE")?;
1473
1474 Ok(Signature {
1475 digest_algo,
1476 key_ids,
1477 signature,
1478 })
1479 }
1480
1481 fn matches_cert(&self, cert: &AuthCert) -> bool {
1484 cert.key_ids() == self.key_ids
1485 }
1486
1487 fn find_cert<'a>(&self, certs: &'a [AuthCert]) -> Option<&'a AuthCert> {
1490 certs.iter().find(|&c| self.matches_cert(c))
1491 }
1492
1493 fn check_signature(&self, signed_digest: &[u8], certs: &[AuthCert]) -> SigCheckResult {
1497 match self.find_cert(certs) {
1498 None => SigCheckResult::MissingCert,
1499 Some(cert) => {
1500 let key = cert.signing_key();
1501 match key.verify(signed_digest, &self.signature[..]) {
1502 Ok(()) => SigCheckResult::Valid,
1503 Err(_) => SigCheckResult::Invalid,
1504 }
1505 }
1506 }
1507 }
1508}
1509
1510impl SignatureGroup {
1511 fn list_missing(&self, certs: &[AuthCert]) -> (usize, Vec<&Signature>) {
1518 let mut ok: HashSet<RsaIdentity> = HashSet::new();
1519 let mut missing = Vec::new();
1520 for sig in &self.signatures {
1521 let id_fingerprint = &sig.key_ids.id_fingerprint;
1522 if ok.contains(id_fingerprint) {
1523 continue;
1524 }
1525 if sig.find_cert(certs).is_some() {
1526 ok.insert(*id_fingerprint);
1527 continue;
1528 }
1529
1530 missing.push(sig);
1531 }
1532 (ok.len(), missing)
1533 }
1534
1535 fn could_validate(&self, authorities: &[&RsaIdentity]) -> bool {
1539 let mut signed_by: HashSet<RsaIdentity> = HashSet::new();
1540 for sig in &self.signatures {
1541 let id_fp = &sig.key_ids.id_fingerprint;
1542 if signed_by.contains(id_fp) {
1543 continue;
1545 }
1546 if authorities.contains(&id_fp) {
1547 signed_by.insert(*id_fp);
1548 }
1549 }
1550
1551 signed_by.len() > (authorities.len() / 2)
1552 }
1553
1554 fn validate(&self, n_authorities: usize, certs: &[AuthCert]) -> bool {
1561 let mut ok: HashSet<RsaIdentity> = HashSet::new();
1565
1566 for sig in &self.signatures {
1567 let id_fingerprint = &sig.key_ids.id_fingerprint;
1568 if ok.contains(id_fingerprint) {
1569 continue;
1572 }
1573
1574 use DirectorySignatureHashAlgo as DSHA;
1575 use KeywordOrString as KOS;
1576
1577 let d: Option<&[u8]> = match sig.digest_algo {
1578 KOS::Known(DSHA::Sha256) => self.sha256.as_ref().map(|a| &a[..]),
1579 KOS::Known(DSHA::Sha1) => self.sha1.as_ref().map(|a| &a[..]),
1580 _ => None, };
1582 if d.is_none() {
1583 continue;
1586 }
1587
1588 #[allow(clippy::unwrap_used)]
1590 match sig.check_signature(d.as_ref().unwrap(), certs) {
1591 SigCheckResult::Valid => {
1592 ok.insert(*id_fingerprint);
1593 }
1594 _ => continue,
1595 }
1596 }
1597
1598 ok.len() > (n_authorities / 2)
1599 }
1600}
1601
1602#[cfg(test)]
1603mod test {
1604 #![allow(clippy::bool_assert_comparison)]
1606 #![allow(clippy::clone_on_copy)]
1607 #![allow(clippy::dbg_macro)]
1608 #![allow(clippy::mixed_attributes_style)]
1609 #![allow(clippy::print_stderr)]
1610 #![allow(clippy::print_stdout)]
1611 #![allow(clippy::single_char_pattern)]
1612 #![allow(clippy::unwrap_used)]
1613 #![allow(clippy::unchecked_time_subtraction)]
1614 #![allow(clippy::useless_vec)]
1615 #![allow(clippy::needless_pass_by_value)]
1616 use super::*;
1618 use hex_literal::hex;
1619 #[cfg(feature = "incomplete")]
1620 use {
1621 crate::parse2::{NetdocUnverified as _, ParseInput, parse_netdoc},
1622 std::fs,
1623 };
1624
1625 const CERTS: &str = include_str!("../../testdata/authcerts2.txt");
1626 const CONSENSUS: &str = include_str!("../../testdata/mdconsensus1.txt");
1627
1628 const PLAIN_CERTS: &str = include_str!("../../testdata2/cached-certs");
1629 const PLAIN_CONSENSUS: &str = include_str!("../../testdata2/cached-consensus");
1630
1631 fn read_bad(fname: &str) -> String {
1632 use std::fs;
1633 use std::path::PathBuf;
1634 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1635 path.push("testdata");
1636 path.push("bad-mdconsensus");
1637 path.push(fname);
1638
1639 fs::read_to_string(path).unwrap()
1640 }
1641
1642 #[test]
1643 fn parse_and_validate_md() -> Result<()> {
1644 use std::net::SocketAddr;
1645 use tor_checkable::{SelfSigned, Timebound};
1646 let mut certs = Vec::new();
1647 for cert in AuthCert::parse_multiple(CERTS)? {
1648 let cert = cert?.check_signature()?.dangerously_assume_timely();
1649 certs.push(cert);
1650 }
1651 let auth_ids: Vec<_> = certs.iter().map(|c| c.id_fingerprint()).collect();
1652
1653 assert_eq!(certs.len(), 3);
1654
1655 let (_, _, consensus) = MdConsensus::parse(CONSENSUS)?;
1656 let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
1657
1658 assert!(consensus.authorities_are_correct(&auth_ids));
1660 assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
1662 {
1663 let bad_auth_id = (*b"xxxxxxxxxxxxxxxxxxxx").into();
1666 assert!(!consensus.authorities_are_correct(&[&bad_auth_id]));
1667 }
1668
1669 let missing = consensus.key_is_correct(&[]).err().unwrap();
1670 assert_eq!(3, missing.len());
1671 assert!(consensus.key_is_correct(&certs).is_ok());
1672 let missing = consensus.key_is_correct(&certs[0..1]).err().unwrap();
1673 assert_eq!(2, missing.len());
1674
1675 let same_three_times = vec![certs[0].clone(), certs[0].clone(), certs[0].clone()];
1677 let missing = consensus.key_is_correct(&same_three_times).err().unwrap();
1678
1679 assert_eq!(2, missing.len());
1680 assert!(consensus.is_well_signed(&same_three_times).is_err());
1681
1682 assert!(consensus.key_is_correct(&certs).is_ok());
1683 let consensus = consensus.check_signature(&certs)?;
1684
1685 assert_eq!(6, consensus.relays().len());
1686 let r0 = &consensus.relays()[0];
1687 assert_eq!(
1688 r0.md_digest(),
1689 &hex!("73dabe0a0468f4f7a67810a18d11e36731bb1d2ec3634db459100609f3b3f535")
1690 );
1691 assert_eq!(
1692 r0.rsa_identity().as_bytes(),
1693 &hex!("0a3057af2910415794d8ea430309d9ac5f5d524b")
1694 );
1695 assert!(!r0.weight().is_measured());
1696 assert!(!r0.weight().is_nonzero());
1697 let pv = &r0.protovers();
1698 assert!(pv.supports_subver("HSDir", 2));
1699 assert!(!pv.supports_subver("HSDir", 3));
1700 let ip4 = "127.0.0.1:5002".parse::<SocketAddr>().unwrap();
1701 let ip6 = "[::1]:5002".parse::<SocketAddr>().unwrap();
1702 assert!(r0.addrs().any(|a| a == ip4));
1703 assert!(r0.addrs().any(|a| a == ip6));
1704
1705 Ok(())
1706 }
1707
1708 #[test]
1709 fn parse_and_validate_ns() -> Result<()> {
1710 use tor_checkable::{SelfSigned, Timebound};
1711 let mut certs = Vec::new();
1712 for cert in AuthCert::parse_multiple(PLAIN_CERTS)? {
1713 let cert = cert?.check_signature()?.dangerously_assume_timely();
1714 certs.push(cert);
1715 }
1716 let auth_ids: Vec<_> = certs.iter().map(|c| c.id_fingerprint()).collect();
1717 assert_eq!(certs.len(), 4);
1718
1719 let (_, _, consensus) = PlainConsensus::parse(PLAIN_CONSENSUS)?;
1720 let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
1721 assert!(consensus.authorities_are_correct(&auth_ids));
1723 assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
1725
1726 assert!(consensus.key_is_correct(&certs).is_ok());
1727
1728 let _consensus = consensus.check_signature(&certs)?;
1729
1730 Ok(())
1731 }
1732
1733 #[test]
1734 #[cfg(feature = "incomplete")]
1735 fn parse2_vote() -> anyhow::Result<()> {
1736 let file = "testdata2/v3-status-votes--1";
1737 let text = fs::read_to_string(file)?;
1738
1739 use crate::parse2::poc::netstatus::NetworkStatusUnverifiedVote;
1741
1742 let input = ParseInput::new(&text, file);
1743 let doc: NetworkStatusUnverifiedVote = parse_netdoc(&input)?;
1744
1745 println!("{doc:?}");
1746 println!("{:#?}", doc.inspect_unverified().0.r[0]);
1747
1748 Ok(())
1749 }
1750
1751 #[test]
1752 fn test_bad() {
1753 use crate::Pos;
1754 fn check(fname: &str, e: &Error) {
1755 let content = read_bad(fname);
1756 let res = MdConsensus::parse(&content);
1757 assert!(res.is_err());
1758 assert_eq!(&res.err().unwrap(), e);
1759 }
1760
1761 check(
1762 "bad-flags",
1763 &EK::BadArgument
1764 .at_pos(Pos::from_line(27, 1))
1765 .with_msg("Flags out of order"),
1766 );
1767 check(
1768 "bad-md-digest",
1769 &EK::BadArgument
1770 .at_pos(Pos::from_line(40, 3))
1771 .with_msg("Invalid base64"),
1772 );
1773 check(
1774 "bad-weight",
1775 &EK::BadArgument
1776 .at_pos(Pos::from_line(67, 141))
1777 .with_msg("invalid digit found in string"),
1778 );
1779 check(
1780 "bad-weights",
1781 &EK::BadArgument
1782 .at_pos(Pos::from_line(51, 13))
1783 .with_msg("invalid digit found in string"),
1784 );
1785 check(
1786 "wrong-order",
1787 &EK::WrongSortOrder.at_pos(Pos::from_line(52, 1)),
1788 );
1789 check(
1790 "wrong-start",
1791 &EK::UnexpectedToken
1792 .with_msg("vote-status")
1793 .at_pos(Pos::from_line(1, 1)),
1794 );
1795 check("wrong-version", &EK::BadDocumentVersion.with_msg("10"));
1796 }
1797
1798 fn gettok(s: &str) -> Result<Item<'_, NetstatusKwd>> {
1799 let mut reader = NetDocReader::new(s)?;
1800 let tok = reader.next().unwrap();
1801 assert!(reader.next().is_none());
1802 tok
1803 }
1804
1805 #[test]
1806 fn test_weight() {
1807 let w = gettok("w Unmeasured=1 Bandwidth=6\n").unwrap();
1808 let w = RelayWeight::from_item(&w).unwrap();
1809 assert!(!w.is_measured());
1810 assert!(w.is_nonzero());
1811
1812 let w = gettok("w Bandwidth=10\n").unwrap();
1813 let w = RelayWeight::from_item(&w).unwrap();
1814 assert!(w.is_measured());
1815 assert!(w.is_nonzero());
1816
1817 let w = RelayWeight::default();
1818 assert!(!w.is_measured());
1819 assert!(!w.is_nonzero());
1820
1821 let w = gettok("w Mustelid=66 Cheato=7 Unmeasured=1\n").unwrap();
1822 let w = RelayWeight::from_item(&w).unwrap();
1823 assert!(!w.is_measured());
1824 assert!(!w.is_nonzero());
1825
1826 let w = gettok("r foo\n").unwrap();
1827 let w = RelayWeight::from_item(&w);
1828 assert!(w.is_err());
1829
1830 let w = gettok("r Bandwidth=6 Unmeasured=Frog\n").unwrap();
1831 let w = RelayWeight::from_item(&w);
1832 assert!(w.is_err());
1833
1834 let w = gettok("r Bandwidth=6 Unmeasured=3\n").unwrap();
1835 let w = RelayWeight::from_item(&w);
1836 assert!(w.is_err());
1837 }
1838
1839 #[test]
1840 fn test_netparam() {
1841 let p = "Hello=600 Goodbye=5 Fred=7"
1842 .parse::<NetParams<u32>>()
1843 .unwrap();
1844 assert_eq!(p.get("Hello"), Some(&600_u32));
1845
1846 let p = "Hello=Goodbye=5 Fred=7".parse::<NetParams<u32>>();
1847 assert!(p.is_err());
1848
1849 let p = "Hello=Goodbye Fred=7".parse::<NetParams<u32>>();
1850 assert!(p.is_err());
1851
1852 for bad_kw in ["What=The", "", "\n", "\0"] {
1853 let p = [(bad_kw, 42)].into_iter().collect::<NetParams<i32>>();
1854 let mut d = NetdocEncoder::new();
1855 let d = (|| {
1856 let i = d.item("bad-psrams");
1857 p.write_item_value_onto(i)?;
1858 d.finish()
1859 })();
1860 let _: tor_error::Bug = d.expect_err(bad_kw);
1861 }
1862 }
1863
1864 #[test]
1865 fn test_sharedrand() {
1866 let sr =
1867 gettok("shared-rand-previous-value 9 5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4\n")
1868 .unwrap();
1869 let sr = SharedRandStatus::from_item(&sr).unwrap();
1870
1871 assert_eq!(sr.n_reveals, 9);
1872 assert_eq!(
1873 sr.value.0,
1874 hex!("e4ba1d638c96c458532adc6957dc0080d03d37c7e5854087d0da90bf5ff4e72e")
1875 );
1876 assert!(sr.timestamp.is_none());
1877
1878 let sr2 = gettok(
1879 "shared-rand-current-value 9 \
1880 5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4 2022-01-20T12:34:56\n",
1881 )
1882 .unwrap();
1883 let sr2 = SharedRandStatus::from_item(&sr2).unwrap();
1884 assert_eq!(sr2.n_reveals, sr.n_reveals);
1885 assert_eq!(sr2.value.0, sr.value.0);
1886 assert_eq!(
1887 sr2.timestamp.unwrap().0,
1888 humantime::parse_rfc3339("2022-01-20T12:34:56Z").unwrap()
1889 );
1890
1891 let sr = gettok("foo bar\n").unwrap();
1892 let sr = SharedRandStatus::from_item(&sr);
1893 assert!(sr.is_err());
1894 }
1895
1896 #[test]
1897 fn test_protostatus() {
1898 let my_protocols: Protocols = "Link=7 Cons=1-5 Desc=3-10".parse().unwrap();
1899
1900 let outcome = ProtoStatus {
1901 recommended: "Link=7".parse().unwrap(),
1902 required: "Desc=5".parse().unwrap(),
1903 }
1904 .check_protocols(&my_protocols);
1905 assert!(outcome.is_ok());
1906
1907 let outcome = ProtoStatus {
1908 recommended: "Microdesc=4 Link=7".parse().unwrap(),
1909 required: "Desc=5".parse().unwrap(),
1910 }
1911 .check_protocols(&my_protocols);
1912 assert_eq!(
1913 outcome,
1914 Err(ProtocolSupportError::MissingRecommended(
1915 "Microdesc=4".parse().unwrap()
1916 ))
1917 );
1918
1919 let outcome = ProtoStatus {
1920 recommended: "Microdesc=4 Link=7".parse().unwrap(),
1921 required: "Desc=5 Cons=5-12 Wombat=15".parse().unwrap(),
1922 }
1923 .check_protocols(&my_protocols);
1924 assert_eq!(
1925 outcome,
1926 Err(ProtocolSupportError::MissingRequired(
1927 "Cons=6-12 Wombat=15".parse().unwrap()
1928 ))
1929 );
1930 }
1931
1932 #[test]
1933 fn serialize_protostatus() {
1934 let ps = ProtoStatuses {
1935 client: ProtoStatus {
1936 recommended: "Link=1-5 LinkAuth=2-5".parse().unwrap(),
1937 required: "Link=5 LinkAuth=3".parse().unwrap(),
1938 },
1939 relay: ProtoStatus {
1940 recommended: "Wombat=20-30 Knish=20-30".parse().unwrap(),
1941 required: "Wombat=20-22 Knish=25-27".parse().unwrap(),
1942 },
1943 };
1944 let json = serde_json::to_string(&ps).unwrap();
1945 let ps2 = serde_json::from_str(json.as_str()).unwrap();
1946 assert_eq!(ps, ps2);
1947
1948 let ps3: ProtoStatuses = serde_json::from_str(
1949 r#"{
1950 "client":{
1951 "required":"Link=5 LinkAuth=3",
1952 "recommended":"Link=1-5 LinkAuth=2-5"
1953 },
1954 "relay":{
1955 "required":"Wombat=20-22 Knish=25-27",
1956 "recommended":"Wombat=20-30 Knish=20-30"
1957 }
1958 }"#,
1959 )
1960 .unwrap();
1961 assert_eq!(ps, ps3);
1962 }
1963}