Passed
Push — develop ( 642602...3cbe61 )
by Aleksandr
01:20
created

Client::readPacket()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 10
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace AlexCool\Rcon\Client\Minecraft;
4
5
use Swoole\Client as SwooleClient;
6
use AlexCool\Rcon\Client\ClientInterface;
7
8
/**
9
 * See https://developer.valvesoftware.com/wiki/Source_RCON_Protocol for
10
 * more information about Source RCON Packets
11
 *
12
 * @author Chris Churchwell <[email protected]>
13
 * @author Aleksandr Kulina <[email protected]>
14
 *
15
 * @package AlexCool\Rcon\Client
16
 */
17
final class Client implements ClientInterface
18
{
19
    const SERVER_DATA_AUTH = 3;
20
    const SERVER_DATA_AUTH_RESPONSE = 2;
21
    const SERVER_DATA_EXEC_COMMAND = 2;
22
    const SERVER_DATA_RESPONSE_VALUE = 0;
23
24
    const PACKET_AUTHORIZE = 5;
25
    const PACKET_COMMAND = 6;
26
27
    /**
28
     * @var SwooleClient
29
     */
30
    private $client;
31
32
    /**
33
     * @var string
34
     */
35
    private $host;
36
37
    /**
38
     * @var int
39
     */
40
    private $port;
41
42
    /**
43
     * @var string
44
     */
45
    private $password;
46
47
    /**
48
     * @var float
49
     */
50
    private $timeout;
51
52
    /**
53
     * @var bool
54
     */
55
    private $authorized = false;
56
57
    /**
58
     * @var string
59
     */
60
    private $lastResponse = '';
61
62
    /**
63
     * @param SwooleClient $client
64
     * @param string $host
65
     * @param int $port
66
     * @param string $password
67
     * @param float $timeout
68
     */
69
    public function __construct(SwooleClient $client, string $host, int $port, string $password, float $timeout)
70
    {
71
        $this->client = $client;
72
        $this->host = $host;
73
        $this->port = $port;
74
        $this->password = $password;
75
        $this->timeout = $timeout;
76
    }
77
78
    /**
79
     * @param ClientBuilder $builder
80
     *
81
     * @return Client
82
     */
83
    public static function build(ClientBuilder $builder)
84
    {
85
        return $builder
86
            ->createSwooleClient()
87
            ->getClient();
88
    }
89
90
    /**
91
     * Get the latest response from the server.
92
     *
93
     * @return string
94
     */
95
    public function getResponse()
96
    {
97
        return $this->lastResponse;
98
    }
99
100
    /**
101
     * True if socket is connected and authorized.
102
     *
103
     * @return boolean
104
     */
105
    public function isConnected(): bool
106
    {
107
        return $this->authorized;
108
    }
109
110
    /**
111
     * Connect to a server.
112
     *
113
     * @return boolean
114
     */
115
    public function connect()
116
    {
117
        if (!$this->client->isConnected()) {
118
            return $this->client->connect($this->host, $this->port, $this->timeout)
119
                ? $this->authorize()
120
                : false;
121
        }
122
123
        // check authorization
124
        return $this->authorize();
125
    }
126
127
    /**
128
     * Disconnect from server.
129
     *
130
     * @return void
131
     */
132
    public function disconnect()
133
    {
134
        if ($this->client->isConnected()) {
135
            $this->client->close();
136
        }
137
    }
138
139
    /**
140
     * Send a command to the connected server.
141
     *
142
     * @param string $command
143
     *
144
     * @return boolean|mixed
145
     */
146
    public function sendCommand(string $command)
147
    {
148
        if (!$this->authorized) {
149
            return false;
150
        }
151
152
        // send command packet
153
        $this->writePacket(self::PACKET_COMMAND, self::SERVER_DATA_EXEC_COMMAND, $command);
154
155
        // get response
156
        $responsePacket = $this->readPacket();
157
        if ($responsePacket['id'] == self::PACKET_COMMAND) {
158
            if ($responsePacket['type'] == self::SERVER_DATA_RESPONSE_VALUE) {
159
                $this->lastResponse = $responsePacket['body'];
160
161
                return $responsePacket['body'];
162
            }
163
        }
164
165
        return false;
166
    }
167
168
    /**
169
     * Log into the server with the given credentials.
170
     *
171
     * @return boolean
172
     */
173
    private function authorize()
174
    {
175
        $this->writePacket(self::PACKET_AUTHORIZE, self::SERVER_DATA_AUTH, $this->password);
176
        $responsePacket = $this->readPacket();
177
178
        if ($responsePacket['type'] == self::SERVER_DATA_AUTH_RESPONSE) {
179
            if ($responsePacket['id'] == self::PACKET_AUTHORIZE) {
180
                $this->authorized = true;
181
182
                return true;
183
            }
184
        }
185
186
        $this->disconnect();
187
        return false;
188
    }
189
190
    /**
191
     * Writes a packet to the socket stream.
192
     *
193
     * @param $id
194
     * @param $type
195
     * @param string $body
196
     *
197
     * @return void
198
     */
199
    private function writePacket(int $id, int $type, $body = '')
200
    {
201
        /**
202
         * Size -> 32-bit little-endian Signed Integer Varies, see below.
203
         * ID -> 32-bit little-endian Signed Integer Varies, see below.
204
         * Type -> 32-bit little-endian Signed Integer Varies, see below.
205
         * Body -> Null-terminated ASCII String Varies, see below.
206
         * Empty String -> Null-terminated ASCII String 0x00
207
         */
208
209
        //create packet
210
        $packet = pack('VV', $id, $type); // ID, type
211
        $packet .= $body . "\x00\x00"; // body, 2 null bytes
212
213
        // get packet size.
214
        $packetSize = strlen($packet);
215
216
        // send prepared packet
217
        $this->client->send(pack('V', $packetSize) . $packet);
218
    }
219
220
    /**
221
     * Read a packet from the socket stream.
222
     *
223
     * @return array
224
     */
225
    private function readPacket()
226
    {
227
        /**
228
         * If size is > 4096, the response will be in multiple packets. This needs to be address.
229
         * Get more info about multi-packet responses from the RCON protocol specification at
230
         * https://developer.valvesoftware.com/wiki/Source_RCON_Protocol
231
         *
232
         * Currently, this script does not support multi-packet responses.
233
         */
234
        return unpack('V1size/V1id/V1type/a*body', $this->client->recv());
235
    }
236
}
237