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
205 lines
5.6 KiB
Go
205 lines
5.6 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (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
|
|
}
|