// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package websocket

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
	"testing"
)

// Test the getNonceAccept function with values in
// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17
func TestSecWebSocketAccept(t *testing.T) {
	nonce := []byte("dGhlIHNhbXBsZSBub25jZQ==")
	expected := []byte("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=")
	accept, err := getNonceAccept(nonce)
	if err != nil {
		t.Errorf("getNonceAccept: returned error %v", err)
		return
	}
	if !bytes.Equal(expected, accept) {
		t.Errorf("getNonceAccept: expected %q got %q", expected, accept)
	}
}

func TestHybiClientHandshake(t *testing.T) {
	type test struct {
		url, host string
	}
	tests := []test{
		{"ws://server.example.com/chat", "server.example.com"},
		{"ws://127.0.0.1/chat", "127.0.0.1"},
	}
	if _, err := url.ParseRequestURI("http://[fe80::1%25lo0]"); err == nil {
		tests = append(tests, test{"ws://[fe80::1%25lo0]/chat", "[fe80::1]"})
	}

	for _, tt := range tests {
		var b bytes.Buffer
		bw := bufio.NewWriter(&b)
		br := bufio.NewReader(strings.NewReader(`HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat

`))
		var err error
		var config Config
		config.Location, err = url.ParseRequestURI(tt.url)
		if err != nil {
			t.Fatal("location url", err)
		}
		config.Origin, err = url.ParseRequestURI("http://example.com")
		if err != nil {
			t.Fatal("origin url", err)
		}
		config.Protocol = append(config.Protocol, "chat")
		config.Protocol = append(config.Protocol, "superchat")
		config.Version = ProtocolVersionHybi13
		config.handshakeData = map[string]string{
			"key": "dGhlIHNhbXBsZSBub25jZQ==",
		}
		if err := hybiClientHandshake(&config, br, bw); err != nil {
			t.Fatal("handshake", err)
		}
		req, err := http.ReadRequest(bufio.NewReader(&b))
		if err != nil {
			t.Fatal("read request", err)
		}
		if req.Method != "GET" {
			t.Errorf("request method expected GET, but got %s", req.Method)
		}
		if req.URL.Path != "/chat" {
			t.Errorf("request path expected /chat, but got %s", req.URL.Path)
		}
		if req.Proto != "HTTP/1.1" {
			t.Errorf("request proto expected HTTP/1.1, but got %s", req.Proto)
		}
		if req.Host != tt.host {
			t.Errorf("request host expected %s, but got %s", tt.host, req.Host)
		}
		var expectedHeader = map[string]string{
			"Connection":             "Upgrade",
			"Upgrade":                "websocket",
			"Sec-Websocket-Key":      config.handshakeData["key"],
			"Origin":                 config.Origin.String(),
			"Sec-Websocket-Protocol": "chat, superchat",
			"Sec-Websocket-Version":  fmt.Sprintf("%d", ProtocolVersionHybi13),
		}
		for k, v := range expectedHeader {
			if req.Header.Get(k) != v {
				t.Errorf("%s expected %s, but got %v", k, v, req.Header.Get(k))
			}
		}
	}
}

func TestHybiClientHandshakeWithHeader(t *testing.T) {
	b := bytes.NewBuffer([]byte{})
	bw := bufio.NewWriter(b)
	br := bufio.NewReader(strings.NewReader(`HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat

`))
	var err error
	config := new(Config)
	config.Location, err = url.ParseRequestURI("ws://server.example.com/chat")
	if err != nil {
		t.Fatal("location url", err)
	}
	config.Origin, err = url.ParseRequestURI("http://example.com")
	if err != nil {
		t.Fatal("origin url", err)
	}
	config.Protocol = append(config.Protocol, "chat")
	config.Protocol = append(config.Protocol, "superchat")
	config.Version = ProtocolVersionHybi13
	config.Header = http.Header(make(map[string][]string))
	config.Header.Add("User-Agent", "test")

	config.handshakeData = map[string]string{
		"key": "dGhlIHNhbXBsZSBub25jZQ==",
	}
	err = hybiClientHandshake(config, br, bw)
	if err != nil {
		t.Errorf("handshake failed: %v", err)
	}
	req, err := http.ReadRequest(bufio.NewReader(b))
	if err != nil {
		t.Fatalf("read request: %v", err)
	}
	if req.Method != "GET" {
		t.Errorf("request method expected GET, but got %q", req.Method)
	}
	if req.URL.Path != "/chat" {
		t.Errorf("request path expected /chat, but got %q", req.URL.Path)
	}
	if req.Proto != "HTTP/1.1" {
		t.Errorf("request proto expected HTTP/1.1, but got %q", req.Proto)
	}
	if req.Host != "server.example.com" {
		t.Errorf("request Host expected server.example.com, but got %v", req.Host)
	}
	var expectedHeader = map[string]string{
		"Connection":             "Upgrade",
		"Upgrade":                "websocket",
		"Sec-Websocket-Key":      config.handshakeData["key"],
		"Origin":                 config.Origin.String(),
		"Sec-Websocket-Protocol": "chat, superchat",
		"Sec-Websocket-Version":  fmt.Sprintf("%d", ProtocolVersionHybi13),
		"User-Agent":             "test",
	}
	for k, v := range expectedHeader {
		if req.Header.Get(k) != v {
			t.Errorf("%s expected %q but got %q", k, v, req.Header.Get(k))
		}
	}
}

