<?php
/**
* RouterOS API client implementation.
*
* 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.
*
* PHP version 5
*
* @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
* @version 1.0.0b4
* @link http://pear2.php.net/PEAR2_Net_RouterOS
*/
/**
* The namespace declaration.
*/
namespace PEAR2\Net\RouterOS;
/**
* Refers to transmitter direction constants.
*/
use PEAR2\Net\Transmitter as T;
/**
* Locks are released upon any exception from anywhere.
*/
use Exception as E;
/**
* Represents a RouterOS response.
*
* @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
*/
class Response extends Message
{
/**
* The last response for a request.
*/
const TYPE_FINAL = '!done';
/**
* A response with data.
*/
const TYPE_DATA = '!re';
/**
* A response signifying error.
*/
const TYPE_ERROR = '!trap';
/**
* A response signifying a fatal error, due to which the connection would be
* terminated.
*/
const TYPE_FATAL = '!fatal';
/**
* @var array An array of unrecognized words in network order.
*/
protected $unrecognizedWords = array();
/**
* @var string The response type.
*/
private $_type;
/**
* Extracts a new response from a communicator.
*
* @param Communicator $com The communicator from which to extract
* the new response.
* @param bool $asStream Whether to populate the argument values
* with streams instead of strings.
* @param int $timeout_s If a response is not immediatly
* available, wait this many seconds. If NULL, wait indefinetly.
* @param int $timeout_us Microseconds to add to the waiting time.
* @param Registry $reg An optional registry to sync the
* response with.
*
* @see getType()
* @see getArgument()
*/
public function __construct(
Communicator $com,
$asStream = false,
$timeout_s = 0,
$timeout_us = null,
Registry $reg = null
) {
if (null === $reg) {
if ($com->getTransmitter()->isPersistent()) {
$old = $com->getTransmitter()
->lock(T\Stream::DIRECTION_RECEIVE);
try {
$this->_receive($com, $asStream, $timeout_s, $timeout_us);
} catch (E $e) {
$com->getTransmitter()->lock($old, true);
throw $e;
}
$com->getTransmitter()->lock($old, true);
} else {
$this->_receive($com, $asStream, $timeout_s, $timeout_us);
}
} else {
while (null === ($response = $reg->getNextResponse())) {
$newResponse = new self($com, true, $timeout_s, $timeout_us);
$tagInfo = $reg::parseTag($newResponse->getTag());
$newResponse->setTag($tagInfo[1]);
if (!$reg->add($newResponse, $tagInfo[0])) {
$response = $newResponse;
break;
}
}
$this->_type = $response->_type;
$this->arguments = $response->arguments;
$this->unrecognizedWords = $response->unrecognizedWords;
$this->setTag($response->getTag());
if (!$asStream) {
foreach ($this->arguments as $name => $value) {
$this->setArgument(
$name,
stream_get_contents($value)
);
}
foreach ($response->unrecognizedWords as $i => $value) {
$this->unrecognizedWords[$i] = stream_get_contents($value);
}
}
}
}
/**
* Extracts a new response from a communicator.
*
* This is the function that performs the actual receiving, while the
* constructor is also involved in locks and registry sync.
*
* @param Communicator $com The communicator from which to extract
* the new response.
* @param bool $asStream Whether to populate the argument values
* with streams instead of strings.
* @param int $timeout_s If a response is not immediatly
* available, wait this many seconds. If NULL, wait indefinetly.
* @param int $timeout_us Microseconds to add to the waiting time.
*
* @return void
*/
private function _receive(
Communicator $com,
$asStream = false,
$timeout_s = 0,
$timeout_us = null
) {
if (!$com->getTransmitter()->isDataAwaiting(
$timeout_s,
$timeout_us
)) {
throw new SocketException(
'No data within the time limit',
SocketException::CODE_NO_DATA
);
}
$this->setType($com->getNextWord());
if ($asStream) {
for (
$word = $com->getNextWordAsStream(), fseek($word, 0, SEEK_END);
ftell($word) !== 0;
$word = $com->getNextWordAsStream(), fseek(
$word,
0,
SEEK_END
)
) {
rewind($word);
$ind = fread($word, 1);
if ('=' === $ind || '.' === $ind) {
$prefix = stream_get_line($word, null, '=');
}
if ('=' === $ind) {
$value = fopen('php://temp', 'r+b');
$bytesCopied = ftell($word);
while (!feof($word)) {
$bytesCopied += stream_copy_to_stream(
$word,
$value,
0xFFFFF,
$bytesCopied
);
}
rewind($value);
$this->setArgument($prefix, $value);
continue;
}
if ('.' === $ind && 'tag' === $prefix) {
$this->setTag(stream_get_contents($word, -1, -1));
continue;
}
rewind($word);
$this->unrecognizedWords[] = $word;
}
} else {
for ($word = $com->getNextWord(); '' !== $word;
$word = $com->getNextWord()
) {
if (preg_match('/^=([^=]+)=(.*)$/sS', $word, $matches)) {
$this->setArgument($matches[1], $matches[2]);
} elseif (preg_match('/^\.tag=(.*)$/sS', $word, $matches)) {
$this->setTag($matches[1]);
} else {
$this->unrecognizedWords[] = $word;
}
}
}
}
/**
* Sets the response type.
*
* Sets the response type. Valid values are the TYPE_* constants.
*
* @param string $type The new response type.
*
* @return self|Response The response object.
* @see getType()
*/
protected function setType($type)
{
switch ($type) {
case self::TYPE_FINAL:
case self::TYPE_DATA:
case self::TYPE_ERROR:
case self::TYPE_FATAL:
$this->_type = $type;
return $this;
default:
throw new UnexpectedValueException(
'Unrecognized response type.',
UnexpectedValueException::CODE_RESPONSE_TYPE_UNKNOWN,
null,
$type
);
}
}
/**
* Gets the response type.
*
* @return string The response type.
* @see setType()
*/
public function getType()
{
return $this->_type;
}
/**
* Gets a list of unrecognized words.
*
* @return array The list of unrecognized words.
*/
public function getUnrecognizedWords()
{
return $this->unrecognizedWords;
}
}