137 lines
3.8 KiB
Go
137 lines
3.8 KiB
Go
package client
|
|
|
|
import (
|
|
"bytes"
|
|
|
|
"github.com/hamburghammer/grcon"
|
|
"github.com/hamburghammer/grcon/util"
|
|
)
|
|
|
|
// NewSimpleClient is a constructor for the SimpleClient struct.
|
|
// The util.GenerateNewId can be used as idGenFunc.
|
|
func NewSimpleClient(r util.RemoteConsole, idGenFunc func() grcon.PacketId) SimpleClient {
|
|
return SimpleClient{RemoteConsole: r, IdGenFunc: idGenFunc}
|
|
}
|
|
|
|
// SimpleClient is a wrapper for a RemoteConsole that provides some utility functions.
|
|
// It simplifies the interaction with a remote console.
|
|
type SimpleClient struct {
|
|
// RemoteConsole is the console to use for the interactions.
|
|
util.RemoteConsole
|
|
// IdGenFunc is the function to use to generate ids.
|
|
IdGenFunc func() grcon.PacketId
|
|
}
|
|
|
|
// Auth should be used to authenticate the connection.
|
|
//
|
|
// It expects to receive an empty initial response value packet.
|
|
// https://developer.valvesoftware.com/wiki/Source_RCON_Protocol#SERVERDATA_AUTH_RESPONSE
|
|
//
|
|
// It can return following errors:
|
|
// - InvalidResponseTypeError
|
|
// - ResponseIdMismatchError
|
|
// - ResponseBodyError
|
|
// - AuthFailedError
|
|
func (sc SimpleClient) Auth(password string) error {
|
|
reqID := sc.IdGenFunc()
|
|
err := sc.Write(grcon.Packet{Id: reqID, Type: grcon.SERVERDATA_AUTH, Body: []byte(password)})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// read first empty SERVERDATA_RESPONSE_VALUE
|
|
packet, err := sc.Read()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if packet.Type != grcon.SERVERDATA_RESPONSE_VALUE {
|
|
return newInvalidResponseTypeError(grcon.SERVERDATA_RESPONSE_VALUE, packet.Type)
|
|
}
|
|
if packet.Id != reqID {
|
|
return newResponseIdMismatchError(reqID, packet.Id)
|
|
}
|
|
// check if response is empty
|
|
if !bytes.Equal(packet.Body, []byte{}) {
|
|
return newResponseBodyError(string([]byte{}), string(packet.Body))
|
|
}
|
|
|
|
// read final SERVERDATA_AUTH_RESPONSE
|
|
packet, err = sc.Read()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if packet.Type != grcon.SERVERDATA_AUTH_RESPONSE {
|
|
return newInvalidResponseTypeError(grcon.SERVERDATA_AUTH_RESPONSE, packet.Type)
|
|
}
|
|
if packet.Id == -1 {
|
|
return newAuthFailedError()
|
|
}
|
|
if packet.Id != reqID {
|
|
return newResponseIdMismatchError(reqID, packet.Id)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Exec executes the command on the given RemoteConsole implementation and
|
|
// waits till the response is read returns it.
|
|
// Supports multi-packet responses.
|
|
//
|
|
// The server has to response synchronously!
|
|
//
|
|
// Errors:
|
|
// Returns all errors returned from the Write and Read methode from the RemoteConsole implementation.
|
|
// Can also return an InvalidResponseTypeError if the response is not of the type
|
|
// grcon.SERVERDATA_RESPONSE_VALUE.
|
|
func (sc SimpleClient) Exec(cmd string) ([]byte, error) {
|
|
cmdPacket := grcon.Packet{
|
|
Id: sc.IdGenFunc(),
|
|
Type: grcon.SERVERDATA_EXECCOMMAND,
|
|
Body: []byte(cmd),
|
|
}
|
|
err := sc.Write(cmdPacket)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
// write delimiter packet
|
|
delimiterPacket := grcon.Packet{
|
|
Id: sc.IdGenFunc(),
|
|
Type: grcon.SERVERDATA_RESPONSE_VALUE,
|
|
Body: []byte(""),
|
|
}
|
|
err = sc.Write(delimiterPacket)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
// we assume that it won't be a multi packet response by giving the slice an initial capacity of 1.
|
|
responsePackets := make([]grcon.Packet, 0, 1)
|
|
|
|
// read until delimiterPacket is reached.
|
|
for {
|
|
packet, err := sc.Read()
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
if packet.Type != grcon.SERVERDATA_RESPONSE_VALUE {
|
|
return []byte{}, newInvalidResponseTypeError(grcon.SERVERDATA_RESPONSE_VALUE, packet.Type)
|
|
}
|
|
// early break if delimiter packet is read.
|
|
if packet.Id == delimiterPacket.Id {
|
|
break
|
|
}
|
|
if packet.Id != cmdPacket.Id {
|
|
return []byte{}, newResponseIdMismatchError(cmdPacket.Id, packet.Id)
|
|
}
|
|
responsePackets = append(responsePackets, packet)
|
|
}
|
|
|
|
// concatenate bodies
|
|
response := make([]byte, 0, len(responsePackets))
|
|
for _, packet := range responsePackets {
|
|
response = append(response, packet.Body...)
|
|
}
|
|
|
|
return response, nil
|
|
}
|