1#![forbid(unsafe_code)]
2use crate::error::{Ext4Error, Result};
3
4pub const EXTENT_MAGIC: u16 = 0xF30A;
5
6#[inline]
11fn le16(buf: &[u8], off: usize) -> u16 {
12 u16::from_le_bytes([buf[off], buf[off + 1]])
13}
14
15#[inline]
16fn le32(buf: &[u8], off: usize) -> u32 {
17 u32::from_le_bytes([buf[off], buf[off + 1], buf[off + 2], buf[off + 3]])
18}
19
20#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct ExtentHeader {
31 pub magic: u16,
32 pub entries: u16,
33 pub max: u16,
34 pub depth: u16,
35 pub generation: u32,
36}
37
38impl ExtentHeader {
39 pub fn parse(buf: &[u8]) -> Result<Self> {
40 if buf.len() < 12 {
41 return Err(Ext4Error::TooShort {
42 structure: "ExtentHeader",
43 expected: 12,
44 found: buf.len(),
45 });
46 }
47 let magic = le16(buf, 0);
48 if magic != EXTENT_MAGIC {
49 return Err(Ext4Error::CorruptMetadata {
50 structure: "ExtentHeader",
51 detail: format!("bad magic: 0x{magic:04X}"),
52 });
53 }
54 Ok(Self {
55 magic,
56 entries: le16(buf, 2),
57 max: le16(buf, 4),
58 depth: le16(buf, 6),
59 generation: le32(buf, 8),
60 })
61 }
62}
63
64#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct ExtentLeaf {
74 pub logical_block: u32,
75 pub length: u16,
76 pub physical_block: u64,
77 pub unwritten: bool,
78}
79
80impl ExtentLeaf {
81 pub fn parse(buf: &[u8]) -> Self {
82 let logical_block = le32(buf, 0);
83 let ee_len = le16(buf, 4);
84 let start_hi = u64::from(le16(buf, 6));
85 let start_lo = u64::from(le32(buf, 8));
86
87 let unwritten = (ee_len & 0x8000) != 0;
88 let length = ee_len & 0x7FFF;
89 let physical_block = (start_hi << 32) | start_lo;
90
91 Self {
92 logical_block,
93 length,
94 physical_block,
95 unwritten,
96 }
97 }
98}
99
100#[derive(Debug, Clone, PartialEq, Eq)]
109pub struct ExtentIndex {
110 pub logical_block: u32,
111 pub child_block: u64,
112}
113
114impl ExtentIndex {
115 pub fn parse(buf: &[u8]) -> Self {
116 let logical_block = le32(buf, 0);
117 let leaf_lo = u64::from(le32(buf, 4));
118 let leaf_hi = u64::from(le16(buf, 8));
119 let child_block = (leaf_hi << 32) | leaf_lo;
120 Self {
121 logical_block,
122 child_block,
123 }
124 }
125}
126
127#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[test]
136 fn parse_extent_header() {
137 let mut buf = vec![0u8; 12];
138 buf[0] = 0x0A;
139 buf[1] = 0xF3; buf[2] = 3; buf[4] = 4; buf[6] = 0; let hdr = ExtentHeader::parse(&buf).unwrap();
144 assert_eq!(hdr.magic, EXTENT_MAGIC);
145 assert_eq!(hdr.entries, 3);
146 assert_eq!(hdr.max, 4);
147 assert_eq!(hdr.depth, 0);
148 }
149
150 #[test]
151 fn reject_bad_magic() {
152 let buf = vec![0u8; 12];
153 let err = ExtentHeader::parse(&buf).unwrap_err();
154 assert!(matches!(
155 err,
156 crate::error::Ext4Error::CorruptMetadata { .. }
157 ));
158 }
159
160 #[test]
161 fn parse_extent_leaf() {
162 let mut buf = vec![0u8; 12];
163 buf[4] = 10; buf[8] = 0xF4;
165 buf[9] = 0x01; let leaf = ExtentLeaf::parse(&buf);
167 assert_eq!(leaf.logical_block, 0);
168 assert_eq!(leaf.length, 10);
169 assert_eq!(leaf.physical_block, 500);
170 assert!(!leaf.unwritten);
171 }
172
173 #[test]
174 fn extent_leaf_unwritten_flag() {
175 let mut buf = vec![0u8; 12];
176 buf[4] = 0x05;
177 buf[5] = 0x80; let leaf = ExtentLeaf::parse(&buf);
179 assert_eq!(leaf.length, 5);
180 assert!(leaf.unwritten);
181 }
182
183 #[test]
184 fn parse_extent_index() {
185 let mut buf = vec![0u8; 12];
186 buf[0] = 0xE8;
187 buf[1] = 0x03; buf[4] = 0xD0;
189 buf[5] = 0x07; buf[8] = 1; let idx = ExtentIndex::parse(&buf);
192 assert_eq!(idx.logical_block, 1000);
193 assert_eq!(idx.child_block, (1u64 << 32) | 0x7D0);
194 }
195}