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)
);
}
}