use crate::headers::{
compression_error::CompressionError,
entry_name::EntryName,
huffman, integer_prefix,
qpack::{FieldLineValue, instruction::encode_string},
};
const BASE_DELTA_SIGN: u8 = 0x80;
const INDEXED_FIELD_LINE: u8 = 0x80;
const INDEXED_STATIC_FLAG: u8 = 0x40;
const POST_BASE_INDEXED: u8 = 0x10;
const LITERAL_WITH_NAME_REF: u8 = 0x40;
const NAME_REF_NEVER_INDEXED_FLAG: u8 = 0x20;
const NAME_REF_STATIC_FLAG: u8 = 0x10;
const POST_BASE_NAME_REF_NEVER_INDEXED_FLAG: u8 = 0x08;
const LITERAL_WITH_LITERAL_NAME: u8 = 0x20;
const LITERAL_NAME_NEVER_INDEXED_FLAG: u8 = 0x10;
#[derive(Debug, PartialEq, Eq)]
pub(in crate::headers) enum FieldLineInstruction<'a> {
IndexedStatic { index: usize },
IndexedDynamic { relative_index: usize },
IndexedPostBase { post_base_index: usize },
LiteralStaticNameRef {
name_index: usize,
value: FieldLineValue<'a>,
never_indexed: bool,
},
LiteralDynamicNameRef {
relative_index: usize,
value: FieldLineValue<'a>,
never_indexed: bool,
},
LiteralPostBaseNameRef {
post_base_index: usize,
value: FieldLineValue<'a>,
never_indexed: bool,
},
LiteralLiteralName {
name: EntryName<'a>,
value: FieldLineValue<'a>,
never_indexed: bool,
},
}
impl<'a> FieldLineInstruction<'a> {
pub(in crate::headers) fn parse(input: &'a [u8]) -> Result<(Self, &'a [u8]), CompressionError> {
let &[first, ..] = input else {
return Err(CompressionError::UnexpectedEnd);
};
if first & INDEXED_FIELD_LINE != 0 {
let (index, rest) = integer_prefix::decode(input, 6)?;
let instr = if first & INDEXED_STATIC_FLAG != 0 {
FieldLineInstruction::IndexedStatic { index }
} else {
FieldLineInstruction::IndexedDynamic {
relative_index: index,
}
};
Ok((instr, rest))
} else if first & LITERAL_WITH_NAME_REF != 0 {
let never_indexed = first & NAME_REF_NEVER_INDEXED_FLAG != 0;
let is_static = first & NAME_REF_STATIC_FLAG != 0;
let (index, rest) = integer_prefix::decode(input, 4)?;
let (value, rest) = decode_string(rest, 7)?;
let instr = if is_static {
FieldLineInstruction::LiteralStaticNameRef {
name_index: index,
value,
never_indexed,
}
} else {
FieldLineInstruction::LiteralDynamicNameRef {
relative_index: index,
value,
never_indexed,
}
};
Ok((instr, rest))
} else if first & LITERAL_WITH_LITERAL_NAME != 0 {
let never_indexed = first & LITERAL_NAME_NEVER_INDEXED_FLAG != 0;
let (name, rest) = decode_name(input, 3)?;
let (value, rest) = decode_string(rest, 7)?;
Ok((
FieldLineInstruction::LiteralLiteralName {
name,
value,
never_indexed,
},
rest,
))
} else if first & POST_BASE_INDEXED != 0 {
let (post_base_index, rest) = integer_prefix::decode(input, 4)?;
Ok((
FieldLineInstruction::IndexedPostBase { post_base_index },
rest,
))
} else {
let never_indexed = first & POST_BASE_NAME_REF_NEVER_INDEXED_FLAG != 0;
let (post_base_index, rest) = integer_prefix::decode(input, 3)?;
let (value, rest) = decode_string(rest, 7)?;
Ok((
FieldLineInstruction::LiteralPostBaseNameRef {
post_base_index,
value,
never_indexed,
},
rest,
))
}
}
pub(in crate::headers) fn encode(&self, buf: &mut Vec<u8>) {
match *self {
FieldLineInstruction::IndexedStatic { index } => {
let start = buf.len();
integer_prefix::encode_into(index, 6, buf);
buf[start] |= INDEXED_FIELD_LINE | INDEXED_STATIC_FLAG;
}
FieldLineInstruction::IndexedDynamic { relative_index } => {
let start = buf.len();
integer_prefix::encode_into(relative_index, 6, buf);
buf[start] |= INDEXED_FIELD_LINE;
}
FieldLineInstruction::IndexedPostBase { post_base_index } => {
let start = buf.len();
integer_prefix::encode_into(post_base_index, 4, buf);
buf[start] |= POST_BASE_INDEXED;
}
FieldLineInstruction::LiteralStaticNameRef {
name_index,
ref value,
never_indexed,
} => {
let start = buf.len();
integer_prefix::encode_into(name_index, 4, buf);
buf[start] |= LITERAL_WITH_NAME_REF
| NAME_REF_STATIC_FLAG
| if never_indexed {
NAME_REF_NEVER_INDEXED_FLAG
} else {
0
};
encode_string(value.as_bytes(), 7, buf);
}
FieldLineInstruction::LiteralDynamicNameRef {
relative_index,
ref value,
never_indexed,
} => {
let start = buf.len();
integer_prefix::encode_into(relative_index, 4, buf);
buf[start] |= LITERAL_WITH_NAME_REF
| if never_indexed {
NAME_REF_NEVER_INDEXED_FLAG
} else {
0
};
encode_string(value.as_bytes(), 7, buf);
}
FieldLineInstruction::LiteralPostBaseNameRef {
post_base_index,
ref value,
never_indexed,
} => {
let start = buf.len();
integer_prefix::encode_into(post_base_index, 3, buf);
buf[start] |= if never_indexed {
POST_BASE_NAME_REF_NEVER_INDEXED_FLAG
} else {
0
};
encode_string(value.as_bytes(), 7, buf);
}
FieldLineInstruction::LiteralLiteralName {
ref name,
ref value,
never_indexed,
} => {
let start = buf.len();
encode_string(name.as_bytes(), 3, buf);
buf[start] |= LITERAL_WITH_LITERAL_NAME
| if never_indexed {
LITERAL_NAME_NEVER_INDEXED_FLAG
} else {
0
};
encode_string(value.as_bytes(), 7, buf);
}
}
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
pub(in crate::headers) struct FieldSectionPrefix {
pub encoded_required_insert_count: usize,
pub base_is_negative: bool,
pub delta_base: usize,
}
impl FieldSectionPrefix {
pub(in crate::headers) fn parse(input: &[u8]) -> Result<(Self, &[u8]), CompressionError> {
let (encoded_required_insert_count, rest) = integer_prefix::decode(input, 8)?;
let &[first, ..] = rest else {
return Err(CompressionError::UnexpectedEnd);
};
let base_is_negative = first & BASE_DELTA_SIGN != 0;
let (delta_base, rest) = integer_prefix::decode(rest, 7)?;
Ok((
Self {
encoded_required_insert_count,
base_is_negative,
delta_base,
},
rest,
))
}
pub(in crate::headers) fn encode(&self, buf: &mut Vec<u8>) {
integer_prefix::encode_into(self.encoded_required_insert_count, 8, buf);
let start = buf.len();
integer_prefix::encode_into(self.delta_base, 7, buf);
if self.base_is_negative {
buf[start] |= BASE_DELTA_SIGN;
}
}
}
fn decode_string(
input: &[u8],
prefix_size: u8,
) -> Result<(FieldLineValue<'_>, &[u8]), CompressionError> {
let &[first, ..] = input else {
return Err(CompressionError::UnexpectedEnd);
};
let huffman_encoded = first & (1 << prefix_size) != 0;
let (length, rest) = integer_prefix::decode(input, prefix_size)?;
if rest.len() < length {
return Err(CompressionError::UnexpectedEnd);
}
let (body, rest) = rest.split_at(length);
let value = if huffman_encoded {
FieldLineValue::Owned(huffman::decode(body)?)
} else {
FieldLineValue::Borrowed(body)
};
Ok((value, rest))
}
fn decode_name(input: &[u8], prefix_size: u8) -> Result<(EntryName<'_>, &[u8]), CompressionError> {
let &[first, ..] = input else {
return Err(CompressionError::UnexpectedEnd);
};
let huffman_encoded = first & (1 << prefix_size) != 0;
let (length, rest) = integer_prefix::decode(input, prefix_size)?;
if rest.len() < length {
return Err(CompressionError::UnexpectedEnd);
}
let (body, rest) = rest.split_at(length);
let name = if huffman_encoded {
EntryName::try_from(huffman::decode(body)?)
.map_err(|()| CompressionError::InvalidHeaderName)?
} else {
EntryName::try_from(body).map_err(|()| CompressionError::InvalidHeaderName)?
};
Ok((name, rest))
}
#[cfg(test)]
mod tests {
use super::*;
fn fv(s: &'static [u8]) -> FieldLineValue<'static> {
FieldLineValue::Static(s)
}
#[allow(clippy::needless_pass_by_value)]
fn roundtrip(instr: FieldLineInstruction<'static>) {
let mut buf = Vec::new();
instr.encode(&mut buf);
let (parsed, rest) = FieldLineInstruction::parse(&buf).expect("parse succeeds");
assert!(rest.is_empty(), "instruction left trailing bytes");
assert_eq!(parsed, instr);
}
#[test]
fn roundtrip_indexed_static() {
roundtrip(FieldLineInstruction::IndexedStatic { index: 0 });
roundtrip(FieldLineInstruction::IndexedStatic { index: 25 });
roundtrip(FieldLineInstruction::IndexedStatic { index: 98 });
roundtrip(FieldLineInstruction::IndexedStatic { index: 10_000 });
}
#[test]
fn roundtrip_indexed_dynamic() {
roundtrip(FieldLineInstruction::IndexedDynamic { relative_index: 0 });
roundtrip(FieldLineInstruction::IndexedDynamic { relative_index: 62 });
roundtrip(FieldLineInstruction::IndexedDynamic { relative_index: 63 });
roundtrip(FieldLineInstruction::IndexedDynamic {
relative_index: 5_000,
});
}
#[test]
fn roundtrip_indexed_post_base() {
roundtrip(FieldLineInstruction::IndexedPostBase { post_base_index: 0 });
roundtrip(FieldLineInstruction::IndexedPostBase {
post_base_index: 14,
});
roundtrip(FieldLineInstruction::IndexedPostBase {
post_base_index: 15,
});
roundtrip(FieldLineInstruction::IndexedPostBase {
post_base_index: 2_000,
});
}
#[test]
fn roundtrip_literal_static_name_ref() {
for never_indexed in [false, true] {
roundtrip(FieldLineInstruction::LiteralStaticNameRef {
name_index: 15, value: fv(b"200"),
never_indexed,
});
roundtrip(FieldLineInstruction::LiteralStaticNameRef {
name_index: 0,
value: fv(b""),
never_indexed,
});
roundtrip(FieldLineInstruction::LiteralStaticNameRef {
name_index: 500,
value: fv(b"a value long enough to exercise multi-byte varint"),
never_indexed,
});
}
}
#[test]
fn roundtrip_literal_dynamic_name_ref() {
for never_indexed in [false, true] {
roundtrip(FieldLineInstruction::LiteralDynamicNameRef {
relative_index: 0,
value: fv(b"v"),
never_indexed,
});
roundtrip(FieldLineInstruction::LiteralDynamicNameRef {
relative_index: 14,
value: fv(b"v"),
never_indexed,
});
roundtrip(FieldLineInstruction::LiteralDynamicNameRef {
relative_index: 1_000,
value: fv(b"longer value that might benefit from huffman"),
never_indexed,
});
}
}
#[test]
fn roundtrip_literal_post_base_name_ref() {
for never_indexed in [false, true] {
roundtrip(FieldLineInstruction::LiteralPostBaseNameRef {
post_base_index: 0,
value: fv(b"v"),
never_indexed,
});
roundtrip(FieldLineInstruction::LiteralPostBaseNameRef {
post_base_index: 6,
value: fv(b"v"),
never_indexed,
});
roundtrip(FieldLineInstruction::LiteralPostBaseNameRef {
post_base_index: 500,
value: fv(b"longer value"),
never_indexed,
});
}
}
#[test]
fn roundtrip_literal_literal_name() {
for never_indexed in [false, true] {
roundtrip(FieldLineInstruction::LiteralLiteralName {
name: EntryName::try_from(b"x-custom".as_slice())
.unwrap()
.into_owned(),
value: fv(b"value"),
never_indexed,
});
roundtrip(FieldLineInstruction::LiteralLiteralName {
name: EntryName::try_from(b"content-type".as_slice())
.unwrap()
.into_owned(),
value: fv(b"application/json"),
never_indexed,
});
}
}
#[test]
fn field_section_prefix_roundtrip() {
for prefix in [
FieldSectionPrefix {
encoded_required_insert_count: 0,
base_is_negative: false,
delta_base: 0,
},
FieldSectionPrefix {
encoded_required_insert_count: 5,
base_is_negative: false,
delta_base: 0,
},
FieldSectionPrefix {
encoded_required_insert_count: 5,
base_is_negative: true,
delta_base: 3,
},
FieldSectionPrefix {
encoded_required_insert_count: 1_000,
base_is_negative: false,
delta_base: 500,
},
] {
let mut buf = Vec::new();
prefix.encode(&mut buf);
let (parsed, rest) = FieldSectionPrefix::parse(&buf).unwrap();
assert!(rest.is_empty());
assert_eq!(parsed, prefix);
}
}
#[test]
fn parse_multiple_field_lines() {
let mut buf = Vec::new();
FieldLineInstruction::IndexedStatic { index: 25 }.encode(&mut buf);
FieldLineInstruction::LiteralStaticNameRef {
name_index: 15,
value: fv(b"200"),
never_indexed: false,
}
.encode(&mut buf);
FieldLineInstruction::IndexedDynamic { relative_index: 0 }.encode(&mut buf);
let (a, rest) = FieldLineInstruction::parse(&buf).unwrap();
let (b, rest) = FieldLineInstruction::parse(rest).unwrap();
let (c, rest) = FieldLineInstruction::parse(rest).unwrap();
assert!(rest.is_empty());
assert_eq!(a, FieldLineInstruction::IndexedStatic { index: 25 });
assert_eq!(
b,
FieldLineInstruction::LiteralStaticNameRef {
name_index: 15,
value: fv(b"200"),
never_indexed: false,
}
);
assert_eq!(
c,
FieldLineInstruction::IndexedDynamic { relative_index: 0 }
);
}
#[test]
fn parse_empty_input_is_error() {
assert!(matches!(
FieldLineInstruction::parse(&[]),
Err(CompressionError::UnexpectedEnd)
));
}
#[test]
fn parse_truncated_string_literal() {
let buf = [0x20 | 0x05, b'a', b'b'];
assert!(matches!(
FieldLineInstruction::parse(&buf),
Err(CompressionError::UnexpectedEnd)
));
}
mod spec_vectors {
use super::*;
#[track_caller]
fn parse_all(bytes: &[u8]) -> (FieldSectionPrefix, Vec<FieldLineInstruction<'_>>) {
let (prefix, mut rest) = FieldSectionPrefix::parse(bytes).expect("prefix parses");
let mut lines = Vec::new();
while !rest.is_empty() {
let (instr, tail) = FieldLineInstruction::parse(rest).expect("field line parses");
lines.push(instr);
rest = tail;
}
(prefix, lines)
}
#[test]
fn b1_literal_with_static_name_ref() {
let bytes = [
0x00, 0x00, 0x51, 0x0b, 0x2f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x2e, 0x68, 0x74, 0x6d,
0x6c,
];
let (prefix, lines) = parse_all(&bytes);
assert_eq!(prefix, FieldSectionPrefix::default());
assert_eq!(
lines,
vec![FieldLineInstruction::LiteralStaticNameRef {
name_index: 1,
value: FieldLineValue::Static(b"/index.html"),
never_indexed: false,
}],
);
}
#[test]
fn b2_post_base_indexed_with_negative_base() {
let bytes = [0x03, 0x81, 0x10, 0x11];
let (prefix, lines) = parse_all(&bytes);
assert_eq!(
prefix,
FieldSectionPrefix {
encoded_required_insert_count: 3,
base_is_negative: true,
delta_base: 1,
},
);
assert_eq!(
lines,
vec![
FieldLineInstruction::IndexedPostBase { post_base_index: 0 },
FieldLineInstruction::IndexedPostBase { post_base_index: 1 },
],
);
}
#[test]
fn b4_mixed_dynamic_and_static_indexed() {
let bytes = [0x05, 0x00, 0x80, 0xc1, 0x81];
let (prefix, lines) = parse_all(&bytes);
assert_eq!(
prefix,
FieldSectionPrefix {
encoded_required_insert_count: 5,
base_is_negative: false,
delta_base: 0,
},
);
assert_eq!(
lines,
vec![
FieldLineInstruction::IndexedDynamic { relative_index: 0 },
FieldLineInstruction::IndexedStatic { index: 1 },
FieldLineInstruction::IndexedDynamic { relative_index: 1 },
],
);
}
}
}