]> git.mxchange.org Git - hub.git/blob - application/hub/main/handler/chunks/class_ChunkHandler.php
a0d0bfcacaadfc55fc75d2a2b5e4e551094b958a
[hub.git] / application / hub / main / handler / chunks / class_ChunkHandler.php
1 <?php
2 /**
3  * A Chunk handler
4  *
5  * @author              Roland Haeder <webmaster@ship-simu.org>
6  * @version             0.0.0
7  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2011 Hub Developer Team
8  * @license             GNU GPL 3.0 or any newer version
9  * @link                http://www.ship-simu.org
10  *
11  * This program is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation, either version 3 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23  */
24 class ChunkHandler extends BaseHandler implements HandleableChunks, Registerable {
25         /**
26          * Stacker for chunks with final EOP
27          */
28         const STACKER_NAME_CHUNKS_WITH_FINAL_EOP = 'final_chunks';
29
30         /**
31          * The final array for assembling the original package back together
32          */
33         private $finalPackageChunks = array(
34                 // Array for package content
35                 'content'     => array(),
36                 // ... and for the hashes
37                 'hashes'      => array(),
38                 // ... marker for that the final array is complete for assembling all chunks
39                 'is_complete' => false
40         );
41
42         /**
43          * Protected constructor
44          *
45          * @return      void
46          */
47         protected function __construct () {
48                 // Call parent constructor
49                 parent::__construct(__CLASS__);
50
51                 // Set handler name
52                 $this->setHandlerName('chunk');
53         }
54
55         /**
56          * Creates an instance of this class
57          *
58          * @return      $handlerInstance        An instance of a chunk Handler class
59          */
60         public final static function createChunkHandler () {
61                 // Get new instance
62                 $handlerInstance = new ChunkHandler();
63
64                 // Get a FIFO stacker
65                 $stackerInstance = ObjectFactory::createObjectByConfiguredName('chunk_handler_stacker_class');
66
67                 // Init all stacker
68                 $stackerInstance->initStacker(self::STACKER_NAME_CHUNKS_WITH_FINAL_EOP);
69
70                 // Set the stacker in this handler
71                 $handlerInstance->setStackerInstance($stackerInstance);
72
73                 // Get a crypto instance ...
74                 $cryptoInstance = ObjectFactory::createObjectByConfiguredName('crypto_class');
75
76                 // ... and set it in this handler
77                 $handlerInstance->setCryptoInstance($cryptoInstance);
78
79                 // Return the prepared instance
80                 return $handlerInstance;
81         }
82
83         /**
84          * Checks whether the hash generated from package content is the same ("valid") as given
85          *
86          * @param       $chunkSplits    An array from a splitted chunk
87          * @return      $isValid                Whether the hash is "valid"
88          */
89         private function isChunkHashValid (array $chunkSplits) {
90                 // Now hash the raw data again
91                 $chunkHash = $this->getCryptoInstance()->hashString($chunkSplits[2], $chunkSplits[0], false);
92
93                 // Debug output
94                 //* NOISY-DEBUG: */ $this->debugOutput('CHUNK-HANDLER: chunkHash=' . $chunkHash . ',chunkSplits[0]=' . $chunkSplits[0] . ',chunkSplits[1]=' . $chunkSplits[1]);
95
96                 // Check it
97                 $isValid = ($chunkSplits[0] === $chunkHash);
98
99                 // ... and return it
100                 return $isValid;
101         }
102
103         /**
104          * Checks whether the given serial number is valid
105          *
106          * @param       $serialNumber   A serial number from a chunk
107          * @return      $isValid                Whether the serial number is valid
108          */
109         private function isSerialNumberValid ($serialNumber) {
110                 // Check it
111                 $isValid = ((strlen($serialNumber) == PackageFragmenter::MAX_SERIAL_LENGTH) && ($this->bigintval($serialNumber, false) === $serialNumber));
112
113                 // Return result
114                 return $isValid;
115         }
116
117         /**
118          * Adds the chunk to the final array which will be used for the final step
119          * which will be to assemble all chunks back to the original package content
120          * and for the final hash check.
121          *
122          * This method may throw an exception if a chunk with the same serial number
123          * has already been added to avoid mixing chunks from different packages.
124          *
125          * @param       $chunkSplits    An array from a splitted chunk
126          * @return      void
127          */
128         private function addChunkToFinalArray (array $chunkSplits) {
129                 // Is the serial number (index 1) already been added?
130                 if (isset($this->finalPackageChunks[$chunkSplits[1]])) {
131                         // Then throw an exception
132                         throw new ChunkAlreadyAssembledException(array($this, $chunkSplits), self::EXCEPTION_CHUNK_ALREADY_ASSEMBLED);
133                 } // END - if
134
135                 // Add the chunk data (index 2) to the final array and use the serial number as index
136                 $this->finalPackageChunks['content'][$chunkSplits[1]] = $chunkSplits[2];
137
138                 // ... and the hash as well
139                 $this->finalPackageChunks['hashes'][$chunkSplits[1]] = $chunkSplits[0];
140         }
141
142         /**
143          * Marks the final array as completed, do only this if you really have all
144          * chunks together including EOP and "hash chunk".
145          *
146          * @return      void
147          */
148         private function markFinalArrayAsCompleted () {
149                 /*
150                  * As for now, just set the array element. If any further steps are
151                  * being added, this should always be the last step.
152                  */
153                 $this->finalPackageChunks['is_complete'] = true;
154         }
155
156         /**
157          * Adds all chunks if the last one verifies as a 'final chunk'.
158          *
159          * @param       $chunks         An array with chunks, the last one should be a 'final'
160          * @return      void
161          * @throws      FinalChunkVerificationException         If the final chunk does not start with 'EOP:'
162          */
163         public function addAllChunksWithFinal (array $chunks) {
164                 // Validate final chunk
165                 if (!$this->isValidFinalChunk($chunks)) {
166                         // Last chunk is not valid
167                         throw new FinalChunkVerificationException(array($this, $chunks), BaseListener::EXCEPTION_FINAL_CHUNK_VERIFICATION);
168                 } // END - if
169
170                 // Add all chunks to the FIFO stacker
171                 foreach ($chunks as $chunk) {
172                         // Add the chunk
173                         $this->getStackerInstance()->pushNamed(self::STACKER_NAME_CHUNKS_WITH_FINAL_EOP, $chunk);
174                 } // END - foreach
175         }
176
177         /**
178          * Checks whether unhandled chunks are available
179          *
180          * @return      $unhandledChunks        Whether unhandled chunks are left
181          */
182         public function ifUnhandledChunksWithFinalAvailable () {
183                 // Simply check if the stacker is not empty
184                 $unhandledChunks = $this->getStackerInstance()->isStackEmpty(self::STACKER_NAME_CHUNKS_WITH_FINAL_EOP) === false;
185
186                 // Return result
187                 return $unhandledChunks;
188         }
189
190         /**
191          * Handles available chunks by processing one-by-one (not all together,
192          * this would slow-down the whole application) with the help of an
193          * iterator.
194          *
195          * @return      void
196          */
197         public function handleAvailableChunksWithFinal () {
198                 // First check if there are undhandled chunks available
199                 assert($this->ifUnhandledChunksWithFinalAvailable());
200
201                 // Get an entry from the stacker
202                 $chunk = $this->getStackerInstance()->popNamed(self::STACKER_NAME_CHUNKS_WITH_FINAL_EOP);
203
204                 // Split the string with proper separator character
205                 $chunkSplits = explode(PackageFragmenter::CHUNK_DATA_HASH_SEPARATOR, $chunk);
206
207                 /*
208                  * Make sure three elements are always found:
209                  * 0 = Hash
210                  * 1 = Serial number
211                  * 2 = Raw data
212                  */
213                 assert(count($chunkSplits) == 3);
214
215                 // Is the generated hash from data same ("valid") as given hash?
216                 if (!$this->isChunkHashValid($chunkSplits)) {
217                         // Do some logging
218                         $this->debugOutput('CHUNK-HANDLER: Chunk content is not validating against given hash.');
219
220                         // Re-request this chunk (trust the hash in index # 0)
221                         $this->rerequestChunkBySplitsArray($chunkSplits);
222
223                         // Don't process this chunk
224                         return;
225                 } // END - if
226
227                 // Is the serial number valid (chars 0-9, length equals PackageFragmenter::MAX_SERIAL_LENGTH)?
228                 if (!$this->isSerialNumberValid($chunkSplits[1])) {
229                         // Do some logging
230                         $this->debugOutput('CHUNK-HANDLER: Chunk serial numberĀ for hash ' . $chunkSplits[0] . ' is invalid.');
231
232                         // Re-request this chunk
233                         $this->rerequestChunkBySplitsArray($chunkSplits);
234
235                         // Don't process this chunk
236                         return;
237                 } // END - if
238
239                 /*
240                  * It is now known that (as long as the hash algorithm has no
241                  * collisions) the content is the same as the sender sends it to this
242                  * peer.
243                  *
244                  * And also the serial number is valid (basicly) at this point. Now the
245                  * chunk can be added to the final array.
246                  */
247                 $this->addChunkToFinalArray($chunkSplits);
248
249                 // Is the stack now empty?
250                 if ($this->getStackerInstance()->isStackEmpty(self::STACKER_NAME_CHUNKS_WITH_FINAL_EOP)) {
251                         // Then mark the final array as complete
252                         $this->markFinalArrayAsCompleted();
253                 } // END - if
254         }
255
256         /**
257          * Checks whether unassembled chunks are available (ready) in final array
258          *
259          * @return      $unassembledChunksAvailable             Whether unassembled chunks are available
260          */
261         public function ifUnassembledChunksAvailable () {
262                 // For now do only check the array element 'is_complete'
263                 $unassembledChunksAvailable = ($this->finalPackageChunks['is_complete'] === true);
264
265                 // Return status
266                 return $unassembledChunksAvailable;
267         }
268
269         /**
270          * Assembles all chunks (except EOP and "hash chunk") back together to the original package data.
271          *
272          * This is done by the following steps:
273 *
274          * 1) Sort the final array with ksort(). This will bring the "hash
275          *    chunk" up to the last array index and the EOP chunk to the
276          *    pre-last array index
277          * 2) Assemble all chunks except two last (see above step)
278          * 3) While so, do the final check on all hashes
279          * 4) If the package is assembled back together, hash it again for
280          *    the very final verification.
281          *
282          * @return      void
283          */
284         public function assembleChunksFromFinalArray () {
285                 // Make sure the final array is really completed
286                 assert($this->ifUnassembledChunksAvailable());
287
288                 $this->partialStub('Please implement this method.');
289         }
290 }
291
292 // [EOF]
293 ?>