func TestHybiServerHandshake(t *testing.T) {
	config := new(Config)
	handshaker := &hybiServerHandshaker{Config: config}
	br := bufio.NewReader(strings.NewReader(`GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13

`))
	req, err := http.ReadRequest(br)
	if err != nil {
		t.Fatal("request", err)
	}
	code, err := handshaker.ReadHandshake(br, req)
	if err != nil {
		t.Errorf("handshake failed: %v", err)
	}
	if code != http.StatusSwitchingProtocols {
		t.Errorf("status expected %q but got %q", http.StatusSwitchingProtocols, code)
	}
	expectedProtocols := []string{"chat", "superchat"}
	if fmt.Sprintf("%v", config.Protocol) != fmt.Sprintf("%v", expectedProtocols) {
		t.Errorf("protocol expected %q but got %q", expectedProtocols, config.Protocol)
	}
	b := bytes.NewBuffer([]byte{})
	bw := bufio.NewWriter(b)

	config.Protocol = config.Protocol[:1]

	err = handshaker.AcceptHandshake(bw)
	if err != nil {
		t.Errorf("handshake response failed: %v", err)
	}
	expectedResponse := strings.Join([]string{
		"HTTP/1.1 101 Switching Protocols",
		"Upgrade: websocket",
		"Connection: Upgrade",
		"Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",
		"Sec-WebSocket-Protocol: chat",
		"", ""}, "\r\n")

	if b.String() != expectedResponse {
		t.Errorf("handshake expected %q but got %q", expectedResponse, b.String())
	}
}

func TestHybiServerHandshakeNoSubProtocol(t *testing.T) {
	config := new(Config)
	handshaker := &hybiServerHandshaker{Config: config}
	br := bufio.NewReader(strings.NewReader(`GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Version: 13

`))
	req, err := http.ReadRequest(br)
	if err != nil {
		t.Fatal("request", err)
	}
	code, err := handshaker.ReadHandshake(br, req)
	if err != nil {
		t.Errorf("handshake failed: %v", err)
	}
	if code != http.StatusSwitchingProtocols {
		t.Errorf("status expected %q but got %q", http.StatusSwitchingProtocols, code)
	}
	if len(config.Protocol) != 0 {
		t.Errorf("len(config.Protocol) expected 0, but got %q", len(config.Protocol))
	}
	b := bytes.NewBuffer([]byte{})
	bw := bufio.NewWriter(b)

	err = handshaker.AcceptHandshake(bw)
	if err != nil {
		t.Errorf("handshake response failed: %v", err)
	}
	expectedResponse := strings.Join([]string{
		"HTTP/1.1 101 Switching Protocols",
		"Upgrade: websocket",
		"Connection: Upgrade",
		"Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",
		"", ""}, "\r\n")

	if b.String() != expectedResponse {
		t.Errorf("handshake expected %q but got %q", expectedResponse, b.String())
	}
}

func TestHybiServerHandshakeHybiBadVersion(t *testing.T) {
	config := new(Config)
	handshaker := &hybiServerHandshaker{Config: config}
	br := bufio.NewReader(strings.NewReader(`GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 9

`))
	req, err := http.ReadRequest(br)
	if err != nil {
		t.Fatal("request", err)
	}
	code, err := handshaker.ReadHandshake(br, req)
	if err != ErrBadWebSocketVersion {
		t.Errorf("handshake expected err %q but got %q", ErrBadWebSocketVersion, err)
	}
	if code != http.StatusBadRequest {
		t.Errorf("status expected %q but got %q", http.StatusBadRequest, code)
	}
}

