use byteorder::{LittleEndian, WriteBytesExt};
use std::io::{Cursor, Seek, SeekFrom, Write};
use super::section_definition::{ac21_section_info, names};
use crate::error::DxfError;
use crate::io::dwg::compressor_ac21::compress_ac21;
use crate::io::dwg::crc::{
dwg_ac21_check_data_mirrored_crc64, dwg_ac21_check_data_normal_crc64, dwg_ac21_header_crc64,
dwg_ac21_mirrored_crc64, dwg_ac21_normal_crc64, dwg_ac21_normal_crc64_seed1,
dwg_ac21_page_checksum,
};
use crate::io::dwg::dwg21_metadata::{Dwg21CompressedMetadata, METADATA_SIZE};
use crate::io::dwg::reed_solomon::{
reed_solomon_encode, RS_DATA_K, RS_DATA_PRIM_POLY, RS_N, RS_SYSTEM_K, RS_SYSTEM_PRIM_POLY,
};
use crate::types::DxfVersion;
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;
const FILE_RANDOM_SEED: u64 = 0;
struct CrcRandomEncoder {
table: Vec<u32>,
index: usize,
padding: Vec<u32>,
}
impl CrcRandomEncoder {
fn new(seed: u64) -> Self {
let mut table = vec![0u32; 0x270];
table[0] = (seed as u32).wrapping_mul(0x343fd).wrapping_add(0x269ec3);
table[1] = ((seed >> 32) as u32)
.wrapping_mul(0x343fd)
.wrapping_add(0x269ec3);
let mut value = table[1];
for i in 2..0x270usize {
value = ((value >> 30) ^ value)
.wrapping_mul(0x6c078965)
.wrapping_add(i as u32);
table[i] = value;
}
let mut encoder = Self {
table,
index: 0,
padding: vec![0u32; 0x80],
};
encoder.init_padding();
encoder
}
fn init_padding(&mut self) {
for i in 0..0x80 {
self.update_index();
self.padding[i] = self.table[self.index];
self.index += 1;
}
}
fn update_index(&mut self) {
if self.index >= 0x270 {
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_u64(&mut self) -> u64 {
self.index += 2;
self.update_index();
let lo = self.table[self.index] as u64;
let hi = self.table[self.index + 1] as u64;
lo | (hi << 32)
}
fn encode(&mut self, value: u32) -> u64 {
let random = self.next_u64();
let mut lo = (random as u32) & 0xdf7df7dfu32;
let mut hi = ((random >> 32) as u32) & 0xf7df7df7u32;
if value & 0x200 != 0 {
lo |= 0x20;
} if value & 0x100 != 0 {
lo |= 0x800;
} if value & 0x80 != 0 {
lo |= 0x20000;
} if value & 0x40 != 0 {
lo |= 0x800000;
} if value & 0x20 != 0 {
lo |= 0x20000000;
} if value & 0x10 != 0 {
hi |= 0x08;
} if value & 0x8 != 0 {
hi |= 0x200;
} if value & 0x4 != 0 {
hi |= 0x8000;
} if value & 0x2 != 0 {
hi |= 0x200000;
} if value & 0x1 != 0 {
hi |= 0x8000000;
}
(lo as u64) | ((hi as u64) << 32)
}
fn encode_crc_seed(&mut self, seed: u64) -> u64 {
self.encode(seed as u32)
}
fn fill_random(&mut self, buffer: &mut [u8]) {
let mut buf_idx = 0;
let mut pad_idx = 0;
while buf_idx + 4 <= buffer.len() {
let val = if pad_idx < self.padding.len() {
let v = self.padding[pad_idx];
pad_idx += 1;
v
} else {
self.index += 1;
self.update_index();
self.table[self.index]
};
buffer[buf_idx..buf_idx + 4].copy_from_slice(&val.to_le_bytes());
buf_idx += 4;
}
if buf_idx < buffer.len() {
let val = if pad_idx < self.padding.len() {
self.padding[pad_idx]
} else {
self.index += 1;
self.update_index();
self.table[self.index]
};
let bytes = val.to_le_bytes();
for j in 0..(buffer.len() - buf_idx) {
buffer[buf_idx + 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,
}
struct EncodedDataPage {
bytes: Vec<u8>,
uncompressed_size: u64,
compressed_size: u64,
checksum: u64,
crc: u64,
}
fn encode_data_page(data: &[u8], encoding: u64, skip_lz77: bool) -> EncodedDataPage {
let checksum = dwg_ac21_page_checksum(0, data) as u64;
if encoding == 1 {
return EncodedDataPage {
bytes: rs_encode_data_page_non_interleaved(data, FILE_RANDOM_SEED),
uncompressed_size: data.len() as u64,
compressed_size: data.len() as u64,
checksum,
crc: dwg_ac21_mirrored_crc64(0, data.len() as u32, data),
};
}
let compressed = compress_ac21(data);
let page_data = if skip_lz77 || compressed.len() >= data.len() {
data
} else {
compressed.as_slice()
};
EncodedDataPage {
bytes: rs_encode_data_page_interleaved(page_data, encoding),
uncompressed_size: data.len() as u64,
compressed_size: page_data.len() as u64,
checksum,
crc: dwg_ac21_mirrored_crc64(0, page_data.len() as u32, page_data),
}
}
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_non_interleaved(data: &[u8], random_seed: u64) -> Vec<u8> {
let aligned_size = (data.len() + CRC_BLOCK_SIZE - 1) & !(CRC_BLOCK_SIZE - 1);
let factor = (aligned_size + RS_DATA_K - 1) / RS_DATA_K;
let mut padded = vec![0u8; factor * RS_DATA_K];
padded[..data.len()].copy_from_slice(data);
CrcRandomEncoder::new(random_seed).fill_random(&mut padded[aligned_size..]);
let mut encoded = vec![0u8; factor * RS_N];
reed_solomon_encode(&padded, &mut encoded, factor, RS_DATA_K, RS_DATA_PRIM_POLY);
let mut output = padded[..aligned_size].to_vec();
for block in 0..factor {
for symbol in RS_DATA_K..RS_N {
output.push(encoded[symbol * factor + block]);
}
}
output
}
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 perf = std::env::var_os("PERF").is_some();
let started = web_time::Instant::now();
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 mut max_page_size = ac21_section_info::page_size(name).unwrap_or(0xF800);
if name == names::PREVIEW {
max_page_size = max_page_size.max(align32(data.len()) as u64);
}
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;
use crate::io::dwg::parallel::{map_chunks_indexed, worker_count};
let skip_lz77 = self.skip_lz77;
let batch_pages = worker_count().saturating_mul(2);
let batch_bytes = page_size.saturating_mul(batch_pages).max(page_size);
for (batch_index, batch) in data.chunks(batch_bytes).enumerate() {
let first_page = batch_index.saturating_mul(batch_pages);
let encoded: Vec<_> = map_chunks_indexed(batch, page_size, |index, bytes| {
(
(first_page + index) as u64 * max_page_size,
encode_data_page(bytes, encoding, skip_lz77),
)
});
for (offset, page) in encoded {
let page_record =
self.write_encoded_data_page(output, page, offset, max_page_size)?;
section.pages.push(page_record);
}
}
let pages = section.pages.len();
self.sections.push(section);
if perf {
eprintln!(
"[perf] dwg-write-section name={} format=ac21 time={:.1}ms bytes={} pages={} encoding={}",
name,
started.elapsed().as_secs_f64() * 1000.0,
data.len(),
pages,
encoding,
);
}
Ok(())
}
pub fn write_file<W: Write + Seek>(&mut self, output: &mut W) -> Result<(), DxfError> {
self.crc_seed = 0;
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_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;
let random_seed = FILE_RANDOM_SEED;
metadata.random_seed = random_seed;
metadata.crc_seed = self.crc_seed; let mut rng = CrcRandomEncoder::new(random_seed);
metadata.sections_map_crc_seed = rng.encode_crc_seed(self.crc_seed);
metadata.pages_map_crc_seed = rng.encode_crc_seed(self.crc_seed);
let check_random1 = rng.next_u64();
let check_random2 = rng.next_u64();
let check_encoded_seed = rng.encode_crc_seed(self.crc_seed);
let check_normal_crc = {
let mut buf = [0u64; 8];
buf[0] = encode_value(check_random1, check_random2);
buf[1] = encode_value(buf[0], buf[0]);
buf[2] = encode_value(check_random2, buf[1]);
buf[3] = encode_value(buf[2], buf[2]);
buf[4] = encode_value(check_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_check_data_normal_crc64(check_random2, &bytes)
};
let check_mirrored_crc = {
let mut buf = [0u64; 8];
buf[0] = encode_value(check_random1, check_random2);
buf[1] = encode_value(check_normal_crc, buf[0]);
buf[2] = encode_value(check_random2, buf[1]);
buf[3] = encode_value(check_normal_crc, buf[2]);
buf[4] = encode_value(check_random1, buf[3]);
buf[5] = encode_value(check_normal_crc, buf[4]);
buf[6] = encode_value(check_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_check_data_mirrored_crc64(check_random1, &bytes)
};
let mut check_data = [0u8; CHECK_DATA_SIZE];
check_data[0..8].copy_from_slice(&check_normal_crc.to_le_bytes());
check_data[8..16].copy_from_slice(&check_mirrored_crc.to_le_bytes());
check_data[16..24].copy_from_slice(&check_random1.to_le_bytes());
check_data[24..32].copy_from_slice(&check_random2.to_le_bytes());
check_data[32..40].copy_from_slice(&check_encoded_seed.to_le_bytes());
metadata.crc_seed_encoded = rng.encode_crc_seed(self.crc_seed);
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, &check_data)?;
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(random_seed);
metadata.sections_map_crc_seed = rng2.encode_crc_seed(self.crc_seed); metadata.pages_map_crc_seed = rng2.encode_crc_seed(self.crc_seed); let check_random1_2 = rng2.next_u64();
let check_random2_2 = rng2.next_u64();
let check_encoded_seed_2 = rng2.encode_crc_seed(self.crc_seed);
let check_normal_crc_2 = {
let mut buf = [0u64; 8];
buf[0] = encode_value(check_random1_2, check_random2_2);
buf[1] = encode_value(buf[0], buf[0]);
buf[2] = encode_value(check_random2_2, buf[1]);
buf[3] = encode_value(buf[2], buf[2]);
buf[4] = encode_value(check_random1_2, 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_check_data_normal_crc64(check_random2_2, &bytes)
};
let check_mirrored_crc_2 = {
let mut buf = [0u64; 8];
buf[0] = encode_value(check_random1_2, check_random2_2);
buf[1] = encode_value(check_normal_crc_2, buf[0]);
buf[2] = encode_value(check_random2_2, buf[1]);
buf[3] = encode_value(check_normal_crc_2, buf[2]);
buf[4] = encode_value(check_random1_2, buf[3]);
buf[5] = encode_value(check_normal_crc_2, buf[4]);
buf[6] = encode_value(check_random2_2, 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_check_data_mirrored_crc64(check_random1_2, &bytes)
};
let mut check_data_2 = [0u8; CHECK_DATA_SIZE];
check_data_2[0..8].copy_from_slice(&check_normal_crc_2.to_le_bytes());
check_data_2[8..16].copy_from_slice(&check_mirrored_crc_2.to_le_bytes());
check_data_2[16..24].copy_from_slice(&check_random1_2.to_le_bytes());
check_data_2[24..32].copy_from_slice(&check_random2_2.to_le_bytes());
check_data_2[32..40].copy_from_slice(&check_encoded_seed_2.to_le_bytes());
metadata.crc_seed_encoded = rng2.encode_crc_seed(self.crc_seed); 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, &check_data_2)?;
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_encoded_data_page<W: Write + Seek>(
&mut self,
output: &mut W,
page: EncodedDataPage,
data_offset: u64,
max_page_size: u64,
) -> Result<AC21SectionPageRecord, DxfError> {
let page_id = self.next_page_id;
self.next_page_id += 1;
let aligned_size = align32(page.bytes.len());
let offset = output.seek(SeekFrom::Current(0))?;
output.write_all(&page.bytes)?;
let padding = aligned_size - page.bytes.len();
if padding > 0 {
output.write_all(&[0u8; PAGE_ALIGN_SIZE][..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: page.uncompressed_size,
compressed_size: page.compressed_size,
checksum: page.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];
if aligned_comp != 0 {
for copy in padded_data[..total_size].chunks_mut(aligned_comp) {
copy[..page_data.len()].copy_from_slice(page_data);
}
}
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,
check_data: &[u8; CHECK_DATA_SIZE],
) -> Result<Vec<u8>, DxfError> {
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;
if block.len() < padded_block_size {
let pad_needed = padded_block_size - block.len();
let mut pad_buf = vec![0u8; pad_needed];
rng.fill_random(&mut pad_buf);
block.extend_from_slice(&pad_buf);
}
let max_copies = total_rs_data / block.len();
let mut offset = 0usize;
for _ in 0..max_copies {
pre_rs[offset..offset + block.len()].copy_from_slice(&block);
offset += block.len();
}
if offset < total_rs_data {
rng.fill_random(&mut pre_rs[offset..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..50 {
assert_eq!(rng1.next_u64(), rng2.next_u64());
}
}
#[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<u64> = (0..10).map(|_| rng1.next_u64()).collect();
let seq2: Vec<u64> = (0..10).map(|_| rng2.next_u64()).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);
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_advances_state() {
let mut rng1 = CrcRandomEncoder::new(42);
let mut rng2 = CrcRandomEncoder::new(42);
rng1.encode_crc_seed(0);
rng2.next_u64();
assert_eq!(rng1.next_u64(), rng2.next_u64());
}
#[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_section_page_sizes_match_page_records() {
let mut output = Cursor::new(Vec::new());
let mut writer = DwgFileHeaderWriterAC21::new(DxfVersion::AC1021, &mut output).unwrap();
let data = vec![0x5A; 0xF800 * 2 + 321];
writer
.add_section(&mut output, names::ACDB_OBJECTS, &data)
.unwrap();
let section = &writer.sections[0];
assert_eq!(section.pages.len(), writer.page_records.len());
let expected_max = section.max_page_size;
for page in §ion.pages {
assert_eq!(
page.page_size, expected_max,
"section map page_size should be max_page_size"
);
let _record = writer
.page_records
.iter()
.find(|r| r.id == page.page_id)
.expect("missing page record for section page");
}
}
#[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 non_interleaved_parity_matches_r2007_fixture() {
let data = [
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x41, 0x00, 0x75, 0x00, 0x74, 0x00,
0x6F, 0x00, 0x64, 0x00, 0x65, 0x00, 0x73, 0x00, 0x6B, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD2, 0x8E, 0x0B, 0x00,
0xAF, 0x65, 0x25, 0x00, 0x7D, 0xB7, 0xD9, 0x03, 0x0F, 0x71, 0x25, 0x00, 0xF8, 0x06,
0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let encoded = rs_encode_data_page_non_interleaved(&data, 15_793_935_738_994_308_941);
assert_eq!(encoded.len(), 92);
assert_eq!(&encoded[..82], &data);
assert_eq!(&encoded[82..88], &[0; 6]);
assert_eq!(&encoded[88..], &[0x05, 0xEF, 0x99, 0xA4]);
}
#[test]
fn stored_pages_have_valid_rs_codewords_at_block_boundaries() {
fn multiply(mut a: u8, mut b: u8) -> u8 {
let mut product = 0;
while b != 0 {
if b & 1 != 0 {
product ^= a;
}
a = (a << 1) ^ if a & 0x80 != 0 { 0x1D } else { 0 };
b >>= 1;
}
product
}
for size in [
0, 1, 7, 8, 248, 249, 250, 251, 252, 500, 502, 503, 768, 4097,
] {
let data: Vec<u8> = (0..size).map(|i| (i * 73 + 11) as u8).collect();
let page = encode_data_page(&data, 1, false);
let aligned = (size + 7) & !7;
let blocks = (aligned + RS_DATA_K - 1) / RS_DATA_K;
assert_eq!(page.bytes.len(), aligned + blocks * 4, "size={size}");
assert_eq!(&page.bytes[..size], &data);
assert!(page.bytes[size..aligned].iter().all(|&b| b == 0));
assert_eq!(page.compressed_size, size as u64);
assert_eq!(page.uncompressed_size, size as u64);
assert_eq!(page.crc, dwg_ac21_mirrored_crc64(0, size as u32, &data));
let mut padded = page.bytes[..aligned].to_vec();
padded.resize(blocks * RS_DATA_K, 0);
CrcRandomEncoder::new(FILE_RANDOM_SEED).fill_random(&mut padded[aligned..]);
for block in 0..blocks {
let mut codeword = padded[block * RS_DATA_K..(block + 1) * RS_DATA_K].to_vec();
codeword
.extend_from_slice(&page.bytes[aligned + block * 4..aligned + (block + 1) * 4]);
for exponent in 251..255 {
let root = (0..exponent).fold(1, |value, _| multiply(value, 2));
let syndrome = codeword
.iter()
.fold(0, |value, &symbol| multiply(value, root) ^ symbol);
assert_eq!(syndrome, 0, "size={size}, block={block}, root={exponent}");
}
}
}
}
#[test]
fn system_pages_store_every_advertised_copy() {
use crate::io::dwg::reed_solomon::reed_solomon_decode;
let mut output = Cursor::new(Vec::new());
let writer = DwgFileHeaderWriterAC21::new(DxfVersion::AC1021, &mut output).unwrap();
for size in [64, 2048, 8192] {
let data: Vec<u8> = (0..size).map(|i| (i * 73 + i / 17) as u8).collect();
let page_size = get_system_page_size(size as u64) as usize;
let (encoded, compressed_size, _, compressed_crc, _, repeats) =
writer.encode_system_page(&data, page_size);
let blocks = encoded.len() / RS_N;
let mut decoded = vec![0; blocks * RS_SYSTEM_K];
reed_solomon_decode(&encoded, &mut decoded, blocks, RS_SYSTEM_K);
let stride = (compressed_size as usize + 7) & !7;
assert!(repeats >= 2);
let first = &decoded[..stride];
assert_eq!(
dwg_ac21_mirrored_crc64(
0,
compressed_size as u32,
&first[..compressed_size as usize]
),
compressed_crc
);
for copy in decoded[..stride * repeats as usize].chunks_exact(stride) {
assert_eq!(copy, first);
}
}
}
#[test]
fn test_rs_encode_data_page_encoding1_size() {
let data = vec![0xAB; 500];
let encoded = encode_data_page(&data, 1, false).bytes;
assert_eq!(encoded.len(), 504 + 3 * 4);
}
#[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 = encode_data_page(&data, 1, false).bytes;
assert_eq!(encoded.len(), 104 + 4);
}
#[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_compact;
let original = vec![0x77u8; 200];
let encoded = encode_data_page(&original, 1, false).bytes;
let total_size = (200 + 7) & !7; let factor = (total_size + 251 - 1) / 251; assert_eq!(encoded.len(), total_size + factor * 4);
let decoded = reed_solomon_decode_compact(&encoded, original.len());
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);
}
}