tor_netdoc/parse2/poc/netstatus/
flavoured.rs1use super::super::*;
15
16const TOPLEVEL_DOCTYPE_FOR_ERROR: &str = ns_expr!(
18 "NetworkStatusVote",
19 "NetworkStatusNs",
20 "NetworkStatusMd",
21);
22
23pub type Router = ns_type!(
25 crate::doc::netstatus::VoteRouterStatus,
26 crate::doc::netstatus::PlainRouterStatus,
27 crate::doc::netstatus::MdRouterStatus,
28);
29
30#[derive(Deftly, Clone, Debug)]
35#[derive_deftly(NetdocParseable, NetdocSigned)]
36#[deftly(netdoc(doctype_for_error = "TOPLEVEL_DOCTYPE_FOR_ERROR"))]
37#[non_exhaustive]
38pub struct NetworkStatus {
39 pub network_status_version: (NdaNetworkStatusVersion, NdaNetworkStatusVersionFlavour),
41
42 pub vote_status: NdiVoteStatus,
44
45 pub published: ns_type!(
47 (NdaSystemTimeDeprecatedSyntax,),
48 Option<Void>,
49 ),
50
51 pub valid_after: (NdaSystemTimeDeprecatedSyntax,),
53
54 pub valid_until: (NdaSystemTimeDeprecatedSyntax,),
56
57 pub voting_delay: NdiVotingDelay,
59
60 #[deftly(netdoc(default))]
62 pub params: NdiParams,
63
64 #[deftly(netdoc(subdoc))]
66 pub authority: NddAuthoritySection,
67
68 #[deftly(netdoc(subdoc))]
70 pub r: Vec<Router>,
71
72 #[deftly(netdoc(subdoc))]
74 pub directory_footer: Option<NddDirectoryFooter>,
75}
76
77#[derive(Deftly, Clone, Debug)]
79#[derive_deftly(NetdocParseable)]
80#[deftly(netdoc(signatures))]
81#[non_exhaustive]
82pub struct NetworkStatusSignatures {
83 pub directory_signature: ns_type!(NdiDirectorySignature, Vec<NdiDirectorySignature>),
85}
86
87#[derive(Deftly, Clone, Debug, Hash, Eq, PartialEq)]
93#[derive_deftly(ItemValueParseable)]
94#[non_exhaustive]
95pub struct NdiVoteStatus {
96 pub status: NdaVoteStatus,
98}
99
100#[derive(Clone, Debug, Hash, Eq, PartialEq)]
102#[non_exhaustive]
103pub struct NdaVoteStatus {}
104
105#[derive(Clone, Debug, Hash, Eq, PartialEq)]
107#[non_exhaustive]
108pub struct NdaNetworkStatusVersionFlavour {}
109
110const NDA_NETWORK_STATUS_VERSION_FLAVOUR: Option<&str> = ns_expr!(None, None, Some("microdesc"));
112
113impl ItemArgumentParseable for NdaNetworkStatusVersionFlavour {
114 fn from_args<'s>(args: &mut ArgumentStream<'s>)
115 -> Result<Self, AE>
116 {
117 let exp: Option<&str> = NDA_NETWORK_STATUS_VERSION_FLAVOUR;
118 if let Some(exp) = exp {
119 let got = args.next().ok_or(AE::Missing)?;
120 if got != exp { return Err(AE::Invalid) };
121 } else {
122 args.reject_extra_args()?;
126 }
127 Ok(Self {})
128 }
129}
130
131const NDA_VOTE_STATUS: &str = ns_expr!("vote", "consensus", "consensus");
133
134impl FromStr for NdaVoteStatus {
135 type Err = InvalidNetworkStatusVoteStatus;
136 fn from_str(s: &str) -> Result<Self, InvalidNetworkStatusVoteStatus> {
137 if s == NDA_VOTE_STATUS {
138 Ok(Self {})
139 } else {
140 Err(InvalidNetworkStatusVoteStatus { })
141 }
142 }
143}
144
145impl Display for NdaVoteStatus {
146 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
147 Display::fmt(NDA_VOTE_STATUS, f)
148 }
149}
150
151impl NormalItemArgument for NdaVoteStatus {}
152
153#[derive(Deftly, Clone, Debug, Hash, Eq, PartialEq)]
155#[derive_deftly(ItemValueParseable)]
156#[non_exhaustive]
157pub struct NdiVotingDelay {
158 pub vote_seconds: u32,
160 pub dist_seconds: u32,
162}
163
164#[derive(Deftly, Clone, Debug)]
166#[derive_deftly(NetdocParseable)]
167#[non_exhaustive]
168pub struct NddDirectoryFooter {
169 pub directory_footer: (),
171}
172
173#[derive(Deftly, Clone, Debug)]
175#[derive_deftly(NetdocParseable)]
176#[non_exhaustive]
177pub struct NddAuthorityEntry {
178 pub dir_source: NdiAuthorityDirSource,
180}
181
182#[derive(Deftly, Clone, Debug)]
184#[derive_deftly(ItemValueParseable)]
185#[non_exhaustive]
186pub struct NdiAuthorityDirSource {
187 pub nickname: types::Nickname,
189 pub h_p_auth_id_rsa: types::Fingerprint,
191}
192
193ns_choose! { (
194 define_derive_deftly! {
195 NddAuthoritySection:
196
197 impl NetdocParseable for NddAuthoritySection {
198 fn doctype_for_error() -> &'static str {
199 "vote.authority.section"
200 }
201 fn is_intro_item_keyword(kw: KeywordRef<'_>) -> bool {
202 NddAuthorityEntry::is_intro_item_keyword(kw)
203 }
204 fn from_items<'s>(
205 input: &mut ItemStream<'s>,
206 stop_outer: stop_at!(),
207 ) -> Result<Self, ErrorProblem> {
208 let stop_inner = stop_outer
209 $(
210 | StopAt($ftype::is_intro_item_keyword)
211 )
212 ;
213 Ok(NddAuthoritySection { $(
214 $fname: NetdocParseable::from_items(input, stop_inner)?,
215 ) })
216 }
217 }
218 }
219
220 #[derive(Deftly, Clone, Debug)]
228 #[derive_deftly(NddAuthoritySection)]
229 #[non_exhaustive]
230 pub struct NddAuthoritySection {
231 pub authority: NddAuthorityEntry,
233 pub cert: authcert::DirAuthKeyCertSigned,
235 }
236)(
237 #[derive(Deftly, Clone, Debug)]
245 #[non_exhaustive]
246 pub struct NddAuthoritySection {
247 pub authorities: Vec<NddAuthorityEntryOrSuperseded>,
251 }
252
253 #[derive(Clone, Debug)]
255 #[non_exhaustive]
256 pub enum NddAuthorityEntryOrSuperseded {
257 Entry(NddAuthorityEntry),
259 Superseded(NdiAuthorityDirSource),
263 }
264
265 impl NetdocParseable for NddAuthoritySection {
266 fn doctype_for_error() -> &'static str {
267 "consensus.authority.section"
268 }
269 fn is_intro_item_keyword(kw: KeywordRef<'_>) -> bool {
270 NddAuthorityEntry::is_intro_item_keyword(kw)
271 }
272 fn from_items(
273 input: &mut ItemStream<'_>,
274 stop_outer: stop_at!(),
275 ) -> Result<Self, ErrorProblem> {
276 let is_our_keyword = NddAuthorityEntry::is_intro_item_keyword;
277 let stop_inner = stop_outer | StopAt(is_our_keyword);
278 let mut authorities = vec![];
279 loop {
280 let Some(peek) = input.peek_keyword()? else { break };
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::Other(
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::Other(
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.authority.cert.clone();
338 let cert = cert.verify_selfcert(now)?;
339
340 netstatus::verify_general_timeless(
341 slice::from_ref(&self.signatures.directory_signature),
342 &[*cert.h_kp_auth_id_rsa.0],
343 &[&cert],
344 1,
345 )?;
346
347 Ok(self.unwrap_unverified())
348 }
349 }
350
351 impl NetworkStatus {
352 pub fn h_kp_auth_id_rsa(&self) -> pk::rsa::RsaIdentity {
362 *self.authority.cert
363 .inspect_unverified()
367 .0
368 .h_kp_auth_id_rsa.0
369 }
370 }
371) (
372 impl NetworkStatusSigned {
373 pub fn verify(
389 self,
390 now: SystemTime,
391 authorities: &[pk::rsa::RsaIdentity],
392 certs: &[&DirAuthKeyCert],
393 ) -> Result<(NetworkStatus, NetworkStatusSignatures), VF> {
394 let threshold = authorities.len() / 2 + 1; let validity_start = self.body.valid_after.0
396 .checked_sub(Duration::from_secs(self.body.voting_delay.dist_seconds.into()))
397 .ok_or(VF::Other)?;
398 check_validity_time(now, validity_start..= *self.body.valid_until.0)?;
399
400 netstatus::verify_general_timeless(
401 &self.signatures.directory_signature,
402 authorities,
403 certs,
404 threshold,
405 )?;
406
407 Ok(self.unwrap_unverified())
408 }
409 }
410)}