Passed
Push — master ( 9d6ab9...f4215b )
by Aleksandr
01:32
created

MinecraftClient::isConnected()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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