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}", |
|
|
|
|
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
|
|
|
} |
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.