use core::convert::TryFrom;
use crate::blockdevice::BlockIdx;
use crate::fat::{FatType, OnDiskDirEntry};
pub const MAX_FILE_SIZE: u32 = core::u32::MAX;
pub trait TimeSource {
fn get_timestamp(&self) -> Timestamp;
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Cluster(pub(crate) u32);
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct DirEntry {
pub name: ShortFileName,
pub mtime: Timestamp,
pub ctime: Timestamp,
pub attributes: Attributes,
pub cluster: Cluster,
pub size: u32,
pub entry_block: BlockIdx,
pub entry_offset: u32,
}
#[derive(PartialEq, Eq, Clone)]
pub struct ShortFileName {
pub(crate) contents: [u8; 11],
}
#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq)]
pub struct Timestamp {
pub year_since_1970: u8,
pub zero_indexed_month: u8,
pub zero_indexed_day: u8,
pub hours: u8,
pub minutes: u8,
pub seconds: u8,
}
#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq)]
pub struct Attributes(pub(crate) u8);
#[derive(Debug)]
pub struct File {
pub(crate) starting_cluster: Cluster,
pub(crate) current_cluster: (u32, Cluster),
pub(crate) current_offset: u32,
pub(crate) length: u32,
pub(crate) mode: Mode,
pub(crate) entry: DirEntry,
}
#[derive(Debug)]
pub struct Directory {
pub(crate) cluster: Cluster,
pub(crate) entry: Option<DirEntry>,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum Mode {
ReadOnly,
ReadWriteAppend,
ReadWriteTruncate,
ReadWriteCreate,
ReadWriteCreateOrTruncate,
ReadWriteCreateOrAppend,
}
#[derive(Debug, Clone)]
pub enum FilenameError {
InvalidCharacter,
FilenameEmpty,
NameTooLong,
MisplacedPeriod,
}
impl Cluster {
pub const INVALID: Cluster = Cluster(0xFFFF_FFF6);
pub const BAD: Cluster = Cluster(0xFFFF_FFF7);
pub const EMPTY: Cluster = Cluster(0x0000_0000);
pub const ROOT_DIR: Cluster = Cluster(0xFFFF_FFFC);
pub const END_OF_FILE: Cluster = Cluster(0xFFFF_FFFF);
}
impl core::ops::Add<u32> for Cluster {
type Output = Cluster;
fn add(self, rhs: u32) -> Cluster {
Cluster(self.0 + rhs)
}
}
impl core::ops::AddAssign<u32> for Cluster {
fn add_assign(&mut self, rhs: u32) {
self.0 += rhs;
}
}
impl core::ops::Add<Cluster> for Cluster {
type Output = Cluster;
fn add(self, rhs: Cluster) -> Cluster {
Cluster(self.0 + rhs.0)
}
}
impl core::ops::AddAssign<Cluster> for Cluster {
fn add_assign(&mut self, rhs: Cluster) {
self.0 += rhs.0;
}
}
impl DirEntry {
pub(crate) fn serialize(&self, fat_type: FatType) -> [u8; OnDiskDirEntry::LEN] {
let mut data = [0u8; OnDiskDirEntry::LEN];
data[0..11].copy_from_slice(&self.name.contents);
data[11] = self.attributes.0;
data[14..18].copy_from_slice(&self.ctime.serialize_to_fat()[..]);
let cluster_number = self.cluster.0;
let cluster_hi = if fat_type == FatType::Fat16 {
[0u8; 2]
} else {
u16::try_from((cluster_number >> 16) & 0x0000_FFFF)
.unwrap()
.to_le_bytes()
};
data[20..22].copy_from_slice(&cluster_hi[..]);
data[22..26].copy_from_slice(&self.mtime.serialize_to_fat()[..]);
let cluster_lo = u16::try_from(cluster_number & 0x0000_FFFF)
.unwrap()
.to_le_bytes();
data[26..28].copy_from_slice(&cluster_lo[..]);
data[28..32].copy_from_slice(&self.size.to_le_bytes()[..]);
data
}
pub(crate) fn new(
name: ShortFileName,
attributes: Attributes,
cluster: Cluster,
ctime: Timestamp,
entry_block: BlockIdx,
entry_offset: u32,
) -> Self {
Self {
name,
mtime: ctime,
ctime,
attributes,
cluster,
size: 0,
entry_block,
entry_offset,
}
}
}
impl ShortFileName {
const FILENAME_BASE_MAX_LEN: usize = 8;
const FILENAME_EXT_MAX_LEN: usize = 3;
const FILENAME_MAX_LEN: usize = 11;
pub fn create_from_str(name: &str) -> Result<ShortFileName, FilenameError> {
let mut sfn = ShortFileName {
contents: [b' '; Self::FILENAME_MAX_LEN],
};
let mut idx = 0;
let mut seen_dot = false;
for ch in name.bytes() {
match ch {
0x00..=0x1F
| 0x20
| 0x22
| 0x2A
| 0x2B
| 0x2C
| 0x2F
| 0x3A
| 0x3B
| 0x3C
| 0x3D
| 0x3E
| 0x3F
| 0x5B
| 0x5C
| 0x5D
| 0x7C => {
return Err(FilenameError::InvalidCharacter);
}
b'.' => {
if idx >= 1 && idx <= Self::FILENAME_BASE_MAX_LEN {
idx = Self::FILENAME_BASE_MAX_LEN;
seen_dot = true;
} else {
return Err(FilenameError::MisplacedPeriod);
}
}
_ => {
let ch = if ch >= b'a' && ch <= b'z' {
ch - 32
} else {
ch
};
if seen_dot {
if idx >= Self::FILENAME_BASE_MAX_LEN && idx < Self::FILENAME_MAX_LEN {
sfn.contents[idx] = ch;
} else {
return Err(FilenameError::NameTooLong);
}
} else if idx < Self::FILENAME_BASE_MAX_LEN {
sfn.contents[idx] = ch;
} else {
return Err(FilenameError::NameTooLong);
}
idx += 1;
}
}
}
if idx == 0 {
return Err(FilenameError::FilenameEmpty);
}
Ok(sfn)
}
pub fn create_from_str_mixed_case(name: &str) -> Result<ShortFileName, FilenameError> {
let mut sfn = ShortFileName {
contents: [b' '; Self::FILENAME_MAX_LEN],
};
let mut idx = 0;
let mut seen_dot = false;
for ch in name.bytes() {
match ch {
0x00..=0x1F
| 0x20
| 0x22
| 0x2A
| 0x2B
| 0x2C
| 0x2F
| 0x3A
| 0x3B
| 0x3C
| 0x3D
| 0x3E
| 0x3F
| 0x5B
| 0x5C
| 0x5D
| 0x7C => {
return Err(FilenameError::InvalidCharacter);
}
b'.' => {
if idx >= 1 && idx <= Self::FILENAME_BASE_MAX_LEN {
idx = Self::FILENAME_BASE_MAX_LEN;
seen_dot = true;
} else {
return Err(FilenameError::MisplacedPeriod);
}
}
_ => {
if seen_dot {
if idx >= Self::FILENAME_BASE_MAX_LEN && idx < Self::FILENAME_MAX_LEN {
sfn.contents[idx] = ch;
} else {
return Err(FilenameError::NameTooLong);
}
} else if idx < Self::FILENAME_BASE_MAX_LEN {
sfn.contents[idx] = ch;
} else {
return Err(FilenameError::NameTooLong);
}
idx += 1;
}
}
}
if idx == 0 {
return Err(FilenameError::FilenameEmpty);
}
Ok(sfn)
}
}
impl core::fmt::Display for ShortFileName {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let mut printed = 0;
for (i, &c) in self.contents.iter().enumerate() {
if c != b' ' {
if i == Self::FILENAME_BASE_MAX_LEN {
write!(f, ".")?;
printed += 1;
}
write!(f, "{}", c as char)?;
printed += 1;
}
}
if let Some(mut width) = f.width() {
if width > printed {
width -= printed;
for _ in 0..width {
write!(f, "{}", f.fill())?;
}
}
}
Ok(())
}
}
impl core::fmt::Debug for ShortFileName {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "ShortFileName(\"{}\")", self)
}
}
impl Timestamp {
const MONTH_LOOKUP: [u32; 12] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
pub fn from_fat(date: u16, time: u16) -> Timestamp {
let year = (1980 + (date >> 9)) as u16;
let month = ((date >> 5) & 0x000F) as u8;
let day = (date & 0x001F) as u8;
let hours = ((time >> 11) & 0x001F) as u8;
let minutes = ((time >> 5) & 0x0003F) as u8;
let seconds = ((time << 1) & 0x0003F) as u8;
Timestamp {
year_since_1970: (year - 1970) as u8,
zero_indexed_month: if month == 0 { 0 } else { month - 1 },
zero_indexed_day: if day == 0 { 0 } else { day - 1 },
hours,
minutes,
seconds,
}
}
pub fn serialize_to_fat(self) -> [u8; 4] {
let mut data = [0u8; 4];
let hours = (u16::from(self.hours) << 11) & 0xF800;
let minutes = (u16::from(self.minutes) << 5) & 0x07E0;
let seconds = (u16::from(self.seconds / 2)) & 0x001F;
data[..2].copy_from_slice(&(hours | minutes | seconds).to_le_bytes()[..]);
let year = if self.year_since_1970 < 10 {
0
} else {
(u16::from(self.year_since_1970 - 10) << 9) & 0xFE00
};
let month = (u16::from(self.zero_indexed_month + 1) << 5) & 0x01E0;
let day = u16::from(self.zero_indexed_day + 1) & 0x001F;
data[2..].copy_from_slice(&(year | month | day).to_le_bytes()[..]);
data
}
pub fn from_calendar(
year: u16,
month: u8,
day: u8,
hours: u8,
minutes: u8,
seconds: u8,
) -> Result<Timestamp, &'static str> {
Ok(Timestamp {
year_since_1970: if year >= 1970 && year <= (1970 + 255) {
(year - 1970) as u8
} else {
return Err("Bad year");
},
zero_indexed_month: if month >= 1 && month <= 12 {
month - 1
} else {
return Err("Bad month");
},
zero_indexed_day: if day >= 1 && day <= 31 {
day - 1
} else {
return Err("Bad day");
},
hours: if hours <= 23 {
hours
} else {
return Err("Bad hours");
},
minutes: if minutes <= 59 {
minutes
} else {
return Err("Bad minutes");
},
seconds: if seconds <= 59 {
seconds
} else {
return Err("Bad seconds");
},
})
}
}
impl core::fmt::Debug for Timestamp {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "Timestamp({})", self)
}
}
impl core::fmt::Display for Timestamp {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(
f,
"{}-{:02}-{:02} {:02}:{:02}:{:02}",
u16::from(self.year_since_1970) + 1970,
self.zero_indexed_month + 1,
self.zero_indexed_day + 1,
self.hours,
self.minutes,
self.seconds
)
}
}
impl Attributes {
pub const READ_ONLY: u8 = 0x01;
pub const HIDDEN: u8 = 0x02;
pub const SYSTEM: u8 = 0x04;
pub const VOLUME: u8 = 0x08;
pub const DIRECTORY: u8 = 0x10;
pub const ARCHIVE: u8 = 0x20;
pub const LFN: u8 = Self::READ_ONLY | Self::HIDDEN | Self::SYSTEM | Self::VOLUME;
pub(crate) fn create_from_fat(value: u8) -> Attributes {
Attributes(value)
}
pub(crate) fn set_archive(&mut self, flag: bool) {
let archive = if flag { 0x20 } else { 0x00 };
self.0 |= archive;
}
pub fn is_read_only(self) -> bool {
(self.0 & Self::READ_ONLY) == Self::READ_ONLY
}
pub fn is_hidden(self) -> bool {
(self.0 & Self::HIDDEN) == Self::HIDDEN
}
pub fn is_system(self) -> bool {
(self.0 & Self::SYSTEM) == Self::SYSTEM
}
pub fn is_volume(self) -> bool {
(self.0 & Self::VOLUME) == Self::VOLUME
}
pub fn is_directory(self) -> bool {
(self.0 & Self::DIRECTORY) == Self::DIRECTORY
}
pub fn is_archive(self) -> bool {
(self.0 & Self::ARCHIVE) == Self::ARCHIVE
}
pub fn is_lfn(self) -> bool {
(self.0 & Self::LFN) == Self::LFN
}
}
impl core::fmt::Debug for Attributes {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
if self.is_lfn() {
write!(f, "LFN")?;
} else {
if self.is_directory() {
write!(f, "D")?;
} else {
write!(f, "F")?;
}
if self.is_read_only() {
write!(f, "R")?;
}
if self.is_hidden() {
write!(f, "H")?;
}
if self.is_system() {
write!(f, "S")?;
}
if self.is_volume() {
write!(f, "V")?;
}
if self.is_archive() {
write!(f, "A")?;
}
}
Ok(())
}
}
impl File {
pub(crate) fn new(cluster: Cluster, length: u32, mode: Mode, entry: DirEntry) -> File {
File {
starting_cluster: cluster,
current_cluster: (0, cluster),
mode,
length,
current_offset: 0,
entry,
}
}
pub fn eof(&self) -> bool {
self.current_offset == self.length
}
pub fn length(&self) -> u32 {
self.length
}
pub fn seek_from_start(&mut self, offset: u32) -> Result<(), ()> {
if offset <= self.length {
self.current_offset = offset;
if offset < self.current_cluster.0 {
self.current_cluster = (0, self.starting_cluster);
}
Ok(())
} else {
Err(())
}
}
pub fn seek_from_end(&mut self, offset: u32) -> Result<(), ()> {
if offset <= self.length {
self.current_offset = self.length - offset;
if offset < self.current_cluster.0 {
self.current_cluster = (0, self.starting_cluster);
}
Ok(())
} else {
Err(())
}
}
pub fn seek_from_current(&mut self, offset: i32) -> Result<(), ()> {
let new_offset = i64::from(self.current_offset) + i64::from(offset);
if new_offset >= 0 && new_offset <= i64::from(self.length) {
self.current_offset = new_offset as u32;
Ok(())
} else {
Err(())
}
}
pub fn left(&self) -> u32 {
self.length - self.current_offset
}
pub(crate) fn update_length(&mut self, new: u32) {
self.length = new;
self.entry.size = new;
}
}
impl Directory {}
impl FilenameError {}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn filename_no_extension() {
let sfn = ShortFileName {
contents: *b"HELLO ",
};
assert_eq!(format!("{}", &sfn), "HELLO");
assert_eq!(sfn, ShortFileName::create_from_str("HELLO").unwrap());
assert_eq!(sfn, ShortFileName::create_from_str("hello").unwrap());
assert_eq!(sfn, ShortFileName::create_from_str("HeLlO").unwrap());
assert_eq!(sfn, ShortFileName::create_from_str("HELLO.").unwrap());
}
#[test]
fn filename_extension() {
let sfn = ShortFileName {
contents: *b"HELLO TXT",
};
assert_eq!(format!("{}", &sfn), "HELLO.TXT");
assert_eq!(sfn, ShortFileName::create_from_str("HELLO.TXT").unwrap());
}
#[test]
fn filename_fulllength() {
let sfn = ShortFileName {
contents: *b"12345678TXT",
};
assert_eq!(format!("{}", &sfn), "12345678.TXT");
assert_eq!(sfn, ShortFileName::create_from_str("12345678.TXT").unwrap());
}
#[test]
fn filename_short_extension() {
let sfn = ShortFileName {
contents: *b"12345678C ",
};
assert_eq!(format!("{}", &sfn), "12345678.C");
assert_eq!(sfn, ShortFileName::create_from_str("12345678.C").unwrap());
}
#[test]
fn filename_short() {
let sfn = ShortFileName {
contents: *b"1 C ",
};
assert_eq!(format!("{}", &sfn), "1.C");
assert_eq!(sfn, ShortFileName::create_from_str("1.C").unwrap());
}
#[test]
fn filename_bad() {
assert!(ShortFileName::create_from_str("").is_err());
assert!(ShortFileName::create_from_str(" ").is_err());
assert!(ShortFileName::create_from_str("123456789").is_err());
assert!(ShortFileName::create_from_str("12345678.ABCD").is_err());
}
}