1use 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
13const FILETIME_EPOCH_DIFF: u64 = 116_444_736_000_000_000;
16
17pub 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 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
189pub 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}