This commit is contained in:
2022-10-23 01:39:27 +02:00
parent 8c17aab483
commit 1929b84685
4130 changed files with 479334 additions and 0 deletions

21
vendor/binsoul/net-mqtt/LICENSE.md vendored Executable file
View File

@@ -0,0 +1,21 @@
# The MIT License (MIT)
Copyright (c) 2015 Sebastian Mößler <code@binsoul.de>
> Permission is hereby granted, free of charge, to any person obtaining a copy
> of this software and associated documentation files (the "Software"), to deal
> in the Software without restriction, including without limitation the rights
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> copies of the Software, and to permit persons to whom the Software is
> furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in
> all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
> THE SOFTWARE.

36
vendor/binsoul/net-mqtt/README.md vendored Executable file
View File

@@ -0,0 +1,36 @@
# net-mqtt
[![Latest Version on Packagist][ico-version]][link-packagist]
[![Software License][ico-license]](LICENSE.md)
[![Total Downloads][ico-downloads]][link-downloads]
MQTT is a machine-to-machine (M2M) / Internet of Things (IoT) connectivity protocol. It provides a lightweight method of carrying out messaging using a publish/subscribe model.
This package implements the MQTT protocol versions 3.1 and 3.1.1.
## Install
Via composer:
``` bash
$ composer require binsoul/net-mqtt
```
## Testing
``` bash
$ composer test
```
## License
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
[ico-version]: https://img.shields.io/packagist/v/binsoul/net-mqtt.svg?style=flat-square
[ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square
[ico-downloads]: https://img.shields.io/packagist/dt/binsoul/net-mqtt.svg?style=flat-square
[link-packagist]: https://packagist.org/packages/binsoul/net-mqtt
[link-downloads]: https://packagist.org/packages/binsoul/net-mqtt
[link-author]: https://github.com/binsoul

47
vendor/binsoul/net-mqtt/composer.json vendored Executable file
View File

@@ -0,0 +1,47 @@
{
"name": "binsoul/net-mqtt",
"description": "MQTT protocol implementation",
"keywords": [
"net",
"mqtt"
],
"homepage": "https://github.com/binsoul/net-mqtt",
"license": "MIT",
"authors": [
{
"name": "Sebastian Mößler",
"email": "code@binsoul.de",
"homepage": "https://github.com/binsoul",
"role": "Developer"
}
],
"require": {
"php": "~5.6|~7.0"
},
"require-dev": {
"phpunit/phpunit": "~4.0||~5.0",
"friendsofphp/php-cs-fixer": "~1.0"
},
"autoload": {
"psr-4": {
"BinSoul\\Net\\Mqtt\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"BinSoul\\Test\\Net\\Mqtt\\": "tests"
}
},
"scripts": {
"test": "phpunit",
"fix-style": [
"php-cs-fixer fix src",
"php-cs-fixer fix tests"
]
},
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
}
}

90
vendor/binsoul/net-mqtt/src/Connection.php vendored Executable file
View File

@@ -0,0 +1,90 @@
<?php
namespace BinSoul\Net\Mqtt;
/**
* Represents the connection of a MQTT client.
*/
interface Connection
{
/**
* @return int
*/
public function getProtocol();
/**
* @return string
*/
public function getClientID();
/**
* @return bool
*/
public function isCleanSession();
/**
* @return string
*/
public function getUsername();
/**
* @return string
*/
public function getPassword();
/**
* @return Message|null
*/
public function getWill();
/**
* @return int
*/
public function getKeepAlive();
/**
* Returns a new connection with the given protocol.
*
* @param int $protocol
*
* @return self
*/
public function withProtocol($protocol);
/**
* Returns a new connection with the given client id.
*
* @param string $clientID
*
* @return self
*/
public function withClientID($clientID);
/**
* Returns a new connection with the given credentials.
*
* @param string $username
* @param string $password
*
* @return self
*/
public function withCredentials($username, $password);
/**
* Returns a new connection with the given will.
*
* @param Message $will
*
* @return self
*/
public function withWill(Message $will);
/**
* Returns a new connection with the given keep alive timeout.
*
* @param int $timeout
*
* @return self
*/
public function withKeepAlive($timeout);
}

View File

