diff --git a/Cargo.toml b/Cargo.toml index 4a4c426..f44e620 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,9 @@ exclude = ["src/tests.rs", "tests/*"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bitflags = "2.6" -deflate = "1.0" -image = { version = "0.25.4", default-features = false, features = ["png"] } -inflate = "0.4.5" -thiserror = "1.0" +bitflags = "2" +crc32fast = "1" +deflate = "1" +image = { version = "0.25", default-features = false, features = ["png"] } +inflate = "0.4" +thiserror = "2" diff --git a/src/chunk.rs b/src/chunk.rs index 044c550..039c82f 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -1,116 +1,116 @@ -use crate::{crc, error}; -use std::io::prelude::*; - -#[derive(Clone, PartialEq, Eq, Debug, Default)] -pub struct RawGenericChunk { - pub data_length: [u8; 4], - pub chunk_type: [u8; 4], - pub data: Vec, - pub crc: [u8; 4], -} - -impl RawGenericChunk { - pub fn load(reader: &mut R) -> Result { - let mut chunk_bytes = Vec::new(); - reader.read_to_end(&mut chunk_bytes)?; - - // 4 bytes for the length. - // 4 bytes for the type. - // Data can be 0 bytes. - // 4 bytes for the CRC. - - // Total minimum size for an undetermined PNG chunk: 12 bytes. - let chunk_length = chunk_bytes.len(); - - if chunk_length < 12 { - return Err(error::DmiError::Generic(format!("Failed to load Chunk. Supplied reader contained size of {} bytes, lower than the required 12.", chunk_length))); - }; - - let data_length = [ - chunk_bytes[0], - chunk_bytes[1], - chunk_bytes[2], - chunk_bytes[3], - ]; - - let chunk_type = [ - chunk_bytes[4], - chunk_bytes[5], - chunk_bytes[6], - chunk_bytes[7], - ]; - - // The chunk type is made of four ascii characters. The valid ranges are A-Z and a-z. - if !chunk_type - .iter() - .all(|c| (b'A' <= *c && *c <= b'Z') || (b'a' <= *c && *c <= b'z')) - { - return Err(error::DmiError::Generic(format!( - "Failed to load Chunk. Type contained unlawful characters: {:#?}", - chunk_type - ))); - }; - - let data: Vec = chunk_bytes[8..(chunk_length - 4)].to_vec(); - - let crc = [ - chunk_bytes[chunk_length - 4], - chunk_bytes[chunk_length - 3], - chunk_bytes[chunk_length - 2], - chunk_bytes[chunk_length - 1], - ]; - - let recalculated_crc = crc::calculate_crc(chunk_type.iter().chain(data.iter())); - if u32::from_be_bytes(crc) != recalculated_crc { - let chunk_name = String::from_utf8(chunk_type.to_vec())?; - return Err(error::DmiError::Generic(format!("Failed to load Chunk of type {}. Supplied CRC invalid: {:#?}. Its value ({}) does not match the recalculated one ({}).", chunk_name, crc, u32::from_be_bytes(crc), recalculated_crc))); - } - - Ok(RawGenericChunk { - data_length, - chunk_type, - data, - crc, - }) - } - - pub fn save(&self, writter: &mut W) -> Result { - let bytes_written = writter.write(&self.data_length)?; - let mut total_bytes_written = bytes_written; - if bytes_written < self.data_length.len() { - return Err(error::DmiError::Generic(format!( - "Failed to save Chunk. Buffer unable to hold the data, only {} bytes written.", - total_bytes_written - ))); - }; - - let bytes_written = writter.write(&self.chunk_type)?; - total_bytes_written += bytes_written; - if bytes_written < self.chunk_type.len() { - return Err(error::DmiError::Generic(format!( - "Failed to save Chunk. Buffer unable to hold the data, only {} bytes written.", - total_bytes_written - ))); - }; - - let bytes_written = writter.write(&self.data)?; - total_bytes_written += bytes_written; - if bytes_written < self.data.len() { - return Err(error::DmiError::Generic(format!( - "Failed to save Chunk. Buffer unable to hold the data, only {} bytes written.", - total_bytes_written - ))); - }; - - let bytes_written = writter.write(&self.crc)?; - total_bytes_written += bytes_written; - if bytes_written < self.crc.len() { - return Err(error::DmiError::Generic(format!( - "Failed to save Chunk. Buffer unable to hold the data, only {} bytes written.", - total_bytes_written - ))); - }; - - Ok(total_bytes_written) - } -} +use crate::{crc, error}; +use std::io::prelude::*; + +#[derive(Clone, PartialEq, Eq, Debug, Default)] +pub struct RawGenericChunk { + pub data_length: [u8; 4], + pub chunk_type: [u8; 4], + pub data: Vec, + pub crc: [u8; 4], +} + +impl RawGenericChunk { + pub fn load(reader: &mut R) -> Result { + let mut chunk_bytes = Vec::new(); + reader.read_to_end(&mut chunk_bytes)?; + + // 4 bytes for the length. + // 4 bytes for the type. + // Data can be 0 bytes. + // 4 bytes for the CRC. + + // Total minimum size for an undetermined PNG chunk: 12 bytes. + let chunk_length = chunk_bytes.len(); + + if chunk_length < 12 { + return Err(error::DmiError::Generic(format!("Failed to load Chunk. Supplied reader contained size of {} bytes, lower than the required 12.", chunk_length))); + }; + + let data_length = [ + chunk_bytes[0], + chunk_bytes[1], + chunk_bytes[2], + chunk_bytes[3], + ]; + + let chunk_type = [ + chunk_bytes[4], + chunk_bytes[5], + chunk_bytes[6], + chunk_bytes[7], + ]; + + // The chunk type is made of four ascii characters. The valid ranges are A-Z and a-z. + if !chunk_type + .iter() + .all(|c| (b'A' <= *c && *c <= b'Z') || (b'a' <= *c && *c <= b'z')) + { + return Err(error::DmiError::Generic(format!( + "Failed to load Chunk. Type contained unlawful characters: {:#?}", + chunk_type + ))); + }; + + let data: Vec = chunk_bytes[8..(chunk_length - 4)].to_vec(); + + let crc = [ + chunk_bytes[chunk_length - 4], + chunk_bytes[chunk_length - 3], + chunk_bytes[chunk_length - 2], + chunk_bytes[chunk_length - 1], + ]; + + let recalculated_crc = crc::calculate_chunk_data_crc(chunk_type, &data); + if u32::from_be_bytes(crc) != recalculated_crc { + let chunk_name = String::from_utf8(chunk_type.to_vec())?; + return Err(error::DmiError::Generic(format!("Failed to load Chunk of type {}. Supplied CRC invalid: {:#?}. Its value ({}) does not match the recalculated one ({}).", chunk_name, crc, u32::from_be_bytes(crc), recalculated_crc))); + } + + Ok(RawGenericChunk { + data_length, + chunk_type, + data, + crc, + }) + } + + pub fn save(&self, writter: &mut W) -> Result { + let bytes_written = writter.write(&self.data_length)?; + let mut total_bytes_written = bytes_written; + if bytes_written < self.data_length.len() { + return Err(error::DmiError::Generic(format!( + "Failed to save Chunk. Buffer unable to hold the data, only {} bytes written.", + total_bytes_written + ))); + }; + + let bytes_written = writter.write(&self.chunk_type)?; + total_bytes_written += bytes_written; + if bytes_written < self.chunk_type.len() { + return Err(error::DmiError::Generic(format!( + "Failed to save Chunk. Buffer unable to hold the data, only {} bytes written.", + total_bytes_written + ))); + }; + + let bytes_written = writter.write(&self.data)?; + total_bytes_written += bytes_written; + if bytes_written < self.data.len() { + return Err(error::DmiError::Generic(format!( + "Failed to save Chunk. Buffer unable to hold the data, only {} bytes written.", + total_bytes_written + ))); + }; + + let bytes_written = writter.write(&self.crc)?; + total_bytes_written += bytes_written; + if bytes_written < self.crc.len() { + return Err(error::DmiError::Generic(format!( + "Failed to save Chunk. Buffer unable to hold the data, only {} bytes written.", + total_bytes_written + ))); + }; + + Ok(total_bytes_written) + } +} diff --git a/src/crc.rs b/src/crc.rs index 3733b9d..b4038ff 100644 --- a/src/crc.rs +++ b/src/crc.rs @@ -1,17 +1,6 @@ -pub(crate) fn calculate_crc<'a, I: IntoIterator>(buffer: I) -> u32 { - const CRC_POLYNOMIAL: u32 = 0xedb8_8320; - - fn update_crc(crc: u32, message: u8) -> u32 { - let message: u32 = u32::from(message); - let mut crc = crc ^ message; - for _ in 0..8 { - crc = (if crc & 1 != 0 { CRC_POLYNOMIAL } else { 0 }) ^ (crc >> 1); - } - crc - } - - buffer - .into_iter() - .fold(u32::MAX, |crc, message| update_crc(crc, *message)) - ^ u32::MAX -} +pub(crate) fn calculate_chunk_data_crc(chunk_type: [u8; 4], data: &[u8]) -> u32 { + let mut hasher = crc32fast::Hasher::new(); + hasher.update(&chunk_type); + hasher.update(data); + hasher.finalize() +} diff --git a/src/ztxt.rs b/src/ztxt.rs index f8da3df..ecbd48a 100644 --- a/src/ztxt.rs +++ b/src/ztxt.rs @@ -20,11 +20,11 @@ pub fn create_ztxt_chunk(dmi_signature: &[u8]) -> Result> for RawZtxtChunk { let data_bytes = &raw_chunk_bytes[8..(total_bytes_length - 4)]; let data = RawZtxtData::load(data_bytes)?; let crc = [raw_chunk_bytes[total_bytes_length - 4], raw_chunk_bytes[total_bytes_length - 3], raw_chunk_bytes[total_bytes_length - 2], raw_chunk_bytes[total_bytes_length - 1]]; - let calculated_crc = crc::calculate_crc(chunk_type.iter().chain(data_bytes.iter())); + let calculated_crc = crc::calculate_chunk_data_crc(chunk_type, data_bytes); if u32::from_be_bytes(crc) != calculated_crc { bail!("Failed to convert Vec into RawZtxtChunk. Given CRC ({}) does not match the calculated one ({}).", u32::from_be_bytes(crc), calculated_crc) } @@ -299,13 +299,12 @@ impl RawZtxtData { } fn crc(&self) -> u32 { - crc::calculate_crc( - ZTXT_TYPE - .iter() - .chain(self.keyword.iter()) - .chain([self.null_separator, self.compression_method].iter()) - .chain(self.compressed_text.iter()), - ) + let mut hasher = crc32fast::Hasher::new(); + hasher.update(&ZTXT_TYPE); + hasher.update(&self.keyword); + hasher.update(&[self.null_separator, self.compression_method]); + hasher.update(&self.compressed_text); + hasher.finalize() } }