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

APILengthCoDec::decodeLength()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 93

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 32
CRAP Score 7

Importance

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