use alloc::vec::Vec;
use bitflags::bitflags;
use thiserror::Error;
use super::{
bisync,
directory::DirectoryEntryFilter,
error::ExFatError,
file::FileDetails,
file_system::{FileSystem, FileSystemDetails},
io::{BLOCK_SIZE, BlockDevice},
utils::decode_utf16,
utils::{read_u16_le, read_u32_le, read_u64_le},
};
pub const RAW_ENTRY_LEN: usize = 32;
pub const DIR_ENTRIES_PER_BLOCK: usize = BLOCK_SIZE / RAW_ENTRY_LEN;
pub type RawDirEntry = [u8; RAW_ENTRY_LEN];
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Error, Debug, Clone, Copy)]
pub enum Error {
#[error("invalid uft16 string encountered ({reason})")]
InvalidUtf16String { reason: &'static str },
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub(crate) enum EntryType {
UnusedOrEndOfDirectory,
AllocationBitmap,
UpcaseTable,
VolumeLabel,
FileAndDirectory,
VolumeGuid,
TexFATPadding,
StreamExtension,
Filename,
Reserved(u8),
}
#[derive(Debug)]
pub(crate) struct AllocationBitmapDirEntry {
pub _bitmap_flags: BitmapFlags,
pub first_cluster: u32,
pub data_length: u64,
}
#[derive(Debug)]
pub(crate) struct UpcaseTableDirEntry {
pub _table_checksum: u32,
pub first_cluster: u32,
pub _data_length: u64,
}
#[derive(Debug)]
#[allow(unused)]
pub(crate) struct VolumeLabelDirEntry(pub heapless::String<22>);
#[derive(Debug)]
pub(crate) struct FileDirEntry {
pub secondary_count: u8,
pub set_checksum: u16,
pub file_attributes: FileAttributes,
pub create_timestamp: u32,
pub last_modified_timestamp: u32,
pub last_accessed_timestamp: u32,
pub create_10ms_increment: u8,
pub last_modified_10ms_increment: u8,
pub create_utc_offset: u8,
pub last_modified_utc_offset: u8,
pub last_accessed_utc_offset: u8,
}
impl FileDirEntry {
pub(crate) fn serialize(&self) -> RawDirEntry {
let mut raw = [0u8; RAW_ENTRY_LEN];
raw[0] = EntryType::FileAndDirectory.serialize();
raw[1] = self.secondary_count;
raw[2..4].copy_from_slice(&self.set_checksum.to_le_bytes());
raw[4..6].copy_from_slice(&self.file_attributes.bits().to_le_bytes());
raw[8..12].copy_from_slice(&self.create_timestamp.to_le_bytes());
raw[12..16].copy_from_slice(&self.last_modified_timestamp.to_le_bytes());
raw[16..20].copy_from_slice(&self.last_accessed_timestamp.to_le_bytes());
raw[20] = self.create_10ms_increment;
raw[21] = self.last_modified_10ms_increment;
raw[22] = self.create_utc_offset;
raw[23] = self.last_modified_utc_offset;
raw[24] = self.last_accessed_utc_offset;
raw
}
}
#[derive(Debug)]
pub(crate) struct StreamExtensionDirEntry {
pub general_secondary_flags: GeneralSecondaryFlags,
pub name_length: u8,
pub name_hash: u16,
pub valid_data_length: u64,
pub first_cluster: u32,
pub data_length: u64,
}
impl StreamExtensionDirEntry {
pub(crate) fn serialize(&self) -> RawDirEntry {
let mut raw = [0u8; RAW_ENTRY_LEN];
raw[0] = EntryType::StreamExtension.serialize();
raw[1] = self.general_secondary_flags.bits();
raw[3] = self.name_length;
raw[4..6].copy_from_slice(&self.name_hash.to_le_bytes());
raw[8..16].copy_from_slice(&self.valid_data_length.to_le_bytes());
raw[20..24].copy_from_slice(&self.first_cluster.to_le_bytes());
raw[24..32].copy_from_slice(&self.data_length.to_le_bytes());
raw
}
}
#[derive(Debug)]
pub(crate) struct FileNameDirEntry {
pub general_secondary_flags: GeneralSecondaryFlags,
pub file_name: [u16; 15], }
impl FileNameDirEntry {
pub(crate) fn serialize(&self) -> RawDirEntry {
let mut raw = [0u8; RAW_ENTRY_LEN];
raw[0] = EntryType::Filename.serialize();
raw[1] = self.general_secondary_flags.bits();
let (chunks, _remainder) = raw[2..32].as_chunks_mut::<2>();
for (to, from) in chunks.iter_mut().zip(&self.file_name) {
to.copy_from_slice(&from.to_le_bytes());
}
raw
}
}
impl From<u8> for EntryType {
fn from(value: u8) -> Self {
match value {
0x00 => Self::UnusedOrEndOfDirectory,
0x81 => Self::AllocationBitmap,
0x82 => Self::UpcaseTable,
0x83 => Self::VolumeLabel,
0x85 => Self::FileAndDirectory,
0xA0 => Self::VolumeGuid,
0xA1 => Self::TexFATPadding,
0xC0 => Self::StreamExtension,
0xC1 => Self::Filename,
x => Self::Reserved(x),
}
}
}
impl EntryType {
pub(crate) fn serialize(&self) -> u8 {
match self {
Self::UnusedOrEndOfDirectory => 0x00,
Self::AllocationBitmap => 0x81,
Self::UpcaseTable => 0x82,
Self::VolumeLabel => 0x83,
Self::FileAndDirectory => 0x85,
Self::VolumeGuid => 0xA0,
Self::TexFATPadding => 0xA1,
Self::StreamExtension => 0xC0,
Self::Filename => 0xC1,
Self::Reserved(x) => *x,
}
}
}
impl From<&[u8; RAW_ENTRY_LEN]> for AllocationBitmapDirEntry {
fn from(value: &[u8; RAW_ENTRY_LEN]) -> Self {
let _bitmap_flags = BitmapFlags::from_bits_truncate(value[1]);
let first_cluster = read_u32_le::<20, _>(value);
let data_length = read_u64_le::<24, _>(value);
Self {
_bitmap_flags,
first_cluster,
data_length,
}
}
}
impl From<&[u8; RAW_ENTRY_LEN]> for UpcaseTableDirEntry {
fn from(value: &[u8; RAW_ENTRY_LEN]) -> Self {
let _table_checksum = read_u32_le::<4, _>(value);
let first_cluster = read_u32_le::<20, _>(value);
let _data_length = read_u64_le::<24, _>(value);
Self {
_table_checksum,
first_cluster,
_data_length,
}
}
}
impl TryFrom<&[u8; RAW_ENTRY_LEN]> for VolumeLabelDirEntry {
type Error = Error;
fn try_from(value: &[u8; RAW_ENTRY_LEN]) -> Result<Self, Self::Error> {
let character_count = value[1] as usize;
if character_count > 11 {
return Err(Error::InvalidUtf16String {
reason: "character count exceeds 11",
});
}
let character_num_bytes = character_count * 2;
let volume_label = decode_utf16_le(&value[2..2 + character_num_bytes])?;
Ok(Self(volume_label))
}
}
impl From<&[u8; RAW_ENTRY_LEN]> for FileDirEntry {
fn from(value: &[u8; RAW_ENTRY_LEN]) -> Self {
let secondary_count = value[1];
let set_checksum = read_u16_le::<2, _>(value);
let file_attributes = FileAttributes::from_bits_truncate(read_u16_le::<4, _>(value));
let create_timestamp = read_u32_le::<8, _>(value);
let last_modified_timestamp = read_u32_le::<12, _>(value);
let last_accessed_timestamp = read_u32_le::<16, _>(value);
let create_10ms_increment = value[20];
let last_modified_10ms_increment = value[21];
let create_utc_offset = value[22];
let last_modified_utc_offset = value[23];
let last_accessed_utc_offset = value[24];
Self {
secondary_count,
set_checksum,
file_attributes,
create_timestamp,
last_modified_timestamp,
last_accessed_timestamp,
create_10ms_increment,
last_modified_10ms_increment,
create_utc_offset,
last_modified_utc_offset,
last_accessed_utc_offset,
}
}
}
impl From<&[u8; RAW_ENTRY_LEN]> for StreamExtensionDirEntry {
fn from(value: &[u8; RAW_ENTRY_LEN]) -> Self {
let general_secondary_flags = GeneralSecondaryFlags::from_bits_truncate(value[1]);
let name_length = value[3];
let name_hash = read_u16_le::<4, _>(value);
let valid_data_length = read_u64_le::<8, _>(value);
let first_cluster = read_u32_le::<20, _>(value);
let data_length = read_u64_le::<24, _>(value);
Self {
general_secondary_flags,
name_length,
name_hash,
valid_data_length,
first_cluster,
data_length,
}
}
}
impl From<&[u8; RAW_ENTRY_LEN]> for FileNameDirEntry {
fn from(value: &[u8; RAW_ENTRY_LEN]) -> Self {
let general_secondary_flags = GeneralSecondaryFlags::from_bits_truncate(value[1]);
let mut file_name: [u16; 15] = [0; 15];
let (chunks, _remainder) = value[2..RAW_ENTRY_LEN].as_chunks::<2>();
let u16_iter = chunks.iter().map(|x| u16::from_le_bytes(*x));
for (from, to) in u16_iter.zip(file_name.iter_mut()) {
*to = from;
}
Self {
general_secondary_flags,
file_name,
}
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for FileAttributes {
fn format(&self, f: defmt::Formatter) {
defmt::write!(f, "FileAttributes({=u16:#010b})", self.bits());
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for GeneralSecondaryFlags {
fn format(&self, f: defmt::Formatter) {
defmt::write!(f, "GeneralSecondaryFlags({=u8:#010b})", self.bits());
}
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct BitmapFlags: u8 {
const FirstOrSecondBitmap = 0b0000_0001;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct GeneralSecondaryFlags: u8 {
const AllocationPossible = 0b0000_0001;
const NoFatChain = 0b0000_0010;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct FileAttributes: u16 {
const ReadOnly = 0b0000_0001;
const Hidden = 0b0000_0010;
const System = 0b0000_0100;
const Directory = 0b0001_0000;
const Archive = 0b0010_0000;
}
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Debug, Clone, Copy)]
pub(crate) struct Location {
pub sector_id: u32,
pub dir_entry_offset: usize,
}
impl Location {
pub(crate) fn new(sector_id: u32, dir_entry_offset: usize) -> Self {
Self {
sector_id,
dir_entry_offset,
}
}
}
fn decode_utf16_le<const N: usize>(bytes: &[u8]) -> Result<heapless::String<N>, Error> {
let (chunks, _remainder) = bytes.as_chunks::<2>();
let u16_iter = chunks.iter().map(|x| u16::from_le_bytes(*x));
let decoded = core::char::decode_utf16(u16_iter)
.map(|r| {
r.map_err(|_| Error::InvalidUtf16String {
reason: "invalid u16 char detected",
})
})
.collect::<Result<heapless::String<N>, _>>()?;
Ok(decoded)
}
pub(crate) struct DirectoryEntryChain {
cluster_id: u32,
fs: FileSystemDetails,
cluster_offset: usize,
dir_entry_offset: usize,
buf: [u8; BLOCK_SIZE],
fetch_required: bool,
}
impl DirectoryEntryChain {
pub(crate) fn new_from_location(location: &Location, fs: &FileSystemDetails) -> Self {
let sector_id_from_start = location.sector_id - fs.cluster_heap_offset;
let cluster_id = 2 + sector_id_from_start / fs.sectors_per_cluster as u32;
let cluster_offset = (sector_id_from_start % fs.sectors_per_cluster as u32) as usize;
let dir_entry_offset = location.dir_entry_offset;
Self {
buf: [0; BLOCK_SIZE],
fs: fs.clone(),
cluster_id,
cluster_offset,
dir_entry_offset,
fetch_required: true,
}
}
pub(crate) fn new(cluster_id: u32, fs: &FileSystemDetails) -> Self {
Self {
buf: [0; BLOCK_SIZE],
fs: fs.clone(),
cluster_id,
cluster_offset: 0,
dir_entry_offset: 0,
fetch_required: true,
}
}
#[bisync]
pub(crate) async fn next_file_dir_entry<D: BlockDevice, const N: usize>(
&mut self,
fs: &mut FileSystem<D, N>,
) -> Result<Option<(FileDirEntry, Location)>, ExFatError<D>> {
while let Some((entry, location)) = self.next(fs).await? {
let entry_type_val = entry[0];
match EntryType::from(entry_type_val) {
EntryType::UnusedOrEndOfDirectory => {
if is_end_of_directory(entry) {
return Ok(None);
}
}
EntryType::FileAndDirectory => {
let file_entry: FileDirEntry = entry.into();
return Ok(Some((file_entry, location)));
}
_entry_type => {} }
}
Ok(None)
}
#[bisync]
pub(crate) async fn next_file_entry<D: BlockDevice, const N: usize>(
&mut self,
fs: &mut FileSystem<D, N>,
filter: &impl DirectoryEntryFilter,
) -> Result<Option<FileDetails>, ExFatError<D>> {
'outer: loop {
if let Some((file_dir_entry, location)) = self.next_file_dir_entry(fs).await? {
if let Some((stream_entry, _location)) = self.next(fs).await? {
let stream_entry: StreamExtensionDirEntry = stream_entry.into();
if !filter.hash(stream_entry.name_hash, file_dir_entry.file_attributes) {
continue 'outer;
}
let name_length = stream_entry.name_length as usize;
let mut file_name: Vec<u16> = Vec::with_capacity(name_length);
'inner: loop {
if let Some((file_name_entry, _location)) = self.next(fs).await? {
let file_name_entry: FileNameDirEntry = file_name_entry.into();
let len = (name_length - file_name.len())
.min(file_name_entry.file_name.len());
file_name.extend_from_slice(&file_name_entry.file_name[..len]);
if file_name.len() == name_length {
break 'inner;
}
} else {
return Ok(None);
}
}
if !filter.file_name(&file_name, &fs.upcase_table) {
continue 'outer;
}
let name = decode_utf16(file_name)?;
let file_details = FileDetails {
attributes: file_dir_entry.file_attributes,
data_length: stream_entry.data_length,
valid_data_length: stream_entry.valid_data_length,
first_cluster: stream_entry.first_cluster,
name,
location,
flags: stream_entry.general_secondary_flags,
secondary_count: file_dir_entry.secondary_count,
};
return Ok(Some(file_details));
} else {
return Ok(None);
}
} else {
return Ok(None);
}
}
}
fn get_current_sector_id<D: BlockDevice>(&self) -> Result<u32, ExFatError<D>> {
let mut sector_id = self.fs.get_heap_sector_id::<D>(self.cluster_id)?;
sector_id += self.cluster_offset as u32;
Ok(sector_id)
}
#[bisync]
pub(crate) async fn next<D: BlockDevice, const N: usize>(
&mut self,
fs: &mut FileSystem<D, N>,
) -> Result<Option<(&[u8; RAW_ENTRY_LEN], Location)>, ExFatError<D>> {
if self.dir_entry_offset >= DIR_ENTRIES_PER_BLOCK {
self.cluster_offset += 1;
self.dir_entry_offset = 0;
self.fetch_required = true;
}
if self.cluster_offset > self.fs.sectors_per_cluster as usize {
let cluster_id = fs
.fat
.next_cluster_in_fat_chain(self.cluster_id, &mut fs.dev)
.await?;
match cluster_id {
Some(cluster_id) => {
self.cluster_id = cluster_id;
self.cluster_offset = 0;
self.dir_entry_offset = 0;
self.fetch_required = true;
}
None => return Ok(None),
}
}
if self.fetch_required {
let sector_id = self.get_current_sector_id()?;
let slot = fs.data_blocks.read(sector_id, &mut fs.dev).await?;
self.buf.copy_from_slice(&slot.block);
self.fetch_required = false;
}
let (entries, _remainder) = self.buf.as_chunks::<RAW_ENTRY_LEN>();
let entry = &entries[self.dir_entry_offset];
let location = Location::new(self.get_current_sector_id()?, self.dir_entry_offset);
self.dir_entry_offset += 1;
Ok(Some((entry, location)))
}
}
pub(crate) fn is_end_of_directory(directory_entry: &[u8; 32]) -> bool {
directory_entry.iter().all(|&x| x == 0)
}
fn calc_checksum(dir_entry_set: &[RawDirEntry]) -> u16 {
if dir_entry_set.is_empty() {
return 0;
}
let mut checksum: u16 = 0;
for (entry_index, entry) in dir_entry_set.iter().enumerate() {
for (byte_index, &b) in entry.iter().enumerate() {
if entry_index == 0 && (byte_index == 2 || byte_index == 3) {
continue;
}
checksum = checksum.rotate_right(1).wrapping_add(b as u16);
}
}
checksum
}
pub(crate) fn update_checksum(dir_entries: &mut [RawDirEntry]) {
let set_checksum = calc_checksum(dir_entries);
dir_entries[0][2..4].copy_from_slice(&set_checksum.to_le_bytes());
}