use super::Header; use fnv::FnvHasher; use http::header; use http::method::Method; use std::collections::VecDeque; use std::hash::{Hash, Hasher}; use std::{cmp, mem, usize}; /// HPACK encoder table #[derive(Debug)] pub struct Table { mask: usize, indices: Vec>, slots: VecDeque, inserted: usize, // Size is in bytes size: usize, max_size: usize, } #[derive(Debug)] pub enum Index { // The header is already fully indexed Indexed(usize, Header), // The name is indexed, but not the value Name(usize, Header), // The full header has been inserted into the table. Inserted(usize), // Only the value has been inserted (hpack table idx, slots idx) InsertedValue(usize, usize), // The header is not indexed by this table NotIndexed(Header), } #[derive(Debug)] struct Slot { hash: HashValue, header: Header, next: Option, } #[derive(Debug, Clone, Copy, Eq, PartialEq)] struct Pos { index: usize, hash: HashValue, } #[derive(Debug, Copy, Clone, Eq, PartialEq)] struct HashValue(usize); const MAX_SIZE: usize = 1 << 16; const DYN_OFFSET: usize = 62; macro_rules! probe_loop { ($probe_var: ident < $len: expr, $body: expr) => { debug_assert!($len > 0); loop { if $probe_var < $len { $body $probe_var += 1; } else { $probe_var = 0; } } }; } impl Table { pub fn new(max_size: usize, capacity: usize) -> Table { if capacity == 0 { Table { mask: 0, indices: vec![], slots: VecDeque::new(), inserted: 0, size: 0, max_size, } } else { let capacity = cmp::max(to_raw_capacity(capacity).next_power_of_two(), 8); Table { mask: capacity.wrapping_sub(1), indices: vec![None; capacity], slots: VecDeque::with_capacity(usable_capacity(capacity)), inserted: 0, size: 0, max_size, } } } #[inline] pub fn capacity(&self) -> usize { usable_capacity(self.indices.len()) } pub fn max_size(&self) -> usize { self.max_size } /// Gets the header stored in the table pub fn resolve<'a>(&'a self, index: &'a Index) -> &'a Header { use self::Index::*; match *index { Indexed(_, ref h) => h, Name(_, ref h) => h, Inserted(idx) => &self.slots[idx].header, InsertedValue(_, idx) => &self.slots[idx].header, NotIndexed(ref h) => h, } } pub fn resolve_idx(&self, index: &Index) -> usize { use self::Index::*; match *index { Indexed(idx, ..) => idx, Name(idx, ..) => idx, Inserted(idx) => idx + DYN_OFFSET, InsertedValue(_name_idx, slot_idx) => slot_idx + DYN_OFFSET, NotIndexed(_) => panic!("cannot resolve index"), } } /// Index the header in the HPACK table. pub fn index(&mut self, header: Header) -> Index { // Check the static table let statik = index_static(&header); // Don't index certain headers. This logic is borrowed from nghttp2. if header.skip_value_index() { // Right now, if this is true, the header name is always in the // static table. At some point in the future, this might not be true // and this logic will need to be updated. debug_assert!(statik.is_some(), "skip_value_index requires a static name",); return Index::new(statik, header); } // If the header is already indexed by the static table, return that if let Some((n, true)) = statik { return Index::Indexed(n, header); } // Don't index large headers if header.len() * 4 > self.max_size * 3 { return Index::new(statik, header); } self.index_dynamic(header, statik) } fn index_dynamic(&mut self, header: Header, statik: Option<(usize, bool)>) -> Index { debug_assert!(self.assert_valid_state("one")); if header.len() + self.size < self.max_size || !header.is_sensitive() { // Only grow internal storage if needed self.reserve_one(); } if self.indices.is_empty() { // If `indices` is not empty, then it is impossible for all // `indices` entries to be `Some`. So, we only need to check for the // empty case. return Index::new(statik, header); } let hash = hash_header(&header); let desired_pos = desired_pos(self.mask, hash); let mut probe = desired_pos; let mut dist = 0; // Start at the ideal position, checking all slots probe_loop!(probe < self.indices.len(), { if let Some(pos) = self.indices[probe] { // The slot is already occupied, but check if it has a lower // displacement. let their_dist = probe_distance(self.mask, pos.hash, probe); let slot_idx = pos.index.wrapping_add(self.inserted); if their_dist < dist { // Index robinhood return self.index_vacant(header, hash, dist, probe, statik); } else if pos.hash == hash && self.slots[slot_idx].header.name() == header.name() { // Matching name, check values return self.index_occupied(header, hash, pos.index, statik.map(|(n, _)| n)); } } else { return self.index_vacant(header, hash, dist, probe, statik); } dist += 1; }); } fn index_occupied( &mut self, header: Header, hash: HashValue, mut index: usize, statik: Option, ) -> Index { debug_assert!(self.assert_valid_state("top")); // There already is a match for the given header name. Check if a value // matches. The header will also only be inserted if the table is not at // capacity. loop { // Compute the real index into the VecDeque let real_idx = index.wrapping_add(self.inserted); if self.slots[real_idx].header.value_eq(&header) { // We have a full match! return Index::Indexed(real_idx + DYN_OFFSET, header); } if let Some(next) = self.slots[real_idx].next { index = next; continue; } if header.is_sensitive() { // Should we assert this? // debug_assert!(statik.is_none()); return Index::Name(real_idx + DYN_OFFSET, header); } self.update_size(header.len(), Some(index)); // Insert the new header self.insert(header, hash); // Recompute real_idx as it just changed. let new_real_idx = index.wrapping_add(self.inserted); // The previous node in the linked list may have gotten evicted // while making room for this header. if new_real_idx < self.slots.len() { let idx = 0usize.wrapping_sub(self.inserted); self.slots[new_real_idx].next = Some(idx); } debug_assert!(self.assert_valid_state("bottom")); // Even if the previous header was evicted, we can still reference // it when inserting the new one... return if let Some(n) = statik { // If name is in static table, use it instead Index::InsertedValue(n, 0) } else { Index::InsertedValue(real_idx + DYN_OFFSET, 0) }; } } fn index_vacant( &mut self, header: Header, hash: HashValue, mut dist: usize, mut probe: usize, statik: Option<(usize, bool)>, ) -> Index { if header.is_sensitive() { return Index::new(statik, header); } debug_assert!(self.assert_valid_state("top")); debug_assert!(dist == 0 || self.indices[probe.wrapping_sub(1) & self.mask].is_some()); // Passing in `usize::MAX` for prev_idx since there is no previous // header in this case. if self.update_size(header.len(), None) { while dist != 0 { let back = probe.wrapping_sub(1) & self.mask; if let Some(pos) = self.indices[back] { let their_dist = probe_distance(self.mask, pos.hash, back); if their_dist < (dist - 1) { probe = back; dist -= 1; } else { break; } } else { probe = back; dist -= 1; } } } debug_assert!(self.assert_valid_state("after update")); self.insert(header, hash); let pos_idx = 0usize.wrapping_sub(self.inserted); let prev = mem::replace( &mut self.indices[probe], Some(Pos { index: pos_idx, hash, }), ); if let Some(mut prev) = prev { // Shift forward let mut probe = probe + 1; probe_loop!(probe < self.indices.len(), { let pos = &mut self.indices[probe]; prev = match mem::replace(pos, Some(prev)) { Some(p) => p, None => break, }; }); } debug_assert!(self.assert_valid_state("bottom")); if let Some((n, _)) = statik { Index::InsertedValue(n, 0) } else { Index::Inserted(0) } } fn insert(&mut self, header: Header, hash: HashValue) { self.inserted = self.inserted.wrapping_add(1); self.slots.push_front(Slot { hash, header, next: None, }); } pub fn resize(&mut self, size: usize) { self.max_size = size; if size == 0 { self.size = 0; for i in &mut self.indices { *i = None; } self.slots.clear(); self.inserted = 0; } else { self.converge(None); } } fn update_size(&mut self, len: usize, prev_idx: Option) -> bool { self.size += len; self.converge(prev_idx) } fn converge(&mut self, prev_idx: Option) -> bool { let mut ret = false; while self.size > self.max_size { ret = true; self.evict(prev_idx); } ret } fn evict(&mut self, prev_idx: Option) { let pos_idx = (self.slots.len() - 1).wrapping_sub(self.inserted); debug_assert!(!self.slots.is_empty()); debug_assert!(self.assert_valid_state("one")); // Remove the header let slot = self.slots.pop_back().unwrap(); let mut probe = desired_pos(self.mask, slot.hash); // Update the size self.size -= slot.header.len(); debug_assert_eq!( self.indices .iter() .filter_map(|p| *p) .filter(|p| p.index == pos_idx) .count(), 1 ); // Find the associated position probe_loop!(probe < self.indices.len(), { debug_assert!(self.indices[probe].is_some()); let mut pos = self.indices[probe].unwrap(); if pos.index == pos_idx { if let Some(idx) = slot.next { pos.index = idx; self.indices[probe] = Some(pos); } else if Some(pos.index) == prev_idx { pos.index = 0usize.wrapping_sub(self.inserted + 1); self.indices[probe] = Some(pos); } else { self.indices[probe] = None; self.remove_phase_two(probe); } break; } }); debug_assert!(self.assert_valid_state("two")); } // Shifts all indices that were displaced by the header that has just been // removed. fn remove_phase_two(&mut self, probe: usize) { let mut last_probe = probe; let mut probe = probe + 1; probe_loop!(probe < self.indices.len(), { if let Some(pos) = self.indices[probe] { if probe_distance(self.mask, pos.hash, probe) > 0 { self.indices[last_probe] = self.indices[probe].take(); } else { break; } } else { break; } last_probe = probe; }); debug_assert!(self.assert_valid_state("two")); } fn reserve_one(&mut self) { let len = self.slots.len(); if len == self.capacity() { if len == 0 { let new_raw_cap = 8; self.mask = 8 - 1; self.indices = vec![None; new_raw_cap]; } else { let raw_cap = self.indices.len(); self.grow(raw_cap << 1); } } } #[inline] fn grow(&mut self, new_raw_cap: usize) { // This path can never be reached when handling the first allocation in // the map. debug_assert!(self.assert_valid_state("top")); // find first ideally placed element -- start of cluster let mut first_ideal = 0; for (i, pos) in self.indices.iter().enumerate() { if let Some(pos) = *pos { if 0 == probe_distance(self.mask, pos.hash, i) { first_ideal = i; break; } } } // visit the entries in an order where we can simply reinsert them // into self.indices without any bucket stealing. let old_indices = mem::replace(&mut self.indices, vec![None; new_raw_cap]); self.mask = new_raw_cap.wrapping_sub(1); for &pos in &old_indices[first_ideal..] { self.reinsert_entry_in_order(pos); } for &pos in &old_indices[..first_ideal] { self.reinsert_entry_in_order(pos); } debug_assert!(self.assert_valid_state("bottom")); } fn reinsert_entry_in_order(&mut self, pos: Option) { if let Some(pos) = pos { // Find first empty bucket and insert there let mut probe = desired_pos(self.mask, pos.hash); probe_loop!(probe < self.indices.len(), { if self.indices[probe].is_none() { // empty bucket, insert here self.indices[probe] = Some(pos); return; } debug_assert!({ let them = self.indices[probe].unwrap(); let their_distance = probe_distance(self.mask, them.hash, probe); let our_distance = probe_distance(self.mask, pos.hash, probe); their_distance >= our_distance }); }); } } #[cfg(not(test))] fn assert_valid_state(&self, _: &'static str) -> bool { true } #[cfg(test)] fn assert_valid_state(&self, _msg: &'static str) -> bool { /* // Checks that the internal map structure is valid // // Ensure all hash codes in indices match the associated slot for pos in &self.indices { if let Some(pos) = *pos { let real_idx = pos.index.wrapping_add(self.inserted); if real_idx.wrapping_add(1) != 0 { assert!(real_idx < self.slots.len(), "out of index; real={}; len={}, msg={}", real_idx, self.slots.len(), msg); assert_eq!(pos.hash, self.slots[real_idx].hash, "index hash does not match slot; msg={}", msg); } } } // Every index is only available once for i in 0..self.indices.len() { if self.indices[i].is_none() { continue; } for j in i+1..self.indices.len() { assert_ne!(self.indices[i], self.indices[j], "duplicate indices; msg={}", msg); } } for (index, slot) in self.slots.iter().enumerate() { let mut indexed = None; // First, see if the slot is indexed for (i, pos) in self.indices.iter().enumerate() { if let Some(pos) = *pos { let real_idx = pos.index.wrapping_add(self.inserted); if real_idx == index { indexed = Some(i); // Already know that there is no dup, so break break; } } } if let Some(actual) = indexed { // Ensure that it is accessible.. let desired = desired_pos(self.mask, slot.hash); let mut probe = desired; let mut dist = 0; probe_loop!(probe < self.indices.len(), { assert!(self.indices[probe].is_some(), "unexpected empty slot; probe={}; hash={:?}; msg={}", probe, slot.hash, msg); let pos = self.indices[probe].unwrap(); let their_dist = probe_distance(self.mask, pos.hash, probe); let real_idx = pos.index.wrapping_add(self.inserted); if real_idx == index { break; } assert!(dist <= their_dist, "could not find entry; actual={}; desired={}" + "probe={}, dist={}; their_dist={}; index={}; msg={}", actual, desired, probe, dist, their_dist, index.wrapping_sub(self.inserted), msg); dist += 1; }); } else { // There is exactly one next link let cnt = self.slots.iter().map(|s| s.next) .filter(|n| *n == Some(index.wrapping_sub(self.inserted))) .count(); assert_eq!(1, cnt, "more than one node pointing here; msg={}", msg); } } */ // TODO: Ensure linked lists are correct: no cycles, etc... true } } #[cfg(test)] impl Table { /// Returns the number of headers in the table pub fn len(&self) -> usize { self.slots.len() } /// Returns the table size pub fn size(&self) -> usize { self.size } } impl Index { fn new(v: Option<(usize, bool)>, e: Header) -> Index { match v { None => Index::NotIndexed(e), Some((n, true)) => Index::Indexed(n, e), Some((n, false)) => Index::Name(n, e), } } } #[inline] fn usable_capacity(cap: usize) -> usize { cap - cap / 4 } #[inline] fn to_raw_capacity(n: usize) -> usize { n + n / 3 } #[inline] fn desired_pos(mask: usize, hash: HashValue) -> usize { hash.0 & mask } #[inline] fn probe_distance(mask: usize, hash: HashValue, current: usize) -> usize { current.wrapping_sub(desired_pos(mask, hash)) & mask } fn hash_header(header: &Header) -> HashValue { const MASK: u64 = (MAX_SIZE as u64) - 1; let mut h = FnvHasher::default(); header.name().hash(&mut h); HashValue((h.finish() & MASK) as usize) } /// Checks the static table for the header. If found, returns the index and a /// boolean representing if the value matched as well. fn index_static(header: &Header) -> Option<(usize, bool)> { match *header { Header::Field { ref name, ref value, } => match *name { header::ACCEPT_CHARSET => Some((15, false)), header::ACCEPT_ENCODING => { if value == "gzip, deflate" { Some((16, true)) } else { Some((16, false)) } } header::ACCEPT_LANGUAGE => Some((17, false)), header::ACCEPT_RANGES => Some((18, false)), header::ACCEPT => Some((19, false)), header::ACCESS_CONTROL_ALLOW_ORIGIN => Some((20, false)), header::AGE => Some((21, false)), header::ALLOW => Some((22, false)), header::AUTHORIZATION => Some((23, false)), header::CACHE_CONTROL => Some((24, false)), header::CONTENT_DISPOSITION => Some((25, false)), header::CONTENT_ENCODING => Some((26, false)), header::CONTENT_LANGUAGE => Some((27, false)), header::CONTENT_LENGTH => Some((28, false)), header::CONTENT_LOCATION => Some((29, false)), header::CONTENT_RANGE => Some((30, false)), header::CONTENT_TYPE => Some((31, false)), header::COOKIE => Some((32, false)), header::DATE => Some((33, false)), header::ETAG => Some((34, false)), header::EXPECT => Some((35, false)), header::EXPIRES => Some((36, false)), header::FROM => Some((37, false)), header::HOST => Some((38, false)), header::IF_MATCH => Some((39, false)), header::IF_MODIFIED_SINCE => Some((40, false)), header::IF_NONE_MATCH => Some((41, false)), header::IF_RANGE => Some((42, false)), header::IF_UNMODIFIED_SINCE => Some((43, false)), header::LAST_MODIFIED => Some((44, false)), header::LINK => Some((45, false)), header::LOCATION => Some((46, false)), header::MAX_FORWARDS => Some((47, false)), header::PROXY_AUTHENTICATE => Some((48, false)), header::PROXY_AUTHORIZATION => Some((49, false)), header::RANGE => Some((50, false)), header::REFERER => Some((51, false)), header::REFRESH => Some((52, false)), header::RETRY_AFTER => Some((53, false)), header::SERVER => Some((54, false)), header::SET_COOKIE => Some((55, false)), header::STRICT_TRANSPORT_SECURITY => Some((56, false)), header::TRANSFER_ENCODING => Some((57, false)), header::USER_AGENT => Some((58, false)), header::VARY => Some((59, false)), header::VIA => Some((60, false)), header::WWW_AUTHENTICATE => Some((61, false)), _ => None, }, Header::Authority(_) => Some((1, false)), Header::Method(ref v) => match *v { Method::GET => Some((2, true)), Method::POST => Some((3, true)), _ => Some((2, false)), }, Header::Scheme(ref v) => match &**v { "http" => Some((6, true)), "https" => Some((7, true)), _ => Some((6, false)), }, Header::Path(ref v) => match &**v { "/" => Some((4, true)), "/index.html" => Some((5, true)), _ => Some((4, false)), }, Header::Protocol(..) => None, Header::Status(ref v) => match u16::from(*v) { 200 => Some((8, true)), 204 => Some((9, true)), 206 => Some((10, true)), 304 => Some((11, true)), 400 => Some((12, true)), 404 => Some((13, true)), 500 => Some((14, true)), _ => Some((8, false)), }, } }