/* * (C) 2014,2015 Jack Lloyd * (C) 2015 Simon Warta (Kullo GmbH) * * Botan is released under the Simplified BSD License (see license.txt) */ #ifndef BOTAN_TESTS_H_ #define BOTAN_TESTS_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Botan { #if defined(BOTAN_HAS_BIGINT) class BigInt; #endif #if defined(BOTAN_HAS_EC_CURVE_GFP) class PointGFp; #endif } namespace Botan_Tests { #if defined(BOTAN_HAS_BIGINT) using Botan::BigInt; #endif class Test_Error final : public Botan::Exception { public: explicit Test_Error(const std::string& what) : Exception("Test error", what) {} }; class Provider_Filter final { public: void set(const std::string& provider) { m_provider = provider; } std::vector filter(const std::vector&) const; private: std::string m_provider; }; /* * A generic test which returns a set of results when run. * The tests may not all have the same type (for example test * "block" returns results for "AES-128" and "AES-256"). * * For most test cases you want Text_Based_Test derived below */ class Test { public: /* * Some number of test results, all associated with who() */ class Result final { public: explicit Result(const std::string& who) : m_who(who) {} size_t tests_passed() const { return m_tests_passed; } size_t tests_failed() const { return m_fail_log.size(); } size_t tests_run() const { return tests_passed() + tests_failed(); } bool any_results() const { return tests_run() > 0; } const std::string& who() const { return m_who; } std::string result_string(bool verbose) const; static Result Failure(const std::string& who, const std::string& what) { Result r(who); r.test_failure(what); return r; } static Result Note(const std::string& who, const std::string& what) { Result r(who); r.test_note(what); return r; } static Result OfExpectedFailure(bool expecting_failure, const Test::Result& result) { if(!expecting_failure) { return result; } if(result.tests_failed() == 0) { Result r = result; r.test_failure("Expected this test to fail, but it did not"); return r; } else { Result r(result.who()); r.test_note("Got expected failure"); return r; } } void merge(const Result& other); void test_note(const std::string& note, const char* extra = nullptr); template void test_note(const std::string& who, const std::vector& vec) { const std::string hex = Botan::hex_encode(vec); return test_note(who, hex.c_str()); } void note_missing(const std::string& thing); bool test_success(const std::string& note = ""); bool test_failure(const std::string& err); bool test_failure(const std::string& what, const std::string& error); void test_failure(const std::string& what, const uint8_t buf[], size_t buf_len); template void test_failure(const std::string& what, const std::vector& buf) { test_failure(what, buf.data(), buf.size()); } bool confirm(const std::string& what, bool expr) { return test_eq(what, expr, true); } template bool test_is_eq(const T& produced, const T& expected) { return test_is_eq("comparison", produced, expected); } template bool test_is_eq(const std::string& what, const T& produced, const T& expected) { std::ostringstream out; out << m_who << " " << what; if(produced == expected) { out << " produced expected result " << produced; return test_success(out.str()); } else { out << " produced unexpected result '" << produced << "' expected '" << expected << "'"; return test_failure(out.str()); } } bool test_eq(const std::string& what, const char* produced, const char* expected); bool test_is_nonempty(const std::string& what_is_it, const std::string& to_examine); bool test_eq(const std::string& what, const std::string& produced, const std::string& expected); bool test_eq(const std::string& what, bool produced, bool expected); bool test_eq(const std::string& what, size_t produced, size_t expected); bool test_eq_sz(const std::string& what, size_t produced, size_t expected); bool test_eq(const std::string& what, Botan::OctetString produced, Botan::OctetString expected); template bool test_int_eq(I1 x, I2 y, const char* what) { return test_eq(what, static_cast(x), static_cast(y)); } template bool test_int_eq(const std::string& what, I1 x, I2 y) { return test_eq(what.c_str(), static_cast(x), static_cast(y)); } bool test_lt(const std::string& what, size_t produced, size_t expected); bool test_lte(const std::string& what, size_t produced, size_t expected); bool test_gte(const std::string& what, size_t produced, size_t expected); template bool test_rc_ok(const std::string& func, T rc) { static_assert(std::is_integral::value, "Integer required."); if(rc != 0) { std::ostringstream err; err << m_who; err << " " << func; err << " unexpectedly failed with error code " << rc; return test_failure(err.str()); } return test_success(); } template bool test_rc_fail(const std::string& func, const std::string& why, T rc) { static_assert(std::is_integral::value, "Integer required."); if(rc == 0) { std::ostringstream err; err << m_who; err << " call to " << func << " unexpectedly succeeded"; err << " expecting failure because " << why; return test_failure(err.str()); } return test_success(); } bool test_rc(const std::string& func, int expected, int rc); bool test_ne(const std::string& what, size_t produced, size_t expected); bool test_ne(const std::string& what, const std::string& str1, const std::string& str2); #if defined(BOTAN_HAS_BIGINT) bool test_eq(const std::string& what, const BigInt& produced, const BigInt& expected); bool test_ne(const std::string& what, const BigInt& produced, const BigInt& expected); #endif #if defined(BOTAN_HAS_EC_CURVE_GFP) bool test_eq(const std::string& what, const Botan::PointGFp& a, const Botan::PointGFp& b); #endif bool test_eq(const char* producer, const std::string& what, const uint8_t produced[], size_t produced_len, const uint8_t expected[], size_t expected_len); bool test_ne(const std::string& what, const uint8_t produced[], size_t produced_len, const uint8_t expected[], size_t expected_len); template bool test_eq(const std::string& what, const std::vector& produced, const std::vector& expected) { return test_eq(nullptr, what, produced.data(), produced.size(), expected.data(), expected.size()); } template bool test_eq(const std::string& producer, const std::string& what, const std::vector& produced, const std::vector& expected) { return test_eq(producer.c_str(), what, produced.data(), produced.size(), expected.data(), expected.size()); } template bool test_eq(const std::string& what, const std::vector& produced, const char* expected_hex) { const std::vector expected = Botan::hex_decode(expected_hex); return test_eq(nullptr, what, produced.data(), produced.size(), expected.data(), expected.size()); } template bool test_ne(const std::string& what, const std::vector& produced, const std::vector& expected) { return test_ne(what, produced.data(), produced.size(), expected.data(), expected.size()); } bool test_throws(const std::string& what, std::function fn); bool test_throws(const std::string& what, const std::string& expected, std::function fn); void set_ns_consumed(uint64_t ns) { m_ns_taken = ns; } void start_timer(); void end_timer(); private: std::string m_who; uint64_t m_started = 0; uint64_t m_ns_taken = 0; size_t m_tests_passed = 0; std::vector m_fail_log; std::vector m_log; }; class Registration final { public: Registration(const std::string& name, Test* test); }; virtual ~Test() = default; virtual std::vector run() = 0; virtual std::vector possible_providers(const std::string&); static std::map>& global_registry(); static std::set registered_tests(); static Test* get_test(const std::string& test_name); static std::string data_file(const std::string& what); static std::string format_time(uint64_t nanoseconds); template static std::vector mutate_vec(const std::vector& v, bool maybe_resize = false, size_t min_offset = 0) { auto& rng = Test::rng(); std::vector r = v; if(maybe_resize && (r.empty() || rng.next_byte() < 32)) { // TODO: occasionally truncate, insert at random index const size_t add = 1 + (rng.next_byte() % 16); r.resize(r.size() + add); rng.randomize(&r[r.size() - add], add); } if(r.size() > min_offset) { const size_t offset = std::max(min_offset, rng.next_byte() % r.size()); const uint8_t perturb = rng.next_nonzero_byte(); r[offset] ^= perturb; } return r; } static void set_test_options(bool log_success, bool run_online_tests, bool run_long_tests, const std::string& data_dir, const std::string& pkcs11_lib, const Botan_Tests::Provider_Filter& pf); static void set_test_rng(std::unique_ptr rng); static bool log_success(); static bool run_online_tests(); static bool run_long_tests(); static std::string pkcs11_lib(); static std::vector provider_filter(const std::vector&); static const std::string& data_dir(); static std::string read_data_file(const std::string& path); static std::vector read_binary_data_file(const std::string& path); static Botan::RandomNumberGenerator& rng(); static std::string random_password(); static uint64_t timestamp(); // nanoseconds arbitrary epoch private: static std::string m_data_dir; static std::unique_ptr m_test_rng; static bool m_log_success, m_run_online_tests, m_run_long_tests; static std::string m_pkcs11_lib; static Botan_Tests::Provider_Filter m_provider_filter; }; /* * Register the test with the runner */ #define BOTAN_REGISTER_TEST(type, Test_Class) \ Test::Registration reg_ ## Test_Class ## _tests(type, new Test_Class) /* * A test based on reading an input file which contains key/value pairs * Special note: the last value in required_key (there must be at least * one), is the output key. This triggers the callback. * * Calls run_one_test with the variables set. If an ini-style [header] * is used in the file, then header will be set to that value. This allows * splitting up tests between [valid] and [invalid] tests, or different * related algorithms tested in the same file. Use the protected get_XXX * functions to retrieve formatted values from the VarMap * * If most of your tests are text-based but you find yourself with a few * odds-and-ends tests that you want to do, override run_final_tests which * can test whatever it likes and returns a vector of Results. */ class Text_Based_Test : public Test { public: Text_Based_Test(const std::string& input_file, const std::string& required_keys, const std::string& optional_keys = ""); virtual bool clear_between_callbacks() const { return true; } std::vector run() override; protected: typedef std::unordered_map VarMap; std::string get_next_line(); virtual Test::Result run_one_test(const std::string& header, const VarMap& vars) = 0; // Called before run_one_test virtual bool skip_this_test(const std::string& header, const VarMap& vars); virtual std::vector run_final_tests() { return std::vector(); } bool get_req_bool(const VarMap& vars, const std::string& key) const; std::vector get_req_bin(const VarMap& vars, const std::string& key) const; std::vector get_opt_bin(const VarMap& vars, const std::string& key) const; #if defined(BOTAN_HAS_BIGINT) Botan::BigInt get_req_bn(const VarMap& vars, const std::string& key) const; Botan::BigInt get_opt_bn(const VarMap& vars, const std::string& key, const Botan::BigInt& def_value) const; #endif std::string get_req_str(const VarMap& vars, const std::string& key) const; std::string get_opt_str(const VarMap& vars, const std::string& key, const std::string& def_value) const; size_t get_req_sz(const VarMap& vars, const std::string& key) const; size_t get_opt_sz(const VarMap& vars, const std::string& key, const size_t def_value) const; private: std::string m_data_src; std::set m_required_keys; std::set m_optional_keys; std::string m_output_key; bool m_first = true; std::unique_ptr m_cur; std::string m_cur_src_name; std::deque m_srcs; std::vector m_cpu_flags; }; } #endif