mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-08-20 08:55:06 +00:00
App metadata filtering in app-module
This commit is contained in:
parent
623ed9414c
commit
e75016f5cf
@ -13,6 +13,8 @@ extern "C" {
|
|||||||
#define APP_METADATA_APP_ID_LENGTH 32
|
#define APP_METADATA_APP_ID_LENGTH 32
|
||||||
#define APP_METADATA_APP_NAME_LENGTH 32
|
#define APP_METADATA_APP_NAME_LENGTH 32
|
||||||
#define APP_METADATA_APP_VERSION_NAME_LENGTH 16
|
#define APP_METADATA_APP_VERSION_NAME_LENGTH 16
|
||||||
|
#define APP_METADATA_REQUIRES_DEVICE_ARCH_LENGTH 32
|
||||||
|
#define APP_METADATA_REQUIRES_DEVICE_ID_LENGTH 64
|
||||||
|
|
||||||
struct AppMetadata {
|
struct AppMetadata {
|
||||||
|
|
||||||
@ -42,6 +44,20 @@ struct AppMetadata {
|
|||||||
|
|
||||||
/** The technical version (must be incremented with new releases of the app) */
|
/** The technical version (must be incremented with new releases of the app) */
|
||||||
uint64_t app_version_code;
|
uint64_t app_version_code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comma-separated list of MCU architectures the app is restricted to (e.g. "esp32s3,esp32p4"),
|
||||||
|
* matching ESP-IDF target names. Empty means unrestricted.
|
||||||
|
* Must be NULL-terminated.
|
||||||
|
*/
|
||||||
|
char requires_device_arch[APP_METADATA_REQUIRES_DEVICE_ARCH_LENGTH + 1];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comma-separated list of device ids the app is restricted to (e.g. "m5stack-tab5"), matching
|
||||||
|
* the folder names under Devices/. Empty means unrestricted.
|
||||||
|
* Must be NULL-terminated.
|
||||||
|
*/
|
||||||
|
char requires_device_id[APP_METADATA_REQUIRES_DEVICE_ID_LENGTH + 1];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -18,6 +18,12 @@ bool app_metadata_is_valid_name(const std::string& name);
|
|||||||
bool app_metadata_is_valid_version_name(const std::string& version);
|
bool app_metadata_is_valid_version_name(const std::string& version);
|
||||||
bool app_metadata_is_valid_version_code(const std::string& version);
|
bool app_metadata_is_valid_version_code(const std::string& version);
|
||||||
|
|
||||||
|
/** Validates a comma-separated list of MCU architecture names (alphanumeric items only). */
|
||||||
|
bool app_metadata_is_valid_device_arch_list(const std::string& value);
|
||||||
|
|
||||||
|
/** Validates a comma-separated list of device ids (alphanumeric + '-' items, matching Devices/<id> folder names). */
|
||||||
|
bool app_metadata_is_valid_device_id_list(const std::string& value);
|
||||||
|
|
||||||
/** Copies @a value into @a dest (a fixed-size buffer of @a dest_size bytes, including the NULL
|
/** Copies @a value into @a dest (a fixed-size buffer of @a dest_size bytes, including the NULL
|
||||||
* terminator) if it fits.
|
* terminator) if it fits.
|
||||||
* @retval false @a value doesn't fit in @a dest_size bytes - @a dest is left untouched */
|
* @retval false @a value doesn't fit in @a dest_size bytes - @a dest is left untouched */
|
||||||
|
|||||||
@ -37,6 +37,26 @@ bool validate_string(const std::string& value, bool (*is_valid_char)(char)) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Validates a comma-separated list: non-empty, no leading/trailing/double commas (which would
|
||||||
|
* produce an empty item), and every item passing @a is_valid_item_char. */
|
||||||
|
bool validate_csv_list(const std::string& value, bool (*is_valid_item_char)(char)) {
|
||||||
|
if (value.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
size_t start = 0;
|
||||||
|
while (true) {
|
||||||
|
auto comma = value.find(',', start);
|
||||||
|
auto end = comma == std::string::npos ? value.size() : comma;
|
||||||
|
if (end == start || !validate_string(value.substr(start, end - start), is_valid_item_char)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (comma == std::string::npos) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
start = comma + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** manifest.properties format: "key=value" lines, "[section]" lines prefix every following key
|
/** manifest.properties format: "key=value" lines, "[section]" lines prefix every following key
|
||||||
* until the next section, "#" lines are comments, blank lines are skipped. Deliberately a local,
|
* until the next section, "#" lines are comments, blank lines are skipped. Deliberately a local,
|
||||||
* minimal re-implementation rather than depending on Tactility's file::loadPropertiesFile() -
|
* minimal re-implementation rather than depending on Tactility's file::loadPropertiesFile() -
|
||||||
@ -130,6 +150,18 @@ bool app_metadata_is_valid_version_code(const std::string& version) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool app_metadata_is_valid_device_arch_list(const std::string& value) {
|
||||||
|
return validate_csv_list(value, [](char c) {
|
||||||
|
return std::isalnum(static_cast<unsigned char>(c)) != 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool app_metadata_is_valid_device_id_list(const std::string& value) {
|
||||||
|
return validate_csv_list(value, [](char c) {
|
||||||
|
return std::isalnum(static_cast<unsigned char>(c)) != 0 || c == '-';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
bool app_metadata_copy_bounded(char* dest, size_t dest_size, const std::string& value) {
|
bool app_metadata_copy_bounded(char* dest, size_t dest_size, const std::string& value) {
|
||||||
if (value.size() >= dest_size) {
|
if (value.size() >= dest_size) {
|
||||||
return false;
|
return false;
|
||||||
@ -141,6 +173,10 @@ bool app_metadata_copy_bounded(char* dest, size_t dest_size, const std::string&
|
|||||||
error_t app_metadata_parse(const char* path, struct AppMetadata* out_metadata) {
|
error_t app_metadata_parse(const char* path, struct AppMetadata* out_metadata) {
|
||||||
LOG_I(TAG, "Parsing manifest %s", path);
|
LOG_I(TAG, "Parsing manifest %s", path);
|
||||||
|
|
||||||
|
// Both requires.* fields are optional in V2 and unwritten by V1; zeroing here (rather than
|
||||||
|
// relying on the caller) guarantees they read back as empty ("unrestricted") either way.
|
||||||
|
*out_metadata = {};
|
||||||
|
|
||||||
std::map<std::string, std::string> properties;
|
std::map<std::string, std::string> properties;
|
||||||
std::string first_line;
|
std::string first_line;
|
||||||
if (!load_properties(path, properties, first_line)) {
|
if (!load_properties(path, properties, first_line)) {
|
||||||
|
|||||||
@ -99,5 +99,33 @@ bool app_metadata_parse_v2(const std::map<std::string, std::string>& properties,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// requires (optional; if present, must be a non-empty comma-separated list)
|
||||||
|
|
||||||
|
auto device_arch_iterator = properties.find("requires.device.arch");
|
||||||
|
if (device_arch_iterator != properties.end()) {
|
||||||
|
const std::string& device_arch = device_arch_iterator->second;
|
||||||
|
if (!app_metadata_is_valid_device_arch_list(device_arch)) {
|
||||||
|
LOG_E(TAG, "Invalid requires.device.arch");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!app_metadata_copy_bounded(out_metadata.requires_device_arch, sizeof(out_metadata.requires_device_arch), device_arch)) {
|
||||||
|
LOG_E(TAG, "requires.device.arch too long");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto device_id_iterator = properties.find("requires.device.id");
|
||||||
|
if (device_id_iterator != properties.end()) {
|
||||||
|
const std::string& device_id = device_id_iterator->second;
|
||||||
|
if (!app_metadata_is_valid_device_id_list(device_id)) {
|
||||||
|
LOG_E(TAG, "Invalid requires.device.id");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!app_metadata_copy_bounded(out_metadata.requires_device_id, sizeof(out_metadata.requires_device_id), device_id)) {
|
||||||
|
LOG_E(TAG, "requires.device.id too long");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user