exfat/region/data/entryset/
primary.rs

1use 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}