diff --git a/rust/candid/src/types/number.rs b/rust/candid/src/types/number.rs index 9f223c9a..14ba0b89 100644 --- a/rust/candid/src/types/number.rs +++ b/rust/candid/src/types/number.rs @@ -208,19 +208,6 @@ impl<'de> Deserialize<'de> for Nat { // LEB128 encoding for bignum. -/// Returns true if OR-ing `low_bits` at position `shift` fits within a u64. -/// The `shift < 64` guard prevents a debug-mode panic from the shift expression. -#[inline] -fn leb128_fits_u64(low_bits: u64, shift: u32) -> bool { - if shift == 0 { - true - } else if shift < 64 { - low_bits < (1u64 << (64 - shift)) - } else { - false - } -} - impl Nat { pub fn encode(&self, w: &mut W) -> crate::Result<()> where @@ -258,7 +245,7 @@ impl Nat { r.read_exact(&mut buf)?; let byte = buf[0]; let low_bits = u64::from(byte & 0x7f); - if leb128_fits_u64(low_bits, shift) { + if shift == 0 || (shift < 64 && low_bits < (1u64 << (64 - shift))) { small |= low_bits << shift; if byte & 0x80u8 == 0 { return Ok(Nat(BigUint::from(small))); @@ -322,59 +309,58 @@ impl Int { where R: io::Read, { - let mut small = 0u64; + let mut small = 0i64; let mut shift = 0u32; loop { let mut buf = [0]; r.read_exact(&mut buf)?; let byte = buf[0]; - let low_bits = u64::from(byte & 0x7f); - if leb128_fits_u64(low_bits, shift) { + let low_bits = i64::from(byte & 0x7f); + + let fits_i64 = if shift < 57 { + true + } else if shift < 64 { + let remaining_bits = 64 - shift; + if (byte & 0x40) != 0 { + (low_bits | !0x7f) >> (remaining_bits - 1) == -1 + } else { + low_bits >> (remaining_bits - 1) == 0 + } + } else { + false + }; + + if fits_i64 { small |= low_bits << shift; - if byte & 0x80u8 == 0 { - // Last byte: sign-extend and return as i64 if possible. - let total_bits = shift + 7; - if byte & 0x40u8 != 0 { - // Negative: fill bits from total_bits upward with 1s. - let signed = if total_bits >= 64 { - // Bit 63 is already the LEB sign bit inside small. - small as i64 - } else { - let sign_mask = !((1u64 << total_bits) - 1); - (small | sign_mask) as i64 - }; - return Ok(Int(BigInt::from(signed))); - } else if small <= i64::MAX as u64 { - return Ok(Int(BigInt::from(small as i64))); - } else { - // Positive value in [2^63, 2^64-1]: fits u64 but not i64. - return Ok(Int(BigInt::from(BigUint::from(small)))); + shift += 7; + if byte & 0x80 == 0 { + if shift < 64 && (byte & 0x40) != 0 { + small |= !0i64 << shift; } + return Ok(Int(BigInt::from(small))); } - shift += 7; continue; } - // Value overflows u64: fall back to BigInt for remaining bytes. - let mut result = BigInt::from(BigUint::from(small)); - result |= BigInt::from(low_bits) << shift; - if byte & 0x80u8 == 0 { - shift += 7; - if byte & 0x40u8 != 0 { + let mut result = BigInt::from(small); + let big_low_bits = BigInt::from(byte & 0x7fu8); + result |= big_low_bits << shift; + shift += 7; + if byte & 0x80 == 0 { + if (byte & 0x40) != 0 { result |= BigInt::from(-1) << shift; } return Ok(Int(result)); } - shift += 7; loop { let mut buf = [0]; r.read_exact(&mut buf)?; let byte = buf[0]; - let low_bits = BigInt::from(byte & 0x7fu8); - result |= low_bits << shift; + let big_low_bits = BigInt::from(byte & 0x7fu8); + result |= big_low_bits << shift; shift += 7; - if byte & 0x80u8 == 0 { - if byte & 0x40u8 != 0 { + if byte & 0x80 == 0 { + if (byte & 0x40) != 0 { result |= BigInt::from(-1) << shift; } return Ok(Int(result)); diff --git a/rust/candid/tests/compatibility_numbers.rs b/rust/candid/tests/compatibility_numbers.rs index 025123c5..c5f756a0 100644 --- a/rust/candid/tests/compatibility_numbers.rs +++ b/rust/candid/tests/compatibility_numbers.rs @@ -13,9 +13,12 @@ fn number_fast_paths_preserve_small_and_large_values() { assert_eq!(decoded_nat_values, nat_values); let int_values = vec![ + Int::from(0), Int::from(-1), Int::from(42), Int::from(i64::MIN), + Int::from(i64::MAX), + Int::parse(b"24197857200151251861972493965091130863").unwrap(), Int::parse(b"-600000000000000000000000000000000000").unwrap(), ]; let int_bytes = Encode!(&int_values).unwrap();