206 lines
6.0 KiB
Go
206 lines
6.0 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|||
|
|
// SPDX-License-Identifier: MIT
|
||
|
|
|
||
|
|
// The callback channel of RFC 8881: the CB_COMPOUND procedure the server
|
||
|
|
// calls on the connection the client opened, CB_SEQUENCE driving the back
|
||
|
|
// channel slot table, and CB_RECALL recalling a delegation.
|
||
|
|
|
||
|
|
package nfs4
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
|
||
|
|
"sourcedock.dev/petrbalvin/nfs/internal/xdr"
|
||
|
|
)
|
||
|
|
|
||
|
|
// Callback operation numbers, RFC 8881 section 15.5.
|
||
|
|
const (
|
||
|
|
OpCBGetattr = 3
|
||
|
|
OpCBRecall = 4
|
||
|
|
OpCBSequence = 11
|
||
|
|
OpCBPushDeleg = 7
|
||
|
|
OpCBNotify = 6
|
||
|
|
OpCBNotifyLock = 13
|
||
|
|
)
|
||
|
|
|
||
|
|
// The callback program rides on the same minor version and the COMPOUND
|
||
|
|
// shape as the fore channel.
|
||
|
|
const (
|
||
|
|
CBDefaultProgram = 0x40000000
|
||
|
|
)
|
||
|
|
|
||
|
|
// AppendCBCompoundArgs encodes the CB_COMPOUND4args: the tag, the minor
|
||
|
|
// version, the callback identifier and the operations. The callback
|
||
|
|
// identifier is the slot the server reserved for this client when the
|
||
|
|
// back channel was negotiated; with sessions it stays zero.
|
||
|
|
func AppendCBCompoundArgs(b []byte, tag string, minorversion, callbackIdent uint32, ops [][]byte) []byte {
|
||
|
|
b = xdr.AppendString(b, tag)
|
||
|
|
b = xdr.AppendUint32(b, minorversion)
|
||
|
|
b = xdr.AppendUint32(b, callbackIdent)
|
||
|
|
b = xdr.AppendUint32(b, uint32(len(ops)))
|
||
|
|
for _, op := range ops {
|
||
|
|
b = append(b, op...)
|
||
|
|
}
|
||
|
|
return b
|
||
|
|
}
|
||
|
|
|
||
|
|
// CBCompoundHeader is the head of a decoded CB_COMPOUND4args.
|
||
|
|
type CBCompoundHeader struct {
|
||
|
|
Tag string
|
||
|
|
Minor uint32
|
||
|
|
CallbackIdent uint32
|
||
|
|
OpCount uint32
|
||
|
|
}
|
||
|
|
|
||
|
|
// DecodeCBCompoundArgs splits CB_COMPOUND4args into its header and a
|
||
|
|
// decoder positioned at the first argop.
|
||
|
|
func DecodeCBCompoundArgs(payload []byte) (CBCompoundHeader, *xdr.Decoder, error) {
|
||
|
|
d := xdr.NewDecoder(payload)
|
||
|
|
var h CBCompoundHeader
|
||
|
|
var err error
|
||
|
|
if h.Tag, err = d.String(); err != nil {
|
||
|
|
return h, nil, err
|
||
|
|
}
|
||
|
|
if h.Minor, err = d.Uint32(); err != nil {
|
||
|
|
return h, nil, err
|
||
|
|
}
|
||
|
|
if h.CallbackIdent, err = d.Uint32(); err != nil {
|
||
|
|
return h, nil, err
|
||
|
|
}
|
||
|
|
if h.OpCount, err = d.Uint32(); err != nil {
|
||
|
|
return h, nil, err
|
||
|
|
}
|
||
|
|
if h.OpCount > maxOps {
|
||
|
|
return h, nil, fmt.Errorf("nfs4: %d operations exceeds %d", h.OpCount, maxOps)
|
||
|
|
}
|
||
|
|
return h, d, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// AppendCBCompoundRes encodes a CB_COMPOUND4res: the top level status, the
|
||
|
|
// echoed tag and the result array.
|
||
|
|
func AppendCBCompoundRes(b []byte, status uint32, tag string, results [][]byte) []byte {
|
||
|
|
return AppendCompoundRes(b, status, tag, results)
|
||
|
|
}
|
||
|
|
|
||
|
|
// AppendCBSequenceArgs encodes the CB_SEQUENCE argop.
|
||
|
|
func AppendCBSequenceArgs(b []byte, id SessionID, sequence, slot, highestSlot uint32, cacheThis bool) []byte {
|
||
|
|
b = xdr.AppendUint32(b, OpCBSequence)
|
||
|
|
return AppendSequenceArgsTail(b, id, sequence, slot, highestSlot, cacheThis)
|
||
|
|
}
|
||
|
|
|
||
|
|
// AppendSequenceArgsTail appends the shared SEQUENCE argument fields: the
|
||
|
|
// session id, the sequence, the slot ids and the caching flag.
|
||
|
|
func AppendSequenceArgsTail(b []byte, id SessionID, sequence, slot, highestSlot uint32, cacheThis bool) []byte {
|
||
|
|
b = append(b, id[:]...)
|
||
|
|
b = xdr.AppendUint32(b, sequence)
|
||
|
|
b = xdr.AppendUint32(b, slot)
|
||
|
|
b = xdr.AppendUint32(b, highestSlot)
|
||
|
|
return xdr.AppendBool(b, cacheThis)
|
||
|
|
}
|
||
|
|
|
||
|
|
// A CBSequenceArgs is the decoded CB_SEQUENCE4args.
|
||
|
|
type CBSequenceArgs struct {
|
||
|
|
SessionID SessionID
|
||
|
|
Sequence uint32
|
||
|
|
Slot uint32
|
||
|
|
HighestSlot uint32
|
||
|
|
CacheThis bool
|
||
|
|
}
|
||
|
|
|
||
|
|
// DecodeCBSequenceArgs decodes the CB_SEQUENCE4args from the decoder,
|
||
|
|
// which is positioned after the operation number.
|
||
|
|
func DecodeCBSequenceArgs(d *xdr.Decoder) (CBSequenceArgs, error) {
|
||
|
|
var a CBSequenceArgs
|
||
|
|
raw, err := d.Raw(16)
|
||
|
|
if err != nil {
|
||
|
|
return a, err
|
||
|
|
}
|
||
|
|
copy(a.SessionID[:], raw)
|
||
|
|
var err2 error
|
||
|
|
if a.Sequence, err2 = d.Uint32(); err2 != nil {
|
||
|
|
return a, err2
|
||
|
|
}
|
||
|
|
if a.Slot, err2 = d.Uint32(); err2 != nil {
|
||
|
|
return a, err2
|
||
|
|
}
|
||
|
|
if a.HighestSlot, err2 = d.Uint32(); err2 != nil {
|
||
|
|
return a, err2
|
||
|
|
}
|
||
|
|
if a.CacheThis, err2 = d.Bool(); err2 != nil {
|
||
|
|
return a, err2
|
||
|
|
}
|
||
|
|
return a, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// AppendCBSequenceRes encodes the successful CB_SEQUENCE4resok of
|
||
|
|
// RFC 7863: the session id, the echoed identifiers and the target
|
||
|
|
// highest slot. No further word follows.
|
||
|
|
func AppendCBSequenceRes(b []byte, id SessionID, sequence, slot, highestSlot uint32) []byte {
|
||
|
|
b = append(b, id[:]...)
|
||
|
|
b = xdr.AppendUint32(b, sequence)
|
||
|
|
b = xdr.AppendUint32(b, slot)
|
||
|
|
b = xdr.AppendUint32(b, highestSlot)
|
||
|
|
return xdr.AppendUint32(b, highestSlot) // cbsr_target_highest_slotid
|
||
|
|
}
|
||
|
|
|
||
|
|
// DecodeCBSequenceRes decodes the successful CB_SEQUENCE4resok.
|
||
|
|
func DecodeCBSequenceRes(d *xdr.Decoder) (id SessionID, sequence, slot, highestSlot, targetHighestSlot uint32, err error) {
|
||
|
|
raw, rerr := d.Raw(16)
|
||
|
|
if rerr != nil {
|
||
|
|
return id, 0, 0, 0, 0, rerr
|
||
|
|
}
|
||
|
|
copy(id[:], raw)
|
||
|
|
if sequence, err = d.Uint32(); err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
if slot, err = d.Uint32(); err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
if highestSlot, err = d.Uint32(); err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
targetHighestSlot, err = d.Uint32()
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// AppendCBRecallArgs encodes the CB_RECALL argop: the delegation stateid,
|
||
|
|
// whether to truncate, and the file handle the delegation names.
|
||
|
|
func AppendCBRecallArgs(b []byte, stateid Stateid, truncate bool, fh []byte) []byte {
|
||
|
|
b = xdr.AppendUint32(b, OpCBRecall)
|
||
|
|
b = stateid.Append(b)
|
||
|
|
b = xdr.AppendBool(b, truncate)
|
||
|
|
return xdr.AppendVarOpaque(b, fh)
|
||
|
|
}
|
||
|
|
|
||
|
|
// A CBRecallArgs is the decoded CB_RECALL argop.
|
||
|
|
type CBRecallArgs struct {
|
||
|
|
Stateid Stateid
|
||
|
|
Truncate bool
|
||
|
|
FH []byte
|
||
|
|
}
|
||
|
|
|
||
|
|
// DecodeCBRecallArgs decodes the CB_RECALL args from the decoder, which is
|
||
|
|
// positioned after the operation number.
|
||
|
|
func DecodeCBRecallArgs(d *xdr.Decoder) (CBRecallArgs, error) {
|
||
|
|
var a CBRecallArgs
|
||
|
|
raw, err := d.Raw(16)
|
||
|
|
if err != nil {
|
||
|
|
return a, err
|
||
|
|
}
|
||
|
|
copy(a.Stateid[:], raw)
|
||
|
|
var err2 error
|
||
|
|
if a.Truncate, err2 = d.Bool(); err2 != nil {
|
||
|
|
return a, err2
|
||
|
|
}
|
||
|
|
a.FH, err2 = d.VarOpaque()
|
||
|
|
return a, err2
|
||
|
|
}
|
||
|
|
|
||
|
|
// AppendCBGetattrArgs encodes the CB_GETATTR argop: the handle and the
|
||
|
|
// requested attribute bitmap.
|
||
|
|
func AppendCBGetattrArgs(b []byte, fh []byte, request Bitmap) []byte {
|
||
|
|
b = xdr.AppendUint32(b, OpCBGetattr)
|
||
|
|
b = xdr.AppendVarOpaque(b, fh)
|
||
|
|
return request.AppendTo(b)
|
||
|
|
}
|