use alloc::string::{String, ToString};
use core::cmp::Ordering;
use core::convert::{TryFrom, TryInto};
use core::fmt;
use core::hash::{Hash, Hasher};
use core::num::NonZeroU128;
use core::str::FromStr;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Copy, Clone, Eq, Deserialize, Serialize, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ID(NonZeroU128);
impl ID {
pub const MAX_SIZE: usize = 16;
#[inline]
pub fn size(&self) -> usize {
16 - (if cfg!(target_endian = "little") {
self.0.leading_zeros()
} else {
self.0.trailing_zeros()
} / 8) as usize
}
#[inline]
pub fn as_slice(&self) -> &[u8] {
let slice = unsafe { core::mem::transmute::<&NonZeroU128, &[u8; 16]>(&self.0) };
&slice[..self.size()]
}
}
impl From<Uuid> for ID {
#[inline]
fn from(uuid: Uuid) -> Self {
uuid.as_bytes()
.try_into()
.expect("Uuids should always be non-null")
}
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct SizeError(pub usize);
impl fmt::Display for SizeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Maximum ID size ({} bytes) exceeded: {}",
ID::MAX_SIZE,
self.0
)
}
}
#[cfg(feature = "std")]
impl std::error::Error for SizeError {}
macro_rules! impl_from_sized_slice_for_id {
($N: expr) => {
impl TryFrom<&[u8; $N]> for ID {
type Error = SizeError;
fn try_from(value: &[u8; $N]) -> Result<Self, Self::Error> {
let mut id = 0u128;
unsafe {
core::mem::transmute::<&mut u128, &mut [u8; 16]>(&mut id)[..$N]
.copy_from_slice(value);
}
match NonZeroU128::new(id) {
Some(id) => Ok(Self(id)),
None => Err(SizeError(0)),
}
}
}
impl TryFrom<[u8; $N]> for ID {
type Error = SizeError;
fn try_from(id: [u8; $N]) -> Result<Self, Self::Error> {
(&id).try_into()
}
}
};
}
impl_from_sized_slice_for_id!(1);
impl_from_sized_slice_for_id!(2);
impl_from_sized_slice_for_id!(3);
impl_from_sized_slice_for_id!(4);
impl_from_sized_slice_for_id!(5);
impl_from_sized_slice_for_id!(6);
impl_from_sized_slice_for_id!(7);
impl_from_sized_slice_for_id!(8);
impl_from_sized_slice_for_id!(9);
impl_from_sized_slice_for_id!(10);
impl_from_sized_slice_for_id!(11);
impl_from_sized_slice_for_id!(12);
impl_from_sized_slice_for_id!(13);
impl_from_sized_slice_for_id!(14);
impl_from_sized_slice_for_id!(15);
impl_from_sized_slice_for_id!(16);
impl TryFrom<&[u8]> for ID {
type Error = SizeError;
fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
let size = slice.len();
if size > Self::MAX_SIZE {
return Err(SizeError(size));
}
let mut id = 0u128;
unsafe {
core::mem::transmute::<&mut u128, &mut [u8; 16]>(&mut id)[..size]
.copy_from_slice(slice);
match NonZeroU128::new(id) {
Some(id) => Ok(Self(id)),
None => Err(SizeError(0)),
}
}
}
}
impl PartialOrd for ID {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ID {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
if cfg!(target_endian = "little") {
self.0.cmp(&other.0)
} else {
u128::from_le(self.0.get()).cmp(&u128::from_le(other.0.get()))
}
}
}
#[allow(clippy::derive_hash_xor_eq)]
impl Hash for ID {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_slice().hash(state);
}
}
impl fmt::Debug for ID {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", hex::encode_upper(self.as_slice()))
}
}
impl fmt::Display for ID {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self, f)
}
}
impl FromStr for ID {
type Err = ParseIDError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
hex::decode(s)
.map_err(|e| ParseIDError {
cause: e.to_string(),
})
.and_then(|bytes| {
ID::try_from(bytes.as_slice()).map_err(|e| ParseIDError {
cause: e.to_string(),
})
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ParseIDError {
pub cause: String,
}