<commit_before><?php

declare(strict_types=1);

namespace unreal4u\MQTT;

use unreal4u\MQTT\Application\EmptyWritableResponse;
use unreal4u\MQTT\Exceptions\NotConnected;
use unreal4u\MQTT\Exceptions\ServerClosedConnection;
use unreal4u\MQTT\Internals\ClientInterface;
use unreal4u\MQTT\Internals\ProtocolBase;
use unreal4u\MQTT\Internals\ReadableContentInterface;
use unreal4u\MQTT\Internals\WritableContentInterface;
use unreal4u\MQTT\Protocol\Connect;
use unreal4u\MQTT\Protocol\Disconnect;

/**
 * Class Client
 * @package unreal4u\MQTT
 */
final class Client extends ProtocolBase implements ClientInterface
{
    /**
     * Where all the magic happens
     * @var Resource
     */
    private $socket;

    /**
     * Fast way to know whether we are connected or not
     * @var bool
     */
    private $isConnected = false;

    /**
     * Fast way to know whether we are currently in locked mode or not
     * @var bool
     */
    private $isCurrentlyLocked = false;

    /**
     * Annotates the last time there was known to be communication with the MQTT server
     * @var \DateTimeImmutable
     */
    private $lastCommunication;

    /**
     * Internal holder of connection parameters
     * @var Connect\Parameters
     */
    private $connectionParameters;

    /**
     * Temporary holder for async requests so that they can be handled synchronously
     * @var WritableContentInterface[]
     */
    private $objectStack = [];

    /**
     * @inheritdoc
     * @throws \unreal4u\MQTT\Exceptions\NotConnected
     * @throws \unreal4u\MQTT\Exceptions\Connect\NoConnectionParametersDefined
     * @throws \unreal4u\MQTT\Exceptions\ServerClosedConnection
     */
    public function __destruct()
    {
        if ($this->socket !== null) {
            $this->logger->info('Currently connected to broker, disconnecting from it');

            $this->processObject(new Disconnect($this->logger));
        }
    }

