292 lines
7.5 KiB
C
292 lines
7.5 KiB
C
|
/*
|
||
|
* libwebsockets - small server side websockets and web server implementation
|
||
|
*
|
||
|
* Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
|
||
|
*
|
||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
|
* of this software and associated documentation files (the "Software"), to
|
||
|
* deal in the Software without restriction, including without limitation the
|
||
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||
|
* sell copies of the Software, and to permit persons to whom the Software is
|
||
|
* furnished to do so, subject to the following conditions:
|
||
|
*
|
||
|
* The above copyright notice and this permission notice shall be included in
|
||
|
* all copies or substantial portions of the Software.
|
||
|
*
|
||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||
|
* IN THE SOFTWARE.
|
||
|
*/
|
||
|
|
||
|
#include <string.h>
|
||
|
#include <esp_partition.h>
|
||
|
#include <esp_ota_ops.h>
|
||
|
#include <nvs.h>
|
||
|
|
||
|
struct per_session_data__esplws_ota {
|
||
|
struct lws_spa *spa;
|
||
|
char filename[32];
|
||
|
char result[LWS_PRE + 512];
|
||
|
int result_len;
|
||
|
int filename_length;
|
||
|
esp_ota_handle_t otahandle;
|
||
|
const esp_partition_t *part;
|
||
|
long file_length;
|
||
|
long last_rep;
|
||
|
nvs_handle nvh;
|
||
|
TimerHandle_t reboot_timer;
|
||
|
};
|
||
|
|
||
|
struct per_vhost_data__esplws_ota {
|
||
|
struct lws_context *context;
|
||
|
struct lws_vhost *vhost;
|
||
|
const struct lws_protocols *protocol;
|
||
|
};
|
||
|
|
||
|
static const char * const ota_param_names[] = {
|
||
|
"upload",
|
||
|
};
|
||
|
|
||
|
enum enum_ota_param_names {
|
||
|
EPN_UPLOAD,
|
||
|
};
|
||
|
|
||
|
static void ota_reboot_timer_cb(TimerHandle_t t)
|
||
|
{
|
||
|
esp_restart();
|
||
|
}
|
||
|
|
||
|
const esp_partition_t *
|
||
|
ota_choose_part(void)
|
||
|
{
|
||
|
const esp_partition_t *bootpart, *part = NULL;
|
||
|
esp_partition_iterator_t i;
|
||
|
|
||
|
bootpart = lws_esp_ota_get_boot_partition();
|
||
|
i = esp_partition_find(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_ANY, NULL);
|
||
|
while (i) {
|
||
|
part = esp_partition_get(i);
|
||
|
|
||
|
/* cannot update ourselves */
|
||
|
if (part == bootpart)
|
||
|
goto next;
|
||
|
|
||
|
/* OTA Partition numbering is from _OTA_MIN to less than _OTA_MAX */
|
||
|
if (part->subtype < ESP_PARTITION_SUBTYPE_APP_OTA_MIN ||
|
||
|
part->subtype >= ESP_PARTITION_SUBTYPE_APP_OTA_MAX)
|
||
|
goto next;
|
||
|
|
||
|
break;
|
||
|
|
||
|
next:
|
||
|
i = esp_partition_next(i);
|
||
|
}
|
||
|
|
||
|
if (!i) {
|
||
|
lwsl_err("Can't find good OTA part\n");
|
||
|
return NULL;
|
||
|
}
|
||
|
lwsl_notice("Directing OTA to part type %d/%d start 0x%x\n",
|
||
|
part->type, part->subtype,
|
||
|
(uint32_t)part->address);
|
||
|
|
||
|
return part;
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
ota_file_upload_cb(void *data, const char *name, const char *filename,
|
||
|
char *buf, int len, enum lws_spa_fileupload_states state)
|
||
|
{
|
||
|
struct per_session_data__esplws_ota *pss =
|
||
|
(struct per_session_data__esplws_ota *)data;
|
||
|
|
||
|
switch (state) {
|
||
|
case LWS_UFS_OPEN:
|
||
|
lwsl_notice("LWS_UFS_OPEN Filename %s\n", filename);
|
||
|
lws_strncpy(pss->filename, filename, sizeof(pss->filename));
|
||
|
if (strcmp(name, "ota"))
|
||
|
return 1;
|
||
|
|
||
|
pss->part = ota_choose_part();
|
||
|
if (!pss->part)
|
||
|
return 1;
|
||
|
|
||
|
if (esp_ota_begin(pss->part, OTA_SIZE_UNKNOWN, &pss->otahandle) != ESP_OK) {
|
||
|
lwsl_err("OTA: Failed to begin\n");
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
pss->file_length = 0;
|
||
|
pss->last_rep = -1;
|
||
|
break;
|
||
|
|
||
|
case LWS_UFS_FINAL_CONTENT:
|
||
|
case LWS_UFS_CONTENT:
|
||
|
if (pss->file_length + len > pss->part->size) {
|
||
|
lwsl_err("OTA: incoming file too large\n");
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
if ((pss->file_length & ~0xffff) != (pss->last_rep & ~0xffff)) {
|
||
|
lwsl_notice("writing 0x%lx...\n",
|
||
|
pss->part->address + pss->file_length);
|
||
|
pss->last_rep = pss->file_length;
|
||
|
}
|
||
|
if (esp_ota_write(pss->otahandle, buf, len) != ESP_OK) {
|
||
|
lwsl_err("OTA: Failed to write\n");
|
||
|
return 1;
|
||
|
}
|
||
|
pss->file_length += len;
|
||
|
|
||
|
if (state == LWS_UFS_CONTENT)
|
||
|
break;
|
||
|
|
||
|
lwsl_notice("LWS_UFS_FINAL_CONTENT\n");
|
||
|
if (esp_ota_end(pss->otahandle) != ESP_OK) {
|
||
|
lwsl_err("OTA: end failed\n");
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
if (esp_ota_set_boot_partition(pss->part) != ESP_OK) {
|
||
|
lwsl_err("OTA: set boot part failed\n");
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
pss->reboot_timer = xTimerCreate("x", pdMS_TO_TICKS(250), 0, NULL,
|
||
|
ota_reboot_timer_cb);
|
||
|
xTimerStart(pss->reboot_timer, 0);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
callback_esplws_ota(struct lws *wsi, enum lws_callback_reasons reason,
|
||
|
void *user, void *in, size_t len)
|
||
|
{
|
||
|
struct per_session_data__esplws_ota *pss =
|
||
|
(struct per_session_data__esplws_ota *)user;
|
||
|
struct per_vhost_data__esplws_ota *vhd =
|
||
|
(struct per_vhost_data__esplws_ota *)
|
||
|
lws_protocol_vh_priv_get(lws_get_vhost(wsi),
|
||
|
lws_get_protocol(wsi));
|
||
|
unsigned char buf[LWS_PRE + 384], *start = buf + LWS_PRE - 1, *p = start,
|
||
|
*end = buf + sizeof(buf) - 1;
|
||
|
int n;
|
||
|
|
||
|
switch (reason) {
|
||
|
|
||
|
case LWS_CALLBACK_PROTOCOL_INIT:
|
||
|
vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
|
||
|
lws_get_protocol(wsi),
|
||
|
sizeof(struct per_vhost_data__esplws_ota));
|
||
|
vhd->context = lws_get_context(wsi);
|
||
|
vhd->protocol = lws_get_protocol(wsi);
|
||
|
vhd->vhost = lws_get_vhost(wsi);
|
||
|
break;
|
||
|
|
||
|
case LWS_CALLBACK_PROTOCOL_DESTROY:
|
||
|
if (!vhd)
|
||
|
break;
|
||
|
break;
|
||
|
|
||
|
/* OTA POST handling */
|
||
|
|
||
|
case LWS_CALLBACK_HTTP_BODY:
|
||
|
/* create the POST argument parser if not already existing */
|
||
|
// lwsl_notice("LWS_CALLBACK_HTTP_BODY (ota) %d %d %p\n", (int)pss->file_length, (int)len, pss->spa);
|
||
|
lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, 30);
|
||
|
if (!pss->spa) {
|
||
|
pss->spa = lws_spa_create(wsi, ota_param_names,
|
||
|
LWS_ARRAY_SIZE(ota_param_names), 4096,
|
||
|
ota_file_upload_cb, pss);
|
||
|
if (!pss->spa)
|
||
|
return -1;
|
||
|
|
||
|
pss->filename[0] = '\0';
|
||
|
pss->file_length = 0;
|
||
|
}
|
||
|
lws_esp32.upload = 1;
|
||
|
|
||
|
/* let it parse the POST data */
|
||
|
if (lws_spa_process(pss->spa, in, len))
|
||
|
return -1;
|
||
|
break;
|
||
|
|
||
|
case LWS_CALLBACK_HTTP_BODY_COMPLETION:
|
||
|
lwsl_notice("LWS_CALLBACK_HTTP_BODY_COMPLETION (ota)\n");
|
||
|
/* call to inform no more payload data coming */
|
||
|
lws_spa_finalize(pss->spa);
|
||
|
|
||
|
pss->result_len = snprintf(pss->result + LWS_PRE, sizeof(pss->result) - LWS_PRE - 1,
|
||
|
"Rebooting after OTA update");
|
||
|
|
||
|
if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end))
|
||
|
goto bail;
|
||
|
|
||
|
if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
|
||
|
(unsigned char *)"text/html", 9, &p, end))
|
||
|
goto bail;
|
||
|
if (lws_add_http_header_content_length(wsi, pss->result_len, &p, end))
|
||
|
goto bail;
|
||
|
if (lws_finalize_http_header(wsi, &p, end))
|
||
|
goto bail;
|
||
|
|
||
|
n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS | LWS_WRITE_H2_STREAM_END);
|
||
|
if (n < 0)
|
||
|
goto bail;
|
||
|
|
||
|
lws_callback_on_writable(wsi);
|
||
|
break;
|
||
|
|
||
|
case LWS_CALLBACK_HTTP_WRITEABLE:
|
||
|
if (!pss->result_len)
|
||
|
break;
|
||
|
lwsl_debug("LWS_CALLBACK_HTTP_WRITEABLE: sending %d\n",
|
||
|
pss->result_len);
|
||
|
n = lws_write(wsi, (unsigned char *)pss->result + LWS_PRE,
|
||
|
pss->result_len, LWS_WRITE_HTTP);
|
||
|
if (n < 0)
|
||
|
return 1;
|
||
|
|
||
|
if (lws_http_transaction_completed(wsi))
|
||
|
return 1;
|
||
|
|
||
|
/* stop further service so we don't serve the probe GET to see if we rebooted */
|
||
|
while (1);
|
||
|
|
||
|
break;
|
||
|
|
||
|
case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
|
||
|
/* called when our wsi user_space is going to be destroyed */
|
||
|
if (pss->spa) {
|
||
|
lws_spa_destroy(pss->spa);
|
||
|
pss->spa = NULL;
|
||
|
}
|
||
|
lws_esp32.upload = 0;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
bail:
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
#define LWS_PLUGIN_PROTOCOL_ESPLWS_OTA \
|
||
|
{ \
|
||
|
"esplws-ota", \
|
||
|
callback_esplws_ota, \
|
||
|
sizeof(struct per_session_data__esplws_ota), \
|
||
|
4096, 0, NULL, 900 \
|
||
|
}
|
||
|
|