use crate::Result;
use crate::block::BlockDevice;
use crate::fs::squashfs::Compression;
use crate::fs::squashfs::metablock::read_metablock;
#[derive(Debug, Default)]
pub struct IdTable {
values: Vec<u32>,
loaded: bool,
}
impl IdTable {
pub fn new() -> Self {
Self::default()
}
pub fn ensure_loaded(
&mut self,
dev: &mut dyn BlockDevice,
table_start: u64,
id_count: u16,
compression: Compression,
) -> Result<()> {
if self.loaded {
return Ok(());
}
self.loaded = true;
if id_count == 0 || table_start == u64::MAX {
return Ok(());
}
let bytes_total = id_count as usize * 4;
let metablock_count = bytes_total.div_ceil(8192);
let mut locs = vec![0u8; metablock_count * 8];
dev.read_at(table_start, &mut locs)?;
let mut values = Vec::with_capacity(id_count as usize);
let mut remaining = bytes_total;
for i in 0..metablock_count {
let off = i * 8;
let mb_disk = u64::from_le_bytes(locs[off..off + 8].try_into().unwrap());
let mb = read_metablock(dev, mb_disk, compression)?;
let want = remaining.min(8192);
if mb.data.len() < want {
return Err(crate::Error::InvalidImage(format!(
"squashfs: id-table metablock {i} decoded to {} bytes, want >= {want}",
mb.data.len()
)));
}
for j in 0..(want / 4) {
let p = j * 4;
values.push(u32::from_le_bytes(mb.data[p..p + 4].try_into().unwrap()));
}
remaining -= want;
}
self.values = values;
Ok(())
}
pub fn resolve(&self, idx: u16) -> u32 {
self.values.get(idx as usize).copied().unwrap_or(0)
}
}
#[allow(dead_code)]
pub fn encode_id_table(
values: &[u32],
base: u64,
compression: Compression,
) -> Result<(Vec<u8>, u64)> {
use crate::fs::squashfs::metablock::encode_metablock;
let mut raw = Vec::with_capacity(values.len() * 4);
for &v in values {
raw.extend_from_slice(&v.to_le_bytes());
}
let mut out = Vec::new();
let mut locations: Vec<u64> = Vec::new();
let mut pos = 0usize;
while pos < raw.len() {
let end = (pos + 8192).min(raw.len());
let mb_offset_abs = base + out.len() as u64;
let mb = encode_metablock(&raw[pos..end], compression)?;
out.extend_from_slice(&mb);
locations.push(mb_offset_abs);
pos = end;
}
let loc_offset = out.len() as u64;
for l in &locations {
out.extend_from_slice(&l.to_le_bytes());
}
Ok((out, loc_offset))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::block::MemoryBackend;
#[test]
fn round_trip_id_table_uncompressed() {
let values: Vec<u32> = (0..3).collect();
let base = 100u64;
let (payload, loc_off) = encode_id_table(&values, base, Compression::Unknown(0)).unwrap();
let mut dev = MemoryBackend::new(base + payload.len() as u64 + 32);
dev.write_at(base, &payload).unwrap();
let mut t = IdTable::new();
t.ensure_loaded(
&mut dev,
base + loc_off,
values.len() as u16,
Compression::Unknown(0),
)
.unwrap();
assert_eq!(t.resolve(0), 0);
assert_eq!(t.resolve(1), 1);
assert_eq!(t.resolve(2), 2);
assert_eq!(t.resolve(99), 0);
}
}