use std::io::{Write, Seek, SeekFrom, Cursor};
use byteorder::{LittleEndian, WriteBytesExt};
use crate::error::DxfError;
use crate::types::DxfVersion;
use super::section_definition::{names, ac21_section_info};
use crate::io::dwg::compressor_ac21::compress_ac21;
use crate::io::dwg::crc::{
dwg_ac21_normal_crc64,
dwg_ac21_normal_crc64_seed1,
dwg_ac21_mirrored_crc64,
dwg_ac21_header_crc64,
dwg_ac21_page_checksum,
};
use crate::io::dwg::dwg21_metadata::{Dwg21CompressedMetadata, METADATA_SIZE};
use crate::io::dwg::reed_solomon::{
reed_solomon_encode,
RS_N, RS_SYSTEM_K, RS_SYSTEM_PRIM_POLY,
RS_DATA_K, RS_DATA_PRIM_POLY,
};
const METADATA_BLOCK_SIZE: usize = 0x80;
const FILE_HEADER_PAGE_SIZE: usize = 0x400;
const RESERVED_HEADER_SIZE: usize = METADATA_BLOCK_SIZE + FILE_HEADER_PAGE_SIZE;
const CRC_BLOCK_SIZE: usize = 8;
const PAGE_ALIGN_SIZE: usize = 0x20;
const MIN_SYSTEM_PAGE_SIZE: usize = 0x400;
const CHECK_DATA_SIZE: usize = 0x28;
const RS_DATA_IN_HEADER: usize = FILE_HEADER_PAGE_SIZE - CHECK_DATA_SIZE;
const FILE_HEADER_RS_FACTOR: usize = 3;
struct CrcRandomEncoder {
table: Vec<u32>,
index: usize,
}
impl CrcRandomEncoder {
fn new(seed: u64) -> Self {
let mut table = vec![0u32; 0x270];
table[0] = seed as u32;
table[1] = (seed >> 32) as u32;
for i in 2..0x270 {
table[i] = (table[i - 1] >> 30 ^ table[i - 1])
.wrapping_mul(0x6C078965)
.wrapping_add(i as u32);
}
let mut encoder = Self { table, index: 0x270 };
encoder.regenerate();
encoder
}
fn regenerate(&mut self) {
for i in 0..0x270 {
let y = (self.table[i] & 0x80000000)
| (self.table[(i + 1) % 0x270] & 0x7FFFFFFF);
self.table[i] = self.table[(i + 0x18D) % 0x270] ^ (y >> 1);
if y & 1 != 0 {
self.table[i] ^= 0x9908B0DF;
}
}
self.index = 0;
}
fn next_u32(&mut self) -> u32 {
if self.index >= 0x270 {
self.regenerate();
}
let mut y = self.table[self.index];
self.index += 1;
y ^= y >> 11;
y ^= (y << 7) & 0x9D2C5680;
y ^= (y << 15) & 0xEFC60000;
y ^= y >> 18;
y
}
fn next_u64(&mut self) -> u64 {
let lo = self.next_u32() as u64;
let hi = self.next_u32() as u64;
lo | (hi << 32)
}
fn encode_crc_seed(&mut self, seed: u64) -> u64 {
let mut result: u64 = 0;
let mut remaining = seed;
for shift in (0..60).step_by(10) {
let random = self.next_u32();
let seed_bits = remaining & 0x3FF;
let random_bits = (random as u64) & 0x3FF;
remaining >>= 10;
result |= (seed_bits ^ random_bits) << shift;
}
let random = self.next_u32();
let seed_bits = remaining & 0xF;
let random_bits = (random as u64) & 0xF;
result |= (seed_bits ^ random_bits) << 60;
result
}
fn fill_random(&mut self, buffer: &mut [u8]) {
let mut i = 0;
while i + 4 <= buffer.len() {
let val = self.next_u32();
buffer[i..i + 4].copy_from_slice(&val.to_le_bytes());
i += 4;
}
if i < buffer.len() {
let val = self.next_u32();
let bytes = val.to_le_bytes();
for j in 0..(buffer.len() - i) {
buffer[i + j] = bytes[j];
}
}
}
}
#[derive(Debug, Clone)]
struct AC21PageRecord {
id: i64,
size: i64,
offset: u64,
}
#[derive(Debug, Clone)]
struct AC21SectionInfo {
name: String,
hash_code: u32,
encoding: u64,
encryption: u64,
max_page_size: u64,
data_size: u64,
pages: Vec<AC21SectionPageRecord>,
}
#[derive(Debug, Clone)]
struct AC21SectionPageRecord {
data_offset: u64,
page_size: u64,
page_id: i64,
uncompressed_size: u64,
compressed_size: u64,
checksum: u64,
crc: u64,
}
fn align32(size: usize) -> usize {
(size + PAGE_ALIGN_SIZE - 1) & !(PAGE_ALIGN_SIZE - 1)
}
#[allow(dead_code)]
fn get_aligned_page_size(page_size: u64) -> u64 {
(page_size + PAGE_ALIGN_SIZE as u64 - 1) & !(PAGE_ALIGN_SIZE as u64 - 1)
}
#[allow(dead_code)]
fn get_system_page_size(data_size: u64) -> u64 {
let aligned = (data_size + CRC_BLOCK_SIZE as u64 - 1)
& !(CRC_BLOCK_SIZE as u64 - 1);
let file_page_size = ((aligned * 2) + RS_SYSTEM_K as u64 - 1)
/ RS_SYSTEM_K as u64
* RS_N as u64;
if file_page_size < MIN_SYSTEM_PAGE_SIZE as u64 {
MIN_SYSTEM_PAGE_SIZE as u64
} else {
get_aligned_page_size(file_page_size)
}
}
fn encode_value(value: u64, control: u64) -> u64 {
let shift = (control & 0x1F) as u32;
if shift != 0 {
(value << shift) | (value >> (64 - shift))
} else {
value
}
}
fn rs_encode_data_page_interleaved(data: &[u8], _encoding: u64) -> Vec<u8> {
let block_size = RS_DATA_K; let prim_poly = RS_DATA_PRIM_POLY;
let total_size = (data.len() + CRC_BLOCK_SIZE - 1) & !(CRC_BLOCK_SIZE - 1);
let factor = (total_size + block_size - 1) / block_size;
let padded_len = factor * block_size;
let mut padded_data = vec![0u8; padded_len];
let copy_len = data.len().min(padded_len);
padded_data[..copy_len].copy_from_slice(&data[..copy_len]);
let mut encoded = vec![0u8; factor * RS_N];
reed_solomon_encode(
&padded_data,
&mut encoded,
factor,
block_size,
prim_poly,
);
encoded
}
pub struct DwgFileHeaderWriterAC21 {
version: DxfVersion,
sections: Vec<AC21SectionInfo>,
page_records: Vec<AC21PageRecord>,
crc_seed: u64,
next_page_id: i64,
pub skip_lz77: bool,
}
impl DwgFileHeaderWriterAC21 {
pub fn new<W: Write + Seek>(version: DxfVersion, output: &mut W) -> Result<Self, DxfError> {
let zeroes = vec![0u8; RESERVED_HEADER_SIZE];
output.write_all(&zeroes)?;
Ok(Self {
version,
sections: Vec::new(),
page_records: Vec::new(),
crc_seed: 0,
next_page_id: 1,
skip_lz77: false,
})
}
pub fn handle_section_offset(&self) -> usize {
0
}
pub fn add_section<W: Write + Seek>(
&mut self,
output: &mut W,
name: &str,
data: &[u8],
) -> Result<(), DxfError> {
let hash_code = ac21_section_info::hash_code(name)
.ok_or_else(|| DxfError::InvalidFormat(
format!("Unknown AC21 section: {}", name),
))?;
let encoding = ac21_section_info::encoding(name).unwrap_or(1);
let encryption: u64 = 0;
let max_page_size = ac21_section_info::page_size(name)
.unwrap_or(0xF800);
let mut section = AC21SectionInfo {
name: name.to_string(),
hash_code,
encoding,
encryption,
max_page_size,
data_size: data.len() as u64,
pages: Vec::new(),
};
let page_size = max_page_size as usize;
let mut offset: usize = 0;
while offset < data.len() {
let remaining = data.len() - offset;
let chunk_size = remaining.min(page_size);
let chunk = &data[offset..offset + chunk_size];
let page_record = self.write_data_page(
output,
chunk,
encoding,
offset as u64,
max_page_size,
)?;
section.pages.push(page_record);
offset += chunk_size;
}
self.sections.push(section);
Ok(())
}
pub fn write_file<W: Write + Seek>(
&mut self,
output: &mut W,
) -> Result<(), DxfError> {
self.crc_seed = 0;
let mut rng = CrcRandomEncoder::new(self.crc_seed);
let section_map_data = self.build_section_map()?;
let section_map_result = self.write_system_page(output, §ion_map_data)?;
let section_map2_result = self.write_system_page(output, §ion_map_data)?;
let page_map_page_id = self.next_page_id;
self.next_page_id += 1;
let page_map2_page_id = self.next_page_id;
self.next_page_id += 1;
let pm_raw_size_est = (self.page_records.len() + 3) * 16;
let pm_page_size = get_system_page_size(pm_raw_size_est as u64) as usize;
let page_map_data = self.build_page_map_ordered(
page_map_page_id, page_map2_page_id, pm_page_size as i64,
);
let pm_abs_offset = output.seek(SeekFrom::Current(0))?;
let page_map_result = self.write_system_page_at(
output, &page_map_data,
pm_abs_offset,
page_map_page_id,
pm_page_size,
)?;
let pm_abs_offset2 = output.seek(SeekFrom::Current(0))?;
let _page_map2_result = self.write_system_page_at(
output, &page_map_data,
pm_abs_offset2,
page_map2_page_id,
pm_page_size,
)?;
let mut metadata = Dwg21CompressedMetadata::default();
metadata.file_size = 0;
metadata.pages_map_crc_compressed = page_map_result.crc_compressed;
metadata.pages_map_correction_factor = page_map_result.correction_factor;
metadata.pages_map_crc_seed = self.crc_seed;
metadata.pages_map_offset = pm_abs_offset - RESERVED_HEADER_SIZE as u64;
metadata.pages_map_id = page_map_page_id as u64;
metadata.map2_offset = pm_abs_offset2 - RESERVED_HEADER_SIZE as u64;
metadata.map2_id = page_map2_page_id as u64;
metadata.pages_map_size_compressed = page_map_result.compressed_size;
metadata.pages_map_size_uncompressed = page_map_result.uncompressed_size;
let total_pages = self.page_records.len() as u64 + 2; metadata.pages_amount = total_pages;
metadata.pages_max_id = (self.next_page_id - 1) as u64;
metadata.pages_map_crc_uncompressed = page_map_result.crc_uncompressed;
metadata.sections_amount = (self.sections.len() + 1) as u64; metadata.sections_map_crc_uncompressed = section_map_result.crc_uncompressed;
metadata.sections_map_size_compressed = section_map_result.compressed_size;
metadata.sections_map2_id = section_map2_result.page_id as u64;
metadata.sections_map_id = section_map_result.page_id as u64;
metadata.sections_map_size_uncompressed = section_map_result.uncompressed_size;
metadata.sections_map_crc_compressed = section_map_result.crc_compressed;
metadata.sections_map_correction_factor = section_map_result.correction_factor;
metadata.sections_map_crc_seed = self.crc_seed;
metadata.crc_seed = self.crc_seed;
metadata.crc_seed_encoded = rng.encode_crc_seed(self.crc_seed);
metadata.random_seed = rng.next_u64();
let meta_bytes = metadata.to_bytes();
let header_crc = dwg_ac21_header_crc64(&meta_bytes);
metadata.header_crc64 = header_crc;
let file_header_page = self.build_file_header_page(&metadata, &mut rng)?;
debug_assert_eq!(file_header_page.len(), FILE_HEADER_PAGE_SIZE);
output.seek(SeekFrom::Start(METADATA_BLOCK_SIZE as u64))?;
output.write_all(&file_header_page)?;
output.seek(SeekFrom::End(0))?;
let header2_offset = output.seek(SeekFrom::Current(0))?;
output.write_all(&file_header_page)?;
let file_size = output.seek(SeekFrom::Current(0))?;
metadata.file_size = file_size;
metadata.header2_offset = header2_offset - RESERVED_HEADER_SIZE as u64;
let mut rng2 = CrcRandomEncoder::new(self.crc_seed);
metadata.crc_seed_encoded = rng2.encode_crc_seed(self.crc_seed);
metadata.random_seed = rng2.next_u64();
let meta_bytes = metadata.to_bytes();
let header_crc = dwg_ac21_header_crc64(&meta_bytes);
metadata.header_crc64 = header_crc;
let file_header_page = self.build_file_header_page(&metadata, &mut rng2)?;
output.seek(SeekFrom::Start(METADATA_BLOCK_SIZE as u64))?;
output.write_all(&file_header_page)?;
output.seek(SeekFrom::Start(header2_offset))?;
output.write_all(&file_header_page)?;
self.write_metadata(output, &metadata)?;
output.seek(SeekFrom::End(0))?;
Ok(())
}
fn write_data_page<W: Write + Seek>(
&mut self,
output: &mut W,
data: &[u8],
encoding: u64,
data_offset: u64,
max_page_size: u64,
) -> Result<AC21SectionPageRecord, DxfError> {
let page_id = self.next_page_id;
self.next_page_id += 1;
let checksum = dwg_ac21_page_checksum(0, data) as u64;
if encoding == 1 {
let page_crc = dwg_ac21_mirrored_crc64(0, data.len() as u32, data);
let aligned_size = align32(data.len());
let offset = output.seek(SeekFrom::Current(0))?;
output.write_all(data)?;
let padding = aligned_size - data.len();
if padding > 0 {
output.write_all(&vec![0u8; padding])?;
}
self.page_records.push(AC21PageRecord {
id: page_id,
size: aligned_size as i64,
offset,
});
Ok(AC21SectionPageRecord {
data_offset,
page_size: max_page_size,
page_id,
uncompressed_size: data.len() as u64,
compressed_size: data.len() as u64,
checksum,
crc: page_crc,
})
} else {
let compressed = compress_ac21(data);
let (page_data, comp_size) = if self.skip_lz77 || compressed.len() >= data.len() {
(data, data.len() as u64)
} else {
(compressed.as_slice(), compressed.len() as u64)
};
let page_crc = dwg_ac21_mirrored_crc64(0, page_data.len() as u32, page_data);
let encoded = rs_encode_data_page_interleaved(page_data, encoding);
let aligned_size = align32(encoded.len());
let offset = output.seek(SeekFrom::Current(0))?;
output.write_all(&encoded)?;
let padding = aligned_size - encoded.len();
if padding > 0 {
output.write_all(&vec![0u8; padding])?;
}
self.page_records.push(AC21PageRecord {
id: page_id,
size: aligned_size as i64,
offset,
});
Ok(AC21SectionPageRecord {
data_offset,
page_size: max_page_size,
page_id,
uncompressed_size: data.len() as u64,
compressed_size: comp_size,
checksum,
crc: page_crc,
})
}
}
fn encode_system_page(
&self,
data: &[u8],
target_page_size: usize,
) -> (Vec<u8>, u64, u64, u64, u64, u64) {
let uncompressed_size = data.len() as u64;
let crc_uncompressed = dwg_ac21_mirrored_crc64(
self.crc_seed,
data.len() as u32,
data,
);
let compressed = compress_ac21(data);
let use_compressed = compressed.len() < data.len();
let page_data = if use_compressed { &compressed } else { data };
let compressed_size = if use_compressed {
compressed.len() as u64
} else {
data.len() as u64
};
let crc_compressed = dwg_ac21_mirrored_crc64(
self.crc_seed,
page_data.len() as u32,
page_data,
);
let aligned_comp = ((page_data.len() as u64 + 7) & !7) as usize;
let correction_factor: u64 = if aligned_comp == 0 {
1
} else {
let max_rs_blocks = target_page_size / RS_N; let max_data_capacity = max_rs_blocks * RS_SYSTEM_K;
let cf = max_data_capacity / aligned_comp;
cf.max(2) as u64 };
let total_size = aligned_comp * correction_factor as usize;
let factor = (total_size + RS_SYSTEM_K - 1) / RS_SYSTEM_K;
let mut padded_data = vec![0u8; factor * RS_SYSTEM_K];
let copy_len = page_data.len().min(padded_data.len());
padded_data[..copy_len].copy_from_slice(&page_data[..copy_len]);
let mut encoded = vec![0u8; factor * RS_N];
reed_solomon_encode(
&padded_data,
&mut encoded,
factor,
RS_SYSTEM_K,
RS_SYSTEM_PRIM_POLY,
);
(encoded, compressed_size, uncompressed_size, crc_compressed, crc_uncompressed, correction_factor)
}
fn write_system_page<W: Write + Seek>(
&mut self,
output: &mut W,
data: &[u8],
) -> Result<SystemPageResult, DxfError> {
let page_id = self.next_page_id;
self.next_page_id += 1;
let target_page_size = get_system_page_size(data.len() as u64) as usize;
let (encoded, compressed_size, uncompressed_size, crc_compressed, crc_uncompressed, correction_factor)
= self.encode_system_page(data, target_page_size);
let page_size = align32(encoded.len()).max(target_page_size);
let offset = output.seek(SeekFrom::Current(0))?;
output.write_all(&encoded)?;
let padding = page_size - encoded.len();
if padding > 0 {
output.write_all(&vec![0u8; padding])?;
}
self.page_records.push(AC21PageRecord {
id: page_id,
size: page_size as i64,
offset,
});
Ok(SystemPageResult {
page_id,
offset,
compressed_size,
uncompressed_size,
crc_compressed,
crc_uncompressed,
correction_factor,
})
}
fn write_system_page_at<W: Write + Seek>(
&self,
output: &mut W,
data: &[u8],
abs_position: u64,
page_id: i64,
target_size: usize,
) -> Result<SystemPageResult, DxfError> {
let (encoded, compressed_size, uncompressed_size, crc_compressed, crc_uncompressed, correction_factor)
= self.encode_system_page(data, target_size);
let actual_page_size = align32(encoded.len()).max(target_size);
output.seek(SeekFrom::Start(abs_position))?;
output.write_all(&encoded)?;
let padding = actual_page_size - encoded.len();
if padding > 0 {
output.write_all(&vec![0u8; padding])?;
}
Ok(SystemPageResult {
page_id,
offset: abs_position,
compressed_size,
uncompressed_size,
crc_compressed,
crc_uncompressed,
correction_factor,
})
}
fn build_section_map(&self) -> Result<Vec<u8>, DxfError> {
let mut stream = Vec::new();
let map_order = ac21_section_info::SECTION_MAP_ORDER;
let mut sorted: Vec<&AC21SectionInfo> = self.sections.iter().collect();
sorted.sort_by_key(|s| {
map_order.iter().position(|&n| n == s.name).unwrap_or(usize::MAX)
});
for section in &sorted {
stream.write_u64::<LittleEndian>(section.data_size)?;
stream.write_u64::<LittleEndian>(section.max_page_size)?;
stream.write_u64::<LittleEndian>(section.encryption)?;
stream.write_u64::<LittleEndian>(section.hash_code as u64)?;
let name_chars: Vec<u16> = section.name.encode_utf16().collect();
let name_byte_len = if name_chars.is_empty() {
0u64
} else {
(name_chars.len() as u64 + 1) * 2 };
stream.write_u64::<LittleEndian>(name_byte_len)?;
stream.write_u64::<LittleEndian>(0)?;
stream.write_u64::<LittleEndian>(section.encoding)?;
stream.write_u64::<LittleEndian>(section.pages.len() as u64)?;
if !name_chars.is_empty() {
for &ch in &name_chars {
stream.write_u16::<LittleEndian>(ch)?;
}
stream.write_u16::<LittleEndian>(0)?;
}
for page in §ion.pages {
stream.write_u64::<LittleEndian>(page.data_offset)?;
stream.write_u64::<LittleEndian>(page.page_size)?;
stream.write_u64::<LittleEndian>(page.page_id as u64)?;
stream.write_u64::<LittleEndian>(page.uncompressed_size)?;
stream.write_u64::<LittleEndian>(page.compressed_size)?;
stream.write_u64::<LittleEndian>(page.checksum)?;
stream.write_u64::<LittleEndian>(page.crc)?;
}
}
Ok(stream)
}
fn build_page_map_ordered(
&self,
page_map_id: i64,
page_map2_id: i64,
page_map_page_size: i64,
) -> Vec<u8> {
let capacity = (self.page_records.len() + 3) * 16;
let mut stream = Vec::with_capacity(capacity);
for record in &self.page_records {
stream.extend_from_slice(&record.size.to_le_bytes());
stream.extend_from_slice(&record.id.to_le_bytes());
}
stream.extend_from_slice(&page_map_page_size.to_le_bytes());
stream.extend_from_slice(&page_map_id.to_le_bytes());
stream.extend_from_slice(&page_map_page_size.to_le_bytes());
stream.extend_from_slice(&page_map2_id.to_le_bytes());
stream.extend_from_slice(&0i64.to_le_bytes());
stream.extend_from_slice(&0i64.to_le_bytes());
stream
}
fn build_file_header_page(
&self,
metadata: &Dwg21CompressedMetadata,
rng: &mut CrcRandomEncoder,
) -> Result<Vec<u8>, DxfError> {
let random1 = rng.next_u64();
let random2 = rng.next_u64();
let encoded_crc_seed = rng.encode_crc_seed(self.crc_seed);
let normal_crc = {
let mut buf = [0u64; 8];
buf[0] = encode_value(random1, random2);
buf[1] = encode_value(buf[0], buf[0]);
buf[2] = encode_value(random2, buf[1]);
buf[3] = encode_value(buf[2], buf[2]);
buf[4] = encode_value(random1, buf[3]);
buf[5] = encode_value(buf[4], buf[4]);
buf[6] = encode_value(buf[5], buf[5]);
buf[7] = encode_value(buf[6], buf[6]);
let mut bytes = [0u8; 64];
for (i, &val) in buf.iter().enumerate() {
bytes[i * 8..(i + 1) * 8].copy_from_slice(&val.to_le_bytes());
}
dwg_ac21_normal_crc64(random2, 64, &bytes)
};
let mirrored_crc = {
let mut buf = [0u64; 8];
buf[0] = encode_value(random1, random2);
buf[1] = encode_value(normal_crc, buf[0]);
buf[2] = encode_value(random2, buf[1]);
buf[3] = encode_value(normal_crc, buf[2]);
buf[4] = encode_value(random1, buf[3]);
buf[5] = encode_value(normal_crc, buf[4]);
buf[6] = encode_value(random2, buf[5]);
buf[7] = encode_value(buf[6], buf[6]);
let mut bytes = [0u8; 64];
for (i, &val) in buf.iter().enumerate() {
bytes[i * 8..(i + 1) * 8].copy_from_slice(&val.to_le_bytes());
}
dwg_ac21_mirrored_crc64(random1, 64, &bytes)
};
let mut check_data = [0u8; CHECK_DATA_SIZE];
check_data[0..8].copy_from_slice(&normal_crc.to_le_bytes());
check_data[8..16].copy_from_slice(&mirrored_crc.to_le_bytes());
check_data[16..24].copy_from_slice(&random1.to_le_bytes());
check_data[24..32].copy_from_slice(&random2.to_le_bytes());
check_data[32..40].copy_from_slice(&encoded_crc_seed.to_le_bytes());
let meta_bytes = metadata.to_bytes();
debug_assert_eq!(meta_bytes.len(), METADATA_SIZE);
let compressed = compress_ac21(&meta_bytes);
let use_compressed = compressed.len() < meta_bytes.len();
let compr_data = if use_compressed { &compressed } else { &meta_bytes };
let compr_len = if use_compressed {
compr_data.len() as i32
} else {
-(meta_bytes.len() as i32)
};
let compr_crc = dwg_ac21_normal_crc64(0, compr_data.len() as u32, compr_data);
let check_seq_val1 = rng.next_u64();
let check_seq_val2 = encode_value(check_seq_val1, check_seq_val1);
let mut check_seq_bytes = [0u8; 16];
check_seq_bytes[0..8].copy_from_slice(&check_seq_val1.to_le_bytes());
check_seq_bytes[8..16].copy_from_slice(&check_seq_val2.to_le_bytes());
let check_seq_crc = dwg_ac21_normal_crc64_seed1(0, 16, &check_seq_bytes);
let total_rs_data = FILE_HEADER_RS_FACTOR * RS_SYSTEM_K; let mut pre_rs = vec![0u8; total_rs_data];
let mut block = Vec::with_capacity(64);
block.extend_from_slice(&check_seq_crc.to_le_bytes());
block.extend_from_slice(&check_seq_val1.to_le_bytes());
block.extend_from_slice(&compr_crc.to_le_bytes());
block.extend_from_slice(&compr_len.to_le_bytes());
block.extend_from_slice(&0i32.to_le_bytes()); block.extend_from_slice(compr_data);
let padded_block_size = (block.len() + 7) & !7;
while block.len() < padded_block_size {
let b = rng.next_u32() as u8;
block.push(b);
}
pre_rs[..block.len()].copy_from_slice(&block);
if block.len() < total_rs_data {
rng.fill_random(&mut pre_rs[block.len()..total_rs_data]);
}
let rs_encoded_size = FILE_HEADER_RS_FACTOR * RS_N; let mut rs_encoded = vec![0u8; rs_encoded_size];
reed_solomon_encode(
&pre_rs,
&mut rs_encoded,
FILE_HEADER_RS_FACTOR,
RS_SYSTEM_K,
RS_SYSTEM_PRIM_POLY,
);
let mut page = vec![0u8; FILE_HEADER_PAGE_SIZE];
let copy_len = rs_encoded_size.min(RS_DATA_IN_HEADER);
page[..copy_len].copy_from_slice(&rs_encoded[..copy_len]);
if copy_len < RS_DATA_IN_HEADER {
rng.fill_random(&mut page[copy_len..RS_DATA_IN_HEADER]);
}
page[RS_DATA_IN_HEADER..FILE_HEADER_PAGE_SIZE]
.copy_from_slice(&check_data);
Ok(page)
}
fn write_metadata<W: Write + Seek>(
&self,
output: &mut W,
_metadata: &Dwg21CompressedMetadata,
) -> Result<(), DxfError> {
output.seek(SeekFrom::Start(0))?;
let mut buf = [0u8; METADATA_BLOCK_SIZE];
{
let mut cursor = Cursor::new(&mut buf[..]);
cursor.write_all(self.version.as_str().as_bytes())?;
cursor.write_all(&[0u8; 5])?;
cursor.write_all(&[0x19u8])?;
cursor.write_all(&[0x03u8])?;
let preview_addr = self.find_section_page_offset(names::PREVIEW);
cursor.write_u32::<LittleEndian>(preview_addr)?;
cursor.write_all(&[0x1Bu8])?;
cursor.write_all(&[0x19u8])?;
cursor.write_u16::<LittleEndian>(30)?;
cursor.write_all(&[0u8; 3])?;
cursor.write_i32::<LittleEndian>(0)?;
cursor.write_i32::<LittleEndian>(0)?;
let summary_addr = self.find_section_page_offset(names::SUMMARY_INFO);
cursor.write_u32::<LittleEndian>(summary_addr)?;
let vba_addr = self.find_section_page_offset(names::VBA_PROJECT);
cursor.write_u32::<LittleEndian>(vba_addr)?;
cursor.write_u32::<LittleEndian>(0x80)?;
let app_info_addr = self.find_section_page_offset(names::APP_INFO);
cursor.write_u32::<LittleEndian>(app_info_addr)?;
}
output.write_all(&buf)?;
Ok(())
}
fn find_section_page_offset(&self, name: &str) -> u32 {
for section in &self.sections {
if section.name == name {
if let Some(first_page) = section.pages.first() {
for record in &self.page_records {
if record.id == first_page.page_id {
return record.offset as u32;
}
}
}
}
}
0
}
}
#[derive(Debug)]
struct SystemPageResult {
page_id: i64,
#[allow(dead_code)]
offset: u64,
compressed_size: u64,
uncompressed_size: u64,
crc_compressed: u64,
crc_uncompressed: u64,
correction_factor: u64,
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_crc_random_encoder_deterministic() {
let mut rng1 = CrcRandomEncoder::new(42);
let mut rng2 = CrcRandomEncoder::new(42);
for _ in 0..100 {
assert_eq!(rng1.next_u32(), rng2.next_u32());
}
}
#[test]
fn test_crc_random_encoder_different_seeds() {
let mut rng1 = CrcRandomEncoder::new(0x12345678_9ABCDEF0);
let mut rng2 = CrcRandomEncoder::new(0xFEDCBA98_76543210);
let seq1: Vec<u32> = (0..10).map(|_| rng1.next_u32()).collect();
let seq2: Vec<u32> = (0..10).map(|_| rng2.next_u32()).collect();
assert_ne!(seq1, seq2);
}
#[test]
fn test_crc_random_encoder_fill_random() {
let mut rng = CrcRandomEncoder::new(123);
let mut buf = [0u8; 37]; rng.fill_random(&mut buf);
assert!(!buf.iter().all(|&b| b == 0));
}
#[test]
fn test_crc_random_encoder_encode_seed() {
let mut rng = CrcRandomEncoder::new(42);
let seed = 0x12345678_9ABCDEF0u64;
let encoded = rng.encode_crc_seed(seed);
assert_ne!(encoded, seed, "Encoded value should differ from seed due to XOR");
let mut rng2 = CrcRandomEncoder::new(42);
let encoded2 = rng2.encode_crc_seed(seed);
assert_eq!(encoded, encoded2, "Same RNG state should produce same encoding");
}
#[test]
fn test_crc_random_encoder_encode_seed_advances_state() {
let mut rng1 = CrcRandomEncoder::new(42);
let mut rng2 = CrcRandomEncoder::new(42);
rng1.encode_crc_seed(0x12345678_9ABCDEF0);
for _ in 0..7 {
rng2.next_u32();
}
assert_eq!(rng1.next_u32(), rng2.next_u32());
assert_eq!(rng1.next_u32(), rng2.next_u32());
}
#[test]
fn test_system_page_size_minimum() {
assert_eq!(get_system_page_size(8), 0x400);
assert_eq!(get_system_page_size(100), 0x400);
assert_eq!(get_system_page_size(200), 0x400);
}
#[test]
fn test_system_page_size_large_data() {
let size = get_system_page_size(10000);
assert!(size >= 10000 * 2 / RS_SYSTEM_K as u64 * RS_N as u64);
assert_eq!(size % PAGE_ALIGN_SIZE as u64, 0);
}
#[test]
fn test_system_page_size_alignment() {
for data_size in [8, 100, 500, 1000, 5000, 10000, 50000u64] {
let ps = get_system_page_size(data_size);
assert!(
ps == 0x400 || ps % PAGE_ALIGN_SIZE as u64 == 0,
"Page size {ps:#X} for data_size {data_size} not aligned"
);
}
}
#[test]
fn test_encode_value_zero_shift() {
assert_eq!(encode_value(0x1234, 0), 0x1234);
assert_eq!(encode_value(0x1234, 0x20), 0x1234);
}
#[test]
fn test_encode_value_shift_one() {
let result = encode_value(0x8000_0000_0000_0001, 1);
assert_eq!(result, 0x0000_0000_0000_0003);
}
#[test]
fn test_new_reserves_header_space() {
let mut output = Cursor::new(Vec::new());
let _writer = DwgFileHeaderWriterAC21::new(DxfVersion::AC1021, &mut output).unwrap();
assert_eq!(output.position(), RESERVED_HEADER_SIZE as u64);
assert_eq!(output.get_ref().len(), RESERVED_HEADER_SIZE);
assert!(output.get_ref().iter().all(|&b| b == 0));
}
#[test]
fn test_handle_section_offset_always_zero() {
let mut output = Cursor::new(Vec::new());
let writer = DwgFileHeaderWriterAC21::new(DxfVersion::AC1021, &mut output).unwrap();
assert_eq!(writer.handle_section_offset(), 0);
}
#[test]
fn test_add_section_creates_pages() {
let mut output = Cursor::new(Vec::new());
let mut writer = DwgFileHeaderWriterAC21::new(DxfVersion::AC1021, &mut output).unwrap();
let data = vec![0xAA; 100];
writer.add_section(&mut output, names::HEADER, &data).unwrap();
assert_eq!(writer.sections.len(), 1);
assert_eq!(writer.sections[0].pages.len(), 1);
assert_eq!(writer.page_records.len(), 1);
}
#[test]
fn test_add_section_multi_page() {
let mut output = Cursor::new(Vec::new());
let mut writer = DwgFileHeaderWriterAC21::new(DxfVersion::AC1021, &mut output).unwrap();
let data = vec![0xBB; 0x800 * 2 + 100];
writer.add_section(&mut output, names::HEADER, &data).unwrap();
assert_eq!(writer.sections.len(), 1);
assert_eq!(writer.sections[0].pages.len(), 3);
assert_eq!(writer.page_records.len(), 3);
}
#[test]
fn test_add_multiple_sections() {
let mut output = Cursor::new(Vec::new());
let mut writer = DwgFileHeaderWriterAC21::new(DxfVersion::AC1021, &mut output).unwrap();
writer.add_section(&mut output, names::HEADER, &vec![0xAA; 100]).unwrap();
writer.add_section(&mut output, names::CLASSES, &vec![0xBB; 200]).unwrap();
assert_eq!(writer.sections.len(), 2);
assert_eq!(writer.sections[0].name, names::HEADER);
assert_eq!(writer.sections[1].name, names::CLASSES);
}
#[test]
fn test_add_section_unknown_name_fails() {
let mut output = Cursor::new(Vec::new());
let mut writer = DwgFileHeaderWriterAC21::new(DxfVersion::AC1021, &mut output).unwrap();
let result = writer.add_section(&mut output, "AcDb:Nonexistent", &vec![0; 10]);
assert!(result.is_err());
}
#[test]
fn test_build_section_map_format() {
let mut output = Cursor::new(Vec::new());
let mut writer = DwgFileHeaderWriterAC21::new(DxfVersion::AC1021, &mut output).unwrap();
writer.add_section(&mut output, names::HEADER, &vec![0xAA; 100]).unwrap();
let map_data = writer.build_section_map().unwrap();
assert_eq!(map_data.len(), 0x40 + 24 + 56);
let hash = u64::from_le_bytes(map_data[0x18..0x20].try_into().unwrap());
assert_eq!(hash, 0x32B803D9);
let name_len = u64::from_le_bytes(map_data[0x20..0x28].try_into().unwrap());
assert_eq!(name_len, 24, "SectionNameLength should be byte count including null terminator");
}
#[test]
fn test_section_map_name_readable_by_reader() {
let mut output = Cursor::new(Vec::new());
let mut writer = DwgFileHeaderWriterAC21::new(DxfVersion::AC1021, &mut output).unwrap();
writer.add_section(&mut output, names::CLASSES, &vec![0xBB; 50]).unwrap();
let map_data = writer.build_section_map().unwrap();
let name_len = u64::from_le_bytes(map_data[0x20..0x28].try_into().unwrap()) as usize;
let name_bytes = &map_data[0x40..0x40 + name_len];
let words: Vec<u16> = name_bytes
.chunks_exact(2)
.map(|c| u16::from_le_bytes([c[0], c[1]]))
.collect();
let name = String::from_utf16_lossy(&words)
.trim_end_matches('\0')
.to_string();
assert_eq!(name, "AcDb:Classes");
}
#[test]
fn test_build_page_map_format() {
let mut output = Cursor::new(Vec::new());
let mut writer = DwgFileHeaderWriterAC21::new(DxfVersion::AC1021, &mut output).unwrap();
writer.add_section(&mut output, names::HEADER, &vec![0xAA; 100]).unwrap();
let map_data = writer.build_page_map_ordered(100, 101, 0x400);
assert_eq!(map_data.len(), 64);
let first_size = i64::from_le_bytes(map_data[0..8].try_into().unwrap());
let first_id = i64::from_le_bytes(map_data[8..16].try_into().unwrap());
assert_eq!(first_id, 1);
assert!(first_size > 0);
let pm_size = i64::from_le_bytes(map_data[16..24].try_into().unwrap());
let pm_id = i64::from_le_bytes(map_data[24..32].try_into().unwrap());
assert_eq!(pm_size, 0x400);
assert_eq!(pm_id, 100);
let term_size = i64::from_le_bytes(map_data[48..56].try_into().unwrap());
let term_id = i64::from_le_bytes(map_data[56..64].try_into().unwrap());
assert_eq!(term_size, 0);
assert_eq!(term_id, 0);
}
#[test]
fn test_write_complete_file() {
let mut output = Cursor::new(Vec::new());
let mut writer = DwgFileHeaderWriterAC21::new(DxfVersion::AC1021, &mut output).unwrap();
writer.add_section(&mut output, names::HEADER, &vec![0xAA; 100]).unwrap();
writer.add_section(&mut output, names::CLASSES, &vec![0xBB; 50]).unwrap();
writer.add_section(&mut output, names::SUMMARY_INFO, &vec![0; 10]).unwrap();
writer.add_section(&mut output, names::PREVIEW, &vec![0xCC; 20]).unwrap();
writer.add_section(&mut output, names::APP_INFO, &vec![0xDD; 30]).unwrap();
writer.add_section(&mut output, names::AUX_HEADER, &vec![0; 50]).unwrap();
writer.add_section(&mut output, names::ACDB_OBJECTS, &vec![0xEE; 300]).unwrap();
writer.add_section(&mut output, names::HANDLES, &vec![0xFF; 100]).unwrap();
writer.write_file(&mut output).unwrap();
let data = output.into_inner();
assert_eq!(&data[0..6], b"AC1021");
assert!(data.len() > RESERVED_HEADER_SIZE);
assert!(!data[0x80..0x480].iter().all(|&b| b == 0));
}
#[test]
fn test_write_file_metadata_preview_addr() {
let mut output = Cursor::new(Vec::new());
let mut writer = DwgFileHeaderWriterAC21::new(DxfVersion::AC1021, &mut output).unwrap();
writer.add_section(&mut output, names::HEADER, &vec![0xAA; 100]).unwrap();
writer.add_section(&mut output, names::CLASSES, &vec![0xBB; 50]).unwrap();
writer.add_section(&mut output, names::PREVIEW, &vec![0xCC; 20]).unwrap();
writer.write_file(&mut output).unwrap();
let data = output.into_inner();
let preview_addr = u32::from_le_bytes(data[0x0D..0x11].try_into().unwrap());
assert!(preview_addr > 0, "Preview address should be non-zero");
}
#[test]
fn test_file_header_page_has_check_data() {
let mut output = Cursor::new(Vec::new());
let mut writer = DwgFileHeaderWriterAC21::new(DxfVersion::AC1021, &mut output).unwrap();
writer.add_section(&mut output, names::HEADER, &vec![0xAA; 100]).unwrap();
writer.write_file(&mut output).unwrap();
let data = output.into_inner();
let check_data = &data[0x458..0x480];
assert!(!check_data.iter().all(|&b| b == 0),
"Check data should be non-zero");
}
#[test]
fn test_file_header_copy_at_end() {
let mut output = Cursor::new(Vec::new());
let mut writer = DwgFileHeaderWriterAC21::new(DxfVersion::AC1021, &mut output).unwrap();
writer.add_section(&mut output, names::HEADER, &vec![0xAA; 100]).unwrap();
writer.write_file(&mut output).unwrap();
let data = output.into_inner();
let file_header = &data[0x80..0x480];
let header_copy = &data[data.len() - FILE_HEADER_PAGE_SIZE..];
assert_eq!(file_header, header_copy,
"File header copy at end should match header at 0x80");
}
#[test]
fn test_rs_encode_data_page_encoding1_size() {
let data = vec![0xAB; 500];
let encoded = rs_encode_data_page_interleaved(&data, 1);
assert_eq!(encoded.len(), 3 * 255);
}
#[test]
fn test_rs_encode_data_page_encoding4_size() {
let data = vec![0xAB; 500];
let encoded = rs_encode_data_page_interleaved(&data, 4);
assert_eq!(encoded.len(), 3 * 255);
}
#[test]
fn test_rs_encode_data_page_single_block() {
let data = vec![0xCD; 100];
let encoded = rs_encode_data_page_interleaved(&data, 1);
assert_eq!(encoded.len(), 255);
}
#[test]
fn test_rs_encode_data_page_encoding4_single_block() {
let data = vec![0xCD; 100];
let encoded = rs_encode_data_page_interleaved(&data, 4);
assert_eq!(encoded.len(), 255);
}
#[test]
fn test_rs_encode_data_page_roundtrip_encoding4() {
use crate::io::dwg::reed_solomon::reed_solomon_decode;
let original = vec![0x42u8; 300];
let encoded = rs_encode_data_page_interleaved(&original, 4);
let total_size = (300 + 7) & !7; let factor = (total_size + 251 - 1) / 251; assert_eq!(encoded.len(), factor * 255);
let mut decoded = vec![0u8; total_size];
reed_solomon_decode(&encoded, &mut decoded, factor, 251);
assert_eq!(&decoded[..300], &original[..]);
}
#[test]
fn test_rs_encode_data_page_roundtrip_encoding1() {
use crate::io::dwg::reed_solomon::reed_solomon_decode;
let original = vec![0x77u8; 200];
let encoded = rs_encode_data_page_interleaved(&original, 1);
let total_size = (200 + 7) & !7; let factor = (total_size + 251 - 1) / 251; assert_eq!(encoded.len(), factor * 255);
let mut decoded = vec![0u8; total_size];
reed_solomon_decode(&encoded, &mut decoded, factor, 251);
assert_eq!(&decoded[..200], &original[..]);
}
#[test]
fn test_get_aligned_page_size() {
assert_eq!(get_aligned_page_size(0x20), 0x20);
assert_eq!(get_aligned_page_size(0x21), 0x40);
assert_eq!(get_aligned_page_size(0x3F), 0x40);
assert_eq!(get_aligned_page_size(0x40), 0x40);
assert_eq!(get_aligned_page_size(1), 0x20);
}
}