Files
petrbalvin a9b8039ef7
Test / test (push) Successful in 2m4s
Release / gates (push) Successful in 2m5s
Release / build (amd64, freebsd) (push) Successful in 1m27s
Release / build (amd64, linux) (push) Successful in 1m22s
Release / build (amd64, netbsd) (push) Successful in 1m19s
Release / build (amd64, openbsd) (push) Successful in 1m20s
Release / build (arm64, darwin) (push) Successful in 1m21s
Release / build (arm64, freebsd) (push) Successful in 1m26s
Release / build (arm64, linux) (push) Successful in 1m25s
Release / build (arm64, netbsd) (push) Successful in 1m31s
Release / build (arm64, openbsd) (push) Successful in 1m27s
Release / build (loong64, linux) (push) Successful in 1m37s
Release / build (riscv64, linux) (push) Successful in 1m21s
Release / release (push) Successful in 40s
feat: full NFSv4.2 server and client in pure Go
Assisted-by: GLM 5.3 Flash
2026-09-21 18:51:17 +02:00

104 lines
3.4 KiB
Go

// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: MIT
package nfs4
import (
"testing"
"sourcedock.dev/petrbalvin/nfs/internal/xdr"
)
// FuzzDecodeCompound feeds arbitrary compound payloads through the
// argument and result walkers: no input may panic, and every malformed
// body must arrive as an error, never as a wrong split.
func FuzzDecodeCompound(f *testing.F) {
args := AppendCompoundArgs(nil, "seed", MinorVersion, [][]byte{
AppendSequenceArgs(nil, SessionID{1}, 1, 0, 0, true),
AppendPutRootfh(nil),
AppendLookup(nil, "a"),
})
f.Add(args)
res := AppendCompoundRes(nil, ErrOK, "seed", [][]byte{
append(AppendOpHeader(nil, OpSequence, ErrOK), AppendSequenceRes(nil, SessionID{1}, 1, 0, 0, 0)...),
AppendOpHeader(nil, OpPutRootfh, ErrOK),
})
f.Add(res)
f.Add([]byte{0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 75, 0, 0, 0, 0})
f.Add([]byte{0, 0, 0, 9, 0, 0, 0, 1, 0, 0, 0, 1})
f.Fuzz(func(t *testing.T, data []byte) {
// The property under test is that none of this panics; a
// malformed body is an ordinary error and a well formed one
// decodes with bodies that stay inside the payload.
_, d, err := DecodeCompoundArgs(data)
if err == nil {
for {
op, oerr := d.Uint32()
if oerr != nil || walkArgs(op, d) {
break
}
}
}
_, _, _ = DecodeCompoundResBodies(data)
_, _, _ = DecodeCompoundResBodiesCB(data)
})
}
// walkArgs walks one operation's arguments the way the dispatcher does,
// so the fuzzed stream exercises the real decoders. The boolean reports
// that the stream ended or was refused.
func walkArgs(op uint32, d *xdr.Decoder) bool {
switch op {
case OpExchangeID:
_, err := DecodeExchangeIDArgs(d)
return err != nil
case OpCreateSession:
_, err := DecodeCreateSessionArgs(d)
return err != nil
case OpSequence:
_, err := DecodeSequenceArgs(d)
return err != nil
case OpSecinfoNoName:
_, err := DecodeSecinfoNoNameArgs(d)
return err != nil
}
_, err := d.Uint32()
return err != nil
}
// TestRegistryValues pins the registry numbers this package speaks
// against the standards: an accidental renumbering of any of these
// breaks interoperation with every conformant peer, so the values
// themselves are the contract.
func TestRegistryValues(t *testing.T) {
pins := []struct {
name string
got uint32
want uint32
rfc string
}{
{"session id size", uint32(len(SessionID{})), 16, "RFC 7863"},
{"FATTR4_TIME_ACCESS_SET", AttrTimeAccessSet, 48, "RFC 7863"},
{"FATTR4_TIME_MODIFY_SET", AttrTimeModifySet, 54, "RFC 7863"},
{"LAYOUT4_FLEX_FILES", LayoutTypeFlexfiles, 4, "RFC 8435"},
{"LAYOUT4_NFSV4_1_FILES", LayoutTypeFiles, 1, "RFC 7863"},
{"LAYOUT4_OSD2_OBJECTS", LayoutTypeObjects, 2, "RFC 7863"},
{"LAYOUT4_BLOCK_VOLUME", LayoutTypeBlock, 3, "RFC 7863"},
{"layoutiomode4 RW", IoModeRW, 2, "RFC 7863"},
{"layoutiomode4 ANY", IoModeAny, 3, "RFC 7863"},
{"SP4_MACH_CRED", StateProtectMachCred, 1, "RFC 8881"},
{"SP4_SSV", StateProtectSSV, 2, "RFC 8881"},
{"SECINFO_STYLE4_CURRENT_FH", StyleCurrentFH, 0, "RFC 8881"},
{"SECINFO_STYLE4_PARENT", StyleParent, 1, "RFC 8881"},
{"NFS4ERR_NXIO", ErrNXIO, 6, "RFC 8881"},
{"NFS4ERR_BADOWNER", ErrBadOwner, 10093, "RFC 8881"},
{"NFS4ERR_NOXATTR", ErrNoXattr, 10095, "RFC 8276"},
{"NFS4ERR_XATTR2BIG", ErrXattr2Big, 10096, "RFC 8276"},
}
for _, p := range pins {
if p.got != p.want {
t.Errorf("%s: got %d, want %d per %s", p.name, p.got, p.want, p.rfc)
}
}
}