blob: d5996abe1a5836461b9503efbece00a31fbab28b [file] [log] [blame]
// 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.
// The websocket package implements a client and server for the Web Socket protocol.
// The protocol is defined at http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol
package websocket
// TODO(ukai):
// better logging.
import (
"bufio"
"crypto/md5"
"encoding/binary"
"io"
"net"
"os"
)
// WebSocketAddr is an implementation of net.Addr for Web Sockets.
type WebSocketAddr string
// Network returns the network type for a Web Socket, "websocket".
func (addr WebSocketAddr) Network() string { return "websocket" }
// String returns the network address for a Web Socket.
func (addr WebSocketAddr) String() string { return string(addr) }
const (
stateFrameByte = iota
stateFrameLength
stateFrameData
stateFrameTextData
)
// Conn is a channel to communicate to a Web Socket.
// It implements the net.Conn interface.
type Conn struct {
// The origin URI for the Web Socket.
Origin string
// The location URI for the Web Socket.
Location string
// The subprotocol for the Web Socket.
Protocol string
buf *bufio.ReadWriter
rwc io.ReadWriteCloser
// It holds text data in previous Read() that failed with small buffer.
data []byte
reading bool
}
// newConn creates a new Web Socket.
func newConn(origin, location, protocol string, buf *bufio.ReadWriter, rwc io.ReadWriteCloser) *Conn {
if buf == nil {
br := bufio.NewReader(rwc)
bw := bufio.NewWriter(rwc)
buf = bufio.NewReadWriter(br, bw)
}
ws := &Conn{Origin: origin, Location: location, Protocol: protocol, buf: buf, rwc: rwc}
return ws
}
// Read implements the io.Reader interface for a Conn.
func (ws *Conn) Read(msg []byte) (n int, err os.Error) {
Frame:
for !ws.reading && len(ws.data) == 0 {
// Beginning of frame, possibly.
b, err := ws.buf.ReadByte()
if err != nil {
return 0, err
}
if b&0x80 == 0x80 {
// Skip length frame.
length := 0
for {
c, err := ws.buf.ReadByte()
if err != nil {
return 0, err
}
length = length*128 + int(c&0x7f)
if c&0x80 == 0 {
break
}
}
for length > 0 {
_, err := ws.buf.ReadByte()
if err != nil {
return 0, err
}
}
continue Frame
}
// In text mode
if b != 0 {
// Skip this frame
for {
c, err := ws.buf.ReadByte()
if err != nil {
return 0, err
}
if c == '\xff' {
break
}
}
continue Frame
}
ws.reading = true
}
if len(ws.data) == 0 {
ws.data, err = ws.buf.ReadSlice('\xff')
if err == nil {
ws.reading = false
ws.data = ws.data[:len(ws.data)-1] // trim \xff
}
}
n = copy(msg, ws.data)
ws.data = ws.data[n:]
return n, err
}
// Write implements the io.Writer interface for a Conn.
func (ws *Conn) Write(msg []byte) (n int, err os.Error) {
ws.buf.WriteByte(0)
ws.buf.Write(msg)
ws.buf.WriteByte(0xff)
err = ws.buf.Flush()
return len(msg), err
}
// Close implements the io.Closer interface for a Conn.
func (ws *Conn) Close() os.Error { return ws.rwc.Close() }
// LocalAddr returns the WebSocket Origin for the connection.
func (ws *Conn) LocalAddr() net.Addr { return WebSocketAddr(ws.Origin) }
// RemoteAddr returns the WebSocket locations for the connection.
func (ws *Conn) RemoteAddr() net.Addr { return WebSocketAddr(ws.Location) }
// SetTimeout sets the connection's network timeout in nanoseconds.
func (ws *Conn) SetTimeout(nsec int64) os.Error {
if conn, ok := ws.rwc.(net.Conn); ok {
return conn.SetTimeout(nsec)
}
return os.EINVAL
}
// SetReadTimeout sets the connection's network read timeout in nanoseconds.
func (ws *Conn) SetReadTimeout(nsec int64) os.Error {
if conn, ok := ws.rwc.(net.Conn); ok {
return conn.SetReadTimeout(nsec)
}
return os.EINVAL
}
// SetWritetTimeout sets the connection's network write timeout in nanoseconds.
func (ws *Conn) SetWriteTimeout(nsec int64) os.Error {
if conn, ok := ws.rwc.(net.Conn); ok {
return conn.SetWriteTimeout(nsec)
}
return os.EINVAL
}
// getChallengeResponse computes the expected response from the
// challenge as described in section 5.1 Opening Handshake steps 42 to
// 43 of http://www.whatwg.org/specs/web-socket-protocol/
func getChallengeResponse(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)
binary.BigEndian.PutUint32(challenge[0:], number1)
binary.BigEndian.PutUint32(challenge[4:], 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
}
var _ net.Conn = (*Conn)(nil) // compile-time check that *Conn implements net.Conn.