package diag import ( "bufio" "bytes" "io" "strings" "testing" "time" ) // trickle hands out at most n bytes per Read, so a message arrives in as many // pieces as a pipe might deliver it in. type trickle struct { r io.Reader n int } func (t trickle) Read(p []byte) (int, error) { if len(p) > t.n { p = p[:t.n] } return t.r.Read(p) } func TestReadMessageBodyArrivingInPieces(t *testing.T) { body := `{"jsonrpc":"2.0","id":1,"result":{"capabilities":{}}}` var buf bytes.Buffer if err := writeMessage(&buf, []byte(body)); err != nil { t.Fatal(err) } // One byte at a time is the worst case a pipe can do; bufio's own // buffering must not be what makes this pass, so its size is tiny too. r := bufio.NewReaderSize(trickle{&buf, 1}, 16) got, err := readMessage(r) if err != nil { t.Fatal(err) } if string(got) != body { t.Errorf("got %q, want %q", got, body) } } func TestReadMessageHeaderSplitAcrossWrites(t *testing.T) { pr, pw := io.Pipe() go func() { defer pw.Close() for _, piece := range []string{"Content-Le", "ngth: 2", "\r\n", "Content-Type: application/vscode-jsonrpc; charset=utf-8\r\n", "\r", "\n", "{", "}"} { _, _ = io.WriteString(pw, piece) time.Sleep(2 * time.Millisecond) } }() got, err := readMessage(bufio.NewReader(pr)) if err != nil { t.Fatal(err) } if string(got) != "{}" { t.Errorf("got %q", got) } } func TestReadMessageTwoMessagesBackToBack(t *testing.T) { var buf bytes.Buffer _ = writeMessage(&buf, []byte(`{"a":1}`)) _ = writeMessage(&buf, []byte(`{"b":2}`)) r := bufio.NewReader(&buf) for _, want := range []string{`{"a":1}`, `{"b":2}`} { got, err := readMessage(r) if err != nil { t.Fatal(err) } if string(got) != want { t.Errorf("got %q, want %q", got, want) } } if _, err := readMessage(r); err != io.EOF { t.Errorf("after the last message: %v, want io.EOF", err) } } func TestReadMessageRejectsBadFraming(t *testing.T) { cases := map[string]string{ "no content-length": "Content-Type: x\r\n\r\n{}", "garbage header": "hello\r\n\r\n", "negative length": "Content-Length: -1\r\n\r\n", "non-numeric": "Content-Length: abc\r\n\r\n", "over the limit": "Content-Length: 99999999999\r\n\r\n", "truncated body": "Content-Length: 10\r\n\r\n{}", "truncated header": "Content-Length: 2", } for name, in := range cases { t.Run(name, func(t *testing.T) { done := make(chan error, 1) go func() { _, err := readMessage(bufio.NewReader(strings.NewReader(in))) done <- err }() select { case err := <-done: if err == nil { t.Errorf("%q was accepted", in) } if err == io.EOF { t.Errorf("%q reported clean EOF rather than an error", in) } case <-time.After(time.Second): t.Fatal("readMessage hung") } }) } } func TestReadMessageOversizeIsNotAllocated(t *testing.T) { // The limit check has to happen before the make, or a hostile header // would be a multi-gigabyte allocation. The over-the-limit case above // proves the error; this one proves the body is not read either, by // handing over a reader that would block forever if asked. pr, _ := io.Pipe() header := strings.NewReader("Content-Length: 99999999\r\n\r\n") done := make(chan error, 1) go func() { _, err := readMessage(bufio.NewReader(io.MultiReader(header, pr))) done <- err }() select { case err := <-done: if err == nil { t.Error("oversize message accepted") } case <-time.After(time.Second): t.Fatal("readMessage tried to read an oversize body") } }