1use alloc::vec::Vec;
15
16use crate::integer::encode_integer;
17use crate::string::encode_string;
18use crate::table::{HeaderField, Table};
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum EncoderError {
23 Reserved,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Default)]
29pub struct Encoder {
30 table: Table,
31 pub use_huffman: bool,
34}
35
36impl Encoder {
37 #[must_use]
39 pub fn new() -> Self {
40 Self::default()
41 }
42
43 #[must_use]
45 pub fn with_max_size(max: usize) -> Self {
46 Self {
47 table: Table::new(max),
48 use_huffman: false,
49 }
50 }
51
52 #[must_use]
54 pub fn table(&self) -> &Table {
55 &self.table
56 }
57
58 pub fn table_mut(&mut self) -> &mut Table {
60 &mut self.table
61 }
62
63 #[must_use]
71 pub fn encode(&mut self, headers: &[HeaderField]) -> Vec<u8> {
72 let mut out = Vec::new();
73 for h in headers {
74 match self.table.find(&h.name, &h.value) {
75 Some((index, true)) => {
76 let buf = encode_integer(index as u64, 7, 0x80);
78 out.extend_from_slice(&buf);
79 }
80 Some((index, false)) => {
81 let buf = encode_integer(index as u64, 6, 0x40);
83 out.extend_from_slice(&buf);
84 out.extend_from_slice(&encode_string(&h.value, self.use_huffman));
85 self.table.add(h.clone());
86 }
87 None => {
88 out.push(0x40);
90 out.extend_from_slice(&encode_string(&h.name, self.use_huffman));
91 out.extend_from_slice(&encode_string(&h.value, self.use_huffman));
92 self.table.add(h.clone());
93 }
94 }
95 }
96 out
97 }
98}
99
100#[cfg(test)]
101#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
102mod tests {
103 use super::*;
104
105 fn hf(n: &str, v: &str) -> HeaderField {
106 HeaderField {
107 name: n.into(),
108 value: v.into(),
109 }
110 }
111
112 #[test]
113 fn full_static_match_is_single_byte() {
114 let mut e = Encoder::new();
115 let buf = e.encode(&[hf(":method", "GET")]);
116 assert_eq!(buf, alloc::vec![0x82]); }
118
119 #[test]
120 fn name_only_static_emits_literal_value() {
121 let mut e = Encoder::new();
122 let buf = e.encode(&[hf(":method", "PATCH")]);
123 assert!(buf[0] & 0xc0 == 0x40);
125 assert_eq!(e.table().len(), 1);
127 }
128
129 #[test]
130 fn unknown_header_emits_literal_name_and_value() {
131 let mut e = Encoder::new();
132 let buf = e.encode(&[hf("custom", "value")]);
133 assert_eq!(buf[0], 0x40); assert_eq!(e.table().len(), 1);
135 }
136
137 #[test]
138 fn second_encode_uses_dynamic_table_match() {
139 let mut e = Encoder::new();
140 let _ = e.encode(&[hf("custom", "value")]);
141 let buf = e.encode(&[hf("custom", "value")]);
142 assert_eq!(buf[0] & 0x80, 0x80);
144 }
145
146 #[test]
147 fn huffman_flag_compresses_literal_strings() {
148 let mut e = Encoder::with_max_size(4096);
149 e.use_huffman = true;
150 let buf = e.encode(&[hf("custom", "value")]);
151 assert!(buf[1] & 0x80 == 0x80, "name string should have H-flag");
154 }
155
156 #[test]
157 fn multiple_headers_encoded_sequentially() {
158 let mut e = Encoder::new();
159 let buf = e.encode(&[hf(":method", "GET"), hf(":scheme", "https")]);
160 assert_eq!(buf, alloc::vec![0x82, 0x87]); }
162
163 #[test]
164 fn encoder_default_uses_no_huffman() {
165 let e = Encoder::new();
166 assert!(!e.use_huffman);
167 }
168}