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