// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: MIT package nfs4 import ( "bytes" "testing" "sourcedock.dev/petrbalvin/nfs/internal/xdr" ) func TestDirDelegationShapes(t *testing.T) { t.Parallel() st := Stateid{1, 'D', 'D', 'E'} args := AppendGetDirDelegationArgs(nil, true, OfBits(NotifyAddEntry), NfsTime{Seconds: 5, Nseconds: 6}, NfsTime{Seconds: 7, Nseconds: 8}, Bitmap{}, Bitmap{}) d := xdr.NewDecoder(args[4:]) if sig, err := d.Bool(); err != nil || !sig { t.Fatalf("signal %v: %v", sig, err) } if _, err := ReadBitmap(d); err != nil { t.Fatal(err) } for _, want := range []NfsTime{{Seconds: 5, Nseconds: 6}, {Seconds: 7, Nseconds: 8}} { s, err := d.Int64() if err != nil || s != want.Seconds { t.Fatalf("delay seconds %d: %v", s, err) } ns, err := d.Uint32() if err != nil || ns != want.Nseconds { t.Fatalf("delay nseconds %d: %v", ns, err) } } if _, err := ReadBitmap(d); err != nil { t.Fatal(err) } if _, err := ReadBitmap(d); err != nil { t.Fatal(err) } if d.Remaining() != 0 { t.Fatalf("%d bytes left in the dir delegation args", d.Remaining()) } ok := AppendGetDirDelegationResOK(nil, [8]byte{9}, st, OfBits(NotifyAddEntry), Bitmap{}, Bitmap{}) n, err := resBodyLen(OpGetDirDelegation, ErrOK, ok) if err != nil || n != len(ok) { t.Fatalf("resok measured %d of %d: %v", n, len(ok), err) } un := AppendGetDirDelegationResUnavail(nil, true) if n, err := resBodyLen(OpGetDirDelegation, ErrOK, un); err != nil || n != len(un) { t.Fatalf("unavail measured %d of %d: %v", n, len(un), err) } d = xdr.NewDecoder(un) if s, err := d.Uint32(); err != nil || s != GddUnavail { t.Fatalf("unavail status %d: %v", s, err) } } func TestNotifyShapes(t *testing.T) { t.Parallel() add := AppendNotifyAdd(nil, "entry", 0, true) d := xdr.NewDecoder(add) if n, err := d.Uint32(); err != nil || n != 0 { t.Fatalf("old entries %d: %v", n, err) } if name, err := d.String(); err != nil || name != "entry" { t.Fatalf("name %q: %v", name, err) } if _, err := ReadBitmap(d); err != nil { // empty fattr bitmap t.Fatal(err) } if _, err := d.VarOpaque(); err != nil { // empty fattr blob t.Fatal(err) } for range 2 { if _, err := d.Uint32(); err != nil { t.Fatal(err) } } if last, err := d.Bool(); err != nil || !last { t.Fatalf("last %v: %v", last, err) } if d.Remaining() != 0 { t.Fatalf("%d bytes left in the add body", d.Remaining()) } rem := AppendNotifyRemove(nil, "gone", 7) d = xdr.NewDecoder(rem) if name, err := d.String(); err != nil || name != "gone" { t.Fatalf("name %q: %v", name, err) } if _, err := ReadBitmap(d); err != nil { t.Fatal(err) } if _, err := d.VarOpaque(); err != nil { t.Fatal(err) } if c, err := d.Uint64(); err != nil || c != 7 { t.Fatalf("cookie %d: %v", c, err) } // CB_NOTIFY args round trip through the decoder the client uses. st := Stateid{2, 'N'} fh := []byte("dir-fh") changes := []Notify4{{Mask: OfBits(NotifyAddEntry), Vals: add}} cb := AppendCBNotifyArgs(nil, st, fh, changes) if n, err := resBodyLenCB(OpCBSequence, ErrOK, make([]byte, 32)); err != nil { t.Fatal(err) } else if n != 32 { t.Fatalf("cb sequence body %d", n) } got, err := DecodeCBNotifyArgs(xdr.NewDecoder(cb[4:])) if err != nil { t.Fatal(err) } if got.Stateid != st || !bytes.Equal(got.FH, fh) || len(got.Changes) != 1 { t.Fatalf("decoded %+v", got) } if !got.Changes[0].Mask.Has(NotifyAddEntry) { t.Fatalf("mask %v", got.Changes[0].Mask) } // CB_NOTIFY_LOCK args round trip. nl := AppendCBNotifyLockArgs(nil, fh, 0x77, []byte("owner")) lock, err := DecodeCBNotifyLockArgs(xdr.NewDecoder(nl[4:])) if err != nil { t.Fatal(err) } if lock.Clientid != 0x77 || string(lock.Owner) != "owner" || !bytes.Equal(lock.FH, fh) { t.Fatalf("decoded %+v", lock) } if n, err := resBodyLenCB(OpCBNotifyLock, ErrOK, nil); err != nil || n != 0 { t.Fatalf("notify lock res body %d: %v", n, err) } }