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
252 lines
8.3 KiB
Go
252 lines
8.3 KiB
Go
// 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)
|
|
}
|
|
}
|
|
}
|