use crate::block_index::FsBlockIndex;
use crate::checksum::Checksum;
use crate::error::{CorruptKind, Ext4Error, IncompatibleKind};
use crate::journal::superblock::JournalSuperblock;
use crate::util::{read_u32be, u64_from_hilo};
use bitflags::bitflags;
pub(super) fn validate_descriptor_block_checksum(
superblock: &JournalSuperblock,
block: &[u8],
) -> Result<(), Ext4Error> {
let checksum_offset = block.len().checked_sub(4).unwrap();
let expected_checksum = read_u32be(block, checksum_offset);
let mut checksum = Checksum::new();
checksum.update(superblock.uuid.as_bytes());
checksum.update(&block[..checksum_offset]);
checksum.update_u32_be(0);
if checksum.finalize() == expected_checksum {
Ok(())
} else {
Err(CorruptKind::JournalDescriptorBlockChecksum.into())
}
}
#[derive(Debug, Eq, PartialEq)]
pub(super) struct DescriptorBlockTag {
pub(super) block_index: FsBlockIndex,
pub(super) checksum: u32,
flags: DescriptorBlockTagFlags,
}
impl DescriptorBlockTag {
const SIZE_WITHOUT_UUID: usize = 16;
const SIZE_WITH_UUID: usize = 32;
fn encoded_size(&self) -> usize {
if self.flags.contains(DescriptorBlockTagFlags::UUID_OMITTED) {
Self::SIZE_WITHOUT_UUID
} else {
Self::SIZE_WITH_UUID
}
}
fn read_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() < Self::SIZE_WITHOUT_UUID {
return None;
}
let t_blocknr = read_u32be(bytes, 0);
let t_flags = read_u32be(bytes, 4);
let t_blocknr_high = read_u32be(bytes, 8);
let t_checksum = read_u32be(bytes, 12);
let flags = DescriptorBlockTagFlags::from_bits_retain(t_flags);
if !flags.contains(DescriptorBlockTagFlags::UUID_OMITTED)
&& bytes.len() < Self::SIZE_WITH_UUID
{
return None;
}
Some(Self {
block_index: u64_from_hilo(t_blocknr_high, t_blocknr),
flags,
checksum: t_checksum,
})
}
}
pub(super) struct DescriptorBlockTagIter<'a> {
bytes: &'a [u8],
is_done: bool,
}
impl<'a> DescriptorBlockTagIter<'a> {
pub(super) fn new(bytes: &'a [u8]) -> Self {
Self {
bytes,
is_done: false,
}
}
}
impl Iterator for DescriptorBlockTagIter<'_> {
type Item = Result<DescriptorBlockTag, Ext4Error>;
fn next(&mut self) -> Option<Self::Item> {
if self.is_done {
return None;
}
let tag = if let Some(tag) = DescriptorBlockTag::read_bytes(self.bytes)
{
tag
} else {
self.is_done = true;
return Some(Err(
CorruptKind::JournalDescriptorBlockTruncated.into()
));
};
if tag.flags.contains(DescriptorBlockTagFlags::ESCAPED) {
self.is_done = true;
return Some(Err(IncompatibleKind::JournalBlockEscaped.into()));
}
if tag.flags.contains(DescriptorBlockTagFlags::LAST_TAG) {
self.is_done = true;
return Some(Ok(tag));
}
self.bytes = &self.bytes[tag.encoded_size()..];
Some(Ok(tag))
}
}
bitflags! {
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
struct DescriptorBlockTagFlags: u32 {
const ESCAPED = 0x1;
const UUID_OMITTED = 0x2;
const DELETED = 0x4;
const LAST_TAG = 0x8;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Uuid;
#[test]
fn test_validate_descriptor_block_checksum() {
let superblock = JournalSuperblock {
block_size: 1024,
sequence: 0,
start_block: 0,
uuid: Uuid([0; 16]),
};
let mut block = vec![0; 1024];
assert_eq!(
validate_descriptor_block_checksum(&superblock, &block)
.unwrap_err(),
CorruptKind::JournalDescriptorBlockChecksum
);
block[1020..].copy_from_slice(&[0x74, 0xef, 0x0e, 0xf6]);
assert!(
validate_descriptor_block_checksum(&superblock, &block).is_ok()
);
}
fn push_u32be(bytes: &mut Vec<u8>, value: u32) {
bytes.extend(&value.to_be_bytes());
}
#[test]
fn test_descriptor_block_tag_iter() {
let mut bytes = vec![];
push_u32be(&mut bytes, 0x1000);
push_u32be(&mut bytes, DescriptorBlockTagFlags::UUID_OMITTED.bits());
push_u32be(&mut bytes, 0xa000);
push_u32be(&mut bytes, 0x123);
push_u32be(&mut bytes, 0x2000);
push_u32be(&mut bytes, DescriptorBlockTagFlags::LAST_TAG.bits());
push_u32be(&mut bytes, 0xb000);
push_u32be(&mut bytes, 0x456);
bytes.extend([0; 16]);
assert_eq!(
DescriptorBlockTagIter::new(&bytes)
.map(Result::unwrap)
.collect::<Vec<_>>(),
[
DescriptorBlockTag {
block_index: 0xa000_0000_1000,
flags: DescriptorBlockTagFlags::UUID_OMITTED,
checksum: 0x123,
},
DescriptorBlockTag {
block_index: 0xb000_0000_2000,
flags: DescriptorBlockTagFlags::LAST_TAG,
checksum: 0x456,
}
]
);
}
#[test]
fn test_descriptor_block_tag_iter_empty() {
let bytes = vec![];
assert_eq!(
DescriptorBlockTagIter::new(&bytes)
.next()
.unwrap()
.unwrap_err(),
CorruptKind::JournalDescriptorBlockTruncated
);
}
#[test]
fn test_descriptor_block_tag_iter_missing_uuid() {
let mut bytes = vec![];
push_u32be(&mut bytes, 0x2000);
push_u32be(&mut bytes, DescriptorBlockTagFlags::LAST_TAG.bits());
push_u32be(&mut bytes, 0xb000);
push_u32be(&mut bytes, 0x456);
assert_eq!(
DescriptorBlockTagIter::new(&bytes)
.next()
.unwrap()
.unwrap_err(),
CorruptKind::JournalDescriptorBlockTruncated
);
}
#[test]
fn test_descriptor_block_tag_iter_escaped_error() {
let mut bytes = vec![];
push_u32be(&mut bytes, 0x2000);
push_u32be(
&mut bytes,
(DescriptorBlockTagFlags::ESCAPED
| DescriptorBlockTagFlags::UUID_OMITTED
| DescriptorBlockTagFlags::LAST_TAG)
.bits(),
);
push_u32be(&mut bytes, 0xb000);
push_u32be(&mut bytes, 0x456);
assert_eq!(
DescriptorBlockTagIter::new(&bytes)
.next()
.unwrap()
.unwrap_err(),
IncompatibleKind::JournalBlockEscaped
);
}
}