Inflater   B
last analyzed

Complexity

Total Complexity 52

Size/Duplication

Total Lines 465
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 72.67%

Importance

Changes 0
Metric Value
wmc 52
lcom 1
cbo 2
dl 0
loc 465
ccs 125
cts 172
cp 0.7267
rs 7.44
c 0
b 0
f 0

2 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 27 6
F inflate() 0 210 46

How to fix   Complexity   

Complex Class

Complex classes like Inflater often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Inflater, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace CHMLib\LZX;
4
5
use Exception;
6
use CHMLib\Reader\BitReader;
7
8
/**
9
 * Decompressor of LXZ-compressed data.
10
 */
11
class Inflater
12
{
13
    /**
14
     * The smallest allowable match length.
15
     *
16
     * @var int
17
     */
18
    const MIN_MATCH = 2;
19
20
    /**
21
     * The number of uncompressed character types.
22
     *
23
     * @var int
24
     */
25
    const NUM_CHARS = 256;
26
27
    /**
28
     * Block type: verbatim.
29
     *
30
     * @var int
31
     */
32
    const BLOCKTYPE_VERBATIM = 1;
33
34
    /**
35
     * Block type: aligned offset.
36
     *
37
     * @var int
38
     */
39
    const BLOCKTYPE_ALIGNED = 2;
40
41
    /**
42
     * Block type: uncompressed.
43
     *
44
     * @var int
45
     */
46
    const BLOCKTYPE_UNCOMPRESSED = 3;
47
48
    /**
49
     * The number of elements in the aligned offset tree.
50
     *
51
     * @var int
52
     */
53
    const ALIGNED_NUM_ELEMENTS = 8;
54
55
    /**
56
     * Unknown.
57
     *
58
     * @var int
59
     */
60
    const NUM_PRIMARY_LENGTHS = 7;
61
62
    /**
63
     * The number of elements in the length tree.
64
     *
65
     * @var int
66
     */
67
    const NUM_SECONDARY_LENGTHS = 249;
68
69
    /**
70
     * The index matrix of the position slot bases.
71
     *
72
     * @var int[]
73
     */
74
    protected static $POSITION_BASE = array(
75
              0,       1,       2,      3,      4,      6,      8,     12,     16,     24,     32,       48,      64,      96,     128,     192,
76
            256,     384,     512,    768,   1024,   1536,   2048,   3072,   4096,   6144,   8192,    12288,   16384,   24576,   32768,   49152,
77
          65536,   98304,  131072, 196608, 262144, 393216, 524288, 655360, 786432, 917504, 1048576, 1179648, 1310720, 1441792, 1572864, 1703936,
78
        1835008, 1966080, 2097152,
79
    );
80
81
    /**
82
     * Number of needed bits for offset-from-base data.
83
     *
84
     * @var int
85
     */
86
    protected static $EXTRA_BITS = array(
87
         0,  0,  0,  0,  1,  1,  2,  2,  3,  3,  4,  4,  5,  5,  6,  6,
88
         7,  7,  8,  8,  9,  9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14,
89
        15, 15, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
90
        17, 17, 17,
91
    );
92
93
    /**
94
     * The size of the actual decoding window.
95
     *
96
     * @var int
97
     */
98
    protected $windowSize;
99
100
    /**
101
     * The actual decoding window.
102
     *
103
     * @var int[]
104
     */
105
    protected $window;
106
107
    /**
108
     * The current offset within the window.
109
     *
110
     * @var int
111
     */
112
    protected $windowPosition;
113
114
    /**
115
     * The most recent non-repeated offset (LRU offset system).
116
     *
117
     * @var int
118
     */
119
    protected $r0;
120
121
    /**
122
     * The second most recent non-repeated offset (LRU offset system).
123
     *
124
     * @var int
125
     */
126
    protected $r1;
127
128
    /**
129
     * The third most recent non-repeated offset (LRU offset system).
130
     *
131
     * @var int
132
     */
133
    protected $r2;
134
135
    /**
136
     * The number of main tree elements.
137
     *
138
     * @var int
139
     */
140
    protected $mainElements;
141
142
    /**
143
     * Decoding has already been started?
144
     *
145
     * @var bool
146
     */
147
    protected $headerRead;
148
149
    /**
150
     * The type of the current block.
151
     *
152
     * @var int
153
     */
154
    protected $blockType;
155
156
    /**
157
     * The uncompressed length of the current block.
158
     *
159
     * @var int
160
     */
161
    protected $blockLength;
162
163
    /**
164
     * The number of ncompressed bytes still left to decode in the current block.
165
     *
166
     * @var int
167
     */
168
    protected $remainingInBlock;
169
170
    /**
171
     * The number of CFDATA blocks processed.
172
     *
173
     * @var int
174
     */
175
    protected $framesRead;
176
177
    /**
178
     * The magic header value used for transform (0 if not encoded).
179
     *
180
     * @var int
181
     */
182
    protected $intelFilesize;
183
184
    /**
185
     * The current offset in transform space.
186
     *
187
     * @var int
188
     */
189
    protected $intelCurrentPosition;
190
191
    /**
192
     * Have we seen any translatable data yet?
193
     *
194
     * @var bool
195
     */
196
    protected $intelStarted;
197
198
    /**
199
     * The main Huffman tree.
200
     *
201
     * @var \CHMLib\LZX\Tree
202
     */
203
    protected $mainTree;
204
205
    /**
206
     * The length Huffman tree.
207
     *
208
     * @var \CHMLib\LZX\Tree
209
     */
210
    protected $lengthTree;
211
212
    /**
213
     * The aligned offset Huffman tree.
214
     *
215
     * @var \CHMLib\LZX\Tree
216
     */
217
    protected $alignedTree;
218
219
    /**
220
     * Initializes the instance.
221
     *
222
     * @param int $windowSize The window size (determines the number of window subdivisions, or "position slots"),
223
     *
224
     * @throws \Exception Throws an Exception in case of errors.
225
     */
226 483
    public function __construct($windowSize)
227
    {
228 483
        $this->windowSize = (int) $windowSize;
229 483
        if ($this->windowSize < 0x8000 || $this->windowSize > 0x200000) {
230
            throw new Exception("Unsupported window size: $windowSize");
231
        }
232 483
        $this->mainTree = new Tree(12, static::NUM_CHARS + 50 * 8);
233 483
        $this->lengthTree = new Tree(12, static::NUM_SECONDARY_LENGTHS + 1);
234 483
        $this->alignedTree = new Tree(7, static::ALIGNED_NUM_ELEMENTS);
235 483
        $this->window = array_fill(0, $this->windowSize, 0);
236 483
        $this->intelFilesize = 0;
237 483
        $numPositionSlots = 0;
238 483
        $windowSize = $this->windowSize;
239 483
        while ($windowSize > 1) {
240 483
            $windowSize >>= 1;
241 483
            $numPositionSlots += 2;
242
        }
243
        switch ($numPositionSlots) {
244 483
            case 40:
245
                $numPositionSlots = 42;
246
                break;
247 483
            case 42:
248
                $numPositionSlots = 50;
249
                break;
250
        }
251 483
        $this->mainElements = static::NUM_CHARS + ($numPositionSlots << 3);
252 483
    }
253
254
    /**
255
     * Uncompress bytes.
256
     *
257
     * @param bool $reset Reset the LXZ state?
258
     * @param \CHMLib\Reader\BitReader $reader The reader that provides the data.
259
     * @param int $numberOfBytes The number of decompressed bytes to retrieve.
260
     *
261
     * @throws \Exception Throws an Exception in case of errors.
262
     *
263
     * @return string
264
     */
265 26
    public function inflate($reset, BitReader $reader, $numberOfBytes)
266
    {
267 26
        if ($reset) {
268 26
            $this->r2 = $this->r1 = $this->r0 = 1;
269 26
            $this->headerRead = false;
270 26
            $this->framesRead = 0;
271 26
            $this->remainingInBlock = 0;
272 26
            $this->blockType = null;
273 26
            $this->intelCurrentPosition = 0;
274 26
            $this->intelStarted = false;
275 26
            $this->windowPosition = 0;
276 26
            $this->mainTree->clear();
277 26
            $this->lengthTree->clear();
278
        }
279
280 26
        if ($this->headerRead === false) {
281 26
            if ($reader->readLE(1) > 0) {
282
                $this->intelFilesize = ($reader->readLE(16) << 16) | $reader->readLE(16);
283
            }
284 26
            $this->headerRead = true;
285
        }
286
287 26
        $togo = $numberOfBytes;
288 26
        while ($togo > 0) {
289 26
            if ($this->remainingInBlock === 0) {
290 26
                if ($this->blockType === static::BLOCKTYPE_UNCOMPRESSED) {
291
                    if (($this->blockLength & 1) !== 0) {
292
                        $reader->skip(1);
293
                    }
294
                }
295 26
                $this->blockType = $reader->readLE(3);
296 26
                $this->remainingInBlock = $this->blockLength = $reader->readLE(16) << 8 | $reader->readLE(8);
297 26
                switch ($this->blockType) {
298 26
                    case static::BLOCKTYPE_ALIGNED:
299 6
                        $this->alignedTree->readAlignLengthTable($reader);
300 6
                        $this->alignedTree->makeSymbolTable();
301
                        /* @noinspection PhpMissingBreakStatementInspection */
302 22
                    case static::BLOCKTYPE_VERBATIM:
303 26
                        $this->mainTree->readLengthTable($reader, 0, static::NUM_CHARS);
304 26
                        $this->mainTree->readLengthTable($reader, static::NUM_CHARS, $this->mainElements);
305 26
                        $this->mainTree->makeSymbolTable();
306 26
                        if ($this->mainTree->isIntel()) {
307 6
                            $this->intelStarted = true;
308
                        }
309 26
                        $this->lengthTree->readLengthTable($reader, 0, static::NUM_SECONDARY_LENGTHS);
310 26
                        $this->lengthTree->makeSymbolTable();
311 26
                        break;
312
                    case static::BLOCKTYPE_UNCOMPRESSED:
313
                        $this->intelStarted = true;
314
                        if ($reader->ensure(16) > 16) {
315
                            $reader->skip(-2);
316
                        }
317
                        $this->r0 = $reader->readUInt32();
318
                        $this->r1 = $reader->readUInt32();
319
                        $this->r2 = $reader->readUInt32();
320
                        break;
321
                    default:
322
                        throw new Exception('Unexpected block type '.$this->blockType);
323
                }
324
            }
325 26
            while (($thisRun = $this->remainingInBlock) > 0 && $togo > 0) {
326 26
                if ($thisRun > $togo) {
327 23
                    $thisRun = $togo;
328
                }
329 26
                $togo -= $thisRun;
330 26
                $this->remainingInBlock -= $thisRun;
331 26
                $this->windowPosition %= $this->windowSize;
332 26
                if ($this->windowPosition + $thisRun > $this->windowSize) {
333
                    throw new Exception('Trying to read more that window size bytes');
334
                }
335 26
                if ($this->blockType === static::BLOCKTYPE_UNCOMPRESSED) {
336
                    $this->window = $reader->readFully($this->window, $this->windowPosition, $thisRun);
337
                    $this->windowPosition += $thisRun;
338
                } else {
339 26
                    while ($thisRun > 0) {
340 26
                        $mainElement = $this->mainTree->readHuffmanSymbol($reader);
341 26
                        if ($mainElement < static::NUM_CHARS) {
342 26
                            $this->window[$this->windowPosition++] = $mainElement;
343 26
                            --$thisRun;
344
                        } else {
345 26
                            $mainElement -= static::NUM_CHARS;
346 26
                            $matchLength = $mainElement & static::NUM_PRIMARY_LENGTHS;
347 26
                            if ($matchLength === static::NUM_PRIMARY_LENGTHS) {
348 26
                                $matchLength += $this->lengthTree->readHuffmanSymbol($reader);
349
                            }
350 26
                            $matchLength += static::MIN_MATCH;
351 26
                            $matchOffset = $mainElement >> 3;
352
                            switch ($matchOffset) {
353 26
                                case 0:
354 26
                                    $matchOffset = $this->r0;
355 26
                                    break;
356 26
                                case 1:
357 26
                                    $matchOffset = $this->r1;
358 26
                                    $this->r1 = $this->r0;
359 26
                                    $this->r0 = $matchOffset;
360 26
                                    break;
361 26
                                case 2:
362 26
                                    $matchOffset = $this->r2;
363 26
                                    $this->r2 = $this->r0;
364 26
                                    $this->r0 = $matchOffset;
365 26
                                    break;
366
                                default:
367 26
                                    switch ($this->blockType) {
368 26
                                        case static::BLOCKTYPE_VERBATIM:
369 22
                                            if ($matchOffset !== 3) {
370 22
                                                $extra = static::$EXTRA_BITS[$matchOffset];
371 22
                                                $matchOffset = static::$POSITION_BASE[$matchOffset] - 2 + $reader->readLE($extra);
372
                                            } else {
373 21
                                                $matchOffset = 1;
374
                                            }
375 22
                                            break;
376 6
                                        case static::BLOCKTYPE_ALIGNED:
377 6
                                            $extra = static::$EXTRA_BITS[$matchOffset];
378 6
                                            $matchOffset = static::$POSITION_BASE[$matchOffset] - 2;
379 6
                                            switch ($extra) {
380 6
                                                case 0:
381 6
                                                    $matchOffset = 1;
382 6
                                                    break;
383 6
                                                case 1:
384 6
                                                case 2:
385
                                                    // verbatim bits only
386 6
                                                    $matchOffset += $reader->readLE($extra);
387 6
                                                    break;
388 6
                                                case 3:
389
                                                    // aligned bits only
390 6
                                                    $matchOffset += $this->alignedTree->readHuffmanSymbol($reader);
391 6
                                                    break;
392
                                                default:
393
                                                    // verbatim and aligned bits
394 6
                                                    $extra -= 3;
395 6
                                                    $matchOffset += ($reader->readLE($extra) << 3);
396 6
                                                    $matchOffset += $this->alignedTree->readHuffmanSymbol($reader);
397 6
                                                    break;
398
                                            }
399 6
                                            break;
400
                                        default:
401
                                            throw new Exception('Unexpected block type ' + $this->blockType);
402
                                    }
403 26
                                    $this->r2 = $this->r1;
404 26
                                    $this->r1 = $this->r0;
405 26
                                    $this->r0 = $matchOffset;
406 26
                                    break;
407
                            }
408 26
                            $runSrc = 0;
0 ignored issues
show
Unused Code introduced by
$runSrc is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
409 26
                            $runDest = $this->windowPosition;
410 26
                            $thisRun -= $matchLength;
411
                            // copy any wrapped around source data
412 26
                            if ($this->windowPosition >= $matchOffset) {
413
                                // no wrap
414 26
                                $runSrc = $runDest - $matchOffset;
415
                            } else {
416
                                // wrap around
417
                                $runSrc = $runDest + ($this->windowSize - $matchOffset);
418
                                $copyLength = $matchOffset - $this->windowPosition;
419
                                if ($copyLength < $matchLength) {
420
                                    $matchLength -= $copyLength;
421
                                    $this->windowPosition += $copyLength;
422
                                    while ($copyLength-- > 0) {
423
                                        $this->window[$runDest++] = $this->window[$runSrc++];
424
                                    }
425
                                    $runSrc = 0;
426
                                }
427
                            }
428 26
                            $this->windowPosition += $matchLength;
429
                            // copy match data - no worries about destination wraps
430 26
                            while ($matchLength-- > 0) {
431 26
                                $this->window[$runDest++] = $this->window[$runSrc++];
432
                            }
433
                        }
434
                    }
435
                }
436
            }
437
        }
438
439 26
        if ($togo !== 0) {
440
            throw new Exception('should never happens');
441
        }
442
443 26
        $result = array_slice($this->window, ($this->windowPosition === 0 ? $this->windowSize : $this->windowPosition) - $numberOfBytes, $numberOfBytes);
444
445
        // Intel E8 decoding
446 26
        if (($this->intelFilesize !== 0)) {
447
            if ($this->framesRead++ < 32768) {
448
                if ($numberOfBytes <= 6 || $this->intelStarted === false) {
449
                    $this->intelCurrentPosition += $numberOfBytes;
450
                } else {
451
                    $currentPosition = $this->intelCurrentPosition;
452
                    $this->intelCurrentPosition += $numberOfBytes;
453
                    for ($i = 0; $i < $numberOfBytes - 10;) {
454
                        if ($result[$i++] !== 0xe8) {
455
                            ++$currentPosition;
456
                        } else {
457
                            $absoluteOffset = ($result[$i] & 0xff) | (($result[$i + 1] & 0xff) << 8) | (($result[$i + 2] & 0xff) << 16) | (($result[$i + 3] & 0xff) << 24);
458
                            if (($absoluteOffset >= -$currentPosition) && ($absoluteOffset < $this->intelFilesize)) {
459
                                $referenceOffset = ($absoluteOffset >= 0) ? $absoluteOffset - $currentPosition : $absoluteOffset + $this->intelFilesize;
460
                                $result[$i] = $referenceOffset;
461
                                $result[$i + 1] = $referenceOffset >> 8;
462
                                $result[$i + 2] = $referenceOffset >> 16;
463
                                $result[$i + 3] = $referenceOffset >> 24;
464
                            }
465
                            $i += 4;
466
                            $currentPosition += 5;
467
                        }
468
                    }
469
                }
470
            }
471
        }
472
473 26
        return implode('', array_map('chr', $result));
474
    }
475
}
476