embedded_sdmmc_dev/filesystem/
filename.rs1use crate::fat::VolumeName;
4use crate::trace;
5
6#[cfg_attr(feature = "defmt-log", derive(defmt::Format))]
8#[derive(Debug, Clone)]
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.clone())
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(PartialEq, Eq, Clone)]
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: usize,
232 overflow: bool,
234 unpaired_surrogate: Option<u16>,
236}
237
238impl<'a> LfnBuffer<'a> {
239 pub fn new(storage: &'a mut [u8]) -> LfnBuffer<'a> {
241 let len = storage.len();
242 LfnBuffer {
243 inner: storage,
244 free: len,
245 overflow: false,
246 unpaired_surrogate: None,
247 }
248 }
249
250 pub fn clear(&mut self) {
252 self.free = self.inner.len();
253 self.overflow = false;
254 self.unpaired_surrogate = None;
255 }
256
257 pub fn push(&mut self, buffer: &[u16; 13]) {
275 let null_idx = buffer
277 .iter()
278 .position(|&b| b == 0x0000)
279 .unwrap_or(buffer.len());
280 let buffer = &buffer[0..null_idx];
282
283 let mut char_vec: heapless::Vec<char, 13> = heapless::Vec::new();
290 let mut is_first = true;
293 for ch in char::decode_utf16(
294 buffer
295 .iter()
296 .cloned()
297 .chain(self.unpaired_surrogate.take().iter().cloned()),
298 ) {
299 match ch {
300 Ok(ch) => {
301 char_vec.push(ch).expect("Vec was full!?");
302 }
303 Err(e) => {
304 if is_first {
307 trace!("LFN saved {:?}", e.unpaired_surrogate());
310 self.unpaired_surrogate = Some(e.unpaired_surrogate());
311 } else {
312 trace!("LFN replaced {:?}", e.unpaired_surrogate());
315 char_vec.push('\u{fffd}').expect("Vec was full?!");
316 }
317 }
318 }
319 is_first = false;
320 }
321
322 for ch in char_vec.iter().rev() {
323 trace!("LFN push {:?}", ch);
324 let mut encoded_ch = [0u8; 4];
326 let encoded_ch = ch.encode_utf8(&mut encoded_ch);
327 if self.free < encoded_ch.len() {
328 self.overflow = true;
331 return;
332 }
333 for b in encoded_ch.bytes().rev() {
336 self.free -= 1;
337 self.inner[self.free] = b;
338 }
339 }
340 }
341
342 pub fn as_str(&self) -> &str {
347 if self.overflow {
348 ""
349 } else {
350 unsafe { core::str::from_utf8_unchecked(&self.inner[self.free..]) }
352 }
353 }
354}
355
356#[cfg(test)]
363mod test {
364 use super::*;
365
366 #[test]
367 fn filename_no_extension() {
368 let sfn = ShortFileName {
369 contents: *b"HELLO ",
370 };
371 assert_eq!(format!("{}", &sfn), "HELLO");
372 assert_eq!(sfn, ShortFileName::create_from_str("HELLO").unwrap());
373 assert_eq!(sfn, ShortFileName::create_from_str("hello").unwrap());
374 assert_eq!(sfn, ShortFileName::create_from_str("HeLlO").unwrap());
375 assert_eq!(sfn, ShortFileName::create_from_str("HELLO.").unwrap());
376 }
377
378 #[test]
379 fn filename_extension() {
380 let sfn = ShortFileName {
381 contents: *b"HELLO TXT",
382 };
383 assert_eq!(format!("{}", &sfn), "HELLO.TXT");
384 assert_eq!(sfn, ShortFileName::create_from_str("HELLO.TXT").unwrap());
385 }
386
387 #[test]
388 fn filename_get_extension() {
389 let mut sfn = ShortFileName::create_from_str("hello.txt").unwrap();
390 assert_eq!(sfn.extension(), "TXT".as_bytes());
391 sfn = ShortFileName::create_from_str("hello").unwrap();
392 assert_eq!(sfn.extension(), "".as_bytes());
393 sfn = ShortFileName::create_from_str("hello.a").unwrap();
394 assert_eq!(sfn.extension(), "A".as_bytes());
395 }
396
397 #[test]
398 fn filename_get_base_name() {
399 let mut sfn = ShortFileName::create_from_str("hello.txt").unwrap();
400 assert_eq!(sfn.base_name(), "HELLO".as_bytes());
401 sfn = ShortFileName::create_from_str("12345678").unwrap();
402 assert_eq!(sfn.base_name(), "12345678".as_bytes());
403 sfn = ShortFileName::create_from_str("1").unwrap();
404 assert_eq!(sfn.base_name(), "1".as_bytes());
405 }
406
407 #[test]
408 fn filename_fulllength() {
409 let sfn = ShortFileName {
410 contents: *b"12345678TXT",
411 };
412 assert_eq!(format!("{}", &sfn), "12345678.TXT");
413 assert_eq!(sfn, ShortFileName::create_from_str("12345678.TXT").unwrap());
414 }
415
416 #[test]
417 fn filename_short_extension() {
418 let sfn = ShortFileName {
419 contents: *b"12345678C ",
420 };
421 assert_eq!(format!("{}", &sfn), "12345678.C");
422 assert_eq!(sfn, ShortFileName::create_from_str("12345678.C").unwrap());
423 }
424
425 #[test]
426 fn filename_short() {
427 let sfn = ShortFileName {
428 contents: *b"1 C ",
429 };
430 assert_eq!(format!("{}", &sfn), "1.C");
431 assert_eq!(sfn, ShortFileName::create_from_str("1.C").unwrap());
432 }
433
434 #[test]
435 fn filename_empty() {
436 assert_eq!(
437 ShortFileName::create_from_str("").unwrap(),
438 ShortFileName::this_dir()
439 );
440 }
441
442 #[test]
443 fn filename_bad() {
444 assert!(ShortFileName::create_from_str(" ").is_err());
445 assert!(ShortFileName::create_from_str("123456789").is_err());
446 assert!(ShortFileName::create_from_str("12345678.ABCD").is_err());
447 }
448
449 #[test]
450 fn checksum() {
451 assert_eq!(
452 0xB3,
453 ShortFileName::create_from_str("UNARCH~1.DAT")
454 .unwrap()
455 .csum()
456 );
457 }
458
459 #[test]
460 fn one_piece() {
461 let mut storage = [0u8; 64];
462 let mut buf: LfnBuffer = LfnBuffer::new(&mut storage);
463 buf.push(&[
464 0x0030, 0x0031, 0x0032, 0x0033, 0x2202, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
465 0xFFFF, 0xFFFF,
466 ]);
467 assert_eq!(buf.as_str(), "0123∂");
468 }
469
470 #[test]
471 fn two_piece() {
472 let mut storage = [0u8; 64];
473 let mut buf: LfnBuffer = LfnBuffer::new(&mut storage);
474 buf.push(&[
475 0x0030, 0x0031, 0x0032, 0x0033, 0x2202, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
476 0xFFFF, 0xFFFF,
477 ]);
478 buf.push(&[
479 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b,
480 0x004c, 0x004d,
481 ]);
482 assert_eq!(buf.as_str(), "ABCDEFGHIJKLM0123∂");
483 }
484
485 #[test]
486 fn two_piece_split_surrogate() {
487 let mut storage = [0u8; 64];
488 let mut buf: LfnBuffer = LfnBuffer::new(&mut storage);
489
490 buf.push(&[
491 0xde00, 0x002e, 0x0074, 0x0078, 0x0074, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
492 0xffff, 0xffff,
493 ]);
494 buf.push(&[
495 0xd83d, 0xde00, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038,
496 0x0039, 0xd83d,
497 ]);
498 assert_eq!(buf.as_str(), "😀0123456789😀.txt");
499 }
500}
501
502