1use crate::Error;
19use crate::hpack::static_table;
20
21pub fn encode_header(name: &str, value: &str, out: &mut [u8], len: usize) -> Result<usize, Error> {
23 if let Some(index) = static_table::find(name, value) {
24 return encode_integer(index as u64, 7, 0x80, out, len);
26 }
27
28 let mut len = match static_table::find_name(name) {
29 Some(index) => encode_integer(index as u64, 4, 0x00, out, len)?,
31 None => {
33 let len = encode_integer(0, 4, 0x00, out, len)?;
34 encode_string(name, out, len)?
35 }
36 };
37 len = encode_string(value, out, len)?;
38 Ok(len)
39}
40
41pub fn encode_integer(
43 value: u64,
44 prefix_bits: u32,
45 flags: u8,
46 out: &mut [u8],
47 mut len: usize,
48) -> Result<usize, Error> {
49 if !(1..=8).contains(&prefix_bits) {
50 return Err(Error::Hpack);
51 }
52 let mask = (1u64 << prefix_bits) - 1;
53
54 if value < mask {
55 *out.get_mut(len).ok_or(Error::BufferTooSmall)? = flags | value as u8;
56 return Ok(len + 1);
57 }
58
59 *out.get_mut(len).ok_or(Error::BufferTooSmall)? = flags | mask as u8;
60 len += 1;
61 let mut remaining = value - mask;
62 while remaining >= 0x80 {
63 *out.get_mut(len).ok_or(Error::BufferTooSmall)? = (remaining as u8 & 0x7f) | 0x80;
64 len += 1;
65 remaining >>= 7;
66 }
67 *out.get_mut(len).ok_or(Error::BufferTooSmall)? = remaining as u8;
68 Ok(len + 1)
69}
70
71fn encode_string(text: &str, out: &mut [u8], len: usize) -> Result<usize, Error> {
74 let mut len = encode_integer(text.len() as u64, 7, 0x00, out, len)?;
75 let end = len + text.len();
76 out.get_mut(len..end)
77 .ok_or(Error::BufferTooSmall)?
78 .copy_from_slice(text.as_bytes());
79 len = end;
80 Ok(len)
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use crate::hpack::Decoder;
87
88 #[test]
89 fn encodes_integers_as_the_rfc_section_5_1_examples_do() {
90 let mut out = [0u8; 8];
91 assert_eq!(encode_integer(10, 5, 0x00, &mut out, 0).unwrap(), 1);
92 assert_eq!(out[0], 0x0a);
93
94 let n = encode_integer(1337, 5, 0x00, &mut out, 0).unwrap();
95 assert_eq!(&out[..n], &[0x1f, 0x9a, 0x0a]);
96
97 let n = encode_integer(42, 8, 0x00, &mut out, 0).unwrap();
98 assert_eq!(&out[..n], &[0x2a]);
99 }
100
101 #[test]
102 fn a_header_matching_the_static_table_exactly_costs_one_byte() {
103 let mut out = [0u8; 64];
104 let n = encode_header(":method", "GET", &mut out, 0).unwrap();
105 assert_eq!(&out[..n], &[0x82]);
106
107 let n = encode_header(":scheme", "http", &mut out, 0).unwrap();
108 assert_eq!(&out[..n], &[0x86]);
109 }
110
111 #[test]
115 fn everything_encoded_here_round_trips_through_the_decoder() {
116 let headers = [
117 (":method", "POST"),
118 (":path", "/machine/register"),
119 (":scheme", "http"),
120 (":authority", "127.0.0.1:8080"),
121 ("content-type", "application/json"),
122 ("content-length", "1234"),
123 ("x-tailscale-something", "a value with spaces and ünïcode"),
124 ];
125
126 let mut block = [0u8; 512];
127 let mut len = 0;
128 for (name, value) in headers {
129 len = encode_header(name, value, &mut block, len).unwrap();
130 }
131
132 let mut decoder = Decoder::new(4096);
133 let mut seen = 0;
134 decoder
135 .decode(&block[..len], |name, value| {
136 let (expected_name, expected_value) = headers[seen];
137 assert_eq!(name, expected_name);
138 assert_eq!(value, expected_value);
139 seen += 1;
140 })
141 .unwrap();
142 assert_eq!(seen, headers.len());
143 }
144
145 #[test]
146 fn nothing_we_emit_enters_the_peers_dynamic_table() {
147 let mut block = [0u8; 256];
151 let mut len = 0;
152 len = encode_header(":method", "POST", &mut block, len).unwrap();
153 len = encode_header("content-type", "application/json", &mut block, len).unwrap();
154 len = encode_header("custom", "value", &mut block, len).unwrap();
155
156 let mut decoder = Decoder::new(4096);
157 decoder.decode(&block[..len], |_, _| {}).unwrap();
158 assert!(decoder.table().is_empty());
159 }
160
161 #[test]
162 fn a_buffer_too_small_is_an_error_not_a_truncated_block() {
163 let mut out = [0u8; 4];
164 assert_eq!(
165 encode_header("content-type", "application/json", &mut out, 0),
166 Err(Error::BufferTooSmall)
167 );
168 }
169}