@@ -0,0 +1,129 @@
<?php
namespace BinSoul\Net\Mqtt;
/**
* Provides a default implementation of the {@see Connection} interface.
*/
class DefaultConnection implements Connection
{
/** @var string */
private $username;
/** @var string */
private $password;
/** @var Message|null */
private $will;
/** @var string */
private $clientID;
/** @var int */
private $keepAlive;
/** @var int */
private $protocol;
/** @var bool */
private $clean;
/**
* Constructs an instance of this class.
*
* @param string $username
* @param string $password
* @param Message|null $will
* @param string $clientID
* @param int $keepAlive
* @param int $protocol
* @param bool $clean
*/
public function __construct(
$username = '',
$password = '',
Message $will = null,
$clientID = '',
$keepAlive = 60,
$protocol = 4,
$clean = true
) {
$this->username = $username;
$this->password = $password;
$this->will = $will;
$this->clientID = $clientID;
$this->keepAlive = $keepAlive;
$this->protocol = $protocol;
$this->clean = $clean;
}
public function getProtocol()
{
return $this->protocol;
}
public function getClientID()
{
return $this->clientID;
}
public function isCleanSession()
{
return $this->clean;
}
public function getUsername()
{
return $this->username;
}
public function getPassword()
{
return $this->password;
}
public function getWill()
{
return $this->will;
}
public function getKeepAlive()
{
return $this->keepAlive;
}
public function withProtocol($protocol)
{
$result = clone $this;
$result->protocol = $protocol;
return $result;
}
public function withClientID($clientID)
{
$result = clone $this;
$result->clientID = $clientID;
return $result;
}
public function withCredentials($username, $password)
{
$result = clone $this;
$result->username = $username;
$result->password = $password;
return $result;
}
public function withWill(Message $will = null)
{
$result = clone $this;
$result->will = $will;
return $result;
}
public function withKeepAlive($timeout)
{
$result = clone $this;
$result->keepAlive = $timeout;
return $result;
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace BinSoul\Net\Mqtt;
/**
* Provides a default implementation of the {@see IdentifierGenerator} interface.
*/
class DefaultIdentifierGenerator implements IdentifierGenerator
{
/** @var int */
private $currentIdentifier = 0;
public function generatePacketID()
{
++$this->currentIdentifier;
if ($this->currentIdentifier > 0xFFFF) {
$this->currentIdentifier = 1;
}
return $this->currentIdentifier;
}
public function generateClientID()
{
if (function_exists('random_bytes')) {
$data = random_bytes(9);
} elseif (function_exists('openssl_random_pseudo_bytes')) {
$data = openssl_random_pseudo_bytes(9);
} else {
$data = '';
for ($i = 1; $i <= 8; ++$i) {
$data = chr(mt_rand(0, 255)).$data;
}
}
return 'BNMCR'.bin2hex($data);
}
}

119
vendor/binsoul/net-mqtt/src/DefaultMessage.php vendored Executable file
View File

@@ -0,0 +1,119 @@
<?php
namespace BinSoul\Net\Mqtt;
/**
* Provides a default implementation of the {@see Message} interface.
*/
class DefaultMessage implements Message
{
/** @var string */
private $topic;
/** @var string */
private $payload;
/** @var bool */
private $isRetained;
/** @var bool */
private $isDuplicate = false;
/** @var int */
private $qosLevel;
/**
* Constructs an instance of this class.
*
* @param string $topic
* @param string $payload
* @param int $qosLevel
* @param bool $retain
* @param bool $isDuplicate
*/
public function __construct($topic, $payload = '', $qosLevel = 0, $retain = false, $isDuplicate = false)
{
$this->topic = $topic;
$this->payload = $payload;
$this->isRetained = $retain;
$this->qosLevel = $qosLevel;
$this->isDuplicate = $isDuplicate;
}
public function getTopic()
{
return $this->topic;
}
public function getPayload()
{
return $this->payload;
}
public function getQosLevel()
{
return $this->qosLevel;
}
public function isDuplicate()
{
return $this->isDuplicate;
}
public function isRetained()
{
return $this->isRetained;
}
public function withTopic($topic)
{
$result = clone $this;
$result->topic = $topic;
return $result;
}
public function withPayload($payload)
{
$result = clone $this;
$result->payload = $payload;
return $result;
}
public function withQosLevel($level)
{
$result = clone $this;
$result->qosLevel = $level;
return $result;
}
public function retain()
{
$result = clone $this;
$result->isRetained = true;
return $result;
}
public function release()
{
$result = clone $this;
$result->isRetained = false;
return $result;
}
public function duplicate()
{
$result = clone $this;
$result->isDuplicate = true;
return $result;
}
public function original()
{
$result = clone $this;
$result->isDuplicate = false;
return $result;
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace BinSoul\Net\Mqtt;
/**
* Provides a default implementation of the {@see Subscription} interface.
*/
class DefaultSubscription implements Subscription
{
/** @var string */
private $filter;
/** @var int */
private $qosLevel;
/**
* Constructs an instance of this class.
*
* @param string $filter
* @param int $qosLevel
*/
public function __construct($filter, $qosLevel = 0)
{
$this->filter = $filter;
$this->qosLevel = $qosLevel;
}
public function getFilter()
{
return $this->filter;
}
public function getQosLevel()
{
return $this->qosLevel;
}
public function withFilter($filter)
{
$result = clone $this;
$result->filter = $filter;
return $result;
}
public function withQosLevel($level)
{
$result = clone $this;
$result->qosLevel = $level;
return $result;
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace BinSoul\Net\Mqtt\Exception;
/**
* Will be thrown if the end of a stream is reached but more bytes were requested.
*/
class EndOfStreamException extends \Exception
{
}

View File

@@ -0,0 +1,10 @@
<?php
namespace BinSoul\Net\Mqtt\Exception;
/**
* Will be thrown if a packet is malformed.
*/
class MalformedPacketException extends \Exception
{
}

View File

@@ -0,0 +1,10 @@
<?php
namespace BinSoul\Net\Mqtt\Exception;
/**
* Will be thrown if a packet type is unknown.
*/
class UnknownPacketTypeException extends \Exception
{
}

71
vendor/binsoul/net-mqtt/src/Flow.php vendored Executable file
View File

@@ -0,0 +1,71 @@
<?php
namespace BinSoul\Net\Mqtt;
/**
* Represents a sequence of packages exchanged between clients and brokers.
*/
interface Flow
{
/**
* Returns the unique code.
*
* @return string
*/
public function getCode();
/**
* Starts the flow.
*
* @return Packet|null First packet of the flow
*
* @throws \Exception
*/
public function start();
/**
* Indicates if the flow can handle the given packet.
*
* @param Packet $packet Packet to accept
*
* @return bool
*/
public function accept(Packet $packet);
/**
* Continues the flow.
*
* @param Packet $packet Packet to respond
*
* @return Packet|null Next packet of the flow
*/
public function next(Packet $packet);
/**
* Indicates if the flow is finished.
*
* @return bool
*/
public function isFinished();
/**
* Indicates if the flow finished successfully.
*
* @return bool
*/
public function isSuccess();
/**
* Returns the result of the flow if it finished successfully.
*
* @return mixed
*/
public function getResult();
/**
* Returns an error message if the flow didn't finish successfully.
*
* @return string
*/
public function getErrorMessage();
}

View File

@@ -0,0 +1,74 @@
<?php
namespace BinSoul\Net\Mqtt\Flow;
use BinSoul\Net\Mqtt\Flow;
use BinSoul\Net\Mqtt\Packet;
/**
* Provides an abstract implementation of the {@see Flow} interface.
*/
abstract class AbstractFlow implements Flow
{
/** @var bool */
private $isFinished = false;
/** @var bool */
private $isSuccess = false;
/** @var mixed */
private $result;
/** @var string */
private $error = '';
public function accept(Packet $packet)
{
return false;
}
public function next(Packet $packet)
{
}
public function isFinished()
{
return $this->isFinished;
}
public function isSuccess()
{
return $this->isFinished && $this->isSuccess;
}
public function getResult()
{
return $this->result;
}
public function getErrorMessage()
{
return $this->error;
}
/**
* Marks the flow as successful and sets the result.
*
* @param mixed|null $result
*/
protected function succeed($result = null)
{
$this->isFinished = true;
$this->isSuccess = true;
$this->result = $result;
}
/**
* Marks the flow as failed and sets the error message.
*
* @param string $error
*/
protected function fail($error = '')
{
$this->isFinished = true;
$this->isSuccess = false;
$this->error = $error;
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace BinSoul\Net\Mqtt\Flow;
use BinSoul\Net\Mqtt\Packet\PingResponsePacket;
/**
* Represents a flow starting with an incoming PING packet.
*/
class IncomingPingFlow extends AbstractFlow
{
public function getCode()
{
return 'pong';
}
public function start()
{
$this->succeed();
return new PingResponsePacket();
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace BinSoul\Net\Mqtt\Flow;
use BinSoul\Net\Mqtt\Message;
use BinSoul\Net\Mqtt\Packet;
use BinSoul\Net\Mqtt\Packet\PublishAckPacket;
use BinSoul\Net\Mqtt\Packet\PublishCompletePacket;
use BinSoul\Net\Mqtt\Packet\PublishReceivedPacket;
use BinSoul\Net\Mqtt\Packet\PublishReleasePacket;
/**
* Represents a flow starting with an incoming PUBLISH packet.
*/
class IncomingPublishFlow extends AbstractFlow
{
/** @var int|null */
private $identifier;
/** @var Message */
private $message;
/**
* Constructs an instance of this class.
*
* @param Message $message
* @param int|null $identifier
*/
public function __construct(Message $message, $identifier = null)
{
$this->message = $message;
$this->identifier = $identifier;
}
public function getCode()
{
return 'message';
}
public function start()
{
$packet = null;
$emit = true;
if ($this->message->getQosLevel() === 1) {
$packet = new PublishAckPacket();
} elseif ($this->message->getQosLevel() === 2) {
$packet = new PublishReceivedPacket();
$emit = false;
}
if ($packet !== null) {
$packet->setIdentifier($this->identifier);
}
if ($emit) {
$this->succeed($this->message);
}
return $packet;
}
public function accept(Packet $packet)
{
if ($this->message->getQosLevel() !== 2 || $packet->getPacketType() !== Packet::TYPE_PUBREL) {
return false;
}
/* @var PublishReleasePacket $packet */
return $packet->getIdentifier() === $this->identifier;
}
public function next(Packet $packet)
{
$this->succeed($this->message);
$response = new PublishCompletePacket();
$response->setIdentifier($this->identifier);
return $response;
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace BinSoul\Net\Mqtt\Flow;
use BinSoul\Net\Mqtt\Connection;
use BinSoul\Net\Mqtt\IdentifierGenerator;
use BinSoul\Net\Mqtt\Packet;
use BinSoul\Net\Mqtt\Packet\ConnectRequestPacket;
use BinSoul\Net\Mqtt\Packet\ConnectResponsePacket;
/**
* Represents a flow starting with an outgoing CONNECT packet.
*/
class OutgoingConnectFlow extends AbstractFlow
{
/** @var Connection */
private $connection;
/**
* Constructs an instance of this class.
*
* @param Connection $connection
* @param IdentifierGenerator $generator
*/
public function __construct(Connection $connection, IdentifierGenerator $generator)
{
$this->connection = $connection;
if ($this->connection->getClientID() === '') {
$this->connection = $this->connection->withClientID($generator->generateClientID());
}
}
public function getCode()
{
return 'connect';
}
public function start()
{
$packet = new ConnectRequestPacket();
$packet->setProtocolLevel($this->connection->getProtocol());
$packet->setKeepAlive($this->connection->getKeepAlive());
$packet->setClientID($this->connection->getClientID());
$packet->setCleanSession($this->connection->isCleanSession());
$packet->setUsername($this->connection->getUsername());
$packet->setPassword($this->connection->getPassword());
$will = $this->connection->getWill();
if ($will !== null && $will->getTopic() !== '' && $will->getPayload() !== '') {
$packet->setWill($will->getTopic(), $will->getPayload(), $will->getQosLevel(), $will->isRetained());
}
return $packet;
}
public function accept(Packet $packet)
{
return $packet->getPacketType() === Packet::TYPE_CONNACK;
}
public function next(Packet $packet)
{
/** @var ConnectResponsePacket $packet */
if ($packet->isSuccess()) {
$this->succeed($this->connection);
} else {
$this->fail($packet->getErrorName());
}
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace BinSoul\Net\Mqtt\Flow;
use BinSoul\Net\Mqtt\Connection;
use BinSoul\Net\Mqtt\Packet\DisconnectRequestPacket;
/**
* Represents a flow starting with an outgoing DISCONNECT packet.
*/
class OutgoingDisconnectFlow extends AbstractFlow
{
/** @var Connection */
private $connection;
/**
* Constructs an instance of this class.
*
* @param Connection $connection
*/
public function __construct(Connection $connection)
{
$this->connection = $connection;
}
public function getCode()
{
return 'disconnect';
}
public function start()
{
$this->succeed($this->connection);
return new DisconnectRequestPacket();
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace BinSoul\Net\Mqtt\Flow;
use BinSoul\Net\Mqtt\Packet;
use BinSoul\Net\Mqtt\Packet\PingRequestPacket;
/**
* Represents a flow starting with an outgoing PING packet.
*/
class OutgoingPingFlow extends AbstractFlow
{
public function getCode()
{
return 'ping';
}
public function start()
{
return new PingRequestPacket();
}
public function accept(Packet $packet)
{
return $packet->getPacketType() === Packet::TYPE_PINGRESP;
}
public function next(Packet $packet)
{
$this->succeed();
}
}

View File

@@ -0,0 +1,102 @@
<?php
namespace BinSoul\Net\Mqtt\Flow;
use BinSoul\Net\Mqtt\IdentifierGenerator;
use BinSoul\Net\Mqtt\Message;
use BinSoul\Net\Mqtt\Packet;
use BinSoul\Net\Mqtt\Packet\PublishAckPacket;
use BinSoul\Net\Mqtt\Packet\PublishCompletePacket;
use BinSoul\Net\Mqtt\Packet\PublishReceivedPacket;
use BinSoul\Net\Mqtt\Packet\PublishReleasePacket;
use BinSoul\Net\Mqtt\Packet\PublishRequestPacket;
/**
* Represents a flow starting with an outgoing PUBLISH packet.
*/
class OutgoingPublishFlow extends AbstractFlow
{
/** @var int|null */
private $identifier;
/** @var Message */
private $message;
/** @var bool */
private $receivedPubRec = false;
/**
* Constructs an instance of this class.
*
* @param Message $message
* @param IdentifierGenerator $generator
*/
public function __construct(Message $message, IdentifierGenerator $generator)
{
$this->message = $message;
if ($this->message->getQosLevel() > 0) {
$this->identifier = $generator->generatePacketID();
}
}
public function getCode()
{
return 'publish';
}
public function start()
{
$packet = new PublishRequestPacket();
$packet->setTopic($this->message->getTopic());
$packet->setPayload($this->message->getPayload());
$packet->setRetained($this->message->isRetained());
$packet->setDuplicate($this->message->isDuplicate());
$packet->setQosLevel($this->message->getQosLevel());
if ($this->message->getQosLevel() === 0) {
$this->succeed($this->message);
} else {
$packet->setIdentifier($this->identifier);
}
return $packet;
}
public function accept(Packet $packet)
{
if ($this->message->getQosLevel() === 0) {
return false;
}
$packetType = $packet->getPacketType();
if ($packetType === Packet::TYPE_PUBACK && $this->message->getQosLevel() === 1) {
/* @var PublishAckPacket $packet */
return $packet->getIdentifier() === $this->identifier;
} elseif ($this->message->getQosLevel() === 2) {
if ($packetType === Packet::TYPE_PUBREC) {
/* @var PublishReceivedPacket $packet */
return $packet->getIdentifier() === $this->identifier;
} elseif ($this->receivedPubRec && $packetType === Packet::TYPE_PUBCOMP) {
/* @var PublishCompletePacket $packet */
return $packet->getIdentifier() === $this->identifier;
}
}
return false;
}
public function next(Packet $packet)
{
$packetType = $packet->getPacketType();
if ($packetType === Packet::TYPE_PUBACK || $packetType === Packet::TYPE_PUBCOMP) {
$this->succeed($this->message);
} elseif ($packetType === Packet::TYPE_PUBREC) {
$this->receivedPubRec = true;
$response = new PublishReleasePacket();
$response->setIdentifier($this->identifier);
return $response;
}
}
}

View File

@@ -0,0 +1,82 @@
<?php
namespace BinSoul\Net\Mqtt\Flow;
use BinSoul\Net\Mqtt\IdentifierGenerator;
use BinSoul\Net\Mqtt\Packet;
use BinSoul\Net\Mqtt\Packet\SubscribeRequestPacket;
use BinSoul\Net\Mqtt\Packet\SubscribeResponsePacket;
use BinSoul\Net\Mqtt\Subscription;
/**
* Represents a flow starting with an outgoing SUBSCRIBE packet.
*/
class OutgoingSubscribeFlow extends AbstractFlow
{
/** @var int */
private $identifier;
/** @var Subscription[] */
private $subscriptions;
/**
* Constructs an instance of this class.
*
* @param Subscription[] $subscriptions
* @param IdentifierGenerator $generator
*/
public function __construct(array $subscriptions, IdentifierGenerator $generator)
{
$this->subscriptions = array_values($subscriptions);
$this->identifier = $generator->generatePacketID();
}
public function getCode()
{
return 'subscribe';
}
public function start()
{
$packet = new SubscribeRequestPacket();
$packet->setTopic($this->subscriptions[0]->getFilter());
$packet->setQosLevel($this->subscriptions[0]->getQosLevel());
$packet->setIdentifier($this->identifier);
return $packet;
}
public function accept(Packet $packet)
{
if ($packet->getPacketType() !== Packet::TYPE_SUBACK) {
return false;
}
/* @var SubscribeResponsePacket $packet */
return $packet->getIdentifier() === $this->identifier;
}
public function next(Packet $packet)
{
/* @var SubscribeResponsePacket $packet */
$returnCodes = $packet->getReturnCodes();
if (count($returnCodes) !== count($this->subscriptions)) {
throw new \LogicException(
sprintf(
'SUBACK: Expected %d return codes but got %d.',
count($this->subscriptions),
count($returnCodes)
)
);
}
foreach ($returnCodes as $index => $code) {
if ($packet->isError($code)) {
$this->fail(sprintf('Failed to subscribe to "%s".', $this->subscriptions[$index]->getFilter()));
return;
}
}
$this->succeed($this->subscriptions[0]);
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace BinSoul\Net\Mqtt\Flow;
use BinSoul\Net\Mqtt\IdentifierGenerator;
use BinSoul\Net\Mqtt\Packet;
use BinSoul\Net\Mqtt\Packet\UnsubscribeRequestPacket;
use BinSoul\Net\Mqtt\Packet\UnsubscribeResponsePacket;
use BinSoul\Net\Mqtt\Subscription;
/**
* Represents a flow starting with an outgoing UNSUBSCRIBE packet.
*/
class OutgoingUnsubscribeFlow extends AbstractFlow
{
/** @var int */
private $identifier;
/** @var Subscription[] */
private $subscriptions;
/**
* Constructs an instance of this class.
*
* @param Subscription[] $subscriptions
* @param IdentifierGenerator $generator
*/
public function __construct(array $subscriptions, IdentifierGenerator $generator)
{
$this->subscriptions = array_values($subscriptions);
$this->identifier = $generator->generatePacketID();
}
public function getCode()
{
return 'unsubscribe';
}
public function start()
{
$packet = new UnsubscribeRequestPacket();
$packet->setTopic($this->subscriptions[0]->getFilter());
$packet->setIdentifier($this->identifier);
return $packet;
}
public function accept(Packet $packet)
{
if ($packet->getPacketType() !== Packet::TYPE_UNSUBACK) {
return false;
}
/* @var UnsubscribeResponsePacket $packet */
return $packet->getIdentifier() === $this->identifier;
}
public function next(Packet $packet)
{
$this->succeed($this->subscriptions[0]);
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace BinSoul\Net\Mqtt;
/**
* Generates identifiers.
*/
interface IdentifierGenerator
{
/**
* Generates a packet identifier between 1 and 0xFFFF.
*
* @return int
*/
public function generatePacketID();
/**
* Generates a client identifier of up to 23 bytes.
*
* @return string
*/
public function generateClientID();
}

99
vendor/binsoul/net-mqtt/src/Message.php vendored Executable file
View File

@@ -0,0 +1,99 @@
<?php
namespace BinSoul\Net\Mqtt;
/**
* Represents a message.
*/
interface Message
{
/**
* Returns the topic.
*
* @return string
*/
public function getTopic();
/**
* Returns the payload.
*
* @return string
*/
public function getPayload();
/**
* Returns the quality of service level.
*
* @return int
*/
public function getQosLevel();
/**
* Indicates if the message is a duplicate.
*
* @return bool
*/
public function isDuplicate();
/**
* Indicates if the message is retained.
*
* @return bool
*/
public function isRetained();
/**
* Returns a new message with the given topic.
*
* @param string $topic
*
* @return self
*/
public function withTopic($topic);
/**
* Returns a new message with the given payload.
*
* @param string $payload
*
* @return self
*/
public function withPayload($payload);
/**
* Returns a new message with the given quality of service level.
*
* @param int $level
*
* @return self
*/
public function withQosLevel($level);
/**
* Returns a new message flagged as retained.
*
* @return self
*/
public function retain();
/**
* Returns a new message flagged as not retained.
*
* @return self
*/
public function release();
/**
* Returns a new message flagged as duplicate.
*
* @return self
*/
public function duplicate();
/**
* Returns a new message flagged as original.
*
* @return self
*/
public function original();
}

52
vendor/binsoul/net-mqtt/src/Packet.php vendored Executable file
View File

@@ -0,0 +1,52 @@
<?php
namespace BinSoul\Net\Mqtt;
/**
* Represent a packet of the MQTT protocol.
*/
interface Packet
{
const TYPE_CONNECT = 1;
const TYPE_CONNACK = 2;
const TYPE_PUBLISH = 3;
const TYPE_PUBACK = 4;
const TYPE_PUBREC = 5;
const TYPE_PUBREL = 6;
const TYPE_PUBCOMP = 7;
const TYPE_SUBSCRIBE = 8;
const TYPE_SUBACK = 9;
const TYPE_UNSUBSCRIBE = 10;
const TYPE_UNSUBACK = 11;
const TYPE_PINGREQ = 12;
const TYPE_PINGRESP = 13;
const TYPE_DISCONNECT = 14;
/**
* Returns the type of the packet.
*
* @return int
*/
public function getPacketType();
/**
* Reads the packet from the given stream.
*
* @param PacketStream $stream
*/
public function read(PacketStream $stream);
/**
* Writes the packet to the given stream.
*
* @param PacketStream $stream
*/
public function write(PacketStream $stream);
/**
* Returns the serialized form of the packet.
*
* @return string
*/
public function __toString();
}

View File

@@ -0,0 +1,279 @@
<?php
namespace BinSoul\Net\Mqtt\Packet;
use BinSoul\Net\Mqtt\Exception\MalformedPacketException;
use BinSoul\Net\Mqtt\PacketStream;
use BinSoul\Net\Mqtt\Packet;
/**
* Represents the base class for all packets.
*/
abstract class BasePacket implements Packet
{
/**
* Type of the packet. See {@see Packet}.
*
* @var int
*/
protected static $packetType = 0;
/**
* Flags of the packet.
*
* @var int
*/
protected $packetFlags = 0;
/**
* Number of bytes of a variable length packet.
*
* @var int
*/
protected $remainingPacketLength = 0;
public function __toString()
{
$output = new PacketStream();
$this->write($output);
return $output->getData();
}
public function read(PacketStream $stream)
{
$byte = $stream->readByte();
if ($byte >> 4 !== static::$packetType) {
throw new MalformedPacketException(
sprintf(
'Expected packet type %02x but got %02x.',
$byte >> 4,
static::$packetType
)
);
}
$this->packetFlags = $byte & 0x0F;
$this->readRemainingLength($stream);
}
public function write(PacketStream $stream)
{
$stream->writeByte(((static::$packetType & 0x0F) << 4) + ($this->packetFlags & 0x0F));
$this->writeRemainingLength($stream);
}
/**
* Reads the remaining length from the given stream.
*
* @param PacketStream $stream
*
* @throws MalformedPacketException
*/
private function readRemainingLength(PacketStream $stream)
{
$this->remainingPacketLength = 0;
$multiplier = 1;
do {
$encodedByte = $stream->readByte();
$this->remainingPacketLength += ($encodedByte & 127) * $multiplier;
$multiplier *= 128;
if ($multiplier > 128 * 128 * 128 * 128) {
throw new MalformedPacketException('Malformed remaining length.');
}
} while (($encodedByte & 128) !== 0);
}
/**
* Writes the remaining length to the given stream.
*
* @param PacketStream $stream
*/
private function writeRemainingLength(PacketStream $stream)
{
$x = $this->remainingPacketLength;
do {
$encodedByte = $x % 128;
$x = (int) ($x / 128);
if ($x > 0) {
$encodedByte |= 128;
}
$stream->writeByte($encodedByte);
} while ($x > 0);
}
public function getPacketType()
{
return static::$packetType;
}
/**
* Returns the packet flags.
*
* @return int
*/
public function getPacketFlags()
{
return $this->packetFlags;
}
/**
* Returns the remaining length.
*
* @return int
*/
public function getRemainingPacketLength()
{
return $this->remainingPacketLength;
}
/**
* Asserts that the packet flags have a specific value.
*
* @param int $value
* @param bool $fromPacket
*
* @throws MalformedPacketException
* @throws \InvalidArgumentException
*/
protected function assertPacketFlags($value, $fromPacket = true)
{
if ($this->packetFlags !== $value) {
$this->throwException(
sprintf(
'Expected flags %02x but got %02x.',
$value,
$this->packetFlags
),
$fromPacket
);
}
}
/**
* Asserts that the remaining length is greater than zero and has a specific value.
*
* @param int|null $value value to test or null if any value greater than zero is valid
* @param bool $fromPacket
*
* @throws MalformedPacketException
* @throws \InvalidArgumentException
*/
protected function assertRemainingPacketLength($value = null, $fromPacket = true)
{
if ($value === null && $this->remainingPacketLength === 0) {
$this->throwException('Expected payload but remaining packet length is zero.', $fromPacket);
}
if ($value !== null && $this->remainingPacketLength !== $value) {
$this->throwException(
sprintf(
'Expected remaining packet length of %d bytes but got %d.',
$value,
$this->remainingPacketLength
),
$fromPacket
);
}
}
/**
* Asserts that the given string is a well-formed MQTT string.
*
* @param string $value
* @param bool $fromPacket
*
* @throws MalformedPacketException
* @throws \InvalidArgumentException
*/
protected function assertValidStringLength($value, $fromPacket = true)
{
if (strlen($value) > 0xFFFF) {
$this->throwException(
sprintf(
'The string "%s" is longer than 65535 byte.',
substr($value, 0, 50)
),
$fromPacket
);
}
}
/**
* Asserts that the given string is a well-formed MQTT string.
*
* @param string $value
* @param bool $fromPacket
*
* @throws MalformedPacketException
* @throws \InvalidArgumentException
*/
protected function assertValidString($value, $fromPacket = true)
{
$this->assertValidStringLength($value, $fromPacket);
if (!mb_check_encoding($value, 'UTF-8')) {
$this->throwException(
sprintf(
'The string "%s" is not well-formed UTF-8.',
substr($value, 0, 50)
),
$fromPacket
);
}
if (preg_match('/[\xD8-\xDF][\x00-\xFF]|\x00\x00/x', $value)) {
$this->throwException(
sprintf(
'The string "%s" contains invalid characters.',
substr($value, 0, 50)
),
$fromPacket
);
}
}
/**
* Asserts that the given quality of service level is valid.
*
* @param int $level
* @param bool $fromPacket
*
* @throws MalformedPacketException
* @throws \InvalidArgumentException
*/
protected function assertValidQosLevel($level, $fromPacket = true)
{
if ($level < 0 || $level > 2) {
$this->throwException(
sprintf(
'Expected a quality of service level between 0 and 2 but got %d.',
$level
),
$fromPacket
);
}
}
/**
* Throws a MalformedPacketException for packet validation and an InvalidArgumentException otherwise.
*
* @param string $message
* @param bool $fromPacket
*
* @throws MalformedPacketException
* @throws \InvalidArgumentException
*/
protected function throwException($message, $fromPacket)
{
if ($fromPacket) {
throw new MalformedPacketException($message);
}
throw new \InvalidArgumentException($message);
}
}

View File

@@ -0,0 +1,405 @@
<?php
namespace BinSoul\Net\Mqtt\Packet;
use BinSoul\Net\Mqtt\Exception\MalformedPacketException;
use BinSoul\Net\Mqtt\PacketStream;
use BinSoul\Net\Mqtt\Packet;
/**
* Represents the CONNECT packet.
*/
class ConnectRequestPacket extends BasePacket
{
/** @var int */
private $protocolLevel = 4;
/** @var string */
private $protocolName = 'MQTT';
/** @var int */
private $flags = 2;
/** @var string */
protected $clientID = '';
/** @var int */
private $keepAlive = 60;
/** @var string */
private $willTopic = '';
/** @var string */
private $willMessage = '';
/** @var string */
private $username = '';
/** @var string */
private $password = '';
protected static $packetType = Packet::TYPE_CONNECT;
public function read(PacketStream $stream)
{
parent::read($stream);
$this->assertPacketFlags(0);
$this->assertRemainingPacketLength();
$this->protocolName = $stream->readString();
$this->protocolLevel = $stream->readByte();
$this->flags = $stream->readByte();
$this->keepAlive = $stream->readWord();
$this->clientID = $stream->readString();
if ($this->hasWill()) {
$this->willTopic = $stream->readString();
$this->willMessage = $stream->readString();
}
if ($this->hasUsername()) {
$this->username = $stream->readString();
}
if ($this->hasPassword()) {
$this->password = $stream->readString();
}
$this->assertValidWill();
$this->assertValidString($this->clientID);
$this->assertValidString($this->willTopic);
$this->assertValidString($this->username);
}
public function write(PacketStream $stream)
{
if ($this->clientID === '') {
$this->clientID = 'BinSoul'.mt_rand(100000, 999999);
}
$data = new PacketStream();
$data->writeString($this->protocolName);
$data->writeByte($this->protocolLevel);
$data->writeByte($this->flags);
$data->writeWord($this->keepAlive);
$data->writeString($this->clientID);
if ($this->hasWill()) {
$data->writeString($this->willTopic);
$data->writeString($this->willMessage);
}
if ($this->hasUsername()) {
$data->writeString($this->username);
}
if ($this->hasPassword()) {
$data->writeString($this->password);
}
$this->remainingPacketLength = $data->length();
parent::write($stream);
$stream->write($data->getData());
}
/**
* Returns the protocol level.
*
* @return int
*/
public function getProtocolLevel()
{
return $this->protocolLevel;
}
/**
* Sets the protocol level.
*
* @param int $value
*
* @throws \InvalidArgumentException
*/
public function setProtocolLevel($value)
{
if ($value < 3 || $value > 4) {
throw new \InvalidArgumentException(sprintf('Unknown protocol level %d.', $value));
}
$this->protocolLevel = $value;
if ($this->protocolLevel === 3) {
$this->protocolName = 'MQIsdp';
} elseif ($this->protocolLevel === 4) {
$this->protocolName = 'MQTT';
}
}
/**
* Returns the client id.
*
* @return string
*/
public function getClientID()
{
return $this->clientID;
}
/**
* Sets the client id.
*
* @param string $value
*/
public function setClientID($value)
{
$this->clientID = $value;
}
/**
* Returns the keep alive time in seconds.
*
* @return int
*/
public function getKeepAlive()
{
return $this->keepAlive;
}
/**
* Sets the keep alive time in seconds.
*
* @param int $value
*
* @throws \InvalidArgumentException
*/
public function setKeepAlive($value)
{
if ($value > 65535) {
throw new \InvalidArgumentException(
sprintf(
'Expected a keep alive time lower than 65535 but got %d.',
$value
)
);
}
$this->keepAlive = $value;
}
/**
* Indicates if the clean session flag is set.
*
* @return bool
*/
public function isCleanSession()
{
return ($this->flags & 2) === 2;
}
/**
* Changes the clean session flag.
*
* @param bool $value
*/
public function setCleanSession($value)
{
if ($value) {
$this->flags |= 2;
} else {
$this->flags &= ~2;
}
}
/**
* Indicates if a will is set.
*
* @return bool
*/
public function hasWill()
{
return ($this->flags & 4) === 4;
}
/**
* Returns the desired quality of service level of the will.
*
* @return int
*/
public function getWillQosLevel()
{
return ($this->flags & 24) >> 3;
}
/**
* Indicates if the will should be retained.
*
* @return bool
*/
public function isWillRetained()
{
return ($this->flags & 32) === 32;
}
/**
* Returns the will topic.
*
* @return string
*/
public function getWillTopic()
{
return $this->willTopic;
}
/**
* Returns the will message.
*
* @return string
*/
public function getWillMessage()
{
return $this->willMessage;
}
/**
* Sets the will.
*
* @param string $topic
* @param string $message
* @param int $qosLevel
* @param bool $isRetained
*
* @throws \InvalidArgumentException
*/
public function setWill($topic, $message, $qosLevel = 0, $isRetained = false)
{
$this->assertValidString($topic, false);
if ($topic === '') {
throw new \InvalidArgumentException('The topic must not be empty.');
}
$this->assertValidStringLength($message, false);
if ($message === '') {
throw new \InvalidArgumentException('The message must not be empty.');
}
$this->assertValidQosLevel($qosLevel, false);
$this->willTopic = $topic;
$this->willMessage = $message;
$this->flags |= 4;
$this->flags |= ($qosLevel << 3);
if ($isRetained) {
$this->flags |= 32;
} else {
$this->flags &= ~32;
}
}
/**
* Removes the will.
*/
public function removeWill()
{
$this->flags &= ~60;
$this->willTopic = '';
$this->willMessage = '';
}
/**
* Indicates if a username is set.
*
* @return bool
*/
public function hasUsername()
{
return $this->flags & 64;
}
/**
* Returns the username.
*
* @return string
*/
public function getUsername()
{
return $this->username;
}
/**
* Sets the username.
*
* @param string $value
*
* @throws \InvalidArgumentException
*/
public function setUsername($value)
{
$this->assertValidString($value, false);
$this->username = $value;
if ($this->username !== '') {
$this->flags |= 64;
} else {
$this->flags &= ~64;
}
}
/**
* Indicates if a password is set.
*
* @return bool
*/
public function hasPassword()
{
return $this->flags & 128;
}
/**
* Returns the password.
*
* @return string
*/
public function getPassword()
{
return $this->password;
}
/**
* Sets the password.
*
* @param string $value
*
* @throws \InvalidArgumentException
*/
public function setPassword($value)
{
$this->assertValidStringLength($value, false);
$this->password = $value;
if ($this->password !== '') {
$this->flags |= 128;
} else {
$this->flags &= ~128;
}
}
/**
* Asserts that all will flags and quality of service are correct.
*
* @throws MalformedPacketException
*/
private function assertValidWill()
{
if ($this->hasWill()) {
$this->assertValidQosLevel($this->getWillQosLevel(), true);
} else {
if ($this->getWillQosLevel() > 0) {
$this->throwException(
sprintf(
'Expected a will quality of service level of zero but got %d.',
$this->getWillQosLevel()
),
true
);
}
if ($this->isWillRetained()) {
$this->throwException('There is not will but the will retain flag is set.', true);
}
}
}
}

View File

@@ -0,0 +1,111 @@
<?php
namespace BinSoul\Net\Mqtt\Packet;
use BinSoul\Net\Mqtt\PacketStream;
use BinSoul\Net\Mqtt\Packet;
/**
* Represents the CONNACK packet.
*/
class ConnectResponsePacket extends BasePacket
{
/** @var string[][] */
private static $returnCodes = [
0 => [
'Connection accepted',
'',
],
1 => [
'Unacceptable protocol version',
'The Server does not support the level of the MQTT protocol requested by the client.',
],
2 => [
'Identifier rejected',
'The client identifier is correct UTF-8 but not allowed by the server.',
],
3 => [
'Server unavailable',
'The network connection has been made but the MQTT service is unavailable',
],
4 => [
'Bad user name or password',
'The data in the user name or password is malformed.',
],
5 => [
'Not authorized',
'The client is not authorized to connect.',
],
];
/** @var int */
private $flags = 0;
/** @var int */
private $returnCode;
protected static $packetType = Packet::TYPE_CONNACK;
protected $remainingPacketLength = 2;
public function read(PacketStream $stream)
{
parent::read($stream);
$this->assertPacketFlags(0);
$this->assertRemainingPacketLength(2);
$this->flags = $stream->readByte();
$this->returnCode = $stream->readByte();
}
public function write(PacketStream $stream)
{
$this->remainingPacketLength = 2;
parent::write($stream);
$stream->writeByte($this->flags);
$stream->writeByte($this->returnCode);
}
/**
* Returns the return code.
*
* @return int
*/
public function getReturnCode()
{
return $this->returnCode;
}
/**
* Indicates if the connection was successful.
*
* @return bool
*/
public function isSuccess()
{
return $this->returnCode === 0;
}
/**
* Indicates if the connection failed.
*
* @return bool
*/
public function isError()
{
return $this->returnCode > 0;
}
/**
* Returns a string representation of the returned error code.
*
* @return int
*/
public function getErrorName()
{
if (isset(self::$returnCodes[$this->returnCode])) {
return self::$returnCodes[$this->returnCode][0];
}
return 'Error '.$this->returnCode;
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace BinSoul\Net\Mqtt\Packet;
use BinSoul\Net\Mqtt\PacketStream;
use BinSoul\Net\Mqtt\Packet;
/**
* Represents the DISCONNECT packet.
*/
class DisconnectRequestPacket extends BasePacket
{
protected static $packetType = Packet::TYPE_DISCONNECT;
public function read(PacketStream $stream)
{
parent::read($stream);
$this->assertPacketFlags(0);
$this->assertRemainingPacketLength(0);
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace BinSoul\Net\Mqtt\Packet;
/**
* Provides methods for packets with an identifier.
*/
trait IdentifiablePacket
{
/** @var int */
private static $nextIdentifier = 0;
/** @var int|null */
protected $identifier;
/**
* Returns the identifier or generates a new one.
*
* @return int
*/
protected function generateIdentifier()
{
if ($this->identifier === null) {
++self::$nextIdentifier;
self::$nextIdentifier &= 0xFFFF;
$this->identifier = self::$nextIdentifier;
}
return $this->identifier;
}
/**
* Returns the identifier.
*
* @return int|null
*/
public function getIdentifier()
{
return $this->identifier;
}
/**
* Sets the identifier.
*
* @param int|null $value
*
* @throws \InvalidArgumentException
*/
public function setIdentifier($value)
{
if ($value !== null && ($value < 0 || $value > 0xFFFF)) {
throw new \InvalidArgumentException(
sprintf(
'Expected an identifier between 0x0000 and 0xFFFF but got %x',
$value
)
);
}
$this->identifier = $value;
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace BinSoul\Net\Mqtt\Packet;
use BinSoul\Net\Mqtt\PacketStream;
/**
* Provides a base class for PUB* packets.
*/
abstract class IdentifierOnlyPacket extends BasePacket
{
use IdentifiablePacket;
protected $remainingPacketLength = 2;
public function read(PacketStream $stream)
{
parent::read($stream);
$this->assertPacketFlags($this->getExpectedPacketFlags());
$this->assertRemainingPacketLength(2);
$this->identifier = $stream->readWord();
}
public function write(PacketStream $stream)
{
$this->remainingPacketLength = 2;
parent::write($stream);
$stream->writeWord($this->generateIdentifier());
}
/**
* Returns the expected packet flags.
*
* @return int
*/
protected function getExpectedPacketFlags()
{
return 0;
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace BinSoul\Net\Mqtt\Packet;
use BinSoul\Net\Mqtt\PacketStream;
use BinSoul\Net\Mqtt\Packet;
/**
* Represents the PINGREQ packet.
*/
class PingRequestPacket extends BasePacket
{
protected static $packetType = Packet::TYPE_PINGREQ;
public function read(PacketStream $stream)
{
parent::read($stream);
$this->assertPacketFlags(0);
$this->assertRemainingPacketLength(0);
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace BinSoul\Net\Mqtt\Packet;
use BinSoul\Net\Mqtt\PacketStream;
use BinSoul\Net\Mqtt\Packet;
/**
* Represents the PINGRESP packet.
*/
class PingResponsePacket extends BasePacket
{
protected static $packetType = Packet::TYPE_PINGRESP;
public function read(PacketStream $stream)
{
parent::read($stream);
$this->assertPacketFlags(0);
$this->assertRemainingPacketLength(0);
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace BinSoul\Net\Mqtt\Packet;
use BinSoul\Net\Mqtt\Packet;
/**
* Represents the PUBACK packet.
*/
class PublishAckPacket extends IdentifierOnlyPacket
{
protected static $packetType = Packet::TYPE_PUBACK;
}

View File

@@ -0,0 +1,13 @@
<?php
namespace BinSoul\Net\Mqtt\Packet;
use BinSoul\Net\Mqtt\Packet;
/**
* Represents the PUBCOMP packet.
*/
class PublishCompletePacket extends IdentifierOnlyPacket
{
protected static $packetType = Packet::TYPE_PUBCOMP;
}

View File

@@ -0,0 +1,13 @@
<?php
namespace BinSoul\Net\Mqtt\Packet;
use BinSoul\Net\Mqtt\Packet;
/**
* Represents the PUBREC packet.
*/
class PublishReceivedPacket extends IdentifierOnlyPacket
{
protected static $packetType = Packet::TYPE_PUBREC;
}

View File

@@ -0,0 +1,19 @@
<?php
namespace BinSoul\Net\Mqtt\Packet;
use BinSoul\Net\Mqtt\Packet;
/**
* Represents the PUBREL packet.
*/
class PublishReleasePacket extends IdentifierOnlyPacket
{
protected static $packetType = Packet::TYPE_PUBREL;
protected $packetFlags = 2;
protected function getExpectedPacketFlags()
{
return 2;
}
}

View File

@@ -0,0 +1,176 @@
<?php
namespace BinSoul\Net\Mqtt\Packet;
use BinSoul\Net\Mqtt\PacketStream;
use BinSoul\Net\Mqtt\Packet;
/**
* Represents the PUBLISH packet.
*/
class PublishRequestPacket extends BasePacket
{
use IdentifiablePacket;
/** @var string */
private $topic;
/** @var string */
private $payload;
protected static $packetType = Packet::TYPE_PUBLISH;
public function read(PacketStream $stream)
{
parent::read($stream);
$this->assertRemainingPacketLength();
$originalPosition = $stream->getPosition();
$this->topic = $stream->readString();
$this->identifier = null;
if ($this->getQosLevel() > 0) {
$this->identifier = $stream->readWord();
}
$payloadLength = $this->remainingPacketLength - ($stream->getPosition() - $originalPosition);
$this->payload = $stream->read($payloadLength);
$this->assertValidQosLevel($this->getQosLevel());
$this->assertValidString($this->topic);
}
public function write(PacketStream $stream)
{
$data = new PacketStream();
$data->writeString($this->topic);
if ($this->getQosLevel() > 0) {
$data->writeWord($this->generateIdentifier());
}
$data->write($this->payload);
$this->remainingPacketLength = $data->length();
parent::write($stream);
$stream->write($data->getData());
}
/**
* Returns the topic.
*
* @return string
*/
public function getTopic()
{
return $this->topic;
}
/**
* Sets the topic.
*
* @param string $value
*
* @throws \InvalidArgumentException
*/
public function setTopic($value)
{
$this->assertValidString($value, false);
if ($value === '') {
throw new \InvalidArgumentException('The topic must not be empty.');
}
$this->topic = $value;
}
/**
* Returns the payload.
*
* @return string
*/
public function getPayload()
{
return $this->payload;
}
/**
* Sets the payload.
*
* @param string $value
*/
public function setPayload($value)
{
$this->payload = $value;
}
/**
* Indicates if the packet is a duplicate.
*
* @return bool
*/
public function isDuplicate()
{
return ($this->packetFlags & 8) === 8;
}
/**
* Marks the packet as duplicate.
*
* @param bool $value
*/
public function setDuplicate($value)
{
if ($value) {
$this->packetFlags |= 8;
} else {
$this->packetFlags &= ~8;
}
}
/**
* Indicates if the packet is retained.
*
* @return bool
*/
public function isRetained()
{
return ($this->packetFlags & 1) === 1;
}
/**
* Marks the packet as retained.
*
* @param bool $value
*/
public function setRetained($value)
{
if ($value) {
$this->packetFlags |= 1;
} else {
$this->packetFlags &= ~1;
}
}
/**
* Returns the quality of service level.
*
* @return int
*/
public function getQosLevel()
{
return ($this->packetFlags & 6) >> 1;
}
/**
* Sets the quality of service level.
*
* @param int $value
*
* @throws \InvalidArgumentException
*/
public function setQosLevel($value)
{
$this->assertValidQosLevel($value, false);
$this->packetFlags |= ($value & 3) << 1;
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace BinSoul\Net\Mqtt\Packet;
use BinSoul\Net\Mqtt\Exception\MalformedPacketException;
use BinSoul\Net\Mqtt\PacketStream;
/**
* Represents the CONNECT packet with strict rules for client ids.
*/
class StrictConnectRequestPacket extends ConnectRequestPacket
{
public function read(PacketStream $stream)
{
parent::read($stream);
$this->assertValidClientID($this->clientID, true);
}
/**
* Sets the client id.
*
* @param string $value
*
* @throws \InvalidArgumentException
*/
public function setClientID($value)
{
$this->assertValidClientID($value, false);
$this->clientID = $value;
}
/**
* Asserts that a client id is shorter than 24 bytes and only contains characters 0-9, a-z or A-Z.
*
* @param string $value
* @param bool $fromPacket
*
* @throws MalformedPacketException
* @throws \InvalidArgumentException
*/
private function assertValidClientID($value, $fromPacket)
{
if (strlen($value) > 23) {
$this->throwException(
sprintf(
'Expected client id shorter than 24 bytes but got "%s".',
$value
),
$fromPacket
);
}
if ($value !== '' && !ctype_alnum($value)) {
$this->throwException(
sprintf(
'Expected a client id containing characters 0-9, a-z or A-Z but got "%s".',
$value
),
$fromPacket
);
}
}
}

View File

@@ -0,0 +1,101 @@
<?php
namespace BinSoul\Net\Mqtt\Packet;
use BinSoul\Net\Mqtt\PacketStream;
use BinSoul\Net\Mqtt\Packet;
/**
* Represents the SUBSCRIBE packet.
*/
class SubscribeRequestPacket extends BasePacket
{
use IdentifiablePacket;
/** @var string */
private $topic;
/** @var int */
private $qosLevel;
protected static $packetType = Packet::TYPE_SUBSCRIBE;
protected $packetFlags = 2;
public function read(PacketStream $stream)
{
parent::read($stream);
$this->assertPacketFlags(2);
$this->assertRemainingPacketLength();
$this->identifier = $stream->readWord();
$this->topic = $stream->readString();
$this->qosLevel = $stream->readByte();
$this->assertValidQosLevel($this->qosLevel);
$this->assertValidString($this->topic);
}
public function write(PacketStream $stream)
{
$data = new PacketStream();
$data->writeWord($this->generateIdentifier());
$data->writeString($this->topic);
$data->writeByte($this->qosLevel);
$this->remainingPacketLength = $data->length();
parent::write($stream);
$stream->write($data->getData());
}
/**
* Returns the topic.
*
* @return string
*/
public function getTopic()
{
return $this->topic;
}
/**
* Sets the topic.
*
* @param string $value
*
* @throws \InvalidArgumentException
*/
public function setTopic($value)
{
$this->assertValidString($value, false);
if ($value === '') {
throw new \InvalidArgumentException('The topic must not be empty.');
}
$this->topic = $value;
}
/**
* Returns the quality of service level.
*
* @return int
*/
public function getQosLevel()
{
return $this->qosLevel;
}
/**
* Sets the quality of service level.
*
* @param int $value
*
* @throws \InvalidArgumentException
*/
public function setQosLevel($value)
{
$this->assertValidQosLevel($value, false);
$this->qosLevel = $value;
}
}

View File

@@ -0,0 +1,132 @@
<?php
namespace BinSoul\Net\Mqtt\Packet;
use BinSoul\Net\Mqtt\Exception\MalformedPacketException;
use BinSoul\Net\Mqtt\PacketStream;
use BinSoul\Net\Mqtt\Packet;
/**
* Represents the SUBACK packet.
*/
class SubscribeResponsePacket extends BasePacket
{
use IdentifiablePacket;
private static $qosLevels = [
0 => ['Maximum QoS 0'],
1 => ['Maximum QoS 1'],
2 => ['Maximum QoS 2'],
128 => ['Failure'],
];
/** @var int[] */
private $returnCodes;
protected static $packetType = Packet::TYPE_SUBACK;
public function read(PacketStream $stream)
{
parent::read($stream);
$this->assertPacketFlags(0);
$this->assertRemainingPacketLength();
$this->identifier = $stream->readWord();
$returnCodeLength = $this->remainingPacketLength - 2;
for ($n = 0; $n < $returnCodeLength; ++$n) {
$returnCode = $stream->readByte();
$this->assertValidReturnCode($returnCode);
$this->returnCodes[] = $returnCode;
}
}
public function write(PacketStream $stream)
{
$data = new PacketStream();
$data->writeWord($this->generateIdentifier());
foreach ($this->returnCodes as $returnCode) {
$data->writeByte($returnCode);
}
$this->remainingPacketLength = $data->length();
parent::write($stream);
$stream->write($data->getData());
}
/**
* Indicates if the given return code is an error.
*
* @param int $returnCode
*
* @return bool
*/
public function isError($returnCode)
{
return $returnCode === 128;
}
/**
* Indicates if the given return code is an error.
*
* @param int $returnCode
*
* @return bool
*/
public function getReturnCodeName($returnCode)
{
if (isset(self::$qosLevels[$returnCode])) {
return self::$qosLevels[$returnCode][0];
}
return 'Unknown '.$returnCode;
}
/**
* Returns the return codes.
*
* @return int[]
*/
public function getReturnCodes()
{
return $this->returnCodes;
}
/**
* Sets the return codes.
*
* @param int[] $value
*
* @throws \InvalidArgumentException
*/
public function setReturnCodes(array $value)
{
foreach ($value as $returnCode) {
$this->assertValidReturnCode($returnCode, false);
}
$this->returnCodes = $value;
}
/**
* Asserts that a return code is valid.
*
* @param int $returnCode
* @param bool $fromPacket
*
* @throws MalformedPacketException
* @throws \InvalidArgumentException
*/
private function assertValidReturnCode($returnCode, $fromPacket = true)
{
if (!in_array($returnCode, [0, 1, 2, 128])) {
$this->throwException(
sprintf('Malformed return code %02x.', $returnCode),
$fromPacket
);
}
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace BinSoul\Net\Mqtt\Packet;
use BinSoul\Net\Mqtt\PacketStream;
use BinSoul\Net\Mqtt\Packet;
/**
* Represents the UNSUBSCRIBE packet.
*/
class UnsubscribeRequestPacket extends BasePacket
{
use IdentifiablePacket;
/** @var string */
private $topic;
protected static $packetType = Packet::TYPE_UNSUBSCRIBE;
protected $packetFlags = 2;
public function read(PacketStream $stream)
{
parent::read($stream);
$this->assertPacketFlags(2);
$this->assertRemainingPacketLength();
$originalPosition = $stream->getPosition();
do {
$this->identifier = $stream->readWord();
$this->topic = $stream->readString();
} while (($stream->getPosition() - $originalPosition) <= $this->remainingPacketLength);
}
public function write(PacketStream $stream)
{
$data = new PacketStream();
$data->writeWord($this->generateIdentifier());
$data->writeString($this->topic);
$this->remainingPacketLength = $data->length();
parent::write($stream);
$stream->write($data->getData());
}
/**
* Returns the topic.
*
* @return string
*/
public function getTopic()
{
return $this->topic;
}
/**
* Sets the topic.
*
* @param string $value
*/
public function setTopic($value)
{
$this->topic = $value;
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace BinSoul\Net\Mqtt\Packet;
use BinSoul\Net\Mqtt\Packet;
/**
* Represents the UNSUBACK packet.
*/
class UnsubscribeResponsePacket extends IdentifierOnlyPacket
{
protected static $packetType = Packet::TYPE_UNSUBACK;
}

67
vendor/binsoul/net-mqtt/src/PacketFactory.php vendored Executable file
View File

@@ -0,0 +1,67 @@
<?php
namespace BinSoul\Net\Mqtt;
use BinSoul\Net\Mqtt\Exception\UnknownPacketTypeException;
use BinSoul\Net\Mqtt\Packet\ConnectRequestPacket;
use BinSoul\Net\Mqtt\Packet\ConnectResponsePacket;
use BinSoul\Net\Mqtt\Packet\DisconnectRequestPacket;
use BinSoul\Net\Mqtt\Packet\PingRequestPacket;
use BinSoul\Net\Mqtt\Packet\PingResponsePacket;
use BinSoul\Net\Mqtt\Packet\PublishRequestPacket;
use BinSoul\Net\Mqtt\Packet\PublishAckPacket;
use BinSoul\Net\Mqtt\Packet\PublishCompletePacket;
use BinSoul\Net\Mqtt\Packet\PublishReceivedPacket;
use BinSoul\Net\Mqtt\Packet\PublishReleasePacket;
use BinSoul\Net\Mqtt\Packet\SubscribeRequestPacket;
use BinSoul\Net\Mqtt\Packet\SubscribeResponsePacket;
use BinSoul\Net\Mqtt\Packet\UnsubscribeRequestPacket;
use BinSoul\Net\Mqtt\Packet\UnsubscribeResponsePacket;
/**
* Builds instances of the {@see Packet} interface.
*/
class PacketFactory
{
/**
* Map of packet types to packet classes.
*
* @var string[]
*/
private static $mapping = [
Packet::TYPE_CONNECT => ConnectRequestPacket::class,
Packet::TYPE_CONNACK => ConnectResponsePacket::class,
Packet::TYPE_PUBLISH => PublishRequestPacket::class,
Packet::TYPE_PUBACK => PublishAckPacket::class,
Packet::TYPE_PUBREC => PublishReceivedPacket::class,
Packet::TYPE_PUBREL => PublishReleasePacket::class,
Packet::TYPE_PUBCOMP => PublishCompletePacket::class,
Packet::TYPE_SUBSCRIBE => SubscribeRequestPacket::class,
Packet::TYPE_SUBACK => SubscribeResponsePacket::class,
Packet::TYPE_UNSUBSCRIBE => UnsubscribeRequestPacket::class,
Packet::TYPE_UNSUBACK => UnsubscribeResponsePacket::class,
Packet::TYPE_PINGREQ => PingRequestPacket::class,
Packet::TYPE_PINGRESP => PingResponsePacket::class,
Packet::TYPE_DISCONNECT => DisconnectRequestPacket::class,
];
/**
* Builds a packet object for the given type.
*
* @param int $type
*
* @throws UnknownPacketTypeException
*
* @return Packet
*/
public function build($type)
{
if (!isset(self::$mapping[$type])) {
throw new UnknownPacketTypeException(sprintf('Unknown packet type %d.', $type));
}
$class = self::$mapping[$type];
return new $class();
}
}

216
vendor/binsoul/net-mqtt/src/PacketStream.php vendored Executable file
View File

@@ -0,0 +1,216 @@
<?php
namespace BinSoul\Net\Mqtt;
use BinSoul\Net\Mqtt\Exception\EndOfStreamException;
/**
* Provides methods to operate on a stream of bytes.
*/
class PacketStream
{
/** @var string */
private $data;
/** @var int */
private $position;
/**
* Constructs an instance of this class.
*
* @param string $data initial data of the stream
*/
public function __construct($data = '')
{
$this->data = $data;
$this->position = 0;
}
/**
* @return string
*/
public function __toString()
{
return $this->data;
}
/**
* Returns the desired number of bytes.
*
* @param int $count
*
* @throws EndOfStreamException
*
* @return string
*/
public function read($count)
{
$contentLength = strlen($this->data);
if ($this->position > $contentLength || $count > $contentLength - $this->position) {
throw new EndOfStreamException(
sprintf(
'End of stream reached when trying to read %d bytes. content length=%d, position=%d',
$count,
$contentLength,
$this->position
)
);
}
$chunk = substr($this->data, $this->position, $count);
if ($chunk === false) {
$chunk = '';
}
$readBytes = strlen($chunk);
$this->position += $readBytes;
return $chunk;
}
/**
* Returns a single byte.
*
* @return int
*/
public function readByte()
{
return ord($this->read(1));
}
/**
* Returns a single word.
*
* @return int
*/
public function readWord()
{
return ($this->readByte() << 8) + $this->readByte();
}
/**
* Returns a length prefixed string.
*
* @return string
*/
public function readString()
{
$length = $this->readWord();
return $this->read($length);
}
/**
* Appends the given value.
*
* @param string $value
*/
public function write($value)
{
$this->data .= $value;
}
/**
* Appends a single byte.
*
* @param int $value
*/
public function writeByte($value)
{
$this->write(chr($value));
}
/**
* Appends a single word.
*
* @param int $value
*/
public function writeWord($value)
{
$this->write(chr(($value & 0xFFFF) >> 8));
$this->write(chr($value & 0xFF));
}
/**
* Appends a length prefixed string.
*
* @param string $string
*/
public function writeString($string)
{
$this->writeWord(strlen($string));
$this->write($string);
}
/**
* Returns the length of the stream.
*
* @return int
*/
public function length()
{
return strlen($this->data);
}
/**
* Returns the number of bytes until the end of the stream.
*
* @return int
*/
public function getRemainingBytes()
{
return $this->length() - $this->position;
}
/**
* Returns the whole content of the stream.
*
* @return string
*/
public function getData()
{
return $this->data;
}
/**
* Changes the internal position of the stream relative to the current position.
*
* @param int $offset
*/
public function seek($offset)
{
$this->position += $offset;
}
/**
* Returns the internal position of the stream.
*
* @return int
*/
public function getPosition()
{
return $this->position;
}
/**
* Sets the internal position of the stream.
*
* @param int $value
*/
public function setPosition($value)
{
$this->position = $value;
}
/**
* Removes all bytes from the beginning to the current position.
*/
public function cut()
{
$this->data = substr($this->data, $this->position);
if ($this->data === false) {
$this->data = '';
}
$this->position = 0;
}
}

90
vendor/binsoul/net-mqtt/src/StreamParser.php vendored Executable file
View File

@@ -0,0 +1,90 @@
<?php
namespace BinSoul\Net\Mqtt;
use BinSoul\Net\Mqtt\Exception\EndOfStreamException;
use BinSoul\Net\Mqtt\Exception\MalformedPacketException;
use BinSoul\Net\Mqtt\Exception\UnknownPacketTypeException;
/**
* Provides methods to parse a stream of bytes into packets.
*/
class StreamParser
{
/** @var PacketStream */
private $buffer;
/** @var PacketFactory */
private $factory;
/** @var callable */
private $errorCallback;
/**
* Constructs an instance of this class.
*/
public function __construct()
{
$this->buffer = new PacketStream();
$this->factory = new PacketFactory();
}
/**
* Registers an error callback.
*
* @param callable $callback
*/
public function onError($callback)
{
$this->errorCallback = $callback;
}
/**
* Appends the given data to the internal buffer and parses it.
*
* @param string $data
*
* @return Packet[]
*/
public function push($data)
{
$this->buffer->write($data);
$result = [];
while ($this->buffer->getRemainingBytes() > 0) {
$type = $this->buffer->readByte() >> 4;
try {
$packet = $this->factory->build($type);
} catch (UnknownPacketTypeException $e) {
$this->handleError($e);
continue;
}
$this->buffer->seek(-1);
$position = $this->buffer->getPosition();
try {
$packet->read($this->buffer);
$result[] = $packet;
$this->buffer->cut();
} catch (EndOfStreamException $e) {
$this->buffer->setPosition($position);
break;
} catch (MalformedPacketException $e) {
$this->handleError($e);
}
}
return $result;
}
/**
* Executes the registered error callback.
*
* @param \Throwable $exception
*/
private function handleError($exception)
{
if ($this->errorCallback !== null) {
$callback = $this->errorCallback;
$callback($exception);
}
}
}

41
vendor/binsoul/net-mqtt/src/Subscription.php vendored Executable file
View File

@@ -0,0 +1,41 @@
<?php
namespace BinSoul\Net\Mqtt;
/**
* Represents a subscription.
*/
interface Subscription
{
/**
* Returns the topic filter.
*
* @return string
*/
public function getFilter();
/**
* Returns the quality of service level.
*
* @return int
*/
public function getQosLevel();
/**
* Returns a new subscription with the given topic filter.
*
* @param string $filter
*
* @return self
*/
public function withFilter($filter);
/**
* Returns a new subscription with the given quality of service level.
*
* @param int $level
*
* @return self
*/
public function withQosLevel($level);
}

53
vendor/binsoul/net-mqtt/src/TopicMatcher.php vendored Executable file
View File

@@ -0,0 +1,53 @@
<?php
namespace BinSoul\Net\Mqtt;
/**
* Matches a topic filter with an actual topic.
*
* @author Alin Eugen Deac <ade@vestergaardcompany.com>
*/
class TopicMatcher
{
/**
* Check if the given topic matches the filter.
*
* @param string $filter e.g. A/B/+, A/B/#
* @param string $topic e.g. A/B/C, A/B/foo/bar/baz
*
* @return bool true if topic matches the pattern
*/
public function matches($filter, $topic)
{
// Created by Steffen (https://github.com/kernelguy)
$tokens = explode('/', $filter);
$parts = [];
for ($i = 0, $count = count($tokens); $i < $count; ++$i) {
$token = $tokens[$i];
switch ($token) {
case '+':
$parts[] = '[^/#\+]*';
break;
case '#':
if ($i === 0) {
$parts[] = '[^\+\$]*';
} else {
$parts[] = '[^\+]*';
}
break;
default:
$parts[] = str_replace('+', '\+', $token);
break;
}
}
$regex = implode('/', $parts);
$regex = str_replace('$', '\$', $regex);
$regex = ';^'.$regex.'$;';
return preg_match($regex, $topic) === 1;
}
}