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

4
vendor/valga/fbns-react/.gitignore vendored Executable file
View File

@@ -0,0 +1,4 @@
/.idea/
/vendor/
/composer.lock
/.php_cs.cache

20
vendor/valga/fbns-react/.php_cs vendored Executable file
View File

@@ -0,0 +1,20 @@
<?php
return \PhpCsFixer\Config::create()
->setFinder(
\PhpCsFixer\Finder::create()
->in('src')
)
->setRules([
'@Symfony' => true,
// Override @Symfony rules
'pre_increment' => false,
'blank_line_before_statement' => ['statements' => ['return']],
'phpdoc_align' => ['tags' => ['param', 'throws']],
'phpdoc_annotation_without_dot' => false,
// Custom rules
'phpdoc_add_missing_param_annotation' => ['only_untyped' => false],
'ordered_imports' => true,
'phpdoc_order' => true,
'array_syntax' => ['syntax' => 'short'],
]);

21
vendor/valga/fbns-react/LICENSE vendored Executable file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2017 Abyr Valg <valga.github@abyrga.ru>
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.

84
vendor/valga/fbns-react/README.md vendored Executable file
View File

@@ -0,0 +1,84 @@
# fbns-react
A PHP client for the FBNS built on top of ReactPHP.
## Requirements
You need to install the [GMP extension](http://php.net/manual/en/book.gmp.php) to be able to run this code on x86 PHP builds.
## Installation
```sh
composer require valga/fbns-react
```
## Basic Usage
```php
// Set up a FBNS client.
$loop = \React\EventLoop\Factory::create();
$client = new \Fbns\Client\Lite($loop);
// Read saved credentials from a storage.
$auth = new \Fbns\Client\Auth\DeviceAuth();
try {
$auth->read($storage->get('fbns_auth'));
} catch (\Exception $e) {
}
// Connect to a broker.
$connection = new \Fbns\Client\Connection($deviceAuth, USER_AGENT);
$client->connect(HOSTNAME, PORT, $connection);
// Bind events.
$client
->on('connect', function (\Fbns\Client\Lite\ConnectResponsePacket $responsePacket) use ($client, $auth, $storage) {
// Update credentials and save them to a storage for future use.
try {
$auth->read($responsePacket->getAuth());
$storage->set('fbns_auth', $responsePacket->getAuth());
} catch (\Exception $e) {
}
// Register an application.
$client->register(PACKAGE_NAME, APPLICATION_ID);
})
->on('register', function (\Fbns\Client\Message\Register $message) use ($app) {
// Register received token with an application.
$app->registerPushToken($message->getToken());
})
->on('push', function (\Fbns\Client\Message\Push $message) use ($app) {
// Handle received notification payload.
$app->handlePushNotification($message->getPayload());
});
// Run main loop.
$loop->run();
```
## Advanced Usage
```php
// Set up a proxy.
$connector = new \React\Socket\Connector($loop);
$proxy = new \Clue\React\HttpProxy('username:password@127.0.0.1:3128', $connector);
// Disable SSL verification.
$ssl = new \React\Socket\SecureConnector($proxy, $loop, ['verify_peer' => false, 'verify_peer_name' => false]);
// Enable logging to stdout.
$logger = new \Monolog\Logger('fbns');
$logger->pushHandler(new \Monolog\Handler\StreamHandler('php://stdout', \Monolog\Logger::INFO));
// Set up a client.
$client = new \Fbns\Client\Lite($loop, $ssl, $logger);
// Persistence.
$client->on('disconnect', function () {
// Network connection has been closed. You can reestablish it if you want to.
});
$client->connect(HOSTNAME, PORT, $connection)
->otherwise(function () {
// Connection attempt was unsuccessful, retry with an exponential backoff.
});
```

33
vendor/valga/fbns-react/bin/thrift_debug vendored Executable file
View File

@@ -0,0 +1,33 @@
#!/usr/bin/env php
<?php
require __DIR__.'/../vendor/autoload.php';
if ($argc > 1) {
$data = @file_get_contents($argv[1]);
} elseif (!posix_isatty(STDIN)) {
$data = @stream_get_contents(STDIN);
} else {
echo 'Usage: ', $argv[0], ' [FILE]', PHP_EOL;
echo 'Dump the contents of Thrift FILE.', PHP_EOL;
echo PHP_EOL;
echo 'With no FILE read standard input.', PHP_EOL;
exit(2);
}
if ($data === false) {
fwrite(STDERR, 'Failed to read the input.'.PHP_EOL);
exit(1);
}
try {
new \Fbns\Client\Thrift\Debug($data);
} catch (\Exception $e) {
fwrite(STDERR, $e->getMessage().PHP_EOL);
exit(1);
}
exit(0);

45
vendor/valga/fbns-react/composer.json vendored Executable file
View File

@@ -0,0 +1,45 @@
{
"name": "valga/fbns-react",
"description": "A PHP client for the FBNS built on top of ReactPHP",
"keywords": [
"FBNS",
"Client",
"PHP"
],
"type": "library",
"minimum-stability": "stable",
"license": "MIT",
"authors": [
{
"name": "Abyr Valg",
"email": "valga.github@abyrga.ru"
}
],
"autoload": {
"psr-4": {
"Fbns\\Client\\": "src/"
}
},
"require": {
"php": "~5.6|~7.0",
"ext-mbstring": "*",
"ext-zlib": "*",
"evenement/evenement": "~2.0|~3.0",
"react/event-loop": "^0.4.3",
"react/promise": "~2.0",
"react/socket": "~0.8",
"binsoul/net-mqtt": "~0.2",
"psr/log": "~1.0"
},
"require-dev": {
"monolog/monolog": "~1.23",
"friendsofphp/php-cs-fixer": "~2.4"
},
"suggest": {
"ext-event": "For more efficient event loop implementation.",
"ext-gmp": "To be able to run this code on x86 PHP builds."
},
"scripts": {
"codestyle": "php-cs-fixer fix --config=.php_cs"
}
}

View File

@@ -0,0 +1,162 @@
<?php
namespace Fbns\Client\Auth;
use Fbns\Client\AuthInterface;
use Fbns\Client\Json;
class DeviceAuth implements AuthInterface
{
const TYPE = 'device_auth';
/**
* @var string
*/
private $json;
/**
* @var int
*/
private $clientId;
/**
* @var int
*/
private $userId;
/**
* @var string
*/
private $password;
/**
* @var string
*/
private $deviceId;
/**
* @var string
*/
private $deviceSecret;
/**
* @return string
*/
private function randomUuid()
{
return sprintf(
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0xffff)
);
}
/**
* Constructor.
*/
public function __construct()
{
$this->clientId = substr($this->randomUuid(), 0, 20);
$this->userId = 0;
$this->password = '';
$this->deviceSecret = '';
$this->deviceId = '';
}
/**
* @param string $json
*/
public function read($json)
{
$data = Json::decode($json);
$this->json = $json;
if (isset($data->ck)) {
$this->userId = $data->ck;
} else {
$this->userId = 0;
}
if (isset($data->cs)) {
$this->password = $data->cs;
} else {
$this->password = '';
}
if (isset($data->di)) {
$this->deviceId = $data->di;
$this->clientId = substr($this->deviceId, 0, 20);
} else {
$this->deviceId = '';
$this->clientId = substr($this->randomUuid(), 0, 20);
}
if (isset($data->ds)) {
$this->deviceSecret = $data->ds;
} else {
$this->deviceSecret = '';
}
// TODO: sr ?
// TODO: rc ?
}
/**
* @return string
*/
public function __toString()
{
return $this->json !== null ? $this->json : '';
}
/**
* @return int
*/
public function getUserId()
{
return $this->userId;
}
/**
* @return string
*/
public function getPassword()
{
return $this->password;
}
/**
* @return string
*/
public function getDeviceId()
{
return $this->deviceId;
}
/**
* @return string
*/
public function getDeviceSecret()
{
return $this->deviceSecret;
}
/**
* @return string
*/
public function getClientType()
{
return self::TYPE;
}
/**
* @return string
*/
public function getClientId()
{
return $this->clientId;
}
}

41
vendor/valga/fbns-react/src/AuthInterface.php vendored Executable file
View File

@@ -0,0 +1,41 @@
<?php
namespace Fbns\Client;
interface AuthInterface
{
/**
* @return string
*/
public function getClientId();
/**
* @return string
*/
public function getClientType();
/**
* @return int
*/
public function getUserId();
/**
* @return string
*/
public function getPassword();
/**
* @return string
*/
public function getDeviceId();
/**
* @return string
*/
public function getDeviceSecret();
/**
* @return string
*/
public function __toString();
}

View File

