use yo_common::{parse_i64, push_i64};
const HDR: usize = 6;
const END: u8 = 0xFF;
const COUNT_UNKNOWN: u16 = 65535;
const COUNT_MAX: usize = 65534;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Listpack {
bytes: Vec<u8>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Entry<'a> {
Int(i64),
Str(&'a [u8]),
}
impl Entry<'_> {
pub fn write_to(&self, out: &mut Vec<u8>) {
match self {
Entry::Int(n) => push_i64(out, *n),
Entry::Str(s) => out.extend_from_slice(s),
}
}
#[must_use]
#[inline]
pub fn byte_len(&self) -> usize {
match self {
Entry::Int(n) => yo_common::num::i64_len(*n),
Entry::Str(s) => s.len(),
}
}
#[must_use]
pub fn to_vec(&self) -> Vec<u8> {
let mut out = Vec::new();
self.write_to(&mut out);
out
}
}
impl Default for Listpack {
fn default() -> Listpack {
Listpack::new()
}
}
impl Listpack {
#[must_use]
pub fn new() -> Listpack {
let mut bytes = Vec::with_capacity(HDR + 1 + 64);
bytes.extend_from_slice(&[0, 0, 0, 0, 0, 0, END]);
let mut lp = Listpack { bytes };
lp.set_total(HDR + 1);
lp
}
pub fn from_bytes(bytes: &[u8]) -> Result<Listpack, Malformed> {
if bytes.len() < HDR + 1 {
return Err(Malformed::Short);
}
let total = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize;
if total != bytes.len() {
return Err(Malformed::Length);
}
if bytes[total - 1] != END {
return Err(Malformed::Terminator);
}
let mut at = HDR;
let mut seen = 0usize;
while at < total - 1 {
let (_, len) = decode(&bytes[at..total - 1]).ok_or(Malformed::Entry)?;
let back = backlen_len(len);
if at + len + back > total - 1 {
return Err(Malformed::Entry);
}
if read_backlen(&bytes[..at + len + back]) != Some(len) {
return Err(Malformed::BackLength);
}
at += len + back;
seen += 1;
}
let count = u16::from_le_bytes([bytes[4], bytes[5]]);
if count != COUNT_UNKNOWN && count as usize != seen {
return Err(Malformed::Count);
}
Ok(Listpack {
bytes: bytes.to_vec(),
})
}
#[inline]
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
#[inline]
#[must_use]
pub(crate) fn entries(&self) -> &[u8] {
&self.bytes[HDR..self.bytes.len() - 1]
}
#[must_use]
pub fn len(&self) -> usize {
let count = u16::from_le_bytes([self.bytes[4], self.bytes[5]]);
if count == COUNT_UNKNOWN {
self.iter().count()
} else {
count as usize
}
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.bytes.len() == HDR + 1
}
#[inline]
#[must_use]
pub fn byte_len(&self) -> usize {
self.bytes.len()
}
pub fn iter(&self) -> Iter<'_> {
Iter {
bytes: &self.bytes,
at: HDR,
}
}
#[must_use]
pub fn get(&self, index: usize) -> Option<Entry<'_>> {
let at = self.offset_of(index)?;
decode(&self.bytes[at..self.bytes.len() - 1]).map(|(e, _)| e)
}
pub fn iter_from(&self, index: usize) -> Iter<'_> {
Iter {
bytes: &self.bytes,
at: self
.offset_of(index)
.unwrap_or(self.bytes.len().saturating_sub(1)),
}
}
pub fn iter_back(&self) -> RevIter<'_> {
let entries = self.entries();
RevIter {
bytes: entries,
at: entries.len(),
}
}
#[must_use]
pub fn get_back(&self, from_end: usize) -> Option<Entry<'_>> {
let mut end = self.bytes.len() - 1;
for _ in 0..=from_end {
if end <= HDR {
return None;
}
let len = read_backlen(&self.bytes[..end])?;
end = end.checked_sub(len + backlen_len(len))?;
}
if end < HDR {
return None;
}
decode(&self.bytes[end..self.bytes.len() - 1]).map(|(e, _)| e)
}
#[must_use]
pub fn find(&self, needle: &[u8], step: usize) -> Option<usize> {
self.find_parsed(needle, parse_i64(needle), step)
}
#[must_use]
pub fn find_parsed(&self, needle: &[u8], as_int: Option<i64>, step: usize) -> Option<usize> {
scan_for(self.entries(), needle, as_int, step)
}
pub fn find_each(
&self,
needle: &[u8],
as_int: Option<i64>,
limit: usize,
hit: &mut dyn FnMut(usize) -> bool,
) -> usize {
scan_each(self.entries(), needle, as_int, limit, hit)
}
pub fn find_each_back(
&self,
needle: &[u8],
as_int: Option<i64>,
limit: usize,
hit: &mut dyn FnMut(usize) -> bool,
) -> usize {
scan_each_back(self.entries(), needle, as_int, limit, hit)
}
pub fn push(&mut self, value: &[u8]) {
let at = self.bytes.len() - 1;
self.splice(at, 0, Some(value), 1);
}
pub fn insert(&mut self, index: usize, value: &[u8]) {
let at = self.offset_of(index).unwrap_or(self.bytes.len() - 1);
self.splice(at, 0, Some(value), 1);
}
pub fn replace(&mut self, index: usize, value: &[u8]) -> bool {
let Some(at) = self.offset_of(index) else {
return false;
};
let old = self.entry_bytes(at);
self.splice(at, old, Some(value), 0);
true
}
pub fn delete(&mut self, index: usize, count: usize) -> bool {
let Some(at) = self.offset_of(index) else {
return false;
};
let mut end = at;
let mut gone = 0usize;
while gone < count && end < self.bytes.len() - 1 {
end += self.entry_bytes(end);
gone += 1;
}
if gone == 0 {
return false;
}
self.splice(at, end - at, None, -(gone as i32));
true
}
fn offset_of(&self, index: usize) -> Option<usize> {
let n = self.len();
if index >= n {
return None;
}
if index * 2 <= n {
let mut at = HDR;
for _ in 0..index {
at += self.entry_bytes(at);
}
return Some(at);
}
let mut end = self.bytes.len() - 1;
for _ in index..n {
let len = read_backlen(&self.bytes[..end])?;
end = end.checked_sub(len + backlen_len(len))?;
}
Some(end)
}
fn entry_bytes(&self, at: usize) -> usize {
let (_, len) = decode(&self.bytes[at..self.bytes.len() - 1]).expect("our own blob decodes");
len + backlen_len(len)
}
fn splice(&mut self, at: usize, remove: usize, insert: Option<&[u8]>, delta: i32) {
let mut buf = [0u8; 16];
let (head, body) = match insert {
Some(v) => {
let (head, payload) = encode(v, &mut buf);
(head, if payload { v } else { &[][..] })
}
None => (&[][..], &[][..]),
};
let entry = head.len() + body.len();
let add = if entry == 0 {
0
} else {
entry + backlen_len(entry)
};
let old = self.bytes.len();
match add.cmp(&remove) {
std::cmp::Ordering::Greater => {
self.bytes.resize(old + (add - remove), 0);
self.bytes.copy_within(at + remove..old, at + add);
}
std::cmp::Ordering::Less => {
self.bytes.copy_within(at + remove..old, at + add);
self.bytes.truncate(old - (remove - add));
}
std::cmp::Ordering::Equal => {}
}
if add > 0 {
let hole = &mut self.bytes[at..at + add];
hole[..head.len()].copy_from_slice(head);
hole[head.len()..entry].copy_from_slice(body);
write_backlen_into(&mut hole[entry..], entry);
}
let total = self.bytes.len();
self.set_total(total);
let count = i64::from(u16::from_le_bytes([self.bytes[4], self.bytes[5]]));
let count = usize::try_from(count + i64::from(delta)).unwrap_or(0);
let count = u16::try_from(count.min(COUNT_MAX)).expect("clamped to the field");
self.bytes[4..6].copy_from_slice(&count.to_le_bytes());
}
fn set_total(&mut self, total: usize) {
let total = u32::try_from(total).expect("the inline band is far under 4 GiB");
self.bytes[0..4].copy_from_slice(&total.to_le_bytes());
}
}
#[derive(Debug, Clone)]
pub struct Iter<'a> {
bytes: &'a [u8],
at: usize,
}
impl<'a> Iterator for Iter<'a> {
type Item = Entry<'a>;
#[inline]
fn next(&mut self) -> Option<Entry<'a>> {
if self.at >= self.bytes.len() - 1 {
return None;
}
let (entry, len) = decode(&self.bytes[self.at..self.bytes.len() - 1])?;
self.at += len + backlen_len(len);
Some(entry)
}
}
#[derive(Debug, Clone)]
pub struct RevIter<'a> {
bytes: &'a [u8],
at: usize,
}
impl<'a> Iterator for RevIter<'a> {
type Item = Entry<'a>;
#[inline]
fn next(&mut self) -> Option<Entry<'a>> {
if self.at == 0 {
return None;
}
let len = read_backlen(&self.bytes[..self.at])?;
let start = self.at.checked_sub(len + backlen_len(len))?;
let (entry, _) = decode(&self.bytes[start..self.at])?;
self.at = start;
Some(entry)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Malformed {
Short,
Length,
Terminator,
Entry,
BackLength,
Count,
}
fn encode<'b>(v: &[u8], buf: &'b mut [u8; 16]) -> (&'b [u8], bool) {
if let Some(n) = parse_i64(v) {
let head: &[u8] = match n {
0..=127 => {
buf[0] = n as u8;
&buf[..1]
}
-4096..=4095 => {
let u = (n as u16) & 0x1FFF;
buf[0] = 0xC0 | (u >> 8) as u8;
buf[1] = (u & 0xFF) as u8;
&buf[..2]
}
-32768..=32767 => {
buf[0] = 0xF1;
buf[1..3].copy_from_slice(&(n as i16).to_le_bytes());
&buf[..3]
}
-8_388_608..=8_388_607 => {
buf[0] = 0xF2;
buf[1..4].copy_from_slice(&(n as i32).to_le_bytes()[..3]);
&buf[..4]
}
-2_147_483_648..=2_147_483_647 => {
buf[0] = 0xF3;
buf[1..5].copy_from_slice(&(n as i32).to_le_bytes());
&buf[..5]
}
_ => {
buf[0] = 0xF4;
buf[1..9].copy_from_slice(&n.to_le_bytes());
&buf[..9]
}
};
return (head, false);
}
let head: &[u8] = match v.len() {
0..=63 => {
buf[0] = 0x80 | v.len() as u8;
&buf[..1]
}
64..=4095 => {
buf[0] = 0xE0 | (v.len() >> 8) as u8;
buf[1] = (v.len() & 0xFF) as u8;
&buf[..2]
}
_ => {
buf[0] = 0xF0;
buf[1..5].copy_from_slice(&(v.len() as u32).to_le_bytes());
&buf[..5]
}
};
(head, true)
}
#[inline]
pub(crate) fn entry_len(v: &[u8]) -> usize {
let mut buf = [0u8; 16];
let (head, payload) = encode(v, &mut buf);
let len = head.len() + if payload { v.len() } else { 0 };
len + backlen_len(len)
}
#[inline]
pub(crate) fn write_entry(dst: &mut [u8], v: &[u8]) -> usize {
let mut buf = [0u8; 16];
let (head, payload) = encode(v, &mut buf);
dst[..head.len()].copy_from_slice(head);
let mut at = head.len();
if payload {
dst[at..at + v.len()].copy_from_slice(v);
at += v.len();
}
at + write_backlen_into(&mut dst[at..], at)
}
#[inline]
pub(crate) fn decode(b: &[u8]) -> Option<(Entry<'_>, usize)> {
let first = *b.first()?;
let (at, len) = match first {
0x00..=0x7F => return Some((Entry::Int(i64::from(first)), 1)),
0x80..=0xBF => (1, (first & 0x3F) as usize),
0xC0..=0xDF => {
let raw = (u16::from(first & 0x1F) << 8) | u16::from(*b.get(1)?);
let n = if raw & 0x1000 != 0 {
i64::from(raw) - 8192
} else {
i64::from(raw)
};
return Some((Entry::Int(n), 2));
}
0xE0..=0xEF => {
let lo = *b.get(1)?;
(2, (usize::from(first & 0x0F) << 8) | usize::from(lo))
}
0xF0 => {
let n = u32::from_le_bytes([*b.get(1)?, *b.get(2)?, *b.get(3)?, *b.get(4)?]);
(5, n as usize)
}
0xF1 => {
let n = i16::from_le_bytes([*b.get(1)?, *b.get(2)?]);
return Some((Entry::Int(i64::from(n)), 3));
}
0xF2 => {
let n = i32::from_le_bytes([0, *b.get(1)?, *b.get(2)?, *b.get(3)?]) >> 8;
return Some((Entry::Int(i64::from(n)), 4));
}
0xF3 => {
let n = i32::from_le_bytes([*b.get(1)?, *b.get(2)?, *b.get(3)?, *b.get(4)?]);
return Some((Entry::Int(i64::from(n)), 5));
}
0xF4 => {
let mut w = [0u8; 8];
w.copy_from_slice(b.get(1..9)?);
return Some((Entry::Int(i64::from_le_bytes(w)), 9));
}
_ => return None,
};
let s = b.get(at..at + len)?;
Some((Entry::Str(s), at + len))
}
#[inline(always)]
fn word(s: &[u8], at: usize) -> u64 {
u64::from_le_bytes(s[at..at + 8].try_into().expect("eight bytes"))
}
struct Needle<'a> {
bytes: &'a [u8],
len: usize,
head: u64,
tail: u64,
num: Option<i64>,
}
impl<'a> Needle<'a> {
fn new(bytes: &'a [u8], num: Option<i64>) -> Needle<'a> {
let len = bytes.len();
let wide = len >= 8;
Needle {
bytes,
len,
head: if wide { word(bytes, 0) } else { 0 },
tail: if wide { word(bytes, len - 8) } else { 0 },
num,
}
}
#[inline(always)]
fn is(&self, p: &[u8]) -> bool {
debug_assert_eq!(p.len(), self.len, "the caller checks the length first");
let n = p.len();
if n < 8 {
return p == self.bytes;
}
word(p, 0) == self.head
&& word(p, n - 8) == self.tail
&& (n <= 16 || p[8..n - 8] == self.bytes[8..n - 8])
}
}
#[inline]
pub(crate) fn scan_for(b: &[u8], needle: &[u8], as_int: Option<i64>, step: usize) -> Option<usize> {
let mut got = None;
let needle = Needle::new(needle, as_int);
if step <= 1 {
walk::<true, false, _>(b, &needle, 1, 0, &mut |at| {
got = Some(at);
false
});
} else {
walk::<false, false, _>(b, &needle, step, 0, &mut |at| {
got = Some(at);
false
});
}
got
}
#[inline]
pub(crate) fn scan_each(
b: &[u8],
needle: &[u8],
as_int: Option<i64>,
limit: usize,
mut hit: &mut dyn FnMut(usize) -> bool,
) -> usize {
let needle = Needle::new(needle, as_int);
if limit == 0 {
walk::<true, false, _>(b, &needle, 1, 0, &mut hit)
} else {
walk::<true, true, _>(b, &needle, 1, limit, &mut hit)
}
}
#[inline]
pub(crate) fn scan_each_back(
b: &[u8],
needle: &[u8],
as_int: Option<i64>,
limit: usize,
mut hit: &mut dyn FnMut(usize) -> bool,
) -> usize {
let needle = Needle::new(needle, as_int);
if limit == 0 {
walk_back::<false, _>(b, &needle, 0, &mut hit)
} else {
walk_back::<true, _>(b, &needle, limit, &mut hit)
}
}
#[inline(never)]
fn head_at(b: &[u8], at: usize) -> Option<(usize, usize, bool)> {
let tag = *b.get(at)?;
Some(match tag {
0x00..=0x7F => (1, 0, false),
0x80..=0xBF => (1, (tag & 0x3F) as usize, true),
0xC0..=0xDF => (2, 0, false),
0xE0..=0xEF => (
2,
(usize::from(tag & 0x0F) << 8) | usize::from(*b.get(at + 1)?),
true,
),
0xF0 => (
5,
u32::from_le_bytes([
*b.get(at + 1)?,
*b.get(at + 2)?,
*b.get(at + 3)?,
*b.get(at + 4)?,
]) as usize,
true,
),
0xF1 => (3, 0, false),
0xF2 => (4, 0, false),
0xF3 => (5, 0, false),
0xF4 => (9, 0, false),
_ => return None,
})
}
#[inline]
fn walk<const EVERY: bool, const LIMITED: bool, F: FnMut(usize) -> bool>(
b: &[u8],
needle: &Needle<'_>,
step: usize,
limit: usize,
hit: &mut F,
) -> usize {
let want = needle.len;
let mut at = 0usize;
let mut idx = 0usize;
let mut until = 0usize;
while at < b.len() {
if LIMITED && idx == limit {
break;
}
let tag = b[at];
if EVERY && tag & 0xC0 == 0x80 {
let len = (tag & 0x3F) as usize;
if len != want {
at += len + 2;
idx += 1;
continue;
}
let Some(p) = b.get(at + 1..at + 1 + want) else {
break;
};
let matched = needle.is(p);
at += want + 2;
idx += 1;
if matched && !hit(idx - 1) {
break;
}
continue;
}
let Some((hdr, len, text)) = head_at(b, at) else {
break;
};
let total = hdr + len;
let mut matched = false;
if EVERY || until == 0 {
matched = if text {
let Some(p) = b.get(at + hdr..at + total) else {
break;
};
len == want && needle.is(p)
} else {
needle
.num
.is_some_and(|v| matches!(decode(&b[at..]), Some((Entry::Int(n), _)) if n == v))
};
if !EVERY {
until = step;
}
}
if !EVERY {
until -= 1;
}
at += total + backlen_len(total);
idx += 1;
if matched && !hit(idx - 1) {
break;
}
}
idx
}
#[inline]
fn walk_back<const LIMITED: bool, F: FnMut(usize) -> bool>(
b: &[u8],
needle: &Needle<'_>,
limit: usize,
hit: &mut F,
) -> usize {
let want = needle.len;
let mut at = b.len();
let mut idx = 0usize;
while at > 0 {
if LIMITED && idx == limit {
break;
}
let last = b[at - 1];
let (total, blen) = if last < 128 {
(usize::from(last), 1)
} else {
let Some(t) = read_backlen(&b[..at]) else {
break;
};
(t, backlen_len(t))
};
let Some(start) = at.checked_sub(total + blen) else {
break;
};
let tag = b[start];
let matched = if tag & 0xC0 == 0x80 {
let len = usize::from(tag & 0x3F);
match b.get(start + 1..start + 1 + len) {
Some(p) => len == want && needle.is(p),
None => break,
}
} else {
match head_at(b, start) {
Some((hdr, len, true)) => match b.get(start + hdr..start + hdr + len) {
Some(p) => len == want && needle.is(p),
None => break,
},
Some((_, _, false)) => needle.num.is_some_and(
|v| matches!(decode(&b[start..]), Some((Entry::Int(n), _)) if n == v),
),
None => break,
}
};
at = start;
idx += 1;
if matched && !hit(idx - 1) {
break;
}
}
idx
}
#[inline]
pub(crate) const fn backlen_len(len: usize) -> usize {
if len <= 127 {
1
} else if len <= 16383 {
2
} else if len <= 2_097_151 {
3
} else if len <= 268_435_455 {
4
} else {
5
}
}
#[inline]
fn write_backlen_into(dst: &mut [u8], len: usize) -> usize {
let n = backlen_len(len);
for (i, b) in dst[..n].iter_mut().enumerate() {
let shift = 7 * (n - 1 - i);
*b = ((len >> shift) & 127) as u8 | if i == 0 { 0 } else { 128 };
}
n
}
pub(crate) fn read_backlen(upto: &[u8]) -> Option<usize> {
let mut val = 0usize;
let mut shift = 0u32;
let mut at = upto.len().checked_sub(1)?;
loop {
let b = *upto.get(at)?;
val |= usize::from(b & 127) << shift;
if b & 128 == 0 {
return Some(val);
}
shift += 7;
if shift > 28 {
return None;
}
at = at.checked_sub(1)?;
}
}
#[cfg(test)]
mod tests {
use super::*;
fn of(members: &[&[u8]]) -> Listpack {
let mut lp = Listpack::new();
for m in members {
lp.push(m);
}
lp
}
fn all(lp: &Listpack) -> Vec<Vec<u8>> {
lp.iter().map(|e| e.to_vec()).collect()
}
#[test]
fn editing_a_blob_that_has_room_does_not_allocate() {
let mut lp = Listpack::new();
for i in 0..200 {
lp.push(format!("member:{i:04}").as_bytes());
}
lp.delete(100, 100);
for i in 0..100 {
lp.push(format!("member:{i:04}").as_bytes());
}
let names: Vec<(Vec<u8>, Vec<u8>)> = (0..100)
.map(|i| {
(
format!("other:{i:05}").into_bytes(),
format!("member:{i:04}").into_bytes(),
)
})
.collect();
let (_, allocs) = crate::tally::counted(|| {
for (i, (other, original)) in names.iter().enumerate() {
lp.replace(i, other);
lp.replace(i, b"x");
lp.replace(i, original);
}
});
assert_eq!(allocs, 0, "editing allocated {allocs} times");
assert_eq!(lp.len(), 200);
assert_eq!(lp.get(0), Some(Entry::Str(b"member:0000")));
}
#[test]
fn an_empty_blob_is_a_header_and_a_terminator() {
let lp = Listpack::new();
assert!(lp.is_empty());
assert_eq!(lp.len(), 0);
assert_eq!(lp.byte_len(), 7);
assert_eq!(lp.as_bytes(), &[7, 0, 0, 0, 0, 0, 0xFF]);
assert_eq!(lp.get(0), None);
assert_eq!(lp.iter().count(), 0);
}
#[test]
fn what_goes_in_comes_out_in_order() {
let lp = of(&[b"one", b"two", b"three"]);
assert_eq!(lp.len(), 3);
assert_eq!(
all(&lp),
vec![b"one".to_vec(), b"two".to_vec(), b"three".to_vec()]
);
assert_eq!(lp.get(1), Some(Entry::Str(b"two")));
assert_eq!(lp.get(3), None);
}
#[test]
fn an_integer_takes_the_narrowest_encoding_that_holds_it() {
for (text, first, len) in [
(&b"0"[..], 0x00u8, 1usize),
(b"127", 0x7F, 1),
(b"128", 0xC0, 2),
(b"4095", 0xCF, 2),
(b"-4096", 0xD0, 2),
(b"-1", 0xDF, 2),
(b"4096", 0xF1, 3),
(b"-4097", 0xF1, 3),
(b"32767", 0xF1, 3),
(b"32768", 0xF2, 4),
(b"8388607", 0xF2, 4),
(b"8388608", 0xF3, 5),
(b"2147483647", 0xF3, 5),
(b"2147483648", 0xF4, 9),
(b"-9223372036854775808", 0xF4, 9),
] {
let lp = of(&[text]);
let at = HDR;
assert_eq!(
lp.as_bytes()[at],
first,
"{} took the wrong encoding",
String::from_utf8_lossy(text)
);
assert_eq!(lp.byte_len(), HDR + len + 1 + 1, "{first:#x}");
assert_eq!(
lp.get(0),
Some(Entry::Int(parse_i64(text).expect("a number"))),
"{first:#x}"
);
assert_eq!(all(&lp), vec![text.to_vec()], "and it formats back");
}
}
#[test]
fn something_that_only_looks_like_a_number_stays_a_string() {
for text in [&b"01"[..], b"+1", b"1 ", b" 1", b"1.0", b"-0", b""] {
let lp = of(&[text]);
assert_eq!(
lp.get(0),
Some(Entry::Str(text)),
"{}",
String::from_utf8_lossy(text)
);
}
}
#[test]
fn a_string_takes_the_narrowest_length_field() {
for (len, first, head) in [(1usize, 0x81u8, 1usize), (63, 0xBF, 1), (64, 0xE0, 2)] {
let s = vec![b'x'; len];
let lp = of(&[&s]);
assert_eq!(lp.as_bytes()[HDR], first, "length {len}");
assert_eq!(
lp.byte_len(),
HDR + head + len + backlen_len(head + len) + 1
);
assert_eq!(lp.get(0), Some(Entry::Str(&s[..])));
}
}
#[test]
fn a_long_string_takes_the_wide_length_and_a_wide_back_length() {
let s = vec![b'y'; 5000];
let lp = of(&[&s, b"after"]);
assert_eq!(lp.as_bytes()[HDR], 0xF0);
assert_eq!(backlen_len(5005), 2);
assert_eq!(lp.get(0), Some(Entry::Str(&s[..])));
assert_eq!(lp.get(1), Some(Entry::Str(b"after")));
assert_eq!(lp.get_back(0), Some(Entry::Str(b"after")));
assert_eq!(lp.get_back(1), Some(Entry::Str(&s[..])));
}
#[test]
fn the_back_length_reads_the_same_as_it_was_written() {
for len in [1usize, 127, 128, 16382, 16383, 16384, 2_097_150, 2_097_151] {
let mut buf = [0u8; 5];
let n = write_backlen_into(&mut buf, len);
let out = &buf[..n];
assert_eq!(out.len(), backlen_len(len), "length {len}");
assert_eq!(read_backlen(out), Some(len), "length {len}");
}
}
#[test]
fn the_back_length_boundaries_are_the_ones_redis_uses() {
for (len, want) in [
(0usize, 1usize),
(127, 1),
(128, 2),
(16383, 2),
(16384, 3),
(2_097_151, 3),
(2_097_152, 4),
(268_435_455, 4),
(268_435_456, 5),
] {
assert_eq!(backlen_len(len), want, "an entry of {len} bytes");
}
}
#[test]
fn a_walk_backward_reaches_every_element() {
let lp = of(&[b"a", b"bb", b"1", b"999999", b"dddd"]);
let back: Vec<Vec<u8>> = (0..5)
.map(|i| lp.get_back(i).expect("in range").to_vec())
.collect();
let mut forward = all(&lp);
forward.reverse();
assert_eq!(back, forward);
assert_eq!(lp.get_back(5), None);
}
#[test]
fn find_locates_an_element_however_it_is_stored() {
let lp = of(&[b"alpha", b"42", b"01", b"beta"]);
assert_eq!(lp.find(b"alpha", 1), Some(0));
assert_eq!(lp.find(b"42", 1), Some(1), "stored as an integer");
assert_eq!(lp.find(b"01", 1), Some(2), "stored as a string");
assert_eq!(lp.find(b"beta", 1), Some(3));
assert_eq!(lp.find(b"gamma", 1), None);
assert_eq!(lp.find(b"1", 1), None, "01 is not 1");
}
fn every_encoding() -> Vec<Vec<u8>> {
let mut members: Vec<Vec<u8>> = Vec::new();
for n in [
0i64,
127,
-1,
4095,
-4096,
32767,
-32768,
8_388_607,
-8_388_608,
2_147_483_647,
-2_147_483_648,
i64::MAX,
i64::MIN,
] {
members.push(n.to_string().into_bytes());
}
for len in [
0usize, 1, 7, 8, 9, 15, 16, 17, 31, 63, 64, 100, 125, 126, 127, 128, 200,
] {
let mut v = vec![b'a'; len];
if len > 0 {
v[len - 1] = b'0' + (len % 10) as u8;
}
members.push(v);
}
members
}
#[test]
fn both_paths_through_the_scan_agree_about_every_encoding() {
let members = every_encoding();
let lp = of(&members.iter().map(Vec::as_slice).collect::<Vec<_>>());
assert_eq!(lp.len(), members.len());
for (at, m) in members.iter().enumerate() {
assert_eq!(lp.find(m, 1), Some(at), "member {at} went missing");
}
for miss in [
b"aaaaaaaaaaaa".as_slice(),
b"baaaaaa7".as_slice(),
b"aaaaaaa9".as_slice(),
b"128".as_slice(),
b"-2".as_slice(),
] {
assert_eq!(lp.find(miss, 1), None, "{miss:?} is not in here");
}
}
#[test]
fn a_stepped_scan_agrees_with_itself_about_every_encoding() {
let members: Vec<Vec<u8>> = (0..40i32)
.map(|i| {
if i % 3 == 0 {
(i64::from(i) * 1000 - 20_000).to_string().into_bytes()
} else {
format!("field:{i:0width$}", width = (i % 20) as usize).into_bytes()
}
})
.collect();
let lp = of(&members.iter().map(Vec::as_slice).collect::<Vec<_>>());
for (at, m) in members.iter().enumerate() {
let want = if at % 2 == 0 {
Some(at)
} else {
None
};
assert_eq!(lp.find(m, 2), want, "member {at} under a step of two");
}
}
#[test]
fn the_backward_scan_agrees_with_the_forward_one_about_every_encoding() {
let members = every_encoding();
let lp = of(&members.iter().map(Vec::as_slice).collect::<Vec<_>>());
let n = members.len();
for (at, m) in members.iter().enumerate() {
let mut got = Vec::new();
lp.find_each_back(m, parse_i64(m), 0, &mut |back| {
got.push(n - back - 1);
true
});
assert_eq!(got, vec![at], "member {at} from the back");
}
for miss in [
b"aaaaaaaaaaaa".as_slice(),
b"baaaaaa7".as_slice(),
b"aaaaaaa9".as_slice(),
b"128".as_slice(),
b"-2".as_slice(),
] {
let mut got = 0usize;
lp.find_each_back(miss, parse_i64(miss), 0, &mut |_| {
got += 1;
true
});
assert_eq!(got, 0, "{miss:?} is not in here");
}
}
#[test]
fn a_walk_over_every_match_gives_them_all_in_order_from_either_end() {
let members: Vec<Vec<u8>> = (0..30)
.map(|i| {
if i % 4 == 0 {
b"x".to_vec()
} else {
format!("element:{i:08}").into_bytes()
}
})
.collect();
let lp = of(&members.iter().map(Vec::as_slice).collect::<Vec<_>>());
let want: Vec<usize> = (0..30).filter(|i| i % 4 == 0).collect();
let mut got = Vec::new();
let looked = lp.find_each(b"x", None, 0, &mut |at| {
got.push(at);
true
});
assert_eq!(got, want);
assert_eq!(looked, 30, "no budget means the whole thing is read");
let mut got = Vec::new();
lp.find_each_back(b"x", None, 0, &mut |back| {
got.push(29 - back);
true
});
got.reverse();
assert_eq!(got, want, "the same matches, found the other way round");
let mut got = Vec::new();
let looked = lp.find_each(b"x", None, 0, &mut |at| {
got.push(at);
got.len() < 2
});
assert_eq!(got, vec![0, 4]);
assert_eq!(looked, 5, "the walk stopped where the second match was");
let mut got = Vec::new();
let looked = lp.find_each(b"x", None, 10, &mut |at| {
got.push(at);
true
});
assert_eq!(got, vec![0, 4, 8]);
assert_eq!(looked, 10);
let mut got = Vec::new();
let looked = lp.find_each_back(b"x", None, 10, &mut |back| {
got.push(29 - back);
true
});
assert_eq!(got, vec![28, 24, 20], "ten from the back is 20 up");
assert_eq!(looked, 10);
}
#[test]
fn find_with_a_step_only_looks_at_the_fields() {
let lp = of(&[b"name", b"age", b"age", b"41"]);
assert_eq!(lp.find(b"name", 2), Some(0));
assert_eq!(lp.find(b"age", 2), Some(2), "the value at 1 is not a field");
assert_eq!(lp.find(b"41", 2), None);
assert_eq!(lp.get(3), Some(Entry::Int(41)));
}
#[test]
fn inserting_puts_an_element_in_front_of_another() {
let mut lp = of(&[b"a", b"c"]);
lp.insert(1, b"b");
assert_eq!(all(&lp), vec![b"a".to_vec(), b"b".to_vec(), b"c".to_vec()]);
lp.insert(0, b"start");
lp.insert(99, b"end");
assert_eq!(lp.len(), 5);
assert_eq!(all(&lp)[0], b"start".to_vec());
assert_eq!(all(&lp)[4], b"end".to_vec());
}
#[test]
fn replacing_keeps_the_position_and_can_change_the_size() {
let mut lp = of(&[b"a", b"b", b"c"]);
assert!(lp.replace(1, b"a much longer value than before"));
assert_eq!(lp.len(), 3);
assert_eq!(all(&lp)[1], b"a much longer value than before".to_vec());
assert!(lp.replace(1, b"7"));
assert_eq!(lp.get(1), Some(Entry::Int(7)), "and can shrink to an int");
assert_eq!(all(&lp), vec![b"a".to_vec(), b"7".to_vec(), b"c".to_vec()]);
assert!(!lp.replace(9, b"nothing there"));
}
#[test]
fn deleting_takes_out_a_run_in_one_edit() {
let mut lp = of(&[b"f1", b"v1", b"f2", b"v2", b"f3", b"v3"]);
assert!(lp.delete(2, 2), "a field and its value together");
assert_eq!(lp.len(), 4);
assert_eq!(
all(&lp),
vec![
b"f1".to_vec(),
b"v1".to_vec(),
b"f3".to_vec(),
b"v3".to_vec()
]
);
assert!(!lp.delete(9, 1));
assert!(
lp.delete(0, 99),
"asking for more than is there takes the rest"
);
assert!(lp.is_empty());
assert_eq!(lp.len(), 0);
}
#[test]
fn the_header_survives_every_edit() {
let mut lp = Listpack::new();
for i in 0..64u32 {
lp.push(format!("member-{i}").as_bytes());
}
for i in 0..20 {
lp.delete(i, 1);
lp.replace(i, b"replaced");
lp.insert(i, b"1234567");
}
let total = u32::from_le_bytes([lp.bytes[0], lp.bytes[1], lp.bytes[2], lp.bytes[3]]);
assert_eq!(total as usize, lp.byte_len());
assert_eq!(lp.len(), lp.iter().count());
assert_eq!(Listpack::from_bytes(lp.as_bytes()), Ok(lp.clone()));
}
#[test]
fn a_blob_round_trips_through_its_bytes() {
let lp = of(&[b"a", b"12345", b"", &[b'z'; 200]]);
let back = Listpack::from_bytes(lp.as_bytes()).expect("our own bytes check out");
assert_eq!(back, lp);
assert_eq!(all(&back), all(&lp));
}
#[test]
fn a_blob_that_does_not_check_out_is_refused() {
assert_eq!(Listpack::from_bytes(&[]), Err(Malformed::Short));
assert_eq!(Listpack::from_bytes(&[0; 4]), Err(Malformed::Short));
let good = of(&[b"alpha", b"beta"]);
let mut wrong_len = good.as_bytes().to_vec();
wrong_len[0] = 99;
assert_eq!(Listpack::from_bytes(&wrong_len), Err(Malformed::Length));
let mut no_end = good.as_bytes().to_vec();
let last = no_end.len() - 1;
no_end[last] = 0x00;
assert_eq!(Listpack::from_bytes(&no_end), Err(Malformed::Terminator));
let mut wrong_count = good.as_bytes().to_vec();
wrong_count[4] = 7;
assert_eq!(Listpack::from_bytes(&wrong_count), Err(Malformed::Count));
let mut bad_entry = good.as_bytes().to_vec();
bad_entry[HDR] = 0xF7;
assert_eq!(Listpack::from_bytes(&bad_entry), Err(Malformed::Entry));
let mut bad_back = good.as_bytes().to_vec();
bad_back[HDR + 6] = 3;
assert_eq!(Listpack::from_bytes(&bad_back), Err(Malformed::BackLength));
}
#[test]
fn an_unknown_count_is_walked_and_not_refused() {
let mut lp = of(&[b"a", b"b", b"c"]);
lp.bytes[4..6].copy_from_slice(&COUNT_UNKNOWN.to_le_bytes());
let back = Listpack::from_bytes(lp.as_bytes()).expect("unknown is allowed");
assert_eq!(back.len(), 3);
}
fn hex(lp: &Listpack) -> String {
lp.as_bytes().iter().map(|b| format!("{b:02x}")).collect()
}
#[test]
fn the_bytes_are_the_ones_redis_writes() {
assert_eq!(hex(&Listpack::new()), "070000000000ff");
assert_eq!(
hex(&of(&[b"one", b"two", b"three"])),
"180000000300836f6e65048374776f0485746872656506ff"
);
let ints: Vec<&[u8]> = vec![
b"0",
b"127",
b"128",
b"4095",
b"-4096",
b"-1",
b"4096",
b"-4097",
b"32767",
b"32768",
b"8388607",
b"8388608",
b"2147483647",
b"2147483648",
b"-9223372036854775808",
];
assert_eq!(
hex(&of(&ints)),
"4d0000000f0000017f01c08002cfff02d00002dfff02f1001003f1ffef03f1ff7f\
03f200800004f2ffff7f04f30000800005f3ffffff7f05f4000000800000000009\
f4000000000000008009ff"
);
let not_ints: Vec<&[u8]> = vec![b"01", b"+1", b"1 ", b" 1", b"1.0", b"-0", b""];
assert_eq!(
hex(&of(¬_ints)),
"22000000070082303103822b3103823120038220310383312e3004822d30038001ff"
);
assert_eq!(
hex(&of(&[b"name", b"age", b"age", b"41"])),
"190000000400846e616d6505836167650483616765042901ff"
);
}
#[test]
fn a_long_element_is_framed_the_way_redis_frames_it() {
for (len, total, head, tail) in [
(
63usize,
72usize,
"480000000100bf7878787878",
"78787878787840ff",
),
(64, 74, "4a0000000100e04078787878", "78787878787842ff"),
(4095, 4106, "0a1000000100efff78787878", "78787878782081ff"),
(4096, 4110, "0e1000000100f00010000078", "78787878782085ff"),
] {
let lp = of(&[&vec![b'x'; len]]);
let h = hex(&lp);
assert_eq!(lp.byte_len(), total, "a {len} byte element");
assert_eq!(&h[..head.len()], head, "a {len} byte element");
assert_eq!(&h[h.len() - tail.len()..], tail, "a {len} byte element");
}
let lp = of(&[&vec![b'y'; 5000], b"after"]);
let h = hex(&lp);
assert_eq!(lp.byte_len(), 5021);
assert_eq!(&h[..24], "9d1300000200f08813000079");
assert_eq!(&h[h.len() - 16..], "85616674657206ff");
}
#[test]
fn a_full_inline_band_still_reads_correctly() {
let members: Vec<Vec<u8>> = (0..128u32).map(|i| format!("m{i}").into_bytes()).collect();
let mut lp = Listpack::new();
for m in &members {
lp.push(m);
}
assert_eq!(lp.len(), 128);
for (i, m) in members.iter().enumerate() {
assert_eq!(lp.find(m, 1), Some(i));
}
assert!(lp.byte_len() < 1024, "{} bytes", lp.byte_len());
}
}