Completed
Pull Request — master (#8)
by Mr
03:41
created

APILengthCoDec::decodeLength()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 92

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 32
CRAP Score 7

Importance

Changes 0
Metric Value
dl 0
loc 92
ccs 32
cts 32
cp 1
rs 7.2412
c 0
b 0
f 0
cc 7
nc 7
nop 1
crap 7

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace RouterOS;
4
5
use RouterOS\Interfaces\StreamInterface;
6
use RouterOS\Helpers\BinaryStringHelper;
7
8
/**
9
 * class APILengthCoDec
10
 *
11
 * Coder / Decoder for length field in mikrotik API communication protocol
12
 *
13
 * @package RouterOS
14
 * @since   0.9
15
 */
16
class APILengthCoDec
17
{
18 29
    public static function encodeLength(int $length): string
19
    {
20
        // Encode the length :
21
        // - if length <= 0x7F (binary : 01111111 => 7 bits set to 1)
22
        //      - encode length with one byte
23
        //      - set the byte to length value, as length maximal value is 7 bits set to 1, the most significant bit is always 0
24
        //      - end
25
        // - length <= 0x3FFF (binary : 00111111 11111111 => 14 bits set to 1)
26
        //      - encode length with two bytes
27
        //      - set length value to 0x8000 (=> 10000000 00000000)
28
        //      - add length : as length maximumal value is 14 bits to 1, this does not modify the 2 most significance bits (10)
29
        //      - end
30
        //      => minimal encoded value is 10000000 10000000
31
        // - length <= 0x1FFFFF (binary : 00011111 11111111 11111111 => 21 bits set to 1)
32
        //      - encode length with three bytes
33
        //      - set length value to 0xC00000 (binary : 11000000 00000000 00000000)
34
        //      - add length : as length maximal vlaue is 21 bits to 1, this does not modify the 3 most significance bits (110)
35
        //      - end
36
        //      => minimal encoded value is 11000000 01000000 00000000
37
        // - length <= 0x0FFFFFFF (binary : 00001111 11111111 11111111 11111111 => 28 bits set to 1)
38
        //      - encode length with four bytes
39
        //      - set length value to 0xE0000000 (binary : 11100000 00000000 00000000 00000000)
40
        //      - add length : as length maximal vlaue is 28 bits to 1, this does not modify the 4 most significance bits (1110)
41
        //      - end
42
        //      => minimal encoded value is 11100000 00100000 00000000 00000000
43
        // - length <= 0x7FFFFFFFFF (binary : 00000111 11111111 11111111 11111111 11111111 => 35 bits set to 1)
44
        //      - encode length with five bytes
45
        //      - set length value to 0xF000000000 (binary : 11110000 00000000 00000000 00000000 00000000)
46
        //      - add length : as length maximal vlaue is 35 bits to 1, this does not modify the 5 most significance bits (11110)
47
        //      - end
48
        // - length > 0x7FFFFFFFFF : not supported
49
50 29
        if ($length < 0) {
51 2
            throw new \DomainException("Length of word could not to be negative ($length)");
52
        }
53
54 27
        if ($length <= 0x7F) {
55 15
            return BinaryStringHelper::IntegerToNBOBinaryString($length);
56
        }
57
58 12
        if ($length <= 0x3FFF) {
59 3
            return BinaryStringHelper::IntegerToNBOBinaryString(0x8000 + $length);
60
        }
61
62 9
        if ($length <= 0x1FFFFF) {
63 3
            return BinaryStringHelper::IntegerToNBOBinaryString(0xC00000 + $length);
64
        }
65
66 6
        if ($length <= 0x0FFFFFFF) {
67 3
            return BinaryStringHelper::IntegerToNBOBinaryString(0xE0000000 + $length);
68
        }
69
70
        // https://wiki.mikrotik.com/wiki/Manual:API#API_words
71
        // If len >= 0x10000000 then 0xF0 and len as four bytes
72 3
        return BinaryStringHelper::IntegerToNBOBinaryString(0xF000000000 + $length);
73
    }
74
75
    // Decode length of data when reading :
76
    // The 5 firsts bits of the first byte specify how the length is encoded.
77
    // The position of the first 0 value bit, starting from the most significant postion. 
78
    // - 0xxxxxxx => The 7 remainings bits of the first byte is the length : 
79
    //            => min value of length is 0x00 
80
    //            => max value of length is 0x7F (127 bytes)
81
    // - 10xxxxxx => The 6 remainings bits of the first byte plus the next byte represent the lenght
82
    //            NOTE : the next byte MUST be at least 0x80 !!
83
    //            => min value of length is 0x80 
84
    //            => max value of length is 0x3FFF (16,383 bytes, near 16 KB)
85
    // - 110xxxxx => The 5 remainings bits of th first byte and the two next bytes represent the length
86
    //             => max value of length is 0x1FFFFF (2,097,151 bytes, near 2 MB)
87
    // - 1110xxxx => The 4 remainings bits of the first byte and the three next bytes represent the length
88
    //            => max value of length is 0xFFFFFFF (268,435,455 bytes, near 270 MB)
89
    // - 11110xxx => The 3 remainings bits of the first byte and the four next bytes represent the length
90
    //            => max value of length is 0x7FFFFFFF (2,147,483,647 byes, 2GB)
91
    // - 11111xxx => This byte is not a length-encoded word but a control byte.
92
    //          =>  Extracted from Mikrotik API doc : 
93
    //              it is a reserved control byte. 
94
    //              After receiving unknown control byte API client cannot proceed, because it cannot know how to interpret following bytes
95
    //              Currently control bytes are not used
96
97 30
    public static function decodeLength(StreamInterface $stream): int
98
    {
99
        // if (false === is_resource($stream)) {
100
        //     throw new \InvalidArgumentException(
101
        //         sprintf(
102
        //             'Argument must be a stream resource type. %s given.',
103
        //             gettype($stream)
104
        //         )
105
        //     );
106
        // }
107
108
        // Read first byte
109 30
        $firstByte = ord($stream->read(1));
110
111
        // If first byte is not set, length is the value of the byte
112 30
        if (0 === ($firstByte & 0x80)) {
113 15
            return $firstByte;
114
        }
115
116
        // if 10xxxxxx, length is 2 bytes encoded
117 15
        if (0x80 === ($firstByte & 0xC0)) {
118
            // Set 2 most significands bits to 0
119 3
            $result = $firstByte & 0x3F;
120
121
            // shift left 8 bits to have 2 bytes
122 3
            $result <<= 8;
123
124
            // read next byte and use it as least significant
125 3
            $result |= ord($stream->read(1));
126 3
            return $result;
127
        }
128
129
        // if 110xxxxx, length is 3 bytes encoded
130 12
        if (0xC0 === ($firstByte & 0xE0)) {
131
            // Set 3 most significands bits to 0
132 3
            $result = $firstByte & 0x1F;
133
134
            // shift left 16 bits to have 3 bytes
135 3
            $result <<= 16;
136
137
            // read next 2 bytes as value and use it as least significant position
138 3
            $result |= (ord($stream->read(1)) << 8);
139 3
            $result |= ord($stream->read(1));
140 3
            return $result;
141
        }
142
143
        // if 1110xxxx, length is 4 bytes encoded
144 9
        if (0xE0 === ($firstByte & 0xF0)) {
145
            // Set 4 most significance bits to 0
146 3
            $result = $firstByte & 0x0F;
147
148
            // shift left 24 bits to have 4 bytes
149 3
            $result <<= 24;
150
151
            // read next 3 bytes as value and use it as least significant position
152 3
            $result |= (ord($stream->read(1)) << 16);
153 3
            $result |= (ord($stream->read(1)) << 8);
154 3
            $result |= ord($stream->read(1));
155 3
            return $result;
156
        }
157
158
        // if 11110xxx, length is 5 bytes encoded
159 6
        if (0xF0 === ($firstByte & 0xF8)) {
160
            // Not possible on 32 bits systems
161 3
            if (PHP_INT_SIZE < 8) {
162
                // Cannot be done on 32 bits systems
163
                // PHP5 windows versions of php, even on 64 bits systems was impacted
164
                // see : https://stackoverflow.com/questions/27865340/php-int-size-returns-4-but-my-operating-system-is-64-bit 
165
                // How can we test it ?
166
                // @codeCoverageIgnoreStart
167
                throw new \OverflowException("Your system is using 32 bits integers, cannot decode this value ($firstByte) on this system");
168
                // @codeCoverageIgnoreEnd
169
            }
170
171
            // Set 5 most significance bits to 0
172 3
            $result = $firstByte & 0x07;
173
174
            // shift left 232 bits to have 5 bytes
175 3
            $result <<= 32;
176
177
            // read next 4 bytes as value and use it as least significant position
178 3
            $result |= (ord($stream->read(1)) << 24);
179 3
            $result |= (ord($stream->read(1)) << 16);
180 3
            $result |= (ord($stream->read(1)) << 8);
181 3
            $result |= ord($stream->read(1));
182 3
            return $result;
183
        }
184
185
        // Now the only solution is 5 most significance bits are set to 1 (11111xxx)
186
        // This is a control word, not implemented by Mikrotik for the moment 
187 3
        throw new \UnexpectedValueException('Control Word found');
188
    }
189
}
190