mirror of
https://github.com/hamburghammer/grcon.git
synced 2024-12-23 05:27:42 +01:00
Augusto Dwenger J
1cda9ca591
Initially it was the idea that this would be version 2 of the https://github.com/hamburghammer/rcon lib but I changed my mind and decided to make a new Project out of it. It has formally nothing to do with the original lib.
219 lines
5.6 KiB
Go
219 lines
5.6 KiB
Go
/*
|
|
A Go written library for the RCON Protocol from Valve.
|
|
|
|
Information to the protocol can be found under:
|
|
https://developer.valvesoftware.com/wiki/Source_RCON_Protocol
|
|
*/
|
|
package grcon
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"net"
|
|
"sync"
|
|
)
|
|
|
|
// NewRemoteConsole creates a new RemoteConsole with the given connection and default values.
|
|
func NewRemoteConsole(conn net.Conn) *RemoteConsole {
|
|
remoteConsole := &RemoteConsole{
|
|
Conn: conn,
|
|
// Initialize a buffer to hold at least a hole packet:
|
|
// Since the only one of the packet values that can change in length is the body,
|
|
// an easy way to calculate the size of a packet is to find the byte-length of the packet body,
|
|
// then add 10 to it.
|
|
ReadBuff: make([]byte, MaxPacket+sizeField),
|
|
}
|
|
|
|
return remoteConsole
|
|
}
|
|
|
|
// RemoteConsole holds the information to communicate withe remote console (server).
|
|
// To optain a preconfigured RemoteConsole use the NewRemoteConsole() function.
|
|
// The NewRemoteConsole() function is also the recommended way to get a *RemoteConsole.
|
|
//
|
|
// This struct can be used concurrently.
|
|
// All exported fields are not allowed to be nil!
|
|
type RemoteConsole struct {
|
|
// Conn is the connection to read and write to.
|
|
Conn net.Conn
|
|
|
|
// ReadBuff should at least have the capacity for a hole packet.
|
|
// Capacity >= MaxPacket + 4.
|
|
ReadBuff []byte
|
|
|
|
readMutex sync.Mutex
|
|
queuedBuff []byte
|
|
}
|
|
|
|
// Write writes a packet with a given id, type and body.
|
|
// The body should be a ASCII string.
|
|
// Returns an RequestTooLongError if the body is greater than the max
|
|
// length which is MaxPacket - MinPacket.
|
|
func (r *RemoteConsole) Write(packet Packet) error {
|
|
bodySize := size(len(packet.Body))
|
|
if bodySize > MaxBody {
|
|
return newRequestTooLongError()
|
|
}
|
|
|
|
buffer := bytes.NewBuffer(make([]byte, 0, MinPacket+sizeField+bodySize))
|
|
|
|
// size
|
|
err := binary.Write(buffer, binary.LittleEndian, bodySize+MinPacket)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// id
|
|
err = binary.Write(buffer, binary.LittleEndian, packet.Id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// type
|
|
err = binary.Write(buffer, binary.LittleEndian, packet.Type)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// body
|
|
_, err = buffer.Write(packet.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// double null termination
|
|
err = binary.Write(buffer, binary.LittleEndian, byte(0))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = binary.Write(buffer, binary.LittleEndian, byte(0))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// writing to the connection
|
|
_, err = r.Conn.Write(buffer.Bytes())
|
|
|
|
return err
|
|
}
|
|
|
|
// Read returns all the parts of the read packet.
|
|
// Returns an ResponseTooLongError if the size of the packet is bigger
|
|
// than the MaxPacket siz. It can also return an UnexpectedForamatError
|
|
// if the packet size is smaller than the MinPacket size.
|
|
func (r *RemoteConsole) Read() (Packet, error) {
|
|
r.readMutex.Lock()
|
|
defer r.readMutex.Unlock()
|
|
|
|
var readBytes int
|
|
var err error
|
|
if r.queuedBuff != nil {
|
|
copy(r.ReadBuff, r.queuedBuff)
|
|
readBytes = len(r.queuedBuff)
|
|
r.queuedBuff = nil
|
|
} else {
|
|
readBytes, err = r.Conn.Read(r.ReadBuff)
|
|
if err != nil {
|
|
return Packet{}, err
|
|
}
|
|
}
|
|
|
|
dataSize, readBytes, err := r.readPacketSize(readBytes)
|
|
if err != nil {
|
|
return Packet{}, err
|
|
}
|
|
|
|
if dataSize > MaxPacket {
|
|
return Packet{}, newResponseTooLongError()
|
|
}
|
|
|
|
totalPacketSize := dataSize + sizeField
|
|
readBytes, err = r.readPacket(totalPacketSize, readBytes)
|
|
if err != nil {
|
|
return Packet{}, err
|
|
}
|
|
|
|
// The data has to be explicitly selected to prevent copying empty bytes.
|
|
data := r.ReadBuff[sizeField:totalPacketSize]
|
|
|
|
// Save not packet related bytes for the next read.
|
|
if readBytes > int(totalPacketSize) {
|
|
// start of the next buffer was at the end of this packet.
|
|
// save it for the next read.
|
|
// The data has to be explicitly selected to prevent copying empty bytes.
|
|
r.queuedBuff = r.ReadBuff[totalPacketSize:readBytes]
|
|
}
|
|
|
|
return r.parsePacket(data)
|
|
}
|
|
|
|
// readPacketSize wait until first 4 bytes are read to get the packet size.
|
|
// Takes as param how many bytes are already read. The returned size does not include the size field.
|
|
func (r *RemoteConsole) readPacketSize(readBytes int) (size, int, error) {
|
|
for readBytes < int(sizeField) {
|
|
// need the 4 byte packet size...
|
|
b, err := r.Conn.Read(r.ReadBuff[readBytes:])
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
readBytes += b
|
|
}
|
|
|
|
// Does not include the packetSize field.
|
|
var totalPacketSize size
|
|
b := bytes.NewBuffer(r.ReadBuff[:sizeField])
|
|
err := binary.Read(b, binary.LittleEndian, &totalPacketSize)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
|
|
if totalPacketSize < MinPacket {
|
|
return 0, 0, newUnexpectedFormatError()
|
|
}
|
|
|
|
return totalPacketSize, readBytes, nil
|
|
}
|
|
|
|
// readPacket waits until the whole packet is read including the size field.
|
|
func (r *RemoteConsole) readPacket(totalPacketSize size, readBytes int) (int, error) {
|
|
for int(totalPacketSize) > readBytes {
|
|
b, err := r.Conn.Read(r.ReadBuff[readBytes:])
|
|
if err != nil {
|
|
return readBytes, err
|
|
}
|
|
|
|
readBytes += b
|
|
}
|
|
|
|
return readBytes, nil
|
|
}
|
|
|
|
// parsePacket reads the a packet from an array byte.
|
|
// The array has only to contain the 'id', 'type' and 'body' data.
|
|
func (r *RemoteConsole) parsePacket(data []byte) (Packet, error) {
|
|
var requestID, responseType int32
|
|
buffer := bytes.NewBuffer(data)
|
|
|
|
err := binary.Read(buffer, binary.LittleEndian, &requestID)
|
|
if err != nil {
|
|
return Packet{}, err
|
|
}
|
|
|
|
binary.Read(buffer, binary.LittleEndian, &responseType)
|
|
if err != nil {
|
|
return Packet{}, err
|
|
}
|
|
|
|
// the rest of the buffer is the body.
|
|
body := buffer.Bytes()
|
|
// remove the to null terminations
|
|
body = body[:len(body)-2]
|
|
|
|
parsedPacket := Packet{
|
|
Id: PacketId(requestID),
|
|
Type: PacketType(responseType),
|
|
Body: body,
|
|
}
|
|
|
|
return parsedPacket, nil
|
|
}
|