1use core::convert::TryFrom;
21
22use crate::blockdevice::BlockIdx;
23use crate::fat::{FatType, OnDiskDirEntry};
24
25pub const MAX_FILE_SIZE: u32 = core::u32::MAX;
27
28pub trait TimeSource {
30 fn get_timestamp(&self) -> Timestamp;
32}
33
34#[derive(Debug, Copy, Clone, PartialEq, Eq)]
36pub struct Cluster(pub(crate) u32);
37
38#[derive(Debug, PartialEq, Eq, Clone)]
41pub struct DirEntry {
42 pub name: ShortFileName,
44 pub mtime: Timestamp,
46 pub ctime: Timestamp,
48 pub attributes: Attributes,
50 pub cluster: Cluster,
52 pub size: u32,
54 pub entry_block: BlockIdx,
56 pub entry_offset: u32,
58}
59
60#[derive(PartialEq, Eq, Clone)]
63pub struct ShortFileName {
64 pub(crate) contents: [u8; 11],
65}
66
67#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq)]
71pub struct Timestamp {
72 pub year_since_1970: u8,
74 pub zero_indexed_month: u8,
76 pub zero_indexed_day: u8,
78 pub hours: u8,
80 pub minutes: u8,
82 pub seconds: u8,
84}
85
86#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq)]
89pub struct Attributes(pub(crate) u8);
90
91#[derive(Debug)]
93pub struct File {
94 pub(crate) starting_cluster: Cluster,
96 pub(crate) current_cluster: (u32, Cluster),
98 pub(crate) current_offset: u32,
100 pub(crate) length: u32,
102 pub(crate) mode: Mode,
104 pub(crate) entry: DirEntry,
106}
107
108#[derive(Debug)]
110pub struct Directory {
111 pub(crate) cluster: Cluster,
113 pub(crate) entry: Option<DirEntry>,
115}
116
117#[derive(Debug, PartialEq, Eq, Copy, Clone)]
119pub enum Mode {
120 ReadOnly,
122 ReadWriteAppend,
124 ReadWriteTruncate,
126 ReadWriteCreate,
128 ReadWriteCreateOrTruncate,
130 ReadWriteCreateOrAppend,
132}
133
134#[derive(Debug, Clone)]
136pub enum FilenameError {
137 InvalidCharacter,
139 FilenameEmpty,
141 NameTooLong,
143 MisplacedPeriod,
145}
146
147impl Cluster {
178 pub const INVALID: Cluster = Cluster(0xFFFF_FFF6);
180 pub const BAD: Cluster = Cluster(0xFFFF_FFF7);
182 pub const EMPTY: Cluster = Cluster(0x0000_0000);
184 pub const ROOT_DIR: Cluster = Cluster(0xFFFF_FFFC);
187 pub const END_OF_FILE: Cluster = Cluster(0xFFFF_FFFF);
189}
190
191impl core::ops::Add<u32> for Cluster {
192 type Output = Cluster;
193 fn add(self, rhs: u32) -> Cluster {
194 Cluster(self.0 + rhs)
195 }
196}
197
198impl core::ops::AddAssign<u32> for Cluster {
199 fn add_assign(&mut self, rhs: u32) {
200 self.0 += rhs;
201 }
202}
203
204impl core::ops::Add<Cluster> for Cluster {
205 type Output = Cluster;
206 fn add(self, rhs: Cluster) -> Cluster {
207 Cluster(self.0 + rhs.0)
208 }
209}
210
211impl core::ops::AddAssign<Cluster> for Cluster {
212 fn add_assign(&mut self, rhs: Cluster) {
213 self.0 += rhs.0;
214 }
215}
216
217impl DirEntry {
218 pub(crate) fn serialize(&self, fat_type: FatType) -> [u8; OnDiskDirEntry::LEN] {
219 let mut data = [0u8; OnDiskDirEntry::LEN];
220 data[0..11].copy_from_slice(&self.name.contents);
221 data[11] = self.attributes.0;
222 data[14..18].copy_from_slice(&self.ctime.serialize_to_fat()[..]);
225 let cluster_number = self.cluster.0;
227 let cluster_hi = if fat_type == FatType::Fat16 {
228 [0u8; 2]
229 } else {
230 u16::try_from((cluster_number >> 16) & 0x0000_FFFF)
232 .unwrap()
233 .to_le_bytes()
234 };
235 data[20..22].copy_from_slice(&cluster_hi[..]);
236 data[22..26].copy_from_slice(&self.mtime.serialize_to_fat()[..]);
237 let cluster_lo = u16::try_from(cluster_number & 0x0000_FFFF)
239 .unwrap()
240 .to_le_bytes();
241 data[26..28].copy_from_slice(&cluster_lo[..]);
242 data[28..32].copy_from_slice(&self.size.to_le_bytes()[..]);
243 data
244 }
245
246 pub(crate) fn new(
247 name: ShortFileName,
248 attributes: Attributes,
249 cluster: Cluster,
250 ctime: Timestamp,
251 entry_block: BlockIdx,
252 entry_offset: u32,
253 ) -> Self {
254 Self {
255 name,
256 mtime: ctime,
257 ctime,
258 attributes,
259 cluster,
260 size: 0,
261 entry_block,
262 entry_offset,
263 }
264 }
265}
266
267impl ShortFileName {
268 const FILENAME_BASE_MAX_LEN: usize = 8;
269 const FILENAME_EXT_MAX_LEN: usize = 3;
270 const FILENAME_MAX_LEN: usize = 11;
271
272 pub fn create_from_str(name: &str) -> Result<ShortFileName, FilenameError> {
274 let mut sfn = ShortFileName {
275 contents: [b' '; Self::FILENAME_MAX_LEN],
276 };
277 let mut idx = 0;
278 let mut seen_dot = false;
279 for ch in name.bytes() {
280 match ch {
281 0x00..=0x1F
283 | 0x20
284 | 0x22
285 | 0x2A
286 | 0x2B
287 | 0x2C
288 | 0x2F
289 | 0x3A
290 | 0x3B
291 | 0x3C
292 | 0x3D
293 | 0x3E
294 | 0x3F
295 | 0x5B
296 | 0x5C
297 | 0x5D
298 | 0x7C => {
299 return Err(FilenameError::InvalidCharacter);
300 }
301 b'.' => {
303 if idx >= 1 && idx <= Self::FILENAME_BASE_MAX_LEN {
304 idx = Self::FILENAME_BASE_MAX_LEN;
305 seen_dot = true;
306 } else {
307 return Err(FilenameError::MisplacedPeriod);
308 }
309 }
310 _ => {
311 let ch = if ch >= b'a' && ch <= b'z' {
312 ch - 32
314 } else {
315 ch
316 };
317 if seen_dot {
318 if idx >= Self::FILENAME_BASE_MAX_LEN && idx < Self::FILENAME_MAX_LEN {
319 sfn.contents[idx] = ch;
320 } else {
321 return Err(FilenameError::NameTooLong);
322 }
323 } else if idx < Self::FILENAME_BASE_MAX_LEN {
324 sfn.contents[idx] = ch;
325 } else {
326 return Err(FilenameError::NameTooLong);
327 }
328 idx += 1;
329 }
330 }
331 }
332 if idx == 0 {
333 return Err(FilenameError::FilenameEmpty);
334 }
335 Ok(sfn)
336 }
337
338 pub fn create_from_str_mixed_case(name: &str) -> Result<ShortFileName, FilenameError> {
341 let mut sfn = ShortFileName {
342 contents: [b' '; Self::FILENAME_MAX_LEN],
343 };
344 let mut idx = 0;
345 let mut seen_dot = false;
346 for ch in name.bytes() {
347 match ch {
348 0x00..=0x1F
350 | 0x20
351 | 0x22
352 | 0x2A
353 | 0x2B
354 | 0x2C
355 | 0x2F
356 | 0x3A
357 | 0x3B
358 | 0x3C
359 | 0x3D
360 | 0x3E
361 | 0x3F
362 | 0x5B
363 | 0x5C
364 | 0x5D
365 | 0x7C => {
366 return Err(FilenameError::InvalidCharacter);
367 }
368 b'.' => {
370 if idx >= 1 && idx <= Self::FILENAME_BASE_MAX_LEN {
371 idx = Self::FILENAME_BASE_MAX_LEN;
372 seen_dot = true;
373 } else {
374 return Err(FilenameError::MisplacedPeriod);
375 }
376 }
377 _ => {
378 if seen_dot {
379 if idx >= Self::FILENAME_BASE_MAX_LEN && idx < Self::FILENAME_MAX_LEN {
380 sfn.contents[idx] = ch;
381 } else {
382 return Err(FilenameError::NameTooLong);
383 }
384 } else if idx < Self::FILENAME_BASE_MAX_LEN {
385 sfn.contents[idx] = ch;
386 } else {
387 return Err(FilenameError::NameTooLong);
388 }
389 idx += 1;
390 }
391 }
392 }
393 if idx == 0 {
394 return Err(FilenameError::FilenameEmpty);
395 }
396 Ok(sfn)
397 }
398}
399
400impl core::fmt::Display for ShortFileName {
401 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
402 let mut printed = 0;
403 for (i, &c) in self.contents.iter().enumerate() {
404 if c != b' ' {
405 if i == Self::FILENAME_BASE_MAX_LEN {
406 write!(f, ".")?;
407 printed += 1;
408 }
409 write!(f, "{}", c as char)?;
410 printed += 1;
411 }
412 }
413 if let Some(mut width) = f.width() {
414 if width > printed {
415 width -= printed;
416 for _ in 0..width {
417 write!(f, "{}", f.fill())?;
418 }
419 }
420 }
421 Ok(())
422 }
423}
424
425impl core::fmt::Debug for ShortFileName {
426 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
427 write!(f, "ShortFileName(\"{}\")", self)
428 }
429}
430
431impl Timestamp {
432 const MONTH_LOOKUP: [u32; 12] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
433
434 pub fn from_fat(date: u16, time: u16) -> Timestamp {
436 let year = (1980 + (date >> 9)) as u16;
437 let month = ((date >> 5) & 0x000F) as u8;
438 let day = (date & 0x001F) as u8;
439 let hours = ((time >> 11) & 0x001F) as u8;
440 let minutes = ((time >> 5) & 0x0003F) as u8;
441 let seconds = ((time << 1) & 0x0003F) as u8;
442 Timestamp {
444 year_since_1970: (year - 1970) as u8,
445 zero_indexed_month: if month == 0 { 0 } else { month - 1 },
446 zero_indexed_day: if day == 0 { 0 } else { day - 1 },
447 hours,
448 minutes,
449 seconds,
450 }
451 }
452
453 pub fn serialize_to_fat(self) -> [u8; 4] {
456 let mut data = [0u8; 4];
457
458 let hours = (u16::from(self.hours) << 11) & 0xF800;
459 let minutes = (u16::from(self.minutes) << 5) & 0x07E0;
460 let seconds = (u16::from(self.seconds / 2)) & 0x001F;
461 data[..2].copy_from_slice(&(hours | minutes | seconds).to_le_bytes()[..]);
462
463 let year = if self.year_since_1970 < 10 {
464 0
465 } else {
466 (u16::from(self.year_since_1970 - 10) << 9) & 0xFE00
467 };
468 let month = (u16::from(self.zero_indexed_month + 1) << 5) & 0x01E0;
469 let day = u16::from(self.zero_indexed_day + 1) & 0x001F;
470 data[2..].copy_from_slice(&(year | month | day).to_le_bytes()[..]);
471 data
472 }
473
474 pub fn from_calendar(
479 year: u16,
480 month: u8,
481 day: u8,
482 hours: u8,
483 minutes: u8,
484 seconds: u8,
485 ) -> Result<Timestamp, &'static str> {
486 Ok(Timestamp {
487 year_since_1970: if year >= 1970 && year <= (1970 + 255) {
488 (year - 1970) as u8
489 } else {
490 return Err("Bad year");
491 },
492 zero_indexed_month: if month >= 1 && month <= 12 {
493 month - 1
494 } else {
495 return Err("Bad month");
496 },
497 zero_indexed_day: if day >= 1 && day <= 31 {
498 day - 1
499 } else {
500 return Err("Bad day");
501 },
502 hours: if hours <= 23 {
503 hours
504 } else {
505 return Err("Bad hours");
506 },
507 minutes: if minutes <= 59 {
508 minutes
509 } else {
510 return Err("Bad minutes");
511 },
512 seconds: if seconds <= 59 {
513 seconds
514 } else {
515 return Err("Bad seconds");
516 },
517 })
518 }
519}
520
521impl core::fmt::Debug for Timestamp {
522 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
523 write!(f, "Timestamp({})", self)
524 }
525}
526
527impl core::fmt::Display for Timestamp {
528 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
529 write!(
530 f,
531 "{}-{:02}-{:02} {:02}:{:02}:{:02}",
532 u16::from(self.year_since_1970) + 1970,
533 self.zero_indexed_month + 1,
534 self.zero_indexed_day + 1,
535 self.hours,
536 self.minutes,
537 self.seconds
538 )
539 }
540}
541
542impl Attributes {
543 pub const READ_ONLY: u8 = 0x01;
545 pub const HIDDEN: u8 = 0x02;
547 pub const SYSTEM: u8 = 0x04;
549 pub const VOLUME: u8 = 0x08;
551 pub const DIRECTORY: u8 = 0x10;
553 pub const ARCHIVE: u8 = 0x20;
556 pub const LFN: u8 = Self::READ_ONLY | Self::HIDDEN | Self::SYSTEM | Self::VOLUME;
559
560 pub(crate) fn create_from_fat(value: u8) -> Attributes {
563 Attributes(value)
564 }
565
566 pub(crate) fn set_archive(&mut self, flag: bool) {
567 let archive = if flag { 0x20 } else { 0x00 };
568 self.0 |= archive;
569 }
570
571 pub fn is_read_only(self) -> bool {
573 (self.0 & Self::READ_ONLY) == Self::READ_ONLY
574 }
575
576 pub fn is_hidden(self) -> bool {
578 (self.0 & Self::HIDDEN) == Self::HIDDEN
579 }
580
581 pub fn is_system(self) -> bool {
583 (self.0 & Self::SYSTEM) == Self::SYSTEM
584 }
585
586 pub fn is_volume(self) -> bool {
588 (self.0 & Self::VOLUME) == Self::VOLUME
589 }
590
591 pub fn is_directory(self) -> bool {
593 (self.0 & Self::DIRECTORY) == Self::DIRECTORY
594 }
595
596 pub fn is_archive(self) -> bool {
598 (self.0 & Self::ARCHIVE) == Self::ARCHIVE
599 }
600
601 pub fn is_lfn(self) -> bool {
603 (self.0 & Self::LFN) == Self::LFN
604 }
605}
606
607impl core::fmt::Debug for Attributes {
608 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
609 if self.is_lfn() {
610 write!(f, "LFN")?;
611 } else {
612 if self.is_directory() {
613 write!(f, "D")?;
614 } else {
615 write!(f, "F")?;
616 }
617 if self.is_read_only() {
618 write!(f, "R")?;
619 }
620 if self.is_hidden() {
621 write!(f, "H")?;
622 }
623 if self.is_system() {
624 write!(f, "S")?;
625 }
626 if self.is_volume() {
627 write!(f, "V")?;
628 }
629 if self.is_archive() {
630 write!(f, "A")?;
631 }
632 }
633 Ok(())
634 }
635}
636
637impl File {
638 pub(crate) fn new(cluster: Cluster, length: u32, mode: Mode, entry: DirEntry) -> File {
640 File {
641 starting_cluster: cluster,
642 current_cluster: (0, cluster),
643 mode,
644 length,
645 current_offset: 0,
646 entry,
647 }
648 }
649
650 pub fn eof(&self) -> bool {
652 self.current_offset == self.length
653 }
654
655 pub fn length(&self) -> u32 {
657 self.length
658 }
659
660 pub fn seek_from_start(&mut self, offset: u32) -> Result<(), ()> {
662 if offset <= self.length {
663 self.current_offset = offset;
664 if offset < self.current_cluster.0 {
665 self.current_cluster = (0, self.starting_cluster);
667 }
668 Ok(())
669 } else {
670 Err(())
671 }
672 }
673
674 pub fn seek_from_end(&mut self, offset: u32) -> Result<(), ()> {
676 if offset <= self.length {
677 self.current_offset = self.length - offset;
678 if offset < self.current_cluster.0 {
679 self.current_cluster = (0, self.starting_cluster);
681 }
682 Ok(())
683 } else {
684 Err(())
685 }
686 }
687
688 pub fn seek_from_current(&mut self, offset: i32) -> Result<(), ()> {
690 let new_offset = i64::from(self.current_offset) + i64::from(offset);
691 if new_offset >= 0 && new_offset <= i64::from(self.length) {
692 self.current_offset = new_offset as u32;
693 Ok(())
694 } else {
695 Err(())
696 }
697 }
698
699 pub fn left(&self) -> u32 {
701 self.length - self.current_offset
702 }
703
704 pub(crate) fn update_length(&mut self, new: u32) {
705 self.length = new;
706 self.entry.size = new;
707 }
708}
709
710impl Directory {}
711
712impl FilenameError {}
713
714#[cfg(test)]
729mod test {
730 use super::*;
731
732 #[test]
733 fn filename_no_extension() {
734 let sfn = ShortFileName {
735 contents: *b"HELLO ",
736 };
737 assert_eq!(format!("{}", &sfn), "HELLO");
738 assert_eq!(sfn, ShortFileName::create_from_str("HELLO").unwrap());
739 assert_eq!(sfn, ShortFileName::create_from_str("hello").unwrap());
740 assert_eq!(sfn, ShortFileName::create_from_str("HeLlO").unwrap());
741 assert_eq!(sfn, ShortFileName::create_from_str("HELLO.").unwrap());
742 }
743
744 #[test]
745 fn filename_extension() {
746 let sfn = ShortFileName {
747 contents: *b"HELLO TXT",
748 };
749 assert_eq!(format!("{}", &sfn), "HELLO.TXT");
750 assert_eq!(sfn, ShortFileName::create_from_str("HELLO.TXT").unwrap());
751 }
752
753 #[test]
754 fn filename_fulllength() {
755 let sfn = ShortFileName {
756 contents: *b"12345678TXT",
757 };
758 assert_eq!(format!("{}", &sfn), "12345678.TXT");
759 assert_eq!(sfn, ShortFileName::create_from_str("12345678.TXT").unwrap());
760 }
761
762 #[test]
763 fn filename_short_extension() {
764 let sfn = ShortFileName {
765 contents: *b"12345678C ",
766 };
767 assert_eq!(format!("{}", &sfn), "12345678.C");
768 assert_eq!(sfn, ShortFileName::create_from_str("12345678.C").unwrap());
769 }
770
771 #[test]
772 fn filename_short() {
773 let sfn = ShortFileName {
774 contents: *b"1 C ",
775 };
776 assert_eq!(format!("{}", &sfn), "1.C");
777 assert_eq!(sfn, ShortFileName::create_from_str("1.C").unwrap());
778 }
779
780 #[test]
781 fn filename_bad() {
782 assert!(ShortFileName::create_from_str("").is_err());
783 assert!(ShortFileName::create_from_str(" ").is_err());
784 assert!(ShortFileName::create_from_str("123456789").is_err());
785 assert!(ShortFileName::create_from_str("12345678.ABCD").is_err());
786 }
787}
788
789