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 +}