mirror of
https://github.com/aaru-dps/libaaruformat.git
synced 2026-04-05 21:51:03 +00:00
Merge pull request #32 from RomTholos/fix/verify-v1-lzma-crc
Fix v1 image verification and add aaruf_verify_image tests
This commit is contained in:
22
src/verify.c
22
src/verify.c
@@ -259,11 +259,13 @@ AARU_EXPORT int32_t AARU_CALL aaruf_verify_image(void *context)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// For LZMA compression, skip the 5-byte properties header
|
||||
// For LZMA compression in v2+, skip the 5-byte properties header.
|
||||
// V1 images included LZMA properties in cmpCrc64; v2+ excludes them.
|
||||
crc_length = block_header.cmpLength;
|
||||
if(block_header.compression == kCompressionLzma || block_header.compression == kCompressionLzmaCst)
|
||||
if((block_header.compression == kCompressionLzma || block_header.compression == kCompressionLzmaCst) &&
|
||||
ctx->header.imageMajorVersion > AARUF_VERSION_V1)
|
||||
{
|
||||
// Skip LZMA properties
|
||||
// Skip LZMA properties (v2+ only)
|
||||
uint8_t props[LZMA_PROPERTIES_LENGTH];
|
||||
size_t read_props = fread(props, 1, LZMA_PROPERTIES_LENGTH, ctx->imageStream);
|
||||
if(read_props != LZMA_PROPERTIES_LENGTH)
|
||||
@@ -315,11 +317,13 @@ AARU_EXPORT int32_t AARU_CALL aaruf_verify_image(void *context)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// For LZMA compression, skip the 5-byte properties header
|
||||
// For LZMA compression in v2+, skip the 5-byte properties header.
|
||||
// V1 images included LZMA properties in cmpCrc64; v2+ excludes them.
|
||||
crc_length = ddt_header.cmpLength;
|
||||
if(ddt_header.compression == kCompressionLzma || ddt_header.compression == kCompressionLzmaCst)
|
||||
if((ddt_header.compression == kCompressionLzma || ddt_header.compression == kCompressionLzmaCst) &&
|
||||
ctx->header.imageMajorVersion > AARUF_VERSION_V1)
|
||||
{
|
||||
// Skip LZMA properties
|
||||
// Skip LZMA properties (v2+ only)
|
||||
uint8_t props[LZMA_PROPERTIES_LENGTH];
|
||||
size_t read_props = fread(props, 1, LZMA_PROPERTIES_LENGTH, ctx->imageStream);
|
||||
if(read_props != LZMA_PROPERTIES_LENGTH)
|
||||
@@ -332,7 +336,7 @@ AARU_EXPORT int32_t AARU_CALL aaruf_verify_image(void *context)
|
||||
}
|
||||
|
||||
status = update_crc64_from_stream(ctx->imageStream, crc_length, buffer, VERIFY_SIZE, crc64_context,
|
||||
"data block");
|
||||
"DDT block");
|
||||
if(status != AARUF_STATUS_OK) goto cleanup;
|
||||
|
||||
if(aaruf_crc64_final(crc64_context, &crc64) != 0)
|
||||
@@ -371,7 +375,7 @@ AARU_EXPORT int32_t AARU_CALL aaruf_verify_image(void *context)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// For LZMA compression, skip the 5-byte properties header
|
||||
// DDT2 only appears in v2+ images, so always skip LZMA properties.
|
||||
crc_length = ddt2_header.cmpLength;
|
||||
if(ddt2_header.compression == kCompressionLzma || ddt2_header.compression == kCompressionLzmaCst)
|
||||
{
|
||||
@@ -388,7 +392,7 @@ AARU_EXPORT int32_t AARU_CALL aaruf_verify_image(void *context)
|
||||
}
|
||||
|
||||
status = update_crc64_from_stream(ctx->imageStream, crc_length, buffer, VERIFY_SIZE, crc64_context,
|
||||
"data block");
|
||||
"DDT2 block");
|
||||
if(status != AARUF_STATUS_OK) goto cleanup;
|
||||
|
||||
if(aaruf_crc64_final(crc64_context, &crc64) != 0)
|
||||
|
||||
@@ -90,6 +90,7 @@ add_executable(tests_run
|
||||
sha1.cpp
|
||||
open_image.cpp
|
||||
create_image.cpp
|
||||
verify_image.cpp
|
||||
ps3_aes.cpp
|
||||
ps3_crypto.cpp
|
||||
ps3_encryption_map.cpp
|
||||
|
||||
337
tests/verify_image.cpp
Normal file
337
tests/verify_image.cpp
Normal file
@@ -0,0 +1,337 @@
|
||||
/*
|
||||
* This file is part of the Aaru Data Preservation Suite.
|
||||
* Copyright (c) 2019-2026 Natalia Portillo.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as
|
||||
* published by the Free Software Foundation; either version 2.1 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <climits>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include "../include/aaruformat.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
static bool copy_file(const char *src, const char *dst)
|
||||
{
|
||||
FILE *in = fopen(src, "rb");
|
||||
if(!in) return false;
|
||||
|
||||
fseek(in, 0, SEEK_END);
|
||||
long size = ftell(in);
|
||||
fseek(in, 0, SEEK_SET);
|
||||
|
||||
auto *buf = static_cast<uint8_t *>(malloc(size));
|
||||
if(!buf)
|
||||
{
|
||||
fclose(in);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t read = fread(buf, 1, size, in);
|
||||
fclose(in);
|
||||
if(read != static_cast<size_t>(size))
|
||||
{
|
||||
free(buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
FILE *out = fopen(dst, "wb");
|
||||
if(!out)
|
||||
{
|
||||
free(buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t written = fwrite(buf, 1, size, out);
|
||||
fclose(out);
|
||||
free(buf);
|
||||
|
||||
return written == static_cast<size_t>(size);
|
||||
}
|
||||
|
||||
static bool corrupt_bytes(const char *filepath, long offset, const uint8_t *garbage, size_t len)
|
||||
{
|
||||
FILE *f = fopen(filepath, "r+b");
|
||||
if(!f) return false;
|
||||
|
||||
if(fseek(f, offset, SEEK_SET) != 0)
|
||||
{
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t written = fwrite(garbage, 1, len, f);
|
||||
fclose(f);
|
||||
return written == len;
|
||||
}
|
||||
|
||||
class VerifyImageFixture : public testing::Test
|
||||
{
|
||||
};
|
||||
|
||||
// ==================== A. Happy path — v1 fixtures ====================
|
||||
// V1 images use LZMA props in CRC + byte-swap.
|
||||
|
||||
TEST_F(VerifyImageFixture, verify_floptical_v1)
|
||||
{
|
||||
char path[PATH_MAX], filename[PATH_MAX];
|
||||
getcwd(path, PATH_MAX);
|
||||
snprintf(filename, PATH_MAX, "%s/data/floptical_v1.aif", path);
|
||||
|
||||
void *context = aaruf_open(filename, false, NULL);
|
||||
ASSERT_NE(context, nullptr) << "Failed to open floptical_v1.aif";
|
||||
|
||||
EXPECT_EQ(aaruf_verify_image(context), AARUF_STATUS_OK);
|
||||
EXPECT_EQ(aaruf_close(context), AARUF_STATUS_OK);
|
||||
}
|
||||
|
||||
TEST_F(VerifyImageFixture, verify_mf2hd_v1)
|
||||
{
|
||||
char path[PATH_MAX], filename[PATH_MAX];
|
||||
getcwd(path, PATH_MAX);
|
||||
snprintf(filename, PATH_MAX, "%s/data/mf2hd_v1.aif", path);
|
||||
|
||||
void *context = aaruf_open(filename, false, NULL);
|
||||
ASSERT_NE(context, nullptr) << "Failed to open mf2hd_v1.aif";
|
||||
|
||||
EXPECT_EQ(aaruf_verify_image(context), AARUF_STATUS_OK);
|
||||
EXPECT_EQ(aaruf_close(context), AARUF_STATUS_OK);
|
||||
}
|
||||
|
||||
TEST_F(VerifyImageFixture, verify_cdmode1_v1)
|
||||
{
|
||||
char path[PATH_MAX], filename[PATH_MAX];
|
||||
getcwd(path, PATH_MAX);
|
||||
snprintf(filename, PATH_MAX, "%s/data/cdmode1_v1.aif", path);
|
||||
|
||||
void *context = aaruf_open(filename, false, NULL);
|
||||
ASSERT_NE(context, nullptr) << "Failed to open cdmode1_v1.aif";
|
||||
|
||||
EXPECT_EQ(aaruf_verify_image(context), AARUF_STATUS_OK);
|
||||
EXPECT_EQ(aaruf_close(context), AARUF_STATUS_OK);
|
||||
}
|
||||
|
||||
// ==================== A. Happy path — v2 fixtures ====================
|
||||
// V2 images exclude LZMA props from CRC, no byte-swap.
|
||||
|
||||
TEST_F(VerifyImageFixture, verify_floptical_v2)
|
||||
{
|
||||
char path[PATH_MAX], filename[PATH_MAX];
|
||||
getcwd(path, PATH_MAX);
|
||||
snprintf(filename, PATH_MAX, "%s/data/floptical.aif", path);
|
||||
|
||||
void *context = aaruf_open(filename, false, NULL);
|
||||
ASSERT_NE(context, nullptr) << "Failed to open floptical.aif";
|
||||
|
||||
EXPECT_EQ(aaruf_verify_image(context), AARUF_STATUS_OK);
|
||||
EXPECT_EQ(aaruf_close(context), AARUF_STATUS_OK);
|
||||
}
|
||||
|
||||
TEST_F(VerifyImageFixture, verify_mf2hd_v2)
|
||||
{
|
||||
char path[PATH_MAX], filename[PATH_MAX];
|
||||
getcwd(path, PATH_MAX);
|
||||
snprintf(filename, PATH_MAX, "%s/data/mf2hd.aif", path);
|
||||
|
||||
void *context = aaruf_open(filename, false, NULL);
|
||||
ASSERT_NE(context, nullptr) << "Failed to open mf2hd.aif";
|
||||
|
||||
EXPECT_EQ(aaruf_verify_image(context), AARUF_STATUS_OK);
|
||||
EXPECT_EQ(aaruf_close(context), AARUF_STATUS_OK);
|
||||
}
|
||||
|
||||
TEST_F(VerifyImageFixture, verify_cdmode1_v2)
|
||||
{
|
||||
char path[PATH_MAX], filename[PATH_MAX];
|
||||
getcwd(path, PATH_MAX);
|
||||
snprintf(filename, PATH_MAX, "%s/data/cdmode1.aif", path);
|
||||
|
||||
void *context = aaruf_open(filename, false, NULL);
|
||||
ASSERT_NE(context, nullptr) << "Failed to open cdmode1.aif";
|
||||
|
||||
EXPECT_EQ(aaruf_verify_image(context), AARUF_STATUS_OK);
|
||||
EXPECT_EQ(aaruf_close(context), AARUF_STATUS_OK);
|
||||
}
|
||||
|
||||
// ==================== B. Invalid context ====================
|
||||
|
||||
TEST_F(VerifyImageFixture, verify_null_context) { EXPECT_EQ(aaruf_verify_image(NULL), AARUF_ERROR_NOT_AARUFORMAT); }
|
||||
|
||||
TEST_F(VerifyImageFixture, verify_garbage_context)
|
||||
{
|
||||
// 256 bytes of 0xFF — magic won't match AARU_MAGIC
|
||||
uint8_t garbage[256];
|
||||
memset(garbage, 0xFF, sizeof(garbage));
|
||||
|
||||
EXPECT_EQ(aaruf_verify_image(garbage), AARUF_ERROR_NOT_AARUFORMAT);
|
||||
}
|
||||
|
||||
// ==================== C. Data corruption detection ====================
|
||||
|
||||
TEST_F(VerifyImageFixture, verify_corrupt_data_v1)
|
||||
{
|
||||
char path[PATH_MAX], src[PATH_MAX], dst[PATH_MAX];
|
||||
getcwd(path, PATH_MAX);
|
||||
snprintf(src, PATH_MAX, "%s/data/floptical_v1.aif", path);
|
||||
snprintf(dst, PATH_MAX, "%s/verify_corrupt_data_v1.aif", path);
|
||||
|
||||
ASSERT_TRUE(copy_file(src, dst)) << "Failed to copy fixture";
|
||||
|
||||
// First DBLK at offset 104, BlockHeader 36 bytes, LZMA payload starts at 140.
|
||||
// V1 CRC covers full cmpLength (19 bytes) including LZMA props. Corrupt at 145.
|
||||
uint8_t garbage[4] = {0xDE, 0xAD, 0xBE, 0xEF};
|
||||
ASSERT_TRUE(corrupt_bytes(dst, 145, garbage, sizeof(garbage)));
|
||||
|
||||
void *context = aaruf_open(dst, false, NULL);
|
||||
ASSERT_NE(context, nullptr) << "aaruf_open should succeed on corrupted data (lenient open)";
|
||||
|
||||
EXPECT_EQ(aaruf_verify_image(context), AARUF_ERROR_INVALID_BLOCK_CRC);
|
||||
|
||||
aaruf_close(context);
|
||||
remove(dst);
|
||||
}
|
||||
|
||||
TEST_F(VerifyImageFixture, verify_corrupt_data_v2)
|
||||
{
|
||||
char path[PATH_MAX], src[PATH_MAX], dst[PATH_MAX];
|
||||
getcwd(path, PATH_MAX);
|
||||
snprintf(src, PATH_MAX, "%s/data/floptical.aif", path);
|
||||
snprintf(dst, PATH_MAX, "%s/verify_corrupt_data_v2.aif", path);
|
||||
|
||||
ASSERT_TRUE(copy_file(src, dst)) << "Failed to copy fixture";
|
||||
|
||||
// DBLK at 512, payload at 548, LZMA props 548-552, CRC region 553-561 (9 bytes).
|
||||
// Corrupt 2 bytes within the CRC region.
|
||||
uint8_t garbage[2] = {0xDE, 0xAD};
|
||||
ASSERT_TRUE(corrupt_bytes(dst, 555, garbage, sizeof(garbage)));
|
||||
|
||||
void *context = aaruf_open(dst, false, NULL);
|
||||
ASSERT_NE(context, nullptr) << "aaruf_open should succeed on corrupted data (lenient open)";
|
||||
|
||||
EXPECT_EQ(aaruf_verify_image(context), AARUF_ERROR_INVALID_BLOCK_CRC);
|
||||
|
||||
aaruf_close(context);
|
||||
remove(dst);
|
||||
}
|
||||
|
||||
TEST_F(VerifyImageFixture, verify_corrupt_tracks_block)
|
||||
{
|
||||
char path[PATH_MAX], src[PATH_MAX], dst[PATH_MAX];
|
||||
getcwd(path, PATH_MAX);
|
||||
snprintf(src, PATH_MAX, "%s/data/cdmode1_v1.aif", path);
|
||||
snprintf(dst, PATH_MAX, "%s/verify_corrupt_tracks.aif", path);
|
||||
|
||||
ASSERT_TRUE(copy_file(src, dst)) << "Failed to copy fixture";
|
||||
|
||||
// TracksBlock at offset 4407206. TracksHeader is 14 bytes (packed).
|
||||
// TrackEntry array starts at 4407220. Corrupt bytes in the track data.
|
||||
uint8_t garbage[4] = {0xDE, 0xAD, 0xBE, 0xEF};
|
||||
ASSERT_TRUE(corrupt_bytes(dst, 4407224, garbage, sizeof(garbage)));
|
||||
|
||||
void *context = aaruf_open(dst, false, NULL);
|
||||
ASSERT_NE(context, nullptr) << "aaruf_open should succeed on corrupted tracks data";
|
||||
|
||||
EXPECT_EQ(aaruf_verify_image(context), AARUF_ERROR_INVALID_BLOCK_CRC);
|
||||
|
||||
aaruf_close(context);
|
||||
remove(dst);
|
||||
}
|
||||
|
||||
// ==================== D. Index corruption (in-memory) ====================
|
||||
|
||||
TEST_F(VerifyImageFixture, verify_invalid_index_offset)
|
||||
{
|
||||
char path[PATH_MAX], filename[PATH_MAX];
|
||||
getcwd(path, PATH_MAX);
|
||||
snprintf(filename, PATH_MAX, "%s/data/floptical_v1.aif", path);
|
||||
|
||||
void *context = aaruf_open(filename, false, NULL);
|
||||
ASSERT_NE(context, nullptr);
|
||||
|
||||
// Manipulate the in-memory index offset to point beyond the file
|
||||
auto *ctx = static_cast<aaruformat_context *>(context);
|
||||
ctx->header.indexOffset = 0xFFFFFFFF00ULL;
|
||||
|
||||
EXPECT_EQ(aaruf_verify_image(context), AARUF_ERROR_CANNOT_READ_HEADER);
|
||||
|
||||
aaruf_close(context);
|
||||
}
|
||||
|
||||
TEST_F(VerifyImageFixture, verify_bad_index_signature)
|
||||
{
|
||||
char path[PATH_MAX], filename[PATH_MAX];
|
||||
getcwd(path, PATH_MAX);
|
||||
snprintf(filename, PATH_MAX, "%s/data/floptical_v1.aif", path);
|
||||
|
||||
void *context = aaruf_open(filename, false, NULL);
|
||||
ASSERT_NE(context, nullptr);
|
||||
|
||||
// Point index offset to the middle of a DataBlock payload — the 4 bytes
|
||||
// there will be LZMA data, not a valid index signature
|
||||
auto *ctx = static_cast<aaruformat_context *>(context);
|
||||
ctx->header.indexOffset = 200;
|
||||
|
||||
EXPECT_EQ(aaruf_verify_image(context), AARUF_ERROR_CANNOT_READ_INDEX);
|
||||
|
||||
aaruf_close(context);
|
||||
}
|
||||
|
||||
// ==================== E. Programmatic round-trip ====================
|
||||
|
||||
TEST_F(VerifyImageFixture, verify_created_image)
|
||||
{
|
||||
char path[PATH_MAX], random_file[PATH_MAX], test_file[PATH_MAX];
|
||||
getcwd(path, PATH_MAX);
|
||||
snprintf(random_file, PATH_MAX, "%s/data/random", path);
|
||||
snprintf(test_file, PATH_MAX, "%s/verify_roundtrip.aif", path);
|
||||
|
||||
FILE *f = fopen(random_file, "rb");
|
||||
ASSERT_NE(f, nullptr) << "Failed to open random data file";
|
||||
|
||||
auto *buffer = static_cast<uint8_t *>(malloc(1048576));
|
||||
ASSERT_NE(buffer, nullptr);
|
||||
fread(buffer, 1, 1048576, f);
|
||||
fclose(f);
|
||||
|
||||
constexpr size_t total_sectors = 2048;
|
||||
constexpr size_t sector_size = 512;
|
||||
|
||||
void *context = aaruf_create(test_file, 1, sector_size, total_sectors, 0, 0, "deduplicate=true;compress=true",
|
||||
reinterpret_cast<const uint8_t *>("gtest"), 5, 0, 0, false);
|
||||
ASSERT_NE(context, nullptr) << "Failed to create test image";
|
||||
|
||||
for(size_t sector = 0; sector < total_sectors; ++sector)
|
||||
{
|
||||
const size_t offset = sector * sector_size % 1048576;
|
||||
const int32_t result =
|
||||
aaruf_write_sector(context, sector, false, buffer + offset, SectorStatusDumped, sector_size);
|
||||
ASSERT_EQ(result, AARUF_STATUS_OK) << "Failed to write sector " << sector;
|
||||
}
|
||||
|
||||
ASSERT_EQ(aaruf_close(context), AARUF_STATUS_OK) << "Failed to close image";
|
||||
free(buffer);
|
||||
|
||||
// Reopen and verify
|
||||
context = aaruf_open(test_file, false, NULL);
|
||||
ASSERT_NE(context, nullptr) << "Failed to reopen test image";
|
||||
|
||||
EXPECT_EQ(aaruf_verify_image(context), AARUF_STATUS_OK);
|
||||
|
||||
aaruf_close(context);
|
||||
remove(test_file);
|
||||
}
|
||||
Reference in New Issue
Block a user