use crate::error::{BackupError, Result};
type TLVBlock = Result<([u8; 4], Vec<u8>)>;
pub(crate) fn tlv_blocks(blob: &[u8]) -> impl Iterator<Item = TLVBlock> + '_ {
struct Iter<'a> {
data: &'a [u8],
pos: usize,
}
impl Iterator for Iter<'_> {
type Item = TLVBlock;
fn next(&mut self) -> Option<Self::Item> {
if self.pos + 8 > self.data.len() {
return None;
}
let tag = match self.data[self.pos..self.pos + 4].try_into() {
Ok(t) => t,
Err(e) => {
self.pos = self.data.len();
return Some(Err(BackupError::ConversionFailed(e)));
}
};
let len = match self.data[self.pos + 4..self.pos + 8].try_into() {
Ok(bytes) => u32::from_be_bytes(bytes) as usize,
Err(e) => {
self.pos = self.data.len();
return Some(Err(BackupError::ConversionFailed(e)));
}
};
let start = self.pos + 8;
let end = start + len;
if end > self.data.len() {
self.pos = self.data.len();
return Some(Err(BackupError::InvalidTlvData(format!(
"Value for tag {tag:?} extends beyond blob length",
))));
}
let value = self.data[start..end].to_vec();
self.pos = end;
Some(Ok((tag, value)))
}
}
Iter { data: blob, pos: 0 }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::BackupError;
#[test]
fn test_tlv_blocks_roundtrip() {
let mut blob = Vec::new();
blob.extend(b"ABCD");
blob.extend(&3u32.to_be_bytes());
blob.extend(&[1, 2, 3]);
blob.extend(b"EFGH");
blob.extend(&2u32.to_be_bytes());
blob.extend(&[4, 5]);
let mut iter = tlv_blocks(&blob);
let (tag1, val1) = iter.next().expect("First TLV block missing").unwrap();
assert_eq!(&tag1, b"ABCD");
assert_eq!(val1, vec![1, 2, 3]);
let (tag2, val2) = iter.next().expect("Second TLV block missing").unwrap();
assert_eq!(&tag2, b"EFGH");
assert_eq!(val2, vec![4, 5]);
assert!(iter.next().is_none(), "Unexpected extra TLV block");
}
#[test]
fn test_tlv_blocks_incomplete_blob() {
let mut blob = Vec::new();
blob.extend(b"TAG1");
blob.extend(&5u32.to_be_bytes()); blob.extend(&[1, 2]); let mut iter = tlv_blocks(&blob);
let err = iter
.next()
.expect("Expected error for incomplete blob")
.unwrap_err();
match err {
BackupError::InvalidTlvData(_) => {}
_ => panic!("Expected InvalidTlvData error, got {err:?}"),
}
assert!(iter.next().is_none());
}
}