Loading src/hpack.cpp +70 −3 Changes for src/hpack.cpp: 70 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ #include <array> #include <cstring> #include <stdexcept> Loading Loading @@ -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) { Loading @@ -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; Loading Loading
src/hpack.cpp +70 −3 Changes for src/hpack.cpp: 70 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ #include <array> #include <cstring> #include <stdexcept> Loading Loading @@ -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) { Loading @@ -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; Loading