Commit 35440920 authored by jan.koester's avatar jan.koester
Browse files

bugfix

parent 6a16b739
Loading
Loading
Loading
Loading
+70 −3
Changes for src/hpack.cpp: 70 added lines, 3 removed lines.
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *******************************************************************************/

#include <array>
#include <cstring>
#include <stdexcept>

@@ -173,20 +174,86 @@ static const HuffEntry HUFF_TABLE[257] = {
    {0x3fffffff, 30}, // EOS (256)
};

// Fast path: any Huffman code of length <= kFastBits resolves via one
// array lookup instead of the O(256) linear scan below. RFC 7541 Appendix B's
// codes for space/digits/lowercase letters/common punctuation -- the bulk of
// real header values (paths, cookies, tokens) -- are all <= 8 bits, so this
// table absorbs the overwhelming majority of decode calls; the linear scan
// remains as the exact, unmodified fallback for the ~230 rarer symbols with
// longer codes, so correctness for those is unchanged from before.
namespace {
constexpr int kFastBits = 8;
struct FastEntry { uint8_t symbol; uint8_t bits; }; // bits==0 => no <=kFastBits code matches

const std::array<FastEntry, 1 << kFastBits> &huffFastTable() {
    static const std::array<FastEntry, 1 << kFastBits> table = [] {
        std::array<FastEntry, 1 << kFastBits> t{};  // bits==0 by default (invalid)
        for (int sym = 0; sym < 256; ++sym) {
            uint8_t codelen = HUFF_TABLE[sym].bits;
            if (codelen == 0 || codelen > kFastBits) continue;
            uint32_t code = HUFF_TABLE[sym].code;
            int shift = kFastBits - codelen;
            uint32_t base = code << shift;
            uint32_t count = 1u << shift;
            // Codes are prefix-free, so these ranges never overlap across symbols.
            for (uint32_t j = 0; j < count; ++j) {
                t[base + j] = FastEntry{static_cast<uint8_t>(sym), codelen};
            }
        }
        return t;
    }();
    return table;
}
} // namespace

std::string Decoder::huffmanDecode(const uint8_t *data, size_t len) {
    std::string out;
    uint64_t bits = 0;
    int nbits = 0;
    const auto &fastTable = huffFastTable();
    // Longest real (non-EOS) code in HUFF_TABLE is 30 bits (RFC 7541 App. B).
    constexpr int kMaxCodeBits = 30;

    for (size_t i = 0; i < len; ++i) {
        bits = (bits << 8) | data[i];
        nbits += 8;

        while (nbits >= 5) { // shortest Huffman code is 5 bits
        // On non-conformant input, no code of any length (5..kMaxCodeBits)
        // may ever match a stuck bit window -- appending more trailing bits
        // can't change that (they only extend the window, never its fixed
        // top bits), so nbits would otherwise grow without bound on
        // malformed/adversarial data until it exceeds 64 and `bits` silently
        // loses its high bits (shift-by->=64 is undefined behavior). Once
        // truly stuck past the longest real code length, stop decoding.
        if (nbits > kMaxCodeBits + 8) break;

        for (;;) {
            // minSlowLen bounds the fallback scan below: 0 means "check every
            // length from 5" (the fast table needs a full kFastBits-bit
            // window to index into, so it can't be consulted yet -- this is
            // NOT the same as "no code that short can match"). Once the fast
            // table *has* been consulted and missed, only longer codes are
            // left to check.
            int minSlowLen = 0;

            if (nbits >= kFastBits) {
                uint32_t idx = static_cast<uint32_t>(bits >> (nbits - kFastBits)) & ((1u << kFastBits) - 1);
                const FastEntry &fe = fastTable[idx];
                if (fe.bits != 0) {
                    out.push_back(static_cast<char>(fe.symbol));
                    nbits -= fe.bits;
                    bits &= (1ULL << nbits) - 1;
                    continue;
                }
                minSlowLen = kFastBits + 1;
            }

            if (nbits < 5) break; // shortest Huffman code is 5 bits -- need more input

            bool found = false;
            for (int sym = 0; sym < 256; ++sym) {
                uint8_t codelen = HUFF_TABLE[sym].bits;
                if (codelen > (uint8_t)nbits) continue;
                if (codelen < minSlowLen || codelen > (uint8_t)nbits) continue;
                uint32_t code = HUFF_TABLE[sym].code;
                uint32_t candidate = static_cast<uint32_t>((bits >> (nbits - codelen)) & ((1ULL << codelen) - 1));
                if (candidate == code) {
@@ -197,7 +264,7 @@ std::string Decoder::huffmanDecode(const uint8_t *data, size_t len) {
                    break;
                }
            }
            if (!found) break; // remaining bits are padding
            if (!found) break; // remaining bits are padding, or need more input
        }
    }
    return out;