use alloc::vec::Vec;
use alloc::string::String;
use crate::error::Result;
use crate::flash::{Flash, PageId};
use crate::format::Module;
pub mod journal;
pub mod layout;
pub mod wear_leveling;
pub use journal::{Journal, Transaction};
pub use layout::StorageLayout;
pub use wear_leveling::WearLevelingPolicy;
#[derive(Clone, Debug)]
pub struct StorageConfig {
pub module_slots: usize,
pub enable_journaling: bool,
pub enable_wear_leveling: bool,
pub max_module_size: usize,
}
impl Default for StorageConfig {
fn default() -> Self {
Self {
module_slots: 3, enable_journaling: true,
enable_wear_leveling: true,
max_module_size: 32768, }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum InstallationStatus {
Empty,
Valid,
Pending,
Corrupted,
}
pub struct StorageManager<'f> {
flash: &'f mut Flash,
config: StorageConfig,
layout: StorageLayout,
journal: Option<Journal>,
}
impl<'f> StorageManager<'f> {
pub fn new(flash: &'f mut Flash, config: StorageConfig) -> Result<Self> {
let layout = StorageLayout::new(flash.capacity(), &config)?;
let journal = if config.enable_journaling {
Some(Journal::new(layout.journal_region()))
} else {
None
};
Ok(Self {
flash,
config,
layout,
journal,
})
}
pub fn read_module(&self, slot: usize) -> Result<Option<Module>> {
if slot >= self.config.module_slots {
return Err(crate::Error::Storage(format!(
"invalid slot {}", slot
)));
}
let addr = self.layout.slot_address(slot);
let header_data = self.flash.read(addr, crate::format::FIXED_HEADER_SIZE)?;
if &header_data[0..4] != crate::format::MAGIC {
return Ok(None);
}
let total_size = u32::from_le_bytes([
header_data[6], header_data[7], header_data[8], header_data[9]
]) as usize;
let full_data = self.flash.read(addr, total_size)?;
Module::from_bytes(full_data).map(Some)
}
pub fn begin_install(&mut self, slot: usize, expected_size: usize) -> Result<Transaction> {
if slot >= self.config.module_slots {
return Err(crate::Error::Storage(format!(
"invalid slot {}", slot
)));
}
if expected_size > self.config.max_module_size {
return Err(crate::Error::CapacityExceeded(format!(
"module size {} exceeds maximum {}",
expected_size, self.config.max_module_size
)));
}
let addr = self.layout.slot_address(slot);
let pages_needed = (expected_size + self.flash.page_size() - 1) / self.flash.page_size();
for i in 0..pages_needed {
let page_addr = addr + i * self.flash.page_size();
let page_id = PageId(page_addr / self.flash.page_size());
self.flash.erase_page(page_id)?;
}
Ok(Transaction::new(slot, addr, expected_size))
}
pub fn commit_install(&mut self, tx: Transaction, module: &Module) -> Result<()> {
module.verify()?;
let data = module.to_bytes()?;
self.flash.write(tx.address(), &data)?;
if let Some(ref mut journal) = self.journal {
journal.commit(tx, self.flash)?;
}
Ok(())
}
pub fn status(&self, slot: usize) -> Result<InstallationStatus> {
match self.read_module(slot) {
Ok(Some(_)) => Ok(InstallationStatus::Valid),
Ok(None) => Ok(InstallationStatus::Empty),
Err(_) => Ok(InstallationStatus::Corrupted),
}
}
pub fn current_version(&self) -> Result<u64> {
let meta_addr = self.layout.metadata_address();
let data = self.flash.read(meta_addr, 8)?;
Ok(u64::from_le_bytes([
data[0], data[1], data[2], data[3],
data[4], data[5], data[6], data[7],
]))
}
pub fn update_version(&mut self, version: u64) -> Result<()> {
let current = self.current_version()?;
if version <= current {
return Err(crate::Error::AntiRollback {
current,
attempted: version,
});
}
let meta_addr = self.layout.metadata_address();
let data = version.to_le_bytes();
self.flash.write(meta_addr, &data)?;
Ok(())
}
}