@@ -0,0 +1,30 @@
<?php
/*
* This file is part of net-mqtt.
*
* Copyright (c) 2015 Sebastian Mößler code@binsoul.de
*
* This source file is subject to the MIT license.
*/
namespace Fbns\Client\Common;
use BinSoul\Net\Mqtt\Packet\BasePacket;
use BinSoul\Net\Mqtt\Packet\PublishAckPacket as BasePublishAckPacket;
use BinSoul\Net\Mqtt\PacketStream;
/**
* Represents the PUBACK packet.
*/
class PublishAckPacket extends BasePublishAckPacket
{
public function read(PacketStream $stream)
{
BasePacket::read($stream);
//$this->assertPacketFlags($this->getExpectedPacketFlags());
$this->assertRemainingPacketLength(2);
$this->identifier = $stream->readWord();
}
}

339
vendor/valga/fbns-react/src/Connection.php vendored Executable file
View File

@@ -0,0 +1,339 @@
<?php
namespace Fbns\Client;
use Fbns\Client\Thrift\Compact;
use Fbns\Client\Thrift\Writer;
class Connection
{
const FBNS_CLIENT_CAPABILITIES = 439;
const FBNS_ENDPOINT_CAPABILITIES = 128;
const FBNS_APP_ID = '567310203415052';
const FBNS_CLIENT_STACK = 3;
const FBNS_PUBLISH_FORMAT = 1;
const CLIENT_ID = 1;
const CLIENT_INFO = 4;
const PASSWORD = 5;
const USER_ID = 1;
const USER_AGENT = 2;
const CLIENT_CAPABILITIES = 3;
const ENDPOINT_CAPABILITIES = 4;
const PUBLISH_FORMAT = 5;
const NO_AUTOMATIC_FOREGROUND = 6;
const MAKE_USER_AVAILABLE_IN_FOREGROUND = 7;
const DEVICE_ID = 8;
const IS_INITIALLY_FOREGROUND = 9;
const NETWORK_TYPE = 10;
const NETWORK_SUBTYPE = 11;
const CLIENT_MQTT_SESSION_ID = 12;
const SUBSCRIBE_TOPICS = 14;
const CLIENT_TYPE = 15;
const APP_ID = 16;
const DEVICE_SECRET = 20;
const CLIENT_STACK = 21;
/** @var AuthInterface */
private $auth;
/** @var string */
private $userAgent;
/** @var int */
private $clientCapabilities;
/** @var int */
private $endpointCapabilities;
/** @var int */
private $publishFormat;
/** @var bool */
private $noAutomaticForeground;
/** @var bool */
private $makeUserAvailableInForeground;
/** @var bool */
private $isInitiallyForeground;
/** @var int */
private $networkType;
/** @var int */
private $networkSubtype;
/** @var int */
private $clientMqttSessionId;
/** @var int[] */
private $subscribeTopics;
/** @var int */
private $appId;
/** @var int */
private $clientStack;
/**
* Connection constructor.
*
* @param AuthInterface $auth
* @param string $userAgent
*/
public function __construct(AuthInterface $auth, $userAgent)
{
$this->auth = $auth;
$this->userAgent = $userAgent;
$this->clientCapabilities = self::FBNS_CLIENT_CAPABILITIES;
$this->endpointCapabilities = self::FBNS_ENDPOINT_CAPABILITIES;
$this->publishFormat = self::FBNS_PUBLISH_FORMAT;
$this->noAutomaticForeground = true;
$this->makeUserAvailableInForeground = false;
$this->isInitiallyForeground = false;
$this->networkType = 1;
$this->networkSubtype = 0;
$this->subscribeTopics = [(int) Lite::MESSAGE_TOPIC_ID, (int) Lite::REG_RESP_TOPIC_ID];
$this->appId = self::FBNS_APP_ID;
$this->clientStack = self::FBNS_CLIENT_STACK;
}
/**
* @return string
*/
public function toThrift()
{
$writer = new Writer();
$writer->writeString(self::CLIENT_ID, $this->auth->getClientId());
$writer->writeStruct(self::CLIENT_INFO);
$writer->writeInt64(self::USER_ID, $this->auth->getUserId());
$writer->writeString(self::USER_AGENT, $this->userAgent);
$writer->writeInt64(self::CLIENT_CAPABILITIES, $this->clientCapabilities);
$writer->writeInt64(self::ENDPOINT_CAPABILITIES, $this->endpointCapabilities);
$writer->writeInt32(self::PUBLISH_FORMAT, $this->publishFormat);
$writer->writeBool(self::NO_AUTOMATIC_FOREGROUND, $this->noAutomaticForeground);
$writer->writeBool(self::MAKE_USER_AVAILABLE_IN_FOREGROUND, $this->makeUserAvailableInForeground);
$writer->writeString(self::DEVICE_ID, $this->auth->getDeviceId());
$writer->writeBool(self::IS_INITIALLY_FOREGROUND, $this->isInitiallyForeground);
$writer->writeInt32(self::NETWORK_TYPE, $this->networkType);
$writer->writeInt32(self::NETWORK_SUBTYPE, $this->networkSubtype);
if ($this->clientMqttSessionId === null) {
$sessionId = (int) ((microtime(true) - strtotime('Last Monday')) * 1000);
} else {
$sessionId = $this->clientMqttSessionId;
}
$writer->writeInt64(self::CLIENT_MQTT_SESSION_ID, $sessionId);
$writer->writeList(self::SUBSCRIBE_TOPICS, Compact::TYPE_I32, $this->subscribeTopics);
$writer->writeString(self::CLIENT_TYPE, $this->auth->getClientType());
$writer->writeInt64(self::APP_ID, $this->appId);
$writer->writeString(self::DEVICE_SECRET, $this->auth->getDeviceSecret());
$writer->writeInt8(self::CLIENT_STACK, $this->clientStack);
$writer->writeStop();
$writer->writeString(self::PASSWORD, $this->auth->getPassword());
$writer->writeStop();
return (string) $writer;
}
/**
* @return string
*/
public function getUserAgent()
{
return $this->userAgent;
}
/**
* @param string $userAgent
*/
public function setUserAgent($userAgent)
{
$this->userAgent = $userAgent;
}
/**
* @return int
*/
public function getClientCapabilities()
{
return $this->clientCapabilities;
}
/**
* @param int $clientCapabilities
*/
public function setClientCapabilities($clientCapabilities)
{
$this->clientCapabilities = $clientCapabilities;
}
/**
* @return int
*/
public function getEndpointCapabilities()
{
return $this->endpointCapabilities;
}
/**
* @param int $endpointCapabilities
*/
public function setEndpointCapabilities($endpointCapabilities)
{
$this->endpointCapabilities = $endpointCapabilities;
}
/**
* @return bool
*/
public function isNoAutomaticForeground()
{
return $this->noAutomaticForeground;
}
/**
* @param bool $noAutomaticForeground
*/
public function setNoAutomaticForeground($noAutomaticForeground)
{
$this->noAutomaticForeground = $noAutomaticForeground;
}
/**
* @return bool
*/
public function isMakeUserAvailableInForeground()
{
return $this->makeUserAvailableInForeground;
}
/**
* @param bool $makeUserAvailableInForeground
*/
public function setMakeUserAvailableInForeground($makeUserAvailableInForeground)
{
$this->makeUserAvailableInForeground = $makeUserAvailableInForeground;
}
/**
* @return bool
*/
public function isInitiallyForeground()
{
return $this->isInitiallyForeground;
}
/**
* @param bool $isInitiallyForeground
*/
public function setIsInitiallyForeground($isInitiallyForeground)
{
$this->isInitiallyForeground = $isInitiallyForeground;
}
/**
* @return int
*/
public function getNetworkType()
{
return $this->networkType;
}
/**
* @param int $networkType
*/
public function setNetworkType($networkType)
{
$this->networkType = $networkType;
}
/**
* @return int
*/
public function getNetworkSubtype()
{
return $this->networkSubtype;
}
/**
* @param int $networkSubtype
*/
public function setNetworkSubtype($networkSubtype)
{
$this->networkSubtype = $networkSubtype;
}
/**
* @return int
*/
public function getClientMqttSessionId()
{
return $this->clientMqttSessionId;
}
/**
* @param int $clientMqttSessionId
*/
public function setClientMqttSessionId($clientMqttSessionId)
{
$this->clientMqttSessionId = $clientMqttSessionId;
}
/**
* @return int[]
*/
public function getSubscribeTopics()
{
return $this->subscribeTopics;
}
/**
* @param int[] $subscribeTopics
*/
public function setSubscribeTopics($subscribeTopics)
{
$this->subscribeTopics = $subscribeTopics;
}
/**
* @return int
*/
public function getAppId()
{
return $this->appId;
}
/**
* @param int $appId
*/
public function setAppId($appId)
{
$this->appId = $appId;
}
/**
* @return int
*/
public function getClientStack()
{
return $this->clientStack;
}
/**
* @param int $clientStack
*/
public function setClientStack($clientStack)
{
$this->clientStack = $clientStack;
}
/**
* @return AuthInterface
*/
public function getAuth()
{
return $this->auth;
}
/**
* @param AuthInterface $auth
*/
public function setAuth(AuthInterface $auth)
{
$this->auth = $auth;
}
}

