Loading...
Searching...
No Matches
verify.c
Go to the documentation of this file.
/*
* 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 <aaruformat.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include "internal.h"
#include "log.h"
#include "utarray.h"
#define VERIFY_SIZE 1048576
static int32_t update_crc64_from_stream(FILE *stream, const uint64_t total_length, void *buffer, size_t buffer_size,
crc64_ctx *crc_ctx, const char *label)
{
uint64_t processed = 0;
while(processed < total_length)
{
size_t chunk = buffer_size;
uint64_t remaining = total_length - processed;
if(remaining < chunk) chunk = (size_t)remaining;
if(chunk == 0) break;
size_t read_bytes = fread(buffer, 1, chunk, stream);
if(read_bytes != chunk)
{
FATAL("Could not read %s chunk (expected %zu bytes, got %zu)", label, chunk, read_bytes);
return AARUF_ERROR_CANNOT_READ_BLOCK;
}
aaruf_crc64_update(crc_ctx, buffer, read_bytes);
processed += read_bytes;
}
return AARUF_STATUS_OK;
}
AARU_EXPORT int32_t AARU_CALL aaruf_verify_image(void *context)
{
TRACE("Entering aaruf_verify_image(%p)", context);
aaruformat_context *ctx = NULL;
uint64_t crc64 = 0;
size_t read_bytes = 0;
void *buffer = NULL;
crc64_ctx *crc64_context = NULL;
BlockHeader block_header;
DdtHeader ddt_header;
DdtHeader2 ddt2_header;
TracksHeader tracks_header;
uint32_t signature = 0;
UT_array *index_entries = NULL;
int32_t status = AARUF_STATUS_OK;
if(context == NULL)
{
FATAL("Invalid context");
status = AARUF_ERROR_NOT_AARUFORMAT;
goto cleanup;
}
ctx = context;
if(ctx->magic != AARU_MAGIC)
{
FATAL("Invalid context");
status = AARUF_ERROR_NOT_AARUFORMAT;
goto cleanup;
}
if(ctx->imageStream == NULL)
{
FATAL("Image stream is not available");
status = AARUF_ERROR_NOT_AARUFORMAT;
goto cleanup;
}
if(fseek(ctx->imageStream, ctx->header.indexOffset, SEEK_SET) != 0)
{
FATAL("Could not seek to index offset %" PRIu64, ctx->header.indexOffset);
status = AARUF_ERROR_CANNOT_READ_HEADER;
goto cleanup;
}
TRACE("Reading index signature at position %" PRIu64, ctx->header.indexOffset);
read_bytes = fread(&signature, 1, sizeof(signature), ctx->imageStream);
if(read_bytes != sizeof(signature))
{
FATAL("Could not read index signature");
status = AARUF_ERROR_CANNOT_READ_HEADER;
goto cleanup;
}
if(signature != IndexBlock && signature != IndexBlock2 && signature != IndexBlock3)
{
FATAL("Incorrect index signature %4.4s", (char *)&signature);
status = AARUF_ERROR_CANNOT_READ_INDEX;
goto cleanup;
}
if(signature == IndexBlock)
status = verify_index_v1(ctx);
else if(signature == IndexBlock2)
status = verify_index_v2(ctx);
else
status = verify_index_v3(ctx);
if(status != AARUF_STATUS_OK)
{
FATAL("Index verification failed with error code %d", status);
goto cleanup;
}
if(signature == IndexBlock)
index_entries = process_index_v1(ctx);
else if(signature == IndexBlock2)
index_entries = process_index_v2(ctx);
else
index_entries = process_index_v3(ctx);
if(index_entries == NULL)
{
FATAL("Could not process index");
status = AARUF_ERROR_CANNOT_READ_INDEX;
goto cleanup;
}
buffer = malloc(VERIFY_SIZE);
if(buffer == NULL)
{
FATAL("Cannot allocate memory for verification buffer");
status = AARUF_ERROR_NOT_ENOUGH_MEMORY;
goto cleanup;
}
uint64_t crc_length;
const unsigned int entry_count = utarray_len(index_entries);
for(unsigned int i = 0; i < entry_count; i++)
{
IndexEntry *entry = utarray_eltptr(index_entries, i);
TRACE("Checking block with type %4.4s at position %" PRIu64, (char *)&entry->blockType, entry->offset);
if(fseek(ctx->imageStream, entry->offset, SEEK_SET) != 0)
{
FATAL("Could not seek to block at offset %" PRIu64, entry->offset);
status = AARUF_ERROR_CANNOT_READ_BLOCK;
goto cleanup;
}
switch(entry->blockType)
{
case DataBlock:
read_bytes = fread(&block_header, 1, sizeof(BlockHeader), ctx->imageStream);
if(read_bytes != sizeof(BlockHeader))
{
FATAL("Could not read block header");
status = AARUF_ERROR_CANNOT_READ_BLOCK;
goto cleanup;
}
crc64_context = aaruf_crc64_init();
if(crc64_context == NULL)
{
FATAL("Could not initialize CRC64 context");
status = AARUF_ERROR_CANNOT_READ_BLOCK;
goto cleanup;
}
// For LZMA compression, skip the 5-byte properties header
crc_length = block_header.cmpLength;
if(block_header.compression == Lzma || block_header.compression == LzmaClauniaSubchannelTransform)
{
// Skip LZMA properties
uint8_t props[LZMA_PROPERTIES_LENGTH];
size_t read_props = fread(props, 1, LZMA_PROPERTIES_LENGTH, ctx->imageStream);
if(read_props != LZMA_PROPERTIES_LENGTH)
{
FATAL("Could not read LZMA properties");
status = AARUF_ERROR_CANNOT_READ_BLOCK;
goto cleanup;
}
crc_length -= LZMA_PROPERTIES_LENGTH;
}
status = update_crc64_from_stream(ctx->imageStream, crc_length, buffer, VERIFY_SIZE, crc64_context,
"data block");
if(status != AARUF_STATUS_OK) goto cleanup;
if(aaruf_crc64_final(crc64_context, &crc64) != 0)
{
FATAL("Could not finalize CRC64 for data block");
status = AARUF_ERROR_CANNOT_READ_BLOCK;
goto cleanup;
}
if(ctx->header.imageMajorVersion <= AARUF_VERSION_V1) crc64 = bswap_64(crc64);
if(crc64 != block_header.cmpCrc64)
{
FATAL("Expected block CRC 0x%16llX but got 0x%16llX", block_header.cmpCrc64, crc64);
status = AARUF_ERROR_INVALID_BLOCK_CRC;
goto cleanup;
}
aaruf_crc64_free(crc64_context);
crc64_context = NULL;
break;
case DeDuplicationTable:
read_bytes = fread(&ddt_header, 1, sizeof(DdtHeader), ctx->imageStream);
if(read_bytes != sizeof(DdtHeader))
{
FATAL("Could not read DDT header");
status = AARUF_ERROR_CANNOT_READ_BLOCK;
goto cleanup;
}
crc64_context = aaruf_crc64_init();
if(crc64_context == NULL)
{
FATAL("Could not initialize CRC64 context");
status = AARUF_ERROR_CANNOT_READ_BLOCK;
goto cleanup;
}
// For LZMA compression, skip the 5-byte properties header
crc_length = ddt_header.cmpLength;
if(ddt_header.compression == Lzma || ddt_header.compression == LzmaClauniaSubchannelTransform)
{
// Skip LZMA properties
uint8_t props[LZMA_PROPERTIES_LENGTH];
size_t read_props = fread(props, 1, LZMA_PROPERTIES_LENGTH, ctx->imageStream);
if(read_props != LZMA_PROPERTIES_LENGTH)
{
FATAL("Could not read LZMA properties");
status = AARUF_ERROR_CANNOT_READ_BLOCK;
goto cleanup;
}
crc_length -= LZMA_PROPERTIES_LENGTH;
}
status = update_crc64_from_stream(ctx->imageStream, crc_length, buffer, VERIFY_SIZE, crc64_context,
"data block");
if(status != AARUF_STATUS_OK) goto cleanup;
if(aaruf_crc64_final(crc64_context, &crc64) != 0)
{
FATAL("Could not finalize CRC64 for DDT block");
status = AARUF_ERROR_CANNOT_READ_BLOCK;
goto cleanup;
}
if(ctx->header.imageMajorVersion <= AARUF_VERSION_V1) crc64 = bswap_64(crc64);
if(crc64 != ddt_header.cmpCrc64)
{
FATAL("Expected DDT CRC 0x%16llX but got 0x%16llX", ddt_header.cmpCrc64, crc64);
status = AARUF_ERROR_INVALID_BLOCK_CRC;
goto cleanup;
}
aaruf_crc64_free(crc64_context);
crc64_context = NULL;
break;
case DeDuplicationTable2:
read_bytes = fread(&ddt2_header, 1, sizeof(DdtHeader2), ctx->imageStream);
if(read_bytes != sizeof(DdtHeader2))
{
FATAL("Could not read DDT2 header");
status = AARUF_ERROR_CANNOT_READ_BLOCK;
goto cleanup;
}
crc64_context = aaruf_crc64_init();
if(crc64_context == NULL)
{
FATAL("Could not initialize CRC64 context");
status = AARUF_ERROR_CANNOT_READ_BLOCK;
goto cleanup;
}
// For LZMA compression, skip the 5-byte properties header
crc_length = ddt2_header.cmpLength;
if(ddt2_header.compression == Lzma || ddt2_header.compression == LzmaClauniaSubchannelTransform)
{
// Skip LZMA properties
uint8_t props[LZMA_PROPERTIES_LENGTH];
size_t read_props = fread(props, 1, LZMA_PROPERTIES_LENGTH, ctx->imageStream);
if(read_props != LZMA_PROPERTIES_LENGTH)
{
FATAL("Could not read LZMA properties");
status = AARUF_ERROR_CANNOT_READ_BLOCK;
goto cleanup;
}
crc_length -= LZMA_PROPERTIES_LENGTH;
}
status = update_crc64_from_stream(ctx->imageStream, crc_length, buffer, VERIFY_SIZE, crc64_context,
"data block");
if(status != AARUF_STATUS_OK) goto cleanup;
if(aaruf_crc64_final(crc64_context, &crc64) != 0)
{
FATAL("Could not finalize CRC64 for DDT2 block");
status = AARUF_ERROR_CANNOT_READ_BLOCK;
goto cleanup;
}
if(crc64 != ddt2_header.cmpCrc64)
{
FATAL("Expected DDT2 CRC 0x%16llX but got 0x%16llX", ddt2_header.cmpCrc64, crc64);
status = AARUF_ERROR_INVALID_BLOCK_CRC;
goto cleanup;
}
aaruf_crc64_free(crc64_context);
crc64_context = NULL;
break;
case TracksBlock:
{
read_bytes = fread(&tracks_header, 1, sizeof(TracksHeader), ctx->imageStream);
if(read_bytes != sizeof(TracksHeader))
{
FATAL("Could not read tracks header");
status = AARUF_ERROR_CANNOT_READ_BLOCK;
goto cleanup;
}
const uint64_t tracks_bytes = (uint64_t)tracks_header.entries * sizeof(TrackEntry);
if(tracks_header.entries != 0 && tracks_bytes / sizeof(TrackEntry) != tracks_header.entries)
{
FATAL("Tracks header length overflow (entries=%u)", tracks_header.entries);
status = AARUF_ERROR_CANNOT_READ_BLOCK;
goto cleanup;
}
crc64_context = aaruf_crc64_init();
if(crc64_context == NULL)
{
FATAL("Could not initialize CRC64 context");
status = AARUF_ERROR_CANNOT_READ_BLOCK;
goto cleanup;
}
status = update_crc64_from_stream(ctx->imageStream, tracks_bytes, buffer, VERIFY_SIZE, crc64_context,
"tracks block");
if(status != AARUF_STATUS_OK) goto cleanup;
if(aaruf_crc64_final(crc64_context, &crc64) != 0)
{
FATAL("Could not finalize CRC64 for tracks block");
status = AARUF_ERROR_CANNOT_READ_BLOCK;
goto cleanup;
}
if(ctx->header.imageMajorVersion <= AARUF_VERSION_V1) crc64 = bswap_64(crc64);
if(crc64 != tracks_header.crc64)
{
FATAL("Expected tracks CRC 0x%16llX but got 0x%16llX", tracks_header.crc64, crc64);
status = AARUF_ERROR_INVALID_BLOCK_CRC;
goto cleanup;
}
aaruf_crc64_free(crc64_context);
crc64_context = NULL;
break;
}
default:
TRACE("Ignoring block type %4.4s", (char *)&entry->blockType);
break;
}
}
status = AARUF_STATUS_OK;
cleanup:
if(crc64_context != NULL)
{
aaruf_crc64_free(crc64_context);
crc64_context = NULL;
}
if(buffer != NULL)
{
free(buffer);
buffer = NULL;
}
if(index_entries != NULL)
{
utarray_free(index_entries);
index_entries = NULL;
}
TRACE("Exiting aaruf_verify_image() = %d", status);
return status;
}
aaruformat.h
LZMA_PROPERTIES_LENGTH
#define LZMA_PROPERTIES_LENGTH
Size in bytes of the fixed LZMA properties header (lc/lp/pb + dictionary size).
Definition consts.h:82
AARU_MAGIC
#define AARU_MAGIC
Magic identifier for AaruFormat container (ASCII "AARUFRMT").
Definition consts.h:64
AARUF_VERSION_V1
#define AARUF_VERSION_V1
First on‑disk version (C# implementation).
Definition consts.h:71
AARU_CALL
#define AARU_CALL
Definition decls.h:46
aaruf_crc64_update
int aaruf_crc64_update(crc64_ctx *ctx, const uint8_t *data, uint32_t len)
Updates the CRC64 context with new data.
Definition crc64.c:55
aaruf_crc64_free
void aaruf_crc64_free(crc64_ctx *ctx)
Frees a CRC64 context.
Definition crc64.c:155
aaruf_crc64_init
crc64_ctx * aaruf_crc64_init()
Initializes a CRC64 context.
Definition crc64.c:32
AARU_EXPORT
#define AARU_EXPORT
Definition decls.h:55
aaruf_crc64_final
int aaruf_crc64_final(crc64_ctx *ctx, uint64_t *crc)
Computes the final CRC64 value from the context.
Definition crc64.c:141
bswap_64
#define bswap_64(x)
Definition endian.h:81
IndexBlock3
@ IndexBlock3
Block containing the index v3.
Definition enums.h:151
DataBlock
@ DataBlock
Block containing data.
Definition enums.h:144
IndexBlock2
@ IndexBlock2
Block containing the index v2.
Definition enums.h:150
IndexBlock
@ IndexBlock
Block containing the index (v1).
Definition enums.h:149
DeDuplicationTable2
@ DeDuplicationTable2
Block containing a deduplication table v2.
Definition enums.h:146
DeDuplicationTable
@ DeDuplicationTable
Block containing a deduplication table (v1).
Definition enums.h:145
TracksBlock
@ TracksBlock
Block containing optical disc tracks.
Definition enums.h:154
Lzma
@ Lzma
LZMA compression.
Definition enums.h:34
LzmaClauniaSubchannelTransform
@ LzmaClauniaSubchannelTransform
LZMA applied to Claunia Subchannel Transform processed data.
Definition enums.h:36
AARUF_STATUS_OK
#define AARUF_STATUS_OK
Sector present and read without uncorrectable errors.
Definition errors.h:77
AARUF_ERROR_CANNOT_READ_HEADER
#define AARUF_ERROR_CANNOT_READ_HEADER
Failed to read container header.
Definition errors.h:45
AARUF_ERROR_NOT_ENOUGH_MEMORY
#define AARUF_ERROR_NOT_ENOUGH_MEMORY
Memory allocation failure (critical).
Definition errors.h:48
AARUF_ERROR_CANNOT_READ_BLOCK
#define AARUF_ERROR_CANNOT_READ_BLOCK
Generic block read failure (seek/read error).
Definition errors.h:46
AARUF_ERROR_INVALID_BLOCK_CRC
#define AARUF_ERROR_INVALID_BLOCK_CRC
CRC64 mismatch indicating corruption.
Definition errors.h:57
AARUF_ERROR_CANNOT_READ_INDEX
#define AARUF_ERROR_CANNOT_READ_INDEX
Index block unreadable / truncated / bad identifier.
Definition errors.h:43
AARUF_ERROR_NOT_AARUFORMAT
#define AARUF_ERROR_NOT_AARUFORMAT
Input file/stream failed magic or structural validation.
Definition errors.h:40
internal.h
process_index_v2
UT_array * process_index_v2(aaruformat_context *ctx)
Processes an index block (version 2) from the image stream.
Definition index_v2.c:81
process_index_v1
UT_array * process_index_v1(aaruformat_context *ctx)
Processes an index block (version 1) from the image stream.
Definition index_v1.c:79
verify_index_v1
int32_t verify_index_v1(aaruformat_context *ctx)
Verifies the integrity of an index block (version 1) in the image stream.
Definition index_v1.c:225
verify_index_v3
int32_t verify_index_v3(aaruformat_context *ctx)
Verifies the integrity of an index block (version 3) in the image stream.
Definition index_v3.c:408
verify_index_v2
int32_t verify_index_v2(aaruformat_context *ctx)
Verifies the integrity of an index block (version 2) in the image stream.
Definition index_v2.c:227
process_index_v3
UT_array * process_index_v3(aaruformat_context *ctx)
Processes an index block (version 3) from the image stream.
Definition index_v3.c:98
log.h
FATAL
#define FATAL(fmt,...)
Definition log.h:40
TRACE
#define TRACE(fmt,...)
Definition log.h:25
AaruHeaderV2::indexOffset
uint64_t indexOffset
Absolute byte offset to primary index block (MUST be > 0; 0 => corrupt/unreadable).
Definition header.h:115
AaruHeaderV2::imageMajorVersion
uint8_t imageMajorVersion
Container format major version.
Definition header.h:110
BlockHeader
Header preceding the compressed data payload of a data block (BlockType::DataBlock).
Definition data.h:71
BlockHeader::cmpLength
uint32_t cmpLength
Size in bytes of the compressed payload immediately following this header.
Definition data.h:76
BlockHeader::cmpCrc64
uint64_t cmpCrc64
CRC64-ECMA of the compressed payload (cmpLength bytes).
Definition data.h:78
BlockHeader::compression
uint16_t compression
Compression algorithm used (value from CompressionType).
Definition data.h:74
DdtHeader2
Header preceding a version 2 hierarchical deduplication table.
Definition ddt.h:142
DdtHeader2::cmpCrc64
uint64_t cmpCrc64
CRC64-ECMA of compressed table payload.
Definition ddt.h:161
DdtHeader2::compression
uint16_t compression
Compression algorithm for this table body (CompressionType).
Definition ddt.h:145
DdtHeader2::cmpLength
uint64_t cmpLength
Compressed payload size in bytes.
Definition ddt.h:159
DdtHeader
Header preceding a version 1 (flat) deduplication table body.
Definition ddt.h:66
DdtHeader::compression
uint16_t compression
Compression algorithm for the table body (CompressionType).
Definition ddt.h:69
DdtHeader::cmpLength
uint64_t cmpLength
Size in bytes of compressed entries payload.
Definition ddt.h:72
DdtHeader::cmpCrc64
uint64_t cmpCrc64
CRC64-ECMA of the compressed payload.
Definition ddt.h:74
IndexEntry
Single index entry describing a block's type, (optional) data classification, and file offset.
Definition index.h:109
IndexEntry::blockType
uint32_t blockType
Block identifier of the referenced block (value from BlockType).
Definition index.h:110
IndexEntry::offset
uint64_t offset
Absolute byte offset in the image where the referenced block header begins.
Definition index.h:112
TrackEntry
Single optical disc track descriptor (sequence, type, LBAs, session, ISRC, flags).
Definition optical.h:72
TracksHeader
Header for an optical tracks block listing track entries.
Definition optical.h:62
TracksHeader::entries
uint16_t entries
Number of TrackEntry records following this header.
Definition optical.h:64
TracksHeader::crc64
uint64_t crc64
CRC64-ECMA of the TrackEntry array (header excluded, legacy byte-swap for early versions).
Definition optical.h:65
aaruformat_context
Master context representing an open or in‑creation Aaru image.
Definition context.h:175
aaruformat_context::header
AaruHeaderV2 header
Parsed container header (v2).
Definition context.h:178
aaruformat_context::magic
uint64_t magic
File magic (AARU_MAGIC) post-open.
Definition context.h:177
aaruformat_context::imageStream
FILE * imageStream
Underlying FILE* stream (binary mode).
Definition context.h:179
crc64_ctx
Minimal ECMA-182 CRC64 incremental state container (running value only).
Definition crc64.h:56
VERIFY_SIZE
#define VERIFY_SIZE
Definition verify.c:28
aaruf_verify_image
int32_t aaruf_verify_image(void *context)
Verifies the integrity of an AaruFormat image file.
Definition verify.c:130
update_crc64_from_stream
static int32_t update_crc64_from_stream(FILE *stream, const uint64_t total_length, void *buffer, size_t buffer_size, crc64_ctx *crc_ctx, const char *label)
Definition verify.c:30