libpna/chunk/types.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
use std::{
error::Error,
fmt::{self, Debug, Display, Formatter},
};
/// [ChunkType] validation error.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum ChunkTypeError {
/// Contains non-ascii alphabet error.
NonAsciiAlphabetic,
/// The second character is not lowercase error.
NonPrivateChunkType,
/// The third character is not uppercase error.
Reserved,
}
impl Display for ChunkTypeError {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(
match self {
Self::NonAsciiAlphabetic => "All characters are must be ASCII alphabet",
Self::NonPrivateChunkType => "The second character is must be lowercase",
Self::Reserved => "The third character is must be uppercase",
},
f,
)
}
}
impl Error for ChunkTypeError {}
/// A 4-byte chunk type code.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct ChunkType(pub(crate) [u8; 4]);
impl ChunkType {
// -- Critical chunks --
/// Archive header
pub const AHED: ChunkType = ChunkType(*b"AHED");
/// Archive end marker
pub const AEND: ChunkType = ChunkType(*b"AEND");
/// Archive next part marker
pub const ANXT: ChunkType = ChunkType(*b"ANXT");
/// Entry header
pub const FHED: ChunkType = ChunkType(*b"FHED");
/// Password hash string format
pub const PHSF: ChunkType = ChunkType(*b"PHSF");
/// Entry data stream
pub const FDAT: ChunkType = ChunkType(*b"FDAT");
/// Entry data stream end marker
pub const FEND: ChunkType = ChunkType(*b"FEND");
/// Solid mode data header
pub const SHED: ChunkType = ChunkType(*b"SHED");
/// Solid mode data stream
pub const SDAT: ChunkType = ChunkType(*b"SDAT");
/// Solid mode data stream end marker
pub const SEND: ChunkType = ChunkType(*b"SEND");
// -- Auxiliary chunks --
/// Raw file size
#[allow(non_upper_case_globals)]
pub const fSIZ: ChunkType = ChunkType(*b"fSIZ");
/// Creation datetime
#[allow(non_upper_case_globals)]
pub const cTIM: ChunkType = ChunkType(*b"cTIM");
/// Last modified datetime
#[allow(non_upper_case_globals)]
pub const mTIM: ChunkType = ChunkType(*b"mTIM");
/// Last accessed datetime
#[allow(non_upper_case_globals)]
pub const aTIM: ChunkType = ChunkType(*b"aTIM");
/// Entry permissions
#[allow(non_upper_case_globals)]
pub const fPRM: ChunkType = ChunkType(*b"fPRM");
/// Extended attribute
#[allow(non_upper_case_globals)]
pub const xATR: ChunkType = ChunkType(*b"xATR");
/// Returns the length of the chunk type code.
///
/// # Returns
///
/// An integer value representing the length of the chunk type code.
///
/// # Example
///
/// ```
/// use libpna::ChunkType;
///
/// let chunk_type = ChunkType::AHED;
///
/// assert_eq!(chunk_type.len(), 4);
/// ```
#[allow(clippy::len_without_is_empty)]
#[inline]
pub const fn len(&self) -> usize {
self.0.len()
}
/// Creates private [ChunkType].
///
/// # Errors
///
/// This function will return an error in the following cases:
/// - Value contains non-ASCII alphabet characters
/// - The second character is not lowercase
/// - The third character is not uppercase
///
/// # Examples
///
/// ```
/// # use libpna::{ChunkType, ChunkTypeError};
/// assert!(ChunkType::private(*b"myTy").is_ok());
/// assert_eq!(
/// ChunkType::private(*b"zeR\0").unwrap_err(),
/// ChunkTypeError::NonAsciiAlphabetic
/// );
/// assert_eq!(
/// ChunkType::private(*b"pRIv").unwrap_err(),
/// ChunkTypeError::NonPrivateChunkType
/// );
/// assert_eq!(
/// ChunkType::private(*b"rese").unwrap_err(),
/// ChunkTypeError::Reserved
/// );
/// ```
#[inline]
pub const fn private(ty: [u8; 4]) -> Result<Self, ChunkTypeError> {
// NOTE: use a while statement for const context.
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))
}
/// Creates custom [ChunkType] without any check.
///
/// # Panic
/// Printing ChunkType that contains non-utf8 characters will be panicked.
/// ```no_run
/// # use libpna::ChunkType;
///
/// let custom_chunk_type = unsafe { ChunkType::from_unchecked([0xe3, 0x81, 0x82, 0xe3]) };
/// format!("{}", custom_chunk_type);
/// ```
///
/// # Safety
/// Safe when value consists only of ascii alphabetic characters ('a'...'z' and 'A'...'Z').
/// ```
/// # use libpna::ChunkType;
///
/// let custom_chunk_type = unsafe { ChunkType::from_unchecked(*b"myTy") };
/// format!("{}", custom_chunk_type);
/// ```
#[inline]
pub const unsafe fn from_unchecked(ty: [u8; 4]) -> Self {
Self(ty)
}
// -- Chunk type determination --
/// Returns true if the chunk is critical.
#[inline]
pub const fn is_critical(&self) -> bool {
self.0[0] & 32 == 0
}
/// Returns true if the chunk is private.
#[inline]
pub const fn is_private(&self) -> bool {
self.0[1] & 32 != 0
}
/// Checks whether the reserved bit of the chunk name is set.
/// If it is set, the chunk name is invalid.
#[inline]
pub const fn is_set_reserved(&self) -> bool {
self.0[2] & 32 != 0
}
/// Returns true if the chunk is safe to copy if unknown.
#[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 {
Display::fmt(unsafe { std::str::from_utf8_unchecked(&self.0) }, f)
}
}
#[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());
}
}