use std::str::FromStr;
use crate::parse2::{
ErrorProblem, IsStructural, ItemStream, KeywordRef, NetdocParseable, ParseInput,
};
use ErrorProblem as EP;
use crate::parse2::poc::netstatus::vote::{
NetworkStatusUnverifiedParsedBody as NetworkStatusVoteUnverifiedParsedBody,
};
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, derive_more::AsRef)]
pub struct EncodedAuthCert(#[as_ref(str)] String);
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum ItemSequenceChecker {
Intro,
Body,
End,
}
struct IsOurStructural;
const INTRO_KEYWORD: &str = "dir-key-certificate-version";
const FINAL_KEYWORD: &str = "dir-key-certification";
impl EncodedAuthCert {
pub fn as_str(&self) -> &str {
&self.0
}
}
impl ItemSequenceChecker {
fn start() -> Self {
use ItemSequenceChecker::*;
Intro
}
fn keyword(&mut self, kw: KeywordRef<'_>) -> Result<Option<IsOurStructural>, EP> {
use ItemSequenceChecker::*;
let mut change_state = |from, to| {
if *self == from {
*self = to;
Ok(Some(IsOurStructural))
} else {
Err(EP::OtherBadDocument("authcert bad structure"))
}
};
if kw == INTRO_KEYWORD {
change_state(Intro, Body)
} else if kw == FINAL_KEYWORD {
change_state(Body, End)
} else if *self != Body {
Err(EP::OtherBadDocument(
"authcert loose body item or missing intro keyword",
))
} else if let Some(IsStructural) =
NetworkStatusVoteUnverifiedParsedBody::is_structural_keyword(kw)
{
Err(EP::OtherBadDocument(
"authcert with vote structural keyword",
))
} else if kw == "fingerprint" || kw.as_str().starts_with("dir-") {
Ok(None)
} else {
Err(EP::OtherBadDocument(
"authcert body keyword not dir- or fingerprint",
))
}
}
fn finish(self) -> Result<(), EP> {
use ItemSequenceChecker::*;
match self {
End => Ok(()),
_other => Err(EP::OtherBadDocument(
"authcert missing end (signature) item",
)),
}
}
}
fn extra_lexical_checks(s: &str) -> Result<(), EP> {
let _without_trailing_nl = s
.strip_suffix("\n")
.ok_or(EP::OtherBadDocument("missing final newline"))?;
Ok(())
}
fn check(s: &str) -> Result<(), EP> {
extra_lexical_checks(s)?;
let input = ParseInput::new(s, "<authcert string>");
let mut lex = ItemStream::new(&input).map_err(|e| e.problem)?;
let mut seq = ItemSequenceChecker::start();
while let Some(item) = lex.next_item()? {
seq.keyword(item.keyword())?;
}
seq.finish()
}
impl TryFrom<String> for EncodedAuthCert {
type Error = ErrorProblem;
fn try_from(s: String) -> Result<Self, EP> {
check(&s)?;
Ok(EncodedAuthCert(s))
}
}
impl FromStr for EncodedAuthCert {
type Err = ErrorProblem;
fn from_str(s: &str) -> Result<Self, EP> {
s.to_owned().try_into()
}
}
impl NetdocParseable for EncodedAuthCert {
fn doctype_for_error() -> &'static str {
"encoded authority key certificate"
}
fn is_intro_item_keyword(kw: KeywordRef<'_>) -> bool {
kw == INTRO_KEYWORD
}
fn is_structural_keyword(kw: KeywordRef<'_>) -> Option<IsStructural> {
(Self::is_intro_item_keyword(kw) || kw == FINAL_KEYWORD).then_some(IsStructural)
}
fn from_items(input: &mut ItemStream<'_>, stop_at: stop_at!()) -> Result<Self, EP> {
let start_pos = input.byte_position();
let mut seq = ItemSequenceChecker::start();
while seq != ItemSequenceChecker::End {
let item = input.next_item()?.ok_or(EP::MissingItem {
keyword: FINAL_KEYWORD,
})?;
let kw = item.keyword();
match seq.keyword(kw)? {
Some(IsOurStructural) => {} None => {
if stop_at.stop_at(kw) {
return Err(EP::Internal(
"bug! parent document structural keyword found while trying to process an embedded authcert, but was accepted by ItemSequenceChecker; authcert embedded in something other than a vote?",
));
}
}
}
}
seq.finish()?;
let text = input
.whole_input()
.get(start_pos..)
.expect("start_pos wasn't included in the body so far?!");
extra_lexical_checks(text)?;
if let Some(next_item) = input.peek_keyword()? {
if !stop_at.stop_at(next_item) {
return Err(EP::OtherBadDocument(
"unexpected loose items after embedded authcert",
));
}
}
Ok(EncodedAuthCert(text.to_string()))
}
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_time_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
use super::*;
use crate::parse2::parse_netdoc;
use derive_deftly::Deftly;
use std::fmt::{Debug, Display};
#[derive(Debug, Deftly)]
#[derive_deftly(NetdocParseable)]
#[allow(unused)]
struct Embeds {
e_intro: (),
#[deftly(netdoc(subdoc))]
cert: EncodedAuthCert,
#[deftly(netdoc(subdoc))]
subdocs: Vec<Subdoc>,
}
#[derive(Debug, Deftly)]
#[derive_deftly(NetdocParseable)]
#[allow(unused)]
struct Subdoc {
dir_e_subdoc: (),
}
fn chk(exp_sole: Result<(), &str>, exp_embed: Result<(), &str>, doc: &str) {
fn chk1<T: Debug, E: Debug + tor_error::ErrorReport + Display>(
exp: Result<(), &str>,
doc: &str,
what: &str,
got: Result<T, E>,
) {
eprintln!("==========\n---- {what} 8<- ----\n{doc}---- ->8 {what} ----\n");
match got {
Err(got_e) => {
let got_m = got_e.report().to_string();
eprintln!("{what}, got error: {got_e:?}");
eprintln!("{what}, got error: {got_m:?}");
let exp_m = exp.expect_err("expected success!");
assert!(
got_m.contains(exp_m),
"{what}, expected different error: {exp_m:?}"
);
}
y @ Ok(_) => {
eprintln!("got {y:?}");
assert!(exp.is_ok(), "{what}, unexpected success; expected: {exp:?}");
}
}
}
chk1(exp_sole, doc, "from_str", EncodedAuthCert::from_str(doc));
chk1(
exp_sole,
doc,
"From<String>",
EncodedAuthCert::try_from(doc.to_owned()),
);
let embeds = format!(
r"e-intro
ignored
{doc}dir-e-subdoc
dir-ignored-2
"
);
let parse_input = ParseInput::new(&embeds, "<embeds>");
chk1(
exp_embed,
&embeds,
"embedded",
parse_netdoc::<Embeds>(&parse_input),
);
}
#[test]
fn bad_authcerts() {
NetworkStatusVoteUnverifiedParsedBody::is_structural_keyword(
KeywordRef::new("dir-source").unwrap(),
)
.expect("structural dir-source");
chk(
Err("missing final newline"),
Err("missing item encoded authority key certificate"),
r"",
);
chk(
Err("authcert loose body item or missing intro keyword"),
Err("missing item encoded authority key certificate"),
r"wrong-intro
",
);
chk(
Err("missing final newline"),
Err("missing item dir-key-certification"),
r"dir-key-certificate-version
dir-missing-nl",
);
chk(
Err("authcert bad structure"),
Err("authcert bad structure"),
r"dir-key-certificate-version
dir-key-certificate-version
",
);
chk(
Err("authcert body keyword not dir- or fingerprint"),
Err("authcert body keyword not dir- or fingerprint"),
r"dir-key-certificate-version
wrong-item
dir-key-certification
",
);
chk(
Err("authcert with vote structural keyword"),
Err("authcert with vote structural keyword"),
r"dir-key-certificate-version
r
dir-key-certification
",
);
chk(
Err("authcert with vote structural keyword"),
Err("authcert with vote structural keyword"),
r"dir-key-certificate-version
dir-source
dir-key-certification
",
);
chk(
Ok(()), Err("bug! parent document structural keyword found"),
r"dir-key-certificate-version
dir-e-subdoc
dir-key-certification
",
);
chk(
Err("authcert with vote structural keyword"),
Err("authcert with vote structural keyword"),
r"dir-key-certificate-version
dir-example-item
r
",
);
chk(
Err("authcert loose body item or missing intro keyword"),
Err("unexpected loose items after embedded authcert"),
r"dir-key-certificate-version
dir-example-item
dir-key-certification
dir-extra-item
r
",
);
chk(
Err("authcert bad structure"),
Err("authcert bad structure"),
r"dir-key-certificate-version
dir-key-certificate-version
dir-example-item
dir-key-certification
dir-key-certification
r
",
);
chk(
Err("authcert bad structure"),
Err("unexpected loose items after embedded authcert"),
r"dir-key-certificate-version
dir-example-item
dir-key-certification
dir-key-certification
r
",
);
}
}