From fb17cbd9e3f841ea478f1f88f4efa0422a7ebd69 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Roland=20H=C3=A4der?= Date: Sun, 17 Apr 2011 22:08:24 +0000 Subject: [PATCH] Continued with package fragmenter (a lot new, but unfinished code) --- .gitattributes | 5 + application/hub/config.php | 6 + .../package/fragmenter/class_Fragmentable.php | 12 +- .../class_PeerStateLookupDatabaseWrapper.php | 2 +- .../connection/class_BaseConnectionHelper.php | 45 +- .../hub/main/package/class_NetworkPackage.php | 5 + .../fragmenter/class_PackageFragmenter.php | 395 +++++++++++++++++- application/hub/main/streams/.htaccess | 1 + .../hub/main/streams/package/.htaccess | 1 + .../hub/main/streams/package/input/.htaccess | 1 + .../hub/main/streams/package/output/.htaccess | 1 + .../output/class_PackageOutputStream.php | 65 +++ 12 files changed, 513 insertions(+), 26 deletions(-) create mode 100644 application/hub/main/streams/.htaccess create mode 100644 application/hub/main/streams/package/.htaccess create mode 100644 application/hub/main/streams/package/input/.htaccess create mode 100644 application/hub/main/streams/package/output/.htaccess create mode 100644 application/hub/main/streams/package/output/class_PackageOutputStream.php diff --git a/.gitattributes b/.gitattributes index c046a648d..7327360bc 100644 --- a/.gitattributes +++ b/.gitattributes @@ -459,6 +459,11 @@ application/hub/main/states/peer/class_ -text svneol=unset#text/plain application/hub/main/states/peer/class_BasePeerState.php -text svneol=unset#text/plain application/hub/main/states/peer/new/.htaccess -text svneol=unset#text/plain application/hub/main/states/peer/new/class_NewConnectionPeerState.php svneol=native#text/plain +application/hub/main/streams/.htaccess svneol=native#text/plain +application/hub/main/streams/package/.htaccess svneol=native#text/plain +application/hub/main/streams/package/input/.htaccess svneol=native#text/plain +application/hub/main/streams/package/output/.htaccess svneol=native#text/plain +application/hub/main/streams/package/output/class_PackageOutputStream.php svneol=native#text/plain application/hub/main/tags/.htaccess -text svneol=unset#text/plain application/hub/main/tags/class_ -text svneol=unset#text/plain application/hub/main/tags/class_BaseTags.php svneol=native#text/plain diff --git a/application/hub/config.php b/application/hub/config.php index 450c79167..738f88b9e 100644 --- a/application/hub/config.php +++ b/application/hub/config.php @@ -552,6 +552,12 @@ $cfg->setConfigEntry('external_ip', ''); // CFG: PACKAGE-FRAGMENTER-CLASS $cfg->setConfigEntry('package_fragmenter_class', 'PackageFragmenter'); +// CFG: NODE-RAW-PACKAGE-OUTPUT-STREAM +$cfg->setConfigEntry('node_raw_package_output_stream', 'PackageOutputStream'); + +// CFG: PACKAGE-CHUNK-SIZE +$cfg->setConfigEntry('package_chunk_size', 8*512); + // CFG: CRUNCHER-TEST-UNITS-ENABLED $cfg->setConfigEntry('cruncher_test_units_enabled', 'Y'); diff --git a/application/hub/interfaces/package/fragmenter/class_Fragmentable.php b/application/hub/interfaces/package/fragmenter/class_Fragmentable.php index b511efd66..5a3ebe80d 100644 --- a/application/hub/interfaces/package/fragmenter/class_Fragmentable.php +++ b/application/hub/interfaces/package/fragmenter/class_Fragmentable.php @@ -25,22 +25,24 @@ interface Fragmentable extends FrameworkInterface { /** * This method does "implode" the given package data array into one long * string, splits it into small chunks, adds a serial number and checksum - * to all chunks and prepends a final hashsum chunk. + * to all chunks and prepends a final hashsum chunk. 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 void + * @return $finalHash Final hash for faster processing */ function fragmentPackageArray (array $packageData, BaseConnectionHelper $connectionInstance); /** * This method gets the next chunk from the internal FIFO which should be - * sent to the given recipient. + * sent to the given recipient. It will return an associative array where + * the key is the chunk hash and value the raw chunk data. * - * @param $packageData Raw package data array + * @param $finalHash Final hash for faster lookup * @return $rawDataChunk Raw package data chunk */ - function getNextRawDataChunk (array $packageData); + function getNextRawDataChunk ($finalHash); } // [EOF] diff --git a/application/hub/main/database/wrapper/states/class_PeerStateLookupDatabaseWrapper.php b/application/hub/main/database/wrapper/states/class_PeerStateLookupDatabaseWrapper.php index 86d452867..7450d228f 100644 --- a/application/hub/main/database/wrapper/states/class_PeerStateLookupDatabaseWrapper.php +++ b/application/hub/main/database/wrapper/states/class_PeerStateLookupDatabaseWrapper.php @@ -81,7 +81,7 @@ class PeerStateLookupDatabaseWrapper extends BaseDatabaseWrapper { // Is the package valid? if (!isset($packageData[NetworkPackage::INDEX_PACKAGE_SENDER])) { // Invalid package found, please report this - die('packageData='.print_r($packageData, true)); + die(__METHOD__ . ': packageData=' . print_r($packageData, true)); } // END - if // Remove session id > IP:port diff --git a/application/hub/main/helper/connection/class_BaseConnectionHelper.php b/application/hub/main/helper/connection/class_BaseConnectionHelper.php index 0f2ffcc01..4c68a0251 100644 --- a/application/hub/main/helper/connection/class_BaseConnectionHelper.php +++ b/application/hub/main/helper/connection/class_BaseConnectionHelper.php @@ -57,6 +57,16 @@ class BaseConnectionHelper extends BaseHubHelper implements Registerable, Protoc */ private $shuttedDown = false; + /** + * Currently sent chunks + */ + private $sentChunks = array(); + + /** + * Current final hash + */ + private $currentFinalHash = ''; + /** * Protected constructor * @@ -143,31 +153,38 @@ class BaseConnectionHelper extends BaseHubHelper implements Registerable, Protoc * "Getter" for raw data from a package array. A fragmenter is used which * will returns us only so many raw data which fits into the back buffer. * The rest is being held in a back-buffer and waits there for the next - * cycle and while be then sent. This is done by a FIFO. + * cycle and while be then sent. * * This method does 4 simple steps: * 1) Aquire fragmenter object instance from the factory * 2) Handle over the package data array to the fragmenter - * 3) Request a chunk (which "pops" the chunk from the fragmenter's FIFO) - * 4) Finally return the chunk to the caller + * 3) Request a chunk + * 4) Finally return the chunk (array) to the caller * * @param $packageData Raw package data array - * @return $rawData Raw package data bytes + * @return $rawDataChunk An array with the raw data as value and chunk hash as key */ private function getRawDataFromPackageArray (array $packageData) { - // Get the fragmenter instance - $fragmenterInstance = ObjectFactory::createObjectByConfiguredName('package_fragmenter_class'); + // If there is no fragmenter? + if (!Registry::getRegistry()->instanceExists('package_fragmenter')) { + // Get the fragmenter instance + $fragmenterInstance = ObjectFactory::createObjectByConfiguredName('package_fragmenter_class'); + + // Add it to the registry + Registry::getRegistry()->addInstance('package_fragmenter', $fragmenterInstance); + } else { + // Get fragmenter from registry + $fragmenterInstance = Registry::getRegistry()->getInstance('package_fragmenter'); + } - // Implode the package data array and fragement the resulting string - $fragmenterInstance->fragmentPackageArray($packageData, $this); + // Implode the package data array and fragement the resulting string, returns the final hash + $this->currentFinalHash = $fragmenterInstance->fragmentPackageArray($packageData, $this); - // Get the next raw data chunk from the fragmenter's FIFO - $rawData = $fragmenterInstance->getNextRawDataChunk($packageData); - /* DEBUG: */ $this->debugOutput('rawData['.strlen($rawData).']='.$rawData); - /* DEBUG: */ die(); + // Get the next raw data chunk from the fragmenter + $rawDataChunk = $fragmenterInstance->getNextRawDataChunk($this->currentFinalHash); // Return it - return $rawData; + return $rawDataChunk; } /** @@ -179,7 +196,7 @@ class BaseConnectionHelper extends BaseHubHelper implements Registerable, Protoc */ public function sendRawPackageData (array $packageData) { // Convert the package data array to a raw data stream - $rawData = $this->getRawDataFromPackageArray($packageData); + $rawDataChunk = $this->getRawDataFromPackageArray($packageData); // Get socket resource $socketResource = $this->getSocketResource(); diff --git a/application/hub/main/package/class_NetworkPackage.php b/application/hub/main/package/class_NetworkPackage.php index ddc95fe87..64d3a8f57 100644 --- a/application/hub/main/package/class_NetworkPackage.php +++ b/application/hub/main/package/class_NetworkPackage.php @@ -105,6 +105,11 @@ class NetworkPackage extends BaseFrameworkSystem implements Deliverable, Registe */ const NETWORK_TARGET_SELF = 'self'; + /** + * TCP package size + */ + const TCP_PACKAGE_SIZE = 512; + /** * Protected constructor * diff --git a/application/hub/main/package/fragmenter/class_PackageFragmenter.php b/application/hub/main/package/fragmenter/class_PackageFragmenter.php index 6d69683c2..e7d2349ff 100644 --- a/application/hub/main/package/fragmenter/class_PackageFragmenter.php +++ b/application/hub/main/package/fragmenter/class_PackageFragmenter.php @@ -24,7 +24,61 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -class PackageFragmenter extends BaseFrameworkSystem implements Fragmentable { +class PackageFragmenter extends BaseFrameworkSystem implements Fragmentable, Registerable { + /** + * Cached chunk size in bits + */ + private $chunkSize = 0; + + /** + * Array for chunks + */ + private $chunks = array(); + + /** + * Array for chunk hashes + */ + private $chunkHashes = array(); + + /** + * Array for chunk pointers + */ + private $chunkPointers = array(); + + /** + * Array for processed packages + */ + private $processedPackages = array(); + + /** + * Serial number + */ + private $serialNumber = 0x00000000; + + /** + * Length of largest possible serial number + */ + private $maxSerialLength = 8; + + /** + * Maximum possible serial number + */ + private $maxSerialNumber = 0; + /** + * Seperator between chunk data, serial number and chunk hash + */ + const CHUNK_DATA_HASH_SEPERATOR = '@'; + + /** + * Seperator for all chunk hashes + */ + const CHUNK_HASH_SEPERATOR = ';'; + + /** + * Identifier for hash chunk + */ + const HASH_CHUNK_IDENTIFIER = 'HASH-CHUNK:'; + /** * Protected constructor * @@ -33,6 +87,9 @@ class PackageFragmenter extends BaseFrameworkSystem implements Fragmentable { protected function __construct () { // Call parent constructor parent::__construct(__CLASS__); + + // Init this fragmenter + $this->initFragmenter(); } /** @@ -44,30 +101,356 @@ class PackageFragmenter extends BaseFrameworkSystem implements Fragmentable { // 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); + // Return the prepared instance return $fragmenterInstance; } + /** + * Initializes this fragmenter + * + * @return void + */ + private function initFragmenter () { + // Load some configuration entries and "cache" them: + // - Chunk size in bits + $this->chunkSize = $this->getConfigInstance()->getConfigEntry('package_chunk_size'); + + // - Maximum serial number + $this->maxSerialNumber = $this->hex2dec(str_repeat('f', $this->maxSerialLength)); + } + + /** + * Initializes the pointer for given final hash + * + * @param $finalHash Final hash to initialize pointer for + * @return void + */ + private function initPointer ($finalHash) { + $this->chunkPointers[$finalHash] = 0; + } + + /** + * "Getter" for processedPackages array index + * + * @param $packageData Raw package data array + * @return $index Array index for processedPackages + */ + private function getProcessedPackagesIndex (array $packageData) { + return ( + $packageData['sender'] . NetworkPackage::PACKAGE_DATA_SEPERATOR . + $packageData['recipient'] . NetworkPackage::PACKAGE_DATA_SEPERATOR . + $packageData['content'] . NetworkPackage::PACKAGE_DATA_SEPERATOR + ); + } + + /** + * Checks wether the given package data is already processed by this fragmenter + * + * @param $packageData Raw package data array + * @return $isProcessed Wether the package has been fragmented + */ + private function isPackageProcessed (array $packageData) { + // Get array index + $index = $this->getProcessedPackagesIndex($packageData); + + // Is the array index there? + $isProcessed = ( + (isset($this->processedPackages[$index])) && + ($this->processedPackages[$index] === true) + ); + + // Return it + return $isProcessed; + } + + /** + * Marks the given package data as processed by this fragmenter + * + * @param $packageData Raw package data array + * @return void + */ + private function markPackageDataProcessed (array $packageData) { + // Remember it (until we may remove it) + $this->processedPackages[$this->getProcessedPackagesIndex($packageData)] = true; + } + + /** + * Getter for final hash from given package data + * + * @param $packageData Raw package data array + * @return $finalHash Final hash for package data + */ + private function getFinalHashFromPackageData (array $packageData) { + // Make sure it is there + assert(isset($this->processedPackages[$this->getProcessedPackagesIndex($packageData)])); + + // Return it + return $this->processedPackages[$this->getProcessedPackagesIndex($packageData)]; + } + + /** + * Get next chunk pointer for given final hash + * + * @param $finalHash Final hash to get current pointer for + */ + private function getCurrentChunkPointer ($finalHash) { + // Is the final hash valid? + assert(strlen($finalHash) > 0); + + // Is the pointer already initialized? + //* NOISY-DEBUG: */ $this->debugOutput('FRAGMENTER: finalHash=' . $finalHash); + assert(isset($this->chunkPointers[$finalHash])); + + // Return it + return $this->chunkPointers[$finalHash]; + } + + /** + * Advance the chunk pointer for given final hash + * + * @param $finalHash Final hash to advance the pointer for + */ + private function nextChunkPointer ($finalHash) { + // Is the pointer already initialized? + assert(isset($this->chunkPointers[$finalHash])); + + // Count one up + $this->chunkPointers[$finalHash]++; + } + + /** + * "Getter" for data chunk size of given hash. + * + * @param $hash Hash to substract it's length + * @return $dataChunkSize The chunk size + */ + private function getDataChunkSizeFromHash ($hash) { + // Calculate real (data) chunk size + $dataChunkSize = ( + // Real chunk size + ($this->chunkSize / 8) - + // Hash size + strlen($hash) - + // Length of sperators + (strlen(self::CHUNK_DATA_HASH_SEPERATOR) * 2) - + // Length of max serial number + $this->maxSerialLength + ); + + // This should be larger than zero bytes + assert($dataChunkSize > 0); + + // Return it + return $dataChunkSize; + } + + /** + * Generates a hash from raw data + * + * @param $rawData Raw data bytes to hash + * @return $hash Hash from the raw data + */ + private function generateHashFromRawData ($rawData) { + // Get the crypto instance and hash the data + $hash = $this->getCryptoInstance()->hashString($rawData); + + // Return it + return $hash; + } + + /** + * "Getter" for the next hexadecimal-encoded serial number + * + * @return $encodedSerialNumber The next hexadecimal-encoded serial number + */ + private function getNextHexSerialNumber () { + // Assert on maximum serial number length + assert($this->serialNumber <= $this->maxSerialNumber); + + // Encode the current serial number + $encodedSerialNumber = $this->dec2Hex($this->serialNumber, $this->maxSerialLength); + + // Count one up + $this->serialNumber++; + + // Return the encoded serial number + return $encodedSerialNumber; + } + + /** + * 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 + * TCP package (512 bytes). + * + * @param $encodedData Encoded data string + * @param $finalHash Final hash from the raw data + * @return void + */ + private function splitEncodedDataIntoChunks ($encodedData, $finalHash) { + // Make sure final hashes with at least 32 bytes can pass + assert(strlen($finalHash) >= 32); + + // Calculate real (data) chunk size + $dataChunkSize = $this->getDataChunkSizeFromHash($finalHash); + + // Now split it up + for ($idx = 0; $idx < strlen($encodedData); $idx += $dataChunkSize) { + // Get the next chunk + $chunk = substr($encodedData, $idx, $dataChunkSize); + + // Hash it and remember it in seperate array + $chunkHash = $this->getCryptoInstance()->hashString($chunk); + $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; + + // Make sure the chunk is not larger than a TCP package can hold + assert(strlen($chunk) <= NetworkPackage::TCP_PACKAGE_SIZE); + + // Add it to the array + $this->chunks[$finalHash][] = $chunk; + } // END - for + + // Debug output + $this->debugOutput('FRAGMENTER: Encoded data of ' . strlen($encodedData) . ' bytes has been fragmented into ' . count($this->chunks[$finalHash]) . ' chunk(s).'); + } + + /** + * 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 + * @return void + */ + private function prependHashChunk ($encodedData, $finalHash) { + // "Implode" the whole array of hashes into one string + $rawData = self::HASH_CHUNK_IDENTIFIER . implode(self::CHUNK_HASH_SEPERATOR, $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) { + // Get the next chunk + $chunk = substr($encodedData, $idx, $dataChunkSize); + + // Hash it and remember it in seperate array + $chunkHash = $this->getCryptoInstance()->hashString($chunk); + 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; + + // Make sure the chunk is not larger than a TCP package can hold + assert(strlen($chunk) <= NetworkPackage::TCP_PACKAGE_SIZE); + + // Add it to the array + array_unshift($this->chunks[$finalHash], $chunk); + } // END - for + } + /** * This method does "implode" the given package data array into one long * string, splits it into small chunks, adds a serial number and checksum - * to all chunks and prepends a final hashsum chunk. + * 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 void + * @return $finalHash Final hash for faster processing + * @todo $connectionInstance is unused */ public function fragmentPackageArray (array $packageData, BaseConnectionHelper $connectionInstance) { + // Is this package already fragmented? + if (!$this->isPackageProcessed($packageData)) { + // First we need to "implode" the array + $rawData = implode(NetworkPackage::PACKAGE_DATA_SEPERATOR, $packageData); + + // Generate the final hash from the raw data (not encoded!) + $finalHash = $this->generateHashFromRawData($rawData); + + // Remember it + $this->processedPackages[$this->getProcessedPackagesIndex($packageData)] = $finalHash; + + // 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); + + // Prepend a chunk with all hashes together + $this->prependHashChunk($encodedData, $finalHash); + + // Mark the package as fragmented + $this->markPackageDataProcessed($packageData); + } else { + // Get the final hash from the package data + $finalHash = $this->getFinalHashFromPackageData($packageData); + } + + // Return final hash + return $finalHash; } /** * This method gets the next chunk from the internal FIFO which should be - * sent to the given recipient. + * sent to the given recipient. It will return an associative array where + * the key is the chunk hash and value the raw chunk data. * - * @param $packageData Raw package data array + * @param $finalHash Final hash for faster lookup * @return $rawDataChunk Raw package data chunk */ - public function getNextRawDataChunk (array $packageData) { + public function getNextRawDataChunk ($finalHash) { + try { + // Get current chunk index + $current = $this->getCurrentChunkPointer($finalHash); + } catch (AssertionException $e) { + // This may happen when the final hash is true + if ($finalHash === true) { + // Set current to null + $current = null; + } else { + // Throw the exception + throw $e; + } + } + + // If there is no entry left, return an empty array + if ((!isset($this->chunkHashes[$finalHash][$current])) || (!isset($this->chunks[$finalHash][$current]))) { + // No more entries found + return array(); + } // END - if + + // Generate the array + $rawDataChunk = array( + $this->chunkHashes[$finalHash][$current] => $this->chunks[$finalHash][$current] + ); + + // Count one index up + $this->nextChunkPointer($finalHash); + + // Return the chunk array + return $rawDataChunk; } } diff --git a/application/hub/main/streams/.htaccess b/application/hub/main/streams/.htaccess new file mode 100644 index 000000000..3a4288278 --- /dev/null +++ b/application/hub/main/streams/.htaccess @@ -0,0 +1 @@ +Deny from all diff --git a/application/hub/main/streams/package/.htaccess b/application/hub/main/streams/package/.htaccess new file mode 100644 index 000000000..3a4288278 --- /dev/null +++ b/application/hub/main/streams/package/.htaccess @@ -0,0 +1 @@ +Deny from all diff --git a/application/hub/main/streams/package/input/.htaccess b/application/hub/main/streams/package/input/.htaccess new file mode 100644 index 000000000..3a4288278 --- /dev/null +++ b/application/hub/main/streams/package/input/.htaccess @@ -0,0 +1 @@ +Deny from all diff --git a/application/hub/main/streams/package/output/.htaccess b/application/hub/main/streams/package/output/.htaccess new file mode 100644 index 000000000..3a4288278 --- /dev/null +++ b/application/hub/main/streams/package/output/.htaccess @@ -0,0 +1 @@ +Deny from all diff --git a/application/hub/main/streams/package/output/class_PackageOutputStream.php b/application/hub/main/streams/package/output/class_PackageOutputStream.php new file mode 100644 index 000000000..af98e5eef --- /dev/null +++ b/application/hub/main/streams/package/output/class_PackageOutputStream.php @@ -0,0 +1,65 @@ + + * @version 0.0.0 + * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 Developer Team + * @license GNU GPL 3.0 or any newer version + * @link http://www.ship-simu.org + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +class PackageOutputStream extends BaseStream implements OutputStreamable { + /** + * Protected constructor + * + * @return void + */ + protected function __construct () { + // Call parent constructor + parent::__construct(__CLASS__); + } + + /** + * Creates an instance of this node class + * + * @return $streamInstance An instance of this node class + */ + public final static function createPackageOutputStream () { + // Get a new instance + $streamInstance = new PackageOutputStream(); + + // Return the instance + return $streamInstance; + } + + /** + * Streams the data and maybe does something to it + * + * @param $data The data (string mostly) to "stream" + * @return $data The data (string mostly) to "stream" + * @throws UnsupportedOperationException If this method is called + */ + public function streamData ($data) { + // Encode the data with BASE64 encoding + $data = base64_encode($data); + + // Return it + return $data; + } +} + +// [EOF] +?> -- 2.39.2