use std::fmt::Display;
use std::str::FromStr;
use crate::chunk_type::ChunkType;
use crate::{checksum_32, u8_4_from_slice};
use crate::{Error, Result, CHUNK_SIZE};
use crc::CRC_32_ISO_HDLC;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Chunk {
length: u32,
chunk_type: ChunkType,
chunk_data: Vec<u8>,
crc: u32,
}
impl Chunk {
pub fn new(chunk_type: ChunkType, chunk_data: Vec<u8>) -> Chunk {
let to_check = [&chunk_type.bytes(), chunk_data.as_slice()].concat();
let crc = checksum_32(&CRC_32_ISO_HDLC, &to_check);
Chunk {
length: chunk_data.len() as u32,
chunk_type,
chunk_data,
crc,
}
}
pub fn from_strings(chunk_type: &str, data: &str) -> Result<Chunk> {
let chunk_type = ChunkType::from_str(chunk_type)?;
let data: Vec<u8> = data.bytes().collect();
Ok(Chunk::new(chunk_type, data))
}
pub fn length(&self) -> u32 {
self.length
}
pub fn chunk_type(&self) -> &ChunkType {
&self.chunk_type
}
pub fn data(&self) -> &[u8] {
&self.chunk_data
}
pub fn crc(&self) -> u32 {
self.crc
}
pub fn data_as_string(&self) -> Result<String> {
String::from_utf8(self.data().to_vec()).map_err(|e| Error::from(e))
}
pub fn as_bytes(&self) -> Vec<u8> {
[
self.length.to_be_bytes().as_ref(),
&self.chunk_type.bytes(),
&self.chunk_data,
self.crc.to_be_bytes().as_ref(),
]
.concat()
}
}
impl TryFrom<&[u8]> for Chunk {
type Error = Error;
fn try_from(bytes: &[u8]) -> Result<Self> {
let length = u32::from_be_bytes(u8_4_from_slice(&bytes[0..CHUNK_SIZE]));
if bytes.len() != (length as usize + 3 * CHUNK_SIZE) as usize {
return Err(Error::Custom(
"Chunk contains incorrect length information".to_owned(),
));
}
let chunk_type =
ChunkType::try_from(u8_4_from_slice(&bytes[CHUNK_SIZE..2 * CHUNK_SIZE])).unwrap();
let chunk_data = bytes[2 * CHUNK_SIZE..bytes.len() - CHUNK_SIZE].to_vec();
let crc = u32::from_be_bytes(u8_4_from_slice(
&bytes[bytes.len() - CHUNK_SIZE..bytes.len()],
));
let to_check: Vec<u8> = [&chunk_type.bytes(), chunk_data.as_slice()].concat();
if checksum_32(&CRC_32_ISO_HDLC, &to_check) != crc {
Err(Error::Custom("CRC checksum fails".to_owned()))
} else {
Ok(Chunk {
length,
chunk_type,
chunk_data,
crc,
})
}
}
}
impl Display for Chunk {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Chunk\n{{\n")?;
write!(
f,
"\tlength: {}, chunk_type: {}\n\tdata: {:?}\n\tcrc: {}\n}}",
self.length(),
self.chunk_type(),
self.data(),
self.crc()
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chunk_type::ChunkType;
use std::str::FromStr;
#[test]
fn crc_test() {
let crc = checksum_32(&CRC_32_ISO_HDLC, b"123456789");
println!("{:?}", crc);
assert_eq!(crc, 0xcbf43926);
}
#[test]
fn crc_test2() {
let chunk_type = "RuSt";
let message_bytes = "This is where your secret message will be!".as_bytes();
let crc: u32 = 2882656334;
let checked = checksum_32(
&CRC_32_ISO_HDLC,
&[chunk_type.as_bytes(), message_bytes].concat(),
);
println!("{}", checked);
assert_eq!(checked, crc);
}
fn testing_chunk() -> Chunk {
let data_length: u32 = 42;
let chunk_type = "RuSt".as_bytes();
let message_bytes = "This is where your secret message will be!".as_bytes();
let crc: u32 = 2882656334;
let chunk_data: Vec<u8> = data_length
.to_be_bytes()
.iter()
.chain(chunk_type.iter())
.chain(message_bytes.iter())
.chain(crc.to_be_bytes().iter())
.copied()
.collect();
Chunk::try_from(chunk_data.as_ref()).unwrap()
}
#[test]
fn test_new_chunk() {
let chunk_type = ChunkType::from_str("RuSt").unwrap();
let data = "This is where your secret message will be!"
.as_bytes()
.to_vec();
let chunk = Chunk::new(chunk_type, data);
assert_eq!(chunk.length(), 42);
assert_eq!(chunk.crc(), 2882656334);
}
#[test]
fn test_chunk_length() {
let chunk = testing_chunk();
assert_eq!(chunk.length(), 42);
}
#[test]
fn test_chunk_type() {
let chunk = testing_chunk();
assert_eq!(chunk.chunk_type().to_string(), String::from("RuSt"));
}
#[test]
fn test_chunk_string() {
let chunk = testing_chunk();
let chunk_string = chunk.data_as_string().unwrap();
let expected_chunk_string = String::from("This is where your secret message will be!");
assert_eq!(chunk_string, expected_chunk_string);
}
#[test]
fn test_chunk_crc() {
let chunk = testing_chunk();
assert_eq!(chunk.crc(), 2882656334);
}
#[test]
fn test_valid_chunk_from_bytes() {
let data_length: u32 = 42;
let chunk_type = "RuSt".as_bytes();
let message_bytes = "This is where your secret message will be!".as_bytes();
let crc: u32 = 2882656334;
let chunk_data: Vec<u8> = data_length
.to_be_bytes()
.iter()
.chain(chunk_type.iter())
.chain(message_bytes.iter())
.chain(crc.to_be_bytes().iter())
.copied()
.collect();
let chunk = Chunk::try_from(chunk_data.as_ref()).unwrap();
let chunk_string = chunk.data_as_string().unwrap();
let expected_chunk_string = String::from("This is where your secret message will be!");
assert_eq!(chunk.length(), 42);
assert_eq!(chunk.chunk_type().to_string(), String::from("RuSt"));
assert_eq!(chunk_string, expected_chunk_string);
assert_eq!(chunk.crc(), 2882656334);
}
#[test]
fn test_invalid_chunk_from_bytes() {
let data_length: u32 = 42;
let chunk_type = "RuSt".as_bytes();
let message_bytes = "This is where your secret message will be!".as_bytes();
let crc: u32 = 2882656333;
let chunk_data: Vec<u8> = data_length
.to_be_bytes()
.iter()
.chain(chunk_type.iter())
.chain(message_bytes.iter())
.chain(crc.to_be_bytes().iter())
.copied()
.collect();
let chunk = Chunk::try_from(chunk_data.as_ref());
assert!(chunk.is_err());
}
#[test]
fn test_chunk_trait_impls() {
let data_length: u32 = 42;
let chunk_type = "RuSt".as_bytes();
let message_bytes = "This is where your secret message will be!".as_bytes();
let crc: u32 = 2882656334;
let chunk_data: Vec<u8> = data_length
.to_be_bytes()
.iter()
.chain(chunk_type.iter())
.chain(message_bytes.iter())
.chain(crc.to_be_bytes().iter())
.copied()
.collect();
let chunk: Chunk = TryFrom::try_from(chunk_data.as_ref()).unwrap();
let _chunk_string = format!("{}", chunk);
}
#[test]
fn test_chunk_display() {
let chunk = testing_chunk();
let chunk_string = chunk.data_as_string().unwrap();
let expected_chunk_string = String::from("This is where your secret message will be!");
assert_eq!(chunk.length(), 42);
assert_eq!(chunk.chunk_type().to_string(), String::from("RuSt"));
assert_eq!(chunk_string, expected_chunk_string);
assert_eq!(chunk.crc(), 2882656334);
println!("{}", chunk);
}
#[test]
fn test_chunk_as_bytes() {
let data_length: u32 = 42;
let chunk_type = "RuSt".as_bytes();
let message_bytes = "This is where your secret message will be!".as_bytes();
let crc: u32 = 2882656334;
let chunk_data: Vec<u8> = data_length
.to_be_bytes()
.iter()
.chain(chunk_type.iter())
.chain(message_bytes.iter())
.chain(crc.to_be_bytes().iter())
.copied()
.collect();
let chunk = Chunk::try_from(chunk_data.as_ref()).unwrap();
assert_eq!(chunk.as_bytes(), chunk_data);
}
}