diff --git a/README.md b/README.md index b3ec9ff..f3590fd 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Usage: the_dude_to_human.exe [options] -o, --out Save json database file -c, --credentials Save credentials in plain text -m, --mikrotik user:password@address:port Connect to the specified mikrotik device +-i, --integrity Validate database health -h, --help Display this help and exit -v, --version Print tool version diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index 25900b7..bc01609 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -43,6 +44,16 @@ std::string StringFromBuffer(std::span data) { return std::string(data.begin(), std::find(data.begin(), data.end(), '\0')); } +std::string HexStringFromBuffer(std::span data) { + std::stringstream ss; + ss << std::hex << std::uppercase; + + for (uint8_t byte : data) { + ss << std::setw(2) << std::setfill('0') << static_cast(byte); + } + return ss.str(); +} + // Turns " hej " into "hej". Also handles tabs. std::string StripSpaces(const std::string& str) { const std::size_t s = str.find_first_not_of(" \t\r\n"); diff --git a/src/common/string_util.h b/src/common/string_util.h index 86ce9d1..93cb245 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -21,6 +21,7 @@ namespace Common { [[nodiscard]] std::string StringFromBuffer(std::span data); [[nodiscard]] std::string StringFromBuffer(std::span data); +[[nodiscard]] std::string HexStringFromBuffer(std::span data); [[nodiscard]] std::string StripSpaces(const std::string& s); [[nodiscard]] std::string StripQuotes(const std::string& s); diff --git a/src/the_dude_to_human/TheDudeToHuman.cpp b/src/the_dude_to_human/TheDudeToHuman.cpp index ec8a553..57a746b 100644 --- a/src/the_dude_to_human/TheDudeToHuman.cpp +++ b/src/the_dude_to_human/TheDudeToHuman.cpp @@ -35,6 +35,7 @@ static void PrintHelp(const char* argv0) { "-c, --credentials Save credentials in plain text\n" "-m, --mikrotik user:password@address:port Connect to the specified mikrotik device\n" //"-d, --database=user:password@address:port Connect to the specified database\n" + "-i, --integrity Validate database health\n" "-h, --help Display this help and exit\n" "-v, --version Print tool version\n"; // clang-format on @@ -119,12 +120,15 @@ int main(int argc, char** argv) { std::string database_address{}; [[maybe_unused]] u16 database_port = {3306}; + bool check_integrity{}; + static struct option long_options[] = { // clang-format off {"file", required_argument, 0, 'f'}, {"out", required_argument, 0, 'o'}, {"credentials", no_argument, 0, 'c'}, {"mikrotik", required_argument, 0, 'm'}, + {"integrity", no_argument, 0, 'i'}, //{"database", optional_argument, 0, 'd'}, {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'v'}, @@ -133,7 +137,7 @@ int main(int argc, char** argv) { }; while (optind < argc) { - int arg = getopt_long(argc, argv, "f:o:cm:hv", long_options, &option_index); + int arg = getopt_long(argc, argv, "f:o:cm:ihv", long_options, &option_index); if (arg != -1) { switch (static_cast(arg)) { case 'f': { @@ -151,6 +155,9 @@ int main(int argc, char** argv) { case 'c': has_credentials = true; break; + case 'i': + check_integrity = true; + break; case 'h': PrintHelp(argv[0]); return 0; @@ -253,8 +260,13 @@ int main(int argc, char** argv) { if (has_filepath) { std::cout << "Reading database " << filepath << "\n"; Database::DudeDatabase db{filepath}; - db.ListMapData(); - db.ListDeviceData(); + + if (check_integrity) { + db.CheckIntegrity(); + } else { + db.ListMapData(); + db.ListDeviceData(); + } if (has_out_filepath) { std::cout << "Saving database " << out_filepath << "\n"; diff --git a/src/the_dude_to_human/database/dude_database.cpp b/src/the_dude_to_human/database/dude_database.cpp index 047dbf2..806a871 100644 --- a/src/the_dude_to_human/database/dude_database.cpp +++ b/src/the_dude_to_human/database/dude_database.cpp @@ -8,6 +8,7 @@ #include "the_dude_to_human/database/dude_database.h" #include "the_dude_to_human/database/dude_field_parser.h" #include "the_dude_to_human/database/dude_json.h" +#include "the_dude_to_human/database/dude_validator.h" namespace Database { DudeDatabase::DudeDatabase(const std::string& db_file) : db{db_file} { @@ -51,6 +52,10 @@ int DudeDatabase::SaveDatabase(const std::string& db_file, bool has_credentials) return SerializeDatabaseJson(this, db_file, has_credentials); } +int DudeDatabase::CheckIntegrity() { + return ValidateDatabase(this); +} + std::vector DudeDatabase::ListUsedDataFormats() const { std::vector data_formats{}; Sqlite::SqlData sql_data{}; @@ -66,7 +71,7 @@ std::vector DudeDatabase::ListUsedDataFormats() const { continue; } - printf("New Format 0x%02x in row %d \n", data_format, id); + // printf("New Format 0x%02x in row %d \n", data_format, id); data_formats.push_back(format); } } @@ -109,8 +114,14 @@ std::vector DudeDatabase::GetObjectData(DataFormat format, const T obj_data = (this->*RawToObjData)(parser); - if (id != (u32)obj_data.object_id.value) { - printf("Corrupted Entry %d\n", id); + if (!parser.IsDataValid()) { + printf("Corrupted Entry id %d: %s\n\t'%s'\n", id, parser.GetErrorMessage().c_str(), + Common::HexStringFromBuffer(blob).c_str()); + continue; + } else if (id != (u32)obj_data.object_id.value) { + printf("Invalid Entry expected %d, found %d\n", id, obj_data.object_id.value); + parser.PrintFieldInfo(); + printf("\n"); continue; } diff --git a/src/the_dude_to_human/database/dude_database.h b/src/the_dude_to_human/database/dude_database.h index c2f88c8..b49e702 100644 --- a/src/the_dude_to_human/database/dude_database.h +++ b/src/the_dude_to_human/database/dude_database.h @@ -28,6 +28,7 @@ class DudeDatabase { int GetOutages(Sqlite::SqlData& data) const; int SaveDatabase(const std::string& db_file, bool has_credentials); + int CheckIntegrity(); // Usefull to find new unsuported types std::vector ListUsedDataFormats() const; diff --git a/src/the_dude_to_human/database/dude_field_parser.cpp b/src/the_dude_to_human/database/dude_field_parser.cpp index 246eac4..91c4902 100644 --- a/src/the_dude_to_human/database/dude_field_parser.cpp +++ b/src/the_dude_to_human/database/dude_field_parser.cpp @@ -11,27 +11,28 @@ DudeFieldParser::DudeFieldParser(std::span data) : raw_data{data} { } void DudeFieldParser::Reset() { - is_data_valid = true; + status = ParserResult::Success; + error_count = 0; offset = 0; if (ReadData(&magic, sizeof(u16)) != ParserResult::Success) { - is_data_valid = false; + status = ParserResult::InvalidMagic; return; } if (ReadField(data_format, FieldId::DataFormat) != ParserResult::Success) { - is_data_valid = false; + status = ParserResult::InvalidHeader; return; } if (data_format.entries == 0) { - is_data_valid = false; + status = ParserResult::InvalidHeader; return; } } u16 DudeFieldParser::GetMagic() const { - if (!is_data_valid) { + if (!IsDataValid()) { return {}; } @@ -39,7 +40,7 @@ u16 DudeFieldParser::GetMagic() const { } IntArrayField DudeFieldParser::GetFormat() const { - if (!is_data_valid) { + if (!IsDataValid()) { return {}; } @@ -47,7 +48,7 @@ IntArrayField DudeFieldParser::GetFormat() const { } DataFormat DudeFieldParser::GetMainFormat() const { - if (!is_data_valid) { + if (!IsDataValid()) { return {}; } @@ -69,7 +70,7 @@ ParserResult DudeFieldParser::SkipField() { ParserResult result = GetFieldInfo(info); if (result != ParserResult::Success) { - return result; + return ReturnWithError(result); } BoolField bool_field; @@ -103,7 +104,7 @@ ParserResult DudeFieldParser::SkipField() { case FieldType::StringArray: return ReadField(string_array_field, FieldId::None); default: - return ParserResult::InvalidFieldType; + return ReturnWithError(ParserResult::InvalidFieldType); } } @@ -112,13 +113,13 @@ ParserResult DudeFieldParser::ReadData(void* data, std::size_t size) { return ParserResult::Success; } if (data == nullptr) { - return ParserResult::InvalidFieldArguments; + return ReturnWithError(ParserResult::InvalidFieldArguments); } - if (!is_data_valid) { + if (!IsDataValid()) { return ParserResult::Corrupted; } if (raw_data.size() < size + offset) { - return ParserResult::EndOfFile; + return ReturnWithError(ParserResult::EndOfFile); } std::memcpy(data, raw_data.data() + offset, size); @@ -142,11 +143,11 @@ ParserResult DudeFieldParser::ValidataFieldInfo(const FieldInfo& field_info, Fie case FieldType::StringArray: break; default: - return ParserResult::InvalidFieldType; + return ReturnWithError(ParserResult::InvalidFieldType); } // Allow all id if none is specified - if (field_info.id.Value() == FieldId::None) { + if (id == FieldId::None) { return ParserResult::Success; } @@ -455,4 +456,131 @@ void DudeFieldParser::RestoreOffset() { offset = previous_offset; } +ParserResult DudeFieldParser::ReturnWithError(ParserResult result) { + if (result == ParserResult::Success) { + return ParserResult::Success; + } + + error_count++; + status = result; + return result; +} + +bool DudeFieldParser::IsDataValid() const { + return status == ParserResult::Success; +} + +ParserResult DudeFieldParser::GetStatus() const { + return status; +} + +std::string DudeFieldParser::GetErrorMessage() const { + return GetErrorMessage(status); +} + +std::string DudeFieldParser::GetErrorMessage(ParserResult result) { + switch (result) { + case ParserResult::Success: + return "OK"; + case ParserResult::Corrupted: + return "The field can't be fully parsed"; + case ParserResult::FieldTypeMismatch: + return "Requested field type mismatch"; + case ParserResult::FieldIdMismatch: + return "Requested field id mismatch"; + case ParserResult::InvalidFieldType: + return "Unsupported field type"; + case ParserResult::InvalidFieldArguments: + return "Arguments given are invalid"; + case ParserResult::InvalidMagic: + return "Magic bytes can't be read"; + case ParserResult::InvalidHeader: + return "Header is invalid"; + case ParserResult::EndOfFile: + return "Reached end of file while parsing data"; + default: + return "Unexpected error"; + } +} + +void DudeFieldParser::PrintFieldInfo() { + printf("Field data: "); + Reset(); + if (!IsDataValid()) { + printf(" Error: %s\n", GetErrorMessage().c_str()); + return; + } + printf(" Format 0x%x\n", static_cast(GetMainFormat())); + + while (offset < raw_data.size()) { + FieldInfo info{}; + ParserResult result = GetFieldInfo(info); + + if (result != ParserResult::Success) { + printf("\tError: %s\n", GetErrorMessage(result).c_str()); + return; + } + + std::string field_json = ""; + BoolField bool_field; + IntField int_field; + LongField long_field; + LongLongField long_long_field; + TextField text_field; + LongArrayField long_array_field; + IntArrayField int_array_field; + StringArrayField string_array_field; + + RestoreOffset(); + switch (info.type) { + case FieldType::BoolFalse: + case FieldType::BoolTrue: + ReadField(bool_field, info.id); + field_json = bool_field.SerializeJson(); + break; + case FieldType::Int: + case FieldType::Byte: + ReadField(int_field, info.id); + field_json = int_field.SerializeJson(); + break; + case FieldType::Long: + ReadField(long_field, info.id); + field_json = long_field.SerializeJson(); + break; + case FieldType::LongLong: + ReadField(long_long_field, info.id); + field_json = long_long_field.SerializeJson(); + break; + case FieldType::LongString: + case FieldType::ShortString: + ReadField(text_field, info.id); + field_json = text_field.SerializeJson(); + break; + case FieldType::LongArray: + ReadField(long_array_field, info.id); + field_json = long_array_field.SerializeJson(); + break; + case FieldType::IntArray: + ReadField(int_array_field, info.id); + field_json = int_array_field.SerializeJson(); + break; + case FieldType::StringArray: + ReadField(string_array_field, info.id); + field_json = string_array_field.SerializeJson(); + break; + default: + printf("\tError: %s\n", GetErrorMessage(ParserResult::InvalidFieldType).c_str()); + return; + } + + if (!IsDataValid()) { + printf("\tError: %s\n", GetErrorMessage().c_str()); + return; + } + + printf("\tCategory 0x%x, 0x%x: %s\n", static_cast(info.id.Value()), + static_cast(info.type.Value()), field_json.c_str()); + } +} + } // namespace Database diff --git a/src/the_dude_to_human/database/dude_field_parser.h b/src/the_dude_to_human/database/dude_field_parser.h index c4b53a6..5002ec5 100644 --- a/src/the_dude_to_human/database/dude_field_parser.h +++ b/src/the_dude_to_human/database/dude_field_parser.h @@ -9,13 +9,15 @@ #include "the_dude_to_human/database/dude_types.h" namespace Database { -enum class ParserResult { +enum class ParserResult : u32 { Success, Corrupted, FieldTypeMismatch, FieldIdMismatch, InvalidFieldType, InvalidFieldArguments, + InvalidMagic, + InvalidHeader, EndOfFile, }; @@ -23,6 +25,13 @@ class DudeFieldParser { public: DudeFieldParser(std::span raw_data); + bool IsDataValid() const; + ParserResult GetStatus() const; + std::string GetErrorMessage() const; + static std::string GetErrorMessage(ParserResult result); + + void PrintFieldInfo(); + u16 GetMagic() const; IntArrayField GetFormat() const; DataFormat GetMainFormat() const; @@ -59,8 +68,11 @@ class DudeFieldParser { ParserResult ReadFieldInfo(FieldInfo& field_info, FieldId id = FieldId::None); ParserResult ValidataFieldInfo(const FieldInfo& field_info, FieldId id = FieldId::None); - bool is_data_valid{}; + ParserResult ReturnWithError(ParserResult result); + u16 magic{}; + u32 error_count{}; + ParserResult status{ParserResult::Success}; IntArrayField data_format{}; std::size_t offset; diff --git a/src/the_dude_to_human/database/dude_types.h b/src/the_dude_to_human/database/dude_types.h index 23a0b1e..ab56d0c 100644 --- a/src/the_dude_to_human/database/dude_types.h +++ b/src/the_dude_to_human/database/dude_types.h @@ -103,6 +103,14 @@ struct IntField { std::string SerializeJson() const { return fmt::format("{}", value); } + + bool operator==(s32 rhs) const { + return value == rhs; + } + + bool operator==(IntField rhs) const { + return value == rhs.value; + } }; // This is FieldType::Int @@ -150,12 +158,12 @@ struct TextField { struct IntArrayField { FieldInfo info{}; u16 entries{}; - std::vector data{}; + std::vector data{}; std::string SerializeJson() const { std::string array = ""; - for (u32 entry : data) { + for (s32 entry : data) { array += fmt::format("{},", entry); } if (!data.empty()) { @@ -164,15 +172,19 @@ struct IntArrayField { return fmt::format("[{}]", array); } + + s32 operator[](std::size_t index) const { + return data[index]; + } }; struct IpArrayField : IntArrayField { std::string SerializeJson() const { std::string array = ""; - for (u32 entry : data) { + for (s32 entry : data) { IpAddress ip{}; - memcpy(&ip, &entry, sizeof(u32)); + memcpy(&ip, &entry, sizeof(s32)); array += fmt::format("\"{}.{}.{}.{}\",", ip[0], ip[1], ip[2], ip[3]); } if (!data.empty()) { diff --git a/src/the_dude_to_human/database/dude_validator.cpp b/src/the_dude_to_human/database/dude_validator.cpp index 1555b44..61237d1 100644 --- a/src/the_dude_to_human/database/dude_validator.cpp +++ b/src/the_dude_to_human/database/dude_validator.cpp @@ -1,13 +1,445 @@ -// SPDX-FileCopyrightText: Copyright 2024 Narr the Reg +// SPDX-FileCopyrightText: Copyright 2025 Narr the Reg // SPDX-License-Identifier: GPL-3.0-or-later -#include -#include -#include +#include +#include "common/common_types.h" +#include "the_dude_to_human/database/dude_database.h" +#include "the_dude_to_human/database/dude_types.h" #include "the_dude_to_human/database/dude_validator.h" namespace Database { -DudeValidator::DudeValidator(DudeDatabase* database) : db{database} {} + +static bool CheckNewDataFormats(DudeDatabase* db) { + bool new_format_exist = false; + + for (DataFormat format : db->ListUsedDataFormats()) { + switch (format) { + case DataFormat::ServerConfig: + case DataFormat::Tool: + case DataFormat::File: + case DataFormat::Notes: + case DataFormat::Map: + case DataFormat::Probe: + case DataFormat::DeviceType: + case DataFormat::Device: + case DataFormat::Network: + case DataFormat::Service: + case DataFormat::Notification: + case DataFormat::Link: + case DataFormat::LinkType: + case DataFormat::DataSource: + case DataFormat::ObjectList: + case DataFormat::DeviceGroup: + case DataFormat::Function: + case DataFormat::SnmpProfile: + case DataFormat::Panel: + case DataFormat::SysLogRule: + case DataFormat::NetworkMapElement: + case DataFormat::ChartLine: + case DataFormat::PanelElement: + continue; + default: + new_format_exist = true; + printf("Unsupported data format %d\n", static_cast(format)); + } + } + return new_format_exist; +} + +static bool CheckDatabaseIds(DudeDatabase* db) { + bool is_invalid = false; + // Load objects that don't depend on others + std::vector snmp_profiles = db->GetSnmpProfileData(); + std::vector functions = db->GetFunctionData(); + std::vector object_lists = db->GetObjectListData(); + + // Validate file tree + std::vector files = db->GetFileData(); + for (const FileData& file : files) { + // Folder root + if (file.parent_id == -1) { + continue; + } + bool is_parent_found = false; + for (const FileData& parentFile : files) { + if (file.object_id == parentFile.object_id) + continue; + if (file.parent_id != parentFile.object_id) + continue; + is_parent_found = true; + break; + } + if (!is_parent_found) { + printf("File %d: Invalid file parent found %d in file %s\n", file.object_id.value, + file.parent_id.value, file.name.text.c_str()); + is_invalid = true; + } + } + + std::vector notifications = db->GetNotificationData(); + for (const NotificationData& notification : notifications) { + if (notification.sound_file_id != -1 && notification.sound_file_id != 0) { + bool is_sound_found = false; + for (const FileData& sound_file : files) { + if (notification.sound_file_id != sound_file.object_id) + continue; + is_sound_found = true; + break; + } + if (!is_sound_found) { + printf("Notification %d: Invalid sound file found %d in notification %s\n", + notification.object_id.value, notification.sound_file_id.value, + notification.name.text.c_str()); + is_invalid = true; + } + } + + // TODO: Validate groupNotifyIds + } + + // Validate map tree + std::vector maps = db->GetMapData(); + for (const MapData& map : maps) { + if (map.image_id != -1) { + bool is_image_found = false; + for (const FileData& image_file : files) { + if (map.image_id != image_file.object_id) + continue; + is_image_found = true; + break; + } + if (!is_image_found) { + printf("Map %d: Invalid image file found %d in map %s\n", map.object_id.value, + map.image_id.value, map.name.text.c_str()); + is_invalid = true; + } + } + + for (const s32& notify_id : map.notify_ids.data) { + bool is_notify_id_found = false; + if (notify_id != map.object_id) { + for (const MapData& notify_map : maps) { + if (notify_id != notify_map.object_id) + continue; + is_notify_id_found = true; + break; + } + for (const NotificationData& notification : notifications) { + if (notify_id != notification.object_id) + continue; + is_notify_id_found = true; + break; + } + } + if (!is_notify_id_found) { + printf("Map %d: Invalid notify id found %d in map %s\n", map.object_id.value, + notify_id, map.name.text.c_str()); + is_invalid = true; + } + } + } + + std::vector probes = db->GetProbeData(); + for (const ProbeData& probe : probes) { + if (probe.snmp_profile_id != -1) { + bool is_snmp_profile_found = false; + for (const SnmpProfileData& profile : snmp_profiles) { + if (probe.snmp_profile_id != profile.object_id) + continue; + is_snmp_profile_found = true; + break; + } + + // TODO: Validate logicProbeIds + + if (!is_snmp_profile_found) { + printf("Probe %d: Invalid snmp profile id found %d in probe %s\n", + probe.object_id.value, probe.snmp_profile_id.value, probe.name.text.c_str()); + is_invalid = true; + } + } + } + + std::vector link_types = db->GetLinkTypeData(); + for (const LinkTypeData& link_type : link_types) { + bool is_next_id_found = false; + for (const LinkTypeData& next_link : link_types) { + // Can reference itself + if (link_type.next_id != next_link.object_id) + continue; + is_next_id_found = true; + break; + } + + if (!is_next_id_found) { + printf("Link type %d: Invalid next id found %d in link type %s\n", + link_type.object_id.value, link_type.next_id.value, link_type.name.text.c_str()); + is_invalid = true; + } + } + + std::vector device_types = db->GetDeviceTypeData(); + for (const DeviceTypeData& device_type : device_types) { + for (s32 ignored_service_id : device_type.ignored_services.data) { + bool is_probe_found = false; + for (const ProbeData& probe : probes) { + if (ignored_service_id != probe.object_id) + continue; + is_probe_found = true; + break; + } + if (!is_probe_found) { + printf("Device type %d: Invalid ignored service found %d in device type %s\n", + device_type.object_id.value, ignored_service_id, + device_type.name.text.c_str()); + is_invalid = true; + } + } + for (s32 ignored_service_id : device_type.allowed_services.data) { + bool is_probe_found = false; + for (const ProbeData& probe : probes) { + if (ignored_service_id != probe.object_id) + continue; + is_probe_found = true; + break; + } + if (!is_probe_found) { + printf("Device type %d: Invalid allowed service found %d in device type %s\n", + device_type.object_id.value, ignored_service_id, + device_type.name.text.c_str()); + is_invalid = true; + } + } + for (s32 ignored_service_id : device_type.required_services.data) { + bool is_probe_found = false; + for (const ProbeData& probe : probes) { + if (ignored_service_id != probe.object_id) + continue; + is_probe_found = true; + break; + } + if (!is_probe_found) { + printf("Device type %d: Invalid required service found %d in device type %s\n", + device_type.object_id.value, ignored_service_id, + device_type.name.text.c_str()); + is_invalid = true; + } + } + + if (device_type.image_id != -1) { + bool is_image_found = false; + for (const FileData& image_file : files) { + if (device_type.image_id != image_file.object_id) + continue; + is_image_found = true; + break; + } + + if (!is_image_found) { + printf("Device type %d: Invalid image file found %d in device type %s\n", + device_type.object_id.value, device_type.image_id.value, + device_type.name.text.c_str()); + is_invalid = true; + } + } + + bool is_next_id_found = false; + for (const DeviceTypeData& next_device : device_types) { + // Can reference itself + if (device_type.next_id != next_device.object_id) + continue; + is_next_id_found = true; + break; + } + + if (!is_next_id_found) { + printf("Device type %d: Invalid next id found %d in device type %s\n", + device_type.object_id.value, device_type.next_id.value, + device_type.name.text.c_str()); + is_invalid = true; + } + } + + std::vector devices = db->GetDeviceData(); + for (const DeviceData& device : devices) { + for (const s32& parent_id : device.parent_ids.data) { + bool is_parent_found = false; + for (const DeviceData& parent : devices) { + if (device.object_id == parent.object_id) + continue; + if (parent_id != parent.object_id) + continue; + is_parent_found = true; + break; + } + if (!is_parent_found) { + printf("Device %d: Invalid parent found %d in device %s\n", device.object_id.value, + parent_id, device.name.text.c_str()); + is_invalid = true; + } + } + + for (const s32& notify_id : device.notify_ids.data) { + bool is_notify_id_found = false; + for (const MapData& notify_map : maps) { + if (notify_id != notify_map.object_id) + continue; + is_notify_id_found = true; + break; + } + for (const NotificationData& notification : notifications) { + if (notify_id != notification.object_id) + continue; + is_notify_id_found = true; + break; + } + if (!is_notify_id_found) { + printf("Device %d: Invalid notify id found %d in device %s\n", + device.object_id.value, notify_id, device.name.text.c_str()); + is_invalid = true; + } + } + + if (device.type_id != -1) { + bool is_device_type_found = false; + for (const DeviceTypeData& device_type : device_types) { + if (device.type_id != device_type.object_id) + continue; + is_device_type_found = true; + break; + } + if (!is_device_type_found) { + printf("Device %d: Invalid device type found %d in device %s\n", + device.object_id.value, device.type_id.value, device.name.text.c_str()); + is_invalid = true; + } + } + + if (device.agent_id != -1) { + bool is_agent_found = false; + for (const DeviceData& agent : devices) { + if (device.object_id == agent.object_id) + continue; + if (device.agent_id != agent.object_id) + continue; + is_agent_found = true; + break; + } + if (!is_agent_found) { + printf("Device %d: Invalid agent found %d in device %s\n", device.object_id.value, + device.agent_id.value, device.name.text.c_str()); + is_invalid = true; + } + } + + if (device.snmp_profile_id != -1) { + bool is_snmp_profile_found = false; + for (const SnmpProfileData& profile : snmp_profiles) { + if (device.snmp_profile_id != profile.object_id) + continue; + is_snmp_profile_found = true; + break; + } + if (!is_snmp_profile_found) { + printf("Device %d: Invalid snmp profile id found %d in device %s\n", + device.object_id.value, device.snmp_profile_id.value, + device.name.text.c_str()); + is_invalid = true; + } + } + } + + std::vector data_sources = db->GetDataSourceData(); + /* for (const DataSourceData& data_source : data_sources) { + // TODO: Validate functionDeviceId + }*/ + + std::vector charts = db->GetChartLineData(); + for (const ChartLineData& chart : charts) { + bool is_chart_found = false; + for (const ObjectListData& object : object_lists) { + if (chart.chart_id != object.object_id) + continue; + is_chart_found = true; + break; + } + if (!is_chart_found) { + printf("Chart %d: Invalid chart id found %d in chart %s\n", chart.object_id.value, + chart.chart_id.value, chart.name.text.c_str()); + is_invalid = true; + } + + bool is_data_source_found = false; + for (const DataSourceData& data_source : data_sources) { + if (chart.source_id != data_source.object_id) + continue; + is_data_source_found = true; + break; + } + if (!is_data_source_found) { + printf("Chart %d: Invalid data source found %d in chart %s\n", chart.object_id.value, + chart.source_id.value, chart.name.text.c_str()); + is_invalid = true; + } + + if (chart.next_id != -1) { + bool is_next_id_found = false; + for (const ChartLineData& next_chart : charts) { + // Can reference itself + if (chart.next_id != next_chart.object_id) + continue; + is_next_id_found = true; + break; + } + + if (!is_next_id_found) { + printf("Chart %d: Invalid next id found %d in chart %s\n", chart.object_id.value, + chart.next_id.value, chart.name.text.c_str()); + is_invalid = true; + } + } + } + + std::vector sys_log_rules = db->GetSysLogRuleData(); + for (const SysLogRuleData& sys_log_rule : sys_log_rules) { + if (sys_log_rule.next_id != -1) { + bool is_next_id_found = false; + for (const SysLogRuleData& next_rule : sys_log_rules) { + if (sys_log_rule.object_id == next_rule.object_id) + continue; + if (sys_log_rule.next_id != next_rule.object_id) + continue; + is_next_id_found = true; + break; + } + + if (!is_next_id_found) { + printf("Syslog rule %d: Invalid next id found %d in rule %s\n", + sys_log_rule.object_id.value, sys_log_rule.next_id.value, + sys_log_rule.name.text.c_str()); + is_invalid = true; + } + } + } + + return is_invalid; +} + +int ValidateDatabase(DudeDatabase* db) { + if (CheckNewDataFormats(db)) { + printf( + "This database contains new data formats. Please contact developer to add support.\n"); + return 1; + } + + if (CheckDatabaseIds(db)) { + printf("This database contains invalid object references.\n"); + return 2; + } + + printf("Database health: OK\n"); + return 0; +} } // namespace Database diff --git a/src/the_dude_to_human/database/dude_validator.h b/src/the_dude_to_human/database/dude_validator.h index ccb6d4e..f610b49 100644 --- a/src/the_dude_to_human/database/dude_validator.h +++ b/src/the_dude_to_human/database/dude_validator.h @@ -1,19 +1,9 @@ -// SPDX-FileCopyrightText: Copyright 2024 Narr the Reg +// SPDX-FileCopyrightText: Copyright 2025 Narr the Reg // SPDX-License-Identifier: GPL-3.0-or-later #pragma once -#include "common/common_types.h" -#include "the_dude_to_human/database/dude_types.h" - namespace Database { class DudeDatabase; - -class DudeValidator { -public: - DudeValidator(DudeDatabase* database); - -private: - DudeDatabase* db; -}; +int ValidateDatabase(DudeDatabase* db); } // namespace Database