+ /**
+ * Initialize all stackers
+ *
+ * @param $forceReInit Whether to force reinitialization of all stacks
+ * @return void
+ */
+ protected function initStacks ($forceReInit = FALSE) {
+ // Initialize all
+ $this->getStackInstance()->initStacks(array(
+ self::STACKER_NAME_UNDECLARED,
+ self::STACKER_NAME_DECLARED,
+ self::STACKER_NAME_OUTGOING,
+ self::STACKER_NAME_DECODED_INCOMING,
+ self::STACKER_NAME_DECODED_HANDLED,
+ self::STACKER_NAME_DECODED_CHUNKED,
+ self::STACKER_NAME_NEW_MESSAGE,
+ self::STACKER_NAME_PROCESSED_MESSAGE,
+ self::STACKER_NAME_OUTGOING_STREAM
+ ), $forceReInit);
+ }
+
+ /**
+ * Determines private key hash from given session id
+ *
+ * @param $decodedData Array with decoded data
+ * @return $hash Private key's hash
+ */
+ private function determineSenderPrivateKeyHash (array $decodedData) {
+ // Get DHT instance
+ $dhtInstance = DhtObjectFactory::createDhtInstance('node');
+
+ // Ask DHT for session id
+ $senderData = $dhtInstance->findNodeLocalBySessionId($decodedData[self::PACKAGE_CONTENT_SENDER]);
+
+ // Is an entry found?
+ if (count($senderData) > 0) {
+ // Make sure the element 'private_key_hash' is there
+ /* NOISY-DEBUG */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE[' . __METHOD__ . ':' . __LINE__ . ']: senderData=' . print_r($senderData, TRUE));
+ assert(isset($senderData[NodeDistributedHashTableDatabaseWrapper::DB_COLUMN_PRIVATE_KEY_HASH]));
+
+ // Return it
+ return $senderData[NodeDistributedHashTableDatabaseWrapper::DB_COLUMN_PRIVATE_KEY_HASH];
+ } // END - if
+
+ // Make sure the requested element is there
+ //* DEBUG-DIE */ die('decodedData=' . print_r($decodedData, TRUE));
+ assert(isset($decodedData[self::PACKAGE_CONTENT_PRIVATE_KEY_HASH]));
+
+ // There is no DHT entry so, accept the hash from decoded data
+ return $decodedData[self::PACKAGE_CONTENT_PRIVATE_KEY_HASH];
+ }
+
+ /**
+ * "Getter" for hash from given content
+ *
+ * @param $content Raw package content
+ * @return $hash Hash for given package content
+ */
+ private function getHashFromContent ($content) {
+ // Debug message
+ //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE[' . __METHOD__ . ':' . __LINE__ . ']: content[md5]=' . md5($content) . ',sender=' . $this->getSessionId() . ',compressor=' . $this->getCompressorInstance()->getCompressorExtension());
+
+ // Create the hash
+ // @TODO md5() is very weak, but it needs to be fast
+ $hash = md5(
+ $content .
+ self::PACKAGE_CHECKSUM_SEPARATOR .
+ $this->getSessionId() .
+ self::PACKAGE_CHECKSUM_SEPARATOR .
+ $this->getCompressorInstance()->getCompressorExtension()
+ );
+
+ // Debug message
+ //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE[' . __METHOD__ . ':' . __LINE__ . ']: content[md5]=' . md5($content) . ',sender=' . $this->getSessionId() . ',hash=' . $hash . ',compressor=' . $this->getCompressorInstance()->getCompressorExtension());
+
+ // And return it
+ return $hash;
+ }
+
+ /**
+ * Checks whether the checksum (sometimes called "hash") is the same
+ *
+ * @param $decodedContent Package raw content
+ * @param $decodedData Whole raw package data array
+ * @return $isChecksumValid Whether the checksum is the same
+ */
+ private function isChecksumValid (array $decodedContent, array $decodedData) {
+ // Get checksum
+ $checksum = $this->getHashFromContentSessionId($decodedContent, $decodedData[self::PACKAGE_DATA_SENDER]);
+
+ // Is it the same?
+ $isChecksumValid = ($checksum == $decodedContent[self::PACKAGE_CONTENT_CHECKSUM]);
+
+ // Return it
+ return $isChecksumValid;
+ }
+
+ /**
+ * Change the package with given status in given stack
+ *
+ * @param $packageData Raw package data in an array
+ * @param $stackerName Name of the stacker
+ * @param $newStatus New status to set
+ * @return void
+ */
+ private function changePackageStatus (array $packageData, $stackerName, $newStatus) {
+ // Skip this for empty stacks
+ if ($this->getStackInstance()->isStackEmpty($stackerName)) {
+ // This avoids an exception after all packages has failed
+ return;
+ } // END - if
+
+ // Pop the entry (it should be it)
+ $nextData = $this->getStackInstance()->popNamed($stackerName);
+
+ // Compare both hashes
+ assert($nextData[self::PACKAGE_DATA_HASH] == $packageData[self::PACKAGE_DATA_HASH]);
+
+ // Temporary set the new status
+ $packageData[self::PACKAGE_DATA_STATUS] = $newStatus;
+
+ // And push it again
+ $this->getStackInstance()->pushNamed($stackerName, $packageData);
+ }
+
+ /**
+ * "Getter" for hash from given content and sender's session id
+ *
+ * @param $decodedContent Raw package content
+ * @param $sessionId Session id of the sender
+ * @return $hash Hash for given package content
+ */
+ public function getHashFromContentSessionId (array $decodedContent, $sessionId) {
+ // Debug message
+ //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE[' . __METHOD__ . ':' . __LINE__ . ']: content[md5]=' . md5($decodedContent[self::PACKAGE_CONTENT_MESSAGE]) . ',sender=' . $sessionId . ',compressor=' . $decodedContent[self::PACKAGE_CONTENT_EXTENSION]);
+
+ // Create the hash
+ // @TODO md5() is very weak, but it needs to be fast
+ $hash = md5(
+ $decodedContent[self::PACKAGE_CONTENT_MESSAGE] .
+ self::PACKAGE_CHECKSUM_SEPARATOR .
+ $sessionId .
+ self::PACKAGE_CHECKSUM_SEPARATOR .
+ $decodedContent[self::PACKAGE_CONTENT_EXTENSION]
+ );
+
+ // And return it
+ return $hash;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Delivering packages / raw data
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Declares the given raw package data by discovering recipients
+ *
+ * @param $packageData Raw package data in an array
+ * @return void
+ */
+ private function declareRawPackageData (array $packageData) {
+ // Make sure the required field is there
+ assert(isset($packageData[self::PACKAGE_DATA_RECIPIENT]));
+
+ /*
+ * We need to disover every recipient, just in case we have a
+ * multi-recipient entry like 'upper' is. 'all' may be a not so good
+ * target because it causes an overload on the network and may be
+ * abused for attacking the network with large packages.
+ */
+ $discoveryInstance = PackageDiscoveryFactory::createPackageDiscoveryInstance();
+
+ // Discover all recipients, this may throw an exception
+ $discoveryInstance->discoverRecipients($packageData);
+
+ // Now get an iterator
+ $iteratorInstance = $discoveryInstance->getIterator();
+
+ // Make sure the iterator instance is valid
+ assert($iteratorInstance instanceof Iterator);
+
+ // Rewind back to the beginning
+ $iteratorInstance->rewind();
+
+ // ... and begin iteration
+ while ($iteratorInstance->valid()) {
+ // Get current entry
+ $currentRecipient = $iteratorInstance->current();
+
+ // Debug message
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE[' . __METHOD__ . ':' . __LINE__ . ']: Setting recipient to ' . $currentRecipient . ',previous=' . $packageData[self::PACKAGE_DATA_RECIPIENT]);
+
+ // Set the recipient
+ $packageData[self::PACKAGE_DATA_RECIPIENT] = $currentRecipient;
+
+ // Push the declared package to the next stack.
+ $this->getStackInstance()->pushNamed(self::STACKER_NAME_DECLARED, $packageData);
+
+ // Debug message
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE[' . __METHOD__ . ':' . __LINE__ . ']: Package declared for recipient ' . $currentRecipient);
+
+ // Skip to next entry
+ $iteratorInstance->next();
+ } // END - while
+
+ /*
+ * The recipient list can be cleaned up here because the package which
+ * shall be delivered has already been added for all entries from the
+ * list.
+ */
+ $discoveryInstance->clearRecipients();
+ }
+
+ /**
+ * Delivers raw package data. In short, this will discover the raw socket
+ * resource through a discovery class (which will analyse the receipient of
+ * the package), register the socket with the connection (handler/helper?)
+ * instance and finally push the raw data on our outgoing queue.
+ *
+ * @param $packageData Raw package data in an array
+ * @return void
+ */
+ private function deliverRawPackageData (array $packageData) {
+ /*
+ * This package may become big, depending on the shared object size or
+ * delivered message size which shouldn't be so long (to save
+ * bandwidth). Because of the nature of the used protocol (TCP) we need
+ * to split it up into smaller pieces to fit it into a TCP frame.
+ *
+ * So first we need (again) a discovery class but now a protocol
+ * discovery to choose the right socket resource. The discovery class
+ * should take a look at the raw package data itself and then decide
+ * which (configurable!) protocol should be used for that type of
+ * package.
+ */
+ $discoveryInstance = SocketDiscoveryFactory::createSocketDiscoveryInstance();
+
+ // Now discover the right protocol
+ $socketResource = $discoveryInstance->discoverSocket($packageData, BaseConnectionHelper::CONNECTION_TYPE_OUTGOING);
+
+ // Debug message
+ //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE[' . __METHOD__ . ':' . __LINE__ . ']: Reached line ' . __LINE__ . ' after discoverSocket() has been called.');
+
+ // Debug message
+ //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE[' . __METHOD__ . ':' . __LINE__ . ']: stateInstance=' . $helperInstance->getStateInstance());
+ //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE[' . __METHOD__ . ':' . __LINE__ . ']: Reached line ' . __LINE__ . ' before isSocketRegistered() has been called.');
+
+ // The socket needs to be put in a special registry that can handle such data
+ $registryInstance = SocketRegistryFactory::createSocketRegistryInstance();
+
+ // Get the connection helper from registry
+ $helperInstance = Registry::getRegistry()->getInstance('connection');
+
+ // And make sure it is valid
+ assert($helperInstance instanceof ConnectionHelper);
+
+ // Get connection info class
+ $infoInstance = ConnectionInfoFactory::createConnectionInfoInstance($helperInstance->getProtocolName(), 'helper');
+
+ // Will the info instance with connection helper data
+ $infoInstance->fillWithConnectionHelperInformation($helperInstance);
+
+ // Is it not there?
+ if ((is_resource($socketResource)) && (!$registryInstance->isSocketRegistered($infoInstance, $socketResource))) {
+ // Debug message
+ //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE[' . __METHOD__ . ':' . __LINE__ . ']: Registering socket ' . $socketResource . ' ...');
+
+ // Then register it
+ $registryInstance->registerSocket($infoInstance, $socketResource, $packageData);
+ } elseif (!$helperInstance->getStateInstance()->isPeerStateConnected()) {
+ // Is not connected, then we cannot send
+ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE[' . __METHOD__ . ':' . __LINE__ . ']: Unexpected peer state ' . $helperInstance->getStateInstance()->__toString() . ' detected.');
+
+ // Shutdown the socket
+ $this->shutdownSocket($socketResource);
+ }
+
+ // Debug message
+ //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE[' . __METHOD__ . ':' . __LINE__ . ']: Reached line ' . __LINE__ . ' after isSocketRegistered() has been called.');
+
+ // Make sure the connection is up
+ $helperInstance->getStateInstance()->validatePeerStateConnected();
+
+ // Debug message
+ //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE[' . __METHOD__ . ':' . __LINE__ . ']: Reached line ' . __LINE__ . ' after validatePeerStateConnected() has been called.');
+
+ // Enqueue it again on the out-going queue, the connection is up and working at this point
+ $this->getStackInstance()->pushNamed(self::STACKER_NAME_OUTGOING, $packageData);
+
+ // Debug message
+ //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE[' . __METHOD__ . ':' . __LINE__ . ']: Reached line ' . __LINE__ . ' after pushNamed() has been called.');
+ }
+
+ /**
+ * Sends waiting packages
+ *
+ * @param $packageData Raw package data
+ * @return void
+ */
+ private function sendOutgoingRawPackageData (array $packageData) {
+ // Init sent bytes
+ $sentBytes = 0;
+
+ // Get the right connection instance
+ $infoInstance = SocketRegistryFactory::createSocketRegistryInstance()->getInfoInstanceFromPackageData($packageData);
+
+ // Test helper instance
+ assert($infoInstance instanceof ShareableInfo);
+
+ // Get helper instance
+ $helperInstance = $infoInstance->getHelperInstance();
+
+ // Some sanity-checks on the object
+ //* DEBUG-DIE: */ die('[' . __METHOD__ . ':' . __LINE__ . ']: p1=' . $infoInstance->getProtocolName() . ',p2=' . $helperInstance->getProtocolName() . ',infoInstance=' . print_r($infoInstance, TRUE));
+ assert($helperInstance instanceof ConnectionHelper);
+ assert($infoInstance->getProtocolName() == $helperInstance->getProtocolName());
+
+ // Is this connection still alive?
+ if ($helperInstance->isShuttedDown()) {
+ // This connection is shutting down
+ // @TODO We may want to do somthing more here?
+ return;
+ } // END - if
+
+ // Sent out package data
+ $helperInstance->sendRawPackageData($packageData);
+ }
+
+ /**
+ * Generates a secure hash for given raw package content and sender id
+ *
+ * @param $content Raw package data
+ * @param $senderId Sender id to generate a hash for
+ * @return $hash Hash as hex-encoded string
+ */
+ private function generatePackageHash ($content, $senderId) {
+ // Debug message
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE[' . __METHOD__ . ':' . __LINE__ . ']: isFeatureAvailable(hubcoin_reward)=' . intval(FrameworkFeature::isFeatureAvailable('hubcoin_reward')) . ' - CALLED!');
+
+ // Is the feature enabled?
+ if (!FrameworkFeature::isFeatureAvailable('hubcoin_reward')) {
+ // Feature is not enabled
+ return NULL;
+ } // END - if
+
+ // Fake array
+ $data = array(
+ self::PACKAGE_CONTENT_SENDER => $senderId,
+ self::PACKAGE_CONTENT_MESSAGE => $content,
+ self::PACKAGE_CONTENT_PRIVATE_KEY_HASH => ''
+ );
+
+ // Hash content and sender id together, use scrypt
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE[' . __METHOD__ . ':' . __LINE__ . ']: senderId=' . $senderId . ',content()=' . strlen($content));
+ $hash = FrameworkFeature::callFeature('hubcoin_reward', 'generateHash', array($senderId . ':' . $content . ':' . $this->determineSenderPrivateKeyHash($data)));
+
+ // Debug message
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE[' . __METHOD__ . ':' . __LINE__ . ']: hash=' . $hash . ' - EXIT!');
+
+ // Return it
+ return $hash;
+ }
+
+ /**
+ * Checks whether the hash of given package data is 'valid', here that
+ * means it is the same or not.
+ *
+ * @param $decodedArray An array with 'decoded' (explode() was mostly called) data
+ * @return $isHashValid Whether the hash is valid
+ * @todo Unfinished area, hashes are currently NOT fully supported
+ */
+ private function isPackageHashValid (array $decodedArray) {
+ // Is the feature enabled?
+ if (!FrameworkFeature::isFeatureAvailable('hubcoin_reward')) {
+ // Feature is not enabled, so hashes are always valid
+ return TRUE;
+ } // END - if
+
+ // Check validity
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('NETWORK-PACKAGE[' . __METHOD__ . ':' . __LINE__ . ']: senderId=' . $decodedArray[self::PACKAGE_CONTENT_SENDER] . ',decodedArray=' . print_r($decodedArray, TRUE));
+ //* DEBUG-DIE: */ die(__METHOD__ . ': decodedArray=' . print_r($decodedArray, TRUE));
+ $isHashValid = FrameworkFeature::callFeature('hubcoin_reward', 'checkHash', array($decodedArray[self::PACKAGE_CONTENT_SENDER] . ':' . $decodedArray[self::PACKAGE_CONTENT_MESSAGE] . ':' . $this->determineSenderPrivateKeyHash($decodedArray), $decodedArray[self::PACKAGE_CONTENT_HASH]));
+
+ // Return it
+ //* DEBUG-DIE: */ die(__METHOD__ . ': isHashValid=' . intval($isHashValid) . ',decodedArray=' . print_r($decodedArray, TRUE));
+ return $isHashValid;
+ }
+