28
vendor/valga/fbns-react/src/Json.php vendored Executable file
View File

@@ -0,0 +1,28 @@
<?php
namespace Fbns\Client;
class Json
{
/**
* Special decoder to keep big numbers on x86 PHP builds.
*
* @param string $json
*
* @return mixed
*/
public static function decode($json)
{
$flags = 0;
if (PHP_INT_SIZE === 4) {
$flags |= JSON_BIGINT_AS_STRING;
}
$data = json_decode($json, false, 512, $flags);
$error = json_last_error();
if ($error !== JSON_ERROR_NONE) {
throw new \InvalidArgumentException(sprintf('Failed to decode JSON (%d): %s.', $error, json_last_error_msg()));
}
return $data;
}
}

379
vendor/valga/fbns-react/src/Lite.php vendored Executable file
View File

@@ -0,0 +1,379 @@
<?php
namespace Fbns\Client;
use BinSoul\Net\Mqtt\DefaultMessage;
use BinSoul\Net\Mqtt\Message;
use Evenement\EventEmitterInterface;
use Evenement\EventEmitterTrait;
use Fbns\Client\Lite\ConnectResponsePacket;
use Fbns\Client\Lite\OutgoingConnectFlow;
use Fbns\Client\Lite\ReactMqttClient;
use Fbns\Client\Lite\StreamParser;
use Fbns\Client\Message\Push;
use Fbns\Client\Message\Register;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use React\EventLoop\LoopInterface;
use React\EventLoop\Timer\TimerInterface;
use React\Promise\Deferred;
use React\Promise\FulfilledPromise;
use React\Promise\PromiseInterface;
use React\Socket\Connector;
use React\Socket\ConnectorInterface;
use React\Socket\SecureConnector;
class Lite implements EventEmitterInterface
{
use EventEmitterTrait;
const QOS_LEVEL = 1;
const MESSAGE_TOPIC = '/fbns_msg';
const MESSAGE_TOPIC_ID = '76';
const REG_REQ_TOPIC = '/fbns_reg_req';
const REG_REQ_TOPIC_ID = '79';
const REG_RESP_TOPIC = '/fbns_reg_resp';
const REG_RESP_TOPIC_ID = '80';
const ID_TO_TOPIC_ENUM = [
self::MESSAGE_TOPIC_ID => self::MESSAGE_TOPIC,
self::REG_REQ_TOPIC_ID => self::REG_REQ_TOPIC,
self::REG_RESP_TOPIC_ID => self::REG_RESP_TOPIC,
];
const TOPIC_TO_ID_ENUM = [
self::MESSAGE_TOPIC => self::MESSAGE_TOPIC_ID,
self::REG_REQ_TOPIC => self::REG_REQ_TOPIC_ID,
self::REG_RESP_TOPIC => self::REG_RESP_TOPIC_ID,
];
/**
* @var LoopInterface
*/
private $loop;
/**
* @var ConnectorInterface
*/
private $connector;
/**
* @var LoggerInterface
*/
private $logger;
/**
* @var ReactMqttClient
*/
private $client;
/**
* @var TimerInterface
*/
private $keepaliveTimer;
/**
* Constructor.
*
* @param LoopInterface $loop
* @param ConnectorInterface|null $connector
* @param LoggerInterface|null $logger
*/
public function __construct(LoopInterface $loop, ConnectorInterface $connector = null, LoggerInterface $logger = null)
{
$this->loop = $loop;
if ($connector === null) {
$this->connector = new SecureConnector(new Connector($loop), $loop);
} else {
$this->connector = $connector;
}
if ($logger !== null) {
$this->logger = $logger;
} else {
$this->logger = new NullLogger();
}
$this->client = new ReactMqttClient($this->connector, $this->loop, null, new StreamParser());
$this->client
->on('open', function () {
$this->logger->info('Connection has been established.');
})
->on('close', function () {
$this->logger->info('Network connection has been closed.');
$this->cancelKeepaliveTimer();
$this->emit('disconnect', [$this]);
})
->on('warning', function (\Exception $e) {
$this->logger->warning($e->getMessage());
})
->on('error', function (\Exception $e) {
$this->logger->error($e->getMessage());
$this->emit('error', [$e]);
})
->on('connect', function (ConnectResponsePacket $responsePacket) {
$this->logger->info('Connected to a broker.');
$this->setKeepaliveTimer();
$this->emit('connect', [$responsePacket]);
})
->on('disconnect', function () {
$this->logger->info('Disconnected from the broker.');
})
->on('message', function (Message $message) {
$this->setKeepaliveTimer();
$this->onMessage($message);
})
->on('publish', function () {
$this->logger->info('Publish flow has been completed.');
$this->setKeepaliveTimer();
})
->on('ping', function () {
$this->logger->info('Ping flow has been completed.');
$this->setKeepaliveTimer();
});
}
private function cancelKeepaliveTimer()
{
if ($this->keepaliveTimer !== null) {
if ($this->keepaliveTimer->isActive()) {
$this->logger->info('Existing keepalive timer has been canceled.');
$this->keepaliveTimer->cancel();
}
$this->keepaliveTimer = null;
}
}
private function onKeepalive()
{
$this->logger->info('Keepalive timer has been fired.');
$this->cancelKeepaliveTimer();
$this->disconnect();
}
private function setKeepaliveTimer()
{
$this->cancelKeepaliveTimer();
$keepaliveInterval = OutgoingConnectFlow::KEEPALIVE;
$this->logger->info(sprintf('Setting up keepalive timer to %d seconds', $keepaliveInterval));
$this->keepaliveTimer = $this->loop->addTimer($keepaliveInterval, function () {
$this->onKeepalive();
});
}
/**
* @param string $payload
*/
private function onRegister($payload)
{
try {
$message = new Register($payload);
} catch (\Exception $e) {
$this->logger->warning(sprintf('Failed to decode register message: %s', $e->getMessage()), [$payload]);
return;
}
$this->emit('register', [$message]);
}
/**
* @param string $payload
*/
private function onPush($payload)
{
try {
$message = new Push($payload);
} catch (\Exception $e) {
$this->logger->warning(sprintf('Failed to decode push message: %s', $e->getMessage()), [$payload]);
return;
}
$this->emit('push', [$message]);
}
/**
* @param Message $message
*/
private function onMessage(Message $message)
{
$payload = @zlib_decode($message->getPayload());
if ($payload === false) {
$this->logger->warning('Failed to inflate a payload.');
return;
}
$topic = $this->unmapTopic($message->getTopic());
$this->logger->info(sprintf('Received a message from topic "%s".', $topic), [$payload]);
switch ($topic) {
case self::MESSAGE_TOPIC:
$this->onPush($payload);
break;
case self::REG_RESP_TOPIC:
$this->onRegister($payload);
break;
default:
$this->logger->warning(sprintf('Received a message from unknown topic "%s".', $topic), [$payload]);
}
}
/**
* Establishes a connection to the FBNS server.
*
* @param string $host
* @param int $port
* @param Connection $connection
* @param int $timeout
*
* @return PromiseInterface
*/
private function establishConnection($host, $port, Connection $connection, $timeout)
{
$this->logger->info(sprintf('Connecting to %s:%d...', $host, $port));
return $this->client->connect($host, $port, $connection, $timeout);
}
/**
* Connects to a FBNS server.
*
* @param string $host
* @param int $port
* @param Connection $connection
* @param int $timeout
*
* @return PromiseInterface
*/
public function connect($host, $port, Connection $connection, $timeout = 5)
{
$deferred = new Deferred();
$this->disconnect()
->then(function () use ($deferred, $host, $port, $connection, $timeout) {
$this->establishConnection($host, $port, $connection, $timeout)
->then(function () use ($deferred) {
$deferred->resolve($this);
})
->otherwise(function (\Exception $error) use ($deferred) {
$deferred->reject($error);
});
})
->otherwise(function () use ($deferred) {
$deferred->reject($this);
});
return $deferred->promise();
}
/**
* @return PromiseInterface
*/
public function disconnect()
{
if ($this->client->isConnected()) {
$deferred = new Deferred();
$this->client->disconnect()
->then(function () use ($deferred) {
$deferred->resolve($this);
})
->otherwise(function () use ($deferred) {
$deferred->reject($this);
});
return $deferred->promise();
} else {
return new FulfilledPromise($this);
}
}
/**
* Maps human readable topic to its ID.
*
* @param string $topic
*
* @return string
*/
private function mapTopic($topic)
{
if (array_key_exists($topic, self::TOPIC_TO_ID_ENUM)) {
$result = self::TOPIC_TO_ID_ENUM[$topic];
$this->logger->debug(sprintf('Topic "%s" has been mapped to "%s".', $topic, $result));
} else {
$result = $topic;
$this->logger->debug(sprintf('Topic "%s" does not exist in enum.', $topic));
}
return $result;
}
/**
* Maps topic ID to human readable name.
*
* @param string $topic
*
* @return string
*/
private function unmapTopic($topic)
{
if (array_key_exists($topic, self::ID_TO_TOPIC_ENUM)) {
$result = self::ID_TO_TOPIC_ENUM[$topic];
$this->logger->debug(sprintf('Topic ID "%s" has been unmapped to "%s".', $topic, $result));
} else {
$result = $topic;
$this->logger->debug(sprintf('Topic ID "%s" does not exist in enum.', $topic));
}
return $result;
}
/**
* Publish a message to a topic.
*
* @param string $topic
* @param string $message
* @param int $qosLevel
*
* @return \React\Promise\ExtendedPromiseInterface
*/
private function publish($topic, $message, $qosLevel)
{
$this->logger->info(sprintf('Sending message to topic "%s".', $topic), [$message]);
$topic = $this->mapTopic($topic);
$payload = zlib_encode($message, ZLIB_ENCODING_DEFLATE, 9);
return $this->client->publish(new DefaultMessage($topic, $payload, $qosLevel));
}
/**
* Registers an application.
*
* @param string $packageName
* @param string|int $appId
*
* @return PromiseInterface
*/
public function register($packageName, $appId)
{
$this->logger->info(sprintf('Registering application "%s" (%s).', $packageName, $appId));
$message = json_encode([
'pkg_name' => (string) $packageName,
'appid' => (string) $appId,
]);
return $this->publish(self::REG_REQ_TOPIC, $message, self::QOS_LEVEL);
}
/**
* Checks whether underlying client is connected.
*
* @return bool
*/
public function isConnected()
{
return $this->client->isConnected();
}
}

