use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum PrincipalError {
BytesTooLong(),
InvalidBase32(),
TextTooShort(),
TextTooLong(),
CheckSequenceNotMatch(),
AbnormalGrouped(Principal),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Principal {
len: u8,
bytes: [u8; Self::MAX_LENGTH_IN_BYTES],
}
impl Principal {
pub const MAX_LENGTH_IN_BYTES: usize = 29;
pub const MAX_LENGTH_IN_TEXT: usize = 63;
#[allow(dead_code)]
const CRC_LENGTH_IN_BYTES: usize = 4;
const ANONYMOUS_TAG: u8 = 4;
pub const fn management_canister() -> Self {
Self {
len: 0,
bytes: [0; Self::MAX_LENGTH_IN_BYTES],
}
}
pub fn self_authenticating<P: AsRef<[u8]>>(public_key: P) -> Self {
ExternalPrincipal::self_authenticating(public_key).into()
}
pub const fn anonymous() -> Self {
let mut bytes = [0; Self::MAX_LENGTH_IN_BYTES];
bytes[0] = Self::ANONYMOUS_TAG;
Self { len: 1, bytes }
}
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,
}
}
pub const fn from_slice(slice: &[u8]) -> Self {
match Self::from_slice_core(slice) {
Some(principal) => principal,
_ => panic!("slice length exceeds capacity"),
}
}
pub const fn try_from_slice(slice: &[u8]) -> Result<Self, PrincipalError> {
match Self::from_slice_core(slice) {
Some(principal) => Ok(principal),
None => Err(PrincipalError::BytesTooLong()),
}
}
pub fn from_text<S: AsRef<str>>(text: S) -> Result<Self, PrincipalError> {
let input = text.as_ref();
if input.len() > Self::MAX_LENGTH_IN_TEXT {
return Err(PrincipalError::TextTooLong());
}
let mut encoded = [0u8; Self::MAX_LENGTH_IN_TEXT];
let mut len = 0;
for byte in input.bytes().filter(|&byte| byte != b'-') {
encoded[len] = byte.to_ascii_uppercase();
len += 1;
}
let encoding = data_encoding::BASE32_NOPAD;
let size = encoding
.decode_len(len)
.map_err(|_| PrincipalError::InvalidBase32())?;
let mut decoded = [0u8; 39]; let size = encoding
.decode_mut(&encoded[..len], &mut decoded[..size])
.map_err(|_| PrincipalError::InvalidBase32())?;
if size < Self::CRC_LENGTH_IN_BYTES {
return Err(PrincipalError::TextTooShort());
}
let payload = &decoded[Self::CRC_LENGTH_IN_BYTES..size];
if payload.len() > Self::MAX_LENGTH_IN_BYTES {
return Err(PrincipalError::TextTooLong());
}
if crc32fast::hash(payload).to_be_bytes() != decoded[..Self::CRC_LENGTH_IN_BYTES] {
return Err(PrincipalError::CheckSequenceNotMatch());
}
let result = Self::from_slice(payload);
let mut output = [0u8; Self::MAX_LENGTH_IN_TEXT];
if !input.eq_ignore_ascii_case(result.write_text(&mut output)) {
return Err(PrincipalError::AbnormalGrouped(result));
}
Ok(result)
}
pub fn to_text(&self) -> String {
self.write_text(&mut [0u8; Self::MAX_LENGTH_IN_TEXT])
.to_owned()
}
#[inline]
pub const fn as_slice(&self) -> &[u8] {
self.bytes.split_at(self.len as usize).0
}
#[inline]
#[allow(clippy::len_without_is_empty)]
pub const fn len(&self) -> u8 {
self.len
}
#[inline]
pub const fn as_fixed_bytes(&self) -> &[u8; Self::MAX_LENGTH_IN_BYTES] {
&self.bytes
}
}
impl std::fmt::Display for Principal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.write_text(&mut [0u8; Self::MAX_LENGTH_IN_TEXT]))
}
}
impl std::str::FromStr for Principal {
type Err = PrincipalError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Principal::from_text(s)
}
}
impl TryFrom<&str> for Principal {
type Error = PrincipalError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
Principal::from_text(s)
}
}
impl TryFrom<Vec<u8>> for Principal {
type Error = PrincipalError;
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
Self::try_from(bytes.as_slice())
}
}
impl TryFrom<&Vec<u8>> for Principal {
type Error = PrincipalError;
fn try_from(bytes: &Vec<u8>) -> Result<Self, Self::Error> {
Self::try_from(bytes.as_slice())
}
}
impl TryFrom<&[u8]> for Principal {
type Error = PrincipalError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
Self::try_from_slice(bytes)
}
}
impl AsRef<[u8]> for Principal {
fn as_ref(&self) -> &[u8] {
self.as_slice()
}
}
impl serde::Serialize for Principal {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
if serializer.is_human_readable() {
self.to_text().serialize(serializer)
} else {
serializer.serialize_bytes(self.as_slice())
}
}
}
mod deserialize {
use super::Principal;
use std::convert::TryFrom;
pub(super) struct PrincipalVisitor;
impl serde::de::Visitor<'_> for PrincipalVisitor {
type Value = super::Principal;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("bytes or string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Principal::from_text(v).map_err(E::custom)
}
fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Principal::try_from(value).map_err(E::custom)
}
fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Principal::try_from_slice(&v).map_err(E::custom)
}
}
}
impl<'de> serde::Deserialize<'de> for Principal {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Principal, D::Error> {
use serde::de::Error;
if deserializer.is_human_readable() {
deserializer
.deserialize_str(deserialize::PrincipalVisitor)
.map_err(D::Error::custom)
} else {
deserializer
.deserialize_bytes(deserialize::PrincipalVisitor)
.map_err(D::Error::custom)
}
}
}
pub use ic_principal::Principal as ExternalPrincipal;
pub use ic_principal::PrincipalError as ExternalPrincipalError;
impl From<ExternalPrincipal> for Principal {
fn from(value: ExternalPrincipal) -> Self {
Self::from_slice(value.as_slice())
}
}
impl From<Principal> for ExternalPrincipal {
fn from(value: Principal) -> Self {
Self::from_slice(value.as_slice())
}
}
impl std::fmt::Display for PrincipalError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::BytesTooLong() => f.write_str("Bytes is longer than 29 bytes."),
Self::InvalidBase32() => f.write_str("Text must be in valid Base32 encoding."),
Self::TextTooShort() => f.write_str("Text is too short."),
Self::TextTooLong() => f.write_str("Text is too long."),
Self::CheckSequenceNotMatch() => f.write_str(
"CRC32 check sequence doesn't match with calculated from Principal bytes.",
),
Self::AbnormalGrouped(principal) => write!(
f,
"Text should be separated by - (dash) every 5 characters: expected \"{principal}\""
),
}
}
}
impl std::error::Error for PrincipalError {}
impl Principal {
fn write_text<'a>(&self, output: &'a mut [u8; Self::MAX_LENGTH_IN_TEXT]) -> &'a str {
let mut input = [0u8; 33];
input[..4].copy_from_slice(&crc32fast::hash(self.as_slice()).to_be_bytes());
input[4..4 + self.len as usize].copy_from_slice(self.as_slice());
let encoding = data_encoding::BASE32_NOPAD;
let mut encoded = [0u8; 53];
let count = encoding.encode_len(4 + self.len as usize);
encoding.encode_mut(&input[..4 + self.len as usize], &mut encoded[..count]);
let mut written = 0;
for (i, byte) in encoded[..count].iter().enumerate() {
if i > 0 && i % 5 == 0 {
output[written] = b'-';
written += 1;
}
output[written] = byte.to_ascii_lowercase();
written += 1;
}
std::str::from_utf8(&output[..written]).expect("Base32 is ASCII")
}
}
#[cfg(test)]
mod tests;