diff --git a/src/main/msc/emfat.c b/src/main/msc/emfat.c
index f1db3b6cc..12125c6dd 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 35085d902..055c0ca3a 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 0d137b57c..c080eb209 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 2e0749cf1..c67860907 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 000000000..bb778cd00
--- /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
+}