test(protocol): strengthen PR 167 review coverage
- commit
- 4fabf6fef53c78a9d941c9f439688507208ddbe0
- author
- brice · · 2026-08-02 06:12 UTC
- parent
- e147aea
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +37 -3
M internal/adapters/sshstdio/transport_test.go +18 -0
| @@ -166,6 +166,24 @@ func TestTransportRejectsZeroLengthFrame(t *testing.T) { | |||
| 166 | 166 | } | |
| 167 | 167 | ||
| 168 | 168 | func TestTransportRejectsFramesOverSharedMaximum(t *testing.T) { | |
| 169 | + | boundaryPayload := make([]byte, ports.MaxFrameLen-1) | |
| 170 | + | boundaryWire := &bytes.Buffer{} | |
| 171 | + | boundarySend := NewTransport(nil, boundaryWire, nil) | |
| 172 | + | if err := boundarySend.Send(ports.Frame{Type: ports.MsgOutput, Payload: boundaryPayload}); err != nil { | |
| 173 | + | t.Fatalf("boundary Send error = %v", err) | |
| 174 | + | } | |
| 175 | + | if got := binary.BigEndian.Uint32(boundaryWire.Bytes()[:frameHeaderLen]); got != ports.MaxFrameLen { | |
| 176 | + | t.Fatalf("boundary frame length = %d, want %d", got, ports.MaxFrameLen) | |
| 177 | + | } | |
| 178 | + | boundaryRecv := NewTransport(bytes.NewReader(boundaryWire.Bytes()), io.Discard, nil) | |
| 179 | + | boundaryFrame, boundaryErr := boundaryRecv.Recv() | |
| 180 | + | if boundaryErr != nil { | |
| 181 | + | t.Fatalf("boundary Recv error = %v", boundaryErr) | |
| 182 | + | } | |
| 183 | + | if boundaryFrame.Type != ports.MsgOutput || len(boundaryFrame.Payload) != len(boundaryPayload) { | |
| 184 | + | t.Fatalf("boundary frame = type %d, payload %d bytes; want type %d, payload %d bytes", boundaryFrame.Type, len(boundaryFrame.Payload), ports.MsgOutput, len(boundaryPayload)) | |
| 185 | + | } | |
| 186 | + | ||
| 169 | 187 | send := NewTransport(nil, io.Discard, nil) | |
| 170 | 188 | err := send.Send(ports.Frame{Type: ports.MsgOutput, Payload: make([]byte, ports.MaxFrameLen)}) | |
| 171 | 189 | if !errors.Is(err, ErrFrameTooLarge) { | |
| @@ -166,6 +166,24 @@ func TestTransportRejectsZeroLengthFrame(t *testing.T) { | |||||
| 166 | } | 166 | } | ||
| 167 | 167 | ||||
| 168 | func TestTransportRejectsFramesOverSharedMaximum(t *testing.T) { | 168 | func TestTransportRejectsFramesOverSharedMaximum(t *testing.T) { | ||
| 169 | + | boundaryPayload := make([]byte, ports.MaxFrameLen-1) | |||
| 170 | + | boundaryWire := &bytes.Buffer{} | |||
| 171 | + | boundarySend := NewTransport(nil, boundaryWire, nil) | |||
| 172 | + | if err := boundarySend.Send(ports.Frame{Type: ports.MsgOutput, Payload: boundaryPayload}); err != nil { | |||
| 173 | + | t.Fatalf("boundary Send error = %v", err) | |||
| 174 | + | } | |||
| 175 | + | if got := binary.BigEndian.Uint32(boundaryWire.Bytes()[:frameHeaderLen]); got != ports.MaxFrameLen { | |||
| 176 | + | t.Fatalf("boundary frame length = %d, want %d", got, ports.MaxFrameLen) | |||
| 177 | + | } | |||
| 178 | + | boundaryRecv := NewTransport(bytes.NewReader(boundaryWire.Bytes()), io.Discard, nil) | |||
| 179 | + | boundaryFrame, boundaryErr := boundaryRecv.Recv() | |||
| 180 | + | if boundaryErr != nil { | |||
| 181 | + | t.Fatalf("boundary Recv error = %v", boundaryErr) | |||
| 182 | + | } | |||
| 183 | + | if boundaryFrame.Type != ports.MsgOutput || len(boundaryFrame.Payload) != len(boundaryPayload) { | |||
| 184 | + | t.Fatalf("boundary frame = type %d, payload %d bytes; want type %d, payload %d bytes", boundaryFrame.Type, len(boundaryFrame.Payload), ports.MsgOutput, len(boundaryPayload)) | |||
| 185 | + | } | |||
| 186 | + | ||||
| 169 | send := NewTransport(nil, io.Discard, nil) | 187 | send := NewTransport(nil, io.Discard, nil) | ||
| 170 | err := send.Send(ports.Frame{Type: ports.MsgOutput, Payload: make([]byte, ports.MaxFrameLen)}) | 188 | err := send.Send(ports.Frame{Type: ports.MsgOutput, Payload: make([]byte, ports.MaxFrameLen)}) | ||
| 171 | if !errors.Is(err, ErrFrameTooLarge) { | 189 | if !errors.Is(err, ErrFrameTooLarge) { | ||
M internal/usecase/daemon/proxy_screen_state.go +5 -3
| @@ -183,9 +183,11 @@ func (s *proxyScreenState) AcknowledgeDamage(generation uint64) bool { | |||
| 183 | 183 | return true | |
| 184 | 184 | } | |
| 185 | 185 | ||
| 186 | - | // CaptureInto updates dst from the live frame. Safe scroll/span damage is | |
| 187 | - | // replayed incrementally; snapshots, uncertain damage, and dimensions use a | |
| 188 | - | // complete clone. | |
| 186 | + | // CaptureInto updates dst from the live frame. dst must already contain the | |
| 187 | + | // previous synchronized generation before incremental damage can be replayed; | |
| 188 | + | // callers should initialize it with a full clone or equivalent first capture. | |
| 189 | + | // Safe scroll/span damage is replayed incrementally; snapshots, uncertain | |
| 190 | + | // damage, and dimensions use a complete clone. | |
| 189 | 191 | func (s *proxyScreenState) CaptureInto(dst *renderer.Frame) { | |
| 190 | 192 | if s == nil || dst == nil || !validProxyFrame(*s) { | |
| 191 | 193 | return | |
| @@ -183,9 +183,11 @@ func (s *proxyScreenState) AcknowledgeDamage(generation uint64) bool { | |||||
| 183 | return true | 183 | return true | ||
| 184 | } | 184 | } | ||
| 185 | 185 | ||||
| 186 | - | // CaptureInto updates dst from the live frame. Safe scroll/span damage is | 186 | + | // CaptureInto updates dst from the live frame. dst must already contain the |
| 187 | - | // replayed incrementally; snapshots, uncertain damage, and dimensions use a | 187 | + | // previous synchronized generation before incremental damage can be replayed; |
| 188 | - | // complete clone. | 188 | + | // callers should initialize it with a full clone or equivalent first capture. |
| 189 | + | // Safe scroll/span damage is replayed incrementally; snapshots, uncertain | |||
| 190 | + | // damage, and dimensions use a complete clone. | |||
| 189 | func (s *proxyScreenState) CaptureInto(dst *renderer.Frame) { | 191 | func (s *proxyScreenState) CaptureInto(dst *renderer.Frame) { | ||
| 190 | if s == nil || dst == nil || !validProxyFrame(*s) { | 192 | if s == nil || dst == nil || !validProxyFrame(*s) { | ||
| 191 | return | 193 | return | ||
M internal/usecase/daemon/proxy_screen_state_test.go +14 -0
| @@ -63,6 +63,20 @@ func TestProxyScreenStateInvalidUpdateIsAtomic(t *testing.T) { | |||
| 63 | 63 | t.Fatal("invalid update mutated metadata") | |
| 64 | 64 | } | |
| 65 | 65 | ||
| 66 | + | invalid = ports.ScreenUpdate{ | |
| 67 | + | Kind: ports.ScreenUpdateDelta, | |
| 68 | + | BaseStateNum: 1, | |
| 69 | + | NewStateNum: 2, | |
| 70 | + | Size: domain.Size{Cols: 3, Rows: 2}, | |
| 71 | + | Cursor: ports.ScreenCursor{Row: 1, Col: 1, Visible: true}, | |
| 72 | + | } | |
| 73 | + | if err := s.Apply(invalid); err == nil { | |
| 74 | + | t.Fatal("delta with mismatched dimensions was accepted") | |
| 75 | + | } | |
| 76 | + | if !reflect.DeepEqual(s.frame, beforeFrame) || !reflect.DeepEqual(s.scratch, beforeScratch) || s.cursorOut != beforeCursor || s.generation != beforeGeneration || s.stateNum != beforeState { | |
| 77 | + | t.Fatal("mismatched-dimension update mutated state") | |
| 78 | + | } | |
| 79 | + | ||
| 66 | 80 | invalid = ports.ScreenUpdate{ | |
| 67 | 81 | Kind: ports.ScreenUpdateDelta, | |
| 68 | 82 | BaseStateNum: 1, | |
| @@ -63,6 +63,20 @@ func TestProxyScreenStateInvalidUpdateIsAtomic(t *testing.T) { | |||||
| 63 | t.Fatal("invalid update mutated metadata") | 63 | t.Fatal("invalid update mutated metadata") | ||
| 64 | } | 64 | } | ||
| 65 | 65 | ||||
| 66 | + | invalid = ports.ScreenUpdate{ | |||
| 67 | + | Kind: ports.ScreenUpdateDelta, | |||
| 68 | + | BaseStateNum: 1, | |||
| 69 | + | NewStateNum: 2, | |||
| 70 | + | Size: domain.Size{Cols: 3, Rows: 2}, | |||
| 71 | + | Cursor: ports.ScreenCursor{Row: 1, Col: 1, Visible: true}, | |||
| 72 | + | } | |||
| 73 | + | if err := s.Apply(invalid); err == nil { | |||
| 74 | + | t.Fatal("delta with mismatched dimensions was accepted") | |||
| 75 | + | } | |||
| 76 | + | if !reflect.DeepEqual(s.frame, beforeFrame) || !reflect.DeepEqual(s.scratch, beforeScratch) || s.cursorOut != beforeCursor || s.generation != beforeGeneration || s.stateNum != beforeState { | |||
| 77 | + | t.Fatal("mismatched-dimension update mutated state") | |||
| 78 | + | } | |||
| 79 | + | ||||
| 66 | invalid = ports.ScreenUpdate{ | 80 | invalid = ports.ScreenUpdate{ | ||
| 67 | Kind: ports.ScreenUpdateDelta, | 81 | Kind: ports.ScreenUpdateDelta, | ||
| 68 | BaseStateNum: 1, | 82 | BaseStateNum: 1, | ||