704 lines
23 KiB
PHP
Raw Normal View History

2017-03-11 02:51:06 +07:00
<?php
/**
* RouterOS API client implementation.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* RouterOS is the flag product of the company MikroTik and is a powerful router software. One of its many abilities is to allow control over it via an API. This package provides a client for that API, in turn allowing you to use PHP to control RouterOS hosts.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* PHP version 5
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @category Net
* @package PEAR2_Net_RouterOS
* @author Vasil Rangelov <boen.robot@gmail.com>
* @copyright 2011 Vasil Rangelov
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
2023-10-05 16:55:44 +07:00
* @version 1.0.0b6
2017-03-11 02:51:06 +07:00
* @link http://pear2.php.net/PEAR2_Net_RouterOS
*/
/**
* The namespace declaration.
*/
namespace PEAR2\Net\RouterOS;
/**
* Using transmitters.
*/
use PEAR2\Net\Transmitter as T;
/**
* A RouterOS communicator.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* Implementation of the RouterOS API protocol. Unlike the other classes in this
2023-10-05 16:55:44 +07:00
* package, this class doesn't provide any conveniences beyond the low level
2017-03-11 02:51:06 +07:00
* implementation details (automatic word length encoding/decoding, charset
* translation and data integrity), and because of that, its direct usage is
* strongly discouraged.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @category Net
* @package PEAR2_Net_RouterOS
* @author Vasil Rangelov <boen.robot@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear2.php.net/PEAR2_Net_RouterOS
* @see Client
*/
class Communicator
{
/**
* Used when getting/setting all (default) charsets.
*/
const CHARSET_ALL = -1;
2023-10-05 16:55:44 +07:00
2017-03-11 02:51:06 +07:00
/**
* Used when getting/setting the (default) remote charset.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* The remote charset is the charset in which RouterOS stores its data.
* If you want to keep compatibility with your Winbox, this charset should
* match the default charset from your Windows' regional settings.
*/
const CHARSET_REMOTE = 0;
2023-10-05 16:55:44 +07:00
2017-03-11 02:51:06 +07:00
/**
* Used when getting/setting the (default) local charset.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* The local charset is the charset in which the data from RouterOS will be
* returned as. This charset should match the charset of the place the data
* will eventually be written to.
*/
const CHARSET_LOCAL = 1;
2023-10-05 16:55:44 +07:00
2017-03-11 02:51:06 +07:00
/**
2023-10-05 16:55:44 +07:00
* An array with the default charset.
*
* Charset types as keys, and the default charsets as values.
*
* @var array<string,string|null>
2017-03-11 02:51:06 +07:00
*/
protected static $defaultCharsets = array(
self::CHARSET_REMOTE => null,
self::CHARSET_LOCAL => null
);
2023-10-05 16:55:44 +07:00
2017-03-11 02:51:06 +07:00
/**
2023-10-05 16:55:44 +07:00
* An array with the current charset.
*
* Charset types as keys, and the current charsets as values.
*
* @var array<string,string|null>
2017-03-11 02:51:06 +07:00
*/
protected $charsets = array();
/**
2023-10-05 16:55:44 +07:00
* The transmitter for the connection.
*
* @var T\TcpClient
2017-03-11 02:51:06 +07:00
*/
protected $trans;
/**
* Creates a new connection with the specified options.
2023-10-05 16:55:44 +07:00
*
* @param string $host Hostname (IP or domain) of RouterOS.
* @param int|null $port The port on which the RouterOS host
* provides the API service. You can also specify NULL, in which case
* the port will automatically be chosen between 8728 and 8729,
* depending on the value of $crypto.
* @param bool $persist Whether or not the connection should be a
2017-03-11 02:51:06 +07:00
* persistent one.
2023-10-05 16:55:44 +07:00
* @param double|null $timeout The timeout for the connection.
* @param string $key A string that uniquely identifies the
2017-03-11 02:51:06 +07:00
* connection.
2023-10-05 16:55:44 +07:00
* @param string $crypto The encryption for this connection.
* Must be one of the PEAR2\Net\Transmitter\NetworkStream::CRYPTO_*
* constants. Off by default. RouterOS currently supports only TLS, but
* the setting is provided in this fashion for forward compatibility's
* sake. And for the sake of simplicity, if you specify an encryption,
* don't specify a context and your default context uses the value
* "DEFAULT" for ciphers, "ADH" will be automatically added to the list
* of ciphers.
* @param resource|null $context A context for the socket.
*
2017-03-11 02:51:06 +07:00
* @see sendWord()
*/
public function __construct(
$host,
$port = 8728,
$persist = false,
$timeout = null,
$key = '',
$crypto = T\NetworkStream::CRYPTO_OFF,
$context = null
) {
$isUnencrypted = T\NetworkStream::CRYPTO_OFF === $crypto;
if (($context === null) && !$isUnencrypted) {
$context = stream_context_get_default();
$opts = stream_context_get_options($context);
if (!isset($opts['ssl']['ciphers'])
|| 'DEFAULT' === $opts['ssl']['ciphers']
) {
2023-10-05 16:55:44 +07:00
stream_context_set_option(
$context,
array(
'ssl' => array(
'ciphers' => 'ADH',
'verify_peer' => false,
'verify_peer_name' => false
)
)
);
2017-03-11 02:51:06 +07:00
}
}
// @codeCoverageIgnoreStart
// The $port is customizable in testing.
if (null === $port) {
$port = $isUnencrypted ? 8728 : 8729;
}
// @codeCoverageIgnoreEnd
try {
$this->trans = new T\TcpClient(
$host,
$port,
$persist,
$timeout,
$key,
$crypto,
$context
);
} catch (T\Exception $e) {
throw new SocketException(
'Error connecting to RouterOS',
SocketException::CODE_CONNECTION_FAIL,
$e
);
}
$this->setCharset(
self::getDefaultCharset(self::CHARSET_ALL),
self::CHARSET_ALL
);
}
2023-10-05 16:55:44 +07:00
2017-03-11 02:51:06 +07:00
/**
* A shorthand gateway.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* This is a magic PHP method that allows you to call the object as a
* function. Depending on the argument given, one of the other functions in
* the class is invoked and its returned value is returned by this function.
2023-10-05 16:55:44 +07:00
*
* @param string|null $string A string of the word to send, or NULL to get
* the next word as a string.
*
2017-03-11 02:51:06 +07:00
* @return int|string If a string is provided, returns the number of bytes
2023-10-05 16:55:44 +07:00
* sent, otherwise returns the next word as a string.
2017-03-11 02:51:06 +07:00
*/
public function __invoke($string = null)
{
return null === $string ? $this->getNextWord()
: $this->sendWord($string);
}
2023-10-05 16:55:44 +07:00
2017-03-11 02:51:06 +07:00
/**
* Checks whether a variable is a seekable stream resource.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @param mixed $var The value to check.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @return bool TRUE if $var is a seekable stream, FALSE otherwise.
*/
public static function isSeekableStream($var)
{
if (T\Stream::isStream($var)) {
$meta = stream_get_meta_data($var);
return $meta['seekable'];
}
return false;
}
2023-10-05 16:55:44 +07:00
2017-03-11 02:51:06 +07:00
/**
* Uses iconv to convert a stream from one charset to another.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @param string $inCharset The charset of the stream.
* @param string $outCharset The desired resulting charset.
* @param resource $stream The stream to convert. The stream is assumed
* to be seekable, and is read from its current position to its end,
* after which, it is seeked back to its starting position.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @return resource A new stream that uses the $out_charset. The stream is a
* subset from the original stream, from its current position to its
* end, seeked at its start.
*/
public static function iconvStream($inCharset, $outCharset, $stream)
{
$bytes = 0;
$result = fopen('php://temp', 'r+b');
$iconvFilter = stream_filter_append(
$result,
'convert.iconv.' . $inCharset . '.' . $outCharset,
STREAM_FILTER_WRITE
);
2023-10-05 16:55:44 +07:00
2017-03-11 02:51:06 +07:00
flock($stream, LOCK_SH);
2023-10-05 16:55:44 +07:00
$reader = new T\Stream($stream, false);
$writer = new T\Stream($result, false);
$chunkSize = $reader->getChunk(T\Stream::DIRECTION_RECEIVE);
while ($reader->isAvailable() && $reader->isDataAwaiting()) {
$bytes += $writer->send(fread($stream, $chunkSize));
2017-03-11 02:51:06 +07:00
}
fseek($stream, -$bytes, SEEK_CUR);
flock($stream, LOCK_UN);
2023-10-05 16:55:44 +07:00
2017-03-11 02:51:06 +07:00
stream_filter_remove($iconvFilter);
rewind($result);
return $result;
}
2023-10-05 16:55:44 +07:00
2017-03-11 02:51:06 +07:00
/**
* Sets the default charset(s) for new connections.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @param mixed $charset The charset to set. If $charsetType is
* {@link self::CHARSET_ALL}, you can supply either a string to use for
* all charsets, or an array with the charset types as keys, and the
* charsets as values.
* @param int $charsetType Which charset to set. Valid values are the
* CHARSET_* constants. Any other value is treated as
* {@link self::CHARSET_ALL}.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @return string|array The old charset. If $charsetType is
* {@link self::CHARSET_ALL}, the old values will be returned as an
* array with the types as keys, and charsets as values.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @see setCharset()
*/
public static function setDefaultCharset(
$charset,
$charsetType = self::CHARSET_ALL
) {
if (array_key_exists($charsetType, self::$defaultCharsets)) {
$oldCharset = self::$defaultCharsets[$charsetType];
self::$defaultCharsets[$charsetType] = $charset;
return $oldCharset;
} else {
$oldCharsets = self::$defaultCharsets;
self::$defaultCharsets = is_array($charset) ? $charset : array_fill(
0,
count(self::$defaultCharsets),
$charset
);
return $oldCharsets;
}
}
2023-10-05 16:55:44 +07:00
2017-03-11 02:51:06 +07:00
/**
* Gets the default charset(s).
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @param int $charsetType Which charset to get. Valid values are the
* CHARSET_* constants. Any other value is treated as
* {@link self::CHARSET_ALL}.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @return string|array The current charset. If $charsetType is
* {@link self::CHARSET_ALL}, the current values will be returned as an
* array with the types as keys, and charsets as values.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @see setDefaultCharset()
*/
public static function getDefaultCharset($charsetType)
{
return array_key_exists($charsetType, self::$defaultCharsets)
? self::$defaultCharsets[$charsetType] : self::$defaultCharsets;
}
/**
* Gets the length of a seekable stream.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* Gets the length of a seekable stream.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @param resource $stream The stream to check. The stream is assumed to be
* seekable.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @return double The number of bytes in the stream between its current
* position and its end.
*/
public static function seekableStreamLength($stream)
{
$streamPosition = (double) sprintf('%u', ftell($stream));
fseek($stream, 0, SEEK_END);
$streamLength = ((double) sprintf('%u', ftell($stream)))
- $streamPosition;
fseek($stream, $streamPosition, SEEK_SET);
return $streamLength;
}
2023-10-05 16:55:44 +07:00
2017-03-11 02:51:06 +07:00
/**
* Sets the charset(s) for this connection.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* Sets the charset(s) for this connection. The specified charset(s) will be
* used for all future words. When sending, {@link self::CHARSET_LOCAL} is
* converted to {@link self::CHARSET_REMOTE}, and when receiving,
* {@link self::CHARSET_REMOTE} is converted to {@link self::CHARSET_LOCAL}.
2023-10-05 16:55:44 +07:00
* Setting NULL to either charset will disable charset conversion, and data
2017-03-11 02:51:06 +07:00
* will be both sent and received "as is".
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @param mixed $charset The charset to set. If $charsetType is
* {@link self::CHARSET_ALL}, you can supply either a string to use for
* all charsets, or an array with the charset types as keys, and the
* charsets as values.
* @param int $charsetType Which charset to set. Valid values are the
* CHARSET_* constants. Any other value is treated as
* {@link self::CHARSET_ALL}.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @return string|array The old charset. If $charsetType is
* {@link self::CHARSET_ALL}, the old values will be returned as an
* array with the types as keys, and charsets as values.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @see setDefaultCharset()
*/
public function setCharset($charset, $charsetType = self::CHARSET_ALL)
{
if (array_key_exists($charsetType, $this->charsets)) {
$oldCharset = $this->charsets[$charsetType];
$this->charsets[$charsetType] = $charset;
return $oldCharset;
} else {
$oldCharsets = $this->charsets;
$this->charsets = is_array($charset) ? $charset : array_fill(
0,
count($this->charsets),
$charset
);
return $oldCharsets;
}
}
2023-10-05 16:55:44 +07:00
2017-03-11 02:51:06 +07:00
/**
* Gets the charset(s) for this connection.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @param int $charsetType Which charset to get. Valid values are the
* CHARSET_* constants. Any other value is treated as
* {@link self::CHARSET_ALL}.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @return string|array The current charset. If $charsetType is
* {@link self::CHARSET_ALL}, the current values will be returned as an
* array with the types as keys, and charsets as values.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @see getDefaultCharset()
* @see setCharset()
*/
public function getCharset($charsetType)
{
return array_key_exists($charsetType, $this->charsets)
? $this->charsets[$charsetType] : $this->charsets;
}
/**
* Gets the transmitter for this connection.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @return T\TcpClient The transmitter for this connection.
*/
public function getTransmitter()
{
return $this->trans;
}
/**
* Sends a word.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* Sends a word and automatically encodes its length when doing so.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @param string $word The word to send.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @return int The number of bytes sent.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @see sendWordFromStream()
* @see getNextWord()
*/
public function sendWord($word)
{
if (null !== ($remoteCharset = $this->getCharset(self::CHARSET_REMOTE))
&& null !== ($localCharset = $this->getCharset(self::CHARSET_LOCAL))
) {
$word = iconv(
$localCharset,
$remoteCharset . '//IGNORE//TRANSLIT',
$word
);
}
$length = strlen($word);
static::verifyLengthSupport($length);
if ($this->trans->isPersistent()) {
$old = $this->trans->lock(T\Stream::DIRECTION_SEND);
$bytes = $this->trans->send(self::encodeLength($length) . $word);
$this->trans->lock($old, true);
return $bytes;
}
return $this->trans->send(self::encodeLength($length) . $word);
}
/**
* Sends a word based on a stream.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* Sends a word based on a stream and automatically encodes its length when
* doing so. The stream is read from its current position to its end, and
* then returned to its current position. Because of those operations, the
* supplied stream must be seekable.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @param string $prefix A string to prepend before the stream contents.
* @param resource $stream The seekable stream to send.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @return int The number of bytes sent.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @see sendWord()
*/
public function sendWordFromStream($prefix, $stream)
{
if (!self::isSeekableStream($stream)) {
throw new InvalidArgumentException(
'The stream must be seekable.',
InvalidArgumentException::CODE_SEEKABLE_REQUIRED
);
}
if (null !== ($remoteCharset = $this->getCharset(self::CHARSET_REMOTE))
&& null !== ($localCharset = $this->getCharset(self::CHARSET_LOCAL))
) {
$prefix = iconv(
$localCharset,
$remoteCharset . '//IGNORE//TRANSLIT',
$prefix
);
$stream = self::iconvStream(
$localCharset,
$remoteCharset . '//IGNORE//TRANSLIT',
$stream
);
}
2023-10-05 16:55:44 +07:00
2017-03-11 02:51:06 +07:00
flock($stream, LOCK_SH);
$totalLength = strlen($prefix) + self::seekableStreamLength($stream);
static::verifyLengthSupport($totalLength);
$bytes = $this->trans->send(self::encodeLength($totalLength) . $prefix);
$bytes += $this->trans->send($stream);
2023-10-05 16:55:44 +07:00
2017-03-11 02:51:06 +07:00
flock($stream, LOCK_UN);
return $bytes;
}
/**
* Verifies that the length is supported.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* Verifies if the specified length is supported by the API. Throws a
* {@link LengthException} if that's not the case. Currently, RouterOS
* supports words up to 0xFFFFFFFF in length, so that's the only check
* performed.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @param int $length The length to verify.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @return void
*/
2023-10-05 16:55:44 +07:00
public static function verifyLengthSupport($length)
2017-03-11 02:51:06 +07:00
{
if ($length > 0xFFFFFFFF) {
throw new LengthException(
'Words with length above 0xFFFFFFFF are not supported.',
LengthException::CODE_UNSUPPORTED,
null,
$length
);
}
}
/**
2023-10-05 16:55:44 +07:00
* Encodes the length as required by the RouterOS API.
*
2017-03-11 02:51:06 +07:00
* @param int $length The length to encode.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @return string The encoded length.
*/
public static function encodeLength($length)
{
if ($length < 0) {
throw new LengthException(
'Length must not be negative.',
LengthException::CODE_INVALID,
null,
$length
);
} elseif ($length < 0x80) {
return chr($length);
} elseif ($length < 0x4000) {
return pack('n', $length |= 0x8000);
} elseif ($length < 0x200000) {
$length |= 0xC00000;
return pack('n', $length >> 8) . chr($length & 0xFF);
} elseif ($length < 0x10000000) {
return pack('N', $length |= 0xE0000000);
} elseif ($length <= 0xFFFFFFFF) {
return chr(0xF0) . pack('N', $length);
} elseif ($length <= 0x7FFFFFFFF) {
$length = 'f' . base_convert($length, 10, 16);
return chr(hexdec(substr($length, 0, 2))) .
pack('N', hexdec(substr($length, 2)));
}
throw new LengthException(
'Length must not be above 0x7FFFFFFFF.',
LengthException::CODE_BEYOND_SHEME,
null,
$length
);
}
/**
* Get the next word in queue as a string.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* Get the next word in queue as a string, after automatically decoding its
* length.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @return string The word.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @see close()
*/
public function getNextWord()
{
if ($this->trans->isPersistent()) {
$old = $this->trans->lock(T\Stream::DIRECTION_RECEIVE);
$word = $this->trans->receive(
self::decodeLength($this->trans),
'word'
);
$this->trans->lock($old, true);
} else {
$word = $this->trans->receive(
self::decodeLength($this->trans),
'word'
);
}
2023-10-05 16:55:44 +07:00
2017-03-11 02:51:06 +07:00
if (null !== ($remoteCharset = $this->getCharset(self::CHARSET_REMOTE))
&& null !== ($localCharset = $this->getCharset(self::CHARSET_LOCAL))
) {
$word = iconv(
$remoteCharset,
$localCharset . '//IGNORE//TRANSLIT',
$word
);
}
2023-10-05 16:55:44 +07:00
2017-03-11 02:51:06 +07:00
return $word;
}
/**
* Get the next word in queue as a stream.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* Get the next word in queue as a stream, after automatically decoding its
* length.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @return resource The word, as a stream.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @see close()
*/
public function getNextWordAsStream()
{
$filters = new T\FilterCollection();
if (null !== ($remoteCharset = $this->getCharset(self::CHARSET_REMOTE))
&& null !== ($localCharset = $this->getCharset(self::CHARSET_LOCAL))
) {
$filters->append(
'convert.iconv.' .
$remoteCharset . '.' . $localCharset . '//IGNORE//TRANSLIT'
);
}
2023-10-05 16:55:44 +07:00
2017-03-11 02:51:06 +07:00
if ($this->trans->isPersistent()) {
$old = $this->trans->lock(T\Stream::DIRECTION_RECEIVE);
$stream = $this->trans->receiveStream(
self::decodeLength($this->trans),
$filters,
'stream word'
);
$this->trans->lock($old, true);
} else {
$stream = $this->trans->receiveStream(
self::decodeLength($this->trans),
$filters,
'stream word'
);
}
2023-10-05 16:55:44 +07:00
2017-03-11 02:51:06 +07:00
return $stream;
}
/**
2023-10-05 16:55:44 +07:00
* Decodes the length of the incoming message.
*
* Decodes the length of the incoming message, as specified by the RouterOS
2017-03-11 02:51:06 +07:00
* API.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @param T\Stream $trans The transmitter from which to decode the length of
* the incoming message.
2023-10-05 16:55:44 +07:00
*
* @return int|double The decoded length.
* Is of type "double" only for values above "2 << 31".
2017-03-11 02:51:06 +07:00
*/
public static function decodeLength(T\Stream $trans)
{
if ($trans->isPersistent() && $trans instanceof T\TcpClient) {
$old = $trans->lock($trans::DIRECTION_RECEIVE);
$length = self::_decodeLength($trans);
$trans->lock($old, true);
return $length;
}
return self::_decodeLength($trans);
}
/**
2023-10-05 16:55:44 +07:00
* Decodes the length of the incoming message.
*
* Decodes the length of the incoming message, as specified by the RouterOS
2017-03-11 02:51:06 +07:00
* API.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* Difference with the non private function is that this one doesn't perform
* locking if the connection is a persistent one.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @param T\Stream $trans The transmitter from which to decode the length of
* the incoming message.
2023-10-05 16:55:44 +07:00
*
* @return int|double The decoded length.
* Is of type "double" only for values above "2 << 31".
2017-03-11 02:51:06 +07:00
*/
private static function _decodeLength(T\Stream $trans)
{
$byte = ord($trans->receive(1, 'initial length byte'));
if ($byte & 0x80) {
if (($byte & 0xC0) === 0x80) {
return (($byte & 077) << 8 ) + ord($trans->receive(1));
} elseif (($byte & 0xE0) === 0xC0) {
$rem = unpack('n~', $trans->receive(2));
return (($byte & 037) << 16 ) + $rem['~'];
} elseif (($byte & 0xF0) === 0xE0) {
$rem = unpack('n~/C~~', $trans->receive(3));
return (($byte & 017) << 24 ) + ($rem['~'] << 8) + $rem['~~'];
} elseif (($byte & 0xF8) === 0xF0) {
$rem = unpack('N~', $trans->receive(4));
return (($byte & 007) * 0x100000000/* '<< 32' or '2^32' */)
+ (double) sprintf('%u', $rem['~']);
}
throw new NotSupportedException(
'Unknown control byte encountered.',
NotSupportedException::CODE_CONTROL_BYTE,
null,
$byte
);
} else {
return $byte;
}
}
/**
* Closes the opened connection, even if it is a persistent one.
2023-10-05 16:55:44 +07:00
*
2017-03-11 02:51:06 +07:00
* @return bool TRUE on success, FALSE on failure.
*/
public function close()
{
return $this->trans->close();
}
}