use crate::tags::{MARKER_U128, MARKER_U16, MARKER_U32, MARKER_U64};
pub(crate) const VARINT_MAX_BYTES: usize = 1 + 16;
pub(crate) fn encode_varint_le(value: u128) -> ([u8; VARINT_MAX_BYTES], usize) {
let mut bytes = [0_u8; VARINT_MAX_BYTES];
let length = match value {
0..=250 => {
bytes[0] = value as u8;
1
}
251..=0xffff => {
bytes[0] = MARKER_U16;
bytes[1..3].copy_from_slice(&(value as u16).to_le_bytes());
3
}
0x1_0000..=0xffff_ffff => {
bytes[0] = MARKER_U32;
bytes[1..5].copy_from_slice(&(value as u32).to_le_bytes());
5
}
0x1_0000_0000..=0xffff_ffff_ffff_ffff => {
bytes[0] = MARKER_U64;
bytes[1..9].copy_from_slice(&(value as u64).to_le_bytes());
9
}
_ => {
bytes[0] = MARKER_U128;
bytes[1..17].copy_from_slice(&value.to_le_bytes());
17
}
};
(bytes, length)
}
pub(crate) fn decode_varint_le(marker: u8, payload: &[u8]) -> Option<u128> {
let (value, minimum) = match marker {
0..=250 => return Some(marker as u128),
MARKER_U16 => (read_le16(payload)? as u128, 251),
MARKER_U32 => (read_le32(payload)? as u128, 0x1_0000),
MARKER_U64 => (read_le64(payload)? as u128, 0x1_0000_0000),
MARKER_U128 => (read_le128(payload)?, 0x1_0000_0000_0000_0000),
_ => return None,
};
if value < minimum {
None
} else {
Some(value)
}
}
pub(crate) const fn zigzag_encode(value: i128) -> u128 {
((value << 1) ^ (value >> 127)) as u128
}
pub(crate) const fn zigzag_decode(encoded: u128) -> i128 {
((encoded >> 1) as i128) ^ -((encoded & 1) as i128)
}
fn read_le16(bytes: &[u8]) -> Option<u16> {
if bytes.len() < 2 {
return None;
}
Some(u16::from_le_bytes([bytes[0], bytes[1]]))
}
fn read_le32(bytes: &[u8]) -> Option<u32> {
if bytes.len() < 4 {
return None;
}
let mut value = [0_u8; 4];
value.copy_from_slice(&bytes[..4]);
Some(u32::from_le_bytes(value))
}
fn read_le64(bytes: &[u8]) -> Option<u64> {
if bytes.len() < 8 {
return None;
}
let mut value = [0_u8; 8];
value.copy_from_slice(&bytes[..8]);
Some(u64::from_le_bytes(value))
}
fn read_le128(bytes: &[u8]) -> Option<u128> {
if bytes.len() < 16 {
return None;
}
let mut value = [0_u8; 16];
value.copy_from_slice(&bytes[..16]);
Some(u128::from_le_bytes(value))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn canonical_roundtrip_covers_all_widths() {
let cases = [
0u128,
1,
250,
251,
0xffff,
0x1_0000,
0xffff_ffff,
0x1_0000_0000,
0xffff_ffff_ffff_ffff,
0x1_0000_0000_0000_0000,
u128::MAX,
];
for &value in &cases {
let (bytes, length) = encode_varint_le(value);
let marker = bytes[0];
let payload = &bytes[1..length];
assert_eq!(decode_varint_le(marker, payload), Some(value));
}
}
#[test]
fn non_canonical_forms_are_rejected() {
assert_eq!(decode_varint_le(MARKER_U16, &[250, 0]), None);
assert_eq!(decode_varint_le(MARKER_U16, &[251, 0]), Some(251));
assert_eq!(decode_varint_le(MARKER_U32, &[0xff, 0xff, 0, 0]), None);
assert_eq!(decode_varint_le(MARKER_U32, &[0, 0, 1, 0]), Some(0x1_0000));
assert_eq!(decode_varint_le(255, &[]), None);
assert_eq!(decode_varint_le(MARKER_U64, &[1, 2, 3]), None);
}
#[test]
fn zigzag_roundtrips() {
for &value in &[
i128::MIN,
-1,
0,
1,
i128::MAX,
-123456789012345678901234567890,
] {
assert_eq!(zigzag_decode(zigzag_encode(value)), value);
}
assert_eq!(zigzag_encode(0), 0);
assert_eq!(zigzag_encode(-1), 1);
assert_eq!(zigzag_encode(1), 2);
assert_eq!(zigzag_decode(1), -1);
assert_eq!(zigzag_decode(2), 1);
}
}