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