Passed
Push — master ( bfdde1...63c37e )
by Camilo
02:34
created

Parameters::setWillMessage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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

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