Files
zap/zapclient/opcode_fuzz_test.go
Abhishek Krishna d190668273 test: add composite + schema + opcode fuzz targets
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.
2026-06-01 12:10:51 +00:00

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)
}
})
}