feat: full NFSv4.2 server and client in pure Go
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
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
Assisted-by: GLM 5.3 Flash
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user