Passed
Push — master ( e8c32f...5bd6a5 )
by Nikita
02:18
created

Gdaemon::readSocket()   B

Complexity

Conditions 10
Paths 13

Size

Total Lines 36
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 110

Importance

Changes 0
Metric Value
cc 10
eloc 20
nc 13
nop 2
dl 0
loc 36
ccs 0
cts 22
cp 0
crap 110
rs 7.6666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 DAEMON_SERVER_STATUS_OK   = 100;
16
17
    /**
18
     * @var string
19
     */
20
    const SOCKET_MSG_ENDL = "\xFF\xFF\xFF\xFF";
21
22
    /**
23
     * @var resource
24
     */
25
    private $_connection;
26
27
    /**
28
     * @var resource
29
     */
30
    protected $_socket;
31
32
    /**
33
     * @var string
34
     */
35
    protected $host;
36
37
    /**
38
     * @var int
39
     */
40
    protected $port = 31717;
41
42
    /**
43
     * @var int
44
     */
45
    protected $timeout = 10;
46
47
    /**
48
     * @var array
49
     */
50
    protected $configurable = [
51
        'host',
52
        'port',
53
        // 'username',
54
        // 'password',
55
        // 'privateKey',
56
        // 'privateKeyPass',
57
        'timeout',
58
    ];
59
60
    /**
61
     * @var int
62
     */
63
    protected $maxBufsize = 10240;
64
65
    /**
66
     * @var bool
67
     */
68
    private $_auth = false;
69
70
    /**
71
     * Constructor.
72
     *
73
     * @param array $config
74
     */
75
    public function __construct(array $config)
76
    {
77
        $this->setConfig($config);
78
79
        $this->connect();
80
        $this->login($config['username'], $config['password'], $config['privateKey'], $config['privateKeyPass']);
81
    }
82
83
    /**
84
     * Disconnect on destruction.
85
     */
86
    public function __destruct()
87
    {
88
        $this->disconnect();
89
    }
90
91
    /**
92
     * Set the config.
93
     *
94
     * @param array $config
95
     *
96
     * @return $this
97
     */
98
    public function setConfig(array $config)
99
    {
100
        foreach ($this->configurable as $setting) {
101
            if ( ! isset($config[$setting])) {
102
                continue;
103
            }
104
105
            if (property_exists($this, $setting)) {
106
                $this->$setting = $config[$setting];
107
            }
108
        }
109
110
        return $this;
111
    }
112
113
    /**
114
     * Connect to the server.
115
     */
116
    public function connect()