func testHybiFrame(t *testing.T, testHeader, testPayload, testMaskedPayload []byte, frameHeader *hybiFrameHeader) {
	b := bytes.NewBuffer([]byte{})
	frameWriterFactory := &hybiFrameWriterFactory{bufio.NewWriter(b), false}
	w, _ := frameWriterFactory.NewFrameWriter(TextFrame)
	w.(*hybiFrameWriter).header = frameHeader
	_, err := w.Write(testPayload)
	w.Close()
	if err != nil {
		t.Errorf("Write error %q", err)
	}
	var expectedFrame []byte
	expectedFrame = append(expectedFrame, testHeader...)
	expectedFrame = append(expectedFrame, testMaskedPayload...)
	if !bytes.Equal(expectedFrame, b.Bytes()) {
		t.Errorf("frame expected %q got %q", expectedFrame, b.Bytes())
	}
	frameReaderFactory := &hybiFrameReaderFactory{bufio.NewReader(b)}
	r, err := frameReaderFactory.NewFrameReader()
	if err != nil {
		t.Errorf("Read error %q", err)
	}
	if header := r.HeaderReader(); header == nil {
		t.Errorf("no header")
	} else {
		actualHeader := make([]byte, r.Len())
		n, err := header.Read(actualHeader)
		if err != nil {
			t.Errorf("Read header error %q", err)
		} else {
			if n < len(testHeader) {
				t.Errorf("header too short %q got %q", testHeader, actualHeader[:n])
			}
			if !bytes.Equal(testHeader, actualHeader[:n]) {
				t.Errorf("header expected %q got %q", testHeader, actualHeader[:n])
			}
		}
	}
	if trailer := r.TrailerReader(); trailer != nil {
		t.Errorf("unexpected trailer %q", trailer)
	}
	frame := r.(*hybiFrameReader)
	if frameHeader.Fin != frame.header.Fin ||
		frameHeader.OpCode != frame.header.OpCode ||
		len(testPayload) != int(frame.header.Length) {
		t.Errorf("mismatch %v (%d) vs %v", frameHeader, len(testPayload), frame)
	}
	payload := make([]byte, len(testPayload))
	_, err = r.Read(payload)
	if err != nil && err != io.EOF {
		t.Errorf("read %v", err)
	}
	if !bytes.Equal(testPayload, payload) {
		t.Errorf("payload %q vs %q", testPayload, payload)
	}
}

func TestHybiShortTextFrame(t *testing.T) {
	frameHeader := &hybiFrameHeader{Fin: true, OpCode: TextFrame}
	payload := []byte("hello")
	testHybiFrame(t, []byte{0x81, 0x05}, payload, payload, frameHeader)

	payload = make([]byte, 125)
	testHybiFrame(t, []byte{0x81, 125}, payload, payload, frameHeader)
}

func TestHybiShortMaskedTextFrame(t *testing.T) {
	frameHeader := &hybiFrameHeader{Fin: true, OpCode: TextFrame,
		MaskingKey: []byte{0xcc, 0x55, 0x80, 0x20}}
	payload := []byte("hello")
	maskedPayload := []byte{0xa4, 0x30, 0xec, 0x4c, 0xa3}
	header := []byte{0x81, 0x85}
	header = append(header, frameHeader.MaskingKey...)
	testHybiFrame(t, header, payload, maskedPayload, frameHeader)
}

func TestHybiShortBinaryFrame(t *testing.T) {
	frameHeader := &hybiFrameHeader{Fin: true, OpCode: BinaryFrame}
	payload := []byte("hello")
	testHybiFrame(t, []byte{0x82, 0x05}, payload, payload, frameHeader)

	payload = make([]byte, 125)
	testHybiFrame(t, []byte{0x82, 125}, payload, payload, frameHeader)
}

