Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 33 additions & 47 deletions rust/candid/src/types/number.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<W>(&self, w: &mut W) -> crate::Result<()>
where
Expand Down Expand Up @@ -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)));
Expand Down Expand Up @@ -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));
Expand Down
3 changes: 3 additions & 0 deletions rust/candid/tests/compatibility_numbers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Loading