exfat/region/data/entryset/
primary.rs1use bitfield::bitfield;
2#[cfg(all(feature = "chrono", feature = "std"))]
3use chrono::Local;
4#[cfg(feature = "chrono")]
5use chrono::{Datelike, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Timelike};
6use derive_more::Into;
7
8use super::super::entry_type::{EntryType, RawEntryType};
9use crate::endian::Little as LE;
10
11bitfield! {
12 #[derive(Copy, Clone, Debug, Default, Into)]
13 pub struct Timestamp(u32);
14 year_offset, set_year_offset: 31, 25;
15 pub month, set_month: 24, 21;
16 pub day, set_day: 20, 16;
17 pub hour, set_hour: 15, 11;
18 pub minute, set_minute: 10, 5;
19 pub double_second, set_double_second: 4, 0;
20}
21
22impl Timestamp {
23 pub fn year(&self) -> u32 {
24 self.year_offset() + 1980
25 }
26
27 pub fn set_year(&mut self, year: u32) {
28 self.set_year_offset(year - 1980)
29 }
30
31 pub fn second(&self) -> u32 {
32 self.double_second() * 2
33 }
34
35 pub fn set_second(&mut self, second: u32) {
36 self.set_double_second(second / 2)
37 }
38}
39
40#[cfg(feature = "chrono")]
41impl Into<NaiveDateTime> for Timestamp {
42 fn into(self) -> NaiveDateTime {
43 let date = NaiveDate::from_ymd_opt(self.year() as i32, self.month(), self.day());
44 let time = NaiveTime::from_hms_opt(self.hour(), self.minute(), self.second());
45 NaiveDateTime::new(date.unwrap_or_default(), time.unwrap_or_default())
46 }
47}
48
49#[cfg(feature = "chrono")]
50impl From<NaiveDateTime> for Timestamp {
51 fn from(datetime: NaiveDateTime) -> Self {
52 let mut timestamp = Self::default();
53 timestamp.set_year(datetime.year() as u32);
54 timestamp.set_month(datetime.month());
55 timestamp.set_day(datetime.day());
56 timestamp.set_hour(datetime.hour());
57 timestamp.set_minute(datetime.minute());
58 timestamp.set_second(datetime.second());
59 timestamp
60 }
61}
62
63#[cfg(all(feature = "chrono", feature = "std"))]
64impl Timestamp {
65 fn chrono_with_millis(&self, millis: u32) -> Result<NaiveDateTime, ()> {
66 let date = NaiveDate::from_ymd_opt(self.year() as i32, self.month(), self.day());
67 let (hour, minute, second) = (self.hour(), self.minute(), self.second());
68 let time = NaiveTime::from_hms_milli_opt(hour, minute, second, millis);
69 Ok(NaiveDateTime::new(date.ok_or(())?, time.ok_or(())?))
70 }
71}
72
73bitfield! {
74 #[derive(Copy, Clone, Default, Debug, Into)]
75 pub struct FileAttributes(u16);
76 pub read_only, set_read_only: 0, 0;
77 pub hidden, set_hidden: 1, 1;
78 pub system, set_system: 2, 2;
79 pub directory, set_directory: 4, 4;
80 pub archive, set_archive: 5, 5;
81}
82
83impl FileAttributes {
84 pub fn new(directory: bool) -> Self {
85 let mut attributes = Self::default();
86 if directory {
87 attributes.set_directory(1);
88 } else {
89 attributes.set_archive(1);
90 }
91 attributes
92 }
93}
94
95#[derive(Copy, Clone, Debug, Default)]
96pub struct UTCOffset(u8);
97
98impl UTCOffset {
99 pub fn new(minutes: i16) -> Self {
100 Self((minutes / 15) as u8 | 0x80)
101 }
102
103 pub fn minutes(&self) -> i16 {
104 match self.0 & 0x80 > 0 {
105 true => (((self.0 & 0x7F) << 1) as i8 >> 1) as i16 * 15,
106 false => 0,
107 }
108 }
109}
110
111#[cfg(feature = "chrono")]
112impl core::convert::TryInto<FixedOffset> for UTCOffset {
113 type Error = ();
114 fn try_into(self) -> Result<FixedOffset, Self::Error> {
115 FixedOffset::east_opt(self.minutes() as i32 * 60).ok_or(())
116 }
117}
118
119#[derive(Copy, Clone, Debug, Default)]
120pub struct DateTime {
121 pub timestamp: Timestamp,
122 pub millisecond: u16,
123 pub utc_offset: UTCOffset,
124}
125
126#[cfg(feature = "extern-datetime-now")]
127extern "Rust" {
128 pub(crate) fn exfat_datetime_now() -> DateTime;
129}
130
131impl DateTime {
132 pub fn now() -> Self {
133 match () {
134 #[cfg(feature = "extern-datetime-now")]
135 () => unsafe { exfat_datetime_now() },
136 #[cfg(not(feature = "extern-datetime-now"))]
137 () => Self::default(),
138 }
139 }
140}
141
142#[cfg(all(feature = "chrono", feature = "std"))]
143impl DateTime {
144 pub fn localtime(&self) -> Result<chrono::DateTime<Local>, ()> {
145 let naive = self.timestamp.chrono_with_millis(self.millisecond as u32)?;
146 let offset: FixedOffset = self.utc_offset.try_into()?;
147 let datetime: chrono::DateTime<FixedOffset> = chrono::DateTime::from_naive_utc_and_offset(naive, offset);
148 Ok(datetime.with_timezone(&Local))
149 }
150}
151
152#[cfg(feature = "chrono")]
153impl<TZ: chrono::Offset + chrono::TimeZone> From<chrono::DateTime<TZ>> for DateTime {
154 fn from(datetime: chrono::DateTime<TZ>) -> Self {
155 let offset = datetime.timezone().fix();
156 let seconds = offset.local_minus_utc();
157 let utc_offset = UTCOffset::new((seconds / 60) as i16);
158 let naive = datetime.naive_utc();
159 let millisecond = naive.and_utc().timestamp_subsec_millis() as u16;
160 Self { timestamp: naive.into(), millisecond, utc_offset }
161 }
162}
163
164#[derive(Copy, Clone, Default, Debug)]
165#[repr(C, packed(1))]
166pub struct FileDirectory {
167 pub(crate) entry_type: RawEntryType,
168 pub(crate) secondary_count: u8,
169 pub(crate) set_checksum: LE<u16>,
170 pub(crate) file_attributes: LE<u16>,
171 _reserved1: [u8; 2],
172 create_timestamp: LE<u32>,
173 last_modified_timestamp: LE<u32>,
174 last_accessed_timestamp: LE<u32>,
175 create_10ms_increment: u8,
176 last_modified_10ms_increment: u8,
177 create_utc_offset: UTCOffset,
178 last_modified_utc_offset: UTCOffset,
179 last_accessed_utc_offset: UTCOffset,
180 _reserved2: [u8; 7],
181}
182
183impl FileDirectory {
184 pub(crate) fn new(secondary_count: u8, directory: bool) -> Self {
185 let now = DateTime::now();
186 let timestamp: LE<u32> = u32::from(now.timestamp).into();
187 let millis = (now.millisecond / 10) as u8;
188 FileDirectory {
189 entry_type: RawEntryType::new(EntryType::FileDirectory, true),
190 secondary_count,
191 file_attributes: u16::from(FileAttributes::new(directory)).into(),
192 create_timestamp: timestamp,
193 create_10ms_increment: millis,
194 create_utc_offset: now.utc_offset,
195 last_modified_timestamp: timestamp,
196 last_modified_10ms_increment: millis,
197 last_modified_utc_offset: now.utc_offset,
198 last_accessed_timestamp: timestamp,
199 last_accessed_utc_offset: now.utc_offset,
200 ..Default::default()
201 }
202 }
203
204 pub fn file_attributes(&self) -> FileAttributes {
205 FileAttributes(self.file_attributes.to_ne())
206 }
207
208 pub fn create_timestamp(&self) -> DateTime {
209 DateTime {
210 timestamp: Timestamp(self.create_timestamp.to_ne()),
211 millisecond: self.create_10ms_increment as u16 * 10,
212 utc_offset: self.create_utc_offset,
213 }
214 }
215
216 pub fn last_modified_timestamp(&self) -> DateTime {
217 DateTime {
218 timestamp: Timestamp(self.last_modified_timestamp.to_ne()),
219 millisecond: self.last_modified_10ms_increment as u16 * 10,
220 utc_offset: self.last_modified_utc_offset,
221 }
222 }
223
224 pub(crate) fn update_last_modified_timestamp(&mut self, datetime: DateTime) {
225 self.last_modified_timestamp = datetime.timestamp.0.into();
226 self.last_modified_10ms_increment = (datetime.millisecond / 10) as u8;
227 self.last_modified_utc_offset = datetime.utc_offset;
228 }
229
230 pub fn last_accessed_timestamp(&self) -> DateTime {
231 DateTime {
232 timestamp: Timestamp(self.last_accessed_timestamp.to_ne()),
233 millisecond: 0,
234 utc_offset: self.last_accessed_utc_offset,
235 }
236 }
237
238 pub(crate) fn update_last_accessed_timestamp(&mut self, datetime: DateTime) {
239 self.last_accessed_timestamp = datetime.timestamp.0.into();
240 self.last_accessed_utc_offset = datetime.utc_offset;
241 }
242}
243
244pub(crate) struct Checksum(u16);
245
246impl Checksum {
247 pub(crate) fn new() -> Self {
248 Self(0)
249 }
250
251 pub(crate) fn write(&mut self, value: u16) {
252 self.0 = if self.0 & 1 > 0 { 0x8000 } else { 0 } + (self.0 >> 1) + value
253 }
254
255 pub(crate) fn sum(&self) -> u16 {
256 self.0
257 }
258}
259
260pub(crate) fn name_hash(name: &str) -> u16 {
261 let mut checksum = Checksum::new();
262 for ch in name.chars() {
263 checksum.write(ch as u16);
264 checksum.write(((ch as u32) >> 16) as u16);
265 }
266 checksum.sum()
267}