// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: MIT // Directory delegations and the notification family: the wire codecs of // GET_DIR_DELEGATION (RFC 8881 section 18.39), CB_NOTIFY and // CB_NOTIFY_LOCK (RFC 8881 section 20), with the shapes taken from the // XDR of RFC 7863. package nfs4 import ( "sourcedock.dev/petrbalvin/nfs/internal/xdr" ) // Notification types of the notify_type4 enum. const ( NotifyChangeChildAttrs = 0 NotifyChangeDirAttrs = 1 NotifyRemoveEntry = 2 NotifyAddEntry = 3 NotifyRenameEntry = 4 NotifyChangeCookieVerf = 5 ) // Statuses of the gddrnf4_status union. const ( GddOK = 0 GddUnavail = 1 ) // A Notify4 is one notify4: the kind of the change and the encoded // change body. type Notify4 struct { Mask Bitmap Vals []byte } // A CBNotify is the decoded CB_NOTIFY4args. type CBNotify struct { Stateid Stateid FH []byte Changes []Notify4 } // A CBNotifyLock is the decoded CB_NOTIFY_LOCK4args. type CBNotifyLock struct { FH []byte Clientid uint64 Owner []byte } // AppendGetDirDelegationArgs encodes the GET_DIR_DELEGATION4args: the // signal flag, the notification types, the attr delay hints and the // requested attribute bitmaps. func AppendGetDirDelegationArgs(b []byte, signalAvail bool, notifyTypes Bitmap, childDelay, dirDelay NfsTime, childAttrs, dirAttrs Bitmap) []byte { b = xdr.AppendUint32(b, OpGetDirDelegation) b = xdr.AppendBool(b, signalAvail) b = notifyTypes.AppendTo(b) b = appendNfsTime(b, childDelay) b = appendNfsTime(b, dirDelay) b = childAttrs.AppendTo(b) return dirAttrs.AppendTo(b) } // AppendGetDirDelegationResOK encodes the delegation granted: the // verifier, the stateid, the supported notifications and the attribute // bitmaps the server promises. func AppendGetDirDelegationResOK(b []byte, cookieverf [8]byte, st Stateid, notification, childAttrs, dirAttrs Bitmap) []byte { b = xdr.AppendUint32(b, GddOK) b = append(b, cookieverf[:]...) b = st.Append(b) b = notification.AppendTo(b) b = childAttrs.AppendTo(b) return dirAttrs.AppendTo(b) } // AppendGetDirDelegationResUnavail encodes the no delegation answer: the // server promises to signal when one becomes available. func AppendGetDirDelegationResUnavail(b []byte, willSignal bool) []byte { b = xdr.AppendUint32(b, GddUnavail) return xdr.AppendBool(b, willSignal) } // appendNotifyEntry encodes one notify_entry4: the name and the full // attribute list, which this build carries empty. func appendNotifyEntry(b []byte, name string) []byte { b = xdr.AppendString(b, name) return AppendFattr(b, Bitmap{}, Attrs{}) } // AppendNotifyAdd encodes the notify_add4 body: the optional removed // source of a rename, the new entry, its cookie, the optional previous // entry and the end of page flag. func AppendNotifyAdd(b []byte, name string, cookie uint64, lastEntry bool) []byte { b = xdr.AppendUint32(b, 0) // nad_old_entry<1>: none b = appendNotifyEntry(b, name) b = xdr.AppendUint32(b, 0) // nad_new_entry_cookie<1>: none b = xdr.AppendUint32(b, 0) // nad_prev_entry<1>: none return xdr.AppendBool(b, lastEntry) } // AppendNotifyRemove encodes the notify_remove4 body: the removed entry // and the cookie the client resumes past it from. func AppendNotifyRemove(b []byte, name string, cookie uint64) []byte { b = appendNotifyEntry(b, name) return xdr.AppendUint64(b, cookie) } // AppendNotify4 encodes one notify4: the mask and the encoded body. func AppendNotify4(b []byte, mask Bitmap, vals []byte) []byte { b = mask.AppendTo(b) return xdr.AppendVarOpaque(b, vals) } // AppendCBNotifyArgs encodes the CB_NOTIFY4args: the delegation stateid, // the directory and the changes. func AppendCBNotifyArgs(b []byte, st Stateid, fh []byte, changes []Notify4) []byte { b = xdr.AppendUint32(b, OpCBNotify) b = st.Append(b) b = xdr.AppendVarOpaque(b, fh) b = xdr.AppendUint32(b, uint32(len(changes))) for _, c := range changes { b = AppendNotify4(b, c.Mask, c.Vals) } return b } // DecodeCBNotifyArgs decodes the CB_NOTIFY4args the client receives. func DecodeCBNotifyArgs(d *xdr.Decoder) (CBNotify, error) { var out CBNotify raw, err := d.Raw(16) if err != nil { return out, err } copy(out.Stateid[:], raw) if out.FH, err = d.VarOpaque(); err != nil { return out, err } n, err := d.Uint32() if err != nil { return out, err } for range n { var c Notify4 if c.Mask, err = ReadBitmap(d); err != nil { return out, err } if c.Vals, err = d.VarOpaque(); err != nil { return out, err } out.Changes = append(out.Changes, c) } return out, nil } // AppendCBNotifyLockArgs encodes the CB_NOTIFY_LOCK4args: the file and // the lock owner whose denied lock may be available again. func AppendCBNotifyLockArgs(b []byte, fh []byte, clientid uint64, owner []byte) []byte { b = xdr.AppendUint32(b, OpCBNotifyLock) b = xdr.AppendVarOpaque(b, fh) b = xdr.AppendUint64(b, clientid) return xdr.AppendVarOpaque(b, owner) } // DecodeCBNotifyLockArgs decodes the CB_NOTIFY_LOCK4args the client // receives. func DecodeCBNotifyLockArgs(d *xdr.Decoder) (CBNotifyLock, error) { var out CBNotifyLock fh, err := d.VarOpaque() if err != nil { return out, err } out.FH = fh if out.Clientid, err = d.Uint64(); err != nil { return out, err } out.Owner, err = d.VarOpaque() return out, err }