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::{self, AuthCert, AuthCertKeyIds, AuthCertUnverified};
68use crate::encode::{
69 EncodeOrd, ItemArgument, ItemEncoder, ItemValueEncodable, NetdocEncodable, NetdocEncoder,
70};
71use crate::parse::keyword::Keyword;
72use crate::parse::parser::{Section, SectionRules, SectionRulesBuilder};
73use crate::parse::tokenize::{Item, ItemResult, NetDocReader};
74use crate::parse2::{
75 self, ArgumentError, ArgumentStream, ErrorProblem, IsStructural, ItemArgumentParseable,
76 ItemStream, ItemValueParseable, KeywordRef, NetdocParseable, NetdocParseableUnverified,
77 SignatureHashInputs, SignatureItemParseable, StopAt, UnparsedItem, VerifyFailed,
78};
79use crate::types::relay_flags::{self, DocRelayFlags};
80use crate::types::{self, *};
81use crate::util::PeekableIterator;
82use crate::{Error, KeywordEncodable, NetdocErrorKind as EK, NormalItemArgument, Pos};
83use std::collections::{BTreeSet, HashMap, HashSet};
84use std::fmt::{self, Display};
85use std::slice;
86use std::str::FromStr;
87use std::sync::Arc;
88use std::time::{self, SystemTime};
89use std::{net, result};
90use tor_basic_utils::iter_join;
91use tor_error::{Bug, HasKind, bad_api_usage, internal};
92use tor_protover::Protocols;
93use void::ResultVoidExt as _;
94
95use derive_deftly::{Deftly, define_derive_deftly};
96use digest::Digest;
97use itertools::Itertools;
98use saturating_time::SaturatingTime as _;
99use std::sync::LazyLock;
100use tor_checkable::{ExternallySigned, Timebound, timed::TimerangeBound};
101use tor_llcrypto as ll;
102use tor_llcrypto::pk::rsa::RsaIdentity;
103
104use serde::{Deserialize, Deserializer};
105
106#[cfg(feature = "build_docs")]
107pub use build::MdConsensusBuilder;
108#[cfg(feature = "build_docs")]
109pub use build::PlainConsensusBuilder;
110#[cfg(feature = "build_docs")]
111ns_export_each_flavor! {
112 ty: RouterStatusBuilder;
113}
114
115ns_export_each_variety! {
116 ty: Footer, RouterStatus, Preamble;
117}
118
119#[deprecated]
120pub use PlainConsensus as NsConsensus;
121#[deprecated]
122pub use PlainRouterStatus as NsRouterStatus;
123#[deprecated]
124pub use UncheckedPlainConsensus as UncheckedNsConsensus;
125#[deprecated]
126pub use UnvalidatedPlainConsensus as UnvalidatedNsConsensus;
127
128pub use rs::{RouterStatusMdDigestsVote, SoftwareVersion};
129
130pub use dir_source::{ConsensusAuthoritySection, DirSource, SupersededAuthorityKey};
131
132define_constant_string! {
133 NetworkStatusVersion = "3";
143}
144
145define_constant_string! {
146 VoteStatusConsensus = "consensus";
150}
151
152define_constant_string! {
153 VoteStatusVote = "vote";
157}
158
159#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Default)]
173#[allow(clippy::exhaustive_structs)]
174pub struct IgnoredPublicationTimeSp;
175
176#[derive(Clone, Debug, Deftly)]
184#[derive_deftly(Constructor, NetdocEncodableFields, NetdocParseableFields)]
185#[derive_deftly(Lifetime)]
186#[allow(clippy::exhaustive_structs)]
187pub struct Lifetime {
188 #[deftly(constructor)]
195 #[deftly(netdoc(single_arg))]
196 pub valid_after: Iso8601TimeSp,
197 #[deftly(constructor)]
205 #[deftly(netdoc(single_arg))]
206 pub fresh_until: Iso8601TimeSp,
207 #[deftly(constructor)]
215 #[deftly(netdoc(single_arg))]
216 pub valid_until: Iso8601TimeSp,
217
218 #[doc(hidden)]
219 #[deftly(netdoc(skip))]
220 pub __non_exhaustive: (),
221}
222
223define_derive_deftly! {
224 Lifetime:
226
227 ${defcond FIELD not(approx_equal($fname, __non_exhaustive))}
228
229 impl Lifetime {
230 pub fn new(
232 $( ${when FIELD} $fname: time::SystemTime, )
233 ) -> crate::Result<Self> {
234 let self_ = Lifetime {
238 $( ${when FIELD} $fname: $fname.into(), )
239 __non_exhaustive: (),
240 };
241 if self_.valid_after < self_.fresh_until && self_.fresh_until < self_.valid_until {
242 Ok(self_)
243 } else {
244 Err(EK::InvalidLifetime.err())
245 }
246 }
247 $(
248 ${when FIELD}
249
250 ${fattrs doc}
251 pub fn $fname(&self) -> time::SystemTime {
252 *self.$fname
253 }
254 )
255 pub fn valid_at(&self, when: time::SystemTime) -> bool {
257 *self.valid_after <= when && when <= *self.valid_until
258 }
259
260 pub fn voting_period(&self) -> time::Duration {
265 let valid_after = self.valid_after();
266 let fresh_until = self.fresh_until();
267 fresh_until
268 .duration_since(valid_after)
269 .expect("Mis-formed lifetime")
270 }
271 }
272}
273use derive_deftly_template_Lifetime;
274
275#[derive(Debug, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Copy)] #[derive(derive_more::From, derive_more::Into, derive_more::Display, derive_more::FromStr)]
287pub struct ConsensusMethod(u32);
288impl NormalItemArgument for ConsensusMethod {}
289
290#[derive(Debug, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Deftly)]
297#[derive_deftly(ItemValueEncodable, ItemValueParseable)]
298#[non_exhaustive]
299pub struct ConsensusMethods {
300 pub methods: BTreeSet<ConsensusMethod>,
302}
303
304pub mod consensus_methods_comma_separated {
309 use super::*;
310 use parse2::ArgumentError as AE;
311 use std::result::Result;
312
313 pub fn from_args<'s>(args: &mut ArgumentStream<'s>) -> Result<ConsensusMethods, AE> {
315 let mut methods = BTreeSet::new();
316 for ent in args.next().ok_or(AE::Missing)?.split(',') {
317 let ent = ent.parse().map_err(|_| AE::Invalid)?;
318 if !methods.insert(ent) {
319 return Err(AE::Invalid);
320 }
321 }
322 Ok(ConsensusMethods { methods })
323 }
324
325 #[cfg(feature = "incomplete")] pub fn write_arg_onto(self_: &ConsensusMethods, out: &mut ItemEncoder) -> Result<(), Bug> {
328 out.args_raw_string(&iter_join(",", &self_.methods));
329 Ok(())
330 }
331}
332
333#[derive(Debug, Clone, Default, Eq, PartialEq)]
359pub struct NetParams<T> {
360 params: HashMap<String, T>,
362}
363
364impl<T> NetParams<T> {
365 #[allow(unused)]
367 pub fn new() -> Self {
368 NetParams {
369 params: HashMap::new(),
370 }
371 }
372 pub fn get<A: AsRef<str>>(&self, v: A) -> Option<&T> {
374 self.params.get(v.as_ref())
375 }
376 pub fn iter(&self) -> impl Iterator<Item = (&String, &T)> {
378 self.params.iter()
379 }
380 pub fn set(&mut self, k: String, v: T) {
382 self.params.insert(k, v);
383 }
384}
385
386impl<K: Into<String>, T> FromIterator<(K, T)> for NetParams<T> {
387 fn from_iter<I: IntoIterator<Item = (K, T)>>(i: I) -> Self {
388 NetParams {
389 params: i.into_iter().map(|(k, v)| (k.into(), v)).collect(),
390 }
391 }
392}
393
394impl<T> std::iter::Extend<(String, T)> for NetParams<T> {
395 fn extend<I: IntoIterator<Item = (String, T)>>(&mut self, iter: I) {
396 self.params.extend(iter);
397 }
398}
399
400impl<'de, T> Deserialize<'de> for NetParams<T>
401where
402 T: Deserialize<'de>,
403{
404 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
405 where
406 D: Deserializer<'de>,
407 {
408 let params = HashMap::deserialize(deserializer)?;
409 Ok(NetParams { params })
410 }
411}
412
413#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
422pub struct ProtoStatus {
423 recommended: Protocols,
428 required: Protocols,
433}
434
435impl ProtoStatus {
436 pub fn check_protocols(
446 &self,
447 supported_protocols: &Protocols,
448 ) -> Result<(), ProtocolSupportError> {
449 let missing_required = self.required.difference(supported_protocols);
451 if !missing_required.is_empty() {
452 return Err(ProtocolSupportError::MissingRequired(missing_required));
453 }
454 let missing_recommended = self.recommended.difference(supported_protocols);
455 if !missing_recommended.is_empty() {
456 return Err(ProtocolSupportError::MissingRecommended(
457 missing_recommended,
458 ));
459 }
460
461 Ok(())
462 }
463}
464
465#[derive(Clone, Debug, thiserror::Error)]
467#[cfg_attr(test, derive(PartialEq))]
468#[non_exhaustive]
469pub enum ProtocolSupportError {
470 #[error("Required protocols are not implemented: {0}")]
472 MissingRequired(Protocols),
473
474 #[error("Recommended protocols are not implemented: {0}")]
478 MissingRecommended(Protocols),
479}
480
481impl ProtocolSupportError {
482 pub fn should_shutdown(&self) -> bool {
484 matches!(self, Self::MissingRequired(_))
485 }
486}
487
488impl HasKind for ProtocolSupportError {
489 fn kind(&self) -> tor_error::ErrorKind {
490 tor_error::ErrorKind::SoftwareDeprecated
491 }
492}
493
494#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
501pub struct ProtoStatuses {
502 client: ProtoStatus,
504 relay: ProtoStatus,
506}
507
508impl ProtoStatuses {
509 pub fn client(&self) -> &ProtoStatus {
511 &self.client
512 }
513
514 pub fn relay(&self) -> &ProtoStatus {
516 &self.relay
517 }
518}
519
520#[derive(Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd)] #[derive(derive_more::Deref, derive_more::Into)]
540pub struct RecommendedTorVersions(BTreeSet<String>);
541
542#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
544#[non_exhaustive]
545pub enum InvalidRecommendedTorVersions {
546 #[error("version {_0:?} contains whitespace")]
548 ContainsWhitespace(String),
549
550 #[error("version {_0:?} is repeated")]
552 Repeated(String),
553}
554
555impl RecommendedTorVersions {
556 pub fn new_unknown() -> Self {
558 Self::default()
559 }
560
561 pub fn is_known(&self) -> bool {
567 !self.is_empty()
568 }
569
570 #[allow(clippy::should_implement_trait)] pub fn from_iter<I, S>(i: I) -> Result<Self, InvalidRecommendedTorVersions>
573 where
574 I: IntoIterator<Item = S>,
575 S: AsRef<str>,
576 {
577 let mut set = BTreeSet::new();
578 for v in i {
579 let v = v.as_ref();
580 if v.is_empty() {
581 continue;
582 }
583 if v.chars().any(|c| c.is_whitespace()) {
584 return Err(InvalidRecommendedTorVersions::ContainsWhitespace(
585 v.to_owned(),
586 ));
587 }
588 if !set.insert(v.to_owned()) {
589 return Err(InvalidRecommendedTorVersions::Repeated(v.to_owned()));
590 }
591 }
592 Ok(RecommendedTorVersions(set))
593 }
594}
595
596impl FromStr for RecommendedTorVersions {
597 type Err = InvalidRecommendedTorVersions;
598 fn from_str(s: &str) -> Result<Self, InvalidRecommendedTorVersions> {
599 Self::from_iter(s.split(','))
600 }
601}
602
603impl Display for RecommendedTorVersions {
604 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
605 write!(f, "{}", iter_join(",", &self.0))
606 }
607}
608
609impl NormalItemArgument for RecommendedTorVersions {}
610
611impl ItemValueEncodable for RecommendedTorVersions {
612 fn write_item_value_onto(&self, mut out: ItemEncoder) -> Result<(), Bug> {
613 out.args_raw_string(self);
614 Ok(())
615 }
616}
617
618impl ItemValueParseable for RecommendedTorVersions {
619 fn from_unparsed(mut item: UnparsedItem) -> Result<Self, ErrorProblem> {
620 const FIELD: &str = "versions";
621 item.check_no_object()?;
622 let args = item.args_mut();
623 let arg = args.next().unwrap_or("");
624 arg.parse::<Self>()
625 .map_err(|_| args.handle_error(FIELD, ArgumentError::Invalid))
626 }
627}
628
629#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
637#[allow(clippy::exhaustive_enums)]
638pub enum ConsensusFlavor {
639 Microdesc,
642 Plain,
647}
648
649impl ConsensusFlavor {
650 pub fn name(&self) -> &'static str {
652 match self {
653 ConsensusFlavor::Plain => "ns", ConsensusFlavor::Microdesc => "microdesc",
655 }
656 }
657 pub fn from_opt_name(name: Option<&str>) -> crate::Result<Self> {
662 match name {
663 Some("microdesc") => Ok(ConsensusFlavor::Microdesc),
664 Some("ns") | None => Ok(ConsensusFlavor::Plain),
665 Some(other) => {
666 Err(EK::BadDocumentType.with_msg(format!("unrecognized flavor {:?}", other)))
667 }
668 }
669 }
670}
671
672define_derive_deftly! {
673 DirectorySignaturesHashesAccu:
681
682 ${define FNAME ${paste ${snake_case $vname}} }
683
684 #[derive(Clone, Copy, Default, Debug, Eq, PartialEq, Deftly)]
686 #[derive_deftly(AsMutSelf)]
687 #[non_exhaustive]
688 pub struct DirectorySignaturesHashesAccu {
689 $(
690 ${vattrs doc}
691 pub $FNAME: Option<[u8; ${vmeta(hash_len) as expr}]>,
692 )
693
694 pub sha1_unnamed: Option<[u8; 20]>,
704 }
705
706 impl DirectorySignaturesHashesAccu {
707 fn update_from(
709 &mut self,
710 algo: &DigestAlgoInSignature,
711 body: &SignatureHashInputs,
712 ) {
713 ${define HASH {
716 self.$UPDATE.get_or_insert_with(|| {
718 let mut h = tor_llcrypto::d::$ALGO::new();
719 h.update(body.body().body());
720 h.update(body.signature_item_kw_spc);
721 h.finalize().into()
722 });
723 }}
724
725 match &**algo {
726 $(
727 Some(KeywordOrString::Known($vtype)) => {
728 ${define UPDATE $FNAME}
729 ${define ALGO $vname}
730 $HASH
731 }
732 )
733 None => {
734 ${define UPDATE sha1_unnamed}
735 ${define ALGO Sha1}
736 $HASH
737 }
738 Some(KeywordOrString::Unknown(..)) => {}
739 }
740 }
741
742 pub(crate) fn hash_slice_for_verification(
748 &self,
749 algo: &DigestAlgoInSignature,
750 ) -> Option<&[u8]> {
751 match &**algo {
752 $(
753 Some(KeywordOrString::Known($vtype)) => Some(self.$FNAME.as_ref()?),
754 )
755 None => Some(self.sha1_unnamed.as_ref()?),
756 Some(KeywordOrString::Unknown(..)) => None,
757 }
758 }
759 }
760}
761
762#[derive(Clone, Copy, Debug, Eq, PartialEq, strum::Display, strum::EnumString, Deftly)]
764#[derive_deftly(DirectorySignaturesHashesAccu)]
765#[non_exhaustive]
766#[strum(serialize_all = "snake_case")]
767pub enum DirectorySignatureHashAlgo {
768 #[deftly(hash_len = "20")]
770 Sha1,
771 #[deftly(hash_len = "32")]
773 Sha256,
774}
775
776#[derive(Debug, Clone, derive_more::Deref, derive_more::DerefMut)]
788#[allow(clippy::exhaustive_structs)]
789pub struct DigestAlgoInSignature(pub Option<KeywordOrString<DirectorySignatureHashAlgo>>);
790
791impl ItemArgumentParseable for DigestAlgoInSignature {
792 fn from_args<'s>(args: &mut ArgumentStream<'s>) -> Result<Self, ArgumentError> {
793 let v = if args
794 .clone()
795 .next()
796 .and_then(|s| s.chars().all(|c| c.is_ascii_hexdigit()).then_some(()))
800 .is_some()
801 {
802 None
804 } else {
805 Some(KeywordOrString::from_args(args)?)
806 };
807 Ok(DigestAlgoInSignature(v))
808 }
809}
810impl ItemArgument for DigestAlgoInSignature {
811 fn write_arg_onto(&self, out: &mut ItemEncoder<'_>) -> Result<(), Bug> {
812 if let Some(y) = &self.0 {
813 y.write_arg_onto(out)?;
814 }
815 Ok(())
816 }
817}
818impl DigestAlgoInSignature {
819 pub fn algorithm(&self) -> &KeywordOrString<DirectorySignatureHashAlgo> {
823 self.as_ref()
824 .unwrap_or(&KeywordOrString::Known(DirectorySignatureHashAlgo::Sha1))
825 }
826}
827
828impl NormalItemArgument for DirectorySignatureHashAlgo {}
829
830#[derive(Debug, Clone, Deftly)]
835#[derive_deftly(ItemValueEncodable, ItemValueParseable)]
836#[non_exhaustive]
837pub struct Signature {
838 pub digest_algo: DigestAlgoInSignature,
843 #[deftly(netdoc(with = authcert::keyids_directory_signature_args))]
846 pub key_ids: AuthCertKeyIds,
847 #[deftly(netdoc(object(label = "SIGNATURE"), with = types::raw_data_object))]
849 pub signature: Vec<u8>,
850}
851
852impl SignatureItemParseable for Signature {
853 type HashAccu = DirectorySignaturesHashesAccu;
854
855 fn from_unparsed_and_body(
856 item: UnparsedItem,
857 body: &SignatureHashInputs<'_>,
858 hash: &mut Self::HashAccu,
859 ) -> Result<Self, ErrorProblem> {
860 let signature = Signature::from_unparsed(item)?;
861 hash.update_from(&signature.digest_algo, body);
862 Ok(signature)
863 }
864}
865
866#[derive(Debug, Clone)]
872#[non_exhaustive]
873pub struct SignatureGroup {
874 pub hashes: DirectorySignaturesHashesAccu,
880 pub signatures: Vec<Signature>,
882}
883
884#[derive(Clone, Debug, thiserror::Error)]
895#[non_exhaustive]
896pub enum ConsensusVerifiabilityError {
899 #[error("consensus not signed by enough authorities")]
901 InsufficientTrustedSigners,
902
903 #[error("missing auth certs mean we could not verify enough consensuis signatures (need at least {deficit} more, out of {} that are missing)", missing.len())]
905 MissingAuthCerts {
906 deficit: usize,
908 missing: HashSet<AuthCertKeyIds>,
910 },
911}
912
913#[derive(Clone, Debug, thiserror::Error)]
925#[non_exhaustive]
926pub enum ConsensusVerifyFailed {
927 #[error("certs/sigs insufficient")]
929 CertificationInsufficient(#[from] ConsensusVerifiabilityError),
930
931 #[error("invalid signature")]
933 InvalidSignature(#[source] VerifyFailed),
938}
939
940#[derive(Clone, Debug, thiserror::Error)]
950#[non_exhaustive]
951pub enum VoteVerifyFailed {
952 #[error("invalid signature")]
954 InvalidSignature(#[source] VerifyFailed),
959
960 #[error("unparseable authcert")]
962 AuthCertParseError(#[source] parse2::ParseError),
963
964 #[error("authcert not valid for vote period")]
966 AuthCertWrongValidity(#[source] tor_checkable::TimeValidityError),
967
968 #[error("wrong authcert")]
970 AuthCertWrongAuthority,
971}
972
973#[derive(
975 Debug, Clone, Copy, Eq, PartialEq, derive_more::From, derive_more::Into, derive_more::AsRef,
976)]
977pub struct SharedRandVal([u8; 32]);
979
980#[derive(Debug, Clone, Deftly)]
983#[non_exhaustive]
984#[derive_deftly(ItemValueEncodable, ItemValueParseable)]
985pub struct SharedRandStatus {
986 pub n_reveals: u8,
988 pub value: SharedRandVal,
995
996 pub timestamp: Option<Iso8601TimeNoSp>,
1000}
1001
1002#[derive(Debug, Clone, Default, Deftly)]
1009#[derive_deftly(Constructor, NetdocEncodableFields, NetdocParseableFields)]
1010#[allow(clippy::exhaustive_structs)]
1011pub struct SharedRandStatuses {
1012 pub shared_rand_previous_value: Option<SharedRandStatus>,
1014
1015 pub shared_rand_current_value: Option<SharedRandStatus>,
1017
1018 #[doc(hidden)]
1019 #[deftly(netdoc(skip))]
1020 pub __non_exhaustive: (),
1021}
1022
1023#[derive(Debug, Clone)]
1080pub struct RelayWeightsItem {
1081 effective: RelayWeight,
1083
1084 params: Unknown<Option<NetParams<u32>>>,
1086}
1087
1088#[non_exhaustive]
1092#[derive(Debug, Clone, Copy)]
1093pub enum RelayWeight {
1094 Unmeasured(u32),
1096 Measured(u32),
1098}
1099
1100#[derive(Debug, Clone, thiserror::Error)]
1102#[non_exhaustive]
1103pub enum InvalidRelayWeights {
1104 #[error("invalid value for Unmeasured")]
1106 InvalidUnmeasured,
1107}
1108
1109#[deprecated = "renamed to ConsensusAuthorityEntry"]
1111pub type ConsensusVoterInfo = ConsensusAuthorityEntry;
1112
1113pub type PlainAuthorityEntry = ConsensusAuthorityEntry;
1115pub type MdAuthorityEntry = ConsensusAuthorityEntry;
1117
1118#[derive(Debug, Clone, Deftly)]
1128#[derive_deftly(Constructor, NetdocEncodable, NetdocParseable)]
1129#[allow(clippy::exhaustive_structs)]
1130pub struct ConsensusAuthorityEntry {
1131 #[deftly(constructor)]
1133 pub dir_source: DirSource,
1134
1135 #[deftly(constructor)]
1141 pub contact: ContactInfo,
1142
1143 #[deftly(netdoc(single_arg))]
1150 #[deftly(constructor)]
1151 pub vote_digest: B16U,
1152
1153 #[doc(hidden)]
1154 #[deftly(netdoc(skip))]
1155 pub __non_exhaustive: (),
1156}
1157
1158#[derive(Debug, Clone, Deftly)]
1164#[derive_deftly(Constructor, NetdocEncodable, NetdocParseable)]
1165#[allow(clippy::exhaustive_structs)]
1166pub struct VoteAuthorityEntry {
1167 #[deftly(constructor)]
1169 pub dir_source: DirSource,
1170
1171 #[deftly(constructor)]
1173 pub contact: ContactInfo,
1174
1175 #[deftly(netdoc(single_arg))]
1179 pub legacy_dir_key: Option<Fingerprint>,
1180
1181 pub shared_rand_participate: Option<SharedRandParticipate>,
1185
1186 pub shared_rand_commit: Vec<SharedRandCommit>,
1190
1191 #[deftly(netdoc(flatten))]
1193 pub shared_rand: SharedRandStatuses,
1194
1195 #[doc(hidden)]
1196 #[deftly(netdoc(skip))]
1197 pub __non_exhaustive: (),
1198}
1199
1200#[derive(Debug, Clone, Deftly)]
1210#[derive_deftly(Constructor, ItemValueEncodable, ItemValueParseable)]
1211#[allow(clippy::exhaustive_structs)]
1212pub struct SharedRandParticipate {
1213 #[doc(hidden)]
1214 #[deftly(netdoc(skip))]
1215 pub __non_exhaustive: (),
1216}
1217
1218#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deftly)]
1222#[allow(clippy::exhaustive_enums)]
1224pub enum SharedRandCommit {
1225 V1(SharedRandCommitV1),
1227
1228 Unknown {},
1231}
1232
1233#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deftly)]
1242#[derive_deftly(Constructor, ItemValueEncodable, ItemValueParseable)]
1243#[allow(clippy::exhaustive_structs)]
1244pub struct SharedRandCommitV1 {
1245 #[deftly(constructor)]
1248 h_kp_auth_id_rsa: Fingerprint,
1249
1250 #[deftly(constructor)]
1258 commit: FixedB64<40>,
1259
1260 reveal: Option<FixedB64<40>>,
1265
1266 #[doc(hidden)]
1267 #[deftly(netdoc(skip))]
1268 pub __non_exhaustive: (),
1269}
1270
1271impl SharedRandCommitV1 {
1272 const FIXED_ARGUMENTS: &[&str] = &["1", "sha3-256"];
1274}
1275impl ItemValueEncodable for SharedRandCommit {
1276 fn write_item_value_onto(&self, mut out: ItemEncoder) -> Result<(), Bug> {
1277 match self {
1278 SharedRandCommit::V1(values) => {
1279 for fixed in SharedRandCommitV1::FIXED_ARGUMENTS {
1280 out.args_raw_string(fixed);
1281 }
1282 values.write_item_value_onto(out)
1283 }
1284 SharedRandCommit::Unknown {} => Err(internal!("encoding SharedRandCommit::Unknown")),
1285 }
1286 }
1287}
1288impl ItemValueParseable for SharedRandCommit {
1289 fn from_unparsed(mut item: UnparsedItem<'_>) -> Result<Self, ErrorProblem> {
1290 let mut fixed = SharedRandCommitV1::FIXED_ARGUMENTS.iter().copied();
1291 let args = item.args_mut();
1292 let version = args
1293 .next()
1294 .ok_or_else(|| args.handle_error("version", ArgumentError::Missing))?;
1295 if version != fixed.next().expect("nonempty") {
1296 return Ok(SharedRandCommit::Unknown {});
1297 }
1298 for exp in fixed {
1299 let got = args
1300 .next()
1301 .ok_or_else(|| args.handle_error(exp, ArgumentError::Missing))?;
1302 if got != exp {
1303 return Err(args.handle_error(exp, ArgumentError::Invalid))?;
1304 }
1305 }
1306 let values = SharedRandCommitV1::from_unparsed(item)?;
1307 Ok(SharedRandCommit::V1(values))
1308 }
1309}
1310
1311define_derive_deftly! {
1314 VoteAuthoritySection:
1326
1327 ${defcond F_NORMAL not(fmeta(netdoc(skip)))}
1328
1329 #[cfg(feature = "incomplete")] impl NetdocParseable for VoteAuthoritySection {
1331 fn doctype_for_error() -> &'static str {
1332 "vote.authority.section"
1333 }
1334 fn is_intro_item_keyword(kw: KeywordRef<'_>) -> bool {
1335 VoteAuthorityEntry::is_intro_item_keyword(kw)
1336 }
1337 fn is_structural_keyword(kw: KeywordRef<'_>) -> Option<IsStructural> {
1338 $(
1339 ${when F_NORMAL}
1340 if let y @ Some(_) = $ftype::is_structural_keyword(kw) {
1341 return y;
1342 }
1343 )
1344 None
1345 }
1346 fn from_items<'s>(
1347 input: &mut ItemStream<'s>,
1348 stop_outer: stop_at!(),
1349 ) -> Result<Self, ErrorProblem> {
1350 let stop_inner = stop_outer
1351 $(
1352 ${when F_NORMAL}
1353 | StopAt($ftype::is_intro_item_keyword)
1354 )
1355 ;
1356 Ok(VoteAuthoritySection { $(
1357 ${when F_NORMAL}
1358 $fname: NetdocParseable::from_items(input, stop_inner)?,
1359 )
1360 __non_exhaustive: (),
1361 })
1362 }
1363 }
1364
1365 #[cfg(feature = "incomplete")]
1366 impl NetdocEncodable for VoteAuthoritySection {
1367 fn encode_unsigned(&self, out: &mut NetdocEncoder) -> Result<(), Bug> {
1368 $(
1369 ${when F_NORMAL}
1370 self.$fname.encode_unsigned(out)?;
1371 )
1372 Ok(())
1373 }
1374 }
1375}
1376
1377#[derive(Deftly, Clone, Debug)]
1384#[derive_deftly(VoteAuthoritySection, Constructor)]
1385#[allow(clippy::exhaustive_structs)]
1386#[cfg(feature = "incomplete")] pub struct VoteAuthoritySection {
1388 #[deftly(constructor)]
1390 pub authority: VoteAuthorityEntry,
1391
1392 #[deftly(constructor)]
1394 pub cert: EmbeddedCert<AuthCert, EncodedAuthCert>,
1395
1396 #[doc(hidden)]
1397 #[deftly(netdoc(skip))]
1398 pub __non_exhaustive: (),
1399}
1400
1401#[derive(Debug, Clone, Deftly)]
1407#[derive_deftly(Constructor, NetdocEncodableFields, NetdocParseableFields)]
1408#[allow(clippy::exhaustive_structs)]
1409pub struct ConsensusFooterFields {
1410 #[deftly(netdoc(default))]
1414 pub bandwidth_weights: NetParams<i32>,
1415
1416 #[doc(hidden)]
1417 #[deftly(netdoc(skip))]
1418 pub __non_exhaustive: (),
1419}
1420
1421pub type MdConsensus = md::Consensus;
1424
1425pub type UnvalidatedMdConsensus = md::UnvalidatedConsensus;
1428
1429pub type UncheckedMdConsensus = md::UncheckedConsensus;
1432
1433pub type PlainConsensus = plain::Consensus;
1436
1437pub type UnvalidatedPlainConsensus = plain::UnvalidatedConsensus;
1440
1441pub type UncheckedPlainConsensus = plain::UncheckedConsensus;
1444
1445decl_keyword! {
1446 #[non_exhaustive]
1451 #[allow(missing_docs)]
1452 pub NetstatusKwd {
1453 "network-status-version" => NETWORK_STATUS_VERSION,
1455 "vote-status" => VOTE_STATUS,
1456 "consensus-methods" => CONSENSUS_METHODS,
1457 "consensus-method" => CONSENSUS_METHOD,
1458 "published" => PUBLISHED,
1459 "valid-after" => VALID_AFTER,
1460 "fresh-until" => FRESH_UNTIL,
1461 "valid-until" => VALID_UNTIL,
1462 "voting-delay" => VOTING_DELAY,
1463 "client-versions" => CLIENT_VERSIONS,
1464 "server-versions" => SERVER_VERSIONS,
1465 "known-flags" => KNOWN_FLAGS,
1466 "flag-thresholds" => FLAG_THRESHOLDS,
1467 "recommended-client-protocols" => RECOMMENDED_CLIENT_PROTOCOLS,
1468 "required-client-protocols" => REQUIRED_CLIENT_PROTOCOLS,
1469 "recommended-relay-protocols" => RECOMMENDED_RELAY_PROTOCOLS,
1470 "required-relay-protocols" => REQUIRED_RELAY_PROTOCOLS,
1471 "params" => PARAMS,
1472 "bandwidth-file-headers" => BANDWIDTH_FILE_HEADERS,
1473 "bandwidth-file-digest" => BANDWIDTH_FILE_DIGEST,
1474 "shared-rand-previous-value" => SHARED_RAND_PREVIOUS_VALUE,
1478 "shared-rand-current-value" => SHARED_RAND_CURRENT_VALUE,
1479
1480 "dir-source" => DIR_SOURCE,
1482 "contact" => CONTACT,
1483
1484 "legacy-dir-key" => LEGACY_DIR_KEY,
1486 "shared-rand-participate" => SHARED_RAND_PARTICIPATE,
1487 "shared-rand-commit" => SHARED_RAND_COMMIT,
1488
1489 "vote-digest" => VOTE_DIGEST,
1491
1492 "dir-key-certificate-version" => DIR_KEY_CERTIFICATE_VERSION,
1494
1495 "r" => RS_R,
1497 "a" => RS_A,
1498 "s" => RS_S,
1499 "v" => RS_V,
1500 "pr" => RS_PR,
1501 "w" => RS_W,
1502 "p" => RS_P,
1503 "m" => RS_M,
1504 "id" => RS_ID,
1505
1506 "directory-footer" => DIRECTORY_FOOTER,
1508 "bandwidth-weights" => BANDWIDTH_WEIGHTS,
1509 "directory-signature" => DIRECTORY_SIGNATURE,
1510 }
1511}
1512
1513static NS_HEADER_RULES_COMMON_: LazyLock<SectionRulesBuilder<NetstatusKwd>> = LazyLock::new(|| {
1515 use NetstatusKwd::*;
1516 let mut rules = SectionRules::builder();
1517 rules.add(NETWORK_STATUS_VERSION.rule().required().args(1..=2));
1518 rules.add(VOTE_STATUS.rule().required().args(1..));
1519 rules.add(VALID_AFTER.rule().required());
1520 rules.add(FRESH_UNTIL.rule().required());
1521 rules.add(VALID_UNTIL.rule().required());
1522 rules.add(VOTING_DELAY.rule().args(2..));
1523 rules.add(CLIENT_VERSIONS.rule());
1524 rules.add(SERVER_VERSIONS.rule());
1525 rules.add(KNOWN_FLAGS.rule().required());
1526 rules.add(RECOMMENDED_CLIENT_PROTOCOLS.rule().args(1..));
1527 rules.add(RECOMMENDED_RELAY_PROTOCOLS.rule().args(1..));
1528 rules.add(REQUIRED_CLIENT_PROTOCOLS.rule().args(1..));
1529 rules.add(REQUIRED_RELAY_PROTOCOLS.rule().args(1..));
1530 rules.add(PARAMS.rule());
1531 rules
1532});
1533static NS_HEADER_RULES_CONSENSUS: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
1535 use NetstatusKwd::*;
1536 let mut rules = NS_HEADER_RULES_COMMON_.clone();
1537 rules.add(CONSENSUS_METHOD.rule().args(1..=1));
1538 rules.add(SHARED_RAND_PREVIOUS_VALUE.rule().args(2..));
1539 rules.add(SHARED_RAND_CURRENT_VALUE.rule().args(2..));
1540 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
1541 rules.build()
1542});
1543static NS_VOTERINFO_RULES_CONSENSUS: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
1574 use NetstatusKwd::*;
1575 let mut rules = SectionRules::builder();
1576 rules.add(DIR_SOURCE.rule().required().args(6..));
1577 rules.add(CONTACT.rule().required());
1578 rules.add(VOTE_DIGEST.rule().required());
1579 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
1580 rules.build()
1581});
1582static NS_ROUTERSTATUS_RULES_COMMON_: LazyLock<SectionRulesBuilder<NetstatusKwd>> =
1584 LazyLock::new(|| {
1585 use NetstatusKwd::*;
1586 let mut rules = SectionRules::builder();
1587 rules.add(RS_A.rule().may_repeat().args(1..));
1588 rules.add(RS_S.rule().required());
1589 rules.add(RS_V.rule());
1590 rules.add(RS_PR.rule().required());
1591 rules.add(RS_W.rule());
1592 rules.add(RS_P.rule().args(2..));
1593 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
1594 rules
1595 });
1596
1597static NS_ROUTERSTATUS_RULES_PLAIN: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
1599 use NetstatusKwd::*;
1600 let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
1601 rules.add(RS_R.rule().required().args(8..));
1602 rules.build()
1603});
1604
1605static NS_ROUTERSTATUS_RULES_MDCON: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
1618 use NetstatusKwd::*;
1619 let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
1620 rules.add(RS_R.rule().required().args(6..));
1621 rules.add(RS_M.rule().required().args(1..));
1622 rules.build()
1623});
1624static NS_FOOTER_RULES: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
1626 use NetstatusKwd::*;
1627 let mut rules = SectionRules::builder();
1628 rules.add(DIRECTORY_FOOTER.rule().required().no_args());
1629 rules.add(BANDWIDTH_WEIGHTS.rule());
1631 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
1632 rules.build()
1633});
1634
1635impl ProtoStatus {
1636 fn from_section(
1638 sec: &Section<'_, NetstatusKwd>,
1639 recommend_token: NetstatusKwd,
1640 required_token: NetstatusKwd,
1641 ) -> crate::Result<ProtoStatus> {
1642 fn parse(t: Option<&Item<'_, NetstatusKwd>>) -> crate::Result<Protocols> {
1644 if let Some(item) = t {
1645 item.args_as_str()
1646 .parse::<Protocols>()
1647 .map_err(|e| EK::BadArgument.at_pos(item.pos()).with_source(e))
1648 } else {
1649 Ok(Protocols::new())
1650 }
1651 }
1652
1653 let recommended = parse(sec.get(recommend_token))?;
1654 let required = parse(sec.get(required_token))?;
1655 Ok(ProtoStatus {
1656 recommended,
1657 required,
1658 })
1659 }
1660
1661 pub fn required_protocols(&self) -> &Protocols {
1668 &self.required
1669 }
1670
1671 pub fn recommended_protocols(&self) -> &Protocols {
1676 &self.recommended
1677 }
1678}
1679
1680impl<T> std::str::FromStr for NetParams<T>
1681where
1682 T: std::str::FromStr,
1683 T::Err: std::error::Error,
1684{
1685 type Err = Error;
1686 fn from_str(s: &str) -> crate::Result<Self> {
1687 fn parse_pair<U>(p: &str) -> crate::Result<(String, U)>
1689 where
1690 U: std::str::FromStr,
1691 U::Err: std::error::Error,
1692 {
1693 let parts: Vec<_> = p.splitn(2, '=').collect();
1694 if parts.len() != 2 {
1695 return Err(EK::BadArgument
1696 .at_pos(Pos::at(p))
1697 .with_msg("Missing = in key=value list"));
1698 }
1699 let num = parts[1].parse::<U>().map_err(|e| {
1700 EK::BadArgument
1701 .at_pos(Pos::at(parts[1]))
1702 .with_msg(e.to_string())
1703 })?;
1704 Ok((parts[0].to_string(), num))
1705 }
1706
1707 let params = s
1708 .split(' ')
1709 .filter(|p| !p.is_empty())
1710 .map(parse_pair)
1711 .try_collect()?;
1712 Ok(NetParams { params })
1713 }
1714}
1715
1716impl FromStr for SharedRandVal {
1717 type Err = Error;
1718 fn from_str(s: &str) -> crate::Result<Self> {
1719 let val: B64 = s.parse()?;
1720 let val = SharedRandVal(val.into_array()?);
1721 Ok(val)
1722 }
1723}
1724impl Display for SharedRandVal {
1725 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1726 Display::fmt(&B64::from(Vec::from(self.0)), f)
1727 }
1728}
1729impl NormalItemArgument for SharedRandVal {}
1730
1731impl SharedRandStatus {
1732 fn from_item(item: &Item<'_, NetstatusKwd>) -> crate::Result<Self> {
1735 match item.kwd() {
1736 NetstatusKwd::SHARED_RAND_PREVIOUS_VALUE | NetstatusKwd::SHARED_RAND_CURRENT_VALUE => {}
1737 _ => {
1738 return Err(Error::from(internal!(
1739 "wrong keyword {:?} on shared-random value",
1740 item.kwd()
1741 ))
1742 .at_pos(item.pos()));
1743 }
1744 }
1745 let n_reveals: u8 = item.parse_arg(0)?;
1746 let value: SharedRandVal = item.parse_arg(1)?;
1747 let timestamp = item.parse_optional_arg::<Iso8601TimeNoSp>(2)?;
1749 Ok(SharedRandStatus {
1750 n_reveals,
1751 value,
1752 timestamp,
1753 })
1754 }
1755
1756 pub fn value(&self) -> &SharedRandVal {
1758 &self.value
1759 }
1760
1761 pub fn timestamp(&self) -> Option<std::time::SystemTime> {
1763 self.timestamp.map(|t| t.0)
1764 }
1765}
1766
1767impl DirSource {
1768 fn from_item(item: &Item<'_, NetstatusKwd>) -> crate::Result<Self> {
1770 if item.kwd() != NetstatusKwd::DIR_SOURCE {
1771 return Err(
1772 Error::from(internal!("Bad keyword {:?} on dir-source", item.kwd()))
1773 .at_pos(item.pos()),
1774 );
1775 }
1776 let nickname = item
1777 .required_arg(0)?
1778 .parse()
1779 .map_err(|e: InvalidNickname| {
1780 EK::BadArgument.at_pos(item.pos()).with_msg(e.to_string())
1781 })?;
1782 let identity = item.parse_arg(1)?;
1783 let hostname = item
1784 .required_arg(2)?
1785 .parse()
1786 .map_err(|e: InvalidInternetHost| {
1787 EK::BadArgument.at_pos(item.pos()).with_msg(e.to_string())
1788 })?;
1789 let ip = item.parse_arg(3)?;
1790 let dir_port = item.parse_arg(4)?;
1791 let or_port = item.parse_arg(5)?;
1792
1793 Ok(DirSource {
1794 nickname,
1795 identity,
1796 hostname,
1797 ip,
1798 dir_port,
1799 or_port,
1800 __non_exhaustive: (),
1801 })
1802 }
1803}
1804
1805impl ConsensusAuthorityEntry {
1806 fn from_section(sec: &Section<'_, NetstatusKwd>) -> crate::Result<ConsensusAuthorityEntry> {
1808 use NetstatusKwd::*;
1809 #[allow(clippy::unwrap_used)]
1812 let first = sec.first_item().unwrap();
1813 if first.kwd() != DIR_SOURCE {
1814 return Err(Error::from(internal!(
1815 "Wrong keyword {:?} at start of voter info",
1816 first.kwd()
1817 ))
1818 .at_pos(first.pos()));
1819 }
1820 let dir_source = DirSource::from_item(sec.required(DIR_SOURCE)?)?;
1821
1822 let contact = sec.required(CONTACT)?;
1823 let contact = contact
1829 .args_as_str()
1830 .parse()
1831 .map_err(|err: InvalidContactInfo| {
1832 EK::BadArgument
1833 .with_msg(err.to_string())
1834 .at_pos(contact.pos())
1835 })?;
1836
1837 let vote_digest = sec.required(VOTE_DIGEST)?.parse_arg::<B16U>(0)?;
1838
1839 Ok(ConsensusAuthorityEntry {
1840 dir_source,
1841 contact,
1842 vote_digest,
1843 __non_exhaustive: (),
1844 })
1845 }
1846}
1847
1848impl RelayWeightsItem {
1849 pub fn new_no_info() -> Self {
1853 RelayWeightsItem {
1854 effective: RelayWeight::default(),
1855 params: Unknown::new_discard(),
1856 }
1857 }
1858
1859 pub fn from_effective(effective: RelayWeight) -> Self {
1861 RelayWeightsItem {
1862 effective,
1863 params: Unknown::new_discard(),
1864 }
1865 }
1866
1867 pub fn effective(&self) -> RelayWeight {
1874 self.effective
1875 }
1876
1877 pub fn params(&self) -> Unknown<&Option<NetParams<u32>>> {
1885 self.params.as_ref()
1886 }
1887
1888 fn from_item(item: &Item<'_, NetstatusKwd>) -> crate::Result<RelayWeightsItem> {
1890 if item.kwd() != NetstatusKwd::RS_W {
1891 return Err(
1892 Error::from(internal!("Wrong keyword {:?} on W line", item.kwd()))
1893 .at_pos(item.pos()),
1894 );
1895 }
1896
1897 let params = item.args_as_str().parse()?;
1898 let effective = RelayWeight::from_net_params(¶ms).map_err(|e| e.at_pos(item.pos()))?;
1899
1900 Ok(RelayWeightsItem {
1901 effective,
1902 params: Unknown::new_discard(),
1903 })
1904 }
1905
1906 const KEYWORD: &str = "w";
1908}
1909
1910#[cfg(feature = "retain-unknown")]
1911impl Default for RelayWeightsItem {
1912 fn default() -> Self {
1913 RelayWeightsItem {
1914 effective: RelayWeight::default(),
1915 params: Unknown::Retained(None),
1916 }
1917 }
1918}
1919
1920impl RelayWeight {
1921 pub fn is_measured(&self) -> bool {
1923 matches!(self, RelayWeight::Measured(_))
1924 }
1925
1926 pub fn is_nonzero(&self) -> bool {
1928 !matches!(self, RelayWeight::Unmeasured(0) | RelayWeight::Measured(0))
1929 }
1930
1931 fn from_net_params(params: &NetParams<u32>) -> crate::Result<RelayWeight> {
1935 params
1936 .try_into()
1937 .map_err(|e: InvalidRelayWeights| EK::BadArgument.with_msg(e.to_string()))
1938 }
1939}
1940
1941impl Default for RelayWeight {
1942 fn default() -> RelayWeight {
1943 RelayWeight::Unmeasured(0)
1944 }
1945}
1946
1947impl TryFrom<&NetParams<u32>> for RelayWeight {
1948 type Error = InvalidRelayWeights;
1949
1950 fn try_from(params: &NetParams<u32>) -> Result<RelayWeight, InvalidRelayWeights> {
1951 let bw = params.params.get("Bandwidth");
1952 let unmeas = params.params.get("Unmeasured");
1953
1954 let bw = match bw {
1955 None => return Ok(RelayWeight::Unmeasured(0)),
1956 Some(b) => *b,
1957 };
1958
1959 match unmeas {
1960 None | Some(0) => Ok(RelayWeight::Measured(bw)),
1961 Some(1) => Ok(RelayWeight::Unmeasured(bw)),
1962 _ => Err(InvalidRelayWeights::InvalidUnmeasured),
1963 }
1964 }
1965}
1966
1967#[cfg(feature = "retain-unknown")]
1968impl TryFrom<NetParams<u32>> for RelayWeightsItem {
1969 type Error = InvalidRelayWeights;
1970
1971 fn try_from(params: NetParams<u32>) -> Result<RelayWeightsItem, InvalidRelayWeights> {
1972 Ok(RelayWeightsItem {
1973 effective: (¶ms).try_into()?,
1974 params: Unknown::Retained(Some(params)),
1975 })
1976 }
1977}
1978
1979mod parse2_impls {
1983 use super::*;
1984 pub(super) use parse2::{
1985 ArgumentError as AE, ArgumentStream, ErrorProblem as EP, ItemArgumentParseable,
1986 ItemValueParseable, NetdocParseableFields,
1987 };
1988 use std::result::Result;
1989
1990 impl<T: FromStr + NormalItemArgument> ItemValueParseable for NetParams<T>
1992 where
1993 T::Err: std::error::Error,
1994 {
1995 fn from_unparsed(item: parse2::UnparsedItem<'_>) -> Result<Self, EP> {
1996 item.check_no_object()?;
1997 item.args_copy()
1998 .into_remaining()
1999 .parse()
2000 .map_err(item.invalid_argument_handler("parameters"))
2001 }
2002 }
2003
2004 impl NetdocParseableFields for RelayWeightsItem {
2005 type Accumulator = Option<NetParams<u32>>;
2006
2007 fn is_item_keyword(kw: KeywordRef) -> bool {
2008 kw == Self::KEYWORD
2009 }
2010
2011 fn accumulate_item(acc: &mut Self::Accumulator, item: UnparsedItem) -> Result<(), EP> {
2012 if acc.is_some() {
2013 return Err(EP::ItemRepeated);
2014 }
2015 item.check_no_object()?;
2016 let params = NetParams::from_unparsed(item)?;
2017 *acc = Some(params);
2018 Ok(())
2019 }
2020
2021 fn finish(params: Self::Accumulator, items: &ItemStream) -> Result<Self, EP> {
2022 let effective = params
2023 .as_ref()
2024 .map(TryFrom::try_from)
2025 .transpose()
2026 .map_err(|_| EP::OtherBadDocument("invalid information in `w` item"))?
2027 .unwrap_or_default();
2028
2029 let params = items.parse_options().retain_unknown_values.map(|()| params);
2030
2031 Ok(RelayWeightsItem { effective, params })
2032 }
2033 }
2034
2035 impl ItemValueParseable for rs::SoftwareVersion {
2036 fn from_unparsed(mut item: parse2::UnparsedItem<'_>) -> Result<Self, EP> {
2037 item.check_no_object()?;
2038 item.args_mut()
2039 .into_remaining()
2040 .parse()
2041 .map_err(item.invalid_argument_handler("version"))
2042 }
2043 }
2044
2045 impl ItemArgumentParseable for IgnoredPublicationTimeSp {
2046 fn from_args(a: &mut ArgumentStream) -> Result<IgnoredPublicationTimeSp, AE> {
2047 let mut next_arg = || a.next().ok_or(AE::Missing);
2048 let _: &str = next_arg()?;
2049 let _: &str = next_arg()?;
2050 Ok(IgnoredPublicationTimeSp)
2051 }
2052 }
2053}
2054
2055mod encode_impls {
2059 use super::*;
2060 use std::result::Result;
2061 pub(crate) use {
2062 crate::encode::{ItemEncoder, ItemValueEncodable, NetdocEncodableFields},
2063 tor_error::Bug,
2064 };
2065
2066 #[cfg(feature = "incomplete")] impl NetdocEncodableFields for RelayWeightsItem {
2068 fn encode_fields(&self, out: &mut NetdocEncoder) -> Result<(), Bug> {
2069 if let Some(w) = self.params.as_ref().into_retained()? {
2070 w.write_item_value_onto(out.item(Self::KEYWORD))?;
2071 }
2072 Ok(())
2073 }
2074 }
2075
2076 impl<T: NormalItemArgument + Ord + Display> ItemValueEncodable for NetParams<T> {
2078 fn write_item_value_onto(&self, mut out: ItemEncoder) -> Result<(), Bug> {
2079 for (k, v) in self.iter().collect::<BTreeSet<_>>() {
2080 if k.is_empty()
2081 || k.chars()
2082 .any(|c| c.is_whitespace() || c.is_control() || c == '=')
2083 {
2084 return Err(bad_api_usage!(
2086 "tried to encode NetParms with unreasonable keyword {k:?}"
2087 ));
2088 }
2089 out.args_raw_string(&format_args!("{k}={v}"));
2090 }
2091 Ok(())
2092 }
2093 }
2094
2095 impl ItemValueEncodable for rs::SoftwareVersion {
2096 fn write_item_value_onto(&self, mut out: ItemEncoder) -> Result<(), Bug> {
2097 out.args_raw_string(self);
2098 Ok(())
2099 }
2100 }
2101
2102 impl ItemArgument for IgnoredPublicationTimeSp {
2103 fn write_arg_onto(&self, out: &mut ItemEncoder) -> Result<(), Bug> {
2104 out.args_raw_string(&"2000-01-01 00:00:01");
2105 Ok(())
2106 }
2107 }
2108}
2109
2110impl ConsensusFooterFields {
2111 fn from_section(sec: &Section<'_, NetstatusKwd>) -> crate::Result<ConsensusFooterFields> {
2113 use NetstatusKwd::*;
2114 sec.required(DIRECTORY_FOOTER)?;
2115
2116 let bandwidth_weights = sec
2117 .maybe(BANDWIDTH_WEIGHTS)
2118 .args_as_str()
2119 .unwrap_or("")
2120 .parse()?;
2121
2122 Ok(ConsensusFooterFields {
2123 bandwidth_weights,
2124 __non_exhaustive: (),
2125 })
2126 }
2127}
2128
2129mod proto_statuses_parse2_encode {
2133 use super::encode_impls::*;
2134 use super::parse2_impls::*;
2135 use super::*;
2136 use paste::paste;
2137 use std::result::Result;
2138
2139 macro_rules! impl_proto_statuses { { $( $rr:ident $cr:ident; )* } => { paste! {
2153 #[derive(Deftly)]
2154 #[derive_deftly(NetdocParseableFields)]
2155 #[allow(unreachable_pub)]
2157 pub struct ProtoStatusesParseHelper {
2158 $(
2159 #[deftly(netdoc(default))]
2160 [<$rr _ $cr _protocols>]: Protocols,
2161 )*
2162 }
2163
2164 pub use ProtoStatusesParseHelperNetdocParseAccumulator
2166 as ProtoStatusesNetdocParseAccumulator;
2167
2168 impl NetdocParseableFields for ProtoStatuses {
2169 type Accumulator = ProtoStatusesNetdocParseAccumulator;
2170 fn is_item_keyword(kw: KeywordRef<'_>) -> bool {
2171 ProtoStatusesParseHelper::is_item_keyword(kw)
2172 }
2173 fn accumulate_item(
2174 acc: &mut Self::Accumulator,
2175 item: UnparsedItem<'_>,
2176 ) -> Result<(), EP> {
2177 ProtoStatusesParseHelper::accumulate_item(acc, item)
2178 }
2179 fn finish(acc: Self::Accumulator, items: &ItemStream<'_>) -> Result<Self, EP> {
2180 let parse = ProtoStatusesParseHelper::finish(acc, items)?;
2181 let mut out = ProtoStatuses::default();
2182 $(
2183 out.$cr.$rr = parse.[< $rr _ $cr _protocols >];
2184 )*
2185 Ok(out)
2186 }
2187 }
2188
2189 impl NetdocEncodableFields for ProtoStatuses {
2190 fn encode_fields(&self, out: &mut NetdocEncoder) -> Result<(), Bug> {
2191 $(
2192 self.$cr.$rr.write_item_value_onto(
2193 out.item(concat!(stringify!($rr), "-", stringify!($cr), "-protocols"))
2194 )?;
2195 )*
2196 Ok(())
2197 }
2198 }
2199 } } }
2200
2201 impl_proto_statuses! {
2202 recommended client;
2203 recommended relay;
2204 required client;
2205 required relay;
2206 }
2207}
2208
2209impl Signature {
2210 fn from_item(item: &Item<'_, NetstatusKwd>) -> crate::Result<Signature> {
2212 if item.kwd() != NetstatusKwd::DIRECTORY_SIGNATURE {
2213 return Err(Error::from(internal!(
2214 "Wrong keyword {:?} for directory signature",
2215 item.kwd()
2216 ))
2217 .at_pos(item.pos()));
2218 }
2219
2220 let (digest_algo, id_fp, sk_fp) = if item.n_args() > 2 {
2221 (
2222 item.required_arg(0)?,
2223 item.required_arg(1)?,
2224 item.required_arg(2)?,
2225 )
2226 } else {
2227 ("sha1", item.required_arg(0)?, item.required_arg(1)?)
2229 };
2230
2231 let digest_algo = digest_algo.to_string().parse().void_unwrap();
2232 let digest_algo = DigestAlgoInSignature(Some(digest_algo));
2233 let id_fingerprint = id_fp.parse::<Fingerprint>()?.into();
2234 let sk_fingerprint = sk_fp.parse::<Fingerprint>()?.into();
2235 let key_ids = AuthCertKeyIds {
2236 id_fingerprint,
2237 sk_fingerprint,
2238 };
2239 let signature = item.obj("SIGNATURE")?;
2240
2241 Ok(Signature {
2242 digest_algo,
2243 key_ids,
2244 signature,
2245 })
2246 }
2247
2248 fn matches_cert(&self, cert: &AuthCert) -> bool {
2251 cert.key_ids() == self.key_ids
2252 }
2253
2254 fn find_cert<'a>(&self, certs: &'a [AuthCert]) -> Option<&'a AuthCert> {
2257 certs.iter().find(|&c| self.matches_cert(c))
2258 }
2259
2260 fn signature_to_verify<'r>(
2264 &'r self,
2265 signed_digest: &'r [u8],
2266 certs: &'r [AuthCert],
2267 ) -> Option<ConsensusSignatureToVerify> {
2268 let cert = self.find_cert(certs)?;
2269 let key = cert.signing_key();
2270 Some(ConsensusSignatureToVerify {
2271 key,
2272 signed_digest,
2273 signature: &self.signature,
2274 })
2275 }
2276}
2277
2278impl EncodeOrd for Signature {
2279 fn encode_cmp(&self, other: &Self) -> std::cmp::Ordering {
2280 let k: for<'s> fn(&'_ Signature) -> (&'_ _, &'_ _) = |s| (&s.key_ids, &s.signature);
2281 Ord::cmp(&k(self), &k(other))
2282 }
2283}
2284
2285#[derive(Debug, Clone, Copy)]
2293pub(crate) struct ConsensusSignatureToVerify<'r> {
2294 key: &'r ll::pk::rsa::PublicKey,
2296
2297 signed_digest: &'r [u8],
2299
2300 signature: &'r [u8],
2302}
2303
2304pub(crate) struct SignatureVerifiedIfIntended {}
2313
2314impl<'r> ConsensusSignatureToVerify<'r> {
2315 pub(crate) fn verify(self) -> Result<SignatureVerifiedIfIntended, VerifyFailed> {
2319 self.key.verify(self.signed_digest, self.signature)?;
2320 Ok(SignatureVerifiedIfIntended {})
2321 }
2322}
2323
2324#[derive(Debug, Clone, Copy)]
2328pub(crate) enum VerifyGeneralTrustedAuthorities<'r> {
2329 TrustThese {
2331 trusted: &'r [RsaIdentity],
2333 },
2334
2335 AnyOneOfThese {
2337 trusted: &'r [RsaIdentity],
2339 },
2340
2341 HazardouslyAssumeAllAuthCertsAreReal {
2346 n_authorities: usize,
2350 },
2351}
2352
2353pub fn consensus_threshold(n_authorities: usize) -> std::ops::RangeFrom<usize> {
2383 (n_authorities / 2) + 1 ..
2385}
2386
2387impl SignatureGroup {
2388 fn list_missing(&self, certs: &[AuthCert]) -> (usize, Vec<&Signature>) {
2395 let mut ok: HashSet<RsaIdentity> = HashSet::new();
2396 let mut missing = Vec::new();
2397 for sig in &self.signatures {
2398 let id_fingerprint = &sig.key_ids.id_fingerprint;
2399 if ok.contains(id_fingerprint) {
2400 continue;
2401 }
2402 if sig.find_cert(certs).is_some() {
2403 ok.insert(*id_fingerprint);
2404 continue;
2405 }
2406
2407 missing.push(sig);
2408 }
2409 (ok.len(), missing)
2410 }
2411
2412 fn could_validate(&self, authorities: &[&RsaIdentity]) -> bool {
2416 let mut signed_by: HashSet<RsaIdentity> = HashSet::new();
2417 for sig in &self.signatures {
2418 let id_fp = &sig.key_ids.id_fingerprint;
2419 if signed_by.contains(id_fp) {
2420 continue;
2422 }
2423 if authorities.contains(&id_fp) {
2424 signed_by.insert(*id_fp);
2425 }
2426 }
2427
2428 consensus_threshold(authorities.len()).contains(&signed_by.len())
2429 }
2430
2431 fn validate(&self, n_authorities: usize, certs: &[AuthCert]) -> Result<(), VerifyFailed> {
2438 self.verify_general(
2441 VerifyGeneralTrustedAuthorities::HazardouslyAssumeAllAuthCertsAreReal { n_authorities },
2442 certs,
2443 |tv| tv.verify(),
2444 )
2445 }
2446
2447 pub(crate) fn verify_general<E>(
2477 &self,
2478 trusted_authorities: VerifyGeneralTrustedAuthorities,
2479 certs: &[AuthCert],
2480 do_verify: impl Fn(ConsensusSignatureToVerify) -> Result<SignatureVerifiedIfIntended, E>,
2481 ) -> Result<(), E>
2482 where
2483 ConsensusVerifiabilityError: Into<E>,
2484 {
2485 use VerifyGeneralTrustedAuthorities as TA;
2486
2487 let mut ok: HashSet<RsaIdentity> = HashSet::new();
2491 let mut missing = HashSet::new();
2492 let mut verify_failed = Ok(());
2493
2494 for sig in &self.signatures {
2495 let Signature {
2497 digest_algo,
2498 key_ids:
2499 AuthCertKeyIds {
2500 id_fingerprint,
2501 sk_fingerprint: _,
2504 },
2505 signature: _,
2507 } = sig;
2508
2509 match trusted_authorities {
2510 TA::TrustThese { trusted } | TA::AnyOneOfThese { trusted } => {
2511 if !trusted.contains(id_fingerprint) {
2512 continue;
2513 }
2514 }
2515 TA::HazardouslyAssumeAllAuthCertsAreReal { .. } => {
2516 }
2518 }
2519
2520 if ok.contains(id_fingerprint) {
2521 continue;
2524 }
2525
2526 let Some(d) = self.hashes.hash_slice_for_verification(digest_algo) else {
2527 continue;
2530 };
2531
2532 let Some(tv) = sig.signature_to_verify(d, certs) else {
2533 missing.insert(sig.key_ids);
2534 continue;
2535 };
2536 match do_verify(tv) {
2537 Ok::<SignatureVerifiedIfIntended, _>(_) => {
2538 ok.insert(*id_fingerprint);
2539 }
2540 Err(e) => {
2541 verify_failed = Err(e);
2542 }
2543 }
2544 }
2545
2546 let n_authorities = match trusted_authorities {
2547 TA::TrustThese { trusted } => trusted.len(),
2548 TA::HazardouslyAssumeAllAuthCertsAreReal { n_authorities: n } => n,
2549 TA::AnyOneOfThese { .. } => {
2550 1
2554 }
2555 };
2556 let threshold = consensus_threshold(n_authorities);
2557
2558 if threshold.contains(&ok.len()) {
2559 Ok(())
2560 } else {
2561 verify_failed?;
2563
2564 Err(if missing.is_empty() {
2566 ConsensusVerifiabilityError::InsufficientTrustedSigners
2567 } else {
2568 let deficit = threshold.start - ok.len();
2569 ConsensusVerifiabilityError::MissingAuthCerts { missing, deficit }
2570 }
2571 .into())
2572 }
2573 }
2574}
2575
2576impl From<ConsensusVerifiabilityError> for VerifyFailed {
2577 fn from(cve: ConsensusVerifiabilityError) -> VerifyFailed {
2578 use ConsensusVerifiabilityError as CVE;
2579 use VerifyFailed as VF;
2580 match cve {
2581 CVE::InsufficientTrustedSigners => VF::InsufficientTrustedSigners,
2582 CVE::MissingAuthCerts { .. } => VF::InsufficientTrustedSigners,
2583 }
2584 }
2585}
2586
2587impl From<ConsensusVerifyFailed> for VerifyFailed {
2588 fn from(cvf: ConsensusVerifyFailed) -> VerifyFailed {
2589 use ConsensusVerifyFailed as CVF;
2590 use VerifyFailed as VF;
2591 match cvf {
2592 CVF::CertificationInsufficient { .. } => VF::InsufficientTrustedSigners,
2593 CVF::InvalidSignature { .. } => VF::VerifyFailed,
2594 }
2595 }
2596}
2597
2598#[cfg(test)]
2599mod test {
2600 #![allow(clippy::bool_assert_comparison)]
2602 #![allow(clippy::clone_on_copy)]
2603 #![allow(clippy::dbg_macro)]
2604 #![allow(clippy::mixed_attributes_style)]
2605 #![allow(clippy::print_stderr)]
2606 #![allow(clippy::print_stdout)]
2607 #![allow(clippy::single_char_pattern)]
2608 #![allow(clippy::unwrap_used)]
2609 #![allow(clippy::unchecked_time_subtraction)]
2610 #![allow(clippy::useless_vec)]
2611 #![allow(clippy::needless_pass_by_value)]
2612 #![allow(clippy::string_slice)] use super::*;
2615 use crate::doc::authcert::AuthCertUnverified;
2616 use crate::encode::{NetdocEncodable, NetdocEncodableFields};
2617 use crate::parse2::{ParseInput, parse_netdoc, parse_netdoc_multiple};
2618 use crate::util::regsub;
2619 use anyhow::Context as _;
2620 use assert_matches::assert_matches;
2621 use hex_literal::hex;
2622 use humantime::parse_rfc3339;
2623 use std::fmt::Debug;
2624 use std::fs;
2625 use std::time::Duration;
2626 use tor_checkable::Timebound;
2627
2628 const CERTS: &str = include_str!("../../testdata/authcerts2.txt");
2629 const CONSENSUS: &str = include_str!("../../testdata/mdconsensus1.txt");
2630
2631 const PLAIN_CERTS: &str = include_str!("../../testdata2/cached-certs");
2632 const PLAIN_CONSENSUS: &str = include_str!("../../testdata2/cached-consensus");
2633
2634 fn read_bad(fname: &str) -> String {
2635 use std::fs;
2636 use std::path::PathBuf;
2637 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
2638 path.push("testdata");
2639 path.push("bad-mdconsensus");
2640 path.push(fname);
2641
2642 fs::read_to_string(path).unwrap()
2643 }
2644
2645 #[test]
2646 fn parse_and_validate_md() -> crate::Result<()> {
2647 use std::net::SocketAddr;
2648 use tor_checkable::{SelfSigned, Timebound};
2649 let mut certs = Vec::new();
2650 for cert in AuthCert::parse_multiple(CERTS)? {
2651 let cert = cert?.check_signature()?.dangerously_assume_timely();
2652 certs.push(cert);
2653 }
2654 let auth_ids: Vec<_> = certs.iter().map(|c| c.id_fingerprint()).collect();
2655
2656 assert_eq!(certs.len(), 3);
2657
2658 let (_, _, consensus) = MdConsensus::parse(CONSENSUS)?;
2659 let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
2660
2661 assert!(consensus.authorities_are_correct(&auth_ids));
2663 assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
2665 {
2666 let bad_auth_id = (*b"xxxxxxxxxxxxxxxxxxxx").into();
2669 assert!(!consensus.authorities_are_correct(&[&bad_auth_id]));
2670 }
2671
2672 let missing = consensus.key_is_correct(&[]).err().unwrap();
2673 assert_eq!(3, missing.len());
2674 assert!(consensus.key_is_correct(&certs).is_ok());
2675 let missing = consensus.key_is_correct(&certs[0..1]).err().unwrap();
2676 assert_eq!(2, missing.len());
2677
2678 let same_three_times = vec![certs[0].clone(), certs[0].clone(), certs[0].clone()];
2680 let missing = consensus.key_is_correct(&same_three_times).err().unwrap();
2681
2682 assert_eq!(2, missing.len());
2683 assert!(consensus.is_well_signed(&same_three_times).is_err());
2684
2685 assert!(consensus.key_is_correct(&certs).is_ok());
2686 let consensus = consensus.check_signature(&certs)?;
2687
2688 assert_eq!(6, consensus.relays().len());
2689 let r0 = &consensus.relays()[0];
2690 assert_eq!(
2691 r0.md_digest(),
2692 &hex!("73dabe0a0468f4f7a67810a18d11e36731bb1d2ec3634db459100609f3b3f535")
2693 );
2694 assert_eq!(
2695 r0.rsa_identity().as_bytes(),
2696 &hex!("0a3057af2910415794d8ea430309d9ac5f5d524b")
2697 );
2698 assert!(!r0.weight().is_measured());
2699 assert!(!r0.weight().is_nonzero());
2700 let pv = &r0.protovers();
2701 assert!(pv.supports_subver("HSDir", 2));
2702 assert!(!pv.supports_subver("HSDir", 3));
2703 let ip4 = "127.0.0.1:5002".parse::<SocketAddr>().unwrap();
2704 let ip6 = "[::1]:5002".parse::<SocketAddr>().unwrap();
2705 assert!(r0.addrs().any(|a| a == ip4));
2706 assert!(r0.addrs().any(|a| a == ip6));
2707
2708 Ok(())
2709 }
2710
2711 #[test]
2712 fn parse_and_validate_ns() -> crate::Result<()> {
2713 use tor_checkable::{SelfSigned, Timebound};
2714 let mut certs = Vec::new();
2715 for cert in AuthCert::parse_multiple(PLAIN_CERTS)? {
2716 let cert = cert?.check_signature()?.dangerously_assume_timely();
2717 certs.push(cert);
2718 }
2719 let auth_ids: Vec<_> = certs.iter().map(|c| c.id_fingerprint()).collect();
2720 assert_eq!(certs.len(), 4);
2721
2722 let (_, _, consensus) = PlainConsensus::parse(PLAIN_CONSENSUS)?;
2723 let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
2724 assert!(consensus.authorities_are_correct(&auth_ids));
2726 assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
2728
2729 assert!(consensus.key_is_correct(&certs).is_ok());
2730
2731 let _consensus = consensus.check_signature(&certs)?;
2732
2733 Ok(())
2734 }
2735
2736 #[test]
2737 fn test_bad() {
2738 use crate::Pos;
2739 fn check(fname: &str, e: &Error) {
2740 let content = read_bad(fname);
2741 let res = MdConsensus::parse(&content);
2742 assert!(res.is_err());
2743 assert_eq!(&res.err().unwrap(), e);
2744 }
2745
2746 check(
2747 "bad-flags",
2748 &EK::BadArgument
2749 .at_pos(Pos::from_line(27, 1))
2750 .with_msg("Flags out of order"),
2751 );
2752 check(
2753 "bad-md-digest",
2754 &EK::BadArgument
2755 .at_pos(Pos::from_line(40, 3))
2756 .with_msg("Invalid base64"),
2757 );
2758 check(
2759 "bad-weight",
2760 &EK::BadArgument
2761 .at_pos(Pos::from_line(67, 141))
2762 .with_msg("invalid digit found in string"),
2763 );
2764 check(
2765 "bad-weights",
2766 &EK::BadArgument
2767 .at_pos(Pos::from_line(51, 13))
2768 .with_msg("invalid digit found in string"),
2769 );
2770 check(
2771 "wrong-order",
2772 &EK::WrongSortOrder.at_pos(Pos::from_line(52, 1)),
2773 );
2774 check(
2775 "wrong-start",
2776 &EK::UnexpectedToken
2777 .with_msg("vote-status")
2778 .at_pos(Pos::from_line(1, 1)),
2779 );
2780 check("wrong-version", &EK::BadDocumentVersion.with_msg("10"));
2781 }
2782
2783 fn gettok(s: &str) -> crate::Result<Item<'_, NetstatusKwd>> {
2784 let mut reader = NetDocReader::new(s)?;
2785 let tok = reader.next().unwrap();
2786 assert!(reader.next().is_none());
2787 tok
2788 }
2789
2790 #[test]
2791 fn test_weight() {
2792 let w = gettok("w Unmeasured=1 Bandwidth=6\n").unwrap();
2793 let w = RelayWeightsItem::from_item(&w).unwrap();
2794 assert!(!w.effective.is_measured());
2795 assert!(w.effective.is_nonzero());
2796
2797 let w = gettok("w Bandwidth=10\n").unwrap();
2798 let w = RelayWeightsItem::from_item(&w).unwrap();
2799 assert!(w.effective.is_measured());
2800 assert!(w.effective.is_nonzero());
2801
2802 let w = RelayWeightsItem::new_no_info();
2803 assert!(!w.effective.is_measured());
2804 assert!(!w.effective.is_nonzero());
2805
2806 let w = gettok("w Mustelid=66 Cheato=7 Unmeasured=1\n").unwrap();
2807 let w = RelayWeightsItem::from_item(&w).unwrap();
2808 assert!(!w.effective.is_measured());
2809 assert!(!w.effective.is_nonzero());
2810
2811 let w = gettok("r foo\n").unwrap();
2812 let w = RelayWeightsItem::from_item(&w);
2813 assert!(w.is_err());
2814
2815 let w = gettok("r Bandwidth=6 Unmeasured=Frog\n").unwrap();
2816 let w = RelayWeightsItem::from_item(&w);
2817 assert!(w.is_err());
2818
2819 let w = gettok("r Bandwidth=6 Unmeasured=3\n").unwrap();
2820 let w = RelayWeightsItem::from_item(&w);
2821 assert!(w.is_err());
2822 }
2823
2824 #[test]
2825 fn test_netparam() {
2826 let p = "Hello=600 Goodbye=5 Fred=7"
2827 .parse::<NetParams<u32>>()
2828 .unwrap();
2829 assert_eq!(p.get("Hello"), Some(&600_u32));
2830
2831 let p = "Hello=Goodbye=5 Fred=7".parse::<NetParams<u32>>();
2832 assert!(p.is_err());
2833
2834 let p = "Hello=Goodbye Fred=7".parse::<NetParams<u32>>();
2835 assert!(p.is_err());
2836
2837 for bad_kw in ["What=The", "", "\n", "\0"] {
2838 let p = [(bad_kw, 42)].into_iter().collect::<NetParams<i32>>();
2839 let mut d = NetdocEncoder::new();
2840 let d = (|| {
2841 let i = d.item("bad-psrams");
2842 p.write_item_value_onto(i)?;
2843 d.finish()
2844 })();
2845 let _: tor_error::Bug = d.expect_err(bad_kw);
2846 }
2847 }
2848
2849 #[test]
2850 fn test_sharedrand() {
2851 let sr =
2852 gettok("shared-rand-previous-value 9 5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4\n")
2853 .unwrap();
2854 let sr = SharedRandStatus::from_item(&sr).unwrap();
2855
2856 assert_eq!(sr.n_reveals, 9);
2857 assert_eq!(
2858 sr.value.0,
2859 hex!("e4ba1d638c96c458532adc6957dc0080d03d37c7e5854087d0da90bf5ff4e72e")
2860 );
2861 assert!(sr.timestamp.is_none());
2862
2863 let sr2 = gettok(
2864 "shared-rand-current-value 9 \
2865 5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4 2022-01-20T12:34:56\n",
2866 )
2867 .unwrap();
2868 let sr2 = SharedRandStatus::from_item(&sr2).unwrap();
2869 assert_eq!(sr2.n_reveals, sr.n_reveals);
2870 assert_eq!(sr2.value.0, sr.value.0);
2871 assert_eq!(
2872 sr2.timestamp.unwrap().0,
2873 humantime::parse_rfc3339("2022-01-20T12:34:56Z").unwrap()
2874 );
2875
2876 let sr = gettok("foo bar\n").unwrap();
2877 let sr = SharedRandStatus::from_item(&sr);
2878 assert!(sr.is_err());
2879 }
2880
2881 #[test]
2882 fn test_protostatus() {
2883 let my_protocols: Protocols = "Link=7 Cons=1-5 Desc=3-10".parse().unwrap();
2884
2885 let outcome = ProtoStatus {
2886 recommended: "Link=7".parse().unwrap(),
2887 required: "Desc=5".parse().unwrap(),
2888 }
2889 .check_protocols(&my_protocols);
2890 assert!(outcome.is_ok());
2891
2892 let outcome = ProtoStatus {
2893 recommended: "Microdesc=4 Link=7".parse().unwrap(),
2894 required: "Desc=5".parse().unwrap(),
2895 }
2896 .check_protocols(&my_protocols);
2897 assert_eq!(
2898 outcome,
2899 Err(ProtocolSupportError::MissingRecommended(
2900 "Microdesc=4".parse().unwrap()
2901 ))
2902 );
2903
2904 let outcome = ProtoStatus {
2905 recommended: "Microdesc=4 Link=7".parse().unwrap(),
2906 required: "Desc=5 Cons=5-12 Wombat=15".parse().unwrap(),
2907 }
2908 .check_protocols(&my_protocols);
2909 assert_eq!(
2910 outcome,
2911 Err(ProtocolSupportError::MissingRequired(
2912 "Cons=6-12 Wombat=15".parse().unwrap()
2913 ))
2914 );
2915 }
2916
2917 #[test]
2918 fn serialize_protostatus() {
2919 let ps = ProtoStatuses {
2920 client: ProtoStatus {
2921 recommended: "Link=1-5 LinkAuth=2-5".parse().unwrap(),
2922 required: "Link=5 LinkAuth=3".parse().unwrap(),
2923 },
2924 relay: ProtoStatus {
2925 recommended: "Wombat=20-30 Knish=20-30".parse().unwrap(),
2926 required: "Wombat=20-22 Knish=25-27".parse().unwrap(),
2927 },
2928 };
2929 let json = serde_json::to_string(&ps).unwrap();
2930 let ps2 = serde_json::from_str(json.as_str()).unwrap();
2931 assert_eq!(ps, ps2);
2932
2933 let ps3: ProtoStatuses = serde_json::from_str(
2934 r#"{
2935 "client":{
2936 "required":"Link=5 LinkAuth=3",
2937 "recommended":"Link=1-5 LinkAuth=2-5"
2938 },
2939 "relay":{
2940 "required":"Wombat=20-22 Knish=25-27",
2941 "recommended":"Wombat=20-30 Knish=20-30"
2942 }
2943 }"#,
2944 )
2945 .unwrap();
2946 assert_eq!(ps, ps3);
2947 }
2948
2949 #[test]
2951 #[cfg(feature = "incomplete")]
2952 fn verify_error_netstatus_vote() -> Result<(), anyhow::Error> {
2953 use VerifyFailed as VF;
2954 use VoteVerifyFailed as VVF;
2955 use vote::NetworkStatusUnverified as UV;
2956
2957 let file = "testdata2/v3-status-votes--1";
2958 let text = fs::read_to_string(file).with_context(|| file.to_owned())?;
2959 let input = ParseInput::new(&text, file);
2960 let doc: UV = parse_netdoc(&input)?;
2961 let trusted = [doc.peek_alleged_authority()];
2962
2963 let edit_body = |f: &dyn Fn(&mut _)| {
2964 let (mut body, sigs) = doc.clone().unwrap_unverified();
2965 f(&mut body);
2966 UV::from_parts(body, sigs)
2967 };
2968
2969 {
2971 let mut doc = doc.clone();
2972 for b in &mut doc.sigs.sigs.directory_signature.signature {
2973 *b = 0xff;
2974 }
2975 assert_matches! {
2976 doc.verify(&trusted),
2977 Err(VVF::InvalidSignature(VF::VerifyFailed))
2978 }
2979 }
2980
2981 {
2983 let doc = doc.clone();
2984 assert_matches! {
2985 doc.verify(&[[0x55; _].into()]),
2986 Err(VVF::InvalidSignature(VF::InsufficientTrustedSigners))
2987 }
2988 }
2989
2990 {
2992 let doc = edit_body(&|body| {
2993 body.authority.authority.dir_source.identity.0 = [0x55; _].into();
2994 });
2995 assert_matches! {
2996 doc.verify(&trusted),
2997 Err(VVF::AuthCertWrongAuthority)
2998 }
2999 }
3000
3001 let with_mutated_lifetime = |f: &dyn Fn(&mut Lifetime)| {
3003 let doc = edit_body(&|body| f(&mut body.preamble.lifetime));
3004 assert_matches! {
3005 doc.verify(&trusted),
3006 Err(VVF::AuthCertWrongValidity(_))
3007 }
3008 };
3009 let t_past = parse_rfc3339("1990-01-01T00:02:25Z")?;
3010 let t_future = parse_rfc3339("2010-01-01T00:02:25Z")?;
3011 with_mutated_lifetime(&|lifetime| lifetime.valid_after.0 = t_future);
3012 with_mutated_lifetime(&|lifetime| lifetime.fresh_until.0 = t_past);
3013 with_mutated_lifetime(&|lifetime| lifetime.valid_until.0 = t_past);
3014
3015 {
3017 let mut text = text.clone();
3018 regsub(&mut text, "^dir-key-expires ", "dir-key-expires-SABOTAGED ");
3019 let input = ParseInput::new(&text, file);
3020 let doc: UV = parse_netdoc(&input)?;
3021 assert_matches! {
3022 doc.verify(&trusted),
3023 Err(VVF::AuthCertParseError(..))
3024 }
3025 }
3026
3027 Ok(())
3028 }
3029
3030 #[cfg(feature = "incomplete")]
3043 fn roundtrip_netstatus<UV, V, VE>(
3044 file: &str,
3047 verify: impl FnOnce(UV, &[RsaIdentity], &[AuthCert]) -> Result<TimerangeBound<V>, VE>,
3048 adjust_now: Duration,
3049 ) -> anyhow::Result<()>
3050 where
3051 UV: NetdocParseable + NetdocParseableUnverified + MungeForRoundtrip,
3052 UV::Signatures: Clone + Debug + NetdocEncodableFields,
3053 VE: Debug + std::error::Error + Send + Sync + 'static,
3054 V: Debug + NetdocEncodable,
3055 {
3056 let text = fs::read_to_string(file).with_context(|| file.to_owned())?;
3057 let now = parse_rfc3339("2000-01-01T00:02:25Z")? + adjust_now;
3058
3059 let mut input = ParseInput::new(&text, file);
3060 input.retain_unknown_values();
3061
3062 let doc: UV = parse_netdoc(&input)?;
3063
3064 let certs = {
3065 let file = "testdata2/cached-certs";
3066 let text = fs::read_to_string(file)?;
3067 let input = ParseInput::new(&text, file);
3068 let certs: Vec<AuthCertUnverified> = parse_netdoc_multiple(&input)?;
3069 certs
3070 .into_iter()
3071 .map(|cert| cert.verify_selfcert(now))
3072 .collect::<Result<Vec<AuthCert>, _>>()?
3073 };
3074
3075 let sigs = doc.inspect_unverified().1.sigs.clone();
3076
3077 let doc = verify(
3078 doc,
3079 &certs.iter().map(|cert| *cert.fingerprint).collect_vec(),
3080 &certs,
3081 )?
3082 .check_valid_at(&now)?;
3083
3084 println!("{doc:?}");
3085
3086 let mut enc = NetdocEncoder::new();
3087 doc.encode_unsigned(&mut enc)?;
3088 sigs.encode_fields(&mut enc)?;
3089 let enc = enc.finish()?;
3090
3091 let mut exp: String = text.clone();
3092
3093 regsub(
3095 &mut exp,
3097 r#"^(shared-rand-.*)$"#,
3098 |c: ®ex::Captures| {
3099 let mut s = c[1].to_owned();
3100 regsub(&mut s, r#"="#, "");
3101 s
3102 },
3103 );
3104
3105 let mut regsub = |re, repl| regsub(&mut exp, re, repl);
3106
3107 regsub(
3109 r#"^((?:client|server)-versions) $"#,
3111 "$1",
3112 );
3113
3114 regsub(
3118 r#"(?x)
3119 ( ^ r\ .* \n ) # ( r ) $1, part before where we want to put m's
3120 ( (?: .* \n )*? ) # (.*? ) $2, the rest, before the m's
3121 ( (?: m\ .* \n )+ ) # ( m+ ) $3, one or more m's
3122 "#,
3123 r#"$1$3$2"#,
3124 );
3125
3126 UV::adjust_exp(&mut exp);
3127
3128 assert_eq_or_diff!(&exp, &enc);
3129
3130 Ok(())
3131 }
3132
3133 trait MungeForRoundtrip {
3134 fn adjust_exp(exp: &mut String);
3136 }
3137
3138 #[cfg(feature = "incomplete")]
3144 #[test]
3145 fn roundtrip_netstatus_plain() -> anyhow::Result<()> {
3146 roundtrip_netstatus::<plain::NetworkStatusUnverified, _, _>(
3147 "testdata2/cached-consensus",
3148 plain::NetworkStatusUnverified::verify,
3149 Duration::ZERO,
3150 )
3151 }
3152
3153 #[cfg(feature = "incomplete")]
3154 impl MungeForRoundtrip for plain::NetworkStatusUnverified {
3155 fn adjust_exp(exp: &mut String) {
3156 let mut regsub = |re, repl| regsub(exp, re, repl);
3157
3158 regsub(
3161 r#"^network-status-version 3$"#,
3162 "network-status-version 3 ns",
3163 );
3164
3165 regsub(
3169 r#"^(r \S+ \S+ \S+) \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}"#,
3170 "$1 2000-01-01 00:00:01",
3171 );
3172 }
3173 }
3174
3175 #[cfg(feature = "incomplete")]
3176 #[test]
3177 fn roundtrip_netstatus_md() -> anyhow::Result<()> {
3178 roundtrip_netstatus::<md::NetworkStatusUnverified, _, _>(
3179 "testdata2/cached-microdesc-consensus",
3180 md::NetworkStatusUnverified::verify,
3181 Duration::ZERO,
3182 )
3183 }
3184
3185 #[cfg(feature = "incomplete")]
3186 impl MungeForRoundtrip for md::NetworkStatusUnverified {
3187 fn adjust_exp(exp: &mut String) {
3188 let mut regsub = |re, repl| regsub(exp, re, repl);
3189
3190 regsub(
3196 r#"^(r \S+ \S+) \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}"#,
3197 "$1 2000-01-01 00:00:01",
3198 );
3199 }
3200 }
3201
3202 #[cfg(feature = "incomplete")]
3203 #[test]
3204 fn roundtrip_netstatus_vote() -> anyhow::Result<()> {
3205 roundtrip_netstatus::<vote::NetworkStatusUnverified, _, _>(
3206 "testdata2/v3-status-votes--1",
3207 |doc, trusted, _| vote::NetworkStatusUnverified::verify(doc, trusted),
3208 Duration::from_secs(20),
3209 )
3210 }
3211
3212 #[cfg(feature = "incomplete")]
3213 impl MungeForRoundtrip for vote::NetworkStatusUnverified {
3214 fn adjust_exp(exp: &mut String) {
3215 let stats_massage_entry = |e: &str| {
3219 let mut e = e.to_owned();
3220 if e.contains('.') {
3221 regsub(
3222 &mut e,
3223 r#"(?x)^ ( (?:wfu) = [0-9.]*? )( \.? 0+ ) $"#,
3225 "$1",
3226 );
3227 }
3228 e
3229 };
3230
3231 regsub(exp, r#"^stats (.+)$"#, |c: ®ex::Captures| -> String {
3233 format!(
3234 "stats {}",
3235 iter_join(" ", c[1].split(' ').sorted().map(stats_massage_entry)),
3236 )
3237 });
3238
3239 let mut regsub = |re: &_, repl| regsub(exp, re, repl);
3240
3241 regsub(
3243 r#"(?x)
3244 ^ (recommended-relay-protocols\ .*) \n
3245 (recommended-client-protocols\ .*) \n
3246 (required-relay-protocols\ .*) \n
3247 (required-client-protocols\ .*) \n
3248 (known-flags .*)$ \n
3249 "#,
3250 r#"$5
3251$2
3252$1
3253$4
3254$3
3255"#,
3256 );
3257
3258 regsub(
3264 r#"(?x) ^ (voting-delay\ .*) \n
3265 (known-flags\ .*) \n"#,
3266 "$1
3267client-versions
3268server-versions
3269$2
3270",
3271 );
3272
3273 for missing_field in [
3276 "bandwidth-file-headers", "bandwidth-file-digest", "flag-thresholds", ] {
3280 regsub(&format!(r#"^{missing_field} .*\n"#), "");
3281 }
3282 }
3283 }
3284}