Passed
Push — master ( d7f047...595d53 )
by Nikita
03:09
created

Gdaemon   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 312
Duplicated Lines 0 %

Test Coverage

Coverage 31.73%

Importance

Changes 0
Metric Value
eloc 113
dl 0
loc 312
ccs 33
cts 104
cp 0.3173
rs 10
c 0
b 0
f 0
wmc 24

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __destruct() 0 3 1
A __construct() 0 3 1
A setConfig() 0 17 5
A login() 0 27 3
A connect() 0 36 2
A writeSocket() 0 9 2
A readSocket() 0 13 3
A getSocket() 0 3 1
A disconnect() 0 13 3
A getConnection() 0 9 2
A writeAndReadSocket() 0 7 1
1
<?php
2
3
namespace Knik\Gameap;
4
5
use Knik\Binn\BinnList;
6
use RuntimeException;
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
15
    const STATUS_ERROR                = 1;
16
    const STATUS_CRITICAL_ERROR       = 2;
17
    const STATUS_UNKNOWN_COMMAND      = 3;
18
    const STATUS_OK                   = 100;
19
20
    /**
21
     * @var string
22
     */
23
    const SOCKET_MSG_ENDL = "\xFF\xFF\xFF\xFF";
24
25
    /**
26
     * @var resource
27
     */
28
    private $_connection;
29
30
    /**
31
     * @var resource
32
     */
33
    protected $_socket;
34
35
    /**
36
     * @var string
37
     */
38
    protected $host;
39
40
    /**
41
     * @var int
42
     */
43
    protected $port = 31717;
44
45
    /**
46
     * @var string
47
     */
48
    protected $username = '';
49
50
    /**
51
     * @var string
52
     */
53
    protected $password = '';
54
55
    /**
56
     * @var int
57
     */
58
    protected $timeout = 10;
59
60
    /**
61
     * @var string
62
     */
63
    private $serverCertificate;
64
65
    /**
66
     * @var string
67
     */
68
    private $localCertificate;
69
70
    /**
71
     * @var string
72
     */
73
    private $privateKey;
74
75
    /**
76
     * @var string
77
     */
78
    private $privateKeyPass;
79
80
    /**
81
     * @var array
82
     */
83
    protected $configurable = [
84
        'host',
85
        'port',
86
        'username',
87
        'password',
88
        'serverCertificate',
89
        'localCertificate',
90
        'privateKey',
91
        'privateKeyPass',
92
        'timeout',
93
    ];
94
95
    /**
96
     * @var int
97
     */
98
    protected $maxBufsize = 10240;
99
100
    /**
101
     * @var int
102
     */
103
    protected $mode = self::DAEMON_SERVER_MODE_NOAUTH;
104
105
    /**
106
     * @var bool
107
     */
108
    private $_auth = false;
109
110
    /**
111
     * Constructor.
112
     *
113
     * @param array $config
114
     */
115
    public function __construct(array $config = [])
116
    {
117
        $this->setConfig($config);
118
    }
119
120
    /**
121
     * Disconnect on destruction.
122
     */
123
    public function __destruct()
124
    {
125
        $this->disconnect();
126
    }
127
128
    /**
129
     * Set the config.
130
     *
131
     * @param array $config
132
     *
133
     * @return $this
134
     */
135 6
    public function setConfig(array $config)
136
    {
137 6
        if (empty($config)) {
138 3
            return $this;
139
        }
140
141 6
        foreach ($this->configurable as $setting) {
142 6
            if ( ! isset($config[$setting])) {
143 6
                continue;
144
            }
145
146 6
            if (property_exists($this, $setting)) {
147 6
                $this->$setting = $config[$setting];
148 2
            }
149 2
        }
150
151 6
        return $this;
152
    }
153
154
    /**
155
     * Connect to the server.
156
     */
157
    public function connect()
