#![no_std] //! Epoch-scoped shell line input with bounded cancellation. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Epoch(pub u64); #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { StaleEpoch, Full, Nul, EpochOverflow } pub struct Input { bytes: [u8; N], len: usize, epoch: Epoch, } pub struct Command { bytes: [u8; N], len: usize, pub epoch: Epoch, } impl Command { pub fn as_str(&self) -> &str { core::str::from_utf8(&self.bytes[..self.len]) .expect("input accepted only UTF-8 chunks") } } impl Default for Input { fn default() -> Self { Self::new() } } impl Input { pub const fn new() -> Self { Self {bytes: [0; N], len: 0, epoch: Epoch(1)} } pub const fn epoch(&self) -> Epoch { self.epoch } pub const fn len(&self) -> usize { self.len } pub fn push(&mut self, epoch: Epoch, chunk: &str) -> Result<(), Error> { if epoch != self.epoch { return Err(Error::StaleEpoch); } if chunk.as_bytes().contains(&0) { return Err(Error::Nul); } if chunk.len() > N - self.len { return Err(Error::Full); } self.bytes[self.len..self.len + chunk.len()].copy_from_slice(chunk.as_bytes()); self.len += chunk.len(); Ok(()) } pub fn cancel(&mut self, epoch: Epoch) -> Result { if epoch != self.epoch { return Err(Error::StaleEpoch); } let next = self.epoch.0.checked_add(1).ok_or(Error::EpochOverflow)?; self.bytes.fill(0); self.len = 0; self.epoch = Epoch(next); Ok(self.epoch) } pub fn commit(&mut self, epoch: Epoch) -> Result, Error> { if epoch != self.epoch { return Err(Error::StaleEpoch); } let next = self.epoch.0.checked_add(1).ok_or(Error::EpochOverflow)?; let command = Command {bytes: self.bytes, len: self.len, epoch}; self.bytes.fill(0); self.len = 0; self.epoch = Epoch(next); Ok(command) } }