From 583b9ff43e45e7b8e6d1d5f67d284645064e174e Mon Sep 17 00:00:00 2001 From: DrDeano Date: Tue, 1 Dec 2020 09:54:45 +0000 Subject: [PATCH] Added cluster and entry iterators This adds iterators to loop over the FAT cluster chain and loop over entries in the directory structure of FAT32. The tests use hand crafted buffers for a FAT32 filesystem. These are too small for real FAT32 but will still demonstrate the same functionality as if there were real FAT32 buffers. Added tests for EntryIterator init Fixed memory leaks --- src/kernel/filesystem/fat32.zig | 1826 ++++++++++++++++++++++++++++++- 1 file changed, 1824 insertions(+), 2 deletions(-) diff --git a/src/kernel/filesystem/fat32.zig b/src/kernel/filesystem/fat32.zig index 2371dd4..47cfae8 100644 --- a/src/kernel/filesystem/fat32.zig +++ b/src/kernel/filesystem/fat32.zig @@ -163,6 +163,13 @@ const LongName = struct { /// The last 2 wide characters in the block. third: [2]u16, + /// This is the error set for std.unicode.Utf16LeIterator. + const Error = error{ + UnexpectedSecondSurrogateHalf, + ExpectedSecondSurrogateHalf, + DanglingSurrogateHalf, + }; + /// /// Given a long name entry part, get the name associated with this where the UFT16 encoded /// characters are converted to UTF8. This will exclude the NULL terminator. If an error @@ -178,7 +185,7 @@ const LongName = struct { /// Error: std.unicode.Utf16LeIterator /// An error when parsing the wide UFT16 characters in the long name. /// - pub fn getName(self: *const LongName, buff: []u8) !u32 { + pub fn getName(self: *const LongName, buff: []u8) Error!u32 { // No error can happen when encoding to UFT8 as will be getting valid code points from the // UTF16 iterator var index: u8 = 0; @@ -446,7 +453,7 @@ const FATConfig = struct { cluster_end_marker: u32, /// - /// Convert a cluster to the corresponding sector from the filesystems configuration. + /// Convert a cluster to the corresponding sector from the filesystem's configuration. /// /// Arguments: /// IN self: *const FATConfig - The FAT32 configuration. @@ -652,6 +659,446 @@ pub fn Fat32FS(comptime StreamType: type) type { else => StreamType.GetPosError, }; + /// An iterator for looping over the cluster chain in the FAT. + const ClusterChainIterator = struct { + /// The allocator used for allocating the initial FAT array, then to free in deinit. + allocator: *Allocator, + + /// The current cluster value. + cluster: u32, + + /// The configuration for this FAT instance. Used for converting clusters to sectors + /// and seeking to the correct location to read the next cluster value. + fat_config: FATConfig, + + /// The underlying stream to read the FAT. + stream: StreamType, + + /// The cached FAT. + fat: []u32, + + /// The offset into the FAT. If the next cluster is outside the cached FAT, then will + /// need to read a new part of the FAT. + table_offset: u32, + + /// The offset used when reading part of a cluster. This will range from 0 to + /// bytes_per_sector * sectors_per_cluster. + cluster_offset: u32, + + /// The iterators self. + const ClusterChainIteratorSelf = @This(); + + /// + /// Check if we need to advance the cluster value and in turn check if the new cluster + /// is within the cached FAT. + /// + /// Arguments: + /// IN self: *ClusterChainIteratorSelf - Iterator self. + /// + /// Error: Fat32Self.Error || ReadError || SeekError + /// Fat32Self.Error - If reading the stream didn't fill the cache FAT array. + /// ReadError - If there is an error reading from the stream. + /// SeekError - If there is an error seeking the stream. + /// + fn checkRead(self: *ClusterChainIteratorSelf) (Fat32Self.Error || ReadError || SeekError)!void { + if (self.cluster_offset >= self.fat_config.bytes_per_sector * self.fat_config.sectors_per_cluster) { + self.cluster = self.fat[self.cluster - self.table_offset]; + self.cluster_offset = 0; + // If we are at the end, break + if ((self.cluster & 0x0FFFFFFF) >= self.fat_config.cluster_end_marker) { + return; + } + // This will also allow for forwards and backwards iteration of the FAT + const table_offset = self.cluster / self.fat.len; + if (table_offset != self.table_offset) { + self.table_offset = table_offset; + try self.stream.seekableStream().seekTo((self.fat_config.reserved_sectors + self.table_offset) * self.fat_config.bytes_per_sector); + const read_count = try self.stream.reader().readAll(std.mem.sliceAsBytes(self.fat)); + if (read_count != self.fat.len * @sizeOf(u32)) { + return Fat32Self.Error.BadRead; + } + } + } + } + + /// + /// Iterate the cluster chain of FAT32. This will follow the FAT chain reading the data + /// into the buffer provided where the cluster is pointing to. This will read the + /// entire clusters data into the buffer. If the buffer is full or the end of the + /// cluster chain is reached, then will return null else will return the end index into + /// the buffer to next read into. Currently, this will be the bytes per cluster and + /// only buffers aligned to bytes per cluster will work. + /// + /// Arguments: + /// IN self: *ClusterChainIteratorSelf - Self iterator. + /// IN buff: []u8 - The buffer to read the data into. + /// + /// Return: ?usize + /// The end index into the buffer where the next read should start. If returned + /// null, then the buffer is full or the end of the cluster chain is reached. + /// + /// Error: Fat32Self.Error || ReadError || SeekError + /// Fat32Self.Error - (BadRead) If the buffer isn't aligned to the bytes per cluster boundary. + /// ReadError - If there is an error reading from the stream. + /// SeekError - If there is an error seeking the stream. + /// + pub fn read(self: *ClusterChainIteratorSelf, buff: []u8) (Fat32Self.Error || ReadError || SeekError)!?usize { + // FAT32 is really FAT28, so the top 4 bits are not used, so mask them out + if (buff.len != 0 and self.cluster != 0 and (self.cluster & 0x0FFFFFFF) < self.fat_config.cluster_end_marker and buff.len > 0) { + // Seek to the sector where the cluster is + const sector = self.fat_config.clusterToSector(self.cluster); + try self.stream.seekableStream().seekTo(sector * self.fat_config.bytes_per_sector + self.cluster_offset); + + const read_len = std.math.min(buff.len, self.fat_config.bytes_per_sector * self.fat_config.sectors_per_cluster); + self.cluster_offset += read_len; + + // Read the cluster + // TODO: Maybe cache bytes per cluster block, then can read into a buffer that isn't aligned to 512 bytes. + // So would read from the cache rather than the stream itself. + const read_count = try self.stream.reader().readAll(buff[0..read_len]); + if (read_count != read_len) { + return Fat32Self.Error.BadRead; + } + + // Increment the cluster + // Check if we need to read another part of the FAT + try self.checkRead(); + return read_len; + } + + return null; + } + + /// + /// Deinitialise the cluster chain iterator. + /// + /// Arguments: + /// IN self: *ClusterChainIteratorSelf - Iterator self. + /// + /// + pub fn deinit(self: *ClusterChainIteratorSelf) void { + self.allocator.free(self.fat); + } + + /// + /// Initialise a cluster chain iterator. + /// + /// Arguments: + /// IN allocator: *Allocator - The allocator for allocating a FAT cache. + /// IN fat_config: FATConfig - The FAT configuration. + /// IN cluster: u32 - The first cluster to start reading from. + /// IN stream: StreamType - The underlying stream. + /// + /// Return: ClusterChainIteratorSelf + /// A cluster chain iterator. + /// + /// Error: Allocator.Error || Fat32Self.Error || ReadError || SeekError + /// Allocator.Error - If there is an error allocating the initial FAT cache. + /// Fat32Self.Error - If reading the stream didn't fill the cache FAT array. + /// ReadError - If there is an error reading from the stream. + /// SeekError - If there is an error seeking the stream. + /// + pub fn init(allocator: *Allocator, fat_config: FATConfig, cluster: u32, stream: StreamType) (Allocator.Error || Fat32Self.Error || ReadError || SeekError)!ClusterChainIteratorSelf { + // Create a bytes per sector sized cache of the FAT. + var fat = try allocator.alloc(u32, fat_config.bytes_per_sector / @sizeOf(u32)); + errdefer allocator.free(fat); + + // FAT32 has u32 entries so need to divide + const table_offset = cluster / (fat_config.bytes_per_sector / @sizeOf(u32)); + + // Seek to the FAT + // The FAT is just after the reserved sectors + the index + try stream.seekableStream().seekTo((fat_config.reserved_sectors + table_offset) * fat_config.bytes_per_sector); + const read_count = try stream.reader().readAll(std.mem.sliceAsBytes(fat)); + if (read_count != fat.len * @sizeOf(u32)) { + return Fat32Self.Error.BadRead; + } + + return ClusterChainIteratorSelf{ + .allocator = allocator, + .cluster = cluster, + .fat_config = fat_config, + .stream = stream, + .fat = fat, + .table_offset = table_offset, + .cluster_offset = 0, + }; + } + }; + + /// An iterator for looping over the directory structure of FAT32. + const EntryIterator = struct { + /// An allocator for memory stuff + allocator: *Allocator, + + /// A cache of the current cluster. This wil be read from the cluster chain iterator. + cluster_block: []u8, + + /// The current index into the cluster block. + index: u32, + + /// The cluster chain iterator to read the next custer when needed. + cluster_chain: ClusterChainIterator, + + /// The entry iterator self. + const EntryIteratorSelf = @This(); + + /// The entry returned from 'next()'. + pub const Entry = struct { + /// The allocator used to allocate fields of this entry. Also used to free these + /// entries in the 'deinit()' function. + allocator: *Allocator, + + /// The long name for the entry. This maybe null as not all entry have a long name + /// part, just a short name. + long_name: ?[]const u8, + + /// The short name struct. + short_name: ShortName, + + /// + /// Free the entry. + /// + /// Arguments: + /// IN self: *Entry - The entry self to free. + /// + /// + pub fn deinit(self: *const Entry) void { + if (self.long_name) |name| { + self.allocator.free(name); + } + } + }; + + /// The error set for the entry iterator. + const EntryItError = error{ + /// If the long entry is invalid, so ignore it and use the short name only. + Orphan, + + /// If reading the cluster chain and reach the end unexpectedly. + EndClusterChain, + }; + + /// + /// Check if the next entry will be outside the cluster block cache. If so, read the + /// next cluster and update the index to 0. + /// + /// Arguments: + /// IN self: *EntryIteratorSelf - Iterator self. + /// + /// Error: Fat32Self.Error || ReadError || SeekError || EntryItError + /// Fat32Self.Error - Error reading the cluster chain if the buffer isn't aligned. + /// ReadError - Error reading from the stream in the cluster iterator. + /// SeekError - Error seeking the stream in the cluster iterator. + /// error.EndClusterChain - Reading the next cluster and reach the end unexpectedly. + /// + fn checkRead(self: *EntryIteratorSelf) (Fat32Self.Error || ReadError || SeekError || EntryItError)!void { + if (self.index >= self.cluster_block.len) { + // Read the next block + var index: u32 = 0; + while (try self.cluster_chain.read(self.cluster_block[index..])) |next_index| { + index += next_index; + } + // Didn't read so at end of cluster chain + // TODO: This relies on that cluster chain iterator will return full cluster bytes + // Currently this is the case, but when changed will need to update this to + // include partially full cluster block cache, with an index. + if (index < self.cluster_block.len) { + return EntryItError.EndClusterChain; + } + // Reset the index + self.index = 0; + } + } + + /// + /// A helper function for reading a entry. This is used for catching orphaned long + /// entries and ignoring them so this shouldn't be called directly. + /// + /// Arguments: + /// IN self: *EntryIteratorSelf - Iterator self. + /// + /// Return: ?Entry + /// The FAT entry. Will return null if there are no more entries for the directory. + /// + /// Error: Allocator.Error || Fat32Self.Error || ReadError || SeekError || EntryItError + /// Allocator.Error - Error allocating memory for fields in the return entry. + /// Fat32Self.Error - Error reading the cluster chain if the buffer isn't aligned. + /// ReadError - Error reading from the underlying stream. + /// SeekError - Error seeking the underlying stream. + /// error.Orphan - If there is a long entry without a short name entry or the + /// check sum in the long entry is incorrect with the associated + /// short entry or the long entry is missing entry parts. + /// error.EndClusterChain - Reading the next cluster and reach the end unexpectedly. + /// + fn nextImp(self: *EntryIteratorSelf) (Allocator.Error || Fat32Self.Error || ReadError || SeekError || LongName.Error || EntryItError)!?Entry { + // Do we need to read the next block + try self.checkRead(); + + // Ignore deleted files + while (self.cluster_block[self.index] == 0xE5) : ({ + self.index += 32; + try self.checkRead(); + }) {} + + // Are we at the end of all entries + if (self.cluster_block[self.index] != 0x00) { + // The long name if there is one + var long_name: ?[]u8 = null; + errdefer if (long_name) |name| self.allocator.free(name); + var long_entries: ?[]LongName = null; + defer if (long_entries) |entries| self.allocator.free(entries); + + // If attribute is 0x0F, then is a long file name and + if (self.cluster_block[self.index] & 0x40 == 0x40 and self.cluster_block[self.index + 11] == 0x0F) { + // How many entries do we have, the first byte of the order. This starts at 1 not 0 + var long_entry_count = self.cluster_block[self.index] & ~@as(u32, 0x40); + // Allocate a buffer for the long name. 13 for the 13 characters in the long entry + // * 3 as long names are u16 chars so when converted to u8, there could be 3 u8 to encode a u16 (UTF16 -> UTF8) + long_name = try self.allocator.alloc(u8, 13 * 3 * long_entry_count); + // For convenience + var long_name_temp = long_name.?; + var long_name_index: u32 = 0; + long_entries = try self.allocator.alloc(LongName, long_entry_count); + + // Iterate through the long name entries + while (long_entry_count > 0) : ({ + self.index += 32; + long_entry_count -= 1; + try self.checkRead(); + }) { + // Parse the long entry + long_entries.?[long_entry_count - 1] = initStruct(LongName, self.cluster_block[self.index..]); + // Check the order of the long entry as it could be broken + if ((long_entries.?[long_entry_count - 1].order & ~@as(u32, 0x40)) != long_entry_count) { + // A broken long entry + self.index += 32; + return EntryItError.Orphan; + } + } + + // Parse the name + for (long_entries.?) |entry| { + long_name_index += try entry.getName(long_name_temp[long_name_index..]); + } + + long_name = long_name_temp[0..long_name_index]; + } + + // Make sure we have a short name part, if not then it is a orphan + // Easy check for the attributes as 0x0F is invalid and a long name part + // So if we have one, then this is a long entry not short entry as expected + // Also make are we are not at the end + if (self.cluster_block[self.index] != 0x00 and self.cluster_block[self.index + 11] == 0x0F) { + // This will be an invalid short name + self.index += 32; + return EntryItError.Orphan; + } + + // Parse the short entry + // We need all parts of the short name, not just the name + const short_entry = initStruct(ShortName, self.cluster_block[self.index..]); + // Check the check sum + if (long_entries) |entries| { + for (entries) |entry| { + if (entry.check_sum != short_entry.calcCheckSum()) { + return EntryItError.Orphan; + } + } + } + + // Increment for the next entry + self.index += 32; + + return Entry{ + .allocator = self.allocator, + .long_name = long_name, + .short_name = short_entry, + }; + } + + return null; + } + + /// + /// Get the next entry in the iterator. Will return null when at the end. This will + /// ignore orphaned long entries. + /// + /// Arguments: + /// IN self: *EntryIteratorSelf - Iterator self. + /// + /// Return: ?Entry + /// The FAT entry. Will return null if there are no more entries for the directory. + /// The entry must be free using the deinit() function. + /// + /// Error: Allocator.Error || Fat32Self.Error || ReadError || SeekError + /// Allocator.Error - Error allocating memory for fields in the return entry. + /// Fat32Self.Error - Error reading the cluster chain if the buffer isn't aligned. + /// ReadError - Error reading from the underlying stream. + /// SeekError - Error seeking the underlying stream. + /// + pub fn next(self: *EntryIteratorSelf) (Allocator.Error || Fat32Self.Error || ReadError || SeekError || LongName.Error)!?Entry { + // If there is a orphan file, then just get the next one + // If we hit the end of the cluster chain, return null + while (true) { + if (self.nextImp()) |n| { + return n; + } else |e| switch (e) { + error.Orphan => continue, + error.EndClusterChain => return null, + else => return @errSetCast(Allocator.Error || Fat32Self.Error || ReadError || SeekError || LongName.Error, e), + } + } + } + + /// + /// Destroy the entry iterator freeing any memory. + /// + /// Arguments: + /// IN self: *EntryIteratorSelf - Iterator self. + /// + pub fn deinit(self: *const EntryIteratorSelf) void { + self.allocator.free(self.cluster_block); + self.cluster_chain.deinit(); + } + + /// + /// Initialise a directory entry iterator to looping over the FAT entries. This uses + /// the cluster chain iterator. + /// + /// Arguments: + /// IN allocator: *Allocator - The allocator for allocating a cluster block cache. + /// IN fat_config: FATConfig - The FAT configuration. + /// IN cluster: u32 - The first cluster to start reading from. + /// IN stream: StreamType - The underlying stream. + /// + /// Return: EntryIteratorSelf + /// The entry iterator. + /// + /// Error: Allocator.Error || Fat32Self.Error || ReadError || SeekError + /// Allocator.Error - Error allocating memory for fields in the return entry. + /// Fat32Self.Error - Error reading the cluster chain if the buffer isn't aligned. + /// ReadError - Error reading from the underlying stream. + /// SeekError - Error seeking the underlying stream. + /// + pub fn init(allocator: *Allocator, fat_config: FATConfig, cluster: u32, stream: StreamType) (Allocator.Error || Fat32Self.Error || ReadError || SeekError)!EntryIteratorSelf { + var cluster_block = try allocator.alloc(u8, fat_config.bytes_per_sector * fat_config.sectors_per_cluster); + errdefer allocator.free(cluster_block); + var it = try ClusterChainIterator.init(allocator, fat_config, cluster, stream); + errdefer it.deinit(); + var index: u32 = 0; + while (try it.read(cluster_block[index..])) |next_index| { + index += next_index; + } + return EntryIteratorSelf{ + .allocator = allocator, + .cluster_block = cluster_block, + .index = 0, + .cluster_chain = it, + }; + } + }; + /// See vfs.FileSystem.getRootNode fn getRootNode(fs: *const vfs.FileSystem) *const vfs.DirNode { const self = @fieldParentPtr(Fat32Self, "instance", fs.instance); @@ -1751,3 +2198,1378 @@ test "Fat32FS.init FATConfig mix FSInfo" { test_stream_buff[512 + 4 + 480 + 7] = 0x00; } } + +test "ClusterChainIterator.checkRead - Within cluster, within FAT" { + // The undefined values are not used in checkRead + const fat_config = FATConfig{ + .bytes_per_sector = 16, + .sectors_per_cluster = 1, + .reserved_sectors = 0, + .hidden_sectors = undefined, + .total_sectors = undefined, + .sectors_per_fat = undefined, + .root_directory_cluster = undefined, + .fsinfo_sector = undefined, + .backup_boot_sector = undefined, + .has_fs_info = undefined, + .number_free_clusters = undefined, + .next_free_cluster = undefined, + .cluster_end_marker = undefined, + }; + var buff_stream = [_]u8{}; + var stream = &std.io.fixedBufferStream(buff_stream[0..]); + // First 2 are for other purposed and not needed, the third is the first real FAT entry + var fat = [_]u32{ 0x0FFFFFFF, 0xFFFFFFF8, 0xFFFFFFFF, 0x00000000 }; + var it = Fat32FS(@TypeOf(stream)).ClusterChainIterator{ + .allocator = undefined, + .cluster = 2, + .fat_config = fat_config, + .stream = stream, + .fat = fat[0..], + .table_offset = 0, + .cluster_offset = 0, + }; + + try it.checkRead(); + + // Nothing changed + expectEqual(it.cluster, 2); + expectEqual(it.cluster_offset, 0); + expectEqual(it.table_offset, 0); + expectEqualSlices(u32, it.fat, fat[0..]); +} + +test "ClusterChainIterator.checkRead - Multiple clusters, within FAT" { + // The undefined values are not used in checkRead + // The value won't be valid FAT32 values, but this doesn't matter in this context so can make the stream buffer smaller + const fat_config = FATConfig{ + .bytes_per_sector = 16, + .sectors_per_cluster = 1, + .reserved_sectors = 0, + .hidden_sectors = undefined, + .total_sectors = undefined, + .sectors_per_fat = undefined, + .root_directory_cluster = undefined, + .fsinfo_sector = undefined, + .backup_boot_sector = undefined, + .has_fs_info = undefined, + .number_free_clusters = undefined, + .next_free_cluster = undefined, + .cluster_end_marker = undefined, + }; + var buff_stream = [_]u8{}; + var stream = &std.io.fixedBufferStream(buff_stream[0..]); + // First 2 are for other purposed and not needed, the third is the first real FAT entry + var fat = [_]u32{ 0x0FFFFFFF, 0xFFFFFFF8, 0x00000003, 0xFFFFFFFF }; + var it = Fat32FS(@TypeOf(stream)).ClusterChainIterator{ + .allocator = undefined, + .cluster = 2, + .fat_config = fat_config, + .stream = stream, + .fat = fat[0..], + .table_offset = 0, + .cluster_offset = 16, + }; + + // This will update the next cluster to read from + try it.checkRead(); + + // Updated the cluster only + expectEqual(it.cluster, 3); + expectEqual(it.cluster_offset, 0); + expectEqual(it.table_offset, 0); + expectEqualSlices(u32, it.fat, fat[0..]); +} + +test "ClusterChainIterator.checkRead - Multiple clusters, outside FAT" { + // The undefined values are not used in checkRead + // The value won't be valid FAT32 values, but this doesn't matter in this context so can make the stream buffer smaller + const fat_config = FATConfig{ + .bytes_per_sector = 16, + .sectors_per_cluster = 1, + .reserved_sectors = 0, + .hidden_sectors = undefined, + .total_sectors = undefined, + .sectors_per_fat = 1, + .root_directory_cluster = undefined, + .fsinfo_sector = undefined, + .backup_boot_sector = undefined, + .has_fs_info = undefined, + .number_free_clusters = undefined, + .next_free_cluster = undefined, + .cluster_end_marker = 0x0FFFFFFF, + }; + // Set the stream to all FF which represents the end of a FAT chain + var buff_stream = [_]u8{ + // First 4 FAT (little endian) + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0x04, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + // Second 4 FAT. This is where it will seek to + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + }; + var stream = &std.io.fixedBufferStream(buff_stream[0..]); + // First 2 are for other purposed and not needed, the third is the first real FAT entry + var fat = [_]u32{ 0x0FFFFFFF, 0xFFFFFFF8, 0x00000004, 0xFFFFFFFF }; + var expected_fat = [_]u32{ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; + var it = Fat32FS(@TypeOf(stream)).ClusterChainIterator{ + .allocator = undefined, + .cluster = 2, + .fat_config = fat_config, + .stream = stream, + .fat = fat[0..], + .table_offset = 0, + .cluster_offset = 16, + }; + + // This will read the next cluster and read a new FAT cache + try it.checkRead(); + + // Updated the cluster and table offset + expectEqual(it.cluster, 4); + expectEqual(it.cluster_offset, 0); + expectEqual(it.table_offset, 1); + expectEqualSlices(u32, it.fat, expected_fat[0..]); +} + +test "ClusterChainIterator.read - end of buffer" { + var stream = &std.io.fixedBufferStream(&[_]u8{}); + var it = Fat32FS(@TypeOf(stream)).ClusterChainIterator{ + .allocator = undefined, + .cluster = undefined, + .fat_config = undefined, + .stream = undefined, + .fat = undefined, + .table_offset = undefined, + .cluster_offset = undefined, + }; + const actual = try it.read(&[_]u8{}); + expectEqual(actual, null); +} + +test "ClusterChainIterator.read - cluster 0" { + var stream = &std.io.fixedBufferStream(&[_]u8{}); + var it = Fat32FS(@TypeOf(stream)).ClusterChainIterator{ + .allocator = undefined, + .cluster = 0, + .fat_config = undefined, + .stream = undefined, + .fat = undefined, + .table_offset = undefined, + .cluster_offset = undefined, + }; + var buff: [128]u8 = undefined; + const actual = try it.read(buff[0..]); + expectEqual(actual, null); +} + +test "ClusterChainIterator.read - end of cluster chain" { + // The undefined values are not used in read + const end_cluster: u32 = 0x0FFFFFFF; + const fat_config = FATConfig{ + .bytes_per_sector = undefined, + .sectors_per_cluster = undefined, + .reserved_sectors = undefined, + .hidden_sectors = undefined, + .total_sectors = undefined, + .sectors_per_fat = undefined, + .root_directory_cluster = undefined, + .fsinfo_sector = undefined, + .backup_boot_sector = undefined, + .has_fs_info = undefined, + .number_free_clusters = undefined, + .next_free_cluster = undefined, + .cluster_end_marker = end_cluster, + }; + var stream = &std.io.fixedBufferStream(&[_]u8{}); + var it = Fat32FS(@TypeOf(stream)).ClusterChainIterator{ + .allocator = undefined, + .cluster = end_cluster, + .fat_config = fat_config, + .stream = undefined, + .fat = undefined, + .table_offset = undefined, + .cluster_offset = undefined, + }; + var buff: [128]u8 = undefined; + const actual = try it.read(buff[0..]); + expectEqual(actual, null); +} + +test "ClusterChainIterator.read - BadRead" { + // The undefined values are not used in read + const fat_config = FATConfig{ + .bytes_per_sector = 512, + .sectors_per_cluster = 1, + .reserved_sectors = 0, + .hidden_sectors = undefined, + .total_sectors = undefined, + .sectors_per_fat = 1, + .root_directory_cluster = undefined, + .fsinfo_sector = undefined, + .backup_boot_sector = undefined, + .has_fs_info = undefined, + .number_free_clusters = undefined, + .next_free_cluster = undefined, + .cluster_end_marker = 0x0FFFFFFF, + }; + var stream_buff: [1024]u8 = undefined; + var stream = &std.io.fixedBufferStream(stream_buff[0..]); + var it = Fat32FS(@TypeOf(stream)).ClusterChainIterator{ + .allocator = undefined, + .cluster = 2, + .fat_config = fat_config, + .stream = stream, + .fat = undefined, + .table_offset = 0, + .cluster_offset = 0, + }; + // Buffer is too small + var buff: [128]u8 = undefined; + expectError(error.BadRead, it.read(buff[0..])); +} + +test "ClusterChainIterator.read - success" { + // The undefined values are not used in checkRead + // The value won't be valid FAT32 values, but this doesn't matter in this context so can make the stream buffer smaller + const fat_config = FATConfig{ + .bytes_per_sector = 16, + .sectors_per_cluster = 1, + .reserved_sectors = 0, + .hidden_sectors = undefined, + .total_sectors = undefined, + .sectors_per_fat = 1, + .root_directory_cluster = undefined, + .fsinfo_sector = undefined, + .backup_boot_sector = undefined, + .has_fs_info = undefined, + .number_free_clusters = undefined, + .next_free_cluster = undefined, + .cluster_end_marker = 0x0FFFFFFF, + }; + + var buff_stream = [_]u8{ + // FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + // Backup FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + // Data region + 'a', 'b', 'c', 'd', + '1', '2', '3', '4', + 'A', 'B', 'C', 'D', + '!', '"', '$', '%', + }; + var stream = &std.io.fixedBufferStream(buff_stream[0..]); + // First 2 are for other purposed and not needed, the third is the first real FAT entry + var fat = [_]u32{ 0x0FFFFFFF, 0xFFFFFFF8, 0xFFFFFFFF, 0xFFFFFFFF }; + var expected_fat = [_]u32{ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; + var it = Fat32FS(@TypeOf(stream)).ClusterChainIterator{ + .allocator = undefined, + .cluster = 2, + .fat_config = fat_config, + .stream = stream, + .fat = fat[0..], + .table_offset = 0, + .cluster_offset = 0, + }; + + var buff: [16]u8 = undefined; + const read = try it.read(buff[0..]); + + expectEqual(read, 16); + expectEqualSlices(u8, buff[0..], "abcd1234ABCD!\"$%"); + expectEqual(it.table_offset, 0); + expectEqual(it.cluster_offset, 0); + expectEqual(it.cluster, 0xFFFFFFFF); +} + +test "ClusterChainIterator.init - free on BadRead" { + const fat_config = FATConfig{ + .bytes_per_sector = 16, + .sectors_per_cluster = 1, + .reserved_sectors = 0, + .hidden_sectors = undefined, + .total_sectors = undefined, + .sectors_per_fat = undefined, + .root_directory_cluster = undefined, + .fsinfo_sector = undefined, + .backup_boot_sector = undefined, + .has_fs_info = undefined, + .number_free_clusters = undefined, + .next_free_cluster = undefined, + .cluster_end_marker = 0x0FFFFFFF, + }; + var stream = &std.io.fixedBufferStream(&[_]u8{}); + expectError(error.BadRead, Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(std.testing.allocator, fat_config, 2, stream)); +} + +test "ClusterChainIterator.init - free on OutOfMemory" { + const fat_config = FATConfig{ + .bytes_per_sector = 16, + .sectors_per_cluster = 1, + .reserved_sectors = 0, + .hidden_sectors = undefined, + .total_sectors = undefined, + .sectors_per_fat = 1, + .root_directory_cluster = undefined, + .fsinfo_sector = undefined, + .backup_boot_sector = undefined, + .has_fs_info = undefined, + .number_free_clusters = undefined, + .next_free_cluster = undefined, + .cluster_end_marker = 0x0FFFFFFF, + }; + var buff_stream = [_]u8{ + // FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + // Backup FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + // Data region + 'a', 'b', 'c', 'd', + '1', '2', '3', '4', + 'A', 'B', 'C', 'D', + '!', '"', '$', '%', + }; + var stream = &std.io.fixedBufferStream(buff_stream[0..]); + const allocations: usize = 1; + + var i: usize = 0; + while (i < allocations) : (i += 1) { + var fa = std.testing.FailingAllocator.init(std.testing.allocator, i); + expectError(error.OutOfMemory, Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(&fa.allocator, fat_config, 2, stream)); + } +} + +test "ClusterChainIterator.init - success and good read" { + const fat_config = FATConfig{ + .bytes_per_sector = 16, + .sectors_per_cluster = 1, + .reserved_sectors = 0, + .hidden_sectors = undefined, + .total_sectors = undefined, + .sectors_per_fat = 1, + .root_directory_cluster = undefined, + .fsinfo_sector = undefined, + .backup_boot_sector = undefined, + .has_fs_info = undefined, + .number_free_clusters = undefined, + .next_free_cluster = undefined, + .cluster_end_marker = 0x0FFFFFFF, + }; + var buff_stream = [_]u8{ + // FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + // Backup FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + // Data region + 'a', 'b', 'c', 'd', + '1', '2', '3', '4', + 'A', 'B', 'C', 'D', + '!', '"', '$', '%', + }; + var stream = &std.io.fixedBufferStream(buff_stream[0..]); + var it = try Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(std.testing.allocator, fat_config, 2, stream); + defer it.deinit(); + + var buff: [16]u8 = undefined; + // If orelse, then 'expectEqual(read, 16);' will fail + const read = (try it.read(buff[0..])) orelse 0; + + expectEqual(read, 16); + expectEqualSlices(u8, buff[0..], "abcd1234ABCD!\"$%"); + expectEqual(it.table_offset, 0); + expectEqual(it.cluster_offset, 0); + expectEqual(it.cluster, 0xFFFFFFFF); + + const expect_null = try it.read(buff[read..]); + expectEqual(expect_null, null); +} + +test "EntryIterator.checkRead - inside cluster block" { + const fat_config = FATConfig{ + .bytes_per_sector = 16, + .sectors_per_cluster = 1, + .reserved_sectors = 0, + .hidden_sectors = undefined, + .total_sectors = undefined, + .sectors_per_fat = 1, + .root_directory_cluster = undefined, + .fsinfo_sector = undefined, + .backup_boot_sector = undefined, + .has_fs_info = undefined, + .number_free_clusters = undefined, + .next_free_cluster = undefined, + .cluster_end_marker = 0x0FFFFFFF, + }; + var buff_stream = [_]u8{ + // FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0x04, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + // Backup FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + // Data region cluster 1 + 'a', 'b', 'c', 'd', + '1', '2', '3', '4', + 'A', 'B', 'C', 'D', + '!', '"', '$', '%', + // Data region cluster 2 + 'e', 'f', 'g', 'h', + '5', '6', '7', '8', + 'E', 'F', 'G', 'H', + '^', '&', '*', '(', + }; + var stream = &std.io.fixedBufferStream(buff_stream[0..]); + var cluster_chain = try Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(std.testing.allocator, fat_config, 2, stream); + defer cluster_chain.deinit(); + + var buff: [16]u8 = undefined; + std.mem.copy(u8, buff[0..], buff_stream[32..48]); + var it = Fat32FS(@TypeOf(cluster_chain.stream)).EntryIterator{ + .allocator = undefined, + .cluster_block = buff[0..], + .index = 0, + .cluster_chain = cluster_chain, + }; + + expectEqualSlices(u8, it.cluster_block, "abcd1234ABCD!\"$%"); + try it.checkRead(); + // nothing changed + expectEqualSlices(u8, it.cluster_block, "abcd1234ABCD!\"$%"); +} + +test "EntryIterator.checkRead - read new cluster" { + const fat_config = FATConfig{ + .bytes_per_sector = 16, + .sectors_per_cluster = 1, + .reserved_sectors = 0, + .hidden_sectors = undefined, + .total_sectors = undefined, + .sectors_per_fat = 1, + .root_directory_cluster = undefined, + .fsinfo_sector = undefined, + .backup_boot_sector = undefined, + .has_fs_info = undefined, + .number_free_clusters = undefined, + .next_free_cluster = undefined, + .cluster_end_marker = 0x0FFFFFFF, + }; + var buff_stream = [_]u8{ + // FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0x03, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + // Backup FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0x03, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + // Data region cluster 1 + 'a', 'b', 'c', 'd', + '1', '2', '3', '4', + 'A', 'B', 'C', 'D', + '!', '"', '$', '%', + // Data region cluster 2 + 'e', 'f', 'g', 'h', + '5', '6', '7', '8', + 'E', 'F', 'G', 'H', + '^', '&', '*', '(', + }; + var stream = &std.io.fixedBufferStream(buff_stream[0..]); + var cluster_chain = try Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(std.testing.allocator, fat_config, 2, stream); + defer cluster_chain.deinit(); + + var buff: [16]u8 = undefined; + _ = try cluster_chain.read(buff[0..]); + + var it = Fat32FS(@TypeOf(cluster_chain.stream)).EntryIterator{ + .allocator = undefined, + .cluster_block = buff[0..], + .index = 16, + .cluster_chain = cluster_chain, + }; + + expectEqualSlices(u8, it.cluster_block, "abcd1234ABCD!\"$%"); + try it.checkRead(); + expectEqualSlices(u8, it.cluster_block, "efgh5678EFGH^&*("); + expectEqual(it.index, 0); +} + +test "EntryIterator.checkRead - end of cluster chain" { + const fat_config = FATConfig{ + .bytes_per_sector = 16, + .sectors_per_cluster = 1, + .reserved_sectors = 0, + .hidden_sectors = undefined, + .total_sectors = undefined, + .sectors_per_fat = 1, + .root_directory_cluster = undefined, + .fsinfo_sector = undefined, + .backup_boot_sector = undefined, + .has_fs_info = undefined, + .number_free_clusters = undefined, + .next_free_cluster = undefined, + .cluster_end_marker = 0x0FFFFFFF, + }; + var buff_stream = [_]u8{ + // FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + // Backup FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + // Data region cluster 1 + 'a', 'b', 'c', 'd', + '1', '2', '3', '4', + 'A', 'B', 'C', 'D', + '!', '"', '$', '%', + // Data region cluster 2 + 'e', 'f', 'g', 'h', + '5', '6', '7', '8', + 'E', 'F', 'G', 'H', + '^', '&', '*', '(', + }; + var stream = &std.io.fixedBufferStream(buff_stream[0..]); + var cluster_chain = try Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(std.testing.allocator, fat_config, 2, stream); + defer cluster_chain.deinit(); + + var buff: [16]u8 = undefined; + _ = try cluster_chain.read(buff[0..]); + + var it = Fat32FS(@TypeOf(cluster_chain.stream)).EntryIterator{ + .allocator = undefined, + .cluster_block = buff[0..], + .index = 16, + .cluster_chain = cluster_chain, + }; + + expectEqualSlices(u8, it.cluster_block, "abcd1234ABCD!\"$%"); + expectError(error.EndClusterChain, it.checkRead()); +} + +test "EntryIterator.nextImp - end of entries" { + const fat_config = FATConfig{ + .bytes_per_sector = 32, + .sectors_per_cluster = 1, + .reserved_sectors = 0, + .hidden_sectors = undefined, + .total_sectors = undefined, + .sectors_per_fat = 1, + .root_directory_cluster = undefined, + .fsinfo_sector = undefined, + .backup_boot_sector = undefined, + .has_fs_info = undefined, + .number_free_clusters = undefined, + .next_free_cluster = undefined, + .cluster_end_marker = 0x0FFFFFFF, + }; + var buff_stream = [_]u8{ + // FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + // Backup FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + // Data region cluster 1 + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + // Data region cluster 2 + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + var stream = &std.io.fixedBufferStream(buff_stream[0..]); + var cluster_chain = try Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(std.testing.allocator, fat_config, 2, stream); + defer cluster_chain.deinit(); + + var buff: [32]u8 = undefined; + _ = try cluster_chain.read(buff[0..]); + + var it = Fat32FS(@TypeOf(cluster_chain.stream)).EntryIterator{ + .allocator = undefined, + .cluster_block = buff[0..], + .index = 0, + .cluster_chain = cluster_chain, + }; + + const actual = try it.nextImp(); + expectEqual(actual, null); +} + +test "EntryIterator.nextImp - just deleted files" { + const fat_config = FATConfig{ + .bytes_per_sector = 32, + .sectors_per_cluster = 1, + .reserved_sectors = 0, + .hidden_sectors = undefined, + .total_sectors = undefined, + .sectors_per_fat = 1, + .root_directory_cluster = undefined, + .fsinfo_sector = undefined, + .backup_boot_sector = undefined, + .has_fs_info = undefined, + .number_free_clusters = undefined, + .next_free_cluster = undefined, + .cluster_end_marker = 0x0FFFFFFF, + }; + // These deleted files are taken from a real FAT32 implementation. There is one deleted file for cluster 2 + // This will also read the next cluster chain. + var buff_stream = [_]u8{ + // FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0x03, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + // Backup FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0x03, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + // Data region cluster 1 + 0xE5, 0x41, 0x4D, 0x44, + 0x49, 0x53, 0x7E, 0x32, + 0x54, 0x58, 0x54, 0x00, + 0x18, 0x34, 0x47, 0x76, + 0xF9, 0x50, 0x00, 0x00, + 0x00, 0x00, 0x48, 0x76, + 0xF9, 0x50, 0x04, 0x00, + 0x24, 0x00, 0x00, 0x00, + // Data region cluster 2 + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + var stream = &std.io.fixedBufferStream(buff_stream[0..]); + var cluster_chain = try Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(std.testing.allocator, fat_config, 2, stream); + defer cluster_chain.deinit(); + + var buff: [32]u8 = undefined; + _ = try cluster_chain.read(buff[0..]); + + var it = Fat32FS(@TypeOf(cluster_chain.stream)).EntryIterator{ + .allocator = undefined, + .cluster_block = buff[0..], + .index = 0, + .cluster_chain = cluster_chain, + }; + + const actual = try it.nextImp(); + expectEqual(actual, null); +} + +test "EntryIterator.nextImp - short name only" { + const fat_config = FATConfig{ + .bytes_per_sector = 32, + .sectors_per_cluster = 1, + .reserved_sectors = 0, + .hidden_sectors = undefined, + .total_sectors = undefined, + .sectors_per_fat = 1, + .root_directory_cluster = undefined, + .fsinfo_sector = undefined, + .backup_boot_sector = undefined, + .has_fs_info = undefined, + .number_free_clusters = undefined, + .next_free_cluster = undefined, + .cluster_end_marker = 0x0FFFFFFF, + }; + // This short name files are taken from a real FAT32 implementation. + // This will also read the next cluster chain. + var buff_stream = [_]u8{ + // FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0x03, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + // Backup FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0x03, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + // Data region cluster 1 + 0x42, 0x53, 0x48, 0x4F, + 0x52, 0x54, 0x20, 0x20, + 0x54, 0x58, 0x54, 0x00, + 0x10, 0xA0, 0x68, 0xA9, + 0xFE, 0x50, 0x00, 0x00, + 0x00, 0x00, 0x6E, 0xA9, + 0xFE, 0x50, 0x04, 0x00, + 0x13, 0x00, 0x00, 0x00, + // Data region cluster 2 + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + var stream = &std.io.fixedBufferStream(buff_stream[0..]); + var cluster_chain = try Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(std.testing.allocator, fat_config, 2, stream); + defer cluster_chain.deinit(); + + var buff: [32]u8 = undefined; + _ = try cluster_chain.read(buff[0..]); + + var it = Fat32FS(@TypeOf(cluster_chain.stream)).EntryIterator{ + .allocator = undefined, + .cluster_block = buff[0..], + .index = 0, + .cluster_chain = cluster_chain, + }; + + const actual = (try it.nextImp()) orelse return error.TestFail; + defer actual.deinit(); + expectEqualSlices(u8, actual.short_name.getSFNName()[0..], "BSHORT TXT"); + expectEqual(actual.long_name, null); +} + +test "EntryIterator.nextImp - long name only" { + const fat_config = FATConfig{ + .bytes_per_sector = 32, + .sectors_per_cluster = 1, + .reserved_sectors = 0, + .hidden_sectors = undefined, + .total_sectors = undefined, + .sectors_per_fat = 1, + .root_directory_cluster = undefined, + .fsinfo_sector = undefined, + .backup_boot_sector = undefined, + .has_fs_info = undefined, + .number_free_clusters = undefined, + .next_free_cluster = undefined, + .cluster_end_marker = 0x0FFFFFFF, + }; + // This short name files are taken from a real FAT32 implementation. + // This will also read the next cluster chain. + // FAT 2 long then blank + // FAT 4 2 long entries, no associated short + var buff_stream = [_]u8{ + // FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0x03, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0x05, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + // Backup FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0x03, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0x05, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + // Data region cluster 1 + 0x41, 0x42, 0x00, 0x73, + 0x00, 0x68, 0x00, 0x6F, + 0x00, 0x72, 0x00, 0x0F, + 0x00, 0xA8, 0x74, 0x00, + 0x2E, 0x00, 0x74, 0x00, + 0x78, 0x00, 0x74, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + // Data region cluster 2 + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + // Data region cluster 3 + 0x41, 0x42, 0x00, 0x73, + 0x00, 0x68, 0x00, 0x6F, + 0x00, 0x72, 0x00, 0x0F, + 0x00, 0xA8, 0x74, 0x00, + 0x2E, 0x00, 0x74, 0x00, + 0x78, 0x00, 0x74, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + // Data region cluster 4 + 0x41, 0x42, 0x00, 0x73, + 0x00, 0x68, 0x00, 0x6F, + 0x00, 0x72, 0x00, 0x0F, + 0x00, 0xA8, 0x74, 0x00, + 0x2E, 0x00, 0x74, 0x00, + 0x78, 0x00, 0x74, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + }; + // FAT 2 test + { + var stream = &std.io.fixedBufferStream(buff_stream[0..]); + var cluster_chain = try Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(std.testing.allocator, fat_config, 2, stream); + defer cluster_chain.deinit(); + + var buff: [32]u8 = undefined; + _ = try cluster_chain.read(buff[0..]); + + var it = Fat32FS(@TypeOf(cluster_chain.stream)).EntryIterator{ + .allocator = std.testing.allocator, + .cluster_block = buff[0..], + .index = 0, + .cluster_chain = cluster_chain, + }; + + expectError(error.Orphan, it.nextImp()); + } + + // FAT 4 test + { + var stream = &std.io.fixedBufferStream(buff_stream[0..]); + var cluster_chain = try Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(std.testing.allocator, fat_config, 4, stream); + defer cluster_chain.deinit(); + + var buff: [32]u8 = undefined; + _ = try cluster_chain.read(buff[0..]); + + var it = Fat32FS(@TypeOf(cluster_chain.stream)).EntryIterator{ + .allocator = std.testing.allocator, + .cluster_block = buff[0..], + .index = 0, + .cluster_chain = cluster_chain, + }; + + expectError(error.Orphan, it.nextImp()); + } +} + +test "EntryIterator.nextImp - long name, incorrect check sum" { + const fat_config = FATConfig{ + .bytes_per_sector = 32, + .sectors_per_cluster = 1, + .reserved_sectors = 0, + .hidden_sectors = undefined, + .total_sectors = undefined, + .sectors_per_fat = 1, + .root_directory_cluster = undefined, + .fsinfo_sector = undefined, + .backup_boot_sector = undefined, + .has_fs_info = undefined, + .number_free_clusters = undefined, + .next_free_cluster = undefined, + .cluster_end_marker = 0x0FFFFFFF, + }; + // Values taken from a real FAT32 implementation + // In data region cluster 1, row 4 column 2, + // this has changed from the valid 0xA8 to a invalid 0x55 check sum + var buff_stream = [_]u8{ + // FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + // Backup FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + // Data region cluster 1 + 0x41, 0x42, 0x00, 0x73, + 0x00, 0x68, 0x00, 0x6F, + 0x00, 0x72, 0x00, 0x0F, + 0x00, 0x55, 0x74, 0x00, + 0x2E, 0x00, 0x74, 0x00, + 0x78, 0x00, 0x74, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + // Data region cluster 2 + 0x42, 0x53, 0x48, 0x4F, + 0x52, 0x54, 0x20, 0x20, + 0x54, 0x58, 0x54, 0x00, + 0x10, 0xA0, 0x68, 0xA9, + 0xFE, 0x50, 0x00, 0x00, + 0x00, 0x00, 0x6E, 0xA9, + 0xFE, 0x50, 0x04, 0x00, + 0x13, 0x00, 0x00, 0x00, + // Data region cluster 3 + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + var stream = &std.io.fixedBufferStream(buff_stream[0..]); + var cluster_chain = try Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(std.testing.allocator, fat_config, 2, stream); + defer cluster_chain.deinit(); + + var buff: [32]u8 = undefined; + _ = try cluster_chain.read(buff[0..]); + + var it = Fat32FS(@TypeOf(cluster_chain.stream)).EntryIterator{ + .allocator = std.testing.allocator, + .cluster_block = buff[0..], + .index = 0, + .cluster_chain = cluster_chain, + }; + + expectError(error.Orphan, it.nextImp()); +} + +test "EntryIterator.nextImp - long name missing entry" { + // 0x43 + // 0x01 + // missing 0x02 + const fat_config = FATConfig{ + .bytes_per_sector = 32, + .sectors_per_cluster = 1, + .reserved_sectors = 0, + .hidden_sectors = undefined, + .total_sectors = undefined, + .sectors_per_fat = 1, + .root_directory_cluster = undefined, + .fsinfo_sector = undefined, + .backup_boot_sector = undefined, + .has_fs_info = undefined, + .number_free_clusters = undefined, + .next_free_cluster = undefined, + .cluster_end_marker = 0x0FFFFFFF, + }; + // Values taken from a real FAT32 implementation + // In data region cluster 1, row 4 column 2, + // this has changed from the valid 0xA8 to a invalid 0x55 check sum + var buff_stream = [_]u8{ + // FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + // Backup FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + // Data region cluster 1 + 0x43, 0x6E, 0x00, 0x67, + 0x00, 0x6E, 0x00, 0x61, + 0x00, 0x6D, 0x00, 0x0F, + 0x00, 0x6E, 0x65, 0x00, + 0x2E, 0x00, 0x74, 0x00, + 0x78, 0x00, 0x74, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + // Data region cluster 2 + 0x01, 0x6C, 0x00, 0x6F, + 0x00, 0x6F, 0x00, 0x6F, + 0x00, 0x6F, 0x00, 0x0F, + 0x00, 0x6E, 0x6F, 0x00, + 0x6E, 0x00, 0x67, 0x00, + 0x6C, 0x00, 0x6F, 0x00, + 0x6F, 0x00, 0x00, 0x00, + 0x6F, 0x00, 0x6F, 0x00, + // Data region cluster 3 + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + var stream = &std.io.fixedBufferStream(buff_stream[0..]); + var cluster_chain = try Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(std.testing.allocator, fat_config, 2, stream); + defer cluster_chain.deinit(); + + var buff: [32]u8 = undefined; + _ = try cluster_chain.read(buff[0..]); + + var it = Fat32FS(@TypeOf(cluster_chain.stream)).EntryIterator{ + .allocator = std.testing.allocator, + .cluster_block = buff[0..], + .index = 0, + .cluster_chain = cluster_chain, + }; + + expectError(error.Orphan, it.nextImp()); +} + +test "EntryIterator.nextImp - valid short and long entry" { + const fat_config = FATConfig{ + .bytes_per_sector = 32, + .sectors_per_cluster = 1, + .reserved_sectors = 0, + .hidden_sectors = undefined, + .total_sectors = undefined, + .sectors_per_fat = 1, + .root_directory_cluster = undefined, + .fsinfo_sector = undefined, + .backup_boot_sector = undefined, + .has_fs_info = undefined, + .number_free_clusters = undefined, + .next_free_cluster = undefined, + .cluster_end_marker = 0x0FFFFFFF, + }; + // Values taken from a real FAT32 implementation + var buff_stream = [_]u8{ + // FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + // Backup FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + // Data region cluster 1 + 0x43, 0x6E, 0x00, 0x67, + 0x00, 0x6E, 0x00, 0x61, + 0x00, 0x6D, 0x00, 0x0F, + 0x00, 0x6E, 0x65, 0x00, + 0x2E, 0x00, 0x74, 0x00, + 0x78, 0x00, 0x74, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + // Data region cluster 2 + 0x02, 0x6E, 0x00, 0x67, + 0x00, 0x76, 0x00, 0x65, + 0x00, 0x72, 0x00, 0x0F, + 0x00, 0x6E, 0x79, 0x00, + 0x6C, 0x00, 0x6F, 0x00, + 0x6F, 0x00, 0x6F, 0x00, + 0x6F, 0x00, 0x00, 0x00, + 0x6F, 0x00, 0x6F, 0x00, + // Data region cluster 3 + 0x01, 0x6C, 0x00, 0x6F, + 0x00, 0x6F, 0x00, 0x6F, + 0x00, 0x6F, 0x00, 0x0F, + 0x00, 0x6E, 0x6F, 0x00, + 0x6E, 0x00, 0x67, 0x00, + 0x6C, 0x00, 0x6F, 0x00, + 0x6F, 0x00, 0x00, 0x00, + 0x6F, 0x00, 0x6F, 0x00, + // Data region cluster 4 + 0x4C, 0x4F, 0x4F, 0x4F, + 0x4F, 0x4F, 0x7E, 0x31, + 0x54, 0x58, 0x54, 0x00, + 0x18, 0xA0, 0x68, 0xA9, + 0xFE, 0x50, 0x00, 0x00, + 0x00, 0x00, 0x6E, 0xA9, + 0xFE, 0x50, 0x08, 0x00, + 0x13, 0x00, 0x00, 0x00, + }; + var stream = &std.io.fixedBufferStream(buff_stream[0..]); + var cluster_chain = try Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(std.testing.allocator, fat_config, 2, stream); + defer cluster_chain.deinit(); + + var buff: [32]u8 = undefined; + _ = try cluster_chain.read(buff[0..]); + + var it = Fat32FS(@TypeOf(cluster_chain.stream)).EntryIterator{ + .allocator = std.testing.allocator, + .cluster_block = buff[0..], + .index = 0, + .cluster_chain = cluster_chain, + }; + + const actual = (try it.nextImp()) orelse return error.TestFail; + defer actual.deinit(); + expectEqualSlices(u8, actual.short_name.getSFNName()[0..], "LOOOOO~1TXT"); + expectEqualSlices(u8, actual.long_name.?, "looooongloooongveryloooooongname.txt"); +} + +test "EntryIterator.next - skips orphan long entry" { + const fat_config = FATConfig{ + .bytes_per_sector = 32, + .sectors_per_cluster = 1, + .reserved_sectors = 0, + .hidden_sectors = undefined, + .total_sectors = undefined, + .sectors_per_fat = 1, + .root_directory_cluster = undefined, + .fsinfo_sector = undefined, + .backup_boot_sector = undefined, + .has_fs_info = undefined, + .number_free_clusters = undefined, + .next_free_cluster = undefined, + .cluster_end_marker = 0x0FFFFFFF, + }; + // Values taken from a real FAT32 implementation + var buff_stream = [_]u8{ + // FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + // Backup FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + // Data region cluster 1 + 0x43, 0x6E, 0x00, 0x67, + 0x00, 0x6E, 0x00, 0x61, + 0x00, 0x6D, 0x00, 0x0F, + 0x00, 0x6E, 0x65, 0x00, + 0x2E, 0x00, 0x74, 0x00, + 0x78, 0x00, 0x74, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + // Missing 0x02 + // Data region cluster 2 + 0x01, 0x6C, 0x00, 0x6F, + 0x00, 0x6F, 0x00, 0x6F, + 0x00, 0x6F, 0x00, 0x0F, + 0x00, 0x6E, 0x6F, 0x00, + 0x6E, 0x00, 0x67, 0x00, + 0x6C, 0x00, 0x6F, 0x00, + 0x6F, 0x00, 0x00, 0x00, + 0x6F, 0x00, 0x6F, 0x00, + // Data region cluster 3 + 0x4C, 0x4F, 0x4F, 0x4F, + 0x4F, 0x4F, 0x7E, 0x31, + 0x54, 0x58, 0x54, 0x00, + 0x18, 0xA0, 0x68, 0xA9, + 0xFE, 0x50, 0x00, 0x00, + 0x00, 0x00, 0x6E, 0xA9, + 0xFE, 0x50, 0x08, 0x00, + 0x13, 0x00, 0x00, 0x00, + // Data region cluster 4 + 0x42, 0x2E, 0x00, 0x74, + 0x00, 0x78, 0x00, 0x74, + 0x00, 0x00, 0x00, 0x0F, + 0x00, 0xE9, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + // Data region cluster 5 + 0x01, 0x72, 0x00, 0x61, + 0x00, 0x6D, 0x00, 0x64, + 0x00, 0x69, 0x00, 0x0F, + 0x00, 0xE9, 0x73, 0x00, + 0x6B, 0x00, 0x5F, 0x00, + 0x74, 0x00, 0x65, 0x00, + 0x73, 0x00, 0x00, 0x00, + 0x74, 0x00, 0x31, 0x00, + // Data region cluster 6 + 0x52, 0x41, 0x4D, 0x44, + 0x49, 0x53, 0x7E, 0x31, + 0x54, 0x58, 0x54, 0x00, + 0x18, 0x34, 0x47, 0x76, + 0xF9, 0x50, 0x00, 0x00, + 0x00, 0x00, 0x48, 0x76, + 0xF9, 0x50, 0x03, 0x00, + 0x10, 0x00, 0x00, 0x00, + }; + var stream = &std.io.fixedBufferStream(buff_stream[0..]); + var cluster_chain = try Fat32FS(@TypeOf(stream)).ClusterChainIterator.init(std.testing.allocator, fat_config, 2, stream); + defer cluster_chain.deinit(); + + var buff: [32]u8 = undefined; + _ = try cluster_chain.read(buff[0..]); + + var it = Fat32FS(@TypeOf(cluster_chain.stream)).EntryIterator{ + .allocator = std.testing.allocator, + .cluster_block = buff[0..], + .index = 0, + .cluster_chain = cluster_chain, + }; + + const actual1 = (try it.next()) orelse return error.TestFail; + defer actual1.deinit(); + expectEqualSlices(u8, actual1.short_name.getSFNName()[0..], "LOOOOO~1TXT"); + expectEqual(actual1.long_name, null); + + const actual2 = (try it.next()) orelse return error.TestFail; + defer actual2.deinit(); + expectEqualSlices(u8, actual2.short_name.getSFNName()[0..], "RAMDIS~1TXT"); + expectEqualSlices(u8, actual2.long_name.?, "ramdisk_test1.txt"); + + expectEqual(try it.next(), null); +} + +test "EntryIterator.init - free on OutOfMemory" { + const fat_config = FATConfig{ + .bytes_per_sector = 16, + .sectors_per_cluster = 1, + .reserved_sectors = 0, + .hidden_sectors = undefined, + .total_sectors = undefined, + .sectors_per_fat = 1, + .root_directory_cluster = undefined, + .fsinfo_sector = undefined, + .backup_boot_sector = undefined, + .has_fs_info = undefined, + .number_free_clusters = undefined, + .next_free_cluster = undefined, + .cluster_end_marker = 0x0FFFFFFF, + }; + var buff_stream = [_]u8{ + // FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + // Backup FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + // Data region + 'a', 'b', 'c', 'd', + '1', '2', '3', '4', + 'A', 'B', 'C', 'D', + '!', '"', '$', '%', + }; + var stream = &std.io.fixedBufferStream(buff_stream[0..]); + const allocations: usize = 2; + + var i: usize = 0; + while (i < allocations) : (i += 1) { + var fa = std.testing.FailingAllocator.init(std.testing.allocator, i); + expectError(error.OutOfMemory, Fat32FS(@TypeOf(stream)).EntryIterator.init(&fa.allocator, fat_config, 2, stream)); + } +} + +test "EntryIterator.init - free on BadRead" { + const fat_config = FATConfig{ + .bytes_per_sector = 16, + .sectors_per_cluster = 1, + .reserved_sectors = 0, + .hidden_sectors = undefined, + .total_sectors = undefined, + .sectors_per_fat = 1, + .root_directory_cluster = undefined, + .fsinfo_sector = undefined, + .backup_boot_sector = undefined, + .has_fs_info = undefined, + .number_free_clusters = undefined, + .next_free_cluster = undefined, + .cluster_end_marker = 0x0FFFFFFF, + }; + var buff_stream = [_]u8{ + // FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + // Backup FAT region + 0xFF, 0xFF, 0xFF, 0x0F, + 0xF8, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + // Data region (too short) + 'a', 'b', 'c', 'd', + '1', '2', '3', '4', + }; + var stream = &std.io.fixedBufferStream(buff_stream[0..]); + expectError(error.BadRead, Fat32FS(@TypeOf(stream)).EntryIterator.init(std.testing.allocator, fat_config, 2, stream)); +}