embedded_sdmmc/filesystem/
filename.rs1use crate::fat::VolumeName;
4use crate::trace;
5
6#[cfg_attr(feature = "defmt-log", derive(defmt::Format))]
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub enum FilenameError {
10 InvalidCharacter,
12 FilenameEmpty,
14 NameTooLong,
16 MisplacedPeriod,
18 Utf8Error,
20}
21
22pub trait ToShortFileName {
24 fn to_short_filename(self) -> Result<ShortFileName, FilenameError>;
26}
27
28impl ToShortFileName for ShortFileName {
29 fn to_short_filename(self) -> Result<ShortFileName, FilenameError> {
30 Ok(self)
31 }
32}
33
34impl ToShortFileName for &ShortFileName {
35 fn to_short_filename(self) -> Result<ShortFileName, FilenameError> {
36 Ok(*self)
37 }
38}
39
40impl ToShortFileName for &str {
41 fn to_short_filename(self) -> Result<ShortFileName, FilenameError> {
42 ShortFileName::create_from_str(self)
43 }
44}
45
46#[cfg_attr(feature = "defmt-log", derive(defmt::Format))]
51#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
52pub struct ShortFileName {
53 pub(crate) contents: [u8; Self::TOTAL_LEN],
54}
55
56impl ShortFileName {
57 const BASE_LEN: usize = 8;
58 const TOTAL_LEN: usize = 11;
59
60 pub const fn parent_dir() -> Self {
62 Self {
63 contents: *b".. ",
64 }
65 }
66
67 pub const fn this_dir() -> Self {
69 Self {
70 contents: *b". ",
71 }
72 }
73
74 pub fn base_name(&self) -> &[u8] {
76 Self::bytes_before_space(&self.contents[..Self::BASE_LEN])
77 }
78
79 pub fn extension(&self) -> &[u8] {
81 Self::bytes_before_space(&self.contents[Self::BASE_LEN..])
82 }
83
84 fn bytes_before_space(bytes: &[u8]) -> &[u8] {
85 bytes.split(|b| *b == b' ').next().unwrap_or(&[])
86 }
87
88 pub fn create_from_str(name: &str) -> Result<ShortFileName, FilenameError> {
92 let mut sfn = ShortFileName {
93 contents: [b' '; Self::TOTAL_LEN],
94 };
95
96 if name == ".." {
98 return Ok(ShortFileName::parent_dir());
99 }
100
101 if name.is_empty() || name == "." {
103 return Ok(ShortFileName::this_dir());
104 }
105
106 let mut idx = 0;
107 let mut seen_dot = false;
108 for ch in name.chars() {
109 match ch {
110 '\u{0000}'..='\u{001F}'
112 | '"'
113 | '*'
114 | '+'
115 | ','
116 | '/'
117 | ':'
118 | ';'
119 | '<'
120 | '='
121 | '>'
122 | '?'
123 | '['
124 | '\\'
125 | ']'
126 | ' '
127 | '|' => {
128 return Err(FilenameError::InvalidCharacter);
129 }
130 x if x > '\u{00FF}' => {
131 return Err(FilenameError::InvalidCharacter);
134 }
135 '.' => {
136 if (1..=Self::BASE_LEN).contains(&idx) {
138 idx = Self::BASE_LEN;
139 seen_dot = true;
140 } else {
141 return Err(FilenameError::MisplacedPeriod);
142 }
143 }
144 _ => {
145 let b = ch.to_ascii_uppercase() as u8;
146 if seen_dot {
147 if (Self::BASE_LEN..Self::TOTAL_LEN).contains(&idx) {
148 sfn.contents[idx] = b;
149 } else {
150 return Err(FilenameError::NameTooLong);
151 }
152 } else if idx < Self::BASE_LEN {
153 sfn.contents[idx] = b;
154 } else {
155 return Err(FilenameError::NameTooLong);
156 }
157 idx += 1;
158 }
159 }
160 }
161 if idx == 0 {
162 return Err(FilenameError::FilenameEmpty);
163 }
164 Ok(sfn)
165 }
166
167 pub unsafe fn to_volume_label(self) -> VolumeName {
175 VolumeName {
176 contents: self.contents,
177 }
178 }
179
180 pub fn csum(&self) -> u8 {
182 let mut result = 0u8;
183 for b in self.contents.iter() {
184 result = result.rotate_right(1).wrapping_add(*b);
185 }
186 result
187 }
188}
189
190impl core::fmt::Display for ShortFileName {
191 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
192 let mut printed = 0;
193 for (i, &c) in self.contents.iter().enumerate() {
194 if c != b' ' {
195 if i == Self::BASE_LEN {
196 write!(f, ".")?;
197 printed += 1;
198 }
199 write!(f, "{}", c as char)?;
202 printed += 1;
203 }
204 }
205 if let Some(mut width) = f.width() {
206 if width > printed {
207 width -= printed;
208 for _ in 0..width {
209 write!(f, "{}", f.fill())?;
210 }
211 }
212 }
213 Ok(())
214 }
215}
216
217impl core::fmt::Debug for ShortFileName {
218 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
219 write!(f, "ShortFileName(\"{}\")", self)
220 }
221}
222
223#[derive(Debug)]
225pub struct LfnBuffer<'a> {
226 inner: &'a mut [u8],
228 free: u16,
232 overflow: bool,
234 unpaired_surrogate: Option<u16>,
236}
237
238impl<'a> LfnBuffer<'a> {
239 pub fn new(storage: &'a mut [u8]) -> Self {
241 let len = storage.len().min(usize::from(u16::MAX));
246 LfnBuffer {
247 inner: &mut storage[..len],
248 free: len as u16,
249 overflow: false,
250 unpaired_surrogate: None,
251 }
252 }
253
254 fn free(&self) -> usize {
256 usize::from(self.free)
257 }
258
259 pub fn clear(&mut self) {
261 self.free = self.inner.len() as u16;
262 self.overflow = false;
263 self.unpaired_surrogate = None;
264 }
265
266 pub fn push(&mut self, buffer: &[u16; 13]) {
284 let null_idx = buffer
286 .iter()
287 .position(|&b| b == 0x0000)
288 .unwrap_or(buffer.len());
289 let buffer = &buffer[0..null_idx];
291
292 let mut char_vec: heapless::Vec<char, 13> = heapless::Vec::new();
299 let mut is_first = true;
302 for ch in char::decode_utf16(
303 buffer
304 .iter()
305 .cloned()
306 .chain(self.unpaired_surrogate.take().iter().cloned()),
307 ) {
308 match ch {
309 Ok(ch) => {
310 char_vec.push(ch).expect("Vec was full!?");
311 }
312 Err(e) => {
313 if is_first {
316 trace!("LFN saved {:?}", e.unpaired_surrogate());
319 self.unpaired_surrogate = Some(e.unpaired_surrogate());
320 } else {
321 trace!("LFN replaced {:?}", e.unpaired_surrogate());
324 char_vec.push('\u{fffd}').expect("Vec was full?!");
325 }
326 }
327 }
328 is_first = false;
329 }
330
331 for ch in char_vec.iter().rev() {
332 trace!("LFN push {:?}", ch);
333 let mut encoded_ch = [0u8; 4];
335 let encoded_ch = ch.encode_utf8(&mut encoded_ch);
336 if self.free() < encoded_ch.len() {
337 self.overflow = true;
340 return;
341 }
342 for b in encoded_ch.bytes().rev() {
345 self.free -= 1;
346 self.inner[self.free()] = b;
347 }
348 }
349 }
350
351 pub fn as_str(&self) -> &str {
356 if self.overflow {
357 ""
358 } else {
359 unsafe { core::str::from_utf8_unchecked(&self.inner[self.free()..]) }
361 }
362 }
363}
364
365#[cfg(test)]
372mod test {
373 use super::*;
374
375 #[test]
376 fn filename_no_extension() {
377 let sfn = ShortFileName {
378 contents: *b"HELLO ",
379 };
380 assert_eq!(format!("{}", &sfn), "HELLO");
381 assert_eq!(sfn, ShortFileName::create_from_str("HELLO").unwrap());
382 assert_eq!(sfn, ShortFileName::create_from_str("hello").unwrap());
383 assert_eq!(sfn, ShortFileName::create_from_str("HeLlO").unwrap());
384 assert_eq!(sfn, ShortFileName::create_from_str("HELLO.").unwrap());
385 }
386
387 #[test]
388 fn filename_extension() {
389 let sfn = ShortFileName {
390 contents: *b"HELLO TXT",
391 };
392 assert_eq!(format!("{}", &sfn), "HELLO.TXT");
393 assert_eq!(sfn, ShortFileName::create_from_str("HELLO.TXT").unwrap());
394 }
395
396 #[test]
397 fn filename_get_extension() {
398 let mut sfn = ShortFileName::create_from_str("hello.txt").unwrap();
399 assert_eq!(sfn.extension(), "TXT".as_bytes());
400 sfn = ShortFileName::create_from_str("hello").unwrap();
401 assert_eq!(sfn.extension(), "".as_bytes());
402 sfn = ShortFileName::create_from_str("hello.a").unwrap();
403 assert_eq!(sfn.extension(), "A".as_bytes());
404 }
405
406 #[test]
407 fn filename_get_base_name() {
408 let mut sfn = ShortFileName::create_from_str("hello.txt").unwrap();
409 assert_eq!(sfn.base_name(), "HELLO".as_bytes());
410 sfn = ShortFileName::create_from_str("12345678").unwrap();
411 assert_eq!(sfn.base_name(), "12345678".as_bytes());
412 sfn = ShortFileName::create_from_str("1").unwrap();
413 assert_eq!(sfn.base_name(), "1".as_bytes());
414 }
415
416 #[test]
417 fn filename_fulllength() {
418 let sfn = ShortFileName {
419 contents: *b"12345678TXT",
420 };
421 assert_eq!(format!("{}", &sfn), "12345678.TXT");
422 assert_eq!(sfn, ShortFileName::create_from_str("12345678.TXT").unwrap());
423 }
424
425 #[test]
426 fn filename_short_extension() {
427 let sfn = ShortFileName {
428 contents: *b"12345678C ",
429 };
430 assert_eq!(format!("{}", &sfn), "12345678.C");
431 assert_eq!(sfn, ShortFileName::create_from_str("12345678.C").unwrap());
432 }
433
434 #[test]
435 fn filename_short() {
436 let sfn = ShortFileName {
437 contents: *b"1 C ",
438 };
439 assert_eq!(format!("{}", &sfn), "1.C");
440 assert_eq!(sfn, ShortFileName::create_from_str("1.C").unwrap());
441 }
442
443 #[test]
444 fn filename_ordering() {
445 assert!(
446 ShortFileName::create_from_str("1.C").unwrap()
447 < ShortFileName::create_from_str("2.C").unwrap()
448 );
449 assert!(
450 ShortFileName::create_from_str("1.C").unwrap()
451 < ShortFileName::create_from_str("1.D").unwrap()
452 );
453 assert!(
454 ShortFileName::create_from_str("12.C").unwrap()
455 < ShortFileName::create_from_str("3.C").unwrap()
456 );
457 assert!(
458 ShortFileName::create_from_str("1.D").unwrap()
459 < ShortFileName::create_from_str("12.C").unwrap()
460 );
461 assert_eq!(
462 ShortFileName::create_from_str("1.D")
463 .unwrap()
464 .cmp(&ShortFileName::create_from_str("1.D").unwrap()),
465 core::cmp::Ordering::Equal
466 );
467 assert!(
468 ShortFileName::create_from_str("1").unwrap()
469 < ShortFileName::create_from_str("1.C").unwrap()
470 );
471 assert!(
472 ShortFileName::create_from_str("1.C").unwrap()
473 < ShortFileName::create_from_str("2").unwrap()
474 );
475 }
476
477 #[test]
478 fn filename_empty() {
479 assert_eq!(
480 ShortFileName::create_from_str("").unwrap(),
481 ShortFileName::this_dir()
482 );
483 }
484
485 #[test]
486 fn filename_bad() {
487 assert!(ShortFileName::create_from_str(" ").is_err());
488 assert!(ShortFileName::create_from_str("123456789").is_err());
489 assert!(ShortFileName::create_from_str("12345678.ABCD").is_err());
490 }
491
492 #[test]
493 fn checksum() {
494 assert_eq!(
495 0xB3,
496 ShortFileName::create_from_str("UNARCH~1.DAT")
497 .unwrap()
498 .csum()
499 );
500 }
501
502 #[test]
503 fn one_piece() {
504 let mut storage = [0u8; 64];
505 let mut buf: LfnBuffer = LfnBuffer::new(&mut storage);
506 buf.push(&[
507 0x0030, 0x0031, 0x0032, 0x0033, 0x2202, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
508 0xFFFF, 0xFFFF,
509 ]);
510 assert_eq!(buf.as_str(), "0123∂");
511 }
512
513 #[test]
514 fn two_piece() {
515 let mut storage = [0u8; 64];
516 let mut buf: LfnBuffer = LfnBuffer::new(&mut storage);
517 buf.push(&[
518 0x0030, 0x0031, 0x0032, 0x0033, 0x2202, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
519 0xFFFF, 0xFFFF,
520 ]);
521 buf.push(&[
522 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b,
523 0x004c, 0x004d,
524 ]);
525 assert_eq!(buf.as_str(), "ABCDEFGHIJKLM0123∂");
526 }
527
528 #[test]
529 fn two_piece_split_surrogate() {
530 let mut storage = [0u8; 64];
531 let mut buf: LfnBuffer = LfnBuffer::new(&mut storage);
532
533 buf.push(&[
534 0xde00, 0x002e, 0x0074, 0x0078, 0x0074, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
535 0xffff, 0xffff,
536 ]);
537 buf.push(&[
538 0xd83d, 0xde00, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038,
539 0x0039, 0xd83d,
540 ]);
541 assert_eq!(buf.as_str(), "😀0123456789😀.txt");
542 }
543}
544
545