Handle flux-only images

This commit is contained in:
Rebecca Wallander
2026-05-01 09:14:06 +02:00
parent 8b0f8a37fe
commit a6345b7efc
10 changed files with 312 additions and 15 deletions

View File

@@ -66,6 +66,9 @@ typedef struct FluxCaptureMapEntry FluxCaptureMapEntry;
* - If deduplicate == false, sectorHashMap may still be populated for bookkeeping but duplicates are stored
* independently.
* - If userDataDdtMini != NULL then userDataDdtBig == NULL (and vice versa) for a given level.
* - If no_user_data_ddt == true the image carries no user-data DDT (intentional, e.g. flux-only image): user_data_ddt,
* user_data_ddt2, cached_secondary_ddt2 and tape_ddt MUST all be NULL, and user_data_ddt_header.entries MUST be 0.
* Sector read/write APIs return AARUF_ERROR_USER_DATA_NOT_PRESENT in this state.
*/
#ifndef MD5_DIGEST_LENGTH
@@ -197,6 +200,7 @@ typedef struct aaruformat_context
int ddt_version; ///< DDT version in use (1=legacy, 2=v2 hierarchical).
uint8_t shift; ///< Legacy overall shift (deprecated by data_shift/table_shift).
bool in_memory_ddt; ///< True if primary (and possibly secondary) DDT loaded.
bool no_user_data_ddt; ///< True if image intentionally has no user-data DDT (e.g. flux-only image). When set, user_data_ddt*/tape_ddt MUST be NULL and user_data_ddt_header.entries MUST be 0.
/* Optical auxiliary buffers (NULL if not present) */
uint8_t *sector_prefix; ///< Raw per-sector prefix (e.g., sync+header) uncorrected.

View File

@@ -73,6 +73,7 @@
#define AARUF_ERROR_CANNOT_ENCRYPT_SECTOR (-34) ///< AES sector encryption failed.
#define AARUF_ERROR_CANNOT_DECRYPT_SECTOR (-35) ///< AES sector decryption failed.
#define AARUF_ERROR_MISSING_ENCRYPTION_KEY (-36) ///< Required encryption key not present in media tags.
#define AARUF_ERROR_USER_DATA_NOT_PRESENT (-37) ///< Image has no user-data DDT (e.g. flux-only image); sector data is unavailable.
/** @} */
/** \name Non-fatal sector status codes (non-negative)
@@ -156,6 +157,8 @@ static inline const char *aaruformat_error_string(int code)
return "Cannot decrypt sector";
case AARUF_ERROR_MISSING_ENCRYPTION_KEY:
return "Missing encryption key";
case AARUF_ERROR_USER_DATA_NOT_PRESENT:
return "User data not present (flux-only image)";
/* Status */
case AARUF_STATUS_OK:

View File

