From 5e0eff09b4d104b6470789b15c6cea42b8fa0aac Mon Sep 17 00:00:00 2001 From: Tao Date: Thu, 26 Dec 2024 23:27:03 +0000 Subject: [PATCH] emfat: add FAT long file name support Also add a few test cases to cover the added functions. --- src/main/msc/emfat.c | 241 ++++++++++++++++++++------- src/main/msc/emfat.h | 7 +- src/main/msc/emfat_file.c | 2 +- src/test/Makefile | 3 + src/test/unit/emfat_unittest.cc | 284 ++++++++++++++++++++++++++++++++ 5 files changed, 480 insertions(+), 57 deletions(-) create mode 100644 src/test/unit/emfat_unittest.cc diff --git a/src/main/msc/emfat.c b/src/main/msc/emfat.c index f1db3b6cc8..12125c6ddb 100644 --- a/src/main/msc/emfat.c +++ b/src/main/msc/emfat.c @@ -30,6 +30,8 @@ #include "platform.h" +#include "build/build_config.h" + #include "common/utils.h" #include "emfat.h" @@ -210,13 +212,102 @@ typedef struct uint8_t flag; uint8_t reserved; uint8_t chksum; - uint8_t fname6_11[LFN_SEC_SET_LEN]; + uint8_t fname5_10[LFN_SEC_SET_LEN]; uint8_t empty[LFN_EMPTY_LEN]; - uint8_t fname12_13[LFN_THIRD_SET_LEN]; + uint8_t fname11_12[LFN_THIRD_SET_LEN]; } lfn_entry; +STATIC_ASSERT(sizeof(lfn_entry) == 32, lfn_entry_size); + #pragma pack(pop) +/* + * Calculate the checksum of the short file name. + * `short_name` and `extension` should be exact 8 and 3 characters long + * respectively. + */ +STATIC_UNIT_TESTED uint8_t sfn_checksum(const char* short_name, const char* extension) +{ + uint8_t sum = 0; + + for (uint8_t i = 0; i < FILE_NAME_SHRT_LEN; i++) { + sum = (((sum & 1) << 7) | ((sum & 0xfe) >> 1)) + short_name[i]; + } + + for (uint8_t i = 0; i < FILE_NAME_EXTN_LEN; i++) { + sum = (((sum & 1) << 7) | ((sum & 0xfe) >> 1)) + extension[i]; + } + + return sum; +} + +/* + * Subroutine to populate 8+3 filename, checksum and the number of entries for an + * `emfat_entry_t`. + */ +STATIC_UNIT_TESTED void emfat_init_sfn(emfat_entry_t *entry) +{ + int i, l, l1, l2; + int dot_pos; + bool need_lfn = false; + + l = strlen(entry->name); + dot_pos = -1; + + if ((entry->attr & ATTR_DIR) == 0) { + for (i = l - 1; i >= 0; i--) { + if (entry->name[i] == '.') + { + dot_pos = i; + break; + } + } + } + + if (dot_pos == -1) { + if (l > FILE_NAME_SHRT_LEN) { + l1 = FILE_NAME_SHRT_LEN; + need_lfn = true; + } else { + l1 = l; + } + l1 = l > FILE_NAME_SHRT_LEN ? FILE_NAME_SHRT_LEN : l; + l2 = 0; + } else { + l1 = dot_pos; + if (l1 > FILE_NAME_SHRT_LEN) { + l1 = FILE_NAME_SHRT_LEN; + need_lfn = true; + } + l2 = l - dot_pos - 1; + l2 = l2 > FILE_NAME_EXTN_LEN ? FILE_NAME_EXTN_LEN : l2; + } + + memset(entry->priv.short_name, ' ', FILE_NAME_SHRT_LEN); + memcpy(entry->priv.short_name, entry->name, l1); + memset(entry->priv.extension, ' ', FILE_NAME_EXTN_LEN); + memcpy(entry->priv.extension, entry->name + dot_pos + 1, l2); + + for (i = 0; i < FILE_NAME_SHRT_LEN; i++) { + if (entry->priv.short_name[i] >= 'a' && entry->priv.short_name[i] <= 'z') { + entry->priv.short_name[i] -= 0x20; + } + } + + for (i = 0; i < FILE_NAME_EXTN_LEN; i++) { + if (entry->priv.extension[i] >= 'a' && entry->priv.extension[i] <= 'z') { + entry->priv.extension[i] -= 0x20; + } + } + + entry->priv.checksum = sfn_checksum(entry->priv.short_name, entry->priv.extension); + + entry->priv.num_entry = 1; + if (need_lfn) { + entry->priv.num_entry += (l + LFN_LEN_PER_ENTRY - 1) / LFN_LEN_PER_ENTRY; + } +} + bool emfat_init_entries(emfat_entry_t *entries) { emfat_entry_t *e; @@ -229,6 +320,7 @@ bool emfat_init_entries(emfat_entry_t *entries) e->priv.next = NULL; e->priv.sub = NULL; e->priv.num_subentry = 0; + e->priv.num_entry = 1; n = 0; for (i = 1; entries[i].name != NULL; i++) { @@ -236,6 +328,7 @@ bool emfat_init_entries(emfat_entry_t *entries) entries[i].priv.next = NULL; entries[i].priv.sub = NULL; entries[i].priv.num_subentry = 0; + emfat_init_sfn(&entries[i]); if (entries[i].level == n - 1) { if (n == 0) return false; e = e->priv.top; @@ -253,7 +346,7 @@ bool emfat_init_entries(emfat_entry_t *entries) if (entries[i].level == n) { if (n == 0) return false; - e->priv.top->priv.num_subentry++; + e->priv.top->priv.num_subentry += entries[i].priv.num_entry; entries[i].priv.top = e->priv.top; e->priv.next = &entries[i]; e = &entries[i]; @@ -300,6 +393,10 @@ bool emfat_init(emfat_t *emfat, const char *label, emfat_entry_t *entries) return false; } + if (strlen(label) != VOL_LABEL_LEN) { + return false; + } + clust = 2; for (i = 0; entries[i].name != NULL; i++) { e = &entries[i]; @@ -489,11 +586,41 @@ void read_fat_sector(emfat_t *emfat, uint8_t *sect, uint32_t index) emfat->priv.last_entry = le; } -void fill_entry(dir_entry *entry, const char *name, uint8_t attr, uint32_t clust, const uint32_t cma[3], uint32_t size) +static void fill_lfn(lfn_entry *entry, uint8_t order, const char *name, uint8_t checksum) { - int i, l, l1, l2; - int dot_pos; + char name_segment[26] = {0,}; + + for (int i = 0; i < 13; i++) { + name_segment[i * 2] = name[i]; // Simple ASCII to Unicode conversion + + // According to MS, name ends with 0x0000 and then pads with 0xFFFF. + // If the name is exact 13 chars, none is needed. + if (name[i] == 0) { + for (int j = (i + 1) * 2; j < 26; j++) { + name_segment[j] = 0xFF; + } + break; + } + } + memset(entry, 0x00, sizeof(lfn_entry)); + + entry->ord_field = order; + + entry->flag = ATTR_LONG_FNAME; + entry->reserved = 0; + entry->chksum = checksum; + + entry->empty[0] = 0; + entry->empty[1] = 0; + + memcpy(entry->fname0_4, name_segment, 10); + memcpy(entry->fname5_10, name_segment + 10, 12); + memcpy(entry->fname11_12, name_segment + 22, 4); +} + +void fill_entry(dir_entry *entry, const char *short_name, const char *extension, uint8_t attr, uint32_t clust, const uint32_t cma[3], uint32_t size) +{ memset(entry, 0, sizeof(dir_entry)); if (cma) { @@ -504,45 +631,8 @@ void fill_entry(dir_entry *entry, const char *name, uint8_t attr, uint32_t clust entry->lst_access_date = cma[2] >> 16; } - l = strlen(name); - dot_pos = -1; - - if ((attr & ATTR_DIR) == 0) { - for (i = l - 1; i >= 0; i--) { - if (name[i] == '.') - { - dot_pos = i; - break; - } - } - } - - if (dot_pos == -1) { - l1 = l > FILE_NAME_SHRT_LEN ? FILE_NAME_SHRT_LEN : l; - l2 = 0; - } else { - l1 = dot_pos; - l1 = l1 > FILE_NAME_SHRT_LEN ? FILE_NAME_SHRT_LEN : l1; - l2 = l - dot_pos - 1; - l2 = l2 > FILE_NAME_EXTN_LEN ? FILE_NAME_EXTN_LEN : l2; - } - - memset(entry->name, ' ', FILE_NAME_SHRT_LEN); - memcpy(entry->name, name, l1); - memset(entry->extn, ' ', FILE_NAME_EXTN_LEN); - memcpy(entry->extn, name + dot_pos + 1, l2); - - for (i = 0; i < FILE_NAME_SHRT_LEN; i++) { - if (entry->name[i] >= 'a' && entry->name[i] <= 'z') { - entry->name[i] -= 0x20; - } - } - - for (i = 0; i < FILE_NAME_EXTN_LEN; i++) { - if (entry->extn[i] >= 'a' && entry->extn[i] <= 'z') { - entry->extn[i] -= 0x20; - } - } + memcpy(entry->name, short_name, FILE_NAME_SHRT_LEN); + memcpy(entry->extn, extension, FILE_NAME_EXTN_LEN); entry->attr = attr; entry->reserved = 24; @@ -555,6 +645,11 @@ void fill_entry(dir_entry *entry, const char *name, uint8_t attr, uint32_t clust void fill_dir_sector(emfat_t *emfat, uint8_t *data, emfat_entry_t *entry, uint32_t rel_sect) { + // When LFN is enabled, this holds the dir_entry index of the current "file + // entry". The value is 1-based, meaning that it starts from priv.num_entry + // and decrements to 1. + uint8_t entry_idx; + dir_entry *de; uint32_t avail; @@ -564,38 +659,74 @@ void fill_dir_sector(emfat_t *emfat, uint8_t *data, emfat_entry_t *entry, uint32 if (rel_sect == 0) { // 1. first sector of directory if (entry->priv.top == NULL) { - fill_entry(de++, emfat->vol_label, ATTR_VOL_LABEL, 0, 0, 0); + fill_entry(de++, emfat->vol_label, emfat->vol_label + FILE_NAME_SHRT_LEN, ATTR_VOL_LABEL, 0, 0, 0); avail -= sizeof(dir_entry); } else { - fill_entry(de++, ".", ATTR_DIR | ATTR_READ, entry->priv.first_clust, 0, 0); + fill_entry(de++, ". ", " ", ATTR_DIR | ATTR_READ, entry->priv.first_clust, 0, 0); if (entry->priv.top->priv.top == NULL) { - fill_entry(de++, "..", ATTR_DIR | ATTR_READ, 0, 0, 0); + fill_entry(de++, ".. ", " ", ATTR_DIR | ATTR_READ, 0, 0, 0); } else { - fill_entry(de++, "..", ATTR_DIR | ATTR_READ, entry->priv.top->priv.first_clust, 0, 0); + fill_entry(de++, ".. ", " ", ATTR_DIR | ATTR_READ, entry->priv.top->priv.first_clust, 0, 0); } avail -= sizeof(dir_entry) * 2; } entry = entry->priv.sub; + if (entry != NULL) + entry_idx = entry->priv.num_entry; } else { // 2. not a first sector + // Imaginarily skip entries in previous sectors and make `entry` and + // `entry_idx` point to the record to be filled in the requested sector. int n; n = rel_sect * (SECT / sizeof(dir_entry)); n -= entry->priv.top == NULL ? 1 : 2; + entry = entry->priv.sub; + if (entry != NULL) + entry_idx = entry->priv.num_entry; while (n > 0 && entry != NULL) { - entry = entry->priv.next; + if (--entry_idx == 0) { + entry = entry->priv.next; + if (entry != NULL) + entry_idx = entry->priv.num_entry; + } n--; } } while (entry != NULL && avail >= sizeof(dir_entry)) { - if (entry->dir) { - fill_entry(de++, entry->name, ATTR_DIR | ATTR_READ, entry->priv.first_clust, entry->cma_time, 0); + if (entry_idx != 1) { + // lfn entry + uint8_t checksum = entry->priv.checksum; + + uint8_t order = entry_idx - 1; + if (entry_idx == entry->priv.num_entry) { + order |= LAST_ORD_FIELD_SEQ; + } + + fill_lfn((lfn_entry *)de++, order, + entry->name + (entry_idx - 2) * LFN_LEN_PER_ENTRY, + checksum); + + entry_idx--; } else { - //fill_entry(de++, entry->name, ATTR_ARCHIVE | ATTR_READ, entry->priv.first_clust, entry->cma_time, entry->curr_size); - fill_entry(de++, entry->name, ATTR_ARCHIVE | ATTR_READ | entry->attr, entry->priv.first_clust, entry->cma_time, entry->curr_size); + // 8+3 entry + if (entry->dir) { + fill_entry(de++, entry->priv.short_name, entry->priv.extension, + ATTR_DIR | ATTR_READ, entry->priv.first_clust, + entry->cma_time, 0); + } else { + fill_entry(de++, entry->priv.short_name, entry->priv.extension, + ATTR_ARCHIVE | ATTR_READ | entry->attr, + entry->priv.first_clust, entry->cma_time, + entry->curr_size); + } + + entry = entry->priv.next; + if (entry != NULL) { + entry_idx = entry->priv.num_entry; + } } - entry = entry->priv.next; avail -= sizeof(dir_entry); } } diff --git a/src/main/msc/emfat.h b/src/main/msc/emfat.h index 35085d9022..055c0ca3ad 100644 --- a/src/main/msc/emfat.h +++ b/src/main/msc/emfat.h @@ -60,10 +60,15 @@ typedef struct emfat_entry_s { uint32_t first_clust; uint32_t last_clust; uint32_t last_reserved; - uint32_t num_subentry; + uint32_t num_entry; // number of entries (counting LFN) + uint32_t num_subentry; // total number of sub entries of a directory. struct emfat_entry_s *top; struct emfat_entry_s *sub; struct emfat_entry_s *next; + + char short_name[8]; + char extension[3]; + uint8_t checksum; } priv; } emfat_entry_t; diff --git a/src/main/msc/emfat_file.c b/src/main/msc/emfat_file.c index 0d137b57c7..c080eb2094 100644 --- a/src/main/msc/emfat_file.c +++ b/src/main/msc/emfat_file.c @@ -363,6 +363,6 @@ void emfat_init_files(void) emfat_set_entry_cma(entry); } - emfat_init(&emfat, "RTFL", entries); + emfat_init(&emfat, "RTFL ", entries); LED0_OFF; } diff --git a/src/test/Makefile b/src/test/Makefile index 2e0749cf1f..c678609075 100644 --- a/src/test/Makefile +++ b/src/test/Makefile @@ -488,6 +488,9 @@ sbus_output_unittest_SRC := \ sbus_output_unittest_DEFINES := \ USE_SBUS_OUTPUT= +emfat_unittest_SRC := \ + $(USER_DIR)/msc/emfat.c + # Please tweak the following variable definitions as needed by your # project, except GTEST_HEADERS, which you can use in your own targets # but shouldn't modify. diff --git a/src/test/unit/emfat_unittest.cc b/src/test/unit/emfat_unittest.cc new file mode 100644 index 0000000000..bb778cd00c --- /dev/null +++ b/src/test/unit/emfat_unittest.cc @@ -0,0 +1,284 @@ +/* + * This file is part of Rotorflight. + * + * Rotorflight is free software. You can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rotorflight is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software. If not, see . + */ + +#include "msc/emfat.h" +#include "msc/emfat_file.h" + +#include "gtest/gtest.h" + +extern "C" { +// C functions under test. +uint8_t sfn_checksum(const char* short_name, const char* extension); +void emfat_init_sfn(emfat_entry_t *entry); +bool emfat_init_entries(emfat_entry_t *entries); +void fill_dir_sector(emfat_t *emfat, uint8_t *data, emfat_entry_t *entry, uint32_t rel_sect); +} + +TEST(SfnChecksum, SpotTest) +{ + EXPECT_EQ(sfn_checksum("RTFL_1~1", "BBL"), 0x6F); +} + +TEST(EmfatInitSfn, ShortFileNameNoDot) +{ + emfat_entry_t entry = { + .name = "abcd", + .attr = 0, + }; + emfat_init_sfn(&entry); + EXPECT_TRUE(memcmp(entry.priv.short_name, "ABCD ", 8) == 0); + EXPECT_TRUE(memcmp(entry.priv.extension, " ", 3) == 0); + EXPECT_EQ(entry.priv.num_entry, 1); +} + +TEST(EmfatInitSfn, ShortFileNameWithDot) +{ + emfat_entry_t entry = { + .name = "abcd.xyz", + .attr = 0, + }; + emfat_init_sfn(&entry); + EXPECT_TRUE(memcmp(entry.priv.short_name, "ABCD ", 8) == 0); + EXPECT_TRUE(memcmp(entry.priv.extension, "XYZ", 3) == 0); + EXPECT_EQ(entry.priv.num_entry, 1); +} + +TEST(EmfatInitSfn, ShortDirName) +{ + emfat_entry_t entry = { + .name = "abcd.xy", + .attr = ATTR_DIR, + }; + emfat_init_sfn(&entry); + EXPECT_TRUE(memcmp(entry.priv.short_name, "ABCD.XY ", 8) == 0); + EXPECT_TRUE(memcmp(entry.priv.extension, " ", 3) == 0); + EXPECT_EQ(entry.priv.num_entry, 1); +} + +TEST(EmfatInitSfn, LongFileNameNoDot) +{ + emfat_entry_t entry = { + .name = "abcdefghi", + .attr = 0, + }; + emfat_init_sfn(&entry); + EXPECT_TRUE(memcmp(entry.priv.short_name, "ABCDEFGH", 8) == 0); + EXPECT_TRUE(memcmp(entry.priv.extension, " ", 3) == 0); + EXPECT_EQ(entry.priv.num_entry, 2); +} + +TEST(EmfatInitSfn, LongFileNameWithDot) +{ + emfat_entry_t entry = { + .name = "abcdefghi.txt", + .attr = 0, + }; + emfat_init_sfn(&entry); + EXPECT_TRUE(memcmp(entry.priv.short_name, "ABCDEFGH", 8) == 0); + EXPECT_TRUE(memcmp(entry.priv.extension, "TXT", 3) == 0); + EXPECT_EQ(entry.priv.num_entry, 2); +} + +TEST(EmfatInitSfn, LongFileNameEntry1) +{ + emfat_entry_t entry = { + .name = "1234567890123", + .attr = 0, + }; + emfat_init_sfn(&entry); + EXPECT_TRUE(memcmp(entry.priv.short_name, "12345678", 8) == 0); + EXPECT_TRUE(memcmp(entry.priv.extension, " ", 3) == 0); + EXPECT_EQ(entry.priv.num_entry, 2); +} + +TEST(EmfatInitSfn, LongFileNameEntry2) +{ + emfat_entry_t entry = { + .name = "1234567890123456789012.456", + .attr = 0, + }; + emfat_init_sfn(&entry); + EXPECT_TRUE(memcmp(entry.priv.short_name, "12345678", 8) == 0); + EXPECT_TRUE(memcmp(entry.priv.extension, "456", 3) == 0); + EXPECT_EQ(entry.priv.num_entry, 3); +} + +TEST(EmfatInitSfn, LongDirName) +{ + emfat_entry_t entry = { + .name = "abcdef.xyz", + .attr = ATTR_DIR, + }; + emfat_init_sfn(&entry); + EXPECT_TRUE(memcmp(entry.priv.short_name, "ABCDEF.X", 8) == 0); + EXPECT_TRUE(memcmp(entry.priv.extension, " ", 3) == 0); +} + +class EmfatTestBase : public ::testing::Test +{ + public: + void SetUp() override + { + ASSERT_TRUE(emfat_init(&emfat_, "RTFL ", entries_)); + } + + emfat_entry_t entries_[10] = { + // name dir attr lvl offset size max_size user time read write + { "", true, 0, 0, 0, 0, 0, 0, {0, 0, 0}, NULL, NULL, { 0 } }, + { "autorun.inf", false, ATTR_HIDDEN, 1, 0, 1, 1, 1, {0, 0, 0}, NULL, NULL, { 0 } }, + { "icon.ico", false, ATTR_HIDDEN, 1, 0, 1, 1, 2, {0, 0, 0}, NULL, NULL, { 0 } }, + { "readme.txt", false, 0, 1, 0, 1, 1, 3, {0, 0, 0}, NULL, NULL, { 0 } }, + { "RTFL_ALL.BBL", 0, 0, 1, 0, 0, 0, 0, {0, 0, 0}, NULL, NULL, { 0 } }, + { "PADDING.TXT", 0, ATTR_HIDDEN, 1, 0, 0, 0, 0, {0, 0, 0}, NULL, NULL, { 0 } }, + { "LongFileName1", 0, 0, 1, 0, 0, 0, 0, {0, 0, 0}, NULL, NULL, { 0 } }, + { "LongDirectory", 1, 0, 1, 0, 0, 0, 0, {0, 0, 0}, NULL, NULL, { 0 } }, + { "LongFileN.exe", 0, 0, 2, 0, 0, 0, 0, {0, 0, 0}, NULL, NULL, { 0 } }, + { NULL, } + }; + + emfat_t emfat_ = {}; +}; + +TEST_F(EmfatTestBase, SFN) +{ + EXPECT_TRUE(memcmp(entries_[1].priv.short_name, "AUTORUN ", 8) == 0); + EXPECT_TRUE(memcmp(entries_[1].priv.extension, "INF", 3) == 0); + + EXPECT_TRUE(memcmp(entries_[6].priv.short_name, "LONGFILE", 8) == 0); + EXPECT_TRUE(memcmp(entries_[6].priv.extension, " ", 3) == 0); + + EXPECT_TRUE(memcmp(entries_[7].priv.short_name, "LONGDIRE", 8) == 0); + EXPECT_TRUE(memcmp(entries_[7].priv.extension, " ", 3) == 0); + + EXPECT_TRUE(memcmp(entries_[8].priv.short_name, "LONGFILE", 8) == 0); + EXPECT_TRUE(memcmp(entries_[8].priv.extension, "EXE", 3) == 0); +} + +struct dir_entry +{ + uint8_t name[11]; + uint8_t attr; + uint8_t reserved[10]; + uint16_t time; + uint16_t date; + uint16_t first_clust; + uint32_t size; +}; + +static_assert(sizeof(dir_entry) == 32, "dir_entry size"); + + +TEST_F(EmfatTestBase, DirEntries) +{ + uint8_t data[512] = {}; + dir_entry *de = (dir_entry *)data; + + fill_dir_sector(&emfat_, data, &entries_[0], 0); + EXPECT_EQ(de[0].name[0], 'R'); // volume label (RTFL) + EXPECT_EQ(de[1].name[0], 'A'); // autorun.inf + EXPECT_EQ(de[2].name[0], 'I'); // icon.ico + + EXPECT_EQ(de[6].name[0], 0x41); // LFN entry of LongFileName1 + EXPECT_EQ(de[6].name[1], 'L'); + EXPECT_EQ(de[6].name[3], 'o'); // (unicode) + EXPECT_EQ(de[7].name[0], 'L'); // SFN entry of LongFileName1 + EXPECT_EQ(de[7].name[1], 'O'); + EXPECT_EQ(de[8].name[0], 0x41); // LFN entry of LongDirectory + EXPECT_EQ(de[8].name[1], 'L'); + EXPECT_EQ(de[8].name[3], 'o'); // (unicode) + EXPECT_EQ(de[9].name[0], 'L'); // SFN entry of LongDirectory + EXPECT_EQ(de[9].name[1], 'O'); + + fill_dir_sector(&emfat_, data, &entries_[0], 1); + EXPECT_EQ(de[0].name[0], 0); + EXPECT_EQ(de[1].name[0], 0); + + fill_dir_sector(&emfat_, data, &entries_[7], 0); + EXPECT_EQ(de[0].name[0], '.'); // . + EXPECT_EQ(de[1].name[0], '.'); // .. + EXPECT_EQ(de[2].name[0], 0x41); // LFN entry of LongFileN.exe + EXPECT_EQ(de[2].name[1], 'L'); + EXPECT_EQ(de[2].name[3], 'o'); + EXPECT_EQ(de[3].name[0], 'L'); // SFN entry of LongFileN.exe +} + +TEST(Emfat, LFNAtSectorBoundary) +{ + std::unique_ptr entries = + std::make_unique(16); + std::unique_ptr filenames = + std::make_unique(16); + + entries[0] = { + .name = "", + .dir = true, + .level = 0, + }; + + // Fill trivial emfat_entries 1 - 11 + // These will fill dir_entries 1 - 11. + for (int i = 1; i < 13; ++i) { + filenames[i] = std::to_string(i); + entries[i] = { + .name = filenames[i].c_str(), + .dir = false, + .level = 1, + }; + } + + // Fill long filename emfat_entry 12 + // Filename is exact 13 char. This emfat entry occupies dir_entry 12 and 13. + entries[12] = { + .name = "LongFileName1", + .dir = false, + .level = 1, + }; + EXPECT_EQ(strlen(entries[12].name), 13); + + // Fill very long filename emfat_entry 13 + // Filename is more than 26 char. This emfat entry occupies dir_entry 14, + // 15, 16, where dir_entry 16 is located in the next sector. + entries[13] = { + .name = "ExtraExtraLongLongFileName.txt", + .dir = false, + .level = 1, + }; + EXPECT_GT(strlen(entries[13].name), 26); + + entries[14] = { + NULL, + }; + + emfat_t emfat = {}; + ASSERT_TRUE(emfat_init(&emfat, "RTFL ", entries.get())); + + uint8_t data[512] = {}; + dir_entry *de = (dir_entry *)data; + + fill_dir_sector(&emfat, data, &entries[0], 0); + EXPECT_EQ(de[12].name[0], 0x41); // (final) LFN entry of "LongFileName.txt" + EXPECT_EQ(de[12].name[1], 'L'); + EXPECT_EQ(de[13].name[0], 'L'); // SFN entry of "LongFileName.txt" + + EXPECT_EQ(de[14].name[0], 0x43); // 3rd (final) LFN entry of Extra... + EXPECT_EQ(de[15].name[0], 0x02); // 2nd LFN entry + + fill_dir_sector(&emfat, data, &entries[0], 1); + EXPECT_EQ(de[0].name[0], 0x01); // 1st LFN entry + EXPECT_EQ(de[1].name[0], 'E'); // SFN entry + EXPECT_EQ(de[2].name[0], 0); // NULL +}