Skip to main content

winreg_core/
cell_reader.rs

1//! Cell reading — read typed cells from a hive by offset.
2
3use std::io::Cursor;
4
5use winreg_format::cells::{
6    CellHeader, CellOffset, CellSignature, RawBigData, RawKeyNode, RawKeyValue, RawSecurityKey,
7    SubkeyIndex,
8};
9
10use crate::error::{HiveError, Result};
11use crate::hive::Hive;
12
13/// Typed cell content after dispatching on signature.
14#[derive(Debug)]
15pub enum Cell {
16    KeyNode(RawKeyNode),
17    KeyValue(RawKeyValue),
18    SecurityKey(RawSecurityKey),
19    Index(SubkeyIndex),
20    BigData(RawBigData),
21    /// Raw data cell (no recognized signature — value data, class name, etc.).
22    Data(Vec<u8>),
23}
24
25impl Hive<Cursor<Vec<u8>>> {
26    /// Read raw bytes at a cell offset. Returns (`cell_header`, `cell_body_bytes`).
27    pub fn read_cell_raw(&self, offset: CellOffset) -> Result<(CellHeader, Vec<u8>)> {
28        if offset.is_null() {
29            return Err(HiveError::NullOffset);
30        }
31
32        #[allow(clippy::cast_possible_truncation)]
33        let file_offset = offset.file_offset() as usize;
34        let data = self.reader.get_ref();
35
36        if file_offset + 4 > data.len() {
37            return Err(HiveError::CellOverflow {
38                offset,
39                cell_size: 0,
40                hbin_end: data.len() as u64,
41            });
42        }
43
44        let header_bytes: [u8; 4] = data
45            .get(file_offset..file_offset + 4)
46            .and_then(|s| <[u8; 4]>::try_from(s).ok())
47            .unwrap_or([0; 4]);
48        let header = CellHeader::from_bytes(&header_bytes);
49
50        if !header.is_allocated() {
51            return Err(HiveError::UnallocatedCell { offset });
52        }
53
54        let size = header.size() as usize;
55        let end = file_offset + size;
56        if end > data.len() {
57            return Err(HiveError::CellOverflow {
58                offset,
59                cell_size: header.size(),
60                hbin_end: data.len() as u64,
61            });
62        }
63
64        let body = data[file_offset + 4..end].to_vec();
65        Ok((header, body))
66    }
67
68    /// Read and parse a typed cell at the given offset.
69    pub fn read_cell(&self, offset: CellOffset) -> Result<Cell> {
70        let (_header, body) = self.read_cell_raw(offset)?;
71
72        if body.len() < 2 {
73            return Ok(Cell::Data(body));
74        }
75
76        let sig_bytes: [u8; 2] = [body[0], body[1]];
77        let after_sig = &body[2..];
78
79        match CellSignature::from_bytes(&sig_bytes) {
80            Some(CellSignature::KeyNode) => {
81                let nk = RawKeyNode::parse(after_sig).ok_or(HiveError::InvalidCellSignature {
82                    offset,
83                    expected: "nk (valid key node)",
84                    byte0: sig_bytes[0],
85                    byte1: sig_bytes[1],
86                })?;
87                Ok(Cell::KeyNode(nk))
88            }
89            Some(CellSignature::KeyValue) => {
90                let vk = RawKeyValue::parse(after_sig).ok_or(HiveError::InvalidCellSignature {
91                    offset,
92                    expected: "vk (valid key value)",
93                    byte0: sig_bytes[0],
94                    byte1: sig_bytes[1],
95                })?;
96                Ok(Cell::KeyValue(vk))
97            }
98            Some(CellSignature::SecurityKey) => {
99                let sk =
100                    RawSecurityKey::parse(after_sig).ok_or(HiveError::InvalidCellSignature {
101                        offset,
102                        expected: "sk (valid security key)",
103                        byte0: sig_bytes[0],
104                        byte1: sig_bytes[1],
105                    })?;
106                Ok(Cell::SecurityKey(sk))
107            }
108            Some(CellSignature::FastLeaf) => {
109                let idx =
110                    SubkeyIndex::parse_lf(after_sig).ok_or(HiveError::InvalidCellSignature {
111                        offset,
112                        expected: "lf (valid fast leaf)",
113                        byte0: sig_bytes[0],
114                        byte1: sig_bytes[1],
115                    })?;
116                Ok(Cell::Index(idx))
117            }
118            Some(CellSignature::HashLeaf) => {
119                let idx =
120                    SubkeyIndex::parse_lh(after_sig).ok_or(HiveError::InvalidCellSignature {
121                        offset,
122                        expected: "lh (valid hash leaf)",
123                        byte0: sig_bytes[0],
124                        byte1: sig_bytes[1],
125                    })?;
126                Ok(Cell::Index(idx))
127            }
128            Some(CellSignature::IndexLeaf) => {
129                let idx =
130                    SubkeyIndex::parse_li(after_sig).ok_or(HiveError::InvalidCellSignature {
131                        offset,
132                        expected: "li (valid index leaf)",
133                        byte0: sig_bytes[0],
134                        byte1: sig_bytes[1],
135                    })?;
136                Ok(Cell::Index(idx))
137            }
138            Some(CellSignature::RootIndex) => {
139                let idx =
140                    SubkeyIndex::parse_ri(after_sig).ok_or(HiveError::InvalidCellSignature {
141                        offset,
142                        expected: "ri (valid root index)",
143                        byte0: sig_bytes[0],
144                        byte1: sig_bytes[1],
145                    })?;
146                Ok(Cell::Index(idx))
147            }
148            Some(CellSignature::BigData) => {
149                let db = RawBigData::parse(after_sig).ok_or(HiveError::InvalidCellSignature {
150                    offset,
151                    expected: "db (valid big data)",
152                    byte0: sig_bytes[0],
153                    byte1: sig_bytes[1],
154                })?;
155                Ok(Cell::BigData(db))
156            }
157            None => Ok(Cell::Data(body)),
158        }
159    }
160
161    /// Read raw data bytes at a cell offset (no signature dispatch).
162    /// Used for value data cells, class names, etc.
163    pub fn read_data_cell(&self, offset: CellOffset) -> Result<Vec<u8>> {
164        let (_header, body) = self.read_cell_raw(offset)?;
165        Ok(body)
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172
173    fn build_minimal_hive() -> Vec<u8> {
174        use winreg_format::header::BaseBlock;
175
176        let hbin_size: u32 = 4096;
177        let total_size = BaseBlock::SIZE + hbin_size as usize;
178        let mut buf = vec![0u8; total_size];
179
180        buf[0..4].copy_from_slice(b"regf");
181        buf[0x04..0x08].copy_from_slice(&1u32.to_le_bytes());
182        buf[0x08..0x0C].copy_from_slice(&1u32.to_le_bytes());
183        buf[0x14..0x18].copy_from_slice(&1u32.to_le_bytes());
184        buf[0x18..0x1C].copy_from_slice(&5u32.to_le_bytes());
185        buf[0x20..0x24].copy_from_slice(&1u32.to_le_bytes());
186        buf[0x24..0x28].copy_from_slice(&32u32.to_le_bytes()); // root at cell offset 32
187        buf[0x28..0x2C].copy_from_slice(&hbin_size.to_le_bytes());
188        buf[0x2C..0x30].copy_from_slice(&1u32.to_le_bytes());
189        let checksum = BaseBlock::compute_checksum(&buf);
190        buf[0x1FC..0x200].copy_from_slice(&checksum.to_le_bytes());
191
192        let hbin_start = BaseBlock::SIZE;
193        buf[hbin_start..hbin_start + 4].copy_from_slice(b"hbin");
194        buf[hbin_start + 4..hbin_start + 8].copy_from_slice(&0u32.to_le_bytes());
195        buf[hbin_start + 8..hbin_start + 12].copy_from_slice(&hbin_size.to_le_bytes());
196
197        // Root NK cell at hbin offset 32
198        let cell_start = hbin_start + 32;
199        let cell_size: i32 = -128;
200        buf[cell_start..cell_start + 4].copy_from_slice(&cell_size.to_le_bytes());
201        buf[cell_start + 4..cell_start + 6].copy_from_slice(b"nk");
202        buf[cell_start + 6..cell_start + 8].copy_from_slice(&0x0024u16.to_le_bytes()); // HIVE_ENTRY | COMP_NAME
203                                                                                       // key_name_len = 4 at offset +74 from cell body sig
204        let name_len_offset = cell_start + 4 + 2 + 70; // size(4) + sig(2) + header fields(70)
205        buf[name_len_offset..name_len_offset + 2].copy_from_slice(&4u16.to_le_bytes());
206        // key name "root" at offset +76 from sig
207        let name_offset = name_len_offset + 4; // +2 for class_name_len
208        buf[name_offset..name_offset + 4].copy_from_slice(b"root");
209
210        // Free cell after NK
211        let free_start = cell_start + 128;
212        let free_size = (hbin_size as usize) - 32 - 128;
213        buf[free_start..free_start + 4].copy_from_slice(&(free_size as i32).to_le_bytes());
214
215        buf
216    }
217
218    #[test]
219    fn read_root_nk_cell() {
220        let hive = Hive::from_bytes(build_minimal_hive()).unwrap();
221        let root_offset = hive.root_cell_offset();
222        let cell = hive.read_cell(root_offset).unwrap();
223        match cell {
224            Cell::KeyNode(nk) => {
225                assert!(nk.is_root());
226            }
227            other => panic!("expected KeyNode, got {other:?}"),
228        }
229    }
230
231    #[test]
232    fn null_offset_returns_error() {
233        let hive = Hive::from_bytes(build_minimal_hive()).unwrap();
234        assert!(matches!(
235            hive.read_cell(CellOffset::NULL),
236            Err(HiveError::NullOffset)
237        ));
238    }
239
240    #[test]
241    fn out_of_bounds_offset_returns_error() {
242        let hive = Hive::from_bytes(build_minimal_hive()).unwrap();
243        let bad_offset = CellOffset(0xFFFFFE); // way beyond data
244        assert!(matches!(
245            hive.read_cell(bad_offset),
246            Err(HiveError::CellOverflow { .. })
247        ));
248    }
249}