use alloc::vec::Vec;
use crate::bau::Bau;
use crate::property::PropertyId;
use crate::table_object::TableObject;
const PROGRAM_VERSION_SIZE: usize = 5;
const MEMORY_LENGTH_SIZE: usize = 4;
const TABLE_OBJECT_COUNT: usize = 3;
const HEADER_SIZE: usize =
TableObject::SAVE_SIZE * TABLE_OBJECT_COUNT + PROGRAM_VERSION_SIZE + MEMORY_LENGTH_SIZE;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PersistenceError {
TruncatedData,
InvalidTableObject,
MemoryTooLarge,
}
impl core::fmt::Display for PersistenceError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::TruncatedData => write!(f, "persistence data truncated"),
Self::InvalidTableObject => write!(f, "invalid table object in persistence data"),
Self::MemoryTooLarge => write!(f, "memory area exceeds maximum size"),
}
}
}
impl core::error::Error for PersistenceError {}
pub fn save_bau_state(bau: &Bau) -> Vec<u8> {
let memory = bau.memory_area();
let mut buf = Vec::with_capacity(HEADER_SIZE + memory.len());
bau.addr_table_object.save(&mut buf);
bau.assoc_table_object.save(&mut buf);
bau.app_program_object.save(&mut buf);
let mut ver = Vec::new();
if let Some(obj) = bau.object(3) {
obj.read_property(PropertyId::ProgramVersion, 1, 1, &mut ver);
}
ver.resize(PROGRAM_VERSION_SIZE, 0);
buf.extend_from_slice(&ver);
let mem_len = u32::try_from(memory.len()).unwrap_or(u32::MAX);
buf.extend_from_slice(&mem_len.to_le_bytes());
buf.extend_from_slice(memory);
buf
}
pub fn restore_bau_state(bau: &mut Bau, data: &[u8]) -> Result<(), PersistenceError> {
if data.len() < HEADER_SIZE {
return Err(PersistenceError::TruncatedData);
}
let mut offset = 0;
let n = bau.addr_table_object.restore(&data[offset..]);
if n == 0 {
return Err(PersistenceError::InvalidTableObject);
}
offset += n;
let n = bau.assoc_table_object.restore(&data[offset..]);
if n == 0 {
return Err(PersistenceError::InvalidTableObject);
}
offset += n;
let n = bau.app_program_object.restore(&data[offset..]);
if n == 0 {
return Err(PersistenceError::InvalidTableObject);
}
offset += n;
if offset + PROGRAM_VERSION_SIZE > data.len() {
return Err(PersistenceError::TruncatedData);
}
if let Some(obj) = bau.object_mut(3) {
obj.write_property(
PropertyId::ProgramVersion,
1,
1,
&data[offset..offset + PROGRAM_VERSION_SIZE],
);
}
offset += PROGRAM_VERSION_SIZE;
if offset + MEMORY_LENGTH_SIZE > data.len() {
return Err(PersistenceError::TruncatedData);
}
let mem_len = u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
]) as usize;
offset += MEMORY_LENGTH_SIZE;
if data.len() < offset + mem_len {
return Err(PersistenceError::MemoryTooLarge);
}
bau.set_memory_area(data[offset..offset + mem_len].to_vec());
let addr_data = bau.addr_table_object.data(bau.memory_area()).to_vec();
if !addr_data.is_empty() {
bau.address_table.load(&addr_data);
}
let assoc_data = bau.assoc_table_object.data(bau.memory_area()).to_vec();
if !assoc_data.is_empty() {
bau.association_table.load(&assoc_data);
}
Ok(())
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use crate::device_object;
fn test_bau() -> Bau {
let device =
device_object::new_device_object([0x00, 0xFA, 0x01, 0x02, 0x03, 0x04], [0x00; 6]);
let mut bau = Bau::new(device, 3, 1);
device_object::set_individual_address(bau.device_mut(), 0x1101);
bau
}
#[test]
fn save_restore_roundtrip() {
let mut bau = test_bau();
bau.addr_table_object.handle_load_event(&[1], 0); let alc = [0x03, 0x0B, 0x00, 0x00, 0x00, 0x06, 0x01, 0x00];
bau.addr_table_object.handle_load_event(&alc, 0);
bau.set_memory_area(alloc::vec![0x00, 0x02, 0x08, 0x01, 0x08, 0x02]);
bau.addr_table_object.handle_load_event(&[2], 6);
let addr_data = bau.addr_table_object.data(bau.memory_area()).to_vec();
bau.address_table.load(&addr_data);
assert_eq!(bau.address_table.get_tsap(0x0801), Some(1));
let saved = save_bau_state(&bau);
let mut bau2 = test_bau();
restore_bau_state(&mut bau2, &saved).unwrap();
assert_eq!(bau2.address_table.get_tsap(0x0801), Some(1));
assert_eq!(bau2.address_table.get_tsap(0x0802), Some(2));
assert_eq!(bau2.memory_area(), &[0x00, 0x02, 0x08, 0x01, 0x08, 0x02]);
}
#[test]
fn restore_truncated_data_fails() {
let mut bau = test_bau();
let short_data = [0u8; HEADER_SIZE - 1];
assert_eq!(
restore_bau_state(&mut bau, &short_data),
Err(PersistenceError::TruncatedData)
);
}
#[test]
fn restore_corrupted_header_fails() {
let mut bau = test_bau();
let mut data = alloc::vec![0u8; HEADER_SIZE];
let mem_len_offset = HEADER_SIZE - MEMORY_LENGTH_SIZE;
data[mem_len_offset..HEADER_SIZE].copy_from_slice(&999u32.to_le_bytes());
assert_eq!(
restore_bau_state(&mut bau, &data),
Err(PersistenceError::MemoryTooLarge)
);
}
#[test]
fn restore_corrupted_bytes_in_middle() {
let mut bau = test_bau();
bau.addr_table_object.handle_load_event(&[1], 0);
let alc = [0x03, 0x0B, 0x00, 0x00, 0x00, 0x06, 0x01, 0x00];
bau.addr_table_object.handle_load_event(&alc, 0);
bau.set_memory_area(alloc::vec![0x00, 0x02, 0x08, 0x01, 0x08, 0x02]);
bau.addr_table_object.handle_load_event(&[2], 6);
let mut saved = save_bau_state(&bau);
let mem_len_offset = HEADER_SIZE - MEMORY_LENGTH_SIZE;
saved[mem_len_offset..mem_len_offset + 4].copy_from_slice(&0xFFFF_FFFFu32.to_le_bytes());
let mut bau2 = test_bau();
assert_eq!(
restore_bau_state(&mut bau2, &saved),
Err(PersistenceError::MemoryTooLarge),
"corrupted mem_len should cause MemoryTooLarge error"
);
}
#[test]
fn restore_memory_exceeds_max_size() {
let mut bau = test_bau();
let mut data = alloc::vec![0u8; HEADER_SIZE + 10];
let mem_len_offset = HEADER_SIZE - MEMORY_LENGTH_SIZE;
data[mem_len_offset..mem_len_offset + 4].copy_from_slice(&100u32.to_le_bytes());
assert_eq!(
restore_bau_state(&mut bau, &data),
Err(PersistenceError::MemoryTooLarge),
"mem_len exceeding available data should fail"
);
}
#[test]
fn save_during_loading_state() {
let mut bau = test_bau();
bau.addr_table_object.handle_load_event(&[1], 0); assert_eq!(
bau.addr_table_object.load_state(),
crate::property::LoadState::Loading
);
let saved = save_bau_state(&bau);
let mut bau2 = test_bau();
restore_bau_state(&mut bau2, &saved).unwrap();
assert_eq!(
bau2.addr_table_object.load_state(),
crate::property::LoadState::Loading,
"Loading state should be preserved across save/restore"
);
}
#[test]
fn restore_with_shorter_memory_than_table_offsets() {
let mut bau = test_bau();
bau.addr_table_object.handle_load_event(&[1], 0);
let alc = [0x03, 0x0B, 0x00, 0x00, 0x00, 0x0A, 0x01, 0x00];
bau.addr_table_object.handle_load_event(&alc, 50);
bau.set_memory_area(alloc::vec![0xAA; 60]);
bau.addr_table_object.handle_load_event(&[2], 60);
let saved = save_bau_state(&bau);
let header_size = HEADER_SIZE;
let mut truncated = saved[..header_size].to_vec();
let mem_len_offset = header_size - MEMORY_LENGTH_SIZE;
truncated[mem_len_offset..mem_len_offset + 4].copy_from_slice(&40u32.to_le_bytes());
truncated.extend_from_slice(&[0xBB; 40]);
let mut bau2 = test_bau();
restore_bau_state(&mut bau2, &truncated).unwrap();
assert_eq!(
bau2.addr_table_object.data(bau2.memory_area()),
&[] as &[u8]
);
}
#[test]
fn restore_preserves_program_version() {
use crate::property::PropertyId;
let mut bau = test_bau();
if let Some(obj) = bau.object_mut(3) {
obj.write_property(PropertyId::ProgramVersion, 1, 1, &[1, 2, 3, 4, 5]);
}
let saved = save_bau_state(&bau);
let mut bau2 = test_bau();
restore_bau_state(&mut bau2, &saved).unwrap();
let mut ver = alloc::vec::Vec::new();
if let Some(obj) = bau2.object(3) {
obj.read_property(PropertyId::ProgramVersion, 1, 1, &mut ver);
}
assert_eq!(ver, &[1, 2, 3, 4, 5]);
}
}