use super::*;
ns_use_this_variety! {
use [crate::doc::netstatus::rs]::?::{RouterStatus};
}
#[cfg(feature = "build_docs")]
ns_use_this_variety! {
pub(crate) use [crate::doc::netstatus::build]::?::{ConsensusBuilder};
pub use [crate::doc::netstatus::rs::build]::?::{RouterStatusBuilder};
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Consensus {
pub flavor: ConsensusFlavor,
pub preamble: Preamble,
pub voters: Vec<ConsensusVoterInfo>,
pub relays: Vec<RouterStatus>,
pub footer: Footer,
}
impl Consensus {
pub fn lifetime(&self) -> &Lifetime {
&self.preamble.lifetime
}
pub fn relays(&self) -> &[RouterStatus] {
&self.relays[..]
}
pub fn bandwidth_weights(&self) -> &NetParams<i32> {
&self.footer.weights
}
pub fn params(&self) -> &NetParams<i32> {
&self.preamble.params
}
pub fn shared_rand_cur(&self) -> Option<&SharedRandStatus> {
self.preamble.shared_rand_current_value.as_ref()
}
pub fn shared_rand_prev(&self) -> Option<&SharedRandStatus> {
self.preamble.shared_rand_previous_value.as_ref()
}
pub fn relay_protocol_status(&self) -> &ProtoStatus {
&self.preamble.proto_statuses.relay
}
pub fn client_protocol_status(&self) -> &ProtoStatus {
&self.preamble.proto_statuses.client
}
pub fn protocol_statuses(&self) -> &Arc<ProtoStatuses> {
&self.preamble.proto_statuses
}
}
impl Consensus {
#[cfg(feature = "build_docs")]
pub fn builder() -> ConsensusBuilder {
ConsensusBuilder::new(RouterStatus::flavor())
}
pub fn parse(s: &str) -> Result<(&str, &str, UncheckedConsensus)> {
let mut reader = NetDocReader::new(s)?;
Self::parse_from_reader(&mut reader).map_err(|e| e.within(s))
}
fn take_voterinfo(
r: &mut NetDocReader<'_, NetstatusKwd>,
) -> Result<Option<ConsensusVoterInfo>> {
use NetstatusKwd::*;
match r.peek() {
None => return Ok(None),
Some(e) if e.is_ok_with_kwd_in(&[RS_R, DIRECTORY_FOOTER]) => return Ok(None),
_ => (),
};
let mut first_dir_source = true;
let mut p = r.pause_at(|i| match i {
Err(_) => false,
Ok(item) => {
item.kwd() == RS_R
|| if item.kwd() == DIR_SOURCE {
let was_first = first_dir_source;
first_dir_source = false;
!was_first
} else {
false
}
}
});
let voter_sec = NS_VOTERINFO_RULES_CONSENSUS.parse(&mut p)?;
let voter = ConsensusVoterInfo::from_section(&voter_sec)?;
Ok(Some(voter))
}
fn take_footer(r: &mut NetDocReader<'_, NetstatusKwd>) -> Result<Footer> {
use NetstatusKwd::*;
let mut p = r.pause_at(|i| i.is_ok_with_kwd_in(&[DIRECTORY_SIGNATURE]));
let footer_sec = NS_FOOTER_RULES.parse(&mut p)?;
let footer = Footer::from_section(&footer_sec)?;
Ok(footer)
}
fn take_routerstatus(r: &mut NetDocReader<'_, NetstatusKwd>) -> Result<Option<(Pos, RouterStatus)>> {
use NetstatusKwd::*;
match r.peek() {
None => return Ok(None),
Some(e) if e.is_ok_with_kwd_in(&[DIRECTORY_FOOTER]) => return Ok(None),
_ => (),
};
let pos = r.pos();
let mut first_r = true;
let mut p = r.pause_at(|i| match i {
Err(_) => false,
Ok(item) => {
item.kwd() == DIRECTORY_FOOTER
|| if item.kwd() == RS_R {
let was_first = first_r;
first_r = false;
!was_first
} else {
false
}
}
});
let rules = match RouterStatus::flavor() {
ConsensusFlavor::Microdesc => &NS_ROUTERSTATUS_RULES_MDCON,
ConsensusFlavor::Plain => &NS_ROUTERSTATUS_RULES_PLAIN,
};
let rs_sec = rules.parse(&mut p)?;
let rs = RouterStatus::from_section(&rs_sec)?;
Ok(Some((pos, rs)))
}
fn parse_from_reader<'a>(
r: &mut NetDocReader<'a, NetstatusKwd>,
) -> Result<(&'a str, &'a str, UncheckedConsensus)> {
use NetstatusKwd::*;
let ((flavor, preamble), start_pos) = {
let mut h = r.pause_at(|i| i.is_ok_with_kwd_in(&[DIR_SOURCE]));
let preamble_sec = NS_HEADER_RULES_CONSENSUS.parse(&mut h)?;
#[allow(clippy::unwrap_used)]
let pos = preamble_sec.first_item().unwrap().offset_in(r.str()).unwrap();
(Preamble::from_section(&preamble_sec)?, pos)
};
if RouterStatus::flavor() != flavor {
return Err(EK::BadDocumentType.with_msg(format!(
"Expected {:?}, got {:?}",
RouterStatus::flavor(),
flavor
)));
}
let mut voters = Vec::new();
while let Some(voter) = Self::take_voterinfo(r)? {
voters.push(voter);
}
let mut relays: Vec<RouterStatus> = Vec::new();
while let Some((pos, routerstatus)) = Self::take_routerstatus(r)? {
if let Some(prev) = relays.last() {
if prev.rsa_identity() >= routerstatus.rsa_identity() {
return Err(EK::WrongSortOrder.at_pos(pos));
}
}
relays.push(routerstatus);
}
relays.shrink_to_fit();
let footer = Self::take_footer(r)?;
let consensus = Consensus {
flavor,
preamble,
voters,
relays,
footer,
};
let mut first_sig: Option<Item<'_, NetstatusKwd>> = None;
let mut signatures = Vec::new();
for item in &mut *r {
let item = item?;
if item.kwd() != DIRECTORY_SIGNATURE {
return Err(EK::UnexpectedToken
.with_msg(item.kwd().to_str())
.at_pos(item.pos()));
}
let sig = Signature::from_item(&item)?;
if first_sig.is_none() {
first_sig = Some(item);
}
signatures.push(sig);
}
let end_pos = match first_sig {
None => return Err(EK::MissingToken.with_msg("directory-signature")),
#[allow(clippy::unwrap_used)]
Some(sig) => sig.offset_in(r.str()).unwrap() + "directory-signature ".len(),
};
let signed_str = &r.str()[start_pos..end_pos];
let remainder = &r.str()[end_pos..];
let (sha256, sha1) = match RouterStatus::flavor() {
ConsensusFlavor::Plain => (
None,
Some(ll::d::Sha1::digest(signed_str.as_bytes()).into()),
),
ConsensusFlavor::Microdesc => (
Some(ll::d::Sha256::digest(signed_str.as_bytes()).into()),
None,
),
};
let siggroup = SignatureGroup {
sha256,
sha1,
signatures,
};
let unval = UnvalidatedConsensus {
consensus,
siggroup,
n_authorities: None,
};
let lifetime = unval.consensus.preamble.lifetime.clone();
let delay = unval.consensus.preamble.voting_delay.unwrap_or((0, 0));
let dist_interval = time::Duration::from_secs(delay.1.into());
let starting_time = *lifetime.valid_after - dist_interval;
let timebound = TimerangeBound::new(unval, starting_time..*lifetime.valid_until);
Ok((signed_str, remainder, timebound))
}
}
impl Preamble {
fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<(ConsensusFlavor, Preamble)> {
use NetstatusKwd::*;
{
#[allow(clippy::unwrap_used)]
let first = sec.first_item().unwrap();
if first.kwd() != NETWORK_STATUS_VERSION {
return Err(EK::UnexpectedToken
.with_msg(first.kwd().to_str())
.at_pos(first.pos()));
}
}
let ver_item = sec.required(NETWORK_STATUS_VERSION)?;
let version: u32 = ver_item.parse_arg(0)?;
if version != 3 {
return Err(EK::BadDocumentVersion.with_msg(version.to_string()));
}
let flavor = ConsensusFlavor::from_opt_name(ver_item.arg(1))?;
let valid_after = sec
.required(VALID_AFTER)?
.args_as_str()
.parse::<Iso8601TimeSp>()?
.into();
let fresh_until = sec
.required(FRESH_UNTIL)?
.args_as_str()
.parse::<Iso8601TimeSp>()?
.into();
let valid_until = sec
.required(VALID_UNTIL)?
.args_as_str()
.parse::<Iso8601TimeSp>()?
.into();
let lifetime = Lifetime::new(valid_after, fresh_until, valid_until)?;
let client_versions = sec
.maybe(CLIENT_VERSIONS)
.args_as_str()
.unwrap_or("")
.split(',')
.map(str::to_string)
.collect();
let server_versions = sec
.maybe(SERVER_VERSIONS)
.args_as_str()
.unwrap_or("")
.split(',')
.map(str::to_string)
.collect();
let proto_statuses = {
let client = ProtoStatus::from_section(
sec,
RECOMMENDED_CLIENT_PROTOCOLS,
REQUIRED_CLIENT_PROTOCOLS,
)?;
let relay = ProtoStatus::from_section(
sec,
RECOMMENDED_RELAY_PROTOCOLS,
REQUIRED_RELAY_PROTOCOLS,
)?;
Arc::new(ProtoStatuses { client, relay })
};
let params = sec.maybe(PARAMS).args_as_str().unwrap_or("").parse()?;
let status: &str = sec.required(VOTE_STATUS)?.arg(0).unwrap_or("");
if status != "consensus" {
return Err(EK::BadDocumentType.err());
}
let consensus_method: u32 = sec.required(CONSENSUS_METHOD)?.parse_arg(0)?;
let shared_rand_previous_value = sec
.get(SHARED_RAND_PREVIOUS_VALUE)
.map(SharedRandStatus::from_item)
.transpose()?;
let shared_rand_current_value = sec
.get(SHARED_RAND_CURRENT_VALUE)
.map(SharedRandStatus::from_item)
.transpose()?;
let voting_delay = if let Some(tok) = sec.get(VOTING_DELAY) {
let n1 = tok.parse_arg(0)?;
let n2 = tok.parse_arg(1)?;
Some((n1, n2))
} else {
None
};
let preamble = Preamble {
lifetime,
client_versions,
server_versions,
proto_statuses,
params,
voting_delay,
consensus_method,
published: NotPresent,
consensus_methods: NotPresent,
shared_rand_previous_value,
shared_rand_current_value,
};
Ok((flavor, preamble))
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct UnvalidatedConsensus {
pub consensus: Consensus,
pub siggroup: SignatureGroup,
pub n_authorities: Option<usize>,
}
impl UnvalidatedConsensus {
#[must_use]
pub fn set_n_authorities(self, n_authorities: usize) -> Self {
UnvalidatedConsensus {
n_authorities: Some(n_authorities),
..self
}
}
pub fn signing_cert_ids(&self) -> impl Iterator<Item = AuthCertKeyIds> {
match self.key_is_correct(&[]) {
Ok(()) => Vec::new(),
Err(missing) => missing,
}
.into_iter()
}
pub fn peek_lifetime(&self) -> &Lifetime {
self.consensus.lifetime()
}
pub fn authorities_are_correct(&self, authorities: &[&RsaIdentity]) -> bool {
self.siggroup.could_validate(authorities)
}
#[cfg(feature = "experimental-api")]
pub fn n_relays(&self) -> usize {
self.consensus.relays.len()
}
#[cfg(feature = "experimental-api")]
pub fn modify_relays<F>(&mut self, func: F)
where
F: FnOnce(&mut Vec<RouterStatus>),
{
func(&mut self.consensus.relays);
}
}
impl ExternallySigned<Consensus> for UnvalidatedConsensus {
type Key = [AuthCert];
type KeyHint = Vec<AuthCertKeyIds>;
type Error = Error;
fn key_is_correct(&self, k: &Self::Key) -> result::Result<(), Self::KeyHint> {
let (n_ok, missing) = self.siggroup.list_missing(k);
match self.n_authorities {
Some(n) if n_ok > (n / 2) => Ok(()),
_ => Err(missing.iter().map(|cert| cert.key_ids).collect()),
}
}
fn is_well_signed(&self, k: &Self::Key) -> result::Result<(), Self::Error> {
match self.n_authorities {
None => Err(Error::from(internal!(
"Didn't set authorities on consensus"
))),
Some(authority) => {
if self.siggroup.validate(authority, k) {
Ok(())
} else {
Err(EK::BadSignature.err())
}
}
}
}
fn dangerously_assume_wellsigned(self) -> Consensus {
self.consensus
}
}
pub type UncheckedConsensus = TimerangeBound<UnvalidatedConsensus>;