Passed
Push — develop ( 065f10...1d4e29 )
by Aleksandr
01:25
created

Client::connect()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 12
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 4
nop 0
dl 0
loc 12
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 thedudeguy
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
     * Connect to a server.
102
     *
103
     * @return boolean
104
     */
105
    public function connect()
106
    {
107
        if (!$this->client->isConnected()) {
108
            $this->client->connect($this->host, $this->port, $this->timeout);
109
        }
110
111
        if (!$this->isAuthorized()) {
112
            // check authorization
113
            return $this->authorize();
114
        }
115
116
        return true;
117
    }
118
119
    /**
120
     * Disconnect from server.
121
     *
122
     * @return void
123
     */
124
    public function disconnect()
125
    {
126
        if ($this->client->isConnected()) {
127
            $this->client->close();
128
        }
129
    }
130
131
    /**
132
     * Send a command to the connected server.
133
     *
134
     * @param string $command
135
     *
136
     * @return boolean|mixed
137
     */
138
    public function sendCommand(string $command)
139
    {
140
        if (!$this->isAuthorized()) {
141
            return false;
142
        }
143
144
        // send command packet
145
        $this->writePacket(self::PACKET_COMMAND, self::SERVER_DATA_EXEC_COMMAND, $command);
146
147
        // get response
148
        $responsePacket = $this->readPacket();
149
        if ($responsePacket['id'] == self::PACKET_COMMAND) {
150
            if ($responsePacket['type'] == self::SERVER_DATA_RESPONSE_VALUE) {
151
                $this->lastResponse = $responsePacket['body'];
152
153
                return $responsePacket['body'];
154
            }
155
        }
156
157
        return false;
158
    }
159
160
    /**
161
     * True if socket is authorized.
162
     *
163
     * @return boolean
164
     */
165
    public function isAuthorized()
166
    {
167
        return $this->authorized;
168
    }
169
170
    /**
171
     * Log into the server with the given credentials.
172
     *
173
     * @return boolean
174
     */
175
    private function authorize()
176
    {
177
        $this->writePacket(self::PACKET_AUTHORIZE, self::SERVER_DATA_AUTH, $this->password);
178
        $responsePacket = $this->readPacket();
179
180
        if ($responsePacket['type'] == self::SERVER_DATA_AUTH_RESPONSE) {
181
            if ($responsePacket['id'] == self::PACKET_AUTHORIZE) {
182
                $this->authorized = true;
183
184
                return true;
185
            }
186
        }
187
188
        $this->disconnect();
189
        return false;
190
    }
191
192
    /**
193
     * Writes a packet to the socket stream.
194
     *
195
     * @param $id
196
     * @param $type
197
     * @param string $body
198
     *
199
     * @return void
200
     */
201
    private function writePacket(int $id, int $type, $body = '')
202
    {
203
        /**
204
         * Size -> 32-bit little-endian Signed Integer Varies, see below.
205
         * ID -> 32-bit little-endian Signed Integer Varies, see below.
206
         * Type -> 32-bit little-endian Signed Integer Varies, see below.
207
         * Body -> Null-terminated ASCII String Varies, see below.
208
         * Empty String -> Null-terminated ASCII String 0x00
209
         */
210
211
        //create packet
212
        $packet = pack('VV', $id, $type); // ID, type
213
        $packet .= $body . "\x00\x00"; // body, 2 null bytes
214
215
        // get packet size.
216
        $packetSize = strlen($packet);
217
218
        // send prepared packet
219
        $this->client->send(pack('V', $packetSize) . $packet);
220
    }
221
222
    /**
223
     * Read a packet from the socket stream.
224
     *
225
     * @return array
226
     */
227
    private function readPacket()
228
    {
229
        /**
230
         * If size is > 4096, the response will be in multiple packets. This needs to be address.
231
         * Get more info about multi-packet responses from the RCON protocol specification at
232
         * https://developer.valvesoftware.com/wiki/Source_RCON_Protocol
233
         *
234
         * Currently, this script does not support multi-packet responses.
235
         */
236
        return unpack('V1size/V1id/V1type/a*body', $this->client->recv());
237
    }
238
}
239