ntfs_core/
standard_information.rs1use crate::bytes::{arr, le_u32, le_u64};
10use crate::error::{NtfsError, Result};
11use crate::time::Filetime;
12
13const SI_MIN: usize = 0x30;
16const SI_V3: usize = 0x48;
18
19pub use forensicnomicon::ntfs::file_attributes as file_attr;
22
23#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct StandardInformation {
26 pub created: Filetime,
28 pub modified: Filetime,
30 pub mft_modified: Filetime,
32 pub accessed: Filetime,
34 pub file_attributes: u32,
36 pub security_id: Option<u32>,
38 pub usn: Option<u64>,
40}
41
42impl StandardInformation {
43 #[must_use]
45 pub fn is_hidden(&self) -> bool {
46 self.file_attributes & file_attr::HIDDEN != 0
47 }
48
49 #[must_use]
51 pub fn is_system(&self) -> bool {
52 self.file_attributes & file_attr::SYSTEM != 0
53 }
54
55 #[must_use]
57 pub fn is_read_only(&self) -> bool {
58 self.file_attributes & file_attr::READONLY != 0
59 }
60
61 pub fn parse(content: &[u8]) -> Result<StandardInformation> {
67 if content.len() < SI_MIN {
68 return Err(NtfsError::TooShort {
69 what: "$STANDARD_INFORMATION",
70 need: SI_MIN,
71 got: content.len(),
72 });
73 }
74 let ft = |o: usize| Filetime::from_le(&arr(content, o));
75 let file_attributes = le_u32(content, 0x20);
76
77 let (security_id, usn) = if content.len() >= SI_V3 {
78 (Some(le_u32(content, 0x34)), Some(le_u64(content, 0x40)))
79 } else {
80 (None, None)
81 };
82
83 Ok(StandardInformation {
84 created: ft(0x00),
85 modified: ft(0x08),
86 mft_modified: ft(0x10),
87 accessed: ft(0x18),
88 file_attributes,
89 security_id,
90 usn,
91 })
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 fn make_si(
100 created: u64,
101 modified: u64,
102 mft_modified: u64,
103 accessed: u64,
104 attrs: u32,
105 v3: Option<(u32, u64)>, ) -> Vec<u8> {
107 let len = if v3.is_some() { SI_V3 } else { SI_MIN };
108 let mut c = vec![0u8; len];
109 c[0x00..0x08].copy_from_slice(&created.to_le_bytes());
110 c[0x08..0x10].copy_from_slice(&modified.to_le_bytes());
111 c[0x10..0x18].copy_from_slice(&mft_modified.to_le_bytes());
112 c[0x18..0x20].copy_from_slice(&accessed.to_le_bytes());
113 c[0x20..0x24].copy_from_slice(&attrs.to_le_bytes());
114 if let Some((sid, usn)) = v3 {
115 c[0x34..0x38].copy_from_slice(&sid.to_le_bytes());
116 c[0x40..0x48].copy_from_slice(&usn.to_le_bytes());
117 }
118 c
119 }
120
121 #[test]
122 fn parses_ntfs12_standard_information() {
123 let c = make_si(0x10, 0x20, 0x30, 0x40, file_attr::ARCHIVE, None);
124 let si = StandardInformation::parse(&c).unwrap();
125 assert_eq!(si.created, Filetime(0x10));
126 assert_eq!(si.modified, Filetime(0x20));
127 assert_eq!(si.mft_modified, Filetime(0x30));
128 assert_eq!(si.accessed, Filetime(0x40));
129 assert_eq!(si.file_attributes, file_attr::ARCHIVE);
130 assert_eq!(si.security_id, None);
131 assert_eq!(si.usn, None);
132 }
133
134 #[test]
135 fn parses_ntfs30_security_and_usn() {
136 let c = make_si(1, 2, 3, 4, 0, Some((0x101, 0xDEAD_BEEF)));
137 let si = StandardInformation::parse(&c).unwrap();
138 assert_eq!(si.security_id, Some(0x101));
139 assert_eq!(si.usn, Some(0xDEAD_BEEF));
140 }
141
142 #[test]
143 fn flag_predicates() {
144 let c = make_si(0, 0, 0, 0, file_attr::HIDDEN | file_attr::SYSTEM, None);
145 let si = StandardInformation::parse(&c).unwrap();
146 assert!(si.is_hidden());
147 assert!(si.is_system());
148 assert!(!si.is_read_only());
149 }
150
151 #[test]
152 fn rejects_too_short() {
153 let c = vec![0u8; 0x20];
154 assert!(matches!(
155 StandardInformation::parse(&c),
156 Err(NtfsError::TooShort { .. })
157 ));
158 }
159}