wip:
This commit is contained in:
22
components/ota_ws_update/.gitignore
vendored
Normal file
22
components/ota_ws_update/.gitignore
vendored
Normal file
@@ -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
|
||||
74
components/ota_ws_update/CMakeLists.txt
Normal file
74
components/ota_ws_update/CMakeLists.txt
Normal file
@@ -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()
|
||||
43
components/ota_ws_update/Kconfig.projbuild
Normal file
43
components/ota_ws_update/Kconfig.projbuild
Normal file
@@ -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
|
||||
66
components/ota_ws_update/README-RU.md
Normal file
66
components/ota_ws_update/README-RU.md
Normal file
@@ -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
|
||||
|
||||
65
components/ota_ws_update/README.md
Normal file
65
components/ota_ws_update/README.md
Normal file
@@ -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
|
||||
15
components/ota_ws_update/esp_encrypted_img/CHANGELOG.md
Normal file
15
components/ota_ws_update/esp_encrypted_img/CHANGELOG.md
Normal file
@@ -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.
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "src/esp_encrypted_img.c"
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_REQUIRES mbedtls)
|
||||
202
components/ota_ws_update/esp_encrypted_img/LICENSE
Normal file
202
components/ota_ws_update/esp_encrypted_img/LICENSE
Normal file
@@ -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.
|
||||
56
components/ota_ws_update/esp_encrypted_img/README.md
Normal file
56
components/ota_ws_update/esp_encrypted_img/README.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# ESP Encrypted Image Abstraction Layer
|
||||
|
||||
[](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
|
||||
|
||||

|
||||
|
||||
```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)
|
||||
@@ -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
|
||||
BIN
components/ota_ws_update/esp_encrypted_img/image_format.png
Normal file
BIN
components/ota_ws_update/esp_encrypted_img/image_format.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <esp_err.h>
|
||||
#include <esp_idf_version.h>
|
||||
|
||||
#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
|
||||
@@ -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()
|
||||
@@ -0,0 +1,481 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "esp_encrypted_img.h"
|
||||
#include <errno.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_err.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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-----
|
||||
BIN
components/ota_ws_update/esp_encrypted_img/test/image.bin
Normal file
BIN
components/ota_ws_update/esp_encrypted_img/test/image.bin
Normal file
Binary file not shown.
359
components/ota_ws_update/esp_encrypted_img/test/test.c
Normal file
359
components/ota_ws_update/esp_encrypted_img/test/test.c
Normal file
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
@@ -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()
|
||||
12
components/ota_ws_update/example_ota_ws/CMakeLists.txt
Normal file
12
components/ota_ws_update/example_ota_ws/CMakeLists.txt
Normal file
@@ -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)
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
},
|
||||
{
|
||||
"path": ".."
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
}
|
||||
15
components/ota_ws_update/example_ota_ws/main/CMakeLists.txt
Normal file
15
components/ota_ws_update/example_ota_ws/main/CMakeLists.txt
Normal file
@@ -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
|
||||
)
|
||||
@@ -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 <esp_wifi.h>
|
||||
#include <esp_event.h>
|
||||
#include <esp_log.h>
|
||||
|
||||
#include <esp_http_server.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="ru">
|
||||
|
||||
<head>
|
||||
<title>Example Websocket Echo Server</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<style>
|
||||
.column {
|
||||
float: left;
|
||||
width: 100%;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
float: left;
|
||||
width: 100%;
|
||||
margin: 2px;
|
||||
|
||||
}
|
||||
|
||||
.cl1 {
|
||||
float: left;
|
||||
width: 100%;
|
||||
margin: 2px;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.cl01 {
|
||||
float: left;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.cl02 {
|
||||
float: left;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.hdr {
|
||||
float: left;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: white;
|
||||
background-color: blue;
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.logstr {
|
||||
width: 100%;
|
||||
float: left;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="hdr">Example Websocket Echo Server </div>
|
||||
<div class="column">
|
||||
<button class="btn" id="goWifi" onclick="window.location.href = '/wifi'" >Set WiFi SSID/Password -> /wifi</button>
|
||||
</div>
|
||||
<div class="column">
|
||||
<button class="btn" id="goOta" onclick="window.location.href = '/ota'" >Ota update -> /ota</button>
|
||||
</div>
|
||||
|
||||
<div class="cl1">
|
||||
<label class="cl01" for="exampleSend">Input string for Echo</label>
|
||||
<input class="cl02" type="text" id="exampleSend" placeholder="input">
|
||||
</div>
|
||||
|
||||
<div class="cl1">
|
||||
<label class="cl01" for="exampleEcho">Echo string</label>
|
||||
<input class="cl02" type="text" id="exampleEcho" placeholder="output">
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let exSend = document.getElementById("exampleSend");
|
||||
exSend.addEventListener("input", function (e) {
|
||||
//console.log(exSend.id + " " + exSend.value );
|
||||
socket.send(JSON.stringify({ name: exSend.id, msg: exSend.value }));
|
||||
});
|
||||
function receiveWsData(data) {
|
||||
//console.log(data);
|
||||
try {
|
||||
let obj = JSON.parse(data);
|
||||
let exEcho = document.getElementById("exampleEcho");
|
||||
exEcho.value = obj.msg;
|
||||
}
|
||||
catch {
|
||||
console.log(data + " catch");
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<script> // Прием, обработка и отправка данных в WS
|
||||
</script>
|
||||
|
||||
<script> // основной старт скрипта, открыть сокет
|
||||
// создать сокет по адресу
|
||||
let wsHostStr = "ws://" + document.location.host + document.location.pathname;
|
||||
wsHostStr += (document.location.pathname == '/') ? "ws" : "/ws";
|
||||
var socket = new WebSocket(wsHostStr);
|
||||
</script>
|
||||
|
||||
<script> // события WS
|
||||
socket.onopen = function () {
|
||||
console.log("connect");
|
||||
};
|
||||
socket.onclose = function (event) {
|
||||
console.log("close");
|
||||
};
|
||||
socket.onerror = function () {
|
||||
console.log("error");
|
||||
};
|
||||
socket.onmessage = function (event) {
|
||||
receiveWsData(event.data);
|
||||
};
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -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();
|
||||
}
|
||||
6
components/ota_ws_update/example_ota_ws/partitions.csv
Normal file
6
components/ota_ws_update/example_ota_ws/partitions.csv
Normal file
@@ -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
|
||||
|
@@ -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-----
|
||||
2
components/ota_ws_update/idf_component.yml__
Normal file
2
components/ota_ws_update/idf_component.yml__
Normal file
@@ -0,0 +1,2 @@
|
||||
dependencies:
|
||||
espressif/esp_encrypted_img: "^2.0.1"
|
||||
20
components/ota_ws_update/include/ota_ws_update.h
Normal file
20
components/ota_ws_update/include/ota_ws_update.h
Normal file
@@ -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
|
||||
530
components/ota_ws_update/private_include/jsmn.h
Normal file
530
components/ota_ws_update/private_include/jsmn.h
Normal file
@@ -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 <stddef.h>
|
||||
|
||||
#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 */
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <esp_log.h>
|
||||
#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);
|
||||
40
components/ota_ws_update/rsa_key/private_rsa_3072.pem
Normal file
40
components/ota_ws_update/rsa_key/private_rsa_3072.pem
Normal file
@@ -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-----
|
||||
248
components/ota_ws_update/source/ota_ws_update.html
Normal file
248
components/ota_ws_update/source/ota_ws_update.html
Normal file
@@ -0,0 +1,248 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="ru">
|
||||
|
||||
<head>
|
||||
<title>OTA UPDATE</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<style>
|
||||
.column {
|
||||
float: left;
|
||||
width: 100%;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
float: left;
|
||||
width: 100%;
|
||||
margin: 2px;
|
||||
font-size:large;
|
||||
|
||||
}
|
||||
|
||||
.cl1 {
|
||||
float: left;
|
||||
width: 100%;
|
||||
margin: 2px;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.cl01 {
|
||||
float: left;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.cl02 {
|
||||
float: left;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.hdr {
|
||||
float: left;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: white;
|
||||
background-color: blue;
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.logstr {
|
||||
width: 100%;
|
||||
float: left;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="hdr">OTA UPDATE</div>
|
||||
|
||||
<div class="column">
|
||||
<button class="btn" id="goHome">Home Page</button>
|
||||
</div>
|
||||
<div id="rollback" style="display:none">
|
||||
<div class="column">
|
||||
<button class="btn" id="otaVerifyApp">Click to confirm and commit OTA update</button>
|
||||
</div>
|
||||
<div class="column">
|
||||
<button class="btn" id="otaRollback">Cancel OTA. Click to rollback update and restart</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="update" style="display:block">
|
||||
<div class="cl1" style="display:none">
|
||||
<label class="cl01" for="otaFile">Select the new OTA firmware file</label>
|
||||
<input class="cl02" type="file" id="otaFile" placeholder="select file" onchange="readOtaFile(this)">
|
||||
</div>
|
||||
<div class="column" style="display:block" id="otaFileSelectVisible">
|
||||
<button class="btn" id="otaFileSelect" onclick="document.getElementById('otaFile').click()">File
|
||||
Select</button>
|
||||
</div>
|
||||
|
||||
<div class="column" style="display:none" id="otaStartVisible">
|
||||
<button class="btn" id="otaStartCancel">Start OTA update</button>
|
||||
</div>
|
||||
<div class="column" style="display:none" id="otaReStartVisible">
|
||||
<button class="btn" id="otaReStart">Reboot with new OTA firmware</button>
|
||||
</div>
|
||||
<div id="otaProgressVisible" style="display:none">
|
||||
<div class="cl1">
|
||||
<progress class="cl02" id="otaPogress" max=100 value=0>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
let otaData;
|
||||
let otaSetChunkSize = 0;
|
||||
let otaStartsegment = 0;
|
||||
let otaStarted = 0;
|
||||
|
||||
function readOtaFile(input) {
|
||||
let reader = new FileReader();
|
||||
let file = input.files[0];
|
||||
document.getElementById('otaFileSelect').innerHTML = "Selected firmware file: " + file.name;
|
||||
reader.readAsArrayBuffer(file);
|
||||
input.value = null;
|
||||
|
||||
reader.onload = function () {
|
||||
otaData = new Uint8Array(reader.result);
|
||||
document.getElementById("otaStartVisible").style.display = "block";
|
||||
document.getElementById("otaProgressVisible").style.display = "none";
|
||||
document.getElementById("otaReStartVisible").style.display = "none";
|
||||
};
|
||||
|
||||
reader.onerror = function () {
|
||||
console.log(reader.error);
|
||||
};
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
document.getElementById("otaStartCancel").addEventListener("click", function (e) {
|
||||
if (otaData.length > 0 && otaStarted == 0) {
|
||||
|
||||
socket.send(JSON.stringify({ name: "otaSize", value: otaData.length }));
|
||||
otaStarted = 1;
|
||||
this.innerHTML = "Click to Cancel";
|
||||
document.getElementById("otaFileSelect").disabled = true;
|
||||
document.getElementById("otaProgressVisible").style.display = "block";
|
||||
document.getElementById("otaPogress").max = otaData.length;
|
||||
}
|
||||
else {
|
||||
otaStarted = 0;
|
||||
socket.send(JSON.stringify({ name: "otaCancel", value: "Cancel" }));
|
||||
}
|
||||
|
||||
});
|
||||
document.getElementById("goHome").addEventListener("click", function (e) {
|
||||
//onclick="window.location.href = '/'"
|
||||
socket.close();
|
||||
window.location.href = '/';
|
||||
});
|
||||
document.getElementById("otaReStart").addEventListener("click", function (e) {
|
||||
socket.send(JSON.stringify({ name: "otaRestartEsp", value: "restart" }));
|
||||
});
|
||||
|
||||
function receiveWsData(data) {
|
||||
try {
|
||||
let obj = JSON.parse(data);
|
||||
switch (obj.name) {
|
||||
case "otaSetChunkSize":
|
||||
otaSetChunkSize = obj.value;
|
||||
break;
|
||||
case "otaGetChunk":
|
||||
let otaDataSend = otaData.subarray(obj.value, obj.value + otaSetChunkSize);
|
||||
document.getElementById("otaPogress").value = obj.value;
|
||||
document.getElementById("otaStartCancel").innerHTML = "Ota download. Size = " + otaData.length + " Segment = " + obj.value + " Click to Cancel";
|
||||
socket.send(otaDataSend);
|
||||
break;
|
||||
case "otaEnd":
|
||||
otaStartsegment = 0;
|
||||
otaStarted = 0;
|
||||
document.getElementById("otaStartVisible").style.display = "none";
|
||||
document.getElementById("otaStartCancel").innerHTML = "Start OTA update";
|
||||
document.getElementById("otaPogress").value = otaData.length;
|
||||
document.getElementById("otaFileSelect").disabled = false;
|
||||
document.getElementById("otaReStartVisible").style.display = "block";
|
||||
document.getElementById("otaReStart").innerHTML = "The firmware is loaded. Click to reboot with new OTA firmware";
|
||||
document.getElementById("otaReStart").disabled = false;
|
||||
break;
|
||||
case "otaError":
|
||||
case "otaCancel":
|
||||
otaStartsegment = 0;
|
||||
otaStarted = 0;
|
||||
document.getElementById("otaStartVisible").style.display = "none";
|
||||
document.getElementById("otaStartCancel").innerHTML = "Start OTA update";
|
||||
document.getElementById("otaPogress").value = otaData.length;
|
||||
document.getElementById("otaFileSelect").disabled = false;
|
||||
document.getElementById("otaReStartVisible").style.display = "block";
|
||||
document.getElementById("otaReStart").innerHTML = "ОТА firmware download canceled " + obj.value;
|
||||
document.getElementById("otaReStart").disabled = true;
|
||||
break;
|
||||
case "otaCheckRollback":
|
||||
document.getElementById("rollback").style.display = "block";
|
||||
document.getElementById("update").style.display = "none";
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
console.log(data + "Error msg");
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<script> // rollback
|
||||
document.getElementById("otaVerifyApp").addEventListener("click", function (e) {
|
||||
socket.send(JSON.stringify({ name: "otaProcessRollback", value: "false" }));
|
||||
document.getElementById("rollback").style.display = "none";
|
||||
document.getElementById("update").style.display = "block";
|
||||
});
|
||||
|
||||
document.getElementById("otaRollback").addEventListener("click", function (e) {
|
||||
socket.send(JSON.stringify({ name: "otaProcessRollback", value: "true" }));
|
||||
document.getElementById("rollback").style.display = "none";
|
||||
document.getElementById("update").style.display = "block";
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<script> // основной старт скрипта, открыть сокет
|
||||
// создать сокет по адресу
|
||||
let protocol = "ws:"
|
||||
if(document.location.protocol == "https:") protocol = "wss:"
|
||||
let wsHostStr = protocol + "//" + document.location.host + document.location.pathname;
|
||||
wsHostStr += (document.location.pathname == '/') ? "ws" : "/ws";
|
||||
var socket = new WebSocket(wsHostStr);
|
||||
socket.binaryType = "arraybuffer";
|
||||
</script>
|
||||
|
||||
<script> // события WS
|
||||
socket.onopen = function () {
|
||||
console.log("connect ws");
|
||||
};
|
||||
socket.onclose = function (event) {
|
||||
console.log("close ws - reload");
|
||||
setTimeout(() => document.location.reload(), 2000);
|
||||
};
|
||||
socket.onerror = function () {
|
||||
console.log("error ws");
|
||||
setTimeout(() => document.location.reload(), 2000);
|
||||
};
|
||||
socket.onmessage = function (event) {
|
||||
receiveWsData(event.data);
|
||||
};
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
148
components/ota_ws_update/source/ota_ws_update_esp.c
Normal file
148
components/ota_ws_update/source/ota_ws_update_esp.c
Normal file
@@ -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;
|
||||
}
|
||||
216
components/ota_ws_update/source/ota_ws_update_esp_preencrypted.c
Normal file
216
components/ota_ws_update/source/ota_ws_update_esp_preencrypted.c
Normal file
@@ -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;
|
||||
}
|
||||
263
components/ota_ws_update/source/ota_ws_update_http.c
Normal file
263
components/ota_ws_update/source/ota_ws_update_http.c
Normal file
@@ -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;
|
||||
}
|
||||
616
components/ota_ws_update/source/ota_ws_update — копия.html
Normal file
616
components/ota_ws_update/source/ota_ws_update — копия.html
Normal file
@@ -0,0 +1,616 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<title>OTA UPDATE - Обновление прошивки</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<style>
|
||||
/* Стили для SVG иконок */
|
||||
.icon {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: middle;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
/* Основные стили */
|
||||
:root {
|
||||
--primary: #4361ee;
|
||||
--primary-dark: #3a56d4;
|
||||
--secondary: #6c757d;
|
||||
--success: #28a745;
|
||||
--warning: #ffc107;
|
||||
--danger: #dc3545;
|
||||
--info: #17a2b8;
|
||||
--light: #f8f9fa;
|
||||
--dark: #343a40;
|
||||
--background: #f5f7fa;
|
||||
--card-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
--transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--background);
|
||||
color: var(--dark);
|
||||
line-height: 1.6;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, var(--primary), var(--primary-dark));
|
||||
color: white;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 25px;
|
||||
box-shadow: var(--card-shadow);
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 1.8rem;
|
||||
margin-bottom: 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: 1rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: white;
|
||||
border-radius: 10px;
|
||||
padding: 25px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: var(--card-shadow);
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 14px 20px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
width: 100%;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: var(--primary-dark);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background-color: var(--success);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
background-color: #218838;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background-color: var(--warning);
|
||||
color: var(--dark);
|
||||
}
|
||||
|
||||
.btn-warning:hover {
|
||||
background-color: #e0a800;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background-color: var(--danger);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background-color: #c82333;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: var(--secondary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: #5a6268;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.progress-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 20px;
|
||||
background-color: #e9ecef;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--primary), var(--info));
|
||||
border-radius: 10px;
|
||||
transition: width 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-card {
|
||||
background-color: #f8f9fa;
|
||||
border-left: 4px solid var(--info);
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin: 15px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.status-success {
|
||||
border-left-color: var(--success);
|
||||
background-color: rgba(40, 167, 69, 0.1);
|
||||
}
|
||||
|
||||
.status-error {
|
||||
border-left-color: var(--danger);
|
||||
background-color: rgba(220, 53, 69, 0.1);
|
||||
}
|
||||
|
||||
.status-warning {
|
||||
border-left-color: var(--warning);
|
||||
background-color: rgba(255, 193, 7, 0.1);
|
||||
}
|
||||
|
||||
.connection-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 20px;
|
||||
background-color: #f8f9fa;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.connected {
|
||||
color: var(--success);
|
||||
background-color: rgba(40, 167, 69, 0.1);
|
||||
}
|
||||
|
||||
.disconnected {
|
||||
color: var(--danger);
|
||||
background-color: rgba(220, 53, 69, 0.1);
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.connected .status-indicator {
|
||||
background-color: var(--success);
|
||||
}
|
||||
|
||||
.disconnected .status-indicator {
|
||||
background-color: var(--danger);
|
||||
}
|
||||
|
||||
.file-info {
|
||||
background-color: #e9ecef;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
margin: 15px 0;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
margin-bottom: 15px;
|
||||
color: var(--dark);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
margin-top: auto;
|
||||
padding: 20px;
|
||||
color: var(--secondary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.btn-container {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.btn-container .btn {
|
||||
width: 50%;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- SVG иконки -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
|
||||
<!-- Иконка синхронизации -->
|
||||
<symbol id="sync-icon" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6c0 1.01-.25 1.97-.7 2.8l1.46 1.46A7.93 7.93 0 0020 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6c0-1.01.25-1.97.7-2.8L5.24 7.74A7.93 7.93 0 004 12c0 4.42 3.58 8 8 8v3l4-4l-4-4v3z"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Иконка предупреждения -->
|
||||
<symbol id="warning-icon" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Иконка загрузки файла -->
|
||||
<symbol id="upload-icon" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM14 13v4h-4v-4H7l5-5 5 5h-3z"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Иконка папки -->
|
||||
<symbol id="folder-icon" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M20 6h-8l-2-2H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 12H4V6h5.17l2 2H20v10z"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Иконка файла кода -->
|
||||
<symbol id="file-code-icon" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M13 9h5.5L13 3.5V9M6 2h8l6 6v12a2 2 0 01-2 2H6a2 2 0 01-2-2V4a2 2 0 012-2zm4.5 12.5l-1.77-1.77L10.23 12 8.73 10.5l1.77-1.77L12 10.23l1.5-1.5 1.77 1.77L13.77 12l1.5 1.5-1.77 1.77L12 13.77l-1.5 1.5z"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Иконка воспроизведения -->
|
||||
<symbol id="play-icon" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M8 5v14l11-7z"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Иконка отмены -->
|
||||
<symbol id="cancel-icon" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Иконка подтверждения -->
|
||||
<symbol id="check-icon" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Иконка отката -->
|
||||
<symbol id="undo-icon" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88 3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Иконка питания -->
|
||||
<symbol id="power-icon" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M13 3h-2v10h2V3zm4.83 2.17l-1.42 1.42C17.99 7.86 19 9.81 19 12c0 3.87-3.13 7-7 7s-7-3.13-7-7c0-2.19 1.01-4.14 2.58-5.42L6.17 5.17C4.23 6.82 3 9.26 3 12c0 4.97 4.03 9 9 9s9-4.03 9-9c0-2.74-1.23-5.18-3.17-6.83z"/>
|
||||
</symbol>
|
||||
|
||||
<!-- Иконка дома -->
|
||||
<symbol id="home-icon" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1><svg class="icon" style="width: 24px; height: 24px;"><use href="#sync-icon"></use></svg> OTA UPDATE</h1>
|
||||
<p>Обновление прошивки устройства по воздуху</p>
|
||||
</div>
|
||||
|
||||
<div class="connection-status disconnected">
|
||||
<span class="status-indicator"></span>
|
||||
<span id="status-text">Не подключено</span>
|
||||
</div>
|
||||
|
||||
<div id="rollback" class="card hidden">
|
||||
<h2 class="section-title"><svg class="icon"><use href="#warning-icon"></use></svg> Подтверждение обновления</h2>
|
||||
<div class="status-card status-warning">
|
||||
<p>Обновление было загружено, но не подтверждено. Пожалуйста, подтвердите установку новой прошивки или откатитесь к предыдущей версии.</p>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-success" id="otaVerifyApp">
|
||||
<svg class="icon"><use href="#check-icon"></use></svg> Подтвердить и установить обновление
|
||||
</button>
|
||||
|
||||
<button class="btn btn-danger" id="otaRollback">
|
||||
<svg class="icon"><use href="#undo-icon"></use></svg> Отменить обновление и откатиться
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="update" class="card">
|
||||
<h2 class="section-title"><svg class="icon"><use href="#upload-icon"></use></svg> Загрузка прошивки</h2>
|
||||
|
||||
<input type="file" id="otaFile" class="hidden" accept=".bin" onchange="readOtaFile(this)">
|
||||
|
||||
<button class="btn btn-primary" id="otaFileSelect" onclick="document.getElementById('otaFile').click()">
|
||||
<svg class="icon"><use href="#folder-icon"></use></svg> Выбрать файл прошивки
|
||||
</button>
|
||||
|
||||
<div id="fileInfo" class="file-info hidden">
|
||||
<svg class="icon"><use href="#file-code-icon"></use></svg>
|
||||
<span id="fileName">Файл не выбран</span>
|
||||
</div>
|
||||
|
||||
<div id="otaProgressVisible" class="hidden">
|
||||
<div class="progress-container">
|
||||
<div class="progress-info">
|
||||
<span id="progressStatus">Загрузка...</span>
|
||||
<span id="progressPercent">0%</span>
|
||||
</div>
|
||||
<div class="progress-bar">
|
||||
<div class="progress" id="otaProgress" style="width: 0%">0%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-container">
|
||||
<button class="btn btn-warning" id="otaStartCancel">
|
||||
<svg class="icon"><use href="#play-icon"></use></svg> Начать обновление
|
||||
</button>
|
||||
|
||||
<button class="btn btn-success hidden" id="otaReStart">
|
||||
<svg class="icon"><use href="#power-icon"></use></svg> Перезагрузить с новой прошивкой
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-secondary" id="goHome">
|
||||
<svg class="icon"><use href="#home-icon"></use></svg> Вернуться на главную
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>LLC AEROTECH© 2025</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let otaData;
|
||||
let otaSetChunkSize = 0;
|
||||
let otaStartsegment = 0;
|
||||
let otaStarted = 0;
|
||||
|
||||
function readOtaFile(input) {
|
||||
if (!input.files.length) return;
|
||||
|
||||
let reader = new FileReader();
|
||||
let file = input.files[0];
|
||||
|
||||
// Показываем информацию о файле
|
||||
document.getElementById('fileInfo').classList.remove('hidden');
|
||||
document.getElementById('fileName').textContent = `${file.name} (${formatFileSize(file.size)})`;
|
||||
document.getElementById('otaFileSelect').innerHTML = `<svg class="icon"><use href="#check-icon"></use></svg> Файл выбран: ${file.name}`;
|
||||
|
||||
reader.readAsArrayBuffer(file);
|
||||
input.value = null;
|
||||
|
||||
reader.onload = function () {
|
||||
otaData = new Uint8Array(reader.result);
|
||||
document.getElementById("otaStartCancel").classList.remove('hidden');
|
||||
document.getElementById("otaProgressVisible").classList.add('hidden');
|
||||
document.getElementById("otaReStart").classList.add('hidden');
|
||||
};
|
||||
|
||||
reader.onerror = function () {
|
||||
console.log(reader.error);
|
||||
showStatus("Ошибка чтения файла", "error");
|
||||
};
|
||||
}
|
||||
|
||||
function formatFileSize(bytes) {
|
||||
if (bytes < 1024) return bytes + " байт";
|
||||
else if (bytes < 1048576) return (bytes / 1024).toFixed(2) + " КБ";
|
||||
else return (bytes / 1048576).toFixed(2) + " МБ";
|
||||
}
|
||||
|
||||
function updateProgress(value, max) {
|
||||
const percent = Math.round((value / max) * 100);
|
||||
const progressBar = document.getElementById("otaProgress");
|
||||
progressBar.style.width = percent + "%";
|
||||
progressBar.textContent = percent + "%";
|
||||
document.getElementById("progressPercent").textContent = percent + "%";
|
||||
document.getElementById("progressStatus").textContent = `Загружено: ${formatFileSize(value)} из ${formatFileSize(max)}`;
|
||||
}
|
||||
|
||||
function showStatus(message, type = "info") {
|
||||
// Здесь можно реализовать отображение статусных сообщений
|
||||
console.log(`${type}: ${message}`);
|
||||
}
|
||||
|
||||
// Обработчики событий
|
||||
document.getElementById("otaStartCancel").addEventListener("click", function (e) {
|
||||
if (otaData && otaData.length > 0 && otaStarted == 0) {
|
||||
socket.send(JSON.stringify({ name: "otaSize", value: otaData.length }));
|
||||
otaStarted = 1;
|
||||
this.innerHTML = "<svg class='icon'><use href='#cancel-icon'></use></svg> Отменить загрузку";
|
||||
document.getElementById("otaFileSelect").disabled = true;
|
||||
document.getElementById("otaProgressVisible").classList.remove("hidden");
|
||||
updateProgress(0, otaData.length);
|
||||
} else {
|
||||
otaStarted = 0;
|
||||
socket.send(JSON.stringify({ name: "otaCancel", value: "Cancel" }));
|
||||
this.innerHTML = "<svg class='icon'><use href='#play-icon'></use></svg> Начать обновление";
|
||||
document.getElementById("otaFileSelect").disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("goHome").addEventListener("click", function (e) {
|
||||
socket.close();
|
||||
window.location.href = '/';
|
||||
});
|
||||
|
||||
document.getElementById("otaReStart").addEventListener("click", function (e) {
|
||||
socket.send(JSON.stringify({ name: "otaRestartEsp", value: "restart" }));
|
||||
});
|
||||
|
||||
// Rollback handlers
|
||||
document.getElementById("otaVerifyApp").addEventListener("click", function (e) {
|
||||
socket.send(JSON.stringify({ name: "otaProcessRollback", value: "false" }));
|
||||
document.getElementById("rollback").classList.add("hidden");
|
||||
document.getElementById("update").classList.remove("hidden");
|
||||
});
|
||||
|
||||
document.getElementById("otaRollback").addEventListener("click", function (e) {
|
||||
socket.send(JSON.stringify({ name: "otaProcessRollback", value: "true" }));
|
||||
document.getElementById("rollback").classList.add("hidden");
|
||||
document.getElementById("update").classList.remove("hidden");
|
||||
});
|
||||
|
||||
function receiveWsData(data) {
|
||||
try {
|
||||
let obj = JSON.parse(data);
|
||||
switch (obj.name) {
|
||||
case "otaSetChunkSize":
|
||||
otaSetChunkSize = obj.value;
|
||||
break;
|
||||
case "otaGetChunk":
|
||||
let otaDataSend = otaData.subarray(obj.value, obj.value + otaSetChunkSize);
|
||||
updateProgress(obj.value, otaData.length);
|
||||
document.getElementById("otaStartCancel").innerHTML = `<svg class='icon'><use href='#cancel-icon'></use></svg> Загрузка: ${formatFileSize(obj.value)} из ${formatFileSize(otaData.length)}`;
|
||||
socket.send(otaDataSend);
|
||||
break;
|
||||
case "otaEnd":
|
||||
otaStartsegment = 0;
|
||||
otaStarted = 0;
|
||||
document.getElementById("otaStartCancel").classList.add("hidden");
|
||||
document.getElementById("otaStartCancel").innerHTML = "<svg class='icon'><use href='#play-icon'></use></svg> Начать обновление";
|
||||
updateProgress(otaData.length, otaData.length);
|
||||
document.getElementById("otaFileSelect").disabled = false;
|
||||
document.getElementById("otaReStart").classList.remove("hidden");
|
||||
document.getElementById("otaReStart").innerHTML = "<svg class='icon'><use href='#power-icon'></use></svg> Прошивка загружена. Перезагрузить устройство";
|
||||
document.getElementById("otaReStart").disabled = false;
|
||||
showStatus("Прошивка успешно загружена", "success");
|
||||
break;
|
||||
case "otaError":
|
||||
case "otaCancel":
|
||||
otaStartsegment = 0;
|
||||
otaStarted = 0;
|
||||
document.getElementById("otaStartCancel").classList.remove("hidden");
|
||||
document.getElementById("otaStartCancel").innerHTML = "<svg class='icon'><use href='#play-icon'></use></svg> Начать обновление";
|
||||
document.getElementById("otaFileSelect").disabled = false;
|
||||
document.getElementById("otaReStart").classList.remove("hidden");
|
||||
document.getElementById("otaReStart").innerHTML = "Загрузка отменена: " + obj.value;
|
||||
document.getElementById("otaReStart").disabled = true;
|
||||
showStatus("Загрузка отменена: " + obj.value, "error");
|
||||
break;
|
||||
case "otaCheckRollback":
|
||||
document.getElementById("rollback").classList.remove("hidden");
|
||||
document.getElementById("update").classList.add("hidden");
|
||||
break;
|
||||
}
|
||||
} catch {
|
||||
console.log(data + "Error msg");
|
||||
}
|
||||
}
|
||||
|
||||
// WebSocket connection
|
||||
let protocol = "ws:";
|
||||
if (document.location.protocol == "https:") protocol = "wss:";
|
||||
let wsHostStr = protocol + "//" + document.location.host + document.location.pathname;
|
||||
wsHostStr += (document.location.pathname == '/') ? "ws" : "/ws";
|
||||
var socket = new WebSocket(wsHostStr);
|
||||
socket.binaryType = "arraybuffer";
|
||||
|
||||
// WebSocket events
|
||||
socket.onopen = function () {
|
||||
console.log("connect ws");
|
||||
document.getElementById("status-text").textContent = "Connected";
|
||||
document.querySelector(".connection-status").classList.remove("disconnected");
|
||||
document.querySelector(".connection-status").classList.add("connected");
|
||||
};
|
||||
|
||||
socket.onclose = function (event) {
|
||||
console.log("close ws - reload");
|
||||
document.getElementById("status-text").textContent = "Not connected";
|
||||
document.querySelector(".connection-status").classList.remove("connected");
|
||||
document.querySelector(".connection-status").classList.add("disconnected");
|
||||
setTimeout(() => document.location.reload(), 2000);
|
||||
};
|
||||
|
||||
socket.onerror = function () {
|
||||
console.log("error ws");
|
||||
document.getElementById("status-text").textContent = "Connection error";
|
||||
document.querySelector(".connection-status").classList.remove("connected");
|
||||
document.querySelector(".connection-status").classList.add("disconnected");
|
||||
setTimeout(() => document.location.reload(), 2000);
|
||||
};
|
||||
|
||||
socket.onmessage = function (event) {
|
||||
receiveWsData(event.data);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1 +1 @@
|
||||
idf_component_register(SRCS "ate0004.c" INCLUDE_DIRS "" REQUIRES zh_pcf8574 esp_wifi nvs_flash)
|
||||
idf_component_register(SRCS "ate0004.c" INCLUDE_DIRS "" REQUIRES zh_pcf8574 esp_wifi nvs_flash esp_http_server ota_ws_update)
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
31
sdkconfig
31
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
|
||||
|
||||
Reference in New Issue
Block a user