Passed
Push — master ( 30dabf...70d6c3 )
by Camilo
01:26
created

Connect   A

Complexity

Total Complexity 11

Size/Duplication

Total Lines 116
Duplicated Lines 0 %

Test Coverage

Coverage 83.78%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 11
eloc 35
c 2
b 0
f 0
dl 0
loc 116
ccs 31
cts 37
cp 0.8378
rs 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
A getConnectionParameters() 0 7 2
A setConnectionParameters() 0 4 1
A createVariableHeader() 0 7 1
A expectAnswer() 0 19 3
A shouldExpectAnswer() 0 3 1
A createPayload() 0 23 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace unreal4u\MQTT\Protocol;
6
7
use DomainException;
8
use OutOfRangeException;
9
use unreal4u\MQTT\Exceptions\Connect\IdentifierRejected;
10
use unreal4u\MQTT\Exceptions\Connect\NoConnectionParametersDefined;
11
use unreal4u\MQTT\Exceptions\MustProvideUsername;
12
use unreal4u\MQTT\Internals\ClientInterface;
13
use unreal4u\MQTT\Internals\EventManager;
14
use unreal4u\MQTT\Internals\ProtocolBase;
15
use unreal4u\MQTT\Internals\ReadableContentInterface;
16
use unreal4u\MQTT\Internals\WritableContent;
17
use unreal4u\MQTT\Internals\WritableContentInterface;
18
use unreal4u\MQTT\Protocol\Connect\Parameters;
19
use unreal4u\MQTT\Utilities;
20
21
use function chr;
22
use function get_class;
23
24
/**
25
 * After a Network Connection is established by a Client to a Server, the first Packet sent from the Client to the
26
 * Server MUST be a CONNECT Packet
27
 */
28
final class Connect extends ProtocolBase implements WritableContentInterface
29
{
30
    use /** @noinspection TraitsPropertiesConflictsInspection */
31 1
        WritableContent;
32
33
    private const CONTROL_PACKET_VALUE = 1;
34
35
    /**
36
     * @var Parameters
37
     */
38
    private $connectionParameters;
39
40
    /**
41
     * Saves the mandatory connection parameters onto this object
42
     * @param Parameters $connectionParameters
43
     *
44
     * @return Connect
45
     */
46 5
    public function setConnectionParameters(Parameters $connectionParameters): self
47
    {
48 5
        $this->connectionParameters = $connectionParameters;
49 5
        return $this;
50
    }
51
52
    /**
53
     * Get the connection parameters from the private object
54
     *
55
     * @return Parameters
56
     * @throws NoConnectionParametersDefined
57
     */
58 2
    public function getConnectionParameters(): Parameters
59
    {
60 2
        if ($this->connectionParameters === null) {
61 1
            throw new NoConnectionParametersDefined('You must pass on the connection parameters before connecting');
62
        }
63
64 1
        return $this->connectionParameters;
65
    }
66
67
    /**
68
     * @return string
69
     * @throws OutOfRangeException
70
     */
71 2
    public function createVariableHeader(): string
72
    {
73 2
        $bitString = $this->createUTF8String('MQTT'); // Connect MUST begin with MQTT
74 2
        $bitString .= $this->connectionParameters->getProtocolVersionBinaryRepresentation(); // Protocol level
75 2
        $bitString .= chr($this->connectionParameters->getFlags());
76 2
        $bitString .= Utilities::convertNumberToBinaryString($this->connectionParameters->getKeepAlivePeriod());
77 2
        return $bitString;
78
    }
79
80
    /**
81
     * @return string
82
     * @throws MustProvideUsername
83
     * @throws OutOfRangeException
84
     */
85 3
    public function createPayload(): string
86
    {
87
        // The order in a connect string is clientId first
88 3
        $output = $this->createUTF8String((string)$this->connectionParameters->getClientId());
89
90
        // Then the willTopic if it is set
91 3
        $output .= $this->createUTF8String($this->connectionParameters->getWillTopic());
92
93
        // The willMessage will come next
94 3
        $output .= $this->createUTF8String($this->connectionParameters->getWillMessage());
95
96
        // If the username is set, it will come next
97 3
        $output .= $this->createUTF8String($this->connectionParameters->getUsername());
98
99
        // And finally the password as last parameter
100 3
        if ($this->connectionParameters->getPassword() !== '') {
101 2
            if ($this->connectionParameters->getUsername() === '') {
102 1
                throw new MustProvideUsername('A password can not be set without a username! Please set username');
103
            }
104 1
            $output .= $this->createUTF8String($this->connectionParameters->getPassword());
105
        }
106
107 2
        return $output;
108
    }
109
110 1
    public function shouldExpectAnswer(): bool
111
    {
112 1
        return true;
113
    }
114
115
    /**
116
     * Special handling of the ConnAck object: be able to inject more information into the object before throwing it
117
     *
118
     * @param string $brokerBitStream
119
     * @param ClientInterface $client
120
     *
121
     * @return ReadableContentInterface
122
     * @throws DomainException
123
     * @throws IdentifierRejected
124
     */
125 1
    public function expectAnswer(string $brokerBitStream, ClientInterface $client): ReadableContentInterface
126
    {
127 1
        $this->logger->info('String of incoming data confirmed, returning new object', ['callee' => get_class($this)]);
128
129 1
        $eventManager = new EventManager($this->logger);
130
        try {
131 1
            $connAck = $eventManager->analyzeHeaders($brokerBitStream, $client);
132
        } catch (IdentifierRejected $e) {
133
            $possibleReasons = '';
134
            foreach ($this->connectionParameters->getClientId()->performStrictValidationCheck() as $errorMessage) {
135
                $possibleReasons .= $errorMessage . PHP_EOL;
136
            }
137
138
            $e->fillPossibleReason($possibleReasons);
139
            // Re-throw the exception with all information filled in
140
            throw $e;
141
        }
142
143 1
        return $connAck;
144
    }
145
}
146