use crate::docblobs::{next_varint as read_varint, put_varint};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub struct ColdEntry {
pub key: Vec<u8>,
pub tf: u32,
pub dl: u32,
pub positions: Vec<u8>,
}
pub fn encode_posting(docs: &[ColdEntry]) -> Vec<u8> {
let mut out = Vec::new();
put_varint(&mut out, docs.len() as u32);
for d in docs {
put_varint(&mut out, d.key.len() as u32);
out.extend_from_slice(&d.key);
put_varint(&mut out, d.tf);
put_varint(&mut out, d.dl);
put_varint(&mut out, d.positions.len() as u32);
out.extend_from_slice(&d.positions);
}
out
}
pub fn posting_df(payload: &[u8]) -> Option<u32> {
read_varint(payload, &mut 0)
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub struct FwdRecord {
pub dl: u32,
pub terms: Vec<Vec<u8>>,
pub values: Vec<Option<Vec<u8>>>,
}
pub fn encode_fwd(dl: u32, terms: &[&[u8]], values: &[Option<&[u8]>]) -> Vec<u8> {
let mut out = Vec::new();
put_varint(&mut out, dl);
put_varint(&mut out, terms.len() as u32);
for t in terms {
put_varint(&mut out, t.len() as u32);
out.extend_from_slice(t);
}
put_varint(&mut out, values.len() as u32);
for v in values {
match v {
None => put_varint(&mut out, 0),
Some(b) => {
put_varint(&mut out, 1);
put_varint(&mut out, b.len() as u32);
out.extend_from_slice(b);
}
}
}
out
}
pub(crate) fn entries_fit(n: usize, payload_len: usize) -> usize {
n.min(payload_len)
}
pub fn decode_fwd(payload: &[u8]) -> Option<FwdRecord> {
let mut at = 0usize;
let dl = read_varint(payload, &mut at)?;
let n = read_varint(payload, &mut at)? as usize;
let mut terms = Vec::with_capacity(entries_fit(n, payload.len()));
for _ in 0..n {
let klen = read_varint(payload, &mut at)? as usize;
terms.push(payload.get(at..at + klen)?.to_vec());
at += klen;
}
let nv = read_varint(payload, &mut at)? as usize;
let mut values = Vec::with_capacity(entries_fit(nv, payload.len()));
for _ in 0..nv {
values.push(match read_varint(payload, &mut at)? {
0 => None,
1 => {
let vlen = read_varint(payload, &mut at)? as usize;
let v = payload.get(at..at + vlen)?.to_vec();
at += vlen;
Some(v)
}
_ => return None,
});
}
(at == payload.len()).then_some(FwdRecord { dl, terms, values })
}
pub fn decode_posting(payload: &[u8]) -> Option<Vec<ColdEntry>> {
let mut at = 0usize;
let n = read_varint(payload, &mut at)? as usize;
let mut out = Vec::with_capacity(entries_fit(n, payload.len()));
for _ in 0..n {
let klen = read_varint(payload, &mut at)? as usize;
let key = payload.get(at..at + klen)?.to_vec();
at += klen;
let tf = read_varint(payload, &mut at)?;
let dl = read_varint(payload, &mut at)?;
let plen = read_varint(payload, &mut at)? as usize;
let positions = payload.get(at..at + plen)?.to_vec();
at += plen;
out.push(ColdEntry { key, tf, dl, positions });
}
(at == payload.len()).then_some(out)
}
#[cfg(test)]
mod bound_tests {
#[test]
fn a_count_from_a_payload_cannot_size_an_allocation() {
use super::entries_fit;
assert_eq!(entries_fit(3, 1024), 3, "an honest count is used as-is");
assert_eq!(
entries_fit(u32::MAX as usize, 40),
40,
"4.29e9 entries over forty bytes reserves the ceiling, not 103 GB"
);
for len in [0usize, 1, 64, 4096] {
assert_eq!(entries_fit(len, len), len, "len at {len} still fits exactly");
}
let mut payload = vec![0xffu8, 0xff, 0xff, 0xff, 0x0f]; payload.push(0x00);
assert!(super::decode_posting(&payload).is_none());
}
}