-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsimple_client.go
136 lines (123 loc) · 3.84 KB
/
simple_client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
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
}