//! Seeded, parameterized fuzz targets for the delta format
//! (draft-fossil-delta-format-00). Each target mirrors one of the fixed-seed
//! blocks in delta.zig's own test suite, but takes its seed and round count
//! from the caller (`zig build fuzz -- <target> [--seed N] [--rounds N]`)
//! instead of a hardcoded constant, so an out-of-band runner can sweep many
//! seeds at much higher round counts. Registered in fuzz_tests.zig.
const std = @import("std");
const delta = @import("zit").delta;
/// `apply` on arbitrary source+delta bytes never crashes: only a declared
/// `Error`/`Allocator.Error` or a freed value comes back (delta §4
/// robustness; mirrors delta.zig's "apply on arbitrary source+delta never
/// crashes" block).
pub fn applyGarbage(gpa: std.mem.Allocator, seed: u64, rounds: usize) !void {
var prng = std.Random.DefaultPrng.init(seed);
const rand = prng.random();
var round: usize = 0;
while (round < rounds) : (round += 1) {
var sbuf: [128]u8 = undefined;
var dbuf: [160]u8 = undefined;
const slen = rand.uintLessThan(usize, sbuf.len + 1);
const dlen = rand.uintLessThan(usize, dbuf.len + 1);
for (sbuf[0..slen]) |*b| b.* = rand.int(u8);
// Bias delta bytes toward DIGIT64/opcodes so inputs reach deep into
// the decoder rather than being rejected at byte 0.
for (dbuf[0..dlen]) |*b| b.* = if (rand.boolean())
rand.int(u8)
else
delta.digit64[rand.uintLessThan(usize, delta.digit64.len)];
if (delta.apply(gpa, sbuf[0..slen], dbuf[0..dlen])) |out| gpa.free(out) else |_| {}
}
}
/// `apply(source, create(source, target)) == target` for arbitrary inputs
/// (delta §5 round-trip; mirrors delta.zig's round-trip block).
pub fn roundTrip(gpa: std.mem.Allocator, seed: u64, rounds: usize) !void {
var prng = std.Random.DefaultPrng.init(seed);
const rand = prng.random();
var round: usize = 0;
while (round < rounds) : (round += 1) {
var sbuf: [256]u8 = undefined;
const slen = rand.uintLessThan(usize, sbuf.len + 1);
for (sbuf[0..slen]) |*b| b.* = rand.int(u8);
const source = sbuf[0..slen];
// Build target from a mix of source runs (exercises copies) and
// fresh random bytes (exercises literals).
var tbuf: [384]u8 = undefined;
const tlen = rand.uintLessThan(usize, tbuf.len + 1);
var t: usize = 0;
while (t < tlen) {
if (slen >= 8 and rand.boolean()) {
const start = rand.uintLessThan(usize, slen);
const run = 1 + rand.uintLessThan(usize, @min(slen - start, tlen - t));
@memcpy(tbuf[t..][0..run], source[start..][0..run]);
t += run;
} else {
tbuf[t] = rand.int(u8);
t += 1;
}
}
const target = tbuf[0..tlen];
const d = try delta.create(gpa, source, target);
defer gpa.free(d);
const out = try delta.apply(gpa, source, d);
defer gpa.free(out);
try std.testing.expectEqualSlices(u8, target, out);
}
}
/// `decodeInt` on arbitrary digit strings stays bounded: consumes no more
/// than the input, and the only error it ever returns is `MalformedDelta`
/// (delta §3.1; mirrors delta.zig's "decodeInt on arbitrary digit strings
/// stays bounded" block).
pub fn decodeIntBounded(gpa: std.mem.Allocator, seed: u64, rounds: usize) !void {
_ = gpa;
var prng = std.Random.DefaultPrng.init(seed);
const rand = prng.random();
var round: usize = 0;
while (round < rounds) : (round += 1) {
var buf: [40]u8 = undefined;
const len = rand.uintLessThan(usize, buf.len + 1);
// Bias toward DIGIT64 so long all-digit runs (overflow candidates) occur.
for (buf[0..len]) |*b| b.* = if (rand.uintLessThan(u8, 4) == 0)
rand.int(u8)
else
delta.digit64[rand.uintLessThan(usize, delta.digit64.len)];
if (delta.decodeInt(buf[0..len])) |dec| {
try std.testing.expect(dec.len <= len); // consumed no more than the input
} else |err| {
try std.testing.expect(err == error.MalformedDelta); // rejection is the only error
}
}
}
/// `create` rejects rather than panics/wraps on synthetic source/target sizes
/// `>= 2^32` (delta §3.1: every integer the format carries must be < 2^32;
/// mirrors delta.zig's "create rejects synthetic sizes >= 2^32" block). Sizes
/// are drawn only from `[2^32, 2^40]`: the guard in `create` returns before
/// any deref or allocation, so an oversized slice can be built from a single
/// fake base pointer without touching 4+ GiB of real memory. A length below
/// 2^32 is out of scope here (it would proceed into the loop and dereference)
/// and is covered instead by `roundTrip`.
pub fn createLargeSizes(gpa: std.mem.Allocator, seed: u64, rounds: usize) !void {
if (@bitSizeOf(usize) < 33) return; // a length >= 2^32 is unrepresentable on 32-bit
var prng = std.Random.DefaultPrng.init(seed);
const rand = prng.random();
var byte: u8 = 0;
const min_len: usize = @as(usize, 1) << 32;
const max_span: usize = (@as(usize, 1) << 40) - min_len;
var round: usize = 0;
while (round < rounds) : (round += 1) {
const len = min_len + rand.uintLessThan(usize, max_span + 1);
const huge = @as([*]const u8, @ptrCast(&byte))[0..len]; // never dereferenced: the guard returns first
const variant = rand.uintLessThan(u8, 3); // 0: source oversized, 1: target oversized, 2: both
const source: []const u8 = if (variant == 1) "" else huge;
const target: []const u8 = if (variant == 0) "" else huge;
try std.testing.expectError(delta.CreateError.TargetTooLarge, delta.create(gpa, source, target));
}
}
/// `targetSize` on arbitrary bytes never crashes: only `MalformedDelta` or a
/// value within the u32 cap `decodeInt` enforces comes back (delta §4.1).
pub fn targetSizeGarbage(gpa: std.mem.Allocator, seed: u64, rounds: usize) !void {
_ = gpa;
var prng = std.Random.DefaultPrng.init(seed);
const rand = prng.random();
var round: usize = 0;
while (round < rounds) : (round += 1) {
var buf: [64]u8 = undefined;
const len = rand.uintLessThan(usize, buf.len + 1);
for (buf[0..len]) |*b| b.* = if (rand.boolean())
rand.int(u8)
else
delta.digit64[rand.uintLessThan(usize, delta.digit64.len)];
if (delta.targetSize(buf[0..len])) |size| {
try std.testing.expect(size <= std.math.maxInt(u32));
} else |err| {
try std.testing.expect(err == error.MalformedDelta);
}
}
}