use alloc::vec::Vec;
use crate::{
format::{LFS_TYPE_CCRC_VALUE, Tag, crc32},
types::{Error, Result},
};
#[derive(Debug, Clone)]
pub(crate) struct CommitEntry {
tag: Tag,
data: Vec<u8>,
}
impl CommitEntry {
pub(crate) fn new(tag: Tag, data: &[u8]) -> Self {
Self {
tag,
data: data.to_vec(),
}
}
pub(crate) fn tag(&self) -> Tag {
self.tag
}
pub(crate) fn data(&self) -> &[u8] {
&self.data
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct CommitState {
pub(crate) off: usize,
pub(crate) ptag: u32,
}
pub(crate) struct MetadataCommitWriter<'a> {
block: &'a mut [u8],
prog_size: usize,
off: usize,
ptag: u32,
crc: u32,
}
impl<'a> MetadataCommitWriter<'a> {
pub(crate) fn new(block: &'a mut [u8], prog_size: usize) -> Result<Self> {
if prog_size == 0 || !prog_size.is_power_of_two() {
return Err(Error::InvalidConfig);
}
Ok(Self {
block,
prog_size,
off: 0,
ptag: 0xffff_ffff,
crc: 0xffff_ffff,
})
}
pub(crate) fn append(
block: &'a mut [u8],
prog_size: usize,
state: CommitState,
) -> Result<Self> {
if prog_size == 0 || !prog_size.is_power_of_two() {
return Err(Error::InvalidConfig);
}
if state.off > block.len() {
return Err(Error::NoSpace);
}
Ok(Self {
block,
prog_size,
off: state.off,
ptag: state.ptag,
crc: 0xffff_ffff,
})
}
pub(crate) fn write_revision(&mut self, rev: u32) -> Result<()> {
self.write_raw(&rev.to_le_bytes())
}
pub(crate) fn write_entries(&mut self, entries: &[CommitEntry]) -> Result<()> {
for entry in entries {
self.write_entry(entry)?;
}
Ok(())
}
pub(crate) fn finish(&mut self) -> Result<CommitState> {
self.write_commit_crc()?;
Ok(CommitState {
off: self.off,
ptag: self.ptag,
})
}
fn write_entry(&mut self, entry: &CommitEntry) -> Result<()> {
let expected_size = entry.tag.dsize()?.checked_sub(4).ok_or(Error::Corrupt)?;
if expected_size != entry.data.len() {
return Err(Error::Corrupt);
}
let disk_tag = ((entry.tag.0 & 0x7fff_ffff) ^ self.ptag).to_be_bytes();
self.write_raw(&disk_tag)?;
self.write_raw(&entry.data)?;
self.ptag = entry.tag.0 & 0x7fff_ffff;
Ok(())
}
fn write_commit_crc(&mut self) -> Result<()> {
let payload_size = self.ccrc_payload_size()?;
let tag = Tag::new(LFS_TYPE_CCRC_VALUE, 0x3ff, checked_u10(payload_size)?);
let disk_tag = ((tag.0 & 0x7fff_ffff) ^ self.ptag).to_be_bytes();
self.write_raw(&disk_tag)?;
let crc = self.crc.to_le_bytes();
self.program(&crc)?;
self.off += crc.len();
let padding = payload_size.checked_sub(4).ok_or(Error::Corrupt)?;
self.off += padding;
self.ptag = tag.0 & 0x7fff_ffff;
self.crc = 0xffff_ffff;
Ok(())
}
fn ccrc_payload_size(&self) -> Result<usize> {
let unaligned_end = self.off.checked_add(8).ok_or(Error::NoSpace)?;
let aligned_end = align_up(unaligned_end, self.prog_size)?;
let payload_size = aligned_end
.checked_sub(self.off + 4)
.ok_or(Error::Corrupt)?;
if payload_size < 4 {
return Err(Error::Corrupt);
}
Ok(payload_size)
}
fn write_raw(&mut self, data: &[u8]) -> Result<()> {
self.program(data)?;
self.crc = crc32(self.crc, data);
self.off += data.len();
Ok(())
}
fn program(&mut self, data: &[u8]) -> Result<()> {
let end = self.off.checked_add(data.len()).ok_or(Error::NoSpace)?;
if end > self.block.len() {
return Err(Error::NoSpace);
}
for (dst, src) in self.block[self.off..end].iter_mut().zip(data) {
*dst &= *src;
}
Ok(())
}
}
pub(crate) fn checked_u10(value: usize) -> Result<u16> {
if value > 0x3fe {
return Err(Error::Unsupported);
}
Ok(value as u16)
}
fn align_up(value: usize, align: usize) -> Result<usize> {
if align == 0 || !align.is_power_of_two() {
return Err(Error::InvalidConfig);
}
value
.checked_add(align - 1)
.map(|value| value & !(align - 1))
.ok_or(Error::NoSpace)
}