forked from ruvnet/RuView
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathota_update.c
More file actions
266 lines (227 loc) · 8.82 KB
/
ota_update.c
File metadata and controls
266 lines (227 loc) · 8.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
/**
* @file ota_update.c
* @brief HTTP OTA firmware update for ESP32-S3 CSI Node.
*
* Uses ESP-IDF's native OTA API with rollback support.
* The HTTP server runs on port 8032 and accepts:
* POST /ota — firmware binary payload (application/octet-stream)
* GET /ota/status — current firmware version and partition info
*/
#include "ota_update.h"
#include <string.h>
#include "esp_log.h"
#include "esp_ota_ops.h"
#include "esp_http_server.h"
#include "esp_app_desc.h"
#include "nvs_flash.h"
#include "nvs.h"
static const char *TAG = "ota_update";
/** OTA HTTP server port. */
#define OTA_PORT 8032
/** Maximum firmware size (900 KB — matches CI binary size gate). */
#define OTA_MAX_SIZE (900 * 1024)
/** NVS namespace and key for the OTA pre-shared key. */
#define OTA_NVS_NAMESPACE "security"
#define OTA_NVS_KEY "ota_psk"
/** Maximum PSK length (hex-encoded SHA-256). */
#define OTA_PSK_MAX_LEN 65
/** Cached PSK loaded from NVS at init time. Empty = auth disabled. */
static char s_ota_psk[OTA_PSK_MAX_LEN] = {0};
/**
* ADR-050: Verify the Authorization header contains the correct PSK.
* Returns true if auth is disabled (no PSK provisioned) or if the
* Bearer token matches the stored PSK.
*/
static bool ota_check_auth(httpd_req_t *req)
{
if (s_ota_psk[0] == '\0') {
/* No PSK provisioned — auth disabled (permissive for dev). */
return true;
}
char auth_header[128] = {0};
if (httpd_req_get_hdr_value_str(req, "Authorization", auth_header,
sizeof(auth_header)) != ESP_OK) {
return false;
}
/* Expect "Bearer <psk>" */
const char *prefix = "Bearer ";
if (strncmp(auth_header, prefix, strlen(prefix)) != 0) {
return false;
}
const char *token = auth_header + strlen(prefix);
/* Constant-time comparison to prevent timing attacks. */
size_t psk_len = strlen(s_ota_psk);
size_t tok_len = strlen(token);
if (psk_len != tok_len) return false;
volatile uint8_t result = 0;
for (size_t i = 0; i < psk_len; i++) {
result |= (uint8_t)(s_ota_psk[i] ^ token[i]);
}
return result == 0;
}
/**
* GET /ota/status — return firmware version and partition info.
*/
static esp_err_t ota_status_handler(httpd_req_t *req)
{
const esp_app_desc_t *app = esp_app_get_description();
const esp_partition_t *running = esp_ota_get_running_partition();
const esp_partition_t *update = esp_ota_get_next_update_partition(NULL);
char response[512];
int len = snprintf(response, sizeof(response),
"{\"version\":\"%s\",\"date\":\"%s\",\"time\":\"%s\","
"\"running_partition\":\"%s\",\"next_partition\":\"%s\","
"\"max_size\":%d}",
app->version, app->date, app->time,
running ? running->label : "unknown",
update ? update->label : "none",
OTA_MAX_SIZE);
httpd_resp_set_type(req, "application/json");
httpd_resp_send(req, response, len);
return ESP_OK;
}
/**
* POST /ota — receive and flash firmware binary.
*/
static esp_err_t ota_upload_handler(httpd_req_t *req)
{
/* ADR-050: Authenticate before accepting firmware upload. */
if (!ota_check_auth(req)) {
ESP_LOGW(TAG, "OTA upload rejected: authentication failed");
httpd_resp_send_err(req, HTTPD_403_FORBIDDEN,
"Authentication required. Use: Authorization: Bearer <psk>");
return ESP_FAIL;
}
ESP_LOGI(TAG, "OTA update started, content_length=%d", req->content_len);
if (req->content_len <= 0 || req->content_len > OTA_MAX_SIZE) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST,
"Invalid firmware size (must be 1B - 900KB)");
return ESP_FAIL;
}
const esp_partition_t *update_partition = esp_ota_get_next_update_partition(NULL);
if (update_partition == NULL) {
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
"No OTA partition available");
return ESP_FAIL;
}
esp_ota_handle_t ota_handle;
esp_err_t err = esp_ota_begin(update_partition, OTA_WITH_SEQUENTIAL_WRITES, &ota_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_begin failed: %s", esp_err_to_name(err));
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
"OTA begin failed");
return ESP_FAIL;
}
/* Read firmware in chunks. */
char buf[1024];
int received = 0;
int total = 0;
while (total < req->content_len) {
received = httpd_req_recv(req, buf, sizeof(buf));
if (received <= 0) {
if (received == HTTPD_SOCK_ERR_TIMEOUT) {
continue; /* Retry on timeout. */
}
ESP_LOGE(TAG, "OTA receive error at byte %d", total);
esp_ota_abort(ota_handle);
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
"Receive error");
return ESP_FAIL;
}
err = esp_ota_write(ota_handle, buf, received);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_write failed at byte %d: %s",
total, esp_err_to_name(err));
esp_ota_abort(ota_handle);
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
"OTA write failed");
return ESP_FAIL;
}
total += received;
if ((total % (64 * 1024)) == 0) {
ESP_LOGI(TAG, "OTA progress: %d / %d bytes (%.0f%%)",
total, req->content_len,
(float)total * 100.0f / (float)req->content_len);
}
}
err = esp_ota_end(ota_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_end failed: %s", esp_err_to_name(err));
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
"OTA validation failed");
return ESP_FAIL;
}
err = esp_ota_set_boot_partition(update_partition);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_set_boot_partition failed: %s", esp_err_to_name(err));
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
"Set boot partition failed");
return ESP_FAIL;
}
ESP_LOGI(TAG, "OTA update successful! Rebooting to partition '%s'...",
update_partition->label);
const char *resp = "{\"status\":\"ok\",\"message\":\"OTA update successful. Rebooting...\"}";
httpd_resp_set_type(req, "application/json");
httpd_resp_send(req, resp, strlen(resp));
/* Delay briefly to let the response flush, then reboot. */
vTaskDelay(pdMS_TO_TICKS(1000));
esp_restart();
return ESP_OK; /* Never reached. */
}
/** Internal: start the HTTP server and register OTA endpoints. */
static esp_err_t ota_start_server(httpd_handle_t *out_handle)
{
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.server_port = OTA_PORT;
config.max_uri_handlers = 12; /* Extra slots for WASM endpoints (ADR-040). */
/* Increase receive timeout for large uploads. */
config.recv_wait_timeout = 30;
httpd_handle_t server = NULL;
esp_err_t err = httpd_start(&server, &config);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to start OTA HTTP server on port %d: %s",
OTA_PORT, esp_err_to_name(err));
if (out_handle) *out_handle = NULL;
return err;
}
httpd_uri_t status_uri = {
.uri = "/ota/status",
.method = HTTP_GET,
.handler = ota_status_handler,
.user_ctx = NULL,
};
httpd_register_uri_handler(server, &status_uri);
httpd_uri_t upload_uri = {
.uri = "/ota",
.method = HTTP_POST,
.handler = ota_upload_handler,
.user_ctx = NULL,
};
httpd_register_uri_handler(server, &upload_uri);
ESP_LOGI(TAG, "OTA HTTP server started on port %d", OTA_PORT);
ESP_LOGI(TAG, " GET /ota/status — firmware version info");
ESP_LOGI(TAG, " POST /ota — upload new firmware binary");
if (out_handle) *out_handle = server;
return ESP_OK;
}
esp_err_t ota_update_init(void)
{
/* ADR-050: Load OTA PSK from NVS if provisioned. */
nvs_handle_t nvs;
if (nvs_open(OTA_NVS_NAMESPACE, NVS_READONLY, &nvs) == ESP_OK) {
size_t len = sizeof(s_ota_psk);
if (nvs_get_str(nvs, OTA_NVS_KEY, s_ota_psk, &len) == ESP_OK) {
ESP_LOGI(TAG, "OTA PSK loaded from NVS (%d chars) — authentication enabled", (int)len - 1);
} else {
ESP_LOGW(TAG, "No OTA PSK in NVS — OTA authentication DISABLED (provision with nvs_set)");
}
nvs_close(nvs);
} else {
ESP_LOGW(TAG, "NVS namespace '%s' not found — OTA authentication DISABLED", OTA_NVS_NAMESPACE);
}
return ota_start_server(NULL);
}
esp_err_t ota_update_init_ex(void **out_server)
{
return ota_start_server((httpd_handle_t *)out_server);
}