Skip to content

Commit

Permalink
bignum feature
Browse files Browse the repository at this point in the history
  • Loading branch information
chenyan-dfinity committed Oct 26, 2023
1 parent f04c235 commit 683f46c
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 29 deletions.
18 changes: 13 additions & 5 deletions rust/candid/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ binread = { version = "2.1", features = ["debug_template"] }
byteorder = "1.4.3"
leb128 = "0.2.4"
paste = "1.0.0"
num-bigint = { workspace = true }
num-traits= { workspace = true }
hex.workspace = true
serde.workspace = true
thiserror.workspace = true
anyhow.workspace = true

serde_bytes = { version = "0.11", optional = true }
pretty = { workspace = true, optional = true }
num-bigint = { workspace = true, optional = true }
num-traits = { workspace = true, optional = true }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
stacker = "0.1"
Expand All @@ -46,16 +46,24 @@ harness = false
path = "benches/benchmark.rs"

[features]
#bignum = ["num-bigint", "num-traits"]
value = []
bignum = ["num-bigint", "num-traits"]
value = ["bignum"]
printer = ["pretty"]
default = ["serde_bytes", "printer"]
default = ["serde_bytes", "printer", "bignum"]
all = ["default", "value"]

[[test]]
name = "types"
path = "tests/types.rs"
required-features = ["value"]
[[test]]
name = "serde"
path = "tests/serde.rs"
required-features = ["bignum"]
[[test]]
name = "number"
path = "tests/number.rs"
required-features = ["bignum"]

