// Copyright (c) 2026 Petr BalvĂ­n (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) }