feat: full NFSv4.2 server and client in pure Go
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
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
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Package rpc implements the record marking layer of ONC RPC, RFC 5531.
|
||||
//
|
||||
// An ONC RPC message travels over a byte stream as one record: a sequence of
|
||||
// one or more fragments, each headed by a 32 bit word whose high bit marks
|
||||
// the last fragment of the record and whose low 31 bits carry the fragment
|
||||
// length in bytes.
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"sourcedock.dev/petrbalvin/nfs/internal/xdr"
|
||||
)
|
||||
|
||||
// LastFragment is the high bit of a fragment header, set on the final
|
||||
// fragment of a record.
|
||||
const LastFragment = 1 << 31
|
||||
|
||||
// maxFragment is the largest fragment length the low 31 bits can carry.
|
||||
const maxFragment = 1<<31 - 1
|
||||
|
||||
// ErrRecordTooLarge is returned by ReadRecord when a record exceeds the
|
||||
// caller's limit.
|
||||
var ErrRecordTooLarge = errors.New("rpc: record exceeds the size limit")
|
||||
|
||||
// AppendFragmentHeader appends the record marking header of a fragment
|
||||
// that carries n bytes. It panics when n is negative or above the
|
||||
// largest fragment length; callers reach it through WriteRecord, which
|
||||
// rejects such input with ErrRecordTooLarge instead.
|
||||
func AppendFragmentHeader(b []byte, n int, last bool) []byte {
|
||||
if n < 0 || n > maxFragment {
|
||||
panic(fmt.Sprintf("rpc: fragment length %d out of range", n))
|
||||
}
|
||||
h := uint32(n)
|
||||
if last {
|
||||
h |= LastFragment
|
||||
}
|
||||
return xdr.AppendUint32(b, h)
|
||||
}
|
||||
|
||||
// WriteRecord writes data to w as one record in a single final fragment.
|
||||
// The caller keeps the record under maxFragment bytes; a call that carries a
|
||||
// whole ONC RPC request or reply always fits.
|
||||
func WriteRecord(w io.Writer, data []byte) error {
|
||||
if len(data) > maxFragment {
|
||||
return ErrRecordTooLarge
|
||||
}
|
||||
buf := AppendFragmentHeader(make([]byte, 0, 4+len(data)), len(data), true)
|
||||
buf = append(buf, data...)
|
||||
_, err := w.Write(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
// ReadRecord reads one record from r and returns its reassembled bytes. The
|
||||
// record may arrive in any number of fragments and may exceed the reader's
|
||||
// own buffer only up to limit bytes; a longer record returns
|
||||
// ErrRecordTooLarge before the limit is exceeded in memory.
|
||||
func ReadRecord(r io.Reader, limit int) ([]byte, error) {
|
||||
var header [4]byte
|
||||
var record []byte
|
||||
for {
|
||||
if _, err := io.ReadFull(r, header[:]); err != nil {
|
||||
if errors.Is(err, io.EOF) && len(record) == 0 {
|
||||
return nil, io.EOF
|
||||
}
|
||||
return nil, fmt.Errorf("rpc: fragment header: %w", err)
|
||||
}
|
||||
h, err := xdr.NewDecoder(header[:]).Uint32()
|
||||
if err != nil {
|
||||
// Unreachable: a four byte input always holds a uint32.
|
||||
return nil, err
|
||||
}
|
||||
n := int(h &^ LastFragment)
|
||||
if n > limit-len(record) {
|
||||
return nil, ErrRecordTooLarge
|
||||
}
|
||||
start := len(record)
|
||||
record = append(record, make([]byte, n)...)
|
||||
if _, err := io.ReadFull(r, record[start:]); err != nil {
|
||||
return nil, fmt.Errorf("rpc: fragment body: %w", err)
|
||||
}
|
||||
if h&LastFragment != 0 {
|
||||
return record, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AppendRecord appends the record marking of one last fragment and the
|
||||
// data behind it, for a caller that owns the destination buffer: a
|
||||
// writer that recycles its wire buffers through this function saves the
|
||||
// allocation WriteRecord makes per call.
|
||||
func AppendRecord(dst, data []byte) []byte {
|
||||
if len(data) > maxFragment {
|
||||
return dst
|
||||
}
|
||||
dst = AppendFragmentHeader(dst, len(data), true)
|
||||
return append(dst, data...)
|
||||
}
|
||||
Reference in New Issue
Block a user