use std::fmt;
use std::str::FromStr;
use minicbor::decode::{Decoder, Error as DecodeError};
use minicbor::encode::{Encoder, Error as EncodeError, Write};
use minicbor::{Decode, Encode};
use crate::hex;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ParseIdError {
what: &'static str,
digits: usize,
}
impl fmt::Display for ParseIdError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "a {} is {} lowercase hexadecimal digits", self.what, self.digits)
}
}
impl std::error::Error for ParseIdError {}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct StreamId([u8; 16]);
impl StreamId {
pub const fn from_bytes(bytes: [u8; 16]) -> Self {
Self(bytes)
}
pub const fn as_bytes(&self) -> &[u8; 16] {
&self.0
}
}
impl fmt::Display for StreamId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
hex::write(&self.0, f)
}
}
impl fmt::Debug for StreamId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "StreamId({self})")
}
}
impl FromStr for StreamId {
type Err = ParseIdError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
hex::decode::<16>(s).map(Self).ok_or(ParseIdError { what: "stream identity", digits: 32 })
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct Hash([u8; 32]);
impl Hash {
pub const fn from_bytes(bytes: [u8; 32]) -> Self {
Self(bytes)
}
pub const fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
pub fn short(&self) -> String {
hex::encode(&self.0[..4])
}
}
impl fmt::Display for Hash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
hex::write(&self.0, f)
}
}
impl fmt::Debug for Hash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Hash({self})")
}
}
impl FromStr for Hash {
type Err = ParseIdError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
hex::decode::<32>(s).map(Self).ok_or(ParseIdError { what: "hash", digits: 64 })
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Encode, Decode)]
#[cbor(transparent)]
pub struct Epoch(pub u32);
impl fmt::Display for Epoch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
macro_rules! fixed_bytes {
($type:ident, $len:literal) => {
impl<C> Encode<C> for $type {
fn encode<W: Write>(&self, e: &mut Encoder<W>, _: &mut C) -> Result<(), EncodeError<W::Error>> {
e.bytes(&self.0)?.ok()
}
}
impl<'b, C> Decode<'b, C> for $type {
fn decode(d: &mut Decoder<'b>, _: &mut C) -> Result<Self, DecodeError> {
let position = d.position();
let bytes = d.bytes()?;
<[u8; $len]>::try_from(bytes).map(Self).map_err(|_| {
DecodeError::message(concat!("expected exactly ", stringify!($len), " bytes")).at(position)
})
}
}
};
}
fixed_bytes!(StreamId, 16);
fixed_bytes!(Hash, 32);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn identities_and_hashes_round_trip_through_text() {
let id = StreamId::from_bytes([0xab; 16]);
assert_eq!(id.to_string(), "ab".repeat(16));
assert_eq!(id.to_string().parse::<StreamId>(), Ok(id));
let hash = Hash::from_bytes([0x01; 32]);
assert_eq!(hash.to_string().parse::<Hash>(), Ok(hash));
assert_eq!(hash.short(), "01010101");
}
#[test]
fn a_byte_string_of_the_wrong_length_is_refused() {
let bytes = |len: usize| {
let mut e = Encoder::new(Vec::new());
e.bytes(&vec![0u8; len]).unwrap();
e.into_writer()
};
assert!(minicbor::decode::<StreamId>(&bytes(15)).is_err());
assert!(minicbor::decode::<StreamId>(&bytes(16)).is_ok());
assert!(minicbor::decode::<Hash>(&bytes(33)).is_err());
assert!(minicbor::decode::<Hash>(&bytes(32)).is_ok());
}
#[test]
fn an_array_of_numbers_is_not_a_byte_string() {
let array = minicbor::to_vec([0u8; 16]).unwrap();
assert!(minicbor::decode::<StreamId>(&array).is_err());
}
}