feat: introduce the network state model and secure persistent storage

This commit is contained in:
2026-09-01 21:04:23 +03:00
parent 4ea5280f61
commit bad0803a6a
10 changed files with 312 additions and 8 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
# without default 'CMakeLists.txt' file.
idf_component_register(
SRCS "main.c" "application.c" "command_queue.c" "fleet_generator.c" "game_engine.c" "bot_player.c" "session_manager.c" "game_lifecycle.c" "state_presenter.c" "http_api.c" "sync_service.c"
SRCS "main.c" "application.c" "command_queue.c" "fleet_generator.c" "game_engine.c" "bot_player.c" "session_manager.c" "game_lifecycle.c" "state_presenter.c" "http_api.c" "sync_service.c" "network_state.c" "network_credentials.c" "network_credential_store.c"
INCLUDE_DIRS "../include"
REQUIRES esp_event esp_http_server esp_netif esp_wifi esp_littlefs nvs_flash
)
+9
View File
@@ -0,0 +1,9 @@
#include "network_credential_store.h"
#include "network_credentials.h"
#include "nvs.h"
static const char *const kNamespace = "network";
static const char *const kProfileKey = "profile";
bool network_credential_store_load(network_profile_t *profile) { nvs_handle_t handle; network_credential_record_t record; size_t size = sizeof(record); if (nvs_open(kNamespace, NVS_READONLY, &handle) != ESP_OK) return false; const esp_err_t result = nvs_get_blob(handle, kProfileKey, &record, &size); nvs_close(handle); return result == ESP_OK && size == sizeof(record) && network_credential_decode(&record, profile); }
bool network_credential_store_replace(const network_profile_t *profile) { network_credential_record_t record; nvs_handle_t handle; if (!network_credential_encode(profile, &record) || nvs_open(kNamespace, NVS_READWRITE, &handle) != ESP_OK) return false; const esp_err_t result = nvs_set_blob(handle, kProfileKey, &record, sizeof(record)); const bool ok = result == ESP_OK && nvs_commit(handle) == ESP_OK; nvs_close(handle); return ok; }
bool network_credential_store_delete(void) { nvs_handle_t handle; if (nvs_open(kNamespace, NVS_READWRITE, &handle) != ESP_OK) return false; const esp_err_t result = nvs_erase_key(handle, kProfileKey); const bool ok = (result == ESP_OK || result == ESP_ERR_NVS_NOT_FOUND) && nvs_commit(handle) == ESP_OK; nvs_close(handle); return ok; }
+10
View File
@@ -0,0 +1,10 @@
#include "network_credentials.h"
#include <stddef.h>
#include <string.h>
enum { kCredentialMagic = 0x42534E57U, kCredentialVersion = 1U };
static uint32_t checksum(const uint8_t *bytes, size_t length) { uint32_t value = 2166136261U; for (size_t i = 0; i < length; ++i) value = (value ^ bytes[i]) * 16777619U; return value; }
bool network_credential_encode(const network_profile_t *profile, network_credential_record_t *record) { if (!network_profile_valid(profile) || record == NULL) return false; memset(record, 0, sizeof(*record)); record->magic = kCredentialMagic; record->version = kCredentialVersion; record->profile = *profile; record->checksum = checksum((const uint8_t *)record, offsetof(network_credential_record_t, checksum)); return true; }
bool network_credential_decode(const network_credential_record_t *record, network_profile_t *profile) { if (record == NULL || profile == NULL || record->magic != kCredentialMagic || record->version != kCredentialVersion || record->checksum != checksum((const uint8_t *)record, offsetof(network_credential_record_t, checksum)) || !network_profile_valid(&record->profile)) return false; *profile = record->profile; return true; }
bool network_credential_load(const network_credential_backend_t *backend, network_profile_t *profile) { network_credential_record_t record; return backend != NULL && backend->read != NULL && backend->read(backend->context, &record) && network_credential_decode(&record, profile); }
bool network_credential_replace(const network_credential_backend_t *backend, const network_profile_t *profile) { network_credential_record_t record; return backend != NULL && backend->write != NULL && network_credential_encode(profile, &record) && backend->write(backend->context, &record); }
bool network_credential_delete(const network_credential_backend_t *backend) { return backend != NULL && backend->remove != NULL && backend->remove(backend->context); }
+33
View File
@@ -0,0 +1,33 @@
#include "network_state.h"
#include <string.h>
bool network_profile_valid(const network_profile_t *profile) {
return profile != NULL && profile->ssid[0] != '\0' && memchr(profile->ssid, '\0', sizeof(profile->ssid)) != NULL &&
memchr(profile->password, '\0', sizeof(profile->password)) != NULL;
}
network_action_t network_manager_init(network_manager_t *manager, const network_profile_t *profile, uint32_t now_ms) {
memset(manager, 0, sizeof(*manager));
if (!network_profile_valid(profile)) { manager->state = NETWORK_STATE_FALLBACK; return NETWORK_ACTION_FALLBACK; }
manager->profile = *profile; manager->has_profile = true; manager->state = NETWORK_STATE_CONNECTING; manager->started_ms = now_ms;
return NETWORK_ACTION_CONNECT;
}
network_action_t network_manager_tick(network_manager_t *manager, uint32_t now_ms) {
if (manager->state == NETWORK_STATE_CONNECTING && (uint32_t)(now_ms - manager->started_ms) >= kNetworkConnectWindowMs) { manager->state = NETWORK_STATE_FALLBACK; return NETWORK_ACTION_FALLBACK; }
return NETWORK_ACTION_NONE;
}
network_action_t network_manager_connected(network_manager_t *manager) { manager->state = NETWORK_STATE_EXTERNAL; return NETWORK_ACTION_NONE; }
network_action_t network_manager_disconnected(network_manager_t *manager, uint32_t now_ms) {
if (!manager->has_profile) { manager->state = NETWORK_STATE_FALLBACK; return NETWORK_ACTION_FALLBACK; }
manager->state = NETWORK_STATE_CONNECTING; manager->started_ms = now_ms; return NETWORK_ACTION_CONNECT;
}
bool network_manager_begin_validation(network_manager_t *manager, const network_profile_t *candidate) {
if (!network_profile_valid(candidate) || manager->state == NETWORK_STATE_VALIDATING) return false;
manager->candidate = *candidate; manager->state_before_validation = manager->state; manager->state = NETWORK_STATE_VALIDATING; return true;
}
network_action_t network_manager_finish_validation(network_manager_t *manager, bool success, uint32_t now_ms) {
if (manager->state != NETWORK_STATE_VALIDATING) return NETWORK_ACTION_NONE;
if (!success) { manager->state = manager->state_before_validation; return NETWORK_ACTION_NONE; }
manager->profile = manager->candidate; manager->has_profile = true; manager->state = NETWORK_STATE_CONNECTING; manager->started_ms = now_ms; return NETWORK_ACTION_CONNECT;
}