use std::any::TypeId;
use crate::serialization::bincode_compat;
use serde::de::DeserializeOwned;
use serde::Serialize;
#[inline]
pub fn counter_leaf_to_i128<V: 'static>(le_bytes: &[u8]) -> Option<i128> {
if le_bytes.len() != 8 {
return None;
}
let mut word = [0u8; 8];
word.copy_from_slice(le_bytes);
let tid = TypeId::of::<V>();
if tid == TypeId::of::<u64>() {
Some(u64::from_le_bytes(word) as i128)
} else if tid == TypeId::of::<i64>() {
Some(i64::from_le_bytes(word) as i128)
} else {
None
}
}
#[inline]
pub fn counter_value_to_i128<V: 'static + Serialize>(value: &V) -> Option<i128> {
let bytes = bincode_compat::serialize(value).ok()?;
counter_leaf_to_i128::<V>(&bytes)
}
#[inline]
pub fn i128_to_counter_leaf<V: 'static>(n: i128) -> Option<Vec<u8>> {
let tid = TypeId::of::<V>();
if tid == TypeId::of::<u64>() {
if n < 0 || n > u64::MAX as i128 {
return None;
}
Some((n as u64).to_le_bytes().to_vec())
} else if tid == TypeId::of::<i64>() {
if n < i64::MIN as i128 || n > i64::MAX as i128 {
return None;
}
Some((n as i64).to_le_bytes().to_vec())
} else {
None
}
}
#[inline]
pub fn i128_to_counter_value<V: 'static + DeserializeOwned>(n: i128) -> Option<V> {
let bytes = i128_to_counter_leaf::<V>(n)?;
bincode_compat::deserialize::<V>(&bytes).ok()
}
#[inline]
pub fn counter_return_i64(n: i128) -> i64 {
n as i64
}
#[inline]
pub fn split_u64_delta_to_i64_chunks(delta: u64) -> Vec<i64> {
if delta == 0 {
return vec![0];
}
let mut remaining = delta;
let cap = i64::MAX as u64;
let mut chunks = Vec::with_capacity(3);
while remaining > 0 {
let take = remaining.min(cap);
chunks.push(take as i64);
remaining -= take;
}
chunks
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn u64_above_i64_max_round_trips_and_increments() {
let big: u64 = (i64::MAX as u64) + 10; let leaf = big.to_le_bytes().to_vec();
let n = counter_leaf_to_i128::<u64>(&leaf).expect("u64 leaf decodes");
assert_eq!(n, big as i128);
assert!(
n > i64::MAX as i128,
"must be the unsigned magnitude, not wrapped"
);
let incremented = i128_to_counter_leaf::<u64>(n + 5).expect("in range");
assert_eq!(incremented, (big + 5).to_le_bytes().to_vec());
let decremented = counter_leaf_to_i128::<u64>(&incremented).expect("decode") - 15;
assert_eq!(decremented, (i64::MAX as i128) + 0);
}
#[test]
fn u64_overflow_and_underflow_are_rejected() {
assert_eq!(i128_to_counter_leaf::<u64>(u64::MAX as i128 + 1), None);
assert_eq!(i128_to_counter_leaf::<u64>(-1), None);
assert_eq!(i128_to_counter_value::<u64>(-1), None);
let at_max = i128_to_counter_leaf::<u64>(u64::MAX as i128).expect("u64::MAX ok");
assert_eq!(at_max, u64::MAX.to_le_bytes().to_vec());
assert_eq!(counter_leaf_to_i128::<u64>(&at_max), Some(u64::MAX as i128));
}
#[test]
fn i64_range_round_trips_including_negative() {
for v in [0i64, 1, -1, i64::MAX, i64::MIN, -42, 12345] {
let leaf = v.to_le_bytes().to_vec();
let n = counter_leaf_to_i128::<i64>(&leaf).expect("i64 leaf decodes");
assert_eq!(n, v as i128);
assert_eq!(i128_to_counter_leaf::<i64>(n), Some(leaf));
}
assert_eq!(i128_to_counter_leaf::<i64>(i64::MAX as i128 + 1), None);
assert_eq!(i128_to_counter_leaf::<i64>(i64::MIN as i128 - 1), None);
}
#[test]
fn non_counter_v_is_graceful_none() {
let leaf = [1u8, 0, 0, 0, 0, 0, 0, 0];
assert_eq!(counter_leaf_to_i128::<u32>(&leaf), None);
assert_eq!(counter_leaf_to_i128::<String>(&leaf), None);
assert_eq!(i128_to_counter_leaf::<u32>(1), None);
assert_eq!(i128_to_counter_value::<u32>(1), None);
}
#[test]
fn malformed_leaf_is_none() {
assert_eq!(counter_leaf_to_i128::<u64>(&[1, 2, 3]), None); assert_eq!(counter_leaf_to_i128::<u64>(&[]), None);
}
#[test]
fn leaf_bytes_are_byte_identical_to_bincode() {
for n in [
0i128,
1,
42,
i64::MAX as i128,
(i64::MAX as i128) + 1,
u64::MAX as i128,
] {
let helper = i128_to_counter_leaf::<u64>(n).expect("in u64 range");
let bincode = bincode_compat::serialize(&(n as u64)).expect("bincode u64");
assert_eq!(helper, bincode, "u64 leaf must match bincode for n={}", n);
}
for n in [0i128, -1, 1, i64::MAX as i128, i64::MIN as i128] {
let helper = i128_to_counter_leaf::<i64>(n).expect("in i64 range");
let bincode = bincode_compat::serialize(&(n as i64)).expect("bincode i64");
assert_eq!(helper, bincode, "i64 leaf must match bincode for n={}", n);
}
}
#[test]
fn counter_value_round_trips_through_typed_v() {
let big: u64 = (i64::MAX as u64) + 100;
let n = counter_value_to_i128::<u64>(&big).expect("typed u64 → i128");
assert_eq!(n, big as i128);
let back: u64 = i128_to_counter_value::<u64>(n).expect("i128 → typed u64");
assert_eq!(back, big);
}
#[test]
fn return_i64_is_bit_pattern_recoverable_as_u64() {
let big: u64 = (i64::MAX as u64) + 7;
let r = counter_return_i64(big as i128);
assert!(
r < 0,
"a u64 count > i64::MAX returns a negative i64 bit-pattern"
);
assert_eq!(r as u64, big, "recoverable via `as u64`");
assert_eq!(counter_return_i64(123), 123i64);
}
#[test]
fn delta_split_sums_back_and_is_i64_bounded() {
for delta in [
0u64,
1,
i64::MAX as u64,
(i64::MAX as u64) + 1,
u64::MAX,
u64::MAX - 1,
] {
let chunks = split_u64_delta_to_i64_chunks(delta);
assert!(chunks.len() <= 3, "≤3 chunks for delta={}", delta);
assert!(
chunks.iter().all(|&c| c >= 0),
"all chunks non-negative for delta={}",
delta
);
let sum: i128 = chunks.iter().map(|&c| c as i128).sum();
assert_eq!(sum, delta as i128, "chunks sum back for delta={}", delta);
}
}
}