1use core::fmt;
2
3use hadris_fixed::FixedBytes;
4
5#[repr(transparent)]
7#[derive(Clone, Copy, PartialEq, Eq)]
8pub struct ShortFileName(FixedBytes<12>);
9
10impl fmt::Debug for ShortFileName {
11 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
12 let mut tuple = f.debug_tuple("ShortFileName");
13 match self.0.try_as_str() {
15 Ok(name) => tuple.field(&name),
16 Err(_) => tuple.field(&self.0.as_bytes()),
17 };
18 tuple.finish()
19 }
20}
21
22#[derive(Debug)]
23pub struct CreateShortFileNameError;
25
26impl fmt::Display for CreateShortFileNameError {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 f.write_str("disallowed characters in short file name")
29 }
30}
31
32#[cfg(feature = "std")]
33impl std::error::Error for CreateShortFileNameError {}
34
35impl ShortFileName {
36 pub const ALLOWED_SYMBOLS: &'static [u8] = b"$%'-_@~`!(){}^#&";
38
39 pub fn new(bytes: [u8; 11]) -> Result<Self, CreateShortFileNameError> {
41 if bytes == *b". " {
43 let mut name = FixedBytes::empty();
44 name.push_byte(b'.');
45 return Ok(Self(name));
46 }
47 if bytes == *b".. " {
48 let mut name = FixedBytes::empty();
49 name.push_slice(b"..");
50 return Ok(Self(name));
51 }
52
53 for byte in &bytes {
54 if byte.is_ascii_uppercase()
55 || Self::ALLOWED_SYMBOLS.contains(byte)
56 || byte.is_ascii_digit()
57 || *byte == b' '
58 || *byte > 127
59 {
60 continue;
61 }
62 return Err(CreateShortFileNameError);
63 }
64
65 let mut name = FixedBytes::empty();
66 name.push_slice(&bytes[0..8]);
67 name.push_byte(b'.');
68 name.push_slice(&bytes[8..11]);
69 Ok(Self(name))
70 }
71
72 pub fn raw_bytes(&self) -> [u8; 11] {
80 let bytes = self.0.as_bytes();
81 let mut result = [b' '; 11];
82 let dot_pos = bytes.iter().position(|&b| b == b'.').unwrap_or(bytes.len());
84 let name_len = dot_pos.min(8);
85 result[..name_len].copy_from_slice(&bytes[..name_len]);
86 if dot_pos < bytes.len() {
87 let ext_start = dot_pos + 1;
88 let ext_len = (bytes.len() - ext_start).min(3);
89 result[8..8 + ext_len].copy_from_slice(&bytes[ext_start..ext_start + ext_len]);
90 }
91 result
92 }
93
94 pub fn as_str(&self) -> &str {
103 self.0.as_str()
104 }
105
106 pub fn try_as_str(&self) -> Result<&str, core::str::Utf8Error> {
109 self.0.try_as_str()
110 }
111
112 #[cfg(feature = "alloc")]
115 pub(crate) fn as_padded_bytes(&self) -> &[u8] {
116 self.0.as_bytes()
117 }
118
119 pub fn with_nt_case(&self, flags: crate::raw::NtCaseFlags) -> ShortFileName {
129 use crate::raw::NtCaseFlags;
130
131 fn push_maybe_lower(out: &mut FixedBytes<12>, part: &[u8], lower: bool) {
132 for &byte in part {
133 out.push_byte(if lower {
134 byte.to_ascii_lowercase()
135 } else {
136 byte
137 });
138 }
139 }
140
141 let bytes = self.0.as_bytes();
142 let dot = bytes.iter().position(|&b| b == b'.');
143 let base_end = dot.unwrap_or(bytes.len());
144 let mut out = FixedBytes::<12>::empty();
145 push_maybe_lower(
146 &mut out,
147 &bytes[..base_end],
148 flags.contains(NtCaseFlags::LOWER_BASE),
149 );
150 if let Some(dot) = dot {
151 out.push_byte(b'.');
152 push_maybe_lower(
153 &mut out,
154 &bytes[dot + 1..],
155 flags.contains(NtCaseFlags::LOWER_EXT),
156 );
157 }
158 ShortFileName(out)
159 }
160
161 pub fn matches(&self, name: &str) -> bool {
168 fn trim_end_spaces(mut bytes: &[u8]) -> &[u8] {
169 while let [rest @ .., b' '] = bytes {
170 bytes = rest;
171 }
172 bytes
173 }
174
175 let raw = self.0.as_bytes();
176 let (our_base, our_ext) = match raw.iter().position(|&b| b == b'.') {
178 Some(dot) => (&raw[..dot], &raw[dot + 1..]),
179 None => (raw, &[][..]),
180 };
181 let (our_base, our_ext) = (trim_end_spaces(our_base), trim_end_spaces(our_ext));
182
183 let name = name.as_bytes();
184 let (search_base, search_ext) = match name.iter().rposition(|&b| b == b'.') {
185 Some(dot) => (&name[..dot], &name[dot + 1..]),
186 None => (name, &[][..]),
187 };
188
189 our_base.eq_ignore_ascii_case(search_base) && our_ext.eq_ignore_ascii_case(search_ext)
190 }
191
192 pub fn lfn_checksum(&self) -> u8 {
195 let name = self.raw_bytes();
196 let mut sum: u8 = 0;
197 for &byte in &name {
198 sum = sum.rotate_right(1).wrapping_add(byte);
200 }
201 sum
202 }
203
204 #[cfg(feature = "write")]
206 pub fn to_raw_bytes(&self) -> [u8; 11] {
207 self.raw_bytes()
208 }
209
210 #[cfg(feature = "write")]
223 pub fn from_long_name(name: &str, suffix: u8) -> Result<Self, CreateShortFileNameError> {
224 Self::from_long_name_with(name, suffix, &crate::oem::LossyAsciiOemCpConverter)
225 }
226
227 #[cfg(feature = "write")]
233 pub fn from_long_name_with(
234 name: &str,
235 suffix: u8,
236 oem: &dyn crate::oem::OemCpConverter,
237 ) -> Result<Self, CreateShortFileNameError> {
238 let (base, ext) = match name.rfind('.') {
240 Some(pos) if pos > 0 => (&name[..pos], &name[pos + 1..]),
241 _ => (name, ""),
242 };
243
244 let mut base_chars = [b' '; 8];
246 let mut base_len = 0;
247 for ch in base.chars() {
248 if base_len >= 6 && suffix > 0 {
249 break;
251 }
252 if base_len >= 8 {
253 break;
254 }
255 let processed = Self::process_char(ch, oem);
256 if processed != 0 {
257 base_chars[base_len] = processed;
258 base_len += 1;
259 }
260 }
261
262 if suffix > 0 {
264 if suffix <= 4 {
265 let max_base = 6; if base_len > max_base {
268 base_len = max_base;
269 }
270 base_chars[base_len] = b'~';
271 base_len += 1;
272 base_chars[base_len] = b'0' + suffix;
273 base_len += 1;
274 } else {
275 let hash = Self::lfn_hash(name, suffix);
279 let max_base = 2; if base_len > max_base {
281 base_len = max_base;
282 }
283 base_chars[base_len] = b'~';
284 base_len += 1;
285 for i in (0..4).rev() {
287 let nibble = ((hash >> (i * 4)) & 0xF) as u8;
288 base_chars[base_len] = if nibble < 10 {
289 b'0' + nibble
290 } else {
291 b'A' + nibble - 10
292 };
293 base_len += 1;
294 }
295 }
296 }
297
298 let mut ext_chars = [b' '; 3];
300 let mut ext_len = 0;
301 for ch in ext.chars() {
302 if ext_len >= 3 {
303 break;
304 }
305 let processed = Self::process_char(ch, oem);
306 if processed != 0 {
307 ext_chars[ext_len] = processed;
308 ext_len += 1;
309 }
310 }
311
312 let mut result = [b' '; 11];
314 result[..8].copy_from_slice(&base_chars);
315 result[8..11].copy_from_slice(&ext_chars);
316
317 if base_len == 0 && ext_len == 0 {
319 return Err(CreateShortFileNameError);
320 }
321
322 Self::new(result)
323 }
324
325 #[cfg(feature = "write")]
328 fn lfn_hash(name: &str, suffix: u8) -> u16 {
329 let mut hash: u16 = suffix as u16;
330 for &b in name.as_bytes() {
331 hash = hash.wrapping_mul(37).wrapping_add(b as u16);
332 }
333 hash
334 }
335
336 #[cfg(feature = "write")]
342 fn process_char(ch: char, oem: &dyn crate::oem::OemCpConverter) -> u8 {
343 if ch.is_ascii_alphanumeric() {
344 ch.to_ascii_uppercase() as u8
345 } else if Self::ALLOWED_SYMBOLS.contains(&(ch as u8)) {
346 ch as u8
347 } else if ch == ' ' || ch == '.' {
348 0
350 } else if ch.is_ascii() {
351 b'_'
353 } else {
354 oem.encode(ch).unwrap_or(b'_')
356 }
357 }
358}
359
360pub const LFN_MAX_UTF16_UNITS: usize = 255;
362
363#[cfg(feature = "lfn")]
372#[derive(Clone, PartialEq, Eq)]
373pub struct LongFileName {
374 chars: [u16; LFN_MAX_UTF16_UNITS],
375 len: usize,
376}
377
378#[cfg(feature = "lfn")]
379impl fmt::Debug for LongFileName {
380 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
381 struct LossyChars<'a>(&'a LongFileName);
382 impl fmt::Debug for LossyChars<'_> {
383 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
384 f.write_str("\"")?;
385 for ch in self.0.chars() {
386 fmt::Write::write_char(f, ch)?;
387 }
388 f.write_str("\"")
389 }
390 }
391 f.debug_tuple("LongFileName")
392 .field(&LossyChars(self))
393 .finish()
394 }
395}
396
397#[cfg(feature = "lfn")]
398impl Default for LongFileName {
399 fn default() -> Self {
400 Self::new()
401 }
402}
403
404#[cfg(feature = "lfn")]
405impl LongFileName {
406 pub const CHARS_PER_ENTRY: usize = 13;
408
409 pub fn new() -> Self {
411 Self {
412 chars: [0; LFN_MAX_UTF16_UNITS],
413 len: 0,
414 }
415 }
416
417 pub fn clear(&mut self) {
419 self.len = 0;
420 }
421
422 pub fn is_empty(&self) -> bool {
424 self.len == 0
425 }
426
427 pub fn len(&self) -> usize {
429 self.len
430 }
431
432 pub fn prepend_lfn_entry(&mut self, name1: &[u8; 10], name2: &[u8; 12], name3: &[u8; 4]) {
436 let mut utf16_chars = [0u16; Self::CHARS_PER_ENTRY];
438
439 for i in 0..5 {
441 utf16_chars[i] = u16::from_le_bytes([name1[i * 2], name1[i * 2 + 1]]);
442 }
443 for i in 0..6 {
445 utf16_chars[5 + i] = u16::from_le_bytes([name2[i * 2], name2[i * 2 + 1]]);
446 }
447 for i in 0..2 {
449 utf16_chars[11 + i] = u16::from_le_bytes([name3[i * 2], name3[i * 2 + 1]]);
450 }
451
452 let actual_len = utf16_chars
454 .iter()
455 .position(|&c| c == 0x0000 || c == 0xFFFF)
456 .unwrap_or(Self::CHARS_PER_ENTRY);
457
458 let new_len = self.len + actual_len;
460 if new_len > LFN_MAX_UTF16_UNITS {
461 return;
464 }
465 if self.len > 0 {
466 self.chars.copy_within(0..self.len, actual_len);
467 }
468 self.chars[..actual_len].copy_from_slice(&utf16_chars[..actual_len]);
469 self.len = new_len;
470 }
471
472 pub fn as_utf16(&self) -> &[u16] {
474 &self.chars[..self.len]
475 }
476
477 pub fn chars(&self) -> impl Iterator<Item = char> + '_ {
482 char::decode_utf16(self.chars[..self.len].iter().copied())
483 .map(|r| r.unwrap_or(char::REPLACEMENT_CHARACTER))
484 }
485
486 pub fn eq_str(&self, s: &str) -> bool {
488 self.chars().eq(s.chars())
489 }
490
491 #[cfg(feature = "write")]
497 pub fn from_str_utf16(name: &str) -> Option<Self> {
498 let mut out = Self::new();
499 for ch in name.chars() {
500 let mut tmp = [0u16; 2];
501 for &c in ch.encode_utf16(&mut tmp).iter() {
502 if out.len >= LFN_MAX_UTF16_UNITS {
503 return None;
504 }
505 out.chars[out.len] = c;
506 out.len += 1;
507 }
508 }
509 Some(out)
510 }
511}
512
513#[cfg(feature = "lfn")]
514impl fmt::Display for LongFileName {
515 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
516 for ch in self.chars() {
517 fmt::Write::write_char(f, ch)?;
518 }
519 Ok(())
520 }
521}
522
523#[cfg(feature = "lfn")]
525pub struct LfnBuilder {
526 pub name: LongFileName,
528 pub checksum: u8,
530 pub expected_seq: u8,
532 pub building: bool,
534}
535
536#[cfg(feature = "lfn")]
537impl Default for LfnBuilder {
538 fn default() -> Self {
539 Self::new()
540 }
541}
542
543#[cfg(feature = "lfn")]
544impl LfnBuilder {
545 pub const LAST_ENTRY_MASK: u8 = 0x40;
547 pub const SEQ_NUMBER_MASK: u8 = 0x3F;
549
550 pub fn new() -> Self {
552 Self {
553 name: LongFileName::new(),
554 checksum: 0,
555 expected_seq: 0,
556 building: false,
557 }
558 }
559
560 pub fn reset(&mut self) {
562 self.name.clear();
563 self.checksum = 0;
564 self.expected_seq = 0;
565 self.building = false;
566 }
567
568 pub fn start(&mut self, seq_number: u8, checksum: u8) {
570 self.reset();
571 let count = seq_number & Self::SEQ_NUMBER_MASK;
574 if count == 0 {
575 return;
576 }
577 self.building = true;
578 self.checksum = checksum;
579 self.expected_seq = count;
580 }
581
582 pub fn add_entry(
585 &mut self,
586 seq_number: u8,
587 checksum: u8,
588 name1: &[u8; 10],
589 name2: &[u8; 12],
590 name3: &[u8; 4],
591 ) -> bool {
592 let seq = seq_number & Self::SEQ_NUMBER_MASK;
593
594 if seq == 0 || seq != self.expected_seq {
596 self.reset();
597 return false;
598 }
599
600 if checksum != self.checksum {
602 self.reset();
603 return false;
604 }
605
606 self.name.prepend_lfn_entry(name1, name2, name3);
608
609 self.expected_seq -= 1;
611
612 true
613 }
614
615 pub fn is_complete(&self) -> bool {
617 self.building && self.expected_seq == 0
618 }
619
620 pub fn finish(&mut self, short_name: &ShortFileName) -> Option<LongFileName> {
622 if !self.is_complete() {
623 self.reset();
624 return None;
625 }
626
627 if short_name.lfn_checksum() != self.checksum {
629 self.reset();
630 return None;
631 }
632
633 let result = core::mem::take(&mut self.name);
634 self.reset();
635 Some(result)
636 }
637}
638
639#[cfg(all(test, feature = "lfn", feature = "alloc"))]
640mod lfn_unicode_tests {
641 use super::*;
642 extern crate alloc;
643 use alloc::string::ToString;
644
645 #[test]
651 fn lone_high_surrogate_becomes_replacement_char() {
652 let mut lfn = LongFileName::new();
653 let name1: [u8; 10] = [0x00, 0xD8, b'a', 0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF];
655 let name2: [u8; 12] = [0xFF; 12];
656 let name3: [u8; 4] = [0xFF; 4];
657
658 lfn.prepend_lfn_entry(&name1, &name2, &name3);
659
660 let s = lfn.to_string();
661 assert_eq!(s, "\u{FFFD}a");
662 }
663
664 #[test]
669 fn valid_surrogate_pair_decodes_to_supplementary_codepoint() {
670 let mut lfn = LongFileName::new();
671 let name1: [u8; 10] = [0x3D, 0xD8, 0x00, 0xDE, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF];
673 let name2: [u8; 12] = [0xFF; 12];
674 let name3: [u8; 4] = [0xFF; 4];
675
676 lfn.prepend_lfn_entry(&name1, &name2, &name3);
677
678 assert_eq!(lfn.to_string(), "\u{1F600}");
679 }
680
681 #[test]
687 fn surrogate_pair_split_across_entries() {
688 let mut lfn = LongFileName::new();
689
690 let high_name1: [u8; 10] = [b'a', 0, b'b', 0, b'c', 0, b'd', 0, b'e', 0];
693 let high_name2: [u8; 12] = [b'f', 0, b'g', 0, b'h', 0, b'i', 0, b'j', 0, b'k', 0];
694 let high_name3: [u8; 4] = [b'l', 0, 0x3D, 0xD8]; let low_name1: [u8; 10] = [
699 0x00, 0xDE, b'm', 0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
701 ];
702 let low_name2: [u8; 12] = [0xFF; 12];
703 let low_name3: [u8; 4] = [0xFF; 4];
704
705 lfn.prepend_lfn_entry(&low_name1, &low_name2, &low_name3);
706 lfn.prepend_lfn_entry(&high_name1, &high_name2, &high_name3);
707
708 assert_eq!(lfn.to_string(), "abcdefghijkl\u{1F600}m");
709 }
710
711 #[test]
714 fn two_byte_utf8_codepoint() {
715 let mut lfn = LongFileName::new();
716 let name1: [u8; 10] = [0xE9, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
718 let name2: [u8; 12] = [0xFF; 12];
719 let name3: [u8; 4] = [0xFF; 4];
720
721 lfn.prepend_lfn_entry(&name1, &name2, &name3);
722
723 assert_eq!(lfn.to_string(), "é");
724 }
725
726 #[test]
728 fn eq_str_matches_decoded_chars() {
729 let mut lfn = LongFileName::new();
730 let name1: [u8; 10] = [0x3D, 0xD8, 0x00, 0xDE, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF];
732 let name2: [u8; 12] = [0xFF; 12];
733 let name3: [u8; 4] = [0xFF; 4];
734
735 lfn.prepend_lfn_entry(&name1, &name2, &name3);
736
737 assert!(lfn.eq_str("\u{1F600}"));
738 assert!(!lfn.eq_str("X"));
739 }
740
741 #[test]
745 fn lfn_start_rejects_zero_sequence_count() {
746 let mut builder = LfnBuilder::new();
747 builder.start(0x40, 0);
748 assert!(!builder.building);
749 assert!(!builder.add_entry(0x40, 0, &[0; 10], &[0; 12], &[0; 4]));
750 }
751}