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)]
26#[non_exhaustive]
27pub struct Consensus {
28 pub flavor: ConsensusFlavor,
31 pub preamble: Preamble,
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.preamble.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.preamble.params
65 }
66
67 pub fn shared_rand_cur(&self) -> Option<&SharedRandStatus> {
70 self.preamble.shared_rand_current_value.as_ref()
71 }
72
73 pub fn shared_rand_prev(&self) -> Option<&SharedRandStatus> {
76 self.preamble.shared_rand_previous_value.as_ref()
77 }
78
79 pub fn relay_protocol_status(&self) -> &ProtoStatus {
82 &self.preamble.proto_statuses.relay
83 }
84
85 pub fn client_protocol_status(&self) -> &ProtoStatus {
88 &self.preamble.proto_statuses.client
89 }
90
91 pub fn protocol_statuses(&self) -> &Arc<ProtoStatuses> {
93 &self.preamble.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 ((flavor, preamble), start_pos) = {
203 let mut h = r.pause_at(|i| i.is_ok_with_kwd_in(&[DIR_SOURCE]));
204 let preamble_sec = NS_HEADER_RULES_CONSENSUS.parse(&mut h)?;
205 #[allow(clippy::unwrap_used)]
208 let pos = preamble_sec.first_item().unwrap().offset_in(r.str()).unwrap();
209 (Preamble::from_section(&preamble_sec)?, pos)
210 };
211 if RouterStatus::flavor() != flavor {
212 return Err(EK::BadDocumentType.with_msg(format!(
213 "Expected {:?}, got {:?}",
214 RouterStatus::flavor(),
215 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 flavor,
240 preamble,
241 voters,
242 relays,
243 footer,
244 };
245
246 let mut first_sig: Option<Item<'_, NetstatusKwd>> = None;
248 let mut signatures = Vec::new();
249 for item in &mut *r {
250 let item = item?;
251 if item.kwd() != DIRECTORY_SIGNATURE {
252 return Err(EK::UnexpectedToken
253 .with_msg(item.kwd().to_str())
254 .at_pos(item.pos()));
255 }
256
257 let sig = Signature::from_item(&item)?;
258 if first_sig.is_none() {
259 first_sig = Some(item);
260 }
261 signatures.push(sig);
262 }
263
264 let end_pos = match first_sig {
265 None => return Err(EK::MissingToken.with_msg("directory-signature")),
266 #[allow(clippy::unwrap_used)]
268 Some(sig) => sig.offset_in(r.str()).unwrap() + "directory-signature ".len(),
269 };
270
271 let signed_str = &r.str()[start_pos..end_pos];
273 let remainder = &r.str()[end_pos..];
274 let (sha256, sha1) = match RouterStatus::flavor() {
275 ConsensusFlavor::Plain => (
276 None,
277 Some(ll::d::Sha1::digest(signed_str.as_bytes()).into()),
278 ),
279 ConsensusFlavor::Microdesc => (
280 Some(ll::d::Sha256::digest(signed_str.as_bytes()).into()),
281 None,
282 ),
283 };
284 let siggroup = SignatureGroup {
285 sha256,
286 sha1,
287 signatures,
288 };
289
290 let unval = UnvalidatedConsensus {
291 consensus,
292 siggroup,
293 n_authorities: None,
294 };
295 let lifetime = unval.consensus.preamble.lifetime.clone();
296 let delay = unval.consensus.preamble.voting_delay.unwrap_or((0, 0));
297 let dist_interval = time::Duration::from_secs(delay.1.into());
298 let starting_time = *lifetime.valid_after - dist_interval;
299 let timebound = TimerangeBound::new(unval, starting_time..*lifetime.valid_until);
300 Ok((signed_str, remainder, timebound))
301 }
302}
303
304impl Preamble {
305 fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<(ConsensusFlavor, Preamble)> {
307 use NetstatusKwd::*;
308
309 {
310 #[allow(clippy::unwrap_used)]
313 let first = sec.first_item().unwrap();
314 if first.kwd() != NETWORK_STATUS_VERSION {
315 return Err(EK::UnexpectedToken
316 .with_msg(first.kwd().to_str())
317 .at_pos(first.pos()));
318 }
319 }
320
321 let ver_item = sec.required(NETWORK_STATUS_VERSION)?;
322
323 let version: u32 = ver_item.parse_arg(0)?;
324 if version != 3 {
325 return Err(EK::BadDocumentVersion.with_msg(version.to_string()));
326 }
327 let flavor = ConsensusFlavor::from_opt_name(ver_item.arg(1))?;
328
329 let valid_after = sec
330 .required(VALID_AFTER)?
331 .args_as_str()
332 .parse::<Iso8601TimeSp>()?
333 .into();
334 let fresh_until = sec
335 .required(FRESH_UNTIL)?
336 .args_as_str()
337 .parse::<Iso8601TimeSp>()?
338 .into();
339 let valid_until = sec
340 .required(VALID_UNTIL)?
341 .args_as_str()
342 .parse::<Iso8601TimeSp>()?
343 .into();
344 let lifetime = Lifetime::new(valid_after, fresh_until, valid_until)?;
345
346 let client_versions = sec
347 .maybe(CLIENT_VERSIONS)
348 .args_as_str()
349 .unwrap_or("")
350 .split(',')
351 .map(str::to_string)
352 .collect();
353 let server_versions = sec
354 .maybe(SERVER_VERSIONS)
355 .args_as_str()
356 .unwrap_or("")
357 .split(',')
358 .map(str::to_string)
359 .collect();
360
361 let proto_statuses = {
362 let client = ProtoStatus::from_section(
363 sec,
364 RECOMMENDED_CLIENT_PROTOCOLS,
365 REQUIRED_CLIENT_PROTOCOLS,
366 )?;
367 let relay = ProtoStatus::from_section(
368 sec,
369 RECOMMENDED_RELAY_PROTOCOLS,
370 REQUIRED_RELAY_PROTOCOLS,
371 )?;
372 Arc::new(ProtoStatuses { client, relay })
373 };
374
375 let params = sec.maybe(PARAMS).args_as_str().unwrap_or("").parse()?;
376
377 let status: &str = sec.required(VOTE_STATUS)?.arg(0).unwrap_or("");
378 if status != "consensus" {
379 return Err(EK::BadDocumentType.err());
380 }
381
382 let consensus_method: u32 = sec.required(CONSENSUS_METHOD)?.parse_arg(0)?;
385
386 let shared_rand_previous_value = sec
387 .get(SHARED_RAND_PREVIOUS_VALUE)
388 .map(SharedRandStatus::from_item)
389 .transpose()?;
390
391 let shared_rand_current_value = sec
392 .get(SHARED_RAND_CURRENT_VALUE)
393 .map(SharedRandStatus::from_item)
394 .transpose()?;
395
396 let voting_delay = if let Some(tok) = sec.get(VOTING_DELAY) {
397 let n1 = tok.parse_arg(0)?;
398 let n2 = tok.parse_arg(1)?;
399 Some((n1, n2))
400 } else {
401 None
402 };
403
404 let preamble = Preamble {
405 lifetime,
406 client_versions,
407 server_versions,
408 proto_statuses,
409 params,
410 voting_delay,
411 consensus_method,
412 shared_rand_previous_value,
413 shared_rand_current_value,
414 };
415
416 Ok((flavor, preamble))
417 }
418}
419
420#[derive(Debug, Clone)]
427#[non_exhaustive]
428pub struct UnvalidatedConsensus {
429 pub consensus: Consensus,
432 pub siggroup: SignatureGroup,
435 pub n_authorities: Option<u16>,
439}
440
441impl UnvalidatedConsensus {
442 #[must_use]
446 pub fn set_n_authorities(self, n_authorities: u16) -> Self {
447 UnvalidatedConsensus {
448 n_authorities: Some(n_authorities),
449 ..self
450 }
451 }
452
453 pub fn signing_cert_ids(&self) -> impl Iterator<Item = AuthCertKeyIds> {
456 match self.key_is_correct(&[]) {
457 Ok(()) => Vec::new(),
458 Err(missing) => missing,
459 }
460 .into_iter()
461 }
462
463 pub fn peek_lifetime(&self) -> &Lifetime {
465 self.consensus.lifetime()
466 }
467
468 pub fn authorities_are_correct(&self, authorities: &[&RsaIdentity]) -> bool {
475 self.siggroup.could_validate(authorities)
476 }
477
478 #[cfg(feature = "experimental-api")]
483 pub fn n_relays(&self) -> usize {
484 self.consensus.relays.len()
485 }
486
487 #[cfg(feature = "experimental-api")]
496 pub fn modify_relays<F>(&mut self, func: F)
497 where
498 F: FnOnce(&mut Vec<RouterStatus>),
499 {
500 func(&mut self.consensus.relays);
501 }
502}
503
504impl ExternallySigned<Consensus> for UnvalidatedConsensus {
505 type Key = [AuthCert];
506 type KeyHint = Vec<AuthCertKeyIds>;
507 type Error = Error;
508
509 fn key_is_correct(&self, k: &Self::Key) -> result::Result<(), Self::KeyHint> {
510 let (n_ok, missing) = self.siggroup.list_missing(k);
511 match self.n_authorities {
512 Some(n) if n_ok > (n / 2) as usize => Ok(()),
513 _ => Err(missing.iter().map(|cert| cert.key_ids).collect()),
514 }
515 }
516 fn is_well_signed(&self, k: &Self::Key) -> result::Result<(), Self::Error> {
517 match self.n_authorities {
518 None => Err(Error::from(internal!(
519 "Didn't set authorities on consensus"
520 ))),
521 Some(authority) => {
522 if self.siggroup.validate(authority, k) {
523 Ok(())
524 } else {
525 Err(EK::BadSignature.err())
526 }
527 }
528 }
529 }
530 fn dangerously_assume_wellsigned(self) -> Consensus {
531 self.consensus
532 }
533}
534
535pub type UncheckedConsensus = TimerangeBound<UnvalidatedConsensus>;
538