embedded-exfat 0.4.0

ExFAT filesystem library with async support, mainly focusing on embedded system
Documentation
use bitfield::bitfield;
#[cfg(all(feature = "chrono", feature = "std"))]
use chrono::Local;
#[cfg(feature = "chrono")]
use chrono::{Datelike, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Timelike};
use derive_more::Into;

use super::super::entry_type::{EntryType, RawEntryType};
use crate::endian::Little as LE;

bitfield! {
    #[derive(Copy, Clone, Debug, Default, Into)]
    pub struct Timestamp(u32);
    year_offset, set_year_offset: 31, 25;
    pub month, set_month: 24, 21;
    pub day, set_day: 20, 16;
    pub hour, set_hour: 15, 11;
    pub minute, set_minute: 10, 5;
    pub double_second, set_double_second: 4, 0;
}

impl Timestamp {
    pub fn year(&self) -> u32 {
        self.year_offset() + 1980
    }

    pub fn set_year(&mut self, year: u32) {
        self.set_year_offset(year - 1980)
    }

    pub fn second(&self) -> u32 {
        self.double_second() * 2
    }

    pub fn set_second(&mut self, second: u32) {
        self.set_double_second(second / 2)
    }
}

#[cfg(feature = "chrono")]
impl Into<NaiveDateTime> for Timestamp {
    fn into(self) -> NaiveDateTime {
        let date = NaiveDate::from_ymd_opt(self.year() as i32, self.month(), self.day());
        let time = NaiveTime::from_hms_opt(self.hour(), self.minute(), self.second());
        NaiveDateTime::new(date.unwrap_or_default(), time.unwrap_or_default())
    }
}

#[cfg(feature = "chrono")]
impl From<NaiveDateTime> for Timestamp {
    fn from(datetime: NaiveDateTime) -> Self {
        let mut timestamp = Self::default();
        timestamp.set_year(datetime.year() as u32);
        timestamp.set_month(datetime.month());
        timestamp.set_day(datetime.day());
        timestamp.set_hour(datetime.hour());
        timestamp.set_minute(datetime.minute());
        timestamp.set_second(datetime.second());
        timestamp
    }
}

#[cfg(all(feature = "chrono", feature = "std"))]
impl Timestamp {
    fn chrono_with_millis(&self, millis: u32) -> Result<NaiveDateTime, ()> {
        let date = NaiveDate::from_ymd_opt(self.year() as i32, self.month(), self.day());
        let (hour, minute, second) = (self.hour(), self.minute(), self.second());
        let time = NaiveTime::from_hms_milli_opt(hour, minute, second, millis);
        Ok(NaiveDateTime::new(date.ok_or(())?, time.ok_or(())?))
    }
}

bitfield! {
    #[derive(Copy, Clone, Default, Debug, Into)]
    pub struct FileAttributes(u16);
    pub read_only, set_read_only: 0, 0;
    pub hidden, set_hidden: 1, 1;
    pub system, set_system: 2, 2;
    pub directory, set_directory: 4, 4;
    pub archive, set_archive: 5, 5;
}

impl FileAttributes {
    pub fn new(directory: bool) -> Self {
        let mut attributes = Self::default();
        if directory {
            attributes.set_directory(1);
        } else {
            attributes.set_archive(1);
        }
        attributes
    }
}

#[derive(Copy, Clone, Debug, Default)]
pub struct UTCOffset(u8);

impl UTCOffset {
    pub fn new(minutes: i16) -> Self {
        Self((minutes / 15) as u8 | 0x80)
    }

    pub fn minutes(&self) -> i16 {
        match self.0 & 0x80 > 0 {
            true => (((self.0 & 0x7F) << 1) as i8 >> 1) as i16 * 15,
            false => 0,
        }
    }
}

#[cfg(feature = "chrono")]
impl core::convert::TryInto<FixedOffset> for UTCOffset {
    type Error = ();
    fn try_into(self) -> Result<FixedOffset, Self::Error> {
        FixedOffset::east_opt(self.minutes() as i32 * 60).ok_or(())
    }
}

#[derive(Copy, Clone, Debug, Default)]
pub struct DateTime {
    pub timestamp: Timestamp,
    pub millisecond: u16,
    pub utc_offset: UTCOffset,
}

#[cfg(feature = "extern-datetime-now")]
unsafe extern "Rust" {
    pub(crate) fn exfat_datetime_now() -> DateTime;
}

impl DateTime {
    pub fn now() -> Self {
        match () {
            #[cfg(feature = "extern-datetime-now")]
            () => unsafe { exfat_datetime_now() },
            #[cfg(not(feature = "extern-datetime-now"))]
            () => Self::default(),
        }
    }
}

