//! The `std.testing.Smith` byte encoding, shared by every fuzz corpus.
//!
//! `Smith` does not consume a corpus entry as raw parser input. It reads a byte
//! stream in which a slice is a little-endian `u32` length followed by that many
//! bytes, and an integer is a little-endian `u64`. Every corpus entry in
//! `tests/fuzz/` is therefore length-prefixed, and every fuzz file used to spell
//! the same encoder out.
//!
//! This file imports nothing but `std` on purpose. The fuzz targets root
//! separate modules over different parts of `src/` — `blocklist_fuzz.zig` cannot
//! import `corpus.zig`, because `corpus.zig` needs the `dns` module that the
//! blocklist target's build does not have. Each fuzz module reaches this file by
//! relative path and compiles its own copy, so the dedup is at the source level;
//! the self-tests below run once per fuzz artifact.

const std = @import("std");

/// Encodes `bytes` as a single `Smith.slice` value.
pub fn sliceInput(comptime bytes: []const u8) *const [4 + bytes.len]u8 {
    return &struct {
        const value: [4 + bytes.len]u8 = blk: {
            var buf: [4 + bytes.len]u8 = undefined;
            std.mem.writeInt(u32, buf[0..4], @intCast(bytes.len), .little);
            buf[4..].* = bytes[0..bytes.len].*;
            break :blk buf;
        };
    }.value;
}

/// Encodes two `Smith.slice` values back to back, which is what a target that
/// reads two entities takes: the pattern and the domain, or the query string and
/// the key.
pub fn pairInput(comptime a: []const u8, comptime b: []const u8) *const [8 + a.len + b.len]u8 {
    return &struct {
        const value: [8 + a.len + b.len]u8 = blk: {
            var buf: [8 + a.len + b.len]u8 = undefined;
            buf[0 .. 4 + a.len].* = sliceInput(a).*;
            buf[4 + a.len ..].* = sliceInput(b).*;
            break :blk buf;
        };
    }.value;
}

test "a slice input carries its own length" {
    const encoded = sliceInput("ads.example.com");
    try std.testing.expectEqual(
        @as(u32, "ads.example.com".len),
        std.mem.readInt(u32, encoded[0..4], .little),
    );
    try std.testing.expectEqualSlices(u8, "ads.example.com", encoded[4..]);
}

test "a paired input carries both lengths" {
    const encoded = pairInput("*.a.b", "x.a.b");
    try std.testing.expectEqual(@as(u32, 5), std.mem.readInt(u32, encoded[0..4], .little));
    try std.testing.expectEqualSlices(u8, "*.a.b", encoded[4..9]);
    try std.testing.expectEqual(@as(u32, 5), std.mem.readInt(u32, encoded[9..13], .little));
    try std.testing.expectEqualSlices(u8, "x.a.b", encoded[13..]);
}

test "an empty slice input is four bytes of zero" {
    const encoded = sliceInput("");
    try std.testing.expectEqualSlices(u8, &.{ 0, 0, 0, 0 }, encoded);
}
