]> git.mxchange.org Git - hub.git/blob - application/hub/main/package/class_NetworkPackage.php
Commented out some noisy debug lines
[hub.git] / application / hub / main / package / class_NetworkPackage.php
1 <?php
2 /**
3  * A NetworkPackage class. This class implements Deliverable and Receivable
4  * because all network packages should be deliverable to other nodes and
5  * receivable from other nodes. It further provides methods for reading raw
6  * content from template engines and feeding it to the stacker for undeclared
7  * packages.
8  *
9  * The factory method requires you to provide a compressor class (which must
10  * implement the Compressor interface). If you don't want any compression (not
11  * adviceable due to increased network load), please use the NullCompressor
12  * class and encode it with BASE64 for a more error-free transfer over the
13  * Internet.
14  *
15  * For performance reasons, this class should only be instanciated once and then
16  * used as a "pipe-through" class.
17  *
18  * @author              Roland Haeder <webmaster@ship-simu.org>
19  * @version             0.0.0
20  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2012 Hub Developer Team
21  * @license             GNU GPL 3.0 or any newer version
22  * @link                http://www.ship-simu.org
23  * @todo                Needs to add functionality for handling the object's type
24  *
25  * This program is free software: you can redistribute it and/or modify
26  * it under the terms of the GNU General Public License as published by
27  * the Free Software Foundation, either version 3 of the License, or
28  * (at your option) any later version.
29  *
30  * This program is distributed in the hope that it will be useful,
31  * but WITHOUT ANY WARRANTY; without even the implied warranty of
32  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
33  * GNU General Public License for more details.
34  *
35  * You should have received a copy of the GNU General Public License
36  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
37  */
38 class NetworkPackage extends BaseHubSystem implements Deliverable, Receivable, Registerable, Visitable {
39         /**
40          * Package mask for compressing package data:
41          * 0: Compressor extension
42          * 1: Raw package data
43          * 2: Tags, seperated by semicolons, no semicolon is required if only one tag is needed
44          * 3: Checksum
45          *                     0  1  2  3
46          */
47         const PACKAGE_MASK = '%s%s%s%s%s%s%s';
48
49         /**
50          * Separator for the above mask
51          */
52         const PACKAGE_MASK_SEPARATOR = '^';
53
54         /**
55          * Size of an array created by invoking
56          * explode(NetworkPackage::PACKAGE_MASK_SEPARATOR, $content).
57          */
58         const PACKAGE_CONTENT_ARRAY_SIZE = 4;
59
60         /**
61          * Separator for checksum
62          */
63         const PACKAGE_CHECKSUM_SEPARATOR = '_';
64
65         /**
66          * Array indexes for above mask, start with zero
67          */
68         const INDEX_COMPRESSOR_EXTENSION = 0;
69         const INDEX_PACKAGE_DATA         = 1;
70         const INDEX_TAGS                 = 2;
71         const INDEX_CHECKSUM             = 3;
72
73         /**
74          * Array indexes for raw package array
75          */
76         const INDEX_PACKAGE_SENDER    = 0;
77         const INDEX_PACKAGE_RECIPIENT = 1;
78         const INDEX_PACKAGE_CONTENT   = 2;
79         const INDEX_PACKAGE_STATUS    = 3;
80         const INDEX_PACKAGE_SIGNATURE = 4;
81
82         /**
83          * Size of the decoded data array ('status' is not included)
84          */
85         const DECODED_DATA_ARRAY_SIZE = 4;
86
87         /**
88          * Named array elements for decoded package content
89          */
90         const PACKAGE_CONTENT_EXTENSION = 'compressor';
91         const PACKAGE_CONTENT_MESSAGE   = 'message';
92         const PACKAGE_CONTENT_TAGS      = 'tags';
93         const PACKAGE_CONTENT_CHECKSUM  = 'checksum';
94
95         /**
96          * Named array elements for package data
97          */
98         const PACKAGE_DATA_SENDER    = 'sender';
99         const PACKAGE_DATA_RECIPIENT = 'recipient';
100         const PACKAGE_DATA_PROTOCOL  = 'protocol';
101         const PACKAGE_DATA_CONTENT   = 'content';
102         const PACKAGE_DATA_STATUS    = 'status';
103         const PACKAGE_DATA_SIGNATURE = 'signature';
104
105         /**
106          * All package status
107          */
108         const PACKAGE_STATUS_NEW     = 'new';
109         const PACKAGE_STATUS_FAILED  = 'failed';
110         const PACKAGE_STATUS_DECODED = 'decoded';
111         const PACKAGE_STATUS_FAKED   = 'faked';
112
113         /**
114          * Constants for message data array
115          */
116         const MESSAGE_ARRAY_DATA = 'message_data';
117         const MESSAGE_ARRAY_TYPE = 'message_type';
118
119         /**
120          * Generic answer status field
121          */
122
123         /**
124          * Tags separator
125          */
126         const PACKAGE_TAGS_SEPARATOR = ';';
127
128         /**
129          * Raw package data separator
130          */
131         const PACKAGE_DATA_SEPARATOR = '#';
132
133         /**
134          * Separator for more than one recipient
135          */
136         const PACKAGE_RECIPIENT_SEPARATOR = ':';
137
138         /**
139          * Network target (alias): 'upper nodes'
140          */
141         const NETWORK_TARGET_UPPER_NODES = 'upper';
142
143         /**
144          * Network target (alias): 'self'
145          */
146         const NETWORK_TARGET_SELF = 'self';
147
148         /**
149          * TCP package size in bytes
150          */
151         const TCP_PACKAGE_SIZE = 512;
152
153         /**************************************************************************
154          *                    Stacker for out-going packages                      *
155          **************************************************************************/
156
157         /**
158          * Stacker name for "undeclared" packages
159          */
160         const STACKER_NAME_UNDECLARED = 'package_undeclared';
161
162         /**
163          * Stacker name for "declared" packages (which are ready to send out)
164          */
165         const STACKER_NAME_DECLARED = 'package_declared';
166
167         /**
168          * Stacker name for "out-going" packages
169          */
170         const STACKER_NAME_OUTGOING = 'package_outgoing';
171
172         /**************************************************************************
173          *                     Stacker for incoming packages                      *
174          **************************************************************************/
175
176         /**
177          * Stacker name for "incoming" decoded raw data
178          */
179         const STACKER_NAME_DECODED_INCOMING = 'package_decoded_data';
180
181         /**
182          * Stacker name for handled decoded raw data
183          */
184         const STACKER_NAME_DECODED_HANDLED = 'package_handled_decoded';
185
186         /**
187          * Stacker name for "chunked" decoded raw data
188          */
189         const STACKER_NAME_DECODED_CHUNKED = 'package_chunked_decoded';
190
191         /**************************************************************************
192          *                     Stacker for incoming messages                      *
193          **************************************************************************/
194
195         /**
196          * Stacker name for new messages
197          */
198         const STACKER_NAME_NEW_MESSAGE = 'package_new_message';
199
200         /**
201          * Stacker name for processed messages
202          */
203         const STACKER_NAME_PROCESSED_MESSAGE = 'package_processed_message';
204
205         /**************************************************************************
206          *                   Stacker for other/internal purposes                  *
207          **************************************************************************/
208
209         /**
210          * Stacker name for "back-buffered" packages
211          */
212         const STACKER_NAME_BACK_BUFFER = 'package_backbuffer';
213
214         /**************************************************************************
215          *                            Protocol names                              *
216          **************************************************************************/
217         const PROTOCOL_TCP = 'TCP';
218         const PROTOCOL_UDP = 'UDP';
219
220         /**
221          * Protected constructor
222          *
223          * @return      void
224          */
225         protected function __construct () {
226                 // Call parent constructor
227                 parent::__construct(__CLASS__);
228         }
229
230         /**
231          * Creates an instance of this class
232          *
233          * @param       $compressorInstance             A Compressor instance for compressing the content
234          * @return      $packageInstance                An instance of a Deliverable class
235          */
236         public static final function createNetworkPackage (Compressor $compressorInstance) {
237                 // Get new instance
238                 $packageInstance = new NetworkPackage();
239
240                 // Now set the compressor instance
241                 $packageInstance->setCompressorInstance($compressorInstance);
242
243                 /*
244                  * We need to initialize a stack here for our packages even for those
245                  * which have no recipient address and stamp... ;-) This stacker will
246                  * also be used for incoming raw data to handle it.
247                  */
248                 $stackerInstance = ObjectFactory::createObjectByConfiguredName('network_package_stacker_class');
249
250                 // At last, set it in this class
251                 $packageInstance->setStackerInstance($stackerInstance);
252
253                 // Init all stacker
254                 $packageInstance->initStackers();
255
256                 // Get a visitor instance for speeding up things and set it
257                 $visitorInstance = ObjectFactory::createObjectByConfiguredName('node_raw_data_monitor_visitor_class', array($packageInstance));
258                 $packageInstance->setVisitorInstance($visitorInstance);
259
260                 // Get crypto instance and set it, too
261                 $cryptoInstance = ObjectFactory::createObjectByConfiguredName('crypto_class');
262                 $packageInstance->setCryptoInstance($cryptoInstance);
263
264                 // Get a singleton package assembler instance from factory and set it here, too
265                 $assemblerInstance = PackageAssemblerFactory::createAssemblerInstance($packageInstance);
266                 $packageInstance->setAssemblerInstance($assemblerInstance);
267
268                 // Return the prepared instance
269                 return $packageInstance;
270         }
271
272         /**
273          * Initialize all stackers
274          *
275          * @param       $forceReInit    Whether to force reinitialization of all stacks
276          * @return      void
277          */
278         protected function initStackers ($forceReInit = false) {
279                 // Initialize all
280                 foreach (
281                         array(
282                                 self::STACKER_NAME_UNDECLARED,
283                                 self::STACKER_NAME_DECLARED,
284                                 self::STACKER_NAME_OUTGOING,
285                                 self::STACKER_NAME_DECODED_INCOMING,
286                                 self::STACKER_NAME_DECODED_HANDLED,
287                                 self::STACKER_NAME_DECODED_CHUNKED,
288                                 self::STACKER_NAME_NEW_MESSAGE,
289                                 self::STACKER_NAME_PROCESSED_MESSAGE,
290                                 self::STACKER_NAME_BACK_BUFFER
291                         ) as $stackerName) {
292                                 // Init this stacker
293                                 $this->getStackerInstance()->initStacker($stackerName, $forceReInit);
294                 } // END - foreach
295         }
296
297         /**
298          * "Getter" for hash from given content
299          *
300          * @param       $content        Raw package content
301          * @return      $hash           Hash for given package content
302          */
303         private function getHashFromContent ($content) {
304                 // Debug message
305                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE: content[md5]=' . md5($content) . ',sender=' . $this->getSessionId() . ',compressor=' . $this->getCompressorInstance()->getCompressorExtension());
306
307                 // Create the hash
308                 // @TODO crc32() is very weak, but it needs to be fast
309                 $hash = crc32(
310                         $content .
311                         self::PACKAGE_CHECKSUM_SEPARATOR .
312                         $this->getSessionId() .
313                         self::PACKAGE_CHECKSUM_SEPARATOR .
314                         $this->getCompressorInstance()->getCompressorExtension()
315                 );
316
317                 // And return it
318                 return $hash;
319         }
320
321         /**
322          * Checks whether the checksum (sometimes called "hash") is the same
323          *
324          * @param       $decodedContent         Package raw content
325          * @param       $decodedData            Whole raw package data array
326          * @return      $isChecksumValid        Whether the checksum is the same
327          */
328         private function isChecksumValid (array $decodedContent, array $decodedData) {
329                 // Get checksum
330                 $checksum = $this->getHashFromContentSessionId($decodedContent, $decodedData[self::PACKAGE_DATA_SENDER]);
331
332                 // Is it the same?
333                 $isChecksumValid = ($checksum == $decodedContent[self::PACKAGE_CONTENT_CHECKSUM]);
334
335                 // Return it
336                 return $isChecksumValid;
337         }
338
339         /**
340          * Change the package with given status in given stack
341          *
342          * @param       $packageData    Raw package data in an array
343          * @param       $stackerName    Name of the stacker
344          * @param       $newStatus              New status to set
345          * @return      void
346          */
347         private function changePackageStatus (array $packageData, $stackerName, $newStatus) {
348                 // Skip this for empty stacks
349                 if ($this->getStackerInstance()->isStackEmpty($stackerName)) {
350                         // This avoids an exception after all packages has failed
351                         return;
352                 } // END - if
353
354                 // Pop the entry (it should be it)
355                 $nextData = $this->getStackerInstance()->popNamed($stackerName);
356
357                 // Compare both signatures
358                 assert($nextData[self::PACKAGE_DATA_SIGNATURE] == $packageData[self::PACKAGE_DATA_SIGNATURE]);
359
360                 // Temporary set the new status
361                 $packageData[self::PACKAGE_DATA_STATUS] = $newStatus;
362
363                 // And push it again
364                 $this->getStackerInstance()->pushNamed($stackerName, $packageData);
365         }
366
367         /**
368          * "Getter" for hash from given content and sender's session id
369          *
370          * @param       $decodedContent         Raw package content
371          * @param       $sessionId                      Session id of the sender
372          * @return      $hash                           Hash for given package content
373          */
374         public function getHashFromContentSessionId (array $decodedContent, $sessionId) {
375                 // Debug message
376                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE: content[md5]=' . md5($decodedContent[self::PACKAGE_CONTENT_MESSAGE]) . ',sender=' . $sessionId . ',compressor=' . $decodedContent[self::PACKAGE_CONTENT_EXTENSION]);
377
378                 // Create the hash
379                 // @TODO crc32() is very weak, but it needs to be fast
380                 $hash = crc32(
381                         $decodedContent[self::PACKAGE_CONTENT_MESSAGE] .
382                         self::PACKAGE_CHECKSUM_SEPARATOR .
383                         $sessionId .
384                         self::PACKAGE_CHECKSUM_SEPARATOR .
385                         $decodedContent[self::PACKAGE_CONTENT_EXTENSION]
386                 );
387
388                 // And return it
389                 return $hash;
390         }
391
392         ///////////////////////////////////////////////////////////////////////////
393         //                   Delivering packages / raw data
394         ///////////////////////////////////////////////////////////////////////////
395
396         /**
397          * Delivers the given raw package data.
398          *
399          * @param       $packageData    Raw package data in an array
400          * @return      void
401          */
402         private function declareRawPackageData (array $packageData) {
403                 /*
404                  * We need to disover every recipient, just in case we have a
405                  * multi-recipient entry like 'upper' is. 'all' may be a not so good
406                  * target because it causes an overload on the network and may be
407                  * abused for attacking the network with large packages.
408                  */
409                 $discoveryInstance = PackageDiscoveryFactory::createPackageDiscoveryInstance();
410
411                 // Discover all recipients, this may throw an exception
412                 $discoveryInstance->discoverRecipients($packageData);
413
414                 // Now get an iterator
415                 $iteratorInstance = $discoveryInstance->getIterator();
416
417                 // Rewind back to the beginning
418                 $iteratorInstance->rewind();
419
420                 // ... and begin iteration
421                 while ($iteratorInstance->valid()) {
422                         // Get current entry
423                         $currentRecipient = $iteratorInstance->current();
424
425                         // Debug message
426                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE: Setting recipient to ' . $currentRecipient . ',previous=' . $packageData[self::PACKAGE_DATA_RECIPIENT]);
427
428                         // Set the recipient
429                         $packageData[self::PACKAGE_DATA_RECIPIENT] = $currentRecipient;
430
431                         // And enqueue it to the writer class
432                         $this->getStackerInstance()->pushNamed(self::STACKER_NAME_DECLARED, $packageData);
433
434                         // Debug message
435                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE: Package declared for recipient ' . $currentRecipient);
436
437                         // Skip to next entry
438                         $iteratorInstance->next();
439                 } // END - while
440
441                 /*
442                  * The recipient list can be cleaned up here because the package which
443                  * shall be delivered has already been added for all entries from the
444                  * list.
445                  */
446                 $discoveryInstance->clearRecipients();
447         }
448
449         /**
450          * Delivers raw package data. In short, this will discover the raw socket
451          * resource through a discovery class (which will analyse the receipient of
452          * the package), register the socket with the connection (handler/helper?)
453          * instance and finally push the raw data on our outgoing queue.
454          *
455          * @param       $packageData    Raw package data in an array
456          * @return      void
457          */
458         private function deliverRawPackageData (array $packageData) {
459                 /*
460                  * This package may become big, depending on the shared object size or
461                  * delivered message size which shouldn't be so long (to save
462                  * bandwidth). Because of the nature of the used protocol (TCP) we need
463                  * to split it up into smaller pieces to fit it into a TCP frame.
464                  *
465                  * So first we need (again) a discovery class but now a protocol
466                  * discovery to choose the right socket resource. The discovery class
467                  * should take a look at the raw package data itself and then decide
468                  * which (configurable!) protocol should be used for that type of
469                  * package.
470                  */
471                 $discoveryInstance = SocketDiscoveryFactory::createSocketDiscoveryInstance();
472
473                 // Now discover the right protocol
474                 $socketResource = $discoveryInstance->discoverSocket($packageData, BaseConnectionHelper::CONNECTION_TYPE_OUTGOING);
475
476                 // Debug message
477                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE: Reached line ' . __LINE__ . ' after discoverSocket() has been called.');
478
479                 // We have to put this socket in our registry, so get an instance
480                 $registryInstance = SocketRegistryFactory::createSocketRegistryInstance();
481
482                 // Get the listener from registry
483                 $helperInstance = Registry::getRegistry()->getInstance('connection');
484
485                 // Debug message
486                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE: stateInstance=' . $helperInstance->getStateInstance());
487                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE: Reached line ' . __LINE__ . ' before isSocketRegistered() has been called.');
488
489                 // Is it not there?
490                 if ((is_resource($socketResource)) && (!$registryInstance->isSocketRegistered($helperInstance, $socketResource))) {
491                         // Debug message
492                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE: Registering socket ' . $socketResource . ' ...');
493
494                         // Then register it
495                         $registryInstance->registerSocket($helperInstance, $socketResource, $packageData);
496                 } elseif (!$helperInstance->getStateInstance()->isPeerStateConnected()) {
497                         // Is not connected, then we cannot send
498                         self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE: Unexpected peer state ' . $helperInstance->getStateInstance()->__toString() . ' detected.');
499
500                         // Shutdown the socket
501                         $this->shutdownSocket($socketResource);
502                 }
503
504                 // Debug message
505                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE: Reached line ' . __LINE__ . ' after isSocketRegistered() has been called.');
506
507                 // Make sure the connection is up
508                 $helperInstance->getStateInstance()->validatePeerStateConnected();
509
510                 // Debug message
511                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE: Reached line ' . __LINE__ . ' after validatePeerStateConnected() has been called.');
512
513                 // Enqueue it again on the out-going queue, the connection is up and working at this point
514                 $this->getStackerInstance()->pushNamed(self::STACKER_NAME_OUTGOING, $packageData);
515
516                 // Debug message
517                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE: Reached line ' . __LINE__ . ' after pushNamed() has been called.');
518         }
519
520         /**
521          * Sends waiting packages
522          *
523          * @param       $packageData    Raw package data
524          * @return      void
525          */
526         private function sendOutgoingRawPackageData (array $packageData) {
527                 // Init sent bytes
528                 $sentBytes = 0;
529
530                 // Get the right connection instance
531                 $helperInstance = SocketRegistryFactory::createSocketRegistryInstance()->getHandlerInstanceFromPackageData($packageData);
532
533                 // Is this connection still alive?
534                 if ($helperInstance->isShuttedDown()) {
535                         // This connection is shutting down
536                         // @TODO We may want to do somthing more here?
537                         return;
538                 } // END - if
539
540                 // Sent out package data
541                 $sentBytes = $helperInstance->sendRawPackageData($packageData);
542
543                 // Remember unsent raw bytes in back-buffer, if any
544                 $this->storeUnsentBytesInBackBuffer($packageData, $sentBytes);
545         }
546
547         /**
548          * Generates a signature for given raw package content and sender id
549          *
550          * @param       $content        Raw package data
551          * @param       $senderId       Sender id to generate a signature for
552          * @return      $signature      Signature as BASE64-encoded string
553          */
554         private function generatePackageSignature ($content, $senderId) {
555                 // Hash content and sender id together, use md5() as last algo
556                 $hash = md5($this->getCryptoInstance()->hashString($senderId . $content, $this->getPrivateKey(), false));
557
558                 // Encrypt the content again with the hash as a key
559                 $encryptedContent = $this->getCryptoInstance()->encryptString($content, $hash);
560
561                 // Encode it with BASE64
562                 $signature = base64_encode($encryptedContent);
563
564                 // Return it
565                 return $signature;
566         }
567
568         /**
569          * Checks whether the signature of given package data is 'valid', here that
570          * means it is the same or not.
571          *
572          * @param       $decodedArray           An array with 'decoded' (explode() was mostly called) data
573          * @return      $isSignatureValid       Whether the signature is valid
574          * @todo        Unfinished area, signatures are currently NOT fully supported
575          */
576         private function isPackageSignatureValid (array $decodedArray) {
577                 // Generate the signature of comparing it
578                 $signature = $this->generatePackageSignature($decodedArray[self::INDEX_PACKAGE_CONTENT], $decodedArray[self::INDEX_PACKAGE_SENDER]);
579
580                 // Is it the same?
581                 //$isSignatureValid = 
582                 exit(__METHOD__.': signature='.$signature.chr(10).',decodedArray='.print_r($decodedArray,true));
583         }
584
585         /**
586          * "Enqueues" raw content into this delivery class by reading the raw content
587          * from given helper's template instance and pushing it on the 'undeclared'
588          * stack.
589          *
590          * @param       $helperInstance         An instance of a HelpableHub class
591          * @param       $protocol                       Name of used protocol (TCP/UDP)
592          * @return      void
593          */
594         public function enqueueRawDataFromTemplate (HelpableHub $helperInstance, $protocolName) {
595                 // Get the raw content ...
596                 $content = $helperInstance->getTemplateInstance()->getRawTemplateData();
597                 //* DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('content(' . strlen($content) . ')=' . $content);
598
599                 // ... and compress it
600                 $content = $this->getCompressorInstance()->compressStream($content);
601
602                 // Add magic in front of it and hash behind it, including BASE64 encoding
603                 $packageContent = sprintf(self::PACKAGE_MASK,
604                         // 1.) Compressor's extension
605                         $this->getCompressorInstance()->getCompressorExtension(),
606                         // - separator
607                         self::PACKAGE_MASK_SEPARATOR,
608                         // 2.) Raw package content, encoded with BASE64
609                         base64_encode($content),
610                         // - separator
611                         self::PACKAGE_MASK_SEPARATOR,
612                         // 3.) Tags
613                         implode(self::PACKAGE_TAGS_SEPARATOR, $helperInstance->getPackageTags()),
614                         // - separator
615                         self::PACKAGE_MASK_SEPARATOR,
616                         // 4.) Checksum
617                         $this->getHashFromContent($content)
618                 );
619
620                 // Now prepare the temporary array and push it on the 'undeclared' stack
621                 $this->getStackerInstance()->pushNamed(self::STACKER_NAME_UNDECLARED, array(
622                         self::PACKAGE_DATA_SENDER    => $this->getSessionId(),
623                         self::PACKAGE_DATA_RECIPIENT => $helperInstance->getRecipientType(),
624                         self::PACKAGE_DATA_PROTOCOL  => $protocolName,
625                         self::PACKAGE_DATA_CONTENT   => $packageContent,
626                         self::PACKAGE_DATA_STATUS    => self::PACKAGE_STATUS_NEW,
627                         self::PACKAGE_DATA_SIGNATURE => $this->generatePackageSignature($packageContent, $this->getSessionId())
628                 ));
629         }
630
631         /**
632          * Checks whether a package has been enqueued for delivery.
633          *
634          * @return      $isEnqueued             Whether a package is enqueued
635          */
636         public function isPackageEnqueued () {
637                 // Check whether the stacker is not empty
638                 $isEnqueued = (($this->getStackerInstance()->isStackInitialized(self::STACKER_NAME_UNDECLARED)) && (!$this->getStackerInstance()->isStackEmpty(self::STACKER_NAME_UNDECLARED)));
639
640                 // Return the result
641                 return $isEnqueued;
642         }
643
644         /**
645          * Checks whether a package has been declared
646          *
647          * @return      $isDeclared             Whether a package is declared
648          */
649         public function isPackageDeclared () {
650                 // Check whether the stacker is not empty
651                 $isDeclared = (($this->getStackerInstance()->isStackInitialized(self::STACKER_NAME_DECLARED)) && (!$this->getStackerInstance()->isStackEmpty(self::STACKER_NAME_DECLARED)));
652
653                 // Return the result
654                 return $isDeclared;
655         }
656
657         /**
658          * Checks whether a package should be sent out
659          *
660          * @return      $isWaitingDelivery      Whether a package is waiting for delivery
661          */
662         public function isPackageWaitingForDelivery () {
663                 // Check whether the stacker is not empty
664                 $isWaitingDelivery = (($this->getStackerInstance()->isStackInitialized(self::STACKER_NAME_OUTGOING)) && (!$this->getStackerInstance()->isStackEmpty(self::STACKER_NAME_OUTGOING)));
665
666                 // Return the result
667                 return $isWaitingDelivery;
668         }
669
670         /**
671          * Delivers an enqueued package to the stated destination. If a non-session
672          * id is provided, recipient resolver is being asked (and instanced once).
673          * This allows that a single package is being delivered to multiple targets
674          * without enqueueing it for every target. If no target is provided or it
675          * can't be determined a NoTargetException is being thrown.
676          *
677          * @return      void
678          * @throws      NoTargetException       If no target can't be determined
679          */
680         public function declareEnqueuedPackage () {
681                 // Make sure this method isn't working if there is no package enqueued
682                 if (!$this->isPackageEnqueued()) {
683                         // This is not fatal but should be avoided
684                         // @TODO Add some logging here
685                         return;
686                 } // END - if
687
688                 /*
689                  * Now there are for sure packages to deliver, so start with the first
690                  * one.
691                  */
692                 $packageData = $this->getStackerInstance()->getNamed(self::STACKER_NAME_UNDECLARED);
693
694                 // Declare the raw package data for delivery
695                 $this->declareRawPackageData($packageData);
696
697                 // And remove it finally
698                 $this->getStackerInstance()->popNamed(self::STACKER_NAME_UNDECLARED);
699         }
700
701         /**
702          * Delivers the next declared package. Only one package per time will be sent
703          * because this may take time and slows down the whole delivery
704          * infrastructure.
705          *
706          * @return      void
707          */
708         public function deliverDeclaredPackage () {
709                 // Sanity check if we have packages declared
710                 if (!$this->isPackageDeclared()) {
711                         // This is not fatal but should be avoided
712                         self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE: No package has been declared, but ' . __METHOD__ . ' has been called!');
713                         return;
714                 } // END - if
715
716                 // Get the package
717                 $packageData = $this->getStackerInstance()->getNamed(self::STACKER_NAME_DECLARED);
718
719                 try {
720                         // And try to send it
721                         $this->deliverRawPackageData($packageData);
722
723                         // And remove it finally
724                         $this->getStackerInstance()->popNamed(self::STACKER_NAME_DECLARED);
725                 } catch (InvalidStateException $e) {
726                         // The state is not excepected (shall be 'connected')
727                         self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE: Caught ' . $e->__toString() . ',message=' . $e->getMessage());
728
729                         // Mark the package with status failed
730                         $this->changePackageStatus($packageData, self::STACKER_NAME_DECLARED, self::PACKAGE_STATUS_FAILED);
731                 }
732         }
733
734         /**
735          * Sends waiting packages out for delivery
736          *
737          * @return      void
738          */
739         public function sendWaitingPackage () {
740                 // Send any waiting bytes in the back-buffer before sending a new package
741                 $this->sendBackBufferBytes();
742
743                 // Sanity check if we have packages waiting for delivery
744                 if (!$this->isPackageWaitingForDelivery()) {
745                         // This is not fatal but should be avoided
746                         self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE: No package is waiting for delivery, but ' . __METHOD__ . ' was called.');
747                         return;
748                 } // END - if
749
750                 // Get the package
751                 $packageData = $this->getStackerInstance()->getNamed(self::STACKER_NAME_OUTGOING);
752
753                 try {
754                         // Now try to send it
755                         $this->sendOutgoingRawPackageData($packageData);
756
757                         // And remove it finally
758                         $this->getStackerInstance()->popNamed(self::STACKER_NAME_OUTGOING);
759                 } catch (InvalidSocketException $e) {
760                         // Output exception message
761                         self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE: Package was not delivered: ' . $e->getMessage());
762
763                         // Mark package as failed
764                         $this->changePackageStatus($packageData, self::STACKER_NAME_OUTGOING, self::PACKAGE_STATUS_FAILED);
765                 }
766         }
767
768         ///////////////////////////////////////////////////////////////////////////
769         //                   Receiving packages / raw data
770         ///////////////////////////////////////////////////////////////////////////
771
772         /**
773          * Checks whether decoded raw data is pending
774          *
775          * @return      $isPending      Whether decoded raw data is pending
776          */
777         private function isRawDataPending () {
778                 // Just return whether the stack is not empty
779                 $isPending = (!$this->getStackerInstance()->isStackEmpty(self::STACKER_NAME_DECODED_INCOMING));
780
781                 // Return the status
782                 return $isPending;
783         }
784
785         /**
786          * Checks whether new raw package data has arrived at a socket
787          *
788          * @param       $poolInstance   An instance of a PoolableListener class
789          * @return      $hasArrived             Whether new raw package data has arrived for processing
790          */
791         public function isNewRawDataPending (PoolableListener $poolInstance) {
792                 // Visit the pool. This monitors the pool for incoming raw data.
793                 $poolInstance->accept($this->getVisitorInstance());
794
795                 // Check for new data arrival
796                 $hasArrived = $this->isRawDataPending();
797
798                 // Return the status
799                 return $hasArrived;
800         }
801
802         /**
803          * Handles the incoming decoded raw data. This method does not "convert" the
804          * decoded data back into a package array, it just "handles" it and pushs it
805          * on the next stack.
806          *
807          * @return      void
808          */
809         public function handleIncomingDecodedData () {
810                 /*
811                  * This method should only be called if decoded raw data is pending,
812                  * so check it again.
813                  */
814                 if (!$this->isRawDataPending()) {
815                         // This is not fatal but should be avoided
816                         // @TODO Add some logging here
817                         return;
818                 } // END - if
819
820                 // Very noisy debug message:
821                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE: Stacker size is ' . $this->getStackerInstance()->getStackCount(self::STACKER_NAME_DECODED_INCOMING) . ' entries.');
822
823                 // "Pop" the next entry (the same array again) from the stack
824                 $decodedData = $this->getStackerInstance()->popNamed(self::STACKER_NAME_DECODED_INCOMING);
825
826                 // Make sure both array elements are there
827                 assert(
828                         (is_array($decodedData)) &&
829                         (isset($decodedData[BaseRawDataHandler::PACKAGE_RAW_DATA])) &&
830                         (isset($decodedData[BaseRawDataHandler::PACKAGE_ERROR_CODE]))
831                 );
832
833                 /*
834                  * Also make sure the error code is SOCKET_ERROR_UNHANDLED because we
835                  * only want to handle unhandled packages here.
836                  */
837                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE: errorCode=' . $decodedData[BaseRawDataHandler::PACKAGE_ERROR_CODE] . '(' . BaseRawDataHandler::SOCKET_ERROR_UNHANDLED . ')');
838                 assert($decodedData[BaseRawDataHandler::PACKAGE_ERROR_CODE] == BaseRawDataHandler::SOCKET_ERROR_UNHANDLED);
839
840                 // Remove the last chunk SEPARATOR (because it is being added and we don't need it)
841                 if (substr($decodedData[BaseRawDataHandler::PACKAGE_RAW_DATA], -1, 1) == PackageFragmenter::CHUNK_SEPARATOR) {
842                         // It is there and should be removed
843                         $decodedData[BaseRawDataHandler::PACKAGE_RAW_DATA] = substr($decodedData[BaseRawDataHandler::PACKAGE_RAW_DATA], 0, -1);
844                 } // END - if
845
846                 // This package is "handled" and can be pushed on the next stack
847                 $this->getStackerInstance()->pushNamed(self::STACKER_NAME_DECODED_HANDLED, $decodedData);
848         }
849
850         /**
851          * Adds raw decoded data from the given handler instance to this receiver
852          *
853          * @param       $handlerInstance        An instance of a Networkable class
854          * @return      void
855          */
856         public function addRawDataToIncomingStack (Networkable $handlerInstance) {
857                 /*
858                  * Get the decoded data from the handler, this is an array with
859                  * 'raw_data' and 'error_code' as elements.
860                  */
861                 $decodedData = $handlerInstance->getNextRawData();
862
863                 // Very noisy debug message:
864                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE: decodedData[' . gettype($decodedData) . ']=' . print_r($decodedData, true));
865
866                 // And push it on our stack
867                 $this->getStackerInstance()->pushNamed(self::STACKER_NAME_DECODED_INCOMING, $decodedData);
868         }
869
870         /**
871          * Checks whether incoming decoded data is handled.
872          *
873          * @return      $isHandled      Whether incoming decoded data is handled
874          */
875         public function isIncomingRawDataHandled () {
876                 // Determine if the stack is not empty
877                 $isHandled = (!$this->getStackerInstance()->isStackEmpty(self::STACKER_NAME_DECODED_HANDLED));
878
879                 // Return it
880                 return $isHandled;
881         }
882
883         /**
884          * Checks whether the assembler has pending data left
885          *
886          * @return      $isHandled      Whether the assembler has pending data left
887          */
888         public function ifAssemblerHasPendingDataLeft () {
889                 // Determine if the stack is not empty
890                 $isHandled = (!$this->getAssemblerInstance()->isPendingDataEmpty());
891
892                 // Return it
893                 return $isHandled;
894         }
895
896         /**
897          * Handles the attached assemler's pending data queue to be finally
898          * assembled to the raw package data back.
899          *
900          * @return      void
901          */
902         public function handleAssemblerPendingData () {
903                 // Handle it
904                 $this->getAssemblerInstance()->handlePendingData();
905         }
906
907         /**
908          * Assembles incoming decoded data so it will become an abstract network
909          * package again. The assembler does later do it's job by an other task,
910          * not this one to keep best speed possible.
911          *
912          * @return      void
913          */
914         public function assembleDecodedDataToPackage () {
915                 // Make sure the raw decoded package data is handled
916                 assert($this->isIncomingRawDataHandled());
917
918                 // Get current package content (an array with two elements; see handleIncomingDecodedData() for details)
919                 $packageContent = $this->getStackerInstance()->getNamed(self::STACKER_NAME_DECODED_HANDLED);
920
921                 // Start assembling the raw package data array by chunking it
922                 $this->getAssemblerInstance()->chunkPackageContent($packageContent);
923
924                 // Remove the package from 'handled_decoded' stack ...
925                 $this->getStackerInstance()->popNamed(self::STACKER_NAME_DECODED_HANDLED);
926
927                 // ... and push it on the 'chunked' stacker
928                 $this->getStackerInstance()->pushNamed(self::STACKER_NAME_DECODED_CHUNKED, $packageContent);
929         }
930
931         /**
932          * Accepts the visitor to process the visit "request"
933          *
934          * @param       $visitorInstance        An instance of a Visitor class
935          * @return      void
936          */
937         public function accept (Visitor $visitorInstance) {
938                 // Debug message
939                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE: ' . $visitorInstance->__toString() . ' has visited - START');
940
941                 // Visit the package
942                 $visitorInstance->visitNetworkPackage($this);
943
944                 // Debug message
945                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE: ' . $visitorInstance->__toString() . ' has visited - FINISHED');
946         }
947
948         /**
949          * Clears all stacker
950          *
951          * @return      void
952          */
953         public function clearAllStacker () {
954                 // Call the init method to force re-initialization
955                 $this->initStackers(true);
956
957                 // Debug message
958                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE: All stacker have been re-initialized.');
959         }
960
961         /**
962          * Removes the first failed outoging package from the stack to continue
963          * with next one (it will never work until the issue is fixed by you).
964          *
965          * @return      void
966          * @throws      UnexpectedPackageStatusException        If the package status is not 'failed'
967          * @todo        This may be enchanced for outgoing packages?
968          */
969         public function removeFirstFailedPackage () {
970                 // Get the package again
971                 $packageData = $this->getStackerInstance()->getNamed(self::STACKER_NAME_DECLARED);
972
973                 // Is the package status 'failed'?
974                 if ($packageData[self::PACKAGE_DATA_STATUS] != self::PACKAGE_STATUS_FAILED) {
975                         // Not failed!
976                         throw new UnexpectedPackageStatusException(array($this, $packageData, self::PACKAGE_STATUS_FAILED), BaseListener::EXCEPTION_UNEXPECTED_PACKAGE_STATUS);
977                 } // END - if
978
979                 // Remove this entry
980                 $this->getStackerInstance()->popNamed(self::STACKER_NAME_DECLARED);
981         }
982
983         /**
984          * "Decode" the package content into the same array when it was sent.
985          *
986          * @param       $rawPackageContent      The raw package content to be "decoded"
987          * @return      $decodedData            An array with 'sender', 'recipient', 'content' and 'status' elements
988          */
989         public function decodeRawContent ($rawPackageContent) {
990                 // Use the separator '#' to "decode" it
991                 $decodedArray = explode(self::PACKAGE_DATA_SEPARATOR, $rawPackageContent);
992
993                 // Assert on count (should be always 3)
994                 assert(count($decodedArray) == self::DECODED_DATA_ARRAY_SIZE);
995
996                 // Generate the signature of comparing it
997                 /*
998                  * @todo Unsupported feature of "signed" messages commented out
999                 if (!$this->isPackageSignatureValid($decodedArray)) {
1000                         // Is not valid, so throw an exception here
1001                         exit(__METHOD__ . ':INVALID SIG! UNDER CONSTRUCTION!' . chr(10));
1002                 } // END - if
1003                 */
1004
1005                 /*
1006                  * Create 'decodedData' array with all assoziative array elements,
1007                  * except signature.
1008                  */
1009                 $decodedData = array(
1010                         self::PACKAGE_DATA_SENDER    => $decodedArray[self::INDEX_PACKAGE_SENDER],
1011                         self::PACKAGE_DATA_RECIPIENT => $decodedArray[self::INDEX_PACKAGE_RECIPIENT],
1012                         self::PACKAGE_DATA_CONTENT   => $decodedArray[self::INDEX_PACKAGE_CONTENT],
1013                         self::PACKAGE_DATA_STATUS    => self::PACKAGE_STATUS_DECODED
1014                 );
1015
1016                 // And return it
1017                 return $decodedData;
1018         }
1019
1020         /**
1021          * Handles decoded data for this node by "decoding" the 'content' part of
1022          * it. Again this method uses explode() for the "decoding" process.
1023          *
1024          * @param       $decodedData    An array with decoded raw package data
1025          * @return      void
1026          * @throws      InvalidDataChecksumException    If the checksum doesn't match
1027          */
1028         public function handleRawData (array $decodedData) {
1029                 /*
1030                  * "Decode" the package's content by a simple explode() call, for
1031                  * details of the array elements, see comments for constant
1032                  * PACKAGE_MASK.
1033                  */
1034                 $decodedContent = explode(self::PACKAGE_MASK_SEPARATOR, $decodedData[self::PACKAGE_DATA_CONTENT]);
1035
1036                 // Assert on array count for a very basic validation
1037                 assert(count($decodedContent) == self::PACKAGE_CONTENT_ARRAY_SIZE);
1038
1039                 /*
1040                  * Convert the indexed array into an associative array. This is much
1041                  * better to remember than plain numbers, isn't it?
1042                  */
1043                 $decodedContent = array(
1044                         // Compressor's extension used to compress the data
1045                         self::PACKAGE_CONTENT_EXTENSION => $decodedContent[self::INDEX_COMPRESSOR_EXTENSION],
1046                         // Package data (aka "message") in BASE64-decoded form but still compressed
1047                         self::PACKAGE_CONTENT_MESSAGE   => base64_decode($decodedContent[self::INDEX_PACKAGE_DATA]),
1048                         // Tags as an indexed array for "tagging" the message
1049                         self::PACKAGE_CONTENT_TAGS      => explode(self::PACKAGE_TAGS_SEPARATOR, $decodedContent[self::INDEX_TAGS]),
1050                         // Checksum of the _decoded_ data
1051                         self::PACKAGE_CONTENT_CHECKSUM  => $decodedContent[self::INDEX_CHECKSUM]
1052                 );
1053
1054                 // Is the checksum valid?
1055                 if (!$this->isChecksumValid($decodedContent, $decodedData)) {
1056                         // Is not the same, so throw an exception here
1057                         throw new InvalidDataChecksumException(array($this, $decodedContent, $decodedData), BaseListener::EXCEPTION_INVALID_DATA_CHECKSUM);
1058                 } // END - if
1059
1060                 /*
1061                  * The checksum is the same, then it can be decompressed safely. The
1062                  * original message is at this point fully decoded.
1063                  */
1064                 $decodedContent[self::PACKAGE_CONTENT_MESSAGE] = $this->getCompressorInstance()->decompressStream($decodedContent[self::PACKAGE_CONTENT_MESSAGE]);
1065
1066                 // And push it on the next stack
1067                 $this->getStackerInstance()->pushNamed(self::STACKER_NAME_NEW_MESSAGE, $decodedContent);
1068         }
1069
1070         /**
1071          * Checks whether a new message has arrived
1072          *
1073          * @return      $hasArrived             Whether a new message has arrived for processing
1074          */
1075         public function isNewMessageArrived () {
1076                 // Determine if the stack is not empty
1077                 $hasArrived = (!$this->getStackerInstance()->isStackEmpty(self::STACKER_NAME_NEW_MESSAGE));
1078
1079                 // Return it
1080                 return $hasArrived;
1081         }
1082
1083         /**
1084          * Handles newly arrived messages
1085          *
1086          * @return      void
1087          * @todo        Implement verification of all sent tags here?
1088          */
1089         public function handleNewlyArrivedMessage () {
1090                 // Get it from the stacker, it is the full array with the decoded message
1091                 $decodedContent = $this->getStackerInstance()->popNamed(self::STACKER_NAME_NEW_MESSAGE);
1092
1093                 // Now get a filter chain back from factory with given tags array
1094                 $chainInstance = PackageFilterChainFactory::createChainByTagsArray($decodedContent[self::PACKAGE_CONTENT_TAGS]);
1095
1096                 /*
1097                  * Process the message through all filters, note that all other
1098                  * elements from $decodedContent are no longer needed.
1099                  */
1100                 $chainInstance->processMessage($decodedContent[self::PACKAGE_CONTENT_MESSAGE], $this);
1101         }
1102
1103         /**
1104          * Checks whether a processed message is pending for "interpretation"
1105          *
1106          * @return      $isPending      Whether a processed message is pending
1107          */
1108         public function isProcessedMessagePending () {
1109                 // Check it
1110                 $isPending = (!$this->getStackerInstance()->isStackEmpty(self::STACKER_NAME_PROCESSED_MESSAGE));
1111
1112                 // Return it
1113                 return $isPending;
1114         }
1115
1116         /**
1117          * Handle processed messages by "interpreting" the 'message_type' element
1118          *
1119          * @return      void
1120          */
1121         public function handleProcessedMessage () {
1122                 // Get it from the stacker, it is the full array with the processed message
1123                 $messageArray = $this->getStackerInstance()->popNamed(self::STACKER_NAME_PROCESSED_MESSAGE);
1124
1125                 // Add type for later easier handling
1126                 $messageArray[self::MESSAGE_ARRAY_DATA][self::MESSAGE_ARRAY_TYPE] = $messageArray[self::MESSAGE_ARRAY_TYPE];
1127
1128                 // Debug message
1129                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE: messageArray=' . print_r($messageArray, true));
1130
1131                 // Create a handler instance from given message type
1132                 $handlerInstance = MessageTypeHandlerFactory::createMessageTypeHandlerInstance($messageArray[self::MESSAGE_ARRAY_TYPE]);
1133
1134                 // Handle message data
1135                 $handlerInstance->handleMessageData($messageArray[self::MESSAGE_ARRAY_DATA], $this);
1136         }
1137 }
1138
1139 // [EOF]
1140 ?>