// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: MIT // Package xdr implements the primitive encoding of the External Data // Representation Standard, RFC 4506, on which ONC RPC and the NFS protocols // are built. // // Values are encoded in big endian byte order and every encoding occupies a // multiple of four bytes: a fixed or variable length opaque value and a // string of n bytes are followed by zero to three zero padding bytes. package xdr import "errors" // ErrTruncated is returned when a decode runs past the end of the input. var ErrTruncated = errors.New("xdr: unexpected end of input") // ErrBadLength is returned when a declared length cannot be honoured. var ErrBadLength = errors.New("xdr: impossible length") // AppendUint32 appends v to b as four bytes in big endian order. func AppendUint32(b []byte, v uint32) []byte { return append(b, byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) } // AppendInt32 appends v to b in the XDR integer encoding, which is the // two's complement of the value in four big endian bytes. func AppendInt32(b []byte, v int32) []byte { return AppendUint32(b, uint32(v)) } // AppendUint64 appends v to b as eight bytes in big endian order, most // significant word first. func AppendUint64(b []byte, v uint64) []byte { return AppendUint32(AppendUint32(b, uint32(v>>32)), uint32(v)) } // AppendInt64 appends v to b in the XDR hyper integer encoding, which is the // two's complement of the value in eight big endian bytes. func AppendInt64(b []byte, v int64) []byte { return AppendUint64(b, uint64(v)) } // AppendBool appends v as the XDR boolean, which is the number zero for // false and one for true. func AppendBool(b []byte, v bool) []byte { if v { return AppendUint32(b, 1) } return AppendUint32(b, 0) } // appendPad appends the zero padding that brings an n byte body up to a // multiple of four bytes. func appendPad(b []byte, n int) []byte { var pad [3]byte return append(b, pad[:(4-n%4)%4]...) } // AppendFixedOpaque appends v followed by zero padding to a four byte // boundary. The length is known from the surrounding structure and is not // part of the encoding. func AppendFixedOpaque(b []byte, v []byte) []byte { b = append(b, v...) return appendPad(b, len(v)) } // AppendVarOpaque appends v as a count of bytes followed by the bytes and // their zero padding. func AppendVarOpaque(b []byte, v []byte) []byte { b = AppendUint32(b, uint32(len(v))) b = append(b, v...) return appendPad(b, len(v)) } // AppendString appends s as a variable length opaque value holding UTF-8 // bytes. func AppendString(b []byte, s string) []byte { b = AppendUint32(b, uint32(len(s))) b = append(b, s...) return appendPad(b, len(s)) } // A Decoder reads XDR values from a byte slice. Its methods return the zero // value and an error when the input does not hold the value; the input is // never modified. type Decoder struct { b []byte off int } // NewDecoder returns a decoder over b. func NewDecoder(b []byte) *Decoder { return &Decoder{b: b} } // Remaining reports how many bytes of the input are still unread. func (d *Decoder) Remaining() int { return len(d.b) - d.off } // Uint32 reads four bytes in big endian order. func (d *Decoder) Uint32() (uint32, error) { if d.Remaining() < 4 { return 0, ErrTruncated } v := uint32(d.b[d.off])<<24 | uint32(d.b[d.off+1])<<16 | uint32(d.b[d.off+2])<<8 | uint32(d.b[d.off+3]) d.off += 4 return v, nil } // Int32 reads an XDR integer. func (d *Decoder) Int32() (int32, error) { v, err := d.Uint32() return int32(v), err } // Uint64 reads two words, most significant first. func (d *Decoder) Uint64() (uint64, error) { hi, err := d.Uint32() if err != nil { return 0, err } lo, err := d.Uint32() if err != nil { return 0, err } return uint64(hi)<<32 | uint64(lo), nil } // Int64 reads an XDR hyper integer. func (d *Decoder) Int64() (int64, error) { v, err := d.Uint64() return int64(v), err } // Bool reads an XDR boolean. Any nonzero word decodes as true, because the // standard constrains what a sender writes and not what a receiver accepts. func (d *Decoder) Bool() (bool, error) { v, err := d.Uint32() return v != 0, err } // skipPad consumes the zero padding after an n byte body. func (d *Decoder) skipPad(n int) error { if p := (4 - n%4) % 4; p > 0 { if d.Remaining() < p { return ErrTruncated } d.off += p } return nil } // Raw reads exactly n bytes with no padding. The returned slice aliases // the decoder's input. func (d *Decoder) Raw(n int) ([]byte, error) { if n < 0 { return nil, ErrBadLength } if n > d.Remaining() { return nil, ErrTruncated } v := d.b[d.off : d.off+n] d.off += n return v, nil } // FixedOpaque reads exactly n bytes and skips their padding. func (d *Decoder) FixedOpaque(n int) ([]byte, error) { if n < 0 { return nil, ErrBadLength } if n > d.Remaining() { return nil, ErrTruncated } v := make([]byte, n) copy(v, d.b[d.off:d.off+n]) d.off += n if err := d.skipPad(n); err != nil { return nil, err } return v, nil } // VarOpaque reads a count of bytes followed by the bytes and their padding. // A count beyond the remaining input returns an error before any allocation. func (d *Decoder) VarOpaque() ([]byte, error) { n, err := d.Uint32() if err != nil { return nil, err } if uint64(int(n)) != uint64(n) { // The count does not fit an int on this platform. return nil, ErrBadLength } return d.FixedOpaque(int(n)) } // String reads a variable length opaque value as a string. func (d *Decoder) String() (string, error) { v, err := d.VarOpaque() if err != nil { return "", err } return string(v), nil }