use super::super::*;
const TOPLEVEL_DOCTYPE_FOR_ERROR: &str =
ns_expr!("NetworkStatusVote", "NetworkStatusNs", "NetworkStatusMd",);
pub type Router = ns_type!(
crate::doc::netstatus::VoteRouterStatus,
crate::doc::netstatus::PlainRouterStatus,
crate::doc::netstatus::MdRouterStatus,
);
#[derive(Deftly, Clone, Debug)]
#[derive_deftly(NetdocParseableUnverified)]
#[deftly(netdoc(doctype_for_error = "TOPLEVEL_DOCTYPE_FOR_ERROR"))]
#[non_exhaustive]
pub struct NetworkStatus {
pub network_status_version: (NdaNetworkStatusVersion, NdaNetworkStatusVersionFlavour),
pub vote_status: NdiVoteStatus,
pub published: ns_type!((NdaSystemTimeDeprecatedSyntax,), Option<Void>,),
pub valid_after: (NdaSystemTimeDeprecatedSyntax,),
pub valid_until: (NdaSystemTimeDeprecatedSyntax,),
pub voting_delay: NdiVotingDelay,
#[deftly(netdoc(default))]
pub params: NdiParams,
#[deftly(netdoc(subdoc))]
pub authority: NddAuthoritySection,
#[deftly(netdoc(subdoc))]
pub r: Vec<Router>,
#[deftly(netdoc(subdoc))]
pub directory_footer: Option<NddDirectoryFooter>,
}
#[derive(Deftly, Clone, Debug)]
#[derive_deftly(NetdocParseableSignatures)]
#[deftly(netdoc(signatures(hashes_accu = "DirectorySignaturesHashesAccu")))]
#[non_exhaustive]
pub struct NetworkStatusSignatures {
pub directory_signature: ns_type!(NdiDirectorySignature, Vec<NdiDirectorySignature>),
}
#[derive(Deftly, Clone, Debug, Hash, Eq, PartialEq)]
#[derive_deftly(ItemValueParseable)]
#[non_exhaustive]
pub struct NdiVoteStatus {
pub status: NdaVoteStatus,
}
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
#[non_exhaustive]
pub struct NdaVoteStatus {}
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
#[non_exhaustive]
pub struct NdaNetworkStatusVersionFlavour {}
const NDA_NETWORK_STATUS_VERSION_FLAVOUR: Option<&str> = ns_expr!(None, None, Some("microdesc"));
impl ItemArgumentParseable for NdaNetworkStatusVersionFlavour {
fn from_args<'s>(args: &mut ArgumentStream<'s>) -> Result<Self, AE> {
let exp: Option<&str> = NDA_NETWORK_STATUS_VERSION_FLAVOUR;
if let Some(exp) = exp {
let got = args.next().ok_or(AE::Missing)?;
if got != exp {
return Err(AE::Invalid);
};
} else {
args.reject_extra_args()?;
}
Ok(Self {})
}
}
const NDA_VOTE_STATUS: &str = ns_expr!("vote", "consensus", "consensus");
impl FromStr for NdaVoteStatus {
type Err = InvalidNetworkStatusVoteStatus;
fn from_str(s: &str) -> Result<Self, InvalidNetworkStatusVoteStatus> {
if s == NDA_VOTE_STATUS {
Ok(Self {})
} else {
Err(InvalidNetworkStatusVoteStatus {})
}
}
}
impl Display for NdaVoteStatus {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(NDA_VOTE_STATUS, f)
}
}
impl NormalItemArgument for NdaVoteStatus {}
#[derive(Deftly, Clone, Debug, Hash, Eq, PartialEq)]
#[derive_deftly(ItemValueParseable)]
#[non_exhaustive]
pub struct NdiVotingDelay {
pub vote_seconds: u32,
pub dist_seconds: u32,
}
#[derive(Deftly, Clone, Debug)]
#[derive_deftly(NetdocParseable)]
#[non_exhaustive]
pub struct NddDirectoryFooter {
pub directory_footer: (),
}
#[derive(Deftly, Clone, Debug)]
#[derive_deftly(NetdocParseable)]
#[non_exhaustive]
pub struct NddAuthorityEntry {
pub dir_source: NdiAuthorityDirSource,
}
#[derive(Deftly, Clone, Debug)]
#[derive_deftly(ItemValueParseable)]
#[non_exhaustive]
pub struct NdiAuthorityDirSource {
pub nickname: types::Nickname,
pub h_p_auth_id_rsa: types::Fingerprint,
}
ns_choose! { (
define_derive_deftly! {
NddAuthoritySection:
impl NetdocParseable for NddAuthoritySection {
fn doctype_for_error() -> &'static str {
"vote.authority.section"
}
fn is_intro_item_keyword(kw: KeywordRef<'_>) -> bool {
NddAuthorityEntry::is_intro_item_keyword(kw)
}
fn is_structural_keyword(kw: KeywordRef<'_>) -> Option<IsStructural> {
NddAuthorityEntry::is_structural_keyword(kw)
.or_else(|| authcert::DirAuthKeyCertUnverified::is_structural_keyword(kw))
}
fn from_items<'s>(
input: &mut ItemStream<'s>,
stop_outer: stop_at!(),
) -> Result<Self, ErrorProblem> {
let stop_inner = stop_outer
$(
| StopAt($ftype::is_intro_item_keyword)
)
;
Ok(NddAuthoritySection { $(
$fname: NetdocParseable::from_items(input, stop_inner)?,
) })
}
}
}
#[derive(Deftly, Clone, Debug)]
#[derive_deftly(NddAuthoritySection)]
#[non_exhaustive]
pub struct NddAuthoritySection {
pub authority: NddAuthorityEntry,
pub cert: crate::doc::authcert::EncodedAuthCert,
}
)(
#[derive(Deftly, Clone, Debug)]
#[non_exhaustive]
pub struct NddAuthoritySection {
pub authorities: Vec<NddAuthorityEntryOrSuperseded>,
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum NddAuthorityEntryOrSuperseded {
Entry(NddAuthorityEntry),
Superseded(NdiAuthorityDirSource),
}
impl NetdocParseable for NddAuthoritySection {
fn doctype_for_error() -> &'static str {
"consensus.authority.section"
}
fn is_intro_item_keyword(kw: KeywordRef<'_>) -> bool {
NddAuthorityEntry::is_intro_item_keyword(kw)
}
fn is_structural_keyword(kw: KeywordRef<'_>) -> Option<IsStructural> {
NddAuthorityEntry::is_structural_keyword(kw)
}
fn from_items(
input: &mut ItemStream<'_>,
stop_outer: stop_at!(),
) -> Result<Self, ErrorProblem> {
let is_our_keyword = NddAuthorityEntry::is_intro_item_keyword;
let stop_inner = stop_outer | StopAt(is_our_keyword);
let mut authorities = vec![];
while let Some(peek) = input.peek_keyword()? {
if !is_our_keyword(peek) { break };
let mut lookahead = input.clone();
let _: UnparsedItem<'_> = lookahead.next().expect("peeked")?;
let entry = match lookahead.next().transpose()? {
Some(item) if !stop_inner.stop_at(item.keyword()) => {
let entry = NddAuthorityEntry::from_items(input, stop_inner)?;
NddAuthorityEntryOrSuperseded::Entry(entry)
}
None | Some(_) => {
let item = input.next().expect("just peeked")?;
let entry = NdiAuthorityDirSource::from_unparsed(item)?;
if !entry.nickname.as_str().ends_with("-legacy") {
return Err(EP::OtherBadDocument(
"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"
))
}
NddAuthorityEntryOrSuperseded::Superseded(entry)
}
};
authorities.push(entry);
}
if !authorities.is_sorted_by_key(
|entry| matches!(entry, NddAuthorityEntryOrSuperseded::Superseded(_))
) {
return Err(EP::OtherBadDocument(
"normal (non-superseded) authority entry follows superseded authority key entry"
))
}
Ok(NddAuthoritySection { authorities })
}
}
)}
ns_choose! { (
impl NetworkStatusUnverified {
pub fn verify_selfcert(
self,
now: SystemTime,
) -> Result<(NetworkStatus, SignaturesData<NetworkStatusUnverified>), VF> {
let validity = *self.body.published.0 ..= *self.body.valid_until.0;
check_validity_time(now, validity)?;
let cert = self.body.parse_authcert()?.verify_selfcert(now)?;
netstatus::verify_general_timeless(
&self.sigs.hashes,
slice::from_ref(&self.sigs.sigs.directory_signature),
&[*cert.fingerprint],
&[&cert],
1,
)?;
Ok(self.unwrap_unverified())
}
}
impl NetworkStatus {
fn parse_authcert(&self) -> Result<crate::doc::authcert::AuthCertUnverified, EP> {
let cert_input = ParseInput::new(
self.authority.cert.as_str(),
"<embedded auth cert>",
);
parse_netdoc(&cert_input).map_err(|e| e.problem)
}
pub fn h_kp_auth_id_rsa(&self) -> pk::rsa::RsaIdentity {
*self.parse_authcert()
.expect("was verified already!")
.inspect_unverified()
.0
.fingerprint
}
}
) (
impl NetworkStatusUnverified {
pub fn verify(
self,
now: SystemTime,
authorities: &[pk::rsa::RsaIdentity],
certs: &[&DirAuthKeyCert],
) -> Result<(NetworkStatus, SignaturesData<NetworkStatusUnverified>), VF> {
let threshold = authorities.len() / 2 + 1; let validity_start = self.body.valid_after.0
.checked_sub(Duration::from_secs(self.body.voting_delay.dist_seconds.into()))
.ok_or(VF::Other)?;
check_validity_time(now, validity_start..= *self.body.valid_until.0)?;
netstatus::verify_general_timeless(
&self.sigs.hashes,
&self.sigs.sigs.directory_signature,
authorities,
certs,
threshold,
)?;
Ok(self.unwrap_unverified())
}
}
)}