diff --git a/exes/themis/inc/alarm_database.hpp b/exes/themis/inc/alarm_database.hpp index f8c885173b..e121002900 100644 --- a/exes/themis/inc/alarm_database.hpp +++ b/exes/themis/inc/alarm_database.hpp @@ -1,12 +1,19 @@ #pragma once +#include +#include +#include +#include #include #include #include -#include -#include +#include namespace tfc::themis { + +using enum tfc::snitch::level_e; +using enum tfc::snitch::api::active_e; + inline auto config_file_name_populate_dir() -> std::string { auto const file{ base::make_config_file_name(base::get_exe_name(), "db") }; std::filesystem::create_directories(file.parent_path()); @@ -29,10 +36,11 @@ CREATE TABLE IF NOT EXISTS Alarms( )"; db_ << R"( CREATE TABLE IF NOT EXISTS AlarmTranslation( - sha1sum TEXT PRIMARY KEY, + sha1sum TEXT, locale TEXT NOT NULL, short_msg TEXT NOT NULL, msg TEXT NOT NULL, + PRIMARY KEY(sha1sum, locale) ON CONFLICT REPLACE, FOREIGN KEY(sha1sum) REFERENCES Alarms(sha1sum) ); )"; @@ -72,11 +80,93 @@ CREATE TABLE IF NOT EXISTS AlarmVariables( * @param alarm_level The alarm level * @return The alarm ID */ - [[nodiscard]] auto register_alarm(std::string_view tfc_id, std::string_view msg, std::string_view short_msg, bool latching, std::uint32_t alarm_level) -> std::int64_t { - db_ << fmt::format("INSERT INTO Alarms(tfc_id, sha1sum, alarm_level, short_msg_en, msg_en, alarm_latching) VALUES('{}','{}',{},'{}','{}',{});", tfc_id, msg, alarm_level, short_msg, msg, latching ? 1 : 0); - return db_.last_insert_rowid(); + [[nodiscard]] auto register_alarm(std::string_view tfc_id, + std::string_view msg, + std::string_view short_msg, + bool latching, + tfc::snitch::level_e alarm_level) -> std::uint64_t { + std::string sha1_ascii = get_sha1(fmt::format("{}{}", msg, short_msg)); + db_ << fmt::format( + "INSERT INTO Alarms(tfc_id, sha1sum, alarm_level, short_msg_en, msg_en, alarm_latching) " + "VALUES('{}','{}',{},'{}','{}',{});", + tfc_id, sha1_ascii, static_cast(alarm_level), short_msg, msg, latching ? 1 : 0); + std::int64_t last_insert_rowid = db_.last_insert_rowid(); + if (last_insert_rowid < 0) { + throw std::runtime_error("Failed to insert alarm into database"); + } + return static_cast(last_insert_rowid); + } + + [[nodiscard]] auto list_alarms() -> std::vector { + std::unordered_map alarms; + std::string query = R"( +SELECT + alarm_id, + tfc_id, + Alarms.sha1sum, + alarm_level, + Alarms.short_msg_en, + Alarms.msg_en, + alarm_latching, + locale, + AlarmTranslation.short_msg, + AlarmTranslation.msg +FROM Alarms +LEFT JOIN AlarmTranslation +ON Alarms.sha1sum = AlarmTranslation.sha1sum; +)"; + // Accept the second table paramters as unique ptr's as the values can be null, and we want to preserve that + db_ << query >> [&](std::uint64_t alarm_id, std::string tfc_id, std::string sha1sum, std::uint8_t alarm_level, + std::string short_msg_en, std::string msg_en, bool alarm_latching, + std::unique_ptr locale, std::unique_ptr translated_short_msg, + std::unique_ptr translated_msg) { + auto iterator = alarms.find(alarm_id); + if (iterator == alarms.end()) { + alarms.insert({ alarm_id, tfc::snitch::api::alarm{ alarm_id, + tfc_id, + short_msg_en, + msg_en, + sha1sum, + static_cast(alarm_level), + alarm_latching, + {} } }); + } + if (locale != nullptr && translated_short_msg != nullptr && translated_msg != nullptr) { + alarms[alarm_id].translations.insert({ *locale, { *translated_short_msg, *translated_msg } }); + } + }; + std::vector alarm_list; + for (auto& [_, alarm] : alarms) { + alarm_list.push_back(alarm); + } + return alarm_list; + } + + auto add_alarm_translation(std::string_view sha1sum, + std::string_view locale, + std::string_view short_msg, + std::string_view msg) -> void { + db_ << fmt::format("INSERT INTO AlarmTranslation(sha1sum, locale, short_msg, msg) VALUES('{}','{}','{}','{}');", sha1sum, + locale, short_msg, msg); + } + + // Note. Use `echo -n "value" | sha1sum` to not hash the newline character and + // match the results from this function. + [[nodiscard]] static auto get_sha1(std::string_view msg) -> std::string { + // Calculate sha1 + std::array sha1_res; + SHA1(reinterpret_cast(msg.data()), msg.size(), sha1_res.data()); + + // Convert sha1 to ascii + std::string sha1_ascii; + sha1_ascii.reserve(SHA_DIGEST_LENGTH * 2); + for (size_t i = 0; i < SHA_DIGEST_LENGTH; i++) { + sha1_ascii += fmt::format("{:02x}", sha1_res[i]); + } + return sha1_ascii; } + private: sqlite::database db_; }; -} +} // namespace tfc::themis diff --git a/exes/themis/tests/CMakeLists.txt b/exes/themis/tests/CMakeLists.txt index c7e03bac7e..c4371f5685 100644 --- a/exes/themis/tests/CMakeLists.txt +++ b/exes/themis/tests/CMakeLists.txt @@ -4,13 +4,16 @@ find_path(BEXT_SML_INCLUDE_DIRS "boost/sml.hpp") add_executable(themis_database_test themis_database_test.cpp) find_package(unofficial-sqlite3 CONFIG REQUIRED) +find_package(OpenSSL CONFIG REQUIRED) find_path(SQLITE_MODERN_CPP_INCLUDE_DIRS "sqlite_modern_cpp.h") target_link_libraries(themis_database_test PRIVATE Boost::ut tfc::base + tfc::snitch unofficial::sqlite3::sqlite3 + OpenSSL::Crypto ) target_include_directories(themis_database_test diff --git a/exes/themis/tests/themis_database_test.cpp b/exes/themis/tests/themis_database_test.cpp index 993d145473..bc53c48e57 100644 --- a/exes/themis/tests/themis_database_test.cpp +++ b/exes/themis/tests/themis_database_test.cpp @@ -1,18 +1,64 @@ +#include +#include #include #include -#include -#include +#include namespace ut = boost::ut; using boost::ut::operator""_test; +using boost::ut::operator|; +using boost::ut::operator/; +using boost::ut::expect; +using tfc::themis::alarm_database; auto main(int argc, char** argv) -> int { tfc::base::init(argc, argv); - //"Database correctness check"_test = [] { - tfc::themis::alarm_database alarm_db(false); - auto insert_id = alarm_db.register_alarm("tfc_id", "msg", "short msg", false, 0); - std::cout << insert_id << std::endl; - //}; + "sha1sum calculations correctness"_test = + [](auto& test_data) { + std::string input = test_data.first; + std::string expected = test_data.second; + expect(alarm_database::get_sha1(input) == expected) + << fmt::format("{} != {}", alarm_database::get_sha1(input), expected); + } | + std::vector>{ { "msgshort msg", "654e9cca8eb076653201325a5cf07ed24ee71b52" }, + { "this is a test", "fa26be19de6bff93f70bc2308434e4a440bbad02" }, + { "another test", "afc8edc74ae9e7b8d290f945a6d613f1d264a2b2" } }; + + "Database correctness check"_test = [] { + tfc::themis::alarm_database alarm_db(true); + + // Inserted alarm matches fetched alarm + expect(alarm_db.list_alarms().size() == 0); + auto insert_id = alarm_db.register_alarm("tfc_id", "msg", "short msg", false, tfc::snitch::level_e::info); + auto alarms = alarm_db.list_alarms(); + expect(alarms.size() == 1); + expect(alarms.at(0).tfc_id == "tfc_id"); + expect(alarms.at(0).translations.size() == 0); + expect(alarms.at(0).sha1sum == alarm_database::get_sha1("msgshort msg")); + expect(alarms.at(0).lvl == tfc::snitch::level_e::info); + expect(alarms.at(0).alarm_id == insert_id); + + // Translations are attached to alarms + alarm_db.add_alarm_translation(alarm_db.list_alarms()[0].sha1sum, "es", "some spanish maybe", + "this is is also spanish believe me please"); + alarms = alarm_db.list_alarms(); + expect(alarms.size() == 1); + expect(alarms.at(0).translations.size() == 1); + auto iterator = alarms.at(0).translations.find("es"); + expect(iterator != alarms.at(0).translations.end()); + expect(iterator->second.description == "some spanish maybe"); + expect(iterator->second.details == "this is is also spanish believe me please"); + + alarm_db.add_alarm_translation(alarm_db.list_alarms()[0].sha1sum, "is", "Some icelandic really", + "This is really some icelandic"); + alarms = alarm_db.list_alarms(); + expect(alarms.size() == 1); + expect(alarms.at(0).translations.size() == 2); + iterator = alarms.at(0).translations.find("is"); + expect(iterator != alarms.at(0).translations.end()); + expect(iterator->second.description == "Some icelandic really"); + expect(iterator->second.details == "This is really some icelandic"); + }; } diff --git a/libs/snitch/inc/public/tfc/snitch/common.hpp b/libs/snitch/inc/public/tfc/snitch/common.hpp index 00dd069510..8e0d52d036 100644 --- a/libs/snitch/inc/public/tfc/snitch/common.hpp +++ b/libs/snitch/inc/public/tfc/snitch/common.hpp @@ -1,6 +1,8 @@ #pragma once #include +#include +#include namespace tfc::snitch { @@ -23,9 +25,11 @@ enum struct active_e : std::int32_t { }; struct alarm { + std::uint64_t alarm_id; + std::string tfc_id; std::string description; std::string details; - bool active{}; + std::string sha1sum; level_e lvl{ level_e::unknown }; bool latching{}; struct translation {