117
    {
118
        // $this->_connection = @fsockopen($this->host, $this->port, $errno, $errstr, $this->timeout);
119
        $this->_connection = stream_socket_client("tcp://{$this->host}:{$this->port}", $errno, $errstr, 30);
0 ignored issues
show
Documentation Bug introduced by
It seems like stream_socket_client('tc...t, $errno, $errstr, 30) 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...
120
121
        if ( ! $this->_connection) {
122
            throw new RuntimeException('Could not connect to host: '
123
                . $this->host
124
                . ', port:' . $this->port
125
                . "(Error $errno: $errstr)");
126
        }
127
128
         $this->getSocket();
129
    }
130
131
    /**
132
     * @return mixed
133
     */
134
    protected function getConnection()
135
    {
136
        if (! is_resource($this->_connection)) {
137
            $this->disconnect();
138
            $this->connect();
139
        }
140
141
        return $this->_connection;
142
    }
143
144
    /**
145
     * @param $username
146
     * @param $password
147
     * @param $privateKey
148
     * @param $privateKeyPass
149
     */
150
    protected function login($username, $password, $privateKey, $privateKeyPass)
151
    {
152
        if ($this->_auth) {
153
            return;
154
        }
155
156
        $writeBinn= new BinnList;
157
158
        $writeBinn->addInt16(self::DAEMON_SERVER_MODE_AUTH);
159
        $writeBinn->addStr($username);
160
        $writeBinn->addStr($password);
161
        $writeBinn->addInt16(3); // Set mode DAEMON_SERVER_MODE_FILES
162
163
        $fp = fopen($privateKey, "r");
164
        $privateKey = fread($fp, 8192);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fread() 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

164
        $privateKey = fread(/** @scrutinizer ignore-type */ $fp, 8192);
Loading history...
165
        fclose($fp);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fclose() 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

165
        fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
166
167
        $res = openssl_get_privatekey($privateKey, $privateKeyPass);
168
        openssl_private_encrypt($writeBinn->serialize() . "\00", $encoded, $res);
169
170
        $read = $this->writeAndReadSocket($encoded);
171
172
        $decrypted = "";
173
        if (!openssl_private_decrypt($read, $decrypted, $res)) {
174
            throw new RuntimeException('OpenSSL private decrypt error');
175
        }
176
177
        if ($decrypted == '') {
178
            throw new RuntimeException('Empty decrypted results');
179
        }
180
181
        $readBinn = new BinnList;
182
        $readBinn->binnOpen($decrypted);
183
        $results = $readBinn->unserialize();
184
185
        if ($results[0] == self::DAEMON_SERVER_STATUS_OK) {
186
            $this->_auth = true;
187
        } else {
188
            throw new RuntimeException('Could not login with connection: ' . $this->host . ':' . $this->port
189
                . ', username: ' . $username);
190
        }
191
    }
192
193
    /**
194
     * Disconnect
195
     */
196
    public function disconnect()
197
    {
198
        if (is_resource($this->_socket)) {
199
            socket_close($this->_socket);
200
            $this->_socket = null;
201
        }
202
203
        if (is_resource($this->_connection)) {
204
            fclose($this->_connection);
205
            $this->_connection = null;
206
        }
207
208
        $this->_auth = false;
209
    }
210
211
    /**
212
     * @return bool|null|resource
213
     */
214
    protected function getSocket()
215
    {
216
        if (is_resource($this->_socket)) {
217
            return $this->_socket;
218
        }
219
220
        set_error_handler(function () {});
221
        $this->_socket = socket_import_stream($this->getConnection());
222
        restore_error_handler();
223
224
        if (! $this->_socket) {
225
            $this->disconnect();
226
            throw new RuntimeException('Could not import socket');
227
        }
228
229
        stream_set_timeout($this->getConnection(), $this->timeout);
230
        socket_set_option($this->_socket, SOL_SOCKET, SO_RCVTIMEO, array('sec' => $this->timeout, 'usec' => 0));
231
        socket_set_option($this->_socket, SOL_SOCKET, SO_SNDTIMEO, array('sec'=> $this->timeout, 'usec' => 0));
232
233
        return $this->_socket;
234
    }
235
236
    /**
237
     * @param integer
238
     * @param bool
239
     * @return bool|string
240
     */
241
    protected function readSocket($len = 0, $notTrimEndSymbols = false)
242
    {
243
        $read = '';
244
        $readed = 0;
245
        $tries = 0;
246
        while($len == 0 || $readed < $len) {
247
            $readlen = socket_recv($this->getSocket(), $readBuf, $this->maxBufsize, MSG_DONTWAIT);
0 ignored issues
show
Bug introduced by
It seems like $this->getSocket() can also be of type boolean; however, parameter $socket of socket_recv() 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

247
            $readlen = socket_recv(/** @scrutinizer ignore-type */ $this->getSocket(), $readBuf, $this->maxBufsize, MSG_DONTWAIT);
Loading history...
248
249
            if ($readlen === false) {
250
                $error = socket_last_error($this->getSocket());
0 ignored issues
show
Bug introduced by
It seems like $this->getSocket() can also be of type boolean; however, parameter $socket of socket_last_error() 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

250
                $error = socket_last_error(/** @scrutinizer ignore-type */ $this->getSocket());
Loading history...
251
252
                if ($error == 11 && $tries < 10) {
253
                    // EAGAIN
254
                    usleep(50000);
255
                    $tries++;
256
                    continue;
257
                }
258
259
                throw new RuntimeException('Socket read failed: ' . socket_strerror($error));
260
            }
261
262
            $tries = 0;
263
            $read .= $readBuf;
264
            $readed += $readlen;
265
266
            if (!$notTrimEndSymbols && strpos($read, self::SOCKET_MSG_ENDL) !== false) {
267
                // Message complete
268
                break;
269
            }
270
        }
271
272
        if ($read === false) {
0 ignored issues
show
introduced by
The condition $read === false is always false.
Loading history...
273
            throw new RuntimeException('Socket read failed: ' . socket_strerror(socket_last_error($this->getSocket())));
274
        }
275
276
        return $notTrimEndSymbols ? $read : substr($read, 0, -4);
277
    }
278
279
    /**
280
     * @param $buffer
281
     * @return int
282
     */
283
    protected function writeSocket($buffer)
284
    {
285
        $result = socket_write($this->getSocket(), $buffer);
0 ignored issues
show
Bug introduced by
It seems like $this->getSocket() can also be of type boolean; however, parameter $socket of socket_write() 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

285
        $result = socket_write(/** @scrutinizer ignore-type */ $this->getSocket(), $buffer);
Loading history...
286
287
        if ($result === false) {
288
            throw new RuntimeException('Socket read failed: ' . socket_strerror(socket_last_error($this->getSocket())));
0 ignored issues
show
Bug introduced by
It seems like $this->getSocket() can also be of type boolean; however, parameter $socket of socket_last_error() 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

288
            throw new RuntimeException('Socket read failed: ' . socket_strerror(socket_last_error(/** @scrutinizer ignore-type */ $this->getSocket())));
Loading history...
289
        }
290
291
        return $result;
292
    }
293
294
    /**
295
     * Write data to socket and read
296
     *
297
     * @param string $buffer
298
     * @return bool|string
299
     */
300
    protected function writeAndReadSocket($buffer)
301
    {
302
        $this->writeSocket($buffer . self::SOCKET_MSG_ENDL);
303
304
        $read = $this->readSocket();
305
306
        if (!$read) {
307
            throw new RuntimeException('Read socket error');
308
        }
309
310
        return $read;
311
    }
312
}