#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct HpackHeaderFieldRef<'a> {
name: &'a [u8],
value: &'a [u8],
}
impl<'a> HpackHeaderFieldRef<'a> {
pub const fn new(name: &'a [u8], value: &'a [u8]) -> Self {
Self { name, value }
}
pub const fn name(self) -> &'a [u8] {
self.name
}
pub const fn value(self) -> &'a [u8] {
self.value
}
}
pub const HPACK_STATIC_TABLE_LEN: usize = 61;
pub struct HpackStaticTable;
impl HpackStaticTable {
pub const fn len() -> usize {
HPACK_STATIC_TABLE_LEN
}
pub const fn is_empty() -> bool {
false
}
pub fn get(index: usize) -> Option<HpackHeaderFieldRef<'static>> {
STATIC_TABLE.get(index.checked_sub(1)?).copied()
}
}
const STATIC_TABLE: [HpackHeaderFieldRef<'static>; HPACK_STATIC_TABLE_LEN] = [
HpackHeaderFieldRef::new(b":authority", b""),
HpackHeaderFieldRef::new(b":method", b"GET"),
HpackHeaderFieldRef::new(b":method", b"POST"),
HpackHeaderFieldRef::new(b":path", b"/"),
HpackHeaderFieldRef::new(b":path", b"/index.html"),
HpackHeaderFieldRef::new(b":scheme", b"http"),
HpackHeaderFieldRef::new(b":scheme", b"https"),
HpackHeaderFieldRef::new(b":status", b"200"),
HpackHeaderFieldRef::new(b":status", b"204"),
HpackHeaderFieldRef::new(b":status", b"206"),
HpackHeaderFieldRef::new(b":status", b"304"),
HpackHeaderFieldRef::new(b":status", b"400"),
HpackHeaderFieldRef::new(b":status", b"404"),
HpackHeaderFieldRef::new(b":status", b"500"),
HpackHeaderFieldRef::new(b"accept-charset", b""),
HpackHeaderFieldRef::new(b"accept-encoding", b"gzip, deflate"),
HpackHeaderFieldRef::new(b"accept-language", b""),
HpackHeaderFieldRef::new(b"accept-ranges", b""),
HpackHeaderFieldRef::new(b"accept", b""),
HpackHeaderFieldRef::new(b"access-control-allow-origin", b""),
HpackHeaderFieldRef::new(b"age", b""),
HpackHeaderFieldRef::new(b"allow", b""),
HpackHeaderFieldRef::new(b"authorization", b""),
HpackHeaderFieldRef::new(b"cache-control", b""),
HpackHeaderFieldRef::new(b"content-disposition", b""),
HpackHeaderFieldRef::new(b"content-encoding", b""),
HpackHeaderFieldRef::new(b"content-language", b""),
HpackHeaderFieldRef::new(b"content-length", b""),
HpackHeaderFieldRef::new(b"content-location", b""),
HpackHeaderFieldRef::new(b"content-range", b""),
HpackHeaderFieldRef::new(b"content-type", b""),
HpackHeaderFieldRef::new(b"cookie", b""),
HpackHeaderFieldRef::new(b"date", b""),
HpackHeaderFieldRef::new(b"etag", b""),
HpackHeaderFieldRef::new(b"expect", b""),
HpackHeaderFieldRef::new(b"expires", b""),
HpackHeaderFieldRef::new(b"from", b""),
HpackHeaderFieldRef::new(b"host", b""),
HpackHeaderFieldRef::new(b"if-match", b""),
HpackHeaderFieldRef::new(b"if-modified-since", b""),
HpackHeaderFieldRef::new(b"if-none-match", b""),
HpackHeaderFieldRef::new(b"if-range", b""),
HpackHeaderFieldRef::new(b"if-unmodified-since", b""),
HpackHeaderFieldRef::new(b"last-modified", b""),
HpackHeaderFieldRef::new(b"link", b""),
HpackHeaderFieldRef::new(b"location", b""),
HpackHeaderFieldRef::new(b"max-forwards", b""),
HpackHeaderFieldRef::new(b"proxy-authenticate", b""),
HpackHeaderFieldRef::new(b"proxy-authorization", b""),
HpackHeaderFieldRef::new(b"range", b""),
HpackHeaderFieldRef::new(b"referer", b""),
HpackHeaderFieldRef::new(b"refresh", b""),
HpackHeaderFieldRef::new(b"retry-after", b""),
HpackHeaderFieldRef::new(b"server", b""),
HpackHeaderFieldRef::new(b"set-cookie", b""),
HpackHeaderFieldRef::new(b"strict-transport-security", b""),
HpackHeaderFieldRef::new(b"transfer-encoding", b""),
HpackHeaderFieldRef::new(b"user-agent", b""),
HpackHeaderFieldRef::new(b"vary", b""),
HpackHeaderFieldRef::new(b"via", b""),
HpackHeaderFieldRef::new(b"www-authenticate", b""),
];
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct HpackDynamicTableEntry {
offset: usize,
name_len: usize,
value_len: usize,
}
impl HpackDynamicTableEntry {
pub const EMPTY: Self = Self {
offset: 0,
name_len: 0,
value_len: 0,
};
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum HpackDynamicTableError {
MaximumSizeExceedsCapacity {
requested: usize,
capacity: usize,
},
EntrySizeOverflow,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum HpackDynamicTableInsertResult {
Inserted,
NotInsertedOversized,
}
pub struct HpackDynamicTable<'storage, 'entries> {
storage: &'storage mut [u8],
entries: &'entries mut [HpackDynamicTableEntry],
count: usize,
size: usize,
maximum_size: usize,
capacity: usize,
}
impl<'storage, 'entries> HpackDynamicTable<'storage, 'entries> {
pub fn new(
storage: &'storage mut [u8],
entries: &'entries mut [HpackDynamicTableEntry],
maximum_size: usize,
) -> Result<Self, HpackDynamicTableError> {
let capacity = Self::storage_capacity(storage.len(), entries.len());
if maximum_size > capacity {
return Err(HpackDynamicTableError::MaximumSizeExceedsCapacity {
requested: maximum_size,
capacity,
});
}
Ok(Self {
storage,
entries,
count: 0,
size: 0,
maximum_size,
capacity,
})
}
pub const fn capacity(&self) -> usize {
self.capacity
}
pub const fn len(&self) -> usize {
self.count
}
pub const fn is_empty(&self) -> bool {
self.count == 0
}
pub const fn size(&self) -> usize {
self.size
}
pub const fn maximum_size(&self) -> usize {
self.maximum_size
}
pub fn get(&self, index: usize) -> Option<HpackHeaderFieldRef<'_>> {
let slot = index.checked_sub(1)?;
if slot >= self.count {
return None;
}
let entry = self.entries.get(slot)?;
let name_end = entry.offset.checked_add(entry.name_len)?;
let value_end = name_end.checked_add(entry.value_len)?;
Some(HpackHeaderFieldRef::new(
self.storage.get(entry.offset..name_end)?,
self.storage.get(name_end..value_end)?,
))
}
pub fn clear(&mut self) {
self.count = 0;
self.size = 0;
}
pub fn set_maximum_size(&mut self, maximum_size: usize) -> Result<(), HpackDynamicTableError> {
if maximum_size > self.capacity {
return Err(HpackDynamicTableError::MaximumSizeExceedsCapacity {
requested: maximum_size,
capacity: self.capacity,
});
}
self.set_maximum_size_prevalidated(maximum_size);
Ok(())
}
pub(super) fn set_maximum_size_prevalidated(&mut self, maximum_size: usize) {
let mut count = self.count;
let mut size = self.size;
while count > 0 && size > maximum_size {
count -= 1;
size -= Self::entry_size_from_slot(self.entries[count]);
}
self.count = count;
self.size = size;
self.maximum_size = maximum_size;
}
pub fn entry_size(name: &[u8], value: &[u8]) -> Result<usize, HpackDynamicTableError> {
name.len()
.checked_add(value.len())
.and_then(|length| length.checked_add(32))
.ok_or(HpackDynamicTableError::EntrySizeOverflow)
}
pub fn insert(
&mut self,
name: &[u8],
value: &[u8],
) -> Result<HpackDynamicTableInsertResult, HpackDynamicTableError> {
let entry_size = Self::entry_size(name, value)?;
Ok(self.insert_prevalidated(name, value, entry_size))
}
pub(super) fn insert_prevalidated(
&mut self,
name: &[u8],
value: &[u8],
entry_size: usize,
) -> HpackDynamicTableInsertResult {
if entry_size > self.maximum_size {
self.clear();
return HpackDynamicTableInsertResult::NotInsertedOversized;
}
let byte_len = entry_size - 32;
let mut retained_count = self.count;
let mut retained_size = self.size;
while retained_count > 0 && retained_size > self.maximum_size - entry_size {
retained_count -= 1;
retained_size -= Self::entry_size_from_slot(self.entries[retained_count]);
}
let retained_bytes = if retained_count == 0 {
0
} else {
let last = self.entries[retained_count - 1];
last.offset + last.name_len + last.value_len
};
self.storage.copy_within(0..retained_bytes, byte_len);
if retained_count > 0 {
self.entries.copy_within(0..retained_count, 1);
for entry in &mut self.entries[1..=retained_count] {
entry.offset += byte_len;
}
}
self.storage[..name.len()].copy_from_slice(name);
self.storage[name.len()..byte_len].copy_from_slice(value);
self.entries[0] = HpackDynamicTableEntry {
offset: 0,
name_len: name.len(),
value_len: value.len(),
};
self.count = retained_count + 1;
self.size = retained_size + entry_size;
HpackDynamicTableInsertResult::Inserted
}
const fn storage_capacity(storage_len: usize, entry_capacity: usize) -> usize {
let bytes = storage_len.saturating_add(32);
let entries = entry_capacity.saturating_mul(32).saturating_add(31);
if bytes < entries { bytes } else { entries }
}
const fn entry_size_from_slot(entry: HpackDynamicTableEntry) -> usize {
entry.name_len + entry.value_len + 32
}
}