use super::{Dirblock, Entry, EntryKey, Kind, TreeData};
#[derive(Debug, PartialEq, Eq)]
pub enum DirblocksError {
UnexpectedEof,
MissingNul { trailing: Vec<u8> },
LeadingFieldNotEmpty(Vec<u8>),
BadSize(Vec<u8>),
BadRowTerminator(Vec<u8>),
WrongEntryCount { expected: usize, actual: usize },
InvalidMinikind(u8),
}
impl std::fmt::Display for DirblocksError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DirblocksError::UnexpectedEof => {
write!(f, "get_next() called when there are no chars left")
}
DirblocksError::MissingNul { trailing } => {
let end = std::cmp::min(trailing.len(), 20);
write!(
f,
"failed to find trailing NULL (\\0). Trailing garbage: {:?}",
&trailing[..end]
)
}
DirblocksError::LeadingFieldNotEmpty(field) => {
write!(f, "First field should be empty, not: {:?}", field)
}
DirblocksError::BadSize(bytes) => {
write!(f, "invalid size field: {:?}", bytes)
}
DirblocksError::BadRowTerminator(bytes) => {
write!(
f,
"Bad parse, we expected to end on \\n, not: {} {:?}",
bytes.len(),
bytes
)
}
DirblocksError::WrongEntryCount { expected, actual } => {
write!(
f,
"We read the wrong number of entries. We expected to read {}, but read {}",
expected, actual
)
}
DirblocksError::InvalidMinikind(byte) => {
write!(f, "invalid minikind byte {:?}", byte)
}
}
}
}
impl std::error::Error for DirblocksError {}
fn get_next_field(data: &[u8], pos: usize) -> Result<(&[u8], usize), DirblocksError> {
if pos >= data.len() {
return Err(DirblocksError::UnexpectedEof);
}
let remaining = &data[pos..];
match remaining.iter().position(|&b| b == 0) {
Some(offset) => Ok((&data[pos..pos + offset], pos + offset + 1)),
None => Err(DirblocksError::MissingNul {
trailing: remaining.to_vec(),
}),
}
}
pub fn parse_dirblocks(
text: &[u8],
num_trees: usize,
num_entries: usize,
) -> Result<Vec<Dirblock>, DirblocksError> {
if text.is_empty() {
return Ok(Vec::new());
}
let (first_field, mut pos) = get_next_field(text, 0)?;
if !first_field.is_empty() {
return Err(DirblocksError::LeadingFieldNotEmpty(first_field.to_vec()));
}
let mut dirblocks: Vec<Dirblock> = vec![
Dirblock {
dirname: Vec::new(),
entries: Vec::new(),
},
Dirblock {
dirname: Vec::new(),
entries: Vec::new(),
},
];
let mut current_dirname: Vec<u8> = Vec::new();
let mut current_block_idx: usize = 0;
let mut entry_count: usize = 0;
while pos < text.len() {
let (dirname_bytes, new_pos) = get_next_field(text, pos)?;
pos = new_pos;
if dirname_bytes != current_dirname.as_slice() {
current_dirname = dirname_bytes.to_vec();
dirblocks.push(Dirblock {
dirname: current_dirname.clone(),
entries: Vec::new(),
});
current_block_idx = dirblocks.len() - 1;
}
let (name_bytes, new_pos) = get_next_field(text, pos)?;
pos = new_pos;
let (file_id_bytes, new_pos) = get_next_field(text, pos)?;
pos = new_pos;
let key = EntryKey {
dirname: current_dirname.clone(),
basename: name_bytes.to_vec(),
file_id: file_id_bytes.to_vec(),
};
let mut trees: Vec<TreeData> = Vec::with_capacity(num_trees);
for _ in 0..num_trees {
let (minikind_bytes, new_pos) = get_next_field(text, pos)?;
pos = new_pos;
let (fingerprint_bytes, new_pos) = get_next_field(text, pos)?;
pos = new_pos;
let (size_bytes, new_pos) = get_next_field(text, pos)?;
pos = new_pos;
let (exec_bytes, new_pos) = get_next_field(text, pos)?;
pos = new_pos;
let (info_bytes, new_pos) = get_next_field(text, pos)?;
pos = new_pos;
let size_str = std::str::from_utf8(size_bytes)
.map_err(|_| DirblocksError::BadSize(size_bytes.to_vec()))?;
let size: u64 = size_str
.parse()
.map_err(|_| DirblocksError::BadSize(size_bytes.to_vec()))?;
let executable = !exec_bytes.is_empty() && exec_bytes[0] == b'y';
let minikind_byte = minikind_bytes.first().copied().unwrap_or(0);
let minikind =
Kind::from_minikind(minikind_byte).map_err(DirblocksError::InvalidMinikind)?;
trees.push(TreeData {
minikind,
fingerprint: fingerprint_bytes.to_vec(),
size,
executable,
packed_stat: info_bytes.to_vec(),
});
}
let (trailing, new_pos) = get_next_field(text, pos)?;
pos = new_pos;
if trailing.len() != 1 || trailing[0] != b'\n' {
return Err(DirblocksError::BadRowTerminator(trailing.to_vec()));
}
dirblocks[current_block_idx]
.entries
.push(Entry { key, trees });
entry_count += 1;
}
if entry_count != num_entries {
return Err(DirblocksError::WrongEntryCount {
expected: num_entries,
actual: entry_count,
});
}
Ok(dirblocks)
}
pub fn entry_to_line(entry: &Entry) -> Vec<u8> {
let mut out = Vec::new();
out.extend_from_slice(&entry.key.dirname);
out.push(0);
out.extend_from_slice(&entry.key.basename);
out.push(0);
out.extend_from_slice(&entry.key.file_id);
for tree in &entry.trees {
out.push(0);
out.push(tree.minikind.to_minikind());
out.push(0);
out.extend_from_slice(&tree.fingerprint);
out.push(0);
out.extend_from_slice(format!("{}", tree.size).as_bytes());
out.push(0);
out.push(if tree.executable { b'y' } else { b'n' });
out.push(0);
out.extend_from_slice(&tree.packed_stat);
}
out
}
pub fn dirblocks_to_entry_lines(dirblocks: &[Dirblock]) -> Vec<Vec<u8>> {
let mut out = Vec::new();
for block in dirblocks {
for entry in &block.entries {
out.push(entry_to_line(entry));
}
}
out
}