+
+ // Push array back in stack
+ $this->getStackInstance()->pushNamed(self::STACKER_NAME_OUTGOING_STREAM, $encodedDataArray);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Receiving packages / raw data
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Checks whether decoded raw data is pending
+ *
+ * @return $isPending Whether decoded raw data is pending
+ */
+ private function isRawDataPending () {
+ // Just return whether the stack is not empty
+ $isPending = (!$this->getStackInstance()->isStackEmpty(self::STACKER_NAME_DECODED_INCOMING));
+
+ // Return the status
+ return $isPending;
+ }
+
+ /**
+ * Checks whether new raw package data has arrived at a socket
+ *
+ * @param $poolInstance An instance of a PoolableListener class
+ * @return $hasArrived Whether new raw package data has arrived for processing
+ */
+ public function isNewRawDataPending (PoolableListener $poolInstance) {
+ // Visit the pool. This monitors the pool for incoming raw data.
+ $poolInstance->accept($this->getVisitorInstance());
+
+ // Check for new data arrival
+ $hasArrived = $this->isRawDataPending();
+
+ // Return the status
+ return $hasArrived;
+ }
+
+ /**
+ * Handles the incoming decoded raw data. This method does not "convert" the
+ * decoded data back into a package array, it just "handles" it and pushs it
+ * on the next stack.
+ *
+ * @return void
+ */
+ public function handleIncomingDecodedData () {
+ /*
+ * This method should only be called if decoded raw data is pending,
+ * so check it again.
+ */
+ if (!$this->isRawDataPending()) {
+ // This is not fatal but should be avoided
+ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE[' . __METHOD__ . ':' . __LINE__ . ']: No raw (decoded?) data is pending, but ' . __METHOD__ . ' has been called!');
+ return;
+ } // END - if
+
+ // Very noisy debug message:
+ //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE[' . __METHOD__ . ':' . __LINE__ . ']: Stacker size is ' . $this->getStackInstance()->getStackCount(self::STACKER_NAME_DECODED_INCOMING) . ' entries.');
+
+ // "Pop" the next entry (the same array again) from the stack
+ $decodedData = $this->getStackInstance()->popNamed(self::STACKER_NAME_DECODED_INCOMING);
+
+ // Make sure both array elements are there
+ assert(
+ (is_array($decodedData)) &&
+ (isset($decodedData[BaseRawDataHandler::PACKAGE_RAW_DATA])) &&
+ (isset($decodedData[BaseRawDataHandler::PACKAGE_ERROR_CODE]))
+ );
+
+ /*
+ * Also make sure the error code is SOCKET_ERROR_UNHANDLED because we
+ * only want to handle unhandled packages here.
+ */
+ //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE[' . __METHOD__ . ':' . __LINE__ . ']: errorCode=' . $decodedData[BaseRawDataHandler::PACKAGE_ERROR_CODE] . '(' . BaseRawDataHandler::SOCKET_ERROR_UNHANDLED . ')');
+ assert($decodedData[BaseRawDataHandler::PACKAGE_ERROR_CODE] == BaseRawDataHandler::SOCKET_ERROR_UNHANDLED);
+
+ // Remove the last chunk SEPARATOR (because there is no need for it)
+ if (substr($decodedData[BaseRawDataHandler::PACKAGE_RAW_DATA], -1, 1) == PackageFragmenter::CHUNK_SEPARATOR) {
+ // It is there and should be removed
+ $decodedData[BaseRawDataHandler::PACKAGE_RAW_DATA] = substr($decodedData[BaseRawDataHandler::PACKAGE_RAW_DATA], 0, -1);
+ } // END - if
+
+ // This package is "handled" and can be pushed on the next stack
+ //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE[' . __METHOD__ . ':' . __LINE__ . ']: Pushing ' . strlen($decodedData[BaseRawDataHandler::PACKAGE_RAW_DATA]) . ' bytes to stack ' . self::STACKER_NAME_DECODED_HANDLED . ' ...');
+ $this->getStackInstance()->pushNamed(self::STACKER_NAME_DECODED_HANDLED, $decodedData);
+ }
+
+ /**
+ * Adds raw decoded data from the given handler instance to this receiver
+ *
+ * @param $handlerInstance An instance of a Networkable class
+ * @return void
+ */
+ public function addRawDataToIncomingStack (Networkable $handlerInstance) {
+ /*
+ * Get the decoded data from the handler, this is an array with
+ * 'raw_data' and 'error_code' as elements.
+ */
+ $decodedData = $handlerInstance->getNextRawData();
+
+ // Very noisy debug message:
+ //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE[' . __METHOD__ . ':' . __LINE__ . ']: decodedData[' . gettype($decodedData) . ']=' . print_r($decodedData, TRUE));
+
+ // And push it on our stack
+ $this->getStackInstance()->pushNamed(self::STACKER_NAME_DECODED_INCOMING, $decodedData);
+ }
+
+ /**
+ * Checks whether incoming decoded data is handled.
+ *
+ * @return $isHandled Whether incoming decoded data is handled
+ */
+ public function isIncomingRawDataHandled () {
+ // Determine if the stack is not empty
+ $isHandled = (!$this->getStackInstance()->isStackEmpty(self::STACKER_NAME_DECODED_HANDLED));
+
+ // Return it
+ return $isHandled;
+ }
+
+ /**
+ * Checks whether the assembler has pending data left
+ *
+ * @return $isHandled Whether the assembler has pending data left
+ */
+ public function ifAssemblerHasPendingDataLeft () {
+ // Determine if the stack is not empty
+ $isHandled = (!$this->getAssemblerInstance()->isPendingDataEmpty());
+
+ // Return it
+ return $isHandled;
+ }
+
+ /**
+ * Checks whether the assembler has multiple packages pending
+ *
+ * @return $isPending Whether the assembler has multiple packages pending
+ */
+ public function ifMultipleMessagesPending () {
+ // Determine if the stack is not empty
+ $isPending = ($this->getAssemblerInstance()->ifMultipleMessagesPending());
+
+ // Return it
+ return $isPending;
+ }
+
+ /**
+ * Handles the attached assemler's pending data queue to be finally
+ * assembled to the raw package data back.
+ *
+ * @return void
+ */
+ public function handleAssemblerPendingData () {
+ // Handle it
+ $this->getAssemblerInstance()->handlePendingData();
+ }
+
+ /**
+ * Handles multiple messages.
+ *
+ * @return void
+ */
+ public function handleMultipleMessages () {
+ // Handle it
+ $this->getAssemblerInstance()->handleMultipleMessages();
+ }
+
+ /**
+ * Assembles incoming decoded data so it will become an abstract network
+ * package again. The assembler does later do it's job by an other task,
+ * not this one to keep best speed possible.
+ *
+ * @return void
+ */
+ public function assembleDecodedDataToPackage () {
+ // Make sure the raw decoded package data is handled
+ assert($this->isIncomingRawDataHandled());
+
+ // Get current package content (an array with two elements; see handleIncomingDecodedData() for details)
+ $packageContent = $this->getStackInstance()->getNamed(self::STACKER_NAME_DECODED_HANDLED);
+
+ // Assert on some elements
+ assert(
+ (is_array($packageContent)) &&
+ (isset($packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA])) &&
+ (isset($packageContent[BaseRawDataHandler::PACKAGE_ERROR_CODE]))
+ );
+
+ // Start assembling the raw package data array by chunking it
+ $this->getAssemblerInstance()->chunkPackageContent($packageContent);
+
+ // Remove the package from 'handled_decoded' stack ...
+ $this->getStackInstance()->popNamed(self::STACKER_NAME_DECODED_HANDLED);
+
+ // ... and push it on the 'chunked' stacker
+ //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE[' . __METHOD__ . ':' . __LINE__ . ']: Pushing ' . strlen($packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA]) . ' bytes on stack ' . self::STACKER_NAME_DECODED_CHUNKED . ',packageContent=' . print_r($packageContent, TRUE));
+ $this->getStackInstance()->pushNamed(self::STACKER_NAME_DECODED_CHUNKED, $packageContent);
+ }
+
+ /**
+ * Accepts the visitor to process the visit "request"
+ *
+ * @param $visitorInstance An instance of a Visitor class
+ * @return void
+ */
+ public function accept (Visitor $visitorInstance) {
+ // Debug message
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE[' . __METHOD__ . ':' . __LINE__ . ']: ' . $visitorInstance->__toString() . ' has visited - START');
+
+ // Visit the package
+ $visitorInstance->visitNetworkPackage($this);
+
+ // Then visit the assembler to handle multiple packages
+ $this->getAssemblerInstance()->accept($visitorInstance);
+
+ // Debug message
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE[' . __METHOD__ . ':' . __LINE__ . ']: ' . $visitorInstance->__toString() . ' has visited - FINISHED');
+ }
+
+ /**
+ * Clears all stacks
+ *
+ * @return void
+ */
+ public function clearAllStacks () {
+ // Call the init method to force re-initialization
+ $this->initStacks(TRUE);
+
+ // Debug message
+ //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE[' . __METHOD__ . ':' . __LINE__ . ']: All stacker have been re-initialized.');
+ }
+
+ /**
+ * Removes the first failed outoging package from the stack to continue
+ * with next one (it will never work until the issue is fixed by you).
+ *
+ * @return void
+ * @throws UnexpectedPackageStatusException If the package status is not 'failed'
+ * @todo This may be enchanced for outgoing packages?
+ */
+ public function removeFirstFailedPackage () {
+ // Get the package again
+ $packageData = $this->getStackInstance()->getNamed(self::STACKER_NAME_DECLARED);
+
+ // Is the package status 'failed'?
+ if ($packageData[self::PACKAGE_DATA_STATUS] != self::PACKAGE_STATUS_FAILED) {
+ // Not failed!
+ throw new UnexpectedPackageStatusException(array($this, $packageData, self::PACKAGE_STATUS_FAILED), BaseListener::EXCEPTION_UNEXPECTED_PACKAGE_STATUS);
+ } // END - if
+
+ // Remove this entry
+ $this->getStackInstance()->popNamed(self::STACKER_NAME_DECLARED);
+ }
+
+ /**
+ * "Decode" the package content into the same array when it was sent.
+ *
+ * @param $rawPackageContent The raw package content to be "decoded"
+ * @return $decodedData An array with 'sender', 'recipient', 'content' and 'status' elements
+ */
+ public function decodeRawContent ($rawPackageContent) {
+ // Use the separator '#' to "decode" it
+ $decodedArray = explode(self::PACKAGE_DATA_SEPARATOR, $rawPackageContent);
+
+ // Assert on count (should be always 3)
+ assert(count($decodedArray) == self::DECODED_DATA_ARRAY_SIZE);
+
+ // Generate the signature of comparing it
+ /*
+ * @todo Unsupported feature of "signed" messages commented out
+ if (!$this->isPackageSignatureValid($decodedArray)) {
+ // Is not valid, so throw an exception here
+ exit(__METHOD__ . ':INVALID SIG! UNDER CONSTRUCTION!' . chr(10));
+ } // END - if
+ */
+
+ /*
+ * Create 'decodedData' array with all assoziative array elements,
+ * except signature.
+ */
+ $decodedData = array(
+ self::PACKAGE_DATA_SENDER => $decodedArray[self::INDEX_PACKAGE_SENDER],
+ self::PACKAGE_DATA_RECIPIENT => $decodedArray[self::INDEX_PACKAGE_RECIPIENT],
+ self::PACKAGE_DATA_CONTENT => $decodedArray[self::INDEX_PACKAGE_CONTENT],
+ self::PACKAGE_DATA_STATUS => self::PACKAGE_STATUS_DECODED
+ );
+
+ // And return it
+ return $decodedData;
+ }
+
+ /**
+ * Handles decoded data for this node by "decoding" the 'content' part of
+ * it. Again this method uses explode() for the "decoding" process.
+ *
+ * @param $decodedData An array with decoded raw package data
+ * @return void
+ * @throws InvalidDataChecksumException If the checksum doesn't match
+ */
+ public function handleRawData (array $decodedData) {
+ /*
+ * "Decode" the package's content by a simple explode() call, for
+ * details of the array elements, see comments for constant
+ * PACKAGE_MASK.
+ */
+ $decodedContent = explode(self::PACKAGE_MASK_SEPARATOR, $decodedData[self::PACKAGE_DATA_CONTENT]);
+
+ // Assert on array count for a very basic validation
+ assert(count($decodedContent) == self::PACKAGE_CONTENT_ARRAY_SIZE);
+
+ /*
+ * Convert the indexed array into an associative array. This is much
+ * better to remember than plain numbers, isn't it?
+ */
+ $decodedContent = array(
+ // Compressor's extension used to compress the data
+ self::PACKAGE_CONTENT_EXTENSION => $decodedContent[self::INDEX_COMPRESSOR_EXTENSION],
+ // Package data (aka "message") in BASE64-decoded form but still compressed
+ self::PACKAGE_CONTENT_MESSAGE => base64_decode($decodedContent[self::INDEX_PACKAGE_DATA]),
+ // Tags as an indexed array for "tagging" the message
+ self::PACKAGE_CONTENT_TAGS => explode(self::PACKAGE_TAGS_SEPARATOR, $decodedContent[self::INDEX_TAGS]),
+ // Checksum of the _decoded_ data
+ self::PACKAGE_CONTENT_CHECKSUM => $decodedContent[self::INDEX_CHECKSUM]
+ );
+
+ // Is the checksum valid?
+ if (!$this->isChecksumValid($decodedContent, $decodedData)) {
+ // Is not the same, so throw an exception here
+ throw new InvalidDataChecksumException(array($this, $decodedContent, $decodedData), BaseListener::EXCEPTION_INVALID_DATA_CHECKSUM);
+ } // END - if
+
+ /*
+ * The checksum is the same, then it can be decompressed safely. The
+ * original message is at this point fully decoded.
+ */
+ $decodedContent[self::PACKAGE_CONTENT_MESSAGE] = $this->getCompressorInstance()->decompressStream($decodedContent[self::PACKAGE_CONTENT_MESSAGE]);
+
+ // And push it on the next stack
+ $this->getStackInstance()->pushNamed(self::STACKER_NAME_NEW_MESSAGE, $decodedContent);
+ }
+
+ /**
+ * Checks whether a new message has arrived
+ *
+ * @return $hasArrived Whether a new message has arrived for processing
+ */
+ public function isNewMessageArrived () {
+ // Determine if the stack is not empty
+ $hasArrived = (!$this->getStackInstance()->isStackEmpty(self::STACKER_NAME_NEW_MESSAGE));
+
+ // Return it
+ return $hasArrived;
+ }
+
+ /**
+ * Handles newly arrived messages
+ *
+ * @return void
+ * @todo Implement verification of all sent tags here?
+ */
+ public function handleNewlyArrivedMessage () {
+ // Get it from the stacker, it is the full array with the decoded message
+ $decodedContent = $this->getStackInstance()->popNamed(self::STACKER_NAME_NEW_MESSAGE);
+
+ // Now get a filter chain back from factory with given tags array
+ $chainInstance = PackageFilterChainFactory::createChainByTagsArray($decodedContent[self::PACKAGE_CONTENT_TAGS]);
+
+ /*
+ * Process the message through all filters, note that all other
+ * elements from $decodedContent are no longer needed.
+ */
+ $chainInstance->processMessage($decodedContent[self::PACKAGE_CONTENT_MESSAGE], $this);
+ }
+
+ /**
+ * Checks whether a processed message is pending for "interpretation"
+ *
+ * @return $isPending Whether a processed message is pending
+ */
+ public function isProcessedMessagePending () {
+ // Check it
+ $isPending = (!$this->getStackInstance()->isStackEmpty(self::STACKER_NAME_PROCESSED_MESSAGE));
+
+ // Return it
+ return $isPending;
+ }
+
+ /**
+ * Handle processed messages by "interpreting" the 'message_type' element
+ *
+ * @return void
+ */
+ public function handleProcessedMessage () {
+ // Get it from the stacker, it is the full array with the processed message
+ $messageArray = $this->getStackInstance()->popNamed(self::STACKER_NAME_PROCESSED_MESSAGE);
+
+ // Add type for later easier handling
+ $messageArray[self::MESSAGE_ARRAY_DATA][self::MESSAGE_ARRAY_TYPE] = $messageArray[self::MESSAGE_ARRAY_TYPE];
+
+ // Debug message
+ //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE[' . __METHOD__ . ':' . __LINE__ . ']: messageArray=' . print_r($messageArray, TRUE));
+
+ // Create a handler instance from given message type
+ $handlerInstance = MessageTypeHandlerFactory::createMessageTypeHandlerInstance($messageArray[self::MESSAGE_ARRAY_TYPE]);
+
+ // Handle message data
+ $handlerInstance->handleMessageData($messageArray[self::MESSAGE_ARRAY_DATA], $this);