libaaruformat 1.0
Aaru Data Preservation Suite - Format Library
Loading...
Searching...
No Matches
verify.c
Go to the documentation of this file.
1/*
2 * This file is part of the Aaru Data Preservation Suite.
3 * Copyright (c) 2019-2026 Natalia Portillo.
4 *
5 * This library is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as
7 * published by the Free Software Foundation; either version 2.1 of the
8 * License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19#include <aaruformat.h>
20#include <inttypes.h>
21#include <stdio.h>
22#include <stdlib.h>
23
24#include "internal.h"
25#include "log.h"
26#include "utarray.h"
27
28#define VERIFY_SIZE 1048576
29
30static int32_t update_crc64_from_stream(FILE *stream, const uint64_t total_length, void *buffer, size_t buffer_size,
31 crc64_ctx *crc_ctx, const char *label)
32{
33 uint64_t processed = 0;
34
35 while(processed < total_length)
36 {
37 size_t chunk = buffer_size;
38 uint64_t remaining = total_length - processed;
39
40 if(remaining < chunk) chunk = (size_t)remaining;
41
42 if(chunk == 0) break;
43
44 size_t read_bytes = fread(buffer, 1, chunk, stream);
45 if(read_bytes != chunk)
46 {
47 FATAL("Could not read %s chunk (expected %zu bytes, got %zu)", label, chunk, read_bytes);
49 }
50
51 aaruf_crc64_update(crc_ctx, buffer, read_bytes);
52 processed += read_bytes;
53 }
54
55 return AARUF_STATUS_OK;
56}
57
131{
132 TRACE("Entering aaruf_verify_image(%p)", context);
133
134 aaruformat_context *ctx = NULL;
135 uint64_t crc64 = 0;
136 size_t read_bytes = 0;
137 void *buffer = NULL;
138 crc64_ctx *crc64_context = NULL;
139 BlockHeader block_header;
140 DdtHeader ddt_header;
141 DdtHeader2 ddt2_header;
142 TracksHeader tracks_header;
143 uint32_t signature = 0;
144 UT_array *index_entries = NULL;
145 int32_t status = AARUF_STATUS_OK;
146
147 if(context == NULL)
148 {
149 FATAL("Invalid context");
151 goto cleanup;
152 }
153
154 ctx = context;
155
156 if(ctx->magic != AARU_MAGIC)
157 {
158 FATAL("Invalid context");
160 goto cleanup;
161 }
162
163 if(ctx->imageStream == NULL)
164 {
165 FATAL("Image stream is not available");
167 goto cleanup;
168 }
169
170 if(aaruf_fseek(ctx->imageStream, (aaru_off_t)ctx->header.indexOffset, SEEK_SET) != 0)
171 {
172 FATAL("Could not seek to index offset %" PRIu64, ctx->header.indexOffset);
174 goto cleanup;
175 }
176
177 TRACE("Reading index signature at position %" PRIu64, ctx->header.indexOffset);
178 read_bytes = fread(&signature, 1, sizeof(signature), ctx->imageStream);
179 if(read_bytes != sizeof(signature))
180 {
181 FATAL("Could not read index signature");
183 goto cleanup;
184 }
185
186 if(signature != IndexBlock && signature != IndexBlock2 && signature != IndexBlock3)
187 {
188 FATAL("Incorrect index signature %4.4s", (char *)&signature);
190 goto cleanup;
191 }
192
193 if(signature == IndexBlock)
194 status = verify_index_v1(ctx);
195 else if(signature == IndexBlock2)
196 status = verify_index_v2(ctx);
197 else
198 status = verify_index_v3(ctx);
199
200 if(status != AARUF_STATUS_OK)
201 {
202 FATAL("Index verification failed with error code %d", status);
203 goto cleanup;
204 }
205
206 if(signature == IndexBlock)
207 index_entries = process_index_v1(ctx);
208 else if(signature == IndexBlock2)
209 index_entries = process_index_v2(ctx);
210 else
211 index_entries = process_index_v3(ctx);
212
213 if(index_entries == NULL)
214 {
215 FATAL("Could not process index");
217 goto cleanup;
218 }
219
220 buffer = malloc(VERIFY_SIZE);
221 if(buffer == NULL)
222 {
223 FATAL("Cannot allocate memory for verification buffer");
225 goto cleanup;
226 }
227
228 uint64_t crc_length;
229 const unsigned int entry_count = utarray_len(index_entries);
230 bool has_ddt_block = false;
231 bool has_flux_block = false;
232
233 for(unsigned int i = 0; i < entry_count; i++)
234 {
235 IndexEntry *entry = utarray_eltptr(index_entries, i);
236 TRACE("Checking block with type %4.4s at position %" PRIu64, (char *)&entry->blockType, entry->offset);
237
238 if(entry->blockType == DeDuplicationTable || entry->blockType == DeDuplicationTable2) has_ddt_block = true;
239 if(entry->blockType == FluxDataBlock) has_flux_block = true;
240
241 if(aaruf_fseek(ctx->imageStream, (aaru_off_t)entry->offset, SEEK_SET) != 0)
242 {
243 FATAL("Could not seek to block at offset %" PRIu64, entry->offset);
245 goto cleanup;
246 }
247
248 switch(entry->blockType)
249 {
250 case DataBlock:
251 read_bytes = fread(&block_header, 1, sizeof(BlockHeader), ctx->imageStream);
252 if(read_bytes != sizeof(BlockHeader))
253 {
254 FATAL("Could not read block header");
256 goto cleanup;
257 }
258
259 crc64_context = aaruf_crc64_init();
260 if(crc64_context == NULL)
261 {
262 FATAL("Could not initialize CRC64 context");
264 goto cleanup;
265 }
266
267 // For LZMA compression in v2+, skip the 5-byte properties header.
268 // V1 images included LZMA properties in cmpCrc64; v2+ excludes them.
269 crc_length = block_header.cmpLength;
270 if((block_header.compression == kCompressionLzma || block_header.compression == kCompressionLzmaCst) &&
272 {
273 // Skip LZMA properties (v2+ only)
274 uint8_t props[LZMA_PROPERTIES_LENGTH];
275 size_t read_props = fread(props, 1, LZMA_PROPERTIES_LENGTH, ctx->imageStream);
276 if(read_props != LZMA_PROPERTIES_LENGTH)
277 {
278 FATAL("Could not read LZMA properties");
280 goto cleanup;
281 }
282 crc_length -= LZMA_PROPERTIES_LENGTH;
283 }
284
285 status = update_crc64_from_stream(ctx->imageStream, crc_length, buffer, VERIFY_SIZE, crc64_context,
286 "data block");
287 if(status != AARUF_STATUS_OK) goto cleanup;
288
289 if(aaruf_crc64_final(crc64_context, &crc64) != 0)
290 {
291 FATAL("Could not finalize CRC64 for data block");
293 goto cleanup;
294 }
295
296 if(ctx->header.imageMajorVersion <= AARUF_VERSION_V1) crc64 = bswap_64(crc64);
297
298 if(crc64 != block_header.cmpCrc64)
299 {
300 FATAL("Expected block CRC 0x%16llX but got 0x%16llX", block_header.cmpCrc64, crc64);
302 goto cleanup;
303 }
304
305 aaruf_crc64_free(crc64_context);
306 crc64_context = NULL;
307 break;
309 read_bytes = fread(&ddt_header, 1, sizeof(DdtHeader), ctx->imageStream);
310 if(read_bytes != sizeof(DdtHeader))
311 {
312 FATAL("Could not read DDT header");
314 goto cleanup;
315 }
316
317 crc64_context = aaruf_crc64_init();
318 if(crc64_context == NULL)
319 {
320 FATAL("Could not initialize CRC64 context");
322 goto cleanup;
323 }
324
325 // For LZMA compression in v2+, skip the 5-byte properties header.
326 // V1 images included LZMA properties in cmpCrc64; v2+ excludes them.
327 crc_length = ddt_header.cmpLength;
328 if((ddt_header.compression == kCompressionLzma || ddt_header.compression == kCompressionLzmaCst) &&
330 {
331 // Skip LZMA properties (v2+ only)
332 uint8_t props[LZMA_PROPERTIES_LENGTH];
333 size_t read_props = fread(props, 1, LZMA_PROPERTIES_LENGTH, ctx->imageStream);
334 if(read_props != LZMA_PROPERTIES_LENGTH)
335 {
336 FATAL("Could not read LZMA properties");
338 goto cleanup;
339 }
340 crc_length -= LZMA_PROPERTIES_LENGTH;
341 }
342
343 status = update_crc64_from_stream(ctx->imageStream, crc_length, buffer, VERIFY_SIZE, crc64_context,
344 "DDT block");
345 if(status != AARUF_STATUS_OK) goto cleanup;
346
347 if(aaruf_crc64_final(crc64_context, &crc64) != 0)
348 {
349 FATAL("Could not finalize CRC64 for DDT block");
351 goto cleanup;
352 }
353
354 if(ctx->header.imageMajorVersion <= AARUF_VERSION_V1) crc64 = bswap_64(crc64);
355
356 if(crc64 != ddt_header.cmpCrc64)
357 {
358 FATAL("Expected DDT CRC 0x%16llX but got 0x%16llX", ddt_header.cmpCrc64, crc64);
360 goto cleanup;
361 }
362
363 aaruf_crc64_free(crc64_context);
364 crc64_context = NULL;
365 break;
367 read_bytes = fread(&ddt2_header, 1, sizeof(DdtHeader2), ctx->imageStream);
368 if(read_bytes != sizeof(DdtHeader2))
369 {
370 FATAL("Could not read DDT2 header");
372 goto cleanup;
373 }
374
375 crc64_context = aaruf_crc64_init();
376 if(crc64_context == NULL)
377 {
378 FATAL("Could not initialize CRC64 context");
380 goto cleanup;
381 }
382
383 // DDT2 only appears in v2+ images, so always skip LZMA properties.
384 crc_length = ddt2_header.cmpLength;
385 if(ddt2_header.compression == kCompressionLzma || ddt2_header.compression == kCompressionLzmaCst)
386 {
387 // Skip LZMA properties
388 uint8_t props[LZMA_PROPERTIES_LENGTH];
389 size_t read_props = fread(props, 1, LZMA_PROPERTIES_LENGTH, ctx->imageStream);
390 if(read_props != LZMA_PROPERTIES_LENGTH)
391 {
392 FATAL("Could not read LZMA properties");
394 goto cleanup;
395 }
396 crc_length -= LZMA_PROPERTIES_LENGTH;
397 }
398
399 status = update_crc64_from_stream(ctx->imageStream, crc_length, buffer, VERIFY_SIZE, crc64_context,
400 "DDT2 block");
401 if(status != AARUF_STATUS_OK) goto cleanup;
402
403 if(aaruf_crc64_final(crc64_context, &crc64) != 0)
404 {
405 FATAL("Could not finalize CRC64 for DDT2 block");
407 goto cleanup;
408 }
409
410 if(crc64 != ddt2_header.cmpCrc64)
411 {
412 FATAL("Expected DDT2 CRC 0x%16llX but got 0x%16llX", ddt2_header.cmpCrc64, crc64);
414 goto cleanup;
415 }
416
417 aaruf_crc64_free(crc64_context);
418 crc64_context = NULL;
419 break;
420 case TracksBlock:
421 {
422 read_bytes = fread(&tracks_header, 1, sizeof(TracksHeader), ctx->imageStream);
423 if(read_bytes != sizeof(TracksHeader))
424 {
425 FATAL("Could not read tracks header");
427 goto cleanup;
428 }
429
430 const uint64_t tracks_bytes = (uint64_t)tracks_header.entries * sizeof(TrackEntry);
431 if(tracks_header.entries != 0 && tracks_bytes / sizeof(TrackEntry) != tracks_header.entries)
432 {
433 FATAL("Tracks header length overflow (entries=%u)", tracks_header.entries);
435 goto cleanup;
436 }
437
438 crc64_context = aaruf_crc64_init();
439 if(crc64_context == NULL)
440 {
441 FATAL("Could not initialize CRC64 context");
443 goto cleanup;
444 }
445
446 status = update_crc64_from_stream(ctx->imageStream, tracks_bytes, buffer, VERIFY_SIZE, crc64_context,
447 "tracks block");
448 if(status != AARUF_STATUS_OK) goto cleanup;
449
450 if(aaruf_crc64_final(crc64_context, &crc64) != 0)
451 {
452 FATAL("Could not finalize CRC64 for tracks block");
454 goto cleanup;
455 }
456
457 if(ctx->header.imageMajorVersion <= AARUF_VERSION_V1) crc64 = bswap_64(crc64);
458
459 if(crc64 != tracks_header.crc64)
460 {
461 FATAL("Expected tracks CRC 0x%16llX but got 0x%16llX", tracks_header.crc64, crc64);
463 goto cleanup;
464 }
465
466 aaruf_crc64_free(crc64_context);
467 crc64_context = NULL;
468 break;
469 }
471 {
472 DpmHeader dpm_header;
473 read_bytes = fread(&dpm_header, 1, sizeof(DpmHeader), ctx->imageStream);
474 if(read_bytes != sizeof(DpmHeader) || dpm_header.identifier != DataPositionMeasurementBlock)
475 {
476 FATAL("Could not read DPM header");
478 goto cleanup;
479 }
480
481 if(dpm_header.length != dpm_payload_length(&dpm_header))
482 {
483 FATAL("DPM header length %" PRIu64 " does not match its contents", dpm_header.length);
485 goto cleanup;
486 }
487
488 crc64_context = aaruf_crc64_init();
489 if(crc64_context == NULL)
490 {
491 FATAL("Could not initialize CRC64 context");
493 goto cleanup;
494 }
495
496 status = update_crc64_from_stream(ctx->imageStream, dpm_header.length, buffer, VERIFY_SIZE,
497 crc64_context, "DPM block");
498 if(status != AARUF_STATUS_OK) goto cleanup;
499
500 if(aaruf_crc64_final(crc64_context, &crc64) != 0)
501 {
502 FATAL("Could not finalize CRC64 for DPM block");
504 goto cleanup;
505 }
506
507 if(crc64 != dpm_header.crc64)
508 {
509 FATAL("Expected DPM CRC 0x%16llX but got 0x%16llX", dpm_header.crc64, crc64);
511 goto cleanup;
512 }
513
514 aaruf_crc64_free(crc64_context);
515 crc64_context = NULL;
516 break;
517 }
518 default:
519 TRACE("Ignoring block type %4.4s", (char *)&entry->blockType);
520 break;
521 }
522 }
523
524 /* An image must contain either a user-data DDT or a flux data block (flux-only images are
525 * legitimate). If neither is present the index is structurally invalid. */
526 if(!has_ddt_block && !has_flux_block)
527 {
528 FATAL("Image contains neither a user-data DDT nor a flux data block");
530 goto cleanup;
531 }
532
533 status = AARUF_STATUS_OK;
534
535cleanup:
536 if(crc64_context != NULL)
537 {
538 aaruf_crc64_free(crc64_context);
539 crc64_context = NULL;
540 }
541
542 if(buffer != NULL)
543 {
544 free(buffer);
545 buffer = NULL;
546 }
547
548 if(index_entries != NULL)
549 {
550 utarray_free(index_entries);
551 index_entries = NULL;
552 }
553
554 TRACE("Exiting aaruf_verify_image() = %d", status);
555 return status;
556}
#define LZMA_PROPERTIES_LENGTH
Size in bytes of the fixed LZMA properties header (lc/lp/pb + dictionary size).
Definition consts.h:86
#define AARU_MAGIC
Magic identifier for AaruFormat container (ASCII "AARUFRMT").
Definition consts.h:64
#define AARUF_VERSION_V1
First on‑disk version (C# implementation).
Definition consts.h:71
#define AARU_CALL
Definition decls.h:46
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
void aaruf_crc64_free(crc64_ctx *ctx)
Frees a CRC64 context.
Definition crc64.c:155
int32_t aaruf_verify_image(void *context)
Verifies the integrity of an AaruFormat image file.
Definition verify.c:130
#define AARU_EXPORT
Definition decls.h:55
int aaruf_crc64_final(crc64_ctx *ctx, uint64_t *crc)
Computes the final CRC64 value from the context.
Definition crc64.c:141
crc64_ctx * aaruf_crc64_init(void)
Initializes a CRC64 context.
Definition crc64.c:32
#define bswap_64(x)
Definition endian.h:81
@ IndexBlock3
Block containing the index v3.
Definition enums.h:171
@ DataBlock
Block containing data.
Definition enums.h:164
@ IndexBlock2
Block containing the index v2.
Definition enums.h:170
@ IndexBlock
Block containing the index (v1).
Definition enums.h:169
@ DeDuplicationTable2
Block containing a deduplication table v2.
Definition enums.h:166
@ DeDuplicationTable
Block containing a deduplication table (v1).
Definition enums.h:165
@ DataPositionMeasurementBlock
Block containing data position measurements (reserved / TODO).
Definition enums.h:177
@ TracksBlock
Block containing optical disc tracks.
Definition enums.h:174
@ FluxDataBlock
Block containing flux data metadata.
Definition enums.h:184
@ kCompressionLzmaCst
LZMA applied to Claunia Subchannel Transform processed data.
Definition enums.h:36
@ kCompressionLzma
LZMA compression.
Definition enums.h:34
#define AARUF_STATUS_OK
Sector present and read without uncorrectable errors.
Definition errors.h:82
#define AARUF_ERROR_CANNOT_READ_HEADER
Failed to read container header.
Definition errors.h:45
#define AARUF_ERROR_NOT_ENOUGH_MEMORY
Memory allocation failure (critical).
Definition errors.h:48
#define AARUF_ERROR_CANNOT_READ_BLOCK
Generic block read failure (seek/read error).
Definition errors.h:46
#define AARUF_ERROR_INVALID_BLOCK_CRC
CRC64 mismatch indicating corruption.
Definition errors.h:57
#define AARUF_ERROR_CANNOT_READ_INDEX
Index block unreadable / truncated / bad identifier.
Definition errors.h:43
#define AARUF_ERROR_NOT_AARUFORMAT
Input file/stream failed magic or structural validation.
Definition errors.h:40
UT_array * process_index_v2(aaruformat_context *ctx)
Processes an index block (version 2) from the image stream.
Definition index_v2.c:82
uint64_t dpm_payload_length(const DpmHeader *header)
Computes the payload length a Data Position Measurement header describes.
Definition dpm.c:36
UT_array * process_index_v1(aaruformat_context *ctx)
Processes an index block (version 1) from the image stream.
Definition index_v1.c:80
static int aaruf_fseek(FILE *stream, aaru_off_t offset, int origin)
Definition internal.h:46
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:226
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
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:228
int64_t aaru_off_t
Definition internal.h:42
UT_array * process_index_v3(aaruformat_context *ctx)
Processes an index block (version 3) from the image stream.
Definition index_v3.c:98
#define FATAL(fmt,...)
Definition log.h:40
#define TRACE(fmt,...)
Definition log.h:25
uint64_t indexOffset
Absolute byte offset to primary index block (MUST be > 0; 0 => corrupt/unreadable).
Definition header.h:115
uint8_t imageMajorVersion
Container format major version.
Definition header.h:110
Header preceding the compressed data payload of a data block (BlockType::DataBlock).
Definition data.h:71
uint32_t cmpLength
Size in bytes of the compressed payload immediately following this header.
Definition data.h:76
uint64_t cmpCrc64
CRC64-ECMA of the compressed payload (cmpLength bytes).
Definition data.h:78
uint16_t compression
Compression algorithm used (value from CompressionType).
Definition data.h:74
Header preceding a version 2 hierarchical deduplication table.
Definition ddt.h:142
uint64_t cmpCrc64
CRC64-ECMA of compressed table payload.
Definition ddt.h:161
uint16_t compression
Compression algorithm for this table body (CompressionType).
Definition ddt.h:145
uint64_t cmpLength
Compressed payload size in bytes.
Definition ddt.h:159
Header preceding a version 1 (flat) deduplication table body.
Definition ddt.h:66
uint16_t compression
Compression algorithm for the table body (CompressionType).
Definition ddt.h:69
uint64_t cmpLength
Size in bytes of compressed entries payload.
Definition ddt.h:72
uint64_t cmpCrc64
CRC64-ECMA of the compressed payload.
Definition ddt.h:74
Header of the Data Position Measurement block.
Definition dpm.h:77
uint64_t length
Payload bytes after this header.
Definition dpm.h:87
uint32_t identifier
Block identifier, must be BlockType::DataPositionMeasurementBlock.
Definition dpm.h:78
uint64_t crc64
CRC64-ECMA of the payload.
Definition dpm.h:88
Single index entry describing a block's type, (optional) data classification, and file offset.
Definition index.h:109
uint32_t blockType
Block identifier of the referenced block (value from BlockType).
Definition index.h:110
uint64_t offset
Absolute byte offset in the image where the referenced block header begins.
Definition index.h:112
Single optical disc track descriptor (sequence, type, LBAs, session, ISRC, flags).
Definition optical.h:72
Header for an optical tracks block listing track entries.
Definition optical.h:62
uint16_t entries
Number of TrackEntry records following this header.
Definition optical.h:64
uint64_t crc64
CRC64-ECMA of the TrackEntry array (header excluded, legacy byte-swap for early versions).
Definition optical.h:65
Master context representing an open or in‑creation Aaru image.
Definition context.h:178
AaruHeaderV2 header
Parsed container header (v2).
Definition context.h:181
uint64_t magic
File magic (AARU_MAGIC) post-open.
Definition context.h:180
FILE * imageStream
Underlying FILE* stream (binary mode).
Definition context.h:182
Minimal ECMA-182 CRC64 incremental state container (running value only).
Definition crc64.h:56
#define VERIFY_SIZE
Definition verify.c:28
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