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