]> git.mxchange.org Git - hub.git/blob - application/hub/main/class_BaseHubSystem.php
Handled "connection reset by peer" error.
[hub.git] / application / hub / main / class_BaseHubSystem.php
1 <?php
2 /**
3  * A general hub system class
4  *
5  * @author              Roland Haeder <webmaster@shipsimu.org>
6  * @version             0.0.0
7  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2012 Hub Developer Team
8  * @license             GNU GPL 3.0 or any newer version
9  * @link                http://www.shipsimu.org
10  *
11  * This program is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation, either version 3 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23  */
24 class BaseHubSystem extends BaseFrameworkSystem {
25         // Exception codes
26         const EXCEPTION_UNSUPPORTED_ERROR_HANDLER     = 0x900;
27         const EXCEPTION_CHUNK_ALREADY_ASSEMBLED       = 0x901;
28         const EXCEPTION_ANNOUNCEMENT_NOT_ACCEPTED     = 0x902;
29         const EXCEPTION_INVALID_CONNECTION_TYPE       = 0x903;
30         const EXCEPTION_ANNOUNCEMENT_NOT_ATTEMPTED    = 0x904;
31         const EXCEPTION_BASE64_ENCODING_NOT_MODULO_4  = 0x905;
32         const EXCEPTION_NODE_SESSION_ID_NOT_VERIFYING = 0x906;
33         const EXCEPTION_REQUEST_NOT_ACCEPTED          = 0x907;
34         const SOCKET_ERROR_CONNECTION_RESET_BY_PEER   = 0x908;
35
36         // Message status codes
37         const MESSAGE_STATUS_CODE_OKAY = 'OKAY';
38
39         /**
40          * Separator for all bootstrap node entries
41          */
42         const BOOTSTRAP_NODES_SEPARATOR = ';';
43
44         /**
45          * An instance of a node
46          */
47         private $nodeInstance = NULL;
48
49         /**
50          * An instance of a cruncher
51          */
52         private $cruncherInstance = NULL;
53
54         /**
55          * Listener instance
56          */
57         private $listenerInstance = NULL;
58
59         /**
60          * A network package handler instance
61          */
62         private $packageInstance = NULL;
63
64         /**
65          * A Receivable instance
66          */
67         private $receiverInstance = NULL;
68
69         /**
70          * State instance
71          */
72         private $stateInstance = NULL;
73
74         /**
75          * Listener pool instance
76          */
77         private $listenerPoolInstance = NULL;
78
79         /**
80          * Fragmenter instance
81          */
82         private $fragmenterInstance = NULL;
83
84         /**
85          * Decoder instance
86          */
87         private $decoderInstance = NULL;
88
89         /**
90          * Assembler instance
91          */
92         private $assemblerInstance = NULL;
93
94         /**
95          * Protected constructor
96          *
97          * @param       $className      Name of the class
98          * @return      void
99          */
100         protected function __construct ($className) {
101                 // Call parent constructor
102                 parent::__construct($className);
103         }
104
105         /**
106          * Getter for node instance
107          *
108          * @return      $nodeInstance   An instance of a node node
109          */
110         public final function getNodeInstance () {
111                 return $this->nodeInstance;
112         }
113
114         /**
115          * Setter for node instance
116          *
117          * @param       $nodeInstance   An instance of a node node
118          * @return      void
119          */
120         protected final function setNodeInstance (NodeHelper $nodeInstance) {
121                 $this->nodeInstance = $nodeInstance;
122         }
123
124         /**
125          * Getter for cruncher instance
126          *
127          * @return      $cruncherInstance       An instance of a cruncher cruncher
128          */
129         public final function getCruncherInstance () {
130                 return $this->cruncherInstance;
131         }
132
133         /**
134          * Setter for cruncher instance
135          *
136          * @param       $cruncherInstance       An instance of a cruncher cruncher
137          * @return      void
138          */
139         protected final function setCruncherInstance (CruncherHelper $cruncherInstance) {
140                 $this->cruncherInstance = $cruncherInstance;
141         }
142
143         /**
144          * Setter for listener instance
145          *
146          * @param       $listenerInstance       A Listenable instance
147          * @return      void
148          */
149         protected final function setListenerInstance (Listenable $listenerInstance) {
150                 $this->listenerInstance = $listenerInstance;
151         }
152
153         /**
154          * Getter for listener instance
155          *
156          * @return      $listenerInstance       A Listenable instance
157          */
158         protected final function getListenerInstance () {
159                 return $this->listenerInstance;
160         }
161
162         /**
163          * Setter for network package handler instance
164          *
165          * @param       $packageInstance        The network package instance we shall set
166          * @return      void
167          */
168         protected final function setPackageInstance (Deliverable $packageInstance) {
169                 $this->packageInstance = $packageInstance;
170         }
171
172         /**
173          * Getter for network package handler instance
174          *
175          * @return      $packageInstance        The network package handler instance we shall set
176          */
177         protected final function getPackageInstance () {
178                 return $this->packageInstance;
179         }
180
181         /**
182          * Setter for receiver instance
183          *
184          * @param       $receiverInstance       A Receivable instance we shall set
185          * @return      void
186          */
187         protected final function setReceiverInstance (Receivable $receiverInstance) {
188                 $this->receiverInstance = $receiverInstance;
189         }
190
191         /**
192          * Getter for receiver instance
193          *
194          * @return      $receiverInstance       A Receivable instance we shall get
195          */
196         protected final function getReceiverInstance () {
197                 return $this->receiverInstance;
198         }
199
200         /**
201          * Setter for state instance
202          *
203          * @param       $stateInstance  A Stateable instance
204          * @return      void
205          */
206         public final function setStateInstance (Stateable $stateInstance) {
207                 $this->stateInstance = $stateInstance;
208         }
209
210         /**
211          * Getter for state instance
212          *
213          * @return      $stateInstance  A Stateable instance
214          */
215         public final function getStateInstance () {
216                 return $this->stateInstance;
217         }
218
219         /**
220          * Setter for listener pool instance
221          *
222          * @param       $listenerPoolInstance   The new listener pool instance
223          * @return      void
224          */
225         protected final function setListenerPoolInstance (PoolableListener $listenerPoolInstance) {
226                 $this->listenerPoolInstance = $listenerPoolInstance;
227         }
228
229         /**
230          * Getter for listener pool instance
231          *
232          * @return      $listenerPoolInstance   Our current listener pool instance
233          */
234         public final function getListenerPoolInstance () {
235                 return $this->listenerPoolInstance;
236         }
237
238         /**
239          * Setter for fragmenter instance
240          *
241          * @param       $fragmenterInstance             A Fragmentable instance
242          * @return      void
243          */
244         protected final function setFragmenterInstance (Fragmentable $fragmenterInstance) {
245                 $this->fragmenterInstance = $fragmenterInstance;
246         }
247
248         /**
249          * Getter for fragmenter instance
250          *
251          * @return      $fragmenterInstance             A Fragmentable instance
252          */
253         protected final function getFragmenterInstance () {
254                 return $this->fragmenterInstance;
255         }
256
257         /**
258          * Setter for decoder instance
259          *
260          * @param       $decoderInstance        A Decodeable instance
261          * @return      void
262          */
263         protected final function setDecoderInstance (Decodeable $decoderInstance) {
264                 $this->decoderInstance = $decoderInstance;
265         }
266
267         /**
268          * Getter for decoder instance
269          *
270          * @return      $decoderInstance        A Decodeable instance
271          */
272         protected final function getDecoderInstance () {
273                 return $this->decoderInstance;
274         }
275
276         /**
277          * Setter for assembler instance
278          *
279          * @param       $assemblerInstance      A Decodeable instance
280          * @return      void
281          */
282         protected final function setAssemblerInstance (Assembler $assemblerInstance) {
283                 $this->assemblerInstance = $assemblerInstance;
284         }
285
286         /**
287          * Getter for assembler instance
288          *
289          * @return      $assemblerInstance      A Decodeable instance
290          */
291         protected final function getAssemblerInstance () {
292                 return $this->assemblerInstance;
293         }
294
295         /**
296          * Setter for node id
297          *
298          * @param       $nodeId         The new node id
299          * @return      void
300          */
301         protected final function setNodeId ($nodeId) {
302                 // Set it config now
303                 $this->getConfigInstance()->setConfigEntry('node_id', (string) $nodeId);
304         }
305
306         /**
307          * Getter for node id
308          *
309          * @return      $nodeId         Current node id
310          */
311         public final function getNodeId () {
312                 // Get it from config
313                 return $this->getConfigInstance()->getConfigEntry('node_id');
314         }
315
316         /**
317          * Setter for private key
318          *
319          * @param       $privateKey             The new private key
320          * @return      void
321          */
322         protected final function setPrivateKey ($privateKey) {
323                 // Set it config now
324                 $this->getConfigInstance()->setConfigEntry('private_key', (string) $privateKey);
325         }
326
327         /**
328          * Getter for private key
329          *
330          * @return      $privateKey             Current private key
331          */
332         public final function getPrivateKey () {
333                 // Get it from config
334                 return $this->getConfigInstance()->getConfigEntry('private_key');
335         }
336
337         /**
338          * Setter for private key hash
339          *
340          * @param       $privateKeyHash         The new private key hash
341          * @return      void
342          */
343         protected final function setPrivateKeyHash ($privateKeyHash) {
344                 // Set it config now
345                 $this->getConfigInstance()->setConfigEntry('private_key_hash', (string) $privateKeyHash);
346         }
347
348         /**
349          * Getter for private key hash
350          *
351          * @return      $privateKeyHash         Current private key hash
352          */
353         public final function getPrivateKeyHash () {
354                 // Get it from config
355                 return $this->getConfigInstance()->getConfigEntry('private_key_hash');
356         }
357
358         /**
359          * Setter for session id
360          *
361          * @param       $sessionId              The new session id
362          * @return      void
363          */
364         protected final function setSessionId ($sessionId) {
365                 $this->getConfigInstance()->setConfigEntry('session_id', (string) $sessionId);
366         }
367
368         /**
369          * Getter for session id
370          *
371          * @return      $sessionId              Current session id
372          */
373         public final function getSessionId () {
374                 return $this->getConfigInstance()->getConfigEntry('session_id');
375         }
376
377         /**
378          * Constructs a callable method name from given socket error code. If the
379          * method is not found, a generic one is used.
380          *
381          * @param       $errorCode              Error code from socket_last_error()
382          * @return      $handlerName    Call-back method name for the error handler
383          * @throws      UnsupportedSocketErrorHandlerException If the error handler is not implemented
384          */
385         protected function getSocketErrorHandlerFromCode ($errorCode) {
386                 // Create a name from translated error code
387                 $handlerName = 'socketError' . $this->convertToClassName($this->translateSocketErrorCodeToName($errorCode)) . 'Handler';
388
389                 // Is the call-back method there?
390                 if (!method_exists($this, $handlerName)) {
391                         // Please implement this
392                         throw new UnsupportedSocketErrorHandlerException(array($this, $handlerName, $errorCode), self::EXCEPTION_UNSUPPORTED_ERROR_HANDLER);
393                 } // END - if
394
395                 // Return it
396                 return $handlerName;
397         }
398
399         /**
400          * Handles socket error for given socket resource and peer data. This method
401          * validates $socketResource if it is a valid resource (see is_resource())
402          * but assumes valid data in array $recipientData, except that
403          * count($recipientData) is always 2.
404          *
405          * @param       $method                         Value of __METHOD__ from calling method
406          * @param       $line                           Value of __LINE__ from calling method
407          * @param       $socketResource         A valid socket resource
408          * @param       $recipientData          An array with two elements: 0=IP number, 1=port number
409          * @return      void
410          * @throws      InvalidSocketException  If $socketResource is no socket resource
411          * @throws      NoSocketErrorDetectedException  If socket_last_error() gives zero back
412          */
413         protected final function handleSocketError ($method, $line, $socketResource, array $recipientData) {
414                 // This method handles only socket resources
415                 if (!is_resource($socketResource)) {
416                         // No resource, abort here
417                         throw new InvalidSocketException(array($this, $socketResource), BaseListener::EXCEPTION_INVALID_SOCKET);
418                 } // END - if
419
420                 // Check count of array, should be two
421                 assert(count($recipientData) == 2);
422
423                 // Get error code for first validation (0 is not an error)
424                 $errorCode = socket_last_error($socketResource);
425
426                 // If the error code is zero, someone called this method without an error
427                 if ($errorCode == 0) {
428                         // No error detected (or previously cleared outside this method)
429                         throw new NoSocketErrorDetectedException(array($this, $socketResource), BaseListener::EXCEPTION_NO_SOCKET_ERROR);
430                 } // END - if
431
432                 // Get handler (method) name
433                 $handlerName = $this->getSocketErrorHandlerFromCode($errorCode);
434
435                 // Call-back the error handler method
436                 call_user_func_array(array($this, $handlerName), array($socketResource, $recipientData));
437
438                 // Finally clear the error because it has been handled
439                 socket_clear_error($socketResource);
440         }
441
442         /**
443          * Checks whether the final (last) chunk is valid
444          *
445          * @param       $chunks         An array with chunks and (hopefully) a valid final chunk
446          * @return      $isValid        Whether the final (last) chunk is valid
447          */
448         protected function isValidFinalChunk (array $chunks) {
449                 // Default is all fine
450                 $isValid = TRUE;
451
452                 // Split the (possible) EOP chunk
453                 $chunkSplits = explode(PackageFragmenter::CHUNK_DATA_HASH_SEPARATOR, $chunks[count($chunks) - 1]);
454
455                 // Make sure chunks with only 3 elements are parsed (for details see ChunkHandler)
456                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('eopChunk=' . $chunks[count($chunks) - 1] . ',chunkSplits=' . print_r($chunkSplits, TRUE));
457                 assert(count($chunkSplits) == 3);
458
459                 // Validate final chunk
460                 if (substr($chunkSplits[ChunkHandler::CHUNK_SPLITS_INDEX_RAW_DATA], 0, strlen(PackageFragmenter::END_OF_PACKAGE_IDENTIFIER)) != PackageFragmenter::END_OF_PACKAGE_IDENTIFIER) {
461                         // Not fine
462                         $isValid = FALSE;
463                 } elseif (substr_count($chunkSplits[ChunkHandler::CHUNK_SPLITS_INDEX_RAW_DATA], PackageFragmenter::CHUNK_HASH_SEPARATOR) != 1) {
464                         // CHUNK_HASH_SEPARATOR shall only be found once
465                         $isValid = FALSE;
466                 }
467
468                 // Return status
469                 return $isValid;
470         }
471
472         /**
473          * Translates socket error codes into our own internal names which can be
474          * used for call-backs.
475          *
476          * @param       $errorCode      The error code from socket_last_error() to be translated
477          * @return      $errorName      The translated name (all lower-case, with underlines)
478          */
479         public function translateSocketErrorCodeToName ($errorCode) {
480                 // Nothing bad happened by default
481                 $errorName = BaseRawDataHandler::SOCKET_CONNECTED;
482
483                 // Is the code a number, then we have to change it
484                 switch ($errorCode) {
485                         case 0: // Silently ignored, the socket is connected
486                                 break;
487
488                         case 11:  // "Resource temporary unavailable"
489                                 $errorName = BaseRawDataHandler::SOCKET_ERROR_RESOURCE_UNAVAILABLE;
490                                 break;
491
492                         case 104: // "Connection reset by peer"
493                                 $errorName = BaseRawDataHandler::SOCKET_ERROR_CONNECTION_RESET_BY_PEER;
494                                 break;
495
496                         case 107: // "Transport end-point not connected"
497                         case 134: // On some (?) systems for 'transport end-point not connected'
498                                 // @TODO On some systems it is 134, on some 107?
499                                 $errorName = BaseRawDataHandler::SOCKET_ERROR_TRANSPORT_ENDPOINT;
500                                 break;
501
502                         case 110: // "Connection timed out"
503                                 $errorName = BaseRawDataHandler::SOCKET_ERROR_CONNECTION_TIMED_OUT;
504                                 break;
505
506                         case 111: // "Connection refused"
507                                 $errorName = BaseRawDataHandler::SOCKET_ERROR_CONNECTION_REFUSED;
508                                 break;
509
510                         case 113: // "No route to host"
511                                 $errorName = BaseRawDataHandler::SOCKET_ERROR_NO_ROUTE_TO_HOST;
512                                 break;
513
514                         case 114: // "Operation already in progress"
515                                 $errorName = BaseRawDataHandler::SOCKET_ERROR_OPERATION_ALREADY_PROGRESS;
516                                 break;
517
518                         case 115: // "Operation now in progress"
519                                 $errorName = BaseRawDataHandler::SOCKET_ERROR_OPERATION_IN_PROGRESS;
520                                 break;
521
522                         default: // Everything else <> 0
523                                 // Unhandled error code detected, so first debug it because we may want to handle it like the others
524                                 self::createDebugInstance(__CLASS__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . '] UNKNOWN ERROR CODE = ' . $errorCode . ', MESSAGE = ' . socket_strerror($errorCode));
525
526                                 // Change it only in this class
527                                 $errorName = BaseRawDataHandler::SOCKET_ERROR_UNKNOWN;
528                                 break;
529                 }
530
531                 // Return translated name
532                 return $errorName;
533         }
534
535         /**
536          * Shuts down a given socket resource. This method does only ease calling
537          * the right visitor.
538          *
539          * @param       $socketResource         A valid socket resource
540          * @return      void
541          */
542         public function shutdownSocket ($socketResource) {
543                 // Debug message
544                 self::createDebugInstance(__CLASS__)->debugOutput('HUB-SYSTEM: Shutting down socket resource ' . $socketResource . ' with state ' . $this->getPrintableState() . ' ...');
545
546                 // Set socket resource
547                 $this->setSocketResource($socketResource);
548
549                 // Get a visitor instance
550                 $visitorInstance = ObjectFactory::createObjectByConfiguredName('shutdown_socket_visitor_class');
551
552                 // Debug output
553                 self::createDebugInstance(__CLASS__)->debugOutput('HUB-SYSTEM:' . $this->__toString() . ': visitorInstance=' . $visitorInstance->__toString());
554
555                 // Call the visitor
556                 $this->accept($visitorInstance);
557         }
558
559         /**
560          * Half-shuts down a given socket resource. This method does only ease calling
561          * an other visitor than shutdownSocket() does.
562          *
563          * @param       $socketResource         A valid socket resource
564          * @return      void
565          */
566         public function halfShutdownSocket ($socketResource) {
567                 // Debug message
568                 self::createDebugInstance(__CLASS__)->debugOutput('HUB-SYSTEM: Half-shutting down socket resource ' . $socketResource . ' with state ' . $this->getPrintableState() . ' ...');
569
570                 // Set socket resource
571                 $this->setSocketResource($socketResource);
572
573                 // Get a visitor instance
574                 $visitorInstance = ObjectFactory::createObjectByConfiguredName('half_shutdown_socket_visitor_class');
575
576                 // Debug output
577                 self::createDebugInstance(__CLASS__)->debugOutput('HUB-SYSTEM:' . $this->__toString() . ': visitorInstance=' . $visitorInstance->__toString());
578
579                 // Call the visitor
580                 $this->accept($visitorInstance);
581         }
582
583         /**
584          * "Getter" for a printable state name
585          *
586          * @return      $stateName      Name of the node's state in a printable format
587          */
588         public final function getPrintableState () {
589                 // Default is 'null'
590                 $stateName = 'null';
591
592                 // Get the state instance
593                 $stateInstance = $this->getStateInstance();
594
595                 // Is it an instance of Stateable?
596                 if ($stateInstance instanceof Stateable) {
597                         // Then use that state name
598                         $stateName = $stateInstance->getStateName();
599                 } // END - if
600
601                 // Return result
602                 return $stateName;
603         }
604 }
605
606 // [EOF]
607 ?>