use std::{
error::Error,
fmt::{self, Debug, Display, Formatter},
io,
};
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum ChunkTypeError {
NonAsciiAlphabetic,
NonPrivateChunkType,
Reserved,
}
impl Display for ChunkTypeError {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(
match self {
Self::NonAsciiAlphabetic => "All characters must be ASCII alphabetic",
Self::NonPrivateChunkType => "The second character must be lowercase",
Self::Reserved => "The third character must be uppercase",
},
f,
)
}
}
impl Error for ChunkTypeError {}
impl From<ChunkTypeError> for io::Error {
#[inline]
fn from(e: ChunkTypeError) -> Self {
io::Error::new(io::ErrorKind::InvalidData, e)
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct ChunkType([u8; 4]);
impl ChunkType {
pub const AHED: ChunkType = ChunkType(*b"AHED");
pub const AEND: ChunkType = ChunkType(*b"AEND");
pub const ANXT: ChunkType = ChunkType(*b"ANXT");
pub const FHED: ChunkType = ChunkType(*b"FHED");
pub const PHSF: ChunkType = ChunkType(*b"PHSF");
pub const FDAT: ChunkType = ChunkType(*b"FDAT");
pub const FEND: ChunkType = ChunkType(*b"FEND");
pub const SHED: ChunkType = ChunkType(*b"SHED");
pub const SDAT: ChunkType = ChunkType(*b"SDAT");
pub const SEND: ChunkType = ChunkType(*b"SEND");
#[allow(non_upper_case_globals)]
pub const fSIZ: ChunkType = ChunkType(*b"fSIZ");
#[allow(non_upper_case_globals)]
pub const cTIM: ChunkType = ChunkType(*b"cTIM");
#[allow(non_upper_case_globals)]
pub const mTIM: ChunkType = ChunkType(*b"mTIM");
#[allow(non_upper_case_globals)]
pub const aTIM: ChunkType = ChunkType(*b"aTIM");
#[allow(non_upper_case_globals)]
pub const cTNS: ChunkType = ChunkType(*b"cTNS");
#[allow(non_upper_case_globals)]
pub const mTNS: ChunkType = ChunkType(*b"mTNS");
#[allow(non_upper_case_globals)]
pub const aTNS: ChunkType = ChunkType(*b"aTNS");
#[allow(non_upper_case_globals)]
pub const fPRM: ChunkType = ChunkType(*b"fPRM");
#[allow(non_upper_case_globals)]
pub const xATR: ChunkType = ChunkType(*b"xATR");
#[allow(clippy::len_without_is_empty)]
#[inline]
pub const fn len(&self) -> usize {
self.0.len()
}
#[inline]
pub(crate) const fn as_bytes(&self) -> &[u8; 4] {
&self.0
}
#[inline]
pub(crate) const fn new(ty: [u8; 4]) -> Result<Self, ChunkTypeError> {
let mut idx = 0;
while idx < ty.len() {
if !ty[idx].is_ascii_alphabetic() {
return Err(ChunkTypeError::NonAsciiAlphabetic);
}
idx += 1;
}
Ok(Self(ty))
}
#[inline]
pub const fn private(ty: [u8; 4]) -> Result<Self, ChunkTypeError> {
let mut idx = 0;
while idx < ty.len() {
if !ty[idx].is_ascii_alphabetic() {
return Err(ChunkTypeError::NonAsciiAlphabetic);
}
idx += 1;
}
if !ty[1].is_ascii_lowercase() {
return Err(ChunkTypeError::NonPrivateChunkType);
}
if !ty[2].is_ascii_uppercase() {
return Err(ChunkTypeError::Reserved);
}
Ok(Self(ty))
}
#[inline]
pub const unsafe fn from_unchecked(ty: [u8; 4]) -> Self {
Self(ty)
}
#[inline]
pub const fn is_critical(&self) -> bool {
self.0[0] & 32 == 0
}
#[inline]
pub const fn is_private(&self) -> bool {
self.0[1] & 32 != 0
}
#[inline]
pub const fn is_set_reserved(&self) -> bool {
self.0[2] & 32 != 0
}
#[inline]
pub const fn is_safe_to_copy(&self) -> bool {
self.0[3] & 32 != 0
}
}
impl Debug for ChunkType {
#[inline]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
struct DebugType([u8; 4]);
impl Debug for DebugType {
#[inline]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
for &c in &self.0[..] {
write!(f, "{}", char::from(c).escape_debug())?;
}
Ok(())
}
}
f.debug_struct("ChunkType")
.field("type", &DebugType(self.0))
.field("critical", &self.is_critical())
.field("private", &self.is_private())
.field("reserved", &self.is_set_reserved())
.field("safe_to_copy", &self.is_safe_to_copy())
.finish()
}
}
impl Display for ChunkType {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match std::str::from_utf8(&self.0) {
Ok(s) => Display::fmt(s, f),
Err(_) => write!(f, "{:02x?}", self.0),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
use wasm_bindgen_test::wasm_bindgen_test as test;
#[test]
fn to_string() {
assert_eq!("AHED", ChunkType::AHED.to_string());
}
#[test]
fn is_critical() {
assert!(ChunkType::AHED.is_critical());
assert!(!ChunkType::cTIM.is_critical());
}
#[test]
fn is_private() {
assert!(!ChunkType::AHED.is_private());
assert!(ChunkType::private(*b"myTy").unwrap().is_private());
}
#[test]
fn is_set_reserved() {
assert!(!ChunkType::AHED.is_set_reserved());
}
#[test]
fn is_safe_to_copy() {
assert!(!ChunkType::AHED.is_safe_to_copy());
}
#[test]
fn new_rejects_non_ascii() {
assert_eq!(
ChunkType::new(*b"AB\x00D"),
Err(ChunkTypeError::NonAsciiAlphabetic)
);
assert_eq!(
ChunkType::new(*b"AB1D"),
Err(ChunkTypeError::NonAsciiAlphabetic)
);
}
#[test]
fn new_accepts_valid() {
assert!(ChunkType::new(*b"AHED").is_ok());
assert!(ChunkType::new(*b"myTy").is_ok());
}
}