use crate::{
Error, Result,
blocks::common::{BlockHeader, BlockParse, read_u8, read_u32, read_u64, validate_buffer_size},
};
use alloc::string::ToString;
use alloc::vec;
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DzCompressionType {
Deflate = 0,
TranspositionDeflate = 1,
}
impl DzCompressionType {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(Self::Deflate),
1 => Some(Self::TranspositionDeflate),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct DzBlock<'a> {
pub header: BlockHeader,
pub original_block_type: [u8; 2],
pub zip_type: DzCompressionType,
pub zip_parameter: u32,
pub original_data_length: u64,
pub compressed_data_length: u64,
pub data: &'a [u8],
}
pub const DZ_HEADER_SIZE: usize = 48;
impl<'a> BlockParse<'a> for DzBlock<'a> {
const ID: &'static str = "##DZ";
fn from_bytes(bytes: &'a [u8]) -> Result<Self> {
let header = Self::parse_header(bytes)?;
validate_buffer_size(bytes, DZ_HEADER_SIZE)?;
let original_block_type = [bytes[24], bytes[25]];
let zip_type_raw = read_u8(bytes, 26);
let zip_type = DzCompressionType::from_u8(zip_type_raw).ok_or_else(|| {
Error::BlockSerializationError(alloc::format!(
"Unknown DZ compression type: {}",
zip_type_raw
))
})?;
let zip_parameter = read_u32(bytes, 28);
let original_data_length = read_u64(bytes, 32);
let compressed_data_length = read_u64(bytes, 40);
let data_end = DZ_HEADER_SIZE + compressed_data_length as usize;
validate_buffer_size(bytes, data_end)?;
let data = &bytes[DZ_HEADER_SIZE..data_end];
Ok(Self {
header,
original_block_type,
zip_type,
zip_parameter,
original_data_length,
compressed_data_length,
data,
})
}
}
#[cfg(feature = "compression")]
impl DzBlock<'_> {
pub fn decompress(&self) -> Result<Vec<u8>> {
use miniz_oxide::inflate::decompress_to_vec_zlib;
let decompressed = decompress_to_vec_zlib(self.data).map_err(|e| {
Error::BlockSerializationError(alloc::format!("DZ decompression failed: {:?}", e))
})?;
if decompressed.len() != self.original_data_length as usize {
return Err(Error::BlockSerializationError(alloc::format!(
"DZ decompressed size mismatch: expected {}, got {}",
self.original_data_length,
decompressed.len()
)));
}
match self.zip_type {
DzCompressionType::Deflate => Ok(decompressed),
DzCompressionType::TranspositionDeflate => self.inverse_transpose(decompressed),
}
}
fn inverse_transpose(&self, data: Vec<u8>) -> Result<Vec<u8>> {
let columns = self.zip_parameter as usize;
if columns == 0 {
return Err(Error::BlockSerializationError(
"DZ transposition: zip_parameter (columns) cannot be 0".to_string(),
));
}
let total_bytes = data.len();
let rows = total_bytes.div_ceil(columns);
let mut result = vec![0u8; total_bytes];
for col in 0..columns {
for row in 0..rows {
let src_idx = col * rows + row;
let dst_idx = row * columns + col;
if src_idx < total_bytes && dst_idx < total_bytes {
result[dst_idx] = data[src_idx];
}
}
}
Ok(result)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_dz_header(
original_type: &[u8; 2],
zip_type: u8,
zip_param: u32,
original_len: u64,
compressed_len: u64,
) -> Vec<u8> {
let total_len = DZ_HEADER_SIZE as u64 + compressed_len;
let mut bytes = Vec::with_capacity(DZ_HEADER_SIZE);
bytes.extend_from_slice(b"##DZ");
bytes.extend_from_slice(&0u32.to_le_bytes()); bytes.extend_from_slice(&total_len.to_le_bytes()); bytes.extend_from_slice(&0u64.to_le_bytes());
bytes.extend_from_slice(original_type); bytes.push(zip_type); bytes.push(0); bytes.extend_from_slice(&zip_param.to_le_bytes()); bytes.extend_from_slice(&original_len.to_le_bytes()); bytes.extend_from_slice(&compressed_len.to_le_bytes());
bytes
}
#[test]
fn parse_dz_header() {
let compressed_data = [
0x78, 0x9c, 0x01, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01,
]; let mut bytes = create_dz_header(b"DT", 0, 0, 0, compressed_data.len() as u64);
bytes.extend_from_slice(&compressed_data);
let dz = DzBlock::from_bytes(&bytes).unwrap();
assert_eq!(dz.original_block_type, *b"DT");
assert_eq!(dz.zip_type, DzCompressionType::Deflate);
assert_eq!(dz.zip_parameter, 0);
assert_eq!(dz.original_data_length, 0);
assert_eq!(dz.compressed_data_length, compressed_data.len() as u64);
}
#[test]
fn parse_dz_transposition_type() {
let compressed_data = [
0x78, 0x9c, 0x01, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01,
];
let mut bytes = create_dz_header(b"SD", 1, 8, 0, compressed_data.len() as u64);
bytes.extend_from_slice(&compressed_data);
let dz = DzBlock::from_bytes(&bytes).unwrap();
assert_eq!(dz.original_block_type, *b"SD");
assert_eq!(dz.zip_type, DzCompressionType::TranspositionDeflate);
assert_eq!(dz.zip_parameter, 8);
}
#[test]
fn invalid_compression_type() {
let compressed_data = [
0x78, 0x9c, 0x01, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01,
];
let mut bytes = create_dz_header(b"DT", 99, 0, 0, compressed_data.len() as u64);
bytes.extend_from_slice(&compressed_data);
let result = DzBlock::from_bytes(&bytes);
assert!(result.is_err());
}
#[cfg(feature = "compression")]
mod compression_tests {
use super::*;
use miniz_oxide::deflate::compress_to_vec_zlib;
#[test]
fn decompress_deflate() {
let original_data = b"Hello, MDF4 world! This is test data for compression.";
let compressed = compress_to_vec_zlib(original_data, 6);
let mut bytes = create_dz_header(
b"DT",
0,
0,
original_data.len() as u64,
compressed.len() as u64,
);
bytes.extend_from_slice(&compressed);
let dz = DzBlock::from_bytes(&bytes).unwrap();
let decompressed = dz.decompress().unwrap();
assert_eq!(decompressed.as_slice(), original_data);
}
#[test]
fn decompress_transposition() {
let original_data: Vec<u8> = vec![
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, ];
let columns = 4usize;
let rows = 3usize;
let mut transposed = vec![0u8; 12];
for col in 0..columns {
for row in 0..rows {
transposed[col * rows + row] = original_data[row * columns + col];
}
}
let compressed = compress_to_vec_zlib(&transposed, 6);
let mut bytes = create_dz_header(
b"DT",
1,
columns as u32,
original_data.len() as u64,
compressed.len() as u64,
);
bytes.extend_from_slice(&compressed);
let dz = DzBlock::from_bytes(&bytes).unwrap();
let decompressed = dz.decompress().unwrap();
assert_eq!(decompressed, original_data);
}
#[test]
fn decompress_size_mismatch() {
let original_data = b"test";
let compressed = compress_to_vec_zlib(original_data, 6);
let mut bytes = create_dz_header(b"DT", 0, 0, 100, compressed.len() as u64);
bytes.extend_from_slice(&compressed);
let dz = DzBlock::from_bytes(&bytes).unwrap();
let result = dz.decompress();
assert!(result.is_err());
}
}
}