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
200 lines
5.8 KiB
Go
200 lines
5.8 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package rpc
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"testing"
|
|
|
|
"sourcedock.dev/petrbalvin/nfs/internal/xdr"
|
|
)
|
|
|
|
func TestCallRoundTrip(t *testing.T) {
|
|
cred := Auth{Flavor: FlavorSys, Body: []byte{1, 2, 3}}
|
|
c := Call{XID: 0xfeedface, Program: 100003, Version: 4, Procedure: 1, Cred: cred}
|
|
|
|
rec, err := AppendCall(nil, c)
|
|
if err != nil {
|
|
t.Fatalf("encode: %v", err)
|
|
}
|
|
rec = append(rec, xdr.AppendString(nil, "tag")...)
|
|
|
|
got, args, err := DecodeCall(rec)
|
|
if err != nil {
|
|
t.Fatalf("decode: %v", err)
|
|
}
|
|
if got.XID != c.XID || got.Program != c.Program || got.Version != c.Version ||
|
|
got.Procedure != c.Procedure ||
|
|
got.Cred.Flavor != c.Cred.Flavor || !bytes.Equal(got.Cred.Body, c.Cred.Body) {
|
|
t.Fatalf("round trip: got %+v, want %+v", got, c)
|
|
}
|
|
tag, err := xdr.NewDecoder(args).String()
|
|
if err != nil || tag != "tag" {
|
|
t.Fatalf("arguments after the header: %q, %v", tag, err)
|
|
}
|
|
}
|
|
|
|
func TestDecodeCallRejectsReplies(t *testing.T) {
|
|
rec := xdr.AppendUint32(xdr.AppendUint32(nil, 1), MsgReply)
|
|
if _, _, err := DecodeCall(rec); err == nil {
|
|
t.Fatal("a reply record decoded as a call")
|
|
}
|
|
}
|
|
|
|
func TestOversizedAuthBody(t *testing.T) {
|
|
c := Call{XID: 1, Program: 100003, Version: Version, Procedure: 0,
|
|
Cred: Auth{Flavor: FlavorSys, Body: make([]byte, maxAuthBody+1)}}
|
|
if _, err := AppendCall(nil, c); err == nil {
|
|
t.Fatal("an oversized credential encoded without error")
|
|
}
|
|
}
|
|
|
|
func TestAcceptedReplyRoundTrip(t *testing.T) {
|
|
rec, err := AppendAcceptedReply(nil, 7, AuthNull, AcceptSuccess, Mismatch{})
|
|
if err != nil {
|
|
t.Fatalf("encode: %v", err)
|
|
}
|
|
rec = append(rec, xdr.AppendUint32(nil, 42)...)
|
|
|
|
r, err := DecodeReply(rec)
|
|
if err != nil {
|
|
t.Fatalf("decode: %v", err)
|
|
}
|
|
if r.XID != 7 || r.Status != AcceptSuccess {
|
|
t.Fatalf("got xid %d status %d", r.XID, r.Status)
|
|
}
|
|
body, err := xdr.NewDecoder(r.Body).Uint32()
|
|
if err != nil || body != 42 {
|
|
t.Fatalf("result body: %d, %v", body, err)
|
|
}
|
|
}
|
|
|
|
func TestProgMismatchReply(t *testing.T) {
|
|
rec, err := AppendAcceptedReply(nil, 9, AuthNull, AcceptProgMismatch, Mismatch{Low: 4, High: 4})
|
|
if err != nil {
|
|
t.Fatalf("encode: %v", err)
|
|
}
|
|
r, err := DecodeReply(rec)
|
|
if err != nil {
|
|
t.Fatalf("decode: %v", err)
|
|
}
|
|
if r.Status != AcceptProgMismatch || r.Mismatch != (Mismatch{4, 4}) {
|
|
t.Fatalf("got status %d mismatch %+v", r.Status, r.Mismatch)
|
|
}
|
|
}
|
|
|
|
func TestUnknownAcceptStatus(t *testing.T) {
|
|
if _, err := AppendAcceptedReply(nil, 1, AuthNull, 99, Mismatch{}); err == nil {
|
|
t.Fatal("an unknown accept status encoded without error")
|
|
}
|
|
}
|
|
|
|
func TestAuthSysRoundTrip(t *testing.T) {
|
|
a := AuthSys{Stamp: 12, Machine: "client", UID: 1000, GID: 100, GIDs: []uint32{100, 5, 27}}
|
|
body, err := a.Body()
|
|
if err != nil {
|
|
t.Fatalf("encode: %v", err)
|
|
}
|
|
got, err := DecodeAuthSysBody(body)
|
|
if err != nil {
|
|
t.Fatalf("decode: %v", err)
|
|
}
|
|
if got.Stamp != a.Stamp || got.Machine != a.Machine || got.UID != a.UID ||
|
|
got.GID != a.GID || !equalU32(got.GIDs, a.GIDs) {
|
|
t.Fatalf("round trip: got %+v, want %+v", got, a)
|
|
}
|
|
}
|
|
|
|
func equalU32(a, b []uint32) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i := range a {
|
|
if a[i] != b[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func TestAuthSysTooManyGroups(t *testing.T) {
|
|
a := AuthSys{GIDs: make([]uint32, 17)}
|
|
if _, err := a.Body(); err == nil {
|
|
t.Fatal("17 supplementary groups encoded without error")
|
|
}
|
|
if _, err := DecodeAuthSysBody(xdr.AppendUint32(xdr.AppendUint32(nil, 0), 17)); err == nil {
|
|
t.Fatal("a credential claiming 17 groups decoded without error")
|
|
}
|
|
}
|
|
|
|
func TestDecodeRejectedReply(t *testing.T) {
|
|
// RPC_MISMATCH carries the version range.
|
|
rec := xdr.AppendUint32(nil, 5)
|
|
rec = xdr.AppendUint32(rec, MsgReply)
|
|
rec = xdr.AppendUint32(rec, 1) // rejected
|
|
rec = xdr.AppendUint32(rec, RejectRPCMismatch)
|
|
rec = xdr.AppendUint32(rec, 2)
|
|
rec = xdr.AppendUint32(rec, 4)
|
|
r, err := DecodeReply(rec)
|
|
if err != nil || r.XID != 5 || !r.Rejected || r.Mismatch != (Mismatch{2, 4}) {
|
|
t.Fatalf("rpc mismatch: %+v, %v", r, err)
|
|
}
|
|
if r.Status != 0 || r.Body != nil {
|
|
t.Fatalf("a rejected reply carries no result: %+v", r)
|
|
}
|
|
|
|
// An auth error carries one status word.
|
|
rec = xdr.AppendUint32(nil, 6)
|
|
rec = xdr.AppendUint32(rec, MsgReply)
|
|
rec = xdr.AppendUint32(rec, 1)
|
|
rec = xdr.AppendUint32(rec, RejectAuthError)
|
|
rec = xdr.AppendUint32(rec, AuthBadVerf)
|
|
if r, err = DecodeReply(rec); err != nil || r.XID != 6 || !r.Rejected || r.AuthStat != AuthBadVerf {
|
|
t.Fatalf("auth error: %+v, %v", r, err)
|
|
}
|
|
}
|
|
|
|
func TestDecodeAuthOversizeBody(t *testing.T) {
|
|
// A credential body beyond 400 bytes is refused, not buffered.
|
|
huge := xdr.AppendUint32(nil, FlavorSys)
|
|
huge = xdr.AppendUint32(huge, maxAuthBody+1)
|
|
huge = append(huge, make([]byte, 8)...)
|
|
rec := xdr.AppendUint32(nil, 1)
|
|
rec = xdr.AppendUint32(rec, MsgCall)
|
|
rec = xdr.AppendUint32(rec, Version)
|
|
rec = xdr.AppendUint32(rec, 100003)
|
|
rec = xdr.AppendUint32(rec, 4)
|
|
rec = xdr.AppendUint32(rec, 0)
|
|
rec = append(rec, huge...)
|
|
if _, _, err := DecodeCall(rec); !errors.Is(err, ErrBadMessage) {
|
|
t.Fatalf("an oversized credential decoded as %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDecodeReplyGarbage(t *testing.T) {
|
|
for _, rec := range [][]byte{
|
|
nil,
|
|
{0, 0, 0}, // short
|
|
xdr.AppendUint32(nil, 1), // no type
|
|
{0, 0, 0, 1, 0, 0, 0, 9}, // unknown reply stat 9
|
|
} {
|
|
if _, err := DecodeReply(rec); !errors.Is(err, ErrBadMessage) {
|
|
t.Fatalf("record %x decoded with %v, want ErrBadMessage", rec, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPeekHeader(t *testing.T) {
|
|
// A minimal call record: xid and the call type.
|
|
b := xdr.AppendUint32(xdr.AppendUint32(nil, 0x55), MsgCall)
|
|
xid, mtype, err := PeekHeader(b)
|
|
if err != nil || xid != 0x55 || mtype != MsgCall {
|
|
t.Fatalf("call peek: %x %d, %v", xid, mtype, err)
|
|
}
|
|
if _, _, err := PeekHeader(nil); !errors.Is(err, ErrBadMessage) {
|
|
t.Fatalf("an empty record: %v", err)
|
|
}
|
|
}
|