1mod dir_source;
52mod rs;
53
54pub mod md;
55pub mod plain;
56pub mod vote;
57
58#[cfg(feature = "build_docs")]
59mod build;
60
61pub use proto_statuses_parse2_encode::ProtoStatusesNetdocParseAccumulator;
62
63use crate::doc::authcert::EncodedAuthCert;
64
65use crate::doc::authcert::{self, AuthCert, AuthCertKeyIds, AuthCertUnverified};
66use crate::encode::{
67 EncodeOrd, ItemArgument, ItemEncoder, ItemValueEncodable, NetdocEncodable, NetdocEncoder,
68};
69use crate::parse::keyword::Keyword;
70use crate::parse::parser::{Section, SectionRules, SectionRulesBuilder};
71use crate::parse::tokenize::{Item, ItemResult, NetDocReader};
72use crate::parse2::{
73 self, ArgumentError, ArgumentStream, ErrorProblem, IsStructural, ItemArgumentParseable,
74 ItemStream, ItemValueParseable, KeywordRef, NetdocParseable, NetdocParseableUnverified,
75 SignatureHashInputs, SignatureItemParseable, StopAt, UnparsedItem, VerifyFailed,
76};
77use crate::types::relay_flags::{self, DocRelayFlags};
78use crate::types::{self, *};
79use crate::util::PeekableIterator;
80use crate::{Error, KeywordEncodable, NetdocErrorKind as EK, NormalItemArgument, Pos};
81use std::collections::{BTreeSet, HashMap, HashSet};
82use std::fmt::{self, Display};
83use std::slice;
84use std::str::FromStr;
85use std::sync::Arc;
86use std::time::{self, SystemTime};
87use std::{net, result};
88use tor_basic_utils::iter_join;
89use tor_error::{Bug, HasKind, bad_api_usage, internal};
90use tor_protover::Protocols;
91use void::ResultVoidExt as _;
92
93use derive_deftly::{Deftly, define_derive_deftly};
94use digest::Digest;
95use itertools::Itertools;
96use saturating_time::SaturatingTime as _;
97use std::sync::LazyLock;
98use tor_checkable::{ExternallySigned, TimeBound, timed::TimeRangeBound};
99use tor_llcrypto as ll;
100use tor_llcrypto::pk::rsa::RsaIdentity;
101
102use serde::{Deserialize, Deserializer};
103
104#[cfg(feature = "build_docs")]
105pub use build::MdConsensusBuilder;
106#[cfg(feature = "build_docs")]
107pub use build::PlainConsensusBuilder;
108#[cfg(feature = "build_docs")]
109ns_export_each_flavor! {
110 ty: RouterStatusBuilder;
111}
112
113ns_export_each_variety! {
114 ty: Footer, RouterStatus, Preamble;
115}
116
117#[deprecated]
118pub use PlainConsensus as NsConsensus;
119#[deprecated]
120pub use PlainRouterStatus as NsRouterStatus;
121#[deprecated]
122pub use UncheckedPlainConsensus as UncheckedNsConsensus;
123#[deprecated]
124pub use UnvalidatedPlainConsensus as UnvalidatedNsConsensus;
125
126pub use rs::{RouterStatusMdDigestsVote, SoftwareVersion};
127
128pub use dir_source::{ConsensusAuthoritySection, DirSource, SupersededAuthorityKey};
129
130define_constant_string! {
131 NetworkStatusVersion = "3";
141}
142
143define_constant_string! {
144 VoteStatusConsensus = "consensus";
148}
149
150define_constant_string! {
151 VoteStatusVote = "vote";
155}
156
157#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Default)]
171#[allow(clippy::exhaustive_structs)]
172pub struct IgnoredPublicationTimeSp;
173
174#[derive(Clone, Debug, Deftly)]
182#[derive_deftly(Constructor, NetdocEncodableFields, NetdocParseableFields)]
183#[derive_deftly(Lifetime)]
184#[allow(clippy::exhaustive_structs)]
185pub struct Lifetime {
186 #[deftly(constructor)]
193 #[deftly(netdoc(single_arg))]
194 pub valid_after: Iso8601TimeSp,
195 #[deftly(constructor)]
203 #[deftly(netdoc(single_arg))]
204 pub fresh_until: Iso8601TimeSp,
205 #[deftly(constructor)]
213 #[deftly(netdoc(single_arg))]
214 pub valid_until: Iso8601TimeSp,
215
216 #[doc(hidden)]
217 #[deftly(netdoc(skip))]
218 pub __non_exhaustive: (),
219}
220
221define_derive_deftly! {
222 Lifetime:
224
225 ${defcond FIELD not(approx_equal($fname, __non_exhaustive))}
226
227 impl Lifetime {
228 pub fn new(
230 $( ${when FIELD} $fname: time::SystemTime, )
231 ) -> crate::Result<Self> {
232 let self_ = Lifetime {
236 $( ${when FIELD} $fname: $fname.into(), )
237 __non_exhaustive: (),
238 };
239 if self_.valid_after < self_.fresh_until && self_.fresh_until < self_.valid_until {
240 Ok(self_)
241 } else {
242 Err(EK::InvalidLifetime.err())
243 }
244 }
245 $(
246 ${when FIELD}
247
248 ${fattrs doc}
249 pub fn $fname(&self) -> time::SystemTime {
250 *self.$fname
251 }
252 )
253 pub fn valid_at(&self, when: time::SystemTime) -> bool {
255 *self.valid_after <= when && when <= *self.valid_until
256 }
257
258 pub fn voting_period(&self) -> time::Duration {
263 let valid_after = self.valid_after();
264 let fresh_until = self.fresh_until();
265 fresh_until
266 .duration_since(valid_after)
267 .expect("Mis-formed lifetime")
268 }
269 }
270}
271use derive_deftly_template_Lifetime;
272
273#[derive(Debug, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Copy)] #[derive(derive_more::From, derive_more::Into, derive_more::Display, derive_more::FromStr)]
285#[allow(clippy::exhaustive_structs)] pub struct ConsensusMethod(pub u32);
287impl NormalItemArgument for ConsensusMethod {}
288
289#[derive(Debug, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Deftly)]
296#[derive_deftly(ItemValueEncodable, ItemValueParseable)]
297#[non_exhaustive]
298pub struct ConsensusMethods {
299 pub methods: BTreeSet<ConsensusMethod>,
301}
302
303pub mod consensus_methods_comma_separated {
308 use super::*;
309 use parse2::ArgumentError as AE;
310 use std::result::Result;
311
312 pub fn from_args<'s>(args: &mut ArgumentStream<'s>) -> Result<ConsensusMethods, AE> {
314 let mut methods = BTreeSet::new();
315 for ent in args.next().ok_or(AE::Missing)?.split(',') {
316 let ent = ent.parse().map_err(|_| AE::Invalid)?;
317 if !methods.insert(ent) {
318 return Err(AE::Invalid);
319 }
320 }
321 Ok(ConsensusMethods { methods })
322 }
323
324 pub fn write_arg_onto(self_: &ConsensusMethods, out: &mut ItemEncoder) -> Result<(), Bug> {
326 out.args_raw_string(&iter_join(",", &self_.methods));
327 Ok(())
328 }
329}
330
331#[derive(Debug, Clone, Default, Eq, PartialEq)]
357pub struct NetParams<T> {
358 params: HashMap<String, T>,
360}
361
362impl<T> NetParams<T> {
363 #[allow(unused)]
365 pub fn new() -> Self {
366 NetParams {
367 params: HashMap::new(),
368 }
369 }
370 pub fn get<A: AsRef<str>>(&self, v: A) -> Option<&T> {
372 self.params.get(v.as_ref())
373 }
374 pub fn iter(&self) -> impl Iterator<Item = (&String, &T)> {
376 self.params.iter()
377 }
378 pub fn set(&mut self, k: String, v: T) {
380 self.params.insert(k, v);
381 }
382}
383
384impl<K: Into<String>, T> FromIterator<(K, T)> for NetParams<T> {
385 fn from_iter<I: IntoIterator<Item = (K, T)>>(i: I) -> Self {
386 NetParams {
387 params: i.into_iter().map(|(k, v)| (k.into(), v)).collect(),
388 }
389 }
390}
391
392impl<T> std::iter::Extend<(String, T)> for NetParams<T> {
393 fn extend<I: IntoIterator<Item = (String, T)>>(&mut self, iter: I) {
394 self.params.extend(iter);
395 }
396}
397
398impl<'de, T> Deserialize<'de> for NetParams<T>
399where
400 T: Deserialize<'de>,
401{
402 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
403 where
404 D: Deserializer<'de>,
405 {
406 let params = HashMap::deserialize(deserializer)?;
407 Ok(NetParams { params })
408 }
409}
410
411#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
420pub struct ProtoStatus {
421 recommended: Protocols,
426 required: Protocols,
431}
432
433impl ProtoStatus {
434 pub fn check_protocols(
444 &self,
445 supported_protocols: &Protocols,
446 ) -> Result<(), ProtocolSupportError> {
447 let missing_required = self.required.difference(supported_protocols);
449 if !missing_required.is_empty() {
450 return Err(ProtocolSupportError::MissingRequired(missing_required));
451 }
452 let missing_recommended = self.recommended.difference(supported_protocols);
453 if !missing_recommended.is_empty() {
454 return Err(ProtocolSupportError::MissingRecommended(
455 missing_recommended,
456 ));
457 }
458
459 Ok(())
460 }
461}
462
463#[derive(Clone, Debug, thiserror::Error)]
465#[cfg_attr(test, derive(PartialEq))]
466#[non_exhaustive]
467pub enum ProtocolSupportError {
468 #[error("Required protocols are not implemented: {0}")]
470 MissingRequired(Protocols),
471
472 #[error("Recommended protocols are not implemented: {0}")]
476 MissingRecommended(Protocols),
477}
478
479impl ProtocolSupportError {
480 pub fn should_shutdown(&self) -> bool {
482 matches!(self, Self::MissingRequired(_))
483 }
484}
485
486impl HasKind for ProtocolSupportError {
487 fn kind(&self) -> tor_error::ErrorKind {
488 tor_error::ErrorKind::SoftwareDeprecated
489 }
490}
491
492#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
499pub struct ProtoStatuses {
500 client: ProtoStatus,
502 relay: ProtoStatus,
504}
505
506impl ProtoStatuses {
507 pub fn client(&self) -> &ProtoStatus {
509 &self.client
510 }
511
512 pub fn relay(&self) -> &ProtoStatus {
514 &self.relay
515 }
516}
517
518#[derive(Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd)] #[derive(derive_more::Deref, derive_more::Into)]
539pub struct RecommendedTorVersions(BTreeSet<String>);
540
541#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
543#[non_exhaustive]
544pub enum InvalidRecommendedTorVersions {
545 #[error("version {_0:?} contains whitespace")]
547 ContainsWhitespace(String),
548
549 #[error("version {_0:?} is repeated")]
551 Repeated(String),
552}
553
554impl RecommendedTorVersions {
555 pub fn new_unknown() -> Self {
557 Self::default()
558 }
559
560 pub fn is_known(&self) -> bool {
566 !self.is_empty()
567 }
568
569 #[allow(clippy::should_implement_trait)] pub fn from_iter<I, S>(i: I) -> Result<Self, InvalidRecommendedTorVersions>
572 where
573 I: IntoIterator<Item = S>,
574 S: AsRef<str>,
575 {
576 let mut set = BTreeSet::new();
577 for v in i {
578 let v = v.as_ref();
579 if v.is_empty() {
580 continue;
581 }
582 if v.chars().any(|c| c.is_whitespace()) {
583 return Err(InvalidRecommendedTorVersions::ContainsWhitespace(
584 v.to_owned(),
585 ));
586 }
587 if !set.insert(v.to_owned()) {
588 return Err(InvalidRecommendedTorVersions::Repeated(v.to_owned()));
589 }
590 }
591 Ok(RecommendedTorVersions(set))
592 }
593}
594
595impl FromStr for RecommendedTorVersions {
596 type Err = InvalidRecommendedTorVersions;
597 fn from_str(s: &str) -> Result<Self, InvalidRecommendedTorVersions> {
598 Self::from_iter(s.split(','))
599 }
600}
601
602impl Display for RecommendedTorVersions {
603 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
604 write!(f, "{}", iter_join(",", &self.0))
605 }
606}
607
608impl NormalItemArgument for RecommendedTorVersions {}
609
610impl ItemValueEncodable for RecommendedTorVersions {
611 fn write_item_value_onto(&self, mut out: ItemEncoder) -> Result<(), Bug> {
612 out.args_raw_string(self);
613 Ok(())
614 }
615}
616
617impl ItemValueParseable for RecommendedTorVersions {
618 fn from_unparsed(mut item: UnparsedItem) -> Result<Self, ErrorProblem> {
619 const FIELD: &str = "versions";
620 item.check_no_object()?;
621 let args = item.args_mut();
622 let arg = args.next().unwrap_or("");
623 arg.parse::<Self>()
624 .map_err(|_| args.handle_error(FIELD, ArgumentError::Invalid))
625 }
626}
627
628#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
636#[allow(clippy::exhaustive_enums)]
637pub enum ConsensusFlavor {
638 Microdesc,
641 Plain,
646}
647
648impl ConsensusFlavor {
649 pub fn name(&self) -> &'static str {
651 match self {
652 ConsensusFlavor::Plain => "ns", ConsensusFlavor::Microdesc => "microdesc",
654 }
655 }
656 pub fn from_opt_name(name: Option<&str>) -> crate::Result<Self> {
661 match name {
662 Some("microdesc") => Ok(ConsensusFlavor::Microdesc),
663 Some("ns") | None => Ok(ConsensusFlavor::Plain),
664 Some(other) => {
665 Err(EK::BadDocumentType.with_msg(format!("unrecognized flavor {:?}", other)))
666 }
667 }
668 }
669}
670
671define_derive_deftly! {
672 DirectorySignaturesHashesAccu:
680
681 ${define FNAME ${paste ${snake_case $vname}} }
682
683 #[derive(Clone, Copy, Default, Debug, Eq, PartialEq, Deftly)]
685 #[derive_deftly(AsMutSelf)]
686 #[non_exhaustive]
687 pub struct DirectorySignaturesHashesAccu {
688 $(
689 ${vattrs doc}
690 pub $FNAME: Option<[u8; ${vmeta(hash_len) as expr}]>,
691 )
692
693 pub sha1_unnamed: Option<[u8; 20]>,
703 }
704
705 impl DirectorySignaturesHashesAccu {
706 fn update_from(
708 &mut self,
709 algo: &DigestAlgoInSignature,
710 body: &SignatureHashInputs,
711 ) {
712 ${define HASH {
715 self.$UPDATE.get_or_insert_with(|| {
717 let mut h = tor_llcrypto::d::$ALGO::new();
718 h.update(body.body().body());
719 h.update(body.signature_item_kw_spc);
720 h.finalize().into()
721 });
722 }}
723
724 match &**algo {
725 $(
726 Some(KeywordOrString::Known($vtype)) => {
727 ${define UPDATE $FNAME}
728 ${define ALGO $vname}
729 $HASH
730 }
731 )
732 None => {
733 ${define UPDATE sha1_unnamed}
734 ${define ALGO Sha1}
735 $HASH
736 }
737 Some(KeywordOrString::Unknown(..)) => {}
738 }
739 }
740
741 fn hash_slice_for_verification(
746 &self,
747 algo: &DigestAlgoInSignature,
748 ) -> Option<&[u8]> {
749 match &**algo {
750 $(
751 Some(KeywordOrString::Known($vtype)) => Some(self.$FNAME.as_ref()?),
752 )
753 None => Some(self.sha1_unnamed.as_ref()?),
754 Some(KeywordOrString::Unknown(..)) => None,
755 }
756 }
757 }
758}
759
760#[derive(Clone, Copy, Debug, Eq, PartialEq, strum::Display, strum::EnumString, Deftly)]
762#[derive_deftly(DirectorySignaturesHashesAccu)]
763#[non_exhaustive]
764#[strum(serialize_all = "snake_case")]
765pub enum DirectorySignatureHashAlgo {
766 #[deftly(hash_len = "20")]
768 Sha1,
769 #[deftly(hash_len = "32")]
771 Sha256,
772}
773
774#[derive(Debug, Clone, derive_more::Deref, derive_more::DerefMut)]
786#[allow(clippy::exhaustive_structs)]
787pub struct DigestAlgoInSignature(pub Option<KeywordOrString<DirectorySignatureHashAlgo>>);
788
789impl ItemArgumentParseable for DigestAlgoInSignature {
790 fn from_args<'s>(args: &mut ArgumentStream<'s>) -> Result<Self, ArgumentError> {
791 let v = if args
792 .clone()
793 .next()
794 .and_then(|s| s.chars().all(|c| c.is_ascii_hexdigit()).then_some(()))
798 .is_some()
799 {
800 None
802 } else {
803 Some(KeywordOrString::from_args(args)?)
804 };
805 Ok(DigestAlgoInSignature(v))
806 }
807}
808impl ItemArgument for DigestAlgoInSignature {
809 fn write_arg_onto(&self, out: &mut ItemEncoder<'_>) -> Result<(), Bug> {
810 if let Some(y) = &self.0 {
811 y.write_arg_onto(out)?;
812 }
813 Ok(())
814 }
815}
816impl DigestAlgoInSignature {
817 pub fn algorithm(&self) -> &KeywordOrString<DirectorySignatureHashAlgo> {
821 self.as_ref()
822 .unwrap_or(&KeywordOrString::Known(DirectorySignatureHashAlgo::Sha1))
823 }
824}
825
826impl NormalItemArgument for DirectorySignatureHashAlgo {}
827
828#[derive(Debug, Clone, Deftly)]
833#[derive_deftly(ItemValueEncodable, ItemValueParseable)]
834#[non_exhaustive]
835pub struct Signature {
836 pub digest_algo: DigestAlgoInSignature,
841 #[deftly(netdoc(with = authcert::keyids_directory_signature_args))]
844 pub key_ids: AuthCertKeyIds,
845 #[deftly(netdoc(object(label = "SIGNATURE"), with = types::raw_data_object))]
847 pub signature: Vec<u8>,
848}
849
850impl SignatureItemParseable for Signature {
851 type HashAccu = DirectorySignaturesHashesAccu;
852
853 fn from_unparsed_and_body(
854 item: UnparsedItem,
855 body: &SignatureHashInputs<'_>,
856 hash: &mut Self::HashAccu,
857 ) -> Result<Self, ErrorProblem> {
858 let signature = Signature::from_unparsed(item)?;
859 hash.update_from(&signature.digest_algo, body);
860 Ok(signature)
861 }
862}
863
864#[derive(Debug, Clone)]
870#[non_exhaustive]
871pub struct SignatureGroup {
872 pub hashes: DirectorySignaturesHashesAccu,
878 pub signatures: Vec<Signature>,
880}
881
882#[derive(Clone, Debug, thiserror::Error)]
893#[non_exhaustive]
894pub enum ConsensusVerifiabilityError {
895 #[error("consensus not signed by enough authorities")]
897 InsufficientTrustedSigners,
898
899 #[error("missing auth certs mean we could not verify enough consensuis signatures (need at least {deficit} more, out of {} that are missing)", missing.len())]
901 MissingAuthCerts {
902 deficit: usize,
904 missing: HashSet<AuthCertKeyIds>,
906 },
907}
908
909#[derive(Clone, Debug, thiserror::Error)]
921#[non_exhaustive]
922pub enum ConsensusVerifyFailed {
923 #[error("certs/sigs insufficient")]
925 CertificationInsufficient(#[from] ConsensusVerifiabilityError),
926
927 #[error("invalid signature")]
929 InvalidSignature(#[source] VerifyFailed),
934}
935
936#[derive(Clone, Debug, thiserror::Error)]
946#[non_exhaustive]
947pub enum VoteVerifyFailed {
948 #[error("invalid signature")]
950 InvalidSignature(#[source] VerifyFailed),
955
956 #[error("unparseable authcert")]
958 AuthCertParseError(#[source] parse2::ParseError),
959
960 #[error("authcert not valid for vote period")]
962 AuthCertWrongValidity(#[source] tor_checkable::TimeValidityError),
963
964 #[error("wrong authcert")]
966 AuthCertWrongAuthority,
967}
968
969#[derive(
971 Debug, Clone, Copy, Eq, PartialEq, derive_more::From, derive_more::Into, derive_more::AsRef,
972)]
973pub struct SharedRandVal([u8; 32]);
975
976#[derive(Debug, Clone, Deftly)]
979#[non_exhaustive]
980#[derive_deftly(ItemValueEncodable, ItemValueParseable)]
981pub struct SharedRandStatus {
982 pub n_reveals: u8,
984 pub value: SharedRandVal,
991
992 pub timestamp: Option<Iso8601TimeNoSp>,
996}
997
998#[derive(Debug, Clone, Default, Deftly)]
1005#[derive_deftly(Constructor, NetdocEncodableFields, NetdocParseableFields)]
1006#[allow(clippy::exhaustive_structs)]
1007pub struct SharedRandStatuses {
1008 pub shared_rand_previous_value: Option<SharedRandStatus>,
1010
1011 pub shared_rand_current_value: Option<SharedRandStatus>,
1013
1014 #[doc(hidden)]
1015 #[deftly(netdoc(skip))]
1016 pub __non_exhaustive: (),
1017}
1018
1019#[derive(Debug, Clone)]
1076pub struct RelayWeightsItem {
1077 effective: RelayWeight,
1079
1080 params: Unknown<Option<NetParams<u32>>>,
1082}
1083
1084#[non_exhaustive]
1088#[derive(Debug, Clone, Copy)]
1089pub enum RelayWeight {
1090 Unmeasured(u32),
1092 Measured(u32),
1094}
1095
1096#[derive(Debug, Clone, thiserror::Error)]
1098#[non_exhaustive]
1099pub enum InvalidRelayWeights {
1100 #[error("invalid value for Unmeasured")]
1102 InvalidUnmeasured,
1103}
1104
1105#[deprecated = "renamed to ConsensusAuthorityEntry"]
1107pub type ConsensusVoterInfo = ConsensusAuthorityEntry;
1108
1109pub type PlainAuthorityEntry = ConsensusAuthorityEntry;
1111pub type MdAuthorityEntry = ConsensusAuthorityEntry;
1113
1114#[derive(Debug, Clone, Deftly)]
1124#[derive_deftly(Constructor, NetdocEncodable, NetdocParseable)]
1125#[allow(clippy::exhaustive_structs)]
1126pub struct ConsensusAuthorityEntry {
1127 #[deftly(constructor)]
1129 pub dir_source: DirSource,
1130
1131 #[deftly(constructor)]
1137 pub contact: ContactInfo,
1138
1139 #[deftly(netdoc(single_arg))]
1146 #[deftly(constructor)]
1147 pub vote_digest: B16U,
1148
1149 #[doc(hidden)]
1150 #[deftly(netdoc(skip))]
1151 pub __non_exhaustive: (),
1152}
1153
1154#[derive(Debug, Clone, Deftly)]
1160#[derive_deftly(Constructor, NetdocEncodable, NetdocParseable)]
1161#[allow(clippy::exhaustive_structs)]
1162pub struct VoteAuthorityEntry {
1163 #[deftly(constructor)]
1165 pub dir_source: DirSource,
1166
1167 #[deftly(constructor)]
1169 pub contact: ContactInfo,
1170
1171 #[deftly(netdoc(single_arg))]
1175 pub legacy_dir_key: Option<Fingerprint>,
1176
1177 pub shared_rand_participate: Option<SharedRandParticipate>,
1181
1182 pub shared_rand_commit: Vec<SharedRandCommit>,
1186
1187 #[deftly(netdoc(flatten))]
1189 pub shared_rand: SharedRandStatuses,
1190
1191 #[doc(hidden)]
1192 #[deftly(netdoc(skip))]
1193 pub __non_exhaustive: (),
1194}
1195
1196#[derive(Debug, Clone, Deftly)]
1206#[derive_deftly(Constructor, ItemValueEncodable, ItemValueParseable)]
1207#[allow(clippy::exhaustive_structs)]
1208pub struct SharedRandParticipate {
1209 #[doc(hidden)]
1210 #[deftly(netdoc(skip))]
1211 pub __non_exhaustive: (),
1212}
1213
1214#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deftly)]
1218#[allow(clippy::exhaustive_enums)]
1220pub enum SharedRandCommit {
1221 V1(SharedRandCommitV1),
1223
1224 Unknown {},
1227}
1228
1229#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deftly)]
1238#[derive_deftly(Constructor, ItemValueEncodable, ItemValueParseable)]
1239#[allow(clippy::exhaustive_structs)]
1240pub struct SharedRandCommitV1 {
1241 #[deftly(constructor)]
1244 h_kp_auth_id_rsa: Fingerprint,
1245
1246 #[deftly(constructor)]
1254 commit: FixedB64<40>,
1255
1256 reveal: Option<FixedB64<40>>,
1261
1262 #[doc(hidden)]
1263 #[deftly(netdoc(skip))]
1264 pub __non_exhaustive: (),
1265}
1266
1267impl SharedRandCommitV1 {
1268 const FIXED_ARGUMENTS: &[&str] = &["1", "sha3-256"];
1270}
1271impl ItemValueEncodable for SharedRandCommit {
1272 fn write_item_value_onto(&self, mut out: ItemEncoder) -> Result<(), Bug> {
1273 match self {
1274 SharedRandCommit::V1(values) => {
1275 for fixed in SharedRandCommitV1::FIXED_ARGUMENTS {
1276 out.args_raw_string(fixed);
1277 }
1278 values.write_item_value_onto(out)
1279 }
1280 SharedRandCommit::Unknown {} => Err(internal!("encoding SharedRandCommit::Unknown")),
1281 }
1282 }
1283}
1284impl ItemValueParseable for SharedRandCommit {
1285 fn from_unparsed(mut item: UnparsedItem<'_>) -> Result<Self, ErrorProblem> {
1286 let mut fixed = SharedRandCommitV1::FIXED_ARGUMENTS.iter().copied();
1287 let args = item.args_mut();
1288 let version = args
1289 .next()
1290 .ok_or_else(|| args.handle_error("version", ArgumentError::Missing))?;
1291 if version != fixed.next().expect("nonempty") {
1292 return Ok(SharedRandCommit::Unknown {});
1293 }
1294 for exp in fixed {
1295 let got = args
1296 .next()
1297 .ok_or_else(|| args.handle_error(exp, ArgumentError::Missing))?;
1298 if got != exp {
1299 Err(args.handle_error(exp, ArgumentError::Invalid))?;
1300 }
1301 }
1302 let values = SharedRandCommitV1::from_unparsed(item)?;
1303 Ok(SharedRandCommit::V1(values))
1304 }
1305}
1306
1307define_derive_deftly! {
1310 VoteAuthoritySection:
1322
1323 ${defcond F_NORMAL not(fmeta(netdoc(skip)))}
1324
1325 impl NetdocParseable for VoteAuthoritySection {
1326 fn doctype_for_error() -> &'static str {
1327 "vote.authority.section"
1328 }
1329 fn is_intro_item_keyword(kw: KeywordRef<'_>) -> bool {
1330 VoteAuthorityEntry::is_intro_item_keyword(kw)
1331 }
1332 fn is_structural_keyword(kw: KeywordRef<'_>) -> Option<IsStructural> {
1333 $(
1334 ${when F_NORMAL}
1335 if let y @ Some(_) = $ftype::is_structural_keyword(kw) {
1336 return y;
1337 }
1338 )
1339 None
1340 }
1341 fn from_items<'s>(
1342 input: &mut ItemStream<'s>,
1343 stop_outer: stop_at!(),
1344 ) -> Result<Self, ErrorProblem> {
1345 let stop_inner = stop_outer
1346 $(
1347 ${when F_NORMAL}
1348 | StopAt($ftype::is_intro_item_keyword)
1349 )
1350 ;
1351 Ok(VoteAuthoritySection { $(
1352 ${when F_NORMAL}
1353 $fname: NetdocParseable::from_items(input, stop_inner)?,
1354 )
1355 __non_exhaustive: (),
1356 })
1357 }
1358 }
1359
1360 impl NetdocEncodable for VoteAuthoritySection {
1361 fn encode_unsigned(&self, out: &mut NetdocEncoder) -> Result<(), Bug> {
1362 $(
1363 ${when F_NORMAL}
1364 self.$fname.encode_unsigned(out)?;
1365 )
1366 Ok(())
1367 }
1368 }
1369}
1370
1371#[derive(Deftly, Clone, Debug)]
1378#[derive_deftly(VoteAuthoritySection, Constructor)]
1379#[allow(clippy::exhaustive_structs)]
1380pub struct VoteAuthoritySection {
1381 #[deftly(constructor)]
1383 pub authority: VoteAuthorityEntry,
1384
1385 #[deftly(constructor)]
1387 pub cert: EmbeddedCert<AuthCert, EncodedAuthCert>,
1388
1389 #[doc(hidden)]
1390 #[deftly(netdoc(skip))]
1391 pub __non_exhaustive: (),
1392}
1393
1394#[derive(Debug, Clone, Deftly)]
1400#[derive_deftly(Constructor, NetdocEncodableFields, NetdocParseableFields)]
1401#[allow(clippy::exhaustive_structs)]
1402pub struct ConsensusFooterFields {
1403 #[deftly(netdoc(default))]
1407 pub bandwidth_weights: NetParams<i32>,
1408
1409 #[doc(hidden)]
1410 #[deftly(netdoc(skip))]
1411 pub __non_exhaustive: (),
1412}
1413
1414pub type MdConsensus = md::Consensus;
1417
1418pub type UnvalidatedMdConsensus = md::UnvalidatedConsensus;
1421
1422pub type UncheckedMdConsensus = md::UncheckedConsensus;
1425
1426pub type PlainConsensus = plain::Consensus;
1429
1430pub type UnvalidatedPlainConsensus = plain::UnvalidatedConsensus;
1433
1434pub type UncheckedPlainConsensus = plain::UncheckedConsensus;
1437
1438decl_keyword! {
1439 #[non_exhaustive]
1444 #[allow(missing_docs)]
1445 pub NetstatusKwd {
1446 "network-status-version" => NETWORK_STATUS_VERSION,
1448 "vote-status" => VOTE_STATUS,
1449 "consensus-methods" => CONSENSUS_METHODS,
1450 "consensus-method" => CONSENSUS_METHOD,
1451 "published" => PUBLISHED,
1452 "valid-after" => VALID_AFTER,
1453 "fresh-until" => FRESH_UNTIL,
1454 "valid-until" => VALID_UNTIL,
1455 "voting-delay" => VOTING_DELAY,
1456 "client-versions" => CLIENT_VERSIONS,
1457 "server-versions" => SERVER_VERSIONS,
1458 "known-flags" => KNOWN_FLAGS,
1459 "flag-thresholds" => FLAG_THRESHOLDS,
1460 "recommended-client-protocols" => RECOMMENDED_CLIENT_PROTOCOLS,
1461 "required-client-protocols" => REQUIRED_CLIENT_PROTOCOLS,
1462 "recommended-relay-protocols" => RECOMMENDED_RELAY_PROTOCOLS,
1463 "required-relay-protocols" => REQUIRED_RELAY_PROTOCOLS,
1464 "params" => PARAMS,
1465 "bandwidth-file-headers" => BANDWIDTH_FILE_HEADERS,
1466 "bandwidth-file-digest" => BANDWIDTH_FILE_DIGEST,
1467 "shared-rand-previous-value" => SHARED_RAND_PREVIOUS_VALUE,
1471 "shared-rand-current-value" => SHARED_RAND_CURRENT_VALUE,
1472
1473 "dir-source" => DIR_SOURCE,
1475 "contact" => CONTACT,
1476
1477 "legacy-dir-key" => LEGACY_DIR_KEY,
1479 "shared-rand-participate" => SHARED_RAND_PARTICIPATE,
1480 "shared-rand-commit" => SHARED_RAND_COMMIT,
1481
1482 "vote-digest" => VOTE_DIGEST,
1484
1485 "dir-key-certificate-version" => DIR_KEY_CERTIFICATE_VERSION,
1487
1488 "r" => RS_R,
1490 "a" => RS_A,
1491 "s" => RS_S,
1492 "v" => RS_V,
1493 "pr" => RS_PR,
1494 "w" => RS_W,
1495 "p" => RS_P,
1496 "m" => RS_M,
1497 "id" => RS_ID,
1498
1499 "directory-footer" => DIRECTORY_FOOTER,
1501 "bandwidth-weights" => BANDWIDTH_WEIGHTS,
1502 "directory-signature" => DIRECTORY_SIGNATURE,
1503 }
1504}
1505
1506static NS_HEADER_RULES_COMMON_: LazyLock<SectionRulesBuilder<NetstatusKwd>> = LazyLock::new(|| {
1508 use NetstatusKwd::*;
1509 let mut rules = SectionRules::builder();
1510 rules.add(NETWORK_STATUS_VERSION.rule().required().args(1..=2));
1511 rules.add(VOTE_STATUS.rule().required().args(1..));
1512 rules.add(VALID_AFTER.rule().required());
1513 rules.add(FRESH_UNTIL.rule().required());
1514 rules.add(VALID_UNTIL.rule().required());
1515 rules.add(VOTING_DELAY.rule().args(2..));
1516 rules.add(CLIENT_VERSIONS.rule());
1517 rules.add(SERVER_VERSIONS.rule());
1518 rules.add(KNOWN_FLAGS.rule().required());
1519 rules.add(RECOMMENDED_CLIENT_PROTOCOLS.rule().args(1..));
1520 rules.add(RECOMMENDED_RELAY_PROTOCOLS.rule().args(1..));
1521 rules.add(REQUIRED_CLIENT_PROTOCOLS.rule().args(1..));
1522 rules.add(REQUIRED_RELAY_PROTOCOLS.rule().args(1..));
1523 rules.add(PARAMS.rule());
1524 rules
1525});
1526static NS_HEADER_RULES_CONSENSUS: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
1528 use NetstatusKwd::*;
1529 let mut rules = NS_HEADER_RULES_COMMON_.clone();
1530 rules.add(CONSENSUS_METHOD.rule().args(1..=1));
1531 rules.add(SHARED_RAND_PREVIOUS_VALUE.rule().args(2..));
1532 rules.add(SHARED_RAND_CURRENT_VALUE.rule().args(2..));
1533 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
1534 rules.build()
1535});
1536static NS_VOTERINFO_RULES_CONSENSUS: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
1567 use NetstatusKwd::*;
1568 let mut rules = SectionRules::builder();
1569 rules.add(DIR_SOURCE.rule().required().args(6..));
1570 rules.add(CONTACT.rule().required());
1571 rules.add(VOTE_DIGEST.rule().required());
1572 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
1573 rules.build()
1574});
1575static NS_ROUTERSTATUS_RULES_COMMON_: LazyLock<SectionRulesBuilder<NetstatusKwd>> =
1577 LazyLock::new(|| {
1578 use NetstatusKwd::*;
1579 let mut rules = SectionRules::builder();
1580 rules.add(RS_A.rule().may_repeat().args(1..));
1581 rules.add(RS_S.rule().required());
1582 rules.add(RS_V.rule());
1583 rules.add(RS_PR.rule().required());
1584 rules.add(RS_W.rule());
1585 rules.add(RS_P.rule().args(2..));
1586 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
1587 rules
1588 });
1589
1590static NS_ROUTERSTATUS_RULES_PLAIN: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
1592 use NetstatusKwd::*;
1593 let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
1594 rules.add(RS_R.rule().required().args(8..));
1595 rules.build()
1596});
1597
1598static NS_ROUTERSTATUS_RULES_MDCON: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
1611 use NetstatusKwd::*;
1612 let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
1613 rules.add(RS_R.rule().required().args(6..));
1614 rules.add(RS_M.rule().required().args(1..));
1615 rules.build()
1616});
1617static NS_FOOTER_RULES: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
1619 use NetstatusKwd::*;
1620 let mut rules = SectionRules::builder();
1621 rules.add(DIRECTORY_FOOTER.rule().required().no_args());
1622 rules.add(BANDWIDTH_WEIGHTS.rule());
1624 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
1625 rules.build()
1626});
1627
1628impl ProtoStatus {
1629 fn from_section(
1631 sec: &Section<'_, NetstatusKwd>,
1632 recommend_token: NetstatusKwd,
1633 required_token: NetstatusKwd,
1634 ) -> crate::Result<ProtoStatus> {
1635 fn parse(t: Option<&Item<'_, NetstatusKwd>>) -> crate::Result<Protocols> {
1637 if let Some(item) = t {
1638 item.args_as_str()
1639 .parse::<Protocols>()
1640 .map_err(|e| EK::BadArgument.at_pos(item.pos()).with_source(e))
1641 } else {
1642 Ok(Protocols::new())
1643 }
1644 }
1645
1646 let recommended = parse(sec.get(recommend_token))?;
1647 let required = parse(sec.get(required_token))?;
1648 Ok(ProtoStatus {
1649 recommended,
1650 required,
1651 })
1652 }
1653
1654 pub fn required_protocols(&self) -> &Protocols {
1661 &self.required
1662 }
1663
1664 pub fn recommended_protocols(&self) -> &Protocols {
1669 &self.recommended
1670 }
1671}
1672
1673impl<T> std::str::FromStr for NetParams<T>
1674where
1675 T: std::str::FromStr,
1676 T::Err: std::error::Error,
1677{
1678 type Err = Error;
1679 fn from_str(s: &str) -> crate::Result<Self> {
1680 fn parse_pair<U>(p: &str) -> crate::Result<(String, U)>
1682 where
1683 U: std::str::FromStr,
1684 U::Err: std::error::Error,
1685 {
1686 let parts: Vec<_> = p.splitn(2, '=').collect();
1687 if parts.len() != 2 {
1688 return Err(EK::BadArgument
1689 .at_pos(Pos::at(p))
1690 .with_msg("Missing = in key=value list"));
1691 }
1692 let num = parts[1].parse::<U>().map_err(|e| {
1693 EK::BadArgument
1694 .at_pos(Pos::at(parts[1]))
1695 .with_msg(e.to_string())
1696 })?;
1697 Ok((parts[0].to_string(), num))
1698 }
1699
1700 let params = s
1701 .split(' ')
1702 .filter(|p| !p.is_empty())
1703 .map(parse_pair)
1704 .try_collect()?;
1705 Ok(NetParams { params })
1706 }
1707}
1708
1709impl FromStr for SharedRandVal {
1710 type Err = Error;
1711 fn from_str(s: &str) -> crate::Result<Self> {
1712 let val: B64 = s.parse()?;
1713 let val = SharedRandVal(val.into_array()?);
1714 Ok(val)
1715 }
1716}
1717impl Display for SharedRandVal {
1718 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1719 Display::fmt(&B64::from(Vec::from(self.0)), f)
1720 }
1721}
1722impl NormalItemArgument for SharedRandVal {}
1723
1724impl SharedRandStatus {
1725 fn from_item(item: &Item<'_, NetstatusKwd>) -> crate::Result<Self> {
1728 match item.kwd() {
1729 NetstatusKwd::SHARED_RAND_PREVIOUS_VALUE | NetstatusKwd::SHARED_RAND_CURRENT_VALUE => {}
1730 _ => {
1731 return Err(Error::from(internal!(
1732 "wrong keyword {:?} on shared-random value",
1733 item.kwd()
1734 ))
1735 .at_pos(item.pos()));
1736 }
1737 }
1738 let n_reveals: u8 = item.parse_arg(0)?;
1739 let value: SharedRandVal = item.parse_arg(1)?;
1740 let timestamp = item.parse_optional_arg::<Iso8601TimeNoSp>(2)?;
1742 Ok(SharedRandStatus {
1743 n_reveals,
1744 value,
1745 timestamp,
1746 })
1747 }
1748
1749 pub fn value(&self) -> &SharedRandVal {
1751 &self.value
1752 }
1753
1754 pub fn timestamp(&self) -> Option<std::time::SystemTime> {
1756 self.timestamp.map(|t| t.0)
1757 }
1758}
1759
1760impl DirSource {
1761 fn from_item(item: &Item<'_, NetstatusKwd>) -> crate::Result<Self> {
1763 if item.kwd() != NetstatusKwd::DIR_SOURCE {
1764 return Err(
1765 Error::from(internal!("Bad keyword {:?} on dir-source", item.kwd()))
1766 .at_pos(item.pos()),
1767 );
1768 }
1769 let nickname = item
1770 .required_arg(0)?
1771 .parse()
1772 .map_err(|e: InvalidNickname| {
1773 EK::BadArgument.at_pos(item.pos()).with_msg(e.to_string())
1774 })?;
1775 let identity = item.parse_arg(1)?;
1776 let hostname = item
1777 .required_arg(2)?
1778 .parse()
1779 .map_err(|e: InvalidInternetHost| {
1780 EK::BadArgument.at_pos(item.pos()).with_msg(e.to_string())
1781 })?;
1782 let ip = item.parse_arg(3)?;
1783 let dir_port = item.parse_arg(4)?;
1784 let or_port = item.parse_arg(5)?;
1785
1786 Ok(DirSource {
1787 nickname,
1788 identity,
1789 hostname,
1790 ip,
1791 dir_port,
1792 or_port,
1793 __non_exhaustive: (),
1794 })
1795 }
1796}
1797
1798impl ConsensusAuthorityEntry {
1799 fn from_section(sec: &Section<'_, NetstatusKwd>) -> crate::Result<ConsensusAuthorityEntry> {
1801 use NetstatusKwd::*;
1802 #[allow(clippy::unwrap_used)]
1805 let first = sec.first_item().unwrap();
1806 if first.kwd() != DIR_SOURCE {
1807 return Err(Error::from(internal!(
1808 "Wrong keyword {:?} at start of voter info",
1809 first.kwd()
1810 ))
1811 .at_pos(first.pos()));
1812 }
1813 let dir_source = DirSource::from_item(sec.required(DIR_SOURCE)?)?;
1814
1815 let contact = sec.required(CONTACT)?;
1816 let contact = contact
1822 .args_as_str()
1823 .parse()
1824 .map_err(|err: InvalidContactInfo| {
1825 EK::BadArgument
1826 .with_msg(err.to_string())
1827 .at_pos(contact.pos())
1828 })?;
1829
1830 let vote_digest = sec.required(VOTE_DIGEST)?.parse_arg::<B16U>(0)?;
1831
1832 Ok(ConsensusAuthorityEntry {
1833 dir_source,
1834 contact,
1835 vote_digest,
1836 __non_exhaustive: (),
1837 })
1838 }
1839}
1840
1841impl RelayWeightsItem {
1842 pub fn new_no_info() -> Self {
1846 RelayWeightsItem {
1847 effective: RelayWeight::default(),
1848 params: Unknown::new_discard(),
1849 }
1850 }
1851
1852 pub fn from_effective(effective: RelayWeight) -> Self {
1854 RelayWeightsItem {
1855 effective,
1856 params: Unknown::new_discard(),
1857 }
1858 }
1859
1860 pub fn effective(&self) -> RelayWeight {
1867 self.effective
1868 }
1869
1870 pub fn params(&self) -> Unknown<&Option<NetParams<u32>>> {
1878 self.params.as_ref()
1879 }
1880
1881 fn from_item(item: &Item<'_, NetstatusKwd>) -> crate::Result<RelayWeightsItem> {
1883 if item.kwd() != NetstatusKwd::RS_W {
1884 return Err(
1885 Error::from(internal!("Wrong keyword {:?} on W line", item.kwd()))
1886 .at_pos(item.pos()),
1887 );
1888 }
1889
1890 let params = item.args_as_str().parse()?;
1891 let effective = RelayWeight::from_net_params(¶ms).map_err(|e| e.at_pos(item.pos()))?;
1892
1893 Ok(RelayWeightsItem {
1894 effective,
1895 params: Unknown::new_discard(),
1896 })
1897 }
1898
1899 const KEYWORD: &str = "w";
1901}
1902
1903#[cfg(feature = "retain-unknown")]
1904impl Default for RelayWeightsItem {
1905 fn default() -> Self {
1906 RelayWeightsItem {
1907 effective: RelayWeight::default(),
1908 params: Unknown::Retained(None),
1909 }
1910 }
1911}
1912
1913impl RelayWeight {
1914 pub fn is_measured(&self) -> bool {
1916 matches!(self, RelayWeight::Measured(_))
1917 }
1918
1919 pub fn is_nonzero(&self) -> bool {
1921 !matches!(self, RelayWeight::Unmeasured(0) | RelayWeight::Measured(0))
1922 }
1923
1924 fn from_net_params(params: &NetParams<u32>) -> crate::Result<RelayWeight> {
1928 params
1929 .try_into()
1930 .map_err(|e: InvalidRelayWeights| EK::BadArgument.with_msg(e.to_string()))
1931 }
1932}
1933
1934impl Default for RelayWeight {
1935 fn default() -> RelayWeight {
1936 RelayWeight::Unmeasured(0)
1937 }
1938}
1939
1940impl TryFrom<&NetParams<u32>> for RelayWeight {
1941 type Error = InvalidRelayWeights;
1942
1943 fn try_from(params: &NetParams<u32>) -> Result<RelayWeight, InvalidRelayWeights> {
1944 let bw = params.params.get("Bandwidth");
1945 let unmeas = params.params.get("Unmeasured");
1946
1947 let bw = match bw {
1948 None => return Ok(RelayWeight::Unmeasured(0)),
1949 Some(b) => *b,
1950 };
1951
1952 match unmeas {
1953 None | Some(0) => Ok(RelayWeight::Measured(bw)),
1954 Some(1) => Ok(RelayWeight::Unmeasured(bw)),
1955 _ => Err(InvalidRelayWeights::InvalidUnmeasured),
1956 }
1957 }
1958}
1959
1960#[cfg(feature = "retain-unknown")]
1961impl TryFrom<NetParams<u32>> for RelayWeightsItem {
1962 type Error = InvalidRelayWeights;
1963
1964 fn try_from(params: NetParams<u32>) -> Result<RelayWeightsItem, InvalidRelayWeights> {
1965 Ok(RelayWeightsItem {
1966 effective: (¶ms).try_into()?,
1967 params: Unknown::Retained(Some(params)),
1968 })
1969 }
1970}
1971
1972mod parse2_impls {
1976 use super::*;
1977 pub(super) use parse2::{
1978 ArgumentError as AE, ArgumentStream, ErrorProblem as EP, ItemArgumentParseable,
1979 ItemValueParseable, NetdocParseableFields,
1980 };
1981 use std::result::Result;
1982
1983 impl<T: FromStr + NormalItemArgument> ItemValueParseable for NetParams<T>
1985 where
1986 T::Err: std::error::Error,
1987 {
1988 fn from_unparsed(item: parse2::UnparsedItem<'_>) -> Result<Self, EP> {
1989 item.check_no_object()?;
1990 item.args_copy()
1991 .into_remaining()
1992 .parse()
1993 .map_err(item.invalid_argument_handler("parameters"))
1994 }
1995 }
1996
1997 impl NetdocParseableFields for RelayWeightsItem {
1998 type Accumulator = Option<NetParams<u32>>;
1999
2000 fn is_item_keyword(kw: KeywordRef) -> bool {
2001 kw == Self::KEYWORD
2002 }
2003
2004 fn accumulate_item(acc: &mut Self::Accumulator, item: UnparsedItem) -> Result<(), EP> {
2005 if acc.is_some() {
2006 return Err(EP::ItemRepeated);
2007 }
2008 item.check_no_object()?;
2009 let params = NetParams::from_unparsed(item)?;
2010 *acc = Some(params);
2011 Ok(())
2012 }
2013
2014 fn finish(params: Self::Accumulator, items: &ItemStream) -> Result<Self, EP> {
2015 let effective = params
2016 .as_ref()
2017 .map(TryFrom::try_from)
2018 .transpose()
2019 .map_err(|_| EP::OtherBadDocument("invalid information in `w` item"))?
2020 .unwrap_or_default();
2021
2022 let params = items.parse_options().retain_unknown_values.map(|()| params);
2023
2024 Ok(RelayWeightsItem { effective, params })
2025 }
2026 }
2027
2028 impl ItemValueParseable for rs::SoftwareVersion {
2029 fn from_unparsed(mut item: parse2::UnparsedItem<'_>) -> Result<Self, EP> {
2030 item.check_no_object()?;
2031 item.args_mut()
2032 .into_remaining()
2033 .parse()
2034 .map_err(item.invalid_argument_handler("version"))
2035 }
2036 }
2037
2038 impl ItemArgumentParseable for IgnoredPublicationTimeSp {
2039 fn from_args(a: &mut ArgumentStream) -> Result<IgnoredPublicationTimeSp, AE> {
2040 let mut next_arg = || a.next().ok_or(AE::Missing);
2041 let _: &str = next_arg()?;
2042 let _: &str = next_arg()?;
2043 Ok(IgnoredPublicationTimeSp)
2044 }
2045 }
2046}
2047
2048mod encode_impls {
2052 use super::*;
2053 use std::result::Result;
2054 pub(crate) use {
2055 crate::encode::{ItemEncoder, ItemValueEncodable, NetdocEncodableFields},
2056 tor_error::Bug,
2057 };
2058
2059 impl NetdocEncodableFields for RelayWeightsItem {
2060 fn encode_fields(&self, out: &mut NetdocEncoder) -> Result<(), Bug> {
2061 if let Some(w) = self.params.as_ref().into_retained()? {
2062 w.write_item_value_onto(out.item(Self::KEYWORD))?;
2063 }
2064 Ok(())
2065 }
2066 }
2067
2068 impl<T: NormalItemArgument + Ord + Display> ItemValueEncodable for NetParams<T> {
2070 fn write_item_value_onto(&self, mut out: ItemEncoder) -> Result<(), Bug> {
2071 for (k, v) in self.iter().collect::<BTreeSet<_>>() {
2072 if k.is_empty()
2073 || k.chars()
2074 .any(|c| c.is_whitespace() || c.is_control() || c == '=')
2075 {
2076 return Err(bad_api_usage!(
2078 "tried to encode NetParms with unreasonable keyword {k:?}"
2079 ));
2080 }
2081 out.args_raw_string(&format_args!("{k}={v}"));
2082 }
2083 Ok(())
2084 }
2085 }
2086
2087 impl ItemValueEncodable for rs::SoftwareVersion {
2088 fn write_item_value_onto(&self, mut out: ItemEncoder) -> Result<(), Bug> {
2089 out.args_raw_string(self);
2090 Ok(())
2091 }
2092 }
2093
2094 impl ItemArgument for IgnoredPublicationTimeSp {
2095 fn write_arg_onto(&self, out: &mut ItemEncoder) -> Result<(), Bug> {
2096 out.args_raw_string(&"2000-01-01 00:00:01");
2097 Ok(())
2098 }
2099 }
2100}
2101
2102impl ConsensusFooterFields {
2103 fn from_section(sec: &Section<'_, NetstatusKwd>) -> crate::Result<ConsensusFooterFields> {
2105 use NetstatusKwd::*;
2106 sec.required(DIRECTORY_FOOTER)?;
2107
2108 let bandwidth_weights = sec
2109 .maybe(BANDWIDTH_WEIGHTS)
2110 .args_as_str()
2111 .unwrap_or("")
2112 .parse()?;
2113
2114 Ok(ConsensusFooterFields {
2115 bandwidth_weights,
2116 __non_exhaustive: (),
2117 })
2118 }
2119}
2120
2121mod proto_statuses_parse2_encode {
2125 use super::encode_impls::*;
2126 use super::parse2_impls::*;
2127 use super::*;
2128 use paste::paste;
2129 use std::result::Result;
2130
2131 macro_rules! impl_proto_statuses { { $( $rr:ident $cr:ident; )* } => { paste! {
2145 #[derive(Deftly)]
2146 #[derive_deftly(NetdocParseableFields)]
2147 #[allow(unreachable_pub)]
2149 pub struct ProtoStatusesParseHelper {
2150 $(
2151 #[deftly(netdoc(default))]
2152 [<$rr _ $cr _protocols>]: Protocols,
2153 )*
2154 }
2155
2156 pub use ProtoStatusesParseHelperNetdocParseAccumulator
2158 as ProtoStatusesNetdocParseAccumulator;
2159
2160 impl NetdocParseableFields for ProtoStatuses {
2161 type Accumulator = ProtoStatusesNetdocParseAccumulator;
2162 fn is_item_keyword(kw: KeywordRef<'_>) -> bool {
2163 ProtoStatusesParseHelper::is_item_keyword(kw)
2164 }
2165 fn accumulate_item(
2166 acc: &mut Self::Accumulator,
2167 item: UnparsedItem<'_>,
2168 ) -> Result<(), EP> {
2169 ProtoStatusesParseHelper::accumulate_item(acc, item)
2170 }
2171 fn finish(acc: Self::Accumulator, items: &ItemStream<'_>) -> Result<Self, EP> {
2172 let parse = ProtoStatusesParseHelper::finish(acc, items)?;
2173 let mut out = ProtoStatuses::default();
2174 $(
2175 out.$cr.$rr = parse.[< $rr _ $cr _protocols >];
2176 )*
2177 Ok(out)
2178 }
2179 }
2180
2181 impl NetdocEncodableFields for ProtoStatuses {
2182 fn encode_fields(&self, out: &mut NetdocEncoder) -> Result<(), Bug> {
2183 $(
2184 self.$cr.$rr.write_item_value_onto(
2185 out.item(concat!(stringify!($rr), "-", stringify!($cr), "-protocols"))
2186 )?;
2187 )*
2188 Ok(())
2189 }
2190 }
2191 } } }
2192
2193 impl_proto_statuses! {
2194 recommended client;
2195 recommended relay;
2196 required client;
2197 required relay;
2198 }
2199}
2200
2201impl Signature {
2202 fn from_item(item: &Item<'_, NetstatusKwd>) -> crate::Result<Signature> {
2204 if item.kwd() != NetstatusKwd::DIRECTORY_SIGNATURE {
2205 return Err(Error::from(internal!(
2206 "Wrong keyword {:?} for directory signature",
2207 item.kwd()
2208 ))
2209 .at_pos(item.pos()));
2210 }
2211
2212 let (digest_algo, id_fp, sk_fp) = if item.n_args() > 2 {
2213 (
2214 item.required_arg(0)?,
2215 item.required_arg(1)?,
2216 item.required_arg(2)?,
2217 )
2218 } else {
2219 ("sha1", item.required_arg(0)?, item.required_arg(1)?)
2221 };
2222
2223 let digest_algo = digest_algo.to_string().parse().void_unwrap();
2224 let digest_algo = DigestAlgoInSignature(Some(digest_algo));
2225 let id_fingerprint = id_fp.parse::<Fingerprint>()?.into();
2226 let sk_fingerprint = sk_fp.parse::<Fingerprint>()?.into();
2227 let key_ids = AuthCertKeyIds {
2228 id_fingerprint,
2229 sk_fingerprint,
2230 };
2231 let signature = item.obj("SIGNATURE")?;
2232
2233 Ok(Signature {
2234 digest_algo,
2235 key_ids,
2236 signature,
2237 })
2238 }
2239
2240 fn matches_cert(&self, cert: &AuthCert) -> bool {
2243 cert.key_ids() == self.key_ids
2244 }
2245
2246 fn find_cert<'a>(&self, certs: &'a [AuthCert]) -> Option<&'a AuthCert> {
2249 certs.iter().find(|&c| self.matches_cert(c))
2250 }
2251
2252 fn signature_to_verify<'r>(
2256 &'r self,
2257 signed_digest: &'r [u8],
2258 certs: &'r [AuthCert],
2259 ) -> Option<ConsensusSignatureToVerify> {
2260 let cert = self.find_cert(certs)?;
2261 let key = cert.signing_key();
2262 Some(ConsensusSignatureToVerify {
2263 key,
2264 signed_digest,
2265 signature: &self.signature,
2266 })
2267 }
2268}
2269
2270impl EncodeOrd for Signature {
2271 fn encode_cmp(&self, other: &Self) -> std::cmp::Ordering {
2272 let k: for<'s> fn(&'_ Signature) -> (&'_ _, &'_ _) = |s| (&s.key_ids, &s.signature);
2273 Ord::cmp(&k(self), &k(other))
2274 }
2275}
2276
2277#[derive(Debug, Clone, Copy)]
2283struct ConsensusSignatureToVerify<'r> {
2284 key: &'r ll::pk::rsa::PublicKey,
2286
2287 signed_digest: &'r [u8],
2289
2290 signature: &'r [u8],
2292}
2293
2294pub(crate) struct SignatureVerifiedIfIntended {}
2303
2304impl<'r> ConsensusSignatureToVerify<'r> {
2305 fn verify(self) -> Result<SignatureVerifiedIfIntended, VerifyFailed> {
2308 self.key.verify(self.signed_digest, self.signature)?;
2309 Ok(SignatureVerifiedIfIntended {})
2310 }
2311}
2312
2313#[derive(Debug, Clone, Copy)]
2317pub(crate) enum VerifyGeneralTrustedAuthorities<'r> {
2318 TrustThese {
2320 trusted: &'r [RsaIdentity],
2322 },
2323
2324 AnyOneOfThese {
2326 trusted: &'r [RsaIdentity],
2328 },
2329
2330 HazardouslyAssumeAllAuthCertsAreReal {
2335 n_authorities: usize,
2339 },
2340}
2341
2342pub fn consensus_threshold(n_authorities: usize) -> std::ops::RangeFrom<usize> {
2372 (n_authorities / 2) + 1 ..
2374}
2375
2376impl SignatureGroup {
2377 fn list_missing(&self, certs: &[AuthCert]) -> (usize, Vec<&Signature>) {
2384 let mut ok: HashSet<RsaIdentity> = HashSet::new();
2385 let mut missing = Vec::new();
2386 for sig in &self.signatures {
2387 let id_fingerprint = &sig.key_ids.id_fingerprint;
2388 if ok.contains(id_fingerprint) {
2389 continue;
2390 }
2391 if sig.find_cert(certs).is_some() {
2392 ok.insert(*id_fingerprint);
2393 continue;
2394 }
2395
2396 missing.push(sig);
2397 }
2398 (ok.len(), missing)
2399 }
2400
2401 fn could_validate(&self, authorities: &[&RsaIdentity]) -> bool {
2405 let mut signed_by: HashSet<RsaIdentity> = HashSet::new();
2406 for sig in &self.signatures {
2407 let id_fp = &sig.key_ids.id_fingerprint;
2408 if signed_by.contains(id_fp) {
2409 continue;
2411 }
2412 if authorities.contains(&id_fp) {
2413 signed_by.insert(*id_fp);
2414 }
2415 }
2416
2417 consensus_threshold(authorities.len()).contains(&signed_by.len())
2418 }
2419
2420 fn validate(&self, n_authorities: usize, certs: &[AuthCert]) -> Result<(), VerifyFailed> {
2427 self.verify_general(
2430 VerifyGeneralTrustedAuthorities::HazardouslyAssumeAllAuthCertsAreReal { n_authorities },
2431 certs,
2432 |tv| tv.verify(),
2433 )
2434 }
2435
2436 fn verify_general<E>(
2465 &self,
2466 trusted_authorities: VerifyGeneralTrustedAuthorities,
2467 certs: &[AuthCert],
2468 do_verify: impl Fn(ConsensusSignatureToVerify) -> Result<SignatureVerifiedIfIntended, E>,
2469 ) -> Result<(), E>
2470 where
2471 ConsensusVerifiabilityError: Into<E>,
2472 {
2473 use VerifyGeneralTrustedAuthorities as TA;
2474
2475 let mut ok: HashSet<RsaIdentity> = HashSet::new();
2479 let mut missing = HashSet::new();
2480 let mut verify_failed = Ok(());
2481
2482 for sig in &self.signatures {
2483 let Signature {
2485 digest_algo,
2486 key_ids:
2487 AuthCertKeyIds {
2488 id_fingerprint,
2489 sk_fingerprint: _,
2492 },
2493 signature: _,
2495 } = sig;
2496
2497 match trusted_authorities {
2498 TA::TrustThese { trusted } | TA::AnyOneOfThese { trusted } => {
2499 if !trusted.contains(id_fingerprint) {
2500 continue;
2501 }
2502 }
2503 TA::HazardouslyAssumeAllAuthCertsAreReal { .. } => {
2504 }
2506 }
2507
2508 if ok.contains(id_fingerprint) {
2509 continue;
2512 }
2513
2514 let Some(d) = self.hashes.hash_slice_for_verification(digest_algo) else {
2515 continue;
2518 };
2519
2520 let Some(tv) = sig.signature_to_verify(d, certs) else {
2521 missing.insert(sig.key_ids);
2522 continue;
2523 };
2524 match do_verify(tv) {
2525 Ok::<SignatureVerifiedIfIntended, _>(_) => {
2526 ok.insert(*id_fingerprint);
2527 }
2528 Err(e) => {
2529 verify_failed = Err(e);
2530 }
2531 }
2532 }
2533
2534 let n_authorities = match trusted_authorities {
2535 TA::TrustThese { trusted } => trusted.len(),
2536 TA::HazardouslyAssumeAllAuthCertsAreReal { n_authorities: n } => n,
2537 TA::AnyOneOfThese { .. } => {
2538 1
2542 }
2543 };
2544 let threshold = consensus_threshold(n_authorities);
2545
2546 if threshold.contains(&ok.len()) {
2547 Ok(())
2548 } else {
2549 verify_failed?;
2551
2552 Err(if missing.is_empty() {
2554 ConsensusVerifiabilityError::InsufficientTrustedSigners
2555 } else {
2556 let deficit = threshold.start - ok.len();
2557 ConsensusVerifiabilityError::MissingAuthCerts { missing, deficit }
2558 }
2559 .into())
2560 }
2561 }
2562}
2563
2564impl From<ConsensusVerifiabilityError> for VerifyFailed {
2565 fn from(cve: ConsensusVerifiabilityError) -> VerifyFailed {
2566 use ConsensusVerifiabilityError as CVE;
2567 use VerifyFailed as VF;
2568 match cve {
2569 CVE::InsufficientTrustedSigners => VF::InsufficientTrustedSigners,
2570 CVE::MissingAuthCerts { .. } => VF::InsufficientTrustedSigners,
2571 }
2572 }
2573}
2574
2575impl From<ConsensusVerifyFailed> for VerifyFailed {
2576 fn from(cvf: ConsensusVerifyFailed) -> VerifyFailed {
2577 use ConsensusVerifyFailed as CVF;
2578 use VerifyFailed as VF;
2579 match cvf {
2580 CVF::CertificationInsufficient { .. } => VF::InsufficientTrustedSigners,
2581 CVF::InvalidSignature { .. } => VF::VerifyFailed,
2582 }
2583 }
2584}
2585
2586#[cfg(test)]
2587mod test {
2588 #![allow(clippy::bool_assert_comparison)]
2590 #![allow(clippy::clone_on_copy)]
2591 #![allow(clippy::dbg_macro)]
2592 #![allow(clippy::mixed_attributes_style)]
2593 #![allow(clippy::print_stderr)]
2594 #![allow(clippy::print_stdout)]
2595 #![allow(clippy::single_char_pattern)]
2596 #![allow(clippy::unwrap_used)]
2597 #![allow(clippy::unchecked_time_subtraction)]
2598 #![allow(clippy::useless_vec)]
2599 #![allow(clippy::needless_pass_by_value)]
2600 #![allow(clippy::string_slice)] use super::*;
2603 use crate::doc::authcert::AuthCertUnverified;
2604 use crate::encode::{NetdocEncodable, NetdocEncodableFields};
2605 use crate::parse2::{ParseInput, parse_netdoc, parse_netdoc_multiple};
2606 use crate::test_support::regsub;
2607 use anyhow::Context as _;
2608 use assert_matches::assert_matches;
2609 use hex_literal::hex;
2610 use humantime::parse_rfc3339;
2611 use std::fmt::Debug;
2612 use std::fs;
2613 use std::time::Duration;
2614 use tor_checkable::TimeBound;
2615
2616 const CERTS: &str = include_str!("../../testdata/authcerts2.txt");
2617 const CONSENSUS: &str = include_str!("../../testdata/mdconsensus1.txt");
2618
2619 const PLAIN_CERTS: &str = include_str!("../../testdata2/cached-certs");
2620 const PLAIN_CONSENSUS: &str = include_str!("../../testdata2/cached-consensus");
2621
2622 fn read_bad(fname: &str) -> String {
2623 use std::fs;
2624 use std::path::PathBuf;
2625 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
2626 path.push("testdata");
2627 path.push("bad-mdconsensus");
2628 path.push(fname);
2629
2630 fs::read_to_string(path).unwrap()
2631 }
2632
2633 #[test]
2634 fn parse_and_validate_md() -> crate::Result<()> {
2635 use std::net::SocketAddr;
2636 use tor_checkable::{SelfSigned, TimeBound};
2637 let mut certs = Vec::new();
2638 for cert in AuthCert::parse_multiple(CERTS)? {
2639 let cert = cert?.check_signature()?.dangerously_assume_timely();
2640 certs.push(cert);
2641 }
2642 let auth_ids: Vec<_> = certs.iter().map(|c| c.id_fingerprint()).collect();
2643
2644 assert_eq!(certs.len(), 3);
2645
2646 let (_, _, consensus) = MdConsensus::parse(CONSENSUS)?;
2647 let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
2648
2649 assert!(consensus.authorities_are_correct(&auth_ids));
2651 assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
2653 {
2654 let bad_auth_id = (*b"xxxxxxxxxxxxxxxxxxxx").into();
2657 assert!(!consensus.authorities_are_correct(&[&bad_auth_id]));
2658 }
2659
2660 let missing = consensus.key_is_correct(&[]).err().unwrap();
2661 assert_eq!(3, missing.len());
2662 assert!(consensus.key_is_correct(&certs).is_ok());
2663 let missing = consensus.key_is_correct(&certs[0..1]).err().unwrap();
2664 assert_eq!(2, missing.len());
2665
2666 let same_three_times = vec![certs[0].clone(), certs[0].clone(), certs[0].clone()];
2668 let missing = consensus.key_is_correct(&same_three_times).err().unwrap();
2669
2670 assert_eq!(2, missing.len());
2671 assert!(consensus.is_well_signed(&same_three_times).is_err());
2672
2673 assert!(consensus.key_is_correct(&certs).is_ok());
2674 let consensus = consensus.check_signature(&certs)?;
2675
2676 assert_eq!(6, consensus.relays().len());
2677 let r0 = &consensus.relays()[0];
2678 assert_eq!(
2679 r0.md_digest(),
2680 &hex!("73dabe0a0468f4f7a67810a18d11e36731bb1d2ec3634db459100609f3b3f535")
2681 );
2682 assert_eq!(
2683 r0.rsa_identity().as_bytes(),
2684 &hex!("0a3057af2910415794d8ea430309d9ac5f5d524b")
2685 );
2686 assert!(!r0.weight().is_measured());
2687 assert!(!r0.weight().is_nonzero());
2688 let pv = &r0.protovers();
2689 assert!(pv.supports_subver("HSDir", 2));
2690 assert!(!pv.supports_subver("HSDir", 3));
2691 let ip4 = "127.0.0.1:5002".parse::<SocketAddr>().unwrap();
2692 let ip6 = "[::1]:5002".parse::<SocketAddr>().unwrap();
2693 assert!(r0.addrs().any(|a| a == ip4));
2694 assert!(r0.addrs().any(|a| a == ip6));
2695
2696 Ok(())
2697 }
2698
2699 #[test]
2700 fn parse_and_validate_ns() -> crate::Result<()> {
2701 use tor_checkable::{SelfSigned, TimeBound};
2702 let mut certs = Vec::new();
2703 for cert in AuthCert::parse_multiple(PLAIN_CERTS)? {
2704 let cert = cert?.check_signature()?.dangerously_assume_timely();
2705 certs.push(cert);
2706 }
2707 let auth_ids: Vec<_> = certs.iter().map(|c| c.id_fingerprint()).collect();
2708 assert_eq!(certs.len(), 4);
2709
2710 let (_, _, consensus) = PlainConsensus::parse(PLAIN_CONSENSUS)?;
2711 let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
2712 assert!(consensus.authorities_are_correct(&auth_ids));
2714 assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
2716
2717 assert!(consensus.key_is_correct(&certs).is_ok());
2718
2719 let _consensus = consensus.check_signature(&certs)?;
2720
2721 Ok(())
2722 }
2723
2724 #[test]
2725 fn test_bad() {
2726 use crate::Pos;
2727 fn check(fname: &str, e: &Error) {
2728 let content = read_bad(fname);
2729 let res = MdConsensus::parse(&content);
2730 assert!(res.is_err());
2731 assert_eq!(&res.err().unwrap(), e);
2732 }
2733
2734 check(
2735 "bad-flags",
2736 &EK::BadArgument
2737 .at_pos(Pos::from_line(27, 1))
2738 .with_msg("Flags out of order"),
2739 );
2740 check(
2741 "bad-md-digest",
2742 &EK::BadArgument
2743 .at_pos(Pos::from_line(40, 3))
2744 .with_msg("Invalid base64"),
2745 );
2746 check(
2747 "bad-weight",
2748 &EK::BadArgument
2749 .at_pos(Pos::from_line(67, 141))
2750 .with_msg("invalid digit found in string"),
2751 );
2752 check(
2753 "bad-weights",
2754 &EK::BadArgument
2755 .at_pos(Pos::from_line(51, 13))
2756 .with_msg("invalid digit found in string"),
2757 );
2758 check(
2759 "wrong-order",
2760 &EK::WrongSortOrder.at_pos(Pos::from_line(52, 1)),
2761 );
2762 check(
2763 "wrong-start",
2764 &EK::UnexpectedToken
2765 .with_msg("vote-status")
2766 .at_pos(Pos::from_line(1, 1)),
2767 );
2768 check("wrong-version", &EK::BadDocumentVersion.with_msg("10"));
2769 }
2770
2771 fn gettok(s: &str) -> crate::Result<Item<'_, NetstatusKwd>> {
2772 let mut reader = NetDocReader::new(s)?;
2773 let tok = reader.next().unwrap();
2774 assert!(reader.next().is_none());
2775 tok
2776 }
2777
2778 #[test]
2779 fn test_weight() {
2780 let w = gettok("w Unmeasured=1 Bandwidth=6\n").unwrap();
2781 let w = RelayWeightsItem::from_item(&w).unwrap();
2782 assert!(!w.effective.is_measured());
2783 assert!(w.effective.is_nonzero());
2784
2785 let w = gettok("w Bandwidth=10\n").unwrap();
2786 let w = RelayWeightsItem::from_item(&w).unwrap();
2787 assert!(w.effective.is_measured());
2788 assert!(w.effective.is_nonzero());
2789
2790 let w = RelayWeightsItem::new_no_info();
2791 assert!(!w.effective.is_measured());
2792 assert!(!w.effective.is_nonzero());
2793
2794 let w = gettok("w Mustelid=66 Cheato=7 Unmeasured=1\n").unwrap();
2795 let w = RelayWeightsItem::from_item(&w).unwrap();
2796 assert!(!w.effective.is_measured());
2797 assert!(!w.effective.is_nonzero());
2798
2799 let w = gettok("r foo\n").unwrap();
2800 let w = RelayWeightsItem::from_item(&w);
2801 assert!(w.is_err());
2802
2803 let w = gettok("r Bandwidth=6 Unmeasured=Frog\n").unwrap();
2804 let w = RelayWeightsItem::from_item(&w);
2805 assert!(w.is_err());
2806
2807 let w = gettok("r Bandwidth=6 Unmeasured=3\n").unwrap();
2808 let w = RelayWeightsItem::from_item(&w);
2809 assert!(w.is_err());
2810 }
2811
2812 #[test]
2813 fn test_netparam() {
2814 let p = "Hello=600 Goodbye=5 Fred=7"
2815 .parse::<NetParams<u32>>()
2816 .unwrap();
2817 assert_eq!(p.get("Hello"), Some(&600_u32));
2818
2819 let p = "Hello=Goodbye=5 Fred=7".parse::<NetParams<u32>>();
2820 assert!(p.is_err());
2821
2822 let p = "Hello=Goodbye Fred=7".parse::<NetParams<u32>>();
2823 assert!(p.is_err());
2824
2825 for bad_kw in ["What=The", "", "\n", "\0"] {
2826 let p = [(bad_kw, 42)].into_iter().collect::<NetParams<i32>>();
2827 let mut d = NetdocEncoder::new();
2828 let d = (|| {
2829 let i = d.item("bad-psrams");
2830 p.write_item_value_onto(i)?;
2831 d.finish()
2832 })();
2833 let _: tor_error::Bug = d.expect_err(bad_kw);
2834 }
2835 }
2836
2837 #[test]
2838 fn test_sharedrand() {
2839 let sr =
2840 gettok("shared-rand-previous-value 9 5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4\n")
2841 .unwrap();
2842 let sr = SharedRandStatus::from_item(&sr).unwrap();
2843
2844 assert_eq!(sr.n_reveals, 9);
2845 assert_eq!(
2846 sr.value.0,
2847 hex!("e4ba1d638c96c458532adc6957dc0080d03d37c7e5854087d0da90bf5ff4e72e")
2848 );
2849 assert!(sr.timestamp.is_none());
2850
2851 let sr2 = gettok(
2852 "shared-rand-current-value 9 \
2853 5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4 2022-01-20T12:34:56\n",
2854 )
2855 .unwrap();
2856 let sr2 = SharedRandStatus::from_item(&sr2).unwrap();
2857 assert_eq!(sr2.n_reveals, sr.n_reveals);
2858 assert_eq!(sr2.value.0, sr.value.0);
2859 assert_eq!(
2860 sr2.timestamp.unwrap().0,
2861 humantime::parse_rfc3339("2022-01-20T12:34:56Z").unwrap()
2862 );
2863
2864 let sr = gettok("foo bar\n").unwrap();
2865 let sr = SharedRandStatus::from_item(&sr);
2866 assert!(sr.is_err());
2867 }
2868
2869 #[test]
2870 fn test_protostatus() {
2871 let my_protocols: Protocols = "Link=7 Cons=1-5 Desc=3-10".parse().unwrap();
2872
2873 let outcome = ProtoStatus {
2874 recommended: "Link=7".parse().unwrap(),
2875 required: "Desc=5".parse().unwrap(),
2876 }
2877 .check_protocols(&my_protocols);
2878 assert!(outcome.is_ok());
2879
2880 let outcome = ProtoStatus {
2881 recommended: "Microdesc=4 Link=7".parse().unwrap(),
2882 required: "Desc=5".parse().unwrap(),
2883 }
2884 .check_protocols(&my_protocols);
2885 assert_eq!(
2886 outcome,
2887 Err(ProtocolSupportError::MissingRecommended(
2888 "Microdesc=4".parse().unwrap()
2889 ))
2890 );
2891
2892 let outcome = ProtoStatus {
2893 recommended: "Microdesc=4 Link=7".parse().unwrap(),
2894 required: "Desc=5 Cons=5-12 Wombat=15".parse().unwrap(),
2895 }
2896 .check_protocols(&my_protocols);
2897 assert_eq!(
2898 outcome,
2899 Err(ProtocolSupportError::MissingRequired(
2900 "Cons=6-12 Wombat=15".parse().unwrap()
2901 ))
2902 );
2903 }
2904
2905 #[test]
2906 fn serialize_protostatus() {
2907 let ps = ProtoStatuses {
2908 client: ProtoStatus {
2909 recommended: "Link=1-5 LinkAuth=2-5".parse().unwrap(),
2910 required: "Link=5 LinkAuth=3".parse().unwrap(),
2911 },
2912 relay: ProtoStatus {
2913 recommended: "Wombat=20-30 Knish=20-30".parse().unwrap(),
2914 required: "Wombat=20-22 Knish=25-27".parse().unwrap(),
2915 },
2916 };
2917 let json = serde_json::to_string(&ps).unwrap();
2918 let ps2 = serde_json::from_str(json.as_str()).unwrap();
2919 assert_eq!(ps, ps2);
2920
2921 let ps3: ProtoStatuses = serde_json::from_str(
2922 r#"{
2923 "client":{
2924 "required":"Link=5 LinkAuth=3",
2925 "recommended":"Link=1-5 LinkAuth=2-5"
2926 },
2927 "relay":{
2928 "required":"Wombat=20-22 Knish=25-27",
2929 "recommended":"Wombat=20-30 Knish=20-30"
2930 }
2931 }"#,
2932 )
2933 .unwrap();
2934 assert_eq!(ps, ps3);
2935 }
2936
2937 #[test]
2939 fn verify_error_netstatus_vote() -> Result<(), anyhow::Error> {
2940 use VerifyFailed as VF;
2941 use VoteVerifyFailed as VVF;
2942 use vote::NetworkStatusUnverified as UV;
2943
2944 let file = "testdata2/v3-status-votes--1";
2945 let text = fs::read_to_string(file).with_context(|| file.to_owned())?;
2946 let input = ParseInput::new(&text, file);
2947 let doc: UV = parse_netdoc(&input)?;
2948 let trusted = [doc.peek_alleged_authority()];
2949
2950 let edit_body = |f: &dyn Fn(&mut _)| {
2951 let (mut body, sigs) = doc.clone().unwrap_unverified();
2952 f(&mut body);
2953 UV::from_parts(body, sigs)
2954 };
2955
2956 {
2958 let mut doc = doc.clone();
2959 doc.sigs.sigs.directory_signature.signature.fill(0xff);
2960 assert_matches! {
2961 doc.verify(&trusted),
2962 Err(VVF::InvalidSignature(VF::VerifyFailed))
2963 }
2964 }
2965
2966 {
2968 let doc = doc.clone();
2969 assert_matches! {
2970 doc.verify(&[[0x55; _].into()]),
2971 Err(VVF::InvalidSignature(VF::InsufficientTrustedSigners))
2972 }
2973 }
2974
2975 {
2977 let doc = edit_body(&|body| {
2978 body.authority.authority.dir_source.identity.0 = [0x55; _].into();
2979 });
2980 assert_matches! {
2981 doc.verify(&trusted),
2982 Err(VVF::AuthCertWrongAuthority)
2983 }
2984 }
2985
2986 let with_mutated_lifetime = |f: &dyn Fn(&mut Lifetime)| {
2988 let doc = edit_body(&|body| f(&mut body.preamble.lifetime));
2989 assert_matches! {
2990 doc.verify(&trusted),
2991 Err(VVF::AuthCertWrongValidity(_))
2992 }
2993 };
2994 let t_past = parse_rfc3339("1990-01-01T00:02:25Z")?;
2995 let t_future = parse_rfc3339("2010-01-01T00:02:25Z")?;
2996 with_mutated_lifetime(&|lifetime| lifetime.valid_after.0 = t_future);
2997 with_mutated_lifetime(&|lifetime| lifetime.fresh_until.0 = t_past);
2998 with_mutated_lifetime(&|lifetime| lifetime.valid_until.0 = t_past);
2999
3000 {
3002 let mut text = text.clone();
3003 regsub(&mut text, "^dir-key-expires ", "dir-key-expires-SABOTAGED ");
3004 let input = ParseInput::new(&text, file);
3005 let doc: UV = parse_netdoc(&input)?;
3006 assert_matches! {
3007 doc.verify(&trusted),
3008 Err(VVF::AuthCertParseError(..))
3009 }
3010 }
3011
3012 Ok(())
3013 }
3014
3015 #[cfg(feature = "retain-unknown")]
3016 #[allow(clippy::type_complexity)]
3017 pub(super) fn prep_netstatus_verify<UV: NetdocParseable>(
3018 file: &str,
3019 ) -> anyhow::Result<(UV, String, Vec<AuthCert>, Vec<RsaIdentity>, SystemTime)> {
3020 let text = fs::read_to_string(file).with_context(|| file.to_owned())?;
3021 let now = parse_rfc3339("2000-01-01T00:02:25Z")?;
3022
3023 let mut input = ParseInput::new(&text, file);
3024 input.retain_unknown_values();
3025
3026 let doc: UV = parse_netdoc(&input)?;
3027
3028 let certs = {
3029 let file = "testdata2/cached-certs";
3030 let text = fs::read_to_string(file)?;
3031 let input = ParseInput::new(&text, file);
3032 let certs: Vec<AuthCertUnverified> = parse_netdoc_multiple(&input)?;
3033 certs
3034 .into_iter()
3035 .map(|cert| cert.verify_selfcert(now))
3036 .collect::<Result<Vec<AuthCert>, _>>()?
3037 };
3038
3039 let authorities = certs.iter().map(|cert| *cert.fingerprint).collect_vec();
3040
3041 Ok((doc, text, certs, authorities, now))
3042 }
3043
3044 #[cfg(feature = "retain-unknown")]
3057 fn roundtrip_netstatus<UV, V, VE>(
3058 file: &str,
3061 verify: impl FnOnce(UV, &[RsaIdentity], &[AuthCert]) -> Result<TimeRangeBound<V>, VE>,
3062 adjust_now: Duration,
3063 ) -> anyhow::Result<()>
3064 where
3065 UV: NetdocParseable + NetdocParseableUnverified + MungeForRoundtrip,
3066 UV::Signatures: Clone + Debug + NetdocEncodableFields,
3067 VE: Debug + std::error::Error + Send + Sync + 'static,
3068 V: Debug + NetdocEncodable,
3069 {
3070 let (doc, text, certs, authorities, now) = prep_netstatus_verify::<UV>(file)?;
3071
3072 let now = now + adjust_now;
3073
3074 let sigs = doc.inspect_unverified().1.sigs.clone();
3075
3076 let doc = verify(doc, &authorities, &certs)?.if_valid_at(&now)?;
3077
3078 println!("{doc:?}");
3079
3080 let mut enc = NetdocEncoder::new();
3081 doc.encode_unsigned(&mut enc)?;
3082 sigs.encode_fields(&mut enc)?;
3083 let enc = enc.finish()?;
3084
3085 let mut exp: String = text.clone();
3086
3087 regsub(
3089 &mut exp,
3091 r#"^(shared-rand-.*)$"#,
3092 |c: ®ex::Captures| {
3093 let mut s = c[1].to_owned();
3094 regsub(&mut s, r#"="#, "");
3095 s
3096 },
3097 );
3098
3099 regsub(
3102 &mut exp,
3104 r#"^(client|server)-versions (.+)$"#,
3105 |c: ®ex::Captures| -> String {
3106 format!(
3107 "{}-versions {}",
3108 &c[1],
3109 iter_join(",", c[2].split(',').sorted()),
3110 )
3111 },
3112 );
3113
3114 let mut regsub = |re, repl| regsub(&mut exp, re, repl);
3115
3116 regsub(
3118 r#"^((?:client|server)-versions) $"#,
3120 "$1",
3121 );
3122
3123 regsub(
3127 r#"(?x)
3128 ( ^ r\ .* \n ) # ( r ) $1, part before where we want to put m's
3129 ( (?: .* \n )*? ) # (.*? ) $2, the rest, before the m's
3130 ( (?: m\ .* \n )+ ) # ( m+ ) $3, one or more m's
3131 "#,
3132 r#"$1$3$2"#,
3133 );
3134
3135 UV::adjust_exp(&mut exp);
3136
3137 assert_eq_or_diff!(&exp, &enc);
3138
3139 Ok(())
3140 }
3141
3142 trait MungeForRoundtrip {
3143 fn adjust_exp(exp: &mut String);
3145 }
3146
3147 #[cfg(feature = "retain-unknown")]
3151 #[test]
3152 fn roundtrip_netstatus_plain() -> anyhow::Result<()> {
3153 roundtrip_netstatus::<plain::NetworkStatusUnverified, _, _>(
3154 "testdata2/cached-consensus",
3155 plain::NetworkStatusUnverified::verify,
3156 Duration::ZERO,
3157 )
3158 }
3159
3160 impl MungeForRoundtrip for plain::NetworkStatusUnverified {
3161 fn adjust_exp(exp: &mut String) {
3162 let mut regsub = |re, repl| regsub(exp, re, repl);
3163
3164 regsub(
3167 r#"^network-status-version 3$"#,
3168 "network-status-version 3 ns",
3169 );
3170
3171 regsub(
3175 r#"^(r \S+ \S+ \S+) \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}"#,
3176 "$1 2000-01-01 00:00:01",
3177 );
3178 }
3179 }
3180
3181 #[cfg(feature = "retain-unknown")]
3182 #[test]
3183 fn roundtrip_netstatus_md() -> anyhow::Result<()> {
3184 roundtrip_netstatus::<md::NetworkStatusUnverified, _, _>(
3185 "testdata2/cached-microdesc-consensus",
3186 md::NetworkStatusUnverified::verify,
3187 Duration::ZERO,
3188 )
3189 }
3190
3191 impl MungeForRoundtrip for md::NetworkStatusUnverified {
3192 fn adjust_exp(exp: &mut String) {
3193 let mut regsub = |re, repl| regsub(exp, re, repl);
3194
3195 regsub(
3201 r#"^(r \S+ \S+) \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}"#,
3202 "$1 2000-01-01 00:00:01",
3203 );
3204 }
3205 }
3206
3207 #[cfg(feature = "retain-unknown")]
3208 #[test]
3209 fn roundtrip_netstatus_vote() -> anyhow::Result<()> {
3210 roundtrip_netstatus::<vote::NetworkStatusUnverified, _, _>(
3211 "testdata2/v3-status-votes--1",
3212 |doc, trusted, _| vote::NetworkStatusUnverified::verify(doc, trusted),
3213 Duration::from_secs(20),
3214 )
3215 }
3216
3217 impl MungeForRoundtrip for vote::NetworkStatusUnverified {
3218 fn adjust_exp(exp: &mut String) {
3219 let stats_massage_entry = |e: &str| {
3223 let mut e = e.to_owned();
3224 if e.contains('.') {
3225 regsub(
3226 &mut e,
3227 r#"(?x)^ ( (?:wfu) = [0-9.]*? )( \.? 0+ ) $"#,
3229 "$1",
3230 );
3231 }
3232 e
3233 };
3234
3235 regsub(exp, r#"^stats (.+)$"#, |c: ®ex::Captures| -> String {
3237 format!(
3238 "stats {}",
3239 iter_join(" ", c[1].split(' ').sorted().map(stats_massage_entry)),
3240 )
3241 });
3242
3243 let mut regsub = |re: &_, repl| regsub(exp, re, repl);
3244
3245 regsub(
3247 r#"(?x)
3248 ^ (recommended-relay-protocols\ .*) \n
3249 (recommended-client-protocols\ .*) \n
3250 (required-relay-protocols\ .*) \n
3251 (required-client-protocols\ .*) \n
3252 (known-flags .*)$ \n
3253 "#,
3254 r#"$5
3255$2
3256$1
3257$4
3258$3
3259"#,
3260 );
3261
3262 regsub(
3268 r#"(?x) ^ (voting-delay\ .*) \n
3269 (known-flags\ .*) \n"#,
3270 "$1
3271client-versions
3272server-versions
3273$2
3274",
3275 );
3276
3277 for missing_field in [
3280 "bandwidth-file-headers", "bandwidth-file-digest", "flag-thresholds", ] {
3284 regsub(&format!(r#"^{missing_field} .*\n"#), "");
3285 }
3286 }
3287 }
3288
3289 fn testdata_live(f: &str) -> String {
3290 let var = "TOR_NETDOC_TESTDATA_LIVE_PREFIX";
3294 let prefix = std::env::var_os(var)
3295 .map(|s| s.into_string().expect(var))
3296 .unwrap_or("testdata-live/".into());
3297 format!("{prefix}{f}")
3298 }
3299
3300 #[allow(clippy::unnecessary_wraps)] fn unwrap_unverified_for_test<UV: NetdocParseableUnverified>(
3302 uv: UV,
3303 _ids: &[RsaIdentity],
3304 _certs: &[AuthCert],
3305 ) -> Result<TimeRangeBound<UV::Body>, std::convert::Infallible> {
3306 Ok(TimeRangeBound::new(uv.unwrap_unverified().0, ..))
3307 }
3308
3309 #[cfg(feature = "retain-unknown")]
3310 #[test]
3311 fn roundtrip_live_plain() -> anyhow::Result<()> {
3312 roundtrip_netstatus::<plain::NetworkStatusUnverified, _, _>(
3313 &testdata_live("consensus"),
3314 unwrap_unverified_for_test,
3315 Duration::ZERO,
3316 )
3317 }
3318
3319 #[cfg(feature = "retain-unknown")]
3320 #[test]
3321 fn roundtrip_live_md() -> anyhow::Result<()> {
3322 roundtrip_netstatus::<md::NetworkStatusUnverified, _, _>(
3323 &testdata_live("consensus-microdesc"),
3324 unwrap_unverified_for_test,
3325 Duration::ZERO,
3326 )
3327 }
3328
3329 #[cfg(feature = "retain-unknown")]
3330 #[test]
3331 fn roundtrip_live_vote() -> anyhow::Result<()> {
3332 roundtrip_netstatus::<vote::NetworkStatusUnverified, _, _>(
3333 &testdata_live("authority"),
3334 unwrap_unverified_for_test,
3335 Duration::ZERO,
3336 )
3337 }
3338}