aboutsummaryrefslogtreecommitdiff
path: root/nitrocli/src/args.rs
diff options
context:
space:
mode:
authorDaniel Mueller <deso@posteo.net>2019-01-05 21:41:22 -0800
committerDaniel Mueller <deso@posteo.net>2019-01-05 21:41:22 -0800
commit6d07a0c9d9a9b39247a9727dea2d90eba4e1fe9e (patch)
tree843fb189a543e6bc95ff9e5498f5ca2d09bc67a0 /nitrocli/src/args.rs
parentb750c4b13272908a51b85072008554c344b25016 (diff)
downloadnitrocli-6d07a0c9d9a9b39247a9727dea2d90eba4e1fe9e.tar.gz
nitrocli-6d07a0c9d9a9b39247a9727dea2d90eba4e1fe9e.tar.bz2
Supply customizable stdio channels to argparse
In order to properly test the program we need to have a way to intercept data printed to the stdio channels. There are different ways to accomplish that task. While it is reasonably easy to just start the program as a dedicated process doing so properly may be problematic from inside a test because either the path to the binary has to be retrieved or cargo -- the entity which knows the path -- be invoked. None of these approaches is very appealing from a testing and code complexity point of view: an additional fork means additional sources of errors and flakiness, executing cargo has the potential to even cause rebuilds of parts of the program, and while we are already testing against a slow I/O device this additional code running is unlikely to go unnoticed in the long-term. Lastly, doing so also means that we leave Rust's type safety behind when dealing with errors that could be nicely match'ed on when the test invocation is just a function call. To avoid all this complexity we instead strive for basically just running the main function. This patch marks a first step towards achieving this goal. It introduces the infrastructure to supply custom Write objects to the argument parsing functionality. Once more we piggy-back on the command execution context and add objects representing stdout and stderr to it. We further ensure that this context is passed to the argument parser invocations.
Diffstat (limited to 'nitrocli/src/args.rs')
-rw-r--r--nitrocli/src/args.rs98
1 files changed, 64 insertions, 34 deletions
diff --git a/nitrocli/src/args.rs b/nitrocli/src/args.rs
index e45c4b9..f689643 100644
--- a/nitrocli/src/args.rs
+++ b/nitrocli/src/args.rs
@@ -25,6 +25,7 @@ use std::str;
use crate::commands;
use crate::error::Error;
use crate::pinentry;
+use crate::RunCtx;
type Result<T> = result::Result<T, Error>;
@@ -60,13 +61,29 @@ impl str::FromStr for DeviceModel {
}
}
+trait Stdio {
+ fn stdio(&mut self) -> (&mut dyn io::Write, &mut dyn io::Write);
+}
+
+impl<'io> Stdio for RunCtx<'io> {
+ fn stdio(&mut self) -> (&mut dyn io::Write, &mut dyn io::Write) {
+ (self.stdout, self.stderr)
+ }
+}
+
/// A command execution context that captures additional data pertaining
/// the command execution.
-#[derive(Debug)]
pub struct ExecCtx<'io> {
pub model: Option<DeviceModel>,
+ pub stdout: &'io mut dyn io::Write,
+ pub stderr: &'io mut dyn io::Write,
pub verbosity: u64,
- data: std::marker::PhantomData<&'io u64>,
+}
+
+impl<'io> Stdio for ExecCtx<'io> {
+ fn stdio(&mut self) -> (&mut dyn io::Write, &mut dyn io::Write) {
+ (self.stdout, self.stderr)
+ }
}
/// A top-level command for nitrocli.
@@ -369,7 +386,7 @@ enum PinCommand {
impl PinCommand {
fn execute(&self, ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
match *self {
- PinCommand::Clear => pin_clear(args),
+ PinCommand::Clear => pin_clear(ctx, args),
PinCommand::Set => pin_set(ctx, args),
PinCommand::Unblock => pin_unblock(ctx, args),
}
@@ -451,8 +468,13 @@ impl str::FromStr for PwsCommand {
}
}
-fn parse(parser: &argparse::ArgumentParser<'_>, args: Vec<String>) -> Result<()> {
- if let Err(err) = parser.parse(args, &mut io::stdout(), &mut io::stderr()) {
+fn parse(
+ ctx: &mut impl Stdio,
+ parser: &argparse::ArgumentParser<'_>,
+ args: Vec<String>,
+) -> Result<()> {
+ let (stdout, stderr) = ctx.stdio();
+ if let Err(err) = parser.parse(args, stdout, stderr) {
Err(Error::ArgparseError(err))
} else {
Ok(())
@@ -463,7 +485,7 @@ fn parse(parser: &argparse::ArgumentParser<'_>, args: Vec<String>) -> Result<()>
fn status(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
let mut parser = argparse::ArgumentParser::new();
parser.set_description("Prints the status of the connected Nitrokey device");
- parse(&parser, args)?;
+ parse(ctx, &parser, args)?;
commands::status(ctx)
}
@@ -529,7 +551,7 @@ fn storage(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
"The arguments for the subcommand",
);
parser.stop_on_first_argument(true);
- parse(&parser, args)?;
+ parse(ctx, &parser, args)?;
drop(parser);
subargs.insert(0, format!("nitrocli storage {}", subcommand));
@@ -540,7 +562,7 @@ fn storage(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
fn storage_open(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
let mut parser = argparse::ArgumentParser::new();
parser.set_description("Opens the encrypted volume on a Nitrokey Storage");
- parse(&parser, args)?;
+ parse(ctx, &parser, args)?;
commands::storage_open(ctx)
}
@@ -549,7 +571,7 @@ fn storage_open(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
fn storage_close(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
let mut parser = argparse::ArgumentParser::new();
parser.set_description("Closes the encrypted volume on a Nitrokey Storage");
- parse(&parser, args)?;
+ parse(ctx, &parser, args)?;
commands::storage_close(ctx)
}
@@ -558,7 +580,7 @@ fn storage_close(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
fn storage_status(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
let mut parser = argparse::ArgumentParser::new();
parser.set_description("Prints the status of the Nitrokey's storage");
- parse(&parser, args)?;
+ parse(ctx, &parser, args)?;
commands::storage_status(ctx)
}
@@ -580,7 +602,7 @@ fn config(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
"The arguments for the subcommand",
);
parser.stop_on_first_argument(true);
- parse(&parser, args)?;
+ parse(ctx, &parser, args)?;
drop(parser);
subargs.insert(0, format!("nitrocli config {}", subcommand));
@@ -591,7 +613,7 @@ fn config(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
fn config_get(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
let mut parser = argparse::ArgumentParser::new();
parser.set_description("Prints the Nitrokey configuration");
- parse(&parser, args)?;
+ parse(ctx, &parser, args)?;
commands::config_get(ctx)
}
@@ -648,7 +670,7 @@ fn config_set(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
argparse::StoreTrue,
"Allow one-time password generation without PIN",
);
- parse(&parser, args)?;
+ parse(ctx, &parser, args)?;
drop(parser);
let numlock = ConfigOption::try_from(no_numlock, numlock, "numlock")?;
@@ -668,7 +690,7 @@ fn config_set(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
fn lock(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
let mut parser = argparse::ArgumentParser::new();
parser.set_description("Locks the connected Nitrokey device");
- parse(&parser, args)?;
+ parse(ctx, &parser, args)?;
commands::lock(ctx)
}
@@ -690,7 +712,7 @@ fn otp(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
"The arguments for the subcommand",
);
parser.stop_on_first_argument(true);
- parse(&parser, args)?;
+ parse(ctx, &parser, args)?;
drop(parser);
subargs.insert(0, format!("nitrocli otp {}", subcommand));
@@ -719,7 +741,7 @@ fn otp_get(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
argparse::StoreOption,
"The time to use for TOTP generation (Unix timestamp, default: system time)",
);
- parse(&parser, args)?;
+ parse(ctx, &parser, args)?;
drop(parser);
commands::otp_get(ctx, slot, algorithm, time)
@@ -783,7 +805,7 @@ pub fn otp_set(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
argparse::StoreOption,
"The format of the secret (ascii|base32|hex)",
);
- parse(&parser, args)?;
+ parse(ctx, &parser, args)?;
drop(parser);
if ascii {
@@ -825,7 +847,7 @@ fn otp_clear(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
argparse::Store,
"The OTP algorithm to use (hotp|totp)",
);
- parse(&parser, args)?;
+ parse(ctx, &parser, args)?;
drop(parser);
commands::otp_clear(ctx, slot, algorithm)
@@ -841,7 +863,7 @@ fn otp_status(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
argparse::StoreTrue,
"Show slots that are not programmed",
);
- parse(&parser, args)?;
+ parse(ctx, &parser, args)?;
drop(parser);
commands::otp_status(ctx, all)
@@ -864,7 +886,7 @@ fn pin(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
"The arguments for the subcommand",
);
parser.stop_on_first_argument(true);
- parse(&parser, args)?;
+ parse(ctx, &parser, args)?;
drop(parser);
subargs.insert(0, format!("nitrocli pin {}", subcommand));
@@ -872,10 +894,10 @@ fn pin(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
}
/// Clear the PIN as cached by various other commands.
-fn pin_clear(args: Vec<String>) -> Result<()> {
+fn pin_clear(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
let mut parser = argparse::ArgumentParser::new();
parser.set_description("Clears the cached PINs");
- parse(&parser, args)?;
+ parse(ctx, &parser, args)?;
commands::pin_clear()
}
@@ -890,7 +912,7 @@ fn pin_set(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
argparse::Store,
"The PIN type to change (admin|user)",
);
- parse(&parser, args)?;
+ parse(ctx, &parser, args)?;
drop(parser);
commands::pin_set(ctx, pintype)
@@ -900,7 +922,7 @@ fn pin_set(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
fn pin_unblock(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
let mut parser = argparse::ArgumentParser::new();
parser.set_description("Unblocks and resets the user PIN");
- parse(&parser, args)?;
+ parse(ctx, &parser, args)?;
commands::pin_unblock(ctx)
}
@@ -922,7 +944,7 @@ fn pws(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
"The arguments for the subcommand",
);
parser.stop_on_first_argument(true);
- parse(&parser, args)?;
+ parse(ctx, &parser, args)?;
drop(parser);
subargs.insert(0, format!("nitrocli pws {}", subcommand));
@@ -963,7 +985,7 @@ fn pws_get(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
argparse::StoreTrue,
"Print the stored data without description",
);
- parse(&parser, args)?;
+ parse(ctx, &parser, args)?;
drop(parser);
commands::pws_get(ctx, slot, name, login, password, quiet)
@@ -997,7 +1019,7 @@ fn pws_set(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
argparse::Store,
"The password to store on the slot",
);
- parse(&parser, args)?;
+ parse(ctx, &parser, args)?;
drop(parser);
commands::pws_set(ctx, slot, &name, &login, &password)
@@ -1013,7 +1035,7 @@ fn pws_clear(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
argparse::Store,
"The PWS slot to clear",
);
- parse(&parser, args)?;
+ parse(ctx, &parser, args)?;
drop(parser);
commands::pws_clear(ctx, slot)
@@ -1029,7 +1051,7 @@ fn pws_status(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
argparse::StoreTrue,
"Show slots that are not programmed",
);
- parse(&parser, args)?;
+ parse(ctx, &parser, args)?;
drop(parser);
commands::pws_status(ctx, all)
@@ -1037,7 +1059,10 @@ fn pws_status(ctx: &mut ExecCtx<'_>, args: Vec<String>) -> Result<()> {
/// Parse the command-line arguments and return the selected command and
/// the remaining arguments for the command.
-fn parse_arguments<'io>(args: Vec<String>) -> Result<(Command, ExecCtx<'io>, Vec<String>)> {
+fn parse_arguments<'io, 'ctx: 'io>(
+ ctx: &'ctx mut RunCtx<'_>,
+ args: Vec<String>,
+) -> Result<(Command, ExecCtx<'io>, Vec<String>)> {
let mut model: Option<DeviceModel> = None;
let mut verbosity = 0;
let mut command = Command::Status;
@@ -1065,17 +1090,22 @@ fn parse_arguments<'io>(args: Vec<String>) -> Result<(Command, ExecCtx<'io>, Vec
"The arguments for the command",
);
parser.stop_on_first_argument(true);
- parse(&parser, args)?;
+ parse(ctx, &parser, args)?;
drop(parser);
subargs.insert(0, format!("nitrocli {}", command));
- let ctx = ExecCtx { model, verbosity, data: Default::default() };
+ let ctx = ExecCtx {
+ model,
+ stdout: ctx.stdout,
+ stderr: ctx.stderr,
+ verbosity,
+ };
Ok((command, ctx, subargs))
}
/// Parse the command-line arguments and execute the selected command.
-pub fn handle_arguments(args: Vec<String>) -> Result<()> {
- let (command, mut ctx, args) = parse_arguments(args)?;
+pub(crate) fn handle_arguments(ctx: &mut RunCtx<'_>, args: Vec<String>) -> Result<()> {
+ let (command, mut ctx, args) = parse_arguments(ctx, args)?;
command.execute(&mut ctx, args)
}