use std::collections::HashMap;
pub(crate) fn put_varint(out: &mut Vec<u8>, mut v: u32) {
loop {
let byte = (v & 0x7f) as u8;
v >>= 7;
if v == 0 {
out.push(byte);
return;
}
out.push(byte | 0x80);
}
}
const VARINT_MAX_SHIFT: u32 = 28;
pub(crate) fn next_varint(blob: &[u8], at: &mut usize) -> Option<u32> {
let mut cur = 0u32;
let mut shift = 0u32;
loop {
let b = *blob.get(*at)?;
*at += 1;
cur |= u32::from(b & 0x7f) << shift;
if b & 0x80 == 0 {
return Some(cur);
}
shift += 7;
if shift > VARINT_MAX_SHIFT {
return None;
}
}
}
pub(crate) fn get_varints(blob: &[u8]) -> Vec<u32> {
let mut at = 0;
let mut out = Vec::new();
while let Some(v) = next_varint(blob, &mut at) {
out.push(v);
}
out
}
#[derive(Debug)]
pub(crate) enum DocBlobs {
One { id: u32, blob: Vec<u8> },
Many(HashMap<u32, Vec<u8>>),
}
impl DocBlobs {
pub(crate) fn set(&mut self, id: u32, blob: Vec<u8>) {
match self {
DocBlobs::One { id: id0, blob: b0 } => {
if *id0 == id {
*b0 = blob;
} else {
let mut m = HashMap::with_capacity(2);
m.insert(*id0, std::mem::take(b0));
m.insert(id, blob);
*self = DocBlobs::Many(m);
}
}
DocBlobs::Many(m) => {
m.insert(id, blob);
}
}
}
pub(crate) fn get(&self, id: u32) -> Option<&[u8]> {
match self {
DocBlobs::One { id: id0, blob } => (*id0 == id).then_some(blob.as_slice()),
DocBlobs::Many(m) => m.get(&id).map(Vec::as_slice),
}
}
pub(crate) fn ids(&self) -> Vec<u32> {
match self {
DocBlobs::One { id, .. } => vec![*id],
DocBlobs::Many(m) => m.keys().copied().collect(),
}
}
pub(crate) fn each(&self) -> Vec<(u32, &[u8])> {
match self {
DocBlobs::One { id, blob } => vec![(*id, blob.as_slice())],
DocBlobs::Many(m) => m.iter().map(|(id, b)| (*id, b.as_slice())).collect(),
}
}
#[cfg(test)]
pub(crate) fn remove(&mut self, id: u32) -> bool {
match self {
DocBlobs::One { id: id0, .. } => *id0 == id,
DocBlobs::Many(m) => {
m.remove(&id);
m.is_empty()
}
}
}
#[cfg(test)]
pub(crate) fn approx_bytes(&self) -> u64 {
match self {
DocBlobs::One { blob, .. } => blob_alloc(blob),
DocBlobs::Many(m) => {
let n = m.len() as u64;
let cap = (n * 8 / 7 + 1).next_power_of_two().max(4);
cap * 33 + m.values().map(|b| blob_alloc(b)).sum::<u64>()
}
}
}
}
pub(crate) fn blob_alloc(b: &[u8]) -> u64 {
(b.len().max(1) as u64).next_multiple_of(16) + 16
}
#[cfg(test)]
pub(crate) fn channel_bytes(map: &HashMap<Vec<u8>, DocBlobs>) -> u64 {
map.iter().map(|(t, db)| t.len() as u64 + 24 + 56 + db.approx_bytes()).sum()
}
fn many_table_bytes(n: u64) -> u64 {
let cap = (n * 8 / 7 + 1).next_power_of_two().max(4);
cap * 33
}
#[derive(Debug, Default)]
pub(crate) struct Channel {
map: HashMap<Vec<u8>, DocBlobs>,
bytes: u64,
}
impl Channel {
pub(crate) fn get(&self, token: &[u8]) -> Option<&DocBlobs> {
self.map.get(token)
}
pub(crate) fn bytes(&self) -> u64 {
self.bytes
}
pub(crate) fn set(&mut self, token: &[u8], id: u32, blob: Vec<u8>) {
match self.map.get_mut(token) {
None => {
self.bytes += token.len() as u64 + 24 + 56 + blob_alloc(&blob);
self.map.insert(token.to_vec(), DocBlobs::One { id, blob });
}
Some(db) => {
match db {
DocBlobs::One { id: id0, blob: b0 } if *id0 == id => {
self.bytes += blob_alloc(&blob);
self.bytes -= blob_alloc(b0);
}
DocBlobs::One { .. } => {
self.bytes += many_table_bytes(2) + blob_alloc(&blob);
}
DocBlobs::Many(m) => {
match m.get(&id) {
Some(old) => self.bytes -= blob_alloc(old),
None => {
let n = m.len() as u64;
self.bytes += many_table_bytes(n + 1) - many_table_bytes(n);
}
}
self.bytes += blob_alloc(&blob);
}
}
db.set(id, blob);
}
}
}
pub(crate) fn remove(&mut self, token: &[u8], id: u32) {
let Some(db) = self.map.get_mut(token) else { return };
match db {
DocBlobs::One { id: id0, blob } => {
if *id0 == id {
self.bytes -= token.len() as u64 + 24 + 56 + blob_alloc(blob);
self.map.remove(token);
}
}
DocBlobs::Many(m) => {
let Some(old) = m.remove(&id) else { return };
self.bytes -= blob_alloc(&old);
let n = m.len() as u64;
if n == 0 {
self.bytes -= token.len() as u64 + 24 + 56 + many_table_bytes(1);
self.map.remove(token);
} else {
self.bytes -= many_table_bytes(n + 1) - many_table_bytes(n);
}
}
}
}
#[cfg(test)]
pub(crate) fn recompute(&self) -> u64 {
channel_bytes(&self.map)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn varints_roundtrip() {
for case in
[vec![], vec![0u32], vec![1, 2, 3], vec![5, 127, 128, 129, 16_383, 16_384, 2_000_000]]
{
let mut blob = Vec::new();
for &v in &case {
put_varint(&mut blob, v);
}
assert_eq!(get_varints(&blob), case, "roundtrip {case:?}");
}
}
#[test]
fn an_overlong_varint_ends_the_stream_rather_than_the_process() {
assert_eq!(get_varints(&[0x80, 0x80, 0x80, 0x80, 0x80, 0x01]), Vec::<u32>::new());
let mut at = 0;
assert_eq!(next_varint(&[0x80, 0x80, 0x80, 0x80, 0x80, 0x01], &mut at), None);
}
#[test]
fn a_five_byte_varint_is_legal_and_u32_max_survives_a_roundtrip() {
let mut blob = Vec::new();
put_varint(&mut blob, u32::MAX);
assert_eq!(blob.len(), 5, "u32::MAX needs every one of the five bytes");
assert_eq!(get_varints(&blob), vec![u32::MAX]);
}
#[test]
fn a_trailing_continuation_byte_yields_no_value() {
let mut blob = Vec::new();
put_varint(&mut blob, 9);
blob.push(0x80);
assert_eq!(get_varints(&blob), vec![9]);
}
#[test]
fn one_promotes_to_many_and_each_sees_both() {
let mut db = DocBlobs::One { id: 7, blob: vec![1] };
assert_eq!(db.get(7), Some(&[1u8][..]));
db.set(7, vec![2]);
assert_eq!(db.get(7), Some(&[2u8][..]), "same id overwrites in place");
db.set(9, vec![3]);
let mut got = db.each();
got.sort_by_key(|(id, _)| *id);
assert_eq!(got, vec![(7, &[2u8][..]), (9, &[3u8][..])]);
assert!(!db.remove(7), "one of two left");
assert!(db.remove(9), "last document gone");
}
}