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,251 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package nfs4
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"sourcedock.dev/petrbalvin/nfs/internal/xdr"
|
||||
)
|
||||
|
||||
func TestCBCompoundRoundTrip(t *testing.T) {
|
||||
ops := [][]byte{
|
||||
AppendCBSequenceArgs(nil, MakeSessionID([8]byte{}, 5), 1, 0, 0, true),
|
||||
AppendCBRecallArgs(nil, AllZero, false, []byte{9, 9}),
|
||||
}
|
||||
arg := AppendCBCompoundArgs(nil, "cb", 2, 0, ops)
|
||||
h, d, err := DecodeCBCompoundArgs(arg)
|
||||
if err != nil {
|
||||
t.Fatalf("decode: %v", err)
|
||||
}
|
||||
if h.Tag != "cb" || h.Minor != 2 || h.CallbackIdent != 0 || h.OpCount != 2 {
|
||||
t.Fatalf("header: %+v", h)
|
||||
}
|
||||
if op, err := d.Uint32(); err != nil || op != OpCBSequence {
|
||||
t.Fatalf("op 0: %d, %v", op, err)
|
||||
}
|
||||
a, err := DecodeCBSequenceArgs(d)
|
||||
if err != nil {
|
||||
t.Fatalf("cb sequence: %v", err)
|
||||
}
|
||||
if a.Sequence != 1 || a.Slot != 0 || !a.CacheThis {
|
||||
t.Fatalf("cb sequence args: %+v", a)
|
||||
}
|
||||
if op, err := d.Uint32(); err != nil || op != OpCBRecall {
|
||||
t.Fatalf("op 1: %d, %v", op, err)
|
||||
}
|
||||
r, err := DecodeCBRecallArgs(d)
|
||||
if err != nil || r.Truncate || !bytes.Equal(r.FH, []byte{9, 9}) {
|
||||
t.Fatalf("cb recall args: %+v, %v", r, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCBSequenceResShape(t *testing.T) {
|
||||
id := MakeSessionID([8]byte{}, 0x55)
|
||||
res := AppendCBSequenceRes(nil, id, 9, 2, 7)
|
||||
if len(res) != 32 {
|
||||
t.Fatalf("res of %d bytes, want 32", len(res))
|
||||
}
|
||||
d := xdr.NewDecoder(res)
|
||||
if raw, err := d.Raw(16); err != nil || !bytes.Equal(raw, id[:]) {
|
||||
t.Fatalf("session id: %x, %v", raw, err)
|
||||
}
|
||||
for _, want := range []uint32{9, 2, 7, 7} {
|
||||
if v, err := d.Uint32(); err != nil || v != want {
|
||||
t.Fatalf("word: %d, %v; want %d", v, err, want)
|
||||
}
|
||||
}
|
||||
sid, seq, slot, high, target, err := DecodeCBSequenceRes(xdr.NewDecoder(res))
|
||||
if err != nil || sid != id || seq != 9 || slot != 2 || high != 7 || target != 7 {
|
||||
t.Fatalf("decode: %d %d %d %d %d, %v", seq, slot, high, target, 0, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCBGetattrArgs(t *testing.T) {
|
||||
arg := AppendCBGetattrArgs(nil, []byte("fh"), OfBits(AttrSize, AttrChange))
|
||||
d := xdr.NewDecoder(arg)
|
||||
if op, err := d.Uint32(); err != nil || op != OpCBGetattr {
|
||||
t.Fatalf("op: %d, %v", op, err)
|
||||
}
|
||||
if fh, err := d.VarOpaque(); err != nil || string(fh) != "fh" {
|
||||
t.Fatalf("fh: %q, %v", fh, err)
|
||||
}
|
||||
if m, err := ReadBitmap(d); err != nil || !m.Has(AttrSize) || !m.Has(AttrChange) {
|
||||
t.Fatalf("bitmap: %v", m.Words())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeCompoundResBodiesCB(t *testing.T) {
|
||||
var results [][]byte
|
||||
seqBody := append(AppendOpHeader(nil, OpCBSequence, ErrOK),
|
||||
AppendCBSequenceRes(nil, MakeSessionID([8]byte{}, 3), 4, 0, 7)...)
|
||||
results = append(results, seqBody, AppendOpHeader(nil, OpCBRecall, ErrOK))
|
||||
payload := AppendCBCompoundRes(nil, ErrOK, "cbt", results)
|
||||
|
||||
res, bodies, err := DecodeCompoundResBodiesCB(payload)
|
||||
if err != nil {
|
||||
t.Fatalf("decode: %v", err)
|
||||
}
|
||||
if res.Status != ErrOK || len(res.Ops) != 2 {
|
||||
t.Fatalf("res: status %d ops %d", res.Status, len(res.Ops))
|
||||
}
|
||||
if len(bodies[0]) != 32 || len(bodies[1]) != 0 {
|
||||
t.Fatalf("bodies: %d and %d", len(bodies[0]), len(bodies[1]))
|
||||
}
|
||||
// A failed op carries no body.
|
||||
payload = AppendCBCompoundRes(nil, ErrNotSupp, "", [][]byte{
|
||||
AppendOpHeader(nil, OpCBGetattr, ErrNotSupp),
|
||||
})
|
||||
res, bodies, err = DecodeCompoundResBodiesCB(payload)
|
||||
if err != nil || res.Status != ErrNotSupp || len(bodies[0]) != 0 {
|
||||
t.Fatalf("failed op: status %d body %d, %v", res.Status, len(bodies[0]), err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelegationCodecRoundTrip(t *testing.T) {
|
||||
var st Stateid
|
||||
st[0] = 3
|
||||
st[4], st[5], st[6], st[7] = 'D', 'E', 'L', 'E'
|
||||
// READ delegation round trip. The res body ends with the NONE type,
|
||||
// which the READ delegation replaces.
|
||||
base := AppendOpenRes(nil, AllZero)
|
||||
body := AppendDelegation(base[:len(base)-4], OpenDelegRead, st, false, 0)
|
||||
openSt, delegType, delegSt, err := DecodeOpenResDeleg(body)
|
||||
if err != nil {
|
||||
t.Fatalf("read decode: %v", err)
|
||||
}
|
||||
if delegType != OpenDelegRead || delegSt != st || openSt != AllZero {
|
||||
t.Fatalf("read delegation: %+v", delegSt)
|
||||
}
|
||||
// WRITE delegation round trip with a space limit.
|
||||
body = AppendDelegation(base[:len(base)-4], OpenDelegWrite, st, true, 1<<20)
|
||||
openSt, delegType, delegSt, err = DecodeOpenResDeleg(body)
|
||||
if err != nil {
|
||||
t.Fatalf("write decode: %v", err)
|
||||
}
|
||||
if delegType != OpenDelegWrite || delegSt != st {
|
||||
t.Fatalf("write delegation: %d, %v", delegType, err)
|
||||
}
|
||||
// NONE carries nothing beyond the type.
|
||||
body = AppendDelegation(base[:len(base)-4], OpenDelegNone, st, false, 0)
|
||||
_, delegType, _, derr := DecodeOpenResDeleg(body)
|
||||
if derr != nil || delegType != OpenDelegNone {
|
||||
t.Fatalf("none: %d, %v", delegType, derr)
|
||||
}
|
||||
if n, err := resBodyLenCB(OpCBSequence, ErrOK, append(AppendOpHeader(nil, OpCBSequence, ErrOK),
|
||||
AppendCBSequenceRes(nil, MakeSessionID([8]byte{}, 1), 1, 0, 7)...)); err != nil || n != 32 {
|
||||
t.Fatalf("cb seq measure: %d, %v", n, err)
|
||||
}
|
||||
}
|
||||
|
||||
func seqBodyHelper() []byte {
|
||||
return append(AppendOpHeader(nil, OpCBSequence, ErrOK),
|
||||
AppendCBSequenceRes(nil, MakeSessionID([8]byte{}, 1), 1, 0, 7)...)
|
||||
}
|
||||
|
||||
func TestAppendOpenArgsPrevious(t *testing.T) {
|
||||
arg := AppendOpenArgsPrevious(nil, 0x7777, []byte("owner"), ShareAccessBoth, 0)
|
||||
d := xdr.NewDecoder(arg)
|
||||
if op, err := d.Uint32(); err != nil || op != OpOpen {
|
||||
t.Fatalf("op: %d, %v", op, err)
|
||||
}
|
||||
if seq, err := d.Uint32(); err != nil || seq != 0 {
|
||||
t.Fatalf("seqid: %v", err)
|
||||
}
|
||||
if access, err := d.Uint32(); err != nil || access != ShareAccessBoth {
|
||||
t.Fatalf("access: %v", err)
|
||||
}
|
||||
if deny, err := d.Uint32(); err != nil || deny != 0 {
|
||||
t.Fatalf("deny: %v", err)
|
||||
}
|
||||
if cid, err := d.Uint64(); err != nil || cid != 0x7777 {
|
||||
t.Fatalf("clientid: %v", err)
|
||||
}
|
||||
owner, err := d.VarOpaque()
|
||||
if err != nil || string(owner) != "owner" {
|
||||
t.Fatalf("owner: %q, %v", owner, err)
|
||||
}
|
||||
if ot, err := d.Uint32(); err != nil || ot != 0 {
|
||||
t.Fatalf("opentype: %d, %v", ot, err)
|
||||
}
|
||||
if claim, err := d.Uint32(); err != nil || claim != ClaimPrevious {
|
||||
t.Fatalf("claim: %d, %v", claim, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendOpenArgsPreviousRoundTrip(t *testing.T) {
|
||||
arg := AppendOpenArgsPrevious(nil, 0x7777, []byte("owner"), ShareAccessBoth, 0)
|
||||
d := xdr.NewDecoder(arg)
|
||||
if op, err := d.Uint32(); err != nil || op != OpOpen {
|
||||
t.Fatalf("op: %d, %v", op, err)
|
||||
}
|
||||
if seq, err := d.Uint32(); err != nil || seq != 0 {
|
||||
t.Fatalf("seqid: %v", err)
|
||||
}
|
||||
if access, err := d.Uint32(); err != nil || access != ShareAccessBoth {
|
||||
t.Fatalf("access: %v", err)
|
||||
}
|
||||
if deny, err := d.Uint32(); err != nil || deny != 0 {
|
||||
t.Fatalf("deny: %v", err)
|
||||
}
|
||||
if cid, err := d.Uint64(); err != nil || cid != 0x7777 {
|
||||
t.Fatalf("clientid: %v", err)
|
||||
}
|
||||
owner, err := d.VarOpaque()
|
||||
if err != nil || string(owner) != "owner" {
|
||||
t.Fatalf("owner: %q, %v", owner, err)
|
||||
}
|
||||
if ot, err := d.Uint32(); err != nil || ot != 0 {
|
||||
t.Fatalf("opentype: %d, %v", ot, err)
|
||||
}
|
||||
if claim, err := d.Uint32(); err != nil || claim != ClaimPrevious {
|
||||
t.Fatalf("claim: %d, %v", claim, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeOpenResDelegTruncated(t *testing.T) {
|
||||
// Every proper prefix of a READ delegation answer must fail to decode.
|
||||
st := AllZero
|
||||
st[0] = 3
|
||||
base := AppendOpenRes(nil, AllZero)
|
||||
base = base[:len(base)-4] // drop the NONE delegation type
|
||||
full := append(append([]byte{}, base...), AppendDelegation(nil, OpenDelegRead, st, false, 0)...)
|
||||
for n := 4; n < len(full); n++ {
|
||||
if _, _, _, err := DecodeOpenResDeleg(full[:n]); err == nil {
|
||||
t.Fatalf("a %d byte prefix decoded", n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeSessionChannelRoundTrip(t *testing.T) {
|
||||
// No RDMA: six words and an empty size array.
|
||||
var body []byte
|
||||
body = xdr.AppendUint32(body, 0)
|
||||
body = xdr.AppendUint32(body, 1<<20)
|
||||
body = xdr.AppendUint32(body, 1<<20)
|
||||
body = xdr.AppendUint32(body, 1<<20)
|
||||
body = xdr.AppendUint32(body, 8)
|
||||
body = xdr.AppendUint32(body, 8)
|
||||
body = xdr.AppendUint32(body, 0)
|
||||
c, err := DecodeSessionChannel(xdr.NewDecoder(body))
|
||||
if err != nil {
|
||||
t.Fatalf("decode: %v", err)
|
||||
}
|
||||
if c.MaxRequests != 8 || c.RdmaIRDEnabled {
|
||||
t.Fatalf("channel: %+v", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeExchangeIDArgsTruncated(t *testing.T) {
|
||||
// AppendLockArgsNew writes the op, the open seqid, the open stateid,
|
||||
// the lock seqid, the client id and the owner; the truncation test
|
||||
// walks the payload after the op number.
|
||||
full := AppendExchangeIDArgs(nil, [8]byte{1}, []byte("own"), 0)
|
||||
for n := 1; n < len(full)-4; n++ {
|
||||
if _, err := DecodeExchangeIDArgs(xdr.NewDecoder(full[4 : 4+n])); err == nil {
|
||||
t.Fatalf("a %d byte prefix decoded", n)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user