#[cfg(all(feature = "chrono", feature = "std"))]
impl DateTime {
    pub fn localtime(&self) -> Result<chrono::DateTime<Local>, ()> {
        let naive = self.timestamp.chrono_with_millis(self.millisecond as u32)?;
        let offset: FixedOffset = self.utc_offset.try_into()?;
        let datetime: chrono::DateTime<FixedOffset> =
            chrono::DateTime::from_naive_utc_and_offset(naive, offset);
        Ok(datetime.with_timezone(&Local))
    }
}

#[cfg(feature = "chrono")]
impl<TZ: chrono::Offset + chrono::TimeZone> From<chrono::DateTime<TZ>> for DateTime {
    fn from(datetime: chrono::DateTime<TZ>) -> Self {
        let offset = datetime.timezone().fix();
        let seconds = offset.local_minus_utc();
        let utc_offset = UTCOffset::new((seconds / 60) as i16);
        let naive = datetime.naive_utc();
        let millisecond = naive.and_utc().timestamp_subsec_millis() as u16;
        Self { timestamp: naive.into(), millisecond, utc_offset }
    }
}

#[derive(Copy, Clone, Default, Debug)]
#[repr(C, packed(1))]
pub struct FileDirectory {
    pub(crate) entry_type: RawEntryType,
    pub(crate) secondary_count: u8,
    pub(crate) set_checksum: LE<u16>,
    pub(crate) file_attributes: LE<u16>,
    _reserved1: [u8; 2],
    create_timestamp: LE<u32>,
    last_modified_timestamp: LE<u32>,
    last_accessed_timestamp: LE<u32>,
    create_10ms_increment: u8,
    last_modified_10ms_increment: u8,
    create_utc_offset: UTCOffset,
    last_modified_utc_offset: UTCOffset,
    last_accessed_utc_offset: UTCOffset,
    _reserved2: [u8; 7],
}

impl FileDirectory {
    pub(crate) fn new(secondary_count: u8, directory: bool) -> Self {
        let now = DateTime::now();
        let timestamp: LE<u32> = u32::from(now.timestamp).into();
        let millis = (now.millisecond / 10) as u8;
        FileDirectory {
            entry_type: RawEntryType::new(EntryType::FileDirectory, true),
            secondary_count,
            file_attributes: u16::from(FileAttributes::new(directory)).into(),
            create_timestamp: timestamp,
            create_10ms_increment: millis,
            create_utc_offset: now.utc_offset,
            last_modified_timestamp: timestamp,
            last_modified_10ms_increment: millis,
            last_modified_utc_offset: now.utc_offset,
            last_accessed_timestamp: timestamp,
            last_accessed_utc_offset: now.utc_offset,
            ..Default::default()
        }
    }

    pub fn file_attributes(&self) -> FileAttributes {
        FileAttributes(self.file_attributes.to_ne())
    }

    pub fn create_timestamp(&self) -> DateTime {
        DateTime {
            timestamp: Timestamp(self.create_timestamp.to_ne()),
            millisecond: self.create_10ms_increment as u16 * 10,
            utc_offset: self.create_utc_offset,
        }
    }

    pub fn last_modified_timestamp(&self) -> DateTime {
        DateTime {
            timestamp: Timestamp(self.last_modified_timestamp.to_ne()),
            millisecond: self.last_modified_10ms_increment as u16 * 10,
            utc_offset: self.last_modified_utc_offset,
        }
    }

    pub(crate) fn update_last_modified_timestamp(&mut self, datetime: DateTime) {
        self.last_modified_timestamp = datetime.timestamp.0.into();
        self.last_modified_10ms_increment = (datetime.millisecond / 10) as u8;
        self.last_modified_utc_offset = datetime.utc_offset;
    }

    pub fn last_accessed_timestamp(&self) -> DateTime {
        DateTime {
            timestamp: Timestamp(self.last_accessed_timestamp.to_ne()),
            millisecond: 0,
            utc_offset: self.last_accessed_utc_offset,
        }
    }

    pub(crate) fn update_last_accessed_timestamp(&mut self, datetime: DateTime) {
        self.last_accessed_timestamp = datetime.timestamp.0.into();
        self.last_accessed_utc_offset = datetime.utc_offset;
    }
}

pub(crate) struct Checksum(u16);

impl Checksum {
    pub(crate) fn new() -> Self {
        Self(0)
    }

    pub(crate) fn write(&mut self, value: u16) {
        self.0 = if self.0 & 1 > 0 { 0x8000 } else { 0 } + (self.0 >> 1) + value
    }

    pub(crate) fn sum(&self) -> u16 {
        self.0
    }
}

pub(crate) fn name_hash(name: &str) -> u16 {
    let mut checksum = Checksum::new();
    for ch in name.chars() {
        checksum.write(ch as u16);
        checksum.write(((ch as u32) >> 16) as u16);
    }
    checksum.sum()
}