chat4n6_sqlite_forensics/
freelist.rs1pub struct Freeblock {
2 pub offset: usize,
3 pub size: usize,
4 pub data: Vec<u8>,
5}
6
7pub fn parse_freeblock_chain(page: &[u8], page_size: usize) -> Vec<Freeblock> {
19 if page.len() < 4 {
20 return Vec::new();
21 }
22 let first_fb = u16::from_be_bytes([page[1], page[2]]) as usize;
23
24 let mut freeblocks = Vec::new();
25 let mut fb_offset = first_fb;
26 let mut visited = std::collections::HashSet::new();
27
28 while fb_offset != 0 && fb_offset + 4 <= page.len() {
29 if fb_offset >= page_size {
30 break; }
32 if !visited.insert(fb_offset) {
33 break; }
35 let next = u16::from_be_bytes([page[fb_offset], page[fb_offset + 1]]) as usize;
36 let size = u16::from_be_bytes([page[fb_offset + 2], page[fb_offset + 3]]) as usize;
37
38 if size < 4 || fb_offset + size > page.len() {
40 break;
41 }
42
43 let data = page[fb_offset + 4..fb_offset + size].to_vec();
44 freeblocks.push(Freeblock {
45 offset: fb_offset,
46 size,
47 data,
48 });
49 fb_offset = next;
50 }
51
52 freeblocks
53}
54
55pub fn walk_freelist_chain(db: &[u8], trunk_page: u32, page_size: u32) -> Vec<u32> {
65 let mut pages = Vec::new();
66 if trunk_page == 0 || db.is_empty() {
67 return pages;
68 }
69
70 let mut current = trunk_page;
71 let mut visited = std::collections::HashSet::new();
72
73 while current != 0 {
74 if !visited.insert(current) {
75 break; }
77 let page_start = (current as usize - 1) * page_size as usize;
78 let page_end = page_start + page_size as usize;
79 let Some(page) = db.get(page_start..page_end) else {
80 break;
81 };
82
83 if page.len() < 8 {
84 break;
85 }
86
87 let next_trunk = u32::from_be_bytes([page[0], page[1], page[2], page[3]]);
88 let leaf_count = u32::from_be_bytes([page[4], page[5], page[6], page[7]]) as usize;
89
90 pages.push(current); for i in 0..leaf_count {
94 let leaf_off = 8 + i * 4;
95 if leaf_off + 4 > page.len() {
96 break;
97 }
98 let leaf_page = u32::from_be_bytes([
99 page[leaf_off],
100 page[leaf_off + 1],
101 page[leaf_off + 2],
102 page[leaf_off + 3],
103 ]);
104 if leaf_page != 0 {
105 pages.push(leaf_page);
106 }
107 }
108
109 current = next_trunk;
110 }
111
112 pages
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn test_freelist_chain_empty() {
121 let pages = walk_freelist_chain(&[], 0, 4096);
122 assert!(pages.is_empty());
123 }
124
125 #[test]
126 fn test_walk_freelist_chain_with_trunk_and_leaves() {
127 let page_size: usize = 4096;
128 let mut db = vec![0u8; page_size * 2];
130 let trunk_start = page_size; db[trunk_start..trunk_start + 4].copy_from_slice(&0u32.to_be_bytes());
133 db[trunk_start + 4..trunk_start + 8].copy_from_slice(&2u32.to_be_bytes());
135 db[trunk_start + 8..trunk_start + 12].copy_from_slice(&3u32.to_be_bytes());
137 db[trunk_start + 12..trunk_start + 16].copy_from_slice(&4u32.to_be_bytes());
138
139 let pages = walk_freelist_chain(&db, 2, 4096);
140 assert_eq!(pages, vec![2, 3, 4]);
141 }
142
143 #[test]
144 fn test_freelist_chain_cycle_guard() {
145 let page_size: usize = 4096;
146 let mut db = vec![0u8; page_size * 3];
148 let p2 = page_size;
149 let p3 = page_size * 2;
150 db[p2..p2 + 4].copy_from_slice(&3u32.to_be_bytes());
152 db[p2 + 4..p2 + 8].copy_from_slice(&0u32.to_be_bytes());
153 db[p3..p3 + 4].copy_from_slice(&2u32.to_be_bytes());
155 db[p3 + 4..p3 + 8].copy_from_slice(&0u32.to_be_bytes());
156
157 let pages = walk_freelist_chain(&db, 2, 4096);
158 assert_eq!(pages, vec![2, 3]); }
160
161 #[test]
162 fn test_freeblock_chain_parse() {
163 let mut page = vec![0u8; 4096];
164 page[0] = 0x0d; page[1] = 0x00;
167 page[2] = 0xc8;
168 page[200] = 0x00;
170 page[201] = 0x00; page[202] = 0x00;
172 page[203] = 0x14; for i in 204..220 {
174 page[i] = (i % 256) as u8;
175 }
176
177 let freeblocks = parse_freeblock_chain(&page, 4096);
178 assert_eq!(freeblocks.len(), 1);
179 assert_eq!(freeblocks[0].offset, 200);
180 assert_eq!(freeblocks[0].size, 20);
181 assert_eq!(freeblocks[0].data.len(), 16); }
183
184 #[test]
185 fn test_freeblock_chain_two_blocks() {
186 let mut page = vec![0u8; 4096];
187 page[0] = 0x0d;
188 page[1] = 0x00;
190 page[2] = 0xc8; page[200] = 0x01;
193 page[201] = 0x2c; page[202] = 0x00;
195 page[203] = 0x0a; page[300] = 0x00;
198 page[301] = 0x00; page[302] = 0x00;
200 page[303] = 0x08; let freeblocks = parse_freeblock_chain(&page, 4096);
203 assert_eq!(freeblocks.len(), 2);
204 assert_eq!(freeblocks[0].offset, 200);
205 assert_eq!(freeblocks[0].size, 10);
206 assert_eq!(freeblocks[1].offset, 300);
207 assert_eq!(freeblocks[1].size, 8);
208 }
209
210 #[test]
211 fn test_freeblock_chain_self_cycle() {
212 let mut page = vec![0u8; 4096];
213 page[0] = 0x0d;
214 page[1] = 0x00;
216 page[2] = 0x64; page[100] = 0x00;
219 page[101] = 0x64; page[102] = 0x00;
221 page[103] = 0x08; let freeblocks = parse_freeblock_chain(&page, 4096);
224 assert_eq!(freeblocks.len(), 1); }
226}