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