实现了数据块类型 ChunkType 结构体之后,接下来就可以实现其余的数据块功能了。
引入模块和类型
use std::convert::TryFrom;
use std::fmt;
use anyhow::{Result, Error};
use crc::{Crc, Algorithm};
// 引入之前实现的 ChunkType 结构体
use crate::chunk_type::ChunkType;
crc
库被用来为 PNG 文件的数据块计算 CRC 值。PNG 文件的每个数据块都包含一个 CRC 字段,用于验证数据块内容的完整性。代码中定义了 PNG 文件使用的 CRC-32 算法的多项式和相关参数,通过这些参数可以创建一个 Crc
实例,进而对数据块中的数据(包括数据块类型和数据内容)进行 CRC 计算,以确保数据在传输或存储过程中没有被篡改或损坏。
定义 PNG 数据块的 CRC 多项式
// 定义 PNG 数据块的 CRC 多项式
const CRC_32_POLY: u32 = 0x04C11DB7;
const CRC_32_ALGO: Algorithm<u32> = Algorithm {
poly: CRC_32_POLY,
init: 0xFFFFFFFF,
refin: true,
refout: true,
xorout: 0xFFFFFFFF,
check: 0xCBF43926,
residue: 0x00000000,
width: 32,
};
const CRC_32_POLY: u32 = 0x04C11DB7;
:定义一个常量CRC_32_POLY
,它是一个 32 位无符号整数,表示 PNG 文件使用的 CRC-32 算法的多项式。这个多项式是国际标准 ISO 3309 中定义的标准 CRC-32 多项式。const CRC_32_ALGO: Algorithm<u32> = Algorithm {... };
:定义一个常量CRC_32_ALGO
,它是Algorithm<u32>
类型的实例,用于描述 CRC-32 算法的具体参数:poly: CRC_32_POLY
:指定 CRC 算法使用的多项式,这里使用之前定义的CRC_32_POLY
。init: 0xFFFFFFFF
:指定 CRC 寄存器的初始值,初始值为全 1。refin: true
:表示在计算 CRC 之前需要对输入数据的每个字节进行按位反转。refout: true
:表示在计算完 CRC 之后需要对结果进行按位反转。xorout: 0xFFFFFFFF
:表示在计算完 CRC 并反转结果之后,需要与这个值进行异或操作。check: 0xCBF43926
:这是一个校验值,用于验证 CRC 算法实现的正确性。当对特定的测试数据进行 CRC 计算时,结果应该等于这个校验值。residue: 0x00000000
:表示在处理完所有数据后,CRC 寄存器中剩余的值,这里为 0。width: 32
:表示 CRC 算法的位数,这里是 32 位 CRC 算法。 通过上述代码,我们为后续计算 PNG 数据块的 CRC 值做好了算法参数的准备工作。
定义 Chunk 结构体
#[derive(Debug, Clone)]
pub struct Chunk {
length: u32,
chunk_type: ChunkType,
data: Vec<u8>,
crc: u32,
}
impl Chunk {
// 创建新的 Chunk 实例
pub fn new(chunk_type: ChunkType, data: Vec<u8>) -> Chunk {
let length = data.len() as u32;
let mut all_data = Vec::new();
all_data.extend_from_slice(&chunk_type.bytes());
all_data.extend_from_slice(&data);
let crc_calculator = Crc::<u32>::new(&CRC_32_ALGO);
let crc = crc_calculator.checksum(&all_data);
Chunk {
length,
chunk_type,
data,
crc,
}
}
// 返回数据块数据的长度
pub fn length(&self) -> u32 {
self.length
}
// 返回数据块类型的引用
pub fn chunk_type(&self) -> &ChunkType {
&self.chunk_type
}
// 返回数据块数据的引用
pub fn data(&self) -> &[u8] {
&self.data
}
// 返回数据块的 CRC 值
pub fn crc(&self) -> u32 {
self.crc
}
// 尝试将数据块的数据转换为字符串
pub fn data_as_string(&self) -> Result<String> {
String::from_utf8(self.data.clone()).map_err(|e| Error::msg(e.to_string()))
}
// 将整个数据块转换为字节序列
pub fn as_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.extend_from_slice(&self.length.to_be_bytes());
bytes.extend_from_slice(self.chunk_type.bytes().as_slice());
bytes.extend_from_slice(&self.data);
bytes.extend_from_slice(&self.crc.to_be_bytes());
bytes
}
}
实现从字节切片转换为 Chunk 实例的功能
具体步骤:
- 检查输入字节切片的长度是否足够形成一个有效的数据块。
- 从字节切片中提取数据块的长度、数据块类型和数据。
- 从字节切片中提取预期的 CRC 值,并重新计算数据的 CRC 值。
- 比较计算得到的 CRC 值和预期的 CRC 值,如果不相等则返回错误。
- 如果所有检查都通过,则返回一个新的
Chunk
实例。
impl TryFrom<&[u8]> for Chunk {
type Error = Error;
fn try_from(value: &[u8]) -> Result<Self> {
if value.len() < 12 {
return Err(Error::msg("Input bytes are too short to form a valid chunk"));
}
let length = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
let chunk_type = ChunkType::try_from([value[4], value[5], value[6], value[7]])?;
let data_end = 8 + length as usize;
if value.len() < data_end + 4 {
return Err(Error::msg("Input bytes do not contain enough data for the specified length"));
}
let data = value[8..data_end].to_vec();
let expected_crc = u32::from_be_bytes([value[data_end], value[data_end + 1], value[data_end + 2], value[data_end + 3]]);
let mut all_data = Vec::new();
all_data.extend_from_slice(&chunk_type.bytes());
all_data.extend_from_slice(&data);
let crc_calculator = Crc::<u32>::new(&CRC_32_ALGO);
let calculated_crc = crc_calculator.checksum(&all_data);
if calculated_crc != expected_crc {
return Err(Error::msg("CRC check failed"));
}
Ok(Chunk {
length,
chunk_type,
data,
crc: calculated_crc,
})
}
}
实现 Display 特性,用于格式化输出 Chunk 实例
impl fmt::Display for Chunk {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.data_as_string() {
Ok(s) => write!(f, "Chunk {{ length: {}, type: {}, data: \"{}\", crc: {} }}", self.length, self.chunk_type, s, self.crc),
Err(_) => write!(f, "Chunk {{ length: {}, type: {}, data: {:?}, crc: {} }}", self.length, self.chunk_type, self.data, self.crc),
}
}
}