# docs.rs-specific configuration
# To test locally: RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --features all
Expand Down
38 changes: 18 additions & 20 deletions rust/candid/src/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ use super::{
error::{Error, Result},
types::internal::{text_size, type_of, TypeId},
types::{Field, Label, SharedLabel, Type, TypeEnv, TypeInner},
CandidType, Int, Nat,
CandidType,
};
#[cfg(feature = "bignum")]
use super::{Int, Nat};
use crate::{
binary_parser::{BoolValue, Header, Len, PrincipalBytes},
types::subtype::{subtype, Gamma},
Expand Down Expand Up @@ -337,6 +339,8 @@ impl<'de> Deserializer<'de> {
// int(0), nat(1), principal(2), reserved(3), service(4), function(5)
// This is necessary for deserializing IDLValue because
// it has only one visitor and we need a way to know who called the visitor.
#[cfg_attr(docsrs, doc(cfg(feature = "bignum")))]
#[cfg(feature = "bignum")]
fn deserialize_int<'a, V>(&'a mut self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
Expand All @@ -356,6 +360,8 @@ impl<'de> Deserializer<'de> {
bytes.extend_from_slice(&int.0.to_signed_bytes_le());
visitor.visit_byte_buf(bytes)
}
#[cfg_attr(docsrs, doc(cfg(feature = "bignum")))]
#[cfg(feature = "bignum")]
fn deserialize_nat<'a, V>(&'a mut self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
Expand Down Expand Up @@ -508,8 +514,14 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
}
self.unroll_type()?;
match self.expect_type.as_ref() {
#[cfg(feature = "bignum")]
TypeInner::Int => self.deserialize_int(visitor),
#[cfg(not(feature = "bignum"))]
TypeInner::Int => self.deserialize_i128(visitor),
#[cfg(feature = "bignum")]
TypeInner::Nat => self.deserialize_nat(visitor),
#[cfg(not(feature = "bignum"))]
TypeInner::Nat => self.deserialize_u128(visitor),
TypeInner::Nat8 => self.deserialize_u8(visitor),
TypeInner::Nat16 => self.deserialize_u16(visitor),
TypeInner::Nat32 => self.deserialize_u32(visitor),
Expand Down Expand Up @@ -571,22 +583,13 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
where
V: Visitor<'de>,
{
use num_traits::ToPrimitive;
use crate::types::leb128::{decode_int, decode_nat};
self.unroll_type()?;
assert!(*self.expect_type == TypeInner::Int);
let value: i128 = match self.wire_type.as_ref() {
TypeInner::Int => {
let int = Int::decode(&mut self.input).map_err(Error::msg)?;
int.0
.to_i128()
.ok_or_else(|| Error::msg("Cannot convert int to i128"))?
}
TypeInner::Nat => {
let nat = Nat::decode(&mut self.input).map_err(Error::msg)?;
nat.0
.to_i128()
.ok_or_else(|| Error::msg("Cannot convert nat to i128"))?
}
TypeInner::Int => decode_int(&mut self.input)?,
TypeInner::Nat => i128::try_from(decode_nat(&mut self.input)?)
.map_err(|_| Error::msg("Cannot convert nat to i128"))?,
t => return Err(Error::subtype(format!("{t} cannot be deserialized to int"))),
};
visitor.visit_i128(value)
Expand All @@ -595,17 +598,12 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
where
V: Visitor<'de>,
{
use num_traits::ToPrimitive;
self.unroll_type()?;
check!(
*self.expect_type == TypeInner::Nat && *self.wire_type == TypeInner::Nat,
"nat"
);
let nat = Nat::decode(&mut self.input).map_err(Error::msg)?;
let value = nat
.0
.to_u128()
.ok_or_else(|| Error::msg("Cannot convert nat to u128"))?;
let value = crate::types::leb128::decode_nat(&mut self.input)?;
visitor.visit_u128(value)
}
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value>
Expand Down
11 changes: 9 additions & 2 deletions rust/candid/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,17 @@
//! You can also use `i128` and `u128` to represent Candid `int` and `nat` types respectively (decoding will fail if
//! the number is more than 128 bits).
//! ```
//! #[cfg(feature = "bignum")]
//! # fn f() -> Result<(), candid::Error> {
//! use candid::{Int, Nat, Encode, Decode};
//! let x = "-10000000000000000000".parse::<Int>()?;
//! let bytes = Encode!(&Nat::from(1024), &x)?;
//! let (a, b) = Decode!(&bytes, Nat, Int)?;
//! let (c, d) = Decode!(&bytes, u128, i128)?;
//! assert_eq!(a + 1, 1025);
//! assert_eq!(b, Int::parse(b"-10000000000000000000")?);
//! # Ok::<(), candid::Error>(())
//! # Ok(())
//! # }
//! ```
//!
//! ## Operating on reference types
Expand All @@ -132,6 +135,8 @@
//! instead of the Candid types.
//!
//! ```
//! #[cfg(feature = "bignum")]
//! # fn f() -> Result<(), candid::Error> {
//! use candid::{define_function, define_service, func, Encode, Decode, Principal};
//! let principal = Principal::from_text("aaaaa-aa").unwrap();
//!
Expand All @@ -145,7 +150,8 @@
//! });
//! let serv = MyService::new(principal);
//! assert_eq!(serv, Decode!(&Encode!(&serv)?, MyService)?);
//! # Ok::<(), candid::Error>(())
//! # Ok(())
//! # }
//! ```
//!
//! ## Operating on untyped Candid values
Expand Down Expand Up @@ -235,6 +241,7 @@ pub mod error;
pub use error::{Error, Result};

pub mod types;
#[cfg(feature = "bignum")]
pub use types::number::{Int, Nat};
pub use types::CandidType;
pub use types::{
Expand Down
8 changes: 8 additions & 0 deletions rust/candid/src/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,20 @@ impl<'a> types::Serializer for &'a mut ValueSerializer {
self.write(&[v as u8])?;
Ok(())
}
#[cfg(feature = "bignum")]
fn serialize_int(self, v: &crate::Int) -> Result<()> {
v.encode(&mut self.value)
}
#[cfg(feature = "bignum")]
fn serialize_nat(self, v: &crate::Nat) -> Result<()> {
v.encode(&mut self.value)
}
fn serialize_i128(self, v: i128) -> Result<()> {
crate::types::leb128::encode_int(&mut self.value, v)
}
fn serialize_u128(self, v: u128) -> Result<()> {
crate::types::leb128::encode_nat(&mut self.value, v)
}
serialize_num!(nat8, u8, write_u8);
serialize_num!(nat16, u16, write_u16::<LittleEndian>);
serialize_num!(nat32, u32, write_u32::<LittleEndian>);
Expand Down
4 changes: 2 additions & 2 deletions rust/candid/src/types/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ impl CandidType for i128 {
where
S: Serializer,
{
serializer.serialize_int(&crate::Int::from(*self))
serializer.serialize_i128(*self)
}
}
impl CandidType for u128 {
Expand All @@ -52,7 +52,7 @@ impl CandidType for u128 {
where
S: Serializer,
{
serializer.serialize_nat(&crate::Nat::from(*self))
serializer.serialize_u128(*self)
}
}

Expand Down
97 changes: 97 additions & 0 deletions rust/candid/src/types/leb128.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
use crate::error::{Error, Result};
use std::io;

const CONTINUATION_BIT: u8 = 1 << 7;
const SIGN_BIT: u8 = 1 << 6;

pub fn encode_nat<W>(w: &mut W, mut val: u128) -> Result<()>
where
W: io::Write + ?Sized,
{
loop {
let mut byte = (val & 0x7fu128) as u8;
val >>= 7;
if val != 0 {
byte |= CONTINUATION_BIT;
}
let buf = [byte];
w.write_all(&buf)?;
if val == 0 {
return Ok(());
}
}
}
pub fn encode_int<W>(w: &mut W, mut val: i128) -> Result<()>
where
W: io::Write + ?Sized,
{
loop {
let mut byte = val as u8;
val >>= 6;
let done = val == 0 || val == -1;
if done {
byte &= !CONTINUATION_BIT;
} else {
val >>= 1;
byte |= CONTINUATION_BIT;
}
let buf = [byte];
w.write_all(&buf)?;
if done {
return Ok(());
}
}
}
pub fn decode_nat<R>(r: &mut R) -> Result<u128>
where
R: io::Read + ?Sized,
{
let mut result = 0;
let mut shift = 0;
loop {
let mut buf = [0];
r.read_exact(&mut buf)?;
if shift == 127 && buf[0] != 0x00 && buf[0] != 0x01 {
while buf[0] & CONTINUATION_BIT != 0 {
r.read_exact(&mut buf)?;
}
return Err(Error::msg("nat overflow"));
}
let low_bits = (buf[0] & !CONTINUATION_BIT) as u128;
result |= low_bits << shift;
if buf[0] & CONTINUATION_BIT == 0 {
return Ok(result);
}
shift += 7;
}
}
pub fn decode_int<R>(r: &mut R) -> Result<i128>
where
R: io::Read + ?Sized,
{
let mut result = 0;
let mut shift = 0;
let size = 128;
let mut byte;
loop {
let mut buf = [0];
r.read_exact(&mut buf)?;
byte = buf[0];
if shift == 127 && byte != 0x00 && byte != 0x7f {
while buf[0] & CONTINUATION_BIT != 0 {
r.read_exact(&mut buf)?;
}
return Err(Error::msg("int overflow"));
}
let low_bits = (byte & !CONTINUATION_BIT) as i128;
result |= low_bits << shift;
shift += 7;
if byte & CONTINUATION_BIT == 0 {
break;
}
}
if shift < size && (byte & SIGN_BIT) == SIGN_BIT {
result |= !0 << shift;
}
Ok(result)
}
8 changes: 8 additions & 0 deletions rust/candid/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ pub use self::internal::{
};
pub use type_env::TypeEnv;

pub mod leb128;
#[cfg(feature = "bignum")]
pub mod number;
pub mod principal;
pub mod reference;
Expand Down Expand Up @@ -58,8 +60,14 @@ pub trait Serializer: Sized {
type Error: Error;
type Compound: Compound<Error = Self::Error>;
fn serialize_bool(self, v: bool) -> Result<(), Self::Error>;
#[cfg_attr(docsrs, doc(cfg(feature = "bignum")))]
#[cfg(feature = "bignum")]
fn serialize_int(self, v: &crate::Int) -> Result<(), Self::Error>;
fn serialize_i128(self, v: i128) -> Result<(), Self::Error>;
#[cfg_attr(docsrs, doc(cfg(feature = "bignum")))]
#[cfg(feature = "bignum")]
fn serialize_nat(self, v: &crate::Nat) -> Result<(), Self::Error>;
fn serialize_u128(self, v: u128) -> Result<(), Self::Error>;
fn serialize_nat8(self, v: u8) -> Result<(), Self::Error>;
fn serialize_nat16(self, v: u16) -> Result<(), Self::Error>;
fn serialize_nat32(self, v: u32) -> Result<(), Self::Error>;
Expand Down
13 changes: 13 additions & 0 deletions rust/candid/tests/number.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use candid::types::leb128::*;
use candid::{Int, Nat};
use num_traits::cast::ToPrimitive;

Expand Down Expand Up @@ -51,10 +52,16 @@ fn random_i64() {
let mut encoded = Vec::new();
Int::from(x).encode(&mut encoded).unwrap();
assert_eq!(expected, encoded);
encoded.clear();
encode_int(&mut encoded, x.into()).unwrap();
assert_eq!(expected, encoded);
}
let mut readable = &expected[..];
let decoded = Int::decode(&mut readable).unwrap();
assert_eq!(decoded.0.to_i64().unwrap(), x);
readable = &expected[..];
let decoded = decode_int(&mut readable).unwrap();
assert_eq!(decoded as i64, x);
}
}

Expand All @@ -70,10 +77,16 @@ fn random_u64() {
let mut encoded = Vec::new();
Nat::from(x).encode(&mut encoded).unwrap();
assert_eq!(expected, encoded);
encoded.clear();
encode_nat(&mut encoded, x.into()).unwrap();
assert_eq!(expected, encoded);
}
let mut readable = &expected[..];
let decoded = Nat::decode(&mut readable).unwrap();
assert_eq!(decoded.0.to_u64().unwrap(), x);
readable = &expected[..];
let decoded = decode_nat(&mut readable).unwrap();
assert_eq!(decoded as u64, x);
}
}

Expand Down

0 comments on commit 683f46c

Please sign in to comment.