View File

@@ -0,0 +1,198 @@
<?php
/*
* This file is part of net-mqtt.
*
* Copyright (c) 2015 Sebastian Mößler code@binsoul.de
*
* This source file is subject to the MIT license.
*/
namespace Fbns\Client\Lite;
use BinSoul\Net\Mqtt\Packet;
use BinSoul\Net\Mqtt\Packet\BasePacket;
use BinSoul\Net\Mqtt\PacketStream;
/**
* Represents the CONNECT packet.
*/
class ConnectRequestPacket extends BasePacket
{
/** @var int */
private $protocolLevel = 3;
/** @var string */
private $protocolName = 'MQTToT';
/** @var int */
private $flags = 194;
/** @var int */
private $keepAlive = 900;
/** @var string */
private $payload;
protected static $packetType = Packet::TYPE_CONNECT;
public function read(PacketStream $stream)
{
parent::read($stream);
$this->assertPacketFlags(0);
$this->assertRemainingPacketLength();
$originalPosition = $stream->getPosition();
$this->protocolName = $stream->readString();
$this->protocolLevel = $stream->readByte();
$this->flags = $stream->readByte();
$this->keepAlive = $stream->readWord();
$payloadLength = $this->remainingPacketLength - ($stream->getPosition() - $originalPosition);
$this->payload = $stream->read($payloadLength);
}
public function write(PacketStream $stream)
{
$data = new PacketStream();
$data->writeString($this->protocolName);
$data->writeByte($this->protocolLevel);
$data->writeByte($this->flags);
$data->writeWord($this->keepAlive);
$data->write($this->payload);
$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) {
throw new \InvalidArgumentException(sprintf('Unknown protocol level %d.', $value));
}
$this->protocolLevel = $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;
}
/**
* Returns the flags.
*
* @return int
*/
public function getFlags()
{
return $this->flags;
}
/**
* Sets the flags.
*
* @param int $value
*
* @throws \InvalidArgumentException
*/
public function setFlags($value)
{
if ($value > 255) {
throw new \InvalidArgumentException(
sprintf(
'Expected a flags lower than 255 but got %d.',
$value
)
);
}
$this->flags = $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;
}
/**
* Returns the protocol name.
*
* @return string
*/
public function getProtocolName()
{
return $this->protocolName;
}
/**
* Sets the protocol name.
*
* @param string $value
*
* @throws \InvalidArgumentException
*/
public function setProtocolName($value)
{
$this->assertValidStringLength($value, false);
$this->protocolName = $value;
}
}

View File

@@ -0,0 +1,145 @@
<?php
/*
* This file is part of net-mqtt.
*
* Copyright (c) 2015 Sebastian Mößler code@binsoul.de
*
* This source file is subject to the MIT license.
*/
namespace Fbns\Client\Lite;
use BinSoul\Net\Mqtt\Packet;
use BinSoul\Net\Mqtt\Packet\BasePacket;
use BinSoul\Net\Mqtt\PacketStream;
/**
* 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;
/** @var string */
private $auth;
protected static $packetType = Packet::TYPE_CONNACK;
public function read(PacketStream $stream)
{
parent::read($stream);
$this->assertPacketFlags(0);
$this->assertRemainingPacketLength();
$originalPosition = $stream->getPosition();
$this->flags = $stream->readByte();
$this->returnCode = $stream->readByte();
$authLength = $this->remainingPacketLength - ($stream->getPosition() - $originalPosition);
if ($authLength) {
$this->auth = $stream->readString();
} else {
$this->auth = '';
}
}
public function write(PacketStream $stream)
{
$data = new PacketStream();
$data->writeByte($this->flags);
$data->writeByte($this->returnCode);
if ($this->auth !== null && strlen($this->auth)) {
$data->writeString($this->auth);
}
$this->remainingPacketLength = $data->length();
parent::write($stream);
$stream->write($data->getData());
}
/**
* 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;
}
/**
* @return string
*/
public function getAuth()
{
return $this->auth;
}
}

View File

