Skip to main content

net_wire/http2/hpack/
table.rs

1//! RFC 7541 Appendix A static header table.
2
3/// A borrowed HPACK header field with byte-oriented name and value.
4#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5pub struct HpackHeaderFieldRef<'a> {
6    name: &'a [u8],
7    value: &'a [u8],
8}
9
10impl<'a> HpackHeaderFieldRef<'a> {
11    /// Creates a borrowed header field from opaque name and value bytes.
12    pub const fn new(name: &'a [u8], value: &'a [u8]) -> Self {
13        Self { name, value }
14    }
15
16    /// Returns the opaque header name bytes.
17    pub const fn name(self) -> &'a [u8] {
18        self.name
19    }
20
21    /// Returns the opaque header value bytes.
22    pub const fn value(self) -> &'a [u8] {
23        self.value
24    }
25}
26
27/// Number of entries in the RFC 7541 Appendix A static table.
28pub const HPACK_STATIC_TABLE_LEN: usize = 61;
29
30/// RFC 7541 Appendix A's immutable, one-based static header table.
31pub struct HpackStaticTable;
32
33impl HpackStaticTable {
34    /// Returns the number of entries in the static table.
35    pub const fn len() -> usize {
36        HPACK_STATIC_TABLE_LEN
37    }
38
39    /// Returns whether the static table has no entries.
40    pub const fn is_empty() -> bool {
41        false
42    }
43
44    /// Returns the field at a one-based RFC 7541 Appendix A index.
45    pub fn get(index: usize) -> Option<HpackHeaderFieldRef<'static>> {
46        STATIC_TABLE.get(index.checked_sub(1)?).copied()
47    }
48}
49
50const STATIC_TABLE: [HpackHeaderFieldRef<'static>; HPACK_STATIC_TABLE_LEN] = [
51    HpackHeaderFieldRef::new(b":authority", b""),
52    HpackHeaderFieldRef::new(b":method", b"GET"),
53    HpackHeaderFieldRef::new(b":method", b"POST"),
54    HpackHeaderFieldRef::new(b":path", b"/"),
55    HpackHeaderFieldRef::new(b":path", b"/index.html"),
56    HpackHeaderFieldRef::new(b":scheme", b"http"),
57    HpackHeaderFieldRef::new(b":scheme", b"https"),
58    HpackHeaderFieldRef::new(b":status", b"200"),
59    HpackHeaderFieldRef::new(b":status", b"204"),
60    HpackHeaderFieldRef::new(b":status", b"206"),
61    HpackHeaderFieldRef::new(b":status", b"304"),
62    HpackHeaderFieldRef::new(b":status", b"400"),
63    HpackHeaderFieldRef::new(b":status", b"404"),
64    HpackHeaderFieldRef::new(b":status", b"500"),
65    HpackHeaderFieldRef::new(b"accept-charset", b""),
66    HpackHeaderFieldRef::new(b"accept-encoding", b"gzip, deflate"),
67    HpackHeaderFieldRef::new(b"accept-language", b""),
68    HpackHeaderFieldRef::new(b"accept-ranges", b""),
69    HpackHeaderFieldRef::new(b"accept", b""),
70    HpackHeaderFieldRef::new(b"access-control-allow-origin", b""),
71    HpackHeaderFieldRef::new(b"age", b""),
72    HpackHeaderFieldRef::new(b"allow", b""),
73    HpackHeaderFieldRef::new(b"authorization", b""),
74    HpackHeaderFieldRef::new(b"cache-control", b""),
75    HpackHeaderFieldRef::new(b"content-disposition", b""),
76    HpackHeaderFieldRef::new(b"content-encoding", b""),
77    HpackHeaderFieldRef::new(b"content-language", b""),
78    HpackHeaderFieldRef::new(b"content-length", b""),
79    HpackHeaderFieldRef::new(b"content-location", b""),
80    HpackHeaderFieldRef::new(b"content-range", b""),
81    HpackHeaderFieldRef::new(b"content-type", b""),
82    HpackHeaderFieldRef::new(b"cookie", b""),
83    HpackHeaderFieldRef::new(b"date", b""),
84    HpackHeaderFieldRef::new(b"etag", b""),
85    HpackHeaderFieldRef::new(b"expect", b""),
86    HpackHeaderFieldRef::new(b"expires", b""),
87    HpackHeaderFieldRef::new(b"from", b""),
88    HpackHeaderFieldRef::new(b"host", b""),
89    HpackHeaderFieldRef::new(b"if-match", b""),
90    HpackHeaderFieldRef::new(b"if-modified-since", b""),
91    HpackHeaderFieldRef::new(b"if-none-match", b""),
92    HpackHeaderFieldRef::new(b"if-range", b""),
93    HpackHeaderFieldRef::new(b"if-unmodified-since", b""),
94    HpackHeaderFieldRef::new(b"last-modified", b""),
95    HpackHeaderFieldRef::new(b"link", b""),
96    HpackHeaderFieldRef::new(b"location", b""),
97    HpackHeaderFieldRef::new(b"max-forwards", b""),
98    HpackHeaderFieldRef::new(b"proxy-authenticate", b""),
99    HpackHeaderFieldRef::new(b"proxy-authorization", b""),
100    HpackHeaderFieldRef::new(b"range", b""),
101    HpackHeaderFieldRef::new(b"referer", b""),
102    HpackHeaderFieldRef::new(b"refresh", b""),
103    HpackHeaderFieldRef::new(b"retry-after", b""),
104    HpackHeaderFieldRef::new(b"server", b""),
105    HpackHeaderFieldRef::new(b"set-cookie", b""),
106    HpackHeaderFieldRef::new(b"strict-transport-security", b""),
107    HpackHeaderFieldRef::new(b"transfer-encoding", b""),
108    HpackHeaderFieldRef::new(b"user-agent", b""),
109    HpackHeaderFieldRef::new(b"vary", b""),
110    HpackHeaderFieldRef::new(b"via", b""),
111    HpackHeaderFieldRef::new(b"www-authenticate", b""),
112];
113
114/// A caller-owned slot for an HPACK dynamic table entry.
115///
116/// Slots outside the table's current length are inactive. Their contents are not
117/// cleared when entries are evicted or the table is cleared.
118#[derive(Clone, Copy, Debug, Eq, PartialEq)]
119pub struct HpackDynamicTableEntry {
120    offset: usize,
121    name_len: usize,
122    value_len: usize,
123}
124
125impl HpackDynamicTableEntry {
126    /// An inactive entry slot suitable for initializing caller-owned metadata.
127    pub const EMPTY: Self = Self {
128        offset: 0,
129        name_len: 0,
130        value_len: 0,
131    };
132}
133
134/// An HPACK dynamic-table configuration or entry-size failure.
135#[derive(Clone, Copy, Debug, Eq, PartialEq)]
136pub enum HpackDynamicTableError {
137    /// The requested maximum size exceeds the supplied storage's physical capacity.
138    MaximumSizeExceedsCapacity {
139        /// The requested RFC dynamic-table size.
140        requested: usize,
141        /// The greatest RFC dynamic-table size the supplied stores can support.
142        capacity: usize,
143    },
144    /// The RFC entry-size calculation overflowed `usize`.
145    EntrySizeOverflow,
146}
147
148/// The result of inserting a header field into an HPACK dynamic table.
149#[derive(Clone, Copy, Debug, Eq, PartialEq)]
150pub enum HpackDynamicTableInsertResult {
151    /// The field was copied into the table as its newest entry.
152    Inserted,
153    /// The field exceeded the current maximum, so the table was cleared without insertion.
154    NotInsertedOversized,
155}
156
157/// Caller-owned, allocation-free RFC 7541 dynamic-table storage.
158///
159/// Entries are addressed with one-based dynamic indices, where index 1 is the
160/// newest entry. The table stores only its active packed bytes and active metadata;
161/// stale caller storage is never exposed after eviction or [`Self::clear`]. This
162/// type intentionally does not combine dynamic indices with the static table.
163pub struct HpackDynamicTable<'storage, 'entries> {
164    storage: &'storage mut [u8],
165    entries: &'entries mut [HpackDynamicTableEntry],
166    count: usize,
167    size: usize,
168    maximum_size: usize,
169    capacity: usize,
170}
171
172impl<'storage, 'entries> HpackDynamicTable<'storage, 'entries> {
173    /// Creates a table backed by caller-supplied byte and metadata storage.
174    ///
175    /// The maximum is validated before either store is changed.
176    pub fn new(
177        storage: &'storage mut [u8],
178        entries: &'entries mut [HpackDynamicTableEntry],
179        maximum_size: usize,
180    ) -> Result<Self, HpackDynamicTableError> {
181        let capacity = Self::storage_capacity(storage.len(), entries.len());
182        if maximum_size > capacity {
183            return Err(HpackDynamicTableError::MaximumSizeExceedsCapacity {
184                requested: maximum_size,
185                capacity,
186            });
187        }
188        Ok(Self {
189            storage,
190            entries,
191            count: 0,
192            size: 0,
193            maximum_size,
194            capacity,
195        })
196    }
197
198    /// Returns the maximum RFC size supported by the supplied caller storage.
199    pub const fn capacity(&self) -> usize {
200        self.capacity
201    }
202
203    /// Returns the number of active dynamic entries.
204    pub const fn len(&self) -> usize {
205        self.count
206    }
207
208    /// Returns whether no dynamic entries are active.
209    pub const fn is_empty(&self) -> bool {
210        self.count == 0
211    }
212
213    /// Returns the current RFC dynamic-table size.
214    pub const fn size(&self) -> usize {
215        self.size
216    }
217
218    /// Returns the current configured RFC dynamic-table maximum.
219    pub const fn maximum_size(&self) -> usize {
220        self.maximum_size
221    }
222
223    /// Returns the field at a one-based dynamic index, where 1 is newest.
224    pub fn get(&self, index: usize) -> Option<HpackHeaderFieldRef<'_>> {
225        let slot = index.checked_sub(1)?;
226        if slot >= self.count {
227            return None;
228        }
229        let entry = self.entries.get(slot)?;
230        let name_end = entry.offset.checked_add(entry.name_len)?;
231        let value_end = name_end.checked_add(entry.value_len)?;
232        Some(HpackHeaderFieldRef::new(
233            self.storage.get(entry.offset..name_end)?,
234            self.storage.get(name_end..value_end)?,
235        ))
236    }
237
238    /// Removes all active entries without scrubbing inactive caller storage.
239    pub fn clear(&mut self) {
240        self.count = 0;
241        self.size = 0;
242    }
243
244    /// Updates the configured maximum, evicting oldest entries immediately if needed.
245    pub fn set_maximum_size(&mut self, maximum_size: usize) -> Result<(), HpackDynamicTableError> {
246        if maximum_size > self.capacity {
247            return Err(HpackDynamicTableError::MaximumSizeExceedsCapacity {
248                requested: maximum_size,
249                capacity: self.capacity,
250            });
251        }
252        self.set_maximum_size_prevalidated(maximum_size);
253        Ok(())
254    }
255
256    /// Updates the maximum after the caller established `maximum_size <= self.capacity`.
257    pub(super) fn set_maximum_size_prevalidated(&mut self, maximum_size: usize) {
258        let mut count = self.count;
259        let mut size = self.size;
260        while count > 0 && size > maximum_size {
261            count -= 1;
262            size -= Self::entry_size_from_slot(self.entries[count]);
263        }
264        self.count = count;
265        self.size = size;
266        self.maximum_size = maximum_size;
267    }
268
269    /// Calculates an RFC 7541 entry size from opaque name and value bytes.
270    pub fn entry_size(name: &[u8], value: &[u8]) -> Result<usize, HpackDynamicTableError> {
271        name.len()
272            .checked_add(value.len())
273            .and_then(|length| length.checked_add(32))
274            .ok_or(HpackDynamicTableError::EntrySizeOverflow)
275    }
276
277    /// Copies a field into the table as the newest entry, evicting oldest entries as needed.
278    pub fn insert(
279        &mut self,
280        name: &[u8],
281        value: &[u8],
282    ) -> Result<HpackDynamicTableInsertResult, HpackDynamicTableError> {
283        let entry_size = Self::entry_size(name, value)?;
284        Ok(self.insert_prevalidated(name, value, entry_size))
285    }
286
287    /// Inserts a field whose RFC entry size was already calculated by [`Self::entry_size`].
288    ///
289    /// This HPACK-module path is infallible so callers can preflight before externally visible
290    /// writes; `entry_size - 32` is the checked name-plus-value length established by that call.
291    pub(super) fn insert_prevalidated(
292        &mut self,
293        name: &[u8],
294        value: &[u8],
295        entry_size: usize,
296    ) -> HpackDynamicTableInsertResult {
297        if entry_size > self.maximum_size {
298            self.clear();
299            return HpackDynamicTableInsertResult::NotInsertedOversized;
300        }
301
302        let byte_len = entry_size - 32;
303        let mut retained_count = self.count;
304        let mut retained_size = self.size;
305        while retained_count > 0 && retained_size > self.maximum_size - entry_size {
306            retained_count -= 1;
307            retained_size -= Self::entry_size_from_slot(self.entries[retained_count]);
308        }
309        let retained_bytes = if retained_count == 0 {
310            0
311        } else {
312            let last = self.entries[retained_count - 1];
313            last.offset + last.name_len + last.value_len
314        };
315
316        // Validation above plus the constructor capacity proof guarantees these ranges fit.
317        self.storage.copy_within(0..retained_bytes, byte_len);
318        if retained_count > 0 {
319            self.entries.copy_within(0..retained_count, 1);
320            for entry in &mut self.entries[1..=retained_count] {
321                entry.offset += byte_len;
322            }
323        }
324        self.storage[..name.len()].copy_from_slice(name);
325        self.storage[name.len()..byte_len].copy_from_slice(value);
326        self.entries[0] = HpackDynamicTableEntry {
327            offset: 0,
328            name_len: name.len(),
329            value_len: value.len(),
330        };
331        self.count = retained_count + 1;
332        self.size = retained_size + entry_size;
333        HpackDynamicTableInsertResult::Inserted
334    }
335
336    const fn storage_capacity(storage_len: usize, entry_capacity: usize) -> usize {
337        let bytes = storage_len.saturating_add(32);
338        let entries = entry_capacity.saturating_mul(32).saturating_add(31);
339        if bytes < entries { bytes } else { entries }
340    }
341
342    const fn entry_size_from_slot(entry: HpackDynamicTableEntry) -> usize {
343        entry.name_len + entry.value_len + 32
344    }
345}