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};
68use crate::encode::{
69 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, SignatureHashInputs,
77 SignatureItemParseable, StopAt, UnparsedItem,
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, Result};
83use std::collections::{BTreeSet, HashMap, HashSet};
84use std::fmt::{self, Display};
85use std::result::Result as StdResult;
86use std::str::FromStr;
87use std::sync::Arc;
88use std::{net, result, time};
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 std::sync::LazyLock;
97use tor_checkable::{ExternallySigned, timed::TimerangeBound};
98use tor_llcrypto as ll;
99use tor_llcrypto::pk::rsa::RsaIdentity;
100
101use serde::{Deserialize, Deserializer};
102
103#[cfg(feature = "build_docs")]
104pub use build::MdConsensusBuilder;
105#[cfg(feature = "build_docs")]
106pub use build::PlainConsensusBuilder;
107#[cfg(feature = "build_docs")]
108ns_export_each_flavor! {
109 ty: RouterStatusBuilder;
110}
111
112ns_export_each_variety! {
113 ty: Footer, RouterStatus, Preamble;
114}
115
116#[deprecated]
117pub use PlainConsensus as NsConsensus;
118#[deprecated]
119pub use PlainRouterStatus as NsRouterStatus;
120#[deprecated]
121pub use UncheckedPlainConsensus as UncheckedNsConsensus;
122#[deprecated]
123pub use UnvalidatedPlainConsensus as UnvalidatedNsConsensus;
124
125pub use rs::{RouterStatusMdDigestsVote, SoftwareVersion};
126
127pub use dir_source::{ConsensusAuthoritySection, DirSource, SupersededAuthorityKey};
128
129define_constant_string! {
130 NetworkStatusVersion = "3";
140}
141
142define_constant_string! {
143 VoteStatusConsensus = "consensus";
147}
148
149define_constant_string! {
150 VoteStatusVote = "vote";
154}
155
156#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Default)]
170#[allow(clippy::exhaustive_structs)]
171pub struct IgnoredPublicationTimeSp;
172
173#[derive(Clone, Debug, Deftly)]
181#[derive_deftly(Constructor, NetdocEncodableFields, NetdocParseableFields)]
182#[derive_deftly(Lifetime)]
183#[allow(clippy::exhaustive_structs)]
184pub struct Lifetime {
185 #[deftly(constructor)]
192 #[deftly(netdoc(single_arg))]
193 pub valid_after: Iso8601TimeSp,
194 #[deftly(constructor)]
202 #[deftly(netdoc(single_arg))]
203 pub fresh_until: Iso8601TimeSp,
204 #[deftly(constructor)]
212 #[deftly(netdoc(single_arg))]
213 pub valid_until: Iso8601TimeSp,
214
215 #[doc(hidden)]
216 #[deftly(netdoc(skip))]
217 pub __non_exhaustive: (),
218}
219
220define_derive_deftly! {
221 Lifetime:
223
224 ${defcond FIELD not(approx_equal($fname, __non_exhaustive))}
225
226 impl Lifetime {
227 pub fn new(
229 $( ${when FIELD} $fname: time::SystemTime, )
230 ) -> Result<Self> {
231 let self_ = Lifetime {
235 $( ${when FIELD} $fname: $fname.into(), )
236 __non_exhaustive: (),
237 };
238 if self_.valid_after < self_.fresh_until && self_.fresh_until < self_.valid_until {
239 Ok(self_)
240 } else {
241 Err(EK::InvalidLifetime.err())
242 }
243 }
244 $(
245 ${when FIELD}
246
247 ${fattrs doc}
248 pub fn $fname(&self) -> time::SystemTime {
249 *self.$fname
250 }
251 )
252 pub fn valid_at(&self, when: time::SystemTime) -> bool {
254 *self.valid_after <= when && when <= *self.valid_until
255 }
256
257 pub fn voting_period(&self) -> time::Duration {
262 let valid_after = self.valid_after();
263 let fresh_until = self.fresh_until();
264 fresh_until
265 .duration_since(valid_after)
266 .expect("Mis-formed lifetime")
267 }
268 }
269}
270use derive_deftly_template_Lifetime;
271
272#[derive(Debug, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Copy)] #[derive(derive_more::From, derive_more::Into, derive_more::Display, derive_more::FromStr)]
284pub struct ConsensusMethod(u32);
285impl NormalItemArgument for ConsensusMethod {}
286
287#[derive(Debug, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Deftly)]
294#[derive_deftly(ItemValueEncodable, ItemValueParseable)]
295#[non_exhaustive]
296pub struct ConsensusMethods {
297 pub methods: BTreeSet<ConsensusMethod>,
299}
300
301pub mod consensus_methods_comma_separated {
306 use super::*;
307 use parse2::ArgumentError as AE;
308 use std::result::Result;
309
310 pub fn from_args<'s>(args: &mut ArgumentStream<'s>) -> Result<ConsensusMethods, AE> {
312 let mut methods = BTreeSet::new();
313 for ent in args.next().ok_or(AE::Missing)?.split(',') {
314 let ent = ent.parse().map_err(|_| AE::Invalid)?;
315 if !methods.insert(ent) {
316 return Err(AE::Invalid);
317 }
318 }
319 Ok(ConsensusMethods { methods })
320 }
321
322 #[cfg(feature = "incomplete")] pub fn write_arg_onto(self_: &ConsensusMethods, out: &mut ItemEncoder) -> Result<(), Bug> {
325 for s in Itertools::intersperse(self_.methods.iter().map(|v| v as &dyn Display), &",") {
326 out.args_raw_string(s);
327 }
328 Ok(())
329 }
330}
331
332#[derive(Debug, Clone, Default, Eq, PartialEq)]
358pub struct NetParams<T> {
359 params: HashMap<String, T>,
361}
362
363impl<T> NetParams<T> {
364 #[allow(unused)]
366 pub fn new() -> Self {
367 NetParams {
368 params: HashMap::new(),
369 }
370 }
371 pub fn get<A: AsRef<str>>(&self, v: A) -> Option<&T> {
373 self.params.get(v.as_ref())
374 }
375 pub fn iter(&self) -> impl Iterator<Item = (&String, &T)> {
377 self.params.iter()
378 }
379 pub fn set(&mut self, k: String, v: T) {
381 self.params.insert(k, v);
382 }
383}
384
385impl<K: Into<String>, T> FromIterator<(K, T)> for NetParams<T> {
386 fn from_iter<I: IntoIterator<Item = (K, T)>>(i: I) -> Self {
387 NetParams {
388 params: i.into_iter().map(|(k, v)| (k.into(), v)).collect(),
389 }
390 }
391}
392
393impl<T> std::iter::Extend<(String, T)> for NetParams<T> {
394 fn extend<I: IntoIterator<Item = (String, T)>>(&mut self, iter: I) {
395 self.params.extend(iter);
396 }
397}
398
399impl<'de, T> Deserialize<'de> for NetParams<T>
400where
401 T: Deserialize<'de>,
402{
403 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
404 where
405 D: Deserializer<'de>,
406 {
407 let params = HashMap::deserialize(deserializer)?;
408 Ok(NetParams { params })
409 }
410}
411
412#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
421pub struct ProtoStatus {
422 recommended: Protocols,
427 required: Protocols,
432}
433
434impl ProtoStatus {
435 pub fn check_protocols(
445 &self,
446 supported_protocols: &Protocols,
447 ) -> StdResult<(), ProtocolSupportError> {
448 let missing_required = self.required.difference(supported_protocols);
450 if !missing_required.is_empty() {
451 return Err(ProtocolSupportError::MissingRequired(missing_required));
452 }
453 let missing_recommended = self.recommended.difference(supported_protocols);
454 if !missing_recommended.is_empty() {
455 return Err(ProtocolSupportError::MissingRecommended(
456 missing_recommended,
457 ));
458 }
459
460 Ok(())
461 }
462}
463
464#[derive(Clone, Debug, thiserror::Error)]
466#[cfg_attr(test, derive(PartialEq))]
467#[non_exhaustive]
468pub enum ProtocolSupportError {
469 #[error("Required protocols are not implemented: {0}")]
471 MissingRequired(Protocols),
472
473 #[error("Recommended protocols are not implemented: {0}")]
477 MissingRecommended(Protocols),
478}
479
480impl ProtocolSupportError {
481 pub fn should_shutdown(&self) -> bool {
483 matches!(self, Self::MissingRequired(_))
484 }
485}
486
487impl HasKind for ProtocolSupportError {
488 fn kind(&self) -> tor_error::ErrorKind {
489 tor_error::ErrorKind::SoftwareDeprecated
490 }
491}
492
493#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
500pub struct ProtoStatuses {
501 client: ProtoStatus,
503 relay: ProtoStatus,
505}
506
507impl ProtoStatuses {
508 pub fn client(&self) -> &ProtoStatus {
510 &self.client
511 }
512
513 pub fn relay(&self) -> &ProtoStatus {
515 &self.relay
516 }
517}
518
519#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
527#[allow(clippy::exhaustive_enums)]
528pub enum ConsensusFlavor {
529 Microdesc,
532 Plain,
537}
538
539impl ConsensusFlavor {
540 pub fn name(&self) -> &'static str {
542 match self {
543 ConsensusFlavor::Plain => "ns", ConsensusFlavor::Microdesc => "microdesc",
545 }
546 }
547 pub fn from_opt_name(name: Option<&str>) -> Result<Self> {
552 match name {
553 Some("microdesc") => Ok(ConsensusFlavor::Microdesc),
554 Some("ns") | None => Ok(ConsensusFlavor::Plain),
555 Some(other) => {
556 Err(EK::BadDocumentType.with_msg(format!("unrecognized flavor {:?}", other)))
557 }
558 }
559 }
560}
561
562define_derive_deftly! {
563 DirectorySignaturesHashesAccu:
571
572 ${define FNAME ${paste ${snake_case $vname}} }
573
574 #[derive(Clone, Copy, Default, Debug, Eq, PartialEq, Deftly)]
576 #[derive_deftly(AsMutSelf)]
577 #[non_exhaustive]
578 pub struct DirectorySignaturesHashesAccu {
579 $(
580 ${vattrs doc}
581 pub $FNAME: Option<[u8; ${vmeta(hash_len) as expr}]>,
582 )
583
584 pub sha1_unnamed: Option<[u8; 20]>,
594 }
595
596 impl DirectorySignaturesHashesAccu {
597 fn update_from(
599 &mut self,
600 algo: &DigestAlgoInSignature,
601 body: &SignatureHashInputs,
602 ) {
603 ${define HASH {
606 self.$UPDATE.get_or_insert_with(|| {
608 let mut h = tor_llcrypto::d::$ALGO::new();
609 h.update(body.body().body());
610 h.update(body.signature_item_kw_spc);
611 h.finalize().into()
612 });
613 }}
614
615 match &**algo {
616 $(
617 Some(KeywordOrString::Known($vtype)) => {
618 ${define UPDATE $FNAME}
619 ${define ALGO $vname}
620 $HASH
621 }
622 )
623 None => {
624 ${define UPDATE sha1_unnamed}
625 ${define ALGO Sha1}
626 $HASH
627 }
628 Some(KeywordOrString::Unknown(..)) => {}
629 }
630 }
631
632 pub(crate) fn hash_slice_for_verification(
638 &self,
639 algo: &DigestAlgoInSignature,
640 ) -> Option<&[u8]> {
641 match &**algo {
642 $(
643 Some(KeywordOrString::Known($vtype)) => Some(self.$FNAME.as_ref()?),
644 )
645 None => Some(self.sha1_unnamed.as_ref()?),
646 Some(KeywordOrString::Unknown(..)) => None,
647 }
648 }
649 }
650}
651
652#[derive(Clone, Copy, Debug, Eq, PartialEq, strum::Display, strum::EnumString, Deftly)]
654#[derive_deftly(DirectorySignaturesHashesAccu)]
655#[non_exhaustive]
656#[strum(serialize_all = "snake_case")]
657pub enum DirectorySignatureHashAlgo {
658 #[deftly(hash_len = "20")]
660 Sha1,
661 #[deftly(hash_len = "32")]
663 Sha256,
664}
665
666#[derive(Debug, Clone, derive_more::Deref, derive_more::DerefMut)]
678#[allow(clippy::exhaustive_structs)]
679pub struct DigestAlgoInSignature(pub Option<KeywordOrString<DirectorySignatureHashAlgo>>);
680
681impl ItemArgumentParseable for DigestAlgoInSignature {
682 fn from_args<'s>(args: &mut ArgumentStream<'s>) -> StdResult<Self, ArgumentError> {
683 let v = if args
684 .clone()
685 .next()
686 .and_then(|s| s.chars().all(|c| c.is_ascii_hexdigit()).then_some(()))
690 .is_some()
691 {
692 None
694 } else {
695 Some(KeywordOrString::from_args(args)?)
696 };
697 Ok(DigestAlgoInSignature(v))
698 }
699}
700impl ItemArgument for DigestAlgoInSignature {
701 fn write_arg_onto(&self, out: &mut ItemEncoder<'_>) -> StdResult<(), Bug> {
702 if let Some(y) = &self.0 {
703 y.write_arg_onto(out)?;
704 }
705 Ok(())
706 }
707}
708impl DigestAlgoInSignature {
709 pub fn algorithm(&self) -> &KeywordOrString<DirectorySignatureHashAlgo> {
713 self.as_ref()
714 .unwrap_or(&KeywordOrString::Known(DirectorySignatureHashAlgo::Sha1))
715 }
716}
717
718impl NormalItemArgument for DirectorySignatureHashAlgo {}
719
720#[derive(Debug, Clone, Deftly)]
725#[derive_deftly(ItemValueEncodable, ItemValueParseable)]
726#[non_exhaustive]
727pub struct Signature {
728 pub digest_algo: DigestAlgoInSignature,
733 #[deftly(netdoc(with = authcert::keyids_directory_signature_args))]
736 pub key_ids: AuthCertKeyIds,
737 #[deftly(netdoc(object(label = "SIGNATURE"), with = types::raw_data_object))]
739 pub signature: Vec<u8>,
740}
741
742impl SignatureItemParseable for Signature {
743 type HashAccu = DirectorySignaturesHashesAccu;
744
745 fn from_unparsed_and_body(
746 item: UnparsedItem,
747 body: &SignatureHashInputs<'_>,
748 hash: &mut Self::HashAccu,
749 ) -> StdResult<Self, ErrorProblem> {
750 let signature = Signature::from_unparsed(item)?;
751 hash.update_from(&signature.digest_algo, body);
752 Ok(signature)
753 }
754}
755
756#[derive(Debug, Clone)]
762#[non_exhaustive]
763pub struct SignatureGroup {
764 pub hashes: DirectorySignaturesHashesAccu,
770 pub signatures: Vec<Signature>,
772}
773
774#[derive(
776 Debug, Clone, Copy, Eq, PartialEq, derive_more::From, derive_more::Into, derive_more::AsRef,
777)]
778pub struct SharedRandVal([u8; 32]);
780
781#[derive(Debug, Clone, Deftly)]
784#[non_exhaustive]
785#[derive_deftly(ItemValueEncodable, ItemValueParseable)]
786pub struct SharedRandStatus {
787 pub n_reveals: u8,
789 pub value: SharedRandVal,
796
797 pub timestamp: Option<Iso8601TimeNoSp>,
801}
802
803#[derive(Debug, Clone, Default, Deftly)]
810#[derive_deftly(Constructor, NetdocEncodableFields, NetdocParseableFields)]
811#[allow(clippy::exhaustive_structs)]
812pub struct SharedRandStatuses {
813 pub shared_rand_previous_value: Option<SharedRandStatus>,
815
816 pub shared_rand_current_value: Option<SharedRandStatus>,
818
819 #[doc(hidden)]
820 #[deftly(netdoc(skip))]
821 pub __non_exhaustive: (),
822}
823
824#[derive(Debug, Clone)]
881pub struct RelayWeightsItem {
882 effective: RelayWeight,
884
885 params: Unknown<Option<NetParams<u32>>>,
887}
888
889#[non_exhaustive]
893#[derive(Debug, Clone, Copy)]
894pub enum RelayWeight {
895 Unmeasured(u32),
897 Measured(u32),
899}
900
901#[derive(Debug, Clone, thiserror::Error)]
903#[non_exhaustive]
904pub enum InvalidRelayWeights {
905 #[error("invalid value for Unmeasured")]
907 InvalidUnmeasured,
908}
909
910#[deprecated = "renamed to ConsensusAuthorityEntry"]
912pub type ConsensusVoterInfo = ConsensusAuthorityEntry;
913
914pub type PlainAuthorityEntry = ConsensusAuthorityEntry;
916pub type MdAuthorityEntry = ConsensusAuthorityEntry;
918
919#[derive(Debug, Clone, Deftly)]
929#[derive_deftly(Constructor, NetdocEncodable, NetdocParseable)]
930#[allow(clippy::exhaustive_structs)]
931pub struct ConsensusAuthorityEntry {
932 #[deftly(constructor)]
934 pub dir_source: DirSource,
935
936 #[deftly(constructor)]
942 pub contact: ContactInfo,
943
944 #[deftly(netdoc(single_arg))]
951 #[deftly(constructor)]
952 pub vote_digest: B16U,
953
954 #[doc(hidden)]
955 #[deftly(netdoc(skip))]
956 pub __non_exhaustive: (),
957}
958
959#[derive(Debug, Clone, Deftly)]
965#[derive_deftly(Constructor, NetdocEncodable, NetdocParseable)]
966#[allow(clippy::exhaustive_structs)]
967pub struct VoteAuthorityEntry {
968 #[deftly(constructor)]
970 pub dir_source: DirSource,
971
972 #[deftly(constructor)]
974 pub contact: ContactInfo,
975
976 #[deftly(netdoc(single_arg))]
980 pub legacy_dir_key: Option<Fingerprint>,
981
982 pub shared_rand_participate: Option<SharedRandParticipate>,
986
987 pub shared_rand_commit: Vec<SharedRandCommit>,
991
992 #[deftly(netdoc(flatten))]
994 pub shared_rand: SharedRandStatuses,
995
996 #[doc(hidden)]
997 #[deftly(netdoc(skip))]
998 pub __non_exhaustive: (),
999}
1000
1001#[derive(Debug, Clone, Deftly)]
1011#[derive_deftly(Constructor, ItemValueEncodable, ItemValueParseable)]
1012#[allow(clippy::exhaustive_structs)]
1013pub struct SharedRandParticipate {
1014 #[doc(hidden)]
1015 #[deftly(netdoc(skip))]
1016 pub __non_exhaustive: (),
1017}
1018
1019#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deftly)]
1023#[allow(clippy::exhaustive_enums)]
1025pub enum SharedRandCommit {
1026 V1(SharedRandCommitV1),
1028
1029 Unknown {},
1032}
1033
1034#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deftly)]
1043#[derive_deftly(Constructor, ItemValueEncodable, ItemValueParseable)]
1044#[allow(clippy::exhaustive_structs)]
1045pub struct SharedRandCommitV1 {
1046 #[deftly(constructor)]
1049 h_kp_auth_id_rsa: Fingerprint,
1050
1051 #[deftly(constructor)]
1059 commit: FixedB64<40>,
1060
1061 reveal: Option<FixedB64<40>>,
1066
1067 #[doc(hidden)]
1068 #[deftly(netdoc(skip))]
1069 pub __non_exhaustive: (),
1070}
1071
1072impl SharedRandCommitV1 {
1073 const FIXED_ARGUMENTS: &[&str] = &["1", "sha3-256"];
1075}
1076impl ItemValueEncodable for SharedRandCommit {
1077 fn write_item_value_onto(&self, mut out: ItemEncoder) -> StdResult<(), Bug> {
1078 match self {
1079 SharedRandCommit::V1(values) => {
1080 for fixed in SharedRandCommitV1::FIXED_ARGUMENTS {
1081 out.args_raw_string(fixed);
1082 }
1083 values.write_item_value_onto(out)
1084 }
1085 SharedRandCommit::Unknown {} => Err(internal!("encoding SharedRandCommit::Unknown")),
1086 }
1087 }
1088}
1089impl ItemValueParseable for SharedRandCommit {
1090 fn from_unparsed(mut item: UnparsedItem<'_>) -> StdResult<Self, ErrorProblem> {
1091 let mut fixed = SharedRandCommitV1::FIXED_ARGUMENTS.iter().copied();
1092 let args = item.args_mut();
1093 let version = args
1094 .next()
1095 .ok_or_else(|| args.handle_error("version", ArgumentError::Missing))?;
1096 if version != fixed.next().expect("nonempty") {
1097 return Ok(SharedRandCommit::Unknown {});
1098 }
1099 for exp in fixed {
1100 let got = args
1101 .next()
1102 .ok_or_else(|| args.handle_error(exp, ArgumentError::Missing))?;
1103 if got != exp {
1104 return Err(args.handle_error(exp, ArgumentError::Invalid))?;
1105 }
1106 }
1107 let values = SharedRandCommitV1::from_unparsed(item)?;
1108 Ok(SharedRandCommit::V1(values))
1109 }
1110}
1111
1112define_derive_deftly! {
1115 VoteAuthoritySection:
1127
1128 ${defcond F_NORMAL not(fmeta(netdoc(skip)))}
1129
1130 #[cfg(feature = "incomplete")] impl NetdocParseable for VoteAuthoritySection {
1132 fn doctype_for_error() -> &'static str {
1133 "vote.authority.section"
1134 }
1135 fn is_intro_item_keyword(kw: KeywordRef<'_>) -> bool {
1136 VoteAuthorityEntry::is_intro_item_keyword(kw)
1137 }
1138 fn is_structural_keyword(kw: KeywordRef<'_>) -> Option<IsStructural> {
1139 $(
1140 ${when F_NORMAL}
1141 if let y @ Some(_) = $ftype::is_structural_keyword(kw) {
1142 return y;
1143 }
1144 )
1145 None
1146 }
1147 fn from_items<'s>(
1148 input: &mut ItemStream<'s>,
1149 stop_outer: stop_at!(),
1150 ) -> StdResult<Self, ErrorProblem> {
1151 let stop_inner = stop_outer
1152 $(
1153 ${when F_NORMAL}
1154 | StopAt($ftype::is_intro_item_keyword)
1155 )
1156 ;
1157 Ok(VoteAuthoritySection { $(
1158 ${when F_NORMAL}
1159 $fname: NetdocParseable::from_items(input, stop_inner)?,
1160 )
1161 __non_exhaustive: (),
1162 })
1163 }
1164 }
1165
1166 #[cfg(feature = "incomplete")]
1167 impl NetdocEncodable for VoteAuthoritySection {
1168 fn encode_unsigned(&self, out: &mut NetdocEncoder) -> StdResult<(), Bug> {
1169 $(
1170 ${when F_NORMAL}
1171 self.$fname.encode_unsigned(out)?;
1172 )
1173 Ok(())
1174 }
1175 }
1176}
1177
1178#[derive(Deftly, Clone, Debug)]
1185#[derive_deftly(VoteAuthoritySection, Constructor)]
1186#[allow(clippy::exhaustive_structs)]
1187#[cfg(feature = "incomplete")] pub struct VoteAuthoritySection {
1189 #[deftly(constructor)]
1191 pub authority: VoteAuthorityEntry,
1192
1193 #[deftly(constructor)]
1195 pub cert: EncodedAuthCert,
1196
1197 #[doc(hidden)]
1198 #[deftly(netdoc(skip))]
1199 pub __non_exhaustive: (),
1200}
1201
1202#[derive(Debug, Clone, Deftly)]
1208#[derive_deftly(Constructor, NetdocEncodableFields, NetdocParseableFields)]
1209#[allow(clippy::exhaustive_structs)]
1210pub struct ConsensusFooterFields {
1211 #[deftly(netdoc(default))]
1215 pub bandwidth_weights: NetParams<i32>,
1216
1217 #[doc(hidden)]
1218 #[deftly(netdoc(skip))]
1219 pub __non_exhaustive: (),
1220}
1221
1222pub type MdConsensus = md::Consensus;
1225
1226pub type UnvalidatedMdConsensus = md::UnvalidatedConsensus;
1229
1230pub type UncheckedMdConsensus = md::UncheckedConsensus;
1233
1234pub type PlainConsensus = plain::Consensus;
1237
1238pub type UnvalidatedPlainConsensus = plain::UnvalidatedConsensus;
1241
1242pub type UncheckedPlainConsensus = plain::UncheckedConsensus;
1245
1246decl_keyword! {
1247 #[non_exhaustive]
1252 #[allow(missing_docs)]
1253 pub NetstatusKwd {
1254 "network-status-version" => NETWORK_STATUS_VERSION,
1256 "vote-status" => VOTE_STATUS,
1257 "consensus-methods" => CONSENSUS_METHODS,
1258 "consensus-method" => CONSENSUS_METHOD,
1259 "published" => PUBLISHED,
1260 "valid-after" => VALID_AFTER,
1261 "fresh-until" => FRESH_UNTIL,
1262 "valid-until" => VALID_UNTIL,
1263 "voting-delay" => VOTING_DELAY,
1264 "client-versions" => CLIENT_VERSIONS,
1265 "server-versions" => SERVER_VERSIONS,
1266 "known-flags" => KNOWN_FLAGS,
1267 "flag-thresholds" => FLAG_THRESHOLDS,
1268 "recommended-client-protocols" => RECOMMENDED_CLIENT_PROTOCOLS,
1269 "required-client-protocols" => REQUIRED_CLIENT_PROTOCOLS,
1270 "recommended-relay-protocols" => RECOMMENDED_RELAY_PROTOCOLS,
1271 "required-relay-protocols" => REQUIRED_RELAY_PROTOCOLS,
1272 "params" => PARAMS,
1273 "bandwidth-file-headers" => BANDWIDTH_FILE_HEADERS,
1274 "bandwidth-file-digest" => BANDWIDTH_FILE_DIGEST,
1275 "shared-rand-previous-value" => SHARED_RAND_PREVIOUS_VALUE,
1279 "shared-rand-current-value" => SHARED_RAND_CURRENT_VALUE,
1280
1281 "dir-source" => DIR_SOURCE,
1283 "contact" => CONTACT,
1284
1285 "legacy-dir-key" => LEGACY_DIR_KEY,
1287 "shared-rand-participate" => SHARED_RAND_PARTICIPATE,
1288 "shared-rand-commit" => SHARED_RAND_COMMIT,
1289
1290 "vote-digest" => VOTE_DIGEST,
1292
1293 "dir-key-certificate-version" => DIR_KEY_CERTIFICATE_VERSION,
1295
1296 "r" => RS_R,
1298 "a" => RS_A,
1299 "s" => RS_S,
1300 "v" => RS_V,
1301 "pr" => RS_PR,
1302 "w" => RS_W,
1303 "p" => RS_P,
1304 "m" => RS_M,
1305 "id" => RS_ID,
1306
1307 "directory-footer" => DIRECTORY_FOOTER,
1309 "bandwidth-weights" => BANDWIDTH_WEIGHTS,
1310 "directory-signature" => DIRECTORY_SIGNATURE,
1311 }
1312}
1313
1314static NS_HEADER_RULES_COMMON_: LazyLock<SectionRulesBuilder<NetstatusKwd>> = LazyLock::new(|| {
1316 use NetstatusKwd::*;
1317 let mut rules = SectionRules::builder();
1318 rules.add(NETWORK_STATUS_VERSION.rule().required().args(1..=2));
1319 rules.add(VOTE_STATUS.rule().required().args(1..));
1320 rules.add(VALID_AFTER.rule().required());
1321 rules.add(FRESH_UNTIL.rule().required());
1322 rules.add(VALID_UNTIL.rule().required());
1323 rules.add(VOTING_DELAY.rule().args(2..));
1324 rules.add(CLIENT_VERSIONS.rule());
1325 rules.add(SERVER_VERSIONS.rule());
1326 rules.add(KNOWN_FLAGS.rule().required());
1327 rules.add(RECOMMENDED_CLIENT_PROTOCOLS.rule().args(1..));
1328 rules.add(RECOMMENDED_RELAY_PROTOCOLS.rule().args(1..));
1329 rules.add(REQUIRED_CLIENT_PROTOCOLS.rule().args(1..));
1330 rules.add(REQUIRED_RELAY_PROTOCOLS.rule().args(1..));
1331 rules.add(PARAMS.rule());
1332 rules
1333});
1334static NS_HEADER_RULES_CONSENSUS: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
1336 use NetstatusKwd::*;
1337 let mut rules = NS_HEADER_RULES_COMMON_.clone();
1338 rules.add(CONSENSUS_METHOD.rule().args(1..=1));
1339 rules.add(SHARED_RAND_PREVIOUS_VALUE.rule().args(2..));
1340 rules.add(SHARED_RAND_CURRENT_VALUE.rule().args(2..));
1341 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
1342 rules.build()
1343});
1344static NS_VOTERINFO_RULES_CONSENSUS: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
1375 use NetstatusKwd::*;
1376 let mut rules = SectionRules::builder();
1377 rules.add(DIR_SOURCE.rule().required().args(6..));
1378 rules.add(CONTACT.rule().required());
1379 rules.add(VOTE_DIGEST.rule().required());
1380 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
1381 rules.build()
1382});
1383static NS_ROUTERSTATUS_RULES_COMMON_: LazyLock<SectionRulesBuilder<NetstatusKwd>> =
1385 LazyLock::new(|| {
1386 use NetstatusKwd::*;
1387 let mut rules = SectionRules::builder();
1388 rules.add(RS_A.rule().may_repeat().args(1..));
1389 rules.add(RS_S.rule().required());
1390 rules.add(RS_V.rule());
1391 rules.add(RS_PR.rule().required());
1392 rules.add(RS_W.rule());
1393 rules.add(RS_P.rule().args(2..));
1394 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
1395 rules
1396 });
1397
1398static NS_ROUTERSTATUS_RULES_PLAIN: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
1400 use NetstatusKwd::*;
1401 let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
1402 rules.add(RS_R.rule().required().args(8..));
1403 rules.build()
1404});
1405
1406static NS_ROUTERSTATUS_RULES_MDCON: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
1419 use NetstatusKwd::*;
1420 let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
1421 rules.add(RS_R.rule().required().args(6..));
1422 rules.add(RS_M.rule().required().args(1..));
1423 rules.build()
1424});
1425static NS_FOOTER_RULES: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
1427 use NetstatusKwd::*;
1428 let mut rules = SectionRules::builder();
1429 rules.add(DIRECTORY_FOOTER.rule().required().no_args());
1430 rules.add(BANDWIDTH_WEIGHTS.rule());
1432 rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
1433 rules.build()
1434});
1435
1436impl ProtoStatus {
1437 fn from_section(
1439 sec: &Section<'_, NetstatusKwd>,
1440 recommend_token: NetstatusKwd,
1441 required_token: NetstatusKwd,
1442 ) -> Result<ProtoStatus> {
1443 fn parse(t: Option<&Item<'_, NetstatusKwd>>) -> Result<Protocols> {
1445 if let Some(item) = t {
1446 item.args_as_str()
1447 .parse::<Protocols>()
1448 .map_err(|e| EK::BadArgument.at_pos(item.pos()).with_source(e))
1449 } else {
1450 Ok(Protocols::new())
1451 }
1452 }
1453
1454 let recommended = parse(sec.get(recommend_token))?;
1455 let required = parse(sec.get(required_token))?;
1456 Ok(ProtoStatus {
1457 recommended,
1458 required,
1459 })
1460 }
1461
1462 pub fn required_protocols(&self) -> &Protocols {
1469 &self.required
1470 }
1471
1472 pub fn recommended_protocols(&self) -> &Protocols {
1477 &self.recommended
1478 }
1479}
1480
1481impl<T> std::str::FromStr for NetParams<T>
1482where
1483 T: std::str::FromStr,
1484 T::Err: std::error::Error,
1485{
1486 type Err = Error;
1487 fn from_str(s: &str) -> Result<Self> {
1488 fn parse_pair<U>(p: &str) -> Result<(String, U)>
1490 where
1491 U: std::str::FromStr,
1492 U::Err: std::error::Error,
1493 {
1494 let parts: Vec<_> = p.splitn(2, '=').collect();
1495 if parts.len() != 2 {
1496 return Err(EK::BadArgument
1497 .at_pos(Pos::at(p))
1498 .with_msg("Missing = in key=value list"));
1499 }
1500 let num = parts[1].parse::<U>().map_err(|e| {
1501 EK::BadArgument
1502 .at_pos(Pos::at(parts[1]))
1503 .with_msg(e.to_string())
1504 })?;
1505 Ok((parts[0].to_string(), num))
1506 }
1507
1508 let params = s
1509 .split(' ')
1510 .filter(|p| !p.is_empty())
1511 .map(parse_pair)
1512 .collect::<Result<HashMap<_, _>>>()?;
1513 Ok(NetParams { params })
1514 }
1515}
1516
1517impl FromStr for SharedRandVal {
1518 type Err = Error;
1519 fn from_str(s: &str) -> Result<Self> {
1520 let val: B64 = s.parse()?;
1521 let val = SharedRandVal(val.into_array()?);
1522 Ok(val)
1523 }
1524}
1525impl Display for SharedRandVal {
1526 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1527 Display::fmt(&B64::from(Vec::from(self.0)), f)
1528 }
1529}
1530impl NormalItemArgument for SharedRandVal {}
1531
1532impl SharedRandStatus {
1533 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
1536 match item.kwd() {
1537 NetstatusKwd::SHARED_RAND_PREVIOUS_VALUE | NetstatusKwd::SHARED_RAND_CURRENT_VALUE => {}
1538 _ => {
1539 return Err(Error::from(internal!(
1540 "wrong keyword {:?} on shared-random value",
1541 item.kwd()
1542 ))
1543 .at_pos(item.pos()));
1544 }
1545 }
1546 let n_reveals: u8 = item.parse_arg(0)?;
1547 let value: SharedRandVal = item.parse_arg(1)?;
1548 let timestamp = item.parse_optional_arg::<Iso8601TimeNoSp>(2)?;
1550 Ok(SharedRandStatus {
1551 n_reveals,
1552 value,
1553 timestamp,
1554 })
1555 }
1556
1557 pub fn value(&self) -> &SharedRandVal {
1559 &self.value
1560 }
1561
1562 pub fn timestamp(&self) -> Option<std::time::SystemTime> {
1564 self.timestamp.map(|t| t.0)
1565 }
1566}
1567
1568impl DirSource {
1569 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
1571 if item.kwd() != NetstatusKwd::DIR_SOURCE {
1572 return Err(
1573 Error::from(internal!("Bad keyword {:?} on dir-source", item.kwd()))
1574 .at_pos(item.pos()),
1575 );
1576 }
1577 let nickname = item
1578 .required_arg(0)?
1579 .parse()
1580 .map_err(|e: InvalidNickname| {
1581 EK::BadArgument.at_pos(item.pos()).with_msg(e.to_string())
1582 })?;
1583 let identity = item.parse_arg(1)?;
1584 let hostname = item
1585 .required_arg(2)?
1586 .parse()
1587 .map_err(|e: InvalidInternetHost| {
1588 EK::BadArgument.at_pos(item.pos()).with_msg(e.to_string())
1589 })?;
1590 let ip = item.parse_arg(3)?;
1591 let dir_port = item.parse_arg(4)?;
1592 let or_port = item.parse_arg(5)?;
1593
1594 Ok(DirSource {
1595 nickname,
1596 identity,
1597 hostname,
1598 ip,
1599 dir_port,
1600 or_port,
1601 __non_exhaustive: (),
1602 })
1603 }
1604}
1605
1606impl ConsensusAuthorityEntry {
1607 fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<ConsensusAuthorityEntry> {
1609 use NetstatusKwd::*;
1610 #[allow(clippy::unwrap_used)]
1613 let first = sec.first_item().unwrap();
1614 if first.kwd() != DIR_SOURCE {
1615 return Err(Error::from(internal!(
1616 "Wrong keyword {:?} at start of voter info",
1617 first.kwd()
1618 ))
1619 .at_pos(first.pos()));
1620 }
1621 let dir_source = DirSource::from_item(sec.required(DIR_SOURCE)?)?;
1622
1623 let contact = sec.required(CONTACT)?;
1624 let contact = contact
1630 .args_as_str()
1631 .parse()
1632 .map_err(|err: InvalidContactInfo| {
1633 EK::BadArgument
1634 .with_msg(err.to_string())
1635 .at_pos(contact.pos())
1636 })?;
1637
1638 let vote_digest = sec.required(VOTE_DIGEST)?.parse_arg::<B16U>(0)?;
1639
1640 Ok(ConsensusAuthorityEntry {
1641 dir_source,
1642 contact,
1643 vote_digest,
1644 __non_exhaustive: (),
1645 })
1646 }
1647}
1648
1649impl RelayWeightsItem {
1650 pub fn new_no_info() -> Self {
1654 RelayWeightsItem {
1655 effective: RelayWeight::default(),
1656 params: Unknown::new_discard(),
1657 }
1658 }
1659
1660 pub fn from_effective(effective: RelayWeight) -> Self {
1662 RelayWeightsItem {
1663 effective,
1664 params: Unknown::new_discard(),
1665 }
1666 }
1667
1668 pub fn effective(&self) -> RelayWeight {
1675 self.effective
1676 }
1677
1678 pub fn params(&self) -> Unknown<&Option<NetParams<u32>>> {
1686 self.params.as_ref()
1687 }
1688
1689 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<RelayWeightsItem> {
1691 if item.kwd() != NetstatusKwd::RS_W {
1692 return Err(
1693 Error::from(internal!("Wrong keyword {:?} on W line", item.kwd()))
1694 .at_pos(item.pos()),
1695 );
1696 }
1697
1698 let params = item.args_as_str().parse()?;
1699 let effective = RelayWeight::from_net_params(¶ms).map_err(|e| e.at_pos(item.pos()))?;
1700
1701 Ok(RelayWeightsItem {
1702 effective,
1703 params: Unknown::new_discard(),
1704 })
1705 }
1706
1707 const KEYWORD: &str = "w";
1709}
1710
1711#[cfg(feature = "retain-unknown")]
1712impl Default for RelayWeightsItem {
1713 fn default() -> Self {
1714 RelayWeightsItem {
1715 effective: RelayWeight::default(),
1716 params: Unknown::Retained(None),
1717 }
1718 }
1719}
1720
1721impl RelayWeight {
1722 pub fn is_measured(&self) -> bool {
1724 matches!(self, RelayWeight::Measured(_))
1725 }
1726
1727 pub fn is_nonzero(&self) -> bool {
1729 !matches!(self, RelayWeight::Unmeasured(0) | RelayWeight::Measured(0))
1730 }
1731
1732 fn from_net_params(params: &NetParams<u32>) -> Result<RelayWeight> {
1736 params
1737 .try_into()
1738 .map_err(|e: InvalidRelayWeights| EK::BadArgument.with_msg(e.to_string()))
1739 }
1740}
1741
1742impl Default for RelayWeight {
1743 fn default() -> RelayWeight {
1744 RelayWeight::Unmeasured(0)
1745 }
1746}
1747
1748impl TryFrom<&NetParams<u32>> for RelayWeight {
1749 type Error = InvalidRelayWeights;
1750
1751 fn try_from(params: &NetParams<u32>) -> StdResult<RelayWeight, InvalidRelayWeights> {
1752 let bw = params.params.get("Bandwidth");
1753 let unmeas = params.params.get("Unmeasured");
1754
1755 let bw = match bw {
1756 None => return Ok(RelayWeight::Unmeasured(0)),
1757 Some(b) => *b,
1758 };
1759
1760 match unmeas {
1761 None | Some(0) => Ok(RelayWeight::Measured(bw)),
1762 Some(1) => Ok(RelayWeight::Unmeasured(bw)),
1763 _ => Err(InvalidRelayWeights::InvalidUnmeasured),
1764 }
1765 }
1766}
1767
1768#[cfg(feature = "retain-unknown")]
1769impl TryFrom<NetParams<u32>> for RelayWeightsItem {
1770 type Error = InvalidRelayWeights;
1771
1772 fn try_from(params: NetParams<u32>) -> StdResult<RelayWeightsItem, InvalidRelayWeights> {
1773 Ok(RelayWeightsItem {
1774 effective: (¶ms).try_into()?,
1775 params: Unknown::Retained(Some(params)),
1776 })
1777 }
1778}
1779
1780mod parse2_impls {
1784 use super::*;
1785 pub(super) use parse2::{
1786 ArgumentError as AE, ArgumentStream, ErrorProblem as EP, ItemArgumentParseable,
1787 ItemValueParseable, NetdocParseableFields,
1788 };
1789 use std::result::Result;
1790
1791 impl<T: FromStr + NormalItemArgument> ItemValueParseable for NetParams<T>
1793 where
1794 T::Err: std::error::Error,
1795 {
1796 fn from_unparsed(item: parse2::UnparsedItem<'_>) -> Result<Self, EP> {
1797 item.check_no_object()?;
1798 item.args_copy()
1799 .into_remaining()
1800 .parse()
1801 .map_err(item.invalid_argument_handler("parameters"))
1802 }
1803 }
1804
1805 impl NetdocParseableFields for RelayWeightsItem {
1806 type Accumulator = Option<NetParams<u32>>;
1807
1808 fn is_item_keyword(kw: KeywordRef) -> bool {
1809 kw == Self::KEYWORD
1810 }
1811
1812 fn accumulate_item(acc: &mut Self::Accumulator, item: UnparsedItem) -> Result<(), EP> {
1813 if acc.is_some() {
1814 return Err(EP::ItemRepeated);
1815 }
1816 item.check_no_object()?;
1817 let params = NetParams::from_unparsed(item)?;
1818 *acc = Some(params);
1819 Ok(())
1820 }
1821
1822 fn finish(params: Self::Accumulator, items: &ItemStream) -> Result<Self, EP> {
1823 let effective = params
1824 .as_ref()
1825 .map(TryFrom::try_from)
1826 .transpose()
1827 .map_err(|_| EP::OtherBadDocument("invalid information in `w` item"))?
1828 .unwrap_or_default();
1829
1830 let params = items.parse_options().retain_unknown_values.map(|()| params);
1831
1832 Ok(RelayWeightsItem { effective, params })
1833 }
1834 }
1835
1836 impl ItemValueParseable for rs::SoftwareVersion {
1837 fn from_unparsed(mut item: parse2::UnparsedItem<'_>) -> Result<Self, EP> {
1838 item.check_no_object()?;
1839 item.args_mut()
1840 .into_remaining()
1841 .parse()
1842 .map_err(item.invalid_argument_handler("version"))
1843 }
1844 }
1845
1846 impl ItemArgumentParseable for IgnoredPublicationTimeSp {
1847 fn from_args(a: &mut ArgumentStream) -> Result<IgnoredPublicationTimeSp, AE> {
1848 let mut next_arg = || a.next().ok_or(AE::Missing);
1849 let _: &str = next_arg()?;
1850 let _: &str = next_arg()?;
1851 Ok(IgnoredPublicationTimeSp)
1852 }
1853 }
1854}
1855
1856mod encode_impls {
1860 use super::*;
1861 use std::result::Result;
1862 pub(crate) use {
1863 crate::encode::{ItemEncoder, ItemValueEncodable, NetdocEncodableFields},
1864 tor_error::Bug,
1865 };
1866
1867 #[cfg(feature = "incomplete")] impl NetdocEncodableFields for RelayWeightsItem {
1869 fn encode_fields(&self, out: &mut NetdocEncoder) -> Result<(), Bug> {
1870 if let Some(w) = self.params.as_ref().into_retained()? {
1871 w.write_item_value_onto(out.item(Self::KEYWORD))?;
1872 }
1873 Ok(())
1874 }
1875 }
1876
1877 impl<T: NormalItemArgument + Ord + Display> ItemValueEncodable for NetParams<T> {
1879 fn write_item_value_onto(&self, mut out: ItemEncoder) -> Result<(), Bug> {
1880 for (k, v) in self.iter().collect::<BTreeSet<_>>() {
1881 if k.is_empty()
1882 || k.chars()
1883 .any(|c| c.is_whitespace() || c.is_control() || c == '=')
1884 {
1885 return Err(bad_api_usage!(
1887 "tried to encode NetParms with unreasonable keyword {k:?}"
1888 ));
1889 }
1890 out.args_raw_string(&format_args!("{k}={v}"));
1891 }
1892 Ok(())
1893 }
1894 }
1895
1896 impl ItemValueEncodable for rs::SoftwareVersion {
1897 fn write_item_value_onto(&self, mut out: ItemEncoder) -> Result<(), Bug> {
1898 out.args_raw_string(self);
1899 Ok(())
1900 }
1901 }
1902
1903 impl ItemArgument for IgnoredPublicationTimeSp {
1904 fn write_arg_onto(&self, out: &mut ItemEncoder) -> Result<(), Bug> {
1905 out.args_raw_string(&"2000-01-01 00:00:01");
1906 Ok(())
1907 }
1908 }
1909}
1910
1911impl ConsensusFooterFields {
1912 fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<ConsensusFooterFields> {
1914 use NetstatusKwd::*;
1915 sec.required(DIRECTORY_FOOTER)?;
1916
1917 let bandwidth_weights = sec
1918 .maybe(BANDWIDTH_WEIGHTS)
1919 .args_as_str()
1920 .unwrap_or("")
1921 .parse()?;
1922
1923 Ok(ConsensusFooterFields {
1924 bandwidth_weights,
1925 __non_exhaustive: (),
1926 })
1927 }
1928}
1929
1930mod proto_statuses_parse2_encode {
1934 use super::encode_impls::*;
1935 use super::parse2_impls::*;
1936 use super::*;
1937 use paste::paste;
1938 use std::result::Result;
1939
1940 macro_rules! impl_proto_statuses { { $( $rr:ident $cr:ident; )* } => { paste! {
1954 #[derive(Deftly)]
1955 #[derive_deftly(NetdocParseableFields)]
1956 #[allow(unreachable_pub)]
1958 pub struct ProtoStatusesParseHelper {
1959 $(
1960 #[deftly(netdoc(default))]
1961 [<$rr _ $cr _protocols>]: Protocols,
1962 )*
1963 }
1964
1965 pub use ProtoStatusesParseHelperNetdocParseAccumulator
1967 as ProtoStatusesNetdocParseAccumulator;
1968
1969 impl NetdocParseableFields for ProtoStatuses {
1970 type Accumulator = ProtoStatusesNetdocParseAccumulator;
1971 fn is_item_keyword(kw: KeywordRef<'_>) -> bool {
1972 ProtoStatusesParseHelper::is_item_keyword(kw)
1973 }
1974 fn accumulate_item(
1975 acc: &mut Self::Accumulator,
1976 item: UnparsedItem<'_>,
1977 ) -> Result<(), EP> {
1978 ProtoStatusesParseHelper::accumulate_item(acc, item)
1979 }
1980 fn finish(acc: Self::Accumulator, items: &ItemStream<'_>) -> Result<Self, EP> {
1981 let parse = ProtoStatusesParseHelper::finish(acc, items)?;
1982 let mut out = ProtoStatuses::default();
1983 $(
1984 out.$cr.$rr = parse.[< $rr _ $cr _protocols >];
1985 )*
1986 Ok(out)
1987 }
1988 }
1989
1990 impl NetdocEncodableFields for ProtoStatuses {
1991 fn encode_fields(&self, out: &mut NetdocEncoder) -> Result<(), Bug> {
1992 $(
1993 self.$cr.$rr.write_item_value_onto(
1994 out.item(stringify!([<$rr _ $cr _protocols>]))
1995 )?;
1996 )*
1997 Ok(())
1998 }
1999 }
2000 } } }
2001
2002 impl_proto_statuses! {
2003 required client;
2004 required relay;
2005 recommended client;
2006 recommended relay;
2007 }
2008}
2009
2010enum SigCheckResult {
2012 Valid,
2014 Invalid,
2017 MissingCert,
2020}
2021
2022impl Signature {
2023 fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Signature> {
2025 if item.kwd() != NetstatusKwd::DIRECTORY_SIGNATURE {
2026 return Err(Error::from(internal!(
2027 "Wrong keyword {:?} for directory signature",
2028 item.kwd()
2029 ))
2030 .at_pos(item.pos()));
2031 }
2032
2033 let (digest_algo, id_fp, sk_fp) = if item.n_args() > 2 {
2034 (
2035 item.required_arg(0)?,
2036 item.required_arg(1)?,
2037 item.required_arg(2)?,
2038 )
2039 } else {
2040 ("sha1", item.required_arg(0)?, item.required_arg(1)?)
2041 };
2042
2043 let digest_algo = digest_algo.to_string().parse().void_unwrap();
2044 let digest_algo = DigestAlgoInSignature(Some(digest_algo));
2045 let id_fingerprint = id_fp.parse::<Fingerprint>()?.into();
2046 let sk_fingerprint = sk_fp.parse::<Fingerprint>()?.into();
2047 let key_ids = AuthCertKeyIds {
2048 id_fingerprint,
2049 sk_fingerprint,
2050 };
2051 let signature = item.obj("SIGNATURE")?;
2052
2053 Ok(Signature {
2054 digest_algo,
2055 key_ids,
2056 signature,
2057 })
2058 }
2059
2060 fn matches_cert(&self, cert: &AuthCert) -> bool {
2063 cert.key_ids() == self.key_ids
2064 }
2065
2066 fn find_cert<'a>(&self, certs: &'a [AuthCert]) -> Option<&'a AuthCert> {
2069 certs.iter().find(|&c| self.matches_cert(c))
2070 }
2071
2072 fn check_signature(&self, signed_digest: &[u8], certs: &[AuthCert]) -> SigCheckResult {
2076 match self.find_cert(certs) {
2077 None => SigCheckResult::MissingCert,
2078 Some(cert) => {
2079 let key = cert.signing_key();
2080 match key.verify(signed_digest, &self.signature[..]) {
2081 Ok(()) => SigCheckResult::Valid,
2082 Err(_) => SigCheckResult::Invalid,
2083 }
2084 }
2085 }
2086 }
2087}
2088
2089impl SignatureGroup {
2090 fn list_missing(&self, certs: &[AuthCert]) -> (usize, Vec<&Signature>) {
2097 let mut ok: HashSet<RsaIdentity> = HashSet::new();
2098 let mut missing = Vec::new();
2099 for sig in &self.signatures {
2100 let id_fingerprint = &sig.key_ids.id_fingerprint;
2101 if ok.contains(id_fingerprint) {
2102 continue;
2103 }
2104 if sig.find_cert(certs).is_some() {
2105 ok.insert(*id_fingerprint);
2106 continue;
2107 }
2108
2109 missing.push(sig);
2110 }
2111 (ok.len(), missing)
2112 }
2113
2114 fn could_validate(&self, authorities: &[&RsaIdentity]) -> bool {
2118 let mut signed_by: HashSet<RsaIdentity> = HashSet::new();
2119 for sig in &self.signatures {
2120 let id_fp = &sig.key_ids.id_fingerprint;
2121 if signed_by.contains(id_fp) {
2122 continue;
2124 }
2125 if authorities.contains(&id_fp) {
2126 signed_by.insert(*id_fp);
2127 }
2128 }
2129
2130 signed_by.len() > (authorities.len() / 2)
2131 }
2132
2133 fn validate(&self, n_authorities: usize, certs: &[AuthCert]) -> bool {
2140 let mut ok: HashSet<RsaIdentity> = HashSet::new();
2144
2145 for sig in &self.signatures {
2146 let id_fingerprint = &sig.key_ids.id_fingerprint;
2147 if ok.contains(id_fingerprint) {
2148 continue;
2151 }
2152
2153 use DirectorySignatureHashAlgo as DSHA;
2154 use KeywordOrString as KOS;
2155
2156 let d: Option<&[u8]> = match sig.digest_algo.algorithm() {
2157 KOS::Known(DSHA::Sha256) => self.hashes.sha256.as_ref().map(|a| &a[..]),
2158 KOS::Known(DSHA::Sha1) => self.hashes.sha1.as_ref().map(|a| &a[..]),
2160 _ => None, };
2162 if d.is_none() {
2163 continue;
2166 }
2167
2168 #[allow(clippy::unwrap_used)]
2170 match sig.check_signature(d.as_ref().unwrap(), certs) {
2171 SigCheckResult::Valid => {
2172 ok.insert(*id_fingerprint);
2173 }
2174 _ => continue,
2175 }
2176 }
2177
2178 ok.len() > (n_authorities / 2)
2179 }
2180}
2181
2182#[cfg(test)]
2183mod test {
2184 #![allow(clippy::bool_assert_comparison)]
2186 #![allow(clippy::clone_on_copy)]
2187 #![allow(clippy::dbg_macro)]
2188 #![allow(clippy::mixed_attributes_style)]
2189 #![allow(clippy::print_stderr)]
2190 #![allow(clippy::print_stdout)]
2191 #![allow(clippy::single_char_pattern)]
2192 #![allow(clippy::unwrap_used)]
2193 #![allow(clippy::unchecked_time_subtraction)]
2194 #![allow(clippy::useless_vec)]
2195 #![allow(clippy::needless_pass_by_value)]
2196 use super::*;
2198 use hex_literal::hex;
2199 #[cfg(feature = "incomplete")]
2200 use {
2201 crate::parse2::{NetdocUnverified as _, ParseInput, parse_netdoc},
2202 std::fs,
2203 };
2204
2205 const CERTS: &str = include_str!("../../testdata/authcerts2.txt");
2206 const CONSENSUS: &str = include_str!("../../testdata/mdconsensus1.txt");
2207
2208 const PLAIN_CERTS: &str = include_str!("../../testdata2/cached-certs");
2209 const PLAIN_CONSENSUS: &str = include_str!("../../testdata2/cached-consensus");
2210
2211 fn read_bad(fname: &str) -> String {
2212 use std::fs;
2213 use std::path::PathBuf;
2214 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
2215 path.push("testdata");
2216 path.push("bad-mdconsensus");
2217 path.push(fname);
2218
2219 fs::read_to_string(path).unwrap()
2220 }
2221
2222 #[test]
2223 fn parse_and_validate_md() -> Result<()> {
2224 use std::net::SocketAddr;
2225 use tor_checkable::{SelfSigned, Timebound};
2226 let mut certs = Vec::new();
2227 for cert in AuthCert::parse_multiple(CERTS)? {
2228 let cert = cert?.check_signature()?.dangerously_assume_timely();
2229 certs.push(cert);
2230 }
2231 let auth_ids: Vec<_> = certs.iter().map(|c| c.id_fingerprint()).collect();
2232
2233 assert_eq!(certs.len(), 3);
2234
2235 let (_, _, consensus) = MdConsensus::parse(CONSENSUS)?;
2236 let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
2237
2238 assert!(consensus.authorities_are_correct(&auth_ids));
2240 assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
2242 {
2243 let bad_auth_id = (*b"xxxxxxxxxxxxxxxxxxxx").into();
2246 assert!(!consensus.authorities_are_correct(&[&bad_auth_id]));
2247 }
2248
2249 let missing = consensus.key_is_correct(&[]).err().unwrap();
2250 assert_eq!(3, missing.len());
2251 assert!(consensus.key_is_correct(&certs).is_ok());
2252 let missing = consensus.key_is_correct(&certs[0..1]).err().unwrap();
2253 assert_eq!(2, missing.len());
2254
2255 let same_three_times = vec![certs[0].clone(), certs[0].clone(), certs[0].clone()];
2257 let missing = consensus.key_is_correct(&same_three_times).err().unwrap();
2258
2259 assert_eq!(2, missing.len());
2260 assert!(consensus.is_well_signed(&same_three_times).is_err());
2261
2262 assert!(consensus.key_is_correct(&certs).is_ok());
2263 let consensus = consensus.check_signature(&certs)?;
2264
2265 assert_eq!(6, consensus.relays().len());
2266 let r0 = &consensus.relays()[0];
2267 assert_eq!(
2268 r0.md_digest(),
2269 &hex!("73dabe0a0468f4f7a67810a18d11e36731bb1d2ec3634db459100609f3b3f535")
2270 );
2271 assert_eq!(
2272 r0.rsa_identity().as_bytes(),
2273 &hex!("0a3057af2910415794d8ea430309d9ac5f5d524b")
2274 );
2275 assert!(!r0.weight().is_measured());
2276 assert!(!r0.weight().is_nonzero());
2277 let pv = &r0.protovers();
2278 assert!(pv.supports_subver("HSDir", 2));
2279 assert!(!pv.supports_subver("HSDir", 3));
2280 let ip4 = "127.0.0.1:5002".parse::<SocketAddr>().unwrap();
2281 let ip6 = "[::1]:5002".parse::<SocketAddr>().unwrap();
2282 assert!(r0.addrs().any(|a| a == ip4));
2283 assert!(r0.addrs().any(|a| a == ip6));
2284
2285 Ok(())
2286 }
2287
2288 #[test]
2289 fn parse_and_validate_ns() -> Result<()> {
2290 use tor_checkable::{SelfSigned, Timebound};
2291 let mut certs = Vec::new();
2292 for cert in AuthCert::parse_multiple(PLAIN_CERTS)? {
2293 let cert = cert?.check_signature()?.dangerously_assume_timely();
2294 certs.push(cert);
2295 }
2296 let auth_ids: Vec<_> = certs.iter().map(|c| c.id_fingerprint()).collect();
2297 assert_eq!(certs.len(), 4);
2298
2299 let (_, _, consensus) = PlainConsensus::parse(PLAIN_CONSENSUS)?;
2300 let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
2301 assert!(consensus.authorities_are_correct(&auth_ids));
2303 assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
2305
2306 assert!(consensus.key_is_correct(&certs).is_ok());
2307
2308 let _consensus = consensus.check_signature(&certs)?;
2309
2310 Ok(())
2311 }
2312
2313 #[test]
2314 #[cfg(feature = "incomplete")]
2315 fn parse2_vote() -> anyhow::Result<()> {
2316 let file = "testdata2/v3-status-votes--1";
2317 let text = fs::read_to_string(file)?;
2318
2319 use crate::parse2::poc::netstatus::NetworkStatusUnverifiedVote;
2321
2322 let input = ParseInput::new(&text, file);
2323 let doc: NetworkStatusUnverifiedVote = parse_netdoc(&input)?;
2324
2325 println!("{doc:?}");
2326 println!("{:#?}", doc.inspect_unverified().0.r[0]);
2327
2328 Ok(())
2329 }
2330
2331 #[test]
2332 fn test_bad() {
2333 use crate::Pos;
2334 fn check(fname: &str, e: &Error) {
2335 let content = read_bad(fname);
2336 let res = MdConsensus::parse(&content);
2337 assert!(res.is_err());
2338 assert_eq!(&res.err().unwrap(), e);
2339 }
2340
2341 check(
2342 "bad-flags",
2343 &EK::BadArgument
2344 .at_pos(Pos::from_line(27, 1))
2345 .with_msg("Flags out of order"),
2346 );
2347 check(
2348 "bad-md-digest",
2349 &EK::BadArgument
2350 .at_pos(Pos::from_line(40, 3))
2351 .with_msg("Invalid base64"),
2352 );
2353 check(
2354 "bad-weight",
2355 &EK::BadArgument
2356 .at_pos(Pos::from_line(67, 141))
2357 .with_msg("invalid digit found in string"),
2358 );
2359 check(
2360 "bad-weights",
2361 &EK::BadArgument
2362 .at_pos(Pos::from_line(51, 13))
2363 .with_msg("invalid digit found in string"),
2364 );
2365 check(
2366 "wrong-order",
2367 &EK::WrongSortOrder.at_pos(Pos::from_line(52, 1)),
2368 );
2369 check(
2370 "wrong-start",
2371 &EK::UnexpectedToken
2372 .with_msg("vote-status")
2373 .at_pos(Pos::from_line(1, 1)),
2374 );
2375 check("wrong-version", &EK::BadDocumentVersion.with_msg("10"));
2376 }
2377
2378 fn gettok(s: &str) -> Result<Item<'_, NetstatusKwd>> {
2379 let mut reader = NetDocReader::new(s)?;
2380 let tok = reader.next().unwrap();
2381 assert!(reader.next().is_none());
2382 tok
2383 }
2384
2385 #[test]
2386 fn test_weight() {
2387 let w = gettok("w Unmeasured=1 Bandwidth=6\n").unwrap();
2388 let w = RelayWeightsItem::from_item(&w).unwrap();
2389 assert!(!w.effective.is_measured());
2390 assert!(w.effective.is_nonzero());
2391
2392 let w = gettok("w Bandwidth=10\n").unwrap();
2393 let w = RelayWeightsItem::from_item(&w).unwrap();
2394 assert!(w.effective.is_measured());
2395 assert!(w.effective.is_nonzero());
2396
2397 let w = RelayWeightsItem::new_no_info();
2398 assert!(!w.effective.is_measured());
2399 assert!(!w.effective.is_nonzero());
2400
2401 let w = gettok("w Mustelid=66 Cheato=7 Unmeasured=1\n").unwrap();
2402 let w = RelayWeightsItem::from_item(&w).unwrap();
2403 assert!(!w.effective.is_measured());
2404 assert!(!w.effective.is_nonzero());
2405
2406 let w = gettok("r foo\n").unwrap();
2407 let w = RelayWeightsItem::from_item(&w);
2408 assert!(w.is_err());
2409
2410 let w = gettok("r Bandwidth=6 Unmeasured=Frog\n").unwrap();
2411 let w = RelayWeightsItem::from_item(&w);
2412 assert!(w.is_err());
2413
2414 let w = gettok("r Bandwidth=6 Unmeasured=3\n").unwrap();
2415 let w = RelayWeightsItem::from_item(&w);
2416 assert!(w.is_err());
2417 }
2418
2419 #[test]
2420 fn test_netparam() {
2421 let p = "Hello=600 Goodbye=5 Fred=7"
2422 .parse::<NetParams<u32>>()
2423 .unwrap();
2424 assert_eq!(p.get("Hello"), Some(&600_u32));
2425
2426 let p = "Hello=Goodbye=5 Fred=7".parse::<NetParams<u32>>();
2427 assert!(p.is_err());
2428
2429 let p = "Hello=Goodbye Fred=7".parse::<NetParams<u32>>();
2430 assert!(p.is_err());
2431
2432 for bad_kw in ["What=The", "", "\n", "\0"] {
2433 let p = [(bad_kw, 42)].into_iter().collect::<NetParams<i32>>();
2434 let mut d = NetdocEncoder::new();
2435 let d = (|| {
2436 let i = d.item("bad-psrams");
2437 p.write_item_value_onto(i)?;
2438 d.finish()
2439 })();
2440 let _: tor_error::Bug = d.expect_err(bad_kw);
2441 }
2442 }
2443
2444 #[test]
2445 fn test_sharedrand() {
2446 let sr =
2447 gettok("shared-rand-previous-value 9 5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4\n")
2448 .unwrap();
2449 let sr = SharedRandStatus::from_item(&sr).unwrap();
2450
2451 assert_eq!(sr.n_reveals, 9);
2452 assert_eq!(
2453 sr.value.0,
2454 hex!("e4ba1d638c96c458532adc6957dc0080d03d37c7e5854087d0da90bf5ff4e72e")
2455 );
2456 assert!(sr.timestamp.is_none());
2457
2458 let sr2 = gettok(
2459 "shared-rand-current-value 9 \
2460 5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4 2022-01-20T12:34:56\n",
2461 )
2462 .unwrap();
2463 let sr2 = SharedRandStatus::from_item(&sr2).unwrap();
2464 assert_eq!(sr2.n_reveals, sr.n_reveals);
2465 assert_eq!(sr2.value.0, sr.value.0);
2466 assert_eq!(
2467 sr2.timestamp.unwrap().0,
2468 humantime::parse_rfc3339("2022-01-20T12:34:56Z").unwrap()
2469 );
2470
2471 let sr = gettok("foo bar\n").unwrap();
2472 let sr = SharedRandStatus::from_item(&sr);
2473 assert!(sr.is_err());
2474 }
2475
2476 #[test]
2477 fn test_protostatus() {
2478 let my_protocols: Protocols = "Link=7 Cons=1-5 Desc=3-10".parse().unwrap();
2479
2480 let outcome = ProtoStatus {
2481 recommended: "Link=7".parse().unwrap(),
2482 required: "Desc=5".parse().unwrap(),
2483 }
2484 .check_protocols(&my_protocols);
2485 assert!(outcome.is_ok());
2486
2487 let outcome = ProtoStatus {
2488 recommended: "Microdesc=4 Link=7".parse().unwrap(),
2489 required: "Desc=5".parse().unwrap(),
2490 }
2491 .check_protocols(&my_protocols);
2492 assert_eq!(
2493 outcome,
2494 Err(ProtocolSupportError::MissingRecommended(
2495 "Microdesc=4".parse().unwrap()
2496 ))
2497 );
2498
2499 let outcome = ProtoStatus {
2500 recommended: "Microdesc=4 Link=7".parse().unwrap(),
2501 required: "Desc=5 Cons=5-12 Wombat=15".parse().unwrap(),
2502 }
2503 .check_protocols(&my_protocols);
2504 assert_eq!(
2505 outcome,
2506 Err(ProtocolSupportError::MissingRequired(
2507 "Cons=6-12 Wombat=15".parse().unwrap()
2508 ))
2509 );
2510 }
2511
2512 #[test]
2513 fn serialize_protostatus() {
2514 let ps = ProtoStatuses {
2515 client: ProtoStatus {
2516 recommended: "Link=1-5 LinkAuth=2-5".parse().unwrap(),
2517 required: "Link=5 LinkAuth=3".parse().unwrap(),
2518 },
2519 relay: ProtoStatus {
2520 recommended: "Wombat=20-30 Knish=20-30".parse().unwrap(),
2521 required: "Wombat=20-22 Knish=25-27".parse().unwrap(),
2522 },
2523 };
2524 let json = serde_json::to_string(&ps).unwrap();
2525 let ps2 = serde_json::from_str(json.as_str()).unwrap();
2526 assert_eq!(ps, ps2);
2527
2528 let ps3: ProtoStatuses = serde_json::from_str(
2529 r#"{
2530 "client":{
2531 "required":"Link=5 LinkAuth=3",
2532 "recommended":"Link=1-5 LinkAuth=2-5"
2533 },
2534 "relay":{
2535 "required":"Wombat=20-22 Knish=25-27",
2536 "recommended":"Wombat=20-30 Knish=20-30"
2537 }
2538 }"#,
2539 )
2540 .unwrap();
2541 assert_eq!(ps, ps3);
2542 }
2543}