1
0
Fork 0
forked from External/grumble
grumble/client.go
2010-09-20 15:14:22 +02:00

290 lines
6.6 KiB
Go

// Copyright (c) 2010 The Grumble Authors
// The use of this source code is goverened by a BSD-style
// license that can be found in the LICENSE-file.
package main
import (
"net"
"bufio"
"log"
"os"
"encoding/binary"
"goprotobuf.googlecode.com/hg/proto"
"mumbleproto"
"cryptstate"
"packetdatastream"
)
// A client connection
type ClientConnection struct {
// Connection-related
tcpaddr *net.TCPAddr
udpaddr *net.UDPAddr
conn net.Conn
reader *bufio.Reader
writer *bufio.Writer
state int
server *Server
msgchan chan *Message
udprecv chan []byte
disconnected bool
crypt *cryptstate.CryptState
codecs []int32
udp bool
// Personal
Session uint32
Username string
Tokens []string
}
// Something invalid happened on the wire.
func (client *ClientConnection) Panic(reason string) {
client.disconnected = true
// fixme(mkrautz): we should inform the server "handler" method through a channel of this event,
// so it can perform a proper disconnect.
}
// Read a protobuf message from a client
func (client *ClientConnection) readProtoMessage() (msg *Message, err os.Error) {
var length uint32
var kind uint16
// Read the message type (16-bit big-endian unsigned integer)
err = binary.Read(client.reader, binary.BigEndian, &kind)
if err != nil {
client.Panic("Unable to read packet kind")
return
}
// Read the message length (32-bit big-endian unsigned integer)
err = binary.Read(client.reader, binary.BigEndian, &length)
if err != nil {
client.Panic("Unable to read packet length")
return
}
buf := make([]byte, length)
_, err = client.reader.Read(buf)
if err != nil {
client.Panic("Unable to read packet content")
return
}
msg = &Message{
buf: buf,
kind: kind,
client: client,
}
return
}
// Send a protobuf-encoded message
func (c *ClientConnection) sendProtoMessage(kind uint16, msg interface{}) (err os.Error) {
d, err := proto.Marshal(msg)
if err != nil {
return
}
c.msgchan <- &Message{
buf: d,
kind: kind,
}
return
}
// UDP receiver.
func (client *ClientConnection) udpreceiver() {
for {
buf := <-client.udprecv
kind := (buf[0] >> 5) & 0x07;
switch kind {
case UDPMessageVoiceSpeex: fallthrough;
case UDPMessageVoiceCELTAlpha: fallthrough;
case UDPMessageVoiceCELTBeta:
kind := buf[0] & 0xe0
target := buf[0] & 0x1f
var counter uint8
outbuf := make([]byte, 1024)
incoming := packetdatastream.New(buf[1:1+(len(buf)-1)])
outgoing := packetdatastream.New(outbuf[1:1+(len(outbuf)-1)])
_ = incoming.GetUint32()
for {
counter = incoming.Next8()
incoming.Skip(int(counter & 0x7f))
if !((counter & 0x80) != 0 && incoming.IsValid()) {
break
}
}
outgoing.PutUint32(client.Session)
outgoing.PutBytes(buf[1:1+(len(buf)-1)])
// Sever loopback
if target == 0x1f {
outbuf[0] = kind
client.sendUdp(&Message{
buf: outbuf[0:1+outgoing.Size()],
client: client,
})
}
case UDPMessagePing:
client.server.udpsend <- &Message{
buf: buf,
client: client,
}
}
}
}
func (client *ClientConnection) sendUdp(msg *Message) {
if client.udp {
// Send as UDP
log.Stdoutf("Sent UDP!")
client.server.udpsend <- msg
} else {
// Tunnel through TCP
log.Stdoutf("Sent TCP!")
msg.kind = MessageUDPTunnel
client.msgchan <- msg
}
}
//
// Sender Goroutine
//
func (client *ClientConnection) sender() {
for {
msg := <-client.msgchan
// First, we write out the message type as a big-endian uint16
err := binary.Write(client.writer, binary.BigEndian, msg.kind)
if err != nil {
client.Panic("Unable to write message type to client")
return
}
// Then the length of the protobuf message
err = binary.Write(client.writer, binary.BigEndian, uint32(len(msg.buf)))
if err != nil {
client.Panic("Unable to write message length to client")
return
}
// At last, write the buffer itself
_, err = client.writer.Write(msg.buf)
if err != nil {
client.Panic("Unable to write message content to client")
return
}
// Flush the write buffer
err = client.writer.Flush()
if err != nil {
client.Panic("Unable to flush client write buffer")
return
}
}
}
// Receiver Goroutine
func (client *ClientConnection) receiver() {
for {
// The version handshake is done. Forward this message to the synchronous request handler.
if client.state == StateClientAuthenticated || client.state == StateClientSentVersion {
// Try to read the next message in the pool
msg, err := client.readProtoMessage()
if err != nil {
return
}
// Special case UDPTunnel messages. They're high priority and shouldn't
// go through our synchronous path.
if msg.kind == MessageUDPTunnel {
client.udp = false
client.udprecv <- msg.buf
} else {
client.server.incoming <- msg
}
}
// The client has just connected. Before it sends its authentication
// information we must send it our version information so it knows
// what version of the protocol it should speak.
if client.state == StateClientConnected {
client.sendProtoMessage(MessageVersion, &mumbleproto.Version{
Version: proto.Uint32(0x10203),
Release: proto.String("1.2.2"),
})
// fixme(mkrautz): Re-add OS information... Does it break anything? It seems like
// the client discards the version message if there is no OS information in it.
client.state = StateServerSentVersion
continue
} else if client.state == StateServerSentVersion {
msg, err := client.readProtoMessage()
if err != nil {
return
}
version := &mumbleproto.Version{}
err = proto.Unmarshal(msg.buf, version)
if err != nil {
client.Panic("Unable to unmarshal client version packet.")
return
}
// Don't really do anything with it...
client.state = StateClientSentVersion
}
}
}
// Send the channel list to a client.
func (client *ClientConnection) sendChannelList() {
server := client.server
root := server.root
// Start at the root channel.
err := client.sendProtoMessage(MessageChannelState, &mumbleproto.ChannelState{
ChannelId: proto.Uint32(uint32(root.Id)),
Name: proto.String(root.Name),
Description: proto.String(root.Description),
})
if err != nil {
// panic!
log.Stdoutf("poanic!")
}
}
// Send the userlist to a client.
func (client *ClientConnection) sendUserList() {
server := client.server
server.cmutex.RLock()
defer server.cmutex.RUnlock()
for x := range server.clients.Iter() {
user := x.(*ClientConnection)
err := user.sendProtoMessage(MessageUserState, &mumbleproto.UserState{
Session: proto.Uint32(client.Session),
Name: proto.String(client.Username),
ChannelId: proto.Uint32(0),
})
if err != nil {
log.Stdoutf("unable to send!")
continue
}
}
}