/* * fb_config.c * NVS-backed storage for known WiFi networks and C2 fallback addresses. * Namespace: "fb_cfg" — auto-migrates from old "rt_cfg" on first boot. */ #include "sdkconfig.h" #include "fb_config.h" #ifdef CONFIG_MODULE_FALLBACK #include #include #include "nvs_flash.h" #include "nvs.h" #include "esp_log.h" #include "esp_wifi.h" static const char *TAG = "FB_CFG"; static const char *NVS_NS = "fb_cfg"; /* ============================================================ * NVS migration from old rt_cfg namespace * ============================================================ */ static void migrate_from_rt_cfg(void) { nvs_handle_t old_h, new_h; if (nvs_open("rt_cfg", NVS_READONLY, &old_h) != ESP_OK) return; /* No old data */ if (nvs_open(NVS_NS, NVS_READWRITE, &new_h) != ESP_OK) { nvs_close(old_h); return; } /* Check if already migrated */ int32_t new_count = -1; if (nvs_get_i32(new_h, "fb_count", &new_count) == ESP_OK && new_count >= 0) { nvs_close(old_h); nvs_close(new_h); return; } /* Copy network count */ int32_t old_count = 0; nvs_get_i32(old_h, "rt_count", &old_count); nvs_set_i32(new_h, "fb_count", old_count); /* Copy each network entry */ char key[16]; for (int i = 0; i < old_count && i < CONFIG_FB_MAX_KNOWN_NETWORKS; i++) { char buf[FB_PASS_MAX_LEN]; size_t len; snprintf(key, sizeof(key), "n_%d", i); len = FB_SSID_MAX_LEN; memset(buf, 0, sizeof(buf)); if (nvs_get_str(old_h, key, buf, &len) == ESP_OK) nvs_set_str(new_h, key, buf); snprintf(key, sizeof(key), "p_%d", i); len = FB_PASS_MAX_LEN; memset(buf, 0, sizeof(buf)); if (nvs_get_str(old_h, key, buf, &len) == ESP_OK) nvs_set_str(new_h, key, buf); } /* Copy C2 fallbacks */ int32_t c2_count = 0; nvs_get_i32(old_h, "c2_count", &c2_count); nvs_set_i32(new_h, "c2_count", c2_count); for (int i = 0; i < c2_count && i < CONFIG_FB_MAX_C2_FALLBACKS; i++) { char buf[FB_ADDR_MAX_LEN]; size_t len = FB_ADDR_MAX_LEN; snprintf(key, sizeof(key), "c2_%d", i); memset(buf, 0, sizeof(buf)); if (nvs_get_str(old_h, key, buf, &len) == ESP_OK) nvs_set_str(new_h, key, buf); } /* Copy original MAC */ uint8_t mac[6]; size_t mac_len = 6; if (nvs_get_blob(old_h, "orig_mac", mac, &mac_len) == ESP_OK) nvs_set_blob(new_h, "orig_mac", mac, 6); nvs_commit(new_h); nvs_close(old_h); nvs_close(new_h); ESP_LOGI(TAG, "Migrated %d networks + %d C2 fallbacks from rt_cfg", (int)old_count, (int)c2_count); } /* ============================================================ * Init * ============================================================ */ void fb_config_init(void) { migrate_from_rt_cfg(); nvs_handle_t h; esp_err_t err = nvs_open(NVS_NS, NVS_READWRITE, &h); if (err == ESP_OK) { nvs_close(h); ESP_LOGI(TAG, "NVS namespace '%s' ready", NVS_NS); } else { ESP_LOGE(TAG, "NVS open failed: %s", esp_err_to_name(err)); } } /* ============================================================ * Known WiFi networks * ============================================================ */ static void net_key_ssid(int idx, char *out, size_t len) { snprintf(out, len, "n_%d", idx); } static void net_key_pass(int idx, char *out, size_t len) { snprintf(out, len, "p_%d", idx); } int fb_config_net_count(void) { nvs_handle_t h; if (nvs_open(NVS_NS, NVS_READONLY, &h) != ESP_OK) return 0; int32_t count = 0; nvs_get_i32(h, "fb_count", &count); nvs_close(h); return (int)count; } int fb_config_net_list(fb_network_t *out, int max_count) { nvs_handle_t h; if (nvs_open(NVS_NS, NVS_READONLY, &h) != ESP_OK) return 0; int32_t count = 0; nvs_get_i32(h, "fb_count", &count); if (count > max_count) count = max_count; if (count > CONFIG_FB_MAX_KNOWN_NETWORKS) count = CONFIG_FB_MAX_KNOWN_NETWORKS; char key[16]; for (int i = 0; i < count; i++) { memset(&out[i], 0, sizeof(fb_network_t)); net_key_ssid(i, key, sizeof(key)); size_t len = FB_SSID_MAX_LEN; nvs_get_str(h, key, out[i].ssid, &len); net_key_pass(i, key, sizeof(key)); len = FB_PASS_MAX_LEN; nvs_get_str(h, key, out[i].pass, &len); } nvs_close(h); return (int)count; } bool fb_config_net_add(const char *ssid, const char *pass) { if (!ssid || !ssid[0]) return false; nvs_handle_t h; if (nvs_open(NVS_NS, NVS_READWRITE, &h) != ESP_OK) return false; int32_t count = 0; nvs_get_i32(h, "fb_count", &count); /* Check if SSID already exists → update */ char key[16]; for (int i = 0; i < count; i++) { net_key_ssid(i, key, sizeof(key)); char existing[FB_SSID_MAX_LEN] = {0}; size_t len = FB_SSID_MAX_LEN; if (nvs_get_str(h, key, existing, &len) == ESP_OK) { if (strcmp(existing, ssid) == 0) { net_key_pass(i, key, sizeof(key)); nvs_set_str(h, key, pass ? pass : ""); nvs_commit(h); nvs_close(h); ESP_LOGI(TAG, "Updated network '%s'", ssid); return true; } } } if (count >= CONFIG_FB_MAX_KNOWN_NETWORKS) { nvs_close(h); ESP_LOGW(TAG, "Known networks full (%d)", (int)count); return false; } net_key_ssid(count, key, sizeof(key)); nvs_set_str(h, key, ssid); net_key_pass(count, key, sizeof(key)); nvs_set_str(h, key, pass ? pass : ""); count++; nvs_set_i32(h, "fb_count", count); nvs_commit(h); nvs_close(h); ESP_LOGI(TAG, "Added network '%s' (total: %d)", ssid, (int)count); return true; } bool fb_config_net_remove(const char *ssid) { if (!ssid || !ssid[0]) return false; nvs_handle_t h; if (nvs_open(NVS_NS, NVS_READWRITE, &h) != ESP_OK) return false; int32_t count = 0; nvs_get_i32(h, "fb_count", &count); int found = -1; char key[16]; for (int i = 0; i < count; i++) { net_key_ssid(i, key, sizeof(key)); char existing[FB_SSID_MAX_LEN] = {0}; size_t len = FB_SSID_MAX_LEN; if (nvs_get_str(h, key, existing, &len) == ESP_OK) { if (strcmp(existing, ssid) == 0) { found = i; break; } } } if (found < 0) { nvs_close(h); return false; } /* Shift entries down */ for (int i = found; i < count - 1; i++) { char src_key[16], dst_key[16]; char buf[FB_PASS_MAX_LEN]; size_t len; net_key_ssid(i + 1, src_key, sizeof(src_key)); net_key_ssid(i, dst_key, sizeof(dst_key)); len = FB_SSID_MAX_LEN; memset(buf, 0, sizeof(buf)); nvs_get_str(h, src_key, buf, &len); nvs_set_str(h, dst_key, buf); net_key_pass(i + 1, src_key, sizeof(src_key)); net_key_pass(i, dst_key, sizeof(dst_key)); len = FB_PASS_MAX_LEN; memset(buf, 0, sizeof(buf)); nvs_get_str(h, src_key, buf, &len); nvs_set_str(h, dst_key, buf); } net_key_ssid(count - 1, key, sizeof(key)); nvs_erase_key(h, key); net_key_pass(count - 1, key, sizeof(key)); nvs_erase_key(h, key); count--; nvs_set_i32(h, "fb_count", count); nvs_commit(h); nvs_close(h); ESP_LOGI(TAG, "Removed network '%s' (total: %d)", ssid, (int)count); return true; } /* ============================================================ * C2 fallback addresses * ============================================================ */ int fb_config_c2_count(void) { nvs_handle_t h; if (nvs_open(NVS_NS, NVS_READONLY, &h) != ESP_OK) return 0; int32_t count = 0; nvs_get_i32(h, "c2_count", &count); nvs_close(h); return (int)count; } int fb_config_c2_list(fb_c2_addr_t *out, int max_count) { nvs_handle_t h; if (nvs_open(NVS_NS, NVS_READONLY, &h) != ESP_OK) return 0; int32_t count = 0; nvs_get_i32(h, "c2_count", &count); if (count > max_count) count = max_count; if (count > CONFIG_FB_MAX_C2_FALLBACKS) count = CONFIG_FB_MAX_C2_FALLBACKS; for (int i = 0; i < count; i++) { memset(&out[i], 0, sizeof(fb_c2_addr_t)); char key[16]; snprintf(key, sizeof(key), "c2_%d", i); size_t len = FB_ADDR_MAX_LEN; nvs_get_str(h, key, out[i].addr, &len); } nvs_close(h); return (int)count; } bool fb_config_c2_add(const char *addr) { if (!addr || !addr[0]) return false; nvs_handle_t h; if (nvs_open(NVS_NS, NVS_READWRITE, &h) != ESP_OK) return false; int32_t count = 0; nvs_get_i32(h, "c2_count", &count); for (int i = 0; i < count; i++) { char key[16]; snprintf(key, sizeof(key), "c2_%d", i); char existing[FB_ADDR_MAX_LEN] = {0}; size_t len = FB_ADDR_MAX_LEN; if (nvs_get_str(h, key, existing, &len) == ESP_OK) { if (strcmp(existing, addr) == 0) { nvs_close(h); return true; } } } if (count >= CONFIG_FB_MAX_C2_FALLBACKS) { nvs_close(h); ESP_LOGW(TAG, "C2 fallbacks full (%d)", (int)count); return false; } char key[16]; snprintf(key, sizeof(key), "c2_%d", (int)count); nvs_set_str(h, key, addr); count++; nvs_set_i32(h, "c2_count", count); nvs_commit(h); nvs_close(h); ESP_LOGI(TAG, "Added C2 fallback '%s' (total: %d)", addr, (int)count); return true; } bool fb_config_c2_remove(const char *addr) { if (!addr || !addr[0]) return false; nvs_handle_t h; if (nvs_open(NVS_NS, NVS_READWRITE, &h) != ESP_OK) return false; int32_t count = 0; nvs_get_i32(h, "c2_count", &count); int found = -1; for (int i = 0; i < count; i++) { char key[16]; snprintf(key, sizeof(key), "c2_%d", i); char existing[FB_ADDR_MAX_LEN] = {0}; size_t len = FB_ADDR_MAX_LEN; if (nvs_get_str(h, key, existing, &len) == ESP_OK) { if (strcmp(existing, addr) == 0) { found = i; break; } } } if (found < 0) { nvs_close(h); return false; } for (int i = found; i < count - 1; i++) { char src_key[16], dst_key[16], buf[FB_ADDR_MAX_LEN]; size_t len = FB_ADDR_MAX_LEN; snprintf(src_key, sizeof(src_key), "c2_%d", i + 1); snprintf(dst_key, sizeof(dst_key), "c2_%d", i); memset(buf, 0, sizeof(buf)); nvs_get_str(h, src_key, buf, &len); nvs_set_str(h, dst_key, buf); } char key[16]; snprintf(key, sizeof(key), "c2_%d", (int)(count - 1)); nvs_erase_key(h, key); count--; nvs_set_i32(h, "c2_count", count); nvs_commit(h); nvs_close(h); ESP_LOGI(TAG, "Removed C2 fallback '%s' (total: %d)", addr, (int)count); return true; } /* ============================================================ * Original MAC storage * ============================================================ */ void fb_config_save_orig_mac(void) { uint8_t mac[6]; if (esp_wifi_get_mac(WIFI_IF_STA, mac) != ESP_OK) { ESP_LOGW(TAG, "Failed to read STA MAC"); return; } nvs_handle_t h; if (nvs_open(NVS_NS, NVS_READWRITE, &h) != ESP_OK) return; nvs_set_blob(h, "orig_mac", mac, 6); nvs_commit(h); nvs_close(h); ESP_LOGI(TAG, "Saved original MAC: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } bool fb_config_get_orig_mac(uint8_t mac[6]) { nvs_handle_t h; if (nvs_open(NVS_NS, NVS_READONLY, &h) != ESP_OK) return false; size_t len = 6; esp_err_t err = nvs_get_blob(h, "orig_mac", mac, &len); nvs_close(h); return (err == ESP_OK && len == 6); } #endif /* CONFIG_MODULE_FALLBACK */