// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: MIT package nfsclient import ( "net" "testing" "sourcedock.dev/petrbalvin/nfs/internal/nfs4" "sourcedock.dev/petrbalvin/nfs/internal/rpc" ) // The accessors answer copies of what the callback demux recorded. func TestNotifiedAccessors(t *testing.T) { c := &Client{} if got := c.Notified(); len(got) != 0 { t.Fatal("notified before any delivery") } if got := c.NotifiedLocks(); len(got) != 0 { t.Fatal("locks notified before any delivery") } c.recMu.Lock() c.notified = append(c.notified, nfs4.CBNotify{FH: []byte("d")}) c.notifiedLocks = append(c.notifiedLocks, nfs4.CBNotifyLock{Clientid: 5}) c.recMu.Unlock() if n := c.Notified(); len(n) != 1 || string(n[0].FH) != "d" { t.Fatalf("notified %+v", n) } if n := c.NotifiedLocks(); len(n) != 1 || n[0].Clientid != 5 { t.Fatalf("locks %+v", n) } } // The callback demux answers CB_NOTIFY and CB_NOTIFY_LOCK and records // them for the caller. func TestCBCompoundNotifies(t *testing.T) { // The demux answers every callback on the wire; a pipe with a // draining side stands in for the connection. a, b := net.Pipe() defer a.Close() defer b.Close() go func() { buf := make([]byte, 4096) for { if _, err := b.Read(buf); err != nil { return } } }() c := &Client{conn: a, cbProgram: 0x40000000} st := nfs4.Stateid{1, 'D', 'D'} notify := nfs4.AppendCBNotifyArgs(nil, st, []byte("dir"), []nfs4.Notify4{{Mask: nfs4.OfBits(nfs4.NotifyAddEntry), Vals: nfs4.AppendNotifyAdd(nil, "new", 0, true)}}) lock := nfs4.AppendCBNotifyLockArgs(nil, []byte("f"), 9, []byte("owner")) c.handleCBCall(1, buildCBCall(t, [][]byte{notify, lock})) notes := c.Notified() if len(notes) != 1 || !notes[0].Changes[0].Mask.Has(nfs4.NotifyAddEntry) { t.Fatalf("notified %+v", notes) } if n := c.NotifiedLocks(); len(n) != 1 || n[0].Clientid != 9 { t.Fatalf("locks %+v", n) } } // buildCBCall frames one CB_COMPOUND call record the way the server // sends it. func buildCBCall(t *testing.T, ops [][]byte) []byte { t.Helper() args := nfs4.AppendCBCompoundArgs(nil, "cb", nfs4.MinorVersion, 0, ops) record, err := rpc.AppendCall(nil, rpc.Call{ XID: 1, Program: nfs4.CBDefaultProgram, Version: nfs4.Version, Procedure: 1, Cred: rpc.Auth{Flavor: rpc.FlavorNone}, Verifier: rpc.Auth{Flavor: rpc.FlavorNone}, }) if err != nil { t.Fatal(err) } return append(record, args...) }