    /**
     * @inheritdoc
     */
    public function shutdownConnection(): bool
    {
        return stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);
    }

    /**
     * @inheritdoc
     */
    public function readBrokerData(int $bytes): string
    {
        $this->logger->debug('Reading bytes from socket', [
            'numberOfBytes' => $bytes,
            'isLocked' => $this->isCurrentlyLocked,
        ]);
        return fread($this->socket, $bytes);
    }

    /**
     * @inheritdoc
     */
    public function readBrokerHeader(): string
    {
        $this->logger->debug('Reading header from response');
        return $this->readBrokerData(4);
    }

    /**
     * @inheritdoc
     * @throws \unreal4u\MQTT\Exceptions\ServerClosedConnection
     * @throws \unreal4u\MQTT\Exceptions\NotConnected
     */
    public function sendBrokerData(WritableContentInterface $object): string
    {
        if ($this->socket === null) {
            $this->logger->alert('Not connected before sending data');
            throw new NotConnected('Please connect before performing any other request');
        }

        $writableString = $object->createSendableMessage();
        $sizeOfString = \strlen($writableString);
        $writtenBytes = fwrite($this->socket, $writableString, $sizeOfString);
        // $this->logger->debug('Sent string', ['binaryString' => str2bin($writableString)]); // Handy for debugging
        if ($writtenBytes !== $sizeOfString) {
            $this->logger->error('Written bytes do NOT correspond with size of string!', [
                'writtenBytes' => $writtenBytes,
                'sizeOfString' => $sizeOfString,
            ]);

            throw new ServerClosedConnection('The server may have disconnected the current client');
        }
        $this->logger->debug('Sent data to socket', ['writtenBytes' => $writtenBytes, 'sizeOfString' => $sizeOfString]);

        $returnValue = '';
        if ($object->shouldExpectAnswer() === true) {
            $this->enableSynchronousTransfer(true);
            $returnValue = $this->readBrokerHeader();
            $this->enableSynchronousTransfer(false);
        }

        return $returnValue;
    }

    /**
     * Special handling of the connect part: create the socket
     *
     * @param Connect $connection
     * @return bool
     * @throws \unreal4u\MQTT\Exceptions\Connect\NoConnectionParametersDefined
     */
    private function generateSocketConnection(Connect $connection): bool
    {
        $this->logger->debug('Creating socket connection');
        $this->connectionParameters = $connection->getConnectionParameters();
        $this->socket = stream_socket_client(
            $this->connectionParameters->getConnectionUrl(),
            $errorNumber,
            $errorString,
            60,
            STREAM_CLIENT_CONNECT
        );

        stream_set_timeout($this->socket, (int)floor($this->connectionParameters->getKeepAlivePeriod() * 1.5));

        $this->logger->debug('Created socket connection successfully, continuing', stream_get_meta_data($this->socket));
        return true;
    }

    /**
     * @inheritdoc
     */
    public function enableSynchronousTransfer(bool $newStatus): ClientInterface
    {
        $this->logger->debug('Setting new blocking status', ['newStatus' => $newStatus]);
        stream_set_blocking($this->socket, $newStatus);
        $this->isCurrentlyLocked = $newStatus;
        return $this;
    }

    /**
     * Stuff that has to happen before we actually begin sending data through our socket
     *
     * @param WritableContentInterface $object
     * @return Client
     * @throws \unreal4u\MQTT\Exceptions\Connect\NoConnectionParametersDefined
     */
    private function preSocketCommunication(WritableContentInterface $object): self
    {
        $this->objectStack[$object::getControlPacketValue()] = $object;

        if ($object instanceof Connect) {
            $this->generateSocketConnection($object);
        }

        return $this;<commit_msg>Throw early exception if we can't connect at all to the socket<commit_after><?php

declare(strict_types=1);

namespace unreal4u\MQTT;

use unreal4u\MQTT\Application\EmptyWritableResponse;
use unreal4u\MQTT\Exceptions\NotConnected;
use unreal4u\MQTT\Exceptions\ServerClosedConnection;
use unreal4u\MQTT\Internals\ClientInterface;
use unreal4u\MQTT\Internals\ProtocolBase;
use unreal4u\MQTT\Internals\ReadableContentInterface;
use unreal4u\MQTT\Internals\WritableContentInterface;
use unreal4u\MQTT\Protocol\Connect;
use unreal4u\MQTT\Protocol\Disconnect;

/**
 * Class Client
 * @package unreal4u\MQTT
 */
final class Client extends ProtocolBase implements ClientInterface
{
    /**
     * Where all the magic happens
     * @var Resource
     */
    private $socket;

    /**
     * Fast way to know whether we are connected or not
     * @var bool
     */
    private $isConnected = false;

    /**
     * Fast way to know whether we are currently in locked mode or not
     * @var bool
     */
    private $isCurrentlyLocked = false;

    /**
     * Annotates the last time there was known to be communication with the MQTT server
     * @var \DateTimeImmutable
     */
    private $lastCommunication;

    /**
     * Internal holder of connection parameters
     * @var Connect\Parameters
     */
    private $connectionParameters;

    /**
     * Temporary holder for async requests so that they can be handled synchronously
     * @var WritableContentInterface[]
     */
    private $objectStack = [];

    /**
     * @inheritdoc
     * @throws \LogicException
     * @throws \unreal4u\MQTT\Exceptions\UnmatchingPacketIdentifiers
     * @throws \unreal4u\MQTT\Exceptions\NotConnected
     * @throws \unreal4u\MQTT\Exceptions\Connect\NoConnectionParametersDefined
     * @throws \unreal4u\MQTT\Exceptions\ServerClosedConnection
     */
    public function __destruct()
    {
        if ($this->isConnected() === true) {
            $this->logger->info('Currently connected to broker, disconnecting from it');

            $this->processObject(new Disconnect($this->logger));
        }
    }

    /**
     * @inheritdoc
     */
    public function shutdownConnection(): bool
    {
        return stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);
    }

    /**
     * @inheritdoc
     */
    public function readBrokerData(int $bytes): string
    {
        $this->logger->debug('Reading bytes from socket', [
            'numberOfBytes' => $bytes,
            'isLocked' => $this->isCurrentlyLocked,
        ]);
        return fread($this->socket, $bytes);
    }

    /**
     * @inheritdoc
     */
    public function readBrokerHeader(): string
    {
        $this->logger->debug('Reading header from response');
        return $this->readBrokerData(4);
    }

    /**
     * @inheritdoc
     * @throws \unreal4u\MQTT\Exceptions\ServerClosedConnection
     * @throws \unreal4u\MQTT\Exceptions\NotConnected
     */
    public function sendBrokerData(WritableContentInterface $object): string
    {
        if ($this->socket === null) {
            $this->logger->alert('Not connected before sending data');
            throw new NotConnected('Please connect before performing any other request');
        }

        $writableString = $object->createSendableMessage();
        $sizeOfString = \strlen($writableString);
        $writtenBytes = fwrite($this->socket, $writableString, $sizeOfString);
        // $this->logger->debug('Sent string', ['binaryString' => str2bin($writableString)]); // Handy for debugging
        if ($writtenBytes !== $sizeOfString) {
            $this->logger->error('Written bytes do NOT correspond with size of string!', [
                'writtenBytes' => $writtenBytes,
                'sizeOfString' => $sizeOfString,
            ]);

            throw new ServerClosedConnection('The server may have disconnected the current client');
        }
        $this->logger->debug('Sent data to socket', ['writtenBytes' => $writtenBytes, 'sizeOfString' => $sizeOfString]);

        $returnValue = '';
        if ($object->shouldExpectAnswer() === true) {
            $this->enableSynchronousTransfer(true);
            $returnValue = $this->readBrokerHeader();
            $this->enableSynchronousTransfer(false);
        }

        return $returnValue;
    }

    /**
     * Checks for socket error connections, will throw an exception if any is found
     *
     * @param int $errorCode
     * @param string $errorDescription
     * @return Client
     * @throws \unreal4u\MQTT\Exceptions\NotConnected
     */
    private function checkForConnectionErrors(int $errorCode, string $errorDescription): self
    {
        if ($errorCode !== 0) {
            $this->logger->critical('Could not connect to broker', [
                'errorCode' => $errorCode,
                'errorDescription' => $errorDescription,
            ]);

            throw new NotConnected('Could not connect to broker: ' . $errorDescription, $errorCode);
        }

        return $this;
    }

    /**
     * Special handling of the connect part: create the socket
     *
     * @param Connect $connection
     * @return bool
     * @throws \unreal4u\MQTT\Exceptions\NotConnected
     * @throws \unreal4u\MQTT\Exceptions\Connect\NoConnectionParametersDefined
     */
    private function generateSocketConnection(Connect $connection): bool
    {
        $this->logger->debug('Creating socket connection');
        $this->connectionParameters = $connection->getConnectionParameters();
        $this->socket = stream_socket_client(
            $this->connectionParameters->getConnectionUrl(),
            $errorCode,
            $errorDescription,
            60,
            STREAM_CLIENT_CONNECT
        );

        $this->checkForConnectionErrors($errorCode, $errorDescription);

        stream_set_timeout($this->socket, (int)floor($this->connectionParameters->getKeepAlivePeriod() * 1.5));

        $this->logger->debug('Created socket connection successfully, continuing', stream_get_meta_data($this->socket));
        return true;
    }

    /**
     * @inheritdoc
     */
    public function enableSynchronousTransfer(bool $newStatus): ClientInterface
    {
        $this->logger->debug('Setting new blocking status', ['newStatus' => $newStatus]);
        stream_set_blocking($this->socket, $newStatus);
        $this->isCurrentlyLocked = $newStatus;
        return $this;
    }

    /**
     * Stuff that has to happen before we actually begin sending data through our socket
     *
     * @param WritableContentInterface $object
     * @return Client
     * @throws \unreal4u\MQTT\Exceptions\NotConnected
     * @throws \unreal4u\MQTT\Exceptions\Connect\NoConnectionParametersDefined
     */
    private function preSocketCommunication(WritableContentInterface $object): self
    {
        $this->objectStack[$object::getControlPacketValue()] = $object;

        if ($object instanceof Connect) {
            $this->generateSocketConnection($object);
        }

        return $this;
{'fraction_non_alphanumeric': 0.1146781910788702, 'fraction_numerical': 0.004244482173174873, 'mean_word_length': 2.706807780320366, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 118, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4175810', 'n_tokens_mistral': 3627, 'n_tokens_neox': 3473, 'n_words': 791}