func TestHybiControlFrame(t *testing.T) {
	payload := []byte("hello")

	frameHeader := &hybiFrameHeader{Fin: true, OpCode: PingFrame}
	testHybiFrame(t, []byte{0x89, 0x05}, payload, payload, frameHeader)

	frameHeader = &hybiFrameHeader{Fin: true, OpCode: PingFrame}
	testHybiFrame(t, []byte{0x89, 0x00}, nil, nil, frameHeader)

	frameHeader = &hybiFrameHeader{Fin: true, OpCode: PongFrame}
	testHybiFrame(t, []byte{0x8A, 0x05}, payload, payload, frameHeader)

	frameHeader = &hybiFrameHeader{Fin: true, OpCode: PongFrame}
	testHybiFrame(t, []byte{0x8A, 0x00}, nil, nil, frameHeader)

	frameHeader = &hybiFrameHeader{Fin: true, OpCode: CloseFrame}
	payload = []byte{0x03, 0xe8} // 1000
	testHybiFrame(t, []byte{0x88, 0x02}, payload, payload, frameHeader)
}

func TestHybiLongFrame(t *testing.T) {
	frameHeader := &hybiFrameHeader{Fin: true, OpCode: TextFrame}
	payload := make([]byte, 126)
	testHybiFrame(t, []byte{0x81, 126, 0x00, 126}, payload, payload, frameHeader)

	payload = make([]byte, 65535)
	testHybiFrame(t, []byte{0x81, 126, 0xff, 0xff}, payload, payload, frameHeader)

	payload = make([]byte, 65536)
	testHybiFrame(t, []byte{0x81, 127, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00}, payload, payload, frameHeader)
}

func TestHybiClientRead(t *testing.T) {
	wireData := []byte{0x81, 0x05, 'h', 'e', 'l', 'l', 'o',
		0x89, 0x05, 'h', 'e', 'l', 'l', 'o', // ping
		0x81, 0x05, 'w', 'o', 'r', 'l', 'd'}
	br := bufio.NewReader(bytes.NewBuffer(wireData))
	bw := bufio.NewWriter(bytes.NewBuffer([]byte{}))
	conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, nil)

	msg := make([]byte, 512)
	n, err := conn.Read(msg)
	if err != nil {
		t.Errorf("read 1st frame, error %q", err)
	}
	if n != 5 {
		t.Errorf("read 1st frame, expect 5, got %d", n)
	}
	if !bytes.Equal(wireData[2:7], msg[:n]) {
		t.Errorf("read 1st frame %v, got %v", wireData[2:7], msg[:n])
	}
	n, err = conn.Read(msg)
	if err != nil {
		t.Errorf("read 2nd frame, error %q", err)
	}
	if n != 5 {
		t.Errorf("read 2nd frame, expect 5, got %d", n)
	}
	if !bytes.Equal(wireData[16:21], msg[:n]) {
		t.Errorf("read 2nd frame %v, got %v", wireData[16:21], msg[:n])
	}
	n, err = conn.Read(msg)
	if err == nil {
		t.Errorf("read not EOF")
	}
	if n != 0 {
		t.Errorf("expect read 0, got %d", n)
	}
}

func TestHybiShortRead(t *testing.T) {
	wireData := []byte{0x81, 0x05, 'h', 'e', 'l', 'l', 'o',
		0x89, 0x05, 'h', 'e', 'l', 'l', 'o', // ping
		0x81, 0x05, 'w', 'o', 'r', 'l', 'd'}
	br := bufio.NewReader(bytes.NewBuffer(wireData))
	bw := bufio.NewWriter(bytes.NewBuffer([]byte{}))
	conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, nil)

	step := 0
	pos := 0
	expectedPos := []int{2, 5, 16, 19}
	expectedLen := []int{3, 2, 3, 2}
	for {
		msg := make([]byte, 3)
		n, err := conn.Read(msg)
		if step >= len(expectedPos) {
			if err == nil {
				t.Errorf("read not EOF")
			}
			if n != 0 {
				t.Errorf("expect read 0, got %d", n)
			}
			return
		}
		pos = expectedPos[step]
		endPos := pos + expectedLen[step]
		if err != nil {
			t.Errorf("read from %d, got error %q", pos, err)
			return
		}
		if n != endPos-pos {
			t.Errorf("read from %d, expect %d, got %d", pos, endPos-pos, n)
		}
		if !bytes.Equal(wireData[pos:endPos], msg[:n]) {
			t.Errorf("read from %d, frame %v, got %v", pos, wireData[pos:endPos], msg[:n])
		}
		step++
	}
}