158
    {
159
        $sslContext = stream_context_create([
160
            'ssl' => [
161
                'allow_self_signed' => true,
162
                'verify_peer'       => true,
163
                'verify_peer_name'  => false,
164
                'cafile'            => $this->serverCertificate,
165
                'local_cert'        => $this->localCertificate,
166
                'local_pk'          => $this->privateKey,
167
                'passphrase'        => $this->privateKeyPass,
168
                'crypto_method'     => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
169
            ]
170
        ]);
171
172
        set_error_handler(function ($err_severity, $err_msg) {
173
            throw new RuntimeException($err_msg);
174
        });
175
176
        $this->_connection = stream_socket_client("tls://{$this->host}:{$this->port}",
0 ignored issues
show
Documentation Bug introduced by
It seems like stream_socket_client('tl...T_CONNECT, $sslContext) can also be of type false. However, the property $_connection is declared as type resource. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
177
            $errno,
178
            $errstr,
179
            30,
180
            STREAM_CLIENT_CONNECT,
181
            $sslContext
182
        );
183
        restore_error_handler();
184
185
        if ( ! $this->_connection) {
186
            throw new RuntimeException('Could not connect to host: '
187
                . $this->host
188
                . ', port:' . $this->port
189
                . "(Error $errno: $errstr)");
190
        }
191
192
        stream_set_blocking($this->_connection, true);
193
    }
194
195
    /**
196
     * @return mixed
197
     */
198
    protected function getConnection()
199
    {
200
        if (! is_resource($this->_connection)) {
201
            $this->disconnect();
202
            $this->connect();
203
            $this->login();
204
        }
205
206
        return $this->_connection;
207
    }
208
209
    /**
210
     * @param $username
211
     * @param $password
212
     * @param $privateKey
213
     * @param $privateKeyPass
214
     *
215
     * @return bool
216
     */
217 6
    protected function login()
218
    {
219 6
        if ($this->_auth) {
220 3
            return $this->_auth;
221
        }
222
223 6
        $writeBinn= new BinnList;
224
225 6
        $writeBinn->addInt16(self::DAEMON_SERVER_MODE_AUTH);
226 6
        $writeBinn->addStr($this->username);
227 6
        $writeBinn->addStr($this->password);
228 6
        $writeBinn->addInt16($this->mode);
229
230 6
        $read = $this->writeAndReadSocket($writeBinn->serialize());
231
232 6
        $readBinn = new BinnList;
233 6
        $readBinn->binnOpen($read);
234 6
        $results = $readBinn->unserialize();
235
236 6
        if ($results[0] == self::STATUS_OK) {
237 3
            $this->_auth = true;
238 1
        } else {
239 3
            throw new RuntimeException('Could not login with connection: ' . $this->host . ':' . $this->port
240 3
                . ', username: ' . $this->username);
241
        }
242
243 3
        return $this->_auth;
244
    }
245
246
    /**
247
     * Disconnect
248
     */
249
    public function disconnect()
250
    {
251
        if (is_resource($this->_socket)) {
252
            socket_close($this->_socket);
253
            $this->_socket = null;
254
        }
255
256
        if (is_resource($this->_connection)) {
257
            fclose($this->_connection);
258
            $this->_connection = null;
259
        }
260
261
        $this->_auth = false;
262
    }
263
264
    /**
265
     * @return bool|null|resource
266
     */
267
    protected function getSocket()
268
    {
269
        return $this->getConnection();
270
    }
271
272
    /**
273
     * @param integer
274
     * @param bool
275
     * @return bool|string
276
     */
277
    protected function readSocket($len = 0, $notTrimEndSymbols = false)
278
    {
279
        if ($len == 0) {
280
            $len = $this->maxBufsize;
281
        }
282
283
        if (!$notTrimEndSymbols) {
284
            $read = stream_get_line($this->getConnection(), $len, self::SOCKET_MSG_ENDL);
0 ignored issues
show
Bug introduced by
It seems like $this->getConnection() can also be of type false; however, parameter $handle of stream_get_line() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

284
            $read = stream_get_line(/** @scrutinizer ignore-type */ $this->getConnection(), $len, self::SOCKET_MSG_ENDL);
Loading history...
285
        } else {
286
            $read = stream_get_contents($this->getConnection(), $len);
0 ignored issues
show
Bug introduced by
It seems like $this->getConnection() can also be of type false; however, parameter $handle of stream_get_contents() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

286
            $read = stream_get_contents(/** @scrutinizer ignore-type */ $this->getConnection(), $len);
Loading history...
287
        }
288
289
        return $read;
290
    }
291
292
    /**
293
     * @param $buffer
294
     * @return int
295
     */
296
    protected function writeSocket($buffer)
297
    {
298
        $result = fwrite($this->getConnection(), $buffer);
0 ignored issues
show
Bug introduced by
It seems like $this->getConnection() can also be of type false; however, parameter $handle of fwrite() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

298
        $result = fwrite(/** @scrutinizer ignore-type */ $this->getConnection(), $buffer);
Loading history...
299
300
        if ($result === false) {
301
            throw new RuntimeException('Socket read failed');
302
        }
303
304
        return $result;
305
    }
306
307
    /**
308
     * Write data to socket and read
309
     *
310
     * @param string $buffer
311
     * @return bool|string
312
     */
313 9
    protected function writeAndReadSocket($buffer)
314
    {
315 9
        $this->writeSocket($buffer . self::SOCKET_MSG_ENDL);
316
317 9
        $read = $this->readSocket();
318
319 9
        return $read;
320
    }
321
}