@@ -69,6 +69,9 @@
*/
static int32_t write_cached_secondary_ddt(aaruformat_context *ctx)
{
// Flux-only images carry no DDT.
if(ctx->no_user_data_ddt) return AARUF_STATUS_OK;
// Write cached secondary table to file end and update primary table entry with its position
// Check if we have a cached table that needs to be written (either it has an offset or exists in memory)
bool has_cached_secondary_ddt =
@@ -296,6 +299,9 @@ static int32_t write_cached_secondary_ddt(aaruformat_context *ctx)
*/
static int32_t write_primary_ddt(aaruformat_context *ctx)
{
// Flux-only images carry no DDT.
if(ctx->no_user_data_ddt) return AARUF_STATUS_OK;
// Write the cached primary DDT table back to its position in the file
if(ctx->user_data_ddt_header.tableShift <= 0 || ctx->user_data_ddt2 == NULL) return AARUF_STATUS_OK;
@@ -398,6 +404,9 @@ static int32_t write_primary_ddt(aaruformat_context *ctx)
*/
static int32_t write_single_level_ddt(aaruformat_context *ctx)
{
// Flux-only images carry no DDT.
if(ctx->no_user_data_ddt) return AARUF_STATUS_OK;
// Write the single level DDT table block aligned just after the header
if(ctx->user_data_ddt_header.tableShift != 0 || ctx->user_data_ddt2 == NULL) return AARUF_STATUS_OK;
@@ -661,6 +670,9 @@ static int32_t write_tape_ddt(aaruformat_context *ctx)
{
if(!ctx->is_tape) return AARUF_STATUS_INVALID_CONTEXT;
// Flux-only images carry no DDT (defensive; tape images aren't flux-only today).
if(ctx->no_user_data_ddt) return AARUF_STATUS_OK;
// Traverse the tape DDT uthash and find the biggest key
uint64_t max_key = 0;
TapeDdtHashEntry *entry, *tmp;

View File

@@ -417,7 +417,28 @@ AARU_EXPORT void *AARU_CALL aaruf_create(const char *filepath, const uint32_t me
ctx->library_major_version = LIBAARUFORMAT_MAJOR_VERSION;
ctx->library_minor_version = LIBAARUFORMAT_MINOR_VERSION;
if(!is_tape)
/* Flux-only images carry no sector/DDT data: the caller signals this by passing zero
* for all sector counts. In that case skip DDT initialization entirely and lay out the
* image like a tape (header followed by aligned blocks). Sector read/write APIs return
* AARUF_ERROR_USER_DATA_NOT_PRESENT for such contexts. */
const bool flux_only = !is_tape && user_sectors == 0 && negative_sectors == 0 && overflow_sectors == 0;
if(flux_only)
{
TRACE("Initializing flux-only image (no DDT)");
ctx->no_user_data_ddt = true;
/* Record alignment in both the container header and the (otherwise unused) DDT header
* so existing helpers that consult either get a consistent value. */
ctx->user_data_ddt_header.blockAlignmentShift = parsed_options.block_alignment;
ctx->header.blockAlignmentShift = parsed_options.block_alignment;
ctx->user_data_ddt_header.dataShift = parsed_options.data_shift;
const uint64_t alignment_mask = (1ULL << parsed_options.block_alignment) - 1;
ctx->next_block_position = sizeof(AaruHeaderV2);
ctx->next_block_position = ctx->next_block_position + alignment_mask & ~alignment_mask;
}
else if(!is_tape)
{ // Initialize DDT2
TRACE("Initializing DDT2");
ctx->in_memory_ddt = true;
@@ -541,7 +562,8 @@ AARU_EXPORT void *AARU_CALL aaruf_create(const char *filepath, const uint32_t me
ctx->use_zstd = parsed_options.zstd;
ctx->zstd_level = parsed_options.zstd_level;
ctx->num_threads = parsed_options.num_threads;
if(ctx->deduplicate)
/* Flux-only images have no sector data to deduplicate against. */
if(ctx->deduplicate && !ctx->no_user_data_ddt)
ctx->sector_hash_map = create_map(ctx->user_data_ddt_header.blocks * 25 / 100); // 25% of total sectors
ctx->rewinded = false;
@@ -581,10 +603,12 @@ AARU_EXPORT void *AARU_CALL aaruf_create(const char *filepath, const uint32_t me
ctx->is_writing = true;
ctx->finalize_write = aaruf_finalize_write;
// Initialize dirty flags - all true by default for new images
ctx->dirty_secondary_ddt = true;
ctx->dirty_primary_ddt = true;
ctx->dirty_single_level_ddt = true;
// Initialize dirty flags - all true by default for new images.
// Flux-only images (no DDT) leave the DDT dirty flags cleared so finalize never
// attempts to serialize a non-existent table.
ctx->dirty_secondary_ddt = !ctx->no_user_data_ddt;
ctx->dirty_primary_ddt = !ctx->no_user_data_ddt;
ctx->dirty_single_level_ddt = !ctx->no_user_data_ddt;
ctx->dirty_checksum_block = true;
ctx->dirty_tracks_block = true;
ctx->dirty_mode2_subheaders_block = true;

View File

@@ -543,6 +543,7 @@ AARU_EXPORT void *AARU_CALL aaruf_open(const char *filepath, const bool resume_m
}
bool found_user_data_ddt = false;
bool has_flux_block = false;
ctx->image_info.ImageSize = 0;
for(i = 0; i < utarray_len(index_entries); i++)
{
@@ -663,6 +664,7 @@ AARU_EXPORT void *AARU_CALL aaruf_open(const char *filepath, const bool resume_m
// Store the FluxDataBlock offset for lazy loading
// Don't read flux entries during open - load them on-demand when actually needed
// This avoids unnecessary I/O if flux data is never accessed
has_flux_block = true;
break;
default:
TRACE("Unhandled block type %4.4s with data type %d is indexed to be at %" PRIu64 "",
@@ -675,12 +677,19 @@ AARU_EXPORT void *AARU_CALL aaruf_open(const char *filepath, const bool resume_m
if(!found_user_data_ddt)
{
FATAL("Could not find user data deduplication table, aborting...");
aaruf_close(ctx);
aaruf_set_open_error(AARUF_ERROR_CANNOT_READ_INDEX);
if(!has_flux_block)
{
FATAL("Could not find user data deduplication table or flux data, aborting...");
aaruf_close(ctx);
aaruf_set_open_error(AARUF_ERROR_CANNOT_READ_INDEX);
TRACE("Exiting aaruf_open() = NULL");
return NULL;
TRACE("Exiting aaruf_open() = NULL");
return NULL;
}
TRACE("No user data DDT found but flux data present, opening as flux-only image");
ctx->no_user_data_ddt = true;
/* image_info.Sectors stays 0; biggestSectorSize stays 0; sector APIs will reject. */
}
if(ctx->header.biggestSectorSize != 0)
@@ -748,9 +757,12 @@ AARU_EXPORT void *AARU_CALL aaruf_open(const char *filepath, const bool resume_m
ctx->header.lastWrittenTime = get_filetime_uint64();
ctx->image_info.LastModificationTime = ctx->header.lastWrittenTime;
// Calculate aligned next block position
// Calculate aligned next block position. For flux-only images there is no user-data DDT
// header, so use the alignment shift recorded in the container header instead.
const uint8_t resume_alignment_shift =
ctx->no_user_data_ddt ? ctx->header.blockAlignmentShift : ctx->user_data_ddt_header.blockAlignmentShift;
aaruf_fseek(ctx->imageStream, 0, SEEK_END);
const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
const uint64_t alignment_mask = (1ULL << resume_alignment_shift) - 1;
ctx->next_block_position = (uint64_t)aaruf_ftell(ctx->imageStream); // Start just after the header
ctx->next_block_position = ctx->next_block_position + alignment_mask & ~alignment_mask;
@@ -770,7 +782,8 @@ AARU_EXPORT void *AARU_CALL aaruf_open(const char *filepath, const bool resume_m
ctx->compression_enabled = parsed_options.compress;
ctx->lzma_dict_size = parsed_options.dictionary;
ctx->deduplicate = parsed_options.deduplicate;
if(ctx->deduplicate)
/* Flux-only images have no DDT and therefore no sector deduplication map. */
if(ctx->deduplicate && !ctx->no_user_data_ddt)
ctx->sector_hash_map = create_map(ctx->user_data_ddt_header.blocks * 25 / 100); // 25% of total sectors
// Cannot checksum a resumed file

View File

@@ -396,6 +396,14 @@ AARU_EXPORT int32_t AARU_CALL aaruf_read_sector(void *context, const uint64_t se
return AARUF_ERROR_NOT_AARUFORMAT;
}
// Flux-only images carry no DDT; sector reads cannot be served.
if(ctx->no_user_data_ddt)
{
*sector_status = SectorStatusNotDumped;
TRACE("Exiting aaruf_read_sector() = AARUF_ERROR_USER_DATA_NOT_PRESENT (flux-only image)");
return AARUF_ERROR_USER_DATA_NOT_PRESENT;
}
if(negative && sector_address > ctx->user_data_ddt_header.negative)
{
FATAL("Sector address out of bounds");
@@ -1472,6 +1480,14 @@ AARU_EXPORT int32_t AARU_CALL aaruf_read_sector_long(void *context, const uint64
return AARUF_ERROR_NOT_AARUFORMAT;
}
// Flux-only images carry no DDT; sector reads cannot be served.
if(ctx->no_user_data_ddt)
{
if(sector_status != NULL) *sector_status = SectorStatusNotDumped;
TRACE("Exiting aaruf_read_sector_long() = AARUF_ERROR_USER_DATA_NOT_PRESENT (flux-only image)");
return AARUF_ERROR_USER_DATA_NOT_PRESENT;
}
if(negative && sector_address > ctx->user_data_ddt_header.negative)
{
FATAL("Sector address out of bounds");
@@ -2275,6 +2291,13 @@ AARU_EXPORT int32_t AARU_CALL aaruf_read_sector_tag(const void *context, const u
return AARUF_ERROR_NOT_AARUFORMAT;
}
// Flux-only images carry no DDT and therefore no per-sector tags.
if(ctx->no_user_data_ddt)
{
TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_USER_DATA_NOT_PRESENT (flux-only image)");
return AARUF_ERROR_USER_DATA_NOT_PRESENT;
}
if(negative && sector_address > ctx->user_data_ddt_header.negative)
{
FATAL("Sector address out of bounds");

View File

@@ -226,13 +226,18 @@ AARU_EXPORT int32_t AARU_CALL aaruf_verify_image(void *context)
}
uint64_t crc_length;
const unsigned int entry_count = utarray_len(index_entries);
const unsigned int entry_count = utarray_len(index_entries);
bool has_ddt_block = false;
bool has_flux_block = false;
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(entry->blockType == DeDuplicationTable || entry->blockType == DeDuplicationTable2) has_ddt_block = true;
if(entry->blockType == FluxDataBlock) has_flux_block = true;
if(aaruf_fseek(ctx->imageStream, (aaru_off_t)entry->offset, SEEK_SET) != 0)
{
FATAL("Could not seek to block at offset %" PRIu64, entry->offset);
@@ -468,6 +473,15 @@ AARU_EXPORT int32_t AARU_CALL aaruf_verify_image(void *context)
}
}
/* An image must contain either a user-data DDT or a flux data block (flux-only images are
* legitimate). If neither is present the index is structurally invalid. */
if(!has_ddt_block && !has_flux_block)
{
FATAL("Image contains neither a user-data DDT nor a flux data block");
status = AARUF_ERROR_CANNOT_READ_INDEX;
goto cleanup;
}
status = AARUF_STATUS_OK;
cleanup:

View File

@@ -134,6 +134,15 @@ AARU_EXPORT int32_t AARU_CALL aaruf_write_sector(void *context, uint64_t sector_
return AARUF_READ_ONLY;
}
// Flux-only images carry no DDT and no sector storage.
if(ctx->no_user_data_ddt)
{
FATAL("Cannot write sector to flux-only image (no user-data DDT)");
TRACE("Exiting aaruf_write_sector() = AARUF_ERROR_USER_DATA_NOT_PRESENT");
return AARUF_ERROR_USER_DATA_NOT_PRESENT;
}
if(negative && sector_address > ctx->user_data_ddt_header.negative)
{
FATAL("Sector address out of bounds");
@@ -655,6 +664,15 @@ AARU_EXPORT int32_t AARU_CALL aaruf_write_sector_long(void *context, uint64_t se
return AARUF_READ_ONLY;
}
// Flux-only images carry no DDT and no sector storage.
if(ctx->no_user_data_ddt)
{
FATAL("Cannot write long sector to flux-only image (no user-data DDT)");
TRACE("Exiting aaruf_write_sector_long() = AARUF_ERROR_USER_DATA_NOT_PRESENT");
return AARUF_ERROR_USER_DATA_NOT_PRESENT;
}
if(negative && sector_address > ctx->user_data_ddt_header.negative)
{
FATAL("Sector address out of bounds");
@@ -2305,6 +2323,15 @@ AARU_EXPORT int32_t AARU_CALL aaruf_write_sector_tag(void *context, const uint64
return AARUF_READ_ONLY;
}
// Flux-only images carry no DDT and no per-sector tag storage.
if(ctx->no_user_data_ddt)
{
FATAL("Cannot write sector tag to flux-only image (no user-data DDT)");
TRACE("Exiting aaruf_write_sector_tag() = AARUF_ERROR_USER_DATA_NOT_PRESENT");
return AARUF_ERROR_USER_DATA_NOT_PRESENT;
}
if(negative && sector_address > ctx->user_data_ddt_header.negative)
{
FATAL("Sector address out of bounds");

View File

@@ -104,6 +104,7 @@ add_executable(tests_run
mode2_errored.cpp
reed_solomon.cpp
erasure_coding.cpp
flux_only_image.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../src/lib/aes128.c
${CMAKE_CURRENT_SOURCE_DIR}/../src/ps3/ps3_crypto.c
${CMAKE_CURRENT_SOURCE_DIR}/../src/ps3/ps3_encryption_map.c

176
tests/flux_only_image.cpp Normal file
View File

@@ -0,0 +1,176 @@
/*
* 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 <cstdlib>
#include <cstring>
#include <unistd.h>
#include "../include/aaruformat.h"
#include "gtest/gtest.h"
namespace
{
/// Fill a buffer with a deterministic, easy-to-validate pattern.
void fill_pattern(uint8_t *buf, size_t length, uint32_t seed)
{
for(size_t i = 0; i < length; ++i) buf[i] = static_cast<uint8_t>((seed + i * 7u) & 0xFF);
}
} // namespace
class FluxOnlyImageFixture : public testing::Test
{
public:
FluxOnlyImageFixture() = default;
protected:
void SetUp() override {}
void TearDown() override {}
};
/// Create a flux-only image (all sector counts zero), write a flux capture, close, reopen, verify
/// and read back the flux capture. Sector reads must be rejected with AARUF_ERROR_USER_DATA_NOT_PRESENT.
TEST_F(FluxOnlyImageFixture, create_open_read_verify_flux_only)
{
constexpr const char *kPath = "test_flux_only.aif";
// Flux-only intent: user_sectors=0, negative=0, overflow=0, is_tape=false.
void *context = aaruf_create(kPath, /*media_type=*/1, /*sector_size=*/512, /*user_sectors=*/0, /*negative=*/0,
/*overflow=*/0, "compress=false",
reinterpret_cast<const uint8_t *>("gtest"), 5, 0, 0, /*is_tape=*/false);
ASSERT_NE(context, nullptr) << "Failed to create flux-only image";
constexpr uint32_t kDataLen = 4096;
constexpr uint32_t kIndexLen = 64;
uint8_t data[kDataLen];
uint8_t index[kIndexLen];
fill_pattern(data, kDataLen, 0xA5u);
fill_pattern(index, kIndexLen, 0x5Au);
int32_t flux_write = aaruf_write_flux_capture(context,
/*head=*/0, /*track=*/0, /*subtrack=*/0,
/*capture_index=*/0,
/*data_resolution=*/25000000ULL,
/*index_resolution=*/25000000ULL, data, kDataLen, index, kIndexLen);
ASSERT_EQ(flux_write, AARUF_STATUS_OK) << "Failed to write flux capture";
// Sector writes against a flux-only image must be rejected, not silently accepted.
uint8_t sector[512];
fill_pattern(sector, sizeof(sector), 0u);
const int32_t sector_write =
aaruf_write_sector(context, /*sector_address=*/0, /*negative=*/false, sector, SectorStatusDumped, 512);
EXPECT_EQ(sector_write, AARUF_ERROR_USER_DATA_NOT_PRESENT)
<< "Sector write to flux-only image should be rejected";
int32_t close_result = aaruf_close(context);
ASSERT_EQ(close_result, AARUF_STATUS_OK) << "Failed to close flux-only image";
context = aaruf_open(kPath, /*resume=*/false, /*options=*/nullptr);
ASSERT_NE(context, nullptr) << "Failed to reopen flux-only image";
EXPECT_EQ(aaruf_verify_image(context), AARUF_STATUS_OK)
<< "Flux-only image should pass verification";
uint32_t read_index_len = kIndexLen;
uint32_t read_data_len = kDataLen;
uint8_t read_index[kIndexLen];
uint8_t read_data[kDataLen];
int32_t flux_read = aaruf_read_flux_capture(context, /*head=*/0, /*track=*/0, /*subtrack=*/0,
/*capture_index=*/0, read_index, &read_index_len, read_data,
&read_data_len);
ASSERT_EQ(flux_read, AARUF_STATUS_OK) << "Failed to read back flux capture";
EXPECT_EQ(read_data_len, kDataLen) << "Unexpected flux data length";
EXPECT_EQ(read_index_len, kIndexLen) << "Unexpected flux index length";
EXPECT_EQ(memcmp(read_data, data, kDataLen), 0) << "Flux data round-trip mismatch";
EXPECT_EQ(memcmp(read_index, index, kIndexLen), 0) << "Flux index round-trip mismatch";
// Sector reads against a flux-only image must surface the dedicated error code.
uint32_t sector_length = sizeof(sector);
uint8_t sector_status = 0;
int32_t sector_read =
aaruf_read_sector(context, /*sector_address=*/0, /*negative=*/false, sector, &sector_length, &sector_status);
EXPECT_EQ(sector_read, AARUF_ERROR_USER_DATA_NOT_PRESENT)
<< "Sector read on flux-only image must return AARUF_ERROR_USER_DATA_NOT_PRESENT";
EXPECT_EQ(sector_status, SectorStatusNotDumped)
<< "Sector status should report not-dumped for flux-only images";
sector_length = sizeof(sector);
sector_status = 0;
int32_t sector_long_read = aaruf_read_sector_long(context, /*sector_address=*/0, /*negative=*/false, sector,
&sector_length, &sector_status);
EXPECT_EQ(sector_long_read, AARUF_ERROR_USER_DATA_NOT_PRESENT)
<< "Long sector read on flux-only image must return AARUF_ERROR_USER_DATA_NOT_PRESENT";
uint32_t tag_length = sizeof(sector);
int32_t tag_read =
aaruf_read_sector_tag(context, /*sector_address=*/0, /*negative=*/false, sector, &tag_length,
kSectorTagCdSubchannel);
EXPECT_EQ(tag_read, AARUF_ERROR_USER_DATA_NOT_PRESENT)
<< "Sector tag read on flux-only image must return AARUF_ERROR_USER_DATA_NOT_PRESENT";
close_result = aaruf_close(context);
EXPECT_EQ(close_result, AARUF_STATUS_OK) << "Failed to close reopened flux-only image";
unlink(kPath);
}
/// A standard image (with sectors) that has no flux data should still respond to flux reads with
/// AARUF_ERROR_FLUX_DATA_NOT_FOUND. Regression check for the "DDT-present, flux-absent" path.
TEST_F(FluxOnlyImageFixture, regular_image_flux_read_returns_not_found)
{
constexpr const char *kPath = "test_regular_no_flux.aif";
constexpr size_t kTotalSectors = 16;
void *context = aaruf_create(kPath, /*media_type=*/1, /*sector_size=*/512, kTotalSectors, /*negative=*/0,
/*overflow=*/0, "compress=false",
reinterpret_cast<const uint8_t *>("gtest"), 5, 0, 0, /*is_tape=*/false);
ASSERT_NE(context, nullptr) << "Failed to create regular image";
uint8_t sector[512];
fill_pattern(sector, sizeof(sector), 0x42u);
for(size_t i = 0; i < kTotalSectors; ++i)
{
const int32_t write_result = aaruf_write_sector(context, i, false, sector, SectorStatusDumped, 512);
ASSERT_EQ(write_result, AARUF_STATUS_OK) << "Failed to write sector " << i;
}
int32_t close_result = aaruf_close(context);
ASSERT_EQ(close_result, AARUF_STATUS_OK) << "Failed to close regular image";
context = aaruf_open(kPath, false, nullptr);
ASSERT_NE(context, nullptr) << "Failed to reopen regular image";
EXPECT_EQ(aaruf_verify_image(context), AARUF_STATUS_OK) << "Regular image should pass verification";
uint8_t read_data[16];
uint8_t read_index[16];
uint32_t read_data_len = sizeof(read_data);
uint32_t read_index_len = sizeof(read_index);
int32_t flux_read = aaruf_read_flux_capture(context, 0, 0, 0, 0, read_index, &read_index_len, read_data,
&read_data_len);
EXPECT_EQ(flux_read, AARUF_ERROR_FLUX_DATA_NOT_FOUND)
<< "Regular image without flux should return AARUF_ERROR_FLUX_DATA_NOT_FOUND";
close_result = aaruf_close(context);
EXPECT_EQ(close_result, AARUF_STATUS_OK) << "Failed to close reopened regular image";
unlink(kPath);
}