Skip to main content

winreg_core/
key.rs

1//! Key struct — high-level interface for navigating registry keys.
2
3use std::io::Cursor;
4
5use winreg_format::cells::{CellOffset, RawKeyNode, SubkeyIndex};
6use winreg_format::flags::KeyFlags;
7
8use crate::cell_reader::Cell;
9use crate::error::{HiveError, Result};
10use crate::hive::Hive;
11use crate::value::Value;
12
13/// Difference between the Windows FILETIME epoch (1601-01-01) and
14/// the Unix epoch (1970-01-01) expressed in 100-nanosecond intervals.
15const FILETIME_EPOCH_DIFF: u64 = 116_444_736_000_000_000;
16
17/// A registry key within a hive.
18pub struct Key<'h> {
19    pub(crate) hive: &'h Hive<Cursor<Vec<u8>>>,
20    pub(crate) node: RawKeyNode,
21    pub(crate) offset: CellOffset,
22}
23
24impl<'h> Key<'h> {
25    pub fn name(&self) -> String {
26        self.node.key_name()
27    }
28
29    pub fn last_written_raw(&self) -> u64 {
30        self.node.last_written
31    }
32
33    pub fn last_written(&self) -> Option<chrono::DateTime<chrono::Utc>> {
34        filetime_to_datetime(self.node.last_written)
35    }
36
37    pub fn flags(&self) -> KeyFlags {
38        self.node.flags
39    }
40
41    pub fn is_root(&self) -> bool {
42        self.node.is_root()
43    }
44
45    pub fn subkey_count(&self) -> u32 {
46        self.node.subkey_count
47    }
48
49    pub fn value_count(&self) -> u32 {
50        self.node.value_count
51    }
52
53    pub fn offset(&self) -> CellOffset {
54        self.offset
55    }
56
57    pub fn subkeys(&self) -> Result<Vec<Key<'h>>> {
58        if self.node.subkey_count == 0 || self.node.subkeys_list_offset.is_null() {
59            return Ok(Vec::new());
60        }
61        let offsets = self.collect_subkey_offsets(self.node.subkeys_list_offset)?;
62        let mut keys = Vec::with_capacity(offsets.len());
63        for offset in offsets {
64            let cell = self.hive.read_cell(offset)?;
65            if let Cell::KeyNode(nk) = cell {
66                keys.push(Key {
67                    hive: self.hive,
68                    node: nk,
69                    offset,
70                });
71            }
72        }
73        Ok(keys)
74    }
75
76    pub fn subkey(&self, name: &str) -> Result<Option<Key<'h>>> {
77        let target = name.to_ascii_uppercase();
78        for key in self.subkeys()? {
79            if key.name().to_ascii_uppercase() == target {
80                return Ok(Some(key));
81            }
82        }
83        Ok(None)
84    }
85
86    pub fn subkey_path(&self, path: &str) -> Result<Option<Key<'h>>> {
87        let parts: Vec<&str> = path.split('\\').filter(|s| !s.is_empty()).collect();
88        if parts.is_empty() {
89            return Ok(None);
90        }
91
92        // Walk the path one level at a time, owning each intermediate key.
93        let Some(first) = self.subkey(parts[0])? else {
94            return Ok(None);
95        };
96
97        let mut current = first;
98        for part in &parts[1..] {
99            let Some(next) = current.subkey(part)? else {
100                return Ok(None);
101            };
102            current = next;
103        }
104        Ok(Some(current))
105    }
106
107    pub fn values(&self) -> Result<Vec<Value<'h>>> {
108        if self.node.value_count == 0 || self.node.values_list_offset.is_null() {
109            return Ok(Vec::new());
110        }
111        let (_header, body) = self.hive.read_cell_raw(self.node.values_list_offset)?;
112        let count = self.node.value_count as usize;
113        let mut values = Vec::with_capacity(count);
114        for i in 0..count {
115            let base = i * 4;
116            if base + 4 > body.len() {
117                break;
118            }
119            let vk_offset =
120                CellOffset(crate::bytes::le_u32(&body, base));
121            let cell = self.hive.read_cell(vk_offset)?;
122            if let Cell::KeyValue(vk) = cell {
123                values.push(Value {
124                    hive: self.hive,
125                    vk,
126                    offset: vk_offset,
127                });
128            }
129        }
130        Ok(values)
131    }
132
133    pub fn value(&self, name: &str) -> Result<Option<Value<'h>>> {
134        let target = name.to_ascii_uppercase();
135        for val in self.values()? {
136            if val.name().to_ascii_uppercase() == target {
137                return Ok(Some(val));
138            }
139        }
140        Ok(None)
141    }
142
143    fn collect_subkey_offsets(&self, index_offset: CellOffset) -> Result<Vec<CellOffset>> {
144        let cell = self.hive.read_cell(index_offset)?;
145        match cell {
146            Cell::Index(SubkeyIndex::HashLeaf(elements)) => {
147                Ok(elements.iter().map(|e| e.key_offset).collect())
148            }
149            Cell::Index(SubkeyIndex::FastLeaf(elements)) => {
150                Ok(elements.iter().map(|e| e.key_offset).collect())
151            }
152            Cell::Index(SubkeyIndex::IndexLeaf(offsets)) => Ok(offsets),
153            Cell::Index(SubkeyIndex::RootIndex(sub_indices)) => {
154                let mut all = Vec::new();
155                for sub_offset in sub_indices {
156                    all.extend(self.collect_subkey_offsets(sub_offset)?);
157                }
158                Ok(all)
159            }
160            _ => Ok(Vec::new()),
161        }
162    }
163}
164
165impl Hive<Cursor<Vec<u8>>> {
166    pub fn root_key(&self) -> Result<Key<'_>> {
167        let offset = self.root_cell_offset();
168        let cell = self.read_cell(offset)?;
169        match cell {
170            Cell::KeyNode(nk) => Ok(Key {
171                hive: self,
172                node: nk,
173                offset,
174            }),
175            _ => Err(HiveError::InvalidCellSignature {
176                offset,
177                expected: "nk (root key node)",
178                byte0: 0,
179                byte1: 0,
180            }),
181        }
182    }
183
184    pub fn open_key(&self, path: &str) -> Result<Option<Key<'_>>> {
185        self.root_key()?.subkey_path(path)
186    }
187}
188
189/// Convert a Windows FILETIME value to a chrono [`chrono::DateTime`].
190///
191/// Returns `None` if `filetime` is zero or predates the Unix epoch.
192pub fn filetime_to_datetime(filetime: u64) -> Option<chrono::DateTime<chrono::Utc>> {
193    if filetime == 0 || filetime < FILETIME_EPOCH_DIFF {
194        return None;
195    }
196    let unix_100ns = filetime - FILETIME_EPOCH_DIFF;
197    #[allow(clippy::cast_possible_wrap)]
198    let secs = (unix_100ns / 10_000_000) as i64;
199    #[allow(clippy::cast_possible_truncation)]
200    let nanos = ((unix_100ns % 10_000_000) * 100) as u32;
201    chrono::DateTime::from_timestamp(secs, nanos)
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207    use chrono::Datelike;
208
209    #[test]
210    fn filetime_epoch() {
211        let dt = filetime_to_datetime(133_485_408_000_000_000).unwrap();
212        assert_eq!(dt.year(), 2024);
213        assert_eq!(dt.month(), 1);
214        assert_eq!(dt.day(), 1);
215    }
216
217    #[test]
218    fn filetime_zero_returns_none() {
219        assert!(filetime_to_datetime(0).is_none());
220    }
221}