3 * A PackageAssembler class to assemble a package content stream fragemented
4 * by PackageFragmenter back to a raw package data array.
6 * @author Roland Haeder <webmaster@shipsimu.org>
8 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2012 Hub Developer Team
9 * @license GNU GPL 3.0 or any newer version
10 * @link http://www.shipsimu.org
12 * This program is free software: you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation, either version 3 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program. If not, see <http://www.gnu.org/licenses/>.
25 class PackageAssembler extends BaseHubSystem implements Assembler, Registerable, Visitable {
27 * Name for stacker holding raw data of multiple messages
29 const STACKER_NAME_MULTIPLE_MESSAGE = 'multiple_message';
34 private $pendingData = '';
37 * Private call-back methods
39 private $callbacks = array();
42 * Protected constructor
46 protected function __construct () {
47 // Call parent constructor
48 parent::__construct(__CLASS__);
52 * Creates an instance of this class
54 * @param $packageInstance An instance of a Receivable class
55 * @return $assemblerInstance An instance of an Assembler class
57 public static final function createPackageAssembler (Receivable $packageInstance) {
59 $assemblerInstance = new PackageAssembler();
61 // Set package instance here
62 $assemblerInstance->setPackageInstance($packageInstance);
64 // Create an instance of a raw data input stream
65 $streamInstance = ObjectFactory::createObjectByConfiguredName('node_raw_data_input_stream_class');
68 $assemblerInstance->setInputStreamInstance($streamInstance);
70 // Now get a chunk handler instance
71 $handlerInstance = ChunkHandlerFactory::createChunkHandlerInstance();
73 // Set handler instance
74 $assemblerInstance->setHandlerInstance($handlerInstance);
76 // Get stacker instance
77 $stackerInstance = ObjectFactory::createObjectByConfiguredName('multiple_message_stacker_class');
79 // Initialize the only one stack
80 $stackerInstance->initStack(self::STACKER_NAME_MULTIPLE_MESSAGE);
83 $assemblerInstance->setStackerInstance($stackerInstance);
85 // Return the prepared instance
86 return $assemblerInstance;
90 * Checks whether the input buffer (stacker to be more preceise) is empty.
92 * @return $isInputBufferEmpty Whether the input buffer is empty
94 private function ifInputBufferIsEmpty () {
96 $isInputBufferEmpty = $this->getPackageInstance()->getStackerInstance()->isStackEmpty(NetworkPackage::STACKER_NAME_DECODED_HANDLED);
99 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('PACKAGE-ASSEMBLER[' . __METHOD__ . ':' . __LINE__ . ': isInputBufferEmpty=' . intval($isInputBufferEmpty));
102 return $isInputBufferEmpty;
106 * Checks whether given package content is completed (start/end markers are found)
108 * @param $packageContent An array with two elements: 'raw_data' and 'error_code'
109 * @return $isCompleted Whether the given package content is completed
111 private function isPackageContentCompleted (array $packageContent) {
113 $isCompleted = $this->ifStartEndMarkersSet($packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA]);
120 * Assembles the content from $packageContent. This method does only
121 * initialize the whole process by creating a call-back which will then
122 * itself (99.9% of all cases) "explode" the decoded data stream and add
123 * it to a chunk assembler queue.
125 * If the call-back method or this would attempt to assemble the package
126 * chunks and (maybe) re-request some chunks from the sender, this would
127 * take to much time and therefore slow down this node again.
129 * @param $packageContent An array with two elements: 'raw_data' and 'error_code'
131 * @throws UnsupportedPackageCodeHandlerException If the package code handler is not implemented
133 public function chunkPackageContent (array $packageContent) {
134 // Validate the package content array again
136 (isset($packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA])) &&
137 (isset($packageContent[BaseRawDataHandler::PACKAGE_ERROR_CODE]))
140 // Construct call-back name from package error code
141 $this->callbacks[$packageContent[BaseRawDataHandler::PACKAGE_ERROR_CODE]] = 'handlePackageBy' . $this->convertToClassName($packageContent[BaseRawDataHandler::PACKAGE_ERROR_CODE]);
143 // Abort if the call-back method is not there
144 if (!method_exists($this, $this->callbacks[$packageContent[BaseRawDataHandler::PACKAGE_ERROR_CODE]])) {
145 // Throw an exception
146 throw new UnsupportedPackageCodeHandlerException(array($this, $this->callbacks[$packageContent[BaseRawDataHandler::PACKAGE_ERROR_CODE]], $packageContent), BaseListener::EXCEPTION_UNSUPPORTED_PACKAGE_CODE_HANDLER);
150 call_user_func(array($this, $this->callbacks[$packageContent[BaseRawDataHandler::PACKAGE_ERROR_CODE]]), $packageContent);
153 /**************************************************************************
154 * Call-back methods for above method *
155 **************************************************************************/
158 * Call-back handler to handle unhandled package data. This method
159 * "explodes" the string with the chunk separator from PackageFragmenter
160 * class, does some low checks on it and feeds it into another queue for
161 * verification and re-request for bad chunks.
163 * @param $packageContent An array with two elements: 'raw_data' and 'error_code'
166 private function handlePackageByUnhandledPackage (array $packageContent) {
168 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('PACKAGE-ASSEMBLER[' . __METHOD__ . ':' . __LINE__ . ']: packageData[' . BaseRawDataHandler::PACKAGE_RAW_DATA . ']=' . $packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA]);
170 // Check for some conditions
171 if ((!$this->ifInputBufferIsEmpty()) || (!$this->isPackageContentCompleted($packageContent))) {
172 // Last chunk is not valid, so wait for more
173 $this->pendingData .= $packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA];
176 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('PACKAGE-ASSEMBLER[' . __METHOD__ . ':' . __LINE__ . ': Partial data received. Waiting for more ... ( ' . strlen($packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA]) . ' bytes)');
179 /* DEBUG */ self::createDebugInstance(__CLASS__)->debugOutput('PACKAGE-ASSEMBLER[' . __METHOD__ . ':' . __LINE__ . ': packageContent=' . print_r($packageContent, TRUE) . ',chunks='.print_r($chunks, TRUE));
184 * Checks whether the assembler's pending data is empty which means it has
185 * no pending data left for handling ... ;-)
187 * @return $ifPendingDataIsEmpty Whether pending data is empty
189 public function isPendingDataEmpty () {
191 $ifPendingDataIsEmpty = empty($this->pendingData);
194 return $ifPendingDataIsEmpty;
198 * Checks whether the assembler has multiple messages pending
200 * @return $isPending Whether the assembler has multiple messages pending
202 public function ifMultipleMessagesPending () {
204 $isPending = (!$this->getStackerInstance()->isStackEmpty(self::STACKER_NAME_MULTIPLE_MESSAGE);
211 * Handles multiple messages.
216 public function handleMultipleMessages () {
217 $this->partialStub('Unfinished method.');
221 * Handles the assembler's pending data
225 public function handlePendingData () {
227 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('PACKAGE-ASSEMBLER[' . __METHOD__ . ':' . __LINE__ . ': Going to decode ' . strlen($this->pendingData) . ' Bytes of pending data. pendingData=' . $this->pendingData);
229 // Assert on condition
230 assert(!$this->isPendingDataEmpty());
233 if (!$this->ifStartEndMarkersSet($this->pendingData)) {
234 // This will cause an assertition in next call, so simply wait for more data
235 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('PACKAGE-ASSEMBLER[' . __METHOD__ . ':' . __LINE__ . ': Pending data of ' . strlen($this->pendingData) . ' Bytes are incomplete, waiting for more ...');
237 } elseif (substr_count($this->pendingData, BaseRawDataHandler::STREAM_START_MARKER) > 1) {
239 * Multiple messages found, so split off first message as the input
240 * stream can only handle one message per time.
242 foreach (explode(BaseRawDataHandler::STREAM_START_MARKER, $this->pendingData) as $message) {
243 // Prepend start marker again as it is needed to decode the message.
244 $message = BaseRawDataHandler::STREAM_START_MARKER . $message;
247 $this->getStackerInstance()->pushNamed(self::STACKER_NAME_MULTIPLE_MESSAGE, $message);
250 // Clear pending data
251 $this->clearPendingData();
258 $packageContent = array(
259 BaseRawDataHandler::PACKAGE_RAW_DATA => $this->getInputStreamInstance()->streamData($this->pendingData),
260 BaseRawDataHandler::PACKAGE_ERROR_CODE => BaseRawDataHandler::SOCKET_ERROR_UNHANDLED
263 // Clear pending data
264 $this->clearPendingData();
267 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('PACKAGE-ASSEMBLER[' . __METHOD__ . ':' . __LINE__ . ': Last block of partial data received. A total of ' . strlen($packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA]) . ' bytes has been received.');
269 // Make sure last CHUNK_SEPARATOR is not there
270 if (substr($packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA], -1, 1) == PackageFragmenter::CHUNK_SEPARATOR) {
272 $packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA] = substr($packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA], 0, -1);
276 * "explode" the string from 'raw_data' with chunk separator to
277 * get an array of chunks. These chunks must then be verified by
278 * their checksums. Also the final chunk must be handled.
280 $chunks = explode(PackageFragmenter::CHUNK_SEPARATOR, $packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA]);
282 // Add all chunks because the last final chunk is found
283 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('PACKAGE-ASSEMBLER[' . __METHOD__ . ':' . __LINE__ . ': Going to add ' . count($chunks) . ' to chunk handler ...');
284 $this->getHandlerInstance()->addAllChunksWithFinal($chunks);
288 * Accepts the visitor to process the visit "request"
290 * @param $visitorInstance An instance of a Visitor class
293 public function accept (Visitor $visitorInstance) {
294 // Visit the assembler
295 $visitorInstance->visitAssembler($this);
299 * Clears pending data
303 public function clearPendingData () {
305 $this->pendingData = '';