tor_netdoc/doc/netstatus/
each_flavor.rs1use super::*;
14
15ns_use_this_variety! {
16 use [crate::doc::netstatus::rs]::?::{RouterStatus};
17}
18#[cfg(feature = "build_docs")]
19ns_use_this_variety! {
20 pub(crate) use [crate::doc::netstatus::build]::?::{ConsensusBuilder};
21 pub use [crate::doc::netstatus::rs::build]::?::{RouterStatusBuilder};
22}
23
24#[derive(Debug, Clone)]
29#[non_exhaustive]
30pub struct Consensus {
31 pub header: Header,
33 pub voters: Vec<ConsensusVoterInfo>,
35 pub relays: Vec<RouterStatus>,
41 pub footer: Footer,
43}
44
45impl Consensus {
46 pub fn lifetime(&self) -> &Lifetime {
48 &self.header.lifetime
49 }
50
51 pub fn relays(&self) -> &[RouterStatus] {
53 &self.relays[..]
54 }
55
56 pub fn bandwidth_weights(&self) -> &NetParams<i32> {
59 &self.footer.weights
60 }
61
62 pub fn params(&self) -> &NetParams<i32> {
64 &self.header.params
65 }
66
67 pub fn shared_rand_cur(&self) -> Option<&SharedRandStatus> {
70 self.header.shared_rand_cur.as_ref()
71 }
72
73 pub fn shared_rand_prev(&self) -> Option<&SharedRandStatus> {
76 self.header.shared_rand_prev.as_ref()
77 }
78
79 pub fn relay_protocol_status(&self) -> &ProtoStatus {
82 &self.header.proto_statuses.relay
83 }
84
85 pub fn client_protocol_status(&self) -> &ProtoStatus {
88 &self.header.proto_statuses.client
89 }
90
91 pub fn protocol_statuses(&self) -> &Arc<ProtoStatuses> {
93 &self.header.proto_statuses
94 }
95}
96
97impl Consensus {
98 #[cfg(feature = "build_docs")]
103 pub fn builder() -> ConsensusBuilder {
104 ConsensusBuilder::new(RouterStatus::flavor())
105 }
106
107 pub fn parse(s: &str) -> Result<(&str, &str, UncheckedConsensus)> {
109 let mut reader = NetDocReader::new(s)?;
110 Self::parse_from_reader(&mut reader).map_err(|e| e.within(s))
111 }
112 fn take_voterinfo(
115 r: &mut NetDocReader<'_, NetstatusKwd>,
116 ) -> Result<Option<ConsensusVoterInfo>> {
117 use NetstatusKwd::*;
118
119 match r.peek() {
120 None => return Ok(None),
121 Some(e) if e.is_ok_with_kwd_in(&[RS_R, DIRECTORY_FOOTER]) => return Ok(None),
122 _ => (),
123 };
124
125 let mut first_dir_source = true;
126 let mut p = r.pause_at(|i| match i {
129 Err(_) => false,
130 Ok(item) => {
131 item.kwd() == RS_R
132 || if item.kwd() == DIR_SOURCE {
133 let was_first = first_dir_source;
134 first_dir_source = false;
135 !was_first
136 } else {
137 false
138 }
139 }
140 });
141
142 let voter_sec = NS_VOTERINFO_RULES_CONSENSUS.parse(&mut p)?;
143 let voter = ConsensusVoterInfo::from_section(&voter_sec)?;
144
145 Ok(Some(voter))
146 }
147
148 fn take_footer(r: &mut NetDocReader<'_, NetstatusKwd>) -> Result<Footer> {
150 use NetstatusKwd::*;
151 let mut p = r.pause_at(|i| i.is_ok_with_kwd_in(&[DIRECTORY_SIGNATURE]));
152 let footer_sec = NS_FOOTER_RULES.parse(&mut p)?;
153 let footer = Footer::from_section(&footer_sec)?;
154 Ok(footer)
155 }
156
157 fn take_routerstatus(r: &mut NetDocReader<'_, NetstatusKwd>) -> Result<Option<(Pos, RouterStatus)>> {
160 use NetstatusKwd::*;
161 match r.peek() {
162 None => return Ok(None),
163 Some(e) if e.is_ok_with_kwd_in(&[DIRECTORY_FOOTER]) => return Ok(None),
164 _ => (),
165 };
166
167 let pos = r.pos();
168
169 let mut first_r = true;
170 let mut p = r.pause_at(|i| match i {
171 Err(_) => false,
172 Ok(item) => {
173 item.kwd() == DIRECTORY_FOOTER
174 || if item.kwd() == RS_R {
175 let was_first = first_r;
176 first_r = false;
177 !was_first
178 } else {
179 false
180 }
181 }
182 });
183
184 let rules = match RouterStatus::flavor() {
185 ConsensusFlavor::Microdesc => &NS_ROUTERSTATUS_RULES_MDCON,
186 ConsensusFlavor::Plain => &NS_ROUTERSTATUS_RULES_PLAIN,
187 };
188
189 let rs_sec = rules.parse(&mut p)?;
190 let rs = RouterStatus::from_section(&rs_sec)?;
191 Ok(Some((pos, rs)))
192 }
193
194 fn parse_from_reader<'a>(
199 r: &mut NetDocReader<'a, NetstatusKwd>,
200 ) -> Result<(&'a str, &'a str, UncheckedConsensus)> {
201 use NetstatusKwd::*;
202 let (header, start_pos) = {
203 let mut h = r.pause_at(|i| i.is_ok_with_kwd_in(&[DIR_SOURCE]));
204 let header_sec = NS_HEADER_RULES_CONSENSUS.parse(&mut h)?;
205 #[allow(clippy::unwrap_used)]
208 let pos = header_sec.first_item().unwrap().offset_in(r.str()).unwrap();
209 (Header::from_section(&header_sec)?, pos)
210 };
211 if RouterStatus::flavor() != header.flavor {
212 return Err(EK::BadDocumentType.with_msg(format!(
213 "Expected {:?}, got {:?}",
214 RouterStatus::flavor(),
215 header.flavor
216 )));
217 }
218
219 let mut voters = Vec::new();
220
221 while let Some(voter) = Self::take_voterinfo(r)? {
222 voters.push(voter);
223 }
224
225 let mut relays: Vec<RouterStatus> = Vec::new();
226 while let Some((pos, routerstatus)) = Self::take_routerstatus(r)? {
227 if let Some(prev) = relays.last() {
228 if prev.rsa_identity() >= routerstatus.rsa_identity() {
229 return Err(EK::WrongSortOrder.at_pos(pos));
230 }
231 }
232 relays.push(routerstatus);
233 }
234 relays.shrink_to_fit();
235
236 let footer = Self::take_footer(r)?;
237
238 let consensus = Consensus {
239 header,
240 voters,
241 relays,
242 footer,
243 };
244
245 let mut first_sig: Option<Item<'_, NetstatusKwd>> = None;
247 let mut signatures = Vec::new();
248 for item in &mut *r {
249 let item = item?;
250 if item.kwd() != DIRECTORY_SIGNATURE {
251 return Err(EK::UnexpectedToken
252 .with_msg(item.kwd().to_str())
253 .at_pos(item.pos()));
254 }
255
256 let sig = Signature::from_item(&item)?;
257 if first_sig.is_none() {
258 first_sig = Some(item);
259 }
260 signatures.push(sig);
261 }
262
263 let end_pos = match first_sig {
264 None => return Err(EK::MissingToken.with_msg("directory-signature")),
265 #[allow(clippy::unwrap_used)]
267 Some(sig) => sig.offset_in(r.str()).unwrap() + "directory-signature ".len(),
268 };
269
270 let signed_str = &r.str()[start_pos..end_pos];
272 let remainder = &r.str()[end_pos..];
273 let (sha256, sha1) = match RouterStatus::flavor() {
274 ConsensusFlavor::Plain => (
275 None,
276 Some(ll::d::Sha1::digest(signed_str.as_bytes()).into()),
277 ),
278 ConsensusFlavor::Microdesc => (
279 Some(ll::d::Sha256::digest(signed_str.as_bytes()).into()),
280 None,
281 ),
282 };
283 let siggroup = SignatureGroup {
284 sha256,
285 sha1,
286 signatures,
287 };
288
289 let unval = UnvalidatedConsensus {
290 consensus,
291 siggroup,
292 n_authorities: None,
293 };
294 let lifetime = unval.consensus.header.lifetime.clone();
295 let delay = unval.consensus.header.voting_delay.unwrap_or((0, 0));
296 let dist_interval = time::Duration::from_secs(delay.1.into());
297 let starting_time = lifetime.valid_after - dist_interval;
298 let timebound = TimerangeBound::new(unval, starting_time..lifetime.valid_until);
299 Ok((signed_str, remainder, timebound))
300 }
301}
302
303impl Header {
304 fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<Header> {
306 use NetstatusKwd::*;
307
308 {
309 #[allow(clippy::unwrap_used)]
312 let first = sec.first_item().unwrap();
313 if first.kwd() != NETWORK_STATUS_VERSION {
314 return Err(EK::UnexpectedToken
315 .with_msg(first.kwd().to_str())
316 .at_pos(first.pos()));
317 }
318 }
319
320 let ver_item = sec.required(NETWORK_STATUS_VERSION)?;
321
322 let version: u32 = ver_item.parse_arg(0)?;
323 if version != 3 {
324 return Err(EK::BadDocumentVersion.with_msg(version.to_string()));
325 }
326 let flavor = ConsensusFlavor::from_opt_name(ver_item.arg(1))?;
327
328 let valid_after = sec
329 .required(VALID_AFTER)?
330 .args_as_str()
331 .parse::<Iso8601TimeSp>()?
332 .into();
333 let fresh_until = sec
334 .required(FRESH_UNTIL)?
335 .args_as_str()
336 .parse::<Iso8601TimeSp>()?
337 .into();
338 let valid_until = sec
339 .required(VALID_UNTIL)?
340 .args_as_str()
341 .parse::<Iso8601TimeSp>()?
342 .into();
343 let lifetime = Lifetime::new(valid_after, fresh_until, valid_until)?;
344
345 let client_versions = sec
346 .maybe(CLIENT_VERSIONS)
347 .args_as_str()
348 .unwrap_or("")
349 .split(',')
350 .map(str::to_string)
351 .collect();
352 let relay_versions = sec
353 .maybe(SERVER_VERSIONS)
354 .args_as_str()
355 .unwrap_or("")
356 .split(',')
357 .map(str::to_string)
358 .collect();
359
360 let proto_statuses = {
361 let client = ProtoStatus::from_section(
362 sec,
363 RECOMMENDED_CLIENT_PROTOCOLS,
364 REQUIRED_CLIENT_PROTOCOLS,
365 )?;
366 let relay = ProtoStatus::from_section(
367 sec,
368 RECOMMENDED_RELAY_PROTOCOLS,
369 REQUIRED_RELAY_PROTOCOLS,
370 )?;
371 Arc::new(ProtoStatuses { client, relay })
372 };
373
374 let params = sec.maybe(PARAMS).args_as_str().unwrap_or("").parse()?;
375
376 let status: &str = sec.required(VOTE_STATUS)?.arg(0).unwrap_or("");
377 if status != "consensus" {
378 return Err(EK::BadDocumentType.err());
379 }
380
381 let consensus_method: u32 = sec.required(CONSENSUS_METHOD)?.parse_arg(0)?;
384
385 let shared_rand_prev = sec
386 .get(SHARED_RAND_PREVIOUS_VALUE)
387 .map(SharedRandStatus::from_item)
388 .transpose()?;
389
390 let shared_rand_cur = sec
391 .get(SHARED_RAND_CURRENT_VALUE)
392 .map(SharedRandStatus::from_item)
393 .transpose()?;
394
395 let voting_delay = if let Some(tok) = sec.get(VOTING_DELAY) {
396 let n1 = tok.parse_arg(0)?;
397 let n2 = tok.parse_arg(1)?;
398 Some((n1, n2))
399 } else {
400 None
401 };
402
403 Ok(Header {
404 flavor,
405 lifetime,
406 client_versions,
407 relay_versions,
408 proto_statuses,
409 params,
410 voting_delay,
411 consensus_method,
412 shared_rand_prev,
413 shared_rand_cur,
414 })
415 }
416}
417
418#[derive(Debug, Clone)]
425#[non_exhaustive]
426pub struct UnvalidatedConsensus {
427 pub consensus: Consensus,
430 pub siggroup: SignatureGroup,
433 pub n_authorities: Option<u16>,
437}
438
439impl UnvalidatedConsensus {
440 #[must_use]
444 pub fn set_n_authorities(self, n_authorities: u16) -> Self {
445 UnvalidatedConsensus {
446 n_authorities: Some(n_authorities),
447 ..self
448 }
449 }
450
451 pub fn signing_cert_ids(&self) -> impl Iterator<Item = AuthCertKeyIds> {
454 match self.key_is_correct(&[]) {
455 Ok(()) => Vec::new(),
456 Err(missing) => missing,
457 }
458 .into_iter()
459 }
460
461 pub fn peek_lifetime(&self) -> &Lifetime {
463 self.consensus.lifetime()
464 }
465
466 pub fn authorities_are_correct(&self, authorities: &[&RsaIdentity]) -> bool {
473 self.siggroup.could_validate(authorities)
474 }
475
476 #[cfg(feature = "experimental-api")]
481 pub fn n_relays(&self) -> usize {
482 self.consensus.relays.len()
483 }
484
485 #[cfg(feature = "experimental-api")]
494 pub fn modify_relays<F>(&mut self, func: F)
495 where
496 F: FnOnce(&mut Vec<RouterStatus>),
497 {
498 func(&mut self.consensus.relays);
499 }
500}
501
502impl ExternallySigned<Consensus> for UnvalidatedConsensus {
503 type Key = [AuthCert];
504 type KeyHint = Vec<AuthCertKeyIds>;
505 type Error = Error;
506
507 fn key_is_correct(&self, k: &Self::Key) -> result::Result<(), Self::KeyHint> {
508 let (n_ok, missing) = self.siggroup.list_missing(k);
509 match self.n_authorities {
510 Some(n) if n_ok > (n / 2) as usize => Ok(()),
511 _ => Err(missing.iter().map(|cert| cert.key_ids).collect()),
512 }
513 }
514 fn is_well_signed(&self, k: &Self::Key) -> result::Result<(), Self::Error> {
515 match self.n_authorities {
516 None => Err(Error::from(internal!(
517 "Didn't set authorities on consensus"
518 ))),
519 Some(authority) => {
520 if self.siggroup.validate(authority, k) {
521 Ok(())
522 } else {
523 Err(EK::BadSignature.err())
524 }
525 }
526 }
527 }
528 fn dangerously_assume_wellsigned(self) -> Consensus {
529 self.consensus
530 }
531}
532
533pub type UncheckedConsensus = TimerangeBound<UnvalidatedConsensus>;
536