Skip to content

Commit

Permalink
More tests and fix a couple of argument parsing bugs. Changes the cal…
Browse files Browse the repository at this point in the history
…l tree slightly to allow args to be parsed from within a module without knowing how arg parsing works for dnst vs ldns.
  • Loading branch information
ximon18 committed Nov 13, 2024
1 parent bbd6e6d commit f9d7da4
Show file tree
Hide file tree
Showing 4 changed files with 267 additions and 132 deletions.
13 changes: 8 additions & 5 deletions src/bin/ldns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,21 @@

use std::process::ExitCode;

use dnst::error::Error;
use dnst::try_ldns_compatibility;

fn main() -> ExitCode {
let mut args = std::env::args_os();
args.next().unwrap();
let args = try_ldns_compatibility(args).expect("ldns commmand is not recognized");

match args.execute(&mut std::io::stdout()) {
match run() {
Ok(()) => ExitCode::SUCCESS,
Err(err) => {
err.pretty_print();
ExitCode::FAILURE
}
}
}

fn run() -> Result<(), Error> {
let mut args = std::env::args_os();
args.next().unwrap();
try_ldns_compatibility(args)?.execute(&mut std::io::stdout())
}
326 changes: 224 additions & 102 deletions src/commands/nsec3hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,12 @@ impl LdnsCommand for Nsec3Hash {
match arg {
Arg::Short('a') => {
let val = parser.value()?;
algorithm = parse_os_with("algorithm (-a)", &val, Nsec3Hash::parse_nsec3_alg)?;
algorithm =
parse_os_with("algorithm (-a)", &val, Nsec3Hash::parse_nsec3_alg_as_num)?;
}
Arg::Short('s') => {
let val = parser.value()?;
salt = parse_os("salt (-s)", &val)?;
salt = parse_os_with("salt (-s)", &val, Nsec3Hash::parse_salt)?;
}
Arg::Short('t') => {
let val = parser.value()?;
Expand Down Expand Up @@ -129,16 +130,26 @@ impl Nsec3Hash {

pub fn parse_nsec3_alg(arg: &str) -> Result<Nsec3HashAlg, &'static str> {
if let Ok(num) = arg.parse() {
let alg = Nsec3HashAlg::from_int(num);
if alg.to_mnemonic().is_some() {
Ok(alg)
} else {
Err("unknown algorithm number")
}
Self::num_to_nsec3_alg(num)
} else {
Nsec3HashAlg::from_mnemonic(arg.as_bytes()).ok_or("unknown algorithm mnemonic")
}
}

pub fn parse_nsec3_alg_as_num(arg: &str) -> Result<Nsec3HashAlg, &'static str> {
match arg.parse() {
Ok(num) => Self::num_to_nsec3_alg(num),
Err(_) => Err("malformed algorith number"),
}
}

pub fn num_to_nsec3_alg(num: u8) -> Result<Nsec3HashAlg, &'static str> {
let alg = Nsec3HashAlg::from_int(num);
match alg.to_mnemonic() {
Some(_) => Ok(alg),
None => Err("unknown algorithm number"),
}
}
}

impl Nsec3Hash {
Expand All @@ -158,113 +169,224 @@ impl Nsec3Hash {
// also: fuzz/fuzz_targets/nsec3-hash.rs.
#[cfg(test)]
mod tests {
use clap::Parser;

use crate::Args;

use super::*;
use core::str;

// The types we use are provided by the domain crate and construction of
// them from bad inputs should be tested there.
#[test]
fn accept_good_inputs() {
// We don't test all permutations as that would take too long (~20 seconds)
#[allow(clippy::single_element_loop)]
for algorithm in ["SHA-1"] {
let algorithm = Nsec3HashAlg::from_mnemonic(algorithm.as_bytes())
.unwrap_or_else(|| panic!("Algorithm '{algorithm}' was expected to be okay"));
let nsec3_hash = Nsec3Hash {
algorithm,
iterations: 0,
salt: Nsec3Salt::empty(),
name: Name::root(),
};
nsec3_hash.execute(&mut std::io::sink()).unwrap();
mod without_cli {
use core::str::FromStr;

use domain::base::iana::Nsec3HashAlg;
use domain::base::Name;
use domain::rdata::nsec3::Nsec3Salt;

use crate::commands::nsec3hash::Nsec3Hash;

// Note: For the types we use that are provided by the domain crate,
// construction of them from bad inputs should be tested in that
// crate, not here. This test exercises the just the actual
// functionalty of this module without the outer layer of CLI argument
// parsing which is independent of whether we are invoked as `dnst
// nsec3-hash`` or as `ldns-nsec3-hash`.
#[test]
fn execute() {
// We don't test all permutations as that would take too long (~20 seconds)
#[allow(clippy::single_element_loop)]
for algorithm in ["SHA-1"] {
let algorithm = Nsec3HashAlg::from_mnemonic(algorithm.as_bytes())
.unwrap_or_else(|| panic!("Algorithm '{algorithm}' was expected to be okay"));
let nsec3_hash = Nsec3Hash {
algorithm,
iterations: 0,
salt: Nsec3Salt::empty(),
name: Name::root(),
};
nsec3_hash.execute(&mut std::io::sink()).unwrap();
}

for iterations in [0, 1, u16::MAX - 1, u16::MAX] {
let nsec3_hash = Nsec3Hash {
algorithm: Nsec3HashAlg::SHA1,
iterations,
salt: Nsec3Salt::empty(),
name: Name::root(),
};
nsec3_hash.execute(&mut std::io::sink()).unwrap();
}

for salt in ["", "-", "aa", "aabb", "aa".repeat(255).as_str()] {
let salt = Nsec3Salt::from_str(salt)
.unwrap_or_else(|err| panic!("Salt '{salt}' was expected to be okay: {err}"));
let nsec3_hash = Nsec3Hash {
algorithm: Nsec3HashAlg::SHA1,
iterations: 0,
salt,
name: Name::root(),
};
nsec3_hash.execute(&mut std::io::sink()).unwrap();
}

for name in [
".", "a", "a.", "ab", "ab.", "a.ab", "a.ab.", "ab.ab", "ab.ab.", "a.ab.ab",
"a.ab.ab.",
] {
let name = Name::from_str(name)
.unwrap_or_else(|err| panic!("Name '{name}' was expected to be okay: {err}"));
let nsec3_hash = Nsec3Hash {
algorithm: Nsec3HashAlg::SHA1,
iterations: 0,
salt: Nsec3Salt::empty(),
name,
};
nsec3_hash.execute(&mut std::io::sink()).unwrap();
}
}
}

for iterations in [0, 1, u16::MAX - 1, u16::MAX] {
let nsec3_hash = Nsec3Hash {
algorithm: Nsec3HashAlg::SHA1,
iterations,
salt: Nsec3Salt::empty(),
name: Name::root(),
};
nsec3_hash.execute(&mut std::io::sink()).unwrap();
mod with_dnst_cli {
use core::str;

use crate::error::Error;
use crate::{parse_args, Args};

#[test]
fn accept_good_cli_args() {
assert_cmd_eq(&["nlnetlabs.nl"], "asqe4ap6479d7085ljcs10a2fpb2do94.\n");
assert_cmd_eq(
&["-a", "1", "nlnetlabs.nl"],
"asqe4ap6479d7085ljcs10a2fpb2do94.\n",
);
assert_cmd_eq(
&["-a", "SHA-1", "nlnetlabs.nl"],
"asqe4ap6479d7085ljcs10a2fpb2do94.\n",
);
assert_cmd_eq(
&["-i", "0", "nlnetlabs.nl"],
"asqe4ap6479d7085ljcs10a2fpb2do94.\n",
);
assert_cmd_eq(
&["-i", "1", "nlnetlabs.nl"],
"e3dbcbo05tvq0u7po4emvbu79c8vpcgk.\n",
);
assert_cmd_eq(
&["-s", "", "nlnetlabs.nl"],
"asqe4ap6479d7085ljcs10a2fpb2do94.\n",
);
assert_cmd_eq(
&["-s", "DEADBEEF", "nlnetlabs.nl"],
"dfucs7bmmtsil9gij77k1kmocclg5d8a.\n",
);
}

for salt in ["", "-", "aa", "aabb", "aa".repeat(255).as_str()] {
let salt = Nsec3Salt::from_str(salt)
.unwrap_or_else(|err| panic!("Salt '{salt}' was expected to be okay: {err}"));
let nsec3_hash = Nsec3Hash {
algorithm: Nsec3HashAlg::SHA1,
iterations: 0,
salt,
name: Name::root(),
};
nsec3_hash.execute(&mut std::io::sink()).unwrap();
#[test]
fn reject_bad_cli_args() {
assert!(parse_cmd_line(&[]).is_err());
assert!(parse_cmd_line(&[""]).is_err());

assert!(parse_cmd_line(&["-a"]).is_err());
assert!(parse_cmd_line(&["-a", "nlnetlabs.nl"]).is_err());
assert!(parse_cmd_line(&["-a", "", "nlnetlabs.nl"]).is_err());
assert!(parse_cmd_line(&["-a", "2", "nlnetlabs.nl"]).is_err());
assert!(parse_cmd_line(&["-a", "SHA1", "nlnetlabs.nl"]).is_err());
assert!(parse_cmd_line(&["-a", "SHA-256", "nlnetlabs.nl"]).is_err());

assert!(parse_cmd_line(&["-i"]).is_err());
assert!(parse_cmd_line(&["-i", "nlnetlabs.nl"]).is_err());
assert!(parse_cmd_line(&["-i", "", "nlnetlabs.nl"]).is_err());
assert!(parse_cmd_line(&["-i", "-1", "nlnetlabs.nl"]).is_err());
assert!(parse_cmd_line(&["-i", "abc", "nlnetlabs.nl"]).is_err());
assert!(
parse_cmd_line(&["-i", &((u16::MAX as u32) + 1).to_string(), "nlnetlabs.nl"])
.is_err()
);

assert!(parse_cmd_line(&["-s"]).is_err());
assert!(parse_cmd_line(&["-s", "nlnetlabs.nl"]).is_err());
assert!(parse_cmd_line(&["-s", "NOTHEX", "nlnetlabs.nl"]).is_err());
assert!(parse_cmd_line(&["-s", &"aa".repeat(256), "nlnetlabs.nl"]).is_err());
}

for name in [
".", "a", "a.", "ab", "ab.", "a.ab", "a.ab.", "ab.ab", "ab.ab.", "a.ab.ab", "a.ab.ab.",
] {
let name = Name::from_str(name)
.unwrap_or_else(|err| panic!("Name '{name}' was expected to be okay: {err}"));
let nsec3_hash = Nsec3Hash {
algorithm: Nsec3HashAlg::SHA1,
iterations: 0,
salt: Nsec3Salt::empty(),
name,
};
nsec3_hash.execute(&mut std::io::sink()).unwrap();
//------------ Helper functions ------------------------------------------

fn parse_cmd_line(args: &[&str]) -> Result<Args, Error> {
parse_args(|| ["dnst", "nsec3-hash"].iter().chain(args).map(Into::into))
}
}

#[test]
fn reject_bad_inputs() {
assert_arg_parse_failure(&[]);
assert_arg_parse_failure(&[""]);

assert_arg_parse_failure(&["-a"]);
assert_arg_parse_failure(&["-a", "nlnetlabs.nl"]);
assert_arg_parse_failure(&["-a", "", "nlnetlabs.nl"]);
assert_arg_parse_failure(&["-a", "2", "nlnetlabs.nl"]);
assert_arg_parse_failure(&["-a", "SHA-256", "nlnetlabs.nl"]);

assert_arg_parse_failure(&["-t", "nlnetlabs.nl"]);
assert_arg_parse_failure(&["-t", "", "nlnetlabs.nl"]);
assert_arg_parse_failure(&["-t", "-1", "nlnetlabs.nl"]);
assert_arg_parse_failure(&["-t", "abc", "nlnetlabs.nl"]);
assert_arg_parse_failure(&["-t", &((u16::MAX as u32) + 1).to_string(), "nlnetlabs.nl"]);

assert_arg_parse_failure(&["-s"]);
assert_arg_parse_failure(&["-s", "nlnetlabs.nl"]);
assert_arg_parse_failure(&["-s", "NOTHEX", "nlnetlabs.nl"]);
assert_arg_parse_failure(&["-s", &"aa".repeat(256), "nlnetlabs.nl"]);
fn assert_cmd_eq(args: &[&str], expected_output: &str) {
let parsed_args = parse_cmd_line(args).unwrap();
let mut captured_stdout = vec![];
parsed_args.execute(&mut captured_stdout).unwrap();
assert_eq!(str::from_utf8(&captured_stdout), Ok(expected_output));
}
}

#[test]
fn check_defaults() {
// Equivalent to ldns-nsec3-hash -t 0 nlnetlabs.nl
let args = parse_cmd_line(&["nlnetlabs.nl"]).unwrap();

let mut captured_stdout = vec![];
assert!(args.execute(&mut captured_stdout).is_ok());
assert_eq!(
str::from_utf8(&captured_stdout),
Ok("asqe4ap6479d7085ljcs10a2fpb2do94.\n")
);
}
mod with_ldns_cli {
use core::str;

use crate::error::Error;
use crate::{parse_args, Args};

#[test]
fn accept_good_cli_args() {
assert_cmd_eq(&["nlnetlabs.nl"], "e3dbcbo05tvq0u7po4emvbu79c8vpcgk.\n");
assert_cmd_eq(
&["-a", "1", "nlnetlabs.nl"],
"e3dbcbo05tvq0u7po4emvbu79c8vpcgk.\n",
);
assert_cmd_eq(
&["-t", "0", "nlnetlabs.nl"],
"asqe4ap6479d7085ljcs10a2fpb2do94.\n",
);
assert_cmd_eq(
&["-t", "1", "nlnetlabs.nl"],
"e3dbcbo05tvq0u7po4emvbu79c8vpcgk.\n",
);
assert_cmd_eq(
&["-s", "", "nlnetlabs.nl"],
"e3dbcbo05tvq0u7po4emvbu79c8vpcgk.\n",
);
assert_cmd_eq(
&["-s", "DEADBEEF", "nlnetlabs.nl"],
"2h8rboqdrq0ard25vrmc4hjg7m56hnhd.\n",
);
}

#[test]
fn reject_bad_cli_args() {
assert!(parse_cmd_line(&[]).is_err());
assert!(parse_cmd_line(&[""]).is_err());

assert!(parse_cmd_line(&["-a"]).is_err());
assert!(parse_cmd_line(&["-a", "nlnetlabs.nl"]).is_err());
assert!(parse_cmd_line(&["-a", "", "nlnetlabs.nl"]).is_err());
assert!(parse_cmd_line(&["-a", "2", "nlnetlabs.nl"]).is_err());
assert!(parse_cmd_line(&["-a", "SHA1", "nlnetlabs.nl"]).is_err());
assert!(parse_cmd_line(&["-a", "SHA-1", "nlnetlabs.nl"]).is_err());
assert!(parse_cmd_line(&["-a", "SHA-256", "nlnetlabs.nl"]).is_err());

assert!(parse_cmd_line(&["-t"]).is_err());
assert!(parse_cmd_line(&["-t", "nlnetlabs.nl"]).is_err());
assert!(parse_cmd_line(&["-t", "", "nlnetlabs.nl"]).is_err());
assert!(parse_cmd_line(&["-t", "-1", "nlnetlabs.nl"]).is_err());
assert!(parse_cmd_line(&["-t", "abc", "nlnetlabs.nl"]).is_err());
assert!(
parse_cmd_line(&["-t", &((u16::MAX as u32) + 1).to_string(), "nlnetlabs.nl"])
.is_err()
);

assert!(parse_cmd_line(&["-s"]).is_err());
assert!(parse_cmd_line(&["-s", "nlnetlabs.nl"]).is_err());
assert!(parse_cmd_line(&["-s", "NOTHEX", "nlnetlabs.nl"]).is_err());
assert!(parse_cmd_line(&["-s", &"aa".repeat(256), "nlnetlabs.nl"]).is_err());
}

//------------ Helper functions ------------------------------------------
//------------ Helper functions ------------------------------------------

fn parse_cmd_line(args: &[&str]) -> Result<Args, clap::error::Error> {
Args::try_parse_from(["dnst", "nsec3-hash"].iter().chain(args))
}
fn parse_cmd_line(args: &[&str]) -> Result<Args, Error> {
parse_args(|| ["ldns-nsec3-hash"].iter().chain(args).map(Into::into))
}

fn assert_arg_parse_failure(args: &[&str]) {
if parse_cmd_line(args).is_ok() {
panic!("Expected error with arguments: {args:?}");
fn assert_cmd_eq(args: &[&str], expected_output: &str) {
let parsed_args = parse_cmd_line(args).unwrap();
let mut captured_stdout = vec![];
parsed_args.execute(&mut captured_stdout).unwrap();
assert_eq!(str::from_utf8(&captured_stdout), Ok(expected_output));
}
}
}
Loading

0 comments on commit f9d7da4

Please sign in to comment.