From bb15175b869e3e710399eb124fafe3f5f84a5d06 Mon Sep 17 00:00:00 2001 From: Teodor Calin Date: Wed, 27 May 2026 22:51:25 -0700 Subject: [PATCH] tests: add fuzz targets for trace-frame encode/decode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ReadFrame / WriteFrame / ReadEvent already have Fuzz coverage in zz_fuzz_frames_test.go. The trace-frame inner encoder/decoder (WriteTraceFrame + ReadTracePayload) was not yet fuzzed. zz_fuzz_trace_test.go: - FuzzReadTracePayload: decoder fuzz; 5 seeds including the one-byte-under-minimum guard case - FuzzWriteThenReadTrace: round-trip property — encode arbitrary (innerType, sentAt, payload) and assert the three fields survive ReadFrame + ReadTracePayload 30s local runs: both PASS, ~3M execs each, zero panics, zero hangs, zero crash-corpus entries written. Existing FuzzDataExchangeReadFrame / RoundTrip / FileFrame and FuzzEventStream* targets also re-run clean (2.7M / 3.0M / 250k / 2.4M / 3.3M execs respectively). --- zz_fuzz_trace_test.go | 110 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 zz_fuzz_trace_test.go diff --git a/zz_fuzz_trace_test.go b/zz_fuzz_trace_test.go new file mode 100644 index 0000000..024f889 --- /dev/null +++ b/zz_fuzz_trace_test.go @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +package dataexchange_test + +import ( + "bytes" + "encoding/binary" + "testing" + + "github.com/pilot-protocol/dataexchange" +) + +// FuzzReadTracePayload exercises the trace-frame inner decoder. The +// caller hands ReadTracePayload a Frame whose Payload is wire-controlled; +// the decoder reads inner_type (4B) + sent_at_ns (8B) and slices the rest. +// Out-of-bounds risks: if the length guard (`< 12`) ever regressed, a +// 0-byte payload would crash. Round-trip via WriteTraceFrame + +// ReadFrame + ReadTracePayload validates encode-decode symmetry. +func FuzzReadTracePayload(f *testing.F) { + // Seed: valid trace frame. + { + var buf bytes.Buffer + _ = dataexchange.WriteTraceFrame(&buf, &dataexchange.TraceFrame{ + InnerType: dataexchange.TypeText, SentAtNs: 1_700_000_000_000_000_000, + Payload: []byte("hello"), + }) + frame, _ := dataexchange.ReadFrame(&buf) + if frame != nil { + f.Add(frame.Payload) + } + } + // Seed: minimum valid (12 bytes — empty inner payload). + f.Add(make([]byte, 12)) + // Seed: zero payload (should error cleanly). + f.Add([]byte{}) + // Seed: 11 bytes (one byte under the minimum guard). + f.Add(make([]byte, 11)) + // Seed: large negative SentAtNs (sign-bit set across all 8 bytes). + { + buf := make([]byte, 12+4) + binary.BigEndian.PutUint32(buf[0:4], 0xFFFFFFFF) + for i := 4; i < 12; i++ { + buf[i] = 0xFF + } + f.Add(buf) + } + + f.Fuzz(func(t *testing.T, data []byte) { + defer func() { + if r := recover(); r != nil { + t.Errorf("panic on input %x: %v", data, r) + } + }() + fr := &dataexchange.Frame{Type: dataexchange.TypeTrace, Payload: data} + _, _ = dataexchange.ReadTracePayload(fr) + }) +} + +// FuzzWriteThenReadTrace is the round-trip variant: build a TraceFrame +// from random fields, encode via WriteTraceFrame, decode via ReadFrame + +// ReadTracePayload, and assert the recovered fields match. +func FuzzWriteThenReadTrace(f *testing.F) { + f.Add(uint32(1), int64(0), []byte("hi")) + f.Add(uint32(2), int64(-1), []byte{}) + f.Add(uint32(0xFFFFFFFF), int64(1<<62), bytes.Repeat([]byte{0xAA}, 64)) + + f.Fuzz(func(t *testing.T, innerType uint32, sentAt int64, payload []byte) { + if len(payload) > 1<<16 { + return + } + defer func() { + if r := recover(); r != nil { + t.Errorf("panic on input type=%d sentAt=%d payload=%x: %v", + innerType, sentAt, payload, r) + } + }() + + tf := &dataexchange.TraceFrame{ + InnerType: innerType, SentAtNs: sentAt, Payload: payload, + } + var buf bytes.Buffer + if err := dataexchange.WriteTraceFrame(&buf, tf); err != nil { + t.Errorf("WriteTraceFrame: %v", err) + return + } + frame, err := dataexchange.ReadFrame(&buf) + if err != nil { + t.Errorf("ReadFrame: %v", err) + return + } + if frame.Type != dataexchange.TypeTrace { + t.Errorf("expected TypeTrace, got %d", frame.Type) + return + } + got, err := dataexchange.ReadTracePayload(frame) + if err != nil { + t.Errorf("ReadTracePayload: %v", err) + return + } + if got.InnerType != innerType { + t.Errorf("innerType: %d != %d", got.InnerType, innerType) + } + if got.SentAtNs != sentAt { + t.Errorf("sentAt: %d != %d", got.SentAtNs, sentAt) + } + if !bytes.Equal(got.Payload, payload) { + t.Errorf("payload mismatch") + } + }) +}