mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-26 19:45:35 +00:00
216 lines
7.0 KiB
Go
216 lines
7.0 KiB
Go
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||
|
|
||
|
//go:build !plan9
|
||
|
|
||
|
package ws
|
||
|
|
||
|
import (
|
||
|
"encoding/binary"
|
||
|
"fmt"
|
||
|
"reflect"
|
||
|
"testing"
|
||
|
"time"
|
||
|
|
||
|
"math/rand"
|
||
|
|
||
|
"go.uber.org/zap"
|
||
|
"golang.org/x/net/websocket"
|
||
|
)
|
||
|
|
||
|
func Test_msg_Parse(t *testing.T) {
|
||
|
zl, err := zap.NewDevelopment()
|
||
|
if err != nil {
|
||
|
t.Fatalf("error creating a test logger: %v", err)
|
||
|
}
|
||
|
testMask := [4]byte{1, 2, 3, 4}
|
||
|
bs126, bs126Len := bytesSlice2ByteLen(t)
|
||
|
bs127, bs127Len := byteSlice8ByteLen(t)
|
||
|
tests := []struct {
|
||
|
name string
|
||
|
b []byte
|
||
|
initialPayload []byte
|
||
|
wantPayload []byte
|
||
|
wantIsFinalized bool
|
||
|
wantStreamID uint32
|
||
|
wantErr bool
|
||
|
}{
|
||
|
{
|
||
|
name: "single_fragment_stdout_stream_no_payload_no_mask",
|
||
|
b: []byte{0x82, 0x1, 0x1},
|
||
|
wantPayload: nil,
|
||
|
wantIsFinalized: true,
|
||
|
wantStreamID: 1,
|
||
|
},
|
||
|
{
|
||
|
name: "single_fragment_stderr_steam_no_payload_has_mask",
|
||
|
b: append([]byte{0x82, 0x81, 0x1, 0x2, 0x3, 0x4}, maskedBytes(testMask, []byte{0x2})...),
|
||
|
wantPayload: nil,
|
||
|
wantIsFinalized: true,
|
||
|
wantStreamID: 2,
|
||
|
},
|
||
|
{
|
||
|
name: "single_fragment_stdout_stream_no_mask_has_payload",
|
||
|
b: []byte{0x82, 0x3, 0x1, 0x7, 0x8},
|
||
|
wantPayload: []byte{0x7, 0x8},
|
||
|
wantIsFinalized: true,
|
||
|
wantStreamID: 1,
|
||
|
},
|
||
|
{
|
||
|
name: "single_fragment_stdout_stream_has_mask_has_payload",
|
||
|
b: append([]byte{0x82, 0x83, 0x1, 0x2, 0x3, 0x4}, maskedBytes(testMask, []byte{0x1, 0x7, 0x8})...),
|
||
|
wantPayload: []byte{0x7, 0x8},
|
||
|
wantIsFinalized: true,
|
||
|
wantStreamID: 1,
|
||
|
},
|
||
|
{
|
||
|
name: "initial_fragment_stdout_stream_no_mask_has_payload",
|
||
|
b: []byte{0x2, 0x3, 0x1, 0x7, 0x8},
|
||
|
wantPayload: []byte{0x7, 0x8},
|
||
|
wantStreamID: 1,
|
||
|
},
|
||
|
{
|
||
|
name: "initial_fragment_stdout_stream_has_mask_has_payload",
|
||
|
b: append([]byte{0x2, 0x83, 0x1, 0x2, 0x3, 0x4}, maskedBytes(testMask, []byte{0x1, 0x7, 0x8})...),
|
||
|
wantPayload: []byte{0x7, 0x8},
|
||
|
wantStreamID: 1,
|
||
|
},
|
||
|
{
|
||
|
name: "subsequent_fragment_stdout_stream_no_mask_has_payload",
|
||
|
b: []byte{0x0, 0x3, 0x1, 0x7, 0x8},
|
||
|
initialPayload: []byte{0x1, 0x2, 0x3},
|
||
|
wantPayload: []byte{0x1, 0x2, 0x3, 0x7, 0x8},
|
||
|
wantStreamID: 1,
|
||
|
},
|
||
|
{
|
||
|
name: "subsequent_fragment_stdout_stream_has_mask_has_payload",
|
||
|
b: append([]byte{0x0, 0x83, 0x1, 0x2, 0x3, 0x4}, maskedBytes(testMask, []byte{0x1, 0x7, 0x8})...),
|
||
|
initialPayload: []byte{0x1, 0x2, 0x3},
|
||
|
wantPayload: []byte{0x1, 0x2, 0x3, 0x7, 0x8},
|
||
|
wantStreamID: 1,
|
||
|
},
|
||
|
{
|
||
|
name: "final_fragment_stdout_stream_no_mask_has_payload",
|
||
|
b: []byte{0x80, 0x3, 0x1, 0x7, 0x8},
|
||
|
initialPayload: []byte{0x1, 0x2, 0x3},
|
||
|
wantIsFinalized: true,
|
||
|
wantPayload: []byte{0x1, 0x2, 0x3, 0x7, 0x8},
|
||
|
wantStreamID: 1,
|
||
|
},
|
||
|
{
|
||
|
name: "final_fragment_stdout_stream_has_mask_has_payload",
|
||
|
b: append([]byte{0x80, 0x83, 0x1, 0x2, 0x3, 0x4}, maskedBytes(testMask, []byte{0x1, 0x7, 0x8})...),
|
||
|
initialPayload: []byte{0x1, 0x2, 0x3},
|
||
|
wantIsFinalized: true,
|
||
|
wantPayload: []byte{0x1, 0x2, 0x3, 0x7, 0x8},
|
||
|
wantStreamID: 1,
|
||
|
},
|
||
|
{
|
||
|
name: "single_large_fragment_no_mask_length_hint_126",
|
||
|
b: append(append([]byte{0x80, 0x7e}, bs126Len...), append([]byte{0x1}, bs126...)...),
|
||
|
wantIsFinalized: true,
|
||
|
wantPayload: bs126,
|
||
|
wantStreamID: 1,
|
||
|
},
|
||
|
{
|
||
|
name: "single_large_fragment_no_mask_length_hint_127",
|
||
|
b: append(append([]byte{0x80, 0x7f}, bs127Len...), append([]byte{0x1}, bs127...)...),
|
||
|
wantIsFinalized: true,
|
||
|
wantPayload: bs127,
|
||
|
wantStreamID: 1,
|
||
|
},
|
||
|
{
|
||
|
name: "zero_length_bytes",
|
||
|
b: []byte{},
|
||
|
wantErr: true,
|
||
|
},
|
||
|
}
|
||
|
for _, tt := range tests {
|
||
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
msg := &message{
|
||
|
typ: binaryMessage,
|
||
|
payload: tt.initialPayload,
|
||
|
}
|
||
|
if _, err := msg.Parse(tt.b, zl.Sugar()); (err != nil) != tt.wantErr {
|
||
|
t.Errorf("msg.Parse() = %v, wantsErr: %t", err, tt.wantErr)
|
||
|
}
|
||
|
if msg.isFinalized != tt.wantIsFinalized {
|
||
|
t.Errorf("wants message to be finalized: %t, got: %t", tt.wantIsFinalized, msg.isFinalized)
|
||
|
}
|
||
|
if msg.streamID.Load() != tt.wantStreamID {
|
||
|
t.Errorf("wants stream ID: %d, got: %d", tt.wantStreamID, msg.streamID.Load())
|
||
|
}
|
||
|
if !reflect.DeepEqual(msg.payload, tt.wantPayload) {
|
||
|
t.Errorf("unexpected message payload after Parse, wants %b got %b", tt.wantPayload, msg.payload)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Test_msg_Parse_Rand calls Parse with a randomly generated input to verify
|
||
|
// that it doesn't panic.
|
||
|
func Test_msg_Parse_Rand(t *testing.T) {
|
||
|
zl, err := zap.NewDevelopment()
|
||
|
if err != nil {
|
||
|
t.Fatalf("error creating a test logger: %v", err)
|
||
|
}
|
||
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||
|
for i := range 100 {
|
||
|
n := r.Intn(4096)
|
||
|
b := make([]byte, n)
|
||
|
_, err := r.Read(b)
|
||
|
if err != nil {
|
||
|
t.Fatalf("error generating random byte slice: %v", err)
|
||
|
}
|
||
|
msg := message{typ: binaryMessage}
|
||
|
f := func() {
|
||
|
msg.Parse(b, zl.Sugar())
|
||
|
}
|
||
|
testPanic(t, f, fmt.Sprintf("[%d] Parse panicked running with byte slice of length %d: %v", i, n, r))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// byteSlice2ByteLen generates a number that represents websocket message fragment length and is stored in an 8 byte slice.
|
||
|
// Returns the byte slice with the length as well as a slice of arbitrary bytes of the given length.
|
||
|
// This is used to generate test input representing websocket message with payload length hint 126.
|
||
|
// https://www.rfc-editor.org/rfc/rfc6455#section-5.2
|
||
|
func bytesSlice2ByteLen(t *testing.T) ([]byte, []byte) {
|
||
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||
|
var n uint16
|
||
|
n = uint16(rand.Intn(65535 - 1)) // space for and additional 1 byte stream ID
|
||
|
b := make([]byte, n)
|
||
|
_, err := r.Read(b)
|
||
|
if err != nil {
|
||
|
t.Fatalf("error generating random byte slice: %v ", err)
|
||
|
}
|
||
|
bb := make([]byte, 2)
|
||
|
binary.BigEndian.PutUint16(bb, n+1) // + stream ID
|
||
|
return b, bb
|
||
|
}
|
||
|
|
||
|
// byteSlice8ByteLen generates a number that represents websocket message fragment length and is stored in an 8 byte slice.
|
||
|
// Returns the byte slice with the length as well as a slice of arbitrary bytes of the given length.
|
||
|
// This is used to generate test input representing websocket message with payload length hint 127.
|
||
|
// https://www.rfc-editor.org/rfc/rfc6455#section-5.2
|
||
|
func byteSlice8ByteLen(t *testing.T) ([]byte, []byte) {
|
||
|
nanos := time.Now().UnixNano()
|
||
|
t.Logf("Creating random source with seed %v", nanos)
|
||
|
r := rand.New(rand.NewSource(nanos))
|
||
|
var n uint64
|
||
|
n = uint64(rand.Intn(websocket.DefaultMaxPayloadBytes - 1)) // space for and additional 1 byte stream ID
|
||
|
t.Logf("byteSlice8ByteLen: generating message payload of length %d", n)
|
||
|
b := make([]byte, n)
|
||
|
_, err := r.Read(b)
|
||
|
if err != nil {
|
||
|
t.Fatalf("error generating random byte slice: %v ", err)
|
||
|
}
|
||
|
bb := make([]byte, 8)
|
||
|
binary.BigEndian.PutUint64(bb, n+1) // + stream ID
|
||
|
return b, bb
|
||
|
}
|
||
|
|
||
|
func maskedBytes(mask [4]byte, b []byte) []byte {
|
||
|
maskBytes(mask, b)
|
||
|
return b
|
||
|
}
|