use std::iter::Iterator;
use super::BdecodeError;
#[inline]
pub fn is_numeric(byte: u8) -> bool {
(48 <= byte) && (byte <= 57)
}
#[inline]
fn contains_leading_zeroes(numeric_part: &[u8]) -> bool {
(numeric_part.len() >= 2) && (numeric_part[0] == b'0')
}
#[inline]
pub fn check_integer(bytes: &[u8]) -> Result<(), BdecodeError> {
if bytes.is_empty() {
return Err(BdecodeError::UnexpectedEof);
}
let negative = bytes[0] == b'-';
if negative && bytes.len() == 1 {
return Err(BdecodeError::ExpectedDigit);
}
let numeric_part = &bytes[(negative as usize)..];
let looks_like_a_number = numeric_part.iter().all(|c| is_numeric(*c));
if !looks_like_a_number {
return Err(BdecodeError::ExpectedDigit);
}
if contains_leading_zeroes(numeric_part) {
return Err(BdecodeError::LeadingZero);
}
Ok(())
}
#[inline]
fn decode_int_no_sign(bytes: &[u8], negative: bool) -> Result<i64, BdecodeError> {
let mut result: i64 = 0;
for &byte in bytes {
if !is_numeric(byte) {
return Err(BdecodeError::ExpectedDigit);
}
let digit = byte - 48;
result = match result.checked_mul(10) {
Some(result) => result,
None => return Err(BdecodeError::Overflow),
};
if negative {
result = match result.checked_sub(digit.into()) {
Some(result) => result,
None => return Err(BdecodeError::Overflow),
};
} else {
result = match result.checked_add(digit.into()) {
Some(result) => result,
None => return Err(BdecodeError::Overflow),
};
}
}
Ok(result)
}
#[inline]
pub fn decode_int(bytes: &[u8]) -> Result<i64, BdecodeError> {
let (negative, integer) = match bytes[0] {
b'-' => (true, decode_int_no_sign(&bytes[1..], true)?),
b'0'..=b'9' => (false, decode_int_no_sign(bytes, false)?),
_ => return Err(BdecodeError::ExpectedDigit),
};
if negative && integer == 0 {
return Err(BdecodeError::NegativeZero);
}
Ok(integer)
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! test_invalid_cases {
($($x: expr),*) => {{
$(assert!(check_integer($x).is_err());)*
}}
}
#[inline]
fn assert_roundtrip(number: i64, result: bool) {
let int_string = number.to_string();
let int_bytes = int_string.as_bytes();
assert!(check_integer(int_bytes).is_ok());
assert_eq!(decode_int(int_bytes).unwrap() == number, result);
}
#[test]
fn test_negative_zero() {
let neg_zero = b"-0";
assert_eq!(decode_int(neg_zero), Err(BdecodeError::NegativeZero));
let zero = b"0";
assert_eq!(decode_int(zero).unwrap(), 0);
}
#[test]
fn test_leading_zero() {
test_invalid_cases!(
b"042",
b"0013",
b"01012",
b"-09005",
b"010010000",
b"0000012230100012"
);
}
#[test]
fn test_biggest_possible_number() {
assert_roundtrip(i64::MAX, true);
}
#[test]
fn test_smallest_possible_number() {
assert_roundtrip(i64::MIN, true);
}
#[test]
fn test_lots_of_numbers() {
for n in -100_000..=100_000 {
assert_roundtrip(n, true);
let int_string_2 = " ".to_owned() + &n.to_string();
let int_bytes_2 = int_string_2.as_bytes();
assert!(check_integer(int_bytes_2).is_err());
let int_string_3 = "0".to_owned() + &n.to_string();
let int_bytes_3 = int_string_3.as_bytes();
assert!(check_integer(int_bytes_3).is_err());
let int_string_4 = "+".to_owned() + &n.to_string();
let int_bytes_4 = int_string_4.as_bytes();
assert!(check_integer(int_bytes_4).is_err());
}
}
#[test]
fn test_contains_leading_zeroes() {
const BAD_EXAMPLES: &[&[u8]] = &[b"04", b"00"];
const GOOD_EXAMPLES: &[&[u8]] = &[b"4", b"0"];
for &bad in BAD_EXAMPLES {
assert!(contains_leading_zeroes(bad));
}
for &good in GOOD_EXAMPLES {
assert!(!contains_leading_zeroes(good));
}
}
}