CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
PluginStorage.cpp
Go to the documentation of this file.
2#include "cdc_core/Raii.h"
4#include "cdc_log.h"
5
6#include "esp_vfs.h"
7#include "esp_vfs_fat.h"
8#include "esp_partition.h"
9#include "wear_levelling.h"
10#include "ff.h"
12
13#include <algorithm>
14#include <cerrno>
15#include <cstring>
16#include <cstdio>
17#include <dirent.h>
18#include <sys/stat.h>
19
20namespace cdc::plugin_manager {
21
22static const char* TAG = "PLG_STO";
23static const char* PARTITION_LABEL = "vfat";
24static const char* MOUNT_POINT = "/vfat";
25// System files (plugins, i18n) live in a "system" subfolder so the partition
26// root is a safe, browsable user area.
27static const char* SYSTEM_DIR = "/vfat/system";
28
29static wl_handle_t s_wl_handle = WL_INVALID_HANDLE;
30static bool s_mounted = false;
31static bool s_host_active = false;
32static bool s_remount_pending = false;
33
34// FAT volume label shown to a USB host (uppercase per FAT label convention).
35static const char* VOLUME_LABEL = "CDCBADGE";
36
37// esp_vfs_fat assigns the FatFs drive number internally; locate ours by the
38// presence of the system folder. Returns the drive index, or -1 if not found.
39static int vfat_drive()
40{
41 FILINFO fno;
42 char path[32];
43 for (int d = 0; d < FF_VOLUMES; ++d) {
44 snprintf(path, sizeof(path), "%d:/system", d);
45 if (f_stat(path, &fno) == FR_OK) return d;
46 }
47 return -1;
48}
49
50// Set the FAT volume label so the host shows the drive as CDCBADGE instead of
51// the fatfsgen default. Idempotent; skipped while a host holds the volume.
53{
54 if (!s_mounted || s_host_active) return;
55 const int drive = vfat_drive();
56 if (drive < 0) return;
57 char drv[16];
58 snprintf(drv, sizeof(drv), "%d:", drive);
59 char cur[16] = {0};
60 if (f_getlabel(drv, cur, nullptr) == FR_OK && std::strcmp(cur, VOLUME_LABEL) == 0) {
61 return; // already correct
62 }
63 char lbl[24];
64 snprintf(lbl, sizeof(lbl), "%d:%s", drive, VOLUME_LABEL);
65 if (f_setlabel(lbl) == FR_OK) {
66 LOG_I(TAG, "set vfat volume label to %s", VOLUME_LABEL);
67 } else {
68 LOG_W(TAG, "f_setlabel failed");
69 }
70}
71
73{
74 if (s_mounted) return true;
75
76 const esp_vfs_fat_mount_config_t cfg = {
77 .format_if_mount_failed = true,
78 .max_files = 5,
79 .allocation_unit_size = CONFIG_WL_SECTOR_SIZE,
80 .disk_status_check_enable = false,
81 .use_one_fat = false,
82 };
83
84 esp_err_t err = esp_vfs_fat_spiflash_mount_rw_wl(
86 if (err != ESP_OK) {
87 LOG_E(TAG, "mount failed: 0x%x", err);
88 return false;
89 }
90
91 LOG_I(TAG, "mounted %s on %s", PARTITION_LABEL, MOUNT_POINT);
92 s_mounted = true;
93 mkdir(SYSTEM_DIR, 0777); // ensure the hidden system folder exists
95#if FEATURE_PLUGIN_SYSTEM_LOCK
97#endif
98 return true;
99}
100
102{
103 if (!s_mounted) return;
104 esp_vfs_fat_spiflash_unmount_rw_wl(MOUNT_POINT, s_wl_handle);
105 s_wl_handle = WL_INVALID_HANDLE;
106 s_mounted = false;
107}
108
110{
111 return MOUNT_POINT;
112}
113
114static bool ends_with(const char* s, size_t s_len, const char* suffix, size_t suf_len)
115{
116 return s_len > suf_len && std::strcmp(s + s_len - suf_len, suffix) == 0;
117}
118
119std::vector<std::string> PluginStorage::listPluginIds()
120{
121 std::vector<std::string> ids;
122 if (!s_mounted) return ids;
123
124 DIR* dir = opendir(SYSTEM_DIR);
125 if (!dir) {
126 return ids; // not yet created (fresh format) -> no plugins
127 }
128
129 while (struct dirent* ent = readdir(dir)) {
130 const char* name = ent->d_name;
131 size_t len = std::strlen(name);
132 std::string id;
133 if (ends_with(name, len, ".aot", 4)) {
134 id.assign(name, len - 4);
135 } else if (ends_with(name, len, ".wasm", 5)) {
136 id.assign(name, len - 5);
137 } else {
138 continue;
139 }
140 if (std::find(ids.begin(), ids.end(), id) != ids.end()) continue;
141
142 std::string meta = metaPath(id);
143 struct stat st;
144 if (stat(meta.c_str(), &st) == 0 && (st.st_mode & S_IFREG)) {
145 ids.push_back(std::move(id));
146 }
147 }
148
149 closedir(dir);
150 return ids;
151}
152
153std::string PluginStorage::binaryPath(const std::string& id)
154{
155#if FEATURE_PLUGIN_AOT
156 std::string aot = aotPath(id);
157 struct stat st;
158 if (stat(aot.c_str(), &st) == 0 && (st.st_mode & S_IFREG)) {
159 return aot;
160 }
161#endif
162 return wasmPath(id);
163}
164
165std::string PluginStorage::wasmPath(const std::string& id)
166{
167 return std::string(SYSTEM_DIR) + "/" + id + ".wasm";
168}
169
170std::string PluginStorage::aotPath(const std::string& id)
171{
172 return std::string(SYSTEM_DIR) + "/" + id + ".aot";
173}
174
175std::string PluginStorage::metaPath(const std::string& id)
176{
177 return std::string(SYSTEM_DIR) + "/" + id + ".meta";
178}
179
180std::string PluginStorage::langPath(const std::string& id)
181{
182 return std::string(SYSTEM_DIR) + "/" + id + ".lang";
183}
184
185std::string PluginStorage::disabledPath(const std::string& id)
186{
187 return std::string(SYSTEM_DIR) + "/" + id + ".disabled";
188}
189
190bool PluginStorage::isDisabled(const std::string& id)
191{
192 struct stat st;
193 const std::string path = disabledPath(id);
194 return stat(path.c_str(), &st) == 0 && (st.st_mode & S_IFREG);
195}
196
197bool PluginStorage::setDisabled(const std::string& id, bool disabled)
198{
199 const std::string path = disabledPath(id);
200 if (!disabled) {
201 errno = 0;
202 return std::remove(path.c_str()) == 0 || errno == ENOENT;
203 }
204
205 auto fp = ::cdc::core::openFile(path.c_str(), "wb");
206 if (!fp) return false;
207 static constexpr char kMarker[] = "disabled\n";
208 return std::fwrite(kMarker, 1, sizeof(kMarker) - 1, fp.get()) == sizeof(kMarker) - 1;
209}
210
211bool PluginStorage::stats(uint64_t& free_bytes, uint64_t& total_bytes)
212{
213 if (!s_mounted) return false;
214 return esp_vfs_fat_info(MOUNT_POINT, &total_bytes, &free_bytes) == ESP_OK;
215}
216
218{
219 if (!s_mounted) return 0;
220 return static_cast<uint16_t>(wl_sector_size(s_wl_handle));
221}
222
224{
225 if (!s_mounted) return 0;
226 return wl_size(s_wl_handle);
227}
228
229bool PluginStorage::blockRead(uint32_t lba, uint32_t offset, void* buf, uint32_t len)
230{
231 if (!s_mounted || !buf) return false;
232 const uint16_t bs = static_cast<uint16_t>(wl_sector_size(s_wl_handle));
233 if (!usb_msc_range_ok(wl_size(s_wl_handle), bs, lba, offset, len, false)) return false;
234 const uint64_t addr = static_cast<uint64_t>(lba) * bs + offset;
235 return wl_read(s_wl_handle, static_cast<size_t>(addr), buf, len) == ESP_OK;
236}
237
238bool PluginStorage::blockWrite(uint32_t lba, uint32_t offset, const void* buf, uint32_t len)
239{
240 if (!s_mounted || !buf) return false;
241 const uint16_t bs = static_cast<uint16_t>(wl_sector_size(s_wl_handle));
242 // Wear-levelling writes are erase-then-write at sector granularity, so the
243 // MSC layer must hand us whole, sector-aligned blocks.
244 if (!usb_msc_range_ok(wl_size(s_wl_handle), bs, lba, offset, len, true)) return false;
245 const uint64_t addr = static_cast<uint64_t>(lba) * bs;
246 if (wl_erase_range(s_wl_handle, static_cast<size_t>(addr), len) != ESP_OK) return false;
247 return wl_write(s_wl_handle, static_cast<size_t>(addr), buf, len) == ESP_OK;
248}
249
251{
252 if (active == s_host_active) return;
253 const bool wasActive = s_host_active;
254 s_host_active = active;
255 LOG_I(TAG, "MSC host %s", active ? "attached" : "detached");
256 // Defer the remount out of the USB callback context; remountIfPending()
257 // performs it on a safe task so host-written files become visible.
258 if (usb_msc_should_remount(wasActive, active)) {
259 s_remount_pending = true;
260 }
261}
262
264{
265 return s_host_active;
266}
267
269{
270 if (!s_remount_pending || s_host_active || !s_mounted) return;
271 s_remount_pending = false;
272 unmount();
273 mount();
274 LOG_I(TAG, "remounted %s after MSC host detach", MOUNT_POINT);
275}
276
278{
279 if (!s_mounted || s_host_active) return;
280
281 static constexpr BYTE kAttr = AM_RDO | AM_HID | AM_SYS;
282 static constexpr BYTE kMask = AM_RDO | AM_HID | AM_SYS;
283
284 const int drive = vfat_drive();
285 if (drive < 0) {
286 LOG_W(TAG, "protectSystemDir: system folder not found for chmod");
287 return;
288 }
289 // Hide the directory itself but leave it writable (badge writes into it when
290 // no host is attached); read-only is applied to the files below.
291 char dirPath[32];
292 snprintf(dirPath, sizeof(dirPath), "%d:/system", drive);
293 f_chmod(dirPath, AM_HID | AM_SYS, AM_RDO | AM_HID | AM_SYS);
294
295 // Mark each system file read-only + hidden + system so a host file manager
296 // hides them and refuses to modify or delete them (advisory only).
297 DIR* dir = opendir(SYSTEM_DIR);
298 if (!dir) return;
299 char filePath[280]; // "N:/system/" + up to a 255-char FAT long name
300 while (struct dirent* ent = readdir(dir)) {
301 if (ent->d_name[0] == '.') continue;
302 snprintf(filePath, sizeof(filePath), "%d:/system/%s", drive, ent->d_name);
303 f_chmod(filePath, kAttr, kMask);
304 }
305 closedir(dir);
306 LOG_I(TAG, "protectSystemDir: marked system folder on drive %d", drive);
307}
308
309} // namespace cdc::plugin_manager
Mounts the FAT-FS partition that holds plugin .wasm + .meta files.
char name[cdc::hal::ISecureElement::RMEM_NAME_LEN]
Shared RAII wrappers for firmware resources.
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
Definition cdc_log.h:146
#define LOG_I(tag, fmt,...)
Definition cdc_log.h:147
#define LOG_E(tag, fmt,...)
Definition cdc_log.h:145
static bool blockWrite(uint32_t lba, uint32_t offset, const void *buf, uint32_t len)
Erases and writes raw bytes to the wear-levelling logical space.
static bool mount()
Mount the plugins partition. Auto-formats if empty.
static const char * basePath()
Returns the VFS path prefix, e.g. "/vfat".
static uint16_t blockSize()
Logical sector size of the vfat volume in bytes (MSC block size).
static std::string langPath(const std::string &id)
Returns the full VFS path of <id>.lang (translation overlay).
static void unmount()
Unmount the plugins partition (rarely used; mostly tests).
static std::string aotPath(const std::string &id)
Returns the full VFS path of <id>.aot.
static bool stats(uint64_t &free_bytes, uint64_t &total_bytes)
Returns the free and total bytes on the plugins partition.
static bool blockRead(uint32_t lba, uint32_t offset, void *buf, uint32_t len)
Reads raw bytes from the wear-levelling logical space.
static bool isDisabled(const std::string &id)
True when the plugin has a persistent disabled marker.
static bool setDisabled(const std::string &id, bool disabled)
Create or remove the persistent disabled marker for a plugin.
static std::string disabledPath(const std::string &id)
Returns the full VFS path of <id>.disabled.
static void setHostActive(bool active)
Marks whether a USB host currently holds the volume over MSC.
static void protectSystemDir()
Applies advisory read-only + hidden + system FAT attributes to the system folder so standard host fil...
static std::vector< std::string > listPluginIds()
Discover all installed plugin ids. A plugin is recognised by the presence of both <id>....
static uint64_t blockTotalBytes()
Total accessible size of the vfat volume in bytes.
static bool hostActive()
Reports whether a USB host currently holds the volume over MSC.
static std::string wasmPath(const std::string &id)
Returns the full VFS path of <id>.wasm.
static void remountIfPending()
Performs a deferred remount scheduled by setHostActive(false).
static std::string metaPath(const std::string &id)
Returns the full VFS path of <id>.meta.
static std::string binaryPath(const std::string &id)
Returns the path that should be loaded for <id>: <id>.aot if it exists on disk, otherwise <id>....
FilePtr openFile(const char *path, const char *mode) noexcept
Open a FILE* and wrap it in a FilePtr.
Definition Raii.h:87
static int vfat_drive()
static const char * SYSTEM_DIR
static void ensure_volume_label()
static bool s_remount_pending
static const char * MOUNT_POINT
static bool ends_with(const char *s, size_t s_len, const char *suffix, size_t suf_len)
static wl_handle_t s_wl_handle
static const char * PARTITION_LABEL
static const char * VOLUME_LABEL
static bool s_host_active
static const char * TAG
static bool usb_msc_should_remount(bool prev, bool cur)
Whether a host-active transition should trigger a badge remount.
static bool usb_msc_range_ok(uint64_t total_bytes, uint16_t block_size, uint32_t lba, uint32_t offset, uint32_t len, bool is_write)
Validates an MSC block access against the volume geometry.