Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- *(qpack)* dynamic-table encoder: `QpackEncoder::with_dynamic_table` + `encode`
drive the encoder stream (Set Dynamic Table Capacity, Insert with
Name Reference against static/dynamic names, Insert with Literal Name) and
emit field sections with dynamic indexed / name-reference representations and
a non-zero Required Insert Count. Entries referenced by a field section are
never evicted by inserts in the same batch; sensitive fields stay out of the
table (never-indexed). The previous static-only `encode_field_section` path is
unchanged. QPACK is now fully bidirectional (encode + decode, static +
dynamic), matching HPACK.

## [0.6.5](https://github.com/KarpelesLab/compcol/compare/v0.6.4...v0.6.5) - 2026-06-15

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ flag, and a `compcol` binary turns the library into a Unix-style filter.
| RAR 3.x | `rar3` | `.rar` | `Unsupported` (license) | full LZ77+Huffman + E8 filter; PPMd & VM filters refused | libarchive RAR3 fixtures |
| RAR 5.x | `rar5` | `.rar` | `Unsupported` (license) | full LZ77+Huffman + x86 filter; Delta/ARM refused | RARLAB-CLI fixtures |
| HTTP/2 HPACK (RFC 7541) | `hpack` | — | full (header codec + `h2-huffman` string codec) | full (static+dynamic tables, integer/string coding) | RFC 7541 Appendix C vectors |
| HTTP/3 QPACK (RFC 9204) | `qpack` | — | static-table + literal encoder; full decoder | full (static+dynamic tables via encoder stream, all field representations) | RFC 9204 Appendix B vectors |
| HTTP/3 QPACK (RFC 9204) | `qpack` | — | full (static + dynamic-table encoder driving the encoder stream; eviction-safe) | full (static+dynamic tables via encoder stream, all field representations) | RFC 9204 Appendix B vectors |
| Canonical Huffman (standalone) | `huffman` | `.huff` | full (length-limited, self-delimiting) | full | own round-trip |
| Range coder (adaptive order-0) | `rangecoder` | `.range` | full | full | own round-trip |
| Move-To-Front transform | `mtf` | `mtf` | full (reversible filter) | full | round-trip identity |
Expand Down
43 changes: 43 additions & 0 deletions src/qpack/dynamic_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,49 @@ impl DynamicTable {
Some(abs)
}

/// Find the best live entry for `(name, value)`, used by the encoder mirror
/// to reference existing insertions. Prefers a full name+value match
/// (returning `value_matched = true`), else the most recent name-only match.
/// Returns the **absolute** index. Newest matches are preferred so the
/// reference is least likely to be evicted soon.
pub(crate) fn find(&self, name: &[u8], value: &[u8]) -> Option<(usize, bool)> {
let mut name_only: Option<usize> = None;
for (i, (n, v)) in self.entries.iter().enumerate().rev() {
if n.as_slice() == name {
let abs = self.dropped + i;
if v.as_slice() == value {
return Some((abs, true));
}
if name_only.is_none() {
name_only = Some(abs);
}
}
}
name_only.map(|abs| (abs, false))
}

/// The absolute index of the oldest entry that would survive inserting an
/// entry of `incoming` bytes (i.e. the post-insert `dropped`). Every live
/// entry with absolute index `< evict_floor(incoming)` would be evicted to
/// make room. Pure — does not mutate. The encoder uses this to avoid
/// evicting entries it still references in the field section being built.
pub(crate) fn evict_floor(&self, incoming: usize) -> usize {
let mut size = self.size;
let mut dropped = self.dropped;
let mut i = 0;
while size + incoming > self.capacity {
match self.entries.get(i) {
Some((n, v)) => {
size -= Self::entry_size(n, v);
dropped += 1;
i += 1;
}
None => break,
}
}
dropped
}

/// Look up an entry by **absolute** index. Returns `None` if the index has
/// been evicted or never inserted.
pub(crate) fn get_absolute(&self, abs: usize) -> Option<(&[u8], &[u8])> {
Expand Down
Loading
Loading