Passed
Push — master ( d71bca...578a67 )
by Camilo
02:06
created

Utilities::convertEndianness()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 6
c 1
b 0
f 0
dl 0
loc 14
ccs 7
cts 7
cp 1
rs 10
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace unreal4u\MQTT;
6
7
use LogicException;
8
use OutOfRangeException;
9
use unreal4u\MQTT\Exceptions\MessageTooBig;
10
11
use function chr;
12
use function dechex;
13
use function hexdec;
14
use function ord;
15
use function str_pad;
16
17
/**
18
 * Functionality that is shared across the entire package
19
 * @package unreal4u\MQTT
20
 */
21
final class Utilities
22
{
23
    /**
24
     * Swaps the given number from endian format (INT16, not to be confused with UINT16)
25
     *
26
     * @param int $number
27
     * @return int
28
     * @throws OutOfRangeException
29
     */
30 29
    public static function convertEndianness(int $number): int
31
    {
32 29
        if ($number > 65535) {
33 1
            throw new OutOfRangeException('This is an INT16 conversion, so the maximum is 65535');
34
        }
35
36 28
        $finalNumber = hexdec(
37
            // Invert first byte and make it a complete hexadecimal number
38 28
            str_pad(dechex($number & 255), 2, '0', STR_PAD_LEFT) .
39
            // Invert second byte and make a complete hexadecimal number
40 28
            str_pad(dechex($number >> 8), 2, '0', STR_PAD_LEFT)
41
        );
42
43 28
        return (int)$finalNumber;
44
    }
45
46
    /**
47
     * Converts a number to a binary string that the MQTT protocol understands
48
     *
49
     * @param int $number
50
     * @return string
51
     * @throws OutOfRangeException
52
     */
53 30
    public static function convertNumberToBinaryString(int $number): string
54
    {
55 30
        if ($number > 65535) {
56 1
            throw new OutOfRangeException('This is an INT16 conversion, so the maximum is 65535');
57
        }
58
59 29
        return chr($number >> 8) . chr($number & 255);
60
    }
61
62
    /**
63
     * Converts a binary representation of a number to an actual int
64
     *
65
     * @param string $binaryString
66
     * @return int
67
     * @throws OutOfRangeException
68
     */
69 20
    public static function convertBinaryStringToNumber(string $binaryString): int
70
    {
71 20
        return self::convertEndianness((ord($binaryString[1]) << 8) + (ord($binaryString[0]) & 255));
72
    }
73
74
    /**
75
     * Returns the correct format for the length in bytes of the remaining bytes
76
     *
77
     * Original pseudo-code algorithm as per the documentation:
78
     * <pre>
79
     * do
80
     *     encodedByte = X MOD 128
81
     *     X = X DIV 128
82
     *     // if there are more data to encode, set the top bit of this byte
83
     *     if ( X > 0 )
84
     *         encodedByte = encodedByte OR 128
85
     *     endif
86
     *     'output' encodedByte
87
     * while ( X > 0 )
88
     * </pre>
89
     * @see http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html#_Toc385349213
90
     *
91
     * @param int $lengthInBytes
92
     * @return string
93
     * @throws MessageTooBig
94
     */
95 18
    public static function formatRemainingLengthOutput(int $lengthInBytes): string
96
    {
97 18
        if ($lengthInBytes > 268435455) {
98 1
            throw new MessageTooBig('The message cannot exceed 268435455 bytes in length');
99
        }
100
101 17
        $x = $lengthInBytes;
102 17
        $outputString = '';
103
        do {
104 17
            $encodedByte = $x % 128;
105 17
            $x >>= 7; // Shift 7 bytes
106
            // if there are more data to encode, set the top bit of this byte
107 17
            if ($x > 0) {
108 9
                $encodedByte |= 128;
109
            }
110 17
            $outputString .= chr($encodedByte);
111 17
        } while ($x > 0);
112
113 17
        return $outputString;
114
    }
115
116
    /**
117
     * The remaining length of a message is encoded in this specific way, the opposite of formatRemainingLengthOutput
118
     *
119
     * Many thanks to Peter's blog for your excellent examples and knowledge.
120
     * @see http://indigoo.com/petersblog/?p=263
121
     *
122
     * Original pseudo-algorithm as per the documentation:
123
     * <pre>
124
     * multiplier = 1
125
     * value = 0
126
     * do
127
     *     encodedByte = 'next byte from stream'
128
     *     value += (encodedByte & 127) * multiplier
129
     *     if (multiplier > 128*128*128)
130
     *         throw Error(Malformed Remaining Length)
131
     *     multiplier *= 128
132
     * while ((encodedByte & 128) != 0)
133
     * </pre>
134
     *
135
     * @see http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html#_Toc385349213
136
     * @see Utilities::formatRemainingLengthOutput()
137
     *
138
     * @param string $remainingLengthField
139
     * @return int
140
     */
141 17
    public static function convertRemainingLengthStringToInt(string $remainingLengthField): int
142
    {
143 17
        $multiplier = 128;
144 17
        $value = 0;
145 17
        $iteration = 0;
146
147
        do {
148
            // Extract the next byte in the sequence
149 17
            $encodedByte = ord($remainingLengthField[$iteration]);
150
151
            // Add the current multiplier^iteration * first half of byte
152 17
            $value += ($encodedByte & 127) * (128 ** $iteration);
153 17
            if ($multiplier > (128 ** 3)) {
154
                throw new LogicException('Malformed remaining length field');
155
            }
156
157
            // Prepare for the next iteration
158 17
            $iteration++;
159 17
            $multiplier *= $iteration;
160 17
        } while (($encodedByte & 128) !== 0);
161
162 17
        return (int)$value;
163
    }
164
}
165