Skip to main content

speck_core/storage/
mod.rs

1//! High-level storage management with transactions and wear leveling
2//! 
3//! Provides atomic update semantics and manages the layout of:
4//! - Boot metadata (version info, rollback protection)
5//! - Module slots (active, update, factory)
6//! - Journal (transaction log for atomic updates)
7
8use alloc::vec::Vec;
9use alloc::string::String;
10use crate::error::Result;
11use crate::flash::{Flash, PageId};
12use crate::format::Module;
13
14pub mod journal;
15pub mod layout;
16pub mod wear_leveling;
17
18pub use journal::{Journal, Transaction};
19pub use layout::StorageLayout;
20pub use wear_leveling::WearLevelingPolicy;
21
22/// Storage configuration
23#[derive(Clone, Debug)]
24pub struct StorageConfig {
25    /// Number of module slots (typically 2-3 for A/B+factory)
26    pub module_slots: usize,
27    /// Enable transaction journaling
28    pub enable_journaling: bool,
29    /// Enable automatic wear leveling
30    pub enable_wear_leveling: bool,
31    /// Maximum module size (reserve space per slot)
32    pub max_module_size: usize,
33}
34
35impl Default for StorageConfig {
36    fn default() -> Self {
37        Self {
38            module_slots: 3, // Active, Update, Factory
39            enable_journaling: true,
40            enable_wear_leveling: true,
41            max_module_size: 32768, // 32KB default
42        }
43    }
44}
45
46/// Installation status
47#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48pub enum InstallationStatus {
49    /// Slot is empty/erased
50    Empty,
51    /// Contains valid module
52    Valid,
53    /// Contains module pending verification
54    Pending,
55    /// Installation failed/corrupted
56    Corrupted,
57}
58
59/// High-level storage manager
60pub struct StorageManager<'f> {
61    flash: &'f mut Flash,
62    config: StorageConfig,
63    layout: StorageLayout,
64    journal: Option<Journal>,
65}
66
67impl<'f> StorageManager<'f> {
68    /// Create new storage manager
69    pub fn new(flash: &'f mut Flash, config: StorageConfig) -> Result<Self> {
70        let layout = StorageLayout::new(flash.capacity(), &config)?;
71        let journal = if config.enable_journaling {
72            Some(Journal::new(layout.journal_region()))
73        } else {
74            None
75        };
76        
77        Ok(Self {
78            flash,
79            config,
80            layout,
81            journal,
82        })
83    }
84    
85    /// Read module from slot
86    pub fn read_module(&self, slot: usize) -> Result<Option<Module>> {
87        if slot >= self.config.module_slots {
88            return Err(crate::Error::Storage(format!(
89                "invalid slot {}", slot
90            )));
91        }
92        
93        let addr = self.layout.slot_address(slot);
94        // Read header first to determine size
95        let header_data = self.flash.read(addr, crate::format::FIXED_HEADER_SIZE)?;
96        
97        // Check magic to see if slot is populated
98        if &header_data[0..4] != crate::format::MAGIC {
99            return Ok(None);
100        }
101        
102        let total_size = u32::from_le_bytes([
103            header_data[6], header_data[7], header_data[8], header_data[9]
104        ]) as usize;
105        
106        let full_data = self.flash.read(addr, total_size)?;
107        Module::from_bytes(full_data).map(Some)
108    }
109    
110    /// Begin installation to slot (returns transaction)
111    pub fn begin_install(&mut self, slot: usize, expected_size: usize) -> Result<Transaction> {
112        if slot >= self.config.module_slots {
113            return Err(crate::Error::Storage(format!(
114                "invalid slot {}", slot
115            )));
116        }
117        
118        if expected_size > self.config.max_module_size {
119            return Err(crate::Error::CapacityExceeded(format!(
120                "module size {} exceeds maximum {}",
121                expected_size, self.config.max_module_size
122            )));
123        }
124        
125        let addr = self.layout.slot_address(slot);
126        let pages_needed = (expected_size + self.flash.page_size() - 1) / self.flash.page_size();
127        
128        // Erase required pages
129        for i in 0..pages_needed {
130            let page_addr = addr + i * self.flash.page_size();
131            let page_id = PageId(page_addr / self.flash.page_size());
132            self.flash.erase_page(page_id)?;
133        }
134        
135        Ok(Transaction::new(slot, addr, expected_size))
136    }
137    
138    /// Commit installation (mark as valid)
139    pub fn commit_install(&mut self, tx: Transaction, module: &Module) -> Result<()> {
140        // Verify module
141        module.verify()?;
142        
143        // Write to flash
144        let data = module.to_bytes()?;
145        self.flash.write(tx.address(), &data)?;
146        
147        // Clear transaction
148        if let Some(ref mut journal) = self.journal {
149            journal.commit(tx, self.flash)?;
150        }
151        
152        Ok(())
153    }
154    
155    /// Get installation status of slot
156    pub fn status(&self, slot: usize) -> Result<InstallationStatus> {
157        match self.read_module(slot) {
158            Ok(Some(_)) => Ok(InstallationStatus::Valid),
159            Ok(None) => Ok(InstallationStatus::Empty),
160            Err(_) => Ok(InstallationStatus::Corrupted),
161        }
162    }
163    
164    /// Get current monotonic version from metadata
165    pub fn current_version(&self) -> Result<u64> {
166        let meta_addr = self.layout.metadata_address();
167        let data = self.flash.read(meta_addr, 8)?;
168        Ok(u64::from_le_bytes([
169            data[0], data[1], data[2], data[3],
170            data[4], data[5], data[6], data[7],
171        ]))
172    }
173    
174    /// Update monotonic version (anti-rollback)
175    pub fn update_version(&mut self, version: u64) -> Result<()> {
176        let current = self.current_version()?;
177        if version <= current {
178            return Err(crate::Error::AntiRollback {
179                current,
180                attempted: version,
181            });
182        }
183        
184        let meta_addr = self.layout.metadata_address();
185        let data = version.to_le_bytes();
186        self.flash.write(meta_addr, &data)?;
187        Ok(())
188    }
189}