# Structure-Aware Fuzzing

The bytes-in interface of most [[coverage-guided-fuzzing|coverage-guided fuzzers]] (libFuzzer's `LLVMFuzzerTestOneInput(const uint8_t*, size_t)`, Go's `f.Fuzz(func(t *testing.T, b []byte))`) is the wrong shape when the target is an in-process API that takes a struct. The author has to write a wire-format wrapper that decodes a `[]byte` into the struct, every field becomes a bug surface, and the fuzzer wastes mutations producing inputs that don't decode.

Structure-aware fuzzing lets the harness say: "the input is this type." The fuzzer keeps mutating bytes internally, but the runtime serializes the bytes into a valid value of the declared type on the way in. From the target's perspective, the input is already typed.

```go
type Input struct {
    Data []byte
    S    string
    N    int
}

func FuzzStructInput(f *testing.F) {
    f.Add(Input{Data: []byte("hello"), S: "world", N: 42})
    f.Fuzz(func(t *testing.T, in Input) {
        Process(in)
    })
}
```

The fuzzer is still byte-driven underneath — the corpus, the mutators, the coverage feedback all operate on bytes — but the wrapper layer means a single random mutation can mean "add an element to `Data`," "change `N` by one bit," or "swap two characters in `S`," depending on where the bytes land.

## Vanilla Go fuzzing's limit

Go's stdlib fuzzer is the basic case: `f.Fuzz` only accepts callbacks taking the primitive types (`[]byte`, `string`, integer types, `bool`, the float types). Anything composite — a struct, a slice of structs, a pointer — needs a hand-written decode in the harness body. [[gosentry-go-fuzzing-fork|gosentry]] generalizes this to arbitrary types via the LibAFL runner.

## Rust / C++ analogues

The `arbitrary` crate (Rust) and FuzzedDataProvider (libFuzzer C++) are the same idea: a small library that takes a byte buffer and produces a typed value, deterministic enough that the fuzzer's mutations propagate through it. cargo-fuzz wires `arbitrary` into the `fuzz_target!` macro the same way gosentry wires its decoder into `f.Fuzz`.

## When to reach for it instead of a grammar

[[grammar-based-fuzzing]] is the right tool when the input is bytes on a wire (a serialized RPC frame, a JSON document, source code in some language); the grammar describes the *bytes*. Structure-aware fuzzing is the right tool when the input is a *value the API takes* directly — a Go struct passed to an internal function, a Rust enum, a C++ object. The two compose: a grammar generates the bytes that get parsed into a struct, structure-aware fuzzing then mutates the struct fields directly to exercise the post-parse code.
