Add embedded storage drivers and extend firmware metadata

This commit is contained in:
2026-09-27 01:44:59 +03:00
parent 795a1279b1
commit 2ad29e7ffd
52 changed files with 5801 additions and 3 deletions

View File

@@ -0,0 +1,177 @@
#include "sd_file_browser.h"
#include <stddef.h>
#include <string.h>
static uint8_t has_temperature_name(const char *name)
{
const size_t length = strlen(name);
const char prefix[] = "temperature_";
return (uint8_t)((length > (sizeof(prefix) - 1U + 3U)) &&
(strncmp(name, prefix, sizeof(prefix) - 1U) == 0) &&
(strcmp(name + length - 3U, ".md") == 0));
}
/* Сравнение расширения без учёта регистра для короткого суффикса ASCII. */
static uint8_t has_suffix_ci(const char *name, const char *suffix)
{
const size_t name_length = strlen(name);
const size_t suffix_length = strlen(suffix);
size_t index;
if (name_length <= suffix_length) {
return 0U;
}
for (index = 0U; index < suffix_length; ++index) {
char left = name[name_length - suffix_length + index];
char right = suffix[index];
if ((left >= 'A') && (left <= 'Z')) {
left = (char)(left - 'A' + 'a');
}
if ((right >= 'A') && (right <= 'Z')) {
right = (char)(right - 'A' + 'a');
}
if (left != right) {
return 0U;
}
}
return 1U;
}
/* Сырой образ прошивки: .bin или .fw. Intel HEX здесь не принимается — его
* адреса лежат внутри файла и требуют разбора, недоступного на этом этапе. */
static uint8_t has_firmware_name(const char *name)
{
return (uint8_t)((has_suffix_ci(name, ".bin") != 0U) ||
(has_suffix_ci(name, ".fw") != 0U));
}
/* Разрешены печатные ASCII-имена без разделителей ОС и управляющих байтов. */
static uint8_t path_character_is_safe(char character)
{
unsigned char value = (unsigned char)character;
return (uint8_t)((value >= 0x20U) && (value <= 0x7EU) &&
(character != '\\') && (character != ':'));
}
SdFileBrowserStatus SdFileBrowser_ValidatePath(const char *path)
{
size_t length;
size_t component_start;
size_t index;
/* strlen вызывается только после явной проверки внешнего указателя. */
if (path == NULL) {
return SD_FILE_BROWSER_INVALID_PATH;
}
length = strlen(path);
if ((length == 0U) || (length >= SD_FILE_BROWSER_MAX_PATH) ||
(path[0] != '/')) {
/* Абсолютный путь накопителя и усечённый путь fail-closed отклоняются. */
return SD_FILE_BROWSER_INVALID_PATH;
}
if (length == 1U) {
return SD_FILE_BROWSER_OK;
}
/* Компоненты анализируются за один проход, включая завершающий ноль. */
component_start = 1U;
for (index = 1U; index <= length; ++index) {
char character = path[index];
if ((character == '/') || (character == '\0')) {
size_t component_length = index - component_start;
if ((component_length == 0U) ||
(component_length > SD_FILE_BROWSER_MAX_NAME) ||
((component_length == 1U) &&
(path[component_start] == '.')) ||
((component_length == 2U) &&
(path[component_start] == '.') &&
(path[component_start + 1U] == '.'))) {
/* Пустые, точечные и слишком длинные компоненты не нормализуются. */
return SD_FILE_BROWSER_INVALID_PATH;
}
component_start = index + 1U;
} else if (path_character_is_safe(character) == 0U) {
/* Кодировка текущего FatFs-порта ASCII; байты UTF-8 не угадываются. */
return SD_FILE_BROWSER_INVALID_PATH;
}
}
return SD_FILE_BROWSER_OK;
}
SdFileBrowserStatus SdFileBrowser_ValidateLogFilePath(const char *path)
{
const char root[] = "/niceOne/mounth/";
const char *relative;
const char *separator;
const char *name;
SdFileBrowserStatus status = SdFileBrowser_ValidatePath(path);
if (status != SD_FILE_BROWSER_OK) {
return status;
}
/* TEMPLOG.MD сохранён только для старых карт, где журнал лежал в корне. */
if (strcmp(path, "/TEMPLOG.MD") == 0) {
return SD_FILE_BROWSER_OK;
}
if (strncmp(path, root, sizeof(root) - 1U) != 0) {
return SD_FILE_BROWSER_NOT_LOG_FILE;
}
relative = path + sizeof(root) - 1U;
separator = strchr(relative, '/');
/* Штатное дерево содержит ровно месяц и имя; обход дополнительных уровней запрещён. */
if ((separator == NULL) || (separator == relative) ||
(strchr(separator + 1U, '/') != NULL)) {
return SD_FILE_BROWSER_NOT_LOG_FILE;
}
name = strrchr(path, '/');
if ((name == NULL) || (has_temperature_name(name + 1U) == 0U)) {
return SD_FILE_BROWSER_NOT_LOG_FILE;
}
return SD_FILE_BROWSER_OK;
}
SdFileBrowserStatus SdFileBrowser_ValidateFirmwarePath(const char *path)
{
const char root[] = "/niceOne/firmware/";
const char *name;
SdFileBrowserStatus status = SdFileBrowser_ValidatePath(path);
if (status != SD_FILE_BROWSER_OK) {
return status;
}
/* Образы лежат ровно в одном каталоге; вложенные уровни не обходятся. */
if (strncmp(path, root, sizeof(root) - 1U) != 0) {
return SD_FILE_BROWSER_NOT_LOG_FILE;
}
name = path + sizeof(root) - 1U;
if ((name[0] == '\0') || (strchr(name, '/') != NULL)) {
return SD_FILE_BROWSER_NOT_LOG_FILE;
}
if (has_firmware_name(name) == 0U) {
return SD_FILE_BROWSER_NOT_LOG_FILE;
}
return SD_FILE_BROWSER_OK;
}
SdFileBrowserStatus SdFileBrowser_ListPage(
const SdFileBrowser *browser, const char *path, uint16_t offset,
uint8_t limit, SdFileBrowserPage *page)
{
SdFileBrowserStatus status;
/* limit проверяется до очистки, чтобы invalid call не трогал чужую память. */
if ((browser == NULL) || (browser->list_page == NULL) || (page == NULL) ||
(limit == 0U) || (limit > SD_FILE_BROWSER_MAX_PAGE_ITEMS)) {
return SD_FILE_BROWSER_INVALID_CURSOR;
}
status = SdFileBrowser_ValidatePath(path);
if (status != SD_FILE_BROWSER_OK) {
return status;
}
/* Нулевой tail исключает утечку содержимого предыдущего Modbus-ответа. */
memset(page, 0, sizeof(*page));
/* Portable слой не знает FatFs/HAL и передаёт только проверенный запрос. */
return browser->list_page(browser->context, path, offset, limit, page);
}