Implement HTTPS lifecycle and reboot controls
This commit is contained in:
+94
-16
@@ -13,6 +13,7 @@
|
||||
#include "esp_log.h"
|
||||
#include "esp_netif_ip_addr.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_system.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "secure_random.h"
|
||||
@@ -29,6 +30,7 @@
|
||||
#include "web_display_settings.h"
|
||||
#include "web_broker_settings.h"
|
||||
#include "web_ssh_settings.h"
|
||||
#include "web_lifecycle_settings.h"
|
||||
#include "web_admin_transport.h"
|
||||
#include "web_session_store.h"
|
||||
#include "web_cookie_auth.h"
|
||||
@@ -45,6 +47,8 @@ static SemaphoreHandle_t s_server_mutex;
|
||||
static httpd_handle_t s_server;
|
||||
static bool s_initialized;
|
||||
static bool s_transitioning;
|
||||
/* Firmware-lifetime lifecycle fence, independent of counters and handle reuse. */
|
||||
static uint32_t s_generation = 1U;
|
||||
static bool s_serial_transport_init_attempted;
|
||||
static bool s_serial_transport_initialized;
|
||||
static bool s_serial_transport_attached;
|
||||
@@ -416,6 +420,15 @@ static const httpd_uri_t s_account_generate_password_uri = {
|
||||
static const httpd_uri_t s_network_uri = {
|
||||
.uri = "/api/settings/network", .method = HTTP_GET, .handler = web_network_snapshot_handler,
|
||||
};
|
||||
static const httpd_uri_t s_lifecycle_settings_uri = {
|
||||
.uri = "/api/settings/lifecycle", .method = HTTP_GET, .handler = web_lifecycle_settings_handler,
|
||||
};
|
||||
static const httpd_uri_t s_lifecycle_operation_get_uri = {
|
||||
.uri = "/api/settings/lifecycle-operation", .method = HTTP_GET, .handler = web_lifecycle_operation_handler,
|
||||
};
|
||||
static const httpd_uri_t s_lifecycle_operation_post_uri = {
|
||||
.uri = "/api/settings/lifecycle-operation", .method = HTTP_POST, .handler = web_lifecycle_operation_handler,
|
||||
};
|
||||
static const httpd_uri_t s_ssh_settings_uri = {
|
||||
.uri = "/api/settings/ssh", .method = HTTP_GET, .handler = web_ssh_settings_handler,
|
||||
};
|
||||
@@ -619,30 +632,27 @@ esp_err_t web_server_init(void)
|
||||
s_serial_transport_error = serial_transport_error;
|
||||
s_serial_transport_initialized = serial_transport_error == ESP_OK;
|
||||
}
|
||||
s_initialized = true;
|
||||
if (s_last_error == ESP_ERR_INVALID_STATE) {
|
||||
if (!s_initialized && s_last_error == ESP_ERR_INVALID_STATE) {
|
||||
s_last_error = ESP_OK;
|
||||
}
|
||||
s_initialized = true;
|
||||
xSemaphoreGive(s_server_mutex);
|
||||
|
||||
/* The Phase 5A HTTPS recovery surface remains available if WebSocket setup fails. */
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t web_server_start(void)
|
||||
static esp_err_t start_server(bool reserved)
|
||||
{
|
||||
esp_err_t error = web_server_init();
|
||||
if (error != ESP_OK) {
|
||||
return error;
|
||||
}
|
||||
|
||||
esp_err_t error;
|
||||
bool serial_transport_ready;
|
||||
xSemaphoreTake(s_server_mutex, portMAX_DELAY);
|
||||
if (s_server != NULL || s_transitioning) {
|
||||
if (s_server != NULL || s_transitioning != reserved) {
|
||||
xSemaphoreGive(s_server_mutex);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
s_transitioning = true;
|
||||
if (s_generation != UINT32_MAX) ++s_generation;
|
||||
serial_transport_ready = s_serial_transport_initialized;
|
||||
xSemaphoreGive(s_server_mutex);
|
||||
|
||||
@@ -665,7 +675,7 @@ esp_err_t web_server_start(void)
|
||||
config.httpd.max_open_sockets = 6;
|
||||
config.httpd.max_uri_handlers =
|
||||
sizeof(s_uri_handlers) / sizeof(s_uri_handlers[0]) +
|
||||
sizeof(s_auth_uris) / sizeof(s_auth_uris[0]) + 22U;
|
||||
sizeof(s_auth_uris) / sizeof(s_auth_uris[0]) + 25U;
|
||||
/* Exhaustion rejects new sockets, never evicts an existing serial writer. */
|
||||
config.httpd.lru_purge_enable = false;
|
||||
config.httpd.recv_wait_timeout = 1;
|
||||
@@ -741,6 +751,10 @@ esp_err_t web_server_start(void)
|
||||
web_httpd_register_optional_get(server, &s_ssh_operation_get_uri) == ESP_OK &&
|
||||
web_httpd_register_optional(server, &s_ssh_operation_post_uri) != ESP_OK)
|
||||
(void)httpd_unregister_uri_handler(server, s_ssh_operation_get_uri.uri, HTTP_GET);
|
||||
if (web_httpd_register_optional_get(server, &s_lifecycle_settings_uri) == ESP_OK &&
|
||||
web_httpd_register_optional_get(server, &s_lifecycle_operation_get_uri) == ESP_OK &&
|
||||
web_httpd_register_optional(server, &s_lifecycle_operation_post_uri) != ESP_OK)
|
||||
(void)httpd_unregister_uri_handler(server, s_lifecycle_operation_get_uri.uri, HTTP_GET);
|
||||
}
|
||||
if (error != ESP_OK) {
|
||||
web_cookie_auth_stop();
|
||||
@@ -750,6 +764,7 @@ esp_err_t web_server_start(void)
|
||||
if (cleanup_error == ESP_OK) cleanup_error = httpd_ssl_stop(server);
|
||||
if (cleanup_error == ESP_OK) {
|
||||
web_httpd_idle_stopped(server);
|
||||
web_lifecycle_settings_stopped(server);
|
||||
server = NULL;
|
||||
} else {
|
||||
/* Retain ownership so stop can retry and start cannot allocate a second server. */
|
||||
@@ -775,14 +790,23 @@ esp_err_t web_server_start(void)
|
||||
return error;
|
||||
}
|
||||
|
||||
esp_err_t web_server_stop(void)
|
||||
esp_err_t web_server_start(void)
|
||||
{
|
||||
esp_err_t error = web_server_init();
|
||||
return error == ESP_OK ? start_server(false) : error;
|
||||
}
|
||||
|
||||
static esp_err_t stop_server(uint32_t expected_generation, bool restart)
|
||||
{
|
||||
if (s_server_mutex == NULL) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
xSemaphoreTake(s_server_mutex, portMAX_DELAY);
|
||||
if (s_server == NULL || s_transitioning) {
|
||||
if (xSemaphoreTake(s_server_mutex, expected_generation ? 0U : portMAX_DELAY) != pdTRUE)
|
||||
return ESP_ERR_TIMEOUT;
|
||||
if (s_server == NULL || s_transitioning ||
|
||||
(expected_generation && (expected_generation != s_generation ||
|
||||
s_generation == UINT32_MAX || s_last_error != ESP_OK))) {
|
||||
xSemaphoreGive(s_server_mutex);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
@@ -791,6 +815,7 @@ esp_err_t web_server_stop(void)
|
||||
bool admin_transport_owned = s_admin_transport_owned;
|
||||
esp_err_t serial_transport_error = s_serial_transport_error;
|
||||
s_transitioning = true;
|
||||
if (s_generation != UINT32_MAX) ++s_generation;
|
||||
xSemaphoreGive(s_server_mutex);
|
||||
|
||||
web_cookie_auth_stop();
|
||||
@@ -829,7 +854,10 @@ esp_err_t web_server_stop(void)
|
||||
}
|
||||
|
||||
esp_err_t error = httpd_ssl_stop(server);
|
||||
if (error == ESP_OK) web_httpd_idle_stopped(server);
|
||||
if (error == ESP_OK) {
|
||||
web_httpd_idle_stopped(server);
|
||||
web_lifecycle_settings_stopped(server);
|
||||
}
|
||||
if (error == ESP_OK && admin_transport_owned) web_admin_transport_stopped(server);
|
||||
if (error != ESP_OK && serial_transport_attached) {
|
||||
/* Stay detached: old HTTPD work may still be reading static TX storage. */
|
||||
@@ -837,7 +865,8 @@ esp_err_t web_server_stop(void)
|
||||
}
|
||||
|
||||
xSemaphoreTake(s_server_mutex, portMAX_DELAY);
|
||||
s_transitioning = false;
|
||||
/* Do not expose a stopped/unreserved gap to another lifecycle caller. */
|
||||
s_transitioning = error == ESP_OK && restart;
|
||||
s_last_error = error;
|
||||
s_serial_transport_error = serial_transport_error;
|
||||
s_serial_transport_attached = false;
|
||||
@@ -847,7 +876,56 @@ esp_err_t web_server_stop(void)
|
||||
++s_counters.stops;
|
||||
}
|
||||
xSemaphoreGive(s_server_mutex);
|
||||
return error;
|
||||
return error == ESP_OK && restart ? start_server(true) : error;
|
||||
}
|
||||
|
||||
esp_err_t web_server_stop(void)
|
||||
{
|
||||
return stop_server(0U, false);
|
||||
}
|
||||
|
||||
esp_err_t web_server_stop_current(uint32_t expected_generation)
|
||||
{
|
||||
if (!expected_generation) return ESP_ERR_INVALID_ARG;
|
||||
return stop_server(expected_generation, false);
|
||||
}
|
||||
|
||||
esp_err_t web_server_restart_current(uint32_t expected_generation)
|
||||
{
|
||||
if (!expected_generation) return ESP_ERR_INVALID_ARG;
|
||||
return stop_server(expected_generation, true);
|
||||
}
|
||||
|
||||
esp_err_t web_server_reboot_current(uint32_t expected_generation)
|
||||
{
|
||||
if (!expected_generation) return ESP_ERR_INVALID_ARG;
|
||||
if (s_server_mutex == NULL) return ESP_ERR_INVALID_STATE;
|
||||
if (xSemaphoreTake(s_server_mutex, 0U) != pdTRUE) return ESP_ERR_TIMEOUT;
|
||||
if (s_server == NULL || s_transitioning || s_last_error != ESP_OK ||
|
||||
s_generation == UINT32_MAX || expected_generation != s_generation) {
|
||||
xSemaphoreGive(s_server_mutex);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
s_transitioning = true;
|
||||
++s_generation;
|
||||
xSemaphoreGive(s_server_mutex);
|
||||
esp_restart();
|
||||
return ESP_FAIL; /* Defensive only: reset normally never returns. */
|
||||
}
|
||||
|
||||
esp_err_t web_server_get_management_snapshot(web_server_management_snapshot_t *snapshot)
|
||||
{
|
||||
if (snapshot == NULL) return ESP_ERR_INVALID_ARG;
|
||||
memset(snapshot, 0, sizeof(*snapshot));
|
||||
if (s_server_mutex == NULL) return ESP_ERR_INVALID_STATE;
|
||||
if (xSemaphoreTake(s_server_mutex, 0U) != pdTRUE) return ESP_ERR_TIMEOUT;
|
||||
snapshot->generation = s_generation;
|
||||
snapshot->running = s_server != NULL;
|
||||
snapshot->transitioning = s_transitioning;
|
||||
snapshot->controllable = s_initialized && s_server != NULL && !s_transitioning &&
|
||||
s_last_error == ESP_OK && s_generation != UINT32_MAX;
|
||||
xSemaphoreGive(s_server_mutex);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t web_server_get_snapshot(web_server_snapshot_t *snapshot)
|
||||
|
||||
Reference in New Issue
Block a user