func TestHybiServerRead(t *testing.T) {
	wireData := []byte{0x81, 0x85, 0xcc, 0x55, 0x80, 0x20,
		0xa4, 0x30, 0xec, 0x4c, 0xa3, // hello
		0x89, 0x85, 0xcc, 0x55, 0x80, 0x20,
		0xa4, 0x30, 0xec, 0x4c, 0xa3, // ping: hello
		0x81, 0x85, 0xed, 0x83, 0xb4, 0x24,
		0x9a, 0xec, 0xc6, 0x48, 0x89, // world
	}
	br := bufio.NewReader(bytes.NewBuffer(wireData))
	bw := bufio.NewWriter(bytes.NewBuffer([]byte{}))
	conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, new(http.Request))

	expected := [][]byte{[]byte("hello"), []byte("world")}

	msg := make([]byte, 512)
	n, err := conn.Read(msg)
	if err != nil {
		t.Errorf("read 1st frame, error %q", err)
	}
	if n != 5 {
		t.Errorf("read 1st frame, expect 5, got %d", n)
	}
	if !bytes.Equal(expected[0], msg[:n]) {
		t.Errorf("read 1st frame %q, got %q", expected[0], msg[:n])
	}

	n, err = conn.Read(msg)
	if err != nil {
		t.Errorf("read 2nd frame, error %q", err)
	}
	if n != 5 {
		t.Errorf("read 2nd frame, expect 5, got %d", n)
	}
	if !bytes.Equal(expected[1], msg[:n]) {
		t.Errorf("read 2nd frame %q, got %q", expected[1], msg[:n])
	}

	n, err = conn.Read(msg)
	if err == nil {
		t.Errorf("read not EOF")
	}
	if n != 0 {
		t.Errorf("expect read 0, got %d", n)
	}
}

func TestHybiServerReadWithoutMasking(t *testing.T) {
	wireData := []byte{0x81, 0x05, 'h', 'e', 'l', 'l', 'o'}
	br := bufio.NewReader(bytes.NewBuffer(wireData))
	bw := bufio.NewWriter(bytes.NewBuffer([]byte{}))
	conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, new(http.Request))
	// server MUST close the connection upon receiving a non-masked frame.
	msg := make([]byte, 512)
	_, err := conn.Read(msg)
	if err != io.EOF {
		t.Errorf("read 1st frame, expect %q, but got %q", io.EOF, err)
	}
}

func TestHybiClientReadWithMasking(t *testing.T) {
	wireData := []byte{0x81, 0x85, 0xcc, 0x55, 0x80, 0x20,
		0xa4, 0x30, 0xec, 0x4c, 0xa3, // hello
	}
	br := bufio.NewReader(bytes.NewBuffer(wireData))
	bw := bufio.NewWriter(bytes.NewBuffer([]byte{}))
	conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, nil)

	// client MUST close the connection upon receiving a masked frame.
	msg := make([]byte, 512)
	_, err := conn.Read(msg)
	if err != io.EOF {
		t.Errorf("read 1st frame, expect %q, but got %q", io.EOF, err)
	}
}

// Test the hybiServerHandshaker supports firefox implementation and
// checks Connection request header include (but it's not necessary
// equal to) "upgrade"
func TestHybiServerFirefoxHandshake(t *testing.T) {
	config := new(Config)
	handshaker := &hybiServerHandshaker{Config: config}
	br := bufio.NewReader(strings.NewReader(`GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: keep-alive, upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13

`))
	req, err := http.ReadRequest(br)
	if err != nil {
		t.Fatal("request", err)
	}
	code, err := handshaker.ReadHandshake(br, req)
	if err != nil {
		t.Errorf("handshake failed: %v", err)
	}
	if code != http.StatusSwitchingProtocols {
		t.Errorf("status expected %q but got %q", http.StatusSwitchingProtocols, code)
	}
	b := bytes.NewBuffer([]byte{})
	bw := bufio.NewWriter(b)

	config.Protocol = []string{"chat"}

	err = handshaker.AcceptHandshake(bw)
	if err != nil {
		t.Errorf("handshake response failed: %v", err)
	}
	expectedResponse := strings.Join([]string{
		"HTTP/1.1 101 Switching Protocols",
		"Upgrade: websocket",
		"Connection: Upgrade",
		"Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",
		"Sec-WebSocket-Protocol: chat",
		"", ""}, "\r\n")

	if b.String() != expectedResponse {
		t.Errorf("handshake expected %q but got %q", expectedResponse, b.String())
	}
}
