]> git.mxchange.org Git - hub.git/blob - application/hub/main/package/fragmenter/class_PackageFragmenter.php
New singleton-factories introduced:
[hub.git] / application / hub / main / package / fragmenter / class_PackageFragmenter.php
1 <?php
2 /**
3  * A PackageFragmenter class to fragment package data into smaller chunks for
4  * delivery. This class calculates a final hash on the raw input data and
5  * fragments the data into smaller chunks after it has been encoded by a
6  * "outgoing encoding stream".
7  *
8  * All chunks are extended with a hash and a serial number to make it later
9  * easier to verify them and put them back in the right order and to, if
10  * required, request a re-delivery of an invalid chunk (e.g. hash didn't match).
11  * Also an "end-of-package" marker is being added as the last chunk to mark the
12  * end of of the whole package submission.
13  *
14  * @author              Roland Haeder <webmaster@ship-simu.org>
15  * @version             0.0.0
16  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2011 Hub Developer Team
17  * @license             GNU GPL 3.0 or any newer version
18  * @link                http://www.ship-simu.org
19  *
20  * This program is free software: you can redistribute it and/or modify
21  * it under the terms of the GNU General Public License as published by
22  * the Free Software Foundation, either version 3 of the License, or
23  * (at your option) any later version.
24  *
25  * This program is distributed in the hope that it will be useful,
26  * but WITHOUT ANY WARRANTY; without even the implied warranty of
27  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
28  * GNU General Public License for more details.
29  *
30  * You should have received a copy of the GNU General Public License
31  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
32  */
33 class PackageFragmenter extends BaseFrameworkSystem implements Fragmentable, Registerable {
34         /**
35          * Cached chunk size in bits
36          */
37         private $chunkSize = 0;
38
39         /**
40          * Array for chunks
41          */
42         private $chunks = array();
43
44         /**
45          * Array for chunk hashes
46          */
47         private $chunkHashes = array();
48
49         /**
50          * Array for chunk pointers
51          */
52         private $chunkPointers = array();
53
54         /**
55          * Array for processed packages
56          */
57         private $processedPackages = array();
58
59         /**
60          * Serial number
61          */
62         private $serialNumber = 0x00000000;
63
64         /**
65          * Length of largest possible serial number
66          */
67         private $maxSerialLength = 8;
68
69         /**
70          * Maximum possible serial number
71          */
72         private $maxSerialNumber  = 0;
73
74         /**
75          * Seperator between chunk data, serial number and chunk hash
76          */
77         const CHUNK_DATA_HASH_SEPERATOR = '@';
78
79         /**
80          * Seperator for all chunk hashes
81          */
82         const CHUNK_HASH_SEPERATOR = ';';
83
84         /**
85          * Seperator between two chunks
86          */
87         const CHUNK_SEPERATOR = '|';
88
89         /**
90          * Identifier for hash chunk
91          */
92         const HASH_CHUNK_IDENTIFIER = 'HASH-CHUNK:';
93
94         /**
95          * Identifier for end-of-package marker
96          */
97         const END_OF_PACKAGE_IDENTIFIER = 'EOP:';
98
99         /**
100          * Protected constructor
101          *
102          * @return      void
103          */
104         protected function __construct () {
105                 // Call parent constructor
106                 parent::__construct(__CLASS__);
107
108                 // Init this fragmenter
109                 $this->initFragmenter();
110         }
111
112         /**
113          * Creates an instance of this class
114          *
115          * @return      $fragmenterInstance             An instance of a Fragmentable class
116          */
117         public static final function createPackageFragmenter () {
118                 // Get new instance
119                 $fragmenterInstance = new PackageFragmenter();
120
121                 // And also a crypto instance (for our encrypted messages)
122                 $cryptoInstance = ObjectFactory::createObjectByConfiguredName('crypto_class');
123                 $fragmenterInstance->setCryptoInstance($cryptoInstance);
124
125                 // Return the prepared instance
126                 return $fragmenterInstance;
127         }
128
129         /**
130          * Initializes this fragmenter
131          *
132          * @return      void
133          */
134         private function initFragmenter () {
135                 // Load some configuration entries and "cache" them:
136                 // - Chunk size in bits
137                 $this->chunkSize = $this->getConfigInstance()->getConfigEntry('package_chunk_size');
138
139                 // - Maximum serial number
140                 $this->maxSerialNumber = $this->hex2dec(str_repeat('f', $this->maxSerialLength));
141         }
142
143         /**
144          * Initializes the pointer for given final hash
145          *
146          * @param       $finalHash      Final hash to initialize pointer for
147          * @return      void
148          */
149         private function initPointer ($finalHash) {
150                 $this->chunkPointers[$finalHash] = 0;
151         }
152
153         /**
154          * "Getter" for processedPackages array index
155          *
156          * @param       $packageData    Raw package data array
157          * @return      $index                  Array index for processedPackages
158          */
159         private function getProcessedPackagesIndex (array $packageData) {
160                 return (
161                         $packageData['sender']    . NetworkPackage::PACKAGE_DATA_SEPERATOR .
162                         $packageData['recipient'] . NetworkPackage::PACKAGE_DATA_SEPERATOR .
163                         $packageData['content']   . NetworkPackage::PACKAGE_DATA_SEPERATOR
164                 );
165         }
166
167         /**
168          * Checks wether the given package data is already processed by this fragmenter
169          *
170          * @param       $packageData    Raw package data array
171          * @return      $isProcessed    Wether the package has been fragmented
172          */
173         private function isPackageProcessed (array $packageData) {
174                 // Get array index
175                 $index = $this->getProcessedPackagesIndex($packageData);
176
177                 // Is the array index there?
178                 $isProcessed = (
179                         (isset($this->processedPackages[$index])) &&
180                         ($this->processedPackages[$index] === true)
181                 );
182
183                 // Return it
184                 return $isProcessed;
185         }
186
187         /**
188          * Marks the given package data as processed by this fragmenter
189          *
190          * @param       $packageData    Raw package data array
191          * @return      void
192          */
193         private function markPackageDataProcessed (array $packageData) {
194                 // Remember it (until we may remove it)
195                 $this->processedPackages[$this->getProcessedPackagesIndex($packageData)] = true;
196         }
197
198         /**
199          * Getter for final hash from given package data
200          *
201          * @param       $packageData    Raw package data array
202          * @return      $finalHash              Final hash for package data
203          */
204         private function getFinalHashFromPackageData (array $packageData) {
205                 // Make sure it is there
206                 assert(isset($this->processedPackages[$this->getProcessedPackagesIndex($packageData)]));
207
208                 // Return it
209                 return $this->processedPackages[$this->getProcessedPackagesIndex($packageData)];
210         }
211
212         /**
213          * Get next chunk pointer for given final hash
214          *
215          * @param       $finalHash      Final hash to get current pointer for
216          */
217         private function getCurrentChunkPointer ($finalHash) {
218                 // Is the final hash valid?
219                 assert(strlen($finalHash) > 0);
220
221                 // Is the pointer already initialized?
222                 //* NOISY-DEBUG: */ $this->debugOutput('FRAGMENTER: finalHash=' . $finalHash);
223                 assert(isset($this->chunkPointers[$finalHash]));
224
225                 // Return it
226                 return $this->chunkPointers[$finalHash];
227         }
228
229         /**
230          * Advance the chunk pointer for given final hash
231          *
232          * @param       $finalHash      Final hash to advance the pointer for
233          */
234         private function nextChunkPointer ($finalHash) {
235                 // Is the pointer already initialized?
236                 assert(isset($this->chunkPointers[$finalHash]));
237
238                 // Count one up
239                 $this->chunkPointers[$finalHash]++;
240         }
241
242         /**
243          * "Getter" for data chunk size of given hash.
244          *
245          * @param       $hash                   Hash to substract it's length
246          * @return      $dataChunkSize  The chunk size
247          */
248         private function getDataChunkSizeFromHash ($hash) {
249                 // Calculate real (data) chunk size
250                 $dataChunkSize = (
251                         // Real chunk size
252                         ($this->chunkSize / 8) -
253                         // Hash size
254                         strlen($hash) -
255                         // Length of sperators
256                         (strlen(self::CHUNK_DATA_HASH_SEPERATOR) * 2) -
257                         // Length of max serial number
258                         $this->maxSerialLength
259                 );
260
261                 // This should be larger than zero bytes
262                 assert($dataChunkSize > 0);
263
264                 // Return it
265                 return $dataChunkSize;
266         }
267
268         /**
269          * Generates a hash from raw data
270          *
271          * @param       $rawData        Raw data bytes to hash
272          * @return      $hash           Hash from the raw data
273          */
274         private function generateHashFromRawData ($rawData) {
275                 // Get the crypto instance and hash the data
276                 $hash = $this->getCryptoInstance()->hashString($rawData);
277
278                 // Return it
279                 return $hash;
280         }
281
282         /**
283          * "Getter" for the next hexadecimal-encoded serial number
284          *
285          * @return      $encodedSerialNumber    The next hexadecimal-encoded serial number
286          */
287         private function getNextHexSerialNumber () {
288                 // Assert on maximum serial number length
289                 assert($this->serialNumber <= $this->maxSerialNumber);
290
291                 // Encode the current serial number
292                 $encodedSerialNumber = $this->dec2Hex($this->serialNumber, $this->maxSerialLength);
293
294                 // Count one up
295                 $this->serialNumber++;
296
297                 // Return the encoded serial number
298                 return $encodedSerialNumber;
299         }
300
301         /**
302          * Appends an end-of-package chunk to the chunk list for given chunk and
303          * final hash.
304          *
305          * @param       $chunkHash      Last chunk's hash
306          * @param       $finalHash      Final hash for raw (unencoded) data
307          * @return      void
308          */
309         private function appendEndOfPackageChunk ($chunkHash, $finalHash) {
310                 // Generate end-of-package marker
311                 $rawData =
312                         self::END_OF_PACKAGE_IDENTIFIER .
313                         $finalHash . self::CHUNK_HASH_SEPERATOR .
314                         $chunkHash . self::CHUNK_SEPERATOR;
315
316                 // Also get a hash from it
317                 $chunkHash = $this->generateHashFromRawData($rawData);
318
319                 // Append it to the chunk's data and hash array
320                 $this->chunkHashes[$finalHash][] = $chunkHash;
321                 $this->chunks[$finalHash][]      = $rawData;
322         }
323
324         /**
325          * Splits the given encoded data into smaller chunks, the size of the final
326          * and the seperator is being subtracted from chunk size to fit it into a
327          * TCP package (512 bytes).
328          *
329          * @param       $rawData        Raw data string
330          * @param       $finalHash      Final hash from the raw data
331          * @return      void
332          */
333         private function splitEncodedDataIntoChunks ($rawData, $finalHash) {
334                 // Make sure final hashes with at least 32 bytes can pass
335                 assert(strlen($finalHash) >= 32);
336
337                 // Calculate real (data) chunk size
338                 $dataChunkSize = $this->getDataChunkSizeFromHash($finalHash);
339                 //* NOISY-DEBUG: */ $this->debugOutput('FRAGMENTER: dataChunkSize=' . $dataChunkSize);
340
341                 // Init variables
342                 $chunkHash = '';
343
344                 // Now split it up
345                 for ($idx = 0; $idx < strlen($rawData); $idx += $dataChunkSize) {
346                         // Get the next chunk
347                         $chunk = substr($rawData, $idx, $dataChunkSize);
348
349                         // Hash it and remember it in seperate array
350                         $chunkHash = $this->getCryptoInstance()->hashString($chunk);
351                         $this->chunkHashes[$finalHash][] = $chunkHash;
352
353                         // Prepend the hash to the chunk
354                         $chunk =
355                                 $chunkHash . self::CHUNK_DATA_HASH_SEPERATOR .
356                                 $this->getNextHexSerialNumber() . self::CHUNK_DATA_HASH_SEPERATOR .
357                                 $chunk . self::CHUNK_SEPERATOR
358                         ;
359
360                         // Make sure the chunk is not larger than a TCP package can hold
361                         assert(strlen($chunk) <= NetworkPackage::TCP_PACKAGE_SIZE);
362
363                         // Add it to the array
364                         //* NOISY-DEBUG: */ $this->debugOutput('FRAGMENTER: Adding ' . strlen($chunk) . ' bytes of a chunk.');
365                         $this->chunks[$finalHash][] = $chunk;
366                 } // END - for
367
368                 // Debug output
369                 //* NOISY-DEBUG: */ $this->debugOutput('FRAGMENTER: Raw data of ' . strlen($rawData) . ' bytes has been fragmented into ' . count($this->chunks[$finalHash]) . ' chunk(s).');
370
371                 // Add end-of-package chunk
372                 $this->appendEndOfPackageChunk($chunkHash, $finalHash);
373         }
374
375         /**
376          * Prepends a chunk (or more) with all hashes from all chunks + final chunk.
377          *
378          * @param       $rawData        Raw data string
379          * @param       $finalHash      Final hash from the raw data
380          * @return      void
381          */
382         private function prependHashChunk ($rawData, $finalHash) {
383                 // "Implode" the whole array of hashes into one string
384                 $rawData = self::HASH_CHUNK_IDENTIFIER . implode(self::CHUNK_HASH_SEPERATOR, $this->chunkHashes[$finalHash]);
385
386                 // Also get a hash from it
387                 $chunkHash = $this->generateHashFromRawData($rawData);
388
389                 // Calulcate chunk size
390                 $dataChunkSize = $this->getDataChunkSizeFromHash($chunkHash);
391
392                 // Now array_unshift() it to the two chunk arrays
393                 for ($idx = 0; $idx < strlen($rawData); $idx += $dataChunkSize) {
394                         // Get the next chunk
395                         $chunk = substr($rawData, $idx, $dataChunkSize);
396
397                         // Hash it and remember it in seperate array
398                         $chunkHash = $this->getCryptoInstance()->hashString($chunk);
399                         array_unshift($this->chunkHashes[$finalHash], $chunkHash);
400
401                         // Prepend the hash to the chunk
402                         $chunk =
403                                 $chunkHash . self::CHUNK_DATA_HASH_SEPERATOR .
404                                 $this->getNextHexSerialNumber() . self::CHUNK_DATA_HASH_SEPERATOR .
405                                 $chunk . self::CHUNK_SEPERATOR
406                         ;
407
408                         // Make sure the chunk is not larger than a TCP package can hold
409                         assert(strlen($chunk) <= NetworkPackage::TCP_PACKAGE_SIZE);
410
411                         // Add it to the array
412                         //* NOISY-DEBUG: */ $this->debugOutput('FRAGMENTER: Adding ' . strlen($chunk) . ' bytes of a chunk.');
413                         array_unshift($this->chunks[$finalHash], $chunk);
414                 } // END - for
415         }
416
417         /**
418          * This method does "implode" the given package data array into one long
419          * string, splits it into small chunks, adds a serial number and checksum
420          * to all chunks and prepends a chunk with all hashes only in it. It will
421          * return the final hash for faster processing of packages.
422          *
423          * @param       $packageData                    Raw package data array
424          * @param       $connectionInstance             A helper instance for connections
425          * @return      $finalHash                              Final hash for faster processing
426          * @todo        $connectionInstance is unused
427          */
428         public function fragmentPackageArray (array $packageData, BaseConnectionHelper $connectionInstance) {
429                 // Is this package already fragmented?
430                 if (!$this->isPackageProcessed($packageData)) {
431                         // First we need to "implode" the array
432                         $rawData = implode(NetworkPackage::PACKAGE_DATA_SEPERATOR, $packageData);
433
434                         // Generate the final hash from the raw data (not encoded!)
435                         $finalHash = $this->generateHashFromRawData($rawData);
436
437                         // Remember it
438                         $this->processedPackages[$this->getProcessedPackagesIndex($packageData)] = $finalHash;
439
440                         // Init pointer
441                         $this->initPointer($finalHash);
442
443                         // Split the encoded data into smaller chunks
444                         $this->splitEncodedDataIntoChunks($rawData, $finalHash);
445
446                         // Prepend a chunk with all hashes together
447                         $this->prependHashChunk($rawData, $finalHash);
448
449                         // Mark the package as fragmented
450                         $this->markPackageDataProcessed($packageData);
451                 } else {
452                         // Get the final hash from the package data
453                         $finalHash = $this->getFinalHashFromPackageData($packageData);
454                 }
455
456                 // Return final hash
457                 //* NOISY-DEBUG: */ $this->debugOutput('FRAGMENTER: finalHash[' . gettype($finalHash) . ']=' . $finalHash);
458                 return $finalHash;
459         }
460
461         /**
462          * This method gets the next chunk from the internal FIFO which should be
463          * sent to the given recipient. It will return an associative array where
464          * the key is the chunk hash and value the raw chunk data.
465          *
466          * @param       $finalHash              Final hash for faster lookup
467          * @return      $rawDataChunk   Raw package data chunk
468          */
469         public function getNextRawDataChunk ($finalHash) {
470                 try {
471                         // Get current chunk index
472                         $current = $this->getCurrentChunkPointer($finalHash);
473                 } catch (AssertionException $e) {
474                         // This may happen when the final hash is true
475                         if ($finalHash === true) {
476                                 // Set current to null
477                                 $current = null;
478                         } else {
479                                 // Throw the exception
480                                 throw $e;
481                         }
482                 }
483
484                 // If there is no entry left, return an empty array
485                 if ((!isset($this->chunkHashes[$finalHash][$current])) || (!isset($this->chunks[$finalHash][$current]))) {
486                         // No more entries found
487                         return array();
488                 } // END - if
489
490                 // Generate the array
491                 $rawDataChunk = array(
492                         $this->chunkHashes[$finalHash][$current] => $this->chunks[$finalHash][$current]
493                 );
494
495                 // Count one index up
496                 $this->nextChunkPointer($finalHash);
497
498                 // Return the chunk array
499                 return $rawDataChunk;
500         }
501 }
502
503 // [EOF]
504 ?>