sozu_lib/protocol/mux/hpack/encoder.rs
1//! HPACK encoder (RFC 7541 §6) writing into a buffer the caller owns.
2//!
3//! [`Encoder::encode_header_into`] applies the representation policy sozu has
4//! always sent — the one `loona-hpack` 0.4.3 applied, reproduced here as
5//! behavior so that the bytes on the wire do not change:
6//!
7//! - name and value found in a table: Indexed Header Field (§6.1);
8//! - only the name found: Literal without Indexing, indexed name (§6.2.2),
9//! naming the *last* entry holding that name when the static table is
10//! scanned, then the dynamic table from its newest entry — `:method: PUT`
11//! names index 3 (`:method: POST`), `:status: 201` names index 14;
12//! - neither found: Literal with Incremental Indexing, new name (§6.2.1), and
13//! the field enters the dynamic table.
14//!
15//! Strings are sent without Huffman coding. The dynamic table only ever holds
16//! fields whose name is in no table, so the peer's decoder sees at most one
17//! entry per name.
18
19use super::{
20 encode_integer, huffman,
21 table::{DEFAULT_MAX_SIZE, DYNAMIC_TABLE_START, DynamicTable, STATIC_TABLE},
22};
23
24/// The representation [`Encoder::encode_field`] emits. Sozu itself only sends
25/// [`Representation::Proxy`]; the others reproduce RFC 7541 Appendix C and let
26/// a test peer or a fuzz target produce every representation a decoder meets.
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum Representation {
29 /// The policy of [`Encoder::encode_header_into`].
30 Proxy,
31 /// §6.2.1, or §6.1 when name and value are found.
32 IncrementalIndexing,
33 /// §6.2.2, never §6.1.
34 WithoutIndexing,
35 /// §6.2.3, never §6.1.
36 NeverIndexed,
37}
38
39/// What a table search found.
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41enum Found {
42 Field(usize),
43 /// The first and the last index holding the name. [`Representation::Proxy`]
44 /// names the last one, as `loona-hpack` did; the others name the first,
45 /// as RFC 7541 Appendix C does.
46 Name {
47 first: usize,
48 last: usize,
49 },
50 Nothing,
51}
52
53/// The table-size changes the peer's decoder has not been told about yet.
54///
55/// RFC 7541 §4.2: when the maximum size changes more than once between two
56/// blocks, the next block opens with the smallest size reached, then the final
57/// size. The smallest one is what decides which entries this encoder evicted;
58/// the final one is what both tables hold from then on.
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60struct PendingSizeUpdate {
61 /// The smallest maximum size set since the last size update was encoded.
62 smallest: usize,
63 /// The maximum size set last.
64 last: usize,
65}
66
67/// The connection-level HPACK encoder of one direction.
68#[derive(Debug)]
69pub struct Encoder {
70 table: DynamicTable,
71 /// Set by [`Encoder::change_max_table_size`], emptied by
72 /// [`Encoder::encode_size_updates_into`].
73 pending_size_update: Option<PendingSizeUpdate>,
74}
75
76impl Default for Encoder {
77 fn default() -> Self {
78 Self::new()
79 }
80}
81
82impl Encoder {
83 /// An encoder with the default 4096-octet table. Allocates nothing.
84 pub const fn new() -> Self {
85 Encoder {
86 table: DynamicTable::new(DEFAULT_MAX_SIZE),
87 pending_size_update: None,
88 }
89 }
90
91 /// Sets the maximum size of the table, at most the peer's
92 /// `SETTINGS_HEADER_TABLE_SIZE`, and evicts until it fits (§4.3). The
93 /// caller signals the change to the peer with a size update (§6.3) at the
94 /// start of its next block; [`Self::change_max_table_size`] records it for
95 /// [`Self::encode_size_updates_into`] instead.
96 pub fn set_max_table_size(&mut self, max_size: usize) {
97 self.table.set_max_size(max_size);
98 }
99
100 /// Sets the maximum size like [`Self::set_max_table_size`] and records the
101 /// change for the size updates that open the next block (§4.2): the
102 /// smallest size set since the last block, and the last one.
103 pub fn change_max_table_size(&mut self, max_size: usize) {
104 self.table.set_max_size(max_size);
105 let smallest = self
106 .pending_size_update
107 .map_or(max_size, |pending| pending.smallest.min(max_size));
108 self.pending_size_update = Some(PendingSizeUpdate {
109 smallest,
110 last: max_size,
111 });
112 debug_assert!(
113 smallest <= max_size,
114 "the smallest size reached never exceeds the last one"
115 );
116 debug_assert!(
117 self.pending_size_update
118 .is_some_and(|pending| pending.last == max_size),
119 "the change is recorded as the last one"
120 );
121 }
122
123 /// Appends the size updates (§6.3) that open a block after the maximum
124 /// size changed to `last_size`, and forgets the changes recorded by
125 /// [`Self::change_max_table_size`].
126 ///
127 /// When the size went below `last_size` since the last block, the
128 /// smallest size goes first (§4.2): the peer's decoder must evict what
129 /// this encoder evicted, or the two tables no longer hold the same
130 /// entries. `last_size` follows, alone when nothing smaller was set.
131 /// Returns the number of updates appended, 1 or 2 — the most a block may
132 /// open with.
133 pub fn encode_size_updates_into(&mut self, last_size: usize, out: &mut Vec<u8>) -> usize {
134 let pending = self.pending_size_update.take();
135 debug_assert!(
136 pending.is_none_or(|pending| pending.last == last_size),
137 "the size the caller signals is the last one recorded"
138 );
139 let start = out.len();
140 let mut updates = 0;
141 if let Some(pending) = pending
142 && pending.smallest < last_size
143 {
144 encode_integer(pending.smallest, 5, 0x20, out);
145 updates += 1;
146 }
147 encode_integer(last_size, 5, 0x20, out);
148 updates += 1;
149 debug_assert!(
150 (1..=2).contains(&updates),
151 "a block opens with at most two size updates (§4.2)"
152 );
153 debug_assert!(
154 out[start] & 0xe0 == 0x20 && self.pending_size_update.is_none(),
155 "the appended bytes open with a size update, and nothing stays recorded"
156 );
157 updates
158 }
159
160 /// Empties the dynamic table and records the size updates that make the
161 /// peer's decoder empty its own: `0`, then the current maximum size. RFC
162 /// 7541 §4.2 provides exactly this: "This mechanism can be used to
163 /// completely clear entries from the dynamic table by setting a maximum
164 /// size of 0, which can subsequently be restored." Returns that maximum
165 /// size, which the caller signals through
166 /// [`Self::encode_size_updates_into`] at the start of its next block.
167 ///
168 /// The table changes when a block is ENCODED, not when it reaches the
169 /// peer, so this is how a caller that dropped a block it encoded brings
170 /// the two tables back together (sozu-proxy/sozu#1627). Entries are
171 /// numbered from the newest (§2.3.3): every insertion the peer missed
172 /// moves the older entries down one index in this table only, and a later
173 /// block naming one of them makes the peer read another field, with no
174 /// error. After the reset both tables are empty, whatever the peer missed,
175 /// at the cost of the entries the next blocks would have reused.
176 ///
177 /// Sound only when no block encoded before this call reaches the peer
178 /// after the block that carries the updates: that later block would name
179 /// entries of the table this empties.
180 pub fn reset_table(&mut self) -> usize {
181 let max_size = self.table.max_size();
182 self.change_max_table_size(0);
183 self.change_max_table_size(max_size);
184 debug_assert!(
185 self.table.len() == 0 && self.table.max_size() == max_size,
186 "the table is empty and keeps its maximum size"
187 );
188 debug_assert_eq!(
189 self.pending_size_update,
190 Some(PendingSizeUpdate {
191 smallest: 0,
192 last: max_size,
193 }),
194 "the next block opens with 0, then the maximum size"
195 );
196 max_size
197 }
198
199 #[cfg(test)]
200 pub(super) fn table(&self) -> &DynamicTable {
201 &self.table
202 }
203
204 /// Appends the representation of one field to `out`.
205 pub fn encode_header_into(&mut self, (name, value): (&[u8], &[u8]), out: &mut Vec<u8>) {
206 self.encode_field(name, value, Representation::Proxy, false, out);
207 }
208
209 /// Appends the representation of every field to `out`, in order.
210 pub fn encode_into<'a, I>(&mut self, headers: I, out: &mut Vec<u8>)
211 where
212 I: IntoIterator<Item = (&'a [u8], &'a [u8])>,
213 {
214 for header in headers {
215 self.encode_header_into(header, out);
216 }
217 }
218
219 /// Encodes `headers` into a new block.
220 #[cfg(test)]
221 pub fn encode<'a, I>(&mut self, headers: I) -> Vec<u8>
222 where
223 I: IntoIterator<Item = (&'a [u8], &'a [u8])>,
224 {
225 let mut out = Vec::new();
226 self.encode_into(headers, &mut out);
227 out
228 }
229
230 /// Appends one field in the requested representation, the strings Huffman
231 /// coded when `huffman`.
232 pub fn encode_field(
233 &mut self,
234 name: &[u8],
235 value: &[u8],
236 representation: Representation,
237 huffman: bool,
238 out: &mut Vec<u8>,
239 ) {
240 let found = self.find(name, value);
241 match (representation, found) {
242 (Representation::Proxy | Representation::IncrementalIndexing, Found::Field(index)) => {
243 encode_integer(index, 7, 0x80, out);
244 }
245 (Representation::Proxy, Found::Name { last, .. }) => {
246 encode_integer(last, 4, 0x00, out);
247 encode_string(value, huffman, out);
248 }
249 (Representation::Proxy | Representation::IncrementalIndexing, Found::Nothing) => {
250 out.push(0x40);
251 encode_string(name, huffman, out);
252 encode_string(value, huffman, out);
253 self.table.insert(name, value);
254 }
255 (Representation::IncrementalIndexing, Found::Name { first, .. }) => {
256 encode_integer(first, 6, 0x40, out);
257 encode_string(value, huffman, out);
258 self.table.insert(name, value);
259 }
260 (Representation::WithoutIndexing | Representation::NeverIndexed, found) => {
261 let flags = if representation == Representation::NeverIndexed {
262 0x10
263 } else {
264 0x00
265 };
266 match found {
267 Found::Field(index) | Found::Name { first: index, .. } => {
268 encode_integer(index, 4, flags, out);
269 }
270 Found::Nothing => {
271 out.push(flags);
272 encode_string(name, huffman, out);
273 }
274 }
275 encode_string(value, huffman, out);
276 }
277 }
278 }
279
280 /// Searches the static table, then the dynamic table from its newest
281 /// entry: the first index holding both name and value, else the first and
282 /// the last index holding the name.
283 fn find(&self, name: &[u8], value: &[u8]) -> Found {
284 let entries = DYNAMIC_TABLE_START + self.table.len();
285 let mut found = Found::Nothing;
286 let tables = STATIC_TABLE
287 .iter()
288 .copied()
289 .chain(self.table.iter())
290 .enumerate();
291 for (position, (entry_name, entry_value)) in tables {
292 if entry_name != name {
293 continue;
294 }
295 let index = position + 1;
296 if entry_value == value {
297 return Found::Field(index);
298 }
299 found = match found {
300 Found::Name { first, .. } => Found::Name { first, last: index },
301 _ => Found::Name {
302 first: index,
303 last: index,
304 },
305 };
306 }
307 debug_assert!(
308 match found {
309 Found::Field(index) => (1..entries).contains(&index),
310 Found::Name { first, last } => 0 < first && first <= last && last < entries,
311 Found::Nothing => true,
312 },
313 "a found index addresses an entry of one of the two tables"
314 );
315 found
316 }
317}
318
319/// Appends a string literal (§5.2).
320fn encode_string(string: &[u8], huffman: bool, out: &mut Vec<u8>) {
321 if huffman {
322 encode_integer(huffman::encoded_len(string), 7, 0x80, out);
323 huffman::encode(string, out);
324 } else {
325 encode_integer(string.len(), 7, 0x00, out);
326 out.extend_from_slice(string);
327 }
328}