feat: full NFSv4.2 server and client in pure Go
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
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
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package xdr
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// FuzzDecoder feeds arbitrary bytes through every read of the decoder:
|
||||
// no input may panic, hang or index out of range, and every failure
|
||||
// arrives as an error.
|
||||
func FuzzDecoder(f *testing.F) {
|
||||
f.Add([]byte{0, 0, 0, 1})
|
||||
f.Add([]byte{0xff, 0xff, 0xff, 0xff})
|
||||
f.Add([]byte{0, 0, 0, 2, 'a', 'b', 0})
|
||||
f.Add([]byte{0, 0, 0, 5, 'h', 'e', 'l', 'l', 'o'})
|
||||
f.Add([]byte{0x7f})
|
||||
f.Add(make([]byte, 64))
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
d := NewDecoder(data)
|
||||
_, _ = d.Uint32()
|
||||
_, _ = d.Int64()
|
||||
_, _ = d.Uint64()
|
||||
_, _ = d.Bool()
|
||||
_, _ = d.String()
|
||||
_, _ = d.VarOpaque()
|
||||
_, _ = d.FixedOpaque(4)
|
||||
_, _ = d.Raw(3)
|
||||
_ = d.Remaining()
|
||||
|
||||
// The writers must accept the decoded values back and the
|
||||
// re-decode must agree or fail cleanly.
|
||||
d2 := NewDecoder(data)
|
||||
if s, err := d2.String(); err == nil {
|
||||
again, aerr := NewDecoder(AppendString(nil, s)).String()
|
||||
if aerr != nil || again != s {
|
||||
t.Fatalf("string round trip: %q %v %q", s, aerr, again)
|
||||
}
|
||||
}
|
||||
if o, err := d2.VarOpaque(); err == nil {
|
||||
again, aerr := NewDecoder(AppendVarOpaque(nil, o)).VarOpaque()
|
||||
if aerr != nil || string(again) != string(o) {
|
||||
t.Fatal("opaque round trip")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// FuzzPadding checks the pad arithmetic against the property that an
|
||||
// encoded value always decodes back to itself, whatever the length.
|
||||
func FuzzPadding(f *testing.F) {
|
||||
f.Add([]byte{})
|
||||
f.Add([]byte("a"))
|
||||
f.Add([]byte("abc"))
|
||||
f.Add([]byte("abcd"))
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
enc := AppendVarOpaque(nil, data)
|
||||
got, err := NewDecoder(enc).VarOpaque()
|
||||
if err != nil {
|
||||
t.Fatalf("decode of a value this package encoded: %v", err)
|
||||
}
|
||||
if string(got) != string(data) {
|
||||
t.Fatalf("round trip: %q became %q", data, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
// 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
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package xdr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"math"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// wantEncoding asserts the exact bytes of one encoding.
|
||||
func wantEncoding(t *testing.T, got, want []byte) {
|
||||
t.Helper()
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Fatalf("encoding mismatch:\n got: %x\nwant: %x", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUint32Layout(t *testing.T) {
|
||||
wantEncoding(t, AppendUint32(nil, 0), []byte{0, 0, 0, 0})
|
||||
wantEncoding(t, AppendUint32(nil, 1), []byte{0, 0, 0, 1})
|
||||
wantEncoding(t, AppendUint32(nil, math.MaxUint32), []byte{0xff, 0xff, 0xff, 0xff})
|
||||
wantEncoding(t, AppendUint32(nil, 0xdeadbeef), []byte{0xde, 0xad, 0xbe, 0xef})
|
||||
}
|
||||
|
||||
func TestInt32Layout(t *testing.T) {
|
||||
wantEncoding(t, AppendInt32(nil, 0), []byte{0, 0, 0, 0})
|
||||
wantEncoding(t, AppendInt32(nil, -1), []byte{0xff, 0xff, 0xff, 0xff})
|
||||
wantEncoding(t, AppendInt32(nil, math.MinInt32), []byte{0x80, 0, 0, 0})
|
||||
wantEncoding(t, AppendInt32(nil, math.MaxInt32), []byte{0x7f, 0xff, 0xff, 0xff})
|
||||
}
|
||||
|
||||
func TestUint64Layout(t *testing.T) {
|
||||
wantEncoding(t, AppendUint64(nil, 0), make([]byte, 8))
|
||||
wantEncoding(t, AppendUint64(nil, math.MaxUint64),
|
||||
[]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff})
|
||||
wantEncoding(t, AppendUint64(nil, 1), []byte{0, 0, 0, 0, 0, 0, 0, 1})
|
||||
wantEncoding(t, AppendUint64(nil, 1<<32), []byte{0, 0, 0, 1, 0, 0, 0, 0})
|
||||
}
|
||||
|
||||
func TestInt64Layout(t *testing.T) {
|
||||
wantEncoding(t, AppendInt64(nil, -1),
|
||||
[]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff})
|
||||
wantEncoding(t, AppendInt64(nil, math.MinInt64),
|
||||
[]byte{0x80, 0, 0, 0, 0, 0, 0, 0})
|
||||
}
|
||||
|
||||
func TestBoolLayout(t *testing.T) {
|
||||
wantEncoding(t, AppendBool(nil, true), []byte{0, 0, 0, 1})
|
||||
wantEncoding(t, AppendBool(nil, false), []byte{0, 0, 0, 0})
|
||||
}
|
||||
|
||||
func TestOpaquePadding(t *testing.T) {
|
||||
// A body of n bytes is followed by (4 - n%4) % 4 zero bytes.
|
||||
for n := range 9 {
|
||||
got := AppendFixedOpaque(nil, bytes.Repeat([]byte{0xa5}, n))
|
||||
pad := (4 - n%4) % 4
|
||||
if len(got) != n+pad {
|
||||
t.Fatalf("fixed opaque of %d bytes encoded as %d bytes, want %d", n, len(got), n+pad)
|
||||
}
|
||||
for _, b := range got[n:] {
|
||||
if b != 0 {
|
||||
t.Fatalf("fixed opaque of %d bytes carries nonzero padding", n)
|
||||
}
|
||||
}
|
||||
}
|
||||
wantEncoding(t, AppendVarOpaque(nil, []byte("ab")),
|
||||
[]byte{0, 0, 0, 2, 'a', 'b', 0, 0})
|
||||
wantEncoding(t, AppendString(nil, "abcd"),
|
||||
[]byte{0, 0, 0, 4, 'a', 'b', 'c', 'd'})
|
||||
wantEncoding(t, AppendString(nil, ""),
|
||||
[]byte{0, 0, 0, 0})
|
||||
}
|
||||
|
||||
func TestRoundTrip(t *testing.T) {
|
||||
const long = "pěkný řetězec s diakritikou, aby UTF-8 prošlo bez úhony"
|
||||
inputs := struct {
|
||||
u32 []uint32
|
||||
i32 []int32
|
||||
u64 []uint64
|
||||
i64 []int64
|
||||
bl []bool
|
||||
str []string
|
||||
op [][]byte
|
||||
}{
|
||||
u32: []uint32{0, 1, 2049, math.MaxUint32},
|
||||
i32: []int32{0, -1, 1, math.MinInt32, math.MaxInt32},
|
||||
u64: []uint64{0, 1, 2049, 1 << 40, math.MaxUint64},
|
||||
i64: []int64{0, -1, math.MinInt64, math.MaxInt64},
|
||||
bl: []bool{true, false},
|
||||
str: []string{"", "a", "abcd", "abcde", long, strings.Repeat("x", 1000)},
|
||||
op: [][]byte{nil, {}, []byte("ab"), bytes.Repeat([]byte{0x5a}, 7)},
|
||||
}
|
||||
|
||||
var buf []byte
|
||||
for _, v := range inputs.u32 {
|
||||
buf = AppendUint32(buf, v)
|
||||
}
|
||||
for _, v := range inputs.i32 {
|
||||
buf = AppendInt32(buf, v)
|
||||
}
|
||||
for _, v := range inputs.u64 {
|
||||
buf = AppendUint64(buf, v)
|
||||
}
|
||||
for _, v := range inputs.i64 {
|
||||
buf = AppendInt64(buf, v)
|
||||
}
|
||||
for _, v := range inputs.bl {
|
||||
buf = AppendBool(buf, v)
|
||||
}
|
||||
for _, v := range inputs.str {
|
||||
buf = AppendString(buf, v)
|
||||
}
|
||||
for _, v := range inputs.op {
|
||||
buf = AppendVarOpaque(buf, v)
|
||||
}
|
||||
if len(buf)%4 != 0 {
|
||||
t.Fatalf("the joined encoding has %d bytes, not a multiple of four", len(buf))
|
||||
}
|
||||
|
||||
d := NewDecoder(buf)
|
||||
for _, want := range inputs.u32 {
|
||||
got, err := d.Uint32()
|
||||
if err != nil || got != want {
|
||||
t.Fatalf("uint32: got %d, %v; want %d", got, err, want)
|
||||
}
|
||||
}
|
||||
for _, want := range inputs.i32 {
|
||||
got, err := d.Int32()
|
||||
if err != nil || got != want {
|
||||
t.Fatalf("int32: got %d, %v; want %d", got, err, want)
|
||||
}
|
||||
}
|
||||
for _, want := range inputs.u64 {
|
||||
got, err := d.Uint64()
|
||||
if err != nil || got != want {
|
||||
t.Fatalf("uint64: got %d, %v; want %d", got, err, want)
|
||||
}
|
||||
}
|
||||
for _, want := range inputs.i64 {
|
||||
got, err := d.Int64()
|
||||
if err != nil || got != want {
|
||||
t.Fatalf("int64: got %d, %v; want %d", got, err, want)
|
||||
}
|
||||
}
|
||||
for _, want := range inputs.bl {
|
||||
got, err := d.Bool()
|
||||
if err != nil || got != want {
|
||||
t.Fatalf("bool: got %v, %v; want %v", got, err, want)
|
||||
}
|
||||
}
|
||||
for _, want := range inputs.str {
|
||||
got, err := d.String()
|
||||
if err != nil || got != want {
|
||||
t.Fatalf("string: got %q, %v; want %q", got, err, want)
|
||||
}
|
||||
}
|
||||
for _, want := range inputs.op {
|
||||
got, err := d.VarOpaque()
|
||||
if err != nil || !bytes.Equal(got, want) {
|
||||
t.Fatalf("var opaque: got %x, %v; want %x", got, err, want)
|
||||
}
|
||||
}
|
||||
if d.Remaining() != 0 {
|
||||
t.Fatalf("%d bytes left over after the full round trip", d.Remaining())
|
||||
}
|
||||
}
|
||||
|
||||
func TestFixedOpaqueRoundTrip(t *testing.T) {
|
||||
for n := range 9 {
|
||||
want := bytes.Repeat([]byte{0x3c}, n)
|
||||
got, err := NewDecoder(AppendFixedOpaque(nil, want)).FixedOpaque(n)
|
||||
if err != nil || !bytes.Equal(got, want) {
|
||||
t.Fatalf("fixed opaque %d: got %x, %v", n, got, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTruncated(t *testing.T) {
|
||||
// Every proper prefix of an encoded value fails to decode, and the
|
||||
// failure is a truncation rather than anything else.
|
||||
u64 := AppendUint64(nil, math.MaxUint64)
|
||||
for n := range len(u64) {
|
||||
if _, err := NewDecoder(u64[:n]).Uint64(); !errors.Is(err, ErrTruncated) {
|
||||
t.Fatalf("a %d byte prefix decoded as a uint64: %v", n, err)
|
||||
}
|
||||
}
|
||||
vo := AppendVarOpaque(nil, []byte("0123456789"))
|
||||
for n := range len(vo) {
|
||||
if _, err := NewDecoder(vo[:n]).VarOpaque(); !errors.Is(err, ErrTruncated) {
|
||||
t.Fatalf("a %d byte prefix decoded as a var opaque: %v", n, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTruncatedPadding(t *testing.T) {
|
||||
// A two byte body carries two padding bytes, so an input that ends
|
||||
// after one of them is truncated, not complete.
|
||||
_, err := NewDecoder([]byte{0, 0, 0, 2, 'a', 'b', 0}).VarOpaque()
|
||||
if !errors.Is(err, ErrTruncated) {
|
||||
t.Fatalf("a short padding decoded as a value: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImpossibleLengths(t *testing.T) {
|
||||
// A count of 0xffffffff on a tiny input is refused; on a 32 bit
|
||||
// platform the count does not fit an int at all and the bad length
|
||||
// guard fires first, which the test accepts either way.
|
||||
d := NewDecoder([]byte{0xff, 0xff, 0xff, 0xff})
|
||||
if _, err := d.VarOpaque(); !errors.Is(err, ErrTruncated) && !errors.Is(err, ErrBadLength) {
|
||||
t.Fatalf("a count of 0xffffffff on a tiny input returned %v", err)
|
||||
}
|
||||
if _, err := d.FixedOpaque(-1); !errors.Is(err, ErrBadLength) {
|
||||
t.Fatalf("a negative fixed length returned %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRaw(t *testing.T) {
|
||||
in := []byte{1, 2, 3, 4, 5}
|
||||
d := NewDecoder(in)
|
||||
got, err := d.Raw(3)
|
||||
if err != nil || !bytes.Equal(got, in[:3]) {
|
||||
t.Fatalf("raw: %x, %v", got, err)
|
||||
}
|
||||
if d.Remaining() != 2 {
|
||||
t.Fatalf("%d bytes left, want 2", d.Remaining())
|
||||
}
|
||||
if _, err := d.Raw(3); !errors.Is(err, ErrTruncated) {
|
||||
t.Fatalf("a raw read past the end: %v", err)
|
||||
}
|
||||
if _, err := NewDecoder(nil).Raw(-1); !errors.Is(err, ErrBadLength) {
|
||||
t.Fatalf("a negative raw read: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonzeroBool(t *testing.T) {
|
||||
// The receiver accepts any nonzero word as true.
|
||||
got, err := NewDecoder([]byte{0, 0, 0, 0x7f}).Bool()
|
||||
if err != nil || !got {
|
||||
t.Fatalf("the word 0x7f decoded as %v, %v", got, err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user