@@ -0,0 +1,74 @@
<?php
/*
* This file is part of net-mqtt.
*
* Copyright (c) 2015 Sebastian Mößler code@binsoul.de
*
* This source file is subject to the MIT license.
*/
namespace Fbns\Client\Lite;
use BinSoul\Net\Mqtt\Flow\AbstractFlow;
use BinSoul\Net\Mqtt\Packet;
use BinSoul\Net\Mqtt\Packet\ConnectResponsePacket;
use Fbns\Client\Connection;
/**
* Represents a flow starting with an outgoing CONNECT packet.
*/
class OutgoingConnectFlow extends AbstractFlow
{
const PROTOCOL_LEVEL = 3;
const PROTOCOL_NAME = 'MQTToT';
const KEEPALIVE = 900;
const KEEPALIVE_TIMEOUT = 60;
/** @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 'connect';
}
public function start()
{
$packet = new ConnectRequestPacket();
$packet->setProtocolLevel(self::PROTOCOL_LEVEL);
$packet->setProtocolName(self::PROTOCOL_NAME);
$packet->setKeepAlive(self::KEEPALIVE);
$packet->setFlags(194);
$packet->setPayload(zlib_encode($this->connection->toThrift(), ZLIB_ENCODING_DEFLATE, 9));
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($packet);
} else {
$this->fail($packet->getErrorName());
}
}
}

View File

@@ -0,0 +1,74 @@
<?php
/*
* This file is part of net-mqtt.
*
* Copyright (c) 2015 Sebastian Mößler code@binsoul.de
*
* This source file is subject to the MIT license.
*/
namespace Fbns\Client\Lite;
use BinSoul\Net\Mqtt\Exception\UnknownPacketTypeException;
use BinSoul\Net\Mqtt\Packet;
use BinSoul\Net\Mqtt\Packet\DisconnectRequestPacket;
use BinSoul\Net\Mqtt\Packet\PingRequestPacket;
use BinSoul\Net\Mqtt\Packet\PingResponsePacket;
use BinSoul\Net\Mqtt\Packet\PublishCompletePacket;
use BinSoul\Net\Mqtt\Packet\PublishReceivedPacket;
use BinSoul\Net\Mqtt\Packet\PublishReleasePacket;
use BinSoul\Net\Mqtt\Packet\PublishRequestPacket;
use BinSoul\Net\Mqtt\Packet\SubscribeRequestPacket;
use BinSoul\Net\Mqtt\Packet\SubscribeResponsePacket;
use BinSoul\Net\Mqtt\Packet\UnsubscribeRequestPacket;
use BinSoul\Net\Mqtt\Packet\UnsubscribeResponsePacket;
use Fbns\Client\Common\PublishAckPacket;
/**
* 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();
}
}

120
vendor/valga/fbns-react/src/Lite/ReactFlow.php vendored Executable file
View File

@@ -0,0 +1,120 @@
<?php
/*
* This file is part of net-mqtt-client-react.
*
* Copyright (c) 2015 Sebastian Mößler code@binsoul.de
*
* This source file is subject to the MIT license.
*/
namespace Fbns\Client\Lite;
use BinSoul\Net\Mqtt\Flow;
use BinSoul\Net\Mqtt\Packet;
use React\Promise\Deferred;
/**
* Decorates flows with data required for the {@see ReactMqttClient} class.
*/
class ReactFlow implements Flow
{
/** @var Flow */
private $decorated;
/** @var Deferred */
private $deferred;
/** @var Packet */
private $packet;
/** @var bool */
private $isSilent;
/**
* Constructs an instance of this class.
*
* @param Flow $decorated
* @param Deferred $deferred
* @param Packet $packet
* @param bool $isSilent
*/
public function __construct(Flow $decorated, Deferred $deferred, Packet $packet = null, $isSilent = false)
{
$this->decorated = $decorated;
$this->deferred = $deferred;
$this->packet = $packet;
$this->isSilent = $isSilent;
}
public function getCode()
{
return $this->decorated->getCode();
}
public function start()
{
$this->packet = $this->decorated->start();
return $this->packet;
}
public function accept(Packet $packet)
{
return $this->decorated->accept($packet);
}
public function next(Packet $packet)
{
$this->packet = $this->decorated->next($packet);
return $this->packet;
}
public function isFinished()
{
return $this->decorated->isFinished();
}
public function isSuccess()
{
return $this->decorated->isSuccess();
}
public function getResult()
{
return $this->decorated->getResult();
}
public function getErrorMessage()
{
return $this->decorated->getErrorMessage();
}
/**
* Returns the associated deferred.
*
* @return Deferred
*/
public function getDeferred()
{
return $this->deferred;
}
/**
* Returns the current packet.
*
* @return Packet
*/
public function getPacket()
{
return $this->packet;
}
/**
* Indicates if the flow should emit events.
*
* @return bool
*/
public function isSilent()
{
return $this->isSilent;
}
}

View File

@@ -0,0 +1,695 @@
<?php
/*
* This file is part of net-mqtt-client-react.
*
* Copyright (c) 2015 Sebastian Mößler code@binsoul.de
*
* This source file is subject to the MIT license.
*/
namespace Fbns\Client\Lite;
use BinSoul\Net\Mqtt\DefaultConnection;
use BinSoul\Net\Mqtt\DefaultIdentifierGenerator;
use BinSoul\Net\Mqtt\DefaultMessage;
use BinSoul\Net\Mqtt\Flow;
use BinSoul\Net\Mqtt\Flow\IncomingPublishFlow;
use BinSoul\Net\Mqtt\Flow\OutgoingDisconnectFlow;
use BinSoul\Net\Mqtt\Flow\OutgoingPingFlow;
use BinSoul\Net\Mqtt\Flow\OutgoingPublishFlow;
use BinSoul\Net\Mqtt\Flow\OutgoingSubscribeFlow;
use BinSoul\Net\Mqtt\Flow\OutgoingUnsubscribeFlow;
use BinSoul\Net\Mqtt\IdentifierGenerator;
use BinSoul\Net\Mqtt\Message;
use BinSoul\Net\Mqtt\Packet;
use BinSoul\Net\Mqtt\Packet\PublishRequestPacket;
use BinSoul\Net\Mqtt\StreamParser;
use BinSoul\Net\Mqtt\Subscription;
use Evenement\EventEmitter;
use Fbns\Client\Connection;
use React\EventLoop\LoopInterface;
use React\EventLoop\Timer\TimerInterface;
use React\Promise\Deferred;
use React\Promise\ExtendedPromiseInterface;
use React\Promise\RejectedPromise;
use React\Socket\ConnectorInterface;
use React\Stream\DuplexStreamInterface;
/**
* Connects to a MQTT broker and subscribes to topics or publishes messages.
*
* The following events are emitted:
* - open - The network connection to the server is established.
* - close - The network connection to the server is closed.
* - warning - An event of severity "warning" occurred.
* - error - An event of severity "error" occurred.
*
* If a flow finishes it's result is also emitted, e.g.:
* - connect - The client connected to the broker.
* - disconnect - The client disconnected from the broker.
* - subscribe - The client subscribed to a topic filter.
* - unsubscribe - The client unsubscribed from topic filter.
* - publish - A message was published.
* - message - A message was received.
*/
class ReactMqttClient extends EventEmitter
{
/** @var ConnectorInterface */
private $connector;
/** @var LoopInterface */
private $loop;
/** @var DuplexStreamInterface */
private $stream;
/** @var StreamParser */
private $parser;
/** @var IdentifierGenerator */
private $identifierGenerator;
/** @var string */
private $host;
/** @var int */
private $port;
/** @var Connection */
private $connection;
/** @var bool */
private $isConnected = false;
/** @var bool */
private $isConnecting = false;
/** @var bool */
private $isDisconnecting = false;
/** @var TimerInterface[] */
private $timer = [];
/** @var ReactFlow[] */
private $receivingFlows = [];
/** @var ReactFlow[] */
private $sendingFlows = [];
/** @var ReactFlow */
private $writtenFlow;
/**
* Constructs an instance of this class.
*
* @param ConnectorInterface $connector
* @param LoopInterface $loop
* @param IdentifierGenerator $identifierGenerator
* @param StreamParser $parser
*/
public function __construct(
ConnectorInterface $connector,
LoopInterface $loop,
IdentifierGenerator $identifierGenerator = null,
StreamParser $parser = null
) {
$this->connector = $connector;
$this->loop = $loop;
$this->parser = $parser;
if ($this->parser === null) {
$this->parser = new StreamParser();
}
$this->parser->onError(function (\Exception $e) {
$this->emitWarning($e);
});
$this->identifierGenerator = $identifierGenerator;
if ($this->identifierGenerator === null) {
$this->identifierGenerator = new DefaultIdentifierGenerator();
}
}
/**
* Return the host.
*
* @return string
*/
public function getHost()
{
return $this->host;
}
/**
* Return the port.
*
* @return string
*/
public function getPort()
{
return $this->port;
}
/**
* Indicates if the client is connected.
*
* @return bool
*/
public function isConnected()
{
return $this->isConnected;
}
/**
* Returns the underlying stream or null if the client is not connected.
*
* @return DuplexStreamInterface|null
*/
public function getStream()
{
return $this->stream;
}
/**
* Connects to a broker.
*
* @param string $host
* @param int $port
* @param Connection $connection
* @param int $timeout
*
* @return ExtendedPromiseInterface
*/
public function connect($host, $port, Connection $connection, $timeout = 5)
{
if ($this->isConnected || $this->isConnecting) {
return new RejectedPromise(new \LogicException('The client is already connected.'));
}
$this->isConnecting = true;
$this->isConnected = false;
$this->host = $host;
$this->port = $port;
$deferred = new Deferred();
$this->establishConnection($this->host, $this->port, $timeout)
->then(function (DuplexStreamInterface $stream) use ($connection, $deferred, $timeout) {
$this->stream = $stream;
$this->emit('open', [$connection, $this]);
$this->registerClient($connection, $timeout)
->then(function (ConnectResponsePacket $responsePacket) use ($deferred, $connection) {
$this->isConnecting = false;
$this->isConnected = true;
$this->connection = $connection;
$this->emit('connect', [$responsePacket, $this]);
$deferred->resolve($responsePacket);
})
->otherwise(function (\Exception $e) use ($deferred, $connection) {
$this->isConnecting = false;
$this->emitError($e);
$deferred->reject($e);
if ($this->stream !== null) {
$this->stream->close();
}
$this->emit('close', [$connection, $this]);
});
})
->otherwise(function (\Exception $e) use ($deferred) {
$this->isConnecting = false;
$this->emitError($e);
$deferred->reject($e);
});
return $deferred->promise();
}
/**
* Disconnects from a broker.
*
* @return ExtendedPromiseInterface
*/
public function disconnect()
{
if (!$this->isConnected || $this->isDisconnecting) {
return new RejectedPromise(new \LogicException('The client is not connected.'));
}
$this->isDisconnecting = true;
$deferred = new Deferred();
$connection = new DefaultConnection();
$this->startFlow(new OutgoingDisconnectFlow($connection), true)
->then(function () use ($connection, $deferred) {
$this->isDisconnecting = false;
$this->isConnected = false;
$this->emit('disconnect', [$connection, $this]);
$deferred->resolve($connection);
if ($this->stream !== null) {
$this->stream->close();
}
})
->otherwise(function () use ($deferred) {
$this->isDisconnecting = false;
$deferred->reject($this->connection);
});
return $deferred->promise();
}
/**
* Subscribes to a topic filter.
*
* @param Subscription $subscription
*
* @return ExtendedPromiseInterface
*/
public function subscribe(Subscription $subscription)
{
if (!$this->isConnected) {
return new RejectedPromise(new \LogicException('The client is not connected.'));
}
return $this->startFlow(new OutgoingSubscribeFlow([$subscription], $this->identifierGenerator));
}
/**
* Unsubscribes from a topic filter.
*
* @param Subscription $subscription
*
* @return ExtendedPromiseInterface
*/
public function unsubscribe(Subscription $subscription)
{
if (!$this->isConnected) {
return new RejectedPromise(new \LogicException('The client is not connected.'));
}
return $this->startFlow(new OutgoingUnsubscribeFlow([$subscription], $this->identifierGenerator));
}
/**
* Publishes a message.
*
* @param Message $message
*
* @return ExtendedPromiseInterface
*/
public function publish(Message $message)
{
if (!$this->isConnected) {
return new RejectedPromise(new \LogicException('The client is not connected.'));
}
return $this->startFlow(new OutgoingPublishFlow($message, $this->identifierGenerator));
}
/**
* Calls the given generator periodically and publishes the return value.
*
* @param int $interval
* @param Message $message
* @param callable $generator
*
* @return ExtendedPromiseInterface
*/
public function publishPeriodically($interval, Message $message, callable $generator)
{
if (!$this->isConnected) {
return new RejectedPromise(new \LogicException('The client is not connected.'));
}
$deferred = new Deferred();
$this->timer[] = $this->loop->addPeriodicTimer(
$interval,
function () use ($message, $generator, $deferred) {
$this->publish($message->withPayload($generator($message->getTopic())))->then(
function ($value) use ($deferred) {
$deferred->notify($value);
},
function (\Exception $e) use ($deferred) {
$deferred->reject($e);
}
);
}
);
return $deferred->promise();
}
/**
* Emits warnings.
*
* @param \Exception $e
*/
private function emitWarning(\Exception $e)
{
$this->emit('warning', [$e, $this]);
}
/**
* Emits errors.
*
* @param \Exception $e
*/
private function emitError(\Exception $e)
{
$this->emit('error', [$e, $this]);
}
/**
* Establishes a network connection to a server.
*
* @param string $host
* @param int $port
* @param int $timeout
*
* @return ExtendedPromiseInterface
*/
private function establishConnection($host, $port, $timeout)
{
$deferred = new Deferred();
$timer = $this->loop->addTimer(
$timeout,
function () use ($deferred, $timeout) {
$exception = new \RuntimeException(sprintf('Connection timed out after %d seconds.', $timeout));
$deferred->reject($exception);
}
);
$this->connector->connect($host.':'.$port)
->always(function () use ($timer) {
$this->loop->cancelTimer($timer);
})
->then(function (DuplexStreamInterface $stream) use ($deferred) {
$stream->on('data', function ($data) {
$this->handleReceive($data);
});
$stream->on('close', function () {
$this->handleClose();
});
$stream->on('error', function (\Exception $e) {
$this->handleError($e);
});
$deferred->resolve($stream);
})
->otherwise(function (\Exception $e) use ($deferred) {
$deferred->reject($e);
});
return $deferred->promise();
}
/**
* Registers a new client with the broker.
*
* @param Connection $connection
* @param int $timeout
*
* @return ExtendedPromiseInterface
*/
private function registerClient(Connection $connection, $timeout)
{
$deferred = new Deferred();
$responseTimer = $this->loop->addTimer(
$timeout,
function () use ($deferred, $timeout) {
$exception = new \RuntimeException(sprintf('No response after %d seconds.', $timeout));
$deferred->reject($exception);
}
);
$this->startFlow(new OutgoingConnectFlow($connection), true)
->always(function () use ($responseTimer) {
$this->loop->cancelTimer($responseTimer);
})->then(function (ConnectResponsePacket $responsePacket) use ($deferred) {
$this->timer[] = $this->loop->addPeriodicTimer(
OutgoingConnectFlow::KEEPALIVE - OutgoingConnectFlow::KEEPALIVE_TIMEOUT,
function () {
$this->startFlow(new OutgoingPingFlow());
}
);
$deferred->resolve($responsePacket);
})->otherwise(function (\Exception $e) use ($deferred) {
$deferred->reject($e);
});
return $deferred->promise();
}
/**
* Handles incoming data.
*
* @param string $data
*/
private function handleReceive($data)
{
if (!$this->isConnected && !$this->isConnecting) {
return;
}
$flowCount = count($this->receivingFlows);
$packets = $this->parser->push($data);
foreach ($packets as $packet) {
$this->handlePacket($packet);
}
if ($flowCount > count($this->receivingFlows)) {
$this->receivingFlows = array_values($this->receivingFlows);
}
$this->handleSend();
}
/**
* Handles an incoming packet.
*
* @param Packet $packet
*/
private function handlePacket(Packet $packet)
{
switch ($packet->getPacketType()) {
case Packet::TYPE_PUBLISH:
/* @var PublishRequestPacket $packet */
$message = new DefaultMessage(
$packet->getTopic(),
$packet->getPayload(),
$packet->getQosLevel(),
$packet->isRetained(),
$packet->isDuplicate()
);
$this->startFlow(new IncomingPublishFlow($message, $packet->getIdentifier()));
break;
case Packet::TYPE_CONNACK:
case Packet::TYPE_PINGRESP:
case Packet::TYPE_SUBACK:
case Packet::TYPE_UNSUBACK:
case Packet::TYPE_PUBREL:
case Packet::TYPE_PUBACK:
case Packet::TYPE_PUBREC:
case Packet::TYPE_PUBCOMP:
$flowFound = false;
foreach ($this->receivingFlows as $index => $flow) {
if ($flow->accept($packet)) {
$flowFound = true;
unset($this->receivingFlows[$index]);
$this->continueFlow($flow, $packet);
break;
}
}
if (!$flowFound) {
$this->emitWarning(
new \LogicException(sprintf('Received unexpected packet of type %d.', $packet->getPacketType()))
);
}
break;
default:
$this->emitWarning(
new \LogicException(sprintf('Cannot handle packet of type %d.', $packet->getPacketType()))
);
}
}
/**
* Handles outgoing packets.
*/
private function handleSend()
{
$flow = null;
if ($this->writtenFlow !== null) {
$flow = $this->writtenFlow;
$this->writtenFlow = null;
}
if (count($this->sendingFlows) > 0) {
$this->writtenFlow = array_shift($this->sendingFlows);
$this->stream->write($this->writtenFlow->getPacket());
}
if ($flow !== null) {
if ($flow->isFinished()) {
$this->loop->nextTick(function () use ($flow) {
$this->finishFlow($flow);
});
} else {
$this->receivingFlows[] = $flow;
}
}
}
/**
* Handles closing of the stream.
*/
private function handleClose()
{
foreach ($this->timer as $timer) {
$this->loop->cancelTimer($timer);
}
$this->timer = [];
$this->cleanPreviousSession();
$connection = $this->connection;
$this->isConnecting = false;
$this->isDisconnecting = false;
$this->isConnected = false;
$this->connection = null;
$this->stream = null;
if ($connection !== null) {
$this->emit('close', [$connection, $this]);
}
}
/**
* Handles errors of the stream.
*
* @param \Exception $e
*/
private function handleError(\Exception $e)
{
$this->emitError($e);
}
/**
* Starts the given flow.
*
* @param Flow $flow
* @param bool $isSilent
*
* @return ExtendedPromiseInterface
*/
private function startFlow(Flow $flow, $isSilent = false)
{
try {
$packet = $flow->start();
} catch (\Exception $e) {
$this->emitError($e);
return new RejectedPromise($e);
}
$deferred = new Deferred();
$internalFlow = new ReactFlow($flow, $deferred, $packet, $isSilent);
if ($packet !== null) {
if ($this->writtenFlow !== null) {
$this->sendingFlows[] = $internalFlow;
} else {
$this->stream->write($packet);
$this->writtenFlow = $internalFlow;
$this->handleSend();
}
} else {
$this->loop->nextTick(function () use ($internalFlow) {
$this->finishFlow($internalFlow);
});
}
return $deferred->promise();
}
/**
* Continues the given flow.
*
* @param ReactFlow $flow
* @param Packet $packet
*/
private function continueFlow(ReactFlow $flow, Packet $packet)
{
try {
$response = $flow->next($packet);
} catch (\Exception $e) {
$this->emitError($e);
return;
}
if ($response !== null) {
if ($this->writtenFlow !== null) {
$this->sendingFlows[] = $flow;
} else {
$this->stream->write($response);
$this->writtenFlow = $flow;
$this->handleSend();
}
} elseif ($flow->isFinished()) {
$this->loop->nextTick(function () use ($flow) {
$this->finishFlow($flow);
});
}
}
/**
* Finishes the given flow.
*
* @param ReactFlow $flow
*/
private function finishFlow(ReactFlow $flow)
{
if ($flow->isSuccess()) {
if (!$flow->isSilent()) {
$this->emit($flow->getCode(), [$flow->getResult(), $this]);
}
$flow->getDeferred()->resolve($flow->getResult());
} else {
$result = new \RuntimeException($flow->getErrorMessage());
$this->emitWarning($result);
$flow->getDeferred()->reject($result);
}
}
/**
* Cleans previous session by rejecting all pending flows.
*/
private function cleanPreviousSession()
{
$error = new \RuntimeException('Connection has been closed.');
foreach ($this->receivingFlows as $receivingFlow) {
$receivingFlow->getDeferred()->reject($error);
}
$this->receivingFlows = [];
foreach ($this->sendingFlows as $sendingFlow) {
$sendingFlow->getDeferred()->reject($error);
}
$this->sendingFlows = [];
}
}

View File

@@ -0,0 +1,102 @@
<?php
/*
* This file is part of net-mqtt.
*
* Copyright (c) 2015 Sebastian Mößler code@binsoul.de
*
* This source file is subject to the MIT license.
*/
namespace Fbns\Client\Lite;
use BinSoul\Net\Mqtt\Exception\EndOfStreamException;
use BinSoul\Net\Mqtt\Exception\MalformedPacketException;
use BinSoul\Net\Mqtt\Exception\UnknownPacketTypeException;
use BinSoul\Net\Mqtt\Packet;
use BinSoul\Net\Mqtt\PacketStream;
use BinSoul\Net\Mqtt\StreamParser as BaseStreamParser;
/**
* Provides methods to parse a stream of bytes into packets.
*/
class StreamParser extends BaseStreamParser
{
/** @var PacketStream */
private $buffer;
/** @var PacketFactory */
private $factory;
/** @var callable */
private $errorCallback;
/**
* Constructs an instance of this class.
*/
public function __construct()
{
parent::__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);
}
}
}

