use bytes::Bytes;
#[derive(Debug, Clone, PartialEq)]
pub enum Element {
Int(i64),
Str(Bytes),
}
impl Element {
pub fn into_bytes(self) -> Bytes {
match self {
Element::Int(i) => Bytes::from(i.to_string()),
Element::Str(s) => s,
}
}
pub fn as_int(&self) -> Option<i64> {
match self {
Element::Int(i) => Some(*i),
Element::Str(_) => None,
}
}
}
fn le_u16(b: &[u8], at: usize) -> Option<u16> {
Some(u16::from_le_bytes(b.get(at..at + 2)?.try_into().ok()?))
}
fn le_u32(b: &[u8], at: usize) -> Option<u32> {
Some(u32::from_le_bytes(b.get(at..at + 4)?.try_into().ok()?))
}
fn le_u64(b: &[u8], at: usize) -> Option<u64> {
Some(u64::from_le_bytes(b.get(at..at + 8)?.try_into().ok()?))
}
fn sign_extend(v: u64, bits: u32) -> i64 {
let shift = 64 - bits;
((v << shift) as i64) >> shift
}
fn backlen_size(n: usize) -> usize {
if n < 128 {
1
} else if n < 16384 {
2
} else if n < 2_097_152 {
3
} else if n < 268_435_456 {
4
} else {
5
}
}
fn listpack_element(buf: &[u8], pos: usize) -> Option<(Element, usize)> {
let b = *buf.get(pos)?;
let (element, entry_len) = if b & 0x80 == 0 {
(Element::Int((b & 0x7f) as i64), 1)
} else if b & 0xc0 == 0x80 {
let len = (b & 0x3f) as usize;
let data = buf.get(pos + 1..pos + 1 + len)?;
(Element::Str(Bytes::copy_from_slice(data)), 1 + len)
} else if b & 0xe0 == 0xc0 {
let raw = (((b & 0x1f) as u64) << 8) | *buf.get(pos + 1)? as u64;
(Element::Int(sign_extend(raw, 13)), 2)
} else if b == 0xf1 {
(Element::Int(le_u16(buf, pos + 1)? as i16 as i64), 3)
} else if b == 0xf2 {
let raw = (*buf.get(pos + 1)? as u64)
| ((*buf.get(pos + 2)? as u64) << 8)
| ((*buf.get(pos + 3)? as u64) << 16);
(Element::Int(sign_extend(raw, 24)), 4)
} else if b == 0xf3 {
(Element::Int(le_u32(buf, pos + 1)? as i32 as i64), 5)
} else if b == 0xf4 {
(Element::Int(le_u64(buf, pos + 1)? as i64), 9)
} else if b == 0xf0 {
let len = le_u32(buf, pos + 1)? as usize;
let data = buf.get(pos + 5..pos + 5 + len)?;
(Element::Str(Bytes::copy_from_slice(data)), 5 + len)
} else if b & 0xf0 == 0xe0 {
let len = (((b & 0x0f) as usize) << 8) | *buf.get(pos + 1)? as usize;
let data = buf.get(pos + 2..pos + 2 + len)?;
(Element::Str(Bytes::copy_from_slice(data)), 2 + len)
} else {
return None; };
Some((element, entry_len + backlen_size(entry_len)))
}
pub fn listpack(buf: &[u8]) -> Option<Vec<Element>> {
if buf.len() < 7 {
return None;
}
let mut out = Vec::new();
let mut pos = 6; loop {
match buf.get(pos)? {
0xff => return Some(out),
_ => {
let (element, size) = listpack_element(buf, pos)?;
out.push(element);
pos += size;
}
}
}
}
pub fn listpack_strings(buf: &[u8]) -> Option<Vec<Bytes>> {
Some(
listpack(buf)?
.into_iter()
.map(Element::into_bytes)
.collect(),
)
}
fn ziplist_element(buf: &[u8], pos: usize) -> Option<(Element, usize)> {
let prevlen_size = if *buf.get(pos)? < 254 { 1 } else { 5 };
let p = pos + prevlen_size;
let b = *buf.get(p)?;
let (element, rest) = match b >> 6 {
0 => {
let len = (b & 0x3f) as usize;
let data = buf.get(p + 1..p + 1 + len)?;
(Element::Str(Bytes::copy_from_slice(data)), 1 + len)
}
1 => {
let len = (((b & 0x3f) as usize) << 8) | *buf.get(p + 1)? as usize;
let data = buf.get(p + 2..p + 2 + len)?;
(Element::Str(Bytes::copy_from_slice(data)), 2 + len)
}
2 => {
let raw = buf.get(p + 1..p + 5)?;
let len = u32::from_be_bytes(raw.try_into().ok()?) as usize;
let data = buf.get(p + 5..p + 5 + len)?;
(Element::Str(Bytes::copy_from_slice(data)), 5 + len)
}
_ => match b {
0xc0 => (Element::Int(le_u16(buf, p + 1)? as i16 as i64), 3),
0xd0 => (Element::Int(le_u32(buf, p + 1)? as i32 as i64), 5),
0xe0 => (Element::Int(le_u64(buf, p + 1)? as i64), 9),
0xf0 => {
let raw = (*buf.get(p + 1)? as u64)
| ((*buf.get(p + 2)? as u64) << 8)
| ((*buf.get(p + 3)? as u64) << 16);
(Element::Int(sign_extend(raw, 24)), 4)
}
0xfe => (Element::Int(*buf.get(p + 1)? as i8 as i64), 2),
0xf1..=0xfd => (Element::Int(((b & 0x0f) as i64) - 1), 1),
_ => return None,
},
};
Some((element, prevlen_size + rest))
}
pub fn ziplist(buf: &[u8]) -> Option<Vec<Element>> {
if buf.len() < 11 {
return None;
}
let mut out = Vec::new();
let mut pos = 10; loop {
match buf.get(pos)? {
0xff => return Some(out),
_ => {
let (element, size) = ziplist_element(buf, pos)?;
out.push(element);
pos += size;
}
}
}
}
pub fn ziplist_strings(buf: &[u8]) -> Option<Vec<Bytes>> {
Some(ziplist(buf)?.into_iter().map(Element::into_bytes).collect())
}
pub fn intset(buf: &[u8]) -> Option<Vec<Bytes>> {
let width = le_u32(buf, 0)? as usize;
let count = le_u32(buf, 4)? as usize;
if !matches!(width, 2 | 4 | 8) {
return None;
}
let mut out = Vec::with_capacity(count);
for i in 0..count {
let at = 8 + i * width;
let v = match width {
2 => le_u16(buf, at)? as i16 as i64,
4 => le_u32(buf, at)? as i32 as i64,
_ => le_u64(buf, at)? as i64,
};
out.push(Bytes::from(v.to_string()));
}
Some(out)
}
pub struct ListpackWriter {
body: Vec<u8>,
elements: usize,
}
impl ListpackWriter {
pub fn new() -> ListpackWriter {
ListpackWriter {
body: Vec::new(),
elements: 0,
}
}
fn backlen(&mut self, len: usize) {
let mut stack = [0u8; 5];
let mut n = 0;
if len <= 127 {
self.body.push(len as u8);
return;
}
let mut remaining = len;
while remaining > 0 {
stack[n] = (remaining & 127) as u8;
remaining >>= 7;
n += 1;
}
for i in (0..n).rev() {
let marker = if i == n - 1 { 0 } else { 128 };
self.body.push(stack[i] | marker);
}
}
pub fn int(&mut self, v: i64) {
let start = self.body.len();
if (0..=127).contains(&v) {
self.body.push(v as u8);
} else if (-4096..=4095).contains(&v) {
let raw = (v as u64) & 0x1fff;
self.body.push(0xc0 | ((raw >> 8) as u8));
self.body.push((raw & 0xff) as u8);
} else if (i16::MIN as i64..=i16::MAX as i64).contains(&v) {
self.body.push(0xf1);
self.body.extend_from_slice(&(v as i16).to_le_bytes());
} else if (-(1 << 23)..(1 << 23)).contains(&v) {
self.body.push(0xf2);
self.body.extend_from_slice(&(v as i32).to_le_bytes()[..3]);
} else if (i32::MIN as i64..=i32::MAX as i64).contains(&v) {
self.body.push(0xf3);
self.body.extend_from_slice(&(v as i32).to_le_bytes());
} else {
self.body.push(0xf4);
self.body.extend_from_slice(&v.to_le_bytes());
}
let len = self.body.len() - start;
self.backlen(len);
self.elements += 1;
}
pub fn str(&mut self, s: &[u8]) {
let start = self.body.len();
if s.len() < 64 {
self.body.push(0x80 | s.len() as u8);
} else if s.len() < 4096 {
self.body.push(0xe0 | ((s.len() >> 8) as u8));
self.body.push((s.len() & 0xff) as u8);
} else {
self.body.push(0xf0);
self.body.extend_from_slice(&(s.len() as u32).to_le_bytes());
}
self.body.extend_from_slice(s);
let len = self.body.len() - start;
self.backlen(len);
self.elements += 1;
}
pub fn finish(self) -> Vec<u8> {
let total = 6 + self.body.len() + 1;
let mut out = Vec::with_capacity(total);
out.extend_from_slice(&(total as u32).to_le_bytes());
out.extend_from_slice(&(self.elements.min(65535) as u16).to_le_bytes());
out.extend_from_slice(&self.body);
out.push(0xff);
out
}
}
#[cfg(test)]
mod tests {
use super::*;
fn wrap_listpack(elements: &[u8]) -> Vec<u8> {
let total = 6 + elements.len() + 1;
let mut out = Vec::new();
out.extend_from_slice(&(total as u32).to_le_bytes());
out.extend_from_slice(&999u16.to_le_bytes());
out.extend_from_slice(elements);
out.push(0xff);
out
}
#[test]
fn listpack_7bit_and_6bit_encodings() {
let lp = wrap_listpack(&[0x05, 1, 0x82, b'h', b'i', 3]);
assert_eq!(
listpack(&lp).unwrap(),
vec![Element::Int(5), Element::Str(Bytes::from("hi"))]
);
}
#[test]
fn listpack_13bit_int_is_signed() {
let lp = wrap_listpack(&[0xdf, 0xff, 2]);
assert_eq!(listpack(&lp).unwrap(), vec![Element::Int(-1)]);
}
#[test]
fn listpack_wide_int_encodings() {
let mut e = Vec::new();
e.push(0xf1);
e.extend_from_slice(&(-300i16).to_le_bytes());
e.push(3);
e.push(0xf4);
e.extend_from_slice(&(-1_234_567_890_123i64).to_le_bytes());
e.push(9);
assert_eq!(
listpack(&wrap_listpack(&e)).unwrap(),
vec![Element::Int(-300), Element::Int(-1_234_567_890_123)]
);
}
#[test]
fn listpack_24bit_int_sign_extends() {
let mut e = vec![0xf2];
e.extend_from_slice(&[0xff, 0xff, 0xff]); e.push(4);
assert_eq!(
listpack(&wrap_listpack(&e)).unwrap(),
vec![Element::Int(-1)]
);
}
#[test]
fn listpack_12bit_string() {
let body = vec![b'x'; 300];
let mut e = vec![0xe0 | ((300 >> 8) as u8), (300 & 0xff) as u8];
e.extend_from_slice(&body);
e.push((302 >> 7) as u8);
e.push(((302 & 127) | 128) as u8);
let decoded = listpack(&wrap_listpack(&e)).unwrap();
assert_eq!(decoded, vec![Element::Str(Bytes::from(body))]);
}
#[test]
fn listpack_rejects_truncation() {
assert!(listpack(&wrap_listpack(&[0x8a])).is_none());
assert!(listpack(&[1, 2, 3]).is_none());
}
#[test]
fn ints_render_as_decimal_strings() {
let lp = wrap_listpack(&[0x05, 1]);
assert_eq!(listpack_strings(&lp).unwrap(), vec![Bytes::from("5")]);
}
fn wrap_ziplist(entries: &[u8]) -> Vec<u8> {
let mut out = Vec::new();
out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(entries);
out.push(0xff);
out
}
#[test]
fn ziplist_string_and_immediate_int() {
let zl = wrap_ziplist(&[0x00, 0x02, b'a', b'b', 0x03, 0xf5]);
assert_eq!(
ziplist(&zl).unwrap(),
vec![Element::Str(Bytes::from("ab")), Element::Int(4)]
);
}
#[test]
fn ziplist_14bit_length_is_big_endian() {
let body = vec![b'z'; 200];
let mut e = vec![0x00, 0x40 | ((200 >> 8) as u8), (200 & 0xff) as u8];
e.extend_from_slice(&body);
assert_eq!(
ziplist(&wrap_ziplist(&e)).unwrap(),
vec![Element::Str(Bytes::from(body))]
);
}
#[test]
fn ziplist_five_byte_prevlen_is_skipped() {
let mut e = vec![0xfe, 0, 0, 0, 0]; e.extend_from_slice(&[0x01, b'q']);
assert_eq!(
ziplist(&wrap_ziplist(&e)).unwrap(),
vec![Element::Str(Bytes::from("q"))]
);
}
#[test]
fn intset_reads_each_width() {
let mut b = Vec::new();
b.extend_from_slice(&2u32.to_le_bytes());
b.extend_from_slice(&3u32.to_le_bytes());
for v in [-5i16, 0, 300] {
b.extend_from_slice(&v.to_le_bytes());
}
assert_eq!(
intset(&b).unwrap(),
vec![Bytes::from("-5"), Bytes::from("0"), Bytes::from("300")]
);
}
#[test]
fn intset_rejects_bad_width_and_truncation() {
let mut b = Vec::new();
b.extend_from_slice(&3u32.to_le_bytes()); b.extend_from_slice(&1u32.to_le_bytes());
assert!(intset(&b).is_none());
let mut c = Vec::new();
c.extend_from_slice(&8u32.to_le_bytes());
c.extend_from_slice(&5u32.to_le_bytes()); assert!(intset(&c).is_none());
}
#[test]
fn listpack_writer_round_trips_every_encoding() {
let ints = [
0,
127,
128,
-1,
4095,
-4096,
4096,
-4097,
32767,
-32768,
32768,
8_388_607,
-8_388_608,
8_388_608,
2_147_483_647,
-2_147_483_648,
2_147_483_648,
i64::MAX,
i64::MIN,
];
let strings: Vec<Vec<u8>> = vec![
b"".to_vec(),
b"short".to_vec(),
vec![b'a'; 63],
vec![b'b'; 64],
vec![b'c'; 4095],
vec![b'd'; 4096],
vec![b'e'; 70_000],
];
let mut w = ListpackWriter::new();
for i in ints {
w.int(i);
}
for s in &strings {
w.str(s);
}
let encoded = w.finish();
let decoded = listpack(&encoded).expect("writer produced an undecodable listpack");
let mut expected: Vec<Element> = ints.iter().map(|i| Element::Int(*i)).collect();
expected.extend(strings.iter().map(|s| Element::Str(Bytes::from(s.clone()))));
assert_eq!(decoded, expected);
}
#[test]
fn listpack_writer_header_matches_actual_length() {
let mut w = ListpackWriter::new();
w.str(b"hello");
w.int(42);
let encoded = w.finish();
let claimed = u32::from_le_bytes(encoded[..4].try_into().unwrap()) as usize;
assert_eq!(claimed, encoded.len());
assert_eq!(u16::from_le_bytes(encoded[4..6].try_into().unwrap()), 2);
assert_eq!(*encoded.last().unwrap(), 0xff);
}
#[test]
fn listpack_backlen_width_agrees_with_the_decoder() {
for len in [1usize, 127, 128, 200, 16383, 16384, 20000] {
let mut w = ListpackWriter::new();
w.str(&vec![b'x'; len]);
w.int(7); let decoded = listpack(&w.finish()).unwrap();
assert_eq!(decoded.len(), 2, "desynced at string length {len}");
assert_eq!(decoded[1], Element::Int(7));
}
}
}