tor_netdoc/parse2/poc/netstatus/
flavoured.rs1use super::super::*;
15
16const TOPLEVEL_DOCTYPE_FOR_ERROR: &str =
18 ns_expr!("NetworkStatusVote", "NetworkStatusNs", "NetworkStatusMd",);
19
20pub type Router = ns_type!(
22 crate::doc::netstatus::VoteRouterStatus,
23 crate::doc::netstatus::PlainRouterStatus,
24 crate::doc::netstatus::MdRouterStatus,
25);
26
27#[derive(Deftly, Clone, Debug)]
32#[derive_deftly(NetdocParseable, NetdocSigned)]
33#[deftly(netdoc(doctype_for_error = "TOPLEVEL_DOCTYPE_FOR_ERROR"))]
34#[non_exhaustive]
35pub struct NetworkStatus {
36 pub network_status_version: (NdaNetworkStatusVersion, NdaNetworkStatusVersionFlavour),
38
39 pub vote_status: NdiVoteStatus,
41
42 pub published: ns_type!((NdaSystemTimeDeprecatedSyntax,), Option<Void>,),
44
45 pub valid_after: (NdaSystemTimeDeprecatedSyntax,),
47
48 pub valid_until: (NdaSystemTimeDeprecatedSyntax,),
50
51 pub voting_delay: NdiVotingDelay,
53
54 #[deftly(netdoc(default))]
56 pub params: NdiParams,
57
58 #[deftly(netdoc(subdoc))]
60 pub authority: NddAuthoritySection,
61
62 #[deftly(netdoc(subdoc))]
64 pub r: Vec<Router>,
65
66 #[deftly(netdoc(subdoc))]
68 pub directory_footer: Option<NddDirectoryFooter>,
69}
70
71#[derive(Deftly, Clone, Debug)]
73#[derive_deftly(NetdocParseable)]
74#[deftly(netdoc(signatures))]
75#[non_exhaustive]
76pub struct NetworkStatusSignatures {
77 pub directory_signature: ns_type!(NdiDirectorySignature, Vec<NdiDirectorySignature>),
79}
80
81#[derive(Deftly, Clone, Debug, Hash, Eq, PartialEq)]
87#[derive_deftly(ItemValueParseable)]
88#[non_exhaustive]
89pub struct NdiVoteStatus {
90 pub status: NdaVoteStatus,
92}
93
94#[derive(Clone, Debug, Hash, Eq, PartialEq)]
96#[non_exhaustive]
97pub struct NdaVoteStatus {}
98
99#[derive(Clone, Debug, Hash, Eq, PartialEq)]
101#[non_exhaustive]
102pub struct NdaNetworkStatusVersionFlavour {}
103
104const NDA_NETWORK_STATUS_VERSION_FLAVOUR: Option<&str> = ns_expr!(None, None, Some("microdesc"));
106
107impl ItemArgumentParseable for NdaNetworkStatusVersionFlavour {
108 fn from_args<'s>(args: &mut ArgumentStream<'s>) -> Result<Self, AE> {
109 let exp: Option<&str> = NDA_NETWORK_STATUS_VERSION_FLAVOUR;
110 if let Some(exp) = exp {
111 let got = args.next().ok_or(AE::Missing)?;
112 if got != exp {
113 return Err(AE::Invalid);
114 };
115 } else {
116 args.reject_extra_args()?;
120 }
121 Ok(Self {})
122 }
123}
124
125const NDA_VOTE_STATUS: &str = ns_expr!("vote", "consensus", "consensus");
127
128impl FromStr for NdaVoteStatus {
129 type Err = InvalidNetworkStatusVoteStatus;
130 fn from_str(s: &str) -> Result<Self, InvalidNetworkStatusVoteStatus> {
131 if s == NDA_VOTE_STATUS {
132 Ok(Self {})
133 } else {
134 Err(InvalidNetworkStatusVoteStatus {})
135 }
136 }
137}
138
139impl Display for NdaVoteStatus {
140 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
141 Display::fmt(NDA_VOTE_STATUS, f)
142 }
143}
144
145impl NormalItemArgument for NdaVoteStatus {}
146
147#[derive(Deftly, Clone, Debug, Hash, Eq, PartialEq)]
149#[derive_deftly(ItemValueParseable)]
150#[non_exhaustive]
151pub struct NdiVotingDelay {
152 pub vote_seconds: u32,
154 pub dist_seconds: u32,
156}
157
158#[derive(Deftly, Clone, Debug)]
160#[derive_deftly(NetdocParseable)]
161#[non_exhaustive]
162pub struct NddDirectoryFooter {
163 pub directory_footer: (),
165}
166
167#[derive(Deftly, Clone, Debug)]
169#[derive_deftly(NetdocParseable)]
170#[non_exhaustive]
171pub struct NddAuthorityEntry {
172 pub dir_source: NdiAuthorityDirSource,
174}
175
176#[derive(Deftly, Clone, Debug)]
178#[derive_deftly(ItemValueParseable)]
179#[non_exhaustive]
180pub struct NdiAuthorityDirSource {
181 pub nickname: types::Nickname,
183 pub h_p_auth_id_rsa: types::Fingerprint,
185}
186
187ns_choose! { (
188 define_derive_deftly! {
189 NddAuthoritySection:
190
191 impl NetdocParseable for NddAuthoritySection {
192 fn doctype_for_error() -> &'static str {
193 "vote.authority.section"
194 }
195 fn is_intro_item_keyword(kw: KeywordRef<'_>) -> bool {
196 NddAuthorityEntry::is_intro_item_keyword(kw)
197 }
198 fn is_structural_keyword(kw: KeywordRef<'_>) -> Option<IsStructural> {
199 NddAuthorityEntry::is_structural_keyword(kw)
200 .or_else(|| authcert::DirAuthKeyCertSigned::is_structural_keyword(kw))
201 }
202 fn from_items<'s>(
203 input: &mut ItemStream<'s>,
204 stop_outer: stop_at!(),
205 ) -> Result<Self, ErrorProblem> {
206 let stop_inner = stop_outer
207 $(
208 | StopAt($ftype::is_intro_item_keyword)
209 )
210 ;
211 Ok(NddAuthoritySection { $(
212 $fname: NetdocParseable::from_items(input, stop_inner)?,
213 ) })
214 }
215 }
216 }
217
218 #[derive(Deftly, Clone, Debug)]
226 #[derive_deftly(NddAuthoritySection)]
227 #[non_exhaustive]
228 pub struct NddAuthoritySection {
229 pub authority: NddAuthorityEntry,
231 pub cert: crate::doc::authcert::EncodedAuthCert,
233 }
234)(
235 #[derive(Deftly, Clone, Debug)]
243 #[non_exhaustive]
244 pub struct NddAuthoritySection {
245 pub authorities: Vec<NddAuthorityEntryOrSuperseded>,
249 }
250
251 #[derive(Clone, Debug)]
253 #[non_exhaustive]
254 pub enum NddAuthorityEntryOrSuperseded {
255 Entry(NddAuthorityEntry),
257 Superseded(NdiAuthorityDirSource),
261 }
262
263 impl NetdocParseable for NddAuthoritySection {
264 fn doctype_for_error() -> &'static str {
265 "consensus.authority.section"
266 }
267 fn is_intro_item_keyword(kw: KeywordRef<'_>) -> bool {
268 NddAuthorityEntry::is_intro_item_keyword(kw)
269 }
270 fn is_structural_keyword(kw: KeywordRef<'_>) -> Option<IsStructural> {
271 NddAuthorityEntry::is_structural_keyword(kw)
272 }
273 fn from_items(
274 input: &mut ItemStream<'_>,
275 stop_outer: stop_at!(),
276 ) -> Result<Self, ErrorProblem> {
277 let is_our_keyword = NddAuthorityEntry::is_intro_item_keyword;
278 let stop_inner = stop_outer | StopAt(is_our_keyword);
279 let mut authorities = vec![];
280 while let Some(peek) = input.peek_keyword()? {
281 if !is_our_keyword(peek) { break };
282
283 let mut lookahead = input.clone();
285 let _: UnparsedItem<'_> = lookahead.next().expect("peeked")?;
286
287 let entry = match lookahead.next().transpose()? {
288 Some(item) if !stop_inner.stop_at(item.keyword()) => {
289 let entry = NddAuthorityEntry::from_items(input, stop_inner)?;
291 NddAuthorityEntryOrSuperseded::Entry(entry)
292 }
293 None | Some(_) => {
294 let item = input.next().expect("just peeked")?;
298 let entry = NdiAuthorityDirSource::from_unparsed(item)?;
299 if !entry.nickname.as_str().ends_with("-legacy") {
300 return Err(EP::OtherBadDocument(
301 "authority entry lacks mandatory fields (eg `contact`) so is not a proper (non-superseded) entry, but nickname lacks `-legacy` suffix so is not a superseded entry"
302 ))
303 }
304 NddAuthorityEntryOrSuperseded::Superseded(entry)
305 }
306 };
307 authorities.push(entry);
308 }
309 if !authorities.is_sorted_by_key(
310 |entry| matches!(entry, NddAuthorityEntryOrSuperseded::Superseded(_))
311 ) {
312 return Err(EP::OtherBadDocument(
313 "normal (non-superseded) authority entry follows superseded authority key entry"
314 ))
315 }
316
317 Ok(NddAuthoritySection { authorities })
318 }
319 }
320)}
321
322ns_choose! { (
323 impl NetworkStatusSigned {
324 pub fn verify_selfcert(
331 self,
332 now: SystemTime,
333 ) -> Result<(NetworkStatus, NetworkStatusSignatures), VF> {
334 let validity = *self.body.published.0 ..= *self.body.valid_until.0;
335 check_validity_time(now, validity)?;
336
337 let cert = self.body.parse_authcert()?.verify_selfcert(now)?;
338
339 netstatus::verify_general_timeless(
340 slice::from_ref(&self.signatures.directory_signature),
341 &[*cert.fingerprint],
342 &[&cert],
343 1,
344 )?;
345
346 Ok(self.unwrap_unverified())
347 }
348 }
349
350 impl NetworkStatus {
351 fn parse_authcert(&self) -> Result<crate::doc::authcert::AuthCertSigned, EP> {
353 let cert_input = ParseInput::new(
354 self.authority.cert.as_str(),
355 "<embedded auth cert>",
356 );
357 parse_netdoc(&cert_input).map_err(|e| e.problem)
358 }
359
360 pub fn h_kp_auth_id_rsa(&self) -> pk::rsa::RsaIdentity {
370 *self.parse_authcert()
371 .expect("was verified already!")
375 .inspect_unverified()
376 .0
377 .fingerprint
378 }
379 }
380) (
381 impl NetworkStatusSigned {
382 pub fn verify(
398 self,
399 now: SystemTime,
400 authorities: &[pk::rsa::RsaIdentity],
401 certs: &[&DirAuthKeyCert],
402 ) -> Result<(NetworkStatus, NetworkStatusSignatures), VF> {
403 let threshold = authorities.len() / 2 + 1; let validity_start = self.body.valid_after.0
405 .checked_sub(Duration::from_secs(self.body.voting_delay.dist_seconds.into()))
406 .ok_or(VF::Other)?;
407 check_validity_time(now, validity_start..= *self.body.valid_until.0)?;
408
409 netstatus::verify_general_timeless(
410 &self.signatures.directory_signature,
411 authorities,
412 certs,
413 threshold,
414 )?;
415
416 Ok(self.unwrap_unverified())
417 }
418 }
419)}