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