/src/zxc/tests/fuzz_roundtrip.c
Line | Count | Source |
1 | | /* |
2 | | * ZXC - High-performance lossless compression |
3 | | * |
4 | | * Copyright (c) 2025-2026 Bertrand Lebonnois and contributors. |
5 | | * SPDX-License-Identifier: BSD-3-Clause |
6 | | */ |
7 | | |
8 | | /** |
9 | | * @file fuzz_roundtrip.c |
10 | | * @brief Fuzzer for the one-shot compress -> decompress roundtrip. |
11 | | * |
12 | | * Asserts the core invariant of a lossless codec: decompressing what the |
13 | | * compressor produced must reproduce the original bytes exactly. The fuzzer |
14 | | * data is the payload to compress (never untrusted decoder input -- that is |
15 | | * fuzz_decompress.c's job), so this target hunts for encoder/decoder |
16 | | * mismatches rather than malformed-frame handling. |
17 | | * |
18 | | * Strategy: derive the compression level from the first byte so libFuzzer |
19 | | * explores every level, compress the fuzzed bytes, then decompress into an |
20 | | * exact-size buffer and assert a bit-exact roundtrip. Buffers are reused |
21 | | * across iterations to keep allocator pressure low. |
22 | | */ |
23 | | |
24 | | #include <assert.h> |
25 | | #include <stddef.h> |
26 | | #include <stdint.h> |
27 | | #include <stdlib.h> |
28 | | #include <string.h> |
29 | | |
30 | | #include "../include/zxc_buffer.h" |
31 | | |
32 | 11.9k | #define FUZZ_ROUNDTRIP_MAX_INPUT (4 << 20) /* 4 MiB */ |
33 | | |
34 | 11.9k | int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { |
35 | 11.9k | static void* comp_buf = NULL; |
36 | 11.9k | static size_t comp_cap = 0; |
37 | 11.9k | static void* decomp_buf = NULL; |
38 | 11.9k | static size_t decomp_cap = 0; |
39 | | |
40 | 11.9k | if (size > FUZZ_ROUNDTRIP_MAX_INPUT) return 0; |
41 | | |
42 | 11.9k | const uint64_t bound64 = zxc_compress_bound(size); |
43 | 11.9k | if (bound64 == 0 || bound64 > SIZE_MAX) return 0; |
44 | 11.9k | const size_t bound = (size_t)bound64; |
45 | 11.9k | if (bound > comp_cap) { |
46 | 6.26k | void* new_buf = realloc(comp_buf, bound); |
47 | 6.26k | if (!new_buf) return 0; |
48 | 6.26k | comp_buf = new_buf; |
49 | 6.26k | comp_cap = bound; |
50 | 6.26k | } |
51 | | |
52 | 11.9k | const int level = size > 0 ? (data[0] % (unsigned)zxc_max_level()) + 1 : 1; |
53 | 11.9k | zxc_compress_opts_t copts = {.level = level}; |
54 | 11.9k | const int64_t csize = zxc_compress(data, size, comp_buf, bound, &copts); |
55 | 11.9k | if (csize < 0) return 0; |
56 | | |
57 | 11.9k | if (size == 0) return 0; |
58 | | |
59 | 11.9k | if (size > decomp_cap) { |
60 | 6.26k | void* new_buf = realloc(decomp_buf, size); |
61 | 6.26k | if (!new_buf) return 0; |
62 | 6.26k | decomp_buf = new_buf; |
63 | 6.26k | decomp_cap = size; |
64 | 6.26k | } |
65 | | |
66 | 11.9k | const int64_t dsize = zxc_decompress(comp_buf, (size_t)csize, decomp_buf, size, NULL); |
67 | | |
68 | 11.9k | if (dsize >= 0) { |
69 | 11.9k | assert((size_t)dsize == size); |
70 | 11.9k | assert(memcmp(data, decomp_buf, size) == 0); |
71 | 11.9k | } |
72 | | |
73 | 11.9k | return 0; |
74 | 11.9k | } |