use super::*;
use std::result::Result;
const DIR_SOURCE_KEYWORD: &str = "dir-source";
const SUPERSEDED_SUFFIX: &str = "-legacy";
define_derive_deftly! {
SupersededAuthorityKey for struct:
${defcond F_NORMAL not(approx_equal($fname, nickname))}
${define DEFINE_NORMAL_FIELDS { $(
${when F_NORMAL}
${fattrs !_no_such_attr} $fname: $ftype,
) }}
#[derive(Debug, Clone, Deftly, amplify::Getters)]
#[derive_deftly(ItemValueEncodable)]
#[derive_deftly_adhoc] pub struct SupersededAuthorityKey {
#[deftly(netdoc(skip))]
real_nickname: Nickname,
#[getter(skip)]
raw_nickname_string: String,
$DEFINE_NORMAL_FIELDS
}
impl SupersededAuthorityKey {
pub fn raw_nickname_string(&self) -> &str {
&self.raw_nickname_string
}
pub fn from_dir_source(ds: DirSource) -> Self {
SupersededAuthorityKey {
raw_nickname_string: format!("{}{SUPERSEDED_SUFFIX}", ds.nickname),
real_nickname: ds.nickname,
$( ${when F_NORMAL} $fname: ds.$fname, )
}
}
}
#[derive(Debug, Clone, Deftly)]
#[derive_deftly(ItemValueParseable)]
#[derive_deftly_adhoc] struct RawDirSource {
raw_nickname_string: String,
$DEFINE_NORMAL_FIELDS
}
impl RawDirSource {
fn into_superseded(self) -> Result<SupersededAuthorityKey, ErrorProblem> {
let RawDirSource { raw_nickname_string, .. } = self;
let real_nickname = raw_nickname_string
.strip_suffix(SUPERSEDED_SUFFIX)
.ok_or(ErrorProblem::Internal("RawDirSource::into_superseded for non `-legacy`"))?
.parse()
.map_err(|_: InvalidNickname| ErrorProblem::InvalidArgument {
field: "invalid nickname even after stripping `-legacy`",
column: DIR_SOURCE_KEYWORD.len() + 1, })?;
Ok(SupersededAuthorityKey {
real_nickname,
raw_nickname_string,
$( ${when F_NORMAL} $fname: self.$fname, )
})
}
}
}
#[derive(Debug, Clone, Deftly)]
#[derive_deftly(Constructor, ItemValueParseable, ItemValueEncodable)]
#[derive_deftly(SupersededAuthorityKey)]
#[allow(clippy::exhaustive_structs)]
pub struct DirSource {
#[deftly(constructor)]
pub nickname: Nickname,
#[deftly(constructor)]
pub identity: Fingerprint,
#[deftly(constructor)]
pub hostname: InternetHost,
#[deftly(constructor(default = { net::Ipv6Addr::UNSPECIFIED.into() }))]
pub ip: net::IpAddr,
pub dir_port: u16,
pub or_port: u16,
#[doc(hidden)]
#[deftly(netdoc(skip))]
pub __non_exhaustive: (),
}
#[derive(Debug, Clone, Deftly)]
#[derive_deftly(Constructor)]
#[allow(clippy::exhaustive_structs)]
pub struct ConsensusAuthoritySection {
#[deftly(constructor)]
pub authorities: Vec<ConsensusAuthorityEntry>,
pub superseded_keys: Vec<SupersededAuthorityKey>,
#[doc(hidden)]
pub __non_exhaustive: (),
}
impl NetdocEncodable for ConsensusAuthoritySection {
fn encode_unsigned(&self, out: &mut NetdocEncoder) -> Result<(), Bug> {
let ConsensusAuthoritySection {
authorities,
superseded_keys,
__non_exhaustive,
} = self;
if authorities.is_empty() {
return Err(internal!("tried to encode a consensus with 0 authorities"));
}
for a in authorities {
a.encode_unsigned(out)?;
}
for s in superseded_keys {
let out = out.item(DIR_SOURCE_KEYWORD);
s.write_item_value_onto(out)?;
}
Ok(())
}
}
impl NetdocParseable for ConsensusAuthoritySection {
fn doctype_for_error() -> &'static str {
"consensus.authorities"
}
fn is_intro_item_keyword(kw: KeywordRef<'_>) -> bool {
ConsensusAuthorityEntry::is_intro_item_keyword(kw)
}
fn is_structural_keyword(kw: KeywordRef<'_>) -> Option<IsStructural> {
ConsensusAuthorityEntry::is_structural_keyword(kw)
}
fn from_items(input: &mut ItemStream<'_>, stop_at: stop_at!()) -> Result<Self, ErrorProblem> {
let mut accum = ConsensusAuthoritySection {
authorities: vec![],
superseded_keys: vec![],
__non_exhaustive: (),
};
while let Some(peeked) = input.peek_keyword()? {
if !Self::is_intro_item_keyword(peeked) {
break;
}
let rest = &input.whole_input()[input.byte_position()..];
let line = rest.split_once('\n').map(|(l, _)| l).unwrap_or(rest);
let mut line = line.split_ascii_whitespace();
assert_eq!(line.next(), Some(DIR_SOURCE_KEYWORD));
let raw_nickname = line
.next()
.ok_or(ErrorProblem::MissingArgument { field: "nickname" })?;
if raw_nickname.ends_with(SUPERSEDED_SUFFIX) {
let item = input.next().expect("peeked")?;
let s = RawDirSource::from_unparsed(item)?.into_superseded()?;
accum.superseded_keys.push(s);
} else {
let a = ConsensusAuthorityEntry::from_items(input, stop_at)?;
accum.authorities.push(a);
}
}
if accum.authorities.is_empty() {
return Err(ErrorProblem::MissingItem {
keyword: DIR_SOURCE_KEYWORD,
});
}
Ok(accum)
}
}