use crate::aliases;
use core::{fmt, iter, ops, str};
use derive_more::{Deref, DerefMut, From, Index, IndexMut, IntoIterator};
use hex::FromHex;
#[derive(
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Deref,
DerefMut,
From,
Index,
IndexMut,
IntoIterator,
)]
#[cfg_attr(feature = "arbitrary", derive(derive_arbitrary::Arbitrary, proptest_derive::Arbitrary))]
#[cfg_attr(feature = "allocative", derive(allocative::Allocative))]
#[repr(transparent)]
pub struct FixedBytes<const N: usize>(#[into_iterator(owned, ref, ref_mut)] pub [u8; N]);
crate::impl_fb_traits!(FixedBytes<N>, N, const);
impl<const N: usize> Default for FixedBytes<N> {
#[inline]
fn default() -> Self {
Self::ZERO
}
}
impl<const N: usize> Default for &FixedBytes<N> {
#[inline]
fn default() -> Self {
&FixedBytes::ZERO
}
}
impl<const N: usize> From<&[u8; N]> for FixedBytes<N> {
#[inline]
fn from(bytes: &[u8; N]) -> Self {
Self(*bytes)
}
}
impl<const N: usize> From<&mut [u8; N]> for FixedBytes<N> {
#[inline]
fn from(bytes: &mut [u8; N]) -> Self {
Self(*bytes)
}
}
impl<const N: usize> TryFrom<&[u8]> for FixedBytes<N> {
type Error = core::array::TryFromSliceError;
#[inline]
fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
<&Self>::try_from(slice).copied()
}
}
impl<const N: usize> TryFrom<&mut [u8]> for FixedBytes<N> {
type Error = core::array::TryFromSliceError;
#[inline]
fn try_from(slice: &mut [u8]) -> Result<Self, Self::Error> {
Self::try_from(&*slice)
}
}
impl<'a, const N: usize> TryFrom<&'a [u8]> for &'a FixedBytes<N> {
type Error = core::array::TryFromSliceError;
#[inline]
fn try_from(slice: &'a [u8]) -> Result<&'a FixedBytes<N>, Self::Error> {
<&[u8; N]>::try_from(slice).map(|array_ref| unsafe { core::mem::transmute(array_ref) })
}
}
impl<'a, const N: usize> TryFrom<&'a mut [u8]> for &'a mut FixedBytes<N> {
type Error = core::array::TryFromSliceError;
#[inline]
fn try_from(slice: &'a mut [u8]) -> Result<&'a mut FixedBytes<N>, Self::Error> {
<&mut [u8; N]>::try_from(slice).map(|array_ref| unsafe { core::mem::transmute(array_ref) })
}
}
macro_rules! fixed_bytes_uint_conversions {
($($int:ty => $fb:ty),* $(,)?) => {$(
impl From<$int> for $fb {
#[inline]
fn from(value: $int) -> Self {
Self(value.to_be_bytes())
}
}
impl From<$fb> for $int {
#[inline]
fn from(value: $fb) -> Self {
Self::from_be_bytes(value.0)
}
}
const _: () = assert!(<$int>::BITS as usize == <$fb>::len_bytes() * 8);
)*};
}
fixed_bytes_uint_conversions! {
u8 => aliases::B8,
aliases::U8 => aliases::B8,
i8 => aliases::B8,
aliases::I8 => aliases::B8,
u16 => aliases::B16,
aliases::U16 => aliases::B16,
i16 => aliases::B16,
aliases::I16 => aliases::B16,
u32 => aliases::B32,
aliases::U32 => aliases::B32,
i32 => aliases::B32,
aliases::I32 => aliases::B32,
u64 => aliases::B64,
aliases::U64 => aliases::B64,
i64 => aliases::B64,
aliases::I64 => aliases::B64,
u128 => aliases::B128,
aliases::U128 => aliases::B128,
i128 => aliases::B128,
aliases::I128 => aliases::B128,
aliases::U160 => aliases::B160,
aliases::I160 => aliases::B160,
aliases::U256 => aliases::B256,
aliases::I256 => aliases::B256,
aliases::U512 => aliases::B512,
aliases::I512 => aliases::B512,
}
impl<const N: usize> From<FixedBytes<N>> for [u8; N] {
#[inline]
fn from(s: FixedBytes<N>) -> Self {
s.0
}
}
impl<const N: usize> AsRef<[u8; N]> for FixedBytes<N> {
#[inline]
fn as_ref(&self) -> &[u8; N] {
&self.0
}
}
impl<const N: usize> AsMut<[u8; N]> for FixedBytes<N> {
#[inline]
fn as_mut(&mut self) -> &mut [u8; N] {
&mut self.0
}
}
impl<const N: usize> AsRef<[u8]> for FixedBytes<N> {
#[inline]
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl<const N: usize> AsMut<[u8]> for FixedBytes<N> {
#[inline]
fn as_mut(&mut self) -> &mut [u8] {
&mut self.0
}
}
impl<const N: usize> fmt::Debug for FixedBytes<N> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.fmt_hex::<false>(f, true)
}
}
impl<const N: usize> fmt::Display for FixedBytes<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if N <= 4 || !f.alternate() {
return self.fmt_hex::<false>(f, true);
}
const SEP_LEN: usize = '…'.len_utf8();
let mut buf = [0; 2 + 4 + SEP_LEN + 4];
buf[0] = b'0';
buf[1] = b'x';
hex::encode_to_slice(&self.0[0..2], &mut buf[2..6]).unwrap();
'…'.encode_utf8(&mut buf[6..]);
hex::encode_to_slice(&self.0[N - 2..N], &mut buf[6 + SEP_LEN..]).unwrap();
f.write_str(unsafe { str::from_utf8_unchecked(&buf) })
}
}
impl<const N: usize> fmt::LowerHex for FixedBytes<N> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.fmt_hex::<false>(f, f.alternate())
}
}
impl<const N: usize> fmt::UpperHex for FixedBytes<N> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.fmt_hex::<true>(f, f.alternate())
}
}
impl<const N: usize> ops::BitAnd for FixedBytes<N> {
type Output = Self;
#[inline]
fn bitand(mut self, rhs: Self) -> Self::Output {
self &= rhs;
self
}
}
impl<const N: usize> ops::BitAndAssign for FixedBytes<N> {
#[inline]
fn bitand_assign(&mut self, rhs: Self) {
iter::zip(self, &rhs).for_each(|(a, b)| *a &= *b);
}
}
impl<const N: usize> ops::BitOr for FixedBytes<N> {
type Output = Self;
#[inline]
fn bitor(mut self, rhs: Self) -> Self::Output {
self |= rhs;
self
}
}
impl<const N: usize> ops::BitOrAssign for FixedBytes<N> {
#[inline]
fn bitor_assign(&mut self, rhs: Self) {
iter::zip(self, &rhs).for_each(|(a, b)| *a |= *b);
}
}
impl<const N: usize> ops::BitXor for FixedBytes<N> {
type Output = Self;
#[inline]
fn bitxor(mut self, rhs: Self) -> Self::Output {
self ^= rhs;
self
}
}
impl<const N: usize> ops::BitXorAssign for FixedBytes<N> {
#[inline]
fn bitxor_assign(&mut self, rhs: Self) {
iter::zip(self, &rhs).for_each(|(a, b)| *a ^= *b);
}
}
impl<const N: usize> ops::Not for FixedBytes<N> {
type Output = Self;
#[inline]
fn not(mut self) -> Self::Output {
self.iter_mut().for_each(|byte| *byte = !*byte);
self
}
}
impl<const N: usize> str::FromStr for FixedBytes<N> {
type Err = hex::FromHexError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_hex(s)
}
}
#[cfg(feature = "rand")]
impl<const N: usize> rand::distributions::Distribution<FixedBytes<N>>
for rand::distributions::Standard
{
#[inline]
fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> FixedBytes<N> {
FixedBytes::random_with(rng)
}
}
impl<const N: usize> FixedBytes<N> {
pub const ZERO: Self = Self([0u8; N]);
#[inline]
pub const fn new(bytes: [u8; N]) -> Self {
Self(bytes)
}
#[inline]
pub const fn with_last_byte(x: u8) -> Self {
let mut bytes = [0u8; N];
if N > 0 {
bytes[N - 1] = x;
}
Self(bytes)
}
#[inline]
pub const fn repeat_byte(byte: u8) -> Self {
Self([byte; N])
}
#[inline(always)]
pub const fn len_bytes() -> usize {
N
}
#[cfg(feature = "getrandom")]
#[inline]
#[track_caller]
pub fn random() -> Self {
Self::try_random().unwrap()
}
#[cfg(feature = "getrandom")]
#[inline]
pub fn try_random() -> Result<Self, getrandom::Error> {
let mut bytes = Self::ZERO;
bytes.try_randomize()?;
Ok(bytes)
}
#[cfg(feature = "rand")]
#[inline]
#[doc(alias = "random_using")]
pub fn random_with<R: rand::Rng + ?Sized>(rng: &mut R) -> Self {
let mut bytes = Self::ZERO;
bytes.randomize_with(rng);
bytes
}
#[cfg(feature = "getrandom")]
#[inline]
#[track_caller]
pub fn randomize(&mut self) {
self.try_randomize().unwrap()
}
#[inline]
#[cfg(feature = "getrandom")]
pub fn try_randomize(&mut self) -> Result<(), getrandom::Error> {
getrandom::getrandom(&mut self.0)
}
#[cfg(feature = "rand")]
#[doc(alias = "randomize_using")]
pub fn randomize_with<R: rand::Rng + ?Sized>(&mut self, rng: &mut R) {
rng.fill_bytes(&mut self.0);
}
pub const fn concat_const<const M: usize, const Z: usize>(
self,
other: FixedBytes<M>,
) -> FixedBytes<Z> {
assert!(N + M == Z, "Output size `Z` must equal the sum of the input sizes `N` and `M`");
let mut result = [0u8; Z];
let mut i = 0;
while i < Z {
result[i] = if i >= N { other.0[i - N] } else { self.0[i] };
i += 1;
}
FixedBytes(result)
}
#[inline]
#[track_caller]
pub fn from_slice(src: &[u8]) -> Self {
match Self::try_from(src) {
Ok(x) => x,
Err(_) => panic!("cannot convert a slice of length {} to FixedBytes<{N}>", src.len()),
}
}
#[inline]
#[track_caller]
pub fn left_padding_from(value: &[u8]) -> Self {
let len = value.len();
assert!(len <= N, "slice is too large. Expected <={N} bytes, got {len}");
let mut bytes = Self::ZERO;
bytes[N - len..].copy_from_slice(value);
bytes
}
#[inline]
#[track_caller]
pub fn right_padding_from(value: &[u8]) -> Self {
let len = value.len();
assert!(len <= N, "slice is too large. Expected <={N} bytes, got {len}");
let mut bytes = Self::ZERO;
bytes[..len].copy_from_slice(value);
bytes
}
#[inline]
pub const fn as_slice(&self) -> &[u8] {
&self.0
}
#[inline]
pub fn as_mut_slice(&mut self) -> &mut [u8] {
&mut self.0
}
#[inline]
pub fn covers(&self, other: &Self) -> bool {
(*self & *other) == *other
}
pub const fn const_covers(self, other: Self) -> bool {
other.const_eq(&self.bit_and(other))
}
pub const fn const_eq(&self, other: &Self) -> bool {
let mut i = 0;
while i < N {
if self.0[i] != other.0[i] {
return false;
}
i += 1;
}
true
}
#[inline]
pub fn is_zero(&self) -> bool {
*self == Self::ZERO
}
#[inline]
pub const fn const_is_zero(&self) -> bool {
self.const_eq(&Self::ZERO)
}
pub const fn bit_and(self, rhs: Self) -> Self {
let mut ret = Self::ZERO;
let mut i = 0;
while i < N {
ret.0[i] = self.0[i] & rhs.0[i];
i += 1;
}
ret
}
pub const fn bit_or(self, rhs: Self) -> Self {
let mut ret = Self::ZERO;
let mut i = 0;
while i < N {
ret.0[i] = self.0[i] | rhs.0[i];
i += 1;
}
ret
}
pub const fn bit_xor(self, rhs: Self) -> Self {
let mut ret = Self::ZERO;
let mut i = 0;
while i < N {
ret.0[i] = self.0[i] ^ rhs.0[i];
i += 1;
}
ret
}
fn fmt_hex<const UPPER: bool>(&self, f: &mut fmt::Formatter<'_>, prefix: bool) -> fmt::Result {
let mut buf = hex::Buffer::<N, true>::new();
let s = if UPPER { buf.format_upper(self) } else { buf.format(self) };
f.write_str(unsafe { s.get_unchecked((!prefix as usize) * 2..) })
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! test_fmt {
($($fmt:literal, $hex:literal => $expected:literal;)+) => {$(
assert_eq!(
format!($fmt, fixed_bytes!($hex)),
$expected
);
)+};
}
#[test]
fn concat_const() {
const A: FixedBytes<2> = fixed_bytes!("0123");
const B: FixedBytes<2> = fixed_bytes!("4567");
const EXPECTED: FixedBytes<4> = fixed_bytes!("01234567");
const ACTUAL: FixedBytes<4> = A.concat_const(B);
assert_eq!(ACTUAL, EXPECTED);
}
#[test]
fn display() {
test_fmt! {
"{}", "0123456789abcdef" => "0x0123456789abcdef";
"{:#}", "0123" => "0x0123";
"{:#}", "01234567" => "0x01234567";
"{:#}", "0123456789" => "0x0123…6789";
}
}
#[test]
fn debug() {
test_fmt! {
"{:?}", "0123456789abcdef" => "0x0123456789abcdef";
"{:#?}", "0123456789abcdef" => "0x0123456789abcdef";
}
}
#[test]
fn lower_hex() {
test_fmt! {
"{:x}", "0123456789abcdef" => "0123456789abcdef";
"{:#x}", "0123456789abcdef" => "0x0123456789abcdef";
}
}
#[test]
fn upper_hex() {
test_fmt! {
"{:X}", "0123456789abcdef" => "0123456789ABCDEF";
"{:#X}", "0123456789abcdef" => "0x0123456789ABCDEF";
}
}
#[test]
fn left_padding_from() {
assert_eq!(FixedBytes::<4>::left_padding_from(&[0x01, 0x23]), fixed_bytes!("00000123"));
assert_eq!(
FixedBytes::<4>::left_padding_from(&[0x01, 0x23, 0x45, 0x67]),
fixed_bytes!("01234567")
);
}
#[test]
#[should_panic(expected = "slice is too large. Expected <=4 bytes, got 5")]
fn left_padding_from_too_large() {
FixedBytes::<4>::left_padding_from(&[0x01, 0x23, 0x45, 0x67, 0x89]);
}
#[test]
fn right_padding_from() {
assert_eq!(FixedBytes::<4>::right_padding_from(&[0x01, 0x23]), fixed_bytes!("01230000"));
assert_eq!(
FixedBytes::<4>::right_padding_from(&[0x01, 0x23, 0x45, 0x67]),
fixed_bytes!("01234567")
);
}
#[test]
#[should_panic(expected = "slice is too large. Expected <=4 bytes, got 5")]
fn right_padding_from_too_large() {
FixedBytes::<4>::right_padding_from(&[0x01, 0x23, 0x45, 0x67, 0x89]);
}
}