zupt/tests/archive_path_fixture.c
2026-08-31 18:12:27 -03:00

194 lines
7.5 KiB
C

/* SPDX-License-Identifier: AGPL-3.0-or-later
* Copyright (c) 2025-2026 Cristian Cezar Moisés
*
* Build a minimal, structurally valid plaintext archive with an arbitrary
* index path. This is test infrastructure for extraction-path policy: unlike
* byte mutation, the resulting index checksum and archive-integrity trailer
* are valid, so a rejection necessarily reaches the path validation code.
*/
#include "zupt.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int put_u8(FILE *stream, uint8_t value) {
return fputc(value, stream) == EOF ? -1 : 0;
}
static int put_u16le(FILE *stream, uint16_t value) {
return put_u8(stream, (uint8_t)value) ||
put_u8(stream, (uint8_t)(value >> 8)) ? -1 : 0;
}
static size_t put_u32le(uint8_t *out, uint32_t value) {
for (size_t i = 0; i < 4; i++) out[i] = (uint8_t)(value >> (i * 8));
return 4;
}
static size_t put_u64le(uint8_t *out, uint64_t value) {
for (size_t i = 0; i < 8; i++) out[i] = (uint8_t)(value >> (i * 8));
return 8;
}
static size_t put_varint(uint8_t *out, uint64_t value) {
size_t count = 0;
while (value >= 0x80) {
out[count++] = (uint8_t)(value | 0x80);
value >>= 7;
}
out[count++] = (uint8_t)value;
return count;
}
static int hex_nibble(unsigned char value) {
if (value >= '0' && value <= '9') return (int)(value - '0');
if (value >= 'a' && value <= 'f') return (int)(value - 'a') + 10;
if (value >= 'A' && value <= 'F') return (int)(value - 'A') + 10;
return -1;
}
static int decode_hex_entry(const char *hex, uint8_t *out, size_t capacity,
size_t *out_size) {
size_t hex_size = strlen(hex);
if (hex_size == 0 || (hex_size & 1u) != 0 ||
hex_size / 2u >= capacity)
return -1;
size_t decoded_size = hex_size / 2u;
for (size_t i = 0; i < decoded_size; i++) {
int high = hex_nibble((unsigned char)hex[i * 2u]);
int low = hex_nibble((unsigned char)hex[i * 2u + 1u]);
if (high < 0 || low < 0) return -1;
out[i] = (uint8_t)((high << 4) | low);
}
*out_size = decoded_size;
return 0;
}
static int write_block(FILE *stream, uint8_t type, const uint8_t *payload,
size_t payload_size, uint64_t unpacked_size,
uint64_t checksum) {
uint8_t varint[10];
size_t varint_size;
if (put_u8(stream, ZUPT_BLOCK_MAGIC_0) ||
put_u8(stream, ZUPT_BLOCK_MAGIC_1) || put_u8(stream, type) ||
put_u16le(stream, ZUPT_CODEC_STORE) || put_u16le(stream, 0))
return -1;
varint_size = put_varint(varint, unpacked_size);
if (fwrite(varint, 1, varint_size, stream) != varint_size) return -1;
varint_size = put_varint(varint, payload_size);
if (fwrite(varint, 1, varint_size, stream) != varint_size) return -1;
uint8_t checksum_bytes[8];
put_u64le(checksum_bytes, checksum);
if (fwrite(checksum_bytes, 1, sizeof(checksum_bytes), stream) !=
sizeof(checksum_bytes))
return -1;
return payload_size == 0 ||
fwrite(payload, 1, payload_size, stream) == payload_size ? 0 : -1;
}
int main(int argc, char **argv) {
static const uint8_t content[] = "fixture content\n";
uint8_t decoded_entry[ZUPT_MAX_PATH];
const uint8_t *entry = NULL;
size_t path_size = 0;
if (argc == 3 && strncmp(argv[2], "--entry=", 8) == 0) {
entry = (const uint8_t *)argv[2] + 8;
path_size = strlen(argv[2] + 8);
} else if (argc == 3 &&
strncmp(argv[2], "--entry-hex=", 12) == 0 &&
decode_hex_entry(argv[2] + 12, decoded_entry,
sizeof(decoded_entry), &path_size) == 0) {
entry = decoded_entry;
}
if (!entry || argv[1][0] == '\0' || path_size == 0 ||
path_size >= ZUPT_MAX_PATH) {
fprintf(stderr,
"usage: %s ARCHIVE --entry=ENTRY_PATH|--entry-hex=HEX_BYTES\n",
argv[0]);
return 2;
}
FILE *stream = fopen(argv[1], "wb");
if (!stream) return 1;
zupt_archive_header_t header;
memset(&header, 0, sizeof(header));
const uint8_t magic[6] = { ZUPT_MAGIC_0, ZUPT_MAGIC_1, ZUPT_MAGIC_2,
ZUPT_MAGIC_3, ZUPT_MAGIC_4, ZUPT_MAGIC_5 };
memcpy(header.magic, magic, sizeof(magic));
header.version_major = ZUPT_FORMAT_MAJOR;
header.version_minor = ZUPT_FORMAT_MINOR;
uint8_t serialized_header[ZUPT_ARCHIVE_HEADER_SIZE] = {0};
memcpy(serialized_header, header.magic, sizeof(header.magic));
serialized_header[6] = header.version_major;
serialized_header[7] = header.version_minor;
put_u32le(serialized_header + 8, header.global_flags);
put_u64le(serialized_header + 12, header.creation_time);
memcpy(serialized_header + 20, header.archive_id, sizeof(header.archive_id));
put_u64le(serialized_header + 36, header.encryption_header_off);
put_u64le(serialized_header + 44, header.comment_offset);
memcpy(serialized_header + 52, header.reserved, sizeof(header.reserved));
if (fwrite(serialized_header, 1, sizeof(serialized_header), stream) !=
sizeof(serialized_header)) goto fail;
const size_t content_size = sizeof(content) - 1;
uint64_t content_hash = zupt_xxh64(content, content_size, 0);
uint64_t data_offset = (uint64_t)ftell(stream);
if (write_block(stream, ZUPT_BLOCK_DATA, content, content_size,
content_size, content_hash) != 0)
goto fail;
uint8_t index[ZUPT_MAX_PATH + 128];
size_t index_size = 0;
index_size += put_varint(index + index_size, 1);
index_size += put_varint(index + index_size, path_size);
memcpy(index + index_size, entry, path_size);
index_size += path_size;
index_size += put_u64le(index + index_size, content_size);
index_size += put_u64le(index + index_size, content_size);
index_size += put_u64le(index + index_size, 0);
index_size += put_u64le(index + index_size, content_hash);
index_size += put_u64le(index + index_size, data_offset);
index_size += put_varint(index + index_size, 1);
index_size += put_u32le(index + index_size, 0600);
uint64_t index_offset = (uint64_t)ftell(stream);
if (write_block(stream, ZUPT_BLOCK_INDEX, index, index_size, index_size,
zupt_xxh64(index, index_size, 0)) != 0)
goto fail;
zupt_footer_t footer;
memset(&footer, 0, sizeof(footer));
footer.index_offset = index_offset;
footer.total_blocks = 1;
footer.archive_checksum = (uint64_t)ftell(stream);
memcpy(footer.footer_magic, "ZEND", 4);
footer.footer_version = 1;
uint8_t serialized_footer[ZUPT_FOOTER_SIZE] = {0};
put_u64le(serialized_footer, footer.index_offset);
put_u64le(serialized_footer + 8, footer.total_blocks);
put_u64le(serialized_footer + 16, footer.archive_checksum);
memcpy(serialized_footer + 24, footer.footer_magic,
sizeof(footer.footer_magic));
put_u32le(serialized_footer + 28, footer.footer_version);
if (fwrite(serialized_footer, 1, sizeof(serialized_footer), stream) !=
sizeof(serialized_footer)) goto fail;
uint8_t mac_input[ZUPT_AIT_MAC_INPUT_LEN];
uint8_t trailer[ZUPT_AIT_SIZE];
memcpy(mac_input, serialized_header, sizeof(serialized_header));
memcpy(mac_input + sizeof(serialized_header), serialized_footer, 24);
memset(trailer, 0, sizeof(trailer));
put_u64le(trailer, zupt_xxh64(mac_input, sizeof(mac_input), 0));
if (fwrite(trailer, sizeof(trailer), 1, stream) != 1 || fclose(stream) != 0)
return 1;
return 0;
fail:
fclose(stream);
return 1;
}