X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=application%2Fhub%2Fmain%2Fpackage%2Ffragmenter%2Fclass_PackageFragmenter.php;h=ca073ae07e356c9d5265cd16cb2c160182ba580c;hb=69e7b3de7c369cd0ddc74c75d319055cbf4a3a04;hp=1d221578ee0fb54218470002244f97e216179d0a;hpb=1212b8422d9040c36c355c2c81c689dda7fd79ad;p=hub.git diff --git a/application/hub/main/package/fragmenter/class_PackageFragmenter.php b/application/hub/main/package/fragmenter/class_PackageFragmenter.php index 1d221578e..ca073ae07 100644 --- a/application/hub/main/package/fragmenter/class_PackageFragmenter.php +++ b/application/hub/main/package/fragmenter/class_PackageFragmenter.php @@ -1,9 +1,15 @@ * @version 0.0.0 @@ -24,7 +30,72 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -class PackageFragmenter extends BaseFrameworkSystem implements Fragmentable { +class PackageFragmenter extends BaseFrameworkSystem implements Fragmentable, Registerable { + /** + * Cached chunk size in bits + */ + private $chunkSize = 0; + + /** + * Array for chunks + */ + private $chunks = array(); + + /** + * Array for chunk hashes + */ + private $chunkHashes = array(); + + /** + * Array for chunk pointers + */ + private $chunkPointers = array(); + + /** + * Array for processed packages + */ + private $processedPackages = array(); + + /** + * Serial number + */ + private $serialNumber = 0x00000000; + + /** + * Length of largest possible serial number + */ + private $maxSerialLength = 8; + + /** + * Maximum possible serial number + */ + private $maxSerialNumber = 0; + + /** + * Seperator between chunk data, serial number and chunk hash + */ + const CHUNK_DATA_HASH_SEPERATOR = '@'; + + /** + * Seperator for all chunk hashes + */ + const CHUNK_HASH_SEPERATOR = ';'; + + /** + * Seperator between two chunks + */ + const CHUNK_SEPERATOR = '|'; + + /** + * Identifier for hash chunk + */ + const HASH_CHUNK_IDENTIFIER = 'HASH-CHUNK:'; + + /** + * Identifier for end-of-package marker + */ + const END_OF_PACKAGE_IDENTIFIER = 'EOP:'; + /** * Protected constructor * @@ -33,6 +104,9 @@ class PackageFragmenter extends BaseFrameworkSystem implements Fragmentable { protected function __construct () { // Call parent constructor parent::__construct(__CLASS__); + + // Init this fragmenter + $this->initFragmenter(); } /** @@ -44,30 +118,385 @@ class PackageFragmenter extends BaseFrameworkSystem implements Fragmentable { // Get new instance $fragmenterInstance = new PackageFragmenter(); + // And also a crypto instance (for our encrypted messages) + $cryptoInstance = ObjectFactory::createObjectByConfiguredName('crypto_class'); + $fragmenterInstance->setCryptoInstance($cryptoInstance); + // Return the prepared instance return $fragmenterInstance; } + /** + * Initializes this fragmenter + * + * @return void + */ + private function initFragmenter () { + // Load some configuration entries and "cache" them: + // - Chunk size in bits + $this->chunkSize = $this->getConfigInstance()->getConfigEntry('package_chunk_size'); + + // - Maximum serial number + $this->maxSerialNumber = $this->hex2dec(str_repeat('f', $this->maxSerialLength)); + } + + /** + * Initializes the pointer for given final hash + * + * @param $finalHash Final hash to initialize pointer for + * @return void + */ + private function initPointer ($finalHash) { + $this->chunkPointers[$finalHash] = 0; + } + + /** + * "Getter" for processedPackages array index + * + * @param $packageData Raw package data array + * @return $index Array index for processedPackages + */ + private function getProcessedPackagesIndex (array $packageData) { + return ( + $packageData['sender'] . NetworkPackage::PACKAGE_DATA_SEPERATOR . + $packageData['recipient'] . NetworkPackage::PACKAGE_DATA_SEPERATOR . + $packageData['content'] . NetworkPackage::PACKAGE_DATA_SEPERATOR + ); + } + + /** + * Checks wether the given package data is already processed by this fragmenter + * + * @param $packageData Raw package data array + * @return $isProcessed Wether the package has been fragmented + */ + private function isPackageProcessed (array $packageData) { + // Get array index + $index = $this->getProcessedPackagesIndex($packageData); + + // Is the array index there? + $isProcessed = ( + (isset($this->processedPackages[$index])) && + ($this->processedPackages[$index] === true) + ); + + // Return it + return $isProcessed; + } + + /** + * Marks the given package data as processed by this fragmenter + * + * @param $packageData Raw package data array + * @return void + */ + private function markPackageDataProcessed (array $packageData) { + // Remember it (until we may remove it) + $this->processedPackages[$this->getProcessedPackagesIndex($packageData)] = true; + } + + /** + * Getter for final hash from given package data + * + * @param $packageData Raw package data array + * @return $finalHash Final hash for package data + */ + private function getFinalHashFromPackageData (array $packageData) { + // Make sure it is there + assert(isset($this->processedPackages[$this->getProcessedPackagesIndex($packageData)])); + + // Return it + return $this->processedPackages[$this->getProcessedPackagesIndex($packageData)]; + } + + /** + * Get next chunk pointer for given final hash + * + * @param $finalHash Final hash to get current pointer for + */ + private function getCurrentChunkPointer ($finalHash) { + // Is the final hash valid? + assert(strlen($finalHash) > 0); + + // Is the pointer already initialized? + //* NOISY-DEBUG: */ $this->debugOutput('FRAGMENTER: finalHash=' . $finalHash); + assert(isset($this->chunkPointers[$finalHash])); + + // Return it + return $this->chunkPointers[$finalHash]; + } + + /** + * Advance the chunk pointer for given final hash + * + * @param $finalHash Final hash to advance the pointer for + */ + private function nextChunkPointer ($finalHash) { + // Is the pointer already initialized? + assert(isset($this->chunkPointers[$finalHash])); + + // Count one up + $this->chunkPointers[$finalHash]++; + } + + /** + * "Getter" for data chunk size of given hash. + * + * @param $hash Hash to substract it's length + * @return $dataChunkSize The chunk size + */ + private function getDataChunkSizeFromHash ($hash) { + // Calculate real (data) chunk size + $dataChunkSize = ( + // Real chunk size + ($this->chunkSize / 8) - + // Hash size + strlen($hash) - + // Length of sperators + (strlen(self::CHUNK_DATA_HASH_SEPERATOR) * 2) - + // Length of max serial number + $this->maxSerialLength + ); + + // This should be larger than zero bytes + assert($dataChunkSize > 0); + + // Return it + return $dataChunkSize; + } + + /** + * Generates a hash from raw data + * + * @param $rawData Raw data bytes to hash + * @return $hash Hash from the raw data + */ + private function generateHashFromRawData ($rawData) { + // Get the crypto instance and hash the data + $hash = $this->getCryptoInstance()->hashString($rawData); + + // Return it + return $hash; + } + + /** + * "Getter" for the next hexadecimal-encoded serial number + * + * @return $encodedSerialNumber The next hexadecimal-encoded serial number + */ + private function getNextHexSerialNumber () { + // Assert on maximum serial number length + assert($this->serialNumber <= $this->maxSerialNumber); + + // Encode the current serial number + $encodedSerialNumber = $this->dec2Hex($this->serialNumber, $this->maxSerialLength); + + // Count one up + $this->serialNumber++; + + // Return the encoded serial number + return $encodedSerialNumber; + } + + /** + * Appends an end-of-package chunk to the chunk list for given chunk and + * final hash. + * + * @param $chunkHash Last chunk's hash + * @param $finalHash Final hash for raw (unencoded) data + * @return void + */ + private function appendEndOfPackageChunk ($chunkHash, $finalHash) { + // Generate end-of-package marker + $rawData = + self::END_OF_PACKAGE_IDENTIFIER . + $finalHash . self::CHUNK_HASH_SEPERATOR . + $chunkHash . self::CHUNK_SEPERATOR; + + // Also get a hash from it + $chunkHash = $this->generateHashFromRawData($rawData); + + // Append it to the chunk's data and hash array + $this->chunkHashes[$finalHash][] = $chunkHash; + $this->chunks[$finalHash][] = $rawData; + } + + /** + * Splits the given encoded data into smaller chunks, the size of the final + * and the seperator is being subtracted from chunk size to fit it into a + * TCP package (512 bytes). + * + * @param $rawData Raw data string + * @param $finalHash Final hash from the raw data + * @return void + */ + private function splitEncodedDataIntoChunks ($rawData, $finalHash) { + // Make sure final hashes with at least 32 bytes can pass + assert(strlen($finalHash) >= 32); + + // Calculate real (data) chunk size + $dataChunkSize = $this->getDataChunkSizeFromHash($finalHash); + //* NOISY-DEBUG: */ $this->debugOutput('FRAGMENTER: dataChunkSize=' . $dataChunkSize); + + // Init variables + $chunkHash = ''; + + // Now split it up + for ($idx = 0; $idx < strlen($rawData); $idx += $dataChunkSize) { + // Get the next chunk + $chunk = substr($rawData, $idx, $dataChunkSize); + + // Hash it and remember it in seperate array + $chunkHash = $this->getCryptoInstance()->hashString($chunk); + $this->chunkHashes[$finalHash][] = $chunkHash; + + // Prepend the hash to the chunk + $chunk = + $chunkHash . self::CHUNK_DATA_HASH_SEPERATOR . + $this->getNextHexSerialNumber() . self::CHUNK_DATA_HASH_SEPERATOR . + $chunk . self::CHUNK_SEPERATOR + ; + + // Make sure the chunk is not larger than a TCP package can hold + assert(strlen($chunk) <= NetworkPackage::TCP_PACKAGE_SIZE); + + // Add it to the array + //* NOISY-DEBUG: */ $this->debugOutput('FRAGMENTER: Adding ' . strlen($chunk) . ' bytes of a chunk.'); + $this->chunks[$finalHash][] = $chunk; + } // END - for + + // Debug output + //* NOISY-DEBUG: */ $this->debugOutput('FRAGMENTER: Raw data of ' . strlen($rawData) . ' bytes has been fragmented into ' . count($this->chunks[$finalHash]) . ' chunk(s).'); + + // Add end-of-package chunk + $this->appendEndOfPackageChunk($chunkHash, $finalHash); + } + + /** + * Prepends a chunk (or more) with all hashes from all chunks + final chunk. + * + * @param $rawData Raw data string + * @param $finalHash Final hash from the raw data + * @return void + */ + private function prependHashChunk ($rawData, $finalHash) { + // "Implode" the whole array of hashes into one string + $rawData = self::HASH_CHUNK_IDENTIFIER . implode(self::CHUNK_HASH_SEPERATOR, $this->chunkHashes[$finalHash]); + + // Also get a hash from it + $chunkHash = $this->generateHashFromRawData($rawData); + + // Calulcate chunk size + $dataChunkSize = $this->getDataChunkSizeFromHash($chunkHash); + + // Now array_unshift() it to the two chunk arrays + for ($idx = 0; $idx < strlen($rawData); $idx += $dataChunkSize) { + // Get the next chunk + $chunk = substr($rawData, $idx, $dataChunkSize); + + // Hash it and remember it in seperate array + $chunkHash = $this->getCryptoInstance()->hashString($chunk); + array_unshift($this->chunkHashes[$finalHash], $chunkHash); + + // Prepend the hash to the chunk + $chunk = + $chunkHash . self::CHUNK_DATA_HASH_SEPERATOR . + $this->getNextHexSerialNumber() . self::CHUNK_DATA_HASH_SEPERATOR . + $chunk . self::CHUNK_SEPERATOR + ; + + // Make sure the chunk is not larger than a TCP package can hold + assert(strlen($chunk) <= NetworkPackage::TCP_PACKAGE_SIZE); + + // Add it to the array + //* NOISY-DEBUG: */ $this->debugOutput('FRAGMENTER: Adding ' . strlen($chunk) . ' bytes of a chunk.'); + array_unshift($this->chunks[$finalHash], $chunk); + } // END - for + } + /** * This method does "implode" the given package data array into one long * string, splits it into small chunks, adds a serial number and checksum - * to all chunks and prepends a final hashsum chunk. + * to all chunks and prepends a chunk with all hashes only in it. It will + * return the final hash for faster processing of packages. * * @param $packageData Raw package data array * @param $connectionInstance A helper instance for connections - * @return void + * @return $finalHash Final hash for faster processing + * @todo $connectionInstance is unused */ - public function fragmentPackageArray (array $packageData, BaseconnectionInstance $connectionInstance) { + public function fragmentPackageArray (array $packageData, BaseConnectionHelper $connectionInstance) { + // Is this package already fragmented? + if (!$this->isPackageProcessed($packageData)) { + // First we need to "implode" the array + $rawData = implode(NetworkPackage::PACKAGE_DATA_SEPERATOR, $packageData); + + // Generate the final hash from the raw data (not encoded!) + $finalHash = $this->generateHashFromRawData($rawData); + + // Remember it + $this->processedPackages[$this->getProcessedPackagesIndex($packageData)] = $finalHash; + + // Init pointer + $this->initPointer($finalHash); + + // Split the encoded data into smaller chunks + $this->splitEncodedDataIntoChunks($rawData, $finalHash); + + // Prepend a chunk with all hashes together + $this->prependHashChunk($rawData, $finalHash); + + // Mark the package as fragmented + $this->markPackageDataProcessed($packageData); + } else { + // Get the final hash from the package data + $finalHash = $this->getFinalHashFromPackageData($packageData); + } + + // Return final hash + //* NOISY-DEBUG: */ $this->debugOutput('FRAGMENTER: finalHash[' . gettype($finalHash) . ']=' . $finalHash); + return $finalHash; } /** * This method gets the next chunk from the internal FIFO which should be - * sent to the given recipient. + * sent to the given recipient. It will return an associative array where + * the key is the chunk hash and value the raw chunk data. * - * @param $packageData Raw package data array + * @param $finalHash Final hash for faster lookup * @return $rawDataChunk Raw package data chunk */ - public function getNextRawDataChunk (array $packageData) { + public function getNextRawDataChunk ($finalHash) { + try { + // Get current chunk index + $current = $this->getCurrentChunkPointer($finalHash); + } catch (AssertionException $e) { + // This may happen when the final hash is true + if ($finalHash === true) { + // Set current to null + $current = null; + } else { + // Throw the exception + throw $e; + } + } + + // If there is no entry left, return an empty array + if ((!isset($this->chunkHashes[$finalHash][$current])) || (!isset($this->chunks[$finalHash][$current]))) { + // No more entries found + return array(); + } // END - if + + // Generate the array + $rawDataChunk = array( + $this->chunkHashes[$finalHash][$current] => $this->chunks[$finalHash][$current] + ); + + // Count one index up + $this->nextChunkPointer($finalHash); + + // Return the chunk array + return $rawDataChunk; } }