aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJack Lloyd <[email protected]>2017-10-24 13:59:17 -0400
committerJack Lloyd <[email protected]>2017-10-24 13:59:17 -0400
commit7edec05c6056ab890a70eaf9f5c7a73321581ede (patch)
tree4de2627ea54b94e238268f4a26c78acf37e97a23
parentec4f5f1aa187a5416a43f10ae5afa5da137f99ae (diff)
parent08ffc1a49bb0f1a1a42a57d7c55bbf0d9b6b8336 (diff)
Merge GH #1273 Refactor test and cli runner code
-rw-r--r--src/cli/argparse.h278
-rw-r--r--src/cli/cli.cpp250
-rw-r--r--src/cli/cli.h479
-rw-r--r--src/cli/cli_exceptions.h44
-rw-r--r--src/cli/cli_rng.cpp89
-rw-r--r--src/cli/compress.cpp13
-rw-r--r--src/cli/credentials.h1
-rw-r--r--src/cli/encryption.cpp57
-rw-r--r--src/cli/main.cpp10
-rw-r--r--src/cli/speed.cpp1
-rw-r--r--src/cli/timing_tests.cpp2
-rw-r--r--src/cli/tls_client.cpp2
-rw-r--r--src/cli/tls_server.cpp1
-rw-r--r--src/cli/tls_utils.cpp9
-rw-r--r--src/cli/utils.cpp79
-rw-r--r--src/tests/main.cpp439
-rw-r--r--src/tests/test_runner.cpp332
-rw-r--r--src/tests/test_runner.h43
18 files changed, 1156 insertions, 973 deletions
diff --git a/src/cli/argparse.h b/src/cli/argparse.h
new file mode 100644
index 000000000..457716095
--- /dev/null
+++ b/src/cli/argparse.h
@@ -0,0 +1,278 @@
+/*
+* (C) 2015,2017 Jack Lloyd
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#ifndef BOTAN_ARGPARSE_H_
+#define BOTAN_ARGPARSE_H_
+
+#include <string>
+#include <map>
+#include <set>
+#include <vector>
+#include <botan/parsing.h>
+#include "cli_exceptions.h"
+
+namespace Botan_CLI {
+
+class Argument_Parser
+ {
+ public:
+ Argument_Parser(const std::string& spec,
+ const std::vector<std::string>& extra_flags = {},
+ const std::vector<std::string>& extra_opts = {});
+
+ void parse_args(const std::vector<std::string>& params);
+
+ bool flag_set(const std::string& flag) const;
+
+ bool has_arg(const std::string& opt_name) const;
+ std::string get_arg(const std::string& option) const;
+
+ std::string get_arg_or(const std::string& option, const std::string& otherwise) const;
+
+ size_t get_arg_sz(const std::string& option) const;
+
+ std::vector<std::string> get_arg_list(const std::string& what) const;
+
+ private:
+ // set in constructor
+ std::vector<std::string> m_spec_args;
+ std::set<std::string> m_spec_flags;
+ std::map<std::string, std::string> m_spec_opts;
+ std::string m_spec_rest;
+
+ // set in parse_args()
+ std::map<std::string, std::string> m_user_args;
+ std::set<std::string> m_user_flags;
+ std::vector<std::string> m_user_rest;
+ };
+
+bool Argument_Parser::flag_set(const std::string& flag_name) const
+ {
+ return m_user_flags.count(flag_name) > 0;
+ }
+
+bool Argument_Parser::has_arg(const std::string& opt_name) const
+ {
+ return m_user_args.count(opt_name) > 0;
+ }
+
+std::string Argument_Parser::get_arg(const std::string& opt_name) const
+ {
+ auto i = m_user_args.find(opt_name);
+ if(i == m_user_args.end())
+ {
+ // this shouldn't occur unless you passed the wrong thing to get_arg
+ throw CLI_Error("Unknown option " + opt_name + " used (program bug)");
+ }
+ return i->second;
+ }
+
+std::string Argument_Parser::get_arg_or(const std::string& opt_name, const std::string& otherwise) const
+ {
+ auto i = m_user_args.find(opt_name);
+ if(i == m_user_args.end() || i->second.empty())
+ {
+ return otherwise;
+ }
+ return i->second;
+ }
+
+size_t Argument_Parser::get_arg_sz(const std::string& opt_name) const
+ {
+ const std::string s = get_arg(opt_name);
+
+ try
+ {
+ return static_cast<size_t>(std::stoul(s));
+ }
+ catch(std::exception&)
+ {
+ throw CLI_Usage_Error("Invalid integer value '" + s + "' for option " + opt_name);
+ }
+ }
+
+std::vector<std::string> Argument_Parser::get_arg_list(const std::string& what) const
+ {
+ if(what != m_spec_rest)
+ {
+ throw CLI_Error("Unexpected list name '" + what + "'");
+ }
+ return m_user_rest;
+ }
+
+void Argument_Parser::parse_args(const std::vector<std::string>& params)
+ {
+ std::vector<std::string> args;
+ for(auto const& param : params)
+ {
+ if(param.find("--") == 0)
+ {
+ // option
+ const auto eq = param.find('=');
+
+ if(eq == std::string::npos)
+ {
+ const std::string opt_name = param.substr(2, std::string::npos);
+
+ if(m_spec_flags.count(opt_name) == 0)
+ {
+ if(m_spec_opts.count(opt_name))
+ {
+ throw CLI_Usage_Error("Invalid usage of option --" + opt_name +
+ " without value");
+ }
+ else
+ {
+ throw CLI_Usage_Error("Unknown flag --" + opt_name);
+ }
+ }
+ m_user_flags.insert(opt_name);
+ }
+ else
+ {
+ const std::string opt_name = param.substr(2, eq - 2);
+ const std::string opt_val = param.substr(eq + 1, std::string::npos);
+
+ if(m_spec_opts.count(opt_name) == 0)
+ {
+ throw CLI_Usage_Error("Unknown option --" + opt_name);
+ }
+
+ m_user_args.insert(std::make_pair(opt_name, opt_val));
+ }
+ }
+ else
+ {
+ // argument
+ args.push_back(param);
+ }
+ }
+
+ bool seen_stdin_flag = false;
+ size_t arg_i = 0;
+ for(auto const& arg : m_spec_args)
+ {
+ if(arg_i >= args.size())
+ {
+ // not enough arguments
+ throw CLI_Usage_Error("Invalid argument count, got " +
+ std::to_string(args.size()) +
+ " expected " +
+ std::to_string(m_spec_args.size()));
+ }
+
+ m_user_args.insert(std::make_pair(arg, args[arg_i]));
+
+ if(args[arg_i] == "-")
+ {
+ if(seen_stdin_flag)
+ {
+ throw CLI_Usage_Error("Cannot specify '-' (stdin) more than once");
+ }
+ seen_stdin_flag = true;
+ }
+
+ ++arg_i;
+ }
+
+ if(m_spec_rest.empty())
+ {
+ if(arg_i != args.size())
+ {
+ throw CLI_Usage_Error("Too many arguments");
+ }
+ }
+ else
+ {
+ m_user_rest.assign(args.begin() + arg_i, args.end());
+ }
+
+ // Now insert any defaults for options not supplied by the user
+ for(auto const& opt : m_spec_opts)
+ {
+ if(m_user_args.count(opt.first) == 0)
+ {
+ m_user_args.insert(opt);
+ }
+ }
+ }
+
+Argument_Parser::Argument_Parser(const std::string& spec,
+ const std::vector<std::string>& extra_flags,
+ const std::vector<std::string>& extra_opts)
+ {
+ class CLI_Error_Invalid_Spec : public CLI_Error
+ {
+ public:
+ explicit CLI_Error_Invalid_Spec(const std::string& spec)
+ : CLI_Error("Invalid command spec '" + spec + "'") {}
+ };
+
+ const std::vector<std::string> parts = Botan::split_on(spec, ' ');
+
+ if(parts.size() == 0)
+ {
+ throw CLI_Error_Invalid_Spec(spec);
+ }
+
+ for(size_t i = 1; i != parts.size(); ++i)
+ {
+ const std::string s = parts[i];
+
+ if(s.empty()) // ?!? (shouldn't happen)
+ {
+ throw CLI_Error_Invalid_Spec(spec);
+ }
+
+ if(s.size() > 2 && s[0] == '-' && s[1] == '-')
+ {
+ // option or flag
+
+ auto eq = s.find('=');
+
+ if(eq == std::string::npos)
+ {
+ m_spec_flags.insert(s.substr(2, std::string::npos));
+ }
+ else
+ {
+ m_spec_opts.insert(std::make_pair(s.substr(2, eq - 2), s.substr(eq + 1, std::string::npos)));
+ }
+ }
+ else if(s[0] == '*')
+ {
+ // rest argument
+ if(m_spec_rest.empty() && s.size() > 2)
+ {
+ m_spec_rest = s.substr(1, std::string::npos);
+ }
+ else
+ {
+ throw CLI_Error_Invalid_Spec(spec);
+ }
+ }
+ else
+ {
+ // named argument
+ if(!m_spec_rest.empty()) // rest arg wasn't last
+ {
+ throw CLI_Error_Invalid_Spec(spec);
+ }
+
+ m_spec_args.push_back(s);
+ }
+ }
+
+ for(std::string flag : extra_flags)
+ m_spec_flags.insert(flag);
+ for(std::string opt : extra_opts)
+ m_spec_opts.insert(std::make_pair(opt, ""));
+ }
+
+
+}
+
+#endif
diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp
new file mode 100644
index 000000000..f23c3574d
--- /dev/null
+++ b/src/cli/cli.cpp
@@ -0,0 +1,250 @@
+/*
+* (C) 2015,2017 Jack Lloyd
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#include "cli.h"
+#include "argparse.h"
+#include <botan/rng.h>
+#include <botan/parsing.h>
+#include <iostream>
+#include <fstream>
+
+namespace Botan_CLI {
+
+Command::Command(const std::string& cmd_spec) : m_spec(cmd_spec)
+ {
+ // for checking all spec strings at load time
+ //m_args.reset(new Argument_Parser(m_spec));
+ }
+
+Command::~Command() { /* for unique_ptr */ }
+
+std::string Command::help_text() const
+ {
+ return "Usage: " + m_spec +
+ "\n\nAll commands support --verbose --help --output= --error-output= --rng-type= --drbg-seed=";
+ }
+
+int Command::run(const std::vector<std::string>& params)
+ {
+ try
+ {
+ m_args.reset(new Argument_Parser(m_spec,
+ {"verbose", "help"},
+ {"output", "error-output", "rng-type", "drbg-seed"}));
+
+ m_args->parse_args(params);
+
+ if(m_args->has_arg("output"))
+ {
+ const std::string output_file = get_arg("output");
+
+ if(output_file != "")
+ {
+ m_output_stream.reset(new std::ofstream(output_file, std::ios::binary));
+ if(!m_output_stream->good())
+ throw CLI_IO_Error("opening", output_file);
+ }
+ }
+
+ if(m_args->has_arg("error-output"))
+ {
+ const std::string output_file = get_arg("error-output");
+
+ if(output_file != "")
+ {
+ m_error_output_stream.reset(new std::ofstream(output_file, std::ios::binary));
+ if(!m_error_output_stream->good())
+ throw CLI_IO_Error("opening", output_file);
+ }
+ }
+
+ if(flag_set("help"))
+ {
+ output() << help_text() << "\n";
+ return 2;
+ }
+
+ this->go();
+ return 0;
+ }
+ catch(CLI_Usage_Error& e)
+ {
+ error_output() << "Usage error: " << e.what() << "\n";
+ error_output() << help_text() << "\n";
+ return 1;
+ }
+ catch(std::exception& e)
+ {
+ error_output() << "Error: " << e.what() << "\n";
+ return 2;
+ }
+ catch(...)
+ {
+ error_output() << "Error: unknown exception\n";
+ return 2;
+ }
+ }
+
+bool Command::flag_set(const std::string& flag_name) const
+ {
+ return m_args->flag_set(flag_name);
+ }
+
+std::string Command::get_arg(const std::string& opt_name) const
+ {
+ return m_args->get_arg(opt_name);
+ }
+
+/*
+* Like get_arg() but if the argument was not specified or is empty, returns otherwise
+*/
+std::string Command::get_arg_or(const std::string& opt_name, const std::string& otherwise) const
+ {
+ return m_args->get_arg_or(opt_name, otherwise);
+ }
+
+size_t Command::get_arg_sz(const std::string& opt_name) const
+ {
+ return m_args->get_arg_sz(opt_name);
+ }
+
+std::vector<std::string> Command::get_arg_list(const std::string& what) const
+ {
+ return m_args->get_arg_list(what);
+ }
+
+std::ostream& Command::output()
+ {
+ if(m_output_stream.get())
+ {
+ return *m_output_stream;
+ }
+ return std::cout;
+ }
+
+std::ostream& Command::error_output()
+ {
+ if(m_error_output_stream.get())
+ {
+ return *m_error_output_stream;
+ }
+ return std::cerr;
+ }
+
+std::vector<uint8_t> Command::slurp_file(const std::string& input_file,
+ size_t buf_size) const
+ {
+ std::vector<uint8_t> buf;
+ auto insert_fn = [&](const uint8_t b[], size_t l)
+ {
+ buf.insert(buf.end(), b, b + l);
+ };
+ this->read_file(input_file, insert_fn, buf_size);
+ return buf;
+ }
+
+std::string Command::slurp_file_as_str(const std::string& input_file,
+ size_t buf_size) const
+ {
+ std::string str;
+ auto insert_fn = [&](const uint8_t b[], size_t l)
+ {
+ str.append(reinterpret_cast<const char*>(b), l);
+ };
+ this->read_file(input_file, insert_fn, buf_size);
+ return str;
+ }
+
+void Command::read_file(const std::string& input_file,
+ std::function<void (uint8_t[], size_t)> consumer_fn,
+ size_t buf_size) const
+ {
+ if(input_file == "-")
+ {
+ do_read_file(std::cin, consumer_fn, buf_size);
+ }
+ else
+ {
+ std::ifstream in(input_file, std::ios::binary);
+ if(!in)
+ {
+ throw CLI_IO_Error("reading file", input_file);
+ }
+ do_read_file(in, consumer_fn, buf_size);
+ }
+ }
+
+void Command::do_read_file(std::istream& in,
+ std::function<void (uint8_t[], size_t)> consumer_fn,
+ size_t buf_size) const
+ {
+ // Avoid an infinite loop on --buf-size=0
+ std::vector<uint8_t> buf(buf_size == 0 ? 4096 : buf_size);
+
+ while(in.good())
+ {
+ in.read(reinterpret_cast<char*>(buf.data()), buf.size());
+ const size_t got = static_cast<size_t>(in.gcount());
+ consumer_fn(buf.data(), got);
+ }
+ }
+
+Botan::RandomNumberGenerator& Command::rng()
+ {
+ if(m_rng == nullptr)
+ {
+ m_rng = cli_make_rng(get_arg("rng-type"), get_arg("drbg-seed"));
+ }
+
+ return *m_rng.get();
+ }
+
+// Registration code
+
+Command::Registration::Registration(const std::string& name, Command::cmd_maker_fn maker_fn)
+ {
+ std::map<std::string, Command::cmd_maker_fn>& reg = Command::global_registry();
+
+ if(reg.count(name) > 0)
+ {
+ throw CLI_Error("Duplicated registration of command " + name);
+ }
+
+ reg.insert(std::make_pair(name, maker_fn));
+ }
+
+//static
+std::map<std::string, Command::cmd_maker_fn>& Command::global_registry()
+ {
+ static std::map<std::string, Command::cmd_maker_fn> g_cmds;
+ return g_cmds;
+ }
+
+//static
+std::vector<std::string> Command::registered_cmds()
+ {
+ std::vector<std::string> cmds;
+ for(auto& cmd : Command::global_registry())
+ cmds.push_back(cmd.first);
+ return cmds;
+ }
+
+//static
+std::unique_ptr<Command> Command::get_cmd(const std::string& name)
+ {
+ const std::map<std::string, Command::cmd_maker_fn>& reg = Command::global_registry();
+
+ std::unique_ptr<Command> r;
+ auto i = reg.find(name);
+ if(i != reg.end())
+ {
+ r.reset(i->second());
+ }
+
+ return r;
+ }
+
+}
diff --git a/src/cli/cli.h b/src/cli/cli.h
index 1e81c2ff6..75de33515 100644
--- a/src/cli/cli.h
+++ b/src/cli/cli.h
@@ -8,64 +8,39 @@
#define BOTAN_CLI_H_
#include <botan/build.h>
-#include <botan/parsing.h>
-#include <botan/rng.h>
-
-#include <fstream>
-#include <iostream>
#include <functional>
+#include <ostream>
#include <map>
#include <memory>
-#include <set>
#include <string>
#include <vector>
+#include "cli_exceptions.h"
+
+namespace Botan {
+
+class RandomNumberGenerator;
+
+}
namespace Botan_CLI {
-/* Declared in utils.cpp */
+class Argument_Parser;
+
+/* Declared in cli_rng.cpp */
std::unique_ptr<Botan::RandomNumberGenerator>
cli_make_rng(const std::string& type, const std::string& hex_drbg_seed);
-class CLI_Error : public std::runtime_error
- {
- public:
- explicit CLI_Error(const std::string& s) : std::runtime_error(s) {}
- };
-
-class CLI_IO_Error : public CLI_Error
+class Command
{
public:
- CLI_IO_Error(const std::string& op, const std::string& who) :
- CLI_Error("Error " + op + " " + who) {}
- };
-class CLI_Usage_Error : public CLI_Error
- {
- public:
- explicit CLI_Usage_Error(const std::string& what) : CLI_Error(what) {}
- };
+ /**
+ * Get a registered command
+ */
+ static std::unique_ptr<Command> get_cmd(const std::string& name);
-/* Thrown eg when a requested feature was compiled out of the library
- or is not available, eg hashing with
-*/
-class CLI_Error_Unsupported : public CLI_Error
- {
- public:
- CLI_Error_Unsupported(const std::string& what,
- const std::string& who)
- : CLI_Error(what + " with '" + who + "' unsupported or not available") {}
- };
+ static std::vector<std::string> registered_cmds();
-class CLI_Error_Invalid_Spec : public CLI_Error
- {
- public:
- explicit CLI_Error_Invalid_Spec(const std::string& spec)
- : CLI_Error("Invalid command spec '" + spec + "'") {}
- };
-
-class Command
- {
- public:
/**
* The spec string specifies the format of the command line, eg for
* a somewhat complicated command:
@@ -105,156 +80,13 @@ class Command
* Use of --help is captured in run() and returns help_text().
* Use of --verbose can be checked with verbose() or flag_set("verbose")
*/
- explicit Command(const std::string& cmd_spec) : m_spec(cmd_spec)
- {
- // for checking all spec strings at load time
- //parse_spec();
- }
- virtual ~Command() = default;
+ explicit Command(const std::string& cmd_spec);
- int run(const std::vector<std::string>& params)
- {
- try
- {
- // avoid parsing specs except for the command actually running
- parse_spec();
-
- std::vector<std::string> args;
- for(auto const& param : params)
- {
- if(param.find("--") == 0)
- {
- // option
- const auto eq = param.find('=');
-
- if(eq == std::string::npos)
- {
- const std::string opt_name = param.substr(2, std::string::npos);
-
- if(m_spec_flags.count(opt_name) == 0)
- {
- if(m_spec_opts.count(opt_name))
- {
- throw CLI_Usage_Error("Invalid usage of option --" + opt_name +
- " without value");
- }
- else
- {
- throw CLI_Usage_Error("Unknown flag --" + opt_name);
- }
- }
- m_user_flags.insert(opt_name);
- }
- else
- {
- const std::string opt_name = param.substr(2, eq - 2);
- const std::string opt_val = param.substr(eq + 1, std::string::npos);
-
- if(m_spec_opts.count(opt_name) == 0)
- {
- throw CLI_Usage_Error("Unknown option --" + opt_name);
- }
-
- m_user_args.insert(std::make_pair(opt_name, opt_val));
- }
- }
- else
- {
- // argument
- args.push_back(param);
- }
- }
-
- bool seen_stdin_flag = false;
- size_t arg_i = 0;
- for(auto const& arg : m_spec_args)
- {
- if(arg_i >= args.size())
- {
- // not enough arguments
- throw CLI_Usage_Error("Invalid argument count, got " +
- std::to_string(args.size()) +
- " expected " +
- std::to_string(m_spec_args.size()));
- }
-
- m_user_args.insert(std::make_pair(arg, args[arg_i]));
-
- if(args[arg_i] == "-")
- {
- if(seen_stdin_flag)
- {
- throw CLI_Usage_Error("Cannot specifiy '-' (stdin) more than once");
- }
- seen_stdin_flag = true;
- }
-
- ++arg_i;
- }
-
- if(m_spec_rest.empty())
- {
- if(arg_i != args.size())
- {
- throw CLI_Usage_Error("Too many arguments");
- }
- }
- else
- {
- m_user_rest.assign(args.begin() + arg_i, args.end());
- }
-
- if(flag_set("help"))
- {
- output() << help_text() << "\n";
- return 2;
- }
-
- if(m_user_args.count("output"))
- {
- m_output_stream.reset(new std::ofstream(get_arg("output"), std::ios::binary));
- }
-
- if(m_user_args.count("error-output"))
- {
- m_error_output_stream.reset(new std::ofstream(get_arg("error-output"), std::ios::binary));
- }
-
- // Now insert any defaults for options not supplied by the user
- for(auto const& opt : m_spec_opts)
- {
- if(m_user_args.count(opt.first) == 0)
- {
- m_user_args.insert(opt);
- }
- }
-
- this->go();
- return 0;
- }
- catch(CLI_Usage_Error& e)
- {
- error_output() << "Usage error: " << e.what() << "\n";
- error_output() << help_text() << "\n";
- return 1;
- }
- catch(std::exception& e)
- {
- error_output() << "Error: " << e.what() << "\n";
- return 2;
- }
- catch(...)
- {
- error_output() << "Error: unknown exception\n";
- return 2;
- }
- }
+ virtual ~Command();
- virtual std::string help_text() const
- {
- return "Usage: " + m_spec +
- "\n\nAll commands support --verbose --help --output= --error-output= --rng-type= --drbg-seed=";
- }
+ int run(const std::vector<std::string>& params);
+
+ virtual std::string help_text() const;
const std::string& cmd_spec() const
{
@@ -268,230 +100,53 @@ class Command
protected:
- void parse_spec()
- {
- const std::vector<std::string> parts = Botan::split_on(m_spec, ' ');
-
- if(parts.size() == 0)
- {
- throw CLI_Error_Invalid_Spec(m_spec);
- }
-
- for(size_t i = 1; i != parts.size(); ++i)
- {
- const std::string s = parts[i];
-
- if(s.empty()) // ?!? (shouldn't happen)
- {
- throw CLI_Error_Invalid_Spec(m_spec);
- }
-
- if(s.size() > 2 && s[0] == '-' && s[1] == '-')
- {
- // option or flag
-
- auto eq = s.find('=');
-
- if(eq == std::string::npos)
- {
- m_spec_flags.insert(s.substr(2, std::string::npos));
- }
- else
- {
- m_spec_opts.insert(std::make_pair(s.substr(2, eq - 2), s.substr(eq + 1, std::string::npos)));
- }
- }
- else if(s[0] == '*')
- {
- // rest argument
- if(m_spec_rest.empty() && s.size() > 2)
- {
- m_spec_rest = s.substr(1, std::string::npos);
- }
- else
- {
- throw CLI_Error_Invalid_Spec(m_spec);
- }
- }
- else
- {
- // named argument
- if(!m_spec_rest.empty()) // rest arg wasn't last
- {
- throw CLI_Error("Invalid command spec " + m_spec);
- }
-
- m_spec_args.push_back(s);
- }
- }
-
- m_spec_flags.insert("verbose");
- m_spec_flags.insert("help");
- m_spec_opts.insert(std::make_pair("output", ""));
- m_spec_opts.insert(std::make_pair("error-output", ""));
- m_spec_opts.insert(std::make_pair("rng-type", ""));
- m_spec_opts.insert(std::make_pair("drbg-seed", ""));
- }
-
/*
* The actual functionality of the cli command implemented in subclas
*/
virtual void go() = 0;
- std::ostream& output()
- {
- if(m_output_stream.get())
- {
- return *m_output_stream;
- }
- return std::cout;
- }
+ std::ostream& output();
- std::ostream& error_output()
- {
- if(m_error_output_stream.get())
- {
- return *m_error_output_stream;
- }
- return std::cerr;
- }
+ std::ostream& error_output();
bool verbose() const
{
return flag_set("verbose");
}
- bool flag_set(const std::string& flag_name) const
- {
- return m_user_flags.count(flag_name) > 0;
- }
+ bool flag_set(const std::string& flag_name) const;
- std::string get_arg(const std::string& opt_name) const
- {
- auto i = m_user_args.find(opt_name);
- if(i == m_user_args.end())
- {
- // this shouldn't occur unless you passed the wrong thing to get_arg
- throw CLI_Error("Unknown option " + opt_name + " used (program bug)");
- }
- return i->second;
- }
+ std::string get_arg(const std::string& opt_name) const;
/*
* Like get_arg() but if the argument was not specified or is empty, returns otherwise
*/
- std::string get_arg_or(const std::string& opt_name, const std::string& otherwise) const
- {
- auto i = m_user_args.find(opt_name);
- if(i == m_user_args.end() || i->second.empty())
- {
- return otherwise;
- }
- return i->second;
- }
+ std::string get_arg_or(const std::string& opt_name, const std::string& otherwise) const;
- size_t get_arg_sz(const std::string& opt_name) const
- {
- const std::string s = get_arg(opt_name);
-
- try
- {
- return static_cast<size_t>(std::stoul(s));
- }
- catch(std::exception&)
- {
- throw CLI_Usage_Error("Invalid integer value '" + s + "' for option " + opt_name);
- }
- }
+ size_t get_arg_sz(const std::string& opt_name) const;
- std::vector<std::string> get_arg_list(const std::string& what) const
- {
- if(what != m_spec_rest)
- {
- throw CLI_Error("Unexpected list name '" + what + "'");
- }
-
- return m_user_rest;
- }
+ std::vector<std::string> get_arg_list(const std::string& what) const;
/*
* Read an entire file into memory and return the contents
*/
std::vector<uint8_t> slurp_file(const std::string& input_file,
- size_t buf_size = 0) const
- {
- std::vector<uint8_t> buf;
- auto insert_fn = [&](const uint8_t b[], size_t l)
- {
- buf.insert(buf.end(), b, b + l);
- };
- this->read_file(input_file, insert_fn, buf_size);
- return buf;
- }
-
- /*
- * Read an entire file into memory and return the contents
- */
- Botan::secure_vector<uint8_t> slurp_file_locked(const std::string& input_file,
- size_t buf_size = 0) const
- {
- Botan::secure_vector<uint8_t> buf;
- auto insert_fn = [&](const uint8_t b[], size_t l)
- {
- buf.insert(buf.end(), b, b + l);
- };
- this->read_file(input_file, insert_fn, buf_size);
- return buf;
- }
+ size_t buf_size = 0) const;
std::string slurp_file_as_str(const std::string& input_file,
- size_t buf_size = 0) const
- {
- std::string str;
- auto insert_fn = [&](const uint8_t b[], size_t l)
- {
- str.append(reinterpret_cast<const char*>(b), l);
- };
- this->read_file(input_file, insert_fn, buf_size);
- return str;
- }
+ size_t buf_size = 0) const;
/*
* Read a file calling consumer_fn() with the inputs
*/
void read_file(const std::string& input_file,
std::function<void (uint8_t[], size_t)> consumer_fn,
- size_t buf_size = 0) const
- {
- if(input_file == "-")
- {
- do_read_file(std::cin, consumer_fn, buf_size);
- }
- else
- {
- std::ifstream in(input_file, std::ios::binary);
- if(!in)
- {
- throw CLI_IO_Error("reading file", input_file);
- }
- do_read_file(in, consumer_fn, buf_size);
- }
- }
+ size_t buf_size = 0) const;
+
void do_read_file(std::istream& in,
std::function<void (uint8_t[], size_t)> consumer_fn,
- size_t buf_size = 0) const
- {
- // Avoid an infinite loop on --buf-size=0
- std::vector<uint8_t> buf(buf_size == 0 ? 4096 : buf_size);
-
- while(in.good())
- {
- in.read(reinterpret_cast<char*>(buf.data()), buf.size());
- const size_t got = static_cast<size_t>(in.gcount());
- consumer_fn(buf.data(), got);
- }
- }
+ size_t buf_size = 0) const;
template<typename Alloc>
void write_output(const std::vector<uint8_t, Alloc>& vec)
@@ -499,75 +154,31 @@ class Command
output().write(reinterpret_cast<const char*>(vec.data()), vec.size());
}
- Botan::RandomNumberGenerator& rng()
- {
- if(m_rng == nullptr)
- {
- m_rng = cli_make_rng(get_arg("rng-type"), get_arg("drbg-seed"));
- }
-
- return *m_rng.get();
- }
+ Botan::RandomNumberGenerator& rng();
private:
- // set in constructor
- std::string m_spec;
+ void parse_spec();
- // set in parse_spec() from m_spec
- std::vector<std::string> m_spec_args;
- std::set<std::string> m_spec_flags;
- std::map<std::string, std::string> m_spec_opts;
- std::string m_spec_rest;
+ typedef std::function<Command* ()> cmd_maker_fn;
- // set in run() from user args
- std::map<std::string, std::string> m_user_args;
- std::set<std::string> m_user_flags;
- std::vector<std::string> m_user_rest;
+ static std::map<std::string, cmd_maker_fn>& global_registry();
- std::unique_ptr<std::ofstream> m_output_stream;
- std::unique_ptr<std::ofstream> m_error_output_stream;
+ // set in constructor
+ std::string m_spec;
+
+ std::unique_ptr<Argument_Parser> m_args;
+ std::unique_ptr<std::ostream> m_output_stream;
+ std::unique_ptr<std::ostream> m_error_output_stream;
std::unique_ptr<Botan::RandomNumberGenerator> m_rng;
public:
// the registry interface:
- typedef std::function<Command* ()> cmd_maker_fn;
-
- static std::map<std::string, cmd_maker_fn>& global_registry()
- {
- static std::map<std::string, cmd_maker_fn> g_cmds;
- return g_cmds;
- }
-
- static std::unique_ptr<Command> get_cmd(const std::string& name)
- {
- auto& reg = Command::global_registry();
-
- std::unique_ptr<Command> r;
- auto i = reg.find(name);
- if(i != reg.end())
- {
- r.reset(i->second());
- }
-
- return r;
- }
-
class Registration final
{
public:
- Registration(const std::string& name, cmd_maker_fn maker_fn)
- {
- auto& reg = Command::global_registry();
-
- if(reg.count(name) > 0)
- {
- throw CLI_Error("Duplicated registration of command " + name);
- }
-
- Command::global_registry().insert(std::make_pair(name, maker_fn));
- }
+ Registration(const std::string& name, cmd_maker_fn maker_fn);
};
};
diff --git a/src/cli/cli_exceptions.h b/src/cli/cli_exceptions.h
new file mode 100644
index 000000000..ed7be3137
--- /dev/null
+++ b/src/cli/cli_exceptions.h
@@ -0,0 +1,44 @@
+/*
+* (C) 2015 Jack Lloyd
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#ifndef BOTAN_CLI_EXCEPTIONS_H_
+#define BOTAN_CLI_EXCEPTIONS_H_
+
+namespace Botan_CLI {
+
+class CLI_Error : public std::runtime_error
+ {
+ public:
+ explicit CLI_Error(const std::string& s) : std::runtime_error(s) {}
+ };
+
+class CLI_IO_Error : public CLI_Error
+ {
+ public:
+ CLI_IO_Error(const std::string& op, const std::string& who) :
+ CLI_Error("Error " + op + " " + who) {}
+ };
+
+class CLI_Usage_Error : public CLI_Error
+ {
+ public:
+ explicit CLI_Usage_Error(const std::string& what) : CLI_Error(what) {}
+ };
+
+/* Thrown eg when a requested feature was compiled out of the library
+ or is not available, eg hashing with
+*/
+class CLI_Error_Unsupported : public CLI_Error
+ {
+ public:
+ CLI_Error_Unsupported(const std::string& what,
+ const std::string& who)
+ : CLI_Error(what + " with '" + who + "' unsupported or not available") {}
+ };
+
+}
+
+#endif
diff --git a/src/cli/cli_rng.cpp b/src/cli/cli_rng.cpp
new file mode 100644
index 000000000..1e3ecb141
--- /dev/null
+++ b/src/cli/cli_rng.cpp
@@ -0,0 +1,89 @@
+/*
+* (C) 2015,2017 Jack Lloyd
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#include "cli.h"
+#include <botan/rng.h>
+#include <botan/entropy_src.h>
+#include <botan/cpuid.h>
+#include <botan/hex.h>
+
+#if defined(BOTAN_HAS_AUTO_SEEDING_RNG)
+ #include <botan/auto_rng.h>
+#endif
+
+#if defined(BOTAN_HAS_SYSTEM_RNG)
+ #include <botan/system_rng.h>
+#endif
+
+#if defined(BOTAN_HAS_RDRAND_RNG)
+ #include <botan/rdrand_rng.h>
+#endif
+
+#if defined(BOTAN_HAS_HMAC_DRBG)
+ #include <botan/hmac_drbg.h>
+#endif
+
+namespace Botan_CLI {
+
+std::unique_ptr<Botan::RandomNumberGenerator>
+cli_make_rng(const std::string& rng_type, const std::string& hex_drbg_seed)
+ {
+#if defined(BOTAN_HAS_SYSTEM_RNG)
+ if(rng_type == "system" || rng_type.empty())
+ {
+ return std::unique_ptr<Botan::RandomNumberGenerator>(new Botan::System_RNG);
+ }
+#endif
+
+#if defined(BOTAN_HAS_RDRAND_RNG)
+ if(rng_type == "rdrand")
+ {
+ if(Botan::CPUID::has_rdrand())
+ return std::unique_ptr<Botan::RandomNumberGenerator>(new Botan::RDRAND_RNG);
+ else
+ throw CLI_Error("RDRAND instruction not supported on this processor");
+ }
+#endif
+
+ const std::vector<uint8_t> drbg_seed = Botan::hex_decode(hex_drbg_seed);
+
+#if defined(BOTAN_HAS_AUTO_SEEDING_RNG)
+ if(rng_type == "auto" || rng_type == "entropy" || rng_type.empty())
+ {
+ std::unique_ptr<Botan::RandomNumberGenerator> rng;
+
+ if(rng_type == "entropy")
+ rng.reset(new Botan::AutoSeeded_RNG(Botan::Entropy_Sources::global_sources()));
+ else
+ rng.reset(new Botan::AutoSeeded_RNG);
+
+ if(drbg_seed.size() > 0)
+ rng->add_entropy(drbg_seed.data(), drbg_seed.size());
+ return rng;
+ }
+#endif
+
+#if defined(BOTAN_HAS_HMAC_DRBG) && defined(BOTAN_AUTO_RNG_HMAC)
+ if(rng_type == "drbg")
+ {
+ std::unique_ptr<Botan::MessageAuthenticationCode> mac =
+ Botan::MessageAuthenticationCode::create(BOTAN_AUTO_RNG_HMAC);
+ std::unique_ptr<Botan::Stateful_RNG> rng(new Botan::HMAC_DRBG(std::move(mac)));
+ rng->add_entropy(drbg_seed.data(), drbg_seed.size());
+
+ if(rng->is_seeded() == false)
+ throw CLI_Error("For " + rng->name() + " a seed of at least " +
+ std::to_string(rng->security_level()/8) +
+ " bytes must be provided");
+
+ return std::unique_ptr<Botan::RandomNumberGenerator>(rng.release());
+ }
+#endif
+
+ throw CLI_Error_Unsupported("RNG", rng_type);
+ }
+
+}
diff --git a/src/cli/compress.cpp b/src/cli/compress.cpp
index c60a33921..e9cd02290 100644
--- a/src/cli/compress.cpp
+++ b/src/cli/compress.cpp
@@ -8,6 +8,7 @@
#if defined(BOTAN_HAS_COMPRESSION)
#include <botan/compression.h>
+ #include <fstream>
#endif
namespace Botan_CLI {
@@ -75,17 +76,17 @@ class Compress final : public Command
while(in.good())
{
buf.resize(buf_size);
- in.read(reinterpret_cast<char*>(&buf[0]), buf.size());
+ in.read(reinterpret_cast<char*>(buf.data()), buf.size());
buf.resize(in.gcount());
compress->update(buf);
- out.write(reinterpret_cast<const char*>(&buf[0]), buf.size());
+ out.write(reinterpret_cast<const char*>(buf.data()), buf.size());
}
buf.clear();
compress->finish(buf);
- out.write(reinterpret_cast<const char*>(&buf[0]), buf.size());
+ out.write(reinterpret_cast<const char*>(buf.data()), buf.size());
out.close();
}
};
@@ -147,17 +148,17 @@ class Decompress final : public Command
while(in.good())
{
buf.resize(buf_size);
- in.read(reinterpret_cast<char*>(&buf[0]), buf.size());
+ in.read(reinterpret_cast<char*>(buf.data()), buf.size());
buf.resize(in.gcount());
decompress->update(buf);
- out.write(reinterpret_cast<const char*>(&buf[0]), buf.size());
+ out.write(reinterpret_cast<const char*>(buf.data()), buf.size());
}
buf.clear();
decompress->finish(buf);
- out.write(reinterpret_cast<const char*>(&buf[0]), buf.size());
+ out.write(reinterpret_cast<const char*>(buf.data()), buf.size());
out.close();
}
};
diff --git a/src/cli/credentials.h b/src/cli/credentials.h
index 38f91141e..3b46c239c 100644
--- a/src/cli/credentials.h
+++ b/src/cli/credentials.h
@@ -11,7 +11,6 @@
#include <botan/credentials_manager.h>
#include <botan/x509self.h>
#include <botan/data_src.h>
-#include <fstream>
#include <memory>
inline bool value_exists(const std::vector<std::string>& vec,
diff --git a/src/cli/encryption.cpp b/src/cli/encryption.cpp
index e8d5622c2..c041f78ee 100644
--- a/src/cli/encryption.cpp
+++ b/src/cli/encryption.cpp
@@ -9,11 +9,9 @@
#if defined(BOTAN_HAS_AES) && defined(BOTAN_HAS_AEAD_MODES)
#include <botan/aead.h>
-#include <iterator>
+#include <botan/hex.h>
#include <sstream>
-using namespace Botan;
-
namespace Botan_CLI {
namespace {
@@ -32,41 +30,39 @@ auto VALID_MODES = std::map<std::string, std::string>{
{ "aes-256-xts", "AES-256/XTS" },
};
-bool is_aead(const std::string &cipher)
- {
- return cipher.find("/GCM") != std::string::npos
- || cipher.find("/OCB") != std::string::npos;
- }
-
-secure_vector<byte> do_crypt(const std::string &cipher,
- const secure_vector<byte> &input,
- const SymmetricKey &key,
- const InitializationVector &iv,
- const OctetString &ad,
- Cipher_Dir direction)
+Botan::secure_vector<uint8_t>
+do_crypt(const std::string &cipher,
+ const std::vector<uint8_t> &input,
+ const Botan::SymmetricKey &key,
+ const Botan::InitializationVector &iv,
+ const std::vector<uint8_t>& ad,
+ Botan::Cipher_Dir direction)
{
- if (iv.size() == 0) throw std::invalid_argument("IV must not be empty");
+ if(iv.size() == 0)
+ throw CLI_Usage_Error("IV must not be empty");
// TODO: implement streaming
- std::shared_ptr<Botan::Cipher_Mode> processor(Botan::get_cipher_mode(cipher, direction));
- if(!processor) throw std::runtime_error("Cipher algorithm not found");
+ std::unique_ptr<Botan::Cipher_Mode> processor(Botan::get_cipher_mode(cipher, direction));
+ if(!processor)
+ throw CLI_Error("Cipher algorithm not found");
// Set key
processor->set_key(key);
- // Set associated data
- if (is_aead(cipher))
+ if(Botan::AEAD_Mode* aead = dynamic_cast<Botan::AEAD_Mode*>(processor.get()))
+ {
+ aead->set_ad(ad);
+ }
+ else if(ad.size() != 0)
{
- auto aead_processor = std::dynamic_pointer_cast<AEAD_Mode>(processor);
- if(!aead_processor) throw std::runtime_error("Cipher algorithm not could not be converted to AEAD");
- aead_processor->set_ad(ad.bits_of());
+ throw CLI_Usage_Error("Cannot specify associated data with non-AEAD mode");
}
// Set IV
processor->start(iv.bits_of());
- secure_vector<byte> buf(input.begin(), input.end());
+ Botan::secure_vector<uint8_t> buf(input.begin(), input.end());
processor->finish(buf);
return buf;
@@ -97,20 +93,19 @@ class Encryption final : public Command
const std::string ad_hex = get_arg_or("ad", "");
const size_t buf_size = get_arg_sz("buf-size");
- Botan::secure_vector<uint8_t> input = this->slurp_file_locked("-", buf_size);
+ const std::vector<uint8_t> input = this->slurp_file("-", buf_size);
if (verbose())
{
error_output() << "Got " << input.size() << " bytes of input data.\n";
}
- auto key = SymmetricKey(key_hex);
- auto iv = InitializationVector(iv_hex);
- auto ad = OctetString(ad_hex);
+ const Botan::SymmetricKey key(key_hex);
+ const Botan::InitializationVector iv(iv_hex);
+ const std::vector<uint8_t> ad = Botan::hex_decode(ad_hex);
- auto direction = flag_set("decrypt") ? Cipher_Dir::DECRYPTION : Cipher_Dir::ENCRYPTION;
- auto data = do_crypt(VALID_MODES[mode], input, key, iv, ad, direction);
- std::copy(data.begin(), data.end(), std::ostreambuf_iterator<char>(output()));
+ auto direction = flag_set("decrypt") ? Botan::Cipher_Dir::DECRYPTION : Botan::Cipher_Dir::ENCRYPTION;
+ write_output(do_crypt(VALID_MODES[mode], input, key, iv, ad, direction));
}
};
diff --git a/src/cli/main.cpp b/src/cli/main.cpp
index 5fbaf085e..6b0024465 100644
--- a/src/cli/main.cpp
+++ b/src/cli/main.cpp
@@ -7,25 +7,21 @@
#include "cli.h"
#include <botan/version.h>
-#include <botan/internal/stl_util.h>
-#include <iterator>
#include <sstream>
+#include <iostream>
namespace {
std::string main_help()
{
- const std::set<std::string> avail_commands =
- Botan::map_keys_as_set(Botan_CLI::Command::global_registry());
-
std::ostringstream oss;
oss << "Usage: botan <cmd> <cmd-options>\n";
oss << "Available commands:\n";
- for(auto& cmd_name : avail_commands)
+ for(const auto& cmd_name : Botan_CLI::Command::registered_cmds())
{
- auto cmd = Botan_CLI::Command::get_cmd(cmd_name);
+ std::unique_ptr<Botan_CLI::Command> cmd = Botan_CLI::Command::get_cmd(cmd_name);
oss << cmd->cmd_spec() << "\n";
}
diff --git a/src/cli/speed.cpp b/src/cli/speed.cpp
index 15b955b3e..9539e22ec 100644
--- a/src/cli/speed.cpp
+++ b/src/cli/speed.cpp
@@ -23,6 +23,7 @@
#include <botan/mac.h>
#include <botan/cipher_mode.h>
#include <botan/entropy_src.h>
+#include <botan/parsing.h>
#include <botan/cpuid.h>
#include <botan/internal/os_utils.h>
#include <botan/version.h>
diff --git a/src/cli/timing_tests.cpp b/src/cli/timing_tests.cpp
index 258780845..d95df026c 100644
--- a/src/cli/timing_tests.cpp
+++ b/src/cli/timing_tests.cpp
@@ -20,6 +20,8 @@
#include "cli.h"
#include <botan/hex.h>
#include <sstream>
+#include <fstream>
+
#include <botan/internal/os_utils.h>
#if defined(BOTAN_HAS_SYSTEM_RNG)
diff --git a/src/cli/tls_client.cpp b/src/cli/tls_client.cpp
index d773cd81f..db0453a72 100644
--- a/src/cli/tls_client.cpp
+++ b/src/cli/tls_client.cpp
@@ -15,6 +15,8 @@
#include <botan/x509path.h>
#include <botan/ocsp.h>
#include <botan/hex.h>
+#include <botan/parsing.h>
+#include <fstream>
#if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER)
#include <botan/tls_session_manager_sqlite.h>
diff --git a/src/cli/tls_server.cpp b/src/cli/tls_server.cpp
index 703967641..02c15bf7b 100644
--- a/src/cli/tls_server.cpp
+++ b/src/cli/tls_server.cpp
@@ -17,6 +17,7 @@
#include "credentials.h"
#include <list>
+#include <fstream>
#if defined(BOTAN_TARGET_OS_IS_WINDOWS)
#include <winsock2.h>
diff --git a/src/cli/tls_utils.cpp b/src/cli/tls_utils.cpp
index f62781af9..7b2474a69 100644
--- a/src/cli/tls_utils.cpp
+++ b/src/cli/tls_utils.cpp
@@ -114,13 +114,8 @@ class TLS_Ciphersuites final : public Command
}
else
{
- std::ifstream policy_file(policy_type);
- if(!policy_file.good())
- {
- throw CLI_Error("Error TLS policy '" + policy_type + "' is neither a file nor a known policy type");
- }
-
- policy.reset(new Botan::TLS::Text_Policy(policy_file));
+ const std::string policy_txt = slurp_file_as_str(policy_type);
+ policy.reset(new Botan::TLS::Text_Policy(policy_txt));
}
for(uint16_t suite_id : policy->ciphersuite_list(version, with_srp))
diff --git a/src/cli/utils.cpp b/src/cli/utils.cpp
index 6b108eb43..1e2ddf42e 100644
--- a/src/cli/utils.cpp
+++ b/src/cli/utils.cpp
@@ -10,30 +10,15 @@
#include <botan/version.h>
#include <botan/hash.h>
#include <botan/mac.h>
+#include <botan/rng.h>
#include <botan/cpuid.h>
#include <botan/hex.h>
-#include <botan/entropy_src.h>
+#include <botan/parsing.h>
#if defined(BOTAN_HAS_BASE64_CODEC)
#include <botan/base64.h>
#endif
-#if defined(BOTAN_HAS_AUTO_SEEDING_RNG)
- #include <botan/auto_rng.h>
-#endif
-
-#if defined(BOTAN_HAS_SYSTEM_RNG)
- #include <botan/system_rng.h>
-#endif
-
-#if defined(BOTAN_HAS_RDRAND_RNG)
- #include <botan/rdrand_rng.h>
-#endif
-
-#if defined(BOTAN_HAS_HMAC_DRBG)
- #include <botan/hmac_drbg.h>
-#endif
-
#if defined(BOTAN_HAS_HTTP_UTIL)
#include <botan/http_util.h>
#endif
@@ -44,64 +29,6 @@
namespace Botan_CLI {
-std::unique_ptr<Botan::RandomNumberGenerator>
-cli_make_rng(const std::string& rng_type, const std::string& hex_drbg_seed)
- {
-#if defined(BOTAN_HAS_SYSTEM_RNG)
- if(rng_type == "system" || rng_type.empty())
- {
- return std::unique_ptr<Botan::RandomNumberGenerator>(new Botan::System_RNG);
- }
-#endif
-
-#if defined(BOTAN_HAS_RDRAND_RNG)
- if(rng_type == "rdrand")
- {
- if(Botan::CPUID::has_rdrand())
- return std::unique_ptr<Botan::RandomNumberGenerator>(new Botan::RDRAND_RNG);
- else
- throw CLI_Error("RDRAND instruction not supported on this processor");
- }
-#endif
-
- const std::vector<uint8_t> drbg_seed = Botan::hex_decode(hex_drbg_seed);
-
-#if defined(BOTAN_HAS_AUTO_SEEDING_RNG)
- if(rng_type == "auto" || rng_type == "entropy" || rng_type.empty())
- {
- std::unique_ptr<Botan::RandomNumberGenerator> rng;
-
- if(rng_type == "entropy")
- rng.reset(new Botan::AutoSeeded_RNG(Botan::Entropy_Sources::global_sources()));
- else
- rng.reset(new Botan::AutoSeeded_RNG);
-
- if(drbg_seed.size() > 0)
- rng->add_entropy(drbg_seed.data(), drbg_seed.size());
- return rng;
- }
-#endif
-
-#if defined(BOTAN_HAS_HMAC_DRBG) && defined(BOTAN_AUTO_RNG_HMAC)
- if(rng_type == "drbg")
- {
- std::unique_ptr<Botan::MessageAuthenticationCode> mac =
- Botan::MessageAuthenticationCode::create(BOTAN_AUTO_RNG_HMAC);
- std::unique_ptr<Botan::Stateful_RNG> rng(new Botan::HMAC_DRBG(std::move(mac)));
- rng->add_entropy(drbg_seed.data(), drbg_seed.size());
-
- if(rng->is_seeded() == false)
- throw CLI_Error("For " + rng->name() + " a seed of at least " +
- std::to_string(rng->security_level()/8) +
- " bytes must be provided");
-
- return std::unique_ptr<Botan::RandomNumberGenerator>(rng.release());
- }
-#endif
-
- throw CLI_Error_Unsupported("RNG", rng_type);
- }
-
class Config_Info final : public Command
{
public:
@@ -245,7 +172,7 @@ class RNG final : public Command
}
const std::string drbg_seed = get_arg("drbg-seed");
- std::unique_ptr<Botan::RNG> rng = cli_make_rng(type, drbg_seed);
+ std::unique_ptr<Botan::RandomNumberGenerator> rng = cli_make_rng(type, drbg_seed);
for(const std::string& req : get_arg_list("bytes"))
{
diff --git a/src/tests/main.cpp b/src/tests/main.cpp
index 41009ed19..3144b957d 100644
--- a/src/tests/main.cpp
+++ b/src/tests/main.cpp
@@ -4,445 +4,62 @@
* Botan is released under the Simplified BSD License (see license.txt)
*/
-#include "../cli/cli.h"
-#include "tests.h"
+#include "../cli/argparse.h"
+#include "test_runner.h"
#include <iostream>
-#include <sstream>
+#include <vector>
#include <string>
-#include <set>
-#include <deque>
#include <botan/version.h>
-#include <botan/loadstor.h>
-
-#if defined(BOTAN_HAS_HMAC_DRBG)
- #include <botan/hmac_drbg.h>
-#endif
-
-#if defined(BOTAN_HAS_SYSTEM_RNG)
- #include <botan/system_rng.h>
-#endif
-
-#if defined(BOTAN_HAS_AUTO_SEEDING_RNG)
- #include <botan/auto_rng.h>
-#endif
#if defined(BOTAN_HAS_OPENSSL)
#include <botan/internal/openssl.h>
#endif
-#if defined(BOTAN_TARGET_OS_HAS_THREADS)
- #include <thread>
- #include <future>
-#endif
-
-namespace {
-
-class Test_Runner final : public Botan_CLI::Command
+int main(int argc, char* argv[])
{
- public:
- Test_Runner()
- : Command("test --threads=0 --run-long-tests --run-online-tests --test-runs=1 --drbg-seed= --data-dir="
- " --pkcs11-lib= --provider= --log-success *suites") {}
-
- std::unique_ptr<Botan::RandomNumberGenerator>
- create_test_rng(const std::string& drbg_seed)
- {
- std::unique_ptr<Botan::RandomNumberGenerator> rng;
-
-#if defined(BOTAN_HAS_HMAC_DRBG) && defined(BOTAN_AUTO_RNG_HMAC)
-
- std::vector<uint8_t> seed = Botan::hex_decode(drbg_seed);
- if(seed.empty())
- {
- const uint64_t ts = Botan_Tests::Test::timestamp();
- seed.resize(8);
- Botan::store_be(ts, seed.data());
- }
-
- output() << " rng:HMAC_DRBG(" << BOTAN_AUTO_RNG_HMAC << ") with seed '" << Botan::hex_encode(seed) << "'\n";
-
- // Expand out the seed with a hash to make the DRBG happy
- std::unique_ptr<Botan::MessageAuthenticationCode> mac =
- Botan::MessageAuthenticationCode::create(BOTAN_AUTO_RNG_HMAC);
-
- mac->set_key(seed);
- seed.resize(mac->output_length());
- mac->final(seed.data());
-
- std::unique_ptr<Botan::HMAC_DRBG> drbg(new Botan::HMAC_DRBG(std::move(mac)));
- drbg->initialize_with(seed.data(), seed.size());
-
-#if defined(BOTAN_TARGET_OS_HAS_THREADS)
- rng.reset(new Botan::Serialized_RNG(drbg.release()));
-#else
- rng = std::move(drbg);
-#endif
-
-#endif
-
- if(!rng && drbg_seed != "")
- throw Botan_Tests::Test_Error("HMAC_DRBG disabled in build, cannot specify DRBG seed");
-
-#if defined(BOTAN_HAS_SYSTEM_RNG)
- if(!rng)
- {
- output() << " rng:system\n";
- rng.reset(new Botan::System_RNG);
- }
-#endif
-
-#if defined(BOTAN_HAS_AUTO_SEEDING_RNG)
- if(!rng)
- {
- output() << " rng:autoseeded\n";
-#if defined(BOTAN_TARGET_OS_HAS_THREADS)
- rng.reset(new Botan::Serialized_RNG(new Botan::AutoSeeded_RNG));
-#else
- rng.reset(new Botan::AutoSeeded_RNG);
-#endif
-
- }
-#endif
-
- if(!rng)
- {
- // last ditch fallback for RNG-less build
- class Bogus_Fallback_RNG final : public Botan::RandomNumberGenerator
- {
- public:
- std::string name() const override { return "Bogus_Fallback_RNG"; }
-
- void clear() override { /* ignored */ }
- void add_entropy(const uint8_t[], size_t) override { /* ignored */ }
- bool is_seeded() const override { return true; }
-
- void randomize(uint8_t out[], size_t len) override
- {
- for(size_t i = 0; i != len; ++i)
- {
- m_x = (m_x * 31337 + 42);
- out[i] = static_cast<uint8_t>(m_x >> 7);
- }
- }
-
- Bogus_Fallback_RNG() : m_x(1) {}
- private:
- uint32_t m_x;
- };
-
- output() << " rng:bogus\n";
- rng.reset(new Bogus_Fallback_RNG);
- }
-
- return rng;
- }
-
- std::string help_text() const override
- {
- std::ostringstream err;
-
- const std::string& spec = cmd_spec();
-
- err << "Usage: botan-test"
- << spec.substr(spec.find_first_of(' '), std::string::npos)
- << "\n\nAvailable test suites\n"
- << "----------------\n";
-
- size_t line_len = 0;
-
- for(auto const& test : Botan_Tests::Test::registered_tests())
- {
- err << test << " ";
- line_len += test.size() + 1;
-
- if(line_len > 64)
- {
- err << "\n";
- line_len = 0;
- }
- }
-
- if(line_len > 0)
- {
- err << "\n";
- }
-
- return err.str();
- }
-
- void go() override
- {
- const size_t threads = get_arg_sz("threads");
- const std::string drbg_seed = get_arg("drbg-seed");
- const bool log_success = flag_set("log-success");
- const bool run_online_tests = flag_set("run-online-tests");
- const bool run_long_tests = flag_set("run-long-tests");
- const std::string data_dir = get_arg_or("data-dir", "src/tests/data");
- const std::string pkcs11_lib = get_arg("pkcs11-lib");
- const std::string provider = get_arg("provider");
- const size_t runs = get_arg_sz("test-runs");
-
- std::vector<std::string> req = get_arg_list("suites");
-
- if(req.empty())
- {
- /*
- If nothing was requested on the command line, run everything. First
- run the "essentials" to smoke test, then everything else in
- alphabetical order.
- */
- req = {"block", "stream", "hash", "mac", "modes", "aead"
- "kdf", "pbkdf", "hmac_drbg", "x931_rng", "util"
- };
-
- std::set<std::string> all_others = Botan_Tests::Test::registered_tests();
-
- if(pkcs11_lib.empty())
- {
- // do not run pkcs11 tests by default unless pkcs11-lib set
- for(std::set<std::string>::iterator iter = all_others.begin(); iter != all_others.end();)
- {
- if((*iter).find("pkcs11") != std::string::npos)
- {
- iter = all_others.erase(iter);
- }
- else
- {
- ++iter;
- }
- }
- }
+ std::cerr << Botan::runtime_version_check(BOTAN_VERSION_MAJOR, BOTAN_VERSION_MINOR, BOTAN_VERSION_PATCH);
- for(auto f : req)
- {
- all_others.erase(f);
- }
+ try
+ {
+ Botan_CLI::Argument_Parser parser("test --data-dir= --pkcs11-lib= --provider= --log-success "
+ "--verbose --help --run-long-tests --run-online-tests --test-runs=1 --drbg-seed= "
+ "*suites");
- req.insert(req.end(), all_others.begin(), all_others.end());
- }
- else if(req.size() == 1 && req.at(0) == "pkcs11")
- {
- req = {"pkcs11-manage", "pkcs11-module", "pkcs11-slot", "pkcs11-session", "pkcs11-object", "pkcs11-rsa",
- "pkcs11-ecdsa", "pkcs11-ecdh", "pkcs11-rng", "pkcs11-x509"
- };
- }
- else
- {
- std::set<std::string> all = Botan_Tests::Test::registered_tests();
- for(auto const& r : req)
- {
- if(all.find(r) == all.end())
- {
- throw Botan_CLI::CLI_Usage_Error("Unknown test suite: " + r);
- }
- }
- }
+ parser.parse_args(std::vector<std::string>(argv + 1, argv + argc));
- output() << "Testing " << Botan::version_string() << "\n";
- output() << "Starting tests";
+ const std::string data_dir = parser.get_arg_or("data-dir", "src/tests/data");
+ const std::string pkcs11_lib = parser.get_arg("pkcs11-lib");
+ const std::string provider = parser.get_arg("provider");
+ const std::string drbg_seed = parser.get_arg("drbg-seed");
- if(threads > 1)
- {
- output() << " threads:" << threads;
- }
+ const bool log_success = parser.flag_set("log-success");
+ const bool run_long_tests = parser.flag_set("run-long-tests");
+ const bool run_online_tests = parser.flag_set("run-online-tests");
+ const size_t test_runs = parser.get_arg_sz("test-runs");
- if(!pkcs11_lib.empty())
- {
- output() << " pkcs11 library:" << pkcs11_lib;
- }
+ const std::vector<std::string> suites = parser.get_arg_list("suites");
- Botan_Tests::Provider_Filter pf;
- if(!provider.empty())
- {
- output() << " provider:" << provider;
- pf.set(provider);
- }
#if defined(BOTAN_HAS_OPENSSL)
- if(provider.empty() || provider == "openssl")
- {
- ERR_load_crypto_strings();
- }
-#endif
-
- std::unique_ptr<Botan::RandomNumberGenerator> rng = create_test_rng(drbg_seed);
-
- Botan_Tests::Test::setup_tests(log_success, run_online_tests, run_long_tests,
- data_dir, pkcs11_lib, pf, rng.get());
-
- for(size_t i = 0; i != runs; ++i)
- {
- const size_t failed = run_tests(req, output(), threads);
-
- // Throw so main returns an error
- if(failed)
- {
- throw Botan_Tests::Test_Error("Test suite failure");
- }
- }
- }
-
- private:
-
- std::string report_out(const std::vector<Botan_Tests::Test::Result>& results,
- size_t& tests_failed,
- size_t& tests_ran)
+ if(provider.empty() || provider == "openssl")
{
- std::ostringstream out;
-
- std::map<std::string, Botan_Tests::Test::Result> combined;
- for(auto const& result : results)
- {
- const std::string who = result.who();
- auto i = combined.find(who);
- if(i == combined.end())
- {
- combined.insert(std::make_pair(who, Botan_Tests::Test::Result(who)));
- i = combined.find(who);
- }
-
- i->second.merge(result);
- }
-
- for(auto const& result : combined)
- {
- out << result.second.result_string(verbose());
- tests_failed += result.second.tests_failed();
- tests_ran += result.second.tests_run();
- }
-
- return out.str();
+ ::ERR_load_crypto_strings();
}
-
-
- size_t run_tests(const std::vector<std::string>& tests_to_run,
- std::ostream& out,
- size_t threads)
- {
- size_t tests_ran = 0, tests_failed = 0;
-
- const uint64_t start_time = Botan_Tests::Test::timestamp();
-
- if(threads <= 1)
- {
- for(auto const& test_name : tests_to_run)
- {
- try
- {
- out << test_name << ':' << std::endl;
- const auto results = Botan_Tests::Test::run_test(test_name, false);
- out << report_out(results, tests_failed, tests_ran) << std::flush;
- }
- catch(std::exception& e)
- {
- out << "Test " << test_name << " failed with exception " << e.what() << std::flush;
- }
- }
- }
- else
- {
-
-#if defined(BOTAN_TARGET_OS_HAS_THREADS)
- /*
- We're not doing this in a particularly nice way, and variance in time is
- high so commonly we'll 'run dry' by blocking on the first future. But
- plain C++11 <thread> is missing a lot of tools we'd need (like
- wait_for_any on a set of futures) and there is no point pulling in an
- additional dependency just for this. In any case it helps somewhat
- (50-100% speedup) and provides a proof of concept for parallel testing.
- */
-
- typedef std::future<std::vector<Botan_Tests::Test::Result>> FutureResults;
- std::deque<FutureResults> fut_results;
-
- for(auto const& test_name : tests_to_run)
- {
- auto run_it = [test_name]() -> std::vector<Botan_Tests::Test::Result>
- {
- try
- {
- return Botan_Tests::Test::run_test(test_name, false);
- }
- catch(std::exception& e)
- {
- Botan_Tests::Test::Result r(test_name);
- r.test_failure("Exception thrown", e.what());
- return std::vector<Botan_Tests::Test::Result> {r};
- }
- };
-
- fut_results.push_back(std::async(std::launch::async, run_it));
-
- while(fut_results.size() > threads)
- {
- out << report_out(fut_results[0].get(), tests_failed, tests_ran) << std::flush;
- fut_results.pop_front();
- }
- }
-
- while(fut_results.size() > 0)
- {
- out << report_out(fut_results[0].get(), tests_failed, tests_ran) << std::flush;
- fut_results.pop_front();
- }
-#else
- out << "Threading support disabled\n";
- return 1;
#endif
- }
-
- const uint64_t total_ns = Botan_Tests::Test::timestamp() - start_time;
- out << "Tests complete ran " << tests_ran << " tests in "
- << Botan_Tests::Test::format_time(total_ns) << " ";
-
- if(tests_failed > 0)
- {
- out << tests_failed << " tests failed";
- }
- else if(tests_ran > 0)
- {
- out << "all tests ok";
- }
-
- out << std::endl;
-
- return tests_failed;
- }
-
-
- };
-
-BOTAN_REGISTER_COMMAND("test", Test_Runner);
-}
+ Botan_Tests::Test_Runner tests(std::cout);
-int main(int argc, char* argv[])
- {
- std::cerr << Botan::runtime_version_check(BOTAN_VERSION_MAJOR, BOTAN_VERSION_MINOR, BOTAN_VERSION_PATCH);
-
- try
- {
- std::unique_ptr<Botan_CLI::Command> cmd(Botan_CLI::Command::get_cmd("test"));
-
- if(!cmd)
- {
- std::cerr << "Unable to retrieve testing helper (program bug)\n"; // WTF
- return 1;
- }
-
- std::vector<std::string> args(argv + 1, argv + argc);
- return cmd->run(args);
- }
- catch(Botan::Exception& e)
- {
- std::cerr << "Exiting with library exception " << e.what() << std::endl;
+ return tests.run(suites, data_dir, pkcs11_lib, provider,
+ log_success, run_online_tests, run_long_tests,
+ drbg_seed, test_runs);
}
catch(std::exception& e)
{
- std::cerr << "Exiting with std exception " << e.what() << std::endl;
+ std::cerr << "Exiting with error: " << e.what() << std::endl;
}
catch(...)
{
std::cerr << "Exiting with unknown exception" << std::endl;
}
+ return 2;
}
diff --git a/src/tests/test_runner.cpp b/src/tests/test_runner.cpp
new file mode 100644
index 000000000..8d3fe7558
--- /dev/null
+++ b/src/tests/test_runner.cpp
@@ -0,0 +1,332 @@
+/*
+* (C) 2017 Jack Lloyd
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#include "test_runner.h"
+#include "tests.h"
+
+#include <botan/version.h>
+#include <botan/loadstor.h>
+#include <botan/mac.h>
+
+#if defined(BOTAN_HAS_HMAC_DRBG)
+ #include <botan/hmac_drbg.h>
+#endif
+
+#if defined(BOTAN_HAS_SYSTEM_RNG)
+ #include <botan/system_rng.h>
+#endif
+
+#if defined(BOTAN_HAS_AUTO_SEEDING_RNG)
+ #include <botan/auto_rng.h>
+#endif
+
+namespace Botan_Tests {
+
+Test_Runner::Test_Runner(std::ostream& out) : m_output(out) {}
+
+namespace {
+
+std::unique_ptr<Botan::RandomNumberGenerator>
+create_test_rng(const std::string& drbg_seed, std::ostream& output)
+ {
+ std::unique_ptr<Botan::RandomNumberGenerator> rng;
+
+#if defined(BOTAN_HAS_HMAC_DRBG) && defined(BOTAN_AUTO_RNG_HMAC)
+
+ std::vector<uint8_t> seed = Botan::hex_decode(drbg_seed);
+ if(seed.empty())
+ {
+ const uint64_t ts = Botan_Tests::Test::timestamp();
+ seed.resize(8);
+ Botan::store_be(ts, seed.data());
+ }
+
+ output << " rng:HMAC_DRBG(" << BOTAN_AUTO_RNG_HMAC << ") with seed '" << Botan::hex_encode(seed) << "'\n";
+
+ // Expand out the seed with a hash to make the DRBG happy
+ std::unique_ptr<Botan::MessageAuthenticationCode> mac =
+ Botan::MessageAuthenticationCode::create(BOTAN_AUTO_RNG_HMAC);
+
+ mac->set_key(seed);
+ seed.resize(mac->output_length());
+ mac->final(seed.data());
+
+ std::unique_ptr<Botan::HMAC_DRBG> drbg(new Botan::HMAC_DRBG(std::move(mac)));
+ drbg->initialize_with(seed.data(), seed.size());
+
+#if defined(BOTAN_TARGET_OS_HAS_THREADS)
+ rng.reset(new Botan::Serialized_RNG(drbg.release()));
+#else
+ rng = std::move(drbg);
+#endif
+
+#endif
+
+ if(!rng && drbg_seed != "")
+ throw Botan_Tests::Test_Error("HMAC_DRBG disabled in build, cannot specify DRBG seed");
+
+#if defined(BOTAN_HAS_SYSTEM_RNG)
+ if(!rng)
+ {
+ output << " rng:system\n";
+ rng.reset(new Botan::System_RNG);
+ }
+#endif
+
+#if defined(BOTAN_HAS_AUTO_SEEDING_RNG)
+ if(!rng)
+ {
+ output << " rng:autoseeded\n";
+#if defined(BOTAN_TARGET_OS_HAS_THREADS)
+ rng.reset(new Botan::Serialized_RNG(new Botan::AutoSeeded_RNG));
+#else
+ rng.reset(new Botan::AutoSeeded_RNG);
+#endif
+
+ }
+#endif
+
+ if(!rng)
+ {
+ // last ditch fallback for RNG-less build
+ class Bogus_Fallback_RNG final : public Botan::RandomNumberGenerator
+ {
+ public:
+ std::string name() const override { return "Bogus_Fallback_RNG"; }
+
+ void clear() override { /* ignored */ }
+ void add_entropy(const uint8_t[], size_t) override { /* ignored */ }
+ bool is_seeded() const override { return true; }
+
+ void randomize(uint8_t out[], size_t len) override
+ {
+ for(size_t i = 0; i != len; ++i)
+ {
+ m_x = (m_x * 31337 + 42);
+ out[i] = static_cast<uint8_t>(m_x >> 7);
+ }
+ }
+
+ Bogus_Fallback_RNG() : m_x(1) {}
+ private:
+ uint32_t m_x;
+ };
+
+ output << " rng:bogus\n";
+ rng.reset(new Bogus_Fallback_RNG);
+ }
+
+ return rng;
+ }
+
+}
+
+std::string Test_Runner::help_text() const
+ {
+ std::ostringstream err;
+
+ err << "Usage: botan-test "
+ << "--run-long-tests --run-online-tests --test-runs=1 --drbg-seed= --data-dir= --pkcs11-lib= --provider= --log-success"
+ << "\n\nAvailable test suites\n"
+ << "----------------\n";
+
+ size_t line_len = 0;
+
+ for(auto const& test : Botan_Tests::Test::registered_tests())
+ {
+ err << test << " ";
+ line_len += test.size() + 1;
+
+ if(line_len > 64)
+ {
+ err << "\n";
+ line_len = 0;
+ }
+ }
+
+ if(line_len > 0)
+ {
+ err << "\n";
+ }
+
+ return err.str();
+ }
+
+int Test_Runner::run(const std::vector<std::string>& requested_tests,
+ const std::string& data_dir,
+ const std::string& pkcs11_lib,
+ const std::string& provider,
+ bool log_success,
+ bool run_online_tests,
+ bool run_long_tests,
+ const std::string& drbg_seed,
+ size_t runs)
+ {
+ std::vector<std::string> req = requested_tests;
+
+ if(req.empty())
+ {
+ /*
+ If nothing was requested on the command line, run everything. First
+ run the "essentials" to smoke test, then everything else in
+ alphabetical order.
+ */
+ req = {"block", "stream", "hash", "mac", "modes", "aead"
+ "kdf", "pbkdf", "hmac_drbg", "util"
+ };
+
+ std::set<std::string> all_others = Botan_Tests::Test::registered_tests();
+
+ if(pkcs11_lib.empty())
+ {
+ // do not run pkcs11 tests by default unless pkcs11-lib set
+ for(std::set<std::string>::iterator iter = all_others.begin(); iter != all_others.end();)
+ {
+ if((*iter).find("pkcs11") != std::string::npos)
+ {
+ iter = all_others.erase(iter);
+ }
+ else
+ {
+ ++iter;
+ }
+ }
+ }
+
+ for(auto f : req)
+ {
+ all_others.erase(f);
+ }
+
+ req.insert(req.end(), all_others.begin(), all_others.end());
+ }
+ else if(req.size() == 1 && req.at(0) == "pkcs11")
+ {
+ req = {"pkcs11-manage", "pkcs11-module", "pkcs11-slot", "pkcs11-session", "pkcs11-object", "pkcs11-rsa",
+ "pkcs11-ecdsa", "pkcs11-ecdh", "pkcs11-rng", "pkcs11-x509"
+ };
+ }
+ else
+ {
+ std::set<std::string> all = Botan_Tests::Test::registered_tests();
+ for(auto const& r : req)
+ {
+ if(all.find(r) == all.end())
+ {
+ throw Botan_Tests::Test_Error("Unknown test suite: " + r);
+ }
+ }
+ }
+
+ output() << "Testing " << Botan::version_string() << "\n";
+ output() << "Starting tests";
+
+ if(!pkcs11_lib.empty())
+ {
+ output() << " pkcs11 library:" << pkcs11_lib;
+ }
+
+ Botan_Tests::Provider_Filter pf;
+ if(!provider.empty())
+ {
+ output() << " provider:" << provider;
+ pf.set(provider);
+ }
+
+ std::unique_ptr<Botan::RandomNumberGenerator> rng = create_test_rng(drbg_seed, output());
+
+ Botan_Tests::Test::setup_tests(log_success, run_online_tests, run_long_tests,
+ data_dir, pkcs11_lib, pf, rng.get());
+
+ for(size_t i = 0; i != runs; ++i)
+ {
+ const size_t failed = run_tests(req);
+
+ // Throw so main returns an error
+ if(failed)
+ {
+ return failed;
+ }
+ }
+
+ return 0;
+ }
+
+namespace {
+
+std::string report_out(const std::vector<Botan_Tests::Test::Result>& results,
+ size_t& tests_failed,
+ size_t& tests_ran)
+ {
+ std::ostringstream out;
+
+ std::map<std::string, Botan_Tests::Test::Result> combined;
+ for(auto const& result : results)
+ {
+ const std::string who = result.who();
+ auto i = combined.find(who);
+ if(i == combined.end())
+ {
+ combined.insert(std::make_pair(who, Botan_Tests::Test::Result(who)));
+ i = combined.find(who);
+ }
+
+ i->second.merge(result);
+ }
+
+ for(auto const& result : combined)
+ {
+ const bool verbose = false;
+ out << result.second.result_string(verbose);
+ tests_failed += result.second.tests_failed();
+ tests_ran += result.second.tests_run();
+ }
+
+ return out.str();
+ }
+
+}
+
+size_t Test_Runner::run_tests(const std::vector<std::string>& tests_to_run)
+ {
+ size_t tests_ran = 0, tests_failed = 0;
+
+ const uint64_t start_time = Botan_Tests::Test::timestamp();
+
+ for(auto const& test_name : tests_to_run)
+ {
+ try
+ {
+ output() << test_name << ':' << std::endl;
+ const auto results = Botan_Tests::Test::run_test(test_name, false);
+ output() << report_out(results, tests_failed, tests_ran) << std::flush;
+ }
+ catch(std::exception& e)
+ {
+ output() << "Test " << test_name << " failed with exception " << e.what() << std::flush;
+ }
+ }
+
+ const uint64_t total_ns = Botan_Tests::Test::timestamp() - start_time;
+ output() << "Tests complete ran " << tests_ran << " tests in "
+ << Botan_Tests::Test::format_time(total_ns) << " ";
+
+ if(tests_failed > 0)
+ {
+ output() << tests_failed << " tests failed";
+ }
+ else if(tests_ran > 0)
+ {
+ output() << "all tests ok";
+ }
+
+ output() << std::endl;
+
+ return tests_failed;
+ }
+
+}
+
diff --git a/src/tests/test_runner.h b/src/tests/test_runner.h
new file mode 100644
index 000000000..7d111920c
--- /dev/null
+++ b/src/tests/test_runner.h
@@ -0,0 +1,43 @@
+/*
+* (C) 2017 Jack Lloyd
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#ifndef BOTAN_TEST_RUNNER_H_
+#define BOTAN_TEST_RUNNER_H_
+
+#include <iosfwd>
+#include <string>
+#include <vector>
+
+namespace Botan_Tests {
+
+class Test_Runner final
+ {
+ public:
+ Test_Runner(std::ostream& out);
+
+ std::string help_text() const;
+
+ int run(const std::vector<std::string>& requested_tests,
+ const std::string& data_dir,
+ const std::string& pkcs11_lib,
+ const std::string& provider,
+ bool log_success,
+ bool run_online_tests,
+ bool run_long_tests,
+ const std::string& drbg_seed,
+ size_t runs);
+
+ private:
+ std::ostream& output() const { return m_output; }
+
+ size_t run_tests(const std::vector<std::string>& tests_to_run);
+
+ std::ostream& m_output;
+ };
+
+}
+
+#endif