Completed
Push — master ( 8c9d7d...4ca550 )
by Camilo
03:44
created

getProtocolVersionBinaryRepresentation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 0
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace unreal4u\MQTT\Protocol\Connect;
6
7
use Psr\Log\LoggerInterface;
8
use unreal4u\Dummy\Logger;
9
use unreal4u\MQTT\Application\Message;
10
use unreal4u\MQTT\DataTypes\ProtocolVersion;
11
use unreal4u\MQTT\DataTypes\QoSLevel;
12
13
/**
14
 * Special connection parameters will be defined in this class
15
 */
16
final class Parameters
17
{
18
    /**
19
     * The default protocol version this library will be talking with
20
     */
21
    const DEFAULT_PROTOCOL_VERSION = '3.1.1';
22
23
    /**
24
     * @var LoggerInterface
25
     */
26
    private $logger;
27
28
    /**
29
     * The host we'll be connecting to
30
     *
31
     * @var string
32
     */
33
    public $host = '';
34
35
    /**
36
     * The port we must connect to
37
     * @var int
38
     */
39
    public $port = 1883;
40
41
    /**
42
     * Unique (per broker) client Id. Can be empty if $cleanSession is set to true.
43
     *
44
     * SHOULD be within the character set "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
45
     *
46
     * @var string
47
     */
48
    private $clientId = '';
49
50
    /**
51
     * The keep alive is a time interval in seconds (defaults to 60), the clients commits to by sending regular PING
52
     * Request messages to the broker.
53
     *
54
     * The broker response with PING Response and this mechanism will allow both sides to determine if the other one is
55
     * still alive and reachable.
56
     *
57
     * @var int
58
     */
59
    public $keepAlivePeriod = 60;
60
61
    /**
62
     * Whether to create a persistent session (default = false).
63
     *
64
     * It means that the broker will store all subscriptions for the client and also all missed messages, when
65
     * subscribing with Quality of Service (QoS) 1 or 2
66
     * @var bool
67
     */
68
    private $cleanSession = false;
69
70
    /**
71
     * The corresponding field for the username flag
72
     * @var string
73
     */
74
    private $username = '';
75
76
    /**
77
     * The corresponding field for the password flag
78
     * @var string
79
     */
80
    private $password = '';
81
82
    /**
83
     * The will message printed out by the server in case of a sudden unexpected disconnect
84
     * @var string
85
     */
86
    private $willMessage = '';
87
88
    /**
89
     * If the client disconnects unexpectedly, set the will message in this will topic
90
     * @var string
91
     */
92
    private $willTopic = '';
93
94
    /**
95
     * QoS Level of the will
96
     * @var QoSLevel
97
     */
98
    private $willQoS;
99
100
    /**
101
     * Whether the will message should be retained by the server
102
     * @var bool
103
     */
104
    private $willRetain = false;
105
106
    /**
107
     * @var ProtocolVersion
108
     */
109
    private $protocolVersion;
110
111
    /**
112
     * The 10th byte will contain a series of flags
113
     *
114
     * The order of these flags are:
115
     *
116
     *   7-6-5-4-3-2-1-0
117
     * b'0-0-0-0-0-0-0-0'
118
     *
119
     * Bit 7: if username is set, this bit is true
120
     * Bit 6: if password is set, this bit is true
121
     * Bit 5: This bit specifies if the Will Message is to be Retained when it is published
122
     * Bits 4 & 3: These two bits specify the QoS level to be used when publishing the Will Message
123
     * Bit 2: If the Will Flag is set to 1 this indicates that, if the Connect request is accepted, a Will Message MUST
124
     *        be stored on the Server and associated with the Network Connection
125
     * Bit 1: This bit specifies the handling of the Session state
126
     * Bit 0: Reserved
127
     *
128
     * @see http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html#_Toc442180843
129
     * @var string
130
     */
131
    private $bitFlag = b'00000000';
132
133
    /**
134
     * Builds up the connection parameters
135
     *
136
     * @param string $clientId Will default to a clientId set by the broker
137
     * @param string $host Will default to localhost
138
     * @param LoggerInterface $logger
139
     */
140 20
    public function __construct(string $clientId = '', string $host = 'localhost', LoggerInterface $logger = null)
141
    {
142 20
        if ($logger === null) {
143 20
            $logger = new Logger();
144
        }
145
        // Insert name of class within the logger
146 20
        $this->logger = $logger->withName(str_replace('unreal4u\\MQTT\\', '', \get_class($this)));
0 ignored issues
show
Bug introduced by
The method withName() does not exist on Psr\Log\LoggerInterface. It seems like you code against a sub-type of Psr\Log\LoggerInterface such as unreal4u\Dummy\Logger or Monolog\Logger. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

146
        /** @scrutinizer ignore-call */ 
147
        $this->logger = $logger->withName(str_replace('unreal4u\\MQTT\\', '', \get_class($this)));
Loading history...
147
148
        // Once we have a logger, set the clientId
149 20
        $this->setClientId($clientId);
150 20
        $this->setProtocolVersion(new ProtocolVersion(self::DEFAULT_PROTOCOL_VERSION));
151
152 20
        $this->host = $host;
153 20
    }
154
155 20
    public function setProtocolVersion(ProtocolVersion $protocolVersion): self
156
    {
157 20
        $this->protocolVersion = $protocolVersion;
158 20
        return $this;
159
    }
160
161 1
    public function getProtocolVersionBinaryRepresentation(): string
162
    {
163 1
        return $this->protocolVersion->getProtocolVersionBinaryRepresentation();
164
    }
165
166
    /**
167
     * Handles everything related to setting the ClientId
168
     *
169
     * @param string $clientId
170
     * @return Parameters
171
     */
172 20
    public function setClientId(string $clientId = ''): self
173
    {
174 20
        if ($clientId !== '') {
175 6
            $this->clientId = $clientId;
176 6
            $clientIdSize = \strlen($this->clientId);
177 6
            $utf8ClientIdSize = \mb_strlen($this->clientId);
178
179 6
            if ($clientIdSize !== $utf8ClientIdSize) {
180
                $this->logger->warning('The broker MAY reject the connection because of invalid characters');
181
            }
182
183 6
            if ($utf8ClientIdSize > 23) {
184 6
                $this->logger->warning('The broker MAY reject the connection because the ClientId is too long');
185
            }
186
        } else {
187
            /*
188
             * If you ever wind up in this situation, search for MQTT-3.1.3-7 on the following document for more
189
             * information: http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718067
190
             */
191 14
            $this->logger->warning('ClientId size is 0 bytes. This has several implications, check comments', [
192 14
                'file' => __FILE__,
193
                'line' => __LINE__,
194
            ]);
195 14
            $this->cleanSession = true;
196
        }
197
198 20
        return $this;
199
    }
200
201 3
    public function getClientId(): string
202
    {
203 3
        return $this->clientId;
204
    }
205
206
    /**
207
     * Returns the connection string
208
     *
209
     * @TODO Currently only TCP connections supported, SSL will come
210
     *
211
     * @return string
212
     */
213 2
    public function getConnectionUrl(): string
214
    {
215 2
        return 'tcp://' . $this->host . ':' . $this->port;
216
    }
217
218
    /**
219
     * Returns the set of flags we are making the connection with
220
     *
221
     * @return int
222
     */
223 14
    public function getFlags(): int
224
    {
225 14
        return (int)$this->bitFlag;
226
    }
227
228
    /**
229
     * Keep alive period is measured in positive seconds. The maximum is 18h, 12m and 15s, equivalent to 65535 seconds
230
     *
231
     * @param int $keepAlivePeriod
232
     * @return Parameters
233
     * @throws \InvalidArgumentException
234
     */
235 2
    public function setKeepAlivePeriod(int $keepAlivePeriod): Parameters
236
    {
237 2
        if ($keepAlivePeriod > 65535 || $keepAlivePeriod < 0) {
238 1
            $this->logger->error('Keep alive period must be between 0 and 65535');
239 1
            throw new \InvalidArgumentException('Keep alive period must be between 0 and 65535');
240
        }
241
242 1
        $this->keepAlivePeriod = $keepAlivePeriod;
243 1
        return $this;
244
    }
245
246
    /**
247
     * Sets the 7th bit of the connect flag
248
     *
249
     * @see http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc385349230
250
     * @param string $username
251
     * @return Parameters
252
     */
253 4
    public function setUsername(string $username): Parameters
254
    {
255 4
        $this->bitFlag &= ~128;
256 4
        if ($username !== '') {
257 4
            $this->logger->debug('Username set, setting username flag');
258 4
            $this->bitFlag |= 128;
259
        }
260 4
        $this->username = $username;
261 4
        return $this;
262
    }
263
264
    /**
265
     * Sets the 6th bit of the connect flag
266
     *
267
     * @see http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc385349230
268
     * @param string $password
269
     * @return Parameters
270
     */
271 5
    public function setPassword(string $password): Parameters
272
    {
273 5
        $this->bitFlag &= ~64;
274 5
        if ($password !== '') {
275 5
            $this->logger->debug('Password set, setting password flag');
276 5
            $this->bitFlag |= 64;
277
        }
278 5
        $this->password = $password;
279 5
        return $this;
280
    }
281
282
    /**
283
     * Is a private method, so can be trusted with just a string name instead of a Topic object
284
     *
285
     * @param string $willTopic
286
     * @return Parameters
287
     */
288 6
    private function setWillTopic(string $willTopic): Parameters
289
    {
290 6
        $this->willTopic = $willTopic;
291 6
        $this->logger->debug('Setting will topic');
292 6
        return $this;
293
    }
294
295
    /**
296
     * @param string $willMessage
297
     * @return Parameters
298
     */
299 6
    private function setWillMessage(string $willMessage): Parameters
300
    {
301 6
        $this->willMessage = $willMessage;
302 6
        $this->logger->debug('Setting will message');
303 6
        return $this;
304
    }
305
306
    /**
307
     * Sets the 5th bit of the connect flag
308
     *
309
     * @see http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc385349230
310
     * @param bool $willRetain
311
     * @return Parameters
312
     */
313 6
    private function setWillRetain(bool $willRetain): Parameters
314
    {
315 6
        $this->bitFlag &= ~32;
316 6
        if ($willRetain === true) {
317 1
            $this->logger->debug('Setting will retain flag');
318 1
            $this->bitFlag |= 32;
319
        }
320 6
        $this->willRetain = $willRetain;
321 6
        return $this;
322
    }
323
324
    /**
325
     * Determines and sets the 3rd and 4th bits of the connect flag
326
     *
327
     * @see http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc385349230
328
     * @param QoSLevel $QoSLevel
329
     * @return Parameters
330
     */
331 6
    private function setWillQoS(QoSLevel $QoSLevel): Parameters
332
    {
333
        // Reset first the will QoS bits and proceed to set them
334 6
        $this->bitFlag &= ~8; // Third bit: 8
335 6
        $this->bitFlag &= ~16; // Fourth bit: 16
336
337 6
        $this->willQoS = $QoSLevel;
338 6
        switch ($this->willQoS->getQoSLevel()) {
339 6
            case 0:
340
                // Do nothing as the relevant bits will already have been reset
341 4
                break;
342 2
            case 1:
343 1
                $this->logger->debug('Setting will QoS level 1 flag');
344 1
                $this->bitFlag |= 8;
345 1
                break;
346 1
            case 2:
347 1
                $this->logger->debug('Setting will QoS level 2 flag');
348 1
                $this->bitFlag |= 16;
349 1
                break;
350
        }
351
352 6
        return $this;
353
    }
354
355
    /**
356
     * Sets the given will. Will also set the 2nd bit of the connect flags if a message is provided
357
     *
358
     * @see http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc385349230
359
     * @param Message $message
360
     * @return Parameters
361
     * @throws \unreal4u\MQTT\Exceptions\InvalidQoSLevel
362
     * @throws \unreal4u\MQTT\Exceptions\MissingTopicName
363
     * @throws \unreal4u\MQTT\Exceptions\MessageTooBig
364
     */
365 6
    public function setWill(Message $message): Parameters
366
    {
367
        // Proceed only if we have a valid message
368 6
        if ($message->validateMessage()) {
369 6
            $this->bitFlag &= ~4;
370 6
            if ($message->getTopicName() !== '') {
371 6
                $this->logger->debug('Setting will flag');
372 6
                $this->bitFlag |= 4;
373
            }
374
375
            $this
376 6
                ->setWillMessage($message->getPayload())
377 6
                ->setWillRetain($message->isRetained())
378 6
                ->setWillTopic($message->getTopicName())
379 6
                ->setWillQoS(new QoSLevel($message->getQoSLevel()));
380
        }
381
382 6
        return $this;
383
    }
384
385
    /**
386
     * Sets the 1st bit of the connect flags
387
     *
388
     * @param bool $cleanSession
389
     * @return Parameters
390
     */
391 3
    public function setCleanSession(bool $cleanSession): Parameters
392
    {
393 3
        $this->bitFlag &= ~2;
394 3
        if ($cleanSession === true) {
395 3
            $this->logger->debug('Clean session flag set');
396 3
            $this->bitFlag |= 2;
397
        }
398 3
        $this->cleanSession = $cleanSession;
399 3
        return $this;
400
    }
401
402
    /**
403
     * @return int
404
     */
405 1
    public function getKeepAlivePeriod(): int
406
    {
407 1
        return $this->keepAlivePeriod;
408
    }
409
410
    /**
411
     * @return bool
412
     */
413 2
    public function getCleanSession(): bool
414
    {
415 2
        return $this->cleanSession;
416
    }
417
418
    /**
419
     * @return string
420
     */
421 5
    public function getUsername(): string
422
    {
423 5
        return $this->username;
424
    }
425
426
    /**
427
     * @return string
428
     */
429 5
    public function getPassword(): string
430
    {
431 5
        return $this->password;
432
    }
433
434
    /**
435
     * @return string
436
     */
437 3
    public function getWillTopic(): string
438
    {
439 3
        return $this->willTopic;
440
    }
441
442
    /**
443
     * @return string
444
     */
445 3
    public function getWillMessage(): string
446
    {
447 3
        return $this->willMessage;
448
    }
449
450
    /**
451
     * @return bool
452
     */
453
    public function getWillRetain(): bool
454
    {
455
        return $this->willRetain;
456
    }
457
}
458