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")]
127unsafe extern "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> =
148            chrono::DateTime::from_naive_utc_and_offset(naive, offset);
149        Ok(datetime.with_timezone(&Local))
150    }
151}
152
153#[cfg(feature = "chrono")]
154impl<TZ: chrono::Offset + chrono::TimeZone> From<chrono::DateTime<TZ>> for DateTime {
155    fn from(datetime: chrono::DateTime<TZ>) -> Self {
156        let offset = datetime.timezone().fix();
157        let seconds = offset.local_minus_utc();
158        let utc_offset = UTCOffset::new((seconds / 60) as i16);
159        let naive = datetime.naive_utc();
160        let millisecond = naive.and_utc().timestamp_subsec_millis() as u16;
161        Self { timestamp: naive.into(), millisecond, utc_offset }
162    }
163}
164
165#[derive(Copy, Clone, Default, Debug)]
166#[repr(C, packed(1))]
167pub struct FileDirectory {
168    pub(crate) entry_type: RawEntryType,
169    pub(crate) secondary_count: u8,
170    pub(crate) set_checksum: LE<u16>,
171    pub(crate) file_attributes: LE<u16>,
172    _reserved1: [u8; 2],
173    create_timestamp: LE<u32>,
174    last_modified_timestamp: LE<u32>,
175    last_accessed_timestamp: LE<u32>,
176    create_10ms_increment: u8,
177    last_modified_10ms_increment: u8,
178    create_utc_offset: UTCOffset,
179    last_modified_utc_offset: UTCOffset,
180    last_accessed_utc_offset: UTCOffset,
181    _reserved2: [u8; 7],
182}
183
184impl FileDirectory {
185    pub(crate) fn new(secondary_count: u8, directory: bool) -> Self {
186        let now = DateTime::now();
187        let timestamp: LE<u32> = u32::from(now.timestamp).into();
188        let millis = (now.millisecond / 10) as u8;
189        FileDirectory {
190            entry_type: RawEntryType::new(EntryType::FileDirectory, true),
191            secondary_count,
192            file_attributes: u16::from(FileAttributes::new(directory)).into(),
193            create_timestamp: timestamp,
194            create_10ms_increment: millis,
195            create_utc_offset: now.utc_offset,
196            last_modified_timestamp: timestamp,
197            last_modified_10ms_increment: millis,
198            last_modified_utc_offset: now.utc_offset,
199            last_accessed_timestamp: timestamp,
200            last_accessed_utc_offset: now.utc_offset,
201            ..Default::default()
202        }
203    }
204
205    pub fn file_attributes(&self) -> FileAttributes {
206        FileAttributes(self.file_attributes.to_ne())
207    }
208
209    pub fn create_timestamp(&self) -> DateTime {
210        DateTime {
211            timestamp: Timestamp(self.create_timestamp.to_ne()),
212            millisecond: self.create_10ms_increment as u16 * 10,
213            utc_offset: self.create_utc_offset,
214        }
215    }
216
217    pub fn last_modified_timestamp(&self) -> DateTime {
218        DateTime {
219            timestamp: Timestamp(self.last_modified_timestamp.to_ne()),
220            millisecond: self.last_modified_10ms_increment as u16 * 10,
221            utc_offset: self.last_modified_utc_offset,
222        }
223    }
224
225    pub(crate) fn update_last_modified_timestamp(&mut self, datetime: DateTime) {
226        self.last_modified_timestamp = datetime.timestamp.0.into();
227        self.last_modified_10ms_increment = (datetime.millisecond / 10) as u8;
228        self.last_modified_utc_offset = datetime.utc_offset;
229    }
230
231    pub fn last_accessed_timestamp(&self) -> DateTime {
232        DateTime {
233            timestamp: Timestamp(self.last_accessed_timestamp.to_ne()),
234            millisecond: 0,
235            utc_offset: self.last_accessed_utc_offset,
236        }
237    }
238
239    pub(crate) fn update_last_accessed_timestamp(&mut self, datetime: DateTime) {
240        self.last_accessed_timestamp = datetime.timestamp.0.into();
241        self.last_accessed_utc_offset = datetime.utc_offset;
242    }
243}
244
245pub(crate) struct Checksum(u16);
246
247impl Checksum {
248    pub(crate) fn new() -> Self {
249        Self(0)
250    }
251
252    pub(crate) fn write(&mut self, value: u16) {
253        self.0 = if self.0 & 1 > 0 { 0x8000 } else { 0 } + (self.0 >> 1) + value
254    }
255
256    pub(crate) fn sum(&self) -> u16 {
257        self.0
258    }
259}
260
261pub(crate) fn name_hash(name: &str) -> u16 {
262    let mut checksum = Checksum::new();
263    for ch in name.chars() {
264        checksum.write(ch as u16);
265        checksum.write(((ch as u32) >> 16) as u16);
266    }
267    checksum.sum()
268}