mirror of
https://github.com/luxfi/zap.git
synced 2026-10-30 15:04:16 +00:00
The existing fuzz_test.go covers Parse, a flat-object Roundtrip,
malformed headers, and large payloads. This commit adds four targets
that probe shapes and code paths real consumers depend on but the
current corpus does not:
* FuzzZAPCompositeRoundtrip exercises the full Builder pipeline on a
composite shape: an outer object with a nested object, a uint32 list,
a uint64 list, a text field, and a bytes field. Every consumer
(MCP tool calls, consensus messages, agent payments) ships composites
on the wire, so a roundtrip target for them catches the relative-
offset and deferred-data math the flat fuzz cannot.
* FuzzZAPSchemaBuilder drives schema.go's StructBuilder against
arbitrary type-tag sequences and checks the alignment + size
invariants. schema.go had no fuzz coverage; alignment math is
exactly where a generative target pays.
* FuzzZAPFloatRoundtrip verifies Float32/Float64 fields survive
Builder -> Parse with bitwise fidelity (NaN, +/-Inf, subnormals).
The existing roundtrip fuzz covered only ints + text + bool.
* FuzzProcedureOpcode (zapclient) asserts ProcedureOpcode's documented
invariants on arbitrary names: empty -> error, low byte = 0,
high byte not in {0x00, 0xFF}, deterministic.
All four targets pass their seed corpus and run cleanly for tens of
seconds of -fuzztime locally without finding a crash. Pure additive:
new files, no overlap with PRs #1, #2, #3.
69 lines
1.9 KiB
Go
69 lines
1.9 KiB
Go
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package zapclient
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
// FuzzProcedureOpcode asserts the documented invariants of ProcedureOpcode
|
|
// against arbitrary procedure names:
|
|
//
|
|
// 1. Empty name returns an error.
|
|
// 2. Non-empty name never errors and never returns 0x0000 or 0xFF00 as
|
|
// the high byte (both reserved per codec.go's contract).
|
|
// 3. The low byte is always zero (preserves the MsgType<<8 layout used
|
|
// by lux/zap consensus tests).
|
|
// 4. The result is deterministic for the same input.
|
|
//
|
|
// codec.go has unit tests for a handful of curated names; this target
|
|
// drives it against arbitrary bytes (including embedded NULs, very long
|
|
// strings, and non-ASCII input).
|
|
func FuzzProcedureOpcode(f *testing.F) {
|
|
f.Add("")
|
|
f.Add("a")
|
|
f.Add("ListSecurity")
|
|
f.Add("DelistSecurity")
|
|
f.Add("AddIdentitiesBulk")
|
|
f.Add("Settle")
|
|
f.Add("name with\x00embedded null")
|
|
f.Add("unicode ☃ \U0001F600")
|
|
f.Add("very-long-procedure-name-that-exceeds-typical-bounds-1234567890")
|
|
|
|
f.Fuzz(func(t *testing.T, name string) {
|
|
op, err := ProcedureOpcode(name)
|
|
|
|
if name == "" {
|
|
if err == nil {
|
|
t.Errorf("empty name: expected error, got opcode %#x", op)
|
|
}
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
t.Fatalf("ProcedureOpcode(%q) unexpected err: %v", name, err)
|
|
}
|
|
|
|
// Low byte must be zero (MsgType<<8 layout).
|
|
if low := byte(op & 0xFF); low != 0 {
|
|
t.Errorf("low byte of opcode %#x = %#x, want 0", op, low)
|
|
}
|
|
|
|
// High byte must NOT be reserved.
|
|
hi := byte(op >> 8)
|
|
if hi == 0x00 || hi == 0xFF {
|
|
t.Errorf("opcode %#x for %q falls in reserved high byte %#x", op, name, hi)
|
|
}
|
|
|
|
// Determinism: second call returns the same value.
|
|
op2, err2 := ProcedureOpcode(name)
|
|
if err2 != nil {
|
|
t.Fatalf("second ProcedureOpcode(%q) err: %v", name, err2)
|
|
}
|
|
if op != op2 {
|
|
t.Errorf("nondeterministic opcode for %q: %#x vs %#x", name, op, op2)
|
|
}
|
|
})
|
|
}
|