Merge remote-tracking branch 'OFW/dev' into dev

This commit is contained in:
MX
2025-09-24 22:32:16 +03:00
72 changed files with 1940 additions and 172 deletions

View File

@@ -418,6 +418,7 @@ static void bt_change_profile(Bt* bt, BtMessage* message) {
bt->current_profile = furi_hal_bt_change_app(
message->data.profile.template,
message->data.profile.params,
bt_keys_storage_get_root_keys(bt->keys_storage),
bt_on_gap_event_callback,
bt);
if(bt->current_profile) {
@@ -473,7 +474,6 @@ static void bt_load_keys(Bt* bt) {
bt_keys_storage_load(bt->keys_storage);
bt->current_profile = NULL;
} else {
FURI_LOG_I(TAG, "Keys unchanged");
}
@@ -481,8 +481,12 @@ static void bt_load_keys(Bt* bt) {
static void bt_start_application(Bt* bt) {
if(!bt->current_profile) {
bt->current_profile =
furi_hal_bt_change_app(ble_profile_serial, NULL, bt_on_gap_event_callback, bt);
bt->current_profile = furi_hal_bt_change_app(
ble_profile_serial,
NULL,
bt_keys_storage_get_root_keys(bt->keys_storage),
bt_on_gap_event_callback,
bt);
if(!bt->current_profile) {
FURI_LOG_E(TAG, "BLE App start failed");

View File

@@ -2,21 +2,92 @@
#include <furi.h>
#include <furi_hal_bt.h>
#include <lib/toolbox/saved_struct.h>
#include <storage/storage.h>
#include <furi_hal_random.h>
#define BT_KEYS_STORAGE_VERSION (0)
#define BT_KEYS_STORAGE_MAGIC (0x18)
#include <gap.h>
#include <storage/storage.h>
#include <toolbox/saved_struct.h>
#define BT_KEYS_STORAGE_MAGIC (0x18)
#define BT_KEYS_STORAGE_VERSION (1)
#define BT_KEYS_STORAGE_LEGACY_VERSION (0) // Legacy version with no root keys
#define TAG "BtKeyStorage"
// Identity root key
static const uint8_t gap_legacy_irk[16] =
{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0};
// Encryption root key
static const uint8_t gap_legacy_erk[16] =
{0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21};
struct BtKeysStorage {
uint8_t* nvm_sram_buff;
uint16_t nvm_sram_buff_size;
uint16_t current_size;
FuriString* file_path;
GapRootSecurityKeys root_keys;
};
typedef struct {
GapRootSecurityKeys root_keys;
uint8_t pairing_data[];
} BtKeysStorageFile;
static bool bt_keys_storage_save(BtKeysStorage* instance) {
furi_assert(instance);
size_t total_size = sizeof(BtKeysStorageFile) + instance->current_size;
BtKeysStorageFile* save_data = malloc(total_size);
memcpy(&save_data->root_keys, &instance->root_keys, sizeof(GapRootSecurityKeys));
furi_hal_bt_nvm_sram_sem_acquire();
memcpy(save_data->pairing_data, instance->nvm_sram_buff, instance->current_size);
furi_hal_bt_nvm_sram_sem_release();
bool saved = saved_struct_save(
furi_string_get_cstr(instance->file_path),
save_data,
sizeof(GapRootSecurityKeys) + instance->current_size,
BT_KEYS_STORAGE_MAGIC,
BT_KEYS_STORAGE_VERSION);
free(save_data);
return saved;
}
static bool bt_keys_storage_load_keys_and_pairings(
BtKeysStorage* instance,
const uint8_t* file_data,
size_t data_size) {
if(data_size < sizeof(GapRootSecurityKeys)) {
return false;
}
const BtKeysStorageFile* loaded = (const BtKeysStorageFile*)file_data;
memcpy(&instance->root_keys, &loaded->root_keys, sizeof(GapRootSecurityKeys));
size_t ble_data_size = data_size - sizeof(GapRootSecurityKeys);
if(ble_data_size > instance->nvm_sram_buff_size) {
FURI_LOG_E(TAG, "BLE data too large for SRAM buffer");
return false;
}
furi_hal_bt_nvm_sram_sem_acquire();
memcpy(instance->nvm_sram_buff, loaded->pairing_data, ble_data_size);
instance->current_size = ble_data_size;
furi_hal_bt_nvm_sram_sem_release();
return true;
}
static void bt_keys_storage_regenerate_root_keys(BtKeysStorage* instance) {
furi_hal_random_fill_buf(instance->root_keys.erk, sizeof(instance->root_keys.erk));
furi_hal_random_fill_buf(instance->root_keys.irk, sizeof(instance->root_keys.irk));
}
bool bt_keys_storage_delete(BtKeysStorage* instance) {
furi_assert(instance);
@@ -25,6 +96,15 @@ bool bt_keys_storage_delete(BtKeysStorage* instance) {
furi_hal_bt_stop_advertising();
delete_succeed = furi_hal_bt_clear_white_list();
FURI_LOG_I(TAG, "Root keys regen");
bt_keys_storage_regenerate_root_keys(instance);
instance->current_size = 0;
if(!bt_keys_storage_save(instance)) {
FURI_LOG_E(TAG, "Save after delete failed");
}
if(bt_is_active) {
furi_hal_bt_start_advertising();
}
@@ -42,6 +122,7 @@ BtKeysStorage* bt_keys_storage_alloc(const char* keys_storage_path) {
instance->file_path = furi_string_alloc();
furi_string_set_str(instance->file_path, keys_storage_path);
bt_keys_storage_regenerate_root_keys(instance);
return instance;
}
@@ -76,20 +157,31 @@ static bool bt_keys_storage_file_exists(const char* file_path) {
return ret;
}
static bool bt_keys_storage_validate_file(const char* file_path, size_t* payload_size) {
static bool bt_keys_storage_validate_file(
const char* file_path,
size_t* payload_size,
uint8_t* file_version) {
uint8_t magic, version;
size_t size;
if(!saved_struct_get_metadata(file_path, &magic, &version, &size)) {
FURI_LOG_E(TAG, "Failed to get metadata");
FURI_LOG_W(TAG, "Failed to get metadata");
return false;
} else if(magic != BT_KEYS_STORAGE_MAGIC || version != BT_KEYS_STORAGE_VERSION) {
FURI_LOG_E(TAG, "File version mismatch");
} else if(magic != BT_KEYS_STORAGE_MAGIC) {
FURI_LOG_W(TAG, "File magic mismatch");
return false;
} else if(version > BT_KEYS_STORAGE_VERSION) {
FURI_LOG_E(
TAG,
"File version %d is newer than supported version %d",
version,
BT_KEYS_STORAGE_VERSION);
return false;
}
*payload_size = size;
*file_version = version;
return true;
}
@@ -102,32 +194,45 @@ bool bt_keys_storage_is_changed(BtKeysStorage* instance) {
do {
const char* file_path = furi_string_get_cstr(instance->file_path);
size_t payload_size;
uint8_t file_version;
if(!bt_keys_storage_file_exists(file_path)) {
FURI_LOG_W(TAG, "Missing or empty file");
is_changed = true;
break;
}
} else if(!bt_keys_storage_validate_file(file_path, &payload_size)) {
FURI_LOG_E(TAG, "Invalid or corrupted file");
if(!bt_keys_storage_validate_file(file_path, &payload_size, &file_version)) {
FURI_LOG_W(TAG, "Invalid or corrupted file");
is_changed = true;
break;
}
// Early check for legacy version: always considered changed, no need to load
if(file_version == BT_KEYS_STORAGE_LEGACY_VERSION) {
is_changed = true;
break;
}
data_buffer = malloc(payload_size);
const bool data_loaded = saved_struct_load(
file_path, data_buffer, payload_size, BT_KEYS_STORAGE_MAGIC, BT_KEYS_STORAGE_VERSION);
file_path, data_buffer, payload_size, BT_KEYS_STORAGE_MAGIC, file_version);
if(!data_loaded) {
FURI_LOG_E(TAG, "Failed to load file");
break;
}
} else if(payload_size == instance->current_size) {
// At this point, it's version 1 file we have
const BtKeysStorageFile* loaded = (const BtKeysStorageFile*)data_buffer;
size_t expected_file_size = sizeof(GapRootSecurityKeys) + instance->current_size;
if(payload_size == expected_file_size) {
furi_hal_bt_nvm_sram_sem_acquire();
is_changed = memcmp(data_buffer, instance->nvm_sram_buff, payload_size);
is_changed =
memcmp(loaded->pairing_data, instance->nvm_sram_buff, instance->current_size);
furi_hal_bt_nvm_sram_sem_release();
} else {
FURI_LOG_D(TAG, "Size mismatch");
FURI_LOG_D(TAG, "NVRAM sz mismatch (v1)");
is_changed = true;
}
} while(false);
@@ -139,45 +244,59 @@ bool bt_keys_storage_is_changed(BtKeysStorage* instance) {
return is_changed;
}
static bool bt_keys_storage_load_legacy_pairings(
BtKeysStorage* instance,
const uint8_t* file_data,
size_t payload_size) {
FURI_LOG_I(TAG, "Loaded v0, upgrading to v1");
memcpy(instance->root_keys.irk, gap_legacy_irk, sizeof(instance->root_keys.irk));
memcpy(instance->root_keys.erk, gap_legacy_erk, sizeof(instance->root_keys.erk));
if(payload_size > instance->nvm_sram_buff_size) {
FURI_LOG_E(TAG, "Pairing too large");
return false;
}
furi_hal_bt_nvm_sram_sem_acquire();
memcpy(instance->nvm_sram_buff, file_data, payload_size);
instance->current_size = payload_size;
furi_hal_bt_nvm_sram_sem_release();
if(!bt_keys_storage_save(instance)) {
FURI_LOG_W(TAG, "Upgrade to v1 failed");
} else {
FURI_LOG_I(TAG, "Upgraded to v1");
}
return true;
}
bool bt_keys_storage_load(BtKeysStorage* instance) {
furi_assert(instance);
const char* file_path = furi_string_get_cstr(instance->file_path);
size_t payload_size;
uint8_t file_version;
if(!bt_keys_storage_validate_file(file_path, &payload_size, &file_version)) {
FURI_LOG_E(TAG, "Invalid or corrupted file");
return false;
}
bool loaded = false;
uint8_t* file_data = malloc(payload_size);
do {
const char* file_path = furi_string_get_cstr(instance->file_path);
// Get payload size
size_t payload_size;
if(!bt_keys_storage_validate_file(file_path, &payload_size)) {
FURI_LOG_E(TAG, "Invalid or corrupted file");
break;
} else if(payload_size > instance->nvm_sram_buff_size) {
FURI_LOG_E(TAG, "NVM RAM buffer overflow");
if(!saved_struct_load(
file_path, file_data, payload_size, BT_KEYS_STORAGE_MAGIC, file_version)) {
FURI_LOG_E(TAG, "Failed to load");
break;
}
// Load saved data to ram
furi_hal_bt_nvm_sram_sem_acquire();
const bool data_loaded = saved_struct_load(
file_path,
instance->nvm_sram_buff,
payload_size,
BT_KEYS_STORAGE_MAGIC,
BT_KEYS_STORAGE_VERSION);
furi_hal_bt_nvm_sram_sem_release();
if(!data_loaded) {
FURI_LOG_E(TAG, "Failed to load file");
if(file_version == BT_KEYS_STORAGE_LEGACY_VERSION) {
loaded = bt_keys_storage_load_legacy_pairings(instance, file_data, payload_size);
break;
}
instance->current_size = payload_size;
loaded = true;
// Only v1 left
loaded = bt_keys_storage_load_keys_and_pairings(instance, file_data, payload_size);
} while(false);
free(file_data);
return loaded;
}
@@ -203,14 +322,8 @@ bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32
instance->current_size = new_size;
furi_hal_bt_nvm_sram_sem_acquire();
bool data_updated = saved_struct_save(
furi_string_get_cstr(instance->file_path),
instance->nvm_sram_buff,
new_size,
BT_KEYS_STORAGE_MAGIC,
BT_KEYS_STORAGE_VERSION);
furi_hal_bt_nvm_sram_sem_release();
// Save using version 1 format with embedded root keys
bool data_updated = bt_keys_storage_save(instance);
if(!data_updated) {
FURI_LOG_E(TAG, "Failed to update key storage");
@@ -222,3 +335,9 @@ bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32
return updated;
}
const GapRootSecurityKeys* bt_keys_storage_get_root_keys(BtKeysStorage* instance) {
furi_assert(instance);
return &instance->root_keys;
}

View File

@@ -3,6 +3,8 @@
#include <stdint.h>
#include <stdbool.h>
#include <gap.h>
#ifdef __cplusplus
extern "C" {
#endif
@@ -19,6 +21,8 @@ void bt_keys_storage_set_ram_params(BtKeysStorage* instance, uint8_t* buff, uint
bool bt_keys_storage_is_changed(BtKeysStorage* instance);
const GapRootSecurityKeys* bt_keys_storage_get_root_keys(BtKeysStorage* instance);
bool bt_keys_storage_load(BtKeysStorage* instance);
bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32_t size);

View File

@@ -2,6 +2,7 @@
#include "bt_settings_filename.h"
#include <furi.h>
#include <storage/storage.h>
#include <toolbox/saved_struct.h>
@@ -14,12 +15,13 @@
void bt_settings_load(BtSettings* bt_settings) {
furi_assert(bt_settings);
const bool success = saved_struct_load(
const bool load_success = saved_struct_load(
BT_SETTINGS_PATH, bt_settings, sizeof(BtSettings), BT_SETTINGS_MAGIC, BT_SETTINGS_VERSION);
if(!success) {
if(!load_success) {
FURI_LOG_W(TAG, "Failed to load settings, using defaults");
memset(bt_settings, 0, sizeof(BtSettings));
bt_settings->enabled = false;
bt_settings_save(bt_settings);
}
}

View File

@@ -93,12 +93,17 @@ static const FrameBubble*
bubble_animation_pick_bubble(BubbleAnimationViewModel* model, bool active) {
const FrameBubble* bubble = NULL;
if((model->active_bubbles == 0) && (model->passive_bubbles == 0)) {
// Check for division by zero based on the active parameter
if((active && model->active_bubbles == 0) || (!active && model->passive_bubbles == 0)) {
return NULL;
}
uint8_t index =
furi_hal_random_get() % (active ? model->active_bubbles : model->passive_bubbles);
uint8_t random_value = furi_hal_random_get();
// In case random generator return zero lets set it to 3
if(random_value == 0) {
random_value = 3;
}
uint8_t index = random_value % (active ? model->active_bubbles : model->passive_bubbles);
const BubbleAnimation* animation = model->current;
for(int i = 0; i < animation->frame_bubble_sequences_count; ++i) {

View File

@@ -29,7 +29,7 @@ typedef struct {
void* context;
} CanvasCallbackPair;
ARRAY_DEF(CanvasCallbackPairArray, CanvasCallbackPair, M_POD_OPLIST);
ARRAY_DEF(CanvasCallbackPairArray, CanvasCallbackPair, M_POD_OPLIST); //-V658
#define M_OPL_CanvasCallbackPairArray_t() ARRAY_OPLIST(CanvasCallbackPairArray, M_POD_OPLIST)

View File

@@ -912,7 +912,7 @@ void elements_text_box(
for(size_t i = 0; i < line_num; i++) {
for(size_t j = 0; j < line[i].len; j++) {
// Process format symbols
if(line[i].text[j] == '\e' && j < line[i].len - 1) {
if(line[i].text[j] == '\e' && j < line[i].len - 1) { //-V781
++j;
if(line[i].text[j] == ELEMENTS_BOLD_MARKER) {
if(bold) {

View File

@@ -25,7 +25,7 @@ struct ButtonMenuItem {
void* callback_context;
};
ARRAY_DEF(ButtonMenuItemArray, ButtonMenuItem, M_POD_OPLIST);
ARRAY_DEF(ButtonMenuItemArray, ButtonMenuItem, M_POD_OPLIST); //-V658
struct ButtonMenu {
View* view;

View File

@@ -109,7 +109,7 @@ void button_panel_reserve(ButtonPanel* button_panel, size_t reserve_x, size_t re
furi_check(reserve_x > 0);
furi_check(reserve_y > 0);
with_view_model(
with_view_model( //-V621
button_panel->view,
ButtonPanelModel * model,
{

View File

@@ -32,8 +32,8 @@ typedef enum {
(WorkerEvtStop | WorkerEvtLoad | WorkerEvtFolderEnter | WorkerEvtFolderExit | \
WorkerEvtFolderRefresh | WorkerEvtConfigChange)
ARRAY_DEF(IdxLastArray, int32_t)
ARRAY_DEF(ExtFilterArray, FuriString*, FURI_STRING_OPLIST)
ARRAY_DEF(IdxLastArray, int32_t) //-V658
ARRAY_DEF(ExtFilterArray, FuriString*, FURI_STRING_OPLIST) //-V658
struct BrowserWorker {
FuriThread* thread;

View File

@@ -17,7 +17,7 @@ typedef struct {
void* callback_context;
} MenuItem;
ARRAY_DEF(MenuItemArray, MenuItem, M_POD_OPLIST);
ARRAY_DEF(MenuItemArray, MenuItem, M_POD_OPLIST); //-V658
#define M_OPL_MenuItemArray_t() ARRAY_OPLIST(MenuItemArray, M_POD_OPLIST)

View File

@@ -93,13 +93,12 @@ static bool popup_view_input_callback(InputEvent* event, void* context) {
void popup_start_timer(void* context) {
Popup* popup = context;
if(popup->timer_enabled) {
uint32_t timer_period =
popup->timer_period_in_ms / (1000.0f / furi_kernel_get_tick_frequency());
uint32_t timer_period = furi_ms_to_ticks(popup->timer_period_in_ms);
if(timer_period == 0) timer_period = 1;
if(furi_timer_start(popup->timer, timer_period) != FuriStatusOk) {
furi_crash();
};
}
}
}

View File

@@ -41,7 +41,7 @@ void popup_free(Popup* popup);
*/
View* popup_get_view(Popup* popup);
/** Set popup callback function
/** Set popup timeout callback
*
* @param popup Popup instance
* @param callback PopupCallback

View File

@@ -17,7 +17,7 @@ struct VariableItem {
void* context;
};
ARRAY_DEF(VariableItemArray, VariableItem, M_POD_OPLIST);
ARRAY_DEF(VariableItemArray, VariableItem, M_POD_OPLIST); //-V658
struct VariableItemList {
View* view;

View File

@@ -10,7 +10,7 @@ typedef struct {
FuriString* text;
} TextScrollLineArray;
ARRAY_DEF(TextScrollLineArray, TextScrollLineArray, M_POD_OPLIST)
ARRAY_DEF(TextScrollLineArray, TextScrollLineArray, M_POD_OPLIST) //-V658
typedef struct {
TextScrollLineArray_t line_array;

View File

@@ -8,7 +8,7 @@
#include "scene_manager.h"
#include <m-array.h>
ARRAY_DEF(SceneManagerIdStack, uint32_t, M_DEFAULT_OPLIST);
ARRAY_DEF(SceneManagerIdStack, uint32_t, M_DEFAULT_OPLIST); //-V658
typedef struct {
uint32_t state;

View File

@@ -88,6 +88,7 @@ static void loader_show_gui_error(
LoaderMessageLoaderStatusResult status,
const char* name,
FuriString* error_message) {
furi_check(name);
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
DialogMessage* message = dialog_message_alloc();
@@ -654,6 +655,8 @@ static LoaderMessageLoaderStatusResult loader_do_start_by_name(
status.value = loader_make_success_status(error_message);
status.error = LoaderStatusErrorUnknown;
if(name == NULL) return status;
do {
// check lock
if(loader_do_is_locked(loader)) {
@@ -885,7 +888,10 @@ int32_t loader_srv(void* p) {
switch(message.type) {
case LoaderMessageTypeStartByName: {
LoaderMessageLoaderStatusResult status = loader_do_start_by_name(
loader, message.start.name, message.start.args, message.start.error_message);
loader,
message.start.name,
message.start.args,
message.start.error_message); //-V595
*(message.status_value) = status;
if(status.value != LoaderStatusOk) loader_do_emit_queue_empty_event(loader);
api_lock_unlock(message.api_lock);
@@ -894,7 +900,7 @@ int32_t loader_srv(void* p) {
case LoaderMessageTypeStartByNameDetachedWithGuiError: {
FuriString* error_message = furi_string_alloc();
LoaderMessageLoaderStatusResult status = loader_do_start_by_name(
loader, message.start.name, message.start.args, error_message);
loader, message.start.name, message.start.args, error_message); //-V595
loader_show_gui_error(status, message.start.name, error_message);
if(status.value != LoaderStatusOk) loader_do_emit_queue_empty_event(loader);
if(message.start.name) free((void*)message.start.name);

View File

@@ -293,7 +293,7 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex
finish = true;
}
while(!finish) {
while(!finish) { //-V1044
FileInfo fileinfo;
char* name = malloc(MAX_NAME_LENGTH + 1);
if(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) {

View File

@@ -30,7 +30,8 @@ static void rpc_system_system_ping_process(const PB_Main* request, void* context
}
PB_Main response = PB_Main_init_default;
response.command_status = PB_CommandStatus_OK;
// PB_CommandStatus_OK is 0 in current case, and var by default is 0 too, commenting PVS warn
response.command_status = PB_CommandStatus_OK; //-V1048
response.command_id = request->command_id;
response.which_content = PB_Main_system_ping_response_tag;