PEAR2_Net_RouterOS  1.0.0b1
A MikroTik RouterOS client
PEAR2/Net/RouterOS/Communicator.php
Go to the documentation of this file.
00001 <?php
00002 
00022 namespace PEAR2\Net\RouterOS;
00023 
00027 use PEAR2\Net\Transmitter as T;
00028 
00045 class Communicator
00046 {
00050     const CHARSET_ALL = -1;
00051     
00059     const CHARSET_REMOTE = 0;
00060     
00068     const CHARSET_LOCAL = 1;
00069     
00074     protected static $defaultCharsets = array(
00075         self::CHARSET_REMOTE => null,
00076         self::CHARSET_LOCAL  => null
00077     );
00078     
00083     protected $charsets = array();
00084 
00088     protected $trans;
00089 
00105     public function __construct($host, $port = 8728, $persist = false,
00106         $timeout = null, $key = '', $context = null
00107     ) {
00108         $this->trans = new T\SocketClientTransmitter(
00109             $host, $port, $persist, $timeout, $key, $context
00110         );
00111         $this->setCharset(
00112             self::getDefaultCharset(self::CHARSET_ALL), self::CHARSET_ALL
00113         );
00114     }
00115     
00126     public static function iconvStream($in_charset, $out_charset, $stream)
00127     {
00128         $bytes = 0;
00129         $result = fopen('php://temp', 'r+b');
00130         $iconvFilter = stream_filter_append(
00131             $result, 'convert.iconv.' . $in_charset . '.' . $out_charset,
00132             STREAM_FILTER_WRITE
00133         );
00134         
00135         flock($stream, LOCK_SH);
00136         while (!feof($stream)) {
00137             $bytes += stream_copy_to_stream($stream, $result, 0xFFFFF);
00138         }
00139         fseek($stream, -$bytes, SEEK_CUR);
00140         flock($stream, LOCK_UN);
00141         
00142         stream_filter_remove($iconvFilter);
00143         rewind($result);
00144         return $result;
00145     }
00146     
00163     public static function setDefaultCharset(
00164         $charset, $charsetType = self::CHARSET_ALL
00165     ) {
00166         if (array_key_exists($charsetType, self::$defaultCharsets)) {
00167              $oldCharset = self::$defaultCharsets[$charsetType];
00168              self::$defaultCharsets[$charsetType] = $charset;
00169              return $oldCharset;
00170         } else {
00171             $oldCharsets = self::$defaultCharsets;
00172             self::$defaultCharsets = is_array($charset) ? $charset : array_fill(
00173                 0, count(self::$defaultCharsets), $charset
00174             );
00175             return $oldCharsets;
00176         }
00177     }
00178     
00190     public static function getDefaultCharset($charsetType)
00191     {
00192         return array_key_exists($charsetType, self::$defaultCharsets)
00193             ? self::$defaultCharsets[$charsetType] : self::$defaultCharsets;
00194     }
00195     
00219     public function setCharset($charset, $charsetType = self::CHARSET_ALL)
00220     {
00221         if (array_key_exists($charsetType, $this->charsets)) {
00222              $oldCharset = $this->charsets[$charsetType];
00223              $this->charsets[$charsetType] = $charset;
00224              return $oldCharset;
00225         } else {
00226             $oldCharsets = $this->charsets;
00227             $this->charsets = is_array($charset) ? $charset : array_fill(
00228                 0, count($this->charsets), $charset
00229             );
00230             return $oldCharsets;
00231         }
00232     }
00233     
00246     public function getCharset($charsetType)
00247     {
00248         return array_key_exists($charsetType, $this->charsets)
00249             ? $this->charsets[$charsetType] : $this->charsets;
00250     }
00251 
00258     public function getTransmitter()
00259     {
00260         return $this->trans;
00261     }
00262 
00274     public function sendWord($word)
00275     {
00276         if (null !== ($remoteCharset = $this->getCharset(self::CHARSET_REMOTE))
00277             && null !== ($localCharset = $this->getCharset(self::CHARSET_LOCAL))
00278         ) {
00279             $word = iconv(
00280                 $localCharset,
00281                 $remoteCharset . '//IGNORE//TRANSLIT',
00282                 $word
00283             );
00284         }
00285         $length = strlen($word);
00286         static::verifyLengthSupport($length);
00287         return $this->trans->send(self::encodeLength($length) . $word);
00288     }
00289 
00304     public function sendWordFromStream($prefix, $stream)
00305     {
00306         if (null !== ($remoteCharset = $this->getCharset(self::CHARSET_REMOTE))
00307             && null !== ($localCharset = $this->getCharset(self::CHARSET_LOCAL))
00308         ) {
00309             $prefix = iconv(
00310                 $localCharset,
00311                 $remoteCharset . '//IGNORE//TRANSLIT',
00312                 $prefix
00313             );
00314             $stream = self::iconvStream(
00315                 $localCharset,
00316                 $remoteCharset . '//IGNORE//TRANSLIT',
00317                 $stream
00318             );
00319         }
00320         
00321         flock($stream, LOCK_SH);
00322         
00323         $streamPosition = (double) sprintf('%u', ftell($stream));
00324         fseek($stream, 0, SEEK_END);
00325         $streamLength = ((double) sprintf('%u', ftell($stream)))
00326             - $streamPosition;
00327         fseek($stream, $streamPosition, SEEK_SET);
00328         $totalLength = strlen($prefix) + $streamLength;
00329         static::verifyLengthSupport($totalLength);
00330 
00331         $bytes = $this->trans->send(self::encodeLength($totalLength) . $prefix);
00332         $bytes += $this->trans->sendStream($stream);
00333         
00334         flock($stream, LOCK_UN);
00335         return $bytes;
00336     }
00337 
00350     protected static function verifyLengthSupport($length)
00351     {
00352         if ($length > 0xFFFFFFF) {
00353             throw new LengthException(
00354                 'Words with length above 0xFFFFFFF are not supported.', 10,
00355                 null, $length
00356             );
00357         }
00358     }
00359 
00367     public static function encodeLength($length)
00368     {
00369         if ($length < 0) {
00370             throw new LengthException(
00371                 'Length must not be negative.', 11, null, $length
00372             );
00373         } elseif ($length < 0x80) {
00374             return chr($length);
00375         } elseif ($length < 0x4000) {
00376             return pack('n', $length |= 0x8000);
00377         } elseif ($length < 0x200000) {
00378             $length |= 0xC00000;
00379             return pack('n', $length >> 8) . chr($length & 0xFF);
00380         } elseif ($length < 0x10000000) {
00381             return pack('N', $length |= 0xE0000000);
00382         } elseif ($length <= 0xFFFFFFFF) {
00383             return chr(0xF0) . pack('N', $length);
00384         } elseif ($length <= 0x7FFFFFFFF) {
00385             $length = 'f' . base_convert($length, 10, 16);
00386             return chr(hexdec(substr($length, 0, 2))) .
00387                 pack('N', hexdec(substr($length, 2)));
00388         }
00389         throw new LengthException(
00390             'Length must not be above 0x7FFFFFFFF.', 12, null, $length
00391         );
00392     }
00393 
00403     public function getNextWord()
00404     {
00405         $word = $this->trans->receive(self::decodeLength($this->trans), 'word');
00406         if (null !== ($remoteCharset = $this->getCharset(self::CHARSET_REMOTE))
00407             && null !== ($localCharset = $this->getCharset(self::CHARSET_LOCAL))
00408         ) {
00409             $word = iconv(
00410                 $remoteCharset,
00411                 $localCharset . '//IGNORE//TRANSLIT',
00412                 $word
00413             );
00414         }
00415         return $word;
00416     }
00417 
00427     public function getNextWordAsStream()
00428     {
00429         $filters = array();
00430         if (null !== ($remoteCharset = $this->getCharset(self::CHARSET_REMOTE))
00431             && null !== ($localCharset = $this->getCharset(self::CHARSET_LOCAL))
00432         ) {
00433             $filters[
00434                 'convert.iconv.' .
00435                 $remoteCharset . '.' . $localCharset . '//IGNORE//TRANSLIT'
00436             ] = array();
00437         }
00438         $stream = $this->trans->receiveStream(
00439             self::decodeLength($this->trans), $filters, 'stream word'
00440         );
00441         return $stream;
00442     }
00443 
00455     public static function decodeLength(T\StreamTransmitter $trans)
00456     {
00457         $byte = ord($trans->receive(1, 'initial length byte'));
00458         if ($byte & 0x80) {
00459             if (($byte & 0xC0) === 0x80) {
00460                 return (($byte & 077) << 8 ) + ord($trans->receive(1));
00461             } elseif (($byte & 0xE0) === 0xC0) {
00462                 $u = unpack('n~', $trans->receive(2));
00463                 return (($byte & 037) << 16 ) + $u['~'];
00464             } elseif (($byte & 0xF0) === 0xE0) {
00465                 $u = unpack('n~/C~~', $trans->receive(3));
00466                 return (($byte & 017) << 24 ) + ($u['~'] << 8) + $u['~~'];
00467             } elseif (($byte & 0xF8) === 0xF0) {
00468                 $u = unpack('N~', $trans->receive(4));
00469                 return (($byte & 007) * 0x100000000/* '<< 32' or '2^32' */)
00470                     + (double) sprintf('%u', $u['~']);
00471             }
00472             throw new NotSupportedException(
00473                 'Unknown control byte encountered.', 13, null, $byte
00474             );
00475         } else {
00476             return $byte;
00477         }
00478     }
00479 
00485     public function close()
00486     {
00487         return $this->trans->close();
00488     }
00489 
00490 }