test(protocol): cover malformed command boundaries
- commit
- 3217e93d25233e0b85ffef4438046637ea0bc94c
- author
- brice · · 2026-08-28 14:19 UTC
- parent
- f08a628
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
1 file · +44 -16
M internal/app/daemon_integration_test.go +44 -16
| @@ -138,24 +138,52 @@ func killAll(dir string) error { | |||
| 138 | 138 | } | |
| 139 | 139 | ||
| 140 | 140 | func TestIntegration_MalformedCommandPreservesVersionAndRequestID(t *testing.T) { | |
| 141 | - | dir, _ := startDaemon(t) | |
| 142 | - | tr, err := ipc.DialContext(context.Background(), dir) | |
| 141 | + | incompatibleVersion := make([]byte, 10) | |
| 142 | + | binary.BigEndian.PutUint16(incompatibleVersion[:2], protocol.Version+1) | |
| 143 | + | binary.BigEndian.PutUint64(incompatibleVersion[2:], 42) | |
| 144 | + | valid, err := wire.MarshalCommandRequest(protocol.CommandRequest{ | |
| 145 | + | Version: protocol.Version, RequestID: 43, Slug: "list-sessions", | |
| 146 | + | }) | |
| 143 | 147 | require.NoError(t, err) | |
| 144 | - | defer func() { _ = tr.Close() }() | |
| 145 | - | ||
| 146 | - | payload := make([]byte, 10) | |
| 147 | - | binary.BigEndian.PutUint16(payload[:2], protocol.Version+1) | |
| 148 | - | binary.BigEndian.PutUint64(payload[2:], 42) | |
| 149 | - | require.NoError(t, tr.Send(wire.Frame{Type: wire.MsgCommand, Payload: payload})) | |
| 150 | 148 | ||
| 151 | - | frame, err := tr.Recv() | |
| 152 | - | require.NoError(t, err) | |
| 153 | - | require.Equal(t, wire.MsgCommandResult, frame.Type) | |
| 154 | - | result, err := wire.UnmarshalCommandResult(frame.Payload) | |
| 155 | - | require.NoError(t, err) | |
| 156 | - | require.False(t, result.OK) | |
| 157 | - | require.Equal(t, uint64(42), result.RequestID) | |
| 158 | - | require.Equal(t, protocol.ErrVersionMismatch, result.Code) | |
| 149 | + | tests := []struct { | |
| 150 | + | name string | |
| 151 | + | payload []byte | |
| 152 | + | want protocol.CommandResult | |
| 153 | + | }{ | |
| 154 | + | { | |
| 155 | + | name: "incompatible version", | |
| 156 | + | payload: incompatibleVersion, | |
| 157 | + | want: protocol.CommandResult{RequestID: 42, Code: protocol.ErrVersionMismatch, Text: "protocol version mismatch"}, | |
| 158 | + | }, | |
| 159 | + | { | |
| 160 | + | name: "truncated version prefix", | |
| 161 | + | payload: []byte{0}, | |
| 162 | + | want: protocol.CommandResult{Code: protocol.ErrInternal, Text: "malformed command request"}, | |
| 163 | + | }, | |
| 164 | + | { | |
| 165 | + | name: "trailing garbage", | |
| 166 | + | payload: append(append([]byte(nil), valid...), 0xff), | |
| 167 | + | want: protocol.CommandResult{RequestID: 43, Code: protocol.ErrInternal, Text: "malformed command request"}, | |
| 168 | + | }, | |
| 169 | + | } | |
| 170 | + | for _, tt := range tests { | |
| 171 | + | t.Run(tt.name, func(t *testing.T) { | |
| 172 | + | dir, _ := startDaemon(t) | |
| 173 | + | tr, err := ipc.DialContext(context.Background(), dir) | |
| 174 | + | require.NoError(t, err) | |
| 175 | + | defer func() { _ = tr.Close() }() | |
| 176 | + | ||
| 177 | + | require.NoError(t, tr.Send(wire.Frame{Type: wire.MsgCommand, Payload: tt.payload})) | |
| 178 | + | frame, err := tr.Recv() | |
| 179 | + | require.NoError(t, err) | |
| 180 | + | require.Equal(t, wire.MsgCommandResult, frame.Type) | |
| 181 | + | require.Equal(t, wire.MarshalCommandResult(tt.want), frame.Payload) | |
| 182 | + | result, err := wire.UnmarshalCommandResult(frame.Payload) | |
| 183 | + | require.NoError(t, err) | |
| 184 | + | require.Equal(t, tt.want, result) | |
| 185 | + | }) | |
| 186 | + | } | |
| 159 | 187 | } | |
| 160 | 188 | ||
| 161 | 189 | // awaitText decodes MsgOutput frames into a fresh VT screen and returns once | |
| @@ -138,24 +138,52 @@ func killAll(dir string) error { | |||||
| 138 | } | 138 | } | ||
| 139 | 139 | ||||
| 140 | func TestIntegration_MalformedCommandPreservesVersionAndRequestID(t *testing.T) { | 140 | func TestIntegration_MalformedCommandPreservesVersionAndRequestID(t *testing.T) { | ||
| 141 | - | dir, _ := startDaemon(t) | 141 | + | incompatibleVersion := make([]byte, 10) |
| 142 | - | tr, err := ipc.DialContext(context.Background(), dir) | 142 | + | binary.BigEndian.PutUint16(incompatibleVersion[:2], protocol.Version+1) |
| 143 | + | binary.BigEndian.PutUint64(incompatibleVersion[2:], 42) | |||
| 144 | + | valid, err := wire.MarshalCommandRequest(protocol.CommandRequest{ | |||
| 145 | + | Version: protocol.Version, RequestID: 43, Slug: "list-sessions", | |||
| 146 | + | }) | |||
| 143 | require.NoError(t, err) | 147 | require.NoError(t, err) | ||
| 144 | - | defer func() { _ = tr.Close() }() | |||
| 145 | - | ||||
| 146 | - | payload := make([]byte, 10) | |||
| 147 | - | binary.BigEndian.PutUint16(payload[:2], protocol.Version+1) | |||
| 148 | - | binary.BigEndian.PutUint64(payload[2:], 42) | |||
| 149 | - | require.NoError(t, tr.Send(wire.Frame{Type: wire.MsgCommand, Payload: payload})) | |||
| 150 | 148 | ||||
| 151 | - | frame, err := tr.Recv() | 149 | + | tests := []struct { |
| 152 | - | require.NoError(t, err) | 150 | + | name string |
| 153 | - | require.Equal(t, wire.MsgCommandResult, frame.Type) | 151 | + | payload []byte |
| 154 | - | result, err := wire.UnmarshalCommandResult(frame.Payload) | 152 | + | want protocol.CommandResult |
| 155 | - | require.NoError(t, err) | 153 | + | }{ |
| 156 | - | require.False(t, result.OK) | 154 | + | { |
| 157 | - | require.Equal(t, uint64(42), result.RequestID) | 155 | + | name: "incompatible version", |
| 158 | - | require.Equal(t, protocol.ErrVersionMismatch, result.Code) | 156 | + | payload: incompatibleVersion, |
| 157 | + | want: protocol.CommandResult{RequestID: 42, Code: protocol.ErrVersionMismatch, Text: "protocol version mismatch"}, | |||
| 158 | + | }, | |||
| 159 | + | { | |||
| 160 | + | name: "truncated version prefix", | |||
| 161 | + | payload: []byte{0}, | |||
| 162 | + | want: protocol.CommandResult{Code: protocol.ErrInternal, Text: "malformed command request"}, | |||
| 163 | + | }, | |||
| 164 | + | { | |||
| 165 | + | name: "trailing garbage", | |||
| 166 | + | payload: append(append([]byte(nil), valid...), 0xff), | |||
| 167 | + | want: protocol.CommandResult{RequestID: 43, Code: protocol.ErrInternal, Text: "malformed command request"}, | |||
| 168 | + | }, | |||
| 169 | + | } | |||
| 170 | + | for _, tt := range tests { | |||
| 171 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 172 | + | dir, _ := startDaemon(t) | |||
| 173 | + | tr, err := ipc.DialContext(context.Background(), dir) | |||
| 174 | + | require.NoError(t, err) | |||
| 175 | + | defer func() { _ = tr.Close() }() | |||
| 176 | + | ||||
| 177 | + | require.NoError(t, tr.Send(wire.Frame{Type: wire.MsgCommand, Payload: tt.payload})) | |||
| 178 | + | frame, err := tr.Recv() | |||
| 179 | + | require.NoError(t, err) | |||
| 180 | + | require.Equal(t, wire.MsgCommandResult, frame.Type) | |||
| 181 | + | require.Equal(t, wire.MarshalCommandResult(tt.want), frame.Payload) | |||
| 182 | + | result, err := wire.UnmarshalCommandResult(frame.Payload) | |||
| 183 | + | require.NoError(t, err) | |||
| 184 | + | require.Equal(t, tt.want, result) | |||
| 185 | + | }) | |||
| 186 | + | } | |||
| 159 | } | 187 | } | ||
| 160 | 188 | ||||
| 161 | // awaitText decodes MsgOutput frames into a fresh VT screen and returns once | 189 | // awaitText decodes MsgOutput frames into a fresh VT screen and returns once | ||