// Copyright 2009 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 (
	"encoding/binary"
	"bufio"
	"bytes"
	"container/vector"
	"crypto/md5"
	"fmt"
	"http"
	"io"
	"net"
	"os"
	"rand"
	"strings"
)

type ProtocolError struct {
	os.ErrorString
}

var (
	ErrBadStatus            = &ProtocolError{"bad status"}
	ErrBadUpgrade           = &ProtocolError{"missing or bad upgrade"}
	ErrBadWebSocketOrigin   = &ProtocolError{"missing or bad WebSocket-Origin"}
	ErrBadWebSocketLocation = &ProtocolError{"missing or bad WebSocket-Location"}
	ErrBadWebSocketProtocol = &ProtocolError{"missing or bad WebSocket-Protocol"}
	ErrChallengeResponse    = &ProtocolError{"mismatch challange/response"}
	secKeyRandomChars       [0x30 - 0x21 + 0x7F - 0x3A]byte
)

func init() {
	i := 0
	for ch := byte(0x21); ch < 0x30; ch++ {
		secKeyRandomChars[i] = ch
		i++
	}
	for ch := byte(0x3a); ch < 0x7F; ch++ {
		secKeyRandomChars[i] = ch
		i++
	}
}

type handshaker func(resourceName, host, origin, location, protocol string, br *bufio.Reader, bw *bufio.Writer) os.Error

// newClient creates a new Web Socket client connection.
func newClient(resourceName, host, origin, location, protocol string, rwc io.ReadWriteCloser, handshake handshaker) (ws *Conn, err os.Error) {
	br := bufio.NewReader(rwc)
	bw := bufio.NewWriter(rwc)
	err = handshake(resourceName, host, origin, location, protocol, br, bw)
	if err != nil {
		return
	}
	buf := bufio.NewReadWriter(br, bw)
	ws = newConn(origin, location, protocol, buf, rwc)
	return
}

/*
Dial opens a new client connection to a Web Socket.

A trivial example client:

	package main

	import (
		"websocket"
		"strings"
	)

	func main() {
	 	ws, err := websocket.Dial("ws://localhost/ws", "", "http://localhost/");
	 	if err != nil {
			panic("Dial: " + err.String())
		}
		if _, err := ws.Write([]byte("hello, world!\n")); err != nil {
			panic("Write: " + err.String())
		}
		var msg = make([]byte, 512);
		if n, err := ws.Read(msg); err != nil {
			panic("Read: " + err.String())
		}
		// use msg[0:n]
	}
*/
func Dial(url, protocol, origin string) (ws *Conn, err os.Error) {
	parsedUrl, err := http.ParseURL(url)
	if err != nil {
		return
	}
	client, err := net.Dial("tcp", "", parsedUrl.Host)
	if err != nil {
		return
	}
	return newClient(parsedUrl.RawPath, parsedUrl.Host, origin, url, protocol, client, handshake)
}

/*
Generates handshake key as described in 4.1 Opening handshake step 16 to 22.
cf. http://www.whatwg.org/specs/web-socket-protocol/
*/
func generateKeyNumber() (key string, number uint32) {
	// 16.  Let /spaces_n/ be a random integer from 1 to 12 inclusive.
	spaces := rand.Intn(12) + 1

	// 17. Let /max_n/ be the largest integer not greater than
	//     4,294,967,295 divided by /spaces_n/
	max := int(4294967295 / uint32(spaces))

	// 18. Let /number_n/ be a random integer from 0 to /max_n/ inclusive.
	number = uint32(rand.Intn(max + 1))

	// 19. Let /product_n/ be the result of multiplying /number_n/ and
	//     /spaces_n/ together.
	product := number * uint32(spaces)

	// 20. Let /key_n/ be a string consisting of /product_n/, expressed
	// in base ten using the numerals in the range U+0030 DIGIT ZERO (0)
	// to U+0039 DIGIT NINE (9).
	key = fmt.Sprintf("%d", product)

	// 21. Insert /spaces_n/ U+0020 SPACE characters into /key_n/ at random
	//     posisions.
	for i := 0; i < spaces; i++ {
		pos := rand.Intn(len(key)-1) + 1
		key = key[0:pos] + " " + key[pos:]
	}

	// 22. Insert between one and twelve random characters from the ranges
	//     U+0021 to U+002F and U+003A to U+007E into /key_n/ at random
	//     positions.
	n := rand.Intn(12) + 1
	for i := 0; i < n; i++ {
		pos := rand.Intn(len(key)) + 1
		ch := secKeyRandomChars[rand.Intn(len(secKeyRandomChars))]
		key = key[0:pos] + string(ch) + key[pos:]
	}
	return
}

/*
Generates handshake key_3 as described in 4.1 Opening handshake step 26.
cf. http://www.whatwg.org/specs/web-socket-protocol/
*/
func generateKey3() (key []byte) {
	// 26. Let /key3/ be a string consisting of eight random bytes (or
	//  equivalently, a random 64 bit integer encoded in big-endian order).
	key = make([]byte, 8)
	for i := 0; i < 8; i++ {
		key[i] = byte(rand.Intn(256))
	}
	return
}

