//! Two-block read-only cache with full identity keys and pinned eviction refusal. pub const BLOCK_BYTES: usize = 16; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { InvalidKey, Busy, NotFound, Overflow } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Key { pub volume: u8, pub mount_generation: u64, pub media_generation: u64, pub inode: u64, pub block: u64, } impl Key { fn validate(self) -> Result<(), Error> { if self.volume == 0 || self.mount_generation == 0 || self.media_generation == 0 || self.inode == 0 { return Err(Error::InvalidKey); } Ok(()) } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Entry { pub key: Key, pub data: [u8; BLOCK_BYTES], pub stamp: u64, pub pins: u8, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Cache { entries: [Option; 2], next_stamp: u64, } impl Cache { pub const fn new() -> Self { Self {entries: [None; 2], next_stamp: 1} } pub const fn entries(&self) -> [Option; 2] { self.entries } fn stamp(&self) -> Result { self.next_stamp.checked_add(1).ok_or(Error::Overflow)?; Ok(self.next_stamp) } fn advance(&mut self) { self.next_stamp += 1; } pub fn get(&mut self, key: Key) -> Result, Error> { key.validate()?; let index = self.entries.iter().position(|slot| slot.is_some_and(|entry| entry.key.volume == key.volume && entry.key.inode == key.inode && entry.key.block == key.block)); let Some(index) = index else { return Ok(None); }; let stamp = self.stamp()?; let mut entry = self.entries[index].unwrap(); entry.stamp = stamp; self.entries[index] = Some(entry); self.advance(); Ok(Some(entry.data)) } pub fn insert(&mut self, key: Key, data: [u8; BLOCK_BYTES]) -> Result, Error> { key.validate()?; let existing = self.entries.iter().position(|slot| slot.is_some_and(|entry| entry.key == key)); if let Some(index) = existing { let mut entry = self.entries[index].unwrap(); if entry.pins != 0 { return Err(Error::Busy); } let stamp = self.stamp()?; entry.data = data; entry.stamp = stamp; self.entries[index] = Some(entry); self.advance(); return Ok(None); } let free = self.entries.iter().position(Option::is_none); let victim = if free.is_none() { self.entries.iter().enumerate().filter_map(|(index, slot)| { let entry = slot.unwrap(); (entry.pins == 0).then_some((index, entry.stamp)) }).min_by_key(|(_, stamp)| *stamp).map(|(index, _)| index) } else { None }; let index = free.or(victim).ok_or(Error::Busy)?; let stamp = self.stamp()?; let evicted = self.entries[index].map(|entry| entry.key); self.entries[index] = Some(Entry {key, data, stamp, pins: 0}); self.advance(); Ok(evicted) } pub fn pin(&mut self, key: Key) -> Result<(), Error> { key.validate()?; let index = self.entries.iter().position(|slot| slot.is_some_and(|entry| entry.key == key)).ok_or(Error::NotFound)?; let mut entry = self.entries[index].unwrap(); entry.pins = entry.pins.checked_add(1).ok_or(Error::Overflow)?; self.entries[index] = Some(entry); Ok(()) } pub fn unpin(&mut self, key: Key) -> Result<(), Error> { key.validate()?; let index = self.entries.iter().position(|slot| slot.is_some_and(|entry| entry.key == key)).ok_or(Error::NotFound)?; let mut entry = self.entries[index].unwrap(); if entry.pins == 0 { return Err(Error::NotFound); } entry.pins -= 1; self.entries[index] = Some(entry); Ok(()) } pub fn invalidate_mount(&mut self, volume: u8, mount_generation: u64) -> Result { if volume == 0 || mount_generation == 0 { return Err(Error::InvalidKey); } let matching = |entry: Entry| entry.key.volume == volume && entry.key.mount_generation == mount_generation; if self.entries.iter().flatten().any(|entry| matching(*entry) && entry.pins != 0) { return Err(Error::Busy); } let mut removed = 0; for slot in &mut self.entries { if slot.is_some_and(matching) { *slot = None; removed += 1; } } Ok(removed) } }