diff --git a/.DS_Store b/.DS_Store index e892b8d..cdd3dd0 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/components/ota_ws_update/.gitignore b/components/ota_ws_update/.gitignore new file mode 100644 index 0000000..594805f --- /dev/null +++ b/components/ota_ws_update/.gitignore @@ -0,0 +1,22 @@ +.vscode/ +.devcontainer/ +build/ +example_ota_ws/sdkconfig +example_ota_ws/sdkconfig.old +example_ota_ws/managed_components/espressif__esp_encrypted_img/.component_hash +example_ota_ws/managed_components/espressif__esp_encrypted_img/CHANGELOG.md +example_ota_ws/managed_components/espressif__esp_encrypted_img/CMakeLists.txt +example_ota_ws/managed_components/espressif__esp_encrypted_img/idf_component.yml +example_ota_ws/managed_components/espressif__esp_encrypted_img/image_format.png +example_ota_ws/managed_components/espressif__esp_encrypted_img/LICENSE +example_ota_ws/managed_components/espressif__esp_encrypted_img/project_include.cmake +example_ota_ws/managed_components/espressif__esp_encrypted_img/README.md +example_ota_ws/managed_components/espressif__esp_encrypted_img/include/esp_encrypted_img.h +example_ota_ws/managed_components/espressif__esp_encrypted_img/src/esp_encrypted_img.c +example_ota_ws/managed_components/espressif__esp_encrypted_img/test/CMakeLists.txt +example_ota_ws/managed_components/espressif__esp_encrypted_img/test/image.bin +example_ota_ws/managed_components/espressif__esp_encrypted_img/test/test.c +example_ota_ws/managed_components/espressif__esp_encrypted_img/test/certs/test_rsa_private_key.pem +example_ota_ws/managed_components/espressif__esp_encrypted_img/tools/esp_enc_img_gen.py +example_ota_ws/dependencies.lock +example_ota_ws/.clangd diff --git a/components/ota_ws_update/CMakeLists.txt b/components/ota_ws_update/CMakeLists.txt new file mode 100644 index 0000000..12b637c --- /dev/null +++ b/components/ota_ws_update/CMakeLists.txt @@ -0,0 +1,74 @@ +set( srcs + source/ota_ws_update_http.c +) +set( includedir + include +) +set( priv_includedir + private_include + ) +set(require + ) +set( priv_require + app_update + esp_http_server + mbedtls +) +set( embed_file + source/ota_ws_update.html +) +set( embed_txt_file +) + +if(CONFIG_OTA_PRE_ENCRYPTED_MODE) + list(APPEND srcs + source/ota_ws_update_esp_preencrypted.c + esp_encrypted_img/src/esp_encrypted_img.c + ) + list(APPEND priv_includedir + esp_encrypted_img/include + ) + if(CONFIG_OTA_PRE_ENCRYPTED_RSA_KEY_ON_COMPONENT_LOCATION) + list(APPEND embed_txt_file + ${COMPONENT_DIR}/${CONFIG_OTA_PRE_ENCRYPTED_RSA_KEY_DIRECTORY}/private_rsa_3072.pem + ) + else() + list(APPEND embed_txt_file + ${project_dir}/${CONFIG_OTA_PRE_ENCRYPTED_RSA_KEY_DIRECTORY}/private_rsa_3072.pem + ) + endif() +else() + list(APPEND srcs + source/ota_ws_update_esp.c + ) +endif() + +idf_component_register( + SRCS ${srcs} + INCLUDE_DIRS ${includedir} + PRIV_INCLUDE_DIRS ${priv_includedir} + REQUIRES ${require} + PRIV_REQUIRES ${priv_require} + EMBED_FILES ${embed_file} + EMBED_TXTFILES ${embed_txt_file} +) + +if(CONFIG_OTA_PRE_ENCRYPTED_MODE) + + include(esp_encrypted_img/project_include.cmake) + #redefine ESP_IMG_GEN_TOOL_PATH + set(ESP_IMG_GEN_TOOL_PATH ${CMAKE_CURRENT_LIST_DIR}/esp_encrypted_img/tools/esp_enc_img_gen.py) + + # command for generate RSA key + #openssl genrsa -out rsa_key/private_rsa_3072.pem 3072 + + if(CONFIG_OTA_PRE_ENCRYPTED_RSA_KEY_ON_COMPONENT_LOCATION) + # for rsa_key on components project_dir + create_esp_enc_img(${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}.bin + ${COMPONENT_DIR}/${CONFIG_OTA_PRE_ENCRYPTED_RSA_KEY_DIRECTORY}/private_rsa_3072.pem ${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}_secure.bin app) + else() + # for rsa_key on project dir + create_esp_enc_img(${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}.bin + ${project_dir}/${CONFIG_OTA_PRE_ENCRYPTED_RSA_KEY_DIRECTORY}/private_rsa_3072.pem ${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}_secure.bin app) + endif() +endif() diff --git a/components/ota_ws_update/Kconfig.projbuild b/components/ota_ws_update/Kconfig.projbuild new file mode 100644 index 0000000..854ba47 --- /dev/null +++ b/components/ota_ws_update/Kconfig.projbuild @@ -0,0 +1,43 @@ +menu "OTA websocket update" + + config OTA_DEFAULT_URI + string "OTA page URI" + default "/ota" + help + WEB page URI to OTA update. + + config OTA_DEFAULT_WS_URI + string "OTA ws URI" + default "/ota/ws" + help + WEB ws URI to OTA update. + + config OTA_CHUNK_SIZE + int "Ota chunk size" + default 8192 + help + Ota download chunk size. + + config OTA_PRE_ENCRYPTED_MODE + bool "Ota pre-encrypted mode" + default n + help + Ota pre-encrypted mode. + + choice OTA_PRE_ENCRYPTED_RSA_KEY_LOCATION + depends on OTA_PRE_ENCRYPTED_MODE + prompt "RSA key directory" + default OTA_PRE_ENCRYPTED_RSA_KEY_ON_COMPONENT_LOCATION + config OTA_PRE_ENCRYPTED_RSA_KEY_ON_PROJECT_LOCATION + bool "PROJECT_DIR" + config OTA_PRE_ENCRYPTED_RSA_KEY_ON_COMPONENT_LOCATION + bool "COMPONENT_DIR" + endchoice + + config OTA_PRE_ENCRYPTED_RSA_KEY_DIRECTORY + depends on OTA_PRE_ENCRYPTED_MODE + string "Ota pre-encrypted RSA key directory" + default "rsa_key" + + +endmenu \ No newline at end of file diff --git a/components/ota_ws_update/README-RU.md b/components/ota_ws_update/README-RU.md new file mode 100644 index 0000000..87d73f5 --- /dev/null +++ b/components/ota_ws_update/README-RU.md @@ -0,0 +1,66 @@ +[En](/README.md) + +| Supported Targets | +| ESP32 ESP32S3 ESP32C3 | +| ----------------- | + +# ESP32 OTA обновление через WebSocket с простым WEB интерфейсом. Опционально PreEncrypted режим. + - Подключается как компонент к вашей программе + - Не требует внешних серверов для хранения прошивок OTA, предназначен в первую очередь для работы в локальной сети. + - Использует WebsSocket или WebsSocket Secure протокол. + - Подключается к любому web серверу на esp32, использующему WebSocket протокол, например (esp-idf examples/protocols/http_server/ws_echo_server) или (esp-idf examples/protocols/https_server/wss_server) как URI handler. + - в зависимости от протокола сервера (http/https) будет выбран протокол обмена (ws/wss) + - режим PreEncrypted (espressif/esp_encrypted_img) подключается в Menuconfig + - для PreEncrypted режима требуется увеличение размера стека httpd_config_t config.stack_size = 4096*4; + - Подробности использования PreEncrypted режима https://components.espressif.com/components/espressif/esp_encrypted_img + - Пример - example_ota_ws + - Web интерфейс + - Выбор файла прошивки + - Загрузка прошивки в esp32 + - Контроль загрузки прошивки + - После обновления прошивки - подтверждение обновления или откат на предыдущую версию + - Выбор URI страницы OTA в Menuconfig + - Обновление скачивается частями, размер фрагмента закачки в Menuconfig + - Пример подключения +``` +#include "ota_ws_update.h" // handler definition + +// start webserver from esp-idf "examples/protocols/http_server/ws_echo_server" +static httpd_handle_t start_webserver(void) +{ + httpd_handle_t server = NULL; + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + // Start the httpd server + ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port); + if (httpd_start(&server, &config) == ESP_OK) { + ESP_LOGI(TAG, "Registering URI handlers"); + /****************** Registering the ws handler ****************/ + ota_ws_register_uri_handler(server); + // end register ota_ws handler + return server; + } + ESP_LOGI(TAG, "Error starting server!"); + return NULL; +} +``` + - Пример partitions.csv +``` +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x4000 +otadata, data, ota, , 0x2000 +phy_init, data, phy, , 0x1000 +ota_0, app, ota_0, , 1M +ota_1, app, ota_1, , 1M +``` + - Параметры menuconfig + - PARTITION_TABLE_CUSTOM=y + - PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" + - HTTPD_WS_SUPPORT=y + - APP_ROLLBACK_ENABLE=y + - OTA_DEFAULT_URI - адрес web интерфейса OTA + - OTA_DEFAULT_WS_URI - адрес ws/wss интерфейса OTA + - OTA_CHUNK_SIZE - размер фрагментов заказчки + - OTA_PRE_ENCRYPTED_MODE - включение PreEncrypted режима + - OTA_PRE_ENCRYPTED_RSA_KEY_LOCATION - место хранения rsa_private_key ( в каталоге компонента или в каталоге проекта ) + - OTA_PRE_ENCRYPTED_RSA_KEY_DIRECTORY - директория хранения rsa_private_key + diff --git a/components/ota_ws_update/README.md b/components/ota_ws_update/README.md new file mode 100644 index 0000000..49ae259 --- /dev/null +++ b/components/ota_ws_update/README.md @@ -0,0 +1,65 @@ +[Ru](/README-RU.md) + +| Supported Targets | +| ESP32 ESP32S3 ESP32C3 | +| ----------------- | + +# ESP32 OTA update via WebSocket with a simple WEB interface. Optional PreEncrypted mode. + - Connects as a component to your program + - Does not require external servers for storing OTA firmware, designed primarily for working on a local network. + - Uses WebsSocket or WebsSocket Secure protocol. + - Connects to any web server on esp32 that uses the WebSocket protocol, for example (esp-idf examples/protocols/http_server/ws_echo_server) or (esp-idf examples/protocols/https_server/wss_server) as a URI handler. + - depending on the server protocol (http/https), the exchange protocol (ws/wss) will be selected + - PreEncrypted mode (espressif/esp_encrypted_img) is enabled in Menuconfig + - PreEncrypted mode requires an increase in the stack size httpd_config_t config.stack_size = 4096*4; + - Details of using PreEncrypted mode https://components.espressif.com/components/espressif/esp_encrypted_img + - Example - example_ota_ws + - Web interface + - Select firmware file + - Upload firmware to esp32 + - Firmware download control + - After updating the firmware - confirm the update or roll back to the previous version + - Select OTA page URI in Menuconfig + - The update is downloaded in parts, the size of the download fragment is in Menuconfig + - Connection example +``` +#include "ota_ws_update.h" // handler definition + +// start webserver from esp-idf "examples/protocols/http_server/ws_echo_server" +static httpd_handle_t start_webserver(void) +{ + httpd_handle_t server = NULL; + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + // Start the httpd server + ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port); + if (httpd_start(&server, &config) == ESP_OK) { + ESP_LOGI(TAG, "Registering URI handlers"); + /****************** Registering the ws handler ****************/ + ota_ws_register_uri_handler(server); + // end register ota_ws handler + return server; + } + ESP_LOGI(TAG, "Error starting server!"); + return NULL; +} +``` + - Example partitions.csv +``` +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x4000 +otadata, data, ota, , 0x2000 +phy_init, data, phy, , 0x1000 +ota_0, app, ota_0, , 1M +ota_1, app, ota_1, , 1M +``` + - menuconfig parameters + - PARTITION_TABLE_CUSTOM=y + - PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" + - HTTPD_WS_SUPPORT=y + - APP_ROLLBACK_ENABLE=y + - OTA_DEFAULT_URI - OTA web interface address + - OTA_DEFAULT_WS_URI - ws/wss address of the OTA interface + - OTA_CHUNK_SIZE - size of order fragments + - OTA_PRE_ENCRYPTED_MODE - enable PreEncrypted mode + - OTA_PRE_ENCRYPTED_RSA_KEY_LOCATION - rsa_private_key storage location (in the component directory or in the project directory) + - OTA_PRE_ENCRYPTED_RSA_KEY_DIRECTORY - rsa_private_key storage directory \ No newline at end of file diff --git a/components/ota_ws_update/esp_encrypted_img/CHANGELOG.md b/components/ota_ws_update/esp_encrypted_img/CHANGELOG.md new file mode 100644 index 0000000..8eb5415 --- /dev/null +++ b/components/ota_ws_update/esp_encrypted_img/CHANGELOG.md @@ -0,0 +1,15 @@ +## 2.2.0 + +### Enhancements: +- Added an API to get the size of pre encrypted binary image header, this could be useful while computing entire decrypted image length: `esp_encrypted_img_get_header_size` + +## 2.1.0 + +### Enhancements: +- Added an API to abort the decryption process: `esp_encrypted_img_decrypt_abort` +- Added an API to check if the complete data has been received: `esp_encrypted_img_is_complete_data_received` + +## 2.0.4 + +- `rsa_pub_key` member of `esp_decrypt_cfg_t` structure is now deprecated. Please use `rsa_priv_key` instead. +- `rsa_pub_key_len` member of `esp_decrypt_cfg_t` structure is now deprecated. Please use `rsa_priv_key_len` instead. diff --git a/components/ota_ws_update/esp_encrypted_img/CMakeLists.txt b/components/ota_ws_update/esp_encrypted_img/CMakeLists.txt new file mode 100644 index 0000000..9804891 --- /dev/null +++ b/components/ota_ws_update/esp_encrypted_img/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "src/esp_encrypted_img.c" + INCLUDE_DIRS "include" + PRIV_REQUIRES mbedtls) diff --git a/components/ota_ws_update/esp_encrypted_img/LICENSE b/components/ota_ws_update/esp_encrypted_img/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/components/ota_ws_update/esp_encrypted_img/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/components/ota_ws_update/esp_encrypted_img/README.md b/components/ota_ws_update/esp_encrypted_img/README.md new file mode 100644 index 0000000..8841b9e --- /dev/null +++ b/components/ota_ws_update/esp_encrypted_img/README.md @@ -0,0 +1,56 @@ +# ESP Encrypted Image Abstraction Layer + +[![Component Registry](https://components.espressif.com/components/espressif/esp_encrypted_img/badge.svg)](https://components.espressif.com/components/espressif/esp_encrypted_img) + +This component provides an API interface to decrypt data defined in "ESP Encrypted Image" format. This format is as specified at [Image Format](#image-format) + +This component can help in integrating pre encrypted firmware in over-the-air updates. Additionally, this component can also be used for other use-cases which requires addition of encryption layer for custom data. + + +## Image Format + +![Image Format](https://raw.githubusercontent.com/espressif/idf-extra-components/master/esp_encrypted_img/image_format.png) + +```c +typedef struct { + char magic[4]; + char enc_gcm[384]; + char iv[16]; + char bin_size[4]; + char auth[16]; + char extra_header[88]; +} pre_enc_bin_header; +``` + +The above struct represents encrypted image header. + +Note: +* RSA-3072 key is provided to the tool externally. You can generate RSA key pair using following command: + + `openssl genrsa -out rsa_key/private.pem 3072` + +* AES-GCM key and IV are generated by the tool itself. + +## Tool Info + +This component also contains tool ([esp_enc_img_gen.py](https://github.com/espressif/idf-extra-components/blob/master/esp_encrypted_img/tools/esp_enc_img_gen.py)) to generate encrypted images using RSA3072 public key. + +### Encrypt the image + +``` +python esp_enc_img_gen.py encrypt /path/to/input.bin /path/to/RSA-public-key /path/to/enc.bin +``` + +### Decrypt the image +``` +python esp_enc_img_gen.py decrypt /path/to/enc.bin /path/to/RSA-private-key /path/to/output.bin +``` + + +To know more about the tool, use command: +`python esp_enc_img-gen.py --help` + + +## API Reference + +To learn more about how to use this component, please check API Documentation from header file [esp_encrypted_img.h](https://github.com/espressif/idf-extra-components/blob/master/esp_encrypted_img/include/esp_encrypted_img.h) diff --git a/components/ota_ws_update/esp_encrypted_img/idf_component.yml b/components/ota_ws_update/esp_encrypted_img/idf_component.yml new file mode 100644 index 0000000..ee1a3b5 --- /dev/null +++ b/components/ota_ws_update/esp_encrypted_img/idf_component.yml @@ -0,0 +1,6 @@ +dependencies: + idf: + version: '>=4.4' +description: ESP Encrypted Image Abstraction Layer +url: https://github.com/espressif/idf-extra-components/tree/master/esp_encrypted_img +version: 2.2.0 diff --git a/components/ota_ws_update/esp_encrypted_img/image_format.png b/components/ota_ws_update/esp_encrypted_img/image_format.png new file mode 100644 index 0000000..5882211 Binary files /dev/null and b/components/ota_ws_update/esp_encrypted_img/image_format.png differ diff --git a/components/ota_ws_update/esp_encrypted_img/include/esp_encrypted_img.h b/components/ota_ws_update/esp_encrypted_img/include/esp_encrypted_img.h new file mode 100644 index 0000000..663e73a --- /dev/null +++ b/components/ota_ws_update/esp_encrypted_img/include/esp_encrypted_img.h @@ -0,0 +1,168 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include +#include + +#if 0 //High level layout for state machine + +// *INDENT-OFF* +@startuml +[*] --> READ_MAGIC +READ_MAGIC --> READ_MAGIC : READ LEN < 4 +READ_MAGIC --> DECODE_MAGIC : READ LEN = 4 + +DECODE_MAGIC --> READ_GCM : MAGIC VERIFIED +DECODE_MAGIC --> ESP_FAIL : MAGIC VERIFICATION FAILED +PROCESS_BINARY --> ESP_FAIL : DECRYPTION FAILED + +READ_GCM --> READ_GCM : READ_LEN < 384 +READ_GCM --> DECRYPT_GCM : READ_LEN = 384 +DECRYPT_GCM --> ESP_FAIL : DECRYPTION FAILED +DECRYPT_GCM --> READ_IV : DECRYPTION SUCCESSFUL +READ_IV --> READ_IV : READ LEN < 16 +READ_IV --> READ_BIN_SIZE +READ_BIN_SIZE --> READ_BIN_SIZE : READ LEN < 5 +READ_BIN_SIZE --> READ_AUTH +READ_AUTH --> READ_AUTH : READ LEN < 16 +READ_AUTH --> PROCESS_BINARY +PROCESS_BINARY --> PROCESS_BINARY : READ LEN < BIN_SIZE + +PROCESS_BINARY --> ESP_OK : READ LEN = BIN_SIZE +ESP_OK --> [*] +ESP_FAIL --> [*] +@enduml +// *INDENT-OFF* +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)) +#define DEPRECATED_ATTRIBUTE __attribute__((deprecated)) +#else +#define DEPRECATED_ATTRIBUTE +#endif + +typedef void *esp_decrypt_handle_t; + +typedef struct { + union { + const char *rsa_priv_key; /*!< 3072 bit RSA private key in PEM format */ + const char *rsa_pub_key DEPRECATED_ATTRIBUTE; /*!< This name is kept for backward compatibility purpose, + but it is not accurate (meaning wise) and hence it would + be removed in the next major release */ + }; + union { + size_t rsa_priv_key_len; /*!< Length of the buffer pointed to by rsa_priv_key */ + size_t rsa_pub_key_len DEPRECATED_ATTRIBUTE; /*!< This name is kept for backward compatibility purpose, + but it is not accurate (meaning wise) and hence it would + be removed in the next major release */ + }; +} esp_decrypt_cfg_t; + +#undef DEPRECATED_ATTRIBUTE + +typedef struct { + const char *data_in; /*!< Pointer to data to be decrypted */ + size_t data_in_len; /*!< Input data length */ + char *data_out; /*!< Pointer to decrypted data */ + size_t data_out_len; /*!< Output data length */ +} pre_enc_decrypt_arg_t; + + +/** +* @brief This function returns esp_decrypt_handle_t handle. +* +* @param[in] cfg pointer to esp_decrypt_cfg_t structure +* +* @return +* - NULL On failure +* - esp_decrypt_handle_t handle +*/ +esp_decrypt_handle_t esp_encrypted_img_decrypt_start(const esp_decrypt_cfg_t *cfg); + + +/** +* @brief This function performs decryption on input data. +* +* This function must be called only if esp_encrypted_img_decrypt_start() returns successfully. +* This function must be called in a loop since input data might not contain whole binary at once. +* This function must be called till it return ESP_OK. +* +* @note args->data_out must be freed after use provided args->data_out_len is greater than 0 +* +* @param[in] ctx esp_decrypt_handle_t handle +* @param[in/out] args pointer to pre_enc_decrypt_arg_t +* +* @return +* - ESP_FAIL On failure +* - ESP_ERR_INVALID_ARG Invalid arguments +* - ESP_ERR_NOT_FINISHED Decryption is in process +* - ESP_OK Success +*/ +esp_err_t esp_encrypted_img_decrypt_data(esp_decrypt_handle_t ctx, pre_enc_decrypt_arg_t *args); + + +/** +* @brief Clean-up decryption process. +* +* @param[in] ctx esp_decrypt_handle_t handle +* +* @note This API cleans the decrypt handle and return ESP_FAIL if the complete data has not been decrypted. Verify if complete data +* has been decrypted using API `esp_encrypted_img_is_complete_data_received` to prevent an early call to this API. +* +* @return +* - ESP_FAIL On failure +* - ESP_ERR_INVALID_ARG Invalid argument +* - ESP_OK Success +*/ +esp_err_t esp_encrypted_img_decrypt_end(esp_decrypt_handle_t ctx); + +/** +* @brief Checks if the complete data has been decrypted. +* +* @note This API checks if complete data has been supplied to `esp_encrypted_img_decrypt_data`. This can be used to prevent an early +* call to `esp_encrypted_img_decrypt_end` which cleans up the decrypt handle. If this API returns true, then call `esp_encrypted_img_decrypt_end`. +* If this API returns false, and there is some other error (like network error) due to which decryption process should be terminated, +* call `esp_encrypted_img_decrypt_abort` to clean up the handle. +* +* @param[in] ctx esp_decrypt_handle_t handle +* +* @return +* - true +* - false +*/ +bool esp_encrypted_img_is_complete_data_received(esp_decrypt_handle_t ctx); + +/** +* @brief Abort the decryption process +* +* @param[in] ctx esp_decrypt_handle_t handle +* +* @return +* - ESP_ERR_INVALID_ARG Invalid argument +* - ESP_OK Success +*/ +esp_err_t esp_encrypted_img_decrypt_abort(esp_decrypt_handle_t ctx); + +/** +* @brief Get the size of pre encrypted binary image header (`struct pre_enc_bin_header`). The initial header in +* the image contains magic, credentials (symmetric key) and few other parameters. This API could be useful +* for scenarios where the entire decrypted image length must be computed by the application including the +* image header. +* +* @return +* - Header size of pre encrypted image +*/ +uint16_t esp_encrypted_img_get_header_size(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/ota_ws_update/esp_encrypted_img/project_include.cmake b/components/ota_ws_update/esp_encrypted_img/project_include.cmake new file mode 100644 index 0000000..1002cbe --- /dev/null +++ b/components/ota_ws_update/esp_encrypted_img/project_include.cmake @@ -0,0 +1,18 @@ +set(ESP_IMG_GEN_TOOL_PATH ${CMAKE_CURRENT_LIST_DIR}/tools/esp_enc_img_gen.py) + +function(create_esp_enc_img input_file rsa_key_file output_file app) + cmake_parse_arguments(arg "${options}" "" "${multi}" "${ARGN}") + idf_build_get_property(python PYTHON) + + add_custom_command(OUTPUT ${output_file} + POST_BUILD + COMMAND ${python} ${ESP_IMG_GEN_TOOL_PATH} encrypt + ${input_file} + ${rsa_key_file} ${output_file} + DEPENDS gen_project_binary + COMMENT "Generating pre-encrypted binary" + VERBATIM + ) + add_custom_target(encrypt_bin_target DEPENDS ${output_file}) + add_dependencies(${app} encrypt_bin_target) +endfunction() diff --git a/components/ota_ws_update/esp_encrypted_img/src/esp_encrypted_img.c b/components/ota_ws_update/esp_encrypted_img/src/esp_encrypted_img.c new file mode 100644 index 0000000..38b6dd5 --- /dev/null +++ b/components/ota_ws_update/esp_encrypted_img/src/esp_encrypted_img.c @@ -0,0 +1,481 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_encrypted_img.h" +#include +#include +#include + +#include "mbedtls/version.h" +#include "mbedtls/pk.h" +#include "mbedtls/entropy.h" +#include "mbedtls/ctr_drbg.h" +#include "mbedtls/gcm.h" +#include "sys/param.h" + +static const char *TAG = "esp_encrypted_img"; + +typedef enum { + ESP_PRE_ENC_IMG_READ_MAGIC, + ESP_PRE_ENC_IMG_READ_GCM, + ESP_PRE_ENC_IMG_READ_IV, + ESP_PRE_ENC_IMG_READ_BINSIZE, + ESP_PRE_ENC_IMG_READ_AUTH, + ESP_PRE_ENC_IMG_READ_EXTRA_HEADER, + ESP_PRE_ENC_DATA_DECODE_STATE, +} esp_encrypted_img_state; + +#define GCM_KEY_SIZE 32 +#define MAGIC_SIZE 4 +#define ENC_GCM_KEY_SIZE 384 +#define IV_SIZE 16 +#define BIN_SIZE_DATA 4 +#define AUTH_SIZE 16 +#define RESERVED_HEADER 88 + +struct esp_encrypted_img_handle { + char *rsa_pem; + size_t rsa_len; + uint32_t binary_file_len; + uint32_t binary_file_read; + char gcm_key[GCM_KEY_SIZE]; + char iv[IV_SIZE]; + char auth_tag[AUTH_SIZE]; + esp_encrypted_img_state state; + mbedtls_gcm_context gcm_ctx; + size_t cache_buf_len; + char *cache_buf; +}; + +typedef struct { + char magic[MAGIC_SIZE]; + char enc_gcm[ENC_GCM_KEY_SIZE]; + char iv[IV_SIZE]; + char bin_size[BIN_SIZE_DATA]; + char auth[AUTH_SIZE]; + char extra_header[RESERVED_HEADER]; +} pre_enc_bin_header; +#define HEADER_DATA_SIZE sizeof(pre_enc_bin_header) + +// Magic Byte is created using command: echo -n "esp_encrypted_img" | sha256sum +static uint32_t esp_enc_img_magic = 0x0788b6cf; + +typedef struct esp_encrypted_img_handle esp_encrypted_img_t; + +static int decipher_gcm_key(const char *enc_gcm, esp_encrypted_img_t *handle) +{ + int ret = 1; + size_t olen = 0; + mbedtls_pk_context pk; + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + const char *pers = "mbedtls_pk_encrypt"; + + mbedtls_ctr_drbg_init( &ctr_drbg ); + mbedtls_entropy_init( &entropy ); + mbedtls_pk_init( &pk ); + + if ((ret = mbedtls_ctr_drbg_seed( &ctr_drbg, mbedtls_entropy_func, + &entropy, (const unsigned char *) pers, + strlen(pers))) != 0) { + ESP_LOGE(TAG, "failed\n ! mbedtls_ctr_drbg_seed returned -0x%04x\n", (unsigned int) - ret); + goto exit; + } + + ESP_LOGI(TAG, "Reading RSA private key"); + +#if (MBEDTLS_VERSION_NUMBER < 0x03000000) + if ( (ret = mbedtls_pk_parse_key(&pk, (const unsigned char *) handle->rsa_pem, handle->rsa_len, NULL, 0)) != 0) { +#else + if ( (ret = mbedtls_pk_parse_key(&pk, (const unsigned char *) handle->rsa_pem, handle->rsa_len, NULL, 0, mbedtls_ctr_drbg_random, &ctr_drbg)) != 0) { +#endif + ESP_LOGE(TAG, "failed\n ! mbedtls_pk_parse_keyfile returned -0x%04x\n", (unsigned int) - ret ); + goto exit; + } + + if (( ret = mbedtls_pk_decrypt( &pk, (const unsigned char *)enc_gcm, ENC_GCM_KEY_SIZE, (unsigned char *)handle->gcm_key, &olen, GCM_KEY_SIZE, + mbedtls_ctr_drbg_random, &ctr_drbg ) ) != 0 ) { + ESP_LOGE(TAG, "failed\n ! mbedtls_pk_decrypt returned -0x%04x\n", (unsigned int) - ret ); + goto exit; + } + handle->cache_buf = realloc(handle->cache_buf, 16); + if (!handle->cache_buf) { + return ESP_ERR_NO_MEM; + } + handle->state = ESP_PRE_ENC_IMG_READ_IV; + handle->binary_file_read = 0; + handle->cache_buf_len = 0; +exit: + mbedtls_pk_free( &pk ); + mbedtls_entropy_free( &entropy ); + mbedtls_ctr_drbg_free( &ctr_drbg ); + free(handle->rsa_pem); + handle->rsa_pem = NULL; + + return (ret); +} + +esp_decrypt_handle_t esp_encrypted_img_decrypt_start(const esp_decrypt_cfg_t *cfg) +{ + if (cfg == NULL || cfg->rsa_priv_key == NULL) { + ESP_LOGE(TAG, "esp_encrypted_img_decrypt_start : Invalid argument"); + return NULL; + } + ESP_LOGI(TAG, "Starting Decryption Process"); + + esp_encrypted_img_t *handle = calloc(1, sizeof(esp_encrypted_img_t)); + if (!handle) { + ESP_LOGE(TAG, "Couldn't allocate memory to handle"); + goto failure; + } + + handle->rsa_pem = calloc(1, cfg->rsa_priv_key_len); + if (!handle->rsa_pem) { + ESP_LOGE(TAG, "Couldn't allocate memory to handle->rsa_pem"); + goto failure; + } + + handle->cache_buf = calloc(1, ENC_GCM_KEY_SIZE); + if (!handle->cache_buf) { + ESP_LOGE(TAG, "Couldn't allocate memory to handle->cache_buf"); + goto failure; + } + + memcpy(handle->rsa_pem, cfg->rsa_priv_key, cfg->rsa_priv_key_len); + handle->rsa_len = cfg->rsa_priv_key_len; + handle->state = ESP_PRE_ENC_IMG_READ_MAGIC; + + esp_decrypt_handle_t ctx = (esp_decrypt_handle_t)handle; + return ctx; + +failure: + if (handle) { + free(handle->rsa_pem); + free(handle); + } + return NULL; +} + +static esp_err_t process_bin(esp_encrypted_img_t *handle, pre_enc_decrypt_arg_t *args, int curr_index) +{ + size_t data_len = args->data_in_len; + size_t data_out_size = args->data_out_len; +#if !(MBEDTLS_VERSION_NUMBER < 0x03000000) + size_t olen; +#endif + handle->binary_file_read += data_len - curr_index; + int dec_len = 0; + if (handle->binary_file_read != handle->binary_file_len) { + size_t copy_len = 0; + + if ((handle->cache_buf_len + (data_len - curr_index)) - (handle->cache_buf_len + (data_len - curr_index)) % 16 > 0) { + data_out_size = (handle->cache_buf_len + (data_len - curr_index)) - (handle->cache_buf_len + (data_len - curr_index)) % 16; + args->data_out = realloc(args->data_out, data_out_size); + if (!args->data_out) { + return ESP_ERR_NO_MEM; + } + } + if (handle->cache_buf_len != 0) { + copy_len = MIN(16 - handle->cache_buf_len, data_len - curr_index); + memcpy(handle->cache_buf + handle->cache_buf_len, args->data_in + curr_index, copy_len); + handle->cache_buf_len += copy_len; + if (handle->cache_buf_len != 16) { + args->data_out_len = 0; + return ESP_ERR_NOT_FINISHED; + } +#if (MBEDTLS_VERSION_NUMBER < 0x03000000) + if (mbedtls_gcm_update(&handle->gcm_ctx, 16, (const unsigned char *)handle->cache_buf, (unsigned char *) args->data_out) != 0) { +#else + if (mbedtls_gcm_update(&handle->gcm_ctx, (const unsigned char *)handle->cache_buf, 16, (unsigned char *) args->data_out, data_out_size, &olen) != 0) { +#endif + return ESP_FAIL; + } + dec_len = 16; + } + handle->cache_buf_len = (data_len - curr_index - copy_len) % 16; + if (handle->cache_buf_len != 0) { + data_len -= handle->cache_buf_len; + memcpy(handle->cache_buf, args->data_in + (data_len), handle->cache_buf_len); + } + + if (data_len - copy_len - curr_index > 0) { +#if (MBEDTLS_VERSION_NUMBER < 0x03000000) + if (mbedtls_gcm_update(&handle->gcm_ctx, data_len - copy_len - curr_index, (const unsigned char *)args->data_in + curr_index + copy_len, (unsigned char *)args->data_out + dec_len) != 0) { +#else + if (mbedtls_gcm_update(&handle->gcm_ctx, (const unsigned char *)args->data_in + curr_index + copy_len, data_len - copy_len - curr_index, (unsigned char *)args->data_out + dec_len, data_out_size - dec_len, &olen) != 0) { +#endif + return ESP_FAIL; + } + } + args->data_out_len = dec_len + data_len - curr_index - copy_len; + return ESP_ERR_NOT_FINISHED; + } + data_out_size = handle->cache_buf_len + data_len - curr_index; + args->data_out = realloc(args->data_out, data_out_size); + if (!args->data_out) { + return ESP_ERR_NO_MEM; + } + size_t copy_len = 0; + + copy_len = MIN(16 - handle->cache_buf_len, data_len - curr_index); + memcpy(handle->cache_buf + handle->cache_buf_len, args->data_in + curr_index, copy_len); + handle->cache_buf_len += copy_len; +#if (MBEDTLS_VERSION_NUMBER < 0x03000000) + if (mbedtls_gcm_update(&handle->gcm_ctx, handle->cache_buf_len, (const unsigned char *)handle->cache_buf, (unsigned char *)args->data_out) != 0) { +#else + if (mbedtls_gcm_update(&handle->gcm_ctx, (const unsigned char *)handle->cache_buf, handle->cache_buf_len, (unsigned char *)args->data_out, data_out_size, &olen) != 0) { +#endif + return ESP_FAIL; + } + if (data_len - curr_index - copy_len > 0) { +#if (MBEDTLS_VERSION_NUMBER < 0x03000000) + if (mbedtls_gcm_update(&handle->gcm_ctx, data_len - curr_index - copy_len, (const unsigned char *)(args->data_in + curr_index + copy_len), (unsigned char *)(args->data_out + 16)) != 0) { +#else + if (mbedtls_gcm_update(&handle->gcm_ctx, (const unsigned char *)(args->data_in + curr_index + copy_len), data_len - curr_index - copy_len, (unsigned char *)(args->data_out + 16), data_out_size - 16, &olen) != 0) { +#endif + return ESP_FAIL; + } + } + + args->data_out_len = handle->cache_buf_len + data_len - copy_len - curr_index; + handle->cache_buf_len = 0; + + return ESP_OK; +} + +static void read_and_cache_data(esp_encrypted_img_t *handle, pre_enc_decrypt_arg_t *args, int *curr_index, int data_size) +{ + const int data_left = data_size - handle->binary_file_read; + const int data_recv = args->data_in_len - *curr_index; + if (handle->state == ESP_PRE_ENC_IMG_READ_IV) { + memcpy(handle->iv + handle->cache_buf_len, args->data_in + *curr_index, MIN(data_recv, data_left)); + } else if (handle->state == ESP_PRE_ENC_IMG_READ_AUTH) { + memcpy(handle->auth_tag + handle->cache_buf_len, args->data_in + *curr_index, MIN(data_recv, data_left)); + } else { + memcpy(handle->cache_buf + handle->cache_buf_len, args->data_in + *curr_index, MIN(data_recv, data_left)); + } + handle->cache_buf_len += MIN(data_recv, data_left); + int temp = *curr_index; + *curr_index += MIN(data_recv, data_left); + handle->binary_file_read += MIN(args->data_in_len - temp, data_left); +} + +esp_err_t esp_encrypted_img_decrypt_data(esp_decrypt_handle_t ctx, pre_enc_decrypt_arg_t *args) +{ + if (ctx == NULL || args == NULL || args->data_in == NULL) { + return ESP_ERR_INVALID_ARG; + } + esp_encrypted_img_t *handle = (esp_encrypted_img_t *)ctx; + if (handle == NULL) { + ESP_LOGE(TAG, "esp_encrypted_img_decrypt_data: Invalid argument"); + return ESP_ERR_INVALID_ARG; + } + + esp_err_t err; + int curr_index = 0; + + switch (handle->state) { + case ESP_PRE_ENC_IMG_READ_MAGIC: + if (handle->cache_buf_len == 0 && (args->data_in_len - curr_index) >= MAGIC_SIZE) { + uint32_t recv_magic = *(uint32_t *)args->data_in; + + if (recv_magic != esp_enc_img_magic) { + ESP_LOGE(TAG, "Magic Verification failed"); + free(handle->rsa_pem); + handle->rsa_pem = NULL; + return ESP_FAIL; + } + curr_index += MAGIC_SIZE; + } else { + read_and_cache_data(handle, args, &curr_index, MAGIC_SIZE); + if (handle->binary_file_read == MAGIC_SIZE) { + uint32_t recv_magic = *(uint32_t *)handle->cache_buf; + + if (recv_magic != esp_enc_img_magic) { + ESP_LOGE(TAG, "Magic Verification failed"); + free(handle->rsa_pem); + handle->rsa_pem = NULL; + return ESP_FAIL; + } + handle->binary_file_read = 0; + handle->cache_buf_len = 0; + } else { + return ESP_ERR_NOT_FINISHED; + } + } + ESP_LOGI(TAG, "Magic Verified"); + handle->state = ESP_PRE_ENC_IMG_READ_GCM; + /* falls through */ + case ESP_PRE_ENC_IMG_READ_GCM: + if (handle->cache_buf_len == 0 && args->data_in_len - curr_index >= ENC_GCM_KEY_SIZE) { + if (decipher_gcm_key(args->data_in + curr_index, handle) != 0) { + ESP_LOGE(TAG, "Unable to decipher GCM key"); + return ESP_FAIL; + } + curr_index += ENC_GCM_KEY_SIZE; + } else { + read_and_cache_data(handle, args, &curr_index, ENC_GCM_KEY_SIZE); + if (handle->cache_buf_len == ENC_GCM_KEY_SIZE) { + if (decipher_gcm_key(handle->cache_buf, handle) != 0) { + ESP_LOGE(TAG, "Unable to decipher GCM key"); + return ESP_FAIL; + } + } else { + return ESP_ERR_NOT_FINISHED; + } + } + /* falls through */ + case ESP_PRE_ENC_IMG_READ_IV: + if (handle->cache_buf_len == 0 && args->data_in_len - curr_index >= IV_SIZE) { + memcpy(handle->iv, args->data_in + curr_index, IV_SIZE); + handle->binary_file_read = IV_SIZE; + curr_index += IV_SIZE; + } else { + read_and_cache_data(handle, args, &curr_index, IV_SIZE); + } + if (handle->binary_file_read == IV_SIZE) { + handle->state = ESP_PRE_ENC_IMG_READ_BINSIZE; + handle->binary_file_read = 0; + handle->cache_buf_len = 0; + mbedtls_gcm_init(&handle->gcm_ctx); + if ((err = mbedtls_gcm_setkey(&handle->gcm_ctx, MBEDTLS_CIPHER_ID_AES, (const unsigned char *)handle->gcm_key, GCM_KEY_SIZE * 8)) != 0) { + ESP_LOGE(TAG, "Error: mbedtls_gcm_set_key: -0x%04x\n", (unsigned int) - err); + return ESP_FAIL; + } +#if (MBEDTLS_VERSION_NUMBER < 0x03000000) + if (mbedtls_gcm_starts(&handle->gcm_ctx, MBEDTLS_GCM_DECRYPT, (const unsigned char *)handle->iv, IV_SIZE, NULL, 0) != 0) { +#else + if (mbedtls_gcm_starts(&handle->gcm_ctx, MBEDTLS_GCM_DECRYPT, (const unsigned char *)handle->iv, IV_SIZE) != 0) { +#endif + ESP_LOGE(TAG, "Error: mbedtls_gcm_starts: -0x%04x\n", (unsigned int) - err); + return ESP_FAIL; + } + } else { + return ESP_ERR_NOT_FINISHED; + } + /* falls through */ + case ESP_PRE_ENC_IMG_READ_BINSIZE: + if (handle->cache_buf_len == 0 && (args->data_in_len - curr_index) >= BIN_SIZE_DATA) { + handle->binary_file_len = *(uint32_t *)(args->data_in + curr_index); + curr_index += BIN_SIZE_DATA; + } else { + read_and_cache_data(handle, args, &curr_index, BIN_SIZE_DATA); + if (handle->binary_file_read == BIN_SIZE_DATA) { + handle->binary_file_len = *(uint32_t *)handle->cache_buf; + } else { + return ESP_ERR_NOT_FINISHED; + } + } + handle->state = ESP_PRE_ENC_IMG_READ_AUTH; + handle->binary_file_read = 0; + handle->cache_buf_len = 0; + /* falls through */ + case ESP_PRE_ENC_IMG_READ_AUTH: + if (handle->cache_buf_len == 0 && args->data_in_len - curr_index >= AUTH_SIZE) { + memcpy(handle->auth_tag, args->data_in + curr_index, AUTH_SIZE); + handle->binary_file_read = AUTH_SIZE; + curr_index += AUTH_SIZE; + } else { + read_and_cache_data(handle, args, &curr_index, AUTH_SIZE); + } + if (handle->binary_file_read == AUTH_SIZE) { + handle->state = ESP_PRE_ENC_IMG_READ_EXTRA_HEADER; + handle->binary_file_read = 0; + handle->cache_buf_len = 0; + } else { + return ESP_ERR_NOT_FINISHED; + } + /* falls through */ + case ESP_PRE_ENC_IMG_READ_EXTRA_HEADER: { + int temp = curr_index; + curr_index += MIN(args->data_in_len - curr_index, RESERVED_HEADER - handle->binary_file_read); + handle->binary_file_read += MIN(args->data_in_len - temp, RESERVED_HEADER - handle->binary_file_read); + if (handle->binary_file_read == RESERVED_HEADER) { + handle->state = ESP_PRE_ENC_DATA_DECODE_STATE; + handle->binary_file_read = 0; + handle->cache_buf_len = 0; + } else { + return ESP_ERR_NOT_FINISHED; + } + } +/* falls through */ + case ESP_PRE_ENC_DATA_DECODE_STATE: + err = process_bin(handle, args, curr_index); + return err; + } + return ESP_OK; +} + +esp_err_t esp_encrypted_img_decrypt_end(esp_decrypt_handle_t ctx) +{ + if (ctx == NULL) { + return ESP_ERR_INVALID_ARG; + } + esp_encrypted_img_t *handle = (esp_encrypted_img_t *)ctx; + esp_err_t err = ESP_OK; + if (handle == NULL) { + ESP_LOGE(TAG, "esp_encrypted_img_decrypt_data: Invalid argument"); + return ESP_ERR_INVALID_ARG; + } + if (handle->state == ESP_PRE_ENC_DATA_DECODE_STATE) { + if (handle->cache_buf_len != 0 || handle->binary_file_read != handle->binary_file_len) { + ESP_LOGE(TAG, "Invalid operation"); + err = ESP_FAIL; + goto exit; + } + + unsigned char got_auth[AUTH_SIZE] = {0}; +#if (MBEDTLS_VERSION_NUMBER < 0x03000000) + err = mbedtls_gcm_finish(&handle->gcm_ctx, got_auth, AUTH_SIZE); +#else + size_t olen; + err = mbedtls_gcm_finish(&handle->gcm_ctx, NULL, 0, &olen, got_auth, AUTH_SIZE); +#endif + if (err != 0) { + ESP_LOGE(TAG, "Error: %d", err); + err = ESP_FAIL; + goto exit; + } + if (memcmp(got_auth, handle->auth_tag, AUTH_SIZE) != 0) { + ESP_LOGE(TAG, "Invalid Auth"); + err = ESP_FAIL; + goto exit; + } + } + err = ESP_OK; +exit: + mbedtls_gcm_free(&handle->gcm_ctx); + free(handle->cache_buf); + free(handle->rsa_pem); + free(handle); + return err; +} + +bool esp_encrypted_img_is_complete_data_received(esp_decrypt_handle_t ctx) +{ + esp_encrypted_img_t *handle = (esp_encrypted_img_t *)ctx; + return (handle != NULL && handle->binary_file_len == handle->binary_file_read); +} + +esp_err_t esp_encrypted_img_decrypt_abort(esp_decrypt_handle_t ctx) +{ + esp_encrypted_img_t *handle = (esp_encrypted_img_t *)ctx; + if (handle == NULL) { + ESP_LOGE(TAG, "esp_encrypted_img_decrypt_data: Invalid argument"); + return ESP_ERR_INVALID_ARG; + } + mbedtls_gcm_free(&handle->gcm_ctx); + free(handle->cache_buf); + free(handle->rsa_pem); + free(handle); + return ESP_OK; +} + +uint16_t esp_encrypted_img_get_header_size(void) +{ + return HEADER_DATA_SIZE; +} diff --git a/components/ota_ws_update/esp_encrypted_img/test/CMakeLists.txt b/components/ota_ws_update/esp_encrypted_img/test/CMakeLists.txt new file mode 100644 index 0000000..58df9d6 --- /dev/null +++ b/components/ota_ws_update/esp_encrypted_img/test/CMakeLists.txt @@ -0,0 +1,6 @@ +idf_component_register(SRC_DIRS "." + PRIV_INCLUDE_DIRS "." + REQUIRES unity + PRIV_REQUIRES cmock esp_encrypted_img + EMBED_TXTFILES certs/test_rsa_private_key.pem + EMBED_FILES image.bin) diff --git a/components/ota_ws_update/esp_encrypted_img/test/certs/test_rsa_private_key.pem b/components/ota_ws_update/esp_encrypted_img/test/certs/test_rsa_private_key.pem new file mode 100644 index 0000000..44d0970 --- /dev/null +++ b/components/ota_ws_update/esp_encrypted_img/test/certs/test_rsa_private_key.pem @@ -0,0 +1,39 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIG4wIBAAKCAYEA9eoohqolow2z6JsuBvqLLP+jfA/ha8t03skR0F4cKMXNFoc2 +QbqdYjRBaFOrkoUD/KC2TzvCjU3OZ3wuGDDjFWURsezUeYCH+r4PcF/qE6vXW1kV +ueE5jImTtxSsnt1HLFAGiUnILUJ7xi3cd86Y8mF0VH2wfmDKo8ESBRbev0eChCJA +xdynuuyo0m+IN9r7cNStCKz3jglipjnI5+OxtJgw/0fMaCfjtn7KIFktGEeXqJ1r +uRBALN+i70zZjjDtJj37FL5t4LCgrkImwLpBALVYFXy1wXhMXq7h5cU2Ec6bRVBl +6GH1xx2lABQ1LVFwrilgklLjY5UTdx8GHE/veR4Sla3dYyE5MKLJ3Kmc7NxlPlWn +WTmUKAWYIJiRIwBHSBntUgGyBYBqzfBSYFWL3gmTmvV/JQ41laX7qykdabvgdGSR +LJF6wZMdpJpvafYDBRwV7psvhPkHEXBUSe2dtfWKStiUPT7gcZE1ICMxT26Qlaf9 +T2GvYwyq4WBkEntzAgMBAAECggGAJKYUChW7bDRzlnvh/SpDqZ4jmC6psq3sqfMf +U4Vi/vSTnwLhpCQSpnsRMGIf1MM8F98/rElEslhhJW0NVY+bmCmq3HBmLgFowoam +uGGi+fGHM9bv9PbK49XxDLzpCPgDTmhSwQ0c5xncZmmZTMWeZ6j8dEcTEZKNQKBa +diW1Zp5apiSQsKw01xfEBTCYBXL+PA+GBh/4+NMPP6Sm+2AksLxpuPHTVcZ0GlOE +/hMsNE0fHgLv9fGlDsr5dl5modlKg7fnBejEAhnCPQadVdP2DiO6sXJbSGIxRc0c +WRT2nnmbN9UOAxKgO4Lz0ixgcPqBAD5ZDx4p5pVnakH1lB/Gh4VH4nAzxj8sUd0c +QcTJ06oHA8OVL/M01IRn4JUMeWdnO+AcuV45uszAx805ZJGRVeQMw/oGzjtYGjuk +jogdN279LcntDempzafBL8knD7GA/t2eCYf4Glwy7ZL9XW7b6w+J9pPD+4gduVCv +KcvdJxw4SM4q0HJo8YqCDfzQiOKZAoHBAP7JVMrkALdg2AovnRYgzHiLF56JMLip +I3l2jjz1fbi9dl+SRSD2LEhwuxy4/XWv5cu7O6dZJ4vG5RpjmFsCpLBRkuRp8qcx +V15T9M+2wFZX1kYCSgU5nljM+Szchs75k+3rKbRbfMQhreAxxTVV3T7i6aTq9nuR +PI26o8lNTDz6DCfGEkMM2Fg+ydTLGu+o2WIoSWO/o5xc1/l/aBbvbdZ5cRiRUbct +CiAeIO75hGHZJBgGaAqJI+p9g6tQbFXKbQKBwQD3FgJsj86C7qNSj5sVvbUqNI6Y +G0MfDLYlI01JAMtdiwgvafF91LNX2j/bHvglo4j6TI9zw2y4L/J0E3mMN0jCJXKE +DK91vwKwU9yMzacvVH8XgC+ntN9NPG/048Y4j4D3zgfoiNl2/AoazOjpL0iE2gGw +Cayx7JG4KrM41ZybpjafGfp/+ZgTeYZHiBEsNHQ/rSMw7bGkt2jKC4cywEtYCsM/ +mbr0QcUWH74Tx6YWdm6xGODEERjnu7IdbYADsV8CgcEAl5qMzb0lf/gsFMOIISab +BA8fmsHfL8HUze1xbWxVxptV2EBcyeQxLVmGvOyGRITJo5RhRo6SLWXH5Q/mFCFa +hV/EnA0+yaVea05hmUcQ40+YvEeYa8uBIS22Bq+ht35iO2t2gU7+ymWP5Js40Seq +YkT66Zq114jwExU/aASKnK3clb4SF7uI79lMl0XTXU+HKhT2tlfNrri/+kGJWjxV +iwzv8sJlcS1nnPzQc+Icl2xxQapuNfasXFcbBdDw5YtxAoHAH8Zo0WU8/YGK51co +bodTAPZ5T/5Rh3CvC9+aVMURYho7Fz3cnH36AlZC1/8Hkm+Rcf7eg9ih5p3j5CGN +BAcoCC+gpnKrLc0+n0ZpmoHn+iI3peIKPtr3zIr1Kt0P5L4vq66HPdQ7gx2ufvvT +CAnYnZ0bknPsDYWKx9BV8/0kgq/BXnyMxmBmujpqllBdRP4J5RZy7BvlOHWNuE37 +OP+ZsNzRdyBh9n9uxQWYABswtLrOSWAVp6E7PrHYmgg26kKpAoHABDv9fxlBrrCh +VGCRevH4ojHOoSvF0FqDzg0ixttPBxOYlKB6ggf+vOBjZVkE5ahON7IvTBvfiPAY +/H9J3uV6OGufzZURuXNdad1NFbcQvfHWvIgGhBozZe5b1seImG21PXDWIb2uZotw +Fg7eaXVYZDeA9jMA9roBbDnwypLAXO4Ve6/J9VXZmOBeXV9rbb0RuWEXgQ9FI1fT +CG3od/wm0D0sbZsAoNMD/fVOYRhbxQESpzynsgoxlWfzv2+t3Wjn +-----END RSA PRIVATE KEY----- diff --git a/components/ota_ws_update/esp_encrypted_img/test/image.bin b/components/ota_ws_update/esp_encrypted_img/test/image.bin new file mode 100644 index 0000000..6d4bb7c Binary files /dev/null and b/components/ota_ws_update/esp_encrypted_img/test/image.bin differ diff --git a/components/ota_ws_update/esp_encrypted_img/test/test.c b/components/ota_ws_update/esp_encrypted_img/test/test.c new file mode 100644 index 0000000..4d329b6 --- /dev/null +++ b/components/ota_ws_update/esp_encrypted_img/test/test.c @@ -0,0 +1,359 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include + +#include "unity.h" +#if __has_include("esp_random.h") +#include "esp_random.h" +#else +#include "esp_system.h" +#endif +#include "esp_encrypted_img.h" + +extern const uint8_t rsa_private_pem_start[] asm("_binary_test_rsa_private_key_pem_start"); +extern const uint8_t rsa_private_pem_end[] asm("_binary_test_rsa_private_key_pem_end"); + +extern const uint8_t bin_start[] asm("_binary_image_bin_start"); +extern const uint8_t bin_end[] asm("_binary_image_bin_end"); + +TEST_CASE("Sending all data at once", "[encrypted_img]") +{ + esp_decrypt_cfg_t cfg = { + .rsa_priv_key = (char *)rsa_private_pem_start, + .rsa_priv_key_len = rsa_private_pem_end - rsa_private_pem_start, + }; + esp_decrypt_handle_t ctx = esp_encrypted_img_decrypt_start(&cfg); + TEST_ASSERT_NOT_NULL(ctx); + + pre_enc_decrypt_arg_t *args = calloc(1, sizeof(pre_enc_decrypt_arg_t)); + TEST_ASSERT_NOT_NULL(args); + + args->data_in = (char *)bin_start; + args->data_in_len = bin_end - bin_start; + + esp_err_t err; + err = esp_encrypted_img_decrypt_data(ctx, args); + + TEST_ESP_OK(err); + printf("Successful\n"); + printf("\n"); + + err = esp_encrypted_img_decrypt_end(ctx); + TEST_ESP_OK(err); + if (args->data_out) { + free(args->data_out); + } + free(args); +} + +TEST_CASE("Sending 1 byte data at once", "[encrypted_img]") +{ + esp_decrypt_cfg_t cfg = { + .rsa_priv_key = (char *)rsa_private_pem_start, + .rsa_priv_key_len = rsa_private_pem_end - rsa_private_pem_start, + }; + esp_decrypt_handle_t ctx = esp_encrypted_img_decrypt_start(&cfg); + TEST_ASSERT_NOT_NULL(ctx); + + pre_enc_decrypt_arg_t *args = calloc(1, sizeof(pre_enc_decrypt_arg_t)); + TEST_ASSERT_NOT_NULL(args); + + esp_err_t err; + int i = 0; + do { + args->data_in = (char *)(bin_start + i); + i++; + args->data_in_len = 1; + err = esp_encrypted_img_decrypt_data(ctx, args); + if (err == ESP_FAIL) { + printf("ESP_FAIL ERROR\n"); + break; + } + } while (err != ESP_OK); + TEST_ESP_OK(err); + + err = esp_encrypted_img_decrypt_end(ctx); + TEST_ESP_OK(err); + if (args->data_out) { + free(args->data_out); + } + free(args); +} + +TEST_CASE("Invalid Magic", "[encrypted_img]") +{ + uint8_t cipher[] = { + 0x34, 0x12, 0xbc, 0xec, + 0xf5, 0x3a, 0x72, 0x55, 0x36, 0x0f, 0x4a, 0x92, + 0x5f, 0x15, 0xef, 0xbb, 0xe8, 0x9c, 0x7e, 0x43, + 0x7c, 0x87, 0x3d, 0x00, 0xb4, 0x22, 0xcc, 0x35, + 0x77, 0x92, 0x6a, 0x44, 0x80, 0x83, 0x9d, 0x0d, + 0x4c, 0x28, 0x5b, 0x7d, 0xbd, 0xef, 0x59, 0x56, + 0xba, 0x0f, 0x9d, 0xab, 0x3b, 0x09, 0x26, 0x77, + 0x82, 0x28, 0x24, 0x6e, 0xb8, 0x35, 0x96, 0x87, + 0xd6, 0x35, 0x03, 0x9b, 0x4e, 0x60, 0xaf, 0x11, + 0x14, 0x74, 0x13, 0x75, 0xb1, 0xf6, 0x5b, 0xe5, + 0xb9, 0xf0, 0x2b, 0x37, 0x02, 0xaf, 0x76, 0x8f, + 0xe1, 0x08, 0xfa, 0x46, 0xa1, 0xb0, 0x8d, 0x04, + 0xb6, 0x09, 0x8b, 0x0f, 0x8c, 0x54, 0xab, 0x9d, + 0xe9, 0x32, 0xb7, 0xae, 0xc5, 0x6d, 0x5d, 0x85, + 0x54, 0x2b, 0x1e, 0x8c, 0xbe, 0x4c, 0xee, 0x89, + 0xc4, 0x16, 0x97, 0x6c, 0x48, 0xaf, 0x3c, 0xef, + 0x90, 0x01, 0x12, 0xd6, 0x72, 0xf4, 0xca, 0xc5, + 0xa1, 0xec, 0x8d, 0xe0, 0x4b, 0xc8, 0xb2, 0xb4, + 0x66, 0x92, 0xd8, 0x0f, 0x65, 0x4d, 0x16, 0x30, + 0x6d, 0x2e, 0x3b, 0x64, 0x22, 0x36, 0x02, 0x43, + 0x10, 0xa8, 0x77, 0xb1, 0xf2, 0x8c, 0x29, 0x8d, + 0x23, 0xf2, 0xc6, 0xd4, 0x5b, 0x2e, 0x78, 0x15, + 0x8b, 0x5e, 0x0a, 0x2b, 0xd8, 0x76, 0x83, 0xe6, + 0xf9, 0x2a, 0xad, 0xf7, 0xf1, 0x06, 0x18, 0xbf, + 0xe1, 0x50, 0x89, 0xbf, 0x92, 0xfd, 0xe8, 0xcf, + 0x06, 0x5b, 0xf3, 0xa0, 0x3b, 0xa2, 0x60, 0x0e, + 0x04, 0xc8, 0x80, 0xb7, 0x97, 0xce, 0xdc, 0xc6, + 0x7c, 0xdb, 0x82, 0x77, 0xa7, 0x84, 0xf3, 0xf3, + 0x4a, 0xfa, 0xce, 0x9a, 0x1e, 0x4b, 0x81, 0x45, + 0xed, 0xfc, 0xd7, 0xe3, 0x4d, 0x40, 0x03, 0x8f, + 0x17, 0x30, 0x8f, 0x35, 0x62, 0xc3, 0x9e, 0x1d, + 0x11, 0xee, 0x3c, 0x5a, 0xbd, 0x3b, 0x32, 0x6c, + 0x16, 0xef, 0xab, 0x67, 0xe7, 0x57, 0x89, 0x61, + 0xbd, 0x1c, 0xb0, 0x28, 0x58, 0x3b, 0x70, 0xd7, + 0x4a, 0x8e, 0x08, 0x0b, 0x3b, 0x14, 0x15, 0x7c, + 0x0e, 0x97, 0xcc, 0xa6, 0x00, 0xf7, 0x0c, 0xa0, + 0x3a, 0xd0, 0xc7, 0x97, 0xbe, 0xf6, 0xc6, 0xe7, + 0xc4, 0xc4, 0x90, 0x3d, 0x94, 0xf8, 0xd3, 0x71, + 0x85, 0x6c, 0x40, 0x97, 0xae, 0x52, 0x0e, 0xe0, + 0x70, 0x15, 0x13, 0xe4, 0xcd, 0xbb, 0xf0, 0x80, + 0x11, 0x56, 0x08, 0xff, 0x71, 0xa3, 0xca, 0x00, + 0x43, 0x0d, 0xde, 0xa1, 0x81, 0xaa, 0x52, 0x9a, + 0xfd, 0x64, 0x58, 0x6c, 0x99, 0x37, 0x9b, 0x04, + 0x46, 0x33, 0x22, 0x88, 0x53, 0x1a, 0x99, 0x54, + 0xf2, 0x77, 0x00, 0x23, 0xf0, 0xf3, 0x24, 0x02, + 0x7d, 0x2a, 0x93, 0x98, 0x25, 0x5f, 0x55, 0xaf, + 0x99, 0xf6, 0x69, 0x4e, 0x3a, 0xdf, 0x8f, 0xed, + 0x59, 0x91, 0xaa, 0xff, 0x0b, 0xaa, 0x70, 0x8e, + 0x08, 0xfe, 0xa1, 0x32, 0xa9, 0xa7, 0xc6, 0xf9, + 0x1d, 0xfa, 0x52, 0x32, 0xc5, 0x5b, 0x64, 0x1e, + 0x75, 0x67, 0xe0, 0xac, 0xe0, 0x35, 0x9d, 0x4a, + 0x09, 0x00, 0x00, 0x00, 0x00, 0xb5, 0x9a, 0xf1, + 0xe2, 0x4a, 0xe8, 0xea, 0xed, 0x68, 0x15, 0xf4, + 0x04, 0x36, 0x96, 0x60, 0x48, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x5a, 0x2f, 0x2a, 0x16, + 0x6c, 0x8b, 0x34, 0x4e, 0xc0 + }; + + esp_decrypt_cfg_t cfg = { + .rsa_priv_key = (char *)rsa_private_pem_start, + .rsa_priv_key_len = rsa_private_pem_end - rsa_private_pem_start, + }; + esp_decrypt_handle_t ctx = esp_encrypted_img_decrypt_start(&cfg); + TEST_ASSERT_NOT_NULL(ctx); + + pre_enc_decrypt_arg_t *args = calloc(1, sizeof(pre_enc_decrypt_arg_t)); + TEST_ASSERT_NOT_NULL(args); + + args->data_in = (char *)cipher; + args->data_in_len = 521; + + esp_err_t err; + + err = esp_encrypted_img_decrypt_data(ctx, args); + + TEST_ESP_ERR(ESP_FAIL, err); + + err = esp_encrypted_img_decrypt_end(ctx); + free(args); +} + +TEST_CASE("Invalid Image", "[encrypted_img]") +{ + //"Espressif" is encoded using GCM key. After successful decoding, "Espressif" will be printed. + uint8_t cipher[] = { + 0xcf, 0xb6, 0x88, 0x07, + 0xf5, 0x3a, 0x72, 0x55, 0x36, 0x0f, 0x4a, 0x92, + 0x5f, 0x15, 0xef, 0xbb, 0xe8, 0x9c, 0x7e, 0x43, + 0x7c, 0x87, 0x3d, 0x00, 0xb4, 0x22, 0xcc, 0x35, + 0x77, 0x92, 0x6a, 0x44, 0x80, 0x83, 0x9d, 0x0d, + 0x4c, 0x28, 0x5b, 0x7d, 0xbd, 0xef, 0x59, 0x56, + 0xba, 0x0f, 0x9d, 0xab, 0x3b, 0x09, 0x26, 0x77, + 0x82, 0x28, 0x24, 0x6e, 0xb8, 0x35, 0x96, 0x87, + 0xd6, 0x35, 0x03, 0x9b, 0x4e, 0x60, 0xaf, 0x11, + 0x14, 0x74, 0x13, 0x75, 0xb1, 0xf6, 0x5b, 0xe5, + 0xb9, 0xf0, 0x2b, 0x37, 0x02, 0xaf, 0x76, 0x8f, + 0xe1, 0x08, 0xfa, 0x46, 0xa1, 0xb0, 0x8d, 0x04, + 0xb6, 0x09, 0x8b, 0x0f, 0x8c, 0x54, 0xab, 0x9d, + 0xe9, 0x32, 0xb7, 0xae, 0xc5, 0x6d, 0x5d, 0x85, + 0x54, 0x2b, 0x1e, 0x8c, 0xbe, 0x4c, 0xee, 0x89, + 0xc4, 0x16, 0x97, 0x6c, 0x48, 0xaf, 0x3c, 0xef, + 0x90, 0x01, 0x12, 0xd6, 0x72, 0xf4, 0xca, 0xc5, + 0xa1, 0xec, 0x8d, 0xe0, 0x4b, 0xc8, 0xb2, 0xb4, + 0x66, 0x92, 0xd8, 0x0f, 0x65, 0x4d, 0x16, 0x30, + 0x6d, 0x2e, 0x3b, 0x64, 0x22, 0x36, 0x02, 0x43, + 0x10, 0xa8, 0x77, 0xb1, 0xf2, 0x8c, 0x29, 0x8d, + 0x23, 0xf2, 0xc6, 0xd4, 0x5b, 0x2e, 0x78, 0x15, + 0x8b, 0x5e, 0x0a, 0x2b, 0xd8, 0x76, 0x83, 0xe6, + 0xf9, 0x2a, 0xad, 0xf7, 0xf1, 0x06, 0x18, 0xbf, + 0xe1, 0x50, 0x89, 0xbf, 0x92, 0xfd, 0xe8, 0xcf, + 0x06, 0x5b, 0xf3, 0xa0, 0x3b, 0xa2, 0x60, 0x0e, + 0x04, 0xc8, 0x80, 0xb7, 0x97, 0xce, 0xdc, 0xc6, + 0x7c, 0xdb, 0x82, 0x77, 0xa7, 0x84, 0xf3, 0xf3, + 0x4a, 0xfa, 0xce, 0x9a, 0x1e, 0x4b, 0x81, 0x45, + 0xed, 0xfc, 0xd7, 0xe3, 0x4d, 0x40, 0x03, 0x8f, + 0x17, 0x30, 0x8f, 0x35, 0x62, 0xc3, 0x9e, 0x1d, + 0x11, 0xee, 0x3c, 0x5a, 0xbd, 0x3b, 0x32, 0x6c, + 0x16, 0xef, 0xab, 0x67, 0xe7, 0x57, 0x89, 0x61, + 0xbd, 0x1c, 0xb0, 0x28, 0x58, 0x3b, 0x70, 0xd7, + 0x4a, 0x8e, 0x08, 0x0b, 0x3b, 0x14, 0x15, 0x7c, + 0x0e, 0x97, 0xcc, 0xa6, 0x00, 0xf7, 0x0c, 0xa0, + 0x3a, 0xd0, 0xc7, 0x97, 0xbe, 0xf6, 0xc6, 0xe7, + 0xc4, 0xc4, 0x90, 0x3d, 0x94, 0xf8, 0xd3, 0x71, + 0x85, 0x6c, 0x40, 0x97, 0xae, 0x52, 0x0e, 0xe0, + 0x70, 0x15, 0x13, 0xe4, 0xcd, 0xbb, 0xf0, 0x80, + 0x11, 0x56, 0x08, 0xff, 0x71, 0xa3, 0xca, 0x00, + 0x43, 0x0d, 0xde, 0xa1, 0x81, 0xaa, 0x52, 0x9a, + 0xfd, 0x64, 0x58, 0x6c, 0x99, 0x37, 0x9b, 0x04, + 0x46, 0x33, 0x22, 0x88, 0x53, 0x1a, 0x99, 0x54, + 0xf2, 0x77, 0x00, 0x23, 0xf0, 0xf3, 0x24, 0x02, + 0x7d, 0x2a, 0x93, 0x98, 0x25, 0x5f, 0x55, 0xaf, + 0x99, 0xf6, 0x69, 0x4e, 0x3a, 0xdf, 0x8f, 0xed, + 0x59, 0x91, 0xaa, 0xff, 0x0b, 0xaa, 0x70, 0x8e, + 0x08, 0xfe, 0xa1, 0x32, 0xa9, 0xa7, 0xc6, 0xf9, + 0x1d, 0xfa, 0x52, 0x32, 0xc5, 0x5b, 0x64, 0x1e, + 0x75, 0x67, 0xe0, 0xac, 0xe0, 0x35, 0x9d, 0x4a, + 0x09, 0x00, 0x00, 0x00, 0x00, 0xb5, 0x9a, 0xf1, + 0xe2, 0x4a, 0xe8, 0xea, 0xed, 0x68, 0x15, 0xf4, + 0x04, 0x36, 0x96, 0x60, 0x48, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x5a, 0x2f, 0x2a, 0x16, + 0x6c, 0x8b, 0x34, 0x4e, 0xd0 + }; + + esp_decrypt_cfg_t cfg = { + .rsa_priv_key = (char *)rsa_private_pem_start, + .rsa_priv_key_len = rsa_private_pem_end - rsa_private_pem_start, + }; + esp_decrypt_handle_t ctx = esp_encrypted_img_decrypt_start(&cfg); + TEST_ASSERT_NOT_NULL(ctx); + + pre_enc_decrypt_arg_t *args = calloc(1, sizeof(pre_enc_decrypt_arg_t)); + TEST_ASSERT_NOT_NULL(args); + + args->data_in = (char *)cipher; + args->data_in_len = 521; + + esp_err_t err; + err = esp_encrypted_img_decrypt_data(ctx, args); + + TEST_ESP_OK(err); + + err = esp_encrypted_img_decrypt_end(ctx); + TEST_ESP_ERR(ESP_FAIL, err); + if (args->data_out) { + free(args->data_out); + } + free(args); +} + +TEST_CASE("Sending random size data at once", "[encrypted_img]") +{ + esp_decrypt_cfg_t cfg = { + .rsa_priv_key = (char *)rsa_private_pem_start, + .rsa_priv_key_len = rsa_private_pem_end - rsa_private_pem_start, + }; + esp_decrypt_handle_t ctx = esp_encrypted_img_decrypt_start(&cfg); + TEST_ASSERT_NOT_NULL(ctx); + + pre_enc_decrypt_arg_t *args = calloc(1, sizeof(pre_enc_decrypt_arg_t)); + TEST_ASSERT_NOT_NULL(args); + + esp_err_t err; + + int i = 0; + do { + uint32_t y = esp_random(); + y = (y % 16) + 1; + uint32_t x = y < ((bin_end - bin_start) - i) ? y : ((bin_end - bin_start) - i); + args->data_in = (char *)(bin_start + i); + i += x; + args->data_in_len = x; + err = esp_encrypted_img_decrypt_data(ctx, args); + if (err == ESP_FAIL) { + printf("ESP_FAIL ERROR\n"); + break; + } + } while (err != ESP_OK); + + TEST_ESP_OK(err); + + err = esp_encrypted_img_decrypt_end(ctx); + TEST_ESP_OK(err); + if (args->data_out) { + free(args->data_out); + } + free(args); +} + +TEST_CASE("Sending imcomplete data", "[encrypted_img]") +{ + esp_decrypt_cfg_t cfg = { + .rsa_priv_key = (char *)rsa_private_pem_start, + .rsa_priv_key_len = rsa_private_pem_end - rsa_private_pem_start, + }; + esp_decrypt_handle_t ctx = esp_encrypted_img_decrypt_start(&cfg); + TEST_ASSERT_NOT_NULL(ctx); + + pre_enc_decrypt_arg_t *args = calloc(1, sizeof(pre_enc_decrypt_arg_t)); + TEST_ASSERT_NOT_NULL(args); + + args->data_in = (char *)bin_start; + args->data_in_len = (bin_end - bin_start) - 1; + + esp_err_t err; + err = esp_encrypted_img_decrypt_data(ctx, args); + TEST_ESP_ERR(ESP_ERR_NOT_FINISHED, err); + + TEST_ESP_ERR(false, esp_encrypted_img_is_complete_data_received(ctx)); + + err = esp_encrypted_img_decrypt_abort(ctx); + TEST_ESP_OK(err); + if (args->data_out) { + free(args->data_out); + } + free(args); +} + +TEST_CASE("Test canceling decryption frees memory", "[encrypted_img]") +{ + esp_decrypt_cfg_t cfg = { + .rsa_priv_key = (char *)rsa_private_pem_start, + .rsa_priv_key_len = rsa_private_pem_end - rsa_private_pem_start, + }; + int free_bytes_start = xPortGetFreeHeapSize(); + esp_decrypt_handle_t ctx = esp_encrypted_img_decrypt_start(&cfg); + TEST_ASSERT_NOT_NULL(ctx); + + esp_err_t err = esp_encrypted_img_decrypt_abort(ctx); + TEST_ESP_OK(err); + int free_bytes_end = xPortGetFreeHeapSize(); + + // +/- 16 bytes to allow for some small fluctuations + TEST_ASSERT(abs(free_bytes_start - free_bytes_end) <= 16); +} diff --git a/components/ota_ws_update/esp_encrypted_img/tools/esp_enc_img_gen.py b/components/ota_ws_update/esp_encrypted_img/tools/esp_enc_img_gen.py new file mode 100644 index 0000000..4534078 --- /dev/null +++ b/components/ota_ws_update/esp_encrypted_img/tools/esp_enc_img_gen.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python +# +# Encrypted image generation tool. This tool helps in generating encrypted binary image +# in pre-defined format with assistance of RSA-3072 bit key. +# +# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +import argparse +import os +import sys + +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.ciphers.aead import AESGCM + +# Magic Byte is created using command: echo -n "esp_encrypted_img" | sha256sum +esp_enc_img_magic = 0x0788b6cf + +GCM_KEY_SIZE = 32 +MAGIC_SIZE = 4 +ENC_GCM_KEY_SIZE = 384 +IV_SIZE = 16 +BIN_SIZE_DATA = 4 +AUTH_SIZE = 16 +RESERVED_HEADER = (512 - (MAGIC_SIZE + ENC_GCM_KEY_SIZE + IV_SIZE + BIN_SIZE_DATA + AUTH_SIZE)) + + +def generate_key_GCM(size: int) -> bytes: + return os.urandom(int(size)) + + +def generate_IV_GCM() -> bytes: + return os.urandom(IV_SIZE) + + +def encrypt_binary(plaintext: bytes, key: bytes, IV: bytes) -> tuple: + encobj = AESGCM(key) + ct = encobj.encrypt(IV, plaintext, None) + return ct[:len(plaintext)], ct[len(plaintext):] + + +def encrypt(input_file: str, rsa_key_file_name: str, output_file: str) -> None: + print('Encrypting image ...') + with open(input_file, 'rb') as image: + data = image.read() + + with open(rsa_key_file_name, 'rb') as key_file: + key_data = key_file.read() + if b"-BEGIN RSA PRIVATE KEY" in key_data or b"-BEGIN PRIVATE KEY" in key_data: + private_key = serialization.load_pem_private_key(key_data, password=None) + public_key = private_key.public_key() + elif b"-BEGIN PUBLIC KEY" in key_data: + public_key = serialization.load_pem_public_key(key_data) + else: + print("Error: Please specify encryption key in PEM format") + raise SystemExit(1) + + gcm_key = generate_key_GCM(GCM_KEY_SIZE) + iv = generate_IV_GCM() + + encrypted_gcm_key = public_key.encrypt(gcm_key, padding.PKCS1v15()) + ciphertext, authtag = encrypt_binary(data, gcm_key, iv) + + with open(output_file, 'wb') as image: + image.write(esp_enc_img_magic.to_bytes(MAGIC_SIZE, 'little')) + image.write((encrypted_gcm_key)) + image.write((iv)) + image.write(len(ciphertext).to_bytes(BIN_SIZE_DATA, 'little')) + image.write(authtag) + image.write(bytearray(RESERVED_HEADER)) + image.write(ciphertext) + + print('Done') + + +def decrypt_binary(ciphertext: bytes, authTag: bytes, key: bytes, IV: bytes) -> bytes: + encobj = AESGCM(key) + plaintext = encobj.decrypt(IV, ciphertext + authTag, None) + return plaintext + + +def decrypt(input_file: str, rsa_key: str, output_file: str) -> None: + print('Decrypting image ...') + with open(rsa_key, 'rb') as key_file: + private_key = serialization.load_pem_private_key(key_file.read(), password=None) + + with open(input_file, 'rb') as file: + recv_magic = file.read(MAGIC_SIZE) + if(int.from_bytes(recv_magic, 'little') != esp_enc_img_magic): + print('Error: Magic Verification Failed', file=sys.stderr) + raise SystemExit(1) + print('Magic verified successfully') + + encrypted_gcm_key = file.read(ENC_GCM_KEY_SIZE) + gcm_key = private_key.decrypt(encrypted_gcm_key, padding.PKCS1v15()) + + iv = file.read(IV_SIZE) + bin_size = int.from_bytes(file.read(BIN_SIZE_DATA), 'little') + auth = file.read(AUTH_SIZE) + + file.read(RESERVED_HEADER) + enc_bin = file.read(bin_size) + + decrypted_binary = decrypt_binary(enc_bin, auth, gcm_key, iv) + + with open(output_file, 'wb') as file: + file.write(decrypted_binary) + print('Done') + + +def main() -> None: + parser = argparse.ArgumentParser('Encrypted Image Tool') + subparsers = parser.add_subparsers(dest='operation', help='run enc_image -h for additional help') + subparsers.add_parser('encrypt', help='Encrypt an binary') + subparsers.add_parser('decrypt', help='Decrypt an encrypted image') + parser.add_argument('input_file') + parser.add_argument('RSA_key', help='Private key for decryption and Private/Public key for encryption') + parser.add_argument('output_file_name') + + args = parser.parse_args() + + if(args.operation == 'encrypt'): + encrypt(args.input_file, args.RSA_key, args.output_file_name) + if(args.operation == 'decrypt'): + decrypt(args.input_file, args.RSA_key, args.output_file_name) + + +if __name__ == '__main__': + main() diff --git a/components/ota_ws_update/example_ota_ws/CMakeLists.txt b/components/ota_ws_update/example_ota_ws/CMakeLists.txt new file mode 100644 index 0000000..3c250ce --- /dev/null +++ b/components/ota_ws_update/example_ota_ws/CMakeLists.txt @@ -0,0 +1,12 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +# (Not part of the boilerplate) +# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection. +#set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common) + +set(EXTRA_COMPONENT_DIRS ../) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(example_ota_ws_update) diff --git a/components/ota_ws_update/example_ota_ws/example_ota_ws.code-workspace b/components/ota_ws_update/example_ota_ws/example_ota_ws.code-workspace new file mode 100644 index 0000000..437ac53 --- /dev/null +++ b/components/ota_ws_update/example_ota_ws/example_ota_ws.code-workspace @@ -0,0 +1,11 @@ +{ + "folders": [ + { + "path": "." + }, + { + "path": ".." + } + ], + "settings": {} +} \ No newline at end of file diff --git a/components/ota_ws_update/example_ota_ws/main/CMakeLists.txt b/components/ota_ws_update/example_ota_ws/main/CMakeLists.txt new file mode 100644 index 0000000..f1bbcd1 --- /dev/null +++ b/components/ota_ws_update/example_ota_ws/main/CMakeLists.txt @@ -0,0 +1,15 @@ +# Embed the server root certificate into the final binary + +idf_component_register( + SRCS + example_ota_ws_update.c + example_echo_ws_server.c + REQUIRES + ota_ws_update + nvs_wifi_connect + esp_http_server + mdns + esp_wifi + EMBED_FILES + example_echo_ws_server.html +) \ No newline at end of file diff --git a/components/ota_ws_update/example_ota_ws/main/example_echo_ws_server.c b/components/ota_ws_update/example_ota_ws/main/example_echo_ws_server.c new file mode 100644 index 0000000..37414cb --- /dev/null +++ b/components/ota_ws_update/example_ota_ws/main/example_echo_ws_server.c @@ -0,0 +1,174 @@ +/* WebSocket Echo Server Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include + +#include + +#include "nvs_wifi_connect.h" +#include "ota_ws_update.h" + +/* A simple example that demonstrates using websocket echo server + */ +static const char *TAG = "example_ws_echo_server"; + + +/* + * This handler echos back the received ws data + * and triggers an async send if certain message received + */ +static esp_err_t echo_handler(httpd_req_t *req) +{ + if (req->method == HTTP_GET) { + ESP_LOGI(TAG, "Handshake done, the new connection was opened"); + return ESP_OK; + } + httpd_ws_frame_t ws_pkt; + uint8_t *buf = NULL; + memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); + ws_pkt.type = HTTPD_WS_TYPE_TEXT; + /* Set max_len = 0 to get the frame len */ + esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 0); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "httpd_ws_recv_frame failed to get frame len with %d", ret); + return ret; + } + //ESP_LOGI(TAG, "frame len is %d", ws_pkt.len); + if (ws_pkt.len) { + /* ws_pkt.len + 1 is for NULL termination as we are expecting a string */ + buf = calloc(1, ws_pkt.len + 1); + if (buf == NULL) { + ESP_LOGE(TAG, "Failed to calloc memory for buf"); + return ESP_ERR_NO_MEM; + } + ws_pkt.payload = buf; + /* Set max_len = ws_pkt.len to get the frame payload */ + ret = httpd_ws_recv_frame(req, &ws_pkt, ws_pkt.len); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "httpd_ws_recv_frame failed with %d", ret); + free(buf); + return ret; + } + //ESP_LOGI(TAG, "Got packet with message: %s", ws_pkt.payload); + } + //ESP_LOGI(TAG, "Packet type: %d", ws_pkt.type); + + ret = httpd_ws_send_frame(req, &ws_pkt); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "httpd_ws_send_frame failed with %d", ret); + } + free(buf); + return ret; +} + +static const httpd_uri_t example_ws = { + .uri = "/ws", + .method = HTTP_GET, + .handler = echo_handler, + .user_ctx = NULL, + .is_websocket = true +}; + +static esp_err_t get_handler(httpd_req_t *req) +{ + extern const unsigned char example_echo_ws_server_html_start[] asm("_binary_example_echo_ws_server_html_start"); + extern const unsigned char example_echo_ws_server_html_end[] asm("_binary_example_echo_ws_server_html_end"); + const size_t example_echo_ws_server_html_size = (example_echo_ws_server_html_end - example_echo_ws_server_html_start); + + httpd_resp_send_chunk(req, (const char *)example_echo_ws_server_html_start, example_echo_ws_server_html_size); + httpd_resp_sendstr_chunk(req, NULL); + return ESP_OK; +} +static const httpd_uri_t example_gh = { + .uri = "/", + .method = HTTP_GET, + .handler = get_handler, + .user_ctx = NULL}; + +esp_err_t example_register_uri_handler(httpd_handle_t server); + +static httpd_handle_t start_webserver(void) +{ + httpd_handle_t server = NULL; + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + config.stack_size = 4096*4; + + // Start the httpd server + ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port); + if (httpd_start(&server, &config) == ESP_OK) { + // Registering the ws handler + ESP_LOGI(TAG, "Registering URI handlers"); + example_register_uri_handler(server); + nvs_wifi_connect_register_uri_handler(server); + ota_ws_register_uri_handler(server); + return server; + } + + ESP_LOGI(TAG, "Error starting server!"); + return NULL; +} + +static esp_err_t stop_webserver(httpd_handle_t server) +{ + // Stop the httpd server + return httpd_stop(server); +} + +static void disconnect_handler(void* arg, esp_event_base_t event_base, + int32_t event_id, void* event_data) +{ + httpd_handle_t* server = (httpd_handle_t*) arg; + if (*server) { + ESP_LOGI(TAG, "Stopping webserver"); + if (stop_webserver(*server) == ESP_OK) { + *server = NULL; + } else { + ESP_LOGE(TAG, "Failed to stop http server"); + } + } +} + +static void connect_handler(void* arg, esp_event_base_t event_base, + int32_t event_id, void* event_data) +{ + httpd_handle_t* server = (httpd_handle_t*) arg; + if (*server == NULL) { + ESP_LOGI(TAG, "Starting webserver"); + *server = start_webserver(); + } +} + + +void example_echo_ws_server(void) +{ + static httpd_handle_t server = NULL; + + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &connect_handler, &server)); + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &disconnect_handler, &server)); + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_AP_STACONNECTED, &connect_handler, &server)); + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_AP_STADISCONNECTED, &disconnect_handler, &server)); + + /* Start the server for the first time */ + server = start_webserver(); +} + +esp_err_t example_register_uri_handler(httpd_handle_t server) +{ + esp_err_t ret = ESP_OK; + ret = httpd_register_uri_handler(server, &example_gh); + if (ret) + goto _ret; + ret = httpd_register_uri_handler(server, &example_ws); + if (ret) + goto _ret; +_ret: + return ret; +} diff --git a/components/ota_ws_update/example_ota_ws/main/example_echo_ws_server.html b/components/ota_ws_update/example_ota_ws/main/example_echo_ws_server.html new file mode 100644 index 0000000..bbe8257 --- /dev/null +++ b/components/ota_ws_update/example_ota_ws/main/example_echo_ws_server.html @@ -0,0 +1,131 @@ + + + + + + Example Websocket Echo Server + + + + + + + +
Example Websocket Echo Server
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/components/ota_ws_update/example_ota_ws/main/example_ota_ws_update.c b/components/ota_ws_update/example_ota_ws/main/example_ota_ws_update.c new file mode 100644 index 0000000..b9ba58f --- /dev/null +++ b/components/ota_ws_update/example_ota_ws/main/example_ota_ws_update.c @@ -0,0 +1,55 @@ +/* OTA example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#include "esp_log.h" +#include "esp_http_server.h" + +#include "nvs_wifi_connect.h" +#include "ota_ws_update.h" + +extern esp_err_t example_register_uri_handler(httpd_handle_t server); +void example_echo_ws_server(void); + +//static const char *TAG = "ota_ws"; + +#define MDNS +#ifdef MDNS +#define HOST_NAME "esp-ota" +#include "mdns.h" +#include "lwip/apps/netbiosns.h" +#endif // MDNS +#ifdef MDNS +static void initialise_mdns(void) +{ + mdns_init(); + mdns_hostname_set(HOST_NAME); + mdns_instance_name_set("esp home web server"); + + mdns_txt_item_t serviceTxtData[] = { + {"board", "esp32"}, + {"path", "/"}}; + + ESP_ERROR_CHECK(mdns_service_add("ESP32-WebServer", "_http", "_tcp", 80, serviceTxtData, + sizeof(serviceTxtData) / sizeof(serviceTxtData[0]))); + netbiosns_init(); + netbiosns_set_name(HOST_NAME); +} +#endif // MDNS + + + +void app_main(void) +{ + nvs_wifi_connect(); // return with error ? + +#ifdef MDNS + initialise_mdns(); +#endif // MDNS + + example_echo_ws_server(); +} diff --git a/components/ota_ws_update/example_ota_ws/partitions.csv b/components/ota_ws_update/example_ota_ws/partitions.csv new file mode 100644 index 0000000..2a4274c --- /dev/null +++ b/components/ota_ws_update/example_ota_ws/partitions.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x4000 +otadata, data, ota, , 0x2000 +phy_init, data, phy, , 0x1000 +ota_0, app, ota_0, , 1M +ota_1, app, ota_1, , 1M \ No newline at end of file diff --git a/components/ota_ws_update/example_ota_ws/rsa_key/private_rsa_3072.pem b/components/ota_ws_update/example_ota_ws/rsa_key/private_rsa_3072.pem new file mode 100644 index 0000000..2eadbed --- /dev/null +++ b/components/ota_ws_update/example_ota_ws/rsa_key/private_rsa_3072.pem @@ -0,0 +1,40 @@ +-----BEGIN PRIVATE KEY----- +MIIG/gIBADANBgkqhkiG9w0BAQEFAASCBugwggbkAgEAAoIBgQDFmskeWxaJtPCV +Xydauqm6Q/wW9D0ewNUo+L9sopGz/Lrw5JdynLQDcxcioqpow5Nr9A8y8+YJc9zH +sMs4qJYDLcxROeimGlPqtrj4OxFXDyok12fxDquYTXaJzTAM+rp/i/VJJVKonYU0 +DOuG8tX1lsrlfkwYixaoPUGL/WuG1srHay5NKCcXjYVUKsjHkfEgfKAhF/6cPTj6 +yeKH6nNQQofOYUN5hkGgT19ek3or0dYph9r3VozyjV12/D1K+o0Vl+LQc7IX3xxN +DB9s3aGVIMglir8AY2ZjkqRyJRzbIrjufXYxw/e6uP2YQyBagEBWlsEhxKVsVOvi +O5fHfw+Svd9vgO3PRIA9bDh2isO6SHis6akgWBsXNh/FS1Kyl1IrBt+CnIsgQRzz +po7hh/TfU6VCKXL5fZR+fK6Ze12MP+B30HEXrW+VhzvhCMWjbyFIYJ1jGfd5N/zD +3E26cQPn711M+13cbKJeSWU0aX9dwQxPSXJdytORijxlFfUIAmsCAwEAAQKCAYA4 +3/9JJHCNPC4O4C9klttpSE4TkULSSjBQNaBrNTN4uaJY2YKZs27Am2yqRGWF99zD +sqB5SugICngeZc1oRmW/DnyDWIaU4HkM0oDUubOY+j1oEzPQlydek9Utfoh5A+WP +9omn/v0WmRgQzjMwSU65/Cfz+/ENE2N3EwJ9t3gufD4rPbc67/aoecxJWHMnteLQ +Ne6k99IJyDlRPbBc7Gc6T5vlE+um27Sh3paVkx8T47afh4HHtPqmiXfAWoONxCU7 +YXXWfIo4qB80it77QHtHdAgXxEZGug2WsDakf1bC/veZ0Qm4OkfkYzFF3DhRXTia +sffXdP6z+/nB+WG0k/qIqo5BweW3aOb+pyXATGTNVsRp98cxS6GpyZb3yUDnfiLL +vKzzMwPPJyA2X1cDVUrwTETg9jRB/UDTsYLjedar2FRkVI46lPZtvdqHLx5+UpDs +CEAooRaOi10LB52zhqhyKpy2VtsHXuhOLrGlj8R5DPGqzB9ZjlOLqsIxzXgT1+kC +gcEAzDxdkbY7xj1Q7HKOVQiRr45xijkL6kleglmBFt0DqFMETYzb4Y7xDEdU82H0 +XBlOnQIc6GD4PBGQDGGURSkQya7aSEWfIIRG3PCTCzSJhghPK+olFz5+39x/QUSm +zTY9rjIzAXu/VBSnFK+bL2A+etJOQFOrhfGESbWHsbuqPbigU5q9eCAcQs/GBI1F +x1ckjRCkUxpNS1jP92GWpIPVQQYd8IFwLfcA6nJq8UoXpIZazYfqeOOyN47ns7ho +TRhfAoHBAPewKcmEfikaTExIQKckN7rpfMJRmp3oWsq18AF6nywVYylhJPFo74Tw +6P/lsFJqjgCbN2bbMuw7vCq2a36tBy1rRFaNtRNjl8XjO3XP5RZG5Z9lUFA7Gvf+ +pY8qUIWzBWXVi/KugpAwNvy7rgYoqKZyUFOrm/4ehCz7uGD5OmpucEVPvOQIDYPe +cqF27iLXYiTJv20ASVnpmoF5XJaztQa2O9VSupTgxNGXi+iUz+azrIeUviTbOgZP +H5flbfyBdQKBwQCV4WsNhv3g4pijnQIlFy/K9S8PsAO1gPhxknuwqquHeLz8qHWo +1zzAtQx7vBQXDp9pi+ZpBtjFRGJBI88q3hMq3z3jsewwccKLW6WdoIWYLjrj0GY9 +46g6Ytekr1v112t2jfJukUD81Fc1UBYDs47GldXFUWHb3z0k1qppXX524yoNkM6g +/Heg9FeueXqO6r2xJFhjgDbfJ6MfOafSvcjfejy4hlUr9kvewe8HekdVfx/eG3OQ +GhFswlv5wUgR4+MCgcBaOJEAFoLd9fZU0vy55TdnniUToyXu3vQzYpJJ96CDLkcw +i3IpfU/B3P8CN2hCnQ2cqu1DShUCd7/Szx/YxK4YnToHTRboOR7PtjWydEe+FZUO +upjGoMDyFI+51m/+Q3dz4JVZkLd+ThG5faOmGqlT98/Kqnfn2LXMrOQ8bowYuKGs +nZ7wcP57Skv3BJs5lbrqK5LO4YvWdIETKGHRgyQtjbO9wKS2FimbLtiHn60bG9d8 +i3G3eyNnqOqZKbkmgQECgcEAgX9k4kDqK7IoNdk5Keypxvk5xUi+8rM2A52aRjer +m1a32/0EmV2+gggtg5h9K4xUdoaRfvnggx5XqMNoshp4Qm5nKu4bJavdO+us2HVA +urstNyvWaoQQJm26LAoG6u57DqmX6j0RnOUjL9OBgGiNDwqaek+QbqHSc0mrSH36 +Nfo9+XOssR7sZbNzKRGLlWGP1ham7QTjf6wkMFMYozBZZ96NgNbPW+gWd/kZlFCe +3YUVwrqlblfFYykcpa1p1llz +-----END PRIVATE KEY----- diff --git a/components/ota_ws_update/idf_component.yml__ b/components/ota_ws_update/idf_component.yml__ new file mode 100644 index 0000000..ae7bd6a --- /dev/null +++ b/components/ota_ws_update/idf_component.yml__ @@ -0,0 +1,2 @@ +dependencies: + espressif/esp_encrypted_img: "^2.0.1" \ No newline at end of file diff --git a/components/ota_ws_update/include/ota_ws_update.h b/components/ota_ws_update/include/ota_ws_update.h new file mode 100644 index 0000000..30f61f0 --- /dev/null +++ b/components/ota_ws_update/include/ota_ws_update.h @@ -0,0 +1,20 @@ +#pragma once + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* +* @brief register ota_ws httpd handlers ( web page & ws handlers) on existing httpd server with ws support +* uri page -> CONFIG_OTA_DEFAULT_WS_URI +* @param httpd_handle_t server -> existing server handle +* @return +* ESP_OK -> register OK +* ESP_FAIL -> register FAIL +*/ +esp_err_t ota_ws_register_uri_handler(httpd_handle_t server); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/components/ota_ws_update/private_include/jsmn.h b/components/ota_ws_update/private_include/jsmn.h new file mode 100644 index 0000000..f1b2bb7 --- /dev/null +++ b/components/ota_ws_update/private_include/jsmn.h @@ -0,0 +1,530 @@ +/* + * MIT License + * + * Copyright (c) 2010 Serge Zaitsev + * + * 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. + */ +#ifndef JSMN_H +#define JSMN_H + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define JSMN_STATIC + +#ifdef JSMN_STATIC +#define JSMN_API static +#else +#define JSMN_API extern +#endif + + /** + * JSON type identifier. Basic types are: + * o Object + * o Array + * o String + * o Other primitive: number, boolean (true/false) or null + */ + typedef enum + { + JSMN_UNDEFINED = 0, + JSMN_OBJECT = 1, + JSMN_ARRAY = 2, + JSMN_STRING = 3, + JSMN_PRIMITIVE = 4 + } jsmntype_t; + + enum jsmnerr + { + /* Not enough tokens were provided */ + JSMN_ERROR_NOMEM = -1, + /* Invalid character inside JSON string */ + JSMN_ERROR_INVAL = -2, + /* The string is not a full JSON packet, more bytes expected */ + JSMN_ERROR_PART = -3 + }; + + /** + * JSON token description. + * type type (object, array, string etc.) + * start start position in JSON data string + * end end position in JSON data string + */ + typedef struct jsmntok + { + jsmntype_t type; + int start; + int end; + int size; +#ifdef JSMN_PARENT_LINKS + int parent; +#endif + } jsmntok_t; + + /** + * JSON parser. Contains an array of token blocks available. Also stores + * the string being parsed now and current position in that string. + */ + typedef struct jsmn_parser + { + unsigned int pos; /* offset in the JSON string */ + unsigned int toknext; /* next token to allocate */ + int toksuper; /* superior token node, e.g. parent object or array */ + } jsmn_parser; + + /** + * Create JSON parser over an array of tokens + */ + JSMN_API void jsmn_init(jsmn_parser *parser); + + /** + * Run JSON parser. It parses a JSON data string into and array of tokens, each + * describing + * a single JSON object. + */ + JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, + jsmntok_t *tokens, const unsigned int num_tokens); + +#ifndef JSMN_HEADER + /** + * Allocates a fresh unused token from the token pool. + */ + static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, + const size_t num_tokens) + { + jsmntok_t *tok; + if (parser->toknext >= num_tokens) + { + return NULL; + } + tok = &tokens[parser->toknext++]; + tok->start = tok->end = -1; + tok->size = 0; +#ifdef JSMN_PARENT_LINKS + tok->parent = -1; +#endif + return tok; + } + + /** + * Fills token type and boundaries. + */ + static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type, + const int start, const int end) + { + token->type = type; + token->start = start; + token->end = end; + token->size = 0; + } + + /** + * Fills next available token with JSON primitive. + */ + static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, + const size_t len, jsmntok_t *tokens, + const size_t num_tokens) + { + jsmntok_t *token; + int start; + + start = parser->pos; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) + { + switch (js[parser->pos]) + { +#ifndef JSMN_STRICT + /* In strict mode primitive must be followed by "," or "}" or "]" */ + case ':': +#endif + case '\t': + case '\r': + case '\n': + case ' ': + case ',': + case ']': + case '}': + goto found; + default: + /* to quiet a warning from gcc*/ + break; + } + if (js[parser->pos] < 32 || js[parser->pos] >= 127) + { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } +#ifdef JSMN_STRICT + /* In strict mode primitive must be followed by a comma/object/array */ + parser->pos = start; + return JSMN_ERROR_PART; +#endif + + found: + if (tokens == NULL) + { + parser->pos--; + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) + { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + parser->pos--; + return 0; + } + + /** + * Fills next token with JSON string. + */ + static int jsmn_parse_string(jsmn_parser *parser, const char *js, + const size_t len, jsmntok_t *tokens, + const size_t num_tokens) + { + jsmntok_t *token; + + int start = parser->pos; + + parser->pos++; + + /* Skip starting quote */ + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) + { + char c = js[parser->pos]; + + /* Quote: end of string */ + if (c == '\"') + { + if (tokens == NULL) + { + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) + { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + return 0; + } + + /* Backslash: Quoted symbol expected */ + if (c == '\\' && parser->pos + 1 < len) + { + int i; + parser->pos++; + switch (js[parser->pos]) + { + /* Allowed escaped symbols */ + case '\"': + case '/': + case '\\': + case 'b': + case 'f': + case 'r': + case 'n': + case 't': + break; + /* Allows escaped symbol \uXXXX */ + case 'u': + parser->pos++; + for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; + i++) + { + /* If it isn't a hex character we have an error */ + if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ + (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ + (js[parser->pos] >= 97 && js[parser->pos] <= 102))) + { /* a-f */ + parser->pos = start; + return JSMN_ERROR_INVAL; + } + parser->pos++; + } + parser->pos--; + break; + /* Unexpected symbol */ + default: + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } + } + parser->pos = start; + return JSMN_ERROR_PART; + } + + /** + * Parse JSON string and fill tokens. + */ + JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, + jsmntok_t *tokens, const unsigned int num_tokens) + { + int r; + int i; + jsmntok_t *token; + int count = parser->toknext; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) + { + char c; + jsmntype_t type; + + c = js[parser->pos]; + switch (c) + { + case '{': + case '[': + count++; + if (tokens == NULL) + { + break; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) + { + return JSMN_ERROR_NOMEM; + } + if (parser->toksuper != -1) + { + jsmntok_t *t = &tokens[parser->toksuper]; +#ifdef JSMN_STRICT + /* In strict mode an object or array can't become a key */ + if (t->type == JSMN_OBJECT) + { + return JSMN_ERROR_INVAL; + } +#endif + t->size++; +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + } + token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); + token->start = parser->pos; + parser->toksuper = parser->toknext - 1; + break; + case '}': + case ']': + if (tokens == NULL) + { + break; + } + type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); +#ifdef JSMN_PARENT_LINKS + if (parser->toknext < 1) + { + return JSMN_ERROR_INVAL; + } + token = &tokens[parser->toknext - 1]; + for (;;) + { + if (token->start != -1 && token->end == -1) + { + if (token->type != type) + { + return JSMN_ERROR_INVAL; + } + token->end = parser->pos + 1; + parser->toksuper = token->parent; + break; + } + if (token->parent == -1) + { + if (token->type != type || parser->toksuper == -1) + { + return JSMN_ERROR_INVAL; + } + break; + } + token = &tokens[token->parent]; + } +#else + for (i = parser->toknext - 1; i >= 0; i--) + { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) + { + if (token->type != type) + { + return JSMN_ERROR_INVAL; + } + parser->toksuper = -1; + token->end = parser->pos + 1; + break; + } + } + /* Error if unmatched closing bracket */ + if (i == -1) + { + return JSMN_ERROR_INVAL; + } + for (; i >= 0; i--) + { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) + { + parser->toksuper = i; + break; + } + } +#endif + break; + case '\"': + r = jsmn_parse_string(parser, js, len, tokens, num_tokens); + if (r < 0) + { + return r; + } + count++; + if (parser->toksuper != -1 && tokens != NULL) + { + tokens[parser->toksuper].size++; + } + break; + case '\t': + case '\r': + case '\n': + case ' ': + break; + case ':': + parser->toksuper = parser->toknext - 1; + break; + case ',': + if (tokens != NULL && parser->toksuper != -1 && + tokens[parser->toksuper].type != JSMN_ARRAY && + tokens[parser->toksuper].type != JSMN_OBJECT) + { +#ifdef JSMN_PARENT_LINKS + parser->toksuper = tokens[parser->toksuper].parent; +#else + for (i = parser->toknext - 1; i >= 0; i--) + { + if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) + { + if (tokens[i].start != -1 && tokens[i].end == -1) + { + parser->toksuper = i; + break; + } + } + } +#endif + } + break; +#ifdef JSMN_STRICT + /* In strict mode primitives are: numbers and booleans */ + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 't': + case 'f': + case 'n': + /* And they must not be keys of the object */ + if (tokens != NULL && parser->toksuper != -1) + { + const jsmntok_t *t = &tokens[parser->toksuper]; + if (t->type == JSMN_OBJECT || + (t->type == JSMN_STRING && t->size != 0)) + { + return JSMN_ERROR_INVAL; + } + } +#else + /* In non-strict mode every unquoted value is a primitive */ + default: +#endif + r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); + if (r < 0) + { + return r; + } + count++; + if (parser->toksuper != -1 && tokens != NULL) + { + tokens[parser->toksuper].size++; + } + break; + +#ifdef JSMN_STRICT + /* Unexpected char in strict mode */ + default: + return JSMN_ERROR_INVAL; +#endif + } + } + + if (tokens != NULL) + { + for (i = parser->toknext - 1; i >= 0; i--) + { + /* Unmatched opened object or array */ + if (tokens[i].start != -1 && tokens[i].end == -1) + { + return JSMN_ERROR_PART; + } + } + } + + return count; + } + + /** + * Creates a new parser based over a given buffer with an array of tokens + * available. + */ + JSMN_API void jsmn_init(jsmn_parser *parser) + { + parser->pos = 0; + parser->toknext = 0; + parser->toksuper = -1; + } + +#endif /* JSMN_HEADER */ + +#ifdef __cplusplus +} +#endif + +#endif /* JSMN_H */ \ No newline at end of file diff --git a/components/ota_ws_update/private_include/ota_ws_update_private.h b/components/ota_ws_update/private_include/ota_ws_update_private.h new file mode 100644 index 0000000..d303bf1 --- /dev/null +++ b/components/ota_ws_update/private_include/ota_ws_update_private.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include "esp_http_server.h" + +#define OTA_RESTART_ESP "otaRestartEsp" +#define OTA_SIZE_START "otaSize" +#define OTA_SET_CHUNK_SIZE "otaSetChunkSize" +#define OTA_GET_CHUNK "otaGetChunk" +#define OTA_END "otaEnd" +#define OTA_ERROR "otaError" +#define OTA_CANCEL "otaCancel" +#define OTA_CHECK_ROLLBACK "otaCheckRollback" +#define OTA_PROCESS_ROLLBACK "otaProcessRollback" + + +esp_err_t start_ota_ws(void); +esp_err_t write_ota_ws(int data_read, uint8_t *ota_write_data); +esp_err_t end_ota_ws(void); +esp_err_t abort_ota_ws(void); +bool check_ota_ws_rollback_enable(void); +esp_err_t rollback_ota_ws(bool rollback); \ No newline at end of file diff --git a/components/ota_ws_update/rsa_key/private_rsa_3072.pem b/components/ota_ws_update/rsa_key/private_rsa_3072.pem new file mode 100644 index 0000000..2eadbed --- /dev/null +++ b/components/ota_ws_update/rsa_key/private_rsa_3072.pem @@ -0,0 +1,40 @@ +-----BEGIN PRIVATE KEY----- +MIIG/gIBADANBgkqhkiG9w0BAQEFAASCBugwggbkAgEAAoIBgQDFmskeWxaJtPCV +Xydauqm6Q/wW9D0ewNUo+L9sopGz/Lrw5JdynLQDcxcioqpow5Nr9A8y8+YJc9zH +sMs4qJYDLcxROeimGlPqtrj4OxFXDyok12fxDquYTXaJzTAM+rp/i/VJJVKonYU0 +DOuG8tX1lsrlfkwYixaoPUGL/WuG1srHay5NKCcXjYVUKsjHkfEgfKAhF/6cPTj6 +yeKH6nNQQofOYUN5hkGgT19ek3or0dYph9r3VozyjV12/D1K+o0Vl+LQc7IX3xxN +DB9s3aGVIMglir8AY2ZjkqRyJRzbIrjufXYxw/e6uP2YQyBagEBWlsEhxKVsVOvi +O5fHfw+Svd9vgO3PRIA9bDh2isO6SHis6akgWBsXNh/FS1Kyl1IrBt+CnIsgQRzz +po7hh/TfU6VCKXL5fZR+fK6Ze12MP+B30HEXrW+VhzvhCMWjbyFIYJ1jGfd5N/zD +3E26cQPn711M+13cbKJeSWU0aX9dwQxPSXJdytORijxlFfUIAmsCAwEAAQKCAYA4 +3/9JJHCNPC4O4C9klttpSE4TkULSSjBQNaBrNTN4uaJY2YKZs27Am2yqRGWF99zD +sqB5SugICngeZc1oRmW/DnyDWIaU4HkM0oDUubOY+j1oEzPQlydek9Utfoh5A+WP +9omn/v0WmRgQzjMwSU65/Cfz+/ENE2N3EwJ9t3gufD4rPbc67/aoecxJWHMnteLQ +Ne6k99IJyDlRPbBc7Gc6T5vlE+um27Sh3paVkx8T47afh4HHtPqmiXfAWoONxCU7 +YXXWfIo4qB80it77QHtHdAgXxEZGug2WsDakf1bC/veZ0Qm4OkfkYzFF3DhRXTia +sffXdP6z+/nB+WG0k/qIqo5BweW3aOb+pyXATGTNVsRp98cxS6GpyZb3yUDnfiLL +vKzzMwPPJyA2X1cDVUrwTETg9jRB/UDTsYLjedar2FRkVI46lPZtvdqHLx5+UpDs +CEAooRaOi10LB52zhqhyKpy2VtsHXuhOLrGlj8R5DPGqzB9ZjlOLqsIxzXgT1+kC +gcEAzDxdkbY7xj1Q7HKOVQiRr45xijkL6kleglmBFt0DqFMETYzb4Y7xDEdU82H0 +XBlOnQIc6GD4PBGQDGGURSkQya7aSEWfIIRG3PCTCzSJhghPK+olFz5+39x/QUSm +zTY9rjIzAXu/VBSnFK+bL2A+etJOQFOrhfGESbWHsbuqPbigU5q9eCAcQs/GBI1F +x1ckjRCkUxpNS1jP92GWpIPVQQYd8IFwLfcA6nJq8UoXpIZazYfqeOOyN47ns7ho +TRhfAoHBAPewKcmEfikaTExIQKckN7rpfMJRmp3oWsq18AF6nywVYylhJPFo74Tw +6P/lsFJqjgCbN2bbMuw7vCq2a36tBy1rRFaNtRNjl8XjO3XP5RZG5Z9lUFA7Gvf+ +pY8qUIWzBWXVi/KugpAwNvy7rgYoqKZyUFOrm/4ehCz7uGD5OmpucEVPvOQIDYPe +cqF27iLXYiTJv20ASVnpmoF5XJaztQa2O9VSupTgxNGXi+iUz+azrIeUviTbOgZP +H5flbfyBdQKBwQCV4WsNhv3g4pijnQIlFy/K9S8PsAO1gPhxknuwqquHeLz8qHWo +1zzAtQx7vBQXDp9pi+ZpBtjFRGJBI88q3hMq3z3jsewwccKLW6WdoIWYLjrj0GY9 +46g6Ytekr1v112t2jfJukUD81Fc1UBYDs47GldXFUWHb3z0k1qppXX524yoNkM6g +/Heg9FeueXqO6r2xJFhjgDbfJ6MfOafSvcjfejy4hlUr9kvewe8HekdVfx/eG3OQ +GhFswlv5wUgR4+MCgcBaOJEAFoLd9fZU0vy55TdnniUToyXu3vQzYpJJ96CDLkcw +i3IpfU/B3P8CN2hCnQ2cqu1DShUCd7/Szx/YxK4YnToHTRboOR7PtjWydEe+FZUO +upjGoMDyFI+51m/+Q3dz4JVZkLd+ThG5faOmGqlT98/Kqnfn2LXMrOQ8bowYuKGs +nZ7wcP57Skv3BJs5lbrqK5LO4YvWdIETKGHRgyQtjbO9wKS2FimbLtiHn60bG9d8 +i3G3eyNnqOqZKbkmgQECgcEAgX9k4kDqK7IoNdk5Keypxvk5xUi+8rM2A52aRjer +m1a32/0EmV2+gggtg5h9K4xUdoaRfvnggx5XqMNoshp4Qm5nKu4bJavdO+us2HVA +urstNyvWaoQQJm26LAoG6u57DqmX6j0RnOUjL9OBgGiNDwqaek+QbqHSc0mrSH36 +Nfo9+XOssR7sZbNzKRGLlWGP1ham7QTjf6wkMFMYozBZZ96NgNbPW+gWd/kZlFCe +3YUVwrqlblfFYykcpa1p1llz +-----END PRIVATE KEY----- diff --git a/components/ota_ws_update/source/ota_ws_update.html b/components/ota_ws_update/source/ota_ws_update.html new file mode 100644 index 0000000..9f3b601 --- /dev/null +++ b/components/ota_ws_update/source/ota_ws_update.html @@ -0,0 +1,248 @@ + + + + + + OTA UPDATE + + + + + + + +
OTA UPDATE
+ +
+ +
+ +
+ +
+ +
+ + + + +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/components/ota_ws_update/source/ota_ws_update_esp.c b/components/ota_ws_update/source/ota_ws_update_esp.c new file mode 100644 index 0000000..495d154 --- /dev/null +++ b/components/ota_ws_update/source/ota_ws_update_esp.c @@ -0,0 +1,148 @@ +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#include "esp_ota_ops.h" +#include "esp_flash_partitions.h" +#include "esp_partition.h" +#include "esp_image_format.h" + +#include "ota_ws_update_private.h" + +static const char *TAG = "ota_ws_esp"; + +static const esp_partition_t *update_partition = NULL; +static bool image_header_was_checked = false; +static esp_ota_handle_t update_handle = 0; + +//static int tstc=0; + +esp_err_t start_ota_ws(void) +{ + //return ESP_OK; // debug return + //tstc=0; + + esp_err_t err; + ESP_LOGI(TAG, "Starting OTA"); + + const esp_partition_t *configured = esp_ota_get_boot_partition(); + const esp_partition_t *running = esp_ota_get_running_partition(); + if(configured==NULL || running == NULL) + { + ESP_LOGE(TAG,"OTA data not found"); + return ESP_FAIL; + } + + if (configured != running) + { + ESP_LOGW(TAG, "Configured OTA boot partition at offset 0x%08lx, but running from offset 0x%08lx", + configured->address, running->address); + ESP_LOGW(TAG, "(This can happen if either the OTA boot data or preferred boot image become corrupted somehow.)"); + } + ESP_LOGI(TAG, "Running partition type %d subtype %d (offset 0x%08lx)", + running->type, running->subtype, running->address); + + update_partition = esp_ota_get_next_update_partition(NULL); + ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%lx", + update_partition->subtype, update_partition->address); + + err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "esp_ota_begin failed "); + return ESP_FAIL; + } + ESP_LOGI(TAG, "esp_ota_begin succeeded"); + + image_header_was_checked = false; + return ESP_OK; +} +esp_err_t write_ota_ws(int data_read, uint8_t *ota_write_data) +{ + //return ESP_OK; // debug return + + + if (image_header_was_checked == false) // first segment + { + esp_app_desc_t new_app_info; + if (data_read > sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t)) + { + // check current version with downloading + memcpy(&new_app_info, &ota_write_data[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)], sizeof(esp_app_desc_t)); + ESP_LOGI(TAG, "New firmware version: %s", new_app_info.version); + + image_header_was_checked = true; + } + else + { + ESP_LOGE(TAG, "Received package is not fit len"); + return ESP_FAIL; + } + } + esp_err_t err = esp_ota_write(update_handle, (const void *)ota_write_data, data_read); + //tstc+=data_read; + if (err != ESP_OK) + { + return ESP_FAIL; + } + //ESP_LOGI("tstc","%d",tstc); + return ESP_OK; +} +esp_err_t end_ota_ws(void) +{ + //return ESP_OK; // debug return + + esp_err_t err = esp_ota_end(update_handle); + if (err != ESP_OK) { + if (err == ESP_ERR_OTA_VALIDATE_FAILED) { + ESP_LOGE(TAG, "Image validation failed, image is corrupted"); + } + ESP_LOGE(TAG, "esp_ota_end failed (%s)!", esp_err_to_name(err)); + 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)); + return ESP_FAIL; + } + return ESP_OK; +} +esp_err_t abort_ota_ws(void) +{ + return esp_ota_abort(update_handle); +} +// false - rollback disable +// true - rollback enable +bool check_ota_ws_rollback_enable(void) +{ +#ifdef CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE + esp_ota_img_states_t ota_state_running_part; + const esp_partition_t *running = esp_ota_get_running_partition(); + if (esp_ota_get_state_partition(running, &ota_state_running_part) == ESP_OK) { + if (ota_state_running_part == ESP_OTA_IMG_PENDING_VERIFY) { + ESP_LOGI(TAG, "Running app has ESP_OTA_IMG_PENDING_VERIFY state"); + return true; + } + } +#endif + return false; +} +// rollback == true - rollback +// rollback == false - app valid? confirm update -> no rollback +esp_err_t rollback_ota_ws(bool rollback) +{ +#ifdef CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE + if(rollback == false) + { + return esp_ota_mark_app_valid_cancel_rollback(); // app valid + } + else + { + return esp_ota_mark_app_invalid_rollback_and_reboot(); // app rolback & reboot + } +#endif + return ESP_FAIL; +} \ No newline at end of file diff --git a/components/ota_ws_update/source/ota_ws_update_esp_preencrypted.c b/components/ota_ws_update/source/ota_ws_update_esp_preencrypted.c new file mode 100644 index 0000000..ad964e7 --- /dev/null +++ b/components/ota_ws_update/source/ota_ws_update_esp_preencrypted.c @@ -0,0 +1,216 @@ +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include "esp_ota_ops.h" +#include "esp_flash_partitions.h" +#include "esp_partition.h" +#include "esp_image_format.h" +#include "esp_encrypted_img.h" + +#include "ota_ws_update_private.h" + +static const char *TAG = "ota_ws_esp_pre_enc"; + +static const esp_partition_t *update_partition = NULL; +static bool image_header_was_checked = false; +static esp_ota_handle_t update_handle = 0; // ota handle +// pre-encrypted handle +static esp_decrypt_handle_t enc_handle = NULL; // enc handle context +static esp_decrypt_cfg_t enc_cfg = {0}; // enc cfg +static pre_enc_decrypt_arg_t enc_arg = {0}; // enc arg + +// private key +// may be generate cmd +// openssl genrsa -out rsa_key/private_rsa_3072.pem 3072 +// size - 3072 !! +// null terminated - use EMBED_TXTFILES in cmake.txt + +extern const char rsa_private_pem_start[] asm("_binary_private_rsa_3072_pem_start"); +extern const char rsa_private_pem_end[] asm("_binary_private_rsa_3072_pem_end"); + +esp_err_t start_ota_ws(void) +{ + esp_err_t err; + ESP_LOGI(TAG, "Starting OTA"); + + const esp_partition_t *configured = esp_ota_get_boot_partition(); + const esp_partition_t *running = esp_ota_get_running_partition(); + if (configured == NULL || running == NULL) + { + ESP_LOGE(TAG, "OTA data not found"); + return ESP_FAIL; + } + + if (configured != running) + { + ESP_LOGW(TAG, "Configured OTA boot partition at offset 0x%08lx, but running from offset 0x%08lx", + configured->address, running->address); + ESP_LOGW(TAG, "(This can happen if either the OTA boot data or preferred boot image become corrupted somehow.)"); + } + ESP_LOGI(TAG, "Running partition type %d subtype %d (offset 0x%08lx)", + running->type, running->subtype, running->address); + + update_partition = esp_ota_get_next_update_partition(NULL); + ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%lx", + update_partition->subtype, update_partition->address); + + err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "esp_ota_begin failed "); + return ESP_FAIL; + } + + image_header_was_checked = false; // first read on write_ota_ws - check image header + + enc_cfg.rsa_priv_key = rsa_private_pem_start; + enc_cfg.rsa_priv_key_len = rsa_private_pem_end - rsa_private_pem_start; + + enc_handle = esp_encrypted_img_decrypt_start(&enc_cfg); + if (enc_handle == NULL) + { + ESP_LOGE(TAG, "esp_encrypted_img_decrypt_start failed "); + abort_ota_ws(); + return ESP_FAIL; + } + memset(&enc_arg, 0, sizeof(pre_enc_decrypt_arg_t)); //asp_encrypted -> use realloc inside -> enc_arg.data_out may be NULL on first start + ESP_LOGI(TAG, "esp_ota_begin succeeded"); + return ESP_OK; +} +esp_err_t write_ota_ws(int enc_data_read, uint8_t *enc_ota_write_data) +{ + enc_arg.data_in = (char *)enc_ota_write_data; + enc_arg.data_in_len = enc_data_read; + // read enc img, enc_arg.data_out/enc_arg.data_out_len -> decrypted data/len + // dont clear enc_arg.data_out/enc_arg.data_out_len, its used realloc on esp_encrypted_img_decrypt_data + esp_err_t ret = esp_encrypted_img_decrypt_data(enc_handle, &enc_arg); + if (ret == ESP_FAIL || ret == ESP_ERR_INVALID_ARG) + { + ESP_LOGE(TAG, "Data decrypt error %x", ret); + abort_ota_ws(); + return ret; + } + int data_read = enc_arg.data_out_len; + uint8_t *ota_write_data = (uint8_t *)enc_arg.data_out; + + if (image_header_was_checked == false) // first segment - check img header + { + esp_app_desc_t new_app_info; + if (data_read > sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t)) + { + // check current version with downloading + memcpy(&new_app_info, &ota_write_data[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)], sizeof(esp_app_desc_t)); + ESP_LOGI(TAG, "New firmware version: %s", new_app_info.version); + image_header_was_checked = true; + } + else + { + ESP_LOGE(TAG, "Received package is not fit len"); + abort_ota_ws(); + return ESP_FAIL; + } + } + ret = esp_ota_write(update_handle, (const void *)ota_write_data, data_read); + if (ret != ESP_OK) + { + ESP_LOGE(TAG, "esp_ota_write err"); + abort_ota_ws(); + return ret; + } + return ESP_OK; +} +esp_err_t end_ota_ws(void) +{ + // return ESP_OK; // debug return + esp_err_t ret = esp_encrypted_img_decrypt_end(enc_handle); + if (ret) + { + ESP_LOGE(TAG, "esp_encrypted_img_decrypt_end (%s)!", esp_err_to_name(ret)); + abort_ota_ws(); + return ret; + } + ret = esp_ota_end(update_handle); + if (ret != ESP_OK) + { + if (ret == ESP_ERR_OTA_VALIDATE_FAILED) + { + ESP_LOGE(TAG, "Image validation failed, image is corrupted"); + abort_ota_ws(); + return ret; + } + ESP_LOGE(TAG, "esp_ota_end failed (%s)!", esp_err_to_name(ret)); + abort_ota_ws(); + return ret; + } + ret = esp_ota_set_boot_partition(update_partition); + if (ret != ESP_OK) + { + ESP_LOGE(TAG, "esp_ota_set_boot_partition failed (%s)!", esp_err_to_name(ret)); + abort_ota_ws(); + return ret; + } + if (enc_arg.data_out) + { + free(enc_arg.data_out); + } + return ESP_OK; +} +esp_err_t abort_ota_ws(void) +{ + if (enc_handle) + { + ESP_LOGI("abort", "abort cmd"); + esp_encrypted_img_decrypt_abort(enc_handle); + } + if (update_handle) + { + esp_ota_abort(update_handle); + } + if (enc_arg.data_out) + { + free(enc_arg.data_out); + } + enc_handle = NULL; + update_handle = 0; + memset(&enc_arg, 0, sizeof(pre_enc_decrypt_arg_t)); + return ESP_OK; +} +// false - rollback disable +// true - rollback enable +bool check_ota_ws_rollback_enable(void) +{ +#ifdef CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE + esp_ota_img_states_t ota_state_running_part; + const esp_partition_t *running = esp_ota_get_running_partition(); + if (esp_ota_get_state_partition(running, &ota_state_running_part) == ESP_OK) + { + if (ota_state_running_part == ESP_OTA_IMG_PENDING_VERIFY) + { + ESP_LOGI(TAG, "Running app has ESP_OTA_IMG_PENDING_VERIFY state"); + return true; + } + } +#endif + return false; +} +// rollback == true - rollback +// rollback == false - app valid? confirm update -> no rollback +esp_err_t rollback_ota_ws(bool rollback) +{ +#ifdef CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE + if (rollback == false) + { + return esp_ota_mark_app_valid_cancel_rollback(); // app valid + } + else + { + return esp_ota_mark_app_invalid_rollback_and_reboot(); // app rolback & reboot + } +#endif + return ESP_FAIL; +} \ No newline at end of file diff --git a/components/ota_ws_update/source/ota_ws_update_http.c b/components/ota_ws_update/source/ota_ws_update_http.c new file mode 100644 index 0000000..3fdbfe1 --- /dev/null +++ b/components/ota_ws_update/source/ota_ws_update_http.c @@ -0,0 +1,263 @@ +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#include "ota_ws_update_private.h" +#include "ota_ws_update.h" + +#include "jsmn.h" + +#define OTA_DEFAULT_WS_URI CONFIG_OTA_DEFAULT_WS_URI +#define OTA_DEFAULT_URI CONFIG_OTA_DEFAULT_URI +#define OTA_CHUNK_SIZE (CONFIG_OTA_CHUNK_SIZE & ~0xf) + + +static const char *TAG = "ota_ws_http"; + +static int ota_size; // ota firmware size +static int ota_start_chunk; // start address of http chunk +static int ota_started; // ota download started + +static esp_err_t json_to_str_parm(char *jsonstr, char *nameStr, char *valStr); +static esp_err_t send_json_string(char *str, httpd_req_t *req); +static esp_err_t ota_ws_handler(httpd_req_t *req); +static void ota_error(httpd_req_t *req, char *code, char *msg); + +// abort OTA, send error/cancel msg to ws +static void ota_error(httpd_req_t *req, char *code, char *msg) +{ + char json_str[128]; + ota_size = ota_start_chunk = ota_started = 0; + abort_ota_ws(); + ESP_LOGE(TAG, "%s %s", code, msg); + snprintf(json_str, sizeof(json_str), "{\"name\":\"%s\",\"value\":\"%s\"}", code, msg); + send_json_string(json_str, req); +} + +// simple json parse -> only one parametr name/val +static esp_err_t json_to_str_parm(char *jsonstr, char *nameStr, char *valStr) // распаковать строку json в пару name/val +{ + int r; // количество токенов + jsmn_parser p; + jsmntok_t t[5]; // только 2 пары параметров и obj + + jsmn_init(&p); + r = jsmn_parse(&p, jsonstr, strlen(jsonstr), t, sizeof(t) / sizeof(t[0])); + if (r < 2) + { + valStr[0] = 0; + nameStr[0] = 0; + return ESP_FAIL; + } + strncpy(nameStr, jsonstr + t[2].start, t[2].end - t[2].start); + nameStr[t[2].end - t[2].start] = 0; + if (r > 3) + { + strncpy(valStr, jsonstr + t[4].start, t[4].end - t[4].start); + valStr[t[4].end - t[4].start] = 0; + } + else + valStr[0] = 0; + return ESP_OK; +} +// send string to ws +static esp_err_t send_json_string(char *str, httpd_req_t *req) +{ + httpd_ws_frame_t ws_pkt; + memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); + ws_pkt.type = HTTPD_WS_TYPE_TEXT; + ws_pkt.payload = (uint8_t *)str; + ws_pkt.len = strlen(str); + return httpd_ws_send_frame(req, &ws_pkt); +} +// main ws OTA handler +// Handshake and process OTA +static esp_err_t ota_ws_handler(httpd_req_t *req) +{ + char json_key[64] = {0}; + char json_value[64] = {0}; + char json_str[128] = {0}; + + httpd_ws_frame_t ws_pkt; + uint8_t *buf = NULL; + + + if (req->method == HTTP_GET) + { + ESP_LOGI(TAG, "Handshake done, the new connection was opened"); + if(check_ota_ws_rollback_enable()) // check rollback enable, send cmd to enable rollback dialog on html + { + snprintf(json_str, sizeof(json_str), "{\"name\":\"%s\",\"value\":\"%s\" }", OTA_CHECK_ROLLBACK, "true"); + send_json_string(json_str, req); + } + return ESP_OK; + } + memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); + // Set max_len = 0 to get the frame len + esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 0); + if (ret != ESP_OK) + { + ota_error(req, OTA_ERROR, "httpd_ws_recv_frame failed to get frame len"); + return ret; + } + if (ws_pkt.len) + { + // ws_pkt.len + 1 is for NULL termination as we are expecting a string + buf = calloc(1, ws_pkt.len + 1); + if (buf == NULL) + { + ota_error(req, OTA_ERROR, "Failed to calloc memory for buf"); + return ESP_ERR_NO_MEM; + } + ws_pkt.payload = buf; + // Set max_len = ws_pkt.len to get the frame payload + ret = httpd_ws_recv_frame(req, &ws_pkt, ws_pkt.len); + if (ret != ESP_OK) + { + ota_error(req, OTA_ERROR, "httpd_ws_recv_frame failed"); + goto _recv_ret; + } + } + ret = ESP_OK; + if (ws_pkt.type == HTTPD_WS_TYPE_TEXT) // process json cmd + { + if (json_to_str_parm((char *)buf, json_key, json_value)) // decode json to key/value parm + { + ota_error(req, OTA_ERROR, "Error json str"); + goto _recv_ret; + } + if (strncmp(json_key, OTA_SIZE_START, sizeof(OTA_SIZE_START)) == 0) // start ota + { + ota_size = atoi(json_value); + if (ota_size == 0) + { + ota_error(req, OTA_ERROR, "Error ota size = 0"); + goto _recv_ret; + } + ret = start_ota_ws(); + if (ret) + { + ota_error(req, OTA_ERROR, "Error start ota"); + goto _recv_ret; + } + ota_started = 1; + ota_start_chunk = 0; + snprintf(json_str, sizeof(json_str), "{\"name\":\"%s\",\"value\":%d}", OTA_SET_CHUNK_SIZE, OTA_CHUNK_SIZE); // set download chunk + send_json_string(json_str, req); + snprintf(json_str, sizeof(json_str), "{\"name\":\"%s\",\"value\":%d}", OTA_GET_CHUNK, ota_start_chunk); // cmd -> send first chunk with start addresss = 0 + send_json_string(json_str, req); + } + if (strncmp(json_key, OTA_CANCEL, sizeof(OTA_CANCEL)) == 0) // cancel ota + { + ota_error(req, OTA_CANCEL, "Cancel command"); + ret = ESP_OK; + goto _recv_ret; + } + if (strncmp(json_key, OTA_ERROR, sizeof(OTA_ERROR)) == 0) // error ota + { + ota_error(req, OTA_ERROR, "Error command"); + ret = ESP_OK; + goto _recv_ret; + } + if (strncmp(json_key, OTA_PROCESS_ROLLBACK, sizeof(OTA_PROCESS_ROLLBACK)) == 0) // process rollback & + { + if(strncmp(json_value,"true",sizeof("true")) == 0) + { + ESP_LOGI(TAG,"Rollback and restart"); + ret = rollback_ota_ws(true); // rollback and restart + } + else + { + ESP_LOGI(TAG,"App veryfied, fix ota update"); + ret = rollback_ota_ws(false); // app veryfied + } + goto _recv_ret; + } + if (strncmp(json_key, OTA_RESTART_ESP, sizeof(OTA_RESTART_ESP)) == 0) // cancel ota + { + esp_restart(); + } + + } + else if (ws_pkt.type == HTTPD_WS_TYPE_BINARY && ota_started) // download OTA firmware with chunked part + { + + if (ota_start_chunk + ws_pkt.len < ota_size) //read chuk of ota + { + ret = write_ota_ws(ws_pkt.len, buf); // write chunk of ota + if (ret) + { + ota_error(req, OTA_ERROR, "Error write ota"); + goto _recv_ret; + } + ota_start_chunk += ws_pkt.len; + snprintf(json_str, sizeof(json_str), "{\"name\":\"%s\",\"value\": %d }", OTA_GET_CHUNK, ota_start_chunk); // cmd -> next chunk + send_json_string(json_str, req); + + } + else // last chunk and end ota + { + ret = write_ota_ws(ws_pkt.len, buf); // write last chunk of ota + if (ret) + { + ota_error(req, OTA_ERROR, "Error write ota"); + goto _recv_ret; + } + ret = end_ota_ws(); // end ota + if (ret) + { + ota_error(req, OTA_ERROR, "Error end ota"); + goto _recv_ret; + } + ota_size = 0; + ota_start_chunk = 0; + ota_started = 0; + ESP_LOGI(TAG,"OTA END OK"); + snprintf(json_str, sizeof(json_str), "{\"name\":\"%s\",\"value\":\"%s\" }", OTA_END, "OK"); // send ota end cmd ( ota ok ) + send_json_string(json_str, req); + } + } +_recv_ret: + free(buf); + return ret; +} +// main http get handler +// send http initial page and js code +static esp_err_t ota_get_handler(httpd_req_t *req) +{ + extern const unsigned char ota_ws_update_html_start[] asm("_binary_ota_ws_update_html_start"); + extern const unsigned char ota_ws_update_html_end[] asm("_binary_ota_ws_update_html_end"); + const size_t ota_ws_update_html_size = (ota_ws_update_html_end - ota_ws_update_html_start); + + httpd_resp_send_chunk(req, (const char *)ota_ws_update_html_start, ota_ws_update_html_size); + httpd_resp_sendstr_chunk(req, NULL); + return ESP_OK; +} +static const httpd_uri_t gh = { + .uri = OTA_DEFAULT_URI, + .method = HTTP_GET, + .handler = ota_get_handler, + .user_ctx = NULL}; +static const httpd_uri_t ws = { + .uri = OTA_DEFAULT_WS_URI, + .method = HTTP_GET, + .handler = ota_ws_handler, + .user_ctx = NULL, + .is_websocket = true}; + +// register all ota uri handler +esp_err_t ota_ws_register_uri_handler(httpd_handle_t server) +{ + esp_err_t ret = ESP_OK; + ret = httpd_register_uri_handler(server, &gh); + if (ret) + goto _ret; + ret = httpd_register_uri_handler(server, &ws); + if (ret) + goto _ret; +_ret: + return ret; +} diff --git a/components/ota_ws_update/source/ota_ws_update — копия.html b/components/ota_ws_update/source/ota_ws_update — копия.html new file mode 100644 index 0000000..db226e4 --- /dev/null +++ b/components/ota_ws_update/source/ota_ws_update — копия.html @@ -0,0 +1,616 @@ + + + + OTA UPDATE - Обновление прошивки + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

OTA UPDATE

+

Обновление прошивки устройства по воздуху

+
+ +
+ + Не подключено +
+ + + +
+

Загрузка прошивки

+ + + + + + + + + +
+ + + +
+
+ + +
+ + + + + + \ No newline at end of file diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 487f4b3..d599c9a 100755 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1 +1 @@ -idf_component_register(SRCS "ate0004.c" INCLUDE_DIRS "" REQUIRES zh_pcf8574 esp_wifi nvs_flash) \ No newline at end of file +idf_component_register(SRCS "ate0004.c" INCLUDE_DIRS "" REQUIRES zh_pcf8574 esp_wifi nvs_flash esp_http_server ota_ws_update) \ No newline at end of file diff --git a/main/ate0004.c b/main/ate0004.c index a837a4e..a2023a3 100755 --- a/main/ate0004.c +++ b/main/ate0004.c @@ -1,6 +1,7 @@ #include "ate0004.h" static i2c_master_bus_handle_t i2c_bus_handle = NULL; +static httpd_handle_t webserver_handle = NULL; static zh_pcf8574_handle_t button_handle = {0}; static zh_pcf8574_handle_t led_handle = {0}; @@ -11,6 +12,7 @@ static bool is_ret = false; static bool is_ext = false; static void zh_wifi_softap_init(void); +static void zh_webserver_init(void); static void zh_gpio_init(void); static void zh_io_expander_init(void); static void zh_pcf8574_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); @@ -22,6 +24,7 @@ void app_main(void) nvs_flash_init(); esp_event_loop_create_default(); zh_wifi_softap_init(); + zh_webserver_init(); zh_gpio_init(); gpio_set_level(TRIAC_GPIO, LOW); zh_io_expander_init(); @@ -49,6 +52,13 @@ static void zh_wifi_softap_init(void) esp_wifi_start(); } +static void zh_webserver_init(void) +{ + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + httpd_start(&webserver_handle, &config); + ota_ws_register_uri_handler(webserver_handle); +} + static void zh_gpio_init(void) { gpio_config_t triac_pin_config = { @@ -164,5 +174,4 @@ static void zh_pcf8574_event_handler(void *arg, esp_event_base_t event_base, int default: break; } - printf("Interrupt happened on device address 0x%02X on GPIO number %d at level %d.\n", event->i2c_address, event->gpio_number, event->gpio_level); } \ No newline at end of file diff --git a/main/ate0004.h b/main/ate0004.h index e68e23e..52eaabe 100644 --- a/main/ate0004.h +++ b/main/ate0004.h @@ -3,6 +3,8 @@ #include "zh_pcf8574.h" #include "nvs_flash.h" #include "esp_wifi.h" +#include "esp_http_server.h" +#include "ota_ws_update.h" #define HIGH true #define LOW false diff --git a/sdkconfig b/sdkconfig index be81497..4afe427 100644 --- a/sdkconfig +++ b/sdkconfig @@ -286,7 +286,8 @@ CONFIG_BOOTLOADER_PROJECT_VER=1 # # Application Rollback # -# CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE is not set +CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y +# CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK is not set # end of Application Rollback # @@ -432,6 +433,15 @@ CONFIG_PARTITION_TABLE_OFFSET=0x8000 CONFIG_PARTITION_TABLE_MD5=y # end of Partition Table +# +# OTA websocket update +# +CONFIG_OTA_DEFAULT_URI="/" +CONFIG_OTA_DEFAULT_WS_URI="/ws" +CONFIG_OTA_CHUNK_SIZE=8192 +# CONFIG_OTA_PRE_ENCRYPTED_MODE is not set +# end of OTA websocket update + # # Compiler options # @@ -843,7 +853,7 @@ CONFIG_HTTPD_MAX_URI_LEN=512 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y CONFIG_HTTPD_PURGE_BUF_LEN=32 # CONFIG_HTTPD_LOG_PURGE_DATA is not set -# CONFIG_HTTPD_WS_SUPPORT is not set +CONFIG_HTTPD_WS_SUPPORT=y # CONFIG_HTTPD_QUEUE_WORK_BLOCKING is not set CONFIG_HTTPD_SERVER_EVENT_POST_TIMEOUT=2000 # end of HTTP Server @@ -1063,9 +1073,9 @@ CONFIG_ESP_ROM_PRINT_IN_IRAM=y # ESP System Settings # # CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_80 is not set -# CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160 is not set -CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y -CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=240 +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160=y +# CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240 is not set +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=160 # # Memory @@ -1291,7 +1301,7 @@ CONFIG_FATFS_DONT_TRUST_LAST_ALLOC=0 # # CONFIG_FREERTOS_SMP is not set # CONFIG_FREERTOS_UNICORE is not set -CONFIG_FREERTOS_HZ=1000 +CONFIG_FREERTOS_HZ=100 # CONFIG_FREERTOS_CHECK_STACKOVERFLOW_NONE is not set # CONFIG_FREERTOS_CHECK_STACKOVERFLOW_PTRVAL is not set CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y @@ -2020,7 +2030,8 @@ CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y # CONFIG_ESP32_NO_BLOBS is not set # CONFIG_ESP32_COMPATIBLE_PRE_V2_1_BOOTLOADERS is not set # CONFIG_ESP32_COMPATIBLE_PRE_V3_1_BOOTLOADERS is not set -# CONFIG_APP_ROLLBACK_ENABLE is not set +CONFIG_APP_ROLLBACK_ENABLE=y +# CONFIG_APP_ANTI_ROLLBACK is not set # CONFIG_LOG_BOOTLOADER_LEVEL_NONE is not set # CONFIG_LOG_BOOTLOADER_LEVEL_ERROR is not set # CONFIG_LOG_BOOTLOADER_LEVEL_WARN is not set @@ -2111,9 +2122,9 @@ CONFIG_ESP32_PHY_MAX_TX_POWER=20 # CONFIG_SPIRAM_SUPPORT is not set # CONFIG_ESP32_SPIRAM_SUPPORT is not set # CONFIG_ESP32_DEFAULT_CPU_FREQ_80 is not set -# CONFIG_ESP32_DEFAULT_CPU_FREQ_160 is not set -CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y -CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=240 +CONFIG_ESP32_DEFAULT_CPU_FREQ_160=y +# CONFIG_ESP32_DEFAULT_CPU_FREQ_240 is not set +CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=160 CONFIG_TRACEMEM_RESERVE_DRAM=0x0 # CONFIG_ESP32_PANIC_PRINT_HALT is not set CONFIG_ESP32_PANIC_PRINT_REBOOT=y