1use alloc::format;
2use alloc::string::{String, ToString};
3use alloc::vec::Vec;
4use core::mem;
5
6use log::{debug, info};
7use snafu::ensure;
8
9use crate::api::raw_directory_entry::EntryId::Deleted;
10use crate::api::raw_directory_entry::{
11 Attributes, LongFileNameEntry, RegularDirectoryEntry, UnknownDirectoryEntry,
12 VfatDirectoryEntry, unknown_entry_convert_to_bytes_2,
13};
14use crate::api::{DirectoryEntry, File, Metadata, VfatMetadataTrait};
15use crate::cluster::cluster_reader::ClusterChainReader;
16use crate::{ClusterId, VfatFS};
17use crate::{PathBuf, error};
18
19use crate::SECTOR_SIZE;
20const ENTRIES_AMOUNT: usize = SECTOR_SIZE / size_of::<UnknownDirectoryEntry>();
21const BUF_SIZE: usize = size_of::<UnknownDirectoryEntry>() * ENTRIES_AMOUNT;
22
23pub fn unknown_entry_convert_from_bytes_entries(
25 entries: [u8; BUF_SIZE],
26) -> [UnknownDirectoryEntry; ENTRIES_AMOUNT] {
27 let mut result: [UnknownDirectoryEntry; ENTRIES_AMOUNT] =
29 [UnknownDirectoryEntry::from([0u8; 32]); ENTRIES_AMOUNT];
30
31 for (i, chunk) in entries
33 .chunks_exact(size_of::<UnknownDirectoryEntry>())
34 .enumerate()
35 {
36 let entry_bytes: [u8; 32] = chunk.try_into().expect("chunk size mismatch");
37 result[i] = UnknownDirectoryEntry::from(entry_bytes);
38 }
39 result
40}
41
42#[derive(Debug)]
44pub enum EntryType {
45 File,
47 Directory,
49 }
51
52#[derive(Debug)]
56pub struct Directory {
57 pub(crate) vfat_filesystem: VfatFS,
58 pub metadata: Metadata,
60 last_entry_spot: Option<usize>,
62}
63
64impl Directory {
65 pub(crate) fn new(vfat_filesystem: VfatFS, metadata: Metadata) -> Self {
66 Self {
67 vfat_filesystem,
68 metadata,
69 last_entry_spot: None,
70 }
71 }
72
73 pub fn contains(&self, name: &str) -> error::Result<bool> {
76 let lock = self.vfat_filesystem.fs_lock.clone();
77 let _guard = lock.read();
78 self.contains_unlocked(name)
79 }
80
81 pub(crate) fn contains_unlocked(&self, name: &str) -> error::Result<bool> {
82 for entry in self.contents_unlocked()? {
83 if entry.name() == name {
84 return Ok(true);
85 }
86 }
87 Ok(false)
88 }
89 pub fn create_file(&mut self, name: String) -> error::Result<File> {
92 let lock = self.vfat_filesystem.fs_lock.clone();
93 let _guard = lock.write();
94 Ok(self.create(name, EntryType::File)?.into_file_unchecked())
95 }
96
97 pub fn create_directory(&mut self, name: String) -> error::Result<Directory> {
100 let lock = self.vfat_filesystem.fs_lock.clone();
101 let _guard = lock.write();
102 Ok(self
103 .create(name, EntryType::Directory)?
104 .into_directory_unchecked())
105 }
106
107 fn create(&mut self, name: String, entry_type: EntryType) -> error::Result<DirectoryEntry> {
109 info!(
110 "Creating {:?} entry with name '{:?}' in directory '{:?}'",
111 entry_type,
112 name,
113 self.metadata.name()
114 );
115 if self.contains_unlocked(&name)? {
116 return Err(error::VfatRsError::NameAlreadyInUse { target: name });
117 }
118
119 let metadata = self.create_metadata_for_new_entry(name.as_str(), &entry_type)?;
121
122 let existing_short_names = self.collect_short_names()?;
124 let entries: Vec<UnknownDirectoryEntry> = VfatDirectoryEntry::new_vfat_entry(
125 name.as_str(),
126 metadata.cluster,
127 Self::attributes_from_entry(&entry_type),
128 &existing_short_names,
129 )?;
130 let entries_len = entries.len();
131 let first_empty_spot_offset = if let Some(spot) = self.last_entry_spot {
132 spot
133 } else {
134 self.find_first_empty_spot_offset(entries_len)?
135 };
136
137 info!(
138 "Going to use as metadata: {:?}. self metadatapath= '{}', selfmetadata name = '{}'. My attributes: {:?}, cluster: {:?}",
139 metadata,
140 self.metadata.full_path().display(),
141 self.metadata.name(),
142 self.metadata.attributes,
143 self.metadata.cluster
144 );
145 info!(
146 "Found spot: {:?}, Going to append entries: {:?}",
147 first_empty_spot_offset, entries
148 );
149
150 let mut ccw = self
151 .vfat_filesystem
152 .cluster_chain_writer(self.metadata.cluster);
153 ccw.seek(first_empty_spot_offset)?;
154
155 for unknown_entry in entries.into_iter() {
156 let entry: [u8; size_of::<UnknownDirectoryEntry>()] = unknown_entry.into();
157 ccw.write(&entry)?;
158 }
159
160 if let EntryType::Directory = entry_type {
161 let entries =
162 VfatDirectoryEntry::create_pseudo_dir_entries(metadata.cluster, ClusterId::new(0));
163 let mut cw = self.vfat_filesystem.cluster_chain_writer(metadata.cluster);
164 let buf = unknown_entry_convert_to_bytes_2(entries);
165 cw.write(&buf)?;
166 }
167 self.last_entry_spot = None;
169
170 Ok(match entry_type {
171 EntryType::Directory => {
172 DirectoryEntry::new_directory(metadata, self.vfat_filesystem.clone())
173 }
174 EntryType::File => DirectoryEntry::new_file(metadata, self.vfat_filesystem.clone()),
175 })
176 }
177
178 fn find_first_empty_spot_offset(&self, slots_needed: usize) -> error::Result<usize> {
182 let mut cluster_chain_reader = self.cluster_chain_reader();
183 let mut buff = [0u8; BUF_SIZE];
184 let mut offset = 0;
185 let mut run_start: Option<usize> = None;
187 let mut run_len: usize = 0;
188
189 while cluster_chain_reader.read(&mut buff)? > 0 {
190 let unknown_entries: [UnknownDirectoryEntry; ENTRIES_AMOUNT] =
191 unknown_entry_convert_from_bytes_entries(buff);
192 for entry in unknown_entries.iter() {
193 if entry.is_end_of_entries() {
194 if let Some(start) = run_start
196 && run_len >= slots_needed
197 {
198 return Ok(start);
199 }
200 return Ok(offset);
202 } else if entry.is_deleted() {
203 if run_start.is_none() {
204 run_start = Some(offset);
205 run_len = 0;
206 }
207 run_len += 1;
208 if run_len >= slots_needed {
209 return Ok(run_start.unwrap());
210 }
211 } else {
212 run_start = None;
214 run_len = 0;
215 }
216 offset += size_of::<UnknownDirectoryEntry>();
217 }
218 buff = [0u8; BUF_SIZE];
219 }
220 Ok(offset)
222 }
223
224 fn create_metadata_for_new_entry(
225 &mut self,
226 entry_name: &str,
227 entry_type: &EntryType,
228 ) -> error::Result<Metadata> {
229 let path = PathBuf::from(format!(
230 "{}{}",
231 self.metadata.full_path().display(),
232 entry_name
233 ));
234 let attributes = Self::attributes_from_entry(entry_type);
235 let cluster_id = match entry_type {
236 EntryType::File => ClusterId::new(0),
238 EntryType::Directory => self.vfat_filesystem.allocate_cluster_new_entry()?,
240 };
241 debug!("Going to use as cluster id: {}", cluster_id);
242 let size = 0;
243 let metadata = Metadata::new(
244 self.vfat_filesystem
245 .time_manager
246 .get_current_vfat_timestamp(),
247 self.vfat_filesystem
248 .time_manager
249 .get_current_vfat_timestamp(),
250 entry_name,
251 size,
252 path,
253 cluster_id,
254 self.metadata.full_path().clone(),
255 attributes,
256 );
257 Ok(metadata)
258 }
259
260 fn get_entry(&mut self, target_filename: &str) -> error::Result<DirectoryEntry> {
262 self.contents_unlocked()?
263 .into_iter()
264 .find(|name| {
265 debug!(
266 "Checking name: {} == {}",
267 name.metadata.name(),
268 target_filename
269 );
270 name.metadata.name() == target_filename
271 })
272 .ok_or_else(|| error::VfatRsError::FileNotFound {
273 target: target_filename.to_string(),
274 })
275 }
276
277 pub fn delete(&mut self, target_name: String) -> error::Result<()> {
279 let lock = self.vfat_filesystem.fs_lock.clone();
280 let _guard = lock.write();
281 self.delete_unlocked(target_name)
282 }
283
284 fn delete_unlocked(&mut self, target_name: String) -> error::Result<()> {
285 info!("Starting delete routine for entry: '{}'. ", target_name);
286
287 const PSEUDO_CURRENT_FOLDER: &str = ".";
288 const PSEUDO_PARENT_FOLDER: &str = "..";
289 const PSEUDO_FOLDERS: &[&str; 2] = &[PSEUDO_PARENT_FOLDER, PSEUDO_CURRENT_FOLDER];
290
291 ensure!(
292 !PSEUDO_FOLDERS.contains(&target_name.as_str()),
293 error::CannotDeletePseudoDirSnafu {
294 target: target_name,
295 }
296 );
297
298 let mut target_entry = self.get_entry(&target_name)?;
299
300 if target_entry.is_dir() {
301 let directory = target_entry.into_directory_unchecked();
302 let contents = directory.contents_unlocked()?;
303 if contents.len() > PSEUDO_FOLDERS.len() {
304 return Err(error::VfatRsError::NonEmptyDirectory {
305 target: directory.metadata.name().to_string(),
306 contents: contents
307 .into_iter()
308 .map(|entry| entry.name().to_string())
309 .filter(|entry_name| !PSEUDO_FOLDERS.contains(&entry_name.as_str()))
310 .collect::<Vec<_>>()
311 .join(", "),
312 });
313 }
314 target_entry = directory.into();
315 }
316 info!("Found target entry: {:?}", target_entry);
317
318 self.delete_cluster_chain(&target_entry)?;
319 self.delete_entry(target_name)?;
320 Ok(())
321 }
322
323 fn find_entry_index(
325 &self,
326 target_name: &str,
327 ) -> error::Result<(usize, RegularDirectoryEntry, Vec<LongFileNameEntry>)> {
328 let entries = self.contents_direntry()?;
329 let mut lfn_name_buff: Vec<(u8, String)> = Vec::new();
330 let mut lfn_entries_buff: Vec<LongFileNameEntry> = Vec::new();
331
332 for (index, dir_entry) in entries.into_iter().enumerate() {
333 match dir_entry {
334 VfatDirectoryEntry::LongFileName(lfn) => {
335 lfn_name_buff.push((lfn.sequence_number.get_position(), lfn.collect_name()));
336 lfn_entries_buff.push(lfn);
337 }
338 VfatDirectoryEntry::Deleted(_) => {
339 lfn_name_buff.clear();
340 lfn_entries_buff.clear();
341 }
342 VfatDirectoryEntry::Regular(regular) => {
343 let name = if !lfn_name_buff.is_empty() {
344 Self::string_from_lfn(mem::take(&mut lfn_name_buff))
345 } else {
346 regular.full_name()
347 };
348 if name == target_name {
349 return Ok((index, regular, mem::take(&mut lfn_entries_buff)));
350 }
351 lfn_entries_buff.clear();
352 }
353 VfatDirectoryEntry::EndOfEntries(_) => break,
354 }
355 }
356 Err(error::VfatRsError::FileNotFound {
357 target: target_name.to_string(),
358 })
359 }
360
361 fn delete_entry(&mut self, target_name: String) -> error::Result<()> {
362 info!("Running delete entry");
363 let (index, regular, lfn_entries) = self.find_entry_index(&target_name)?;
364
365 for (i, lfn) in lfn_entries.into_iter().rev().enumerate() {
367 let mut unknown: UnknownDirectoryEntry = lfn.into();
368 unknown.set_id(Deleted);
369 self.update_entry_by_index(unknown, index - i - 1)?;
370 }
371 let mut unknown: UnknownDirectoryEntry = regular.into();
372 unknown.set_id(Deleted);
373 self.update_entry_by_index(unknown, index)?;
374 Ok(())
375 }
376 fn delete_cluster_chain(&mut self, entry: &DirectoryEntry) -> error::Result<()> {
377 info!(
378 "Deleting entry's associated clusters starting at {:?}",
379 entry.metadata.cluster
380 );
381 self.vfat_filesystem
382 .delete_fat_cluster_chain(entry.metadata.cluster)
383 }
384
385 fn contents_direntry(&self) -> error::Result<Vec<VfatDirectoryEntry>> {
386 info!("Directory contents, cluster: {:?}", self.metadata.cluster);
387
388 let mut buf = [0; BUF_SIZE];
389 let filter_invalid =
390 |entry: &VfatDirectoryEntry| !matches!(*entry, VfatDirectoryEntry::EndOfEntries(_));
391 let mut cluster_chain_reader = self.cluster_chain_reader();
392
393 let mut entries = Vec::new();
394 while cluster_chain_reader.read(&mut buf)? > 0 {
395 let unknown_entries: [UnknownDirectoryEntry; ENTRIES_AMOUNT] =
396 unknown_entry_convert_from_bytes_entries(buf);
397 entries.extend(
398 unknown_entries
399 .iter()
400 .map(VfatDirectoryEntry::from)
401 .filter(filter_invalid),
402 );
403 }
404 Ok(entries)
405 }
406
407 fn collect_short_names(&self) -> error::Result<Vec<[u8; 8]>> {
409 Ok(self
410 .contents_direntry()?
411 .into_iter()
412 .filter_map(|e| {
413 if let VfatDirectoryEntry::Regular(r) = e {
414 Some(r.file_name)
415 } else {
416 None
417 }
418 })
419 .collect())
420 }
421
422 pub fn raw_entry_count(&self) -> error::Result<usize> {
426 Ok(self.contents_direntry()?.len())
427 }
428
429 pub fn contents(&self) -> error::Result<Vec<DirectoryEntry>> {
431 let lock = self.vfat_filesystem.fs_lock.clone();
432 let _guard = lock.read();
433 self.contents_unlocked()
434 }
435
436 pub(crate) fn contents_unlocked(&self) -> error::Result<Vec<DirectoryEntry>> {
437 info!("Directory contents, cluster: {:?}", self.metadata.cluster);
438
439 let entries = self.contents_direntry()?;
440 let mut contents = Vec::new();
441
442 let mut lfn_buff: Vec<(u8, String)> = Vec::new();
443 for dir_entry in entries {
444 debug!("Found entry: {:?}", dir_entry);
445 match dir_entry {
446 VfatDirectoryEntry::LongFileName(lfn) => {
447 lfn_buff.push((lfn.sequence_number.get_position(), lfn.collect_name()))
448 }
449 VfatDirectoryEntry::Deleted(_) => {
450 lfn_buff.clear();
451 }
452 VfatDirectoryEntry::Regular(regular) => {
453 let name = if !lfn_buff.is_empty() {
454 Self::string_from_lfn(mem::take(&mut lfn_buff))
455 } else {
456 regular.full_name()
457 };
458
459 let path = PathBuf::from(format!(
460 "{}{name}{}",
461 self.metadata.full_path().display(),
462 if regular.is_dir() { "/" } else { "" }
463 ));
464
465 let metadata = Metadata::new(
466 regular.creation_time,
467 regular.last_modification_time,
468 name,
469 regular.file_size,
470 path,
471 regular.cluster(),
472 self.metadata.full_path().clone(),
473 regular.attributes,
474 );
475
476 debug!("Metadata: {:?}", metadata);
477
478 let new_fn = if regular.is_dir() {
479 DirectoryEntry::new_directory
480 } else {
481 DirectoryEntry::new_file
482 };
483
484 contents.push(new_fn(metadata, self.vfat_filesystem.clone()));
485 }
486 VfatDirectoryEntry::EndOfEntries(_) => {
488 panic!("This cannot happen! Found EndOfEntries")
489 }
490 }
491 }
492 Ok(contents)
493 }
494
495 pub(crate) fn update_entry(&mut self, metadata: Metadata) -> error::Result<()> {
496 let target_name = metadata.name().to_string();
497 info!("Running update entry on target name: {}", target_name);
498 let regular: RegularDirectoryEntry = metadata.into();
499 self.update_entry_inner(target_name, regular.into())
500 }
501
502 fn cluster_chain_reader(&self) -> ClusterChainReader {
503 self.vfat_filesystem
504 .cluster_chain_reader(self.metadata.cluster)
505 }
506
507 fn string_from_lfn(mut lfn_vec: Vec<(u8, String)>) -> String {
509 lfn_vec.sort();
512 lfn_vec
514 .into_iter()
515 .map(|(_, s)| s)
516 .fold(String::new(), |mut acc, s| {
517 acc.push_str(&s);
518 acc
519 })
520 }
521
522 pub fn rename(
524 &mut self,
525 target_name: String,
526 destination_path: crate::PathBuf,
527 ) -> error::Result<()> {
528 let lock = self.vfat_filesystem.fs_lock.clone();
529 let _guard = lock.write();
530 self.rename_unlocked(target_name, destination_path)
531 }
532
533 fn rename_unlocked(
534 &mut self,
535 target_name: String,
536 destination_path: crate::PathBuf,
537 ) -> error::Result<()> {
538 let dest_str = destination_path.display().to_string();
539 let dest_trimmed = dest_str.trim_end_matches('/');
540
541 let (dest_parent_str, new_name) = match dest_trimmed.rfind('/') {
543 Some(0) => ("/".to_string(), dest_trimmed[1..].to_string()),
544 Some(pos) => (
545 dest_trimmed[..pos].to_string(),
546 dest_trimmed[pos + 1..].to_string(),
547 ),
548 None => {
549 return Err(error::VfatRsError::FileNotFound { target: dest_str });
550 }
551 };
552 if new_name.is_empty() {
553 return Err(error::VfatRsError::FileNotFound { target: dest_str });
554 }
555 let dest_parent: crate::PathBuf = dest_parent_str.as_str().into();
556
557 let target_entry = self.get_entry(&target_name)?;
558 let mut metadata = target_entry.metadata;
559
560 let source_parent_path = self.metadata.full_path();
562 if dest_parent == *source_parent_path {
563 return self.inner_rename(target_name, new_name, &mut metadata);
565 }
566
567 if metadata.attributes.is_directory() {
570 let parent_str = source_parent_path.display().to_string();
571 let sep = if parent_str.ends_with('/') { "" } else { "/" };
572 let source_entry_path: crate::PathBuf =
573 alloc::format!("{}{}{}", parent_str, sep, target_name).into();
574 if destination_path.starts_with(&source_entry_path) {
575 return Err(error::VfatRsError::CircularMove {
576 source_path: source_entry_path.display().to_string(),
577 destination_path: dest_str,
578 });
579 }
580 }
581
582 let dest_dir_entry = self
584 .vfat_filesystem
585 .get_from_absolute_path_unlocked(dest_parent.clone())?;
586 let mut dest_dir = dest_dir_entry.into_directory_or_not_found()?;
587
588 if dest_dir.contains_unlocked(&new_name)? {
590 dest_dir.delete_unlocked(new_name.clone())?;
591 }
592
593 let attributes = metadata.attributes;
595 let existing_short_names = dest_dir.collect_short_names()?;
596 let entries: Vec<UnknownDirectoryEntry> = VfatDirectoryEntry::new_vfat_entry(
597 new_name.as_str(),
598 metadata.cluster,
599 attributes,
600 &existing_short_names,
601 )?;
602 let entries_len = entries.len();
603 let first_empty_spot_offset = if let Some(spot) = dest_dir.last_entry_spot {
604 spot
605 } else {
606 dest_dir.find_first_empty_spot_offset(entries_len)?
607 };
608 let mut ccw = dest_dir
609 .vfat_filesystem
610 .cluster_chain_writer(dest_dir.metadata.cluster);
611 ccw.seek(first_empty_spot_offset)?;
612 for unknown_entry in entries.into_iter() {
613 let entry: [u8; size_of::<UnknownDirectoryEntry>()] = unknown_entry.into();
614 ccw.write(&entry)?;
615 }
616 dest_dir.last_entry_spot = None;
617
618 self.delete_entry(target_name)?;
620
621 if metadata.attributes.is_directory() && !metadata.has_no_cluster_allocated() {
623 Self::update_dotdot_cluster(
624 &self.vfat_filesystem,
625 metadata.cluster,
626 dest_dir.metadata.cluster,
627 )?;
628 }
629
630 metadata.name = new_name;
631 Ok(())
632 }
633
634 fn update_dotdot_cluster(
636 vfat: &VfatFS,
637 dir_cluster: ClusterId,
638 new_parent_cluster: ClusterId,
639 ) -> error::Result<()> {
640 let dotdot_index = 1;
642 let index_offset = size_of::<UnknownDirectoryEntry>() * dotdot_index;
643
644 let mut buf = [0u8; size_of::<UnknownDirectoryEntry>()];
646 let mut reader = vfat.cluster_chain_reader(dir_cluster);
647 let mut skip_buf = [0u8; size_of::<UnknownDirectoryEntry>()];
649 reader.read(&mut skip_buf)?;
650 reader.read(&mut buf)?;
651
652 let unknown = UnknownDirectoryEntry::from(buf);
653 let mut regular: RegularDirectoryEntry = unknown.into();
654
655 let new_parent = if new_parent_cluster == ClusterId::new(2) {
657 ClusterId::new(0)
658 } else {
659 new_parent_cluster
660 };
661 let (high, low) = new_parent.into_high_low();
662 regular.high_16bits = high;
663 regular.low_16bits = low;
664
665 let updated: UnknownDirectoryEntry = regular.into();
667 let write_buf: [u8; size_of::<UnknownDirectoryEntry>()] = updated.into();
668 let mut writer = vfat.cluster_chain_writer(dir_cluster);
669 writer.seek(index_offset)?;
670 writer.write(&write_buf)?;
671 Ok(())
672 }
673
674 fn inner_rename(
675 &mut self,
676 target_name: String,
677 new_name: String,
678 metadata: &mut Metadata,
679 ) -> error::Result<()> {
680 let attributes = metadata.attributes;
683 let existing_short_names = self.collect_short_names()?;
684 let entries: Vec<UnknownDirectoryEntry> = VfatDirectoryEntry::new_vfat_entry(
685 new_name.as_str(),
686 metadata.cluster,
687 attributes,
688 &existing_short_names,
689 )?;
690 let entries_len = entries.len();
691 let first_empty_spot_offset = if let Some(spot) = self.last_entry_spot {
692 spot
693 } else {
694 self.find_first_empty_spot_offset(entries_len)?
695 };
696 let mut ccw = self
697 .vfat_filesystem
698 .cluster_chain_writer(self.metadata.cluster);
699 ccw.seek(first_empty_spot_offset)?;
700
701 for unknown_entry in entries.into_iter() {
702 let entry: [u8; size_of::<UnknownDirectoryEntry>()] = unknown_entry.into();
703 ccw.write(&entry)?;
704 }
705 metadata.name = new_name;
706
707 self.last_entry_spot = None;
709 self.delete_entry(target_name)?;
710 Ok(())
711 }
712
713 fn update_entry_inner(
715 &mut self,
716 target_name: String,
717 new_entry: UnknownDirectoryEntry,
718 ) -> error::Result<()> {
719 debug!("Running update entry routine...");
720 let (index, _, _) = self.find_entry_index(&target_name)?;
721 self.update_entry_by_index(new_entry, index)
722 }
723
724 pub(crate) fn update_entry_by_index(
727 &self,
728 entry: UnknownDirectoryEntry,
729 index: usize,
730 ) -> error::Result<()> {
731 let index_offset = size_of::<UnknownDirectoryEntry>() * index;
732 let buf: [u8; size_of::<UnknownDirectoryEntry>()] = entry.into();
733
734 let mut ccw = self
735 .vfat_filesystem
736 .cluster_chain_writer(self.metadata.cluster);
737 ccw.seek(index_offset)?;
738 ccw.write(&buf)?;
739 Ok(())
740 }
741
742 fn attributes_from_entry(entry: &EntryType) -> Attributes {
743 match entry {
744 EntryType::Directory => Attributes::new_directory(),
745 EntryType::File => Attributes(0),
746 }
747 }
748}
749
750impl VfatMetadataTrait for Directory {
751 fn metadata(&self) -> &Metadata {
752 &self.metadata
753 }
754}
755
756#[cfg(test)]
757mod test {
758 extern crate std;
759
760 use crate::api::raw_directory_entry::EntryId;
761
762 #[test]
763 fn valid_entry_id() {
764 let id: u8 = 0x10;
765 assert!(matches!(EntryId::from(id), EntryId::Valid(_)));
766 }
768}