// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: MIT package nfs4 import ( "testing" "sourcedock.dev/petrbalvin/nfs/internal/xdr" ) // The numbered session id: the prefix, the number and the client id // halves, RFC 8881 section 18.36. func TestMakeNumberedSessionID(t *testing.T) { id := MakeNumberedSessionID([4]byte{9, 9, 9, 9}, 0x11223344, 0x5566778899aabbcc) if id[0] != 9 || id[3] != 9 { t.Fatalf("prefix % x", id[:4]) } if id[4] != 0x11 || id[7] != 0x44 { t.Fatalf("number % x", id[4:8]) } if id.ClientIDOf() != 0x5566778899aabbcc { t.Fatalf("clientid %x", id.ClientIDOf()) } // Every number mints a distinct id. if MakeNumberedSessionID([4]byte{}, 1, 5) == MakeNumberedSessionID([4]byte{}, 2, 5) { t.Fatal("two numbers minted the same session id") } } // NegotiateChannel clamps each value to the smaller of the request and // the limit and leaves a zero request at zero. func TestNegotiateChannel(t *testing.T) { got := NegotiateChannel( ChannelAttrs{MaxRequest: 1 << 30, MaxResponse: 1 << 10, MaxRespResourced: 1 << 10, MaxOperations: 64, MaxRequests: 0}, ChannelAttrs{MaxRequest: 1 << 22, MaxResponse: 1 << 22, MaxRespResourced: 1 << 22, MaxOperations: 16, MaxRequests: 8}, ) want := ChannelAttrs{MaxRequest: 1 << 22, MaxResponse: 1 << 10, MaxRespResourced: 1 << 10, MaxOperations: 16, MaxRequests: 0} if got.MaxRequest != want.MaxRequest || got.MaxResponse != want.MaxResponse || got.MaxRespResourced != want.MaxRespResourced || got.MaxOperations != want.MaxOperations || got.MaxRequests != want.MaxRequests { t.Fatalf("negotiated %+v, want %+v", got, want) } } // The EXCLUSIVE4_1 open args round trip: guarded create, verifier // consumed, mode carried. func TestOpenArgsExclusive41RoundTrip(t *testing.T) { arg := AppendOpenArgsExclusive41(nil, 0x77, []byte("ex"), [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, 0o640, "ex41.txt") d := xdr.NewDecoder(arg) if op, err := d.Uint32(); err != nil || op != OpOpen { t.Fatalf("op %d %v", op, err) } a, err := DecodeOpenArgs(d) if err != nil { t.Fatal(err) } if !a.Create || !a.Guarded || a.Truncate || a.Perm != 0o640 || a.Name != "ex41.txt" || a.ClientID != 0x77 || string(a.Owner) != "ex" { t.Fatalf("args %+v", a) } if d.Remaining() != 0 { t.Fatalf("%d bytes left", d.Remaining()) } // The guarded form sets the flag the same way. arg = AppendOpenArgs(nil, 1, []byte("o"), ShareAccessRead, 0, true, 0o600, "g.txt") if _, err := DecodeOpenArgs(xdr.NewDecoder(arg[4:])); err != nil { t.Fatal(err) } } // The OPEN result measurement walks the write delegation with its space // limit union and the permissions ace. func TestResBodyLenOpenDelegations(t *testing.T) { var st Stateid st[0] = 1 write := AppendOpenResDeleg(nil, st, OpenDelegWrite, st) if n, err := resBodyLen(OpOpen, ErrOK, write); err != nil || n != len(write) { t.Fatalf("write deleg measured %d of %d: %v", n, len(write), err) } read := AppendOpenResDeleg(nil, st, OpenDelegRead, st) if n, err := resBodyLen(OpOpen, ErrOK, read); err != nil || n != len(read) { t.Fatalf("read deleg measured %d of %d: %v", n, len(read), err) } none := AppendOpenRes(nil, st) if n, err := resBodyLen(OpOpen, ErrOK, none); err != nil || n != len(none) { t.Fatalf("no deleg measured %d of %d: %v", n, len(none), err) } // A truncated body of any of the three fails instead of measuring // past the payload. for n := 0; n < len(write); n += 4 { if _, err := resBodyLen(OpOpen, ErrOK, write[:n]); err == nil { t.Fatalf("a %d byte prefix measured cleanly", n) } } }