#![forbid(unsafe_code)]
use crate::error::{Ext4Error, Result};
pub const EXTENT_MAGIC: u16 = 0xF30A;
#[inline]
fn le16(buf: &[u8], off: usize) -> u16 {
u16::from_le_bytes([buf[off], buf[off + 1]])
}
#[inline]
fn le32(buf: &[u8], off: usize) -> u32 {
u32::from_le_bytes([buf[off], buf[off + 1], buf[off + 2], buf[off + 3]])
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtentHeader {
pub magic: u16,
pub entries: u16,
pub max: u16,
pub depth: u16,
pub generation: u32,
}
impl ExtentHeader {
pub fn parse(buf: &[u8]) -> Result<Self> {
if buf.len() < 12 {
return Err(Ext4Error::TooShort {
structure: "ExtentHeader",
expected: 12,
found: buf.len(),
});
}
let magic = le16(buf, 0);
if magic != EXTENT_MAGIC {
return Err(Ext4Error::CorruptMetadata {
structure: "ExtentHeader",
detail: format!("bad magic: 0x{magic:04X}"),
});
}
Ok(Self {
magic,
entries: le16(buf, 2),
max: le16(buf, 4),
depth: le16(buf, 6),
generation: le32(buf, 8),
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtentLeaf {
pub logical_block: u32,
pub length: u16,
pub physical_block: u64,
pub unwritten: bool,
}
impl ExtentLeaf {
pub fn parse(buf: &[u8]) -> Self {
let logical_block = le32(buf, 0);
let ee_len = le16(buf, 4);
let start_hi = u64::from(le16(buf, 6));
let start_lo = u64::from(le32(buf, 8));
let unwritten = (ee_len & 0x8000) != 0;
let length = ee_len & 0x7FFF;
let physical_block = (start_hi << 32) | start_lo;
Self {
logical_block,
length,
physical_block,
unwritten,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtentIndex {
pub logical_block: u32,
pub child_block: u64,
}
impl ExtentIndex {
pub fn parse(buf: &[u8]) -> Self {
let logical_block = le32(buf, 0);
let leaf_lo = u64::from(le32(buf, 4));
let leaf_hi = u64::from(le16(buf, 8));
let child_block = (leaf_hi << 32) | leaf_lo;
Self {
logical_block,
child_block,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_extent_header() {
let mut buf = vec![0u8; 12];
buf[0] = 0x0A;
buf[1] = 0xF3; buf[2] = 3; buf[4] = 4; buf[6] = 0; let hdr = ExtentHeader::parse(&buf).unwrap();
assert_eq!(hdr.magic, EXTENT_MAGIC);
assert_eq!(hdr.entries, 3);
assert_eq!(hdr.max, 4);
assert_eq!(hdr.depth, 0);
}
#[test]
fn reject_bad_magic() {
let buf = vec![0u8; 12];
let err = ExtentHeader::parse(&buf).unwrap_err();
assert!(matches!(
err,
crate::error::Ext4Error::CorruptMetadata { .. }
));
}
#[test]
fn parse_extent_leaf() {
let mut buf = vec![0u8; 12];
buf[4] = 10; buf[8] = 0xF4;
buf[9] = 0x01; let leaf = ExtentLeaf::parse(&buf);
assert_eq!(leaf.logical_block, 0);
assert_eq!(leaf.length, 10);
assert_eq!(leaf.physical_block, 500);
assert!(!leaf.unwritten);
}
#[test]
fn extent_leaf_unwritten_flag() {
let mut buf = vec![0u8; 12];
buf[4] = 0x05;
buf[5] = 0x80; let leaf = ExtentLeaf::parse(&buf);
assert_eq!(leaf.length, 5);
assert!(leaf.unwritten);
}
#[test]
fn parse_extent_index() {
let mut buf = vec![0u8; 12];
buf[0] = 0xE8;
buf[1] = 0x03; buf[4] = 0xD0;
buf[5] = 0x07; buf[8] = 1; let idx = ExtentIndex::parse(&buf);
assert_eq!(idx.logical_block, 1000);
assert_eq!(idx.child_block, (1u64 << 32) | 0x7D0);
}
}