153
vendor/valga/fbns-react/src/Message/Push.php vendored Executable file
View File

@@ -0,0 +1,153 @@
<?php
namespace Fbns\Client\Message;
use Fbns\Client\Json;
class Push
{
/**
* @var string
*/
private $json;
/**
* @var string
*/
private $token;
/**
* @var string
*/
private $connectionKey;
/**
* @var string
*/
private $packageName;
/**
* @var string
*/
private $collapseKey;
/**
* @var string
*/
private $payload;
/**
* @var string
*/
private $notificationId;
/**
* @var string
*/
private $isBuffered;
/**
* @param string $json
*/
private function parseJson($json)
{
$data = Json::decode($json);
$this->json = $json;
if (isset($data->token)) {
$this->token = (string) $data->token;
}
if (isset($data->ck)) {
$this->connectionKey = (string) $data->ck;
}
if (isset($data->pn)) {
$this->packageName = (string) $data->pn;
}
if (isset($data->cp)) {
$this->collapseKey = (string) $data->cp;
}
if (isset($data->fbpushnotif)) {
$this->payload = (string) $data->fbpushnotif;
}
if (isset($data->nid)) {
$this->notificationId = (string) $data->nid;
}
if (isset($data->bu)) {
$this->isBuffered = (string) $data->bu;
}
}
/**
* Message constructor.
*
* @param string $json
*/
public function __construct($json)
{
$this->parseJson($json);
}
/**
* @return string
*/
public function __toString()
{
return $this->json;
}
/**
* @return string
*/
public function getToken()
{
return $this->token;
}
/**
* @return string
*/
public function getConnectionKey()
{
return $this->connectionKey;
}
/**
* @return string
*/
public function getPackageName()
{
return $this->packageName;
}
/**
* @return string
*/
public function getCollapseKey()
{
return $this->collapseKey;
}
/**
* @return string
*/
public function getPayload()
{
return $this->payload;
}
/**
* @return string
*/
public function getNotificationId()
{
return $this->notificationId;
}
/**
* @return string
*/
public function getIsBuffered()
{
return $this->isBuffered;
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace Fbns\Client\Message;
use Fbns\Client\Json;
class Register
{
/**
* @var string
*/
private $json;
/**
* @var string
*/
private $packageName;
/**
* @var string
*/
private $token;
/**
* @var string
*/
private $error;
/**
* @param string $json
*/
private function parseJson($json)
{
$data = Json::decode($json);
$this->json = $json;
if (isset($data->pkg_name)) {
$this->packageName = (string) $data->pkg_name;
}
if (isset($data->token)) {
$this->token = (string) $data->token;
}
if (isset($data->error)) {
$this->error = (string) $data->error;
}
}
/**
* Message constructor.
*
* @param string $json
*/
public function __construct($json)
{
$this->parseJson($json);
}
/**
* @return string
*/
public function __toString()
{
return $this->json;
}
/**
* @return string
*/
public function getPackageName()
{
return $this->packageName;
}
/**
* @return string
*/
public function getToken()
{
return $this->token;
}
/**
* @return string
*/
public function getError()
{
return $this->error;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Fbns\Client\Thrift;
/**
* @see https://thrift.apache.org/
*/
class Compact
{
const TYPE_STOP = 0x00;
const TYPE_TRUE = 0x01;
const TYPE_FALSE = 0x02;
const TYPE_BYTE = 0x03;
const TYPE_I16 = 0x04;
const TYPE_I32 = 0x05;
const TYPE_I64 = 0x06;
const TYPE_DOUBLE = 0x07;
const TYPE_BINARY = 0x08;
const TYPE_LIST = 0x09;
const TYPE_SET = 0x0A;
const TYPE_MAP = 0x0B;
const TYPE_STRUCT = 0x0C;
const TYPE_FLOAT = 0x0D;
}

52
vendor/valga/fbns-react/src/Thrift/Debug.php vendored Executable file
View File

@@ -0,0 +1,52 @@
<?php
namespace Fbns\Client\Thrift;
class Debug extends Reader
{
/**
* @param string $context
* @param int $field
* @param mixed $value
* @param int $type
*/
private function handler($context, $field, $value, $type)
{
if (strlen($context)) {
$field = $context.'/'.$field;
}
if (is_bool($value)) {
$value = $value ? 'true' : 'false';
} elseif (is_array($value)) {
$value = array_map(function ($value) {
if (is_bool($value)) {
$value = $value ? 'true' : 'false';
} elseif (is_string($value)) {
$value = '"'.$value.'"';
} else {
$value = (string) $value;
}
return $value;
}, $value);
$value = '['.implode(', ', $value).']';
} elseif (is_string($value)) {
$value = '"'.$value.'"';
} else {
$value = (string) $value;
}
printf('%s (%02x): %s%s', $field, $type, $value, PHP_EOL);
}
/**
* Debug constructor.
*
* @param string $buffer
*/
public function __construct($buffer = '')
{
parent::__construct($buffer, function ($context, $field, $value, $type) {
$this->handler($context, $field, $value, $type);
});
}
}

263
vendor/valga/fbns-react/src/Thrift/Reader.php vendored Executable file
View File

@@ -0,0 +1,263 @@
<?php
namespace Fbns\Client\Thrift;
/**
* WARNING: This implementation is not complete.
*
* @see https://thrift.apache.org/
*/
class Reader
{
/**
* @var int[]
*/
private $stack;
/**
* @var int
*/
private $field;
/**
* @var string
*/
private $buffer;
/**
* @var int
*/
private $length;
/**
* @var int
*/
private $position;
/**
* @var callable|null
*/
private $handler;
/**
* Reader constructor.
*
* @param string $buffer
* @param callable|null $handler
*/
public function __construct($buffer = '', callable $handler = null)
{
if (PHP_INT_SIZE === 4 && !extension_loaded('gmp')) {
throw new \RuntimeException('You need to install GMP extension to run this code with x86 PHP build.');
}
$this->buffer = $buffer;
$this->position = 0;
$this->length = strlen($buffer);
$this->field = 0;
$this->stack = [];
$this->handler = $handler;
$this->parse();
}
/**
* Parser.
*/
private function parse()
{
$context = '';
while ($this->position < $this->length) {
$type = $this->readField();
switch ($type) {
case Compact::TYPE_STRUCT:
array_push($this->stack, $this->field);
$this->field = 0;
$context = implode('/', $this->stack);
break;
case Compact::TYPE_STOP:
if (!count($this->stack)) {
return;
}
$this->field = array_pop($this->stack);
$context = implode('/', $this->stack);
break;
case Compact::TYPE_LIST:
$sizeAndType = $this->readUnsignedByte();
$size = $sizeAndType >> 4;
$listType = $sizeAndType & 0x0f;
if ($size === 0x0f) {
$size = $this->readVarint();
}
$this->handleField($context, $this->field, $this->readList($size, $listType), $listType);
break;
case Compact::TYPE_TRUE:
case Compact::TYPE_FALSE:
$this->handleField($context, $this->field, $type === Compact::TYPE_TRUE, $type);
break;
case Compact::TYPE_BYTE:
$this->handleField($context, $this->field, $this->readSignedByte(), $type);
break;
case Compact::TYPE_I16:
case Compact::TYPE_I32:
case Compact::TYPE_I64:
$this->handleField($context, $this->field, $this->fromZigZag($this->readVarint()), $type);
break;
case Compact::TYPE_BINARY:
$this->handleField($context, $this->field, $this->readString($this->readVarint()), $type);
break;
}
}
}
/**
* @param int $size
* @param int $type
*
* @return array
*/
private function readList($size, $type)
{
$result = [];
switch ($type) {
case Compact::TYPE_TRUE:
case Compact::TYPE_FALSE:
for ($i = 0; $i < $size; $i++) {
$result[] = $this->readSignedByte() === Compact::TYPE_TRUE;
}
break;
case Compact::TYPE_BYTE:
for ($i = 0; $i < $size; $i++) {
$result[] = $this->readSignedByte();
}
break;
case Compact::TYPE_I16:
case Compact::TYPE_I32:
case Compact::TYPE_I64:
for ($i = 0; $i < $size; $i++) {
$result[] = $this->fromZigZag($this->readVarint());
}
break;
case Compact::TYPE_BINARY:
$result[] = $this->readString($this->readVarint());
break;
}
return $result;
}
/**
* @return int
*/
private function readField()
{
$typeAndDelta = ord($this->buffer[$this->position++]);
if ($typeAndDelta === Compact::TYPE_STOP) {
return Compact::TYPE_STOP;
}
$delta = $typeAndDelta >> 4;
if ($delta === 0) {
$this->field = $this->fromZigZag($this->readVarint());
} else {
$this->field += $delta;
}
$type = $typeAndDelta & 0x0f;
return $type;
}
/**
* @return int
*/
private function readSignedByte()
{
$result = $this->readUnsignedByte();
if ($result > 0x7f) {
$result = 0 - (($result - 1) ^ 0xff);
}
return $result;
}
/**
* @return int
*/
private function readUnsignedByte()
{
return ord($this->buffer[$this->position++]);
}
/**
* @return int
*/
private function readVarint()
{
$shift = 0;
$result = 0;
if (PHP_INT_SIZE === 4) {
$result = gmp_init($result, 10);
}
while ($this->position < $this->length) {
$byte = ord($this->buffer[$this->position++]);
if (PHP_INT_SIZE === 4) {
$byte = gmp_init($byte, 10);
}
$result |= ($byte & 0x7f) << $shift;
if (PHP_INT_SIZE === 4) {
$byte = (int) gmp_strval($byte, 10);
}
if ($byte >> 7 === 0) {
break;
}
$shift += 7;
}
if (PHP_INT_SIZE === 4) {
$result = gmp_strval($result, 10);
}
return $result;
}
/**
* @param int $n
*
* @return int
*/
private function fromZigZag($n)
{
if (PHP_INT_SIZE === 4) {
$n = gmp_init($n, 10);
}
$result = ($n >> 1) ^ -($n & 1);
if (PHP_INT_SIZE === 4) {
$result = gmp_strval($result, 10);
}
return $result;
}
/**
* @param int $length
*
* @return string
*/
private function readString($length)
{
$result = substr($this->buffer, $this->position, $length);
$this->position += $length;
return $result;
}
/**
* @param string $context
* @param int $field
* @param mixed $value
* @param int $type
*/
private function handleField($context, $field, $value, $type)
{
if (!is_callable($this->handler)) {
return;
}
call_user_func($this->handler, $context, $field, $value, $type);
}
}

279
vendor/valga/fbns-react/src/Thrift/Writer.php vendored Executable file
View File

@@ -0,0 +1,279 @@
<?php
namespace Fbns\Client\Thrift;
/**
* WARNING: This implementation is not complete.
*
* @see https://thrift.apache.org/
*/
class Writer
{
/**
* @var string
*/
private $buffer;
/**
* @var int
*/
private $field;
/**
* @var int[]
*/
private $stack;
/**
* @param int $number
* @param int $bits
*
* @return int
*/
private function toZigZag($number, $bits)
{
if (PHP_INT_SIZE === 4) {
$number = gmp_init($number, 10);
}
$result = ($number << 1) ^ ($number >> ($bits - 1));
if (PHP_INT_SIZE === 4) {
$result = gmp_strval($result, 10);
}
return $result;
}
/**
* @param int $number
*/
private function writeByte($number)
{
$this->buffer .= chr($number);
}
/**
* @param int $number
*/
private function writeWord($number)
{
$this->writeVarint($this->toZigZag($number, 16));
}
/**
* @param int $number
*/
private function writeInt($number)
{
$this->writeVarint($this->toZigZag($number, 32));
}
/**
* @param int $number
*/
private function writeLongInt($number)
{
$this->writeVarint($this->toZigZag($number, 64));
}
/**
* @param int $field
* @param int $type
*/
private function writeField($field, $type)
{
$delta = $field - $this->field;
if ((0 < $delta) && ($delta <= 15)) {
$this->writeByte(($delta << 4) | $type);
} else {
$this->writeByte($type);
$this->writeWord($field);
}
$this->field = $field;
}
/**
* @param int $number
*/
private function writeVarint($number)
{
if (PHP_INT_SIZE === 4) {
$number = gmp_init($number, 10);
}
while (true) {
$byte = $number & (~0x7f);
if (PHP_INT_SIZE === 4) {
$byte = (int) gmp_strval($byte, 10);
}
if ($byte === 0) {
if (PHP_INT_SIZE === 4) {
$number = (int) gmp_strval($number, 10);
}
$this->buffer .= chr($number);
break;
} else {
$byte = ($number & 0xff) | 0x80;
if (PHP_INT_SIZE === 4) {
$byte = (int) gmp_strval($byte, 10);
}
$this->buffer .= chr($byte);
$number = $number >> 7;
}
}
}
/**
* @param string $data
*/
private function writeBinary($data)
{
$this->buffer .= $data;
}
/**
* @param int $field
* @param bool $value
*/
public function writeBool($field, $value)
{
$this->writeField($field, $value ? Compact::TYPE_TRUE : Compact::TYPE_FALSE);
}
/**
* @param int $field
* @param string $string
*/
public function writeString($field, $string)
{
$this->writeField($field, Compact::TYPE_BINARY);
$this->writeVarint(strlen($string));
$this->writeBinary($string);
}
public function writeStop()
{
$this->buffer .= chr(Compact::TYPE_STOP);
if (count($this->stack)) {
$this->field = array_pop($this->stack);
}
}
/**
* @param int $field
* @param int $number
*/
public function writeInt8($field, $number)
{
$this->writeField($field, Compact::TYPE_BYTE);
$this->writeByte($number);
}
/**
* @param int $field
* @param int $number
*/
public function writeInt16($field, $number)
{
$this->writeField($field, Compact::TYPE_I16);
$this->writeWord($number);
}
/**
* @param int $field
* @param int $number
*/
public function writeInt32($field, $number)
{
$this->writeField($field, Compact::TYPE_I32);
$this->writeInt($number);
}
/**
* @param int $field
* @param int $number
*/
public function writeInt64($field, $number)
{
$this->writeField($field, Compact::TYPE_I64);
$this->writeLongInt($number);
}
/**
* @param int $field
* @param int $type
* @param array $list
*/
public function writeList($field, $type, array $list)
{
$this->writeField($field, Compact::TYPE_LIST);
$size = count($list);
if ($size < 0x0f) {
$this->writeByte(($size << 4) | $type);
} else {
$this->writeByte(0xf0 | $type);
$this->writeVarint($size);
}
switch ($type) {
case Compact::TYPE_TRUE:
case Compact::TYPE_FALSE:
foreach ($list as $value) {
$this->writeByte($value ? Compact::TYPE_TRUE : Compact::TYPE_FALSE);
}
break;
case Compact::TYPE_BYTE:
foreach ($list as $number) {
$this->writeByte($number);
}
break;
case Compact::TYPE_I16:
foreach ($list as $number) {
$this->writeWord($number);
}
break;
case Compact::TYPE_I32:
foreach ($list as $number) {
$this->writeInt($number);
}
break;
case Compact::TYPE_I64:
foreach ($list as $number) {
$this->writeLongInt($number);
}
break;
case Compact::TYPE_BINARY:
foreach ($list as $string) {
$this->writeVarint(strlen($string));
$this->writeBinary($string);
}
break;
}
}
/**
* @param int $field
*/
public function writeStruct($field)
{
$this->writeField($field, Compact::TYPE_STRUCT);
$this->stack[] = $this->field;
$this->field = 0;
}
public function __construct()
{
if (PHP_INT_SIZE === 4 && !extension_loaded('gmp')) {
throw new \RuntimeException('You need to install GMP extension to run this code with x86 PHP build.');
}
$this->buffer = '';
$this->field = 0;
$this->stack = [];
}
/**
* @return string
*/
public function __toString()
{
return $this->buffer;
}
}