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)); +}