protected function decodeGeneric(string $input, int $bits) : string { if ($this->symbol === NULL) { list($this->symbol, $this->codes, $this->codeLengths, $this->first, $this->steps) = $this->code->getDecoderData(); $this->stepCount = count($this->steps); } $len = strlen($input); $decoded = ''; $buffer = NULL; $byteOffset = 0; $bitOffset = 7; while (true) { $code = 0; $codeLen = 0; // Increment in steps to avoid checking codes with a length that is not used by Huffman codes. for ($step = 0; $step < $this->stepCount; $step++) { for ($i = 0; $i < $this->steps[$step]; $i++) { if ($bits-- == 0) { break; } if ($buffer === NULL) { if ($byteOffset == $len) { if ($code === 0 || $this->isHuffmanPaddingCode($code)) { return $decoded; } throw new \RuntimeException('Cannot read beyond end of Huffman-encoded string'); } $buffer = ord($input[$byteOffset]); } // Read next bit and and append it as LSB (least significant bit) to the code. $code = $code << 1 | $buffer >> $bitOffset-- & 1; $codeLen++; if ($bitOffset == -1) { $byteOffset++; $bitOffset = 7; $buffer = NULL; } } // Match code against all codes with the same length. for ($i = 0; $i < $this->codeLengths[$codeLen]; $i++) { if ($this->codes[$this->first[$codeLen] + $i] == $code) { $decoded .= $this->symbol[sprintf('%0' . $codeLen . 'b', $code)]; if ($bits == 0) { return $decoded; } continue 3; } } if ($bits == 0) { if ($codeLen == 0) { return $decoded; } break; } } if ($this->isHuffmanPaddingCode($code)) { return $decoded; } break; } throw new \RuntimeException('Invalid Huffman code detected'); }