Gdaemon   A
last analyzed

Complexity

Total Complexity 29

Size/Duplication

Total Lines 326
Duplicated Lines 0 %

Test Coverage

Coverage 68.42%

Importance

Changes 14
Bugs 2 Features 0
Metric Value
eloc 127
c 14
b 2
f 0
dl 0
loc 326
ccs 78
cts 114
cp 0.6842
rs 10
wmc 29

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __destruct() 0 3 1
A setConfig() 0 17 5
A __construct() 0 4 1
A login() 0 27 3
A writeAndReadSocket() 0 9 3
A connect() 0 42 2
A readSocket() 0 27 6
A getSocket() 0 3 1
A disconnect() 0 13 3
A getConnection() 0 8 2
A writeSocket() 0 9 2
1
<?php
2
3
namespace Knik\Gameap;
4
5
use Knik\Binn\Binn;
6
use Knik\Gameap\Exception\GdaemonClientException;
7
8
abstract class Gdaemon
9
{
10
    const DAEMON_SERVER_MODE_NOAUTH = 0;
11
    const DAEMON_SERVER_MODE_AUTH   = 1;
12
    const DAEMON_SERVER_MODE_CMD    = 2;
13
    const DAEMON_SERVER_MODE_FILES  = 3;
14
    const DAEMON_SERVER_MODE_STATUS = 4;
15
16
    const STATUS_ERROR                = 1;
17
    const STATUS_CRITICAL_ERROR       = 2;
18
    const STATUS_UNKNOWN_COMMAND      = 3;
19
    const STATUS_OK                   = 100;
20
21
    /**
22
     * @var string
23
     */
24
    const SOCKET_MSG_ENDL = "\xFF\xFF\xFF\xFF";
25
26
    /**
27
     * @var resource
28
     */
29
    private $_connection;
30
31
    /**
32
     * @var resource
33
     */
34
    protected $_socket;
35
36
    /**
37
     * @var string
38
     */
39
    protected $host;
40
41
    /**
42
     * @var int
43
     */
44
    protected $port = 31717;
45
46
    /**
47
     * @var string
48
     */
49
    protected $username = '';
50
51
    /**
52
     * @var string
53
     */
54
    protected $password = '';
55
56
    /**
57
     * @var int
58
     */
59
    protected $timeout = 10;
60
61
    /**
62
     * @var string
63
     */
64
    private $serverCertificate;
65
66
    /**
67
     * @var string
68
     */
69
    private $localCertificate;
70
71
    /**
72
     * @var string
73
     */
74
    private $privateKey;
75
76
    /**
77
     * @var string
78
     */
79
    private $privateKeyPass;
80
81
    /**
82
     * @var array
83
     */
84
    protected $configurable = [
85
        'host',
86
        'port',
87
        'username',
88
        'password',
89
        'serverCertificate',
90
        'localCertificate',
91
        'privateKey',
92
        'privateKeyPass',
93
        'timeout',
94
    ];
95
96
    /**
97
     * @var int
98
     */
99
    protected $maxBufsize = 20480;
100
101
    /**
102
     * @var int
103
     */
104
    protected $mode = self::DAEMON_SERVER_MODE_NOAUTH;
105
106
    /** @var Binn */
107
    protected $binn;
108
109
    /**
110
     * @var bool
111
     */
112
    private $_auth = false;
113
114
    /**
115
     * Constructor.
116 3
     *
117
     * @param array $config
118 3
     */
119 3
    public function __construct(array $config = [])
120
    {
121
        $this->setConfig($config);
122
        $this->binn = new Binn();
123
    }
124 3
125
    /**
126 3
     * Disconnect on destruction.
127 3
     */
128
    public function __destruct()
129
    {
130
        $this->disconnect();
131
    }
132
133
    /**
134
     * Set the config.
135
     *
136 9
     * @param array $config
137
     *
138 9
     * @return $this
139 3
     */
140
    public function setConfig(array $config): Gdaemon
141
    {
142 9
        if (empty($config)) {
143 9
            return $this;
144 9
        }
145
146
        foreach ($this->configurable as $setting) {
147 9
            if ( ! isset($config[$setting])) {
148 9
                continue;
149 3
            }
150 3
151
            if (property_exists($this, $setting)) {
152 9
                $this->{$setting} = $config[$setting];
153
            }
154
        }
155
156
        return $this;
157
    }
158 96
159
    /**
160 96
     * Connect to the server.
161 64
     */
162 32
    public function connect()
163 32
    {
164 32
        $sslContext = stream_context_create([
165 96
            'ssl' => [
166 96
                'allow_self_signed' => true,
167 96
                'verify_peer'       => true,
168 96
                'verify_peer_name'  => false,
169 64
                'cafile'            => $this->serverCertificate,
170 32
                'local_cert'        => $this->localCertificate,
171 32
                'local_pk'          => $this->privateKey,
172
                'passphrase'        => $this->privateKeyPass,
173 96
                'crypto_method'     => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
174
            ]
175 96
        ]);
176
177 96
        set_error_handler(function ($errSeverity, $errMsg) {
178 96
            restore_error_handler();
179 96
            throw new GdaemonClientException($errMsg);
180 96
        });
181 96
182 64
        $this->_connection = stream_socket_client("tls://{$this->host}:{$this->port}",
183 32
            $errno,
184 96
            $errstr,
185
            $this->timeout,
186 96
            STREAM_CLIENT_CONNECT,
187
            $sslContext
188
        );
189
190
        if ( ! $this->_connection) {
191
            restore_error_handler();
192
            throw new GdaemonClientException('Could not connect to host: '
193 96
                . $this->host
194
                . ', port:' . $this->port
195 96
                . "(Error $errno: $errstr)");
196 96
        }
197
198
        stream_set_blocking($this->_connection, true);
199
        stream_set_timeout($this->_connection, $this->timeout);
200
201 96
        restore_error_handler();
202
203 96
        $this->login();
204 96
    }
205 96
206 32
    /**
207
     * @return mixed
208 96
     */
209
    protected function getConnection()
210
    {
211
        if (! is_resource($this->_connection)) {
212
            $this->disconnect();
213
            $this->connect();
214 99
        }
215
216 99
        return $this->_connection;
217
    }
218
219
    /**
220
     * Disconnect
221 99
     */
222
    public function disconnect()
223
    {
224
        if (is_resource($this->_socket)) {
225
            socket_close($this->_socket);
226 99
            $this->_socket = null;
227 99
        }
228
229
        if (is_resource($this->_connection)) {
230
            fclose($this->_connection);
231
            $this->_connection = null;
232
        }
233
234
        $this->_auth = false;
235
    }
236
237
    /**
238
     * @return bool|null|resource
239
     */
240
    protected function getSocket()
241
    {
242
        return $this->getConnection();
243
    }
244
245
    /**
246
     * @param integer
247
     * @param bool
248
     * @return bool|string
249
     */
250
    protected function readSocket($len = 0, $notTrimEndSymbols = false)
251
    {
252
        if ($len == 0) {
253
            $len = $this->maxBufsize;
254
        }
255
256
        if (!$notTrimEndSymbols) {
257
            $read = '';
258
            while (!feof($this->_connection))
259
            {
260
                $part = fread($this->_connection, $len);
261
262
                $read .= $part;
263
264
                $offset = (strlen($read) > strlen(self::SOCKET_MSG_ENDL))
265
                    ? strlen($read) - strlen(self::SOCKET_MSG_ENDL)
266
                    : 0;
267
268
                if (strpos($read, self::SOCKET_MSG_ENDL, $offset) !== false) {
269
                    break;
270
                }
271
            }
272
        } else {
273
            $read = stream_get_contents($this->_connection, $len);
274
        }
275 96
276
        return $read;
277 96
    }
278
279
    protected function writeSocket(string $buffer): int
280
    {
281 96
        $result = fwrite($this->getConnection(), $buffer);
282
283 96
        if ($result === false) {
0 ignored issues
show
introduced by
The condition $result === false is always false.
Loading history...
284
            throw new GdaemonClientException('Socket write failed');
285
        }
286
287 96
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result could return the type true which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
288
    }
289
290
    /**
291
     * Write data to socket and read
292
     *
293
     * @param string $buffer
294
     * @return string
295
     */
296 96
    protected function writeAndReadSocket(string $buffer)
297
    {
298 96
        $this->writeSocket($buffer . self::SOCKET_MSG_ENDL);
299
        $read = $this->readSocket();
300 96
        if (!is_string($read) || $read === '') {
0 ignored issues
show
introduced by
The condition is_string($read) is always true.
Loading history...
301
            throw new GdaemonClientException('No data from daemon');
302 96
        }
303
304
        return $read;
305
    }
306
307
    private function login(): bool
308 96
    {
309
        if ($this->_auth) {
310 96
            return $this->_auth;
311
        }
312
313
        $message = $this->binn->serialize([
314 96
            self::DAEMON_SERVER_MODE_AUTH,
315
            $this->username,
316 96
            $this->password,
317 96
            $this->mode,
318 96
        ]);
319 96
320
        $read = $this->writeAndReadSocket($message);
321 96
322
        $results = $this->binn->unserialize($read);
323 96
324 96
        if ($results[0] == self::STATUS_OK) {
325 96
            $this->_auth = true;
326
        } else {
327 96
            throw new GdaemonClientException(
328 96
                'Could not login with connection: ' . $this->host . ':' . $this->port
329 32
                . ', username: ' . $this->username
330
            );
331
        }
332
333
        return $this->_auth;
334 96
    }
335
}
336