Skip to content

http2: decode HPACK Huffman strings without repeated slicing - #5102

Merged
gpotter2 merged 1 commit into
secdev:masterfrom
KernelClint:perf/hpack-huffman-decode
Aug 25, 2026
Merged

http2: decode HPACK Huffman strings without repeated slicing#5102
gpotter2 merged 1 commit into
secdev:masterfrom
KernelClint:perf/hpack-huffman-decode

Conversation

@KernelClint

Copy link
Copy Markdown
Contributor

HPACK Huffman decoding rebuilt the remaining bit string on each symbol, so a valid Huffman-coded
value cost time quadratic in its length.

This decodes over the input bytes instead. Full-EOS rejection and the RFC 7541 padding checks are
unchanged; integer callers convert their input to bytes once.

Valid short Huffman parses took 50,945.5 ns unmodified and 42,879.6 ns with the change.

The HTTP/2 tests pass 106 of 106. Two byte-decoding checks were added and fail without the change.

HPACK Huffman decoding rebuilt the remaining bit string on each symbol,
making a valid Huffman-coded value cost time quadratic in its length.

Decode over the input bytes instead. Full-EOS rejection and the RFC 7541
padding checks are unchanged; integer callers convert to bytes once.

Valid short Huffman parses took 50,945.5 ns unmodified and 42,879.6 ns
with this change.

AI-Assisted: yes (GPT-5.6-Cyber)
@KernelClint

Copy link
Copy Markdown
Contributor Author

Adding the numbers I should have put in the description. The two in there (50,945.5 ns → 42,879.6 ns) are only a check that this doesn't slow the normal path — they aren't the benefit.

The problem is how the cost grows. The decoder rebuilds the remaining bit string for every symbol, so parsing one valid HEADERS frame costs time proportional to the square of its length:

frame size parse time
2,013 bytes 6.22 ms
16,013 bytes 272.22 ms

Eight times the size, roughly forty-four times the work.

The same bytes with the Huffman flag cleared parse in under a millisecond, so the cost is entirely in the decoder and not in the surrounding frame handling. Nothing is malformed, and at 16,013 bytes the frame is under the default 16,384-byte maximum — this is something an ordinary client can send.

@codecov

codecov Bot commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.59%. Comparing base (1f87020) to head (1d07032).
⚠️ Report is 4 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5102      +/-   ##
==========================================
+ Coverage   79.42%   80.59%   +1.16%     
==========================================
  Files         372      390      +18     
  Lines       96507    96876     +369     
==========================================
+ Hits        76652    78075    +1423     
+ Misses      19855    18801    -1054     
Files with missing lines Coverage Δ
scapy/contrib/http2.py 97.10% <100.00%> (+0.06%) ⬆️

... and 302 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@gpotter2
gpotter2 merged commit 23f7000 into secdev:master Aug 25, 2026
23 checks passed
@gpotter2 gpotter2 added this to the next milestone Aug 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants