use crate::Error;
use crate::hpack::static_table;
pub fn encode_header(name: &str, value: &str, out: &mut [u8], len: usize) -> Result<usize, Error> {
if let Some(index) = static_table::find(name, value) {
return encode_integer(index as u64, 7, 0x80, out, len);
}
let mut len = match static_table::find_name(name) {
Some(index) => encode_integer(index as u64, 4, 0x00, out, len)?,
None => {
let len = encode_integer(0, 4, 0x00, out, len)?;
encode_string(name, out, len)?
}
};
len = encode_string(value, out, len)?;
Ok(len)
}
pub fn encode_integer(
value: u64,
prefix_bits: u32,
flags: u8,
out: &mut [u8],
mut len: usize,
) -> Result<usize, Error> {
let mask = (1u64 << prefix_bits) - 1;
if value < mask {
*out.get_mut(len).ok_or(Error::BufferTooSmall)? = flags | value as u8;
return Ok(len + 1);
}
*out.get_mut(len).ok_or(Error::BufferTooSmall)? = flags | mask as u8;
len += 1;
let mut remaining = value - mask;
while remaining >= 0x80 {
*out.get_mut(len).ok_or(Error::BufferTooSmall)? = (remaining as u8 & 0x7f) | 0x80;
len += 1;
remaining >>= 7;
}
*out.get_mut(len).ok_or(Error::BufferTooSmall)? = remaining as u8;
Ok(len + 1)
}
fn encode_string(text: &str, out: &mut [u8], len: usize) -> Result<usize, Error> {
let mut len = encode_integer(text.len() as u64, 7, 0x00, out, len)?;
let end = len + text.len();
out.get_mut(len..end)
.ok_or(Error::BufferTooSmall)?
.copy_from_slice(text.as_bytes());
len = end;
Ok(len)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hpack::Decoder;
#[test]
fn encodes_integers_as_the_rfc_section_5_1_examples_do() {
let mut out = [0u8; 8];
assert_eq!(encode_integer(10, 5, 0x00, &mut out, 0).unwrap(), 1);
assert_eq!(out[0], 0x0a);
let n = encode_integer(1337, 5, 0x00, &mut out, 0).unwrap();
assert_eq!(&out[..n], &[0x1f, 0x9a, 0x0a]);
let n = encode_integer(42, 8, 0x00, &mut out, 0).unwrap();
assert_eq!(&out[..n], &[0x2a]);
}
#[test]
fn a_header_matching_the_static_table_exactly_costs_one_byte() {
let mut out = [0u8; 64];
let n = encode_header(":method", "GET", &mut out, 0).unwrap();
assert_eq!(&out[..n], &[0x82]);
let n = encode_header(":scheme", "http", &mut out, 0).unwrap();
assert_eq!(&out[..n], &[0x86]);
}
#[test]
fn everything_encoded_here_round_trips_through_the_decoder() {
let headers = [
(":method", "POST"),
(":path", "/machine/register"),
(":scheme", "http"),
(":authority", "127.0.0.1:8080"),
("content-type", "application/json"),
("content-length", "1234"),
("x-tailscale-something", "a value with spaces and ünïcode"),
];
let mut block = [0u8; 512];
let mut len = 0;
for (name, value) in headers {
len = encode_header(name, value, &mut block, len).unwrap();
}
let mut decoder = Decoder::new(4096);
let mut seen = 0;
decoder
.decode(&block[..len], |name, value| {
let (expected_name, expected_value) = headers[seen];
assert_eq!(name, expected_name);
assert_eq!(value, expected_value);
seen += 1;
})
.unwrap();
assert_eq!(seen, headers.len());
}
#[test]
fn nothing_we_emit_enters_the_peers_dynamic_table() {
let mut block = [0u8; 256];
let mut len = 0;
len = encode_header(":method", "POST", &mut block, len).unwrap();
len = encode_header("content-type", "application/json", &mut block, len).unwrap();
len = encode_header("custom", "value", &mut block, len).unwrap();
let mut decoder = Decoder::new(4096);
decoder.decode(&block[..len], |_, _| {}).unwrap();
assert!(decoder.table().is_empty());
}
#[test]
fn a_buffer_too_small_is_an_error_not_a_truncated_block() {
let mut out = [0u8; 4];
assert_eq!(
encode_header("content-type", "application/json", &mut out, 0),
Err(Error::BufferTooSmall)
);
}
}