diff --git a/Cargo.lock b/Cargo.lock index 5ca030d7..46d4abc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -858,10 +858,8 @@ dependencies = [ "arbitrary", "crc32fast", "data-encoding", - "hex", "impls", "serde", - "serde_bytes", "serde_cbor", "serde_json", "serde_test", diff --git a/rust/ic_principal/Cargo.toml b/rust/ic_principal/Cargo.toml index 1ccc0093..9463af35 100644 --- a/rust/ic_principal/Cargo.toml +++ b/rust/ic_principal/Cargo.toml @@ -6,40 +6,48 @@ edition = "2021" description = "Principal type used on the Internet Computer." homepage = "https://internetcomputer.org/docs/current/references/id-encoding-spec" documentation = "https://docs.rs/ic_principal" -repository="https://github.com/dfinity/candid" +repository = "https://github.com/dfinity/candid" license = "Apache-2.0" readme = "README.md" categories = ["data-structures", "no-std"] keywords = ["internet-computer", "types", "dfinity"] include = ["src", "Cargo.toml", "LICENSE", "README.md"] -[dependencies] -crc32fast = "1.3.0" -data-encoding = "2.3.2" -hex = "0.4.3" -sha2 = "0.10.1" -thiserror = "1.0.30" - [dev-dependencies] serde_cbor = "0.11.2" serde_json = "1.0.74" serde_test = "1.0.137" impls = "1" +[dependencies.arbitrary] +workspace = true +optional = true + +[dependencies.crc32fast] +version = "1.3.0" +optional = true + +[dependencies.data-encoding] +version = "2.3.2" +optional = true + [dependencies.serde] version = "1.0.115" features = ["derive"] optional = true -[dependencies.serde_bytes] -version = "0.11.5" +[dependencies.sha2] +version = "0.10.1" optional = true -[dependencies.arbitrary] -workspace = true +[dependencies.thiserror] +version = "1.0.30" optional = true [features] -# Default features include serde support. -default = ['serde', 'serde_bytes'] -arbitrary = ['default', 'dep:arbitrary'] +all = ['arbitrary', 'default'] +default = ['convert', 'self_authenticating', 'serde'] +arbitrary = ['dep:arbitrary', 'serde'] +convert = ['dep:crc32fast', 'dep:data-encoding', 'dep:thiserror'] +self_authenticating = ['dep:sha2'] +serde = ['dep:serde', 'convert'] diff --git a/rust/ic_principal/src/lib.rs b/rust/ic_principal/src/lib.rs index 9860e8b9..fcf7ea7e 100644 --- a/rust/ic_principal/src/lib.rs +++ b/rust/ic_principal/src/lib.rs @@ -1,15 +1,19 @@ +#[cfg(feature = "arbitrary")] +use arbitrary::{Arbitrary, Result as ArbitraryResult, Unstructured}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +#[cfg(feature = "self_authenticating")] use sha2::{Digest, Sha224}; +#[cfg(feature = "convert")] use std::convert::TryFrom; +#[cfg(feature = "convert")] use std::fmt::Write; +#[cfg(feature = "convert")] use thiserror::Error; -#[cfg(feature = "arbitrary")] -use arbitrary::{Arbitrary, Result as ArbitraryResult, Unstructured}; - /// An error happened while encoding, decoding or serializing a [`Principal`]. #[derive(Error, Clone, Debug, Eq, PartialEq)] +#[cfg(feature = "convert")] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum PrincipalError { #[error("Bytes is longer than 29 bytes.")] @@ -49,12 +53,15 @@ pub enum PrincipalError { /// /// Example of using a Principal object: /// ``` +/// # #[cfg(feature = "convert")] { /// use ic_principal::Principal; /// /// let text = "aaaaa-aa"; // The management canister ID. /// let principal = Principal::from_text(text).expect("Could not decode the principal."); /// assert_eq!(principal.as_slice(), &[]); /// assert_eq!(principal.to_text(), text); +/// +/// # } /// ``` /// /// Serialization is enabled with the "serde" feature. It supports serializing @@ -62,6 +69,7 @@ pub enum PrincipalError { /// readable serializers. /// /// ``` +/// # #[cfg(all(feature = "convert", feature = "serde"))] { /// use ic_principal::Principal; /// use serde::{Deserialize, Serialize}; /// use std::str::FromStr; @@ -85,6 +93,7 @@ pub enum PrincipalError { /// serde_cbor::to_vec(&Data { id: id.clone() }).unwrap(), /// &[161, 98, 105, 100, 73, 239, 205, 171, 0, 0, 0, 0, 0, 1], /// ); +/// # } /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Principal { @@ -98,8 +107,10 @@ pub struct Principal { impl Principal { pub const MAX_LENGTH_IN_BYTES: usize = 29; + #[allow(dead_code)] const CRC_LENGTH_IN_BYTES: usize = 4; + #[allow(dead_code)] const SELF_AUTHENTICATING_TAG: u8 = 2; const ANONYMOUS_TAG: u8 = 4; @@ -112,6 +123,7 @@ impl Principal { } /// Construct a self-authenticating ID from public key + #[cfg(feature = "self_authenticating")] pub fn self_authenticating>(public_key: P) -> Self { let public_key = public_key.as_ref(); let hash = Sha224::digest(public_key); @@ -132,39 +144,48 @@ impl Principal { Self { len: 1, bytes } } + /// Returns `None` if the slice exceeds the max length. + const fn from_slice_core(slice: &[u8]) -> Option { + match slice.len() { + len @ 0..=Self::MAX_LENGTH_IN_BYTES => { + let mut bytes = [0; Self::MAX_LENGTH_IN_BYTES]; + let mut i = 0; + while i < len { + bytes[i] = slice[i]; + i += 1; + } + Some(Self { + len: len as u8, + bytes, + }) + } + _ => None, + } + } + /// Construct a [`Principal`] from a slice of bytes. /// /// # Panics /// /// Panics if the slice is longer than 29 bytes. pub const fn from_slice(slice: &[u8]) -> Self { - match Self::try_from_slice(slice) { - Ok(v) => v, + match Self::from_slice_core(slice) { + Some(principal) => principal, _ => panic!("slice length exceeds capacity"), } } /// Construct a [`Principal`] from a slice of bytes. + #[cfg(feature = "convert")] pub const fn try_from_slice(slice: &[u8]) -> Result { - const MAX_LENGTH_IN_BYTES: usize = Principal::MAX_LENGTH_IN_BYTES; - match slice.len() { - len @ 0..=MAX_LENGTH_IN_BYTES => { - let mut bytes = [0; MAX_LENGTH_IN_BYTES]; - let mut i = 0; - while i < len { - bytes[i] = slice[i]; - i += 1; - } - Ok(Self { - len: len as u8, - bytes, - }) - } - _ => Err(PrincipalError::BytesTooLong()), + match Self::from_slice_core(slice) { + Some(principal) => Ok(principal), + None => Err(PrincipalError::BytesTooLong()), } } /// Parse a [`Principal`] from text representation. + #[cfg(feature = "convert")] pub fn from_text>(text: S) -> Result { // Strategy: Parse very liberally, then pretty-print and compare output // This is both simpler and yields better error messages @@ -206,6 +227,7 @@ impl Principal { } /// Convert [`Principal`] to text representation. + #[cfg(feature = "convert")] pub fn to_text(&self) -> String { format!("{self}") } @@ -217,6 +239,7 @@ impl Principal { } } +#[cfg(feature = "convert")] impl std::fmt::Display for Principal { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let blob: &[u8] = self.as_slice(); @@ -244,6 +267,7 @@ impl std::fmt::Display for Principal { } } +#[cfg(feature = "convert")] impl std::str::FromStr for Principal { type Err = PrincipalError; @@ -252,6 +276,7 @@ impl std::str::FromStr for Principal { } } +#[cfg(feature = "convert")] impl TryFrom<&str> for Principal { type Error = PrincipalError; @@ -260,6 +285,7 @@ impl TryFrom<&str> for Principal { } } +#[cfg(feature = "convert")] impl TryFrom> for Principal { type Error = PrincipalError; @@ -268,6 +294,7 @@ impl TryFrom> for Principal { } } +#[cfg(feature = "convert")] impl TryFrom<&Vec> for Principal { type Error = PrincipalError; @@ -276,6 +303,7 @@ impl TryFrom<&Vec> for Principal { } } +#[cfg(feature = "convert")] impl TryFrom<&[u8]> for Principal { type Error = PrincipalError; diff --git a/rust/ic_principal/tests/principal.rs b/rust/ic_principal/tests/principal.rs index 25393889..541e719e 100644 --- a/rust/ic_principal/tests/principal.rs +++ b/rust/ic_principal/tests/principal.rs @@ -1,18 +1,23 @@ // #![allow(deprecated)] use ic_principal::Principal; +#[cfg(feature = "convert")] use ic_principal::PrincipalError; const MANAGEMENT_CANISTER_BYTES: [u8; 0] = []; +#[allow(dead_code)] const MANAGEMENT_CANISTER_TEXT: &str = "aaaaa-aa"; const ANONYMOUS_CANISTER_BYTES: [u8; 1] = [4u8]; +#[allow(dead_code)] const ANONYMOUS_CANISTER_TEXT: &str = "2vxsx-fae"; const TEST_CASE_BYTES: [u8; 9] = [0xef, 0xcd, 0xab, 0, 0, 0, 0, 0, 1]; +#[allow(dead_code)] const TEST_CASE_TEXT: &str = "2chl6-4hpzw-vqaaa-aaaaa-c"; -mod convert_from_bytes { +#[cfg(feature = "convert")] +mod try_convert_from_bytes { use super::*; #[test] @@ -35,6 +40,10 @@ mod convert_from_bytes { Err(PrincipalError::BytesTooLong()) ); } +} + +mod convert_from_bytes { + use super::*; #[test] fn from_test_case_ok() { @@ -56,6 +65,7 @@ mod convert_from_bytes { } } +#[cfg(feature = "convert")] mod convert_from_text { use super::*; @@ -173,6 +183,7 @@ mod convert_to_bytes { } #[test] + #[cfg(feature = "convert")] fn test_case_to_bytes_correct() { assert_eq!( Principal::from_text(TEST_CASE_TEXT).unwrap().as_slice(), @@ -181,6 +192,7 @@ mod convert_to_bytes { } } +#[cfg(feature = "convert")] mod convert_to_text { use super::*; @@ -211,6 +223,7 @@ mod convert_to_text { } } +#[cfg(feature = "serde")] mod ser_de { use super::*; use serde_test::{assert_tokens, Configure, Token}; @@ -232,24 +245,32 @@ mod ser_de { #[test] fn impl_traits() { + #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; + #[cfg(feature = "convert")] use std::convert::TryFrom; - use std::fmt::{Debug, Display}; + use std::fmt::Debug; + #[cfg(feature = "convert")] + use std::fmt::Display; use std::hash::Hash; + #[cfg(feature = "convert")] use std::str::FromStr; assert!(impls::impls!( - Principal: Debug & Display & Clone & Copy & Eq & PartialOrd & Ord & Hash + Principal: Debug & Clone & Copy & Eq & PartialOrd & Ord & Hash )); + #[cfg(feature = "convert")] assert!( - impls::impls!(Principal: FromStr & TryFrom<&'static str> & TryFrom> & TryFrom<&'static Vec> & TryFrom<&'static [u8]> & AsRef<[u8]>) + impls::impls!(Principal: Display & FromStr & TryFrom<&'static str> & TryFrom> & TryFrom<&'static Vec> & TryFrom<&'static [u8]> & AsRef<[u8]>) ); + #[cfg(feature = "serde")] assert!(impls::impls!(Principal: Serialize & Deserialize<'static>)); } #[test] +#[cfg(feature = "convert")] fn long_blobs_ending_04_is_valid_principal() { let blob: [u8; 18] = [ 10, 116, 105, 100, 0, 0, 0, 0, 0, 144, 0, 51, 1, 1, 0, 0, 0, 4, @@ -258,6 +279,7 @@ fn long_blobs_ending_04_is_valid_principal() { } #[test] +#[cfg(feature = "self_authenticating")] fn self_authenticating_ok() { // self_authenticating doesn't verify the input bytes // this test checks: @@ -265,10 +287,9 @@ fn self_authenticating_ok() { // 2. 0x02 was added in the end // 3. total length is 29 let p1 = Principal::self_authenticating([]); - let p2 = Principal::try_from_slice(&[ + let p2 = Principal::from_slice(&[ 209, 74, 2, 140, 42, 58, 43, 201, 71, 97, 2, 187, 40, 130, 52, 196, 21, 162, 176, 31, 130, 142, 166, 42, 197, 179, 228, 47, 2, - ]) - .unwrap(); + ]); assert_eq!(p1, p2); }