From b446b411374d784b860ab4b6bfc74c66f694fbdb Mon Sep 17 00:00:00 2001 From: SaladDay <1203511142@qq.com> Date: Tue, 6 Oct 2026 23:18:40 +0000 Subject: [PATCH] Keep the first decode error in sandboxwire.Decoder sandboxwire.Decoder now keeps its first error: every later read returns the zero value, Fail records a protocol's own rule unless an error is already recorded, and Err and Finish return that first error. The File, Process, Link and Network decoders lose their private adapters that added the same rule, and the process shim's IPC decoders read in the same style. Protocol enum checks, bounded counts, Link's Hello version handling and the shim's error texts stay where they were; malformed input fails with the same errors and every golden fixture is unchanged. The six copies of the hex fixture reader become sandboxwiretest.ReadHex. --- apps/daemon/internal/processshim/ipc.go | 300 +++++--------- apps/daemon/internal/processshim/ipc_test.go | 25 +- internal/sandboxfs/protocol.go | 398 +++++++++---------- internal/sandboxfs/protocol_test.go | 25 +- internal/sandboxlink/protocol.go | 136 +++---- internal/sandboxlink/protocol_test.go | 26 +- internal/sandboxnet/protocol.go | 42 +- internal/sandboxnet/protocol_test.go | 25 +- internal/sandboxprocess/protocol.go | 156 +++----- internal/sandboxprocess/protocol_test.go | 25 +- internal/sandboxwire/codec.go | 147 +++---- internal/sandboxwire/sandboxwire_test.go | 97 ++--- internal/sandboxwire/sandboxwiretest/hex.go | 31 ++ 13 files changed, 563 insertions(+), 870 deletions(-) create mode 100644 internal/sandboxwire/sandboxwiretest/hex.go diff --git a/apps/daemon/internal/processshim/ipc.go b/apps/daemon/internal/processshim/ipc.go index afd1589f5..37af6746a 100644 --- a/apps/daemon/internal/processshim/ipc.go +++ b/apps/daemon/internal/processshim/ipc.go @@ -91,6 +91,7 @@ package processshim import ( + "cmp" "errors" "fmt" "slices" @@ -525,19 +526,17 @@ func Decode(f sandboxwire.Frame) (Message, error) { return decode(f, func(d *sandboxwire.Decoder) (Message, error) { switch f.Type { case TypeRequest: - r, err := decodeRequest(d) - if err == nil && r.Version != Version { + r := decodeRequest(d) + if r.Version != Version && d.Err() == nil { return r, nil // the rest is another version's } - return r, finish(d, err) + return r, d.Finish() case TypeAck: return Ack{}, d.Finish() case TypeSignal: - n, err := decodeSignal(d) - return Signal{Number: n}, finish(d, err) + return Signal{Number: decodeSignal(d)}, d.Finish() case TypeResult: - r, err := decodeResult(d) - return r, finish(d, err) + return decodeResult(d), d.Finish() } return nil, errType }) @@ -546,48 +545,32 @@ func Decode(f sandboxwire.Frame) (Message, error) { // DecodeRelay decodes and validates a frame from the relay. func DecodeRelay(f sandboxwire.Frame) (RelayMessage, error) { m, err := decode(f, func(d *sandboxwire.Decoder) (Message, error) { - id, err := decodeID(d) - if err != nil { - return nil, err - } + id := decodeID(d) switch f.Type { case TypeOpen: - m := Open{ID: id} - m.Request, err = decodeRequest(d) - if err == nil && m.Request.Version != Version { - err = fmt.Errorf("request version %d", m.Request.Version) - } - if err == nil { - m.Terminal, err = decodeTerminal(d) + m := Open{ID: id, Request: decodeRequest(d)} + if m.Request.Version != Version { + d.Fail(fmt.Errorf("request version %d", m.Request.Version)) } - return m, finish(d, err) + m.Terminal = decodeTerminal(d) + return m, d.Finish() case TypeInput: - m := Input{ID: id} - m.Data, err = decodeData(d) - return m, finish(d, err) + return Input{ID: id, Data: decodeData(d)}, d.Finish() case TypeInputEnd: return InputEnd{ID: id}, d.Finish() case TypeWritten: m := Written{ID: id} - m.FD, m.Seq, err = decodeFDSeq(d) - return m, finish(d, err) + m.FD, m.Seq = decodeFDSeq(d) + return m, d.Finish() case TypeWriteFailed: m := WriteFailed{ID: id} - m.FD, m.Seq, err = decodeFDSeq(d) - if err == nil { - m.Errno, err = d.U32() - } - if err == nil && m.Errno == 0 { - err = errors.New("errno 0") + m.FD, m.Seq = decodeFDSeq(d) + if m.Errno = d.U32(); m.Errno == 0 { + d.Fail(errors.New("errno 0")) } - return m, finish(d, err) + return m, d.Finish() case TypeSignaled: - m := Signaled{ID: id} - m.Number, err = decodeSignal(d) - if err == nil { - m.Size, err = decodeSize(d) - } - return m, finish(d, err) + return Signaled{ID: id, Number: decodeSignal(d), Size: decodeSize(d)}, d.Finish() case TypeGone: return Gone{ID: id}, d.Finish() } @@ -602,51 +585,41 @@ func DecodeRelay(f sandboxwire.Frame) (RelayMessage, error) { // DecodeBroker decodes and validates a frame from the broker. func DecodeBroker(f sandboxwire.Frame) (BrokerMessage, error) { m, err := decode(f, func(d *sandboxwire.Decoder) (Message, error) { - id, err := decodeID(d) - if err != nil { - return nil, err - } + id := decodeID(d) switch f.Type { case TypeAccept: return Accept{ID: id}, d.Finish() case TypeStarted: return Started{ID: id}, d.Finish() case TypeRead: - m := Read{ID: id} - m.Max, err = d.U32() - if err == nil && (m.Max == 0 || m.Max > sandboxwire.MaxChunk) { - err = fmt.Errorf("read of %d bytes", m.Max) + m := Read{ID: id, Max: d.U32()} + if m.Max == 0 || m.Max > sandboxwire.MaxChunk { + d.Fail(fmt.Errorf("read of %d bytes", m.Max)) } - return m, finish(d, err) + return m, d.Finish() case TypeStopInput: return StopInput{ID: id}, d.Finish() case TypeOutput: m := Output{ID: id} - m.FD, m.Seq, err = decodeFDSeq(d) - if err == nil { - m.Data, err = decodeData(d) - } - return m, finish(d, err) + m.FD, m.Seq = decodeFDSeq(d) + m.Data = decodeData(d) + return m, d.Finish() case TypeClose: m := Close{ID: id} - m.FD, m.Seq, err = decodeFDSeq(d) - return m, finish(d, err) + m.FD, m.Seq = decodeFDSeq(d) + return m, d.Finish() case TypeExit: - m := Exit{ID: id} - if m.Result, err = decodeResult(d); err == nil { - m.Marks, err = decodeMarks(d) - } - if err == nil && len(m.Marks) > 0 && len(m.Result.Message) > 0 { - err = errors.New("exit with marks and a message") + m := Exit{ID: id, Result: decodeResult(d), Marks: decodeMarks(d)} + if len(m.Marks) > 0 && len(m.Result.Message) > 0 { + d.Fail(errors.New("exit with marks and a message")) } - return m, finish(d, err) + return m, d.Finish() case TypeNotice: - m := Notice{ID: id} - m.Message, err = d.Bytes() - if err == nil && (len(m.Message) == 0 || len(m.Message) > MaxMessageBytes) { - err = fmt.Errorf("notice of %d bytes", len(m.Message)) + m := Notice{ID: id, Message: d.Bytes()} + if len(m.Message) == 0 || len(m.Message) > MaxMessageBytes { + d.Fail(fmt.Errorf("notice of %d bytes", len(m.Message))) } - return m, finish(d, err) + return m, d.Finish() case TypeEnd: return End{ID: id}, d.Finish() } @@ -660,11 +633,16 @@ func DecodeBroker(f sandboxwire.Frame) (BrokerMessage, error) { var errType = errors.New("message type") +// decode runs body, which returns errType for a type it does not decode. The +// decoder's first error wins, so a bad invocation ID is reported before an +// unknown type. func decode(f sandboxwire.Frame, body func(*sandboxwire.Decoder) (Message, error)) (Message, error) { if f.RequestID != 0 { return nil, fmt.Errorf("%w: request ID %d", ErrProtocol, f.RequestID) } - m, err := body(sandboxwire.NewDecoder(f.Payload)) + d := sandboxwire.NewDecoder(f.Payload) + m, err := body(d) + err = cmp.Or(d.Err(), err) switch { case err == errType: return nil, fmt.Errorf("%w: message type %#x", ErrProtocol, f.Type) @@ -674,177 +652,111 @@ func decode(f sandboxwire.Frame, body func(*sandboxwire.Decoder) (Message, error return m, nil } -// finish returns err, or the decoder's error for trailing bytes. -func finish(d *sandboxwire.Decoder, err error) error { - if err != nil { - return err +func decodeID(d *sandboxwire.Decoder) uint64 { + id := d.U64() + if id == 0 { + d.Fail(errors.New("invocation ID 0")) } - return d.Finish() + return id } -func decodeID(d *sandboxwire.Decoder) (uint64, error) { - id, err := d.U64() - if err == nil && id == 0 { - err = errors.New("invocation ID 0") - } - return id, err -} - -func decodeRequest(d *sandboxwire.Decoder) (Request, error) { - var r Request - var err error - if r.Version, err = d.U16(); err != nil || r.Version != Version { - return Request{Version: r.Version}, err - } - if r.ExecPath, err = d.Bytes(); err != nil { - return r, err - } - if r.Argv, err = decodeList(d); err != nil { - return r, err - } - if r.Env, err = decodeList(d); err != nil { - return r, err - } - if r.Cwd, err = d.Bytes(); err != nil { - return r, err - } - if r.Umask, err = d.U32(); err != nil { - return r, err +// decodeRequest stops after a Version other than Version. +func decodeRequest(d *sandboxwire.Decoder) Request { + v := d.U16() + if v != Version { + return Request{Version: v} } + r := Request{Version: v, ExecPath: d.Bytes(), Argv: decodeList(d), Env: decodeList(d), Cwd: d.Bytes(), Umask: d.U32()} switch { case slices.Contains(r.ExecPath, 0) || slices.Contains(r.Cwd, 0): - return r, errors.New("NUL in path") + d.Fail(errors.New("NUL in path")) case len(r.Cwd) == 0 || r.Cwd[0] != '/': - return r, errors.New("cwd is not absolute") + d.Fail(errors.New("cwd is not absolute")) case r.Umask > 0o777: - return r, fmt.Errorf("umask %#o", r.Umask) + d.Fail(fmt.Errorf("umask %#o", r.Umask)) } for _, b := range slices.Concat(r.Argv, r.Env) { if slices.Contains(b, 0) { - return r, errors.New("NUL in argv or environment") + d.Fail(errors.New("NUL in argv or environment")) } } - return r, nil + return r } -func decodeList(d *sandboxwire.Decoder) ([][]byte, error) { - n, err := d.Count(MaxFrameBytes / 4) - if err != nil { - return nil, err - } - list := make([][]byte, n) +func decodeList(d *sandboxwire.Decoder) [][]byte { + list := make([][]byte, d.Count(MaxFrameBytes/4)) for i := range list { - if list[i], err = d.Bytes(); err != nil { - return nil, err - } + list[i] = d.Bytes() } - return list, nil + return list } -func decodeSignal(d *sandboxwire.Decoder) (uint16, error) { - n, err := d.U16() - if err == nil && (n == 0 || n > 64) { - err = fmt.Errorf("signal %d", n) +func decodeSignal(d *sandboxwire.Decoder) uint16 { + n := d.U16() + if n == 0 || n > 64 { + d.Fail(fmt.Errorf("signal %d", n)) } - return n, err + return n } -func decodeResult(d *sandboxwire.Decoder) (Result, error) { - var r Result - var err error - if r.Signal, err = d.U16(); err != nil { - return r, err - } - if r.Code, err = d.U8(); err != nil { - return r, err - } - if r.Message, err = d.Bytes(); err != nil { - return r, err - } +func decodeResult(d *sandboxwire.Decoder) Result { + r := Result{Signal: d.U16(), Code: d.U8(), Message: d.Bytes()} switch { case r.Signal > 64 || (r.Signal != 0 && r.Code != 0): - return r, fmt.Errorf("signal %d with code %d", r.Signal, r.Code) + d.Fail(fmt.Errorf("signal %d with code %d", r.Signal, r.Code)) case len(r.Message) > MaxMessageBytes: - return r, fmt.Errorf("message of %d bytes", len(r.Message)) + d.Fail(fmt.Errorf("message of %d bytes", len(r.Message))) } - return r, nil + return r } -func decodeSize(d *sandboxwire.Decoder) (*WindowSize, error) { - ok, err := d.Present() - if err != nil || !ok { - return nil, err +func decodeSize(d *sandboxwire.Decoder) *WindowSize { + if !d.Present() { + return nil } - var s WindowSize - for _, v := range []*uint16{&s.Rows, &s.Cols, &s.XPixels, &s.YPixels} { - if *v, err = d.U16(); err != nil { - return nil, err - } - } - return &s, nil + return &WindowSize{Rows: d.U16(), Cols: d.U16(), XPixels: d.U16(), YPixels: d.U16()} } -func decodeTerminal(d *sandboxwire.Decoder) (*Terminal, error) { - size, err := decodeSize(d) - if err != nil || size == nil { - return nil, err - } - t := &Terminal{Size: *size} - for _, v := range []*uint32{&t.Iflag, &t.Oflag, &t.Cflag, &t.Lflag} { - if *v, err = d.U32(); err != nil { - return nil, err - } - } - if t.Cc, err = d.Bytes(); err != nil { - return nil, err +func decodeTerminal(d *sandboxwire.Decoder) *Terminal { + size := decodeSize(d) + if size == nil { + return nil } + t := &Terminal{Size: *size, Iflag: d.U32(), Oflag: d.U32(), Cflag: d.U32(), Lflag: d.U32(), Cc: d.Bytes()} if len(t.Cc) > MaxControlChars { - return nil, fmt.Errorf("%d control characters", len(t.Cc)) + d.Fail(fmt.Errorf("%d control characters", len(t.Cc))) } - return t, nil + return t } -func decodeData(d *sandboxwire.Decoder) ([]byte, error) { - b, err := d.Bytes() - if err == nil && (len(b) == 0 || len(b) > sandboxwire.MaxChunk) { - err = fmt.Errorf("data of %d bytes", len(b)) +func decodeData(d *sandboxwire.Decoder) []byte { + b := d.Bytes() + if len(b) == 0 || len(b) > sandboxwire.MaxChunk { + d.Fail(fmt.Errorf("data of %d bytes", len(b))) } - return b, err + return b } -func decodeFD(d *sandboxwire.Decoder) (uint8, error) { - fd, err := d.U8() - if err == nil && fd != 1 && fd != 2 { - err = fmt.Errorf("fd %d", fd) +func decodeFDSeq(d *sandboxwire.Decoder) (uint8, uint64) { + fd := d.U8() + if fd != 1 && fd != 2 { + d.Fail(fmt.Errorf("fd %d", fd)) } - return fd, err -} - -func decodeFDSeq(d *sandboxwire.Decoder) (uint8, uint64, error) { - fd, err := decodeFD(d) - if err != nil { - return 0, 0, err + seq := d.U64() + if seq == 0 { + d.Fail(errors.New("sequence 0")) } - seq, err := d.U64() - if err == nil && seq == 0 { - err = errors.New("sequence 0") - } - return fd, seq, err + return fd, seq } -func decodeMarks(d *sandboxwire.Decoder) ([]Mark, error) { - n, err := d.Count(2) - if err != nil || n == 0 { - return nil, err - } - marks := make([]Mark, n) - for i := range marks { - if marks[i].FD, marks[i].Seq, err = decodeFDSeq(d); err != nil { - return nil, err - } - if i > 0 && marks[i].FD == marks[0].FD { - return nil, fmt.Errorf("two marks on fd %d", marks[i].FD) +func decodeMarks(d *sandboxwire.Decoder) []Mark { + var marks []Mark + for range d.Count(2) { + var m Mark + if m.FD, m.Seq = decodeFDSeq(d); len(marks) > 0 && m.FD == marks[0].FD { + d.Fail(fmt.Errorf("two marks on fd %d", m.FD)) } + marks = append(marks, m) } - return marks, nil + return marks } diff --git a/apps/daemon/internal/processshim/ipc_test.go b/apps/daemon/internal/processshim/ipc_test.go index 97392f5de..751218942 100644 --- a/apps/daemon/internal/processshim/ipc_test.go +++ b/apps/daemon/internal/processshim/ipc_test.go @@ -3,15 +3,12 @@ package processshim import ( "bytes" "encoding/binary" - "encoding/hex" "errors" - "os" - "path/filepath" "reflect" - "strings" "testing" "github.com/MiniMax-AI/OpenAgentCore/internal/sandboxwire" + "github.com/MiniMax-AI/OpenAgentCore/internal/sandboxwire/sandboxwiretest" ) func b(s string) []byte { return []byte(s) } @@ -75,28 +72,10 @@ func decoderFor(t uint16) func(sandboxwire.Frame) (Message, error) { return decoders[min(t>>4, 2)] } -func readHexFixture(t testing.TB, name string) []byte { - t.Helper() - raw, err := os.ReadFile(filepath.Join("testdata", name)) - if err != nil { - t.Fatal(err) - } - var digits strings.Builder - for _, line := range strings.Split(string(raw), "\n") { - line, _, _ = strings.Cut(line, "#") - digits.WriteString(strings.Join(strings.Fields(line), "")) - } - frame, err := hex.DecodeString(digits.String()) - if err != nil { - t.Fatal(err) - } - return frame -} - func TestGoldenFixtures(t *testing.T) { for _, fx := range fixtures { t.Run(fx.name, func(t *testing.T) { - want := readHexFixture(t, fx.name) + want := sandboxwiretest.ReadHex(t, fx.name) var got bytes.Buffer if err := sandboxwire.WriteFrame(&got, Frame(fx.msg)); err != nil { t.Fatal(err) diff --git a/internal/sandboxfs/protocol.go b/internal/sandboxfs/protocol.go index b0b65fe94..7ce18613b 100644 --- a/internal/sandboxfs/protocol.go +++ b/internal/sandboxfs/protocol.go @@ -914,7 +914,7 @@ type Request interface { type message interface { encode(*sandboxwire.Encoder) - decode(*decoder) + decode(*sandboxwire.Decoder) validate() error } @@ -1142,45 +1142,6 @@ func malformed(format string, args ...any) error { return fmt.Errorf("%w: "+format, append([]any{sandboxwire.ErrMalformed}, args...)...) } -// decoder keeps the first error and reads nothing after it. -type decoder struct { - d *sandboxwire.Decoder - err error -} - -func read[T any](d *decoder, f func() (T, error)) T { - var v T - if d.err == nil { - v, d.err = f() - } - return v -} - -func (d *decoder) u16() uint16 { return read(d, d.d.U16) } -func (d *decoder) u32() uint32 { return read(d, d.d.U32) } -func (d *decoder) u64() uint64 { return read(d, d.d.U64) } -func (d *decoder) i64() int64 { return read(d, d.d.I64) } -func (d *decoder) bool() bool { return read(d, d.d.Bool) } -func (d *decoder) bytes() []byte { return read(d, d.d.Bytes) } -func (d *decoder) present() bool { return read(d, d.d.Present) } -func (d *decoder) id() sandboxwire.ID { return read(d, d.d.ID) } - -func (d *decoder) count(max uint32) int { - return read(d, func() (int, error) { return d.d.Count(max) }) -} - -func decodeMessage(m message, payload []byte) error { - d := &decoder{d: sandboxwire.NewDecoder(payload)} - m.decode(d) - if d.err != nil { - return d.err - } - if err := d.d.Finish(); err != nil { - return err - } - return m.validate() -} - func encodeMessage(e *sandboxwire.Encoder, m message) error { if err := m.validate(); err != nil { return err @@ -1209,7 +1170,7 @@ func decodeRequest(op Op, payload []byte) (Request, error) { return nil, malformed("unknown request %d", uint16(op)) } r := opSpecs[op].newRequest() - return r, decodeMessage(r, payload) + return r, decodeRest(sandboxwire.NewDecoder(payload), r) } // encodeResponse encodes a success carrying r, or the failure f when it is @@ -1236,10 +1197,10 @@ func decodeResponse(op Op, payload []byte) (message, *Failure, error) { if op < 1 || op > OpCancelRequest { return nil, nil, malformed("unknown request %d", uint16(op)) } - d := &decoder{d: sandboxwire.NewDecoder(payload)} - switch result := Result(d.u16()); { - case d.err != nil: - return nil, nil, d.err + d := sandboxwire.NewDecoder(payload) + switch result := Result(d.U16()); { + case d.Err() != nil: + return nil, nil, d.Err() case result == ResultSuccess: r := opSpecs[op].newResponse() return r, nil, decodeRest(d, r) @@ -1251,12 +1212,10 @@ func decodeResponse(op Op, payload []byte) (message, *Failure, error) { } } -func decodeRest(d *decoder, m message) error { +// decodeRest decodes m from the rest of d and validates it. +func decodeRest(d *sandboxwire.Decoder, m message) error { m.decode(d) - if d.err != nil { - return d.err - } - if err := d.d.Finish(); err != nil { + if err := d.Finish(); err != nil { return err } return m.validate() @@ -1264,8 +1223,8 @@ func decodeRest(d *decoder, m message) error { // Shared values. -func (r NodeRef) encode(e *sandboxwire.Encoder) { e.U64(r.ID); e.U64(r.Generation) } -func (r *NodeRef) decode(d *decoder) { r.ID = d.u64(); r.Generation = d.u64() } +func (r NodeRef) encode(e *sandboxwire.Encoder) { e.U64(r.ID); e.U64(r.Generation) } +func (r *NodeRef) decode(d *sandboxwire.Decoder) { r.ID = d.U64(); r.Generation = d.U64() } func (r NodeRef) validate() error { if r.ID == 0 || r.Generation == 0 { return malformed("node reference %d/%d", r.ID, r.Generation) @@ -1280,8 +1239,8 @@ func (h HandleID) validate() error { return nil } -func (t Timestamp) encode(e *sandboxwire.Encoder) { e.I64(t.Sec); e.U32(t.Nsec) } -func (t *Timestamp) decode(d *decoder) { t.Sec = d.i64(); t.Nsec = d.u32() } +func (t Timestamp) encode(e *sandboxwire.Encoder) { e.I64(t.Sec); e.U32(t.Nsec) } +func (t *Timestamp) decode(d *sandboxwire.Decoder) { t.Sec = d.I64(); t.Nsec = d.U32() } func (t Timestamp) validate() error { if t.Nsec >= 1e9 { return malformed("nanoseconds %d", t.Nsec) @@ -1304,16 +1263,16 @@ func (a *Attr) encode(e *sandboxwire.Encoder) { a.Ctime.encode(e) } -func (a *Attr) decode(d *decoder) { - a.Ino = d.u64() - a.Mode = d.u32() - a.Nlink = d.u32() - a.UID = d.u32() - a.GID = d.u32() - a.Rdev = d.u64() - a.Size = d.u64() - a.Blocks = d.u64() - a.Blksize = d.u32() +func (a *Attr) decode(d *sandboxwire.Decoder) { + a.Ino = d.U64() + a.Mode = d.U32() + a.Nlink = d.U32() + a.UID = d.U32() + a.GID = d.U32() + a.Rdev = d.U64() + a.Size = d.U64() + a.Blocks = d.U64() + a.Blksize = d.U32() a.Atime.decode(d) a.Mtime.decode(d) a.Ctime.decode(d) @@ -1330,7 +1289,7 @@ func (a *Attr) validate() error { } func (en *Entry) encode(e *sandboxwire.Encoder) { en.Node.encode(e); en.Attr.encode(e) } -func (en *Entry) decode(d *decoder) { en.Node.decode(d); en.Attr.decode(d) } +func (en *Entry) decode(d *sandboxwire.Decoder) { en.Node.decode(d); en.Attr.decode(d) } func (en *Entry) validate() error { return errors.Join(en.Node.validate(), en.Attr.validate()) } func (t *Target) encode(e *sandboxwire.Encoder) { @@ -1342,12 +1301,12 @@ func (t *Target) encode(e *sandboxwire.Encoder) { } } -func (t *Target) decode(d *decoder) { - switch t.Kind = TargetKind(d.u16()); t.Kind { +func (t *Target) decode(d *sandboxwire.Decoder) { + switch t.Kind = TargetKind(d.U16()); t.Kind { case TargetNode: t.Node.decode(d) case TargetHandle: - t.Handle = HandleID(d.u64()) + t.Handle = HandleID(d.U64()) } } @@ -1368,7 +1327,11 @@ func (t *Target) validate() error { } func (l *Lock) encode(e *sandboxwire.Encoder) { e.Enum(uint16(l.Mode)); e.U64(l.Start); e.U64(l.End) } -func (l *Lock) decode(d *decoder) { l.Mode = LockMode(d.u16()); l.Start = d.u64(); l.End = d.u64() } +func (l *Lock) decode(d *sandboxwire.Decoder) { + l.Mode = LockMode(d.U16()) + l.Start = d.U64() + l.End = d.U64() +} func (l *Lock) validate() error { if l.Mode < LockRead || l.Mode > LockUnlock { return malformed("lock mode %d", l.Mode) @@ -1389,15 +1352,15 @@ func (f *Failure) encode(e *sandboxwire.Encoder) { e.Bytes([]byte(f.Message)) } -func (f *Failure) decode(d *decoder) { - f.Code = ErrorCode(d.u16()) - if d.present() { - if f.Errno = Errno(d.u16()); f.Errno == 0 && d.err == nil { - d.err = malformed("zero errno") +func (f *Failure) decode(d *sandboxwire.Decoder) { + f.Code = ErrorCode(d.U16()) + if d.Present() { + if f.Errno = Errno(d.U16()); f.Errno == 0 { + d.Fail(malformed("zero errno")) } } - f.Effect = sandboxwire.Effect(d.u16()) - f.Message = string(d.bytes()) + f.Effect = sandboxwire.Effect(d.U16()) + f.Message = string(d.Bytes()) } func (f *Failure) validate() error { @@ -1423,8 +1386,8 @@ func encodeOptionalFailure(e *sandboxwire.Encoder, f *Failure) { } } -func decodeOptionalFailure(d *decoder) *Failure { - if !d.present() { +func decodeOptionalFailure(d *sandboxwire.Decoder) *Failure { + if !d.Present() { return nil } f := new(Failure) @@ -1492,15 +1455,15 @@ func (c *Capabilities) encode(e *sandboxwire.Encoder) { } } -func (c *Capabilities) decode(d *decoder) { - c.PathProfile = PathProfile(d.u16()) - c.CacheProfile = CacheProfile(d.u16()) - c.Durability = Durability(d.u16()) +func (c *Capabilities) decode(d *sandboxwire.Decoder) { + c.PathProfile = PathProfile(d.U16()) + c.CacheProfile = CacheProfile(d.U16()) + c.Durability = Durability(d.U16()) for _, v := range []*uint32{&c.MaxNameBytes, &c.MaxPathBytes, &c.MaxReadBytes, &c.MaxWriteBytes, &c.MaxWalkComponents, &c.MaxReadDirBytes, &c.MaxOpenHandles} { - *v = d.u32() + *v = d.U32() } for _, v := range c.flags() { - *v = d.bool() + *v = d.Bool() } } @@ -1534,7 +1497,7 @@ func (c *Capabilities) validate() error { // Messages. func (*DescribeRequest) encode(*sandboxwire.Encoder) {} -func (*DescribeRequest) decode(*decoder) {} +func (*DescribeRequest) decode(*sandboxwire.Decoder) {} func (*DescribeRequest) validate() error { return nil } func (r *DescribeResponse) encode(e *sandboxwire.Encoder) { @@ -1548,14 +1511,14 @@ func (r *DescribeResponse) encode(e *sandboxwire.Encoder) { } } -func (r *DescribeResponse) decode(d *decoder) { - r.ServerInstanceID = d.id() - r.Identity.UID = d.u32() - r.Identity.GID = d.u32() +func (r *DescribeResponse) decode(d *sandboxwire.Decoder) { + r.ServerInstanceID = d.ID() + r.Identity.UID = d.U32() + r.Identity.GID = d.U32() r.Capabilities.decode(d) - r.Exports = make([]sandboxlink.ExportID, d.count(sandboxlink.MaxExports)) + r.Exports = make([]sandboxlink.ExportID, d.Count(sandboxlink.MaxExports)) for i := range r.Exports { - r.Exports[i] = sandboxlink.ExportID(d.bytes()) + r.Exports[i] = sandboxlink.ExportID(d.Bytes()) } } @@ -1577,9 +1540,9 @@ func (r *DescribeResponse) validate() error { } func (r *AttachRequest) encode(e *sandboxwire.Encoder) { e.Bytes([]byte(r.Export)); e.Bool(r.ReadOnly) } -func (r *AttachRequest) decode(d *decoder) { - r.Export = sandboxlink.ExportID(d.bytes()) - r.ReadOnly = d.bool() +func (r *AttachRequest) decode(d *sandboxwire.Decoder) { + r.Export = sandboxlink.ExportID(d.Bytes()) + r.ReadOnly = d.Bool() } func (r *AttachRequest) validate() error { if !r.Export.Valid() { @@ -1589,7 +1552,7 @@ func (r *AttachRequest) validate() error { } func (r *AttachResponse) encode(e *sandboxwire.Encoder) { r.Root.encode(e) } -func (r *AttachResponse) decode(d *decoder) { r.Root.decode(d) } +func (r *AttachResponse) decode(d *sandboxwire.Decoder) { r.Root.decode(d) } func (r *AttachResponse) validate() error { if r.Root.Attr.Mode&ModeType != ModeDirectory { return malformed("export root is not a directory") @@ -1598,18 +1561,18 @@ func (r *AttachResponse) validate() error { } func (*DetachRequest) encode(*sandboxwire.Encoder) {} -func (*DetachRequest) decode(*decoder) {} +func (*DetachRequest) decode(*sandboxwire.Decoder) {} func (*DetachRequest) validate() error { return nil } func (*DetachResponse) encode(*sandboxwire.Encoder) {} -func (*DetachResponse) decode(*decoder) {} +func (*DetachResponse) decode(*sandboxwire.Decoder) {} func (*DetachResponse) validate() error { return nil } func (r *LookupRequest) encode(e *sandboxwire.Encoder) { r.Parent.encode(e); e.Bytes(r.Name) } -func (r *LookupRequest) decode(d *decoder) { r.Parent.decode(d); r.Name = d.bytes() } +func (r *LookupRequest) decode(d *sandboxwire.Decoder) { r.Parent.decode(d); r.Name = d.Bytes() } func (r *LookupRequest) validate() error { return errors.Join(r.Parent.validate(), validName(r.Name)) } func (r *LookupResponse) encode(e *sandboxwire.Encoder) { r.Entry.encode(e) } -func (r *LookupResponse) decode(d *decoder) { r.Entry.decode(d) } +func (r *LookupResponse) decode(d *sandboxwire.Decoder) { r.Entry.decode(d) } func (r *LookupResponse) validate() error { return r.Entry.validate() } func (r *WalkRequest) encode(e *sandboxwire.Encoder) { @@ -1620,11 +1583,11 @@ func (r *WalkRequest) encode(e *sandboxwire.Encoder) { } } -func (r *WalkRequest) decode(d *decoder) { +func (r *WalkRequest) decode(d *sandboxwire.Decoder) { r.Parent.decode(d) - r.Names = make([][]byte, d.count(maxWalkNames)) + r.Names = make([][]byte, d.Count(maxWalkNames)) for i := range r.Names { - r.Names[i] = d.bytes() + r.Names[i] = d.Bytes() } } @@ -1648,8 +1611,8 @@ func (r *WalkResponse) encode(e *sandboxwire.Encoder) { encodeOptionalFailure(e, r.Failure) } -func (r *WalkResponse) decode(d *decoder) { - r.Entries = make([]Entry, d.count(maxWalkNames)) +func (r *WalkResponse) decode(d *sandboxwire.Decoder) { + r.Entries = make([]Entry, d.Count(maxWalkNames)) for i := range r.Entries { r.Entries[i].decode(d) } @@ -1672,11 +1635,11 @@ func (r *WalkResponse) validate() error { } func (r *GetAttrRequest) encode(e *sandboxwire.Encoder) { r.Target.encode(e) } -func (r *GetAttrRequest) decode(d *decoder) { r.Target.decode(d) } +func (r *GetAttrRequest) decode(d *sandboxwire.Decoder) { r.Target.decode(d) } func (r *GetAttrRequest) validate() error { return r.Target.validate() } func (r *GetAttrResponse) encode(e *sandboxwire.Encoder) { r.Attr.encode(e) } -func (r *GetAttrResponse) decode(d *decoder) { r.Attr.decode(d) } +func (r *GetAttrResponse) decode(d *sandboxwire.Decoder) { r.Attr.decode(d) } func (r *GetAttrResponse) validate() error { return r.Attr.validate() } func (r *SetAttrRequest) encode(e *sandboxwire.Encoder) { @@ -1702,20 +1665,20 @@ func (r *SetAttrRequest) encode(e *sandboxwire.Encoder) { } } -func (r *SetAttrRequest) decode(d *decoder) { +func (r *SetAttrRequest) decode(d *sandboxwire.Decoder) { r.Target.decode(d) - r.Set = AttrMask(d.u32()) + r.Set = AttrMask(d.U32()) if r.Set&AttrSize != 0 { - r.Size = d.u64() + r.Size = d.U64() } if r.Set&AttrMode != 0 { - r.Mode = d.u32() + r.Mode = d.U32() } if r.Set&AttrUID != 0 { - r.UID = d.u32() + r.UID = d.U32() } if r.Set&AttrGID != 0 { - r.GID = d.u32() + r.GID = d.U32() } if r.Set&AttrAtime != 0 { r.Atime.decode(d) @@ -1744,11 +1707,14 @@ func (r *SetAttrRequest) validate() error { } func (r *SetAttrResponse) encode(e *sandboxwire.Encoder) { r.Attr.encode(e) } -func (r *SetAttrResponse) decode(d *decoder) { r.Attr.decode(d) } +func (r *SetAttrResponse) decode(d *sandboxwire.Decoder) { r.Attr.decode(d) } func (r *SetAttrResponse) validate() error { return r.Attr.validate() } func (r *AccessRequest) encode(e *sandboxwire.Encoder) { r.Node.encode(e); e.U32(uint32(r.Mask)) } -func (r *AccessRequest) decode(d *decoder) { r.Node.decode(d); r.Mask = AccessMask(d.u32()) } +func (r *AccessRequest) decode(d *sandboxwire.Decoder) { + r.Node.decode(d) + r.Mask = AccessMask(d.U32()) +} func (r *AccessRequest) validate() error { if r.Mask&^accessMaskAll != 0 { return malformed("access mask %#x", uint32(r.Mask)) @@ -1757,7 +1723,7 @@ func (r *AccessRequest) validate() error { } func (*AccessResponse) encode(*sandboxwire.Encoder) {} -func (*AccessResponse) decode(*decoder) {} +func (*AccessResponse) decode(*sandboxwire.Decoder) {} func (*AccessResponse) validate() error { return nil } func (r *OpenRequest) encode(e *sandboxwire.Encoder) { @@ -1767,11 +1733,11 @@ func (r *OpenRequest) encode(e *sandboxwire.Encoder) { e.U32(uint32(r.Flags)) } -func (r *OpenRequest) decode(d *decoder) { - r.Handle = HandleID(d.u64()) +func (r *OpenRequest) decode(d *sandboxwire.Decoder) { + r.Handle = HandleID(d.U64()) r.Node.decode(d) - r.Access = AccessMode(d.u16()) - r.Flags = OpenFlags(d.u32()) + r.Access = AccessMode(d.U16()) + r.Flags = OpenFlags(d.U32()) } func (r *OpenRequest) validate() error { @@ -1779,7 +1745,7 @@ func (r *OpenRequest) validate() error { } func (*OpenResponse) encode(*sandboxwire.Encoder) {} -func (*OpenResponse) decode(*decoder) {} +func (*OpenResponse) decode(*sandboxwire.Decoder) {} func (*OpenResponse) validate() error { return nil } func (r *CreateRequest) encode(e *sandboxwire.Encoder) { @@ -1792,14 +1758,14 @@ func (r *CreateRequest) encode(e *sandboxwire.Encoder) { e.Bool(r.Exclusive) } -func (r *CreateRequest) decode(d *decoder) { - r.Handle = HandleID(d.u64()) +func (r *CreateRequest) decode(d *sandboxwire.Decoder) { + r.Handle = HandleID(d.U64()) r.Parent.decode(d) - r.Name = d.bytes() - r.Mode = d.u32() - r.Access = AccessMode(d.u16()) - r.Flags = OpenFlags(d.u32()) - r.Exclusive = d.bool() + r.Name = d.Bytes() + r.Mode = d.U32() + r.Access = AccessMode(d.U16()) + r.Flags = OpenFlags(d.U32()) + r.Exclusive = d.Bool() } func (r *CreateRequest) validate() error { @@ -1807,7 +1773,7 @@ func (r *CreateRequest) validate() error { } func (r *CreateResponse) encode(e *sandboxwire.Encoder) { r.Entry.encode(e) } -func (r *CreateResponse) decode(d *decoder) { r.Entry.decode(d) } +func (r *CreateResponse) decode(d *sandboxwire.Decoder) { r.Entry.decode(d) } func (r *CreateResponse) validate() error { return r.Entry.validate() } func (r *ReadRequest) encode(e *sandboxwire.Encoder) { @@ -1815,10 +1781,10 @@ func (r *ReadRequest) encode(e *sandboxwire.Encoder) { e.U64(r.Offset) e.U32(r.Size) } -func (r *ReadRequest) decode(d *decoder) { - r.Handle = HandleID(d.u64()) - r.Offset = d.u64() - r.Size = d.u32() +func (r *ReadRequest) decode(d *sandboxwire.Decoder) { + r.Handle = HandleID(d.U64()) + r.Offset = d.U64() + r.Size = d.U32() } func (r *ReadRequest) validate() error { @@ -1829,7 +1795,7 @@ func (r *ReadRequest) validate() error { } func (r *ReadResponse) encode(e *sandboxwire.Encoder) { e.Bytes(r.Data) } -func (r *ReadResponse) decode(d *decoder) { r.Data = d.bytes() } +func (r *ReadResponse) decode(d *sandboxwire.Decoder) { r.Data = d.Bytes() } func (r *ReadResponse) validate() error { if len(r.Data) > sandboxwire.MaxChunk { return malformed("read of %d bytes", len(r.Data)) @@ -1843,11 +1809,11 @@ func (r *WriteRequest) encode(e *sandboxwire.Encoder) { e.Bool(r.Append) e.Bytes(r.Data) } -func (r *WriteRequest) decode(d *decoder) { - r.Handle = HandleID(d.u64()) - r.Offset = d.u64() - r.Append = d.bool() - r.Data = d.bytes() +func (r *WriteRequest) decode(d *sandboxwire.Decoder) { + r.Handle = HandleID(d.U64()) + r.Offset = d.U64() + r.Append = d.Bool() + r.Data = d.Bytes() } func (r *WriteRequest) validate() error { @@ -1861,7 +1827,10 @@ func (r *WriteResponse) encode(e *sandboxwire.Encoder) { e.U32(r.Written) encodeOptionalFailure(e, r.Failure) } -func (r *WriteResponse) decode(d *decoder) { r.Written = d.u32(); r.Failure = decodeOptionalFailure(d) } +func (r *WriteResponse) decode(d *sandboxwire.Decoder) { + r.Written = d.U32() + r.Failure = decodeOptionalFailure(d) +} func (r *WriteResponse) validate() error { if r.Written > sandboxwire.MaxChunk { return malformed("wrote %d bytes", r.Written) @@ -1879,35 +1848,44 @@ func (r *FlushRequest) encode(e *sandboxwire.Encoder) { e.U64(uint64(r.Handle)) e.U64(uint64(r.Owner)) } -func (r *FlushRequest) decode(d *decoder) { r.Handle = HandleID(d.u64()); r.Owner = LockOwner(d.u64()) } -func (r *FlushRequest) validate() error { return r.Handle.validate() } +func (r *FlushRequest) decode(d *sandboxwire.Decoder) { + r.Handle = HandleID(d.U64()) + r.Owner = LockOwner(d.U64()) +} +func (r *FlushRequest) validate() error { return r.Handle.validate() } func (*FlushResponse) encode(*sandboxwire.Encoder) {} -func (*FlushResponse) decode(*decoder) {} +func (*FlushResponse) decode(*sandboxwire.Decoder) {} func (*FlushResponse) validate() error { return nil } func (r *FsyncRequest) encode(e *sandboxwire.Encoder) { e.U64(uint64(r.Handle)); e.Bool(r.DataOnly) } -func (r *FsyncRequest) decode(d *decoder) { r.Handle = HandleID(d.u64()); r.DataOnly = d.bool() } -func (r *FsyncRequest) validate() error { return r.Handle.validate() } +func (r *FsyncRequest) decode(d *sandboxwire.Decoder) { + r.Handle = HandleID(d.U64()) + r.DataOnly = d.Bool() +} +func (r *FsyncRequest) validate() error { return r.Handle.validate() } func (*FsyncResponse) encode(*sandboxwire.Encoder) {} -func (*FsyncResponse) decode(*decoder) {} +func (*FsyncResponse) decode(*sandboxwire.Decoder) {} func (*FsyncResponse) validate() error { return nil } func (r *ReleaseRequest) encode(e *sandboxwire.Encoder) { e.U64(uint64(r.Handle)) } -func (r *ReleaseRequest) decode(d *decoder) { r.Handle = HandleID(d.u64()) } +func (r *ReleaseRequest) decode(d *sandboxwire.Decoder) { r.Handle = HandleID(d.U64()) } func (r *ReleaseRequest) validate() error { return r.Handle.validate() } func (*ReleaseResponse) encode(*sandboxwire.Encoder) {} -func (*ReleaseResponse) decode(*decoder) {} +func (*ReleaseResponse) decode(*sandboxwire.Decoder) {} func (*ReleaseResponse) validate() error { return nil } func (r *OpenDirRequest) encode(e *sandboxwire.Encoder) { e.U64(uint64(r.Handle)); r.Node.encode(e) } -func (r *OpenDirRequest) decode(d *decoder) { r.Handle = HandleID(d.u64()); r.Node.decode(d) } -func (r *OpenDirRequest) validate() error { return errors.Join(r.Handle.validate(), r.Node.validate()) } +func (r *OpenDirRequest) decode(d *sandboxwire.Decoder) { + r.Handle = HandleID(d.U64()) + r.Node.decode(d) +} +func (r *OpenDirRequest) validate() error { return errors.Join(r.Handle.validate(), r.Node.validate()) } func (*OpenDirResponse) encode(*sandboxwire.Encoder) {} -func (*OpenDirResponse) decode(*decoder) {} +func (*OpenDirResponse) decode(*sandboxwire.Decoder) {} func (*OpenDirResponse) validate() error { return nil } func (r *ReadDirRequest) encode(e *sandboxwire.Encoder) { @@ -1917,11 +1895,11 @@ func (r *ReadDirRequest) encode(e *sandboxwire.Encoder) { e.Bool(r.WithAttrs) } -func (r *ReadDirRequest) decode(d *decoder) { - r.Handle = HandleID(d.u64()) - r.Cookie = d.u64() - r.Limit = d.u32() - r.WithAttrs = d.bool() +func (r *ReadDirRequest) decode(d *sandboxwire.Decoder) { + r.Handle = HandleID(d.U64()) + r.Cookie = d.U64() + r.Limit = d.U32() + r.WithAttrs = d.Bool() } func (r *ReadDirRequest) validate() error { @@ -1947,20 +1925,20 @@ func (r *ReadDirResponse) encode(e *sandboxwire.Encoder) { e.Bool(r.End) } -func (r *ReadDirResponse) decode(d *decoder) { - r.Entries = make([]DirEntry, d.count(maxReadDirBytes/dirEntryWireSize)) +func (r *ReadDirResponse) decode(d *sandboxwire.Decoder) { + r.Entries = make([]DirEntry, d.Count(maxReadDirBytes/dirEntryWireSize)) for i := range r.Entries { x := &r.Entries[i] - x.Name = d.bytes() - x.Ino = d.u64() - x.Type = d.u32() - x.Cookie = d.u64() - if d.present() { + x.Name = d.Bytes() + x.Ino = d.U64() + x.Type = d.U32() + x.Cookie = d.U64() + if d.Present() { x.Entry = new(Entry) x.Entry.decode(d) } } - r.End = d.bool() + r.End = d.Bool() } func (r *ReadDirResponse) validate() error { @@ -1999,11 +1977,11 @@ func (r *ReadDirResponse) validate() error { } func (r *ReleaseDirRequest) encode(e *sandboxwire.Encoder) { e.U64(uint64(r.Handle)) } -func (r *ReleaseDirRequest) decode(d *decoder) { r.Handle = HandleID(d.u64()) } +func (r *ReleaseDirRequest) decode(d *sandboxwire.Decoder) { r.Handle = HandleID(d.U64()) } func (r *ReleaseDirRequest) validate() error { return r.Handle.validate() } func (*ReleaseDirResponse) encode(*sandboxwire.Encoder) {} -func (*ReleaseDirResponse) decode(*decoder) {} +func (*ReleaseDirResponse) decode(*sandboxwire.Decoder) {} func (*ReleaseDirResponse) validate() error { return nil } func (r *MkdirRequest) encode(e *sandboxwire.Encoder) { @@ -2011,29 +1989,33 @@ func (r *MkdirRequest) encode(e *sandboxwire.Encoder) { e.Bytes(r.Name) e.U32(r.Mode) } -func (r *MkdirRequest) decode(d *decoder) { r.Parent.decode(d); r.Name = d.bytes(); r.Mode = d.u32() } +func (r *MkdirRequest) decode(d *sandboxwire.Decoder) { + r.Parent.decode(d) + r.Name = d.Bytes() + r.Mode = d.U32() +} func (r *MkdirRequest) validate() error { return errors.Join(r.Parent.validate(), validName(r.Name), validPerm(r.Mode)) } func (r *MkdirResponse) encode(e *sandboxwire.Encoder) { r.Entry.encode(e) } -func (r *MkdirResponse) decode(d *decoder) { r.Entry.decode(d) } +func (r *MkdirResponse) decode(d *sandboxwire.Decoder) { r.Entry.decode(d) } func (r *MkdirResponse) validate() error { return r.Entry.validate() } func (r *UnlinkRequest) encode(e *sandboxwire.Encoder) { r.Parent.encode(e); e.Bytes(r.Name) } -func (r *UnlinkRequest) decode(d *decoder) { r.Parent.decode(d); r.Name = d.bytes() } +func (r *UnlinkRequest) decode(d *sandboxwire.Decoder) { r.Parent.decode(d); r.Name = d.Bytes() } func (r *UnlinkRequest) validate() error { return errors.Join(r.Parent.validate(), validName(r.Name)) } func (*UnlinkResponse) encode(*sandboxwire.Encoder) {} -func (*UnlinkResponse) decode(*decoder) {} +func (*UnlinkResponse) decode(*sandboxwire.Decoder) {} func (*UnlinkResponse) validate() error { return nil } func (r *RmdirRequest) encode(e *sandboxwire.Encoder) { r.Parent.encode(e); e.Bytes(r.Name) } -func (r *RmdirRequest) decode(d *decoder) { r.Parent.decode(d); r.Name = d.bytes() } +func (r *RmdirRequest) decode(d *sandboxwire.Decoder) { r.Parent.decode(d); r.Name = d.Bytes() } func (r *RmdirRequest) validate() error { return errors.Join(r.Parent.validate(), validName(r.Name)) } func (*RmdirResponse) encode(*sandboxwire.Encoder) {} -func (*RmdirResponse) decode(*decoder) {} +func (*RmdirResponse) decode(*sandboxwire.Decoder) {} func (*RmdirResponse) validate() error { return nil } func (r *RenameRequest) encode(e *sandboxwire.Encoder) { @@ -2044,12 +2026,12 @@ func (r *RenameRequest) encode(e *sandboxwire.Encoder) { e.Enum(uint16(r.Mode)) } -func (r *RenameRequest) decode(d *decoder) { +func (r *RenameRequest) decode(d *sandboxwire.Decoder) { r.Parent.decode(d) - r.Name = d.bytes() + r.Name = d.Bytes() r.NewParent.decode(d) - r.NewName = d.bytes() - r.Mode = RenameMode(d.u16()) + r.NewName = d.Bytes() + r.Mode = RenameMode(d.U16()) } func (r *RenameRequest) validate() error { @@ -2060,7 +2042,7 @@ func (r *RenameRequest) validate() error { } func (*RenameResponse) encode(*sandboxwire.Encoder) {} -func (*RenameResponse) decode(*decoder) {} +func (*RenameResponse) decode(*sandboxwire.Decoder) {} func (*RenameResponse) validate() error { return nil } func (r *LinkRequest) encode(e *sandboxwire.Encoder) { @@ -2068,17 +2050,17 @@ func (r *LinkRequest) encode(e *sandboxwire.Encoder) { r.NewParent.encode(e) e.Bytes(r.NewName) } -func (r *LinkRequest) decode(d *decoder) { +func (r *LinkRequest) decode(d *sandboxwire.Decoder) { r.Node.decode(d) r.NewParent.decode(d) - r.NewName = d.bytes() + r.NewName = d.Bytes() } func (r *LinkRequest) validate() error { return errors.Join(r.Node.validate(), r.NewParent.validate(), validName(r.NewName)) } func (r *LinkResponse) encode(e *sandboxwire.Encoder) { r.Entry.encode(e) } -func (r *LinkResponse) decode(d *decoder) { r.Entry.decode(d) } +func (r *LinkResponse) decode(d *sandboxwire.Decoder) { r.Entry.decode(d) } func (r *LinkResponse) validate() error { return r.Entry.validate() } func (r *SymlinkRequest) encode(e *sandboxwire.Encoder) { @@ -2086,29 +2068,29 @@ func (r *SymlinkRequest) encode(e *sandboxwire.Encoder) { e.Bytes(r.Name) e.Bytes(r.Target) } -func (r *SymlinkRequest) decode(d *decoder) { +func (r *SymlinkRequest) decode(d *sandboxwire.Decoder) { r.Parent.decode(d) - r.Name = d.bytes() - r.Target = d.bytes() + r.Name = d.Bytes() + r.Target = d.Bytes() } func (r *SymlinkRequest) validate() error { return errors.Join(r.Parent.validate(), validName(r.Name), validTarget(r.Target)) } func (r *SymlinkResponse) encode(e *sandboxwire.Encoder) { r.Entry.encode(e) } -func (r *SymlinkResponse) decode(d *decoder) { r.Entry.decode(d) } +func (r *SymlinkResponse) decode(d *sandboxwire.Decoder) { r.Entry.decode(d) } func (r *SymlinkResponse) validate() error { return r.Entry.validate() } func (r *ReadlinkRequest) encode(e *sandboxwire.Encoder) { r.Node.encode(e) } -func (r *ReadlinkRequest) decode(d *decoder) { r.Node.decode(d) } +func (r *ReadlinkRequest) decode(d *sandboxwire.Decoder) { r.Node.decode(d) } func (r *ReadlinkRequest) validate() error { return r.Node.validate() } func (r *ReadlinkResponse) encode(e *sandboxwire.Encoder) { e.Bytes(r.Target) } -func (r *ReadlinkResponse) decode(d *decoder) { r.Target = d.bytes() } +func (r *ReadlinkResponse) decode(d *sandboxwire.Decoder) { r.Target = d.Bytes() } func (r *ReadlinkResponse) validate() error { return validTarget(r.Target) } func (r *StatFSRequest) encode(e *sandboxwire.Encoder) { r.Node.encode(e) } -func (r *StatFSRequest) decode(d *decoder) { r.Node.decode(d) } +func (r *StatFSRequest) decode(d *sandboxwire.Decoder) { r.Node.decode(d) } func (r *StatFSRequest) validate() error { return r.Node.validate() } func (r *StatFSResponse) encode(e *sandboxwire.Encoder) { @@ -2120,13 +2102,13 @@ func (r *StatFSResponse) encode(e *sandboxwire.Encoder) { e.U32(r.NameMax) } -func (r *StatFSResponse) decode(d *decoder) { +func (r *StatFSResponse) decode(d *sandboxwire.Decoder) { for _, v := range []*uint64{&r.Blocks, &r.BlocksFree, &r.BlocksAvailable, &r.Files, &r.FilesFree} { - *v = d.u64() + *v = d.U64() } - r.BlockSize = d.u32() - r.FragmentSize = d.u32() - r.NameMax = d.u32() + r.BlockSize = d.U32() + r.FragmentSize = d.U32() + r.NameMax = d.U32() } func (*StatFSResponse) validate() error { return nil } @@ -2139,11 +2121,11 @@ func (r *ForgetRequest) encode(e *sandboxwire.Encoder) { } } -func (r *ForgetRequest) decode(d *decoder) { - r.Entries = make([]ForgetEntry, d.count(maxForgetEntries)) +func (r *ForgetRequest) decode(d *sandboxwire.Decoder) { + r.Entries = make([]ForgetEntry, d.Count(maxForgetEntries)) for i := range r.Entries { r.Entries[i].Node.decode(d) - r.Entries[i].Count = d.u64() + r.Entries[i].Count = d.U64() } } @@ -2165,7 +2147,7 @@ func (r *ForgetRequest) validate() error { } func (*ForgetResponse) encode(*sandboxwire.Encoder) {} -func (*ForgetResponse) decode(*decoder) {} +func (*ForgetResponse) decode(*sandboxwire.Decoder) {} func (*ForgetResponse) validate() error { return nil } func (r *GetLockRequest) encode(e *sandboxwire.Encoder) { @@ -2174,9 +2156,9 @@ func (r *GetLockRequest) encode(e *sandboxwire.Encoder) { r.Lock.encode(e) } -func (r *GetLockRequest) decode(d *decoder) { - r.Handle = HandleID(d.u64()) - r.Owner = LockOwner(d.u64()) +func (r *GetLockRequest) decode(d *sandboxwire.Decoder) { + r.Handle = HandleID(d.U64()) + r.Owner = LockOwner(d.U64()) r.Lock.decode(d) } @@ -2194,8 +2176,8 @@ func (r *GetLockResponse) encode(e *sandboxwire.Encoder) { } } -func (r *GetLockResponse) decode(d *decoder) { - if d.present() { +func (r *GetLockResponse) decode(d *sandboxwire.Decoder) { + if d.Present() { r.Conflict = new(Lock) r.Conflict.decode(d) } @@ -2219,12 +2201,12 @@ func (r *SetLockRequest) encode(e *sandboxwire.Encoder) { e.Bool(r.Wait) } -func (r *SetLockRequest) decode(d *decoder) { - r.Handle = HandleID(d.u64()) - r.Kind = LockKind(d.u16()) - r.Owner = LockOwner(d.u64()) +func (r *SetLockRequest) decode(d *sandboxwire.Decoder) { + r.Handle = HandleID(d.U64()) + r.Kind = LockKind(d.U16()) + r.Owner = LockOwner(d.U64()) r.Lock.decode(d) - r.Wait = d.bool() + r.Wait = d.Bool() } func (r *SetLockRequest) validate() error { @@ -2241,11 +2223,11 @@ func (r *SetLockRequest) validate() error { } func (*SetLockResponse) encode(*sandboxwire.Encoder) {} -func (*SetLockResponse) decode(*decoder) {} +func (*SetLockResponse) decode(*sandboxwire.Decoder) {} func (*SetLockResponse) validate() error { return nil } func (r *CancelRequestRequest) encode(e *sandboxwire.Encoder) { e.U64(r.Target) } -func (r *CancelRequestRequest) decode(d *decoder) { r.Target = d.u64() } +func (r *CancelRequestRequest) decode(d *sandboxwire.Decoder) { r.Target = d.U64() } func (r *CancelRequestRequest) validate() error { if !sandboxwire.ValidRequestID(r.Target) { return malformed("cancel of request 0") @@ -2254,5 +2236,5 @@ func (r *CancelRequestRequest) validate() error { } func (*CancelRequestResponse) encode(*sandboxwire.Encoder) {} -func (*CancelRequestResponse) decode(*decoder) {} +func (*CancelRequestResponse) decode(*sandboxwire.Decoder) {} func (*CancelRequestResponse) validate() error { return nil } diff --git a/internal/sandboxfs/protocol_test.go b/internal/sandboxfs/protocol_test.go index d984ca073..7c5c473c4 100644 --- a/internal/sandboxfs/protocol_test.go +++ b/internal/sandboxfs/protocol_test.go @@ -2,37 +2,18 @@ package sandboxfs import ( "bytes" - "encoding/hex" "errors" "math" "os" - "path/filepath" "reflect" "strings" "testing" "github.com/MiniMax-AI/OpenAgentCore/internal/sandboxlink" "github.com/MiniMax-AI/OpenAgentCore/internal/sandboxwire" + "github.com/MiniMax-AI/OpenAgentCore/internal/sandboxwire/sandboxwiretest" ) -func readHexFixture(t testing.TB, name string) []byte { - t.Helper() - raw, err := os.ReadFile(filepath.Join("testdata", name)) - if err != nil { - t.Fatal(err) - } - var digits strings.Builder - for _, line := range strings.Split(string(raw), "\n") { - line, _, _ = strings.Cut(line, "#") - digits.WriteString(strings.Join(strings.Fields(line), "")) - } - b, err := hex.DecodeString(digits.String()) - if err != nil { - t.Fatal(err) - } - return b -} - var ( testNode = NodeRef{ID: 1, Generation: 7} testNode2 = NodeRef{ID: 2, Generation: 9} @@ -80,7 +61,7 @@ func TestGoldenFixtures(t *testing.T) { {file: "write_request.hex", op: OpWrite, id: 8, req: &WriteRequest{Handle: 9, Append: true, Data: []byte("log\n")}}, } { t.Run(tc.file, func(t *testing.T) { - want := readHexFixture(t, tc.file) + want := sandboxwiretest.ReadHex(t, tc.file) typ := uint16(tc.op) var payload []byte var err error @@ -245,7 +226,7 @@ func FuzzDecode(f *testing.F) { entries, _ := os.ReadDir("testdata") for _, e := range entries { if strings.HasSuffix(e.Name(), ".hex") { - f.Add(readHexFixture(f, e.Name())) + f.Add(sandboxwiretest.ReadHex(f, e.Name())) } } for i, s := range samples() { diff --git a/internal/sandboxlink/protocol.go b/internal/sandboxlink/protocol.go index 36dbfa32e..a069c9544 100644 --- a/internal/sandboxlink/protocol.go +++ b/internal/sandboxlink/protocol.go @@ -549,29 +549,26 @@ func Decode(f sandboxwire.Frame) (Message, error) { if (kind == sandboxwire.KindEvent) == sandboxwire.ValidRequestID(f.RequestID) { return nil, fmt.Errorf("%w: request ID %d for message type %#04x", sandboxwire.ErrMalformed, f.RequestID, f.Type) } - r := &reader{d: sandboxwire.NewDecoder(f.Payload)} + d := sandboxwire.NewDecoder(f.Payload) var m Message switch kind { case sandboxwire.KindRequest: - m = decodeRequest(r, Op(f.Type)) + m = decodeRequest(d, Op(f.Type)) case sandboxwire.KindResponse: op := Op(f.Type &^ sandboxwire.ResponseType(0)) - if r.enum(func(v uint16) bool { return v == resultSuccess || v == resultFailure }) == resultFailure { - m = Failure{Op: op, Code: Code(r.enum(func(v uint16) bool { return Code(v).Valid() })), Effect: r.effect()} + if d.Enum(func(v uint16) bool { return v == resultSuccess || v == resultFailure }) == resultFailure { + m = Failure{Op: op, Code: Code(d.Enum(func(v uint16) bool { return Code(v).Valid() })), Effect: d.Effect()} } else { - m = decodeSuccess(r, op) + m = decodeSuccess(d, op) } case sandboxwire.KindEvent: - m = AttachmentClosed{AttachmentID: r.id(), Reason: CloseReason(r.enum(func(v uint16) bool { return CloseReason(v).Valid() }))} + m = AttachmentClosed{AttachmentID: d.ID(), Reason: CloseReason(d.Enum(func(v uint16) bool { return CloseReason(v).Valid() }))} } - if r.otherVersion { + if m == nil { return nil, Fail(VersionMismatch) } - if r.err == nil { - r.err = r.d.Finish() - } - if r.err != nil { - return nil, r.err + if err := d.Finish(); err != nil { + return nil, err } if err := m.validate(); err != nil { return nil, err @@ -645,68 +642,68 @@ func reply(op Op, m Message) (Message, error) { return m, nil } -func decodeRequest(r *reader, op Op) Message { +// decodeRequest returns nil for a Hello of another version. +func decodeRequest(d *sandboxwire.Decoder, op Op) Message { switch op { case OpHello: - if r.u16() != Version && r.err == nil { + if d.U16() != Version && d.Err() == nil { // Nothing after the version is readable in another version. - r.otherVersion = true return nil } - if Role(r.enum(func(v uint16) bool { return Role(v) == RoleServe || Role(v) == RoleAttach })) == RoleServe { - h := ServeHello{Version: Version, Credential: r.bytes(), Resource: r.resource(), ServerInstanceID: r.id()} - n := r.count(uint32(ServiceNetwork)) + if Role(d.Enum(func(v uint16) bool { return Role(v) == RoleServe || Role(v) == RoleAttach })) == RoleServe { + h := ServeHello{Version: Version, Credential: d.Bytes(), Resource: readResource(d), ServerInstanceID: d.ID()} + n := d.Count(uint32(ServiceNetwork)) for range n { - h.Services = append(h.Services, ServiceVersion{Service: r.service(), Version: r.u16()}) + h.Services = append(h.Services, ServiceVersion{Service: readService(d), Version: d.U16()}) } return h } - return AttachHello{Version: Version, RuntimeID: r.id(), Credential: r.bytes()} + return AttachHello{Version: Version, RuntimeID: d.ID(), Credential: d.Bytes()} case OpOpen: - o := Open{Service: r.service(), Version: r.u16(), Resource: r.resource()} - if r.present() { - o.ExpectedServerInstanceID = r.id() + o := Open{Service: readService(d), Version: d.U16(), Resource: readResource(d)} + if d.Present() { + o.ExpectedServerInstanceID = d.ID() } - o.AttachmentID, o.SessionID, o.AssignmentID = r.id(), r.id(), r.id() - o.AssignmentEpoch, o.AttachGrant = r.u64(), r.bytes() + o.AttachmentID, o.SessionID, o.AssignmentID = d.ID(), d.ID(), d.ID() + o.AssignmentEpoch, o.AttachGrant = d.U64(), d.Bytes() return o case OpBind: - b := Bind{AttachmentID: r.id(), Service: r.service(), Version: r.u16(), SessionID: r.id(), AssignmentID: r.id(), - AssignmentEpoch: r.u64(), LeaseExpiresAt: r.time(), ExpectedServerInstanceID: r.id()} - if r.present() != (b.Service == ServiceFile) && r.err == nil { - r.err = invalid("exports presence does not match service %s", b.Service) + b := Bind{AttachmentID: d.ID(), Service: readService(d), Version: d.U16(), SessionID: d.ID(), AssignmentID: d.ID(), + AssignmentEpoch: d.U64(), LeaseExpiresAt: readTime(d), ExpectedServerInstanceID: d.ID()} + if d.Present() != (b.Service == ServiceFile) { + d.Fail(invalid("exports presence does not match service %s", b.Service)) } if b.Service == ServiceFile { - for range r.count(MaxExports) { - b.Exports = append(b.Exports, ExportGrant{ID: ExportID(r.bytes()), ReadOnly: r.boolean()}) + for range d.Count(MaxExports) { + b.Exports = append(b.Exports, ExportGrant{ID: ExportID(d.Bytes()), ReadOnly: d.Bool()}) } } - if r.present() != (b.Service == ServiceNetwork) && r.err == nil { - r.err = invalid("egress presence does not match service %s", b.Service) + if d.Present() != (b.Service == ServiceNetwork) { + d.Fail(invalid("egress presence does not match service %s", b.Service)) } if b.Service == ServiceNetwork { - for range r.count(MaxEgressRules) { - b.Egress = append(b.Egress, r.egressRule()) + for range d.Count(MaxEgressRules) { + b.Egress = append(b.Egress, readEgressRule(d)) } } return b case OpRenewAttachment: - return RenewAttachment{AttachmentID: r.id(), AttachGrant: r.bytes()} + return RenewAttachment{AttachmentID: d.ID(), AttachGrant: d.Bytes()} default: // OpCloseAttachment; Classify admits no other request tag. - return CloseAttachment{AttachmentID: r.id()} + return CloseAttachment{AttachmentID: d.ID()} } } -func decodeSuccess(r *reader, op Op) Message { +func decodeSuccess(d *sandboxwire.Decoder, op Op) Message { switch op { case OpHello: return HelloAccepted{} case OpOpen: - return Opened{AttachmentID: r.id(), ServerInstanceID: r.id(), LeaseExpiresAt: r.time()} + return Opened{AttachmentID: d.ID(), ServerInstanceID: d.ID(), LeaseExpiresAt: readTime(d)} case OpBind: return Bound{} case OpRenewAttachment: - return AttachmentRenewed{AttachmentID: r.id(), LeaseExpiresAt: r.time()} + return AttachmentRenewed{AttachmentID: d.ID(), LeaseExpiresAt: readTime(d)} default: // OpCloseAttachment return CloseAccepted{} } @@ -1031,62 +1028,29 @@ func (f Failure) validate() error { return nil } -// reader decodes fields in order, keeping the first error. -type reader struct { - d *sandboxwire.Decoder - err error - otherVersion bool // a Hello of another version -} - -func read[T any](r *reader, f func() (T, error)) T { - var v T - if r.err == nil { - v, r.err = f() - } - return v -} - -func (r *reader) u8() uint8 { return read(r, r.d.U8) } -func (r *reader) u16() uint16 { return read(r, r.d.U16) } -func (r *reader) u32() uint32 { return read(r, r.d.U32) } -func (r *reader) u64() uint64 { return read(r, r.d.U64) } -func (r *reader) id() sandboxwire.ID { return read(r, r.d.ID) } -func (r *reader) bytes() []byte { return read(r, r.d.Bytes) } -func (r *reader) present() bool { return read(r, r.d.Present) } -func (r *reader) boolean() bool { return read(r, r.d.Bool) } -func (r *reader) effect() sandboxwire.Effect { return read(r, r.d.Effect) } - -func (r *reader) enum(valid func(uint16) bool) uint16 { - return read(r, func() (uint16, error) { return r.d.Enum(valid) }) -} - -func (r *reader) count(max uint32) int { - return read(r, func() (int, error) { return r.d.Count(max) }) -} - -func (r *reader) service() Service { - return Service(r.enum(func(v uint16) bool { return Service(v).Valid() })) +func readService(d *sandboxwire.Decoder) Service { + return Service(d.Enum(func(v uint16) bool { return Service(v).Valid() })) } -// egressRule reads a rule; checkEgress validates it after decoding. -func (r *reader) egressRule() EgressRule { +// readEgressRule reads a rule; checkEgress validates it after decoding. +func readEgressRule(d *sandboxwire.Decoder) EgressRule { var a netip.Addr - if AddressFamily(r.enum(func(v uint16) bool { return AddressFamily(v) == FamilyIPv4 || AddressFamily(v) == FamilyIPv6 })) == FamilyIPv4 { + if AddressFamily(d.Enum(func(v uint16) bool { return AddressFamily(v) == FamilyIPv4 || AddressFamily(v) == FamilyIPv6 })) == FamilyIPv4 { var v [4]byte - binary.BigEndian.PutUint32(v[:], r.u32()) + binary.BigEndian.PutUint32(v[:], d.U32()) a = netip.AddrFrom4(v) } else { var v [16]byte - binary.BigEndian.PutUint64(v[:8], r.u64()) - binary.BigEndian.PutUint64(v[8:], r.u64()) + binary.BigEndian.PutUint64(v[:8], d.U64()) + binary.BigEndian.PutUint64(v[8:], d.U64()) a = netip.AddrFrom16(v) } - return EgressRule{Prefix: netip.PrefixFrom(a, int(r.u8())), PortFirst: r.u16(), PortLast: r.u16()} + return EgressRule{Prefix: netip.PrefixFrom(a, int(d.U8())), PortFirst: d.U16(), PortLast: d.U16()} } -func (r *reader) time() time.Time { return time.UnixMilli(read(r, r.d.I64)).UTC() } +func readTime(d *sandboxwire.Decoder) time.Time { return time.UnixMilli(d.I64()).UTC() } -func (r *reader) resource() ResourceRef { - return ResourceRef{TenantID: r.id(), EnvironmentID: r.id(), - Kind: ResourceKind(r.enum(func(v uint16) bool { return ResourceKind(v).Valid() })), ID: r.id(), Generation: r.u64()} +func readResource(d *sandboxwire.Decoder) ResourceRef { + return ResourceRef{TenantID: d.ID(), EnvironmentID: d.ID(), + Kind: ResourceKind(d.Enum(func(v uint16) bool { return ResourceKind(v).Valid() })), ID: d.ID(), Generation: d.U64()} } diff --git a/internal/sandboxlink/protocol_test.go b/internal/sandboxlink/protocol_test.go index 02bf5e411..1ea63517d 100644 --- a/internal/sandboxlink/protocol_test.go +++ b/internal/sandboxlink/protocol_test.go @@ -2,17 +2,15 @@ package sandboxlink import ( "bytes" - "encoding/hex" "errors" "io" "net/netip" - "os" "reflect" - "strings" "testing" "time" "github.com/MiniMax-AI/OpenAgentCore/internal/sandboxwire" + "github.com/MiniMax-AI/OpenAgentCore/internal/sandboxwire/sandboxwiretest" ) func testID(b byte) sandboxwire.ID { @@ -56,26 +54,8 @@ var goldenFrames = []golden{ {0, AttachmentClosed{AttachmentID: testID(0x07), Reason: CloseLeaseExpired}}, } -func readHexFixture(t testing.TB, name string) []byte { - t.Helper() - raw, err := os.ReadFile(name) - if err != nil { - t.Fatal(err) - } - var digits strings.Builder - for _, line := range strings.Split(string(raw), "\n") { - line, _, _ = strings.Cut(line, "#") - digits.WriteString(strings.Join(strings.Fields(line), "")) - } - b, err := hex.DecodeString(digits.String()) - if err != nil { - t.Fatal(err) - } - return b -} - func TestGolden(t *testing.T) { - want := readHexFixture(t, "testdata/link_v1.hex") + want := sandboxwiretest.ReadHex(t, "link_v1.hex") var buf bytes.Buffer for _, g := range goldenFrames { if err := WriteMessage(&buf, g.requestID, g.m); err != nil { @@ -197,7 +177,7 @@ func TestCheckRelayURL(t *testing.T) { // FuzzDecode decodes arbitrary frames. Whatever decodes re-encodes to the same // bytes; every failure is ErrMalformed or, for a Hello, VersionMismatch. func FuzzDecode(f *testing.F) { - golden := readHexFixture(f, "testdata/link_v1.hex") + golden := sandboxwiretest.ReadHex(f, "link_v1.hex") for r := bytes.NewReader(golden); r.Len() > 0; { start := len(golden) - r.Len() if _, err := sandboxwire.ReadFrame(r, sandboxwire.MaxPayload); err != nil { diff --git a/internal/sandboxnet/protocol.go b/internal/sandboxnet/protocol.go index 0a88647d8..e9b5c693d 100644 --- a/internal/sandboxnet/protocol.go +++ b/internal/sandboxnet/protocol.go @@ -219,24 +219,21 @@ func Decode(f sandboxwire.Frame) (Message, error) { if !sandboxwire.ValidRequestID(f.RequestID) { return nil, invalid("request ID %d", f.RequestID) } - r := &reader{d: sandboxwire.NewDecoder(f.Payload)} + d := sandboxwire.NewDecoder(f.Payload) var m Message if kind == sandboxwire.KindRequest { - m = ConnectRequest{Network: Network(r.enum(func(v uint16) bool { return Network(v).Valid() })), - Host: string(r.bytes()), Port: r.u16(), TimeoutMillis: r.u32()} + m = ConnectRequest{Network: Network(d.Enum(func(v uint16) bool { return Network(v).Valid() })), + Host: string(d.Bytes()), Port: d.U16(), TimeoutMillis: d.U32()} } else { - resp := ConnectResponse{Result: Result(r.enum(func(v uint16) bool { return Result(v).Valid() }))} + resp := ConnectResponse{Result: Result(d.Enum(func(v uint16) bool { return Result(v).Valid() }))} if resp.Result == ResultFailed { - resp.Code = Code(r.enum(func(v uint16) bool { return Code(v).Valid() })) - resp.Effect = r.effect() + resp.Code = Code(d.Enum(func(v uint16) bool { return Code(v).Valid() })) + resp.Effect = d.Effect() } m = resp } - if r.err == nil { - r.err = r.d.Finish() - } - if r.err != nil { - return nil, r.err + if err := d.Finish(); err != nil { + return nil, err } if err := m.validate(); err != nil { return nil, err @@ -378,26 +375,3 @@ func permitsPort(egress []sandboxlink.EgressRule, port uint16) bool { func invalid(format string, args ...any) error { return fmt.Errorf("%w: "+format, append([]any{sandboxwire.ErrMalformed}, args...)...) } - -// reader decodes fields in order, keeping the first error. -type reader struct { - d *sandboxwire.Decoder - err error -} - -func read[T any](r *reader, f func() (T, error)) T { - var v T - if r.err == nil { - v, r.err = f() - } - return v -} - -func (r *reader) u16() uint16 { return read(r, r.d.U16) } -func (r *reader) u32() uint32 { return read(r, r.d.U32) } -func (r *reader) bytes() []byte { return read(r, r.d.Bytes) } -func (r *reader) effect() sandboxwire.Effect { return read(r, r.d.Effect) } - -func (r *reader) enum(valid func(uint16) bool) uint16 { - return read(r, func() (uint16, error) { return r.d.Enum(valid) }) -} diff --git a/internal/sandboxnet/protocol_test.go b/internal/sandboxnet/protocol_test.go index 1ed814a6e..a97a907cd 100644 --- a/internal/sandboxnet/protocol_test.go +++ b/internal/sandboxnet/protocol_test.go @@ -2,17 +2,16 @@ package sandboxnet import ( "bytes" - "encoding/hex" "errors" "io" "net/netip" - "os" "reflect" "strings" "testing" "github.com/MiniMax-AI/OpenAgentCore/internal/sandboxlink" "github.com/MiniMax-AI/OpenAgentCore/internal/sandboxwire" + "github.com/MiniMax-AI/OpenAgentCore/internal/sandboxwire/sandboxwiretest" ) var goldenFrames = []struct { @@ -24,26 +23,8 @@ var goldenFrames = []struct { {1, ConnectResponse{Result: ResultFailed, Code: CodeDenied, Effect: sandboxwire.EffectNone}}, } -func readHexFixture(t testing.TB, name string) []byte { - t.Helper() - raw, err := os.ReadFile(name) - if err != nil { - t.Fatal(err) - } - var digits strings.Builder - for _, line := range strings.Split(string(raw), "\n") { - line, _, _ = strings.Cut(line, "#") - digits.WriteString(strings.Join(strings.Fields(line), "")) - } - b, err := hex.DecodeString(digits.String()) - if err != nil { - t.Fatal(err) - } - return b -} - func TestGolden(t *testing.T) { - want := readHexFixture(t, "testdata/network_v1.hex") + want := sandboxwiretest.ReadHex(t, "network_v1.hex") var buf bytes.Buffer for _, g := range goldenFrames { if err := WriteMessage(&buf, g.requestID, g.m); err != nil { @@ -154,7 +135,7 @@ func TestEgress(t *testing.T) { } func FuzzDecode(f *testing.F) { - golden := readHexFixture(f, "testdata/network_v1.hex") + golden := sandboxwiretest.ReadHex(f, "network_v1.hex") for r := bytes.NewReader(golden); r.Len() > 0; { start := len(golden) - r.Len() if _, err := sandboxwire.ReadFrame(r, MaxMessageBytes); err != nil { diff --git a/internal/sandboxprocess/protocol.go b/internal/sandboxprocess/protocol.go index 2274138fa..a80159c20 100644 --- a/internal/sandboxprocess/protocol.go +++ b/internal/sandboxprocess/protocol.go @@ -720,7 +720,7 @@ func Decode(t uint16, payload []byte) (Message, error) { if err != nil { return nil, err } - r := &reader{d: sandboxwire.NewDecoder(payload)} + r := sandboxwire.NewDecoder(payload) var m Message switch kind { case sandboxwire.KindRequest: @@ -735,14 +735,11 @@ func Decode(t uint16, payload []byte) (Message, error) { case sandboxwire.KindEvent: m = readEvent(r, t) } - if r.err == nil { - r.err = r.d.Finish() - } - if r.err == nil { - r.err = validate(m) + if err := r.Finish(); err != nil { + return nil, err } - if r.err != nil { - return nil, r.err + if err := validate(m); err != nil { + return nil, err } return m, nil } @@ -950,51 +947,24 @@ func (m ObservationLostEvent) encode(e *sandboxwire.Encoder) { // Decoding. -// reader keeps the first error, after which every read returns a zero value. -type reader struct { - d *sandboxwire.Decoder - err error -} - -func read[T any](r *reader, f func() (T, error)) T { - var v T - if r.err == nil { - v, r.err = f() - } - return v -} - -func (r *reader) u8() uint8 { return read(r, r.d.U8) } -func (r *reader) u16() uint16 { return read(r, r.d.U16) } -func (r *reader) u32() uint32 { return read(r, r.d.U32) } -func (r *reader) u64() uint64 { return read(r, r.d.U64) } -func (r *reader) boolean() bool { return read(r, r.d.Bool) } -func (r *reader) present() bool { return read(r, r.d.Present) } -func (r *reader) bytes() []byte { return read(r, r.d.Bytes) } -func (r *reader) id() sandboxwire.ID { return read(r, r.d.ID) } -func (r *reader) effect() sandboxwire.Effect { return read(r, r.d.Effect) } -func (r *reader) count(max uint32) int { - return read(r, func() (int, error) { return r.d.Count(max) }) -} - func enum[T interface { ~uint16 Valid() bool -}](r *reader) T { - return T(read(r, func() (uint16, error) { return r.d.Enum(func(v uint16) bool { return T(v).Valid() }) })) +}](r *sandboxwire.Decoder) T { + return T(r.Enum(func(v uint16) bool { return T(v).Valid() })) } // enums reads a list of distinct enum values. func enums[T interface { ~uint16 Valid() bool -}](r *reader) []T { - n := r.count(maxListEntries) +}](r *sandboxwire.Decoder) []T { + n := r.Count(maxListEntries) list := make([]T, 0, n) for range n { v := enum[T](r) - if r.err == nil && slices.Contains(list, v) { - r.err = malformed("duplicate list entry %d", v) + if slices.Contains(list, v) { + r.Fail(malformed("duplicate list entry %d", v)) } list = append(list, v) } @@ -1005,37 +975,37 @@ func malformed(format string, args ...any) error { return fmt.Errorf("%w: "+format, append([]any{sandboxwire.ErrMalformed}, args...)...) } -func readRef(r *reader) OperationRef { - return OperationRef{ServerInstanceID: r.id(), OperationID: r.id()} +func readRef(r *sandboxwire.Decoder) OperationRef { + return OperationRef{ServerInstanceID: r.ID(), OperationID: r.ID()} } -func readFailure(r *reader) Failure { - return Failure{Code: enum[ErrorCode](r), Effect: r.effect(), Message: string(r.bytes())} +func readFailure(r *sandboxwire.Decoder) Failure { + return Failure{Code: enum[ErrorCode](r), Effect: r.Effect(), Message: string(r.Bytes())} } -func readWindowSize(r *reader) WindowSize { - return WindowSize{Rows: r.u16(), Cols: r.u16(), XPixels: r.u16(), YPixels: r.u16()} +func readWindowSize(r *sandboxwire.Decoder) WindowSize { + return WindowSize{Rows: r.U16(), Cols: r.U16(), XPixels: r.U16(), YPixels: r.U16()} } -func readSpec(r *reader) ProcessSpec { +func readSpec(r *sandboxwire.Decoder) ProcessSpec { var s ProcessSpec - s.Executable = r.bytes() - s.Argv = make([][]byte, r.count(maxSpecEntries)) + s.Executable = r.Bytes() + s.Argv = make([][]byte, r.Count(maxSpecEntries)) for i := range s.Argv { - s.Argv[i] = r.bytes() + s.Argv[i] = r.Bytes() } - s.Env = make([]EnvVar, r.count(maxSpecEntries)) + s.Env = make([]EnvVar, r.Count(maxSpecEntries)) for i := range s.Env { - s.Env[i] = EnvVar{Name: r.bytes(), Value: r.bytes()} + s.Env[i] = EnvVar{Name: r.Bytes(), Value: r.Bytes()} } - s.Cwd = r.bytes() - s.Umask = r.u32() + s.Cwd = r.Bytes() + s.Umask = r.U32() s.IOMode = enum[IOMode](r) - if r.present() { - p := &PTYSpec{Size: readWindowSize(r), Term: r.bytes()} - p.Modes = make([]PTYModeValue, r.count(maxListEntries)) + if r.Present() { + p := &PTYSpec{Size: readWindowSize(r), Term: r.Bytes()} + p.Modes = make([]PTYModeValue, r.Count(maxListEntries)) for i := range p.Modes { - p.Modes[i] = PTYModeValue{Mode: enum[PTYMode](r), Value: r.u32()} + p.Modes[i] = PTYModeValue{Mode: enum[PTYMode](r), Value: r.U32()} } s.PTY = p } @@ -1043,7 +1013,7 @@ func readSpec(r *reader) ProcessSpec { return s } -func readCapabilities(r *reader) Capabilities { +func readCapabilities(r *sandboxwire.Decoder) Capabilities { return Capabilities{ Platform: enum[Platform](r), Scopes: enums[Scope](r), @@ -1051,55 +1021,55 @@ func readCapabilities(r *reader) Capabilities { Signals: enums[Signal](r), SignalTargets: enums[SignalTarget](r), PTYModes: enums[PTYMode](r), - MaxStartBytes: r.u32(), - MaxDataBytes: r.u32(), - MaxActiveOperations: r.u32(), - MaxOperationRecords: r.u32(), - MaxReplayBytesPerOperation: r.u32(), - OwnerLossGraceMillis: r.u32(), - CancelGraceLimitMillis: r.u32(), + MaxStartBytes: r.U32(), + MaxDataBytes: r.U32(), + MaxActiveOperations: r.U32(), + MaxOperationRecords: r.U32(), + MaxReplayBytesPerOperation: r.U32(), + OwnerLossGraceMillis: r.U32(), + CancelGraceLimitMillis: r.U32(), } } -func readExitStatus(r *reader) ExitStatus { +func readExitStatus(r *sandboxwire.Decoder) ExitStatus { s := ExitStatus{Kind: enum[ExitKind](r)} if s.Kind == ExitCode { - s.Code = r.u8() + s.Code = r.U8() } else { s.Signal = enum[Signal](r) - s.CoreDumped = r.boolean() + s.CoreDumped = r.Bool() } return s } -func readStatus(r *reader) OperationStatus { +func readStatus(r *sandboxwire.Decoder) OperationStatus { s := OperationStatus{State: enum[OperationState](r)} - if r.present() { + if r.Present() { exit := readExitStatus(r) s.Exit = &exit } - if r.present() { + if r.Present() { f := readFailure(r) s.StartFailure = &f } - s.StdinOffset = r.u64() - s.StdinClosed = r.boolean() - if r.present() { + s.StdinOffset = r.U64() + s.StdinClosed = r.Bool() + if r.Present() { d := enum[OutputDisposition](r) s.Output = &d } s.Scope = enum[ScopeState](r) - s.Released = r.boolean() - s.FirstRetained = r.u64() - s.LastSequence = r.u64() + s.Released = r.Bool() + s.FirstRetained = r.U64() + s.LastSequence = r.U64() return s } -func readHeader(r *reader) EventHeader { - return EventHeader{OperationID: r.id(), Sequence: r.u64()} +func readHeader(r *sandboxwire.Decoder) EventHeader { + return EventHeader{OperationID: r.ID(), Sequence: r.U64()} } -func readRequest(r *reader, t uint16) Message { +func readRequest(r *sandboxwire.Decoder, t uint16) Message { if t == OpDescribe { return DescribeRequest{} } @@ -1108,13 +1078,13 @@ func readRequest(r *reader, t uint16) Message { case OpStart: return StartRequest{ref, readSpec(r)} case OpAttach: - return AttachRequest{ref, r.u64()} + return AttachRequest{ref, r.U64()} case OpInspect: return InspectRequest{ref} case OpWriteStdin: - return WriteStdinRequest{ref, r.u64(), r.bytes()} + return WriteStdinRequest{ref, r.U64(), r.Bytes()} case OpCloseStdin: - return CloseStdinRequest{ref, r.u64()} + return CloseStdinRequest{ref, r.U64()} case OpCloseOutput: return CloseOutputRequest{ref, enum[Stream](r)} case OpResizePTY: @@ -1122,18 +1092,18 @@ func readRequest(r *reader, t uint16) Message { case OpSignal: return SignalRequest{ref, enum[Signal](r), enum[SignalTarget](r)} case OpCancel: - return CancelRequest{ref, r.u32()} + return CancelRequest{ref, r.U32()} case OpAckEvents: - return AckEventsRequest{ref, r.u64()} + return AckEventsRequest{ref, r.U64()} default: return ReleaseRequest{ref} } } -func readResponse(r *reader, op uint16) Message { +func readResponse(r *sandboxwire.Decoder, op uint16) Message { switch op { case OpDescribe: - return DescribeResponse{r.id(), readCapabilities(r)} + return DescribeResponse{r.ID(), readCapabilities(r)} case OpStart: return StartResponse{enum[StartDisposition](r)} case OpAttach: @@ -1141,7 +1111,7 @@ func readResponse(r *reader, op uint16) Message { case OpInspect: return InspectResponse{readStatus(r)} case OpWriteStdin: - return WriteStdinResponse{r.u32()} + return WriteStdinResponse{r.U32()} case OpCloseStdin: return CloseStdinResponse{} case OpCloseOutput: @@ -1159,7 +1129,7 @@ func readResponse(r *reader, op uint16) Message { } } -func readEvent(r *reader, t uint16) Message { +func readEvent(r *sandboxwire.Decoder, t uint16) Message { h := readHeader(r) switch t { case EventStarted: @@ -1167,9 +1137,9 @@ func readEvent(r *reader, t uint16) Message { case EventStartFailed: return StartFailedEvent{h, readFailure(r)} case EventOutput: - return OutputEvent{h, enum[Stream](r), r.u64(), r.bytes()} + return OutputEvent{h, enum[Stream](r), r.U64(), r.Bytes()} case EventStreamClosed: - return StreamClosedEvent{h, enum[Stream](r), r.u64(), enum[OutputDisposition](r)} + return StreamClosedEvent{h, enum[Stream](r), r.U64(), enum[OutputDisposition](r)} case EventExited: return ExitedEvent{h, readExitStatus(r)} case EventOutputClosed: diff --git a/internal/sandboxprocess/protocol_test.go b/internal/sandboxprocess/protocol_test.go index d6598b87d..30963e60f 100644 --- a/internal/sandboxprocess/protocol_test.go +++ b/internal/sandboxprocess/protocol_test.go @@ -3,15 +3,12 @@ package sandboxprocess import ( "bytes" "encoding/binary" - "encoding/hex" "errors" - "os" - "path/filepath" "reflect" - "strings" "testing" "github.com/MiniMax-AI/OpenAgentCore/internal/sandboxwire" + "github.com/MiniMax-AI/OpenAgentCore/internal/sandboxwire/sandboxwiretest" ) var ( @@ -57,28 +54,10 @@ var fixtures = []struct { {"failure.hex", 1, ResponseFailure{OpStart, Failure{CodeOperationConflict, sandboxwire.EffectNone, "operation has a different spec"}}}, } -func readHexFixture(t testing.TB, name string) []byte { - t.Helper() - raw, err := os.ReadFile(filepath.Join("testdata", name)) - if err != nil { - t.Fatal(err) - } - var digits strings.Builder - for _, line := range strings.Split(string(raw), "\n") { - line, _, _ = strings.Cut(line, "#") - digits.WriteString(strings.Join(strings.Fields(line), "")) - } - frame, err := hex.DecodeString(digits.String()) - if err != nil { - t.Fatal(err) - } - return frame -} - func TestGoldenFixtures(t *testing.T) { for _, fx := range fixtures { t.Run(fx.name, func(t *testing.T) { - want := readHexFixture(t, fx.name) + want := sandboxwiretest.ReadHex(t, fx.name) var got bytes.Buffer if err := sandboxwire.WriteFrame(&got, sandboxwire.Frame{Type: fx.msg.MessageType(), RequestID: fx.requestID, Payload: Encode(fx.msg)}); err != nil { t.Fatal(err) diff --git a/internal/sandboxwire/codec.go b/internal/sandboxwire/codec.go index da12a9dff..9955490a1 100644 --- a/internal/sandboxwire/codec.go +++ b/internal/sandboxwire/codec.go @@ -48,132 +48,115 @@ func (e *Encoder) ID(id ID) { e.b = append(e.b, id[:]...) } func (e *Encoder) Effect(v Effect) { e.Enum(uint16(v)) } -// Decoder reads primitives from a payload. Every error wraps ErrMalformed; -// after an error the Decoder's position is unspecified. +// Decoder reads primitives from a payload. The first error sticks: every later +// read returns the zero value, and Err and Finish return that error. Its own +// errors wrap ErrMalformed. type Decoder struct { - b []byte + b []byte + err error } func NewDecoder(payload []byte) *Decoder { return &Decoder{b: payload} } -// Remaining returns the number of unread bytes. -func (d *Decoder) Remaining() int { return len(d.b) } +// Err returns the first error. +func (d *Decoder) Err() error { return d.err } -// Finish fails when bytes remain unread. +// Fail records err unless an earlier error is recorded. A protocol reports its +// own decoding rules through it. +func (d *Decoder) Fail(err error) { + if d.err == nil { + d.err = err + } +} + +// Finish returns the first error, or an error when bytes remain unread. func (d *Decoder) Finish() error { if len(d.b) != 0 { - return fmt.Errorf("%w: %d trailing bytes", ErrMalformed, len(d.b)) + d.Fail(fmt.Errorf("%w: %d trailing bytes", ErrMalformed, len(d.b))) } - return nil + return d.err } -func (d *Decoder) take(n uint64, what string) ([]byte, error) { +// take returns the next n bytes, or nil after an error. +func (d *Decoder) take(n uint64, what string) []byte { + if d.err != nil { + return nil + } if n > uint64(len(d.b)) { - return nil, fmt.Errorf("%w: %s needs %d bytes, %d remain", ErrMalformed, what, n, len(d.b)) + d.err = fmt.Errorf("%w: %s needs %d bytes, %d remain", ErrMalformed, what, n, len(d.b)) + return nil } v := d.b[:n:n] d.b = d.b[n:] - return v, nil -} - -func (d *Decoder) U8() (uint8, error) { - b, err := d.take(1, "u8") - if err != nil { - return 0, err - } - return b[0], nil -} - -func (d *Decoder) U16() (uint16, error) { - b, err := d.take(2, "u16") - if err != nil { - return 0, err - } - return binary.BigEndian.Uint16(b), nil + return v } -func (d *Decoder) U32() (uint32, error) { - b, err := d.take(4, "u32") - if err != nil { - return 0, err +// unsigned reads an n-byte big-endian integer. +func (d *Decoder) unsigned(n uint64, what string) uint64 { + var v uint64 + for _, b := range d.take(n, what) { + v = v<<8 | uint64(b) } - return binary.BigEndian.Uint32(b), nil + return v } -func (d *Decoder) U64() (uint64, error) { - b, err := d.take(8, "u64") - if err != nil { - return 0, err - } - return binary.BigEndian.Uint64(b), nil -} - -func (d *Decoder) I64() (int64, error) { - v, err := d.U64() - return int64(v), err -} +func (d *Decoder) U8() uint8 { return uint8(d.unsigned(1, "u8")) } +func (d *Decoder) U16() uint16 { return uint16(d.unsigned(2, "u16")) } +func (d *Decoder) U32() uint32 { return uint32(d.unsigned(4, "u32")) } +func (d *Decoder) U64() uint64 { return d.unsigned(8, "u64") } +func (d *Decoder) I64() int64 { return int64(d.U64()) } // Bool accepts exactly 0 or 1. -func (d *Decoder) Bool() (bool, error) { - v, err := d.U8() - if err == nil && v > 1 { - err = fmt.Errorf("%w: boolean byte %d", ErrMalformed, v) +func (d *Decoder) Bool() bool { + v := d.U8() + if v > 1 { + d.Fail(fmt.Errorf("%w: boolean byte %d", ErrMalformed, v)) } - return v == 1, err + return v == 1 } // Bytes reads a uint32 length and that many bytes. The result aliases the // payload, with its capacity clipped to its length. -func (d *Decoder) Bytes() ([]byte, error) { - n, err := d.U32() - if err != nil { - return nil, err - } - return d.take(uint64(n), "byte string") -} +func (d *Decoder) Bytes() []byte { return d.take(uint64(d.U32()), "byte string") } // Count reads an array's uint32 element count. The count is at most max and at // most the remaining bytes, because every element encodes to at least one byte, -// so a caller may allocate count elements. -func (d *Decoder) Count(max uint32) (int, error) { - n, err := d.U32() - switch { - case err != nil: - return 0, err +// so a caller may allocate count elements. It is zero after an error. +func (d *Decoder) Count(max uint32) int { + switch n := d.U32(); { case n > max: - return 0, fmt.Errorf("%w: count %d exceeds maximum %d", ErrMalformed, n, max) + d.Fail(fmt.Errorf("%w: count %d exceeds maximum %d", ErrMalformed, n, max)) case uint64(n) > uint64(len(d.b)): - return 0, fmt.Errorf("%w: count %d exceeds %d remaining bytes", ErrMalformed, n, len(d.b)) + d.Fail(fmt.Errorf("%w: count %d exceeds %d remaining bytes", ErrMalformed, n, len(d.b))) + default: + return int(n) } - return int(n), nil + return 0 } // Present reads the presence byte of an optional field. -func (d *Decoder) Present() (bool, error) { return d.Bool() } +func (d *Decoder) Present() bool { return d.Bool() } // Enum reads a uint16 enum value, rejecting zero and values valid does not // accept. -func (d *Decoder) Enum(valid func(uint16) bool) (uint16, error) { - v, err := d.U16() - if err == nil && (v == 0 || !valid(v)) { - err = fmt.Errorf("%w: enum value %d", ErrMalformed, v) +func (d *Decoder) Enum(valid func(uint16) bool) uint16 { + v := d.U16() + if v == 0 || !valid(v) { + d.Fail(fmt.Errorf("%w: enum value %d", ErrMalformed, v)) } - return v, err + return v } // ID reads a required identifier, rejecting the zero ID. -func (d *Decoder) ID() (ID, error) { - b, err := d.take(uint64(len(ID{})), "identifier") - if err != nil { - return ID{}, err - } - if id := ID(b); !id.IsZero() { - return id, nil +func (d *Decoder) ID() ID { + var id ID + copy(id[:], d.take(uint64(len(id)), "identifier")) + if id.IsZero() { + d.Fail(fmt.Errorf("%w: zero identifier", ErrMalformed)) } - return ID{}, fmt.Errorf("%w: zero identifier", ErrMalformed) + return id } -func (d *Decoder) Effect() (Effect, error) { - v, err := d.Enum(func(v uint16) bool { return Effect(v).Valid() }) - return Effect(v), err +func (d *Decoder) Effect() Effect { + return Effect(d.Enum(func(v uint16) bool { return Effect(v).Valid() })) } diff --git a/internal/sandboxwire/sandboxwire_test.go b/internal/sandboxwire/sandboxwire_test.go index 9868d1bcc..f6fbc0962 100644 --- a/internal/sandboxwire/sandboxwire_test.go +++ b/internal/sandboxwire/sandboxwire_test.go @@ -3,13 +3,12 @@ package sandboxwire import ( "bytes" "encoding/binary" - "encoding/hex" "errors" "io" - "os" "reflect" - "strings" "testing" + + "github.com/MiniMax-AI/OpenAgentCore/internal/sandboxwire/sandboxwiretest" ) // sample exercises every primitive in the order the golden fixture lists them. @@ -49,67 +48,27 @@ func (s sample) encode(e *Encoder) { } } -// get runs read unless an earlier read failed, keeping the first error. -func get[T any](err *error, read func() (T, error)) T { - var v T - if *err == nil { - v, *err = read() - } - return v -} - func decodeSample(t *testing.T, p []byte) (sample, error) { - var s sample - var err error d := NewDecoder(p) - s.A = get(&err, d.U8) - s.B = get(&err, d.U16) - s.C = get(&err, d.U32) - s.D = get(&err, d.U64) - s.E = get(&err, d.I64) - s.F = get(&err, d.Bool) - s.Kind = get(&err, func() (uint16, error) { return d.Enum(sampleKind) }) - s.Effect = get(&err, d.Effect) - if get(&err, d.Present) { - id := get(&err, d.ID) + s := sample{A: d.U8(), B: d.U16(), C: d.U32(), D: d.U64(), E: d.I64(), F: d.Bool(), Kind: d.Enum(sampleKind), Effect: d.Effect()} + if d.Present() { + id := d.ID() s.Ref = &id } - remaining := d.Remaining() - n := get(&err, func() (int, error) { return d.Count(sampleMaxItems) }) - if n > sampleMaxItems || n > remaining { - t.Fatalf("count %d escapes its bounds (max %d, %d bytes remained)", n, sampleMaxItems, remaining) + n := d.Count(sampleMaxItems) + if n > sampleMaxItems || n > len(p) { + t.Fatalf("count %d escapes its bounds (max %d, %d payload bytes)", n, sampleMaxItems, len(p)) } s.Items = make([][]byte, n) for i := range s.Items { - s.Items[i] = get(&err, d.Bytes) + s.Items[i] = d.Bytes() if cap(s.Items[i]) != len(s.Items[i]) { t.Fatalf("byte string capacity %d exceeds its length %d", cap(s.Items[i]), len(s.Items[i])) } } - if err != nil { - return s, err - } return s, d.Finish() } -func readHexFixture(t testing.TB, name string) []byte { - t.Helper() - raw, err := os.ReadFile(name) - if err != nil { - t.Fatal(err) - } - var digits strings.Builder - for _, line := range strings.Split(string(raw), "\n") { - line, _, _ = strings.Cut(line, "#") - digits.WriteString(strings.Join(strings.Fields(line), "")) - } - b, err := hex.DecodeString(digits.String()) - if err != nil { - t.Fatal(err) - } - return b -} - type countingWriter struct { bytes.Buffer writes int @@ -121,7 +80,7 @@ func (w *countingWriter) Write(p []byte) (int, error) { } func TestGoldenFrame(t *testing.T) { - want := readHexFixture(t, "testdata/frame_v1.hex") + want := sandboxwiretest.ReadHex(t, "frame_v1.hex") ref := ID{0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f} msg := sample{A: 7, B: 0x0102, C: 0x03040506, D: 0x0708090a0b0c0d0e, E: -2, F: true, Kind: 2, Effect: EffectPossible, Ref: &ref, Items: [][]byte{[]byte("ab"), {}}} @@ -157,6 +116,13 @@ func header(length uint32, flags uint16) []byte { func errOf[T any](_ T, err error) error { return err } +// readErr returns the error of read on a Decoder over p. +func readErr[T any](p []byte, read func(*Decoder) T) error { + d := NewDecoder(p) + read(d) + return d.Err() +} + func TestRejectsMalformed(t *testing.T) { anyEnum := func(uint16) bool { return true } tags := Tags{Requests: 2, Events: 1} @@ -170,14 +136,14 @@ func TestRejectsMalformed(t *testing.T) { {"nonzero flags", errOf(ReadFrame(bytes.NewReader(header(0, 1)), MaxPayload))}, {"oversize write", WriteFrame(io.Discard, Frame{Type: 1, RequestID: 1, Payload: make([]byte, MaxPayload+1)})}, {"trailing bytes", NewDecoder([]byte{0}).Finish()}, - {"bool 2", errOf(NewDecoder([]byte{2}).Bool())}, - {"zero enum", errOf(NewDecoder([]byte{0, 0}).Enum(anyEnum))}, - {"unknown enum", errOf(NewDecoder([]byte{0, 4}).Enum(sampleKind))}, - {"zero ID", errOf(NewDecoder(make([]byte, 16)).ID())}, - {"count above remaining bytes", errOf(NewDecoder([]byte{0, 0, 0, 2, 0}).Count(10))}, - {"count above maximum", errOf(NewDecoder([]byte{0, 0, 0, 2, 0, 0}).Count(1))}, - {"byte string above remaining bytes", errOf(NewDecoder([]byte{0, 0, 0, 2, 0}).Bytes())}, - {"truncated integer", errOf(NewDecoder([]byte{0, 0, 0}).U32())}, + {"bool 2", readErr([]byte{2}, (*Decoder).Bool)}, + {"zero enum", readErr([]byte{0, 0}, func(d *Decoder) uint16 { return d.Enum(anyEnum) })}, + {"unknown enum", readErr([]byte{0, 4}, func(d *Decoder) uint16 { return d.Enum(sampleKind) })}, + {"zero ID", readErr(make([]byte, 16), (*Decoder).ID)}, + {"count above remaining bytes", readErr([]byte{0, 0, 0, 2, 0}, func(d *Decoder) int { return d.Count(10) })}, + {"count above maximum", readErr([]byte{0, 0, 0, 2, 0, 0}, func(d *Decoder) int { return d.Count(1) })}, + {"byte string above remaining bytes", readErr([]byte{0, 0, 0, 2, 0}, (*Decoder).Bytes)}, + {"truncated integer", readErr([]byte{0, 0, 0}, (*Decoder).U32)}, {"unknown request", errOf(tags.Classify(3))}, {"unknown response", errOf(tags.Classify(ResponseType(3)))}, {"unknown event", errOf(tags.Classify(FirstEvent + 1))}, @@ -189,6 +155,17 @@ func TestRejectsMalformed(t *testing.T) { } } +// After the first error every read returns the zero value and the error stays. +func TestFirstErrorSticks(t *testing.T) { + d := NewDecoder([]byte{2, 0, 0, 0, 1, 7}) + d.Bool() + first := d.Err() + d.Fail(errors.New("later")) + if v, b := d.U32(), d.Bytes(); v != 0 || b != nil || d.Finish() != first || !errors.Is(first, ErrMalformed) { + t.Fatalf("after %v: read %d and %q, finished with %v", first, v, b, d.Finish()) + } +} + func TestClassify(t *testing.T) { tags := Tags{Requests: 2, Events: 1} for typ, want := range map[uint16]Kind{2: KindRequest, ResponseType(2): KindResponse, FirstEvent: KindEvent} { @@ -219,7 +196,7 @@ func TestRequestSequence(t *testing.T) { // allocation may exceed the input: the payload limit is the input length and // the item count is bounded. func FuzzDecoder(f *testing.F) { - f.Add(readHexFixture(f, "testdata/frame_v1.hex")) + f.Add(sandboxwiretest.ReadHex(f, "frame_v1.hex")) f.Add([]byte{}) f.Fuzz(func(t *testing.T, data []byte) { fr, err := ReadFrame(bytes.NewReader(data), uint32(len(data))) diff --git a/internal/sandboxwire/sandboxwiretest/hex.go b/internal/sandboxwire/sandboxwiretest/hex.go new file mode 100644 index 000000000..6ed89da96 --- /dev/null +++ b/internal/sandboxwire/sandboxwiretest/hex.go @@ -0,0 +1,31 @@ +// Package sandboxwiretest reads the hex golden fixtures of the sandbox wire +// protocols. +package sandboxwiretest + +import ( + "encoding/hex" + "os" + "path/filepath" + "strings" + "testing" +) + +// ReadHex returns the bytes of testdata/name: hex digits, with whitespace +// ignored and text from # to the end of a line a comment. +func ReadHex(t testing.TB, name string) []byte { + t.Helper() + raw, err := os.ReadFile(filepath.Join("testdata", name)) + if err != nil { + t.Fatal(err) + } + var digits strings.Builder + for line := range strings.Lines(string(raw)) { + line, _, _ = strings.Cut(line, "#") + digits.WriteString(strings.Join(strings.Fields(line), "")) + } + b, err := hex.DecodeString(digits.String()) + if err != nil { + t.Fatal(err) + } + return b +}