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
}
#[must_use]
#[inline]
pub(crate) fn is(&self, needle: &[u8], as_int: Option<i64>) -> bool {
match (self, as_int) {
(Entry::Int(n), Some(v)) => *n == v,
(Entry::Int(_), None) => false,
(Entry::Str(s), _) => *s == needle,
}
}
}
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> {
let step = step.max(1);
let mut until = 0usize;
for (at, e) in self.iter().enumerate() {
if until == 0 {
if e.is(needle, as_int) {
return Some(at);
}
until = step;
}
until -= 1;
}
None
}
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 encoded: Vec<u8> = match insert {
Some(v) => {
let (head, payload) = encode(v, &mut buf);
let mut e = Vec::with_capacity(head.len() + v.len() + 5);
e.extend_from_slice(head);
if payload {
e.extend_from_slice(v);
}
let len = e.len();
write_backlen(&mut e, len);
e
}
None => Vec::new(),
};
self.bytes.splice(at..at + remove, encoded);
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)]
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)]
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]
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
}
}
fn write_backlen(out: &mut Vec<u8>, len: usize) {
let mut buf = [0u8; 5];
let n = write_backlen_into(&mut buf, len);
out.extend_from_slice(&buf[..n]);
}
#[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 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 out = Vec::new();
write_backlen(&mut out, len);
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");
}
#[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());
}
}