/*
Gets expected from challenge as described in 4.1 Opening handshake
step 42 to 43.
cf. http://www.whatwg.org/specs/web-socket-protocol/
*/
func getExpectedForChallenge(number1, number2 uint32, key3 []byte) (expected []byte, err os.Error) {
	// 41. Let /challenge/ be the concatenation of /number_1/, expressed
	// a big-endian 32 bit integer, /number_2/, expressed in a big-
	// endian 32 bit integer, and the eight bytes of /key_3/ in the
	// order they were sent to the wire.
	challenge := make([]byte, 16)
	challengeBuf := bytes.NewBuffer(challenge)
	binary.Write(challengeBuf, binary.BigEndian, number1)
	binary.Write(challengeBuf, binary.BigEndian, number2)
	copy(challenge[8:], key3)

	// 42. Let /expected/ be the MD5 fingerprint of /challenge/ as a big-
	// endian 128 bit string.
	h := md5.New()
	if _, err = h.Write(challenge); err != nil {
		return
	}
	expected = h.Sum()
	return
}

/*
Web Socket protocol handshake based on
http://www.whatwg.org/specs/web-socket-protocol/
(draft of http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol)
*/
func handshake(resourceName, host, origin, location, protocol string, br *bufio.Reader, bw *bufio.Writer) (err os.Error) {
	// 4.1. Opening handshake.
	// Step 5.  send a request line.
	bw.WriteString("GET " + resourceName + " HTTP/1.1\r\n")

	// Step 6-14. push request headers in fields.
	var fields vector.StringVector
	fields.Push("Upgrade: WebSocket\r\n")
	fields.Push("Connection: Upgrade\r\n")
	fields.Push("Host: " + host + "\r\n")
	fields.Push("Origin: " + origin + "\r\n")
	if protocol != "" {
		fields.Push("Sec-WebSocket-Protocol: " + protocol + "\r\n")
	}
	// TODO(ukai): Step 15. send cookie if any.

	// Step 16-23. generate keys and push Sec-WebSocket-Key<n> in fields.
	key1, number1 := generateKeyNumber()
	key2, number2 := generateKeyNumber()
	fields.Push("Sec-WebSocket-Key1: " + key1 + "\r\n")
	fields.Push("Sec-WebSocket-Key2: " + key2 + "\r\n")

	// Step 24. shuffle fields and send them out.
	for i := 1; i < len(fields); i++ {
		j := rand.Intn(i)
		fields[i], fields[j] = fields[j], fields[i]
	}
	for i := 0; i < len(fields); i++ {
		bw.WriteString(fields[i])
	}
	// Step 25. send CRLF.
	bw.WriteString("\r\n")

	// Step 26. genearte 8 bytes random key.
	key3 := generateKey3()
	// Step 27. send it out.
	bw.Write(key3)
	if err = bw.Flush(); err != nil {
		return
	}

	// Step 28-29, 32-40. read response from server.
	resp, err := http.ReadResponse(br, "GET")
	if err != nil {
		return err
	}
	// Step 30. check response code is 101.
	if resp.StatusCode != 101 {
		return ErrBadStatus
	}

	// Step 41. check websocket headers.
	if resp.Header["Upgrade"] != "WebSocket" ||
		strings.ToLower(resp.Header["Connection"]) != "upgrade" {
		return ErrBadUpgrade
	}

	if resp.Header["Sec-Websocket-Origin"] != origin {
		return ErrBadWebSocketOrigin
	}

	if resp.Header["Sec-Websocket-Location"] != location {
		return ErrBadWebSocketLocation
	}

	if protocol != "" && resp.Header["Sec-Websocket-Protocol"] != protocol {
		return ErrBadWebSocketProtocol
	}

	// Step 42-43. get expected data from challange data.
	expected, err := getExpectedForChallenge(number1, number2, key3)
	if err != nil {
		return err
	}

	// Step 44. read 16 bytes from server.
	reply := make([]byte, 16)
	if _, err = io.ReadFull(br, reply); err != nil {
		return err
	}

	// Step 45. check the reply equals to expected data.
	if !bytes.Equal(expected, reply) {
		return ErrChallengeResponse
	}
	// WebSocket connection is established.
	return
}

/*
Handhake described in (soon obsolete)
draft-hixie-thewebsocket-protocol-75.
*/
func draft75handshake(resourceName, host, origin, location, protocol string, br *bufio.Reader, bw *bufio.Writer) (err os.Error) {
	bw.WriteString("GET " + resourceName + " HTTP/1.1\r\n")
	bw.WriteString("Upgrade: WebSocket\r\n")
	bw.WriteString("Connection: Upgrade\r\n")
	bw.WriteString("Host: " + host + "\r\n")
	bw.WriteString("Origin: " + origin + "\r\n")
	if protocol != "" {
		bw.WriteString("WebSocket-Protocol: " + protocol + "\r\n")
	}
	bw.WriteString("\r\n")
	bw.Flush()
	resp, err := http.ReadResponse(br, "GET")
	if err != nil {
		return
	}
	if resp.Status != "101 Web Socket Protocol Handshake" {
		return ErrBadStatus
	}
	if resp.Header["Upgrade"] != "WebSocket" ||
		resp.Header["Connection"] != "Upgrade" {
		return ErrBadUpgrade
	}
	if resp.Header["Websocket-Origin"] != origin {
		return ErrBadWebSocketOrigin
	}
	if resp.Header["Websocket-Location"] != location {
		return ErrBadWebSocketLocation
	}
	if protocol != "" && resp.Header["Websocket-Protocol"] != protocol {
		return ErrBadWebSocketProtocol
	}
	return
}
