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
23#[derive(Deftly, Clone, Debug)]
28#[derive_deftly(NetdocParseable, NetdocSigned)]
29#[deftly(netdoc(doctype_for_error = "TOPLEVEL_DOCTYPE_FOR_ERROR"))]
30#[non_exhaustive]
31pub struct NetworkStatus {
32 pub network_status_version: (NdaNetworkStatusVersion, NdaNetworkStatusVersionFlavour),
34
35 pub vote_status: NdiVoteStatus,
37
38 pub published: ns_type!(
40 (NdaSystemTimeDeprecatedSyntax,),
41 Option<Void>,
42 ),
43
44 pub valid_after: (NdaSystemTimeDeprecatedSyntax,),
46
47 pub valid_until: (NdaSystemTimeDeprecatedSyntax,),
49
50 pub voting_delay: NdiVotingDelay,
52
53 #[deftly(netdoc(default))]
55 pub params: NdiParams,
56
57 #[deftly(netdoc(subdoc))]
59 pub authority: NddAuthoritySection,
60
61 #[deftly(netdoc(subdoc))]
63 pub r: Vec<Router>,
64
65 #[deftly(netdoc(subdoc))]
67 pub directory_footer: Option<NddDirectoryFooter>,
68}
69
70#[derive(Deftly, Clone, Debug)]
72#[derive_deftly(NetdocParseable)]
73#[deftly(netdoc(signatures))]
74#[non_exhaustive]
75pub struct NetworkStatusSignatures {
76 pub directory_signature: ns_type!(NdiDirectorySignature, Vec<NdiDirectorySignature>),
78}
79
80#[derive(Deftly, Clone, Debug, Hash, Eq, PartialEq)]
86#[derive_deftly(ItemValueParseable)]
87#[non_exhaustive]
88pub struct NdiVoteStatus {
89 pub status: NdaVoteStatus,
91}
92
93#[derive(Clone, Debug, Hash, Eq, PartialEq)]
95#[non_exhaustive]
96pub struct NdaVoteStatus {}
97
98#[derive(Clone, Debug, Hash, Eq, PartialEq)]
100#[non_exhaustive]
101pub struct NdaNetworkStatusVersionFlavour {}
102
103impl ItemArgumentParseable for NdaNetworkStatusVersionFlavour {
104 fn from_args<'s>(args: &mut ArgumentStream<'s>, field: &'static str)
105 -> Result<Self, ErrorProblem>
106 {
107 let exp: Option<&str> = ns_expr!(None, None, Some("microdesc"));
108 if let Some(exp) = exp {
109 let got = args.next().ok_or(EP::MissingArgument { field })?;
110 if got != exp { return Err(EP::InvalidArgument { field } ) };
111 } else {
112 args.reject_extra_args()?;
116 }
117 Ok(Self {})
118 }
119}
120
121impl FromStr for NdaVoteStatus {
122 type Err = InvalidNetworkStatusVoteStatus;
123 fn from_str(s: &str) -> Result<Self, InvalidNetworkStatusVoteStatus> {
124 let exp = ns_expr!("vote", "consensus", "consensus");
125 if s == exp {
126 Ok(Self {})
127 } else {
128 Err(InvalidNetworkStatusVoteStatus { })
129 }
130 }
131}
132
133#[derive(Deftly, Clone, Debug, Hash, Eq, PartialEq)]
135#[derive_deftly(ItemValueParseable)]
136#[non_exhaustive]
137pub struct NdiVotingDelay {
138 pub vote_seconds: u32,
140 pub dist_seconds: u32,
142}
143
144#[derive(Deftly, Clone, Debug)]
146#[derive_deftly(NetdocParseable)]
147#[non_exhaustive]
148pub struct Router {
149 pub r: NdiR,
151}
152
153#[derive(Deftly, Clone, Debug)]
155#[derive_deftly(NetdocParseable)]
156#[non_exhaustive]
157pub struct NddDirectoryFooter {
158 pub directory_footer: (),
160}
161
162#[derive(Deftly, Clone, Debug)]
164#[derive_deftly(NetdocParseable)]
165#[non_exhaustive]
166pub struct NddAuthorityEntry {
167 pub dir_source: NdiAuthorityDirSource,
169}
170
171#[derive(Deftly, Clone, Debug)]
173#[derive_deftly(ItemValueParseable)]
174#[non_exhaustive]
175pub struct NdiAuthorityDirSource {
176 pub nickname: types::Nickname,
178 pub h_p_auth_id_rsa: types::Fingerprint,
180}
181
182ns_choose! { (
183 define_derive_deftly! {
184 NddAuthoritySection:
185
186 impl NetdocParseable for NddAuthoritySection {
187 fn doctype_for_error() -> &'static str {
188 "vote.authority.section"
189 }
190 fn is_intro_item_keyword(kw: KeywordRef<'_>) -> bool {
191 NddAuthorityEntry::is_intro_item_keyword(kw)
192 }
193 fn from_items<'s>(
194 input: &mut ItemStream<'s>,
195 stop_outer: stop_at!(),
196 ) -> Result<Self, ErrorProblem> {
197 let stop_inner = stop_outer
198 $(
199 | StopAt($ftype::is_intro_item_keyword)
200 )
201 ;
202 Ok(NddAuthoritySection { $(
203 $fname: NetdocParseable::from_items(input, stop_inner)?,
204 ) })
205 }
206 }
207 }
208
209 #[derive(Deftly, Clone, Debug)]
217 #[derive_deftly(NddAuthoritySection)]
218 #[non_exhaustive]
219 pub struct NddAuthoritySection {
220 pub authority: NddAuthorityEntry,
222 pub cert: authcert::DirAuthKeyCertSigned,
224 }
225)(
226 #[derive(Deftly, Clone, Debug)]
234 #[non_exhaustive]
235 pub struct NddAuthoritySection {
236 pub authorities: Vec<NddAuthorityEntryOrSuperseded>,
240 }
241
242 #[derive(Clone, Debug)]
244 #[non_exhaustive]
245 pub enum NddAuthorityEntryOrSuperseded {
246 Entry(NddAuthorityEntry),
248 Superseded(NdiAuthorityDirSource),
252 }
253
254 impl NetdocParseable for NddAuthoritySection {
255 fn doctype_for_error() -> &'static str {
256 "consensus.authority.section"
257 }
258 fn is_intro_item_keyword(kw: KeywordRef<'_>) -> bool {
259 NddAuthorityEntry::is_intro_item_keyword(kw)
260 }
261 fn from_items(
262 input: &mut ItemStream<'_>,
263 stop_outer: stop_at!(),
264 ) -> Result<Self, ErrorProblem> {
265 let is_our_keyword = NddAuthorityEntry::is_intro_item_keyword;
266 let stop_inner = stop_outer | StopAt(is_our_keyword);
267 let mut authorities = vec![];
268 loop {
269 let Some(peek) = input.peek_keyword()? else { break };
270 if !is_our_keyword(peek) { break };
271
272 let mut lookahead = input.clone();
274 let _: UnparsedItem<'_> = lookahead.next().expect("peeked")?;
275
276 let entry = match lookahead.next().transpose()? {
277 Some(item) if !stop_inner.stop_at(item.keyword()) => {
278 let entry = NddAuthorityEntry::from_items(input, stop_inner)?;
280 NddAuthorityEntryOrSuperseded::Entry(entry)
281 }
282 None | Some(_) => {
283 let item = input.next().expect("just peeked")?;
287 let entry = NdiAuthorityDirSource::from_unparsed(item)?;
288 if !entry.nickname.as_str().ends_with("-legacy") {
289 return Err(EP::Other(
290 "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"
291 ))
292 }
293 NddAuthorityEntryOrSuperseded::Superseded(entry)
294 }
295 };
296 authorities.push(entry);
297 }
298 if !authorities.is_sorted_by_key(
299 |entry| matches!(entry, NddAuthorityEntryOrSuperseded::Superseded(_))
300 ) {
301 return Err(EP::Other(
302 "normal (non-superseded) authority entry follows superseded authority key entry"
303 ))
304 }
305
306 Ok(NddAuthoritySection { authorities })
307 }
308 }
309)}
310
311ns_choose! { (
312 impl NetworkStatusSigned {
313 pub fn verify_selfcert(
320 self,
321 now: SystemTime,
322 ) -> Result<(NetworkStatus, NetworkStatusSignatures), VF> {
323 let validity = *self.body.published.0 ..= *self.body.valid_until.0;
324 check_validity_time(now, validity)?;
325
326 let cert = self.body.authority.cert.clone();
327 let cert = cert.verify_selfcert(now)?;
328
329 netstatus::verify_general_timeless(
330 slice::from_ref(&self.signatures.directory_signature),
331 &[*cert.h_kp_auth_id_rsa.0],
332 &[&cert],
333 1,
334 )?;
335
336 Ok(self.unwrap_unverified())
337 }
338 }
339
340 impl NetworkStatus {
341 pub fn h_kp_auth_id_rsa(&self) -> pk::rsa::RsaIdentity {
351 *self.authority.cert
352 .inspect_unverified()
356 .0
357 .h_kp_auth_id_rsa.0
358 }
359 }
360) (
361 impl NetworkStatusSigned {
362 pub fn verify(
378 self,
379 now: SystemTime,
380 authorities: &[pk::rsa::RsaIdentity],
381 certs: &[&DirAuthKeyCert],
382 ) -> Result<(NetworkStatus, NetworkStatusSignatures), VF> {
383 let threshold = authorities.len() / 2 + 1; let validity_start = self.body.valid_after.0
385 .checked_sub(Duration::from_secs(self.body.voting_delay.dist_seconds.into()))
386 .ok_or(VF::Other)?;
387 check_validity_time(now, validity_start..= *self.body.valid_until.0)?;
388
389 netstatus::verify_general_timeless(
390 &self.signatures.directory_signature,
391 authorities,
392 certs,
393 threshold,
394 )?;
395
396 Ok(self.unwrap_unverified())
397 }
398 }
399)}