<?php
/**
* A PackageFragmenter class to fragment package data into smaller chunks for
- * delivery. This class does add a serial number to it and in the first data
- * submission chunk it will add a sumerization of all fragements and their
- * serial numbers.
+ * delivery. This class calculates a final hash on the raw input data and
+ * fragments the data into smaller chunks after it has been encoded by a
+ * "outgoing encoding stream".
+ *
+ * All chunks are extended with a hash and a serial number to make it later
+ * easier to verify them and put them back in the right order and to, if
+ * required, request a re-delivery of an invalid chunk (e.g. hash didn't match).
+ * Also an "end-of-package" marker is being added as the last chunk to mark the
+ * end of of the whole package submission.
*
* @author Roland Haeder <webmaster@ship-simu.org>
* @version 0.0.0
*/
private $serialNumber = 0x00000000;
+ /**
+ * Maximum possible serial number
+ */
+ private $maxSerialNumber = 0;
+
/**
* Length of largest possible serial number
*/
- private $maxSerialLength = 8;
+ const MAX_SERIAL_LENGTH = 8;
/**
- * Maximum possible serial number
+ * Separator between chunk data, serial number and chunk hash
*/
- private $maxSerialNumber = 0;
+ const CHUNK_DATA_HASH_SEPARATOR = '@';
+
/**
- * Seperator between chunk data, serial number and chunk hash
+ * SEPARATOR for all chunk hashes
*/
- const CHUNK_DATA_HASH_SEPERATOR = '@';
+ const CHUNK_HASH_SEPARATOR = ';';
/**
- * Seperator for all chunk hashes
+ * SEPARATOR between two chunks
*/
- const CHUNK_HASH_SEPERATOR = ';';
+ const CHUNK_SEPARATOR = '|';
/**
* Identifier for hash chunk
*/
const HASH_CHUNK_IDENTIFIER = 'HASH-CHUNK:';
+ /**
+ * Identifier for end-of-package marker
+ */
+ const END_OF_PACKAGE_IDENTIFIER = 'EOP:';
+
/**
* Protected constructor
*
// Get new instance
$fragmenterInstance = new PackageFragmenter();
- // Get an output stream for all out-going packages
- $streamInstance = ObjectFactory::createObjectByConfiguredName('node_raw_package_output_stream');
-
- // And set it in this fragmenter
- $fragmenterInstance->setOutputStreamInstance($streamInstance);
-
// And also a crypto instance (for our encrypted messages)
$cryptoInstance = ObjectFactory::createObjectByConfiguredName('crypto_class');
$fragmenterInstance->setCryptoInstance($cryptoInstance);
$this->chunkSize = $this->getConfigInstance()->getConfigEntry('package_chunk_size');
// - Maximum serial number
- $this->maxSerialNumber = $this->hex2dec(str_repeat('f', $this->maxSerialLength));
+ $this->maxSerialNumber = $this->hex2dec(str_repeat('f', self::MAX_SERIAL_LENGTH));
}
/**
*/
private function getProcessedPackagesIndex (array $packageData) {
return (
- $packageData['sender'] . NetworkPackage::PACKAGE_DATA_SEPERATOR .
- $packageData['recipient'] . NetworkPackage::PACKAGE_DATA_SEPERATOR .
- $packageData['content'] . NetworkPackage::PACKAGE_DATA_SEPERATOR
+ $packageData[NetworkPackage::PACKAGE_DATA_SENDER] . NetworkPackage::PACKAGE_DATA_SEPARATOR .
+ $packageData[NetworkPackage::PACKAGE_DATA_RECIPIENT] . NetworkPackage::PACKAGE_DATA_SEPARATOR .
+ $packageData[NetworkPackage::PACKAGE_DATA_CONTENT] . NetworkPackage::PACKAGE_DATA_SEPARATOR
);
}
/**
- * Checks wether the given package data is already processed by this fragmenter
+ * Checks whether the given package data is already processed by this fragmenter
*
* @param $packageData Raw package data array
- * @return $isProcessed Wether the package has been fragmented
+ * @return $isProcessed Whether the package has been fragmented
*/
private function isPackageProcessed (array $packageData) {
// Get array index
// Is the array index there?
$isProcessed = (
- (isset($this->processedPackages[$index])) &&
+ (isset($this->processedPackages[$index]))
+ &&
($this->processedPackages[$index] === true)
);
// Hash size
strlen($hash) -
// Length of sperators
- (strlen(self::CHUNK_DATA_HASH_SEPERATOR) * 2) -
+ (strlen(self::CHUNK_DATA_HASH_SEPARATOR) * 2) -
// Length of max serial number
- $this->maxSerialLength
+ self::MAX_SERIAL_LENGTH
);
// This should be larger than zero bytes
*
* @param $rawData Raw data bytes to hash
* @return $hash Hash from the raw data
+ * @todo Implement a way to send non-announcement packages with extra-salt
*/
private function generateHashFromRawData ($rawData) {
- // Get the crypto instance and hash the data
- $hash = $this->getCryptoInstance()->hashString($rawData);
+ /*
+ * Get the crypto instance and hash the data with no extra salt because
+ * the other peer doesn't have *this* peer's salt.
+ */
+ $hash = $this->getCryptoInstance()->hashString($rawData, '', false);
// Return it
return $hash;
assert($this->serialNumber <= $this->maxSerialNumber);
// Encode the current serial number
- $encodedSerialNumber = $this->dec2Hex($this->serialNumber, $this->maxSerialLength);
+ $encodedSerialNumber = $this->dec2Hex($this->serialNumber, self::MAX_SERIAL_LENGTH);
// Count one up
$this->serialNumber++;
return $encodedSerialNumber;
}
+ /**
+ * Appends an end-of-package chunk to the chunk list for given chunk and
+ * final hash. As of 23-March-2012 the format of this chunk will be as any
+ * regular one to keep things easy (KISS) in ChunkHandler class.
+ *
+ * @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
+ $chunkData =
+ self::END_OF_PACKAGE_IDENTIFIER .
+ $finalHash . self::CHUNK_HASH_SEPARATOR .
+ $chunkHash;
+
+ // Add it as regular chunk
+ $this->addChunkData($finalHash, $chunkData);
+ }
+
/**
* 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
+ * and the SEPARATOR is being subtracted from chunk size to fit it into a
* TCP package (512 bytes).
*
- * @param $encodedData Encoded data string
- * @param $finalHash Final hash from the raw data
+ * @param $rawData Raw data string
+ * @param $finalHash Final hash from the raw data
* @return void
*/
- private function splitEncodedDataIntoChunks ($encodedData, $finalHash) {
+ private function splitEncodedDataIntoChunks ($rawData, $finalHash) {
// Make sure final hashes with at least 32 bytes can pass
assert(strlen($finalHash) >= 32);
$dataChunkSize = $this->getDataChunkSizeFromHash($finalHash);
//* NOISY-DEBUG: */ $this->debugOutput('FRAGMENTER: dataChunkSize=' . $dataChunkSize);
+ // Init variables
+ $chunkHash = '';
+
// Now split it up
- for ($idx = 0; $idx < strlen($encodedData); $idx += $dataChunkSize) {
+ for ($idx = 0; $idx < strlen($rawData); $idx += $dataChunkSize) {
// Get the next chunk
- $chunk = substr($encodedData, $idx, $dataChunkSize);
+ $chunkData = substr($rawData, $idx, $dataChunkSize);
- // Hash it and remember it in seperate array
- $chunkHash = $this->getCryptoInstance()->hashString($chunk);
- $this->chunkHashes[$finalHash][] = $chunkHash;
+ // Add the chunk to the propper array and do all the stuff there
+ $this->addChunkData($finalHash, $chunkData);
+ } // END - for
- // Prepend the hash to the chunk
- $chunk = $chunkHash . self::CHUNK_DATA_HASH_SEPERATOR . $this->getNextHexSerialNumber() . self::CHUNK_DATA_HASH_SEPERATOR . $chunk;
+ // Debug output
+ //* NOISY-DEBUG: */ $this->debugOutput('FRAGMENTER: Raw data of ' . strlen($rawData) . ' bytes has been fragmented into ' . count($this->chunks[$finalHash]) . ' chunk(s).');
- // Make sure the chunk is not larger than a TCP package can hold
- assert(strlen($chunk) <= NetworkPackage::TCP_PACKAGE_SIZE);
+ // Add end-of-package chunk
+ $this->appendEndOfPackageChunk($chunkHash, $finalHash);
+ }
- // Add it to the array
- //* NOISY-DEBUG: */ $this->debugOutput('FRAGMENTER: Adding ' . strlen($chunk) . ' bytes of a chunk.');
- $this->chunks[$finalHash][] = $chunk;
- } // END - for
+ /**
+ * Adds the given chunk (raw data) to the proper array and hashes it for
+ * later verfication.
+ *
+ * @param $finalHash Final hash for faster processing
+ * @param $chunkData Raw chunk data
+ * @return void
+ */
+ private function addChunkData ($finalHash, $chunkData) {
+ // Hash it
+ $rawDataHash = $this->getCryptoInstance()->hashString($chunkData, '', false);
+
+ // Prepend the hash to the chunk
+ $rawData = (
+ $rawDataHash . self::CHUNK_DATA_HASH_SEPARATOR .
+ $this->getNextHexSerialNumber() . self::CHUNK_DATA_HASH_SEPARATOR .
+ $chunkData . self::CHUNK_SEPARATOR
+ );
- // Debug output
- $this->debugOutput('FRAGMENTER: Encoded data of ' . strlen($encodedData) . ' bytes has been fragmented into ' . count($this->chunks[$finalHash]) . ' chunk(s).');
+ // Make sure the chunk is not larger than a TCP package can hold
+ assert(strlen($rawData) <= NetworkPackage::TCP_PACKAGE_SIZE);
+
+ // Add it to the array
+ //* NOISY-DEBUG: */ $this->debugOutput('FRAGMENTER: Adding ' . strlen($rawData) . ' bytes of a chunk ...');
+ $this->chunks[$finalHash][] = $rawData;
+ $this->chunkHashes[$finalHash][] = $rawDataHash;
}
/**
* Prepends a chunk (or more) with all hashes from all chunks + final chunk.
*
- * @param $encodedData Encoded data string
- * @param $finalHash Final hash from the raw data
+ * @param $rawData Raw data string
+ * @param $finalHash Final hash from the raw data
* @return void
*/
- private function prependHashChunk ($encodedData, $finalHash) {
+ 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]);
+ $rawData = self::HASH_CHUNK_IDENTIFIER . implode(self::CHUNK_HASH_SEPARATOR, $this->chunkHashes[$finalHash]);
// Also get a hash from it
$chunkHash = $this->generateHashFromRawData($rawData);
- // Also encode this one
- $encodedData = $this->getOutputStreamInstance()->streamData($rawData);
-
// Calulcate chunk size
$dataChunkSize = $this->getDataChunkSizeFromHash($chunkHash);
// Now array_unshift() it to the two chunk arrays
- for ($idx = 0; $idx < strlen($encodedData); $idx += $dataChunkSize) {
+ for ($idx = 0; $idx < strlen($rawData); $idx += $dataChunkSize) {
// Get the next chunk
- $chunk = substr($encodedData, $idx, $dataChunkSize);
+ $chunk = substr($rawData, $idx, $dataChunkSize);
// Hash it and remember it in seperate array
- $chunkHash = $this->getCryptoInstance()->hashString($chunk);
+ $chunkHash = $this->getCryptoInstance()->hashString($chunk, '', false);
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;
+ $chunk =
+ $chunkHash . self::CHUNK_DATA_HASH_SEPARATOR .
+ $this->getNextHexSerialNumber() . self::CHUNK_DATA_HASH_SEPARATOR .
+ $chunk . self::CHUNK_SEPARATOR
+ ;
// Make sure the chunk is not larger than a TCP package can hold
assert(strlen($chunk) <= NetworkPackage::TCP_PACKAGE_SIZE);
* 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 $finalHash Final hash for faster processing
- * @todo $connectionInstance is unused
+ * @param $packageData Raw package data array
+ * @param $helperInstance An instance of a ConnectionHelper class
+ * @return $finalHash Final hash for faster processing
+ * @todo $helperInstance is unused
*/
- public function fragmentPackageArray (array $packageData, BaseConnectionHelper $connectionInstance) {
+ public function fragmentPackageArray (array $packageData, ConnectionHelper $helperInstance) {
// Is this package already fragmented?
if (!$this->isPackageProcessed($packageData)) {
+ // Remove package status, the recipient doesn't need this
+ unset($packageData[NetworkPackage::PACKAGE_DATA_STATUS]);
+
// First we need to "implode" the array
- $rawData = implode(NetworkPackage::PACKAGE_DATA_SEPERATOR, $packageData);
+ $rawData = implode(NetworkPackage::PACKAGE_DATA_SEPARATOR, $packageData);
// Generate the final hash from the raw data (not encoded!)
$finalHash = $this->generateHashFromRawData($rawData);
// Init pointer
$this->initPointer($finalHash);
- // Encode the package for delivery
- $encodedData = $this->getOutputStreamInstance()->streamData($rawData);
-
// Split the encoded data into smaller chunks
- $this->splitEncodedDataIntoChunks($encodedData, $finalHash);
+ $this->splitEncodedDataIntoChunks($rawData, $finalHash);
// Prepend a chunk with all hashes together
- $this->prependHashChunk($encodedData, $finalHash);
+ $this->prependHashChunk($rawData, $finalHash);
// Mark the package as fragmented
$this->markPackageDataProcessed($packageData);
*
* @param $finalHash Final hash for faster lookup
* @return $rawDataChunk Raw package data chunk
+ * @throws AssertionException If $finalHash was not 'true'
*/
public function getNextRawDataChunk ($finalHash) {
try {
// This may happen when the final hash is true
if ($finalHash === true) {
// Set current to null
- $current = null;
+ $current = NULL;
} else {
// Throw the exception
throw $e;