]> git.mxchange.org Git - hub.git/blobdiff - application/hub/main/handler/chunks/class_ChunkHandler.php
'hub' project continued:
[hub.git] / application / hub / main / handler / chunks / class_ChunkHandler.php
index 5de34ac11f5f1607f04f6993b08dd00c1c469e3a..04db917df4c647706fa1d977836999473c77e00e 100644 (file)
@@ -26,6 +26,7 @@ class ChunkHandler extends BaseHandler implements HandleableChunks, Registerable
         * Stacker for chunks with final EOP
         */
        const STACKER_NAME_CHUNKS_WITH_FINAL_EOP = 'final_chunks';
+       const STACKER_NAME_ASSEMBLED_RAW_DATA    = 'chunk_raw_data';
 
        /**
         * Chunk splits:
@@ -40,14 +41,25 @@ class ChunkHandler extends BaseHandler implements HandleableChunks, Registerable
        /**
         * The final array for assembling the original package back together
         */
-       private $finalPackageChunks = array(
-               // Array for package content
-               'content'     => array(),
-               // ... and for the hashes
-               'hashes'      => array(),
-               // ... marker for that the final array is complete for assembling all chunks
-               'is_complete' => false
-       );
+       private $finalPackageChunks = array();
+
+       /**
+        * Array of chunk hashes
+        */
+       private $chunkHashes = array();
+
+       /**
+        * Raw EOP chunk data in an array:
+        *
+        * 0 = Final hash,
+        * 1 = Hash of last chunk
+        */
+       private $eopChunk = array();
+
+       /**
+        * Raw package data
+        */
+       private $rawPackageData = '';
 
        /**
         * Protected constructor
@@ -60,6 +72,9 @@ class ChunkHandler extends BaseHandler implements HandleableChunks, Registerable
 
                // Set handler name
                $this->setHandlerName('chunk');
+
+               // Initialize handler
+               $this->initHandler();
        }
 
        /**
@@ -76,6 +91,7 @@ class ChunkHandler extends BaseHandler implements HandleableChunks, Registerable
 
                // Init all stacker
                $stackerInstance->initStacker(self::STACKER_NAME_CHUNKS_WITH_FINAL_EOP);
+               $stackerInstance->initStacker(self::STACKER_NAME_ASSEMBLED_RAW_DATA);
 
                // Set the stacker in this handler
                $handlerInstance->setStackerInstance($stackerInstance);
@@ -86,10 +102,44 @@ class ChunkHandler extends BaseHandler implements HandleableChunks, Registerable
                // ... and set it in this handler
                $handlerInstance->setCryptoInstance($cryptoInstance);
 
+               // Get a fragmenter instance for later verification of serial numbers (e.g. if all are received)
+               $fragmenterInstance = FragmenterFactory::createFragmenterInstance('package');
+
+               // Set it in this handler
+               $handlerInstance->setFragmenterInstance($fragmenterInstance);
+
                // Return the prepared instance
                return $handlerInstance;
        }
 
+       /**
+        * Initializes the handler
+        *
+        * @return      void
+        */
+       private function initHandler () {
+               // Init finalPackageChunks
+               $this->finalPackageChunks = array(
+                       // Array for package content
+                       'content'        => array(),
+                       // ... and for the hashes
+                       'hashes'         => array(),
+                       // ... marker for that the final array is complete for assembling all chunks
+                       'is_complete'    => false,
+                       // ... steps done to assemble all chunks
+                       'assemble_steps' => 0,
+               );
+
+               // ... chunkHashes:
+               $this->chunkHashes = array();
+
+               // ... eopChunk:
+               $this->eopChunk = array(
+                       0 => 'INVALID',
+                       1 => 'INVALID',
+               );
+       }
+
        /**
         * Checks whether the hash generated from package content is the same ("valid") as given
         *
@@ -163,6 +213,133 @@ class ChunkHandler extends BaseHandler implements HandleableChunks, Registerable
                $this->finalPackageChunks['is_complete'] = true;
        }
 
+       /**
+        * Sorts the chunks array by using the serial number as a sorting key. In
+        * most situations a call of ksort() is enough to accomblish this. So this
+        * method may only call ksort() on the chunks array.
+        *
+        * This method sorts 'content' and 'hashes' so both must have used the
+        * serial numbers as array indexes.
+        *
+        * @return      void
+        */
+       private function sortChunksArray () {
+               // Sort 'content' first
+               ksort($this->finalPackageChunks['content']);
+
+               // ... then 'hashes'
+               ksort($this->finalPackageChunks['hashes']);
+       }
+
+       /**
+        * Prepares the package assemble by removing last chunks (last shall be
+        * hash chunk, pre-last shall be EOP chunk) and verify that all serial
+        * numbers are valid (same as PackageFragmenter class would generate).
+        *
+        * @return      void
+        */
+       private function preparePackageAssmble () {
+               // Make sure both arrays have same count (this however should always be true)
+               assert(count($this->finalPackageChunks['hashes']) == count($this->finalPackageChunks['content']));
+
+               /*
+                * Remove last element (hash chunk) from 'hashes'. This hash will never
+                * be needed, so ignore it.
+                */
+               array_pop($this->finalPackageChunks['hashes']);
+
+               // ... and from 'content' as well but save it for later use
+               $this->chunkHashes = explode(PackageFragmenter::CHUNK_HASH_SEPARATOR, substr(array_pop($this->finalPackageChunks['content']), strlen(PackageFragmenter::HASH_CHUNK_IDENTIFIER)));
+
+               // Remove EOP chunk and keep a copy of it
+               array_pop($this->finalPackageChunks['hashes']);
+               $this->eopChunk = explode(PackageFragmenter::CHUNK_HASH_SEPARATOR, substr(array_pop($this->finalPackageChunks['content']), strlen(PackageFragmenter::END_OF_PACKAGE_IDENTIFIER)));
+
+               // Verify all serial numbers
+               $this->verifyChunkSerialNumbers();
+       }
+
+       /**
+        * Verifies all chunk serial numbers by using a freshly initialized
+        * fragmenter instance. Do ALWAYS sort the array and array_pop() the hash
+        * chunk before calling this method to avoid re-requests of many chunks.
+        *
+        * @return      void
+        */
+       private function verifyChunkSerialNumbers () {
+               // Reset the serial number generator
+               $this->getFragmenterInstance()->resetSerialNumber();
+
+               // "Walk" through all (content) chunks
+               foreach ($this->finalPackageChunks['content'] as $serialNumber=>$content) {
+                       // Get next serial number
+                       $nextSerial = $this->getFragmenterInstance()->getNextHexSerialNumber();
+
+                       // Debug output
+                       //* NOISY-DEBUG */ $this->debugOutput('CHUNK-HANDLER: serialNumber=' . $serialNumber . ',nextSerial=' . $nextSerial);
+
+                       // Is it not the same? Then re-request it
+                       if ($serialNumber != $nextSerial) {
+                               // This is invalid, so remove it
+                               unset($this->finalPackageChunks['content'][$serialNumber]);
+                               unset($this->finalPackageChunks['hashes'][$serialNumber]);
+
+                               // And re-request it with valid serial number (and hash chunk)
+                               $this->rerequestChunkBySerialNumber($nextSerial);
+                       } // END - if
+               } // END - foreach
+       }
+
+       /**
+        * Assembles and verifies ("final check") chunks back together to the
+        * original package (raw data for the start). This method should only be
+        * called AFTER the EOP and final-chunk chunk have been removed.
+        *
+        * @return      void
+        */
+       private function assembleAllChunksToPackage () {
+               // If chunkHashes is not filled, don't continue
+               assert(count($this->chunkHashes) > 0);
+
+               // Init raw package data string
+               $this->rawPackageData = '';
+
+               // That went well, so start assembling all chunks
+               foreach ($this->finalPackageChunks['content'] as $serialNumber=>$content) {
+                       // Is this chunk valid? This should be the case
+                       assert($this->isChunkHashValid(array(
+                               self::CHUNK_SPLITS_INDEX_HASH     => $this->finalPackageChunks['hashes'][$serialNumber],
+                               self::CHUNK_SPLITS_INDEX_RAW_DATA => $content
+                       )));
+
+                       // ... and is also in the hash chunk?
+                       assert(in_array($this->finalPackageChunks['hashes'][$serialNumber], $this->chunkHashes));
+
+                       // Verification okay, add it to the raw data
+                       $this->rawPackageData .= $content;
+               } // END - foreach
+
+               // Debug output
+               //* NOISY-DEBUG: */ $this->debugOutput('CHUNK-HANDLER: eopChunk[1]=' . $this->eopChunk[1] . ',' . chr(10) . 'index=' . (count($this->chunkHashes) - 2) . ',' . chr(10) . 'chunkHashes='.print_r($this->chunkHashes,true));
+
+               // The last chunk hash must match with the one from eopChunk[1]
+               assert($this->eopChunk[1] == $this->chunkHashes[count($this->chunkHashes) - 2]);
+       }
+
+       /**
+        * Verifies the finally assembled raw package data by comparing it against
+        * the final hash.
+        *
+        * @return      void
+        */
+       private function verifyRawPackageData () {
+               // Hash the raw package data for final verification
+               $finalHash = $this->getCryptoInstance()->hashString($this->rawPackageData, $this->eopChunk[0], false);
+
+               // Is it the same?
+               assert($finalHash == $this->eopChunk[0]);
+       }
+
        /**
         * Adds all chunks if the last one verifies as a 'final chunk'.
         *
@@ -295,7 +472,67 @@ class ChunkHandler extends BaseHandler implements HandleableChunks, Registerable
                // Make sure the final array is really completed
                assert($this->ifUnassembledChunksAvailable());
 
-               $this->partialStub('Please implement this method.');
+               // Count up stepping
+               $this->finalPackageChunks['assemble_steps']++;
+
+               // Do the next step
+               switch ($this->finalPackageChunks['assemble_steps']) {
+                       case 1: // Sort the chunks array (the serial number shall act as a sorting key)
+                               $this->sortChunksArray();
+                               break;
+
+                       case 2: // Prepare the assemble by removing last two indexes
+                               $this->preparePackageAssmble();
+                               break;
+
+                       case 3: // Assemble all chunks back together to the original package
+                               $this->assembleAllChunksToPackage();
+                               break;
+
+                       case 4: // Verify the raw data by hashing it again
+                               $this->verifyRawPackageData();
+                               break;
+
+                       case 5: // Re-initialize handler to reset it to the old state
+                               $this->initHandler();
+                               break;
+
+                       default: // Invalid step found
+                               $this->debugOutput('CHUNK-HANDLER: Invalid step ' . $this->finalPackageChunks['assemble_steps'] . ' detected.');
+                               break;
+               } // END - switch
+       }
+
+       /**
+        * Checks whether the raw package data has been assembled back together.
+        * This can be safely assumed when rawPackageData is not empty and the
+        * collection of all chunks is false (because initHandler() will reset it).
+        *
+        * @return      $isRawPackageDataAvailable      Whether raw package data is available
+        */
+       public function ifRawPackageDataIsAvailable () {
+               // Check it
+               $isRawPackageDataAvailable = ((!empty($this->rawPackageData)) && (!$this->ifUnassembledChunksAvailable()));
+
+               // Return it
+               return $isRawPackageDataAvailable;
+       }
+
+       /**
+        * Handles the finally assembled raw package data by feeding it into another
+        * stacker for further decoding/processing.
+        *
+        * @return      void
+        */
+       public function handledAssembledRawPackageData () {
+               // Assert to make sure that there is raw package data available
+               assert($this->ifRawPackageDataIsAvailable());
+
+               // Then feed it into the next stacker
+               $this->getStackerInstance()->pushNamed(self::STACKER_NAME_ASSEMBLED_RAW_DATA, $this->rawPackageData);
+
+               // ... and reset it
+               $this->rawPackageData = '';
        }
 }