/src/zxc/tests/fuzz/fuzz_roundtrip.c
Line | Count | Source |
1 | | // SPDX-License-Identifier: BSD-3-Clause |
2 | | /* |
3 | | * ZXC - High-performance lossless compression |
4 | | * |
5 | | * Copyright (c) Bertrand Lebonnois and contributors. |
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 | 25.9k | #define FUZZ_ROUNDTRIP_MAX_INPUT (4 << 20) /* 4 MiB */ |
33 | | |
34 | 25.9k | int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { |
35 | 25.9k | static void* comp_buf = NULL; |
36 | 25.9k | static size_t comp_cap = 0; |
37 | 25.9k | static void* decomp_buf = NULL; |
38 | 25.9k | static size_t decomp_cap = 0; |
39 | | |
40 | 25.9k | if (size > FUZZ_ROUNDTRIP_MAX_INPUT) return 0; |
41 | | |
42 | 25.9k | const size_t bound = zxc_compress_bound(size); |
43 | 25.9k | if (bound == 0) return 0; |
44 | 25.9k | if (bound > comp_cap) { |
45 | 13.4k | void* new_buf = realloc(comp_buf, bound); |
46 | 13.4k | if (!new_buf) return 0; |
47 | 13.4k | comp_buf = new_buf; |
48 | 13.4k | comp_cap = bound; |
49 | 13.4k | } |
50 | | |
51 | 25.9k | const int level = size > 0 ? (data[0] % (unsigned)zxc_max_level()) + 1 : 1; |
52 | 25.9k | const int64_t csize = zxc_compress(data, size, comp_buf, bound, level); |
53 | 25.9k | if (csize < 0) return 0; |
54 | | |
55 | 25.9k | if (size == 0) return 0; |
56 | | |
57 | 25.9k | if (size > decomp_cap) { |
58 | 13.4k | void* new_buf = realloc(decomp_buf, size); |
59 | 13.4k | if (!new_buf) return 0; |
60 | 13.4k | decomp_buf = new_buf; |
61 | 13.4k | decomp_cap = size; |
62 | 13.4k | } |
63 | | |
64 | 25.9k | const int64_t dsize = zxc_decompress(comp_buf, (size_t)csize, decomp_buf, size); |
65 | | |
66 | 25.9k | if (dsize >= 0) { |
67 | 25.9k | assert((size_t)dsize == size); |
68 | 25.9k | assert(memcmp(data, decomp_buf, size) == 0); |
69 | 25.9k | } |
70 | | |
71 | 25.9k | return 0; |
72 | 25.9k | } |