From 9104622628fc01235ecfc63748ed1a0df82df08e Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sun, 14 Jan 2024 18:11:55 +0100 Subject: [PATCH] implemented wifi credentials storage --- components/tactility-core/CMakeLists.txt | 2 +- .../src/app_manifest_registry.c | 14 +- components/tactility-core/src/hash.c | 11 + components/tactility-core/src/hash.h | 19 ++ components/tactility-core/src/kernel.h | 8 - components/tactility-core/src/secure.c | 141 ++++++++++++ components/tactility-core/src/secure.h | 57 +++++ components/tactility-core/src/tt_string.c | 1 + .../apps/system/wifi_connect/wifi_connect.c | 13 +- .../wifi_connect/wifi_connect_bindings.h | 2 +- .../system/wifi_connect/wifi_connect_view.c | 61 +++++- .../system/wifi_connect/wifi_connect_view.h | 3 + .../src/apps/system/wifi_manage/wifi_manage.c | 27 ++- components/tactility/src/services/gui/gui.c | 6 + .../tactility/src/services/gui/gui_draw.c | 23 +- components/tactility/src/services/wifi/wifi.c | 15 +- components/tactility/src/services/wifi/wifi.h | 2 +- .../src/services/wifi/wifi_credentials.c | 203 ++++++++++++++++++ .../src/services/wifi/wifi_credentials.h | 25 +++ components/tactility/src/tactility.c | 4 + 20 files changed, 589 insertions(+), 48 deletions(-) create mode 100644 components/tactility-core/src/hash.c create mode 100644 components/tactility-core/src/hash.h create mode 100644 components/tactility-core/src/secure.c create mode 100644 components/tactility-core/src/secure.h create mode 100644 components/tactility/src/services/wifi/wifi_credentials.c create mode 100644 components/tactility/src/services/wifi/wifi_credentials.h diff --git a/components/tactility-core/CMakeLists.txt b/components/tactility-core/CMakeLists.txt index f4232975..6e508360 100644 --- a/components/tactility-core/CMakeLists.txt +++ b/components/tactility-core/CMakeLists.txt @@ -1,5 +1,5 @@ idf_component_register( SRC_DIRS "src" INCLUDE_DIRS "src" - REQUIRES mlib cmsis_core + REQUIRES mlib cmsis_core mbedtls esp_hw_support nvs_flash ) diff --git a/components/tactility-core/src/app_manifest_registry.c b/components/tactility-core/src/app_manifest_registry.c index 7345a975..9e037d8b 100644 --- a/components/tactility-core/src/app_manifest_registry.c +++ b/components/tactility-core/src/app_manifest_registry.c @@ -21,22 +21,22 @@ DICT_DEF2(AppManifestDict, const char*, M_CSTR_DUP_OPLIST, const AppManifest*, M } AppManifestDict_t app_manifest_dict; -Mutex* mutex = NULL; +Mutex* hash_mutex = NULL; void tt_app_manifest_registry_init() { - tt_assert(mutex == NULL); - mutex = tt_mutex_alloc(MutexTypeNormal); + tt_assert(hash_mutex == NULL); + hash_mutex = tt_mutex_alloc(MutexTypeNormal); AppManifestDict_init(app_manifest_dict); } void app_registry_lock() { - tt_assert(mutex != NULL); - tt_mutex_acquire(mutex, TtWaitForever); + tt_assert(hash_mutex != NULL); + tt_mutex_acquire(hash_mutex, TtWaitForever); } void app_registry_unlock() { - tt_assert(mutex != NULL); - tt_mutex_release(mutex); + tt_assert(hash_mutex != NULL); + tt_mutex_release(hash_mutex); } void tt_app_manifest_registry_add(const AppManifest _Nonnull* manifest) { diff --git a/components/tactility-core/src/hash.c b/components/tactility-core/src/hash.c new file mode 100644 index 00000000..36cedf0f --- /dev/null +++ b/components/tactility-core/src/hash.c @@ -0,0 +1,11 @@ +#include "hash.h" + +uint32_t tt_hash_string_djb2(const char* str) { + uint32_t hash = 5381; + char c = (char)*str++; + while (c != 0) { + hash = ((hash << 5) + hash) + (uint32_t)c; // hash * 33 + c + c = (char)*str++; + } + return hash; +} diff --git a/components/tactility-core/src/hash.h b/components/tactility-core/src/hash.h new file mode 100644 index 00000000..9dc20e38 --- /dev/null +++ b/components/tactility-core/src/hash.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * This is quicker than the m-string.h hashing, as the latter + * operates on raw memory blocks and thus a strlen() call is required first. + * @param[in] str the string to calculate the hash for + * @return the hash + */ +uint32_t tt_hash_string_djb2(const char* str); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/components/tactility-core/src/kernel.h b/components/tactility-core/src/kernel.h index c1a430a7..e3302fab 100644 --- a/components/tactility-core/src/kernel.h +++ b/components/tactility-core/src/kernel.h @@ -83,14 +83,6 @@ void tt_delay_tick(uint32_t ticks); */ TtStatus tt_delay_until_tick(uint32_t tick); -/** Get current tick counter - * - * System uptime, may overflow. - * - * @return Current ticks in milliseconds - */ -uint32_t tt_get_tick(void); - /** Convert milliseconds to ticks * * @param[in] milliseconds time in milliseconds diff --git a/components/tactility-core/src/secure.c b/components/tactility-core/src/secure.c new file mode 100644 index 00000000..69314646 --- /dev/null +++ b/components/tactility-core/src/secure.c @@ -0,0 +1,141 @@ +#include "secure.h" + +#include "aes/esp_aes.h" +#include "check.h" +#include "esp_mac.h" +#include "esp_cpu.h" +#include "log.h" +#include "nvs_flash.h" +#include + +#define TAG "secure" +#define TT_NVS_NAMESPACE "tt_secure" + +/** + * Get a key based on hardware parameters. + * @param[out] key the output key + */ +static void get_hardware_key(uint8_t key[32]) { + uint8_t mac[8]; + // MAC can be 6 or 8 bytes + size_t mac_length = esp_mac_addr_len_get(ESP_MAC_EFUSE_FACTORY); + TT_LOG_I(TAG, "Using MAC with length %u", mac_length); + tt_check(mac_length <= 8); + ESP_ERROR_CHECK(esp_read_mac(mac, ESP_MAC_EFUSE_FACTORY)); + + // Fill buffer with repeating MAC + for (size_t i = 0; i < 32; ++i) { + key[i] = mac[i % mac_length]; + } +} + +/** + * The key is built up as follows: + * - Fetch 32 bytes from NVS storage and store as key data + * - Fetch 6-8 MAC bytes and overwrite the first 6-8 bytes of the key with this info + * + * When flash encryption is disabled: + * Without the MAC data, an attack would look like this: + * - Retrieve all the partitions from the ESP32 + * - Read the key from NVS flash + * - Use the key to decrypt + * With the MAC data added, an attacker would have to do much more: + * - Retrieve all the partitions from the ESP32 (copy app) + * - Upload custom app to retrieve internal MAC + * - Read the key from NVS flash + * - Re-flash original app and combine it with the MAC + * - Use the key to decrypt + * - Re-flash the device with original firmware. + * + * Adding the MAC doesn't add a lot of extra security, but I think it's worth it. + * + * @param[out] key the output key + */ +static void get_nvs_key(uint8_t key[32]) { + nvs_handle_t handle; + esp_err_t result = nvs_open(TT_NVS_NAMESPACE, NVS_READWRITE, &handle); + + if (result != ESP_OK) { + TT_LOG_E(TAG, "Failed to get key from NVS (%s)", esp_err_to_name(result)); + tt_crash(); + } + + size_t length = 32; + if (nvs_get_blob(handle, "key", key, &length) == ESP_OK) { + TT_LOG_I(TAG, "Fetched key from NVS (%d bytes)", length); + tt_check(length == 32); + } else { + esp_cpu_cycle_count_t cycle_count = esp_cpu_get_cycle_count(); + unsigned seed = (unsigned)cycle_count; + for (int i = 0; i < 32; ++i) { + key[i] = (uint8_t)rand_r(&seed); + } + ESP_ERROR_CHECK(nvs_set_blob(handle, "key", key, 32)); + TT_LOG_I(TAG, "Stored new key in NVS"); + } + + nvs_close(handle); +} + +/** + * Performs XOR on 2 memory regions and stores it in a third + * @param[in] in_left input buffer for XOR + * @param[in] in_right second input buffer for XOR + * @param[out] out output buffer for result of XOR + * @param[in] length data length (all buffers must be at least this size) + */ +static void xor_key(const uint8_t* in_left, const uint8_t* in_right, uint8_t* out, size_t length) { + for (int i = 0; i < length; ++i) { + out[i] = in_left[i] ^ in_right[i]; + } +} + +/** + * Combines a stored key and a hardware key into a single reliable key value. + * @param[out] key the key output + */ +static void get_key(uint8_t key[32]) { +#if !defined(CONFIG_SECURE_BOOT) || !defined(CONFIG_SECURE_FLASH_ENC_ENABLED) + TT_LOG_W(TAG, "Using tt_secure_* code with secure boot and/or flash encryption disabled."); + TT_LOG_W(TAG, "An attacker with physical access to your ESP32 can decrypt your secure data."); +#endif + + uint8_t hardware_key[32]; + uint8_t nvs_key[32]; + + get_hardware_key(hardware_key); + get_nvs_key(nvs_key); + xor_key(hardware_key, nvs_key, key, 32); +} + +int tt_secure_encrypt(const uint8_t iv[16], uint8_t* in_data, uint8_t* out_data, size_t length) { + tt_check(length % 16 == 0, "Length is not a multiple of 16 bytes (for AES 256"); + uint8_t key[32]; + get_key(key); + + uint8_t iv_copy[16]; + memcpy(iv_copy, iv, sizeof(iv_copy)); + + esp_aes_context ctx; + esp_aes_init(&ctx); + esp_aes_setkey(&ctx, key, 256); + int result = esp_aes_crypt_cbc(&ctx, ESP_AES_ENCRYPT, length, iv_copy, in_data, out_data); + esp_aes_free(&ctx); + return result; +} + +int tt_secure_decrypt(const uint8_t iv[16], uint8_t* in_data, uint8_t* out_data, size_t length) { + tt_check(length % 16 == 0, "Length is not a multiple of 16 bytes (for AES 256"); + uint8_t key[32]; + get_key(key); + + uint8_t iv_copy[16]; + memcpy(iv_copy, iv, sizeof(iv_copy)); + + esp_aes_context ctx; + esp_aes_init(&ctx); + esp_aes_setkey(&ctx, key, 256); + int result = esp_aes_crypt_cbc(&ctx, ESP_AES_DECRYPT, length, iv_copy, in_data, out_data); + esp_aes_free(&ctx); + return result; +} diff --git a/components/tactility-core/src/secure.h b/components/tactility-core/src/secure.h new file mode 100644 index 00000000..7a6b5ae1 --- /dev/null +++ b/components/tactility-core/src/secure.h @@ -0,0 +1,57 @@ +/** @file secure.h + * + * @brief Hardware-bound encryption methods. + * @warning Enable secure boot and flash encryption to increase security. + * + * Offers AES 256 CBC encryption with built-in key. + * The key is built from data including: + * - the internal factory MAC address + * - random data stored in NVS + * + * It's important to use flash encryption to avoid an attacker to get + * access to your encrypted data. If flash encryption is disabled, + * someone can fetch the key from the partitions. + * + * See: + * https://docs.espressif.com/projects/esp-idf/en/latest/esp32/security/secure-boot-v2.html + * https://docs.espressif.com/projects/esp-idf/en/latest/esp32/security/flash-encryption.html + */ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Encrypt data. + * + * Important: Use flash encryption to increase security. + * Important: input and output data must be aligned to 16 bytes. + * + * @param iv the AES IV + * @param data_in input data + * @param data_out output data + * @param length data length, a multiple of 16 + * @return the result of esp_aes_crypt_cbc() (MBEDTLS_ERR_*) + */ +int tt_secure_encrypt(const uint8_t iv[16], uint8_t* in_data, uint8_t* out_data, size_t length); + +/** + * @brief Decrypt data. + * + * Important: Use flash encryption to increase security. + * Important: input and output data must be aligned to 16 bytes. + * + * @param iv AES IV + * @param data_in input data + * @param data_out output data + * @param length data length, a multiple of 16 + * @return the result of esp_aes_crypt_cbc() (MBEDTLS_ERR_*) + */ +int tt_secure_decrypt(const uint8_t iv[16], uint8_t* in_data, uint8_t* out_data, size_t length); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/components/tactility-core/src/tt_string.c b/components/tactility-core/src/tt_string.c index 7f89427b..9370d817 100644 --- a/components/tactility-core/src/tt_string.c +++ b/components/tactility-core/src/tt_string.c @@ -3,6 +3,7 @@ struct TtString { string_t string; + // TODO store optional hash for quick string comparison }; #undef tt_string_alloc_set diff --git a/components/tactility/src/apps/system/wifi_connect/wifi_connect.c b/components/tactility/src/apps/system/wifi_connect/wifi_connect.c index 9c151889..9a81e6ff 100644 --- a/components/tactility/src/apps/system/wifi_connect/wifi_connect.c +++ b/components/tactility/src/apps/system/wifi_connect/wifi_connect.c @@ -6,6 +6,8 @@ #include "tactility_core.h" #include "wifi_connect_state_updating.h" +#define TAG "wifi_connect" + // Forward declarations static void wifi_connect_event_callback(const void* message, void* context); @@ -55,9 +57,12 @@ void wifi_connect_unlock(WifiConnect* wifi) { void wifi_connect_request_view_update(WifiConnect* wifi) { wifi_connect_lock(wifi); if (wifi->view_enabled) { - lvgl_port_lock(100); - wifi_connect_view_update(&wifi->view, &wifi->bindings, &wifi->state); - lvgl_port_unlock(); + if (lvgl_port_lock(1000)) { + wifi_connect_view_update(&wifi->view, &wifi->bindings, &wifi->state); + lvgl_port_unlock(); + } else { + TT_LOG_E(TAG, "failed to lock lvgl"); + } } wifi_connect_unlock(wifi); } @@ -87,6 +92,8 @@ static void app_show(App app, lv_obj_t* parent) { static void app_hide(App app) { WifiConnect* wifi = (WifiConnect*)tt_app_get_data(app); + // No need to lock view, as this is called from within Gui's LVGL context + wifi_connect_view_destroy(&wifi->view); wifi_connect_lock(wifi); wifi->view_enabled = false; wifi_connect_unlock(wifi); diff --git a/components/tactility/src/apps/system/wifi_connect/wifi_connect_bindings.h b/components/tactility/src/apps/system/wifi_connect/wifi_connect_bindings.h index 51678559..e3020075 100644 --- a/components/tactility/src/apps/system/wifi_connect/wifi_connect_bindings.h +++ b/components/tactility/src/apps/system/wifi_connect/wifi_connect_bindings.h @@ -2,7 +2,7 @@ #include -typedef void (*OnConnectSsid)(const char* ssid, const char* password, void* context); +typedef void (*OnConnectSsid)(const char* ssid, const char password[64], void* context); typedef struct { OnConnectSsid on_connect_ssid; diff --git a/components/tactility/src/apps/system/wifi_connect/wifi_connect_view.c b/components/tactility/src/apps/system/wifi_connect/wifi_connect_view.c index 35e48443..f3999b30 100644 --- a/components/tactility/src/apps/system/wifi_connect/wifi_connect_view.c +++ b/components/tactility/src/apps/system/wifi_connect/wifi_connect_view.c @@ -1,24 +1,54 @@ #include "wifi_connect_view.h" +#include "log.h" #include "lvgl.h" +#include "services/wifi/wifi_credentials.h" #include "ui/spacer.h" #include "ui/style.h" #include "wifi_connect.h" #include "wifi_connect_bundle.h" #include "wifi_connect_state.h" +#define TAG "wifi_connect" + +static void show_keyboard(lv_event_t* event) { + WifiConnectView* view = (WifiConnectView*)event->user_data; + lv_obj_clear_flag(view->keyboard, LV_OBJ_FLAG_HIDDEN); + lv_keyboard_set_textarea(view->keyboard, event->current_target); + // TODO: This doesn't work yet as most content is not scrollable + lv_obj_scroll_to_view(event->current_target, LV_ANIM_OFF); +} + +static void hide_keyboard(lv_event_t* event) { + WifiConnectView* view = (WifiConnectView*)event->user_data; + lv_obj_add_flag(view->keyboard, LV_OBJ_FLAG_HIDDEN); + lv_keyboard_set_textarea(view->keyboard, event->current_target); +} + static void on_connect(lv_event_t* event) { WifiConnect* wifi = (WifiConnect*)event->user_data; WifiConnectView* view = &wifi->view; const char* ssid = lv_textarea_get_text(view->ssid_textarea); const char* password = lv_textarea_get_text(view->password_textarea); + if (strlen(password) > 63) { + // TODO: UI feedback + TT_LOG_E(TAG, "Password too long"); + return; + } + char password_buffer[64]; + strcpy(password_buffer, password); + WifiConnectBindings* bindings = &wifi->bindings; bindings->on_connect_ssid( ssid, - password, + password_buffer, bindings->on_connect_ssid_context ); + + if (lv_obj_get_state(view->remember_switch) == LV_STATE_CHECKED) { + tt_wifi_credentials_set(ssid, password_buffer); + } } // TODO: Standardize dialogs @@ -47,6 +77,8 @@ void wifi_connect_view_create(App app, void* wifi, lv_obj_t* parent) { lv_textarea_set_password_show_time(view->password_textarea, 0); lv_textarea_set_password_mode(view->password_textarea, true); + tt_lv_spacer_create(parent, 1, 2); + lv_obj_t* button_container = lv_obj_create(parent); lv_obj_set_width(button_container, LV_PCT(100)); lv_obj_set_height(button_container, LV_SIZE_CONTENT); @@ -54,8 +86,16 @@ void wifi_connect_view_create(App app, void* wifi, lv_obj_t* parent) { lv_obj_set_style_border_width(button_container, 0, 0); lv_obj_set_flex_flow(button_container, LV_FLEX_FLOW_ROW); - lv_obj_t* spacer_left = tt_lv_spacer_create(button_container, 1, 1); - lv_obj_set_flex_grow(spacer_left, 1); + view->remember_switch = lv_switch_create(button_container); + lv_obj_add_state(view->remember_switch, LV_STATE_CHECKED); + + tt_lv_spacer_create(button_container, 2, 1); + + lv_obj_t* remember_label = lv_label_create(button_container); + lv_label_set_text(remember_label, "Remember"); + + lv_obj_t* spacer_center = tt_lv_spacer_create(button_container, 1, 1); + lv_obj_set_flex_grow(spacer_center, 1); view->connect_button = lv_btn_create(button_container); lv_obj_t* connect_label = lv_label_create(view->connect_button); @@ -63,6 +103,16 @@ void wifi_connect_view_create(App app, void* wifi, lv_obj_t* parent) { lv_obj_center(connect_label); lv_obj_add_event_cb(view->connect_button, &on_connect, LV_EVENT_CLICKED, wifi); + view->keyboard = lv_keyboard_create(lv_scr_act()); + lv_obj_add_flag(view->keyboard, LV_OBJ_FLAG_HIDDEN); + + lv_obj_add_event_cb(view->ssid_textarea, show_keyboard, LV_EVENT_FOCUSED, view); + lv_obj_add_event_cb(view->ssid_textarea, hide_keyboard, LV_EVENT_DEFOCUSED, view); + lv_obj_add_event_cb(view->ssid_textarea, hide_keyboard, LV_EVENT_READY, view); + lv_obj_add_event_cb(view->password_textarea, show_keyboard, LV_EVENT_FOCUSED, view); + lv_obj_add_event_cb(view->password_textarea, hide_keyboard, LV_EVENT_DEFOCUSED, view); + lv_obj_add_event_cb(view->password_textarea, hide_keyboard, LV_EVENT_READY, view); + // Init from app parameters Bundle* _Nullable bundle = tt_app_get_parameters(app); if (bundle) { @@ -78,6 +128,11 @@ void wifi_connect_view_create(App app, void* wifi, lv_obj_t* parent) { } } +void wifi_connect_view_destroy(WifiConnectView* view) { + lv_obj_del(view->keyboard); + view->keyboard = NULL; +} + void wifi_connect_view_update(WifiConnectView* view, WifiConnectBindings* bindings, WifiConnectState* state) { UNUSED(view); UNUSED(bindings); diff --git a/components/tactility/src/apps/system/wifi_connect/wifi_connect_view.h b/components/tactility/src/apps/system/wifi_connect/wifi_connect_view.h index 59595652..39a21ebb 100644 --- a/components/tactility/src/apps/system/wifi_connect/wifi_connect_view.h +++ b/components/tactility/src/apps/system/wifi_connect/wifi_connect_view.h @@ -14,10 +14,13 @@ typedef struct { lv_obj_t* password_textarea; lv_obj_t* connect_button; lv_obj_t* cancel_button; + lv_obj_t* remember_switch; + lv_obj_t* keyboard; } WifiConnectView; void wifi_connect_view_create(App app, void* wifi, lv_obj_t* parent); void wifi_connect_view_update(WifiConnectView* view, WifiConnectBindings* bindings, WifiConnectState* state); +void wifi_connect_view_destroy(WifiConnectView* view); #ifdef __cplusplus } diff --git a/components/tactility/src/apps/system/wifi_manage/wifi_manage.c b/components/tactility/src/apps/system/wifi_manage/wifi_manage.c index 8948dd07..29b9ab09 100644 --- a/components/tactility/src/apps/system/wifi_manage/wifi_manage.c +++ b/components/tactility/src/apps/system/wifi_manage/wifi_manage.c @@ -7,15 +7,25 @@ #include "tactility_core.h" #include "wifi_manage_state_updating.h" #include "wifi_manage_view.h" +#include "services/wifi/wifi_credentials.h" + +#define TAG "wifi_manage" // Forward declarations static void wifi_manage_event_callback(const void* message, void* context); static void on_connect(const char* ssid) { - Bundle bundle = tt_bundle_alloc(); - tt_bundle_put_string(bundle, WIFI_CONNECT_PARAM_SSID, ssid); - tt_bundle_put_string(bundle, WIFI_CONNECT_PARAM_PASSWORD, ""); // TODO: Implement from cache - loader_start_app("wifi_connect", false, bundle); + char password[TT_WIFI_CREDENTIALS_PASSWORD_LIMIT]; + if (tt_wifi_credentials_get(ssid, password)) { + TT_LOG_I(TAG, "Connecting with known credentials"); + wifi_connect(ssid, password); + } else { + TT_LOG_I(TAG, "Starting connection dialog"); + Bundle bundle = tt_bundle_alloc(); + tt_bundle_put_string(bundle, WIFI_CONNECT_PARAM_SSID, ssid); + tt_bundle_put_string(bundle, WIFI_CONNECT_PARAM_PASSWORD, ""); + loader_start_app("wifi_connect", false, bundle); + } } static void on_disconnect() { @@ -69,9 +79,12 @@ void wifi_manage_unlock(WifiManage* wifi) { void wifi_manage_request_view_update(WifiManage* wifi) { wifi_manage_lock(wifi); if (wifi->view_enabled) { - lvgl_port_lock(100); - wifi_manage_view_update(&wifi->view, &wifi->bindings, &wifi->state); - lvgl_port_unlock(); + if (lvgl_port_lock(1000)) { + wifi_manage_view_update(&wifi->view, &wifi->bindings, &wifi->state); + lvgl_port_unlock(); + } else { + TT_LOG_E(TAG, "failed to lock lvgl"); + } } wifi_manage_unlock(wifi); } diff --git a/components/tactility/src/services/gui/gui.c b/components/tactility/src/services/gui/gui.c index f79766a0..4f5fdbf6 100644 --- a/components/tactility/src/services/gui/gui.c +++ b/components/tactility/src/services/gui/gui.c @@ -70,7 +70,13 @@ void gui_hide_app() { gui_lock(); ViewPort* view_port = gui->app_view_port; tt_check(view_port != NULL); + + // We must lock the LVGL port, because the viewport hide callbacks + // might call LVGL APIs (e.g. to remove the keyboard from the screen root) + tt_check(lvgl_port_lock(1000)); view_port_hide(view_port); + lvgl_port_unlock(); + view_port_free(view_port); gui->app_view_port = NULL; gui_unlock(); diff --git a/components/tactility/src/services/gui/gui_draw.c b/components/tactility/src/services/gui/gui_draw.c index 29e43358..fd6534f9 100644 --- a/components/tactility/src/services/gui/gui_draw.c +++ b/components/tactility/src/services/gui/gui_draw.c @@ -55,21 +55,24 @@ void gui_redraw(Gui* gui) { // Lock GUI and LVGL gui_lock(); - tt_check(lvgl_port_lock(100)); - lv_obj_clean(gui->lvgl_parent); + if (lvgl_port_lock(1000)) { + lv_obj_clean(gui->lvgl_parent); - if (gui->app_view_port != NULL) { ViewPort* view_port = gui->app_view_port; - tt_assert(view_port); - App app = gui->app_view_port->app; - lv_obj_t* container = create_app_views(gui->lvgl_parent, app); - view_port_show(view_port, container); + if (view_port!= NULL) { + App app = view_port->app; + lv_obj_t* container = create_app_views(gui->lvgl_parent, app); + view_port_show(view_port, container); + } else { + TT_LOG_W(TAG, "nothing to draw"); + } + + // Unlock GUI and LVGL + lvgl_port_unlock(); } else { - TT_LOG_W(TAG, "nothing to draw"); + TT_LOG_E(TAG, "failed to lock lvgl"); } - // Unlock GUI and LVGL - lvgl_port_unlock(); gui_unlock(); } diff --git a/components/tactility/src/services/wifi/wifi.c b/components/tactility/src/services/wifi/wifi.c index 6b1ea610..1e892afb 100644 --- a/components/tactility/src/services/wifi/wifi.c +++ b/components/tactility/src/services/wifi/wifi.c @@ -119,14 +119,13 @@ bool wifi_is_scanning() { return wifi_singleton->scan_active; } -void wifi_connect(const char* ssid, const char* _Nullable password) { +void wifi_connect(const char* ssid, const char _Nullable password[64]) { tt_assert(wifi_singleton); tt_check(strlen(ssid) <= 32); - tt_check(password == NULL || strlen(password) <= 64); WifiMessage message = {.type = WifiMessageTypeConnect}; memcpy(message.connect_message.ssid, ssid, 32); if (password != NULL) { - memcpy(message.connect_message.password, password, 32); + memcpy(message.connect_message.password, password, 64); } else { message.connect_message.password[0] = 0; } @@ -227,15 +226,17 @@ static void wifi_publish_event_simple(Wifi* wifi, WifiEventType type) { static void event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { UNUSED(arg); - if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { - esp_wifi_connect(); + ESP_LOGI(TAG, "event_handler: sta start"); + if (wifi_singleton->radio_state == WIFI_RADIO_CONNECTION_PENDING) { + esp_wifi_connect(); + } } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { xEventGroupSetBits(wifi_singleton->event_group, WIFI_FAIL_BIT); - ESP_LOGI(TAG, "disconnected"); + ESP_LOGI(TAG, "event_handler: disconnected"); } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { ip_event_got_ip_t* event = (ip_event_got_ip_t*)event_data; - ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip)); + ESP_LOGI(TAG, "event_handler: got ip:" IPSTR, IP2STR(&event->ip_info.ip)); xEventGroupSetBits(wifi_singleton->event_group, WIFI_CONNECTED_BIT); } } diff --git a/components/tactility/src/services/wifi/wifi.h b/components/tactility/src/services/wifi/wifi.h index 9e2c2d48..81a38e89 100644 --- a/components/tactility/src/services/wifi/wifi.h +++ b/components/tactility/src/services/wifi/wifi.h @@ -86,7 +86,7 @@ void wifi_set_enabled(bool enabled); * @param ssid * @param password */ -void wifi_connect(const char* ssid, const char* _Nullable password); +void wifi_connect(const char* ssid, const char _Nullable password[64]); /** * @brief Disconnect from the access point. Doesn't have any effect when not connected. diff --git a/components/tactility/src/services/wifi/wifi_credentials.c b/components/tactility/src/services/wifi/wifi_credentials.c new file mode 100644 index 00000000..b95d23b5 --- /dev/null +++ b/components/tactility/src/services/wifi/wifi_credentials.c @@ -0,0 +1,203 @@ +#include "wifi_credentials.h" + +#include "nvs_flash.h" +#include "log.h" +#include "hash.h" +#include "check.h" +#include "mutex.h" + +#define TAG "wifi_credentials" +#define TT_NVS_NAMESPACE "tt_wifi_cred" // limited by NVS_KEY_NAME_MAX_SIZE +#define TT_NVS_PARTITION "nvs" + +static void tt_wifi_credentials_mutex_lock(); +static void tt_wifi_credentials_mutex_unlock(); + +// region Hash + +static Mutex* hash_mutex = NULL; +static int8_t ssid_hash_index = -1; +static uint32_t ssid_hashes[TT_WIFI_CREDENTIALS_LIMIT] = { 0 }; + +static int hash_find_value(uint32_t hash) { + tt_wifi_credentials_mutex_lock(); + for (int i = 0; i < ssid_hash_index; ++i) { + if (ssid_hashes[i] == hash) { + return i; + } + } + tt_wifi_credentials_mutex_unlock(); + return -1; +} + +static int hash_find_string(const char* ssid) { + uint32_t hash = tt_hash_string_djb2(ssid); + return hash_find_value(hash); +} + +static bool hash_contains_string(const char* ssid) { + return hash_find_string(ssid) != -1; +} + +static bool hash_contains_value(uint32_t value) { + return hash_find_value(value) != -1; +} + +static void hash_add(const char* ssid) { + uint32_t hash = tt_hash_string_djb2(ssid); + if (!hash_contains_value(hash)) { + tt_wifi_credentials_mutex_lock(); + tt_check((ssid_hash_index + 1) < TT_WIFI_CREDENTIALS_LIMIT, "exceeding wifi credentials list size"); + ssid_hash_index++; + ssid_hashes[ssid_hash_index] = hash; + tt_wifi_credentials_mutex_unlock(); + } +} + +static void hash_reset_all() { + ssid_hash_index = -1; +} + +// endregion Hash + +// region Wi-Fi Credentials - static + +static void tt_wifi_credentials_mutex_lock() { + tt_mutex_acquire(hash_mutex, TtWaitForever); +} + +static void tt_wifi_credentials_mutex_unlock() { + tt_mutex_release(hash_mutex); +} + +static esp_err_t tt_wifi_credentials_nvs_open(nvs_handle_t* handle, nvs_open_mode_t mode) { + return nvs_open(TT_NVS_NAMESPACE, NVS_READWRITE, handle); +} + +static void tt_wifi_credentials_nvs_close(nvs_handle_t handle) { + nvs_close(handle); +} + +static bool tt_wifi_credentials_contains_in_flash(const char* ssid) { + nvs_handle_t handle; + esp_err_t result = tt_wifi_credentials_nvs_open(&handle, NVS_READONLY); + if (result != ESP_OK) { + TT_LOG_E(TAG, "Failed to open NVS handle: %s", esp_err_to_name(result)); + return false; + } + + hash_reset_all(); + + nvs_iterator_t iterator; + result = nvs_entry_find(TT_NVS_PARTITION, TT_NVS_NAMESPACE, NVS_TYPE_BLOB, &iterator); + bool contains_ssid = false; + while (result == ESP_OK) { + nvs_entry_info_t info; + nvs_entry_info(iterator, &info); // Can omit error check if parameters are guaranteed to be non-NULL + if (strcmp(info.key, ssid) == 0) { + contains_ssid = true; + break; + } + result = nvs_entry_next(&iterator); + } + + nvs_release_iterator(iterator); + tt_wifi_credentials_nvs_close(handle); + + return contains_ssid; +} + +// endregion Wi-Fi Credentials - static + +// region Wi-Fi Credentials - public + +bool tt_wifi_credentials_contains(const char* ssid) { + uint32_t hash = tt_hash_string_djb2(ssid); + if (hash_contains_value(hash)) { + return tt_wifi_credentials_contains_in_flash(ssid); + } else { + return false; + } +} + +void tt_wifi_credentials_init() { + hash_reset_all(); + + if (hash_mutex == NULL) { + hash_mutex = tt_mutex_alloc(MutexTypeRecursive); + } + + nvs_handle_t handle; + esp_err_t result = tt_wifi_credentials_nvs_open(&handle, NVS_READWRITE); + if (result != ESP_OK) { + TT_LOG_E(TAG, "Failed to open NVS handle for init: %s", esp_err_to_name(result)); + return; + } + + nvs_iterator_t iterator; + result = nvs_entry_find(TT_NVS_PARTITION, TT_NVS_NAMESPACE, NVS_TYPE_BLOB, &iterator); + while (result == ESP_OK) { + nvs_entry_info_t info; + nvs_entry_info(iterator, &info); // Can omit error check if parameters are guaranteed to be non-NULL + hash_add(info.key); + result = nvs_entry_next(&iterator); + } + nvs_release_iterator(iterator); + + tt_wifi_credentials_nvs_close(handle); +} + +bool tt_wifi_credentials_get(const char* ssid, char password[TT_WIFI_CREDENTIALS_PASSWORD_LIMIT]) { + nvs_handle_t handle; + esp_err_t result = tt_wifi_credentials_nvs_open(&handle, NVS_READONLY); + if (result != ESP_OK) { + TT_LOG_E(TAG, "Failed to open NVS handle: %s", esp_err_to_name(result)); + return false; + } + + size_t length = TT_WIFI_CREDENTIALS_PASSWORD_LIMIT; + result = nvs_get_blob(handle, ssid, password, &length); + if (result != ESP_OK && result != ESP_ERR_NVS_NOT_FOUND) { + TT_LOG_E(TAG, "Failed to get credentials for \"%s\": %s", ssid, esp_err_to_name(result)); + } + + tt_wifi_credentials_nvs_close(handle); + return result == ESP_OK; +} + +bool tt_wifi_credentials_set(const char* ssid, char password[TT_WIFI_CREDENTIALS_PASSWORD_LIMIT]) { + nvs_handle_t handle; + esp_err_t result = tt_wifi_credentials_nvs_open(&handle, NVS_READWRITE); + if (result != ESP_OK) { + TT_LOG_E(TAG, "Failed to open NVS handle: %s", esp_err_to_name(result)); + return false; + } + + result = nvs_set_blob(handle, ssid, password, TT_WIFI_CREDENTIALS_PASSWORD_LIMIT); + if (result != ESP_OK) { + TT_LOG_E(TAG, "Failed to get credentials for \"%s\": %s", ssid, esp_err_to_name(result)); + } + + tt_wifi_credentials_nvs_close(handle); + return result == ESP_OK; +} + +bool tt_wifi_credentials_remove(const char* ssid) { + nvs_handle_t handle; + esp_err_t result = tt_wifi_credentials_nvs_open(&handle, NVS_READWRITE); + if (result != ESP_OK) { + TT_LOG_E(TAG, "Failed to open NVS handle to store \"%s\": %s", ssid, esp_err_to_name(result)); + return false; + } + + result = nvs_erase_key(handle, ssid); + if (result != ESP_OK) { + TT_LOG_E(TAG, "Failed to erase credentials for \"%s\": %s", ssid, esp_err_to_name(result)); + } + + tt_wifi_credentials_nvs_close(handle); + return result == ESP_OK; +} + +// end region Wi-Fi Credentials - public + diff --git a/components/tactility/src/services/wifi/wifi_credentials.h b/components/tactility/src/services/wifi/wifi_credentials.h new file mode 100644 index 00000000..dee09e73 --- /dev/null +++ b/components/tactility/src/services/wifi/wifi_credentials.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define TT_WIFI_CREDENTIALS_PASSWORD_LIMIT 64 // Should be equal to wifi_sta_config_t.password +// TODO: Move to config file +#define TT_WIFI_CREDENTIALS_LIMIT 16 + +void tt_wifi_credentials_init(); + +bool tt_wifi_credentials_contains(const char* ssid); + +bool tt_wifi_credentials_get(const char* ssid, char password[TT_WIFI_CREDENTIALS_PASSWORD_LIMIT]); + +bool tt_wifi_credentials_set(const char* ssid, char password[TT_WIFI_CREDENTIALS_PASSWORD_LIMIT]); + +bool tt_wifi_credentials_remove(const char* ssid); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/components/tactility/src/tactility.c b/components/tactility/src/tactility.c index aa636a2e..a4e7f445 100644 --- a/components/tactility/src/tactility.c +++ b/components/tactility/src/tactility.c @@ -9,6 +9,7 @@ #include "partitions.h" #include "service_registry.h" #include "services/loader/loader.h" +#include "services/wifi/wifi_credentials.h" #include #define TAG "tactility" @@ -81,12 +82,15 @@ __attribute__((unused)) extern void tactility_start(const Config* _Nonnull confi // Initialize NVS esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + TT_LOG_I(TAG, "nvs erasing"); ESP_ERROR_CHECK(nvs_flash_erase()); ret = nvs_flash_init(); } ESP_ERROR_CHECK(ret); + TT_LOG_I(TAG, "nvs initialized"); tt_partitions_init(); + tt_wifi_credentials_init(); Hardware hardware = tt_hardware_init(config->hardware); /*NbLvgl lvgl =*/tt_graphics_init(&hardware);