Passed
Push — master ( 5bd6a5...cd7482 )
by Nikita
03:52
created

Gdaemon::readSocket()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 6
nc 6
nop 2
dl 0
loc 13
ccs 0
cts 8
cp 0
crap 20
rs 10
c 0
b 0
f 0
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 string
49
     */
50
    private $privateKey;
51
52
    /**
53
     * @var string
54
     */
55
    private $privateKeyPass;
56
57
    /**
58
     * @var array
59
     */
60
    protected $configurable = [
61
        'host',
62
        'port',
63
        // 'username',
64
        // 'password',
65
        'privateKey',
66
        'privateKeyPass',
67
        'timeout',
68
    ];
69
70
    /**
71
     * @var int
72
     */
73
    protected $maxBufsize = 10240;
74
75
    /**
76
     * @var bool
77
     */
78
    private $_auth = false;
79
80
    /**
81
     * Constructor.
82
     *
83
     * @param array $config
84
     */
85
    public function __construct(array $config)
86
    {
87
        $this->setConfig($config);
88
89
        $this->connect();
90
        $this->login($config['username'], $config['password']);
91
    }
92
93
    /**
94
     * Disconnect on destruction.
95
     */
96
    public function __destruct()
97
    {
98
        $this->disconnect();
99
    }
100
101
    /**
102
     * Set the config.
103
     *
104
     * @param array $config
105
     *
106
     * @return $this
107
     */
108
    public function setConfig(array $config)
109
    {
110
        foreach ($this->configurable as $setting) {
111
            if ( ! isset($config[$setting])) {
112
                continue;
113
            }
114
115
            if (property_exists($this, $setting)) {
116
                $this->$setting = $config[$setting];
117
            }
118
        }
119
120
        return $this;
121
    }
122
123
    /**
124
     * Connect to the server.
125
     */
126
    public function connect()
127
    {
128
        $sslContext = stream_context_create([
129
            'ssl' => [
130
                'allow_self_signed' => true,
131
                'verify_peer'       => true,
132
                'verify_peer_name'  => false,
133
                'local_cert'        => $this->privateKey,
134
                'passphrase'        => $this->privateKeyPass,
135
                'crypto_method'     => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
136
            ]
137
        ]);
138
139
        $this->_connection = stream_socket_client("tcp://{$this->host}:{$this->port}",
0 ignored issues
show
Documentation Bug introduced by
It seems like stream_socket_client('tc...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...
140
            $errno,
141
            $errstr,
142
            30,
143
            STREAM_CLIENT_CONNECT,
144
            $sslContext
145
        );
146
147
        if ( ! $this->_connection) {
148
            throw new RuntimeException('Could not connect to host: '
149
                . $this->host
150
                . ', port:' . $this->port
151
                . "(Error $errno: $errstr)");
152
        }
153
154
        stream_set_blocking($this->_connection, true);
155
156
        set_error_handler(function () {});
157
        $enableCryptoResult = stream_socket_enable_crypto($this->_connection, true, STREAM_CRYPTO_METHOD_SSLv23_CLIENT);
158
        restore_error_handler();
159
160
        if (!$enableCryptoResult) {
161
            throw new RuntimeException('SSL Error');
162
        }
163
164
         $this->getSocket();
165
    }
166
167
    /**
168
     * @return mixed
169
     */
170
    protected function getConnection()
171
    {
172
        if (! is_resource($this->_connection)) {
173
            $this->disconnect();
174
            $this->connect();
175
        }
176
177
        return $this->_connection;
178
    }
179
180
    /**
181
     * @param $username
182
     * @param $password
183
     * @param $privateKey
184
     * @param $privateKeyPass
185
     */
186
    protected function login($username, $password)
187
    {
188
        if ($this->_auth) {
189
            return;
190
        }
191
192
        $writeBinn= new BinnList;
193
194
        $writeBinn->addInt16(self::DAEMON_SERVER_MODE_AUTH);
195
        $writeBinn->addStr($username);
196
        $writeBinn->addStr($password);
197
        $writeBinn->addInt16(3); // Set mode DAEMON_SERVER_MODE_FILES
198
199
        $read = $this->writeAndReadSocket($writeBinn->serialize());
200
201
        $readBinn = new BinnList;
202
        $readBinn->binnOpen($read);
203
        $results = $readBinn->unserialize();
204
205
        if ($results[0] == self::DAEMON_SERVER_STATUS_OK) {
206
            $this->_auth = true;
207
        } else {
208
            throw new RuntimeException('Could not login with connection: ' . $this->host . ':' . $this->port
209
                . ', username: ' . $username);
210
        }
211
    }
212
213
    /**
214
     * Disconnect
215
     */
216
    public function disconnect()
217
    {
218
        if (is_resource($this->_socket)) {
219
            socket_close($this->_socket);
220
            $this->_socket = null;
221
        }
222
223
        if (is_resource($this->_connection)) {
224
            fclose($this->_connection);
225
            $this->_connection = null;
226
        }
227
228
        $this->_auth = false;
229
    }
230
231
    /**
232
     * @return bool|null|resource
233
     */
234
    protected function getSocket()
235
    {
236
        return $this->getConnection();
237
    }
238
239
    /**
240
     * @param integer
241
     * @param bool
242
     * @return bool|string
243
     */
244
    protected function readSocket($len = 0, $notTrimEndSymbols = false)
245
    {
246
        if ($len == 0) {
247
            $len = $this->maxBufsize;
248
        }
249
250
        $read = fread($this->getConnection(), $len);
251
252
        if ($read === false) {
253
            throw new RuntimeException('Socket read failed: ' );
254
        }
255
256
        return $notTrimEndSymbols ? $read : substr($read, 0, -4);
257
    }
258
259
    /**
260
     * @param $buffer
261
     * @return int
262
     */
263
    protected function writeSocket($buffer)
264
    {
265
        $result = fwrite($this->getConnection(), $buffer);
266
267
        if ($result === false) {
268
            throw new RuntimeException('Socket read failed');
269
        }
270
271
        return $result;
272
    }
273
274
    /**
275
     * Write data to socket and read
276
     *
277
     * @param string $buffer
278
     * @return bool|string
279
     */
280
    protected function writeAndReadSocket($buffer)
281
    {
282
        $this->writeSocket($buffer . self::SOCKET_MSG_ENDL);
283
284
        $read = $this->readSocket();
285
286
        return $read;
287
    }
288
}