Skip to content

Commit

Permalink
move dependencies to feature (#506)
Browse files Browse the repository at this point in the history
  • Loading branch information
sesi200 committed Dec 14, 2023
1 parent b40a4ab commit eca7f4c
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 44 deletions.
2 changes: 0 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 23 additions & 15 deletions rust/ic_principal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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']
68 changes: 48 additions & 20 deletions rust/ic_principal/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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.")]
Expand Down Expand Up @@ -49,19 +53,23 @@ 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
/// to a byte bufer for non-human readable serializer, and a string version for human
/// readable serializers.
///
/// ```
/// # #[cfg(all(feature = "convert", feature = "serde"))] {
/// use ic_principal::Principal;
/// use serde::{Deserialize, Serialize};
/// use std::str::FromStr;
Expand All @@ -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 {
Expand All @@ -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;

Expand All @@ -112,6 +123,7 @@ impl Principal {
}

/// Construct a self-authenticating ID from public key
#[cfg(feature = "self_authenticating")]
pub fn self_authenticating<P: AsRef<[u8]>>(public_key: P) -> Self {
let public_key = public_key.as_ref();
let hash = Sha224::digest(public_key);
Expand All @@ -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<Self> {
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<Self, PrincipalError> {
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<S: AsRef<str>>(text: S) -> Result<Self, PrincipalError> {
// Strategy: Parse very liberally, then pretty-print and compare output
// This is both simpler and yields better error messages
Expand Down Expand Up @@ -206,6 +227,7 @@ impl Principal {
}

/// Convert [`Principal`] to text representation.
#[cfg(feature = "convert")]
pub fn to_text(&self) -> String {
format!("{self}")
}
Expand All @@ -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();
Expand Down Expand Up @@ -244,6 +267,7 @@ impl std::fmt::Display for Principal {
}
}

#[cfg(feature = "convert")]
impl std::str::FromStr for Principal {
type Err = PrincipalError;

Expand All @@ -252,6 +276,7 @@ impl std::str::FromStr for Principal {
}
}

#[cfg(feature = "convert")]
impl TryFrom<&str> for Principal {
type Error = PrincipalError;

Expand All @@ -260,6 +285,7 @@ impl TryFrom<&str> for Principal {
}
}

#[cfg(feature = "convert")]
impl TryFrom<Vec<u8>> for Principal {
type Error = PrincipalError;

Expand All @@ -268,6 +294,7 @@ impl TryFrom<Vec<u8>> for Principal {
}
}

#[cfg(feature = "convert")]
impl TryFrom<&Vec<u8>> for Principal {
type Error = PrincipalError;

Expand All @@ -276,6 +303,7 @@ impl TryFrom<&Vec<u8>> for Principal {
}
}

#[cfg(feature = "convert")]
impl TryFrom<&[u8]> for Principal {
type Error = PrincipalError;

Expand Down
35 changes: 28 additions & 7 deletions rust/ic_principal/tests/principal.rs
Original file line number Diff line number Diff line change
@@ -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]
Expand All @@ -35,6 +40,10 @@ mod convert_from_bytes {
Err(PrincipalError::BytesTooLong())
);
}
}

mod convert_from_bytes {
use super::*;

#[test]
fn from_test_case_ok() {
Expand All @@ -56,6 +65,7 @@ mod convert_from_bytes {
}
}

#[cfg(feature = "convert")]
mod convert_from_text {
use super::*;

Expand Down Expand Up @@ -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(),
Expand All @@ -181,6 +192,7 @@ mod convert_to_bytes {
}
}

#[cfg(feature = "convert")]
mod convert_to_text {
use super::*;

Expand Down Expand Up @@ -211,6 +223,7 @@ mod convert_to_text {
}
}

#[cfg(feature = "serde")]
mod ser_de {
use super::*;
use serde_test::{assert_tokens, Configure, Token};
Expand All @@ -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<Vec<u8>> & TryFrom<&'static Vec<u8>> & TryFrom<&'static [u8]> & AsRef<[u8]>)
impls::impls!(Principal: Display & FromStr & TryFrom<&'static str> & TryFrom<Vec<u8>> & TryFrom<&'static Vec<u8>> & 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,
Expand All @@ -258,17 +279,17 @@ 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:
// 1. Sha224 hash is used
// 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);
}

0 comments on commit eca7f4c

Please sign in to comment.