diff options
author | Wim <wim@42.be> | 2017-06-22 01:00:27 +0200 |
---|---|---|
committer | Wim <wim@42.be> | 2017-06-22 01:00:27 +0200 |
commit | 1f914618538920db4bfec7b106ee97038b157c9b (patch) | |
tree | 6bd0ab107fe1673dbacdf9dfd10004289cd7bfab /vendor/github.com/Philipp15b/go-steam/client.go | |
parent | 1f9874102aaca09ce5e0289beff376c307b8c57b (diff) | |
download | matterbridge-msglm-1f914618538920db4bfec7b106ee97038b157c9b.tar.gz matterbridge-msglm-1f914618538920db4bfec7b106ee97038b157c9b.tar.bz2 matterbridge-msglm-1f914618538920db4bfec7b106ee97038b157c9b.zip |
Add vendor (steam)
Diffstat (limited to 'vendor/github.com/Philipp15b/go-steam/client.go')
-rw-r--r-- | vendor/github.com/Philipp15b/go-steam/client.go | 383 |
1 files changed, 383 insertions, 0 deletions
diff --git a/vendor/github.com/Philipp15b/go-steam/client.go b/vendor/github.com/Philipp15b/go-steam/client.go new file mode 100644 index 00000000..667ad354 --- /dev/null +++ b/vendor/github.com/Philipp15b/go-steam/client.go @@ -0,0 +1,383 @@ +package steam + +import ( + "bytes" + "compress/gzip" + "crypto/rand" + "encoding/binary" + "fmt" + "hash/crc32" + "io/ioutil" + "net" + "sync" + "sync/atomic" + "time" + + "github.com/Philipp15b/go-steam/cryptoutil" + "github.com/Philipp15b/go-steam/netutil" + . "github.com/Philipp15b/go-steam/protocol" + . "github.com/Philipp15b/go-steam/protocol/protobuf" + . "github.com/Philipp15b/go-steam/protocol/steamlang" + . "github.com/Philipp15b/go-steam/steamid" +) + +// Represents a client to the Steam network. +// Always poll events from the channel returned by Events() or receiving messages will stop. +// All access, unless otherwise noted, should be threadsafe. +// +// When a FatalErrorEvent is emitted, the connection is automatically closed. The same client can be used to reconnect. +// Other errors don't have any effect. +type Client struct { + // these need to be 64 bit aligned for sync/atomic on 32bit + sessionId int32 + _ uint32 + steamId uint64 + currentJobId uint64 + + Auth *Auth + Social *Social + Web *Web + Notifications *Notifications + Trading *Trading + GC *GameCoordinator + + events chan interface{} + handlers []PacketHandler + handlersMutex sync.RWMutex + + tempSessionKey []byte + + ConnectionTimeout time.Duration + + mutex sync.RWMutex // guarding conn and writeChan + conn connection + writeChan chan IMsg + writeBuf *bytes.Buffer + heartbeat *time.Ticker +} + +type PacketHandler interface { + HandlePacket(*Packet) +} + +func NewClient() *Client { + client := &Client{ + events: make(chan interface{}, 3), + writeBuf: new(bytes.Buffer), + } + client.Auth = &Auth{client: client} + client.RegisterPacketHandler(client.Auth) + client.Social = newSocial(client) + client.RegisterPacketHandler(client.Social) + client.Web = &Web{client: client} + client.RegisterPacketHandler(client.Web) + client.Notifications = newNotifications(client) + client.RegisterPacketHandler(client.Notifications) + client.Trading = &Trading{client: client} + client.RegisterPacketHandler(client.Trading) + client.GC = newGC(client) + client.RegisterPacketHandler(client.GC) + return client +} + +// Get the event channel. By convention all events are pointers, except for errors. +// It is never closed. +func (c *Client) Events() <-chan interface{} { + return c.events +} + +func (c *Client) Emit(event interface{}) { + c.events <- event +} + +// Emits a FatalErrorEvent formatted with fmt.Errorf and disconnects. +func (c *Client) Fatalf(format string, a ...interface{}) { + c.Emit(FatalErrorEvent(fmt.Errorf(format, a...))) + c.Disconnect() +} + +// Emits an error formatted with fmt.Errorf. +func (c *Client) Errorf(format string, a ...interface{}) { + c.Emit(fmt.Errorf(format, a...)) +} + +// Registers a PacketHandler that receives all incoming packets. +func (c *Client) RegisterPacketHandler(handler PacketHandler) { + c.handlersMutex.Lock() + defer c.handlersMutex.Unlock() + c.handlers = append(c.handlers, handler) +} + +func (c *Client) GetNextJobId() JobId { + return JobId(atomic.AddUint64(&c.currentJobId, 1)) +} + +func (c *Client) SteamId() SteamId { + return SteamId(atomic.LoadUint64(&c.steamId)) +} + +func (c *Client) SessionId() int32 { + return atomic.LoadInt32(&c.sessionId) +} + +func (c *Client) Connected() bool { + c.mutex.RLock() + defer c.mutex.RUnlock() + return c.conn != nil +} + +// Connects to a random Steam server and returns its address. +// If this client is already connected, it is disconnected first. +// This method tries to use an address from the Steam Directory and falls +// back to the built-in server list if the Steam Directory can't be reached. +// If you want to connect to a specific server, use `ConnectTo`. +func (c *Client) Connect() *netutil.PortAddr { + var server *netutil.PortAddr + if steamDirectoryCache.IsInitialized() { + server = steamDirectoryCache.GetRandomCM() + } else { + server = GetRandomCM() + } + c.ConnectTo(server) + return server +} + +// Connects to a specific server. +// You may want to use one of the `GetRandom*CM()` functions in this package. +// If this client is already connected, it is disconnected first. +func (c *Client) ConnectTo(addr *netutil.PortAddr) { + c.ConnectToBind(addr, nil) +} + +// Connects to a specific server, and binds to a specified local IP +// If this client is already connected, it is disconnected first. +func (c *Client) ConnectToBind(addr *netutil.PortAddr, local *net.TCPAddr) { + c.Disconnect() + + conn, err := dialTCP(local, addr.ToTCPAddr()) + if err != nil { + c.Fatalf("Connect failed: %v", err) + return + } + c.conn = conn + c.writeChan = make(chan IMsg, 5) + + go c.readLoop() + go c.writeLoop() +} + +func (c *Client) Disconnect() { + c.mutex.Lock() + defer c.mutex.Unlock() + + if c.conn == nil { + return + } + + c.conn.Close() + c.conn = nil + if c.heartbeat != nil { + c.heartbeat.Stop() + } + close(c.writeChan) + c.Emit(&DisconnectedEvent{}) + +} + +// Adds a message to the send queue. Modifications to the given message after +// writing are not allowed (possible race conditions). +// +// Writes to this client when not connected are ignored. +func (c *Client) Write(msg IMsg) { + if cm, ok := msg.(IClientMsg); ok { + cm.SetSessionId(c.SessionId()) + cm.SetSteamId(c.SteamId()) + } + c.mutex.RLock() + defer c.mutex.RUnlock() + if c.conn == nil { + return + } + c.writeChan <- msg +} + +func (c *Client) readLoop() { + for { + // This *should* be atomic on most platforms, but the Go spec doesn't guarantee it + c.mutex.RLock() + conn := c.conn + c.mutex.RUnlock() + if conn == nil { + return + } + packet, err := conn.Read() + + if err != nil { + c.Fatalf("Error reading from the connection: %v", err) + return + } + c.handlePacket(packet) + } +} + +func (c *Client) writeLoop() { + for { + c.mutex.RLock() + conn := c.conn + c.mutex.RUnlock() + if conn == nil { + return + } + + msg, ok := <-c.writeChan + if !ok { + return + } + + err := msg.Serialize(c.writeBuf) + if err != nil { + c.writeBuf.Reset() + c.Fatalf("Error serializing message %v: %v", msg, err) + return + } + + err = conn.Write(c.writeBuf.Bytes()) + + c.writeBuf.Reset() + + if err != nil { + c.Fatalf("Error writing message %v: %v", msg, err) + return + } + } +} + +func (c *Client) heartbeatLoop(seconds time.Duration) { + if c.heartbeat != nil { + c.heartbeat.Stop() + } + c.heartbeat = time.NewTicker(seconds * time.Second) + for { + _, ok := <-c.heartbeat.C + if !ok { + break + } + c.Write(NewClientMsgProtobuf(EMsg_ClientHeartBeat, new(CMsgClientHeartBeat))) + } + c.heartbeat = nil +} + +func (c *Client) handlePacket(packet *Packet) { + switch packet.EMsg { + case EMsg_ChannelEncryptRequest: + c.handleChannelEncryptRequest(packet) + case EMsg_ChannelEncryptResult: + c.handleChannelEncryptResult(packet) + case EMsg_Multi: + c.handleMulti(packet) + case EMsg_ClientCMList: + c.handleClientCMList(packet) + } + + c.handlersMutex.RLock() + defer c.handlersMutex.RUnlock() + for _, handler := range c.handlers { + handler.HandlePacket(packet) + } +} + +func (c *Client) handleChannelEncryptRequest(packet *Packet) { + body := NewMsgChannelEncryptRequest() + packet.ReadMsg(body) + + if body.Universe != EUniverse_Public { + c.Fatalf("Invalid univserse %v!", body.Universe) + } + + c.tempSessionKey = make([]byte, 32) + rand.Read(c.tempSessionKey) + encryptedKey := cryptoutil.RSAEncrypt(GetPublicKey(EUniverse_Public), c.tempSessionKey) + + payload := new(bytes.Buffer) + payload.Write(encryptedKey) + binary.Write(payload, binary.LittleEndian, crc32.ChecksumIEEE(encryptedKey)) + payload.WriteByte(0) + payload.WriteByte(0) + payload.WriteByte(0) + payload.WriteByte(0) + + c.Write(NewMsg(NewMsgChannelEncryptResponse(), payload.Bytes())) +} + +func (c *Client) handleChannelEncryptResult(packet *Packet) { + body := NewMsgChannelEncryptResult() + packet.ReadMsg(body) + + if body.Result != EResult_OK { + c.Fatalf("Encryption failed: %v", body.Result) + return + } + c.conn.SetEncryptionKey(c.tempSessionKey) + c.tempSessionKey = nil + + c.Emit(&ConnectedEvent{}) +} + +func (c *Client) handleMulti(packet *Packet) { + body := new(CMsgMulti) + packet.ReadProtoMsg(body) + + payload := body.GetMessageBody() + + if body.GetSizeUnzipped() > 0 { + r, err := gzip.NewReader(bytes.NewReader(payload)) + if err != nil { + c.Errorf("handleMulti: Error while decompressing: %v", err) + return + } + + payload, err = ioutil.ReadAll(r) + if err != nil { + c.Errorf("handleMulti: Error while decompressing: %v", err) + return + } + } + + pr := bytes.NewReader(payload) + for pr.Len() > 0 { + var length uint32 + binary.Read(pr, binary.LittleEndian, &length) + packetData := make([]byte, length) + pr.Read(packetData) + p, err := NewPacket(packetData) + if err != nil { + c.Errorf("Error reading packet in Multi msg %v: %v", packet, err) + continue + } + c.handlePacket(p) + } +} + +func (c *Client) handleClientCMList(packet *Packet) { + body := new(CMsgClientCMList) + packet.ReadProtoMsg(body) + + l := make([]*netutil.PortAddr, 0) + for i, ip := range body.GetCmAddresses() { + l = append(l, &netutil.PortAddr{ + readIp(ip), + uint16(body.GetCmPorts()[i]), + }) + } + + c.Emit(&ClientCMListEvent{l}) +} + +func readIp(ip uint32) net.IP { + r := make(net.IP, 4) + r[3] = byte(ip) + r[2] = byte(ip >> 8) + r[1] = byte(ip >> 16) + r[0] = byte(ip >> 24) + return r +} |