use crate::io::IoError;
use crate::types::SdfTree;
use std::fs::File;
use std::io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write};
use std::path::Path;
pub const ASDF_MAGIC: [u8; 4] = *b"ASDF";
pub const ASDF_VERSION: u16 = 1;
const ASDF_BINCODE_LIMIT: usize = 256 * 1024 * 1024;
#[derive(Debug, Clone, Copy)]
pub struct AsdfHeader {
pub magic: [u8; 4],
pub version: u16,
pub flags: u16,
pub node_count: u32,
pub crc32: u32,
}
impl AsdfHeader {
pub fn new(tree: &SdfTree, body_crc: u32) -> Self {
Self {
magic: ASDF_MAGIC,
version: ASDF_VERSION,
flags: 0,
node_count: tree.node_count(),
crc32: body_crc,
}
}
#[inline(always)]
pub fn to_bytes(&self) -> [u8; 16] {
let mut bytes = [0u8; 16];
bytes[0..4].copy_from_slice(&self.magic);
bytes[4..6].copy_from_slice(&self.version.to_le_bytes());
bytes[6..8].copy_from_slice(&self.flags.to_le_bytes());
bytes[8..12].copy_from_slice(&self.node_count.to_le_bytes());
bytes[12..16].copy_from_slice(&self.crc32.to_le_bytes());
bytes
}
#[inline(always)]
pub fn from_bytes(bytes: &[u8; 16]) -> Result<Self, IoError> {
let magic: [u8; 4] = bytes[0..4].try_into().unwrap();
if magic != ASDF_MAGIC {
return Err(IoError::InvalidFormat(format!(
"Invalid magic bytes: {:?}",
magic
)));
}
let version = u16::from_le_bytes(bytes[4..6].try_into().unwrap());
if version > ASDF_VERSION {
return Err(IoError::UnsupportedVersion(version));
}
let flags = u16::from_le_bytes(bytes[6..8].try_into().unwrap());
let node_count = u32::from_le_bytes(bytes[8..12].try_into().unwrap());
let crc32 = u32::from_le_bytes(bytes[12..16].try_into().unwrap());
Ok(Self {
magic,
version,
flags,
node_count,
crc32,
})
}
}
struct CrcWriter<W: Write> {
inner: W,
hasher: crc32fast::Hasher,
}
impl<W: Write> CrcWriter<W> {
#[inline(always)]
fn new(inner: W) -> Self {
Self {
inner,
hasher: crc32fast::Hasher::new(),
}
}
#[inline(always)]
fn finalize(self) -> u32 {
self.hasher.finalize()
}
}
impl<W: Write> Write for CrcWriter<W> {
#[inline(always)]
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let n = self.inner.write(buf)?;
self.hasher.update(&buf[..n]);
Ok(n)
}
#[inline(always)]
fn flush(&mut self) -> std::io::Result<()> {
self.inner.flush()
}
}
#[allow(dead_code)]
struct CrcReader<R: Read> {
inner: R,
hasher: crc32fast::Hasher,
}
#[allow(dead_code)]
impl<R: Read> CrcReader<R> {
#[inline(always)]
fn new(inner: R) -> Self {
Self {
inner,
hasher: crc32fast::Hasher::new(),
}
}
#[inline(always)]
fn finalize(self) -> u32 {
self.hasher.finalize()
}
}
impl<R: Read> Read for CrcReader<R> {
#[inline(always)]
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let n = self.inner.read(buf)?;
self.hasher.update(&buf[..n]);
Ok(n)
}
}
pub fn save_asdf(tree: &SdfTree, path: impl AsRef<Path>) -> Result<(), IoError> {
let file = File::create(path)?;
let mut writer = BufWriter::new(file);
let placeholder = AsdfHeader {
magic: ASDF_MAGIC,
version: ASDF_VERSION,
flags: 0,
node_count: tree.node_count(),
crc32: 0,
};
writer.write_all(&placeholder.to_bytes())?;
let mut crc_writer = CrcWriter::new(&mut writer);
bincode::serde::encode_into_std_write(
tree,
&mut crc_writer,
bincode::config::legacy().with_limit::<ASDF_BINCODE_LIMIT>(),
)
.map_err(|e| IoError::Serialization(e.to_string()))?;
let crc = crc_writer.finalize();
writer.seek(SeekFrom::Start(0))?;
let real_header = AsdfHeader::new(tree, crc);
writer.write_all(&real_header.to_bytes())?;
writer.flush()?;
Ok(())
}
pub fn load_asdf(path: impl AsRef<Path>) -> Result<SdfTree, IoError> {
let file = File::open(path)?;
let mut reader = BufReader::new(file);
let mut header_bytes = [0u8; 16];
reader.read_exact(&mut header_bytes)?;
let header = AsdfHeader::from_bytes(&header_bytes)?;
let mut body = Vec::new();
reader.read_to_end(&mut body)?;
let actual_crc = crc32fast::hash(&body);
if actual_crc != header.crc32 {
return Err(IoError::CrcMismatch {
expected: header.crc32,
actual: actual_crc,
});
}
let (tree, _consumed): (SdfTree, usize) = bincode::serde::decode_from_slice(
&body,
bincode::config::legacy().with_limit::<ASDF_BINCODE_LIMIT>(),
)
.map_err(|e| IoError::Serialization(e.to_string()))?;
Ok(tree)
}
pub fn read_header(path: impl AsRef<Path>) -> Result<AsdfHeader, IoError> {
let mut file = File::open(path)?;
let mut header_bytes = [0u8; 16];
file.read_exact(&mut header_bytes)?;
AsdfHeader::from_bytes(&header_bytes)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::SdfNode;
use std::fs;
fn temp_path(name: &str) -> std::path::PathBuf {
let mut path = std::env::temp_dir();
path.push(format!("alice_sdf_asdf_{}", name));
path
}
#[test]
fn test_header_roundtrip() {
let tree = SdfTree::new(SdfNode::sphere(1.0));
let header = AsdfHeader::new(&tree, 0x12345678);
let bytes = header.to_bytes();
let parsed = AsdfHeader::from_bytes(&bytes).unwrap();
assert_eq!(parsed.magic, ASDF_MAGIC);
assert_eq!(parsed.version, ASDF_VERSION);
assert_eq!(parsed.node_count, 1);
assert_eq!(parsed.crc32, 0x12345678);
}
#[test]
fn test_save_load_sphere() {
let tree = SdfTree::new(SdfNode::sphere(2.5));
let path = temp_path("sphere.asdf");
save_asdf(&tree, &path).unwrap();
let loaded = load_asdf(&path).unwrap();
assert_eq!(loaded.node_count(), 1);
fs::remove_file(&path).ok();
}
#[test]
fn test_save_load_complex() {
let shape = SdfNode::sphere(1.0)
.subtract(SdfNode::box3d(1.0, 1.0, 1.0))
.translate(1.0, 2.0, 3.0)
.twist(0.5);
let tree = SdfTree::new(shape);
let path = temp_path("complex.asdf");
save_asdf(&tree, &path).unwrap();
let loaded = load_asdf(&path).unwrap();
assert_eq!(loaded.node_count(), tree.node_count());
fs::remove_file(&path).ok();
}
#[test]
fn test_crc_verification() {
let tree = SdfTree::new(SdfNode::sphere(1.0));
let path = temp_path("crc_test.asdf");
save_asdf(&tree, &path).unwrap();
let mut data = fs::read(&path).unwrap();
if data.len() > 20 {
data[20] ^= 0xFF; }
fs::write(&path, &data).unwrap();
let result = load_asdf(&path);
assert!(matches!(result, Err(IoError::CrcMismatch { .. })));
fs::remove_file(&path).ok();
}
#[test]
fn test_invalid_magic() {
let bytes = [b'X', b'X', b'X', b'X', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let result = AsdfHeader::from_bytes(&bytes);
assert!(matches!(result, Err(IoError::InvalidFormat(_))));
}
#[test]
fn test_malformed_body_no_panic() {
let path = temp_path("malformed_body.asdf");
let bytes = [
0x41, 0x53, 0x44, 0x46, 0x00, 0x00, 0x00, 0x00, 0x10, 0x28, 0xdf, 0x28, 0x37, 0xe4, 0xe7, 0x2d, 0x28, 0x00, 0x00, 0x10, 0x28, 0xdf, 0x28, 0xd2, 0x28, 0x28, 0x32, 0x28,
];
fs::write(&path, bytes).unwrap();
let result = load_asdf(&path);
assert!(result.is_err(), "malformed body should return Err");
fs::remove_file(&path).ok();
}
#[test]
fn test_streaming_large_tree() {
let mut shape = SdfNode::sphere(1.0);
for i in 0..10 {
shape = shape.union(SdfNode::box3d(1.0, 1.0, 1.0).translate(i as f32, 0.0, 0.0));
}
let tree = SdfTree::new(shape);
let path = temp_path("streaming_large.asdf");
save_asdf(&tree, &path).unwrap();
let loaded = load_asdf(&path).unwrap();
assert_eq!(loaded.node_count(), tree.node_count());
fs::remove_file(&path).ok();
}
}