Completed
Pull Request — master (#8)
by Mr
03:36
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 31
    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 31
        if ($length < 0) {
51 2
            throw new \DomainException("Length of word can not be negative ($length)");
52
        }
53
54 29
        if ($length <= 0x7F) {
55 15
            return BinaryStringHelper::IntegerToNBOBinaryString($length);
56
        }
57
58 14
        if ($length <= 0x3FFF) {
59 3
            return BinaryStringHelper::IntegerToNBOBinaryString(0x8000 + $length);
60
        }
61
62 11
        if ($length <= 0x1FFFFF) {
63 3
            return BinaryStringHelper::IntegerToNBOBinaryString(0xC00000 + $length);
64
        }
65
66 8
        if ($length <= 0x0FFFFFFF) {
67 3
            return BinaryStringHelper::IntegerToNBOBinaryString(0xE0000000 + $length);
68
        } // cannot compare with 0x7FFFFFFFFF on 32 bits systems
69
70 5
        if (PHP_INT_SIZE < 8) {
71
            // Cannot be done on 32 bits systems
72
            // PHP5 windows versions of php, even on 64 bits systems was impacted
73
            // see : https://stackoverflow.com/questions/27865340/php-int-size-returns-4-but-my-operating-system-is-64-bit
74
75
            // @codeCoverageIgnoreStart
76
            throw new \OverflowException("Your system is using 32 bits integers, cannot encode length of $length bytes on this system");
77
            // @codeCoverageIgnoreEnd
78
        }
79
80 5
        if ($length <= 0x7FFFFFFFFF) {
81 3
            return BinaryStringHelper::IntegerToNBOBinaryString(0xF000000000 + $length);
82
        }
83
84 2
        throw new \DomainException("Length of word too huge ($length)");
85
    }
86
87
    // Decode length of data when reading :
88
    // The 5 firsts bits of the first byte specify how the length is encoded.
89
    // The position of the first 0 value bit, starting from the most significant postion. 
90
    // - 0xxxxxxx => The 7 remainings bits of the first byte is the length : 
91
    //            => min value of length is 0x00 
92
    //            => max value of length is 0x7F (127 bytes)
93
    // - 10xxxxxx => The 6 remainings bits of the first byte plus the next byte represent the lenght
94
    //            NOTE : the next byte MUST be at least 0x80 !!
95
    //            => min value of length is 0x80 
96
    //            => max value of length is 0x3FFF (16,383 bytes, near 16 KB)
97
    // - 110xxxxx => The 5 remainings bits of th first byte and the two next bytes represent the length
98
    //             => max value of length is 0x1FFFFF (2,097,151 bytes, near 2 MB)
99
    // - 1110xxxx => The 4 remainings bits of the first byte and the three next bytes represent the length
100
    //            => max value of length is 0xFFFFFFF (268,435,455 bytes, near 270 MB)
101
    // - 11110xxx => The 3 remainings bits of the first byte and the four next bytes represent the length
102
    //            => max value of length is 0x7FFFFFFF (2,147,483,647 byes, 2GB)
103
    // - 11111xxx => This byte is not a length-encoded word but a control byte.
104
    //          =>  Extracted from Mikrotik API doc : 
105
    //              it is a reserved control byte. 
106
    //              After receiving unknown control byte API client cannot proceed, because it cannot know how to interpret following bytes
107
    //              Currently control bytes are not used
108
109 30
    public static function decodeLength(StreamInterface $stream): int
110
    {
111
        // if (false === is_resource($stream)) {
112
        //     throw new \InvalidArgumentException(
113
        //         sprintf(
114
        //             'Argument must be a stream resource type. %s given.',
115
        //             gettype($stream)
116
        //         )
117
        //     );
118
        // }
119
120
        // Read first byte
121 30
        $firstByte = ord($stream->read(1));
122
123
        // If first byte is not set, length is the value of the byte
124 30
        if (0 === ($firstByte & 0x80)) {
125 15
            return $firstByte;
126
        }
127
128
        // if 10xxxxxx, length is 2 bytes encoded
129 15
        if (0x80 === ($firstByte & 0xC0)) {
130
            // Set 2 most significands bits to 0
131 3
            $result = $firstByte & 0x3F;
132
133
            // shift left 8 bits to have 2 bytes
134 3
            $result <<= 8;
135
136
            // read next byte and use it as least significant
137 3
            $result |= ord($stream->read(1));
138 3
            return $result;
139
        }
140
141
        // if 110xxxxx, length is 3 bytes encoded
142 12
        if (0xC0 === ($firstByte & 0xE0)) {
143
            // Set 3 most significands bits to 0
144 3
            $result = $firstByte & 0x1F;
145
146
            // shift left 16 bits to have 3 bytes
147 3
            $result <<= 16;
148
149
            // read next 2 bytes as value and use it as least significant position
150 3
            $result |= (ord($stream->read(1)) << 8);
151 3
            $result |= ord($stream->read(1));
152 3
            return $result;
153
        }
154
155
        // if 1110xxxx, length is 4 bytes encoded
156 9
        if (0xE0 === ($firstByte & 0xF0)) {
157
            // Set 4 most significance bits to 0
158 3
            $result = $firstByte & 0x0F;
159
160
            // shift left 24 bits to have 4 bytes
161 3
            $result <<= 24;
162
163
            // read next 3 bytes as value and use it as least significant position
164 3
            $result |= (ord($stream->read(1)) << 16);
165 3
            $result |= (ord($stream->read(1)) << 8);
166 3
            $result |= ord($stream->read(1));
167 3
            return $result;
168
        }
169
170
        // if 11110xxx, length is 5 bytes encoded
171 6
        if (0xF0 === ($firstByte & 0xF8)) {
172
            // Not possible on 32 bits systems
173 3
            if (PHP_INT_SIZE < 8) {
174
                // Cannot be done on 32 bits systems
175
                // PHP5 windows versions of php, even on 64 bits systems was impacted
176
                // see : https://stackoverflow.com/questions/27865340/php-int-size-returns-4-but-my-operating-system-is-64-bit 
177
                // How can we test it ?
178
                // @codeCoverageIgnoreStart
179
                throw new \OverflowException("Your system is using 32 bits integers, cannot decode this value ($firstByte) on this system");
180
                // @codeCoverageIgnoreEnd
181
            }
182
183
            // Set 5 most significance bits to 0
184 3
            $result = $firstByte & 0x07;
185
186
            // shift left 232 bits to have 5 bytes
187 3
            $result <<= 32;
188
189
            // read next 4 bytes as value and use it as least significant position
190 3
            $result |= (ord($stream->read(1)) << 24);
191 3
            $result |= (ord($stream->read(1)) << 16);
192 3
            $result |= (ord($stream->read(1)) << 8);
193 3
            $result |= ord($stream->read(1));
194 3
            return $result;
195
        }
196
197
        // Now the only solution is 5 most significance bits are set to 1 (11111xxx)
198
        // This is a control word, not implemented by Mikrotik for the moment 
199 3
        throw new \UnexpectedValueException('Control Word found');
200
    }
201
}
202