diff --git a/CMakeLists.txt b/CMakeLists.txt index 01a153e..c86bb47 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,7 +134,8 @@ add_library(aaruformat SHARED include/aaruformat/consts.h include/aaruformat/enu include/sha256.h src/lisa_tag.c include/aaruformat/structs/lisa_tag.h - src/metadata.c) + src/metadata.c + src/dump.c) include_directories(include include/aaruformat 3rdparty/uthash/include 3rdparty/uthash/src) diff --git a/include/aaruformat/decls.h b/include/aaruformat/decls.h index 76789f7..f5f4b3c 100644 --- a/include/aaruformat/decls.h +++ b/include/aaruformat/decls.h @@ -161,6 +161,8 @@ AARU_EXPORT int32_t AARU_CALL aaruf_get_cicm_metadata(const void *context, uint8 AARU_EXPORT int32_t AARU_CALL aaruf_get_aaru_json_metadata(const void *context, uint8_t *buffer, size_t *length); AARU_EXPORT int32_t AARU_CALL aaruf_set_aaru_json_metadata(void *context, uint8_t *data, size_t length); +AARU_EXPORT int32_t AARU_CALL aaruf_get_dumphw(void* context, uint8_t *buffer, size_t *length); + AARU_EXPORT spamsum_ctx *AARU_CALL aaruf_spamsum_init(void); AARU_EXPORT int AARU_CALL aaruf_spamsum_update(spamsum_ctx *ctx, const uint8_t *data, uint32_t len); AARU_EXPORT int AARU_CALL aaruf_spamsum_final(spamsum_ctx *ctx, uint8_t *result); diff --git a/src/dump.c b/src/dump.c new file mode 100644 index 0000000..c6642f3 --- /dev/null +++ b/src/dump.c @@ -0,0 +1,307 @@ +/* + * This file is part of the Aaru Data Preservation Suite. + * Copyright (c) 2019-2025 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 . + */ + +#include + +#include "aaruformat.h" +#include "log.h" + +/** + * @brief Retrieves the dump hardware block containing acquisition environment information. + * + * Extracts the complete DumpHardwareBlock from the image, which documents the hardware and software + * environments used to create the image. A dump hardware block records one or more "dump environments" – + * typically combinations of physical devices (drives, controllers, adapters) and the software stacks + * that performed the read operations. This metadata is essential for understanding the imaging context, + * validating acquisition integrity, reproducing imaging conditions, and supporting forensic or archival + * documentation requirements. + * + * Each environment entry includes hardware identification (manufacturer, model, revision, firmware, + * serial number), software identification (name, version, operating system), and optional extent ranges + * that specify which logical sectors or units were contributed by that particular environment. This + * structure supports complex imaging scenarios where multiple devices or software configurations were + * used to create a composite image. + * + * The function reconstructs the complete on-disk binary representation of the dump hardware block, + * including the DumpHardwareHeader followed by all entries with their variable-length UTF-8 strings + * and extent arrays. The reconstructed block includes a calculated CRC64 checksum over the payload + * data for integrity verification. + * + * This function supports a two-call pattern for buffer size determination: + * 1. First call with insufficient buffer (or NULL) returns AARUF_ERROR_BUFFER_TOO_SMALL and sets + * *length to the required size (sizeof(DumpHardwareHeader) + total payload length) + * 2. Second call with properly sized buffer retrieves the complete block data + * + * Alternatively, if the caller already knows the buffer is large enough, a single call will succeed + * and populate the buffer with the complete dump hardware block. + * + * @param context Pointer to the aaruformat context (must be a valid, opened image context). + * @param buffer Pointer to a buffer that will receive the dump hardware block data. Must be large + * enough to hold the complete block (at least *length bytes on input). May be NULL + * to query the required buffer size. The buffer will contain the DumpHardwareHeader + * followed by serialized entries, strings, and extent arrays on success. + * @param length Pointer to a size_t that serves dual purpose: + * - On input: size of the provided buffer in bytes (ignored if buffer is NULL) + * - On output: actual size required/used for the dump hardware block in bytes + * If the function returns AARUF_ERROR_BUFFER_TOO_SMALL, this will be updated to + * contain the required buffer size for a subsequent successful call. + * + * @return Returns one of the following status codes: + * @retval AARUF_STATUS_OK (0) Successfully retrieved dump hardware block. This is returned when: + * - The context is valid and properly initialized + * - The dump hardware block is present (identifier == DumpHardwareBlock) + * - The provided buffer is large enough (>= required length) + * - All hardware entries with their strings and extents are copied to the buffer + * - The DumpHardwareHeader is written with calculated CRC64 at buffer offset 0 + * - The *length parameter is set to the actual block size + * + * @retval AARUF_ERROR_NOT_AARUFORMAT (-1) The context is invalid. This occurs when: + * - The context parameter is NULL + * - The context magic number doesn't match AARU_MAGIC (invalid context type) + * - The context was not properly initialized by aaruf_open() or aaruf_create() + * + * @retval AARUF_ERROR_CANNOT_READ_BLOCK (-6) The dump hardware block is not present. This occurs when: + * - The image was created without dump hardware information + * - ctx->dumpHardwareEntriesWithData is NULL (no data loaded) + * - ctx->dumpHardwareHeader.entries == 0 (no hardware entries) + * - ctx->dumpHardwareHeader.identifier doesn't equal DumpHardwareBlock + * - The dump hardware block was not found during image opening + * + * @retval AARUF_ERROR_BUFFER_TOO_SMALL (-12) The provided buffer is insufficient. This occurs when: + * - buffer is NULL (size query mode) + * - The input *length is less than sizeof(DumpHardwareHeader) + payload length + * - The *length parameter is updated to contain the required buffer size + * - No data is copied to the buffer + * - The caller should allocate a larger buffer and call again + * - Also returned if calculated entry size exceeds buffer during iteration (sanity check) + * + * @note Dump Hardware Block Structure: + * - DumpHardwareHeader (16 bytes): identifier, entries count, payload length, CRC64 + * - For each entry (variable size): + * * DumpHardwareEntry (36 bytes): length fields for all strings and extent count + * * Variable-length UTF-8 strings (in order): manufacturer, model, revision, firmware, + * serial, software name, software version, software operating system + * * Array of DumpExtent structures (16 bytes each) if extent count > 0 + * - All strings are UTF-8 encoded and NOT null-terminated in the serialized block + * - String lengths are in bytes, not character counts + * + * @note Dump Hardware Environments: + * - Each entry represents one hardware/software combination used during imaging + * - Multiple entries support scenarios where different devices contributed different sectors + * - Extent arrays specify which logical sector ranges each environment contributed + * - Empty extent arrays (extents == 0) indicate the environment dumped the entire medium + * - Overlapping extents between entries may indicate verification passes or redundancy + * + * @note Hardware Identification Fields: + * - manufacturer: Device manufacturer (e.g., "Plextor", "Sony", "Samsung") + * - model: Device model number (e.g., "PX-716A", "DRU-820A") + * - revision: Hardware revision identifier + * - firmware: Firmware version (e.g., "1.11", "KY08") + * - serial: Device serial number for unique identification + * + * @note Software Identification Fields: + * - softwareName: Dumping software name (e.g., "Aaru", "ddrescue", "IsoBuster") + * - softwareVersion: Software version (e.g., "5.3.0", "1.25") + * - softwareOperatingSystem: Host OS (e.g., "Linux 5.10.0", "Windows 10", "macOS 12.0") + * + * @note CRC64 Calculation: + * - The function calculates CRC64-ECMA over the payload (everything after the header) + * - The calculated CRC64 is stored in the returned DumpHardwareHeader + * - This allows verification of the serialized block integrity + * - The CRC64 is computed from buffer data, not from the original context + * + * @note Buffer Layout After Successful Call: + * - Offset 0: DumpHardwareHeader with calculated CRC64 + * - Offset 16: First DumpHardwareEntry + * - Followed by: First entry's UTF-8 strings (in documented order) + * - Followed by: First entry's DumpExtent array (if extents > 0) + * - Repeated for all remaining entries + * + * @note Use Cases: + * - Forensic documentation requiring complete equipment chain of custody + * - Archival metadata for long-term preservation requirements + * - Reproducing imaging conditions for verification or re-imaging + * - Identifying firmware-specific issues or drive-specific behaviors + * - Multi-device imaging scenario documentation + * - Correlating imaging artifacts with specific hardware/software combinations + * + * @warning This function reads from the in-memory dump hardware data loaded during aaruf_open(). + * It does not perform file I/O operations. The data is reconstructed from the parsed + * context structures into the on-disk binary format. + * + * @warning The buffer must be valid and large enough to hold the entire dump hardware block. + * Passing a buffer smaller than required will result in AARUF_ERROR_BUFFER_TOO_SMALL. + * + * @warning String data in the serialized block is NOT null-terminated. Applications parsing + * the buffer must use the length fields in DumpHardwareEntry to determine string + * boundaries. The library adds null terminators only for in-memory convenience. + * + * @warning The function performs bounds checking during serialization. If calculated entry + * sizes exceed the buffer length, AARUF_ERROR_BUFFER_TOO_SMALL is returned even + * after the initial size check. This should not occur with properly sized buffers + * but protects against data corruption. + * + * @see DumpHardwareHeader for the block header structure definition. + * @see DumpHardwareEntry for the per-environment entry structure definition. + * @see DumpExtent for the extent range structure definition. + * @see process_dumphw_block() for the loading process during image opening. + */ +int32_t aaruf_get_dumphw(void *context, uint8_t *buffer, size_t *length) +{ + TRACE("Entering aaruf_get_dumphw(%p, %p, %u)", context, buffer, *length); + + aaruformatContext *ctx = NULL; + + if(context == NULL) + { + FATAL("Invalid context"); + + TRACE("Exiting aaruf_get_dumphw() = AARUF_ERROR_NOT_AARUFORMAT"); + return AARUF_ERROR_NOT_AARUFORMAT; + } + + ctx = context; + + // Not a libaaruformat context + if(ctx->magic != AARU_MAGIC) + { + FATAL("Invalid context"); + + TRACE("Exiting aaruf_get_dumphw() = AARUF_ERROR_NOT_AARUFORMAT"); + return AARUF_ERROR_NOT_AARUFORMAT; + } + + if(ctx->dumpHardwareEntriesWithData == NULL || ctx->dumpHardwareHeader.entries == 0 || + ctx->dumpHardwareHeader.identifier != DumpHardwareBlock) + { + FATAL("No dump hardware information present"); + + TRACE("Exiting aaruf_get_dumphw() = AARUF_ERROR_CANNOT_READ_BLOCK"); + return AARUF_ERROR_CANNOT_READ_BLOCK; + } + + size_t required_length = sizeof(DumpHardwareHeader) + ctx->dumpHardwareHeader.length; + + if(buffer == NULL || *length < required_length) + { + TRACE("Buffer too small for dump hardware block, required %zu bytes", required_length); + *length = required_length; + + TRACE("Exiting aaruf_get_dumphw() = AARUF_ERROR_BUFFER_TOO_SMALL"); + return AARUF_ERROR_BUFFER_TOO_SMALL; + } + + *length = required_length; + + // Start to iterate and copy the data + size_t offset = 0; + for(int i = 0; i < ctx->dumpHardwareHeader.entries; i++) + { + size_t entry_size = sizeof(DumpHardwareEntry) + ctx->dumpHardwareEntriesWithData[i].entry.manufacturerLength + + ctx->dumpHardwareEntriesWithData[i].entry.modelLength + + ctx->dumpHardwareEntriesWithData[i].entry.revisionLength + + ctx->dumpHardwareEntriesWithData[i].entry.firmwareLength + + ctx->dumpHardwareEntriesWithData[i].entry.serialLength + + ctx->dumpHardwareEntriesWithData[i].entry.softwareNameLength + + ctx->dumpHardwareEntriesWithData[i].entry.softwareVersionLength + + ctx->dumpHardwareEntriesWithData[i].entry.softwareOperatingSystemLength + + ctx->dumpHardwareEntriesWithData[i].entry.extents * sizeof(DumpExtent); + + if(offset + entry_size > *length) + { + FATAL("Calculated size exceeds provided buffer length"); + TRACE("Exiting aaruf_get_dumphw() = AARUF_ERROR_BUFFER_TOO_SMALL"); + return AARUF_ERROR_BUFFER_TOO_SMALL; + } + + memcpy(buffer + offset, &ctx->dumpHardwareEntriesWithData[i].entry, sizeof(DumpHardwareEntry)); + offset += sizeof(DumpHardwareEntry); + if(ctx->dumpHardwareEntriesWithData[i].entry.manufacturerLength > 0 && + ctx->dumpHardwareEntriesWithData[i].manufacturer != NULL) + { + memcpy(buffer + offset, ctx->dumpHardwareEntriesWithData[i].manufacturer, + ctx->dumpHardwareEntriesWithData[i].entry.manufacturerLength); + offset += ctx->dumpHardwareEntriesWithData[i].entry.manufacturerLength; + } + if(ctx->dumpHardwareEntriesWithData[i].entry.modelLength > 0 && + ctx->dumpHardwareEntriesWithData[i].model != NULL) + { + memcpy(buffer + offset, ctx->dumpHardwareEntriesWithData[i].model, + ctx->dumpHardwareEntriesWithData[i].entry.modelLength); + offset += ctx->dumpHardwareEntriesWithData[i].entry.modelLength; + } + if(ctx->dumpHardwareEntriesWithData[i].entry.revisionLength > 0 && + ctx->dumpHardwareEntriesWithData[i].revision != NULL) + { + memcpy(buffer + offset, ctx->dumpHardwareEntriesWithData[i].revision, + ctx->dumpHardwareEntriesWithData[i].entry.revisionLength); + offset += ctx->dumpHardwareEntriesWithData[i].entry.revisionLength; + } + if(ctx->dumpHardwareEntriesWithData[i].entry.firmwareLength > 0 && + ctx->dumpHardwareEntriesWithData[i].firmware != NULL) + { + memcpy(buffer + offset, ctx->dumpHardwareEntriesWithData[i].firmware, + ctx->dumpHardwareEntriesWithData[i].entry.firmwareLength); + offset += ctx->dumpHardwareEntriesWithData[i].entry.firmwareLength; + } + if(ctx->dumpHardwareEntriesWithData[i].entry.serialLength > 0 && + ctx->dumpHardwareEntriesWithData[i].serial != NULL) + { + memcpy(buffer + offset, ctx->dumpHardwareEntriesWithData[i].serial, + ctx->dumpHardwareEntriesWithData[i].entry.serialLength); + offset += ctx->dumpHardwareEntriesWithData[i].entry.serialLength; + } + if(ctx->dumpHardwareEntriesWithData[i].entry.softwareNameLength > 0 && + ctx->dumpHardwareEntriesWithData[i].softwareName != NULL) + { + memcpy(buffer + offset, ctx->dumpHardwareEntriesWithData[i].softwareName, + ctx->dumpHardwareEntriesWithData[i].entry.softwareNameLength); + offset += ctx->dumpHardwareEntriesWithData[i].entry.softwareNameLength; + } + if(ctx->dumpHardwareEntriesWithData[i].entry.softwareVersionLength > 0 && + ctx->dumpHardwareEntriesWithData[i].softwareVersion != NULL) + { + memcpy(buffer + offset, ctx->dumpHardwareEntriesWithData[i].softwareVersion, + ctx->dumpHardwareEntriesWithData[i].entry.softwareVersionLength); + offset += ctx->dumpHardwareEntriesWithData[i].entry.softwareVersionLength; + } + if(ctx->dumpHardwareEntriesWithData[i].entry.softwareOperatingSystemLength > 0 && + ctx->dumpHardwareEntriesWithData[i].softwareOperatingSystem != NULL) + { + memcpy(buffer + offset, ctx->dumpHardwareEntriesWithData[i].softwareOperatingSystem, + ctx->dumpHardwareEntriesWithData[i].entry.softwareOperatingSystemLength); + offset += ctx->dumpHardwareEntriesWithData[i].entry.softwareOperatingSystemLength; + } + if(ctx->dumpHardwareEntriesWithData[i].entry.extents > 0 && ctx->dumpHardwareEntriesWithData[i].extents != NULL) + { + memcpy(buffer + offset, ctx->dumpHardwareEntriesWithData[i].extents, + ctx->dumpHardwareEntriesWithData[i].entry.extents * sizeof(DumpExtent)); + offset += ctx->dumpHardwareEntriesWithData[i].entry.extents * sizeof(DumpExtent); + } + } + + // Calculate CRC64 + ctx->dumpHardwareHeader.crc64 = + aaruf_crc64_data(buffer + sizeof(DumpHardwareHeader), ctx->dumpHardwareHeader.length); + + // Copy header + memcpy(buffer, &ctx->dumpHardwareHeader, sizeof(DumpHardwareHeader)); + + TRACE("Exiting aaruf_get_dumphw() = AARUF_STATUS_OK"); + return AARUF_STATUS_OK; +} \ No newline at end of file