fix(protocol): apply follow-up review fixes
- commit
- 4b7cd9ef7c405c838ba6397d255a153f2a075cc9
- author
- brice · · 2026-08-02 11:39 UTC
- parent
- eed2010
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +50 -1
M internal/adapters/sshstdio/transport.go +1 -1
| @@ -21,7 +21,7 @@ import ( | |||
| 21 | 21 | ) | |
| 22 | 22 | ||
| 23 | 23 | const ( | |
| 24 | - | maxFrameLen = 16 << 20 | |
| 24 | + | maxFrameLen = ports.MaxFrameLen | |
| 25 | 25 | frameHeaderLen = 4 | |
| 26 | 26 | sshCloseTimeout = 3 * time.Second | |
| 27 | 27 | ) | |
| @@ -21,7 +21,7 @@ import ( | |||||
| 21 | ) | 21 | ) | ||
| 22 | 22 | ||||
| 23 | const ( | 23 | const ( | ||
| 24 | - | maxFrameLen = 16 << 20 | 24 | + | maxFrameLen = ports.MaxFrameLen |
| 25 | frameHeaderLen = 4 | 25 | frameHeaderLen = 4 | ||
| 26 | sshCloseTimeout = 3 * time.Second | 26 | sshCloseTimeout = 3 * time.Second | ||
| 27 | ) | 27 | ) | ||
M internal/adapters/sshstdio/transport_test.go +35 -0
| @@ -3,6 +3,7 @@ package sshstdio | |||
| 3 | 3 | import ( | |
| 4 | 4 | "bytes" | |
| 5 | 5 | "context" | |
| 6 | + | "encoding/binary" | |
| 6 | 7 | "errors" | |
| 7 | 8 | "io" | |
| 8 | 9 | "log/slog" | |
| @@ -164,6 +165,40 @@ func TestTransportRejectsZeroLengthFrame(t *testing.T) { | |||
| 164 | 165 | } | |
| 165 | 166 | } | |
| 166 | 167 | ||
| 168 | + | func TestTransportUsesCanonicalFrameMaximum(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, err := boundaryRecv.Recv() | |
| 180 | + | if err != nil { | |
| 181 | + | t.Fatalf("boundary Recv error = %v", err) | |
| 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 | + | ||
| 187 | + | send := NewTransport(nil, io.Discard, nil) | |
| 188 | + | err = send.Send(ports.Frame{Type: ports.MsgOutput, Payload: make([]byte, ports.MaxFrameLen)}) | |
| 189 | + | if !errors.Is(err, ErrFrameTooLarge) { | |
| 190 | + | t.Fatalf("Send error = %v, want ErrFrameTooLarge", err) | |
| 191 | + | } | |
| 192 | + | ||
| 193 | + | var header [frameHeaderLen]byte | |
| 194 | + | binary.BigEndian.PutUint32(header[:], ports.MaxFrameLen+1) | |
| 195 | + | recv := NewTransport(bytes.NewReader(header[:]), io.Discard, nil) | |
| 196 | + | _, err = recv.Recv() | |
| 197 | + | if !errors.Is(err, ErrFrameTooLarge) { | |
| 198 | + | t.Fatalf("Recv error = %v, want ErrFrameTooLarge", err) | |
| 199 | + | } | |
| 200 | + | } | |
| 201 | + | ||
| 167 | 202 | func TestDialContextCanceledBeforeStart(t *testing.T) { | |
| 168 | 203 | ctx, cancel := context.WithCancel(context.Background()) | |
| 169 | 204 | cancel() | |
| @@ -3,6 +3,7 @@ package sshstdio | |||||
| 3 | import ( | 3 | import ( | ||
| 4 | "bytes" | 4 | "bytes" | ||
| 5 | "context" | 5 | "context" | ||
| 6 | + | "encoding/binary" | |||
| 6 | "errors" | 7 | "errors" | ||
| 7 | "io" | 8 | "io" | ||
| 8 | "log/slog" | 9 | "log/slog" | ||
| @@ -164,6 +165,40 @@ func TestTransportRejectsZeroLengthFrame(t *testing.T) { | |||||
| 164 | } | 165 | } | ||
| 165 | } | 166 | } | ||
| 166 | 167 | ||||
| 168 | + | func TestTransportUsesCanonicalFrameMaximum(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, err := boundaryRecv.Recv() | |||
| 180 | + | if err != nil { | |||
| 181 | + | t.Fatalf("boundary Recv error = %v", err) | |||
| 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 | + | ||||
| 187 | + | send := NewTransport(nil, io.Discard, nil) | |||
| 188 | + | err = send.Send(ports.Frame{Type: ports.MsgOutput, Payload: make([]byte, ports.MaxFrameLen)}) | |||
| 189 | + | if !errors.Is(err, ErrFrameTooLarge) { | |||
| 190 | + | t.Fatalf("Send error = %v, want ErrFrameTooLarge", err) | |||
| 191 | + | } | |||
| 192 | + | ||||
| 193 | + | var header [frameHeaderLen]byte | |||
| 194 | + | binary.BigEndian.PutUint32(header[:], ports.MaxFrameLen+1) | |||
| 195 | + | recv := NewTransport(bytes.NewReader(header[:]), io.Discard, nil) | |||
| 196 | + | _, err = recv.Recv() | |||
| 197 | + | if !errors.Is(err, ErrFrameTooLarge) { | |||
| 198 | + | t.Fatalf("Recv error = %v, want ErrFrameTooLarge", err) | |||
| 199 | + | } | |||
| 200 | + | } | |||
| 201 | + | ||||
| 167 | func TestDialContextCanceledBeforeStart(t *testing.T) { | 202 | func TestDialContextCanceledBeforeStart(t *testing.T) { | ||
| 168 | ctx, cancel := context.WithCancel(context.Background()) | 203 | ctx, cancel := context.WithCancel(context.Background()) | ||
| 169 | cancel() | 204 | cancel() | ||
M internal/ports/wire_screen_test.go +3 -0
| @@ -386,6 +386,9 @@ func FuzzScreenUpdate(f *testing.F) { | |||
| 386 | 386 | if err != nil { | |
| 387 | 387 | t.Fatal(err) | |
| 388 | 388 | } | |
| 389 | + | if !bytes.Equal(encoded, data) { | |
| 390 | + | t.Fatalf("accepted data is not canonical: got %x, want %x", encoded, data) | |
| 391 | + | } | |
| 389 | 392 | roundtrip, err := UnmarshalScreenUpdate(encoded) | |
| 390 | 393 | if err != nil { | |
| 391 | 394 | t.Fatal(err) | |
| @@ -386,6 +386,9 @@ func FuzzScreenUpdate(f *testing.F) { | |||||
| 386 | if err != nil { | 386 | if err != nil { | ||
| 387 | t.Fatal(err) | 387 | t.Fatal(err) | ||
| 388 | } | 388 | } | ||
| 389 | + | if !bytes.Equal(encoded, data) { | |||
| 390 | + | t.Fatalf("accepted data is not canonical: got %x, want %x", encoded, data) | |||
| 391 | + | } | |||
| 389 | roundtrip, err := UnmarshalScreenUpdate(encoded) | 392 | roundtrip, err := UnmarshalScreenUpdate(encoded) | ||
| 390 | if err != nil { | 393 | if err != nil { | ||
| 391 | t.Fatal(err) | 394 | t.Fatal(err) | ||
M internal/ports/wire_test.go +11 -0
| @@ -625,6 +625,17 @@ func TestOutputGoldenAndRoundTrip(t *testing.T) { | |||
| 625 | 625 | } | |
| 626 | 626 | ||
| 627 | 627 | assertAllPrefixesFail(t, got[:24], UnmarshalOutput) | |
| 628 | + | ||
| 629 | + | extra := []byte{0x00, 0xff, 0x7f} | |
| 630 | + | withTrailing := append(append([]byte(nil), got...), extra...) | |
| 631 | + | back, err = UnmarshalOutput(withTrailing) | |
| 632 | + | if err != nil { | |
| 633 | + | t.Fatalf("UnmarshalOutput() with trailing payload error = %v", err) | |
| 634 | + | } | |
| 635 | + | wantData := append(append([]byte(nil), msg.Data...), extra...) | |
| 636 | + | if !bytes.Equal(back.Data, wantData) { | |
| 637 | + | t.Fatalf("trailing payload data = %#v, want %#v", back.Data, wantData) | |
| 638 | + | } | |
| 628 | 639 | } | |
| 629 | 640 | ||
| 630 | 641 | func TestAckGoldenAndRoundTrip(t *testing.T) { | |
| @@ -625,6 +625,17 @@ func TestOutputGoldenAndRoundTrip(t *testing.T) { | |||||
| 625 | } | 625 | } | ||
| 626 | 626 | ||||
| 627 | assertAllPrefixesFail(t, got[:24], UnmarshalOutput) | 627 | assertAllPrefixesFail(t, got[:24], UnmarshalOutput) | ||
| 628 | + | ||||
| 629 | + | extra := []byte{0x00, 0xff, 0x7f} | |||
| 630 | + | withTrailing := append(append([]byte(nil), got...), extra...) | |||
| 631 | + | back, err = UnmarshalOutput(withTrailing) | |||
| 632 | + | if err != nil { | |||
| 633 | + | t.Fatalf("UnmarshalOutput() with trailing payload error = %v", err) | |||
| 634 | + | } | |||
| 635 | + | wantData := append(append([]byte(nil), msg.Data...), extra...) | |||
| 636 | + | if !bytes.Equal(back.Data, wantData) { | |||
| 637 | + | t.Fatalf("trailing payload data = %#v, want %#v", back.Data, wantData) | |||
| 638 | + | } | |||
| 628 | } | 639 | } | ||
| 629 | 640 | ||||
| 630 | func TestAckGoldenAndRoundTrip(t *testing.T) { | 641 | func TestAckGoldenAndRoundTrip(t *testing.T) { | ||