/* * libwebsockets - small server side websockets and web server implementation * * Copyright (C) 2010 - 2019 Andy Green * * 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 #include #include #include 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 \ }