1
0

protocol_test.go 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. package localapi
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "net"
  7. "strings"
  8. "testing"
  9. "github.com/stretchr/testify/assert"
  10. "github.com/stretchr/testify/require"
  11. )
  12. // TestReadFrameLimitIsOnTheBody pins the framing rule at its edge: the limit bounds the
  13. // JSON body, so the newline that terminates the frame must not count against it.
  14. func TestReadFrameLimitIsOnTheBody(t *testing.T) {
  15. // A JSON string whose encoded form is exactly n bytes, with or without a terminator.
  16. body := func(n int) string {
  17. return fmt.Sprintf(`"%s"`, strings.Repeat("a", n-2))
  18. }
  19. var v string
  20. // Exactly at the limit, terminated: accepted.
  21. v = ""
  22. require.NoError(t, readFrame(bytes.NewReader([]byte(body(64)+"\n")), 64, "request", &v))
  23. assert.Len(t, v, 62)
  24. // Exactly at the limit, EOF instead of a newline: still accepted.
  25. v = ""
  26. require.NoError(t, readFrame(bytes.NewReader([]byte(body(64))), 64, "request", &v))
  27. assert.Len(t, v, 62)
  28. // One byte over: refused as oversized, not as a parse failure.
  29. err := readFrame(bytes.NewReader([]byte(body(65)+"\n")), 64, "request", &v)
  30. require.Error(t, err)
  31. assert.Contains(t, err.Error(), "exceeded 64 bytes")
  32. }
  33. // stallingReader hands out its bytes and then fails with err, the shape of a peer that
  34. // sends part of a frame and then stops, until the deadline on the connection lands.
  35. type stallingReader struct {
  36. data []byte
  37. err error
  38. }
  39. func (r *stallingReader) Read(p []byte) (int, error) {
  40. if len(r.data) == 0 {
  41. return 0, r.err
  42. }
  43. n := copy(p, r.data)
  44. r.data = r.data[n:]
  45. return n, nil
  46. }
  47. type timeoutError struct{}
  48. func (timeoutError) Error() string { return "i/o timeout" }
  49. func (timeoutError) Timeout() bool { return true }
  50. func (timeoutError) Temporary() bool { return true }
  51. // TestReadFramePartialLineThenReadErrorIsTheError: a read that fails after part of a
  52. // frame has arrived is a failed read, and the caller sees that error. The old behaviour
  53. // treated the fragment as the whole frame and reported a parse failure, so a client
  54. // that stalled mid request was told its JSON was malformed, and a daemon that stalled
  55. // mid response hid the timeout the client's error path looks for.
  56. func TestReadFramePartialLineThenReadErrorIsTheError(t *testing.T) {
  57. var v map[string]any
  58. err := readFrame(&stallingReader{data: []byte(`{"command": "hel`), err: timeoutError{}}, 1024, "request", &v)
  59. require.Error(t, err)
  60. var ne net.Error
  61. require.ErrorAs(t, err, &ne, "the timeout is still visible through the wrapping: %v", err)
  62. assert.True(t, ne.Timeout())
  63. assert.NotContains(t, err.Error(), "parse", "a stalled peer is not a parse failure")
  64. // EOF after an unterminated line is the one exception: a final line with no newline
  65. // is still a frame.
  66. v = nil
  67. require.NoError(t, readFrame(&stallingReader{data: []byte(`{"command": "help"}`), err: io.EOF}, 1024, "request", &v))
  68. assert.Equal(t, "help", v["command"])
  69. // And an unterminated line that is EOF only because the limit cut it off is still
  70. // reported as oversized.
  71. err = readFrame(bytes.NewReader([]byte(strings.Repeat("a", 70)+"\n")), 64, "request", &v)
  72. require.Error(t, err)
  73. assert.Contains(t, err.Error(), "exceeded 64 bytes")
  74. }