const MAX_PADDING_BYTES: usize = 64;
pub fn encode_number(slice: &[u8], negative: bool) -> Vec<u8> {
let mut start = 0;
let pad_byte = if negative { 0xFF } else { 0x00 };
while start < slice.len() && slice[start] == pad_byte {
start += 1;
}
let needs_padding = if negative {
start == slice.len() || (slice[start] & 0x80) == 0
} else {
start < slice.len() && (slice[start] & 0x80) != 0
};
let mut result = Vec::with_capacity(if needs_padding {
slice.len() - start + 1
} else {
slice.len() - start
});
if needs_padding {
result.push(pad_byte);
}
result.extend_from_slice(&slice[start..]);
result
}
pub fn decode_number<const LEN: usize>(mut slice: &[u8], signed: bool) -> Option<[u8; LEN]> {
assert!(LEN > 0, "Array length must be greater than zero");
if slice.is_empty() {
return Some([0; LEN]);
}
if !signed && slice[0] & 0x80 != 0 {
return None;
}
let was_negative = signed && slice[0] & 0x80 != 0;
let padding_byte = if was_negative { 0xFF } else { 0x00 };
let mut padding_bytes = 0;
while slice.len() > LEN && slice[0] == padding_byte {
if padding_bytes == MAX_PADDING_BYTES {
return None;
}
slice = &slice[1..];
padding_bytes += 1;
}
let is_negative = signed && slice[0] & 0x80 != 0;
if slice.len() > LEN || (is_negative != was_negative) {
return None;
}
let mut result = [padding_byte; LEN];
let start = LEN - slice.len();
result[start..].copy_from_slice(slice);
Some(result)
}
#[cfg(test)]
mod tests {
use super::*;
use clvmr::Allocator;
use rstest::rstest;
macro_rules! test_roundtrip {
( $num:expr, $signed:expr ) => {
let mut allocator = Allocator::new();
let ptr = allocator.new_number($num.into()).unwrap();
let atom = allocator.atom(ptr);
let expected = atom.as_ref();
#[allow(unused_comparisons)]
let encoded = encode_number(&$num.to_be_bytes(), $num < 0);
assert_eq!(encoded, expected);
let expected = $num.to_be_bytes();
let decoded = decode_number(&encoded, $signed).unwrap();
assert_eq!(decoded, expected);
};
}
#[test]
fn test_u8() {
for number in u8::MIN..=u8::MAX {
test_roundtrip!(number, false);
}
}
#[test]
fn test_i8() {
for number in i8::MIN..=i8::MAX {
test_roundtrip!(number, true);
}
}
#[test]
fn test_u16() {
for number in u16::MIN..=u16::MAX {
test_roundtrip!(number, false);
}
}
#[test]
fn test_i16() {
for number in i16::MIN..=i16::MAX {
test_roundtrip!(number, true);
}
}
#[rstest]
fn test_u32(
#[values(
0, 1, 10, 100, 1000, i8::MAX as u32, u8::MAX as u32, i16::MAX as u32, u16::MAX as u32, i32::MAX as u32, u32::MAX
)]
number: u32,
) {
test_roundtrip!(number, false);
}
#[rstest]
fn test_i32(
#[values(
0, 1, 10, 100, 1000, i8::MAX as i32, u8::MAX as i32, i16::MAX as i32, u16::MAX as i32, i32::MAX
)]
number: i32,
) {
test_roundtrip!(number, true);
}
#[rstest]
#[case(&[], false, Some([0x00, 0x00]))]
#[case(&[], true, Some([0x00, 0x00]))]
#[case(&[0xFF], true, Some([0xFF, 0xFF]))]
#[case(&[0xFE], true, Some([0xFF, 0xFE]))]
#[case(&[0x80], true, Some([0xFF, 0x80]))]
#[case(&[0x81], true, Some([0xFF, 0x81]))]
#[case(&[0x7F], true, Some([0x00, 0x7F]))]
#[case(&[0x01], true, Some([0x00, 0x01]))]
#[case(&[0x00], true, Some([0x00, 0x00]))]
#[case(&[0x00], false, Some([0x00, 0x00]))]
#[case(&[0x00], true, Some([0x00, 0x00]))]
#[case(&[0x00, 0x00], false, Some([0x00, 0x00]))]
#[case(&[0x00, 0x00], true, Some([0x00, 0x00]))]
#[case(&[0x00, 0x00, 0x00], false, Some([0x00, 0x00]))]
#[case(&[0x00, 0x00, 0x00], true, Some([0x00, 0x00]))]
#[case(&[0x00; MAX_PADDING_BYTES + 2], false, Some([0x00, 0x00]))]
#[case(&[0x00; MAX_PADDING_BYTES + 2], true, Some([0x00, 0x00]))]
#[case(&[0x00; MAX_PADDING_BYTES + 3], false, None)]
#[case(&[0x00; MAX_PADDING_BYTES + 3], true, None)]
#[case(&[0xFF; MAX_PADDING_BYTES + 2], true, Some([0xFF, 0xFF]))]
#[case(&[0xFF; MAX_PADDING_BYTES + 3], true, None)]
#[case(&[0x01, 0x00, 0x00], false, None)]
#[case(&[0x01, 0x00, 0x00], true, None)]
#[case(&[0xFF, 0xFF, 0xFF, 0x00, 0x00], true, None)]
#[case(&[0xFF, 0xFF, 0xFF, 0xFF, 0xFF], true, Some([0xFF, 0xFF]))]
#[case(&[0xFF], false, None)]
#[case(&[0xFF, 0xFF], false, None)]
#[case(&[0x00, 0xFF, 0xFF], false, Some([0xFF, 0xFF]))]
fn test_decode(#[case] input: &[u8], #[case] signed: bool, #[case] expected: Option<[u8; 2]>) {
assert_eq!(
decode_number::<2>(input, signed),
expected,
"input: {input:?}, signed: {signed}, expected: {expected:?}",
);
}
}