feat: implement Wi-Fi provisioning and persistent configuration
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static int initialized;
|
||||
static int present;
|
||||
static uint32_t schema;
|
||||
static char ssid[33];
|
||||
static char password[64];
|
||||
|
||||
esp_err_t nvs_flash_init(void)
|
||||
{
|
||||
initialized = 1;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t nvs_flash_deinit(void)
|
||||
{
|
||||
initialized = 0;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t nvs_flash_erase(void)
|
||||
{
|
||||
present = 0;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t nvs_open(const char *namespace_name, int mode, nvs_handle_t *handle)
|
||||
{
|
||||
if (!initialized || strcmp(namespace_name, "maze32") != 0) return ESP_FAIL;
|
||||
if (mode == NVS_READONLY && !present) return ESP_ERR_NVS_NOT_FOUND;
|
||||
*handle = 1U;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void nvs_close(nvs_handle_t handle)
|
||||
{
|
||||
(void)handle;
|
||||
}
|
||||
|
||||
esp_err_t nvs_get_u32(nvs_handle_t handle, const char *key, uint32_t *value)
|
||||
{
|
||||
(void)handle;
|
||||
if (!present || strcmp(key, "cfg_ver") != 0) return ESP_ERR_NVS_NOT_FOUND;
|
||||
*value = schema;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t nvs_set_u32(nvs_handle_t handle, const char *key, uint32_t value)
|
||||
{
|
||||
(void)handle;
|
||||
if (strcmp(key, "cfg_ver") != 0) return ESP_FAIL;
|
||||
schema = value;
|
||||
present = 1;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t nvs_get_str(nvs_handle_t handle, const char *key, char *output, size_t *length)
|
||||
{
|
||||
(void)handle;
|
||||
const char *source = strcmp(key, "wifi_ssid") == 0 ? ssid :
|
||||
strcmp(key, "wifi_pass") == 0 ? password : NULL;
|
||||
if (!present || source == NULL) return ESP_ERR_NVS_NOT_FOUND;
|
||||
size_t required = strlen(source) + 1U;
|
||||
if (*length < required) return ESP_FAIL;
|
||||
memcpy(output, source, required);
|
||||
*length = required;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t nvs_set_str(nvs_handle_t handle, const char *key, const char *value)
|
||||
{
|
||||
(void)handle;
|
||||
char *destination = strcmp(key, "wifi_ssid") == 0 ? ssid :
|
||||
strcmp(key, "wifi_pass") == 0 ? password : NULL;
|
||||
size_t capacity = strcmp(key, "wifi_ssid") == 0 ? sizeof(ssid) : sizeof(password);
|
||||
if (destination == NULL || strlen(value) >= capacity) return ESP_FAIL;
|
||||
memcpy(destination, value, strlen(value) + 1U);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t nvs_erase_all(nvs_handle_t handle)
|
||||
{
|
||||
(void)handle;
|
||||
present = 0;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t nvs_commit(nvs_handle_t handle)
|
||||
{
|
||||
(void)handle;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void fake_nvs_set_schema(uint32_t version)
|
||||
{
|
||||
schema = version;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef TEST_ESP_ERR_H
|
||||
#define TEST_ESP_ERR_H
|
||||
|
||||
typedef int esp_err_t;
|
||||
|
||||
#define ESP_OK 0
|
||||
#define ESP_FAIL 0x101
|
||||
#define ESP_ERR_NO_MEM 0x102
|
||||
#define ESP_ERR_INVALID_ARG 0x103
|
||||
#define ESP_ERR_INVALID_STATE 0x104
|
||||
#define ESP_ERR_INVALID_VERSION 0x105
|
||||
#define ESP_ERR_NOT_FOUND 0x106
|
||||
#define ESP_ERR_NVS_NOT_FOUND 0x1102
|
||||
#define ESP_ERR_NVS_NO_FREE_PAGES 0x110d
|
||||
#define ESP_ERR_NVS_NEW_VERSION_FOUND 0x1110
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifndef TEST_NVS_H
|
||||
#define TEST_NVS_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
typedef unsigned int nvs_handle_t;
|
||||
|
||||
#define NVS_READONLY 0
|
||||
#define NVS_READWRITE 1
|
||||
|
||||
esp_err_t nvs_open(const char *namespace_name, int mode, nvs_handle_t *handle);
|
||||
void nvs_close(nvs_handle_t handle);
|
||||
esp_err_t nvs_get_u32(nvs_handle_t handle, const char *key, uint32_t *value);
|
||||
esp_err_t nvs_set_u32(nvs_handle_t handle, const char *key, uint32_t value);
|
||||
esp_err_t nvs_get_str(nvs_handle_t handle, const char *key, char *output, size_t *length);
|
||||
esp_err_t nvs_set_str(nvs_handle_t handle, const char *key, const char *value);
|
||||
esp_err_t nvs_erase_all(nvs_handle_t handle);
|
||||
esp_err_t nvs_commit(nvs_handle_t handle);
|
||||
|
||||
void fake_nvs_set_schema(uint32_t version);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,10 @@
|
||||
#ifndef TEST_NVS_FLASH_H
|
||||
#define TEST_NVS_FLASH_H
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
esp_err_t nvs_flash_init(void);
|
||||
esp_err_t nvs_flash_erase(void);
|
||||
esp_err_t nvs_flash_deinit(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,45 @@
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "maze32_config_store.h"
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
static maze32_wifi_credentials_t credentials(const char *ssid, const char *password)
|
||||
{
|
||||
maze32_wifi_credentials_t value = {0};
|
||||
memcpy(value.ssid, ssid, strlen(ssid) + 1U);
|
||||
memcpy(value.password, password, strlen(password) + 1U);
|
||||
return value;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
assert(maze32_config_store_init() == ESP_OK);
|
||||
maze32_wifi_credentials_t loaded = {0};
|
||||
assert(maze32_config_store_load(&loaded) == ESP_ERR_NOT_FOUND);
|
||||
|
||||
maze32_wifi_credentials_t saved = credentials("PersistentNetwork", "safe-passphrase");
|
||||
assert(maze32_config_store_save(&saved) == ESP_OK);
|
||||
assert(nvs_flash_deinit() == ESP_OK);
|
||||
assert(maze32_config_store_init() == ESP_OK);
|
||||
assert(maze32_config_store_load(&loaded) == ESP_OK);
|
||||
assert(strcmp(loaded.ssid, saved.ssid) == 0);
|
||||
assert(strcmp(loaded.password, saved.password) == 0);
|
||||
|
||||
maze32_wifi_credentials_t invalid = credentials("PersistentNetwork", "short");
|
||||
assert(maze32_config_store_save(&invalid) == ESP_ERR_INVALID_ARG);
|
||||
memset(&loaded, 0, sizeof(loaded));
|
||||
assert(maze32_config_store_load(&loaded) == ESP_OK);
|
||||
assert(strcmp(loaded.password, saved.password) == 0);
|
||||
|
||||
fake_nvs_set_schema(MAZE32_WIFI_CONFIG_SCHEMA_VERSION + 1U);
|
||||
assert(maze32_config_store_load(&loaded) == ESP_ERR_INVALID_VERSION);
|
||||
fake_nvs_set_schema(MAZE32_WIFI_CONFIG_SCHEMA_VERSION);
|
||||
|
||||
assert(maze32_config_store_clear() == ESP_OK);
|
||||
assert(maze32_config_store_load(&loaded) == ESP_ERR_NOT_FOUND);
|
||||
puts("configuration NVS tests: PASS");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "maze32_provisioning_protocol.h"
|
||||
|
||||
static void accept(const char *json, const char *ssid, const char *password)
|
||||
{
|
||||
maze32_wifi_credentials_t credentials;
|
||||
assert(maze32_provisioning_parse_credentials(json, strlen(json), &credentials));
|
||||
assert(strcmp(credentials.ssid, ssid) == 0);
|
||||
assert(strcmp(credentials.password, password) == 0);
|
||||
}
|
||||
|
||||
static void reject(const char *json)
|
||||
{
|
||||
maze32_wifi_credentials_t credentials;
|
||||
assert(!maze32_provisioning_parse_credentials(json, strlen(json), &credentials));
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
accept("{\"ssid\":\"Maze WiFi\",\"password\":\"password8\"}",
|
||||
"Maze WiFi", "password8");
|
||||
accept(" { \"password\" : \"quote\\\"pass\" , \"ssid\" : \"Maze\" } ",
|
||||
"Maze", "quote\"pass");
|
||||
accept("{\"ssid\":\"Open\",\"password\":\"\"}", "Open", "");
|
||||
|
||||
reject("{}");
|
||||
reject("{\"ssid\":\"Maze\"}");
|
||||
reject("{\"ssid\":\"Maze\",\"password\":\"short\"}");
|
||||
reject("{\"ssid\":\"Maze\",\"password\":\"password8\",\"extra\":1}");
|
||||
reject("{\"ssid\":12,\"password\":\"password8\"}");
|
||||
reject("{\"ssid\":\"Maze\\nBad\",\"password\":\"password8\"}");
|
||||
reject("{\"ssid\":\"Maze\",\"ssid\":\"Again\"}");
|
||||
reject("not-json");
|
||||
|
||||
puts("provisioning protocol tests: PASS");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "maze32_wifi_config.h"
|
||||
|
||||
typedef struct {
|
||||
int guard_result;
|
||||
int guard_calls;
|
||||
int save_calls;
|
||||
int clear_calls;
|
||||
maze32_wifi_credentials_t saved;
|
||||
} fixture_t;
|
||||
|
||||
static int guard(void *context)
|
||||
{
|
||||
fixture_t *fixture = context;
|
||||
++fixture->guard_calls;
|
||||
return fixture->guard_result;
|
||||
}
|
||||
|
||||
static int save(const maze32_wifi_credentials_t *credentials, void *context)
|
||||
{
|
||||
fixture_t *fixture = context;
|
||||
++fixture->save_calls;
|
||||
fixture->saved = *credentials;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int clear(void *context)
|
||||
{
|
||||
fixture_t *fixture = context;
|
||||
++fixture->clear_calls;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static maze32_wifi_credentials_t valid_credentials(void)
|
||||
{
|
||||
maze32_wifi_credentials_t credentials = {0};
|
||||
memcpy(credentials.ssid, "Labyrinth", sizeof("Labyrinth"));
|
||||
memcpy(credentials.password, "eight888", sizeof("eight888"));
|
||||
return credentials;
|
||||
}
|
||||
|
||||
static void test_validation_boundaries(void)
|
||||
{
|
||||
maze32_wifi_credentials_t credentials = valid_credentials();
|
||||
assert(maze32_wifi_credentials_validate(&credentials) == MAZE32_WIFI_CONFIG_VALID);
|
||||
|
||||
credentials.password[0] = '\0';
|
||||
assert(maze32_wifi_credentials_validate(&credentials) == MAZE32_WIFI_CONFIG_VALID);
|
||||
|
||||
memcpy(credentials.password, "short", sizeof("short"));
|
||||
assert(maze32_wifi_credentials_validate(&credentials) ==
|
||||
MAZE32_WIFI_CONFIG_INVALID_PASSWORD);
|
||||
|
||||
credentials = valid_credentials();
|
||||
credentials.ssid[0] = '\0';
|
||||
assert(maze32_wifi_credentials_validate(&credentials) == MAZE32_WIFI_CONFIG_INVALID_SSID);
|
||||
|
||||
memset(credentials.ssid, 's', sizeof(credentials.ssid));
|
||||
assert(maze32_wifi_credentials_validate(&credentials) == MAZE32_WIFI_CONFIG_INVALID_SSID);
|
||||
|
||||
credentials = valid_credentials();
|
||||
credentials.ssid[2] = '\n';
|
||||
assert(maze32_wifi_credentials_validate(&credentials) == MAZE32_WIFI_CONFIG_INVALID_SSID);
|
||||
}
|
||||
|
||||
static void test_invalid_input_never_mutates_storage(void)
|
||||
{
|
||||
fixture_t fixture = {0};
|
||||
maze32_wifi_credentials_t credentials = valid_credentials();
|
||||
credentials.password[0] = 'x';
|
||||
credentials.password[1] = '\0';
|
||||
assert(maze32_wifi_config_apply(&credentials, guard, save, &fixture) == -1);
|
||||
assert(fixture.guard_calls == 0);
|
||||
assert(fixture.save_calls == 0);
|
||||
}
|
||||
|
||||
static void test_guard_precedes_every_mutation(void)
|
||||
{
|
||||
fixture_t fixture = {.guard_result = -1};
|
||||
maze32_wifi_credentials_t credentials = valid_credentials();
|
||||
assert(maze32_wifi_config_apply(&credentials, guard, save, &fixture) == -2);
|
||||
assert(fixture.guard_calls == 1);
|
||||
assert(fixture.save_calls == 0);
|
||||
assert(maze32_wifi_config_reset(guard, clear, &fixture) == -2);
|
||||
assert(fixture.guard_calls == 2);
|
||||
assert(fixture.clear_calls == 0);
|
||||
}
|
||||
|
||||
static void test_valid_apply_and_reset(void)
|
||||
{
|
||||
fixture_t fixture = {0};
|
||||
maze32_wifi_credentials_t credentials = valid_credentials();
|
||||
assert(maze32_wifi_config_apply(&credentials, guard, save, &fixture) == 0);
|
||||
assert(fixture.guard_calls == 1);
|
||||
assert(fixture.save_calls == 1);
|
||||
assert(strcmp(fixture.saved.ssid, credentials.ssid) == 0);
|
||||
assert(strcmp(fixture.saved.password, credentials.password) == 0);
|
||||
|
||||
assert(maze32_wifi_config_reset(guard, clear, &fixture) == 0);
|
||||
assert(fixture.guard_calls == 2);
|
||||
assert(fixture.clear_calls == 1);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
test_validation_boundaries();
|
||||
test_invalid_input_never_mutates_storage();
|
||||
test_guard_precedes_every_mutation();
|
||||
test_valid_apply_and_reset();
|
||||
puts("wifi configuration tests: PASS");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user