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
|
|
|
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
|
|
|
/** |
107
|
|
|
* @var bool |
108
|
|
|
*/ |
109
|
|
|
private $_auth = false; |
110
|
|
|
|
111
|
|
|
/** |
112
|
|
|
* Constructor. |
113
|
|
|
* |
114
|
|
|
* @param array $config |
115
|
|
|
*/ |
116
|
3 |
|
public function __construct(array $config = []) |
117
|
|
|
{ |
118
|
3 |
|
$this->setConfig($config); |
119
|
3 |
|
} |
120
|
|
|
|
121
|
|
|
/** |
122
|
|
|
* Disconnect on destruction. |
123
|
|
|
*/ |
124
|
3 |
|
public function __destruct() |
125
|
|
|
{ |
126
|
3 |
|
$this->disconnect(); |
127
|
3 |
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* Set the config. |
131
|
|
|
* |
132
|
|
|
* @param array $config |
133
|
|
|
* |
134
|
|
|
* @return $this |
135
|
|
|
*/ |
136
|
9 |
|
public function setConfig(array $config) |
137
|
|
|
{ |
138
|
9 |
|
if (empty($config)) { |
139
|
3 |
|
return $this; |
140
|
|
|
} |
141
|
|
|
|
142
|
9 |
|
foreach ($this->configurable as $setting) { |
143
|
9 |
|
if ( ! isset($config[$setting])) { |
144
|
9 |
|
continue; |
145
|
|
|
} |
146
|
|
|
|
147
|
9 |
|
if (property_exists($this, $setting)) { |
148
|
9 |
|
$this->$setting = $config[$setting]; |
149
|
3 |
|
} |
150
|
3 |
|
} |
151
|
|
|
|
152
|
9 |
|
return $this; |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
/** |
156
|
|
|
* Connect to the server. |
157
|
|
|
*/ |
158
|
96 |
|
public function connect() |
159
|
|
|
{ |
160
|
96 |
|
$sslContext = stream_context_create([ |
161
|
64 |
|
'ssl' => [ |
162
|
32 |
|
'allow_self_signed' => true, |
163
|
32 |
|
'verify_peer' => true, |
164
|
32 |
|
'verify_peer_name' => false, |
165
|
96 |
|
'cafile' => $this->serverCertificate, |
166
|
96 |
|
'local_cert' => $this->localCertificate, |
167
|
96 |
|
'local_pk' => $this->privateKey, |
168
|
96 |
|
'passphrase' => $this->privateKeyPass, |
169
|
64 |
|
'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT |
170
|
32 |
|
] |
171
|
32 |
|
]); |
172
|
|
|
|
173
|
96 |
|
set_error_handler(function ($err_severity, $err_msg) { |
174
|
|
|
throw new RuntimeException($err_msg); |
175
|
96 |
|
}); |
176
|
|
|
|
177
|
96 |
|
$this->_connection = stream_socket_client("tls://{$this->host}:{$this->port}", |
|
|
|
|
178
|
96 |
|
$errno, |
179
|
96 |
|
$errstr, |
180
|
96 |
|
30, |
181
|
96 |
|
STREAM_CLIENT_CONNECT, |
182
|
64 |
|
$sslContext |
183
|
32 |
|
); |
184
|
96 |
|
restore_error_handler(); |
185
|
|
|
|
186
|
96 |
|
if ( ! $this->_connection) { |
187
|
|
|
throw new RuntimeException('Could not connect to host: ' |
188
|
|
|
. $this->host |
189
|
|
|
. ', port:' . $this->port |
190
|
|
|
. "(Error $errno: $errstr)"); |
191
|
|
|
} |
192
|
|
|
|
193
|
96 |
|
stream_set_blocking($this->_connection, true); |
194
|
|
|
|
195
|
96 |
|
$this->login(); |
196
|
96 |
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* @return mixed |
200
|
|
|
*/ |
201
|
96 |
|
protected function getConnection() |
202
|
|
|
{ |
203
|
96 |
|
if (! is_resource($this->_connection)) { |
204
|
96 |
|
$this->disconnect(); |
205
|
96 |
|
$this->connect(); |
206
|
32 |
|
} |
207
|
|
|
|
208
|
96 |
|
return $this->_connection; |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
/** |
212
|
|
|
* Disconnect |
213
|
|
|
*/ |
214
|
99 |
|
public function disconnect() |
215
|
|
|
{ |
216
|
99 |
|
if (is_resource($this->_socket)) { |
217
|
|
|
socket_close($this->_socket); |
218
|
|
|
$this->_socket = null; |
219
|
|
|
} |
220
|
|
|
|
221
|
99 |
|
if (is_resource($this->_connection)) { |
222
|
|
|
fclose($this->_connection); |
223
|
|
|
$this->_connection = null; |
224
|
|
|
} |
225
|
|
|
|
226
|
99 |
|
$this->_auth = false; |
227
|
99 |
|
} |
228
|
|
|
|
229
|
|
|
/** |
230
|
|
|
* @return bool|null|resource |
231
|
|
|
*/ |
232
|
|
|
protected function getSocket() |
233
|
|
|
{ |
234
|
|
|
return $this->getConnection(); |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
/** |
238
|
|
|
* @param integer |
239
|
|
|
* @param bool |
240
|
|
|
* @return bool|string |
241
|
|
|
*/ |
242
|
|
|
protected function readSocket($len = 0, $notTrimEndSymbols = false) |
243
|
|
|
{ |
244
|
|
|
if ($len == 0) { |
245
|
|
|
$len = $this->maxBufsize; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
if (!$notTrimEndSymbols) { |
249
|
|
|
$read = ''; |
250
|
|
|
while (!feof($this->_connection)) |
251
|
|
|
{ |
252
|
|
|
$part = fread($this->_connection, $len); |
253
|
|
|
|
254
|
|
|
$read .= $part; |
255
|
|
|
|
256
|
|
|
$offset = (strlen($read) > strlen(self::SOCKET_MSG_ENDL)) |
257
|
|
|
? strlen($read) - strlen(self::SOCKET_MSG_ENDL) |
258
|
|
|
: 0; |
259
|
|
|
|
260
|
|
|
if (strpos($read, self::SOCKET_MSG_ENDL, $offset) !== false) { |
261
|
|
|
break; |
262
|
|
|
} |
263
|
|
|
} |
264
|
|
|
} else { |
265
|
|
|
$read = stream_get_contents($this->_connection, $len); |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
return $read; |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
/** |
272
|
|
|
* @param $buffer |
273
|
|
|
* @return int |
274
|
|
|
*/ |
275
|
96 |
|
protected function writeSocket($buffer) |
276
|
|
|
{ |
277
|
96 |
|
if (empty($buffer)) { |
278
|
|
|
throw new RuntimeException('Empty write string'); |
279
|
|
|
} |
280
|
|
|
|
281
|
96 |
|
$result = fwrite($this->getConnection(), $buffer); |
282
|
|
|
|
283
|
96 |
|
if ($result === false) { |
|
|
|
|
284
|
|
|
throw new RuntimeException('Socket read failed'); |
285
|
|
|
} |
286
|
|
|
|
287
|
96 |
|
return $result; |
|
|
|
|
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
/** |
291
|
|
|
* Write data to socket and read |
292
|
|
|
* |
293
|
|
|
* @param string $buffer |
294
|
|
|
* @return bool|string |
295
|
|
|
*/ |
296
|
96 |
|
protected function writeAndReadSocket($buffer) |
297
|
|
|
{ |
298
|
96 |
|
$this->writeSocket($buffer . self::SOCKET_MSG_ENDL); |
299
|
|
|
|
300
|
96 |
|
$read = $this->readSocket(); |
301
|
|
|
|
302
|
96 |
|
return $read; |
303
|
|
|
} |
304
|
|
|
|
305
|
|
|
/** |
306
|
|
|
* @return bool |
307
|
|
|
*/ |
308
|
96 |
|
private function login() |
309
|
|
|
{ |
310
|
96 |
|
if ($this->_auth) { |
311
|
|
|
return $this->_auth; |
312
|
|
|
} |
313
|
|
|
|
314
|
96 |
|
$writeBinn= new BinnList; |
315
|
|
|
|
316
|
96 |
|
$writeBinn->addInt16(self::DAEMON_SERVER_MODE_AUTH); |
317
|
96 |
|
$writeBinn->addStr($this->username); |
318
|
96 |
|
$writeBinn->addStr($this->password); |
319
|
96 |
|
$writeBinn->addInt16($this->mode); |
320
|
|
|
|
321
|
96 |
|
$read = $this->writeAndReadSocket($writeBinn->serialize()); |
322
|
|
|
|
323
|
96 |
|
$readBinn = new BinnList; |
324
|
96 |
|
$readBinn->binnOpen($read); |
325
|
96 |
|
$results = $readBinn->unserialize(); |
326
|
|
|
|
327
|
96 |
|
if ($results[0] == self::STATUS_OK) { |
328
|
96 |
|
$this->_auth = true; |
329
|
32 |
|
} else { |
330
|
|
|
throw new RuntimeException('Could not login with connection: ' . $this->host . ':' . $this->port |
331
|
|
|
. ', username: ' . $this->username); |
332
|
|
|
} |
333
|
|
|
|
334
|
96 |
|
return $this->_auth; |
335
|
|
|
} |
336
|
|
|
} |
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 theid
property of an instance of theAccount
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.