use super::super::tag::{self, Tag};
use super::{Error, FlashDriver, Geometry};
const MASK_TYPE1_ID: u32 = ((0x700u32) << 20) | (0x3ffu32 << 10);
const MASK_TYPE3_ID: u32 = ((0x7ffu32) << 20) | (0x3ffu32 << 10);
const MASK_ID: u32 = 0x3ffu32 << 10;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) struct Mdir {
pub pair: [u32; 2],
pub rev: u32,
pub off: u32,
pub etag: u32,
pub count: u16,
pub tail: Option<[u32; 2]>,
pub hard: bool,
pub gdelta: Option<[u8; 12]>,
}
impl Mdir {
pub fn empty(pair: [u32; 2]) -> Self {
Self {
pair,
rev: 0,
off: 0,
etag: tag::PTAG_INIT,
count: 0,
tail: None,
hard: false,
gdelta: None,
}
}
pub fn target(&self) -> u32 {
self.pair[1]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Struct {
Dir([u32; 2]),
Inline { off: u32, len: u32 },
Ctz { head: u32, size: u32 },
}
impl Struct {
fn commit_size(&self) -> usize {
match self {
Struct::Dir(_) | Struct::Ctz { .. } => 4 + 8,
Struct::Inline { len, .. } => 4 + *len as usize,
}
}
}
pub(super) fn parse(buf: &[u8], pair: [u32; 2]) -> Option<Mdir> {
let bs = buf.len();
if bs < 8 {
return None;
}
let rev = tag::le32(&buf[0..4]);
let mut live: Option<Mdir> = None;
let mut cur = Mdir {
rev,
..Mdir::empty(pair)
};
let mut off = 0usize;
let mut ptag = tag::PTAG_INIT;
let mut crc = tag::crc(tag::PTAG_INIT, &buf[0..4]);
loop {
off += Tag(ptag).dsize();
if off + 4 > bs {
break;
}
crc = tag::crc(crc, &buf[off..off + 4]);
let t = Tag(tag::be32(&buf[off..off + 4]) ^ ptag);
if !t.is_valid() || off + t.dsize() > bs {
break;
}
ptag = t.0;
if t.type2() == tag::TYPE_CCRC {
if off + 8 > bs || crc != tag::le32(&buf[off + 4..off + 8]) {
break;
}
ptag ^= ((t.chunk() & 1) as u32) << 31;
live = Some(Mdir {
off: (off + t.dsize()) as u32,
etag: ptag,
..cur
});
crc = tag::PTAG_INIT;
continue;
}
let data = &buf[off + 4..off + t.dsize()];
crc = tag::crc(crc, data);
apply(&mut cur, t, data);
}
live
}
fn apply(m: &mut Mdir, t: Tag, data: &[u8]) {
match t.type1() {
tag::T1_SPLICE => {
match t.type3() {
tag::TYPE_CREATE => m.count = m.count.saturating_add(1),
tag::TYPE_DELETE => m.count = m.count.saturating_sub(1),
_ => {}
}
}
tag::T1_TAIL => {
if data.len() >= 8 {
m.tail = Some([tag::le32(&data[0..4]), tag::le32(&data[4..8])]);
m.hard = t.chunk() & 1 != 0;
}
}
tag::T1_GSTATE => {
if data.len() >= 12 {
let mut g = [0u8; 12];
g.copy_from_slice(&data[..12]);
m.gdelta = Some(g);
}
}
tag::T1_NAME | tag::T1_STRUCT | tag::T1_USERATTR => {
let id = t.id();
if id < tag::ID_NONE && id + 1 > m.count {
m.count = id + 1;
}
}
_ => {}
}
}
pub(super) fn get(buf: &[u8], m: &Mdir, mask: u32, want: u32) -> Option<(Tag, u32)> {
let bs = buf.len();
if m.off as usize > bs {
return None;
}
let mut off = m.off as usize;
let mut ntag = m.etag;
let mut diff: i32 = 0;
let by_id = mask & MASK_ID != 0;
loop {
let dsize = Tag(ntag).dsize();
if off < 4 + dsize {
return None;
}
off -= dsize;
let t = Tag(ntag);
ntag = (tag::be32(&buf[off..off + 4]) ^ t.0) & 0x7fff_ffff;
let sought = want.wrapping_add((diff as u32) << 10);
if by_id && t.type1() == tag::T1_SPLICE && t.id() <= Tag(sought).id() {
if t.0 == (Tag::new(tag::TYPE_CREATE, 0, 0).0 | (MASK_ID & sought)) {
return None;
}
diff -= match t.type3() {
tag::TYPE_CREATE => 1,
tag::TYPE_DELETE => -1,
_ => 0,
};
continue;
}
if mask & t.0 == mask & sought {
if t.is_delete() {
return None;
}
if off + t.dsize() > bs {
return None;
}
let id = (t.id() as i32 - diff) as u32 & 0x3ff;
return Some((Tag((t.0 & !MASK_ID) | (id << 10)), (off + 4) as u32));
}
}
}
pub(super) fn name_of(buf: &[u8], m: &Mdir, id: u16) -> Option<(u8, u32, u32)> {
let (t, off) = get(buf, m, MASK_TYPE1_ID, Tag::new(tag::TYPE_NAME, id, 0).0)?;
Some((t.chunk(), off, t.size() as u32))
}
pub(super) fn struct_of(buf: &[u8], m: &Mdir, id: u16) -> Option<Struct> {
let (t, off) = get(
buf,
m,
MASK_TYPE1_ID,
Tag::new(tag::TYPE_DIRSTRUCT, id, 0).0,
)?;
let data = buf.get(off as usize..off as usize + t.size() as usize)?;
match t.type3() {
tag::TYPE_DIRSTRUCT if data.len() >= 8 => Some(Struct::Dir([
tag::le32(&data[0..4]),
tag::le32(&data[4..8]),
])),
tag::TYPE_CTZSTRUCT if data.len() >= 8 => Some(Struct::Ctz {
head: tag::le32(&data[0..4]),
size: tag::le32(&data[4..8]),
}),
tag::TYPE_INLINESTRUCT => Some(Struct::Inline {
off,
len: data.len() as u32,
}),
_ => None,
}
}
pub(super) fn attr_of(buf: &[u8], m: &Mdir, id: u16, key: u8) -> Option<(u32, u32)> {
let (t, off) = get(
buf,
m,
MASK_TYPE3_ID,
Tag::new(tag::TYPE_USERATTR | key as u16, id, 0).0,
)?;
Some((off, t.size() as u32))
}
pub(super) fn attr_keys(buf: &[u8], m: &Mdir) -> [u32; 8] {
let mut keys = [0u32; 8];
let bs = buf.len();
let mut off = 0usize;
let mut ptag = tag::PTAG_INIT;
let end = m.off as usize;
loop {
off += Tag(ptag).dsize();
if off + 4 > bs || off >= end {
return keys;
}
let t = Tag(tag::be32(&buf[off..off + 4]) ^ ptag);
if !t.is_valid() || off + t.dsize() > bs {
return keys;
}
ptag = t.0;
if t.type2() == tag::TYPE_CCRC {
ptag ^= ((t.chunk() & 1) as u32) << 31;
continue;
}
if t.type1() == tag::T1_USERATTR {
let key = t.chunk();
keys[key as usize / 32] |= 1 << (key % 32);
}
}
}
pub(super) fn no_attrs(keys: &[u32; 8]) -> bool {
keys.iter().all(|w| *w == 0)
}
pub(super) fn has_key(keys: &[u32; 8], key: u8) -> bool {
keys[key as usize / 32] & (1 << (key % 32)) != 0
}
#[derive(Debug, Clone, Copy)]
pub(super) enum Data<'a> {
Bytes(&'a [u8]),
Run { off: u32, len: u32 },
Patch {
old: (u32, u32),
at: u32,
new: &'a [u8],
len: u32,
},
}
impl Data<'_> {
pub fn len(&self) -> u32 {
match self {
Data::Bytes(b) => b.len() as u32,
Data::Run { len, .. } => *len,
Data::Patch { len, .. } => *len,
}
}
fn byte(&self, src: &[u8], at: u32) -> Option<u8> {
match self {
Data::Bytes(b) => b.get(at as usize).copied(),
Data::Run { off, len } => {
if at >= *len {
return None;
}
src.get((off + at) as usize).copied()
}
Data::Patch {
old,
at: patch_at,
new,
len,
} => {
if at >= *len {
return None;
}
if at >= *patch_at && at - *patch_at < new.len() as u32 {
return new.get((at - *patch_at) as usize).copied();
}
if at < old.1 {
return src.get((old.0 + at) as usize).copied();
}
Some(0)
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub(super) enum StructOut<'a> {
Dir([u32; 2]),
Ctz { head: u32, size: u32 },
Inline(Data<'a>),
}
pub(super) struct Commit<'a, D: FlashDriver> {
dev: &'a mut D,
stage: &'a mut [u8],
chunk: usize,
staged: usize,
base: u32,
block: u32,
block_size: u32,
prog_size: u32,
crc: u32,
ptag: u32,
}
impl<'a, D: FlashDriver> Commit<'a, D> {
pub fn new(
dev: &'a mut D,
stage: &'a mut [u8],
geom: &Geometry,
block: u32,
rev: u32,
) -> Result<Self, Error<D::Error>> {
let prog = geom.prog_size.max(1) as usize;
let chunk = (stage.len() / prog) * prog;
let mut c = Self {
dev,
stage,
chunk,
staged: 0,
base: 0,
block,
block_size: geom.block_size,
prog_size: geom.prog_size.max(1),
crc: tag::PTAG_INIT,
ptag: tag::PTAG_INIT,
};
c.push_bytes(&rev.to_le_bytes())?;
Ok(c)
}
fn off(&self) -> u32 {
self.base + self.staged as u32
}
fn stage_bytes(&mut self, data: &[u8]) -> Result<(), Error<D::Error>> {
let mut at = 0;
while at < data.len() {
if self.staged == self.chunk {
self.flush_page()?;
}
let n = (self.chunk - self.staged).min(data.len() - at);
self.stage[self.staged..self.staged + n].copy_from_slice(&data[at..at + n]);
self.staged += n;
at += n;
}
Ok(())
}
fn flush_page(&mut self) -> Result<(), Error<D::Error>> {
if self.staged == 0 {
return Ok(());
}
let whole = (self.staged / self.prog_size as usize) * self.prog_size as usize;
if whole == 0 {
return Err(Error::ScratchTooSmall {
needed: self.prog_size as usize,
got: self.stage.len(),
});
}
self.dev
.prog(self.block, self.base, &self.stage[..whole])
.map_err(Error::Io)?;
self.stage.copy_within(whole..self.staged, 0);
self.staged -= whole;
self.base += whole as u32;
Ok(())
}
fn push_bytes(&mut self, data: &[u8]) -> Result<(), Error<D::Error>> {
self.crc = tag::crc(self.crc, data);
self.stage_bytes(data)
}
pub fn push(&mut self, t: Tag, data: &Data<'_>, src: &[u8]) -> Result<(), Error<D::Error>> {
let stored = (t.0 & 0x7fff_ffff) ^ self.ptag;
self.push_bytes(&stored.to_be_bytes())?;
self.ptag = t.0 & 0x7fff_ffff;
if t.is_delete() {
return Ok(());
}
let mut tmp = [0u8; 64];
let total = data.len();
let mut at = 0;
while at < total {
let n = (total - at).min(tmp.len() as u32) as usize;
for (i, slot) in tmp[..n].iter_mut().enumerate() {
*slot = data.byte(src, at + i as u32).unwrap_or(0);
}
self.push_bytes(&tmp[..n])?;
at += n as u32;
}
Ok(())
}
pub fn push_pair(&mut self, t: Tag, words: [u32; 2]) -> Result<(), Error<D::Error>> {
let mut d = [0u8; 8];
d[0..4].copy_from_slice(&words[0].to_le_bytes());
d[4..8].copy_from_slice(&words[1].to_le_bytes());
self.push(t, &Data::Bytes(&d), &[])
}
pub fn finish(mut self, fcrc: bool) -> Result<u32, Error<D::Error>> {
let bs = self.block_size;
let prog = self.prog_size;
let reserve = if fcrc { 5 * 4 } else { 2 * 4 };
if self.off() + reserve > bs {
return Err(Error::CommitTooLarge);
}
let end = (self.off() + reserve).next_multiple_of(prog);
if end > bs {
return Err(Error::CommitTooLarge);
}
if fcrc && end <= bs - prog {
let erased = [0xffu8; 64];
let mut fc = tag::PTAG_INIT;
let mut left = prog as usize;
while left > 0 {
let n = left.min(erased.len());
fc = tag::crc(fc, &erased[..n]);
left -= n;
}
let mut d = [0u8; 8];
d[0..4].copy_from_slice(&prog.to_le_bytes());
d[4..8].copy_from_slice(&fc.to_le_bytes());
self.push(
Tag::new(tag::TYPE_FCRC, tag::ID_NONE, 8),
&Data::Bytes(&d),
&[],
)?;
}
let pad = end - (self.off() + 4);
if pad as usize > tag::MAX_SIZE {
return Err(Error::CommitTooLarge);
}
let ccrc = Tag::new(tag::TYPE_CCRC, tag::ID_NONE, pad as u16);
let stored = (ccrc.0 & 0x7fff_ffff) ^ self.ptag;
self.push_bytes(&stored.to_be_bytes())?;
let crc = self.crc;
self.stage_bytes(&crc.to_le_bytes())?;
let written = self.off();
if written < end {
let fill = [0xffu8; 64];
let mut left = (end - written) as usize;
while left > 0 {
let n = left.min(fill.len());
self.stage_bytes(&fill[..n])?;
left -= n;
}
}
debug_assert_eq!(self.off(), end);
let staged = self.staged;
if staged > 0 {
self.dev
.prog(self.block, self.base, &self.stage[..staged])
.map_err(Error::Io)?;
self.base += staged as u32;
self.staged = 0;
}
Ok(end)
}
}
pub(super) fn struct_size(s: &Struct) -> usize {
s.commit_size()
}
pub(super) fn struct_out_size(s: &StructOut<'_>) -> usize {
match s {
StructOut::Dir(_) | StructOut::Ctz { .. } => 4 + 8,
StructOut::Inline(d) => 4 + d.len() as usize,
}
}