Add embedded storage drivers and extend firmware metadata
This commit is contained in:
177
c/sd-file-browser/Src/sd_file_browser.c
Normal file
177
c/sd-file-browser/Src/sd_file_browser.c
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user