diff --git a/Makefile b/Makefile index 6a5adcf..a8e0ce1 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,8 @@ TEST_CFLAGS = -std=c17 \ TEST_LDFLAGS = $(shell $(PKG_CONFIG) --libs glib-2.0 gio-2.0) +EVIDENCE_RECORD_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic + SRC := $(shell find src -name "*.c") OBJ := $(SRC:.c=.o) @@ -38,6 +40,8 @@ TEST_STATEMENT = tests/test_statement TEST_TRANSACTION = tests/test_transaction TEST_ERROR = tests/test_error TEST_INVESTIGATION_RECORD = tests/test_investigation_record +TEST_EVIDENCE_RECORD := tests/test_evidence_record +TEST_EVIDENCE_RECORD := tests/test_evidence_record TEST_INVESTIGATION_DAO := tests/test_investigation_dao TEST_INVESTIGATION_SESSION := tests/test_investigation_session TEST_BACKGROUND_TASK := tests/test_background_task @@ -120,6 +124,11 @@ $(TEST_INVESTIGATION_RECORD): \ src/models/investigation_record.c $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) +$(TEST_EVIDENCE_RECORD): \ + tests/test_evidence_record.c \ + src/models/evidence_record.c + $(CC) $(EVIDENCE_RECORD_TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) + $(TEST_INVESTIGATION_DAO): \ tests/test_investigation_dao.c \ src/dao/investigation_dao.c \ @@ -200,6 +209,7 @@ test: \ $(TEST_TRANSACTION) \ $(TEST_ERROR) \ $(TEST_INVESTIGATION_RECORD) \ + $(TEST_EVIDENCE_RECORD) \ $(TEST_INVESTIGATION_DAO) \ $(TEST_INVESTIGATION_SESSION) \ $(TEST_BACKGROUND_TASK) \ @@ -219,6 +229,7 @@ test: \ @$(TEST_TRANSACTION) @$(TEST_ERROR) @$(TEST_INVESTIGATION_RECORD) + @$(TEST_EVIDENCE_RECORD) @$(TEST_INVESTIGATION_DAO) @$(TEST_INVESTIGATION_SESSION) @$(TEST_BACKGROUND_TASK) @@ -247,6 +258,7 @@ clean: $(TEST_TRANSACTION) \ $(TEST_ERROR) \ $(TEST_INVESTIGATION_RECORD) \ + $(TEST_EVIDENCE_RECORD) \ $(TEST_INVESTIGATION_DAO) \ $(TEST_INVESTIGATION_SESSION) \ $(TEST_BACKGROUND_TASK) \ diff --git a/include/models/evidence_record.h b/include/models/evidence_record.h new file mode 100644 index 0000000..b292d0a --- /dev/null +++ b/include/models/evidence_record.h @@ -0,0 +1,274 @@ +/****************************************************************************** + * @file evidence_record.h + * @brief Modèle représentant une preuve numérique persistée. + ******************************************************************************/ + +#ifndef LABFY_INVESTIGATION_EVIDENCE_RECORD_H +#define LABFY_INVESTIGATION_EVIDENCE_RECORD_H + +#include + +G_BEGIN_DECLS + +/** + * @brief Modèle opaque représentant une preuve numérique. + */ +typedef struct EvidenceRecord EvidenceRecord; + +/** + * @brief État connu de l’intégrité d’une preuve. + */ +typedef enum +{ + /** + * Aucune vérification d’intégrité n’a encore été effectuée. + */ + EVIDENCE_INTEGRITY_STATUS_UNKNOWN, + + /** + * Le fichier existe et son empreinte est conforme. + */ + EVIDENCE_INTEGRITY_STATUS_VALID, + + /** + * Le fichier attendu est absent. + */ + EVIDENCE_INTEGRITY_STATUS_MISSING, + + /** + * Le fichier existe, mais son contenu a changé. + */ + EVIDENCE_INTEGRITY_STATUS_MODIFIED, + + /** + * La vérification d’intégrité n’a pas pu être réalisée. + */ + EVIDENCE_INTEGRITY_STATUS_ERROR +} EvidenceIntegrityStatus; + +/** + * @brief Codes d’erreur produits par EvidenceRecord. + */ +typedef enum +{ + /** + * Un argument général est invalide. + */ + EVIDENCE_RECORD_ERROR_INVALID_ARGUMENT, + + /** + * L’identifiant UUID est invalide. + */ + EVIDENCE_RECORD_ERROR_INVALID_IDENTIFIER, + + /** + * Un nom de fichier est invalide. + */ + EVIDENCE_RECORD_ERROR_INVALID_NAME, + + /** + * Le chemin relatif est invalide. + */ + EVIDENCE_RECORD_ERROR_INVALID_PATH, + + /** + * L’identifiant du type de preuve est invalide. + */ + EVIDENCE_RECORD_ERROR_INVALID_TYPE, + + /** + * L’empreinte SHA-256 est invalide. + */ + EVIDENCE_RECORD_ERROR_INVALID_SHA256, + + /** + * Une date est invalide. + */ + EVIDENCE_RECORD_ERROR_INVALID_DATE, + + /** + * Le statut d’intégrité est invalide. + */ + EVIDENCE_RECORD_ERROR_INVALID_STATUS +} EvidenceRecordError; + +/** + * @brief Domaine d’erreur du modèle EvidenceRecord. + */ +#define EVIDENCE_RECORD_ERROR \ + evidence_record_error_quark() + +/** + * @brief Retourne le domaine d’erreur du modèle. + * + * @return Quark GLib associé à EvidenceRecord. + */ +GQuark evidence_record_error_quark(void); + +/** + * @brief Crée un modèle représentant une preuve numérique. + * + * Toutes les chaînes sont copiées et appartiennent au nouvel objet. + * + * Les champs identifier, original_name, internal_name, relative_path, + * type_identifier, sha256 et imported_at sont obligatoires. + * + * collected_at, source et description peuvent être NULL. + * + * @param identifier Identifiant UUID de la preuve. + * @param original_name Nom du fichier au moment de la collecte. + * @param internal_name Nom utilisé pour le stockage dans l’enquête. + * @param relative_path Chemin relatif depuis la racine de l’enquête. + * @param type_identifier Identifiant métier du type de preuve. + * @param size_bytes Taille observée du fichier. + * @param sha256 Empreinte SHA-256 du fichier. + * @param imported_at Date UTC de l’importation. + * @param collected_at Date UTC de collecte, ou NULL. + * @param source Description de la source, ou NULL. + * @param description Description facultative, ou NULL. + * @param integrity_status État d’intégrité de la preuve. + * @param error Emplacement facultatif recevant une erreur. + * + * @return Nouveau modèle, ou NULL lorsque les données sont invalides. + */ +EvidenceRecord *evidence_record_new( + const char *identifier, + const char *original_name, + const char *internal_name, + const char *relative_path, + const char *type_identifier, + guint64 size_bytes, + const char *sha256, + const char *imported_at, + const char *collected_at, + const char *source, + const char *description, + EvidenceIntegrityStatus integrity_status, + GError **error +); + +/** + * @brief Libère un modèle EvidenceRecord. + * + * Cette fonction accepte evidence_record == NULL. + * + * @param evidence_record Modèle à libérer. + */ +void evidence_record_free( + EvidenceRecord *evidence_record +); + +/** + * @brief Retourne l’identifiant UUID de la preuve. + * + * @return Chaîne empruntée, ou NULL. + */ +const char *evidence_record_get_identifier( + const EvidenceRecord *evidence_record +); + +/** + * @brief Retourne le nom original du fichier. + * + * @return Chaîne empruntée, ou NULL. + */ +const char *evidence_record_get_original_name( + const EvidenceRecord *evidence_record +); + +/** + * @brief Retourne le nom interne du fichier. + * + * @return Chaîne empruntée, ou NULL. + */ +const char *evidence_record_get_internal_name( + const EvidenceRecord *evidence_record +); + +/** + * @brief Retourne le chemin relatif de la preuve. + * + * @return Chaîne empruntée, ou NULL. + */ +const char *evidence_record_get_relative_path( + const EvidenceRecord *evidence_record +); + +/** + * @brief Retourne l’identifiant du type de preuve. + * + * @return Chaîne empruntée, ou NULL. + */ +const char *evidence_record_get_type_identifier( + const EvidenceRecord *evidence_record +); + +/** + * @brief Retourne la taille observée de la preuve. + * + * @return Taille en octets, ou 0 lorsque l’objet est NULL. + */ +guint64 evidence_record_get_size_bytes( + const EvidenceRecord *evidence_record +); + +/** + * @brief Retourne l’empreinte SHA-256. + * + * L’empreinte est stockée en minuscules. + * + * @return Chaîne empruntée, ou NULL. + */ +const char *evidence_record_get_sha256( + const EvidenceRecord *evidence_record +); + +/** + * @brief Retourne la date UTC d’importation. + * + * @return Chaîne empruntée, ou NULL. + */ +const char *evidence_record_get_imported_at( + const EvidenceRecord *evidence_record +); + +/** + * @brief Retourne la date UTC de collecte. + * + * @return Chaîne empruntée, ou NULL. + */ +const char *evidence_record_get_collected_at( + const EvidenceRecord *evidence_record +); + +/** + * @brief Retourne la source déclarée de la preuve. + * + * @return Chaîne empruntée, ou NULL. + */ +const char *evidence_record_get_source( + const EvidenceRecord *evidence_record +); + +/** + * @brief Retourne la description de la preuve. + * + * @return Chaîne empruntée, ou NULL. + */ +const char *evidence_record_get_description( + const EvidenceRecord *evidence_record +); + +/** + * @brief Retourne l’état d’intégrité de la preuve. + * + * @return État enregistré, ou EVIDENCE_INTEGRITY_STATUS_UNKNOWN + * lorsque l’objet est NULL. + */ +EvidenceIntegrityStatus evidence_record_get_integrity_status( + const EvidenceRecord *evidence_record +); + +G_END_DECLS + +#endif diff --git a/labfy-investigation b/labfy-investigation index a47839f..ec5ef1d 100755 Binary files a/labfy-investigation and b/labfy-investigation differ diff --git a/src/models/evidence_record.c b/src/models/evidence_record.c new file mode 100644 index 0000000..5b44e49 --- /dev/null +++ b/src/models/evidence_record.c @@ -0,0 +1,967 @@ +/****************************************************************************** + * @file evidence_record.c + * @brief Implémentation du modèle EvidenceRecord. + ******************************************************************************/ + +#include "models/evidence_record.h" + +#include +#include + +/** + * @brief Représentation privée d’une preuve numérique persistée. + */ +struct EvidenceRecord +{ + char *identifier; + char *original_name; + char *internal_name; + char *relative_path; + char *type_identifier; + + guint64 size_bytes; + + char *sha256; + char *imported_at; + char *collected_at; + char *source; + char *description; + + EvidenceIntegrityStatus integrity_status; +}; + +/** + * @brief Produit une erreur du domaine EvidenceRecord. + */ +static void evidence_record_set_error( + GError **error, + EvidenceRecordError error_code, + const char *message +) +{ + if (error == NULL) + { + return; + } + + g_set_error_literal( + error, + EVIDENCE_RECORD_ERROR, + error_code, + message + ); +} + +/** + * @brief Copie une chaîne et retire les espaces extérieurs. + * + * @return Nouvelle chaîne, ou NULL lorsque text vaut NULL. + */ +static char *evidence_record_duplicate_trimmed( + const char *text +) +{ + char *text_copy = NULL; + + if (text == NULL) + { + return NULL; + } + + text_copy = g_strdup( + text + ); + + g_strstrip( + text_copy + ); + + return text_copy; +} + +/** + * @brief Indique si une chaîne obligatoire contient du texte. + */ +static gboolean evidence_record_required_text_is_valid( + const char *text +) +{ + return text != NULL && + text[0] != '\0'; +} + +/** + * @brief Vérifie qu’un nom ne contient aucun composant de chemin. + */ +static gboolean evidence_record_filename_is_valid( + const char *filename +) +{ + if (!evidence_record_required_text_is_valid( + filename + )) + { + return FALSE; + } + + if (g_strcmp0( + filename, + "." + ) == 0 || + g_strcmp0( + filename, + ".." + ) == 0) + { + return FALSE; + } + + /* + * Les deux séparateurs sont refusés, même si l’application cible + * principalement les systèmes Unix. + */ + if (strchr( + filename, + '/' + ) != NULL || + strchr( + filename, + '\\' + ) != NULL) + { + return FALSE; + } + + return TRUE; +} + +/** + * @brief Vérifie qu’un chemin reste relatif à l’enquête. + */ +static gboolean evidence_record_relative_path_is_valid( + const char *relative_path +) +{ + char **components = NULL; + gsize component_index = 0; + + gboolean is_valid = TRUE; + + if (!evidence_record_required_text_is_valid( + relative_path + )) + { + return FALSE; + } + + if (g_path_is_absolute( + relative_path + )) + { + return FALSE; + } + + if (g_strcmp0( + relative_path, + "." + ) == 0 || + g_strcmp0( + relative_path, + ".." + ) == 0) + { + return FALSE; + } + + if (relative_path[ + strlen(relative_path) - 1 + ] == G_DIR_SEPARATOR) + { + return FALSE; + } + + components = g_strsplit( + relative_path, + G_DIR_SEPARATOR_S, + -1 + ); + + if (components == NULL) + { + return FALSE; + } + + for (component_index = 0; + components[component_index] != NULL; + component_index++) + { + const char *component = + components[component_index]; + + if (component[0] == '\0' || + g_strcmp0( + component, + "." + ) == 0 || + g_strcmp0( + component, + ".." + ) == 0) + { + is_valid = FALSE; + break; + } + } + + g_strfreev( + components + ); + + return is_valid; +} + +/** + * @brief Vérifie qu’une empreinte contient 64 caractères hexadécimaux. + */ +static gboolean evidence_record_sha256_is_valid( + const char *sha256 +) +{ + gsize character_index = 0; + + if (sha256 == NULL || + strlen(sha256) != 64) + { + return FALSE; + } + + for (character_index = 0; + character_index < 64; + character_index++) + { + if (!g_ascii_isxdigit( + sha256[character_index] + )) + { + return FALSE; + } + } + + return TRUE; +} + +/** + * @brief Convertit une empreinte hexadécimale en minuscules. + */ +static void evidence_record_sha256_to_lowercase( + char *sha256 +) +{ + gsize character_index = 0; + + if (sha256 == NULL) + { + return; + } + + for (character_index = 0; + sha256[character_index] != '\0'; + character_index++) + { + sha256[character_index] = + g_ascii_tolower( + sha256[character_index] + ); + } +} + +/** + * @brief Lit un nombre décimal à une position déterminée. + */ +static gboolean evidence_record_parse_decimal( + const char *text, + gsize offset, + gsize length, + gint *out_value +) +{ + gsize character_index = 0; + gint value = 0; + + if (text == NULL || + out_value == NULL || + length == 0) + { + return FALSE; + } + + for (character_index = 0; + character_index < length; + character_index++) + { + char character = + text[offset + character_index]; + + if (!g_ascii_isdigit( + character + )) + { + return FALSE; + } + + value = + (value * 10) + + (character - '0'); + } + + *out_value = value; + + return TRUE; +} + +/** + * @brief Vérifie une date UTC au format YYYY-MM-DDTHH:MM:SSZ. + */ +static gboolean evidence_record_utc_date_is_valid( + const char *date_text +) +{ + GDateTime *date_time = NULL; + + gint year = 0; + gint month = 0; + gint day = 0; + gint hour = 0; + gint minute = 0; + gint second = 0; + + if (date_text == NULL || + strlen(date_text) != 20) + { + return FALSE; + } + + if (date_text[4] != '-' || + date_text[7] != '-' || + date_text[10] != 'T' || + date_text[13] != ':' || + date_text[16] != ':' || + date_text[19] != 'Z') + { + return FALSE; + } + + if (!evidence_record_parse_decimal( + date_text, + 0, + 4, + &year + ) || + !evidence_record_parse_decimal( + date_text, + 5, + 2, + &month + ) || + !evidence_record_parse_decimal( + date_text, + 8, + 2, + &day + ) || + !evidence_record_parse_decimal( + date_text, + 11, + 2, + &hour + ) || + !evidence_record_parse_decimal( + date_text, + 14, + 2, + &minute + ) || + !evidence_record_parse_decimal( + date_text, + 17, + 2, + &second + )) + { + return FALSE; + } + + date_time = g_date_time_new_utc( + year, + month, + day, + hour, + minute, + second + ); + + if (date_time == NULL) + { + return FALSE; + } + + g_date_time_unref( + date_time + ); + + return TRUE; +} + +/** + * @brief Vérifie que la valeur appartient à EvidenceIntegrityStatus. + */ +static gboolean evidence_record_integrity_status_is_valid( + EvidenceIntegrityStatus integrity_status +) +{ + switch (integrity_status) + { + case EVIDENCE_INTEGRITY_STATUS_UNKNOWN: + case EVIDENCE_INTEGRITY_STATUS_VALID: + case EVIDENCE_INTEGRITY_STATUS_MISSING: + case EVIDENCE_INTEGRITY_STATUS_MODIFIED: + case EVIDENCE_INTEGRITY_STATUS_ERROR: + return TRUE; + + default: + return FALSE; + } +} + +GQuark evidence_record_error_quark(void) +{ + return g_quark_from_static_string( + "labfy-investigation-evidence-record-error" + ); +} + +EvidenceRecord *evidence_record_new( + const char *identifier, + const char *original_name, + const char *internal_name, + const char *relative_path, + const char *type_identifier, + guint64 size_bytes, + const char *sha256, + const char *imported_at, + const char *collected_at, + const char *source, + const char *description, + EvidenceIntegrityStatus integrity_status, + GError **error +) +{ + EvidenceRecord *evidence_record = NULL; + + char *identifier_copy = NULL; + char *original_name_copy = NULL; + char *internal_name_copy = NULL; + char *relative_path_copy = NULL; + char *type_identifier_copy = NULL; + char *sha256_copy = NULL; + char *imported_at_copy = NULL; + char *collected_at_copy = NULL; + char *source_copy = NULL; + char *description_copy = NULL; + + g_return_val_if_fail( + error == NULL || *error == NULL, + NULL + ); + + identifier_copy = + evidence_record_duplicate_trimmed( + identifier + ); + + original_name_copy = + evidence_record_duplicate_trimmed( + original_name + ); + + internal_name_copy = + evidence_record_duplicate_trimmed( + internal_name + ); + + relative_path_copy = + evidence_record_duplicate_trimmed( + relative_path + ); + + type_identifier_copy = + evidence_record_duplicate_trimmed( + type_identifier + ); + + sha256_copy = + evidence_record_duplicate_trimmed( + sha256 + ); + + imported_at_copy = + evidence_record_duplicate_trimmed( + imported_at + ); + + collected_at_copy = + evidence_record_duplicate_trimmed( + collected_at + ); + + source_copy = + evidence_record_duplicate_trimmed( + source + ); + + description_copy = + evidence_record_duplicate_trimmed( + description + ); + + if (!evidence_record_required_text_is_valid( + identifier_copy + )) + { + evidence_record_set_error( + error, + EVIDENCE_RECORD_ERROR_INVALID_IDENTIFIER, + "L’identifiant de la preuve est obligatoire." + ); + + goto cleanup; + } + + if (!g_uuid_string_is_valid( + identifier_copy + )) + { + evidence_record_set_error( + error, + EVIDENCE_RECORD_ERROR_INVALID_IDENTIFIER, + "L’identifiant de la preuve n’est pas un UUID valide." + ); + + goto cleanup; + } + + if (!evidence_record_filename_is_valid( + original_name_copy + )) + { + evidence_record_set_error( + error, + EVIDENCE_RECORD_ERROR_INVALID_NAME, + "Le nom original de la preuve est invalide." + ); + + goto cleanup; + } + + if (!evidence_record_filename_is_valid( + internal_name_copy + )) + { + evidence_record_set_error( + error, + EVIDENCE_RECORD_ERROR_INVALID_NAME, + "Le nom interne de la preuve est invalide." + ); + + goto cleanup; + } + + if (!evidence_record_relative_path_is_valid( + relative_path_copy + )) + { + evidence_record_set_error( + error, + EVIDENCE_RECORD_ERROR_INVALID_PATH, + "Le chemin relatif de la preuve est invalide." + ); + + goto cleanup; + } + + if (!evidence_record_required_text_is_valid( + type_identifier_copy + )) + { + evidence_record_set_error( + error, + EVIDENCE_RECORD_ERROR_INVALID_TYPE, + "Le type de la preuve est obligatoire." + ); + + goto cleanup; + } + + if (!evidence_record_sha256_is_valid( + sha256_copy + )) + { + evidence_record_set_error( + error, + EVIDENCE_RECORD_ERROR_INVALID_SHA256, + "L’empreinte SHA-256 de la preuve est invalide." + ); + + goto cleanup; + } + + if (!evidence_record_utc_date_is_valid( + imported_at_copy + )) + { + evidence_record_set_error( + error, + EVIDENCE_RECORD_ERROR_INVALID_DATE, + "La date d’importation est invalide." + ); + + goto cleanup; + } + + if (collected_at_copy != NULL && + !evidence_record_utc_date_is_valid( + collected_at_copy + )) + { + evidence_record_set_error( + error, + EVIDENCE_RECORD_ERROR_INVALID_DATE, + "La date de collecte est invalide." + ); + + goto cleanup; + } + + if (!evidence_record_integrity_status_is_valid( + integrity_status + )) + { + evidence_record_set_error( + error, + EVIDENCE_RECORD_ERROR_INVALID_STATUS, + "Le statut d’intégrité de la preuve est invalide." + ); + + goto cleanup; + } + + /* + * Les champs facultatifs composés uniquement d’espaces deviennent + * NULL afin de ne pas conserver de chaînes vides. + */ + if (source_copy != NULL && + source_copy[0] == '\0') + { + g_clear_pointer( + &source_copy, + g_free + ); + } + + if (description_copy != NULL && + description_copy[0] == '\0') + { + g_clear_pointer( + &description_copy, + g_free + ); + } + + evidence_record_sha256_to_lowercase( + sha256_copy + ); + + evidence_record = g_new0( + EvidenceRecord, + 1 + ); + + evidence_record->identifier = + identifier_copy; + + evidence_record->original_name = + original_name_copy; + + evidence_record->internal_name = + internal_name_copy; + + evidence_record->relative_path = + relative_path_copy; + + evidence_record->type_identifier = + type_identifier_copy; + + evidence_record->size_bytes = + size_bytes; + + evidence_record->sha256 = + sha256_copy; + + evidence_record->imported_at = + imported_at_copy; + + evidence_record->collected_at = + collected_at_copy; + + evidence_record->source = + source_copy; + + evidence_record->description = + description_copy; + + evidence_record->integrity_status = + integrity_status; + + return evidence_record; + +cleanup: + g_free( + description_copy + ); + + g_free( + source_copy + ); + + g_free( + collected_at_copy + ); + + g_free( + imported_at_copy + ); + + g_free( + sha256_copy + ); + + g_free( + type_identifier_copy + ); + + g_free( + relative_path_copy + ); + + g_free( + internal_name_copy + ); + + g_free( + original_name_copy + ); + + g_free( + identifier_copy + ); + + return NULL; +} + +void evidence_record_free( + EvidenceRecord *evidence_record +) +{ + if (evidence_record == NULL) + { + return; + } + + g_free( + evidence_record->description + ); + + g_free( + evidence_record->source + ); + + g_free( + evidence_record->collected_at + ); + + g_free( + evidence_record->imported_at + ); + + g_free( + evidence_record->sha256 + ); + + g_free( + evidence_record->type_identifier + ); + + g_free( + evidence_record->relative_path + ); + + g_free( + evidence_record->internal_name + ); + + g_free( + evidence_record->original_name + ); + + g_free( + evidence_record->identifier + ); + + g_free( + evidence_record + ); +} + +const char *evidence_record_get_identifier( + const EvidenceRecord *evidence_record +) +{ + if (evidence_record == NULL) + { + return NULL; + } + + return evidence_record->identifier; +} + +const char *evidence_record_get_original_name( + const EvidenceRecord *evidence_record +) +{ + if (evidence_record == NULL) + { + return NULL; + } + + return evidence_record->original_name; +} + +const char *evidence_record_get_internal_name( + const EvidenceRecord *evidence_record +) +{ + if (evidence_record == NULL) + { + return NULL; + } + + return evidence_record->internal_name; +} + +const char *evidence_record_get_relative_path( + const EvidenceRecord *evidence_record +) +{ + if (evidence_record == NULL) + { + return NULL; + } + + return evidence_record->relative_path; +} + +const char *evidence_record_get_type_identifier( + const EvidenceRecord *evidence_record +) +{ + if (evidence_record == NULL) + { + return NULL; + } + + return evidence_record->type_identifier; +} + +guint64 evidence_record_get_size_bytes( + const EvidenceRecord *evidence_record +) +{ + if (evidence_record == NULL) + { + return 0; + } + + return evidence_record->size_bytes; +} + +const char *evidence_record_get_sha256( + const EvidenceRecord *evidence_record +) +{ + if (evidence_record == NULL) + { + return NULL; + } + + return evidence_record->sha256; +} + +const char *evidence_record_get_imported_at( + const EvidenceRecord *evidence_record +) +{ + if (evidence_record == NULL) + { + return NULL; + } + + return evidence_record->imported_at; +} + +const char *evidence_record_get_collected_at( + const EvidenceRecord *evidence_record +) +{ + if (evidence_record == NULL) + { + return NULL; + } + + return evidence_record->collected_at; +} + +const char *evidence_record_get_source( + const EvidenceRecord *evidence_record +) +{ + if (evidence_record == NULL) + { + return NULL; + } + + return evidence_record->source; +} + +const char *evidence_record_get_description( + const EvidenceRecord *evidence_record +) +{ + if (evidence_record == NULL) + { + return NULL; + } + + return evidence_record->description; +} + +EvidenceIntegrityStatus evidence_record_get_integrity_status( + const EvidenceRecord *evidence_record +) +{ + if (evidence_record == NULL) + { + return EVIDENCE_INTEGRITY_STATUS_UNKNOWN; + } + + return evidence_record->integrity_status; +} diff --git a/tests/test_evidence_record b/tests/test_evidence_record new file mode 100755 index 0000000..f0e6983 Binary files /dev/null and b/tests/test_evidence_record differ diff --git a/tests/test_evidence_record.c b/tests/test_evidence_record.c new file mode 100644 index 0000000..7419802 --- /dev/null +++ b/tests/test_evidence_record.c @@ -0,0 +1,1099 @@ +/****************************************************************************** + * @file test_evidence_record.c + * @brief Tests du modèle EvidenceRecord. + ******************************************************************************/ + +#include "models/evidence_record.h" + +#include +#include + +#define TEST_EVIDENCE_IDENTIFIER \ + "6e62b9af-2046-4efd-b3b9-29869f816951" + +#define TEST_EVIDENCE_SHA256_LOWER \ + "0123456789abcdef" \ + "0123456789abcdef" \ + "0123456789abcdef" \ + "0123456789abcdef" + +#define TEST_EVIDENCE_SHA256_UPPER \ + "0123456789ABCDEF" \ + "0123456789ABCDEF" \ + "0123456789ABCDEF" \ + "0123456789ABCDEF" + +typedef struct +{ + const char *identifier; + const char *original_name; + const char *internal_name; + const char *relative_path; + const char *type_identifier; + + guint64 size_bytes; + + const char *sha256; + const char *imported_at; + const char *collected_at; + const char *source; + const char *description; + + EvidenceIntegrityStatus integrity_status; +} TestEvidenceRecordInput; + +static TestEvidenceRecordInput test_evidence_record_valid_input(void) +{ + return (TestEvidenceRecordInput) + { + .identifier = TEST_EVIDENCE_IDENTIFIER, + .original_name = "capture_originale.png", + .internal_name = "preuve_001.png", + .relative_path = + "01_Preuves_Originales/Captures_Ecran/preuve_001.png", + .type_identifier = "image", + .size_bytes = 4096, + .sha256 = TEST_EVIDENCE_SHA256_LOWER, + .imported_at = "2026-07-18T14:30:00Z", + .collected_at = "2026-07-17T20:15:30Z", + .source = "Conversation Instagram", + .description = "Capture originale reçue par la victime.", + .integrity_status = EVIDENCE_INTEGRITY_STATUS_VALID + }; +} + +static EvidenceRecord *test_evidence_record_create( + const TestEvidenceRecordInput *input, + GError **error +) +{ + g_assert_nonnull( + input + ); + + return evidence_record_new( + input->identifier, + input->original_name, + input->internal_name, + input->relative_path, + input->type_identifier, + input->size_bytes, + input->sha256, + input->imported_at, + input->collected_at, + input->source, + input->description, + input->integrity_status, + error + ); +} + +static void test_evidence_record_assert_invalid( + const TestEvidenceRecordInput *input, + EvidenceRecordError expected_error +) +{ + EvidenceRecord *evidence_record = NULL; + GError *error = NULL; + + evidence_record = + test_evidence_record_create( + input, + &error + ); + + g_assert_null( + evidence_record + ); + + g_assert_error( + error, + EVIDENCE_RECORD_ERROR, + (gint) expected_error + ); + + g_assert_nonnull( + error->message + ); + + g_assert_cmpstr( + error->message, + !=, + "" + ); + + g_clear_error( + &error + ); +} + +static void test_evidence_record_valid_full(void) +{ + TestEvidenceRecordInput input = + test_evidence_record_valid_input(); + + EvidenceRecord *evidence_record = NULL; + GError *error = NULL; + + evidence_record = + test_evidence_record_create( + &input, + &error + ); + + g_assert_no_error( + error + ); + + g_assert_nonnull( + evidence_record + ); + + g_assert_cmpstr( + evidence_record_get_identifier( + evidence_record + ), + ==, + TEST_EVIDENCE_IDENTIFIER + ); + + g_assert_cmpstr( + evidence_record_get_original_name( + evidence_record + ), + ==, + "capture_originale.png" + ); + + g_assert_cmpstr( + evidence_record_get_internal_name( + evidence_record + ), + ==, + "preuve_001.png" + ); + + g_assert_cmpstr( + evidence_record_get_relative_path( + evidence_record + ), + ==, + "01_Preuves_Originales/Captures_Ecran/preuve_001.png" + ); + + g_assert_cmpstr( + evidence_record_get_type_identifier( + evidence_record + ), + ==, + "image" + ); + + g_assert_cmpuint( + evidence_record_get_size_bytes( + evidence_record + ), + ==, + 4096 + ); + + g_assert_cmpstr( + evidence_record_get_sha256( + evidence_record + ), + ==, + TEST_EVIDENCE_SHA256_LOWER + ); + + g_assert_cmpstr( + evidence_record_get_imported_at( + evidence_record + ), + ==, + "2026-07-18T14:30:00Z" + ); + + g_assert_cmpstr( + evidence_record_get_collected_at( + evidence_record + ), + ==, + "2026-07-17T20:15:30Z" + ); + + g_assert_cmpstr( + evidence_record_get_source( + evidence_record + ), + ==, + "Conversation Instagram" + ); + + g_assert_cmpstr( + evidence_record_get_description( + evidence_record + ), + ==, + "Capture originale reçue par la victime." + ); + + g_assert_cmpint( + evidence_record_get_integrity_status( + evidence_record + ), + ==, + EVIDENCE_INTEGRITY_STATUS_VALID + ); + + evidence_record_free( + evidence_record + ); +} + +static void test_evidence_record_valid_optional_null(void) +{ + TestEvidenceRecordInput input = + test_evidence_record_valid_input(); + + EvidenceRecord *evidence_record = NULL; + GError *error = NULL; + + input.collected_at = NULL; + input.source = " "; + input.description = "\t"; + + evidence_record = + test_evidence_record_create( + &input, + &error + ); + + g_assert_no_error( + error + ); + + g_assert_nonnull( + evidence_record + ); + + g_assert_null( + evidence_record_get_collected_at( + evidence_record + ) + ); + + g_assert_null( + evidence_record_get_source( + evidence_record + ) + ); + + g_assert_null( + evidence_record_get_description( + evidence_record + ) + ); + + evidence_record_free( + evidence_record + ); +} + +static void test_evidence_record_copies_strings(void) +{ + char identifier[] = + TEST_EVIDENCE_IDENTIFIER; + + char original_name[] = + "capture_originale.png"; + + char internal_name[] = + "preuve_001.png"; + + char relative_path[] = + "01_Preuves_Originales/Captures_Ecran/preuve_001.png"; + + char type_identifier[] = + "image"; + + char sha256[] = + TEST_EVIDENCE_SHA256_LOWER; + + char imported_at[] = + "2026-07-18T14:30:00Z"; + + char collected_at[] = + "2026-07-17T20:15:30Z"; + + char source[] = + "Conversation Instagram"; + + char description[] = + "Capture originale"; + + TestEvidenceRecordInput input = + { + .identifier = identifier, + .original_name = original_name, + .internal_name = internal_name, + .relative_path = relative_path, + .type_identifier = type_identifier, + .size_bytes = 123, + .sha256 = sha256, + .imported_at = imported_at, + .collected_at = collected_at, + .source = source, + .description = description, + .integrity_status = EVIDENCE_INTEGRITY_STATUS_VALID + }; + + EvidenceRecord *evidence_record = NULL; + GError *error = NULL; + + evidence_record = + test_evidence_record_create( + &input, + &error + ); + + g_assert_no_error( + error + ); + + g_assert_nonnull( + evidence_record + ); + + identifier[0] = 'X'; + original_name[0] = 'X'; + internal_name[0] = 'X'; + relative_path[0] = 'X'; + type_identifier[0] = 'X'; + sha256[0] = 'f'; + imported_at[0] = 'X'; + collected_at[0] = 'X'; + source[0] = 'X'; + description[0] = 'X'; + + g_assert_cmpstr( + evidence_record_get_identifier( + evidence_record + ), + ==, + TEST_EVIDENCE_IDENTIFIER + ); + + g_assert_cmpstr( + evidence_record_get_original_name( + evidence_record + ), + ==, + "capture_originale.png" + ); + + g_assert_cmpstr( + evidence_record_get_internal_name( + evidence_record + ), + ==, + "preuve_001.png" + ); + + g_assert_cmpstr( + evidence_record_get_relative_path( + evidence_record + ), + ==, + "01_Preuves_Originales/Captures_Ecran/preuve_001.png" + ); + + g_assert_cmpstr( + evidence_record_get_type_identifier( + evidence_record + ), + ==, + "image" + ); + + g_assert_cmpstr( + evidence_record_get_sha256( + evidence_record + ), + ==, + TEST_EVIDENCE_SHA256_LOWER + ); + + g_assert_cmpstr( + evidence_record_get_imported_at( + evidence_record + ), + ==, + "2026-07-18T14:30:00Z" + ); + + g_assert_cmpstr( + evidence_record_get_collected_at( + evidence_record + ), + ==, + "2026-07-17T20:15:30Z" + ); + + g_assert_cmpstr( + evidence_record_get_source( + evidence_record + ), + ==, + "Conversation Instagram" + ); + + g_assert_cmpstr( + evidence_record_get_description( + evidence_record + ), + ==, + "Capture originale" + ); + + evidence_record_free( + evidence_record + ); +} + +static void test_evidence_record_trims_text(void) +{ + TestEvidenceRecordInput input = + test_evidence_record_valid_input(); + + EvidenceRecord *evidence_record = NULL; + GError *error = NULL; + + input.identifier = + " 6e62b9af-2046-4efd-b3b9-29869f816951 "; + + input.original_name = + " capture.png "; + + input.internal_name = + "\tpreuve.png\t"; + + input.relative_path = + " 01_Preuves_Originales/preuve.png "; + + input.type_identifier = + " image "; + + input.sha256 = + " 0123456789ABCDEF0123456789ABCDEF" + "0123456789ABCDEF0123456789ABCDEF "; + + input.imported_at = + " 2026-07-18T14:30:00Z "; + + input.collected_at = + "\t2026-07-17T20:15:30Z\t"; + + input.source = + " Téléchargement manuel "; + + input.description = + " Capture principale "; + + evidence_record = + test_evidence_record_create( + &input, + &error + ); + + g_assert_no_error( + error + ); + + g_assert_nonnull( + evidence_record + ); + + g_assert_cmpstr( + evidence_record_get_identifier( + evidence_record + ), + ==, + TEST_EVIDENCE_IDENTIFIER + ); + + g_assert_cmpstr( + evidence_record_get_original_name( + evidence_record + ), + ==, + "capture.png" + ); + + g_assert_cmpstr( + evidence_record_get_internal_name( + evidence_record + ), + ==, + "preuve.png" + ); + + g_assert_cmpstr( + evidence_record_get_relative_path( + evidence_record + ), + ==, + "01_Preuves_Originales/preuve.png" + ); + + g_assert_cmpstr( + evidence_record_get_type_identifier( + evidence_record + ), + ==, + "image" + ); + + g_assert_cmpstr( + evidence_record_get_sha256( + evidence_record + ), + ==, + TEST_EVIDENCE_SHA256_LOWER + ); + + g_assert_cmpstr( + evidence_record_get_source( + evidence_record + ), + ==, + "Téléchargement manuel" + ); + + g_assert_cmpstr( + evidence_record_get_description( + evidence_record + ), + ==, + "Capture principale" + ); + + evidence_record_free( + evidence_record + ); +} + +static void test_evidence_record_invalid_uuid(void) +{ + TestEvidenceRecordInput input = + test_evidence_record_valid_input(); + + input.identifier = + "identifiant-invalide"; + + test_evidence_record_assert_invalid( + &input, + EVIDENCE_RECORD_ERROR_INVALID_IDENTIFIER + ); +} + +static void test_evidence_record_invalid_original_name(void) +{ + TestEvidenceRecordInput input = + test_evidence_record_valid_input(); + + input.original_name = + "dossier/capture.png"; + + test_evidence_record_assert_invalid( + &input, + EVIDENCE_RECORD_ERROR_INVALID_NAME + ); +} + +static void test_evidence_record_invalid_internal_name(void) +{ + TestEvidenceRecordInput input = + test_evidence_record_valid_input(); + + input.internal_name = + ".."; + + test_evidence_record_assert_invalid( + &input, + EVIDENCE_RECORD_ERROR_INVALID_NAME + ); +} + +static void test_evidence_record_absolute_path(void) +{ + TestEvidenceRecordInput input = + test_evidence_record_valid_input(); + + input.relative_path = + "/tmp/preuve.png"; + + test_evidence_record_assert_invalid( + &input, + EVIDENCE_RECORD_ERROR_INVALID_PATH + ); +} + +static void test_evidence_record_parent_component(void) +{ + TestEvidenceRecordInput input = + test_evidence_record_valid_input(); + + input.relative_path = + "01_Preuves_Originales/../preuve.png"; + + test_evidence_record_assert_invalid( + &input, + EVIDENCE_RECORD_ERROR_INVALID_PATH + ); +} + +static void test_evidence_record_relative_path(void) +{ + TestEvidenceRecordInput input = + test_evidence_record_valid_input(); + + EvidenceRecord *evidence_record = NULL; + GError *error = NULL; + + input.relative_path = + "01_Preuves_Originales/Documents/preuve.pdf"; + + evidence_record = + test_evidence_record_create( + &input, + &error + ); + + g_assert_no_error( + error + ); + + g_assert_nonnull( + evidence_record + ); + + g_assert_cmpstr( + evidence_record_get_relative_path( + evidence_record + ), + ==, + "01_Preuves_Originales/Documents/preuve.pdf" + ); + + evidence_record_free( + evidence_record + ); +} + +static void test_evidence_record_empty_type(void) +{ + TestEvidenceRecordInput input = + test_evidence_record_valid_input(); + + input.type_identifier = + " "; + + test_evidence_record_assert_invalid( + &input, + EVIDENCE_RECORD_ERROR_INVALID_TYPE + ); +} + +static void test_evidence_record_short_sha256(void) +{ + TestEvidenceRecordInput input = + test_evidence_record_valid_input(); + + input.sha256 = + "0123456789abcdef"; + + test_evidence_record_assert_invalid( + &input, + EVIDENCE_RECORD_ERROR_INVALID_SHA256 + ); +} + +static void test_evidence_record_invalid_sha256_character(void) +{ + TestEvidenceRecordInput input = + test_evidence_record_valid_input(); + + char invalid_sha256[] = + TEST_EVIDENCE_SHA256_LOWER; + + invalid_sha256[63] = 'g'; + + input.sha256 = + invalid_sha256; + + test_evidence_record_assert_invalid( + &input, + EVIDENCE_RECORD_ERROR_INVALID_SHA256 + ); +} + +static void test_evidence_record_uppercase_sha256(void) +{ + TestEvidenceRecordInput input = + test_evidence_record_valid_input(); + + EvidenceRecord *evidence_record = NULL; + GError *error = NULL; + + input.sha256 = + TEST_EVIDENCE_SHA256_UPPER; + + evidence_record = + test_evidence_record_create( + &input, + &error + ); + + g_assert_no_error( + error + ); + + g_assert_nonnull( + evidence_record + ); + + g_assert_cmpstr( + evidence_record_get_sha256( + evidence_record + ), + ==, + TEST_EVIDENCE_SHA256_LOWER + ); + + evidence_record_free( + evidence_record + ); +} + +static void test_evidence_record_invalid_imported_at(void) +{ + TestEvidenceRecordInput input = + test_evidence_record_valid_input(); + + input.imported_at = + "2026-07-18 14:30:00Z"; + + test_evidence_record_assert_invalid( + &input, + EVIDENCE_RECORD_ERROR_INVALID_DATE + ); +} + +static void test_evidence_record_invalid_collected_at(void) +{ + TestEvidenceRecordInput input = + test_evidence_record_valid_input(); + + input.collected_at = + "2026-02-30T14:30:00Z"; + + test_evidence_record_assert_invalid( + &input, + EVIDENCE_RECORD_ERROR_INVALID_DATE + ); +} + +static void test_evidence_record_invalid_status(void) +{ + TestEvidenceRecordInput input = + test_evidence_record_valid_input(); + + input.integrity_status = + (EvidenceIntegrityStatus) 999; + + test_evidence_record_assert_invalid( + &input, + EVIDENCE_RECORD_ERROR_INVALID_STATUS + ); +} + +static void test_evidence_record_zero_size(void) +{ + TestEvidenceRecordInput input = + test_evidence_record_valid_input(); + + EvidenceRecord *evidence_record = NULL; + GError *error = NULL; + + input.size_bytes = 0; + + evidence_record = + test_evidence_record_create( + &input, + &error + ); + + g_assert_no_error( + error + ); + + g_assert_nonnull( + evidence_record + ); + + g_assert_cmpuint( + evidence_record_get_size_bytes( + evidence_record + ), + ==, + 0 + ); + + evidence_record_free( + evidence_record + ); +} + +static void test_evidence_record_null_accessors(void) +{ + g_assert_null( + evidence_record_get_identifier( + NULL + ) + ); + + g_assert_null( + evidence_record_get_original_name( + NULL + ) + ); + + g_assert_null( + evidence_record_get_internal_name( + NULL + ) + ); + + g_assert_null( + evidence_record_get_relative_path( + NULL + ) + ); + + g_assert_null( + evidence_record_get_type_identifier( + NULL + ) + ); + + g_assert_cmpuint( + evidence_record_get_size_bytes( + NULL + ), + ==, + 0 + ); + + g_assert_null( + evidence_record_get_sha256( + NULL + ) + ); + + g_assert_null( + evidence_record_get_imported_at( + NULL + ) + ); + + g_assert_null( + evidence_record_get_collected_at( + NULL + ) + ); + + g_assert_null( + evidence_record_get_source( + NULL + ) + ); + + g_assert_null( + evidence_record_get_description( + NULL + ) + ); + + g_assert_cmpint( + evidence_record_get_integrity_status( + NULL + ), + ==, + EVIDENCE_INTEGRITY_STATUS_UNKNOWN + ); +} + +static void test_evidence_record_free_null(void) +{ + evidence_record_free( + NULL + ); +} + +static void test_evidence_record_optional_error(void) +{ + TestEvidenceRecordInput input = + test_evidence_record_valid_input(); + + EvidenceRecord *evidence_record = NULL; + + input.identifier = + "uuid-invalide"; + + evidence_record = + test_evidence_record_create( + &input, + NULL + ); + + g_assert_null( + evidence_record + ); + + input = + test_evidence_record_valid_input(); + + evidence_record = + test_evidence_record_create( + &input, + NULL + ); + + g_assert_nonnull( + evidence_record + ); + + evidence_record_free( + evidence_record + ); +} + +int main( + int argc, + char **argv +) +{ + g_test_init( + &argc, + &argv, + NULL + ); + + g_test_add_func( + "/evidence_record/valid_full", + test_evidence_record_valid_full + ); + + g_test_add_func( + "/evidence_record/valid_optional_null", + test_evidence_record_valid_optional_null + ); + + g_test_add_func( + "/evidence_record/copies_strings", + test_evidence_record_copies_strings + ); + + g_test_add_func( + "/evidence_record/trims_text", + test_evidence_record_trims_text + ); + + g_test_add_func( + "/evidence_record/invalid_uuid", + test_evidence_record_invalid_uuid + ); + + g_test_add_func( + "/evidence_record/invalid_original_name", + test_evidence_record_invalid_original_name + ); + + g_test_add_func( + "/evidence_record/invalid_internal_name", + test_evidence_record_invalid_internal_name + ); + + g_test_add_func( + "/evidence_record/absolute_path", + test_evidence_record_absolute_path + ); + + g_test_add_func( + "/evidence_record/parent_component", + test_evidence_record_parent_component + ); + + g_test_add_func( + "/evidence_record/relative_path", + test_evidence_record_relative_path + ); + + g_test_add_func( + "/evidence_record/empty_type", + test_evidence_record_empty_type + ); + + g_test_add_func( + "/evidence_record/short_sha256", + test_evidence_record_short_sha256 + ); + + g_test_add_func( + "/evidence_record/invalid_sha256_character", + test_evidence_record_invalid_sha256_character + ); + + g_test_add_func( + "/evidence_record/uppercase_sha256", + test_evidence_record_uppercase_sha256 + ); + + g_test_add_func( + "/evidence_record/invalid_imported_at", + test_evidence_record_invalid_imported_at + ); + + g_test_add_func( + "/evidence_record/invalid_collected_at", + test_evidence_record_invalid_collected_at + ); + + g_test_add_func( + "/evidence_record/invalid_status", + test_evidence_record_invalid_status + ); + + g_test_add_func( + "/evidence_record/zero_size", + test_evidence_record_zero_size + ); + + g_test_add_func( + "/evidence_record/null_accessors", + test_evidence_record_null_accessors + ); + + g_test_add_func( + "/evidence_record/free_null", + test_evidence_record_free_null + ); + + g_test_add_func( + "/evidence_record/optional_error", + test_evidence_record_optional_error + ); + + return g_test_run(); +} +