use anyhow::{Context, Error, Result};
use bc_envelope::prelude::*;
use hex::FromHexError;
use std::{
array::TryFromSliceError,
fmt,
ops::{
Index, IndexMut, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive,
},
};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum HexParseError {
SliceInvalid { expected: usize, actual: usize },
HexInvalid(FromHexError),
}
impl fmt::Display for HexParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HexParseError::SliceInvalid { expected, actual } => {
write!(f, "Expected {} bytes, got {}", expected, actual)
}
HexParseError::HexInvalid(e) => write!(f, "Not a valid hex string: {}", e),
}
}
}
impl std::error::Error for HexParseError {}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Blob<const N: usize>([u8; N]);
impl<const N: usize> Blob<N> {
pub fn new(data: [u8; N]) -> Self {
Self(data)
}
pub fn len(&self) -> usize {
N
}
pub fn is_empty(&self) -> bool {
N == 0
}
pub fn to_vec(&self) -> Vec<u8> {
self.0.to_vec()
}
pub fn as_slice(&self) -> &[u8] {
&self.0
}
pub fn as_bytes(&self) -> &[u8; N] {
&self.0
}
pub fn from_slice(data: &[u8]) -> Result<Self, TryFromSliceError> {
Ok(Self(<[u8; N]>::try_from(data)?))
}
pub fn from_vec(data: Vec<u8>) -> Result<Self, TryFromSliceError> {
Self::from_slice(&data)
}
pub fn from_hex(hex: &str) -> Result<Self, HexParseError> {
let data = hex::decode(hex).map_err(crate::HexParseError::HexInvalid)?;
Self::from_vec(data).map_err(|_| crate::HexParseError::SliceInvalid {
expected: N * 2,
actual: hex.len(),
})
}
}
impl<const N: usize> Default for Blob<N> {
fn default() -> Self {
Self([0u8; N])
}
}
impl<const N: usize> Index<usize> for Blob<N> {
type Output = u8;
fn index(&self, index: usize) -> &Self::Output {
&self.0[index]
}
}
impl<const N: usize> IndexMut<usize> for Blob<N> {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.0[index]
}
}
impl<const N: usize> Index<Range<usize>> for Blob<N> {
type Output = [u8];
fn index(&self, range: Range<usize>) -> &Self::Output {
&self.0[range]
}
}
impl<const N: usize> Index<RangeTo<usize>> for Blob<N> {
type Output = [u8];
fn index(&self, range: RangeTo<usize>) -> &Self::Output {
&self.0[range]
}
}
impl<const N: usize> Index<RangeFrom<usize>> for Blob<N> {
type Output = [u8];
fn index(&self, range: RangeFrom<usize>) -> &Self::Output {
&self.0[range]
}
}
impl<const N: usize> Index<RangeFull> for Blob<N> {
type Output = [u8];
fn index(&self, range: RangeFull) -> &Self::Output {
&self.0[range]
}
}
impl<const N: usize> Index<RangeInclusive<usize>> for Blob<N> {
type Output = [u8];
fn index(&self, range: RangeInclusive<usize>) -> &Self::Output {
&self.0[range]
}
}
impl<const N: usize> Index<RangeToInclusive<usize>> for Blob<N> {
type Output = [u8];
fn index(&self, range: RangeToInclusive<usize>) -> &Self::Output {
&self.0[range]
}
}
impl<const N: usize> From<Blob<N>> for [u8; N] {
fn from(blob: Blob<N>) -> Self {
blob.0
}
}
impl<const N: usize> AsRef<[u8]> for Blob<N> {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl<const N: usize> fmt::Debug for Blob<N> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Blob<{}>({})", N, hex::encode(self.0))
}
}
impl<const N: usize> fmt::Display for Blob<N> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", hex::encode(self.0))
}
}
impl<const N: usize> From<Blob<N>> for Vec<u8> {
fn from(blob: Blob<N>) -> Vec<u8> {
blob.to_vec()
}
}
impl<const N: usize> From<&Blob<N>> for Vec<u8> {
fn from(blob: &Blob<N>) -> Vec<u8> {
blob.to_vec()
}
}
impl<const N: usize> From<Vec<u8>> for Blob<N> {
fn from(data: Vec<u8>) -> Self {
Self::from_vec(data).unwrap()
}
}
impl<const N: usize> From<&[u8]> for Blob<N> {
fn from(data: &[u8]) -> Self {
Self::from_vec(data.to_vec()).unwrap()
}
}
impl<const N: usize> From<&[u8; N]> for Blob<N> {
fn from(data: &[u8; N]) -> Self {
Self::from_vec(data.to_vec()).unwrap()
}
}
pub type Blob20 = Blob<20>;
impl Copy for Blob20 {}
pub type Blob32 = Blob<32>;
impl Copy for Blob32 {}
pub type Blob64 = Blob<64>;
impl<const N: usize> From<Blob<N>> for CBOR {
fn from(data: Blob<N>) -> Self {
CBOR::to_byte_string(data)
}
}
impl<const N: usize> From<&Blob<N>> for CBOR {
fn from(data: &Blob<N>) -> Self {
CBOR::to_byte_string(data)
}
}
impl<const N: usize> TryFrom<CBOR> for Blob<N> {
type Error = dcbor::Error;
fn try_from(cbor: CBOR) -> dcbor::Result<Self> {
let bytes = cbor.try_into_byte_string()?;
let blob =
Blob::from_slice(&bytes).map_err(|e| dcbor::Error::msg(format!("Blob: {e}")))?;
Ok(blob)
}
}
impl<const N: usize> From<Blob<N>> for Envelope {
fn from(value: Blob<N>) -> Self {
Envelope::new(CBOR::from(value))
}
}
impl<const N: usize> TryFrom<Envelope> for Blob<N> {
type Error = Error;
fn try_from(envelope: Envelope) -> Result<Self> {
envelope.extract_subject().context("Blob")
}
}
#[cfg(test)]
mod tests {
use crate::{test_cbor_roundtrip, test_envelope_roundtrip};
use super::{Blob, Blob32};
impl<const N: usize> crate::RandomInstance for Blob<N> {
fn random() -> Self {
let mut rng = bc_rand::thread_rng();
Self(bc_rand::rng_random_array(&mut rng))
}
}
test_cbor_roundtrip!(Blob32);
test_envelope_roundtrip!(Blob32);
}