From f2223dd719de3d28b6dc2cf3877092c6580829cb Mon Sep 17 00:00:00 2001 From: David Cermak Date: Thu, 21 Sep 2023 09:03:20 +0200 Subject: [PATCH 1/4] feat(modem): Added test that performs OTA to exercise modem layers --- .../include/vfs_resource/vfs_create.hpp | 2 +- .../esp_modem/test/target_ota/CMakeLists.txt | 8 + .../esp_modem/test/target_ota/README.md | 21 ++ .../components/manual_ota/CMakeLists.txt | 3 + .../components/manual_ota/manual_ota.cpp | 259 +++++++++++++++ .../components/manual_ota/manual_ota.hpp | 71 +++++ .../manual_ota/transport_batch_tls.cpp | 232 ++++++++++++++ .../manual_ota/transport_batch_tls.hpp | 25 ++ .../esp_modem/test/target_ota/http_server.py | 15 + .../test/target_ota/main/CMakeLists.txt | 2 + .../test/target_ota/main/Kconfig.projbuild | 47 +++ .../test/target_ota/main/network_dce.hpp | 55 ++++ .../test/target_ota/main/ota_test.cpp | 295 ++++++++++++++++++ 13 files changed, 1034 insertions(+), 1 deletion(-) create mode 100644 components/esp_modem/test/target_ota/CMakeLists.txt create mode 100644 components/esp_modem/test/target_ota/README.md create mode 100644 components/esp_modem/test/target_ota/components/manual_ota/CMakeLists.txt create mode 100644 components/esp_modem/test/target_ota/components/manual_ota/manual_ota.cpp create mode 100644 components/esp_modem/test/target_ota/components/manual_ota/manual_ota.hpp create mode 100644 components/esp_modem/test/target_ota/components/manual_ota/transport_batch_tls.cpp create mode 100644 components/esp_modem/test/target_ota/components/manual_ota/transport_batch_tls.hpp create mode 100644 components/esp_modem/test/target_ota/http_server.py create mode 100644 components/esp_modem/test/target_ota/main/CMakeLists.txt create mode 100644 components/esp_modem/test/target_ota/main/Kconfig.projbuild create mode 100644 components/esp_modem/test/target_ota/main/network_dce.hpp create mode 100644 components/esp_modem/test/target_ota/main/ota_test.cpp diff --git a/components/esp_modem/include/vfs_resource/vfs_create.hpp b/components/esp_modem/include/vfs_resource/vfs_create.hpp index 7b075143b..5d8485ddc 100644 --- a/components/esp_modem/include/vfs_resource/vfs_create.hpp +++ b/components/esp_modem/include/vfs_resource/vfs_create.hpp @@ -31,7 +31,7 @@ */ struct esp_modem_vfs_uart_creator { const char *dev_name; /*!< VFS device name, e.g. /dev/uart/n */ - const struct esp_modem_uart_term_config uart; /*!< UART driver init struct */ + struct esp_modem_uart_term_config uart; /*!< UART driver init struct */ }; /** diff --git a/components/esp_modem/test/target_ota/CMakeLists.txt b/components/esp_modem/test/target_ota/CMakeLists.txt new file mode 100644 index 000000000..ca5c2eaaa --- /dev/null +++ b/components/esp_modem/test/target_ota/CMakeLists.txt @@ -0,0 +1,8 @@ +# 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.8) + +set(EXTRA_COMPONENT_DIRS "../.." "../../examples/modem_tcp_client/components") + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ota_test) diff --git a/components/esp_modem/test/target_ota/README.md b/components/esp_modem/test/target_ota/README.md new file mode 100644 index 000000000..74b98ef4a --- /dev/null +++ b/components/esp_modem/test/target_ota/README.md @@ -0,0 +1,21 @@ +# Target test running OTA update + +## Overview + +The aim of this test is to exercise the most commonly failing scenario, running OTA over PPPoS with https. + +This project opens a data session, runs basic mqtt operations and initiates OTA update. +It supports the following test configurations: +* Using a real modem device (default config) +* Using VFS device (only to exercise VFS DTE) +* Using network-only DCE (connecting directly to PPP server) -- needs some configuration + +### Configuring the PPP server + +You need to run these applications on your host machine: +* PPP server +```bash +sudo pppd /dev/ttyUSB1 115200 192.168.11.1:192.168.11.2 ms-dns 8.8.8.8 modem local noauth debug nocrtscts nodetach +ipv6 +``` +* MQTT broker: Running mosquitto in the default config is enough, configuring the broker's URL to the local PPP address: `config.broker.address.uri = "mqtt://192.168.11.1";` +* HTTP server: Need to support HTTP/1.1 (to support ranges). You can use the script `http_server.py` and configure the OTA endpoint as `"https://192.168.11.1:1234/esp32.bin"` diff --git a/components/esp_modem/test/target_ota/components/manual_ota/CMakeLists.txt b/components/esp_modem/test/target_ota/components/manual_ota/CMakeLists.txt new file mode 100644 index 000000000..5eb262e10 --- /dev/null +++ b/components/esp_modem/test/target_ota/components/manual_ota/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS manual_ota.cpp transport_batch_tls.cpp + INCLUDE_DIRS "." + PRIV_REQUIRES extra_tcp_transports esp_http_client app_update) diff --git a/components/esp_modem/test/target_ota/components/manual_ota/manual_ota.cpp b/components/esp_modem/test/target_ota/components/manual_ota/manual_ota.cpp new file mode 100644 index 000000000..c1aaa3d36 --- /dev/null +++ b/components/esp_modem/test/target_ota/components/manual_ota/manual_ota.cpp @@ -0,0 +1,259 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include "manual_ota.hpp" +#include "esp_log.h" +#include "esp_ota_ops.h" +#include "esp_app_format.h" +#include "esp_http_client.h" +#include "esp_partition.h" +#include "esp_transport_tcp.h" +#include "transport_batch_tls.hpp" + +static const char *TAG = "manual_ota"; + +bool manual_ota::begin() +{ + if (status != state::UNDEF) { + ESP_LOGE(TAG, "Invalid state"); + return false; + } + status = state::INIT; + esp_transport_handle_t tcp = esp_transport_tcp_init(); + ssl_ = esp_transport_batch_tls_init(tcp, max_buffer_size_); + + esp_http_client_config_t config = { }; + config.skip_cert_common_name_check = true; + config.url = uri_; + config.transport = ssl_; + const esp_partition_t *configured = esp_ota_get_boot_partition(); + const esp_partition_t *running = esp_ota_get_running_partition(); + + if (configured != running) { + ESP_LOGE(TAG, "Configured OTA boot partition at offset 0x%08" PRIx32 ", but running from offset 0x%08" PRIx32, configured->address, running->address); + return false; + } + + http_ = esp_http_client_init(&config); + if (http_ == nullptr) { + ESP_LOGE(TAG, "Failed to initialise HTTP connection"); + return false; + } + esp_http_client_set_method(http_, HTTP_METHOD_HEAD); + esp_err_t err = esp_http_client_perform(http_); + if (err == ESP_OK) { + int http_status = esp_http_client_get_status_code(http_); + if (http_status != HttpStatus_Ok) { + ESP_LOGE(TAG, "Received incorrect http status %d", http_status); + return false; + } + } else { + ESP_LOGE(TAG, "ESP HTTP client perform failed: %d", err); + return false; + } + image_length_ = esp_http_client_get_content_length(http_); + ESP_LOGI(TAG, "image_length = %lld", image_length_); + esp_http_client_close(http_); + + if (image_length_ > size_) { + char *header_val = nullptr; + asprintf(&header_val, "bytes=0-%d", max_buffer_size_ - 1); + if (header_val == nullptr) { + ESP_LOGE(TAG, "Failed to allocate memory for HTTP header"); + return false; + } + esp_http_client_set_header(http_, "Range", header_val); + free(header_val); + } + esp_http_client_set_method(http_, HTTP_METHOD_GET); + + partition_ = esp_ota_get_next_update_partition(nullptr); + if (partition_ == nullptr) { + ESP_LOGE(TAG, "Invalid update partition"); + return false; + } + ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%" PRIx32, partition_->subtype, partition_->address); + + file_length_ = 0; + reconnect_attempts_ = 0; + buffer_.resize(max_buffer_size_); + status = state::IMAGE_CHECK; + return true; +} + +bool manual_ota::perform() +{ + if (status != state::IMAGE_CHECK && status != state::START) { + ESP_LOGE(TAG, "Invalid state"); + return false; + } + esp_err_t err = esp_http_client_open(http_, 0); + if (err != ESP_OK) { + if (image_length_ == file_length_) { + status = state::END; + return false; + } + + esp_http_client_close(http_); + ESP_LOGI(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err)); + if (reconnect_attempts_++ < max_reconnect_attempts_) { + if (prepare_reconnect()) { + return true; // will retry in the next iteration + } + } + return fail_cleanup(); + } + esp_http_client_fetch_headers(http_); + + int batch_len = esp_transport_batch_tls_pre_read(ssl_, max_buffer_size_, timeout_ * 1000); + if (batch_len < 0) { + ESP_LOGE(TAG, "Error: Failed to pre-read plain text data!"); + return fail_cleanup(); + } + + int data_read = esp_http_client_read(http_, buffer_.data(), batch_len); + + if (data_read < 0) { + ESP_LOGE(TAG, "Error: SSL data read error"); + return fail_cleanup(); + } else if (data_read > 0) { + esp_http_client_close(http_); + + if (status == state::IMAGE_CHECK) { + 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, &buffer_[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); + + esp_app_desc_t running_app_info; + const esp_partition_t *running = esp_ota_get_running_partition(); + if (esp_ota_get_partition_description(running, &running_app_info) == ESP_OK) { + ESP_LOGI(TAG, "Running firmware version: %s", running_app_info.version); + } + + const esp_partition_t *last_invalid_app = esp_ota_get_last_invalid_partition(); + esp_app_desc_t invalid_app_info; + if (esp_ota_get_partition_description(last_invalid_app, &invalid_app_info) == ESP_OK) { + ESP_LOGI(TAG, "Last invalid firmware version: %s", invalid_app_info.version); + } + + // check current version with last invalid partition + if (last_invalid_app != NULL) { + if (memcmp(invalid_app_info.version, new_app_info.version, sizeof(new_app_info.version)) == 0) { + ESP_LOGW(TAG, "New version is the same as invalid version."); + ESP_LOGW(TAG, "Previously, there was an attempt to launch the firmware with %s version, but it failed.", invalid_app_info.version); + ESP_LOGW(TAG, "The firmware has been rolled back to the previous version."); + return fail_cleanup(); + } + } + + status = state::START; + err = esp_ota_begin(partition_, OTA_WITH_SEQUENTIAL_WRITES, &update_handle_); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ota_begin failed (%s)", esp_err_to_name(err)); + esp_ota_abort(update_handle_); + return fail_cleanup(); + } + ESP_LOGI(TAG, "esp_ota_begin succeeded"); + } else { + ESP_LOGE(TAG, "Received chunk doesn't contain app descriptor"); + esp_ota_abort(update_handle_); + return fail_cleanup(); + } + } + err = esp_ota_write(update_handle_, (const void *)buffer_.data(), data_read); + if (err != ESP_OK) { + esp_ota_abort(update_handle_); + return fail_cleanup(); + } + file_length_ += data_read; + ESP_LOGI(TAG, "Written image length %d", file_length_); + + if (image_length_ == file_length_) { + status = state::END; + return false; + } + + if (!prepare_reconnect()) { + esp_ota_abort(update_handle_); + return fail_cleanup(); + } + + } else if (data_read == 0) { + if (file_length_ == 0) { + // Try to handle possible HTTP redirections + int status_code = esp_http_client_get_status_code(http_); + ESP_LOGW(TAG, "Status code: %d", status_code); + err = esp_http_client_set_redirection(http_); + if (err != ESP_OK) { + ESP_LOGE(TAG, "URL redirection Failed"); + esp_ota_abort(update_handle_); + return fail_cleanup(); + } + + err = esp_http_client_open(http_, 0); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err)); + return fail_cleanup(); + } + esp_http_client_fetch_headers(http_); + } + } + + return true; +} + +bool manual_ota::prepare_reconnect() +{ + esp_http_client_set_method(http_, HTTP_METHOD_GET); + char *header_val = nullptr; + if ((image_length_ - file_length_) > max_buffer_size_) { + asprintf(&header_val, "bytes=%d-%d", file_length_, (file_length_ + max_buffer_size_ - 1)); + } else { + asprintf(&header_val, "bytes=%d-", file_length_); + } + if (header_val == nullptr) { + ESP_LOGE(TAG, "Failed to allocate memory for HTTP header"); + return false; + } + esp_http_client_set_header(http_, "Range", header_val); + free(header_val); + return true; +} + +bool manual_ota::fail_cleanup() +{ + esp_http_client_close(http_); + esp_http_client_cleanup(http_); + status = state::FAIL; + return false; +} + +bool manual_ota::end() +{ + if (status == state::END) { + if (!esp_http_client_is_complete_data_received(http_)) { + ESP_LOGE(TAG, "Error in receiving complete file"); + return fail_cleanup(); + } + 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"); + } else { + ESP_LOGE(TAG, "esp_ota_end failed (%s)!", esp_err_to_name(err)); + } + return fail_cleanup(); + } + err = esp_ota_set_boot_partition(partition_); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ota_set_boot_partition failed (%s)!", esp_err_to_name(err)); + return fail_cleanup(); + } + return true; + } + return false; +} diff --git a/components/esp_modem/test/target_ota/components/manual_ota/manual_ota.hpp b/components/esp_modem/test/target_ota/components/manual_ota/manual_ota.hpp new file mode 100644 index 000000000..ad09e748d --- /dev/null +++ b/components/esp_modem/test/target_ota/components/manual_ota/manual_ota.hpp @@ -0,0 +1,71 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#pragma once +#include +#include "esp_http_client.h" +#include "esp_partition.h" +#include "esp_transport_tcp.h" +#include "esp_ota_ops.h" + +class manual_ota { +public: + enum class state { + UNDEF, + INIT, + IMAGE_CHECK, + START, + END, + FAIL, + }; + size_t size_{32}; + int timeout_{2}; + + /** + * @brief Construct a new manual ota object + * + * @param uri URI of the binary image + */ + explicit manual_ota(const char *uri): uri_(uri) {} + + /** + * @brief Start the manual OTA process + * + * @return true if started successfully + */ + bool begin(); + + /** + * @brief Performs one read-write OTA iteration + * + * @return true if the process is in progress + * @return false if the process finished, call end() to get OTA result + */ + bool perform(); + + /** + * @brief Finishes an OTA update + * + * @return true if the OTA update completed successfully + */ + bool end(); + +private: + const char *uri_{}; + esp_http_client_handle_t http_; + int64_t image_length_; + size_t file_length_; + const size_t max_buffer_size_{size_ * 1024}; + const esp_partition_t *partition_{nullptr}; + state status{state::UNDEF}; + std::vector buffer_{}; + int reconnect_attempts_; + const int max_reconnect_attempts_{3}; + esp_transport_handle_t ssl_; + esp_ota_handle_t update_handle_{0}; + + bool prepare_reconnect(); + bool fail_cleanup(); +}; diff --git a/components/esp_modem/test/target_ota/components/manual_ota/transport_batch_tls.cpp b/components/esp_modem/test/target_ota/components/manual_ota/transport_batch_tls.cpp new file mode 100644 index 000000000..013b4ef6b --- /dev/null +++ b/components/esp_modem/test/target_ota/components/manual_ota/transport_batch_tls.cpp @@ -0,0 +1,232 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include "esp_log.h" +#include "mbedtls_wrap.hpp" +#include "esp_transport_tcp.h" + +#define TAG "batch-tls" + +class TlsTransport: public Tls { +public: + explicit TlsTransport(esp_transport_handle_t parent) : Tls(), transport_(parent), read_len(0), offset(0) {} + int send(const unsigned char *buf, size_t len) override; + int recv(unsigned char *buf, size_t len) override; + static bool set_func(esp_transport_handle_t tls_transport); + int preread(size_t len, int timeout_ms); + bool prepare_buffer(size_t max_size); +private: + esp_transport_handle_t transport_{}; + int connect(const char *host, int port, int timeout_ms); + void delay() override; + + struct transport { + static int connect(esp_transport_handle_t t, const char *host, int port, int timeout_ms); + static int read(esp_transport_handle_t t, char *buffer, int len, int timeout_ms); + static int write(esp_transport_handle_t t, const char *buffer, int len, int timeout_ms); + static int close(esp_transport_handle_t t); + static int poll_read(esp_transport_handle_t t, int timeout_ms); + static int poll_write(esp_transport_handle_t t, int timeout_ms); + static int destroy(esp_transport_handle_t t); + }; + int last_timeout; + std::vector buf; + size_t read_len; + size_t offset; +}; + +esp_transport_handle_t esp_transport_tls_init(esp_transport_handle_t parent) +{ + esp_transport_handle_t ssl = esp_transport_init(); + auto *tls = new TlsTransport(parent); + esp_transport_set_context_data(ssl, tls); + TlsTransport::set_func(ssl); + return ssl; +} + +int TlsTransport::send(const unsigned char *buf, size_t len) +{ + int ret = esp_transport_write(transport_, reinterpret_cast(buf), len, 0); + ESP_LOGD(TAG, "writing(len=%d) ret=%d", len, ret); + return ret; +} + +int TlsTransport::recv(unsigned char *buffer, size_t len) +{ + ESP_LOGD(TAG, "recv(len=%d)", len); + if (read_len != 0) { + + if (read_len > len) { + memcpy((char *)buffer, buf.data() + offset, len); + read_len -= len; + offset += len; + ESP_LOGD(TAG, "read %d from batch read_len = %d", len, read_len); + return len; + } else { + int remaining = len = read_len; + if (remaining > 0) { + memcpy((char *)buffer, buf.data() + offset, remaining); + read_len = 0; + offset = 0; + return remaining; + + } + read_len = 0; + offset = 0; + return ERR_TCP_TRANSPORT_CONNECTION_CLOSED_BY_FIN; + } + } + int ret = esp_transport_read(transport_, reinterpret_cast(buffer), len, last_timeout); + + if (ret == ERR_TCP_TRANSPORT_CONNECTION_TIMEOUT) { + return MBEDTLS_ERR_SSL_WANT_READ; + } + return ret == ERR_TCP_TRANSPORT_CONNECTION_CLOSED_BY_FIN ? 0 : ret; +} + +bool TlsTransport::set_func(esp_transport_handle_t tls_transport) +{ + return esp_transport_set_func(tls_transport, TlsTransport::transport::connect, TlsTransport::transport::read, TlsTransport::transport::write, TlsTransport::transport::close, TlsTransport::transport::poll_read, TlsTransport::transport::poll_write, TlsTransport::transport::destroy) == ESP_OK; +} + +int TlsTransport::connect(const char *host, int port, int timeout_ms) +{ + return esp_transport_connect(transport_, host, port, timeout_ms); +} + +void TlsTransport::delay() +{ + vTaskDelay(pdMS_TO_TICKS(500)); +} + +int TlsTransport::transport::connect(esp_transport_handle_t t, const char *host, int port, int timeout_ms) +{ + auto tls = static_cast(esp_transport_get_context_data(t)); + tls->init(is_server{false}, do_verify{false}); + + ESP_LOGD(TAG, "TLS-connect"); + auto ret = tls->connect(host, port, timeout_ms); + if (ret < 0) { + ESP_LOGI(TAG, "Failed to connect to transport"); + return ret; + } + if (tls->is_session_loaded()) { + tls->set_session(); + } + ESP_LOGI(TAG, "Before handshake"); + ret = tls->handshake(); + if (ret < 0) { + ESP_LOGI(TAG, "Failed to handshake"); + return ret; + } + tls->get_session(); + ESP_LOGI(TAG, "After handshake"); + return 0; +} + +int TlsTransport::transport::read(esp_transport_handle_t t, char *buffer, int len, int timeout_ms) +{ + auto tls = static_cast(esp_transport_get_context_data(t)); + ESP_LOGD(TAG, "available=%d tls->read_len=%d", tls->get_available_bytes(), tls->read_len); + if (tls->get_available_bytes() <= 0 && tls->read_len == 0) { + ESP_LOGD(TAG, "red(len=%d, timeout=%d) tls->read_len=%d", len, timeout_ms, tls->read_len); + tls->last_timeout = timeout_ms; + int poll = esp_transport_poll_read(t, timeout_ms); + if (poll == -1) { + return ERR_TCP_TRANSPORT_CONNECTION_FAILED; + } + if (poll == 0) { + return ERR_TCP_TRANSPORT_CONNECTION_TIMEOUT; + } + } + + auto ret = tls->read(reinterpret_cast(buffer), len); + if (ret == MBEDTLS_ERR_SSL_WANT_READ) { + ret = ERR_TCP_TRANSPORT_CONNECTION_TIMEOUT; + } + ESP_LOGD(TAG, "red(len=%d, timeout=%d) ret=%d", len, timeout_ms, ret); + return ret; +} + +int TlsTransport::transport::write(esp_transport_handle_t t, const char *buffer, int len, int timeout_ms) +{ + int poll; + if ((poll = esp_transport_poll_write(t, timeout_ms)) <= 0) { + ESP_LOGW(TAG, "Poll timeout or error timeout_ms=%d", timeout_ms); + return poll; + } + + auto tls = static_cast(esp_transport_get_context_data(t)); + int ret = tls->write(reinterpret_cast(buffer), len); + ESP_LOGD(TAG, "write ret=%d", ret); + return ret; +} + +int TlsTransport::transport::close(esp_transport_handle_t t) +{ + auto tls = static_cast(esp_transport_get_context_data(t)); + int ret = esp_transport_close(tls->transport_); + tls->deinit(); + return ret; +} + +int TlsTransport::transport::poll_read(esp_transport_handle_t t, int timeout_ms) +{ + auto tls = static_cast(esp_transport_get_context_data(t)); + return esp_transport_poll_read(tls->transport_, timeout_ms); +} + +int TlsTransport::transport::poll_write(esp_transport_handle_t t, int timeout_ms) +{ + auto tls = static_cast(esp_transport_get_context_data(t)); + return esp_transport_poll_write(tls->transport_, timeout_ms); +} + +int TlsTransport::transport::destroy(esp_transport_handle_t t) +{ + auto tls = static_cast(esp_transport_get_context_data(t)); + return esp_transport_destroy(tls->transport_); +} + + +esp_transport_handle_t esp_transport_batch_tls_init(esp_transport_handle_t parent, const size_t max_buffer_size) +{ + esp_transport_handle_t ssl = esp_transport_init(); + auto *tls = new TlsTransport(parent); + esp_transport_set_context_data(ssl, tls); + TlsTransport::set_func(ssl); + tls->prepare_buffer(max_buffer_size); + return ssl; +} + +int TlsTransport::preread(size_t len, int timeout_ms) +{ + while (len != read_len) { + int l = esp_transport_read(transport_, buf.data() + read_len, len - read_len, timeout_ms); + ESP_LOGD(TAG, "need %d read %d already %d", len, l, read_len); + if ((l == ERR_TCP_TRANSPORT_CONNECTION_CLOSED_BY_FIN || l == ERR_TCP_TRANSPORT_CONNECTION_TIMEOUT ) && read_len > 0) { + return read_len; + } + if (l <= 0) { + read_len = 0; + return read_len; + } + read_len += l; + } + return read_len; +} + +bool TlsTransport::prepare_buffer(size_t max_size) +{ + buf.resize(max_size); + return true; +} + +int esp_transport_batch_tls_pre_read(esp_transport_handle_t t, int len, int timeout_ms) +{ + auto tls = static_cast(esp_transport_get_context_data(t)); + return tls->preread(len, timeout_ms); +} diff --git a/components/esp_modem/test/target_ota/components/manual_ota/transport_batch_tls.hpp b/components/esp_modem/test/target_ota/components/manual_ota/transport_batch_tls.hpp new file mode 100644 index 000000000..e91b8b62e --- /dev/null +++ b/components/esp_modem/test/target_ota/components/manual_ota/transport_batch_tls.hpp @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#pragma once + +/** + * @brief Creates batch transport + * + * @param parent tcp-transport handle to the parent transport + * @param max_buffer_size maximum size of one batch + * @return created transport handle + */ +esp_transport_handle_t esp_transport_batch_tls_init(esp_transport_handle_t parent, const size_t max_buffer_size); + +/** + * @brief Performs batch read operation from the underlying transport + * + * @param t Transport handle + * @param len Batch size + * @param timeout_ms Timeout in ms + * @return true If read from the parent transport completed successfully + */ +bool esp_transport_batch_tls_pre_read(esp_transport_handle_t t, int len, int timeout_ms); diff --git a/components/esp_modem/test/target_ota/http_server.py b/components/esp_modem/test/target_ota/http_server.py new file mode 100644 index 000000000..cdea1daa5 --- /dev/null +++ b/components/esp_modem/test/target_ota/http_server.py @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 +import ssl +from http.server import HTTPServer + +from RangeHTTPServer import RangeRequestHandler + +server_address = ('0.0.0.0', 1234) +httpd = HTTPServer(server_address, RangeRequestHandler) +httpd.socket = ssl.wrap_socket(httpd.socket, + server_side=True, + certfile='srv.crt', + keyfile='srv.key', + ssl_version=ssl.PROTOCOL_TLS) +httpd.serve_forever() diff --git a/components/esp_modem/test/target_ota/main/CMakeLists.txt b/components/esp_modem/test/target_ota/main/CMakeLists.txt new file mode 100644 index 000000000..4ee863eb9 --- /dev/null +++ b/components/esp_modem/test/target_ota/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS ota_test.cpp + INCLUDE_DIRS ".") diff --git a/components/esp_modem/test/target_ota/main/Kconfig.projbuild b/components/esp_modem/test/target_ota/main/Kconfig.projbuild new file mode 100644 index 000000000..3d973fb00 --- /dev/null +++ b/components/esp_modem/test/target_ota/main/Kconfig.projbuild @@ -0,0 +1,47 @@ +menu "Test Configuration" + + choice TEST_DEVICE + prompt "Choose supported modem device (DCE)" + default TEST_DEVICE_MODEM_GENERIC + help + Select modem device connected to the ESP DTE. + + config TEST_DEVICE_MODEM_GENERIC + bool "Common modem device" + help + Generic device that could be used with most common modems (BG96, SIM76xx, A76xx). + + config TEST_DEVICE_PPPD_SERVER + bool "PPPD Server" + help + Test device is a pppd service in server mode, running on linux. + endchoice + + config TEST_MODEM_APN + string "Modem APN" + depends on TEST_DEVICE_MODEM_GENERIC + default "lpwa.vodafone.com" + help + Set APN (Access Point Name), a logical name to choose data network + + config TEST_USE_VFS_TERM + bool "Use VFS terminal" + default n + help + Demonstrate use of VFS as a communication terminal of the DTE. + VFS driver implements non-block reads, writes and selects to communicate with esp-modem, + but this implementation uses UART resource only. + + config TEST_OTA_URI + string "URI of the binary" + default "https://192.168.11.1/esp32.bin" + help + HTTPS address of the update binary. + + config BROKER_URI + string "Broker URL" + default "mqtt://test.mosquitto.org" + help + URL of an mqtt broker which this example connects to. + +endmenu diff --git a/components/esp_modem/test/target_ota/main/network_dce.hpp b/components/esp_modem/test/target_ota/main/network_dce.hpp new file mode 100644 index 000000000..db6f0058d --- /dev/null +++ b/components/esp_modem/test/target_ota/main/network_dce.hpp @@ -0,0 +1,55 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include "cxx_include/esp_modem_dte.hpp" +#include "esp_modem_config.h" +#include "cxx_include/esp_modem_api.hpp" +#include "cxx_include/esp_modem_dce_factory.hpp" +#include +#include + +class NetModule; + +/** + * @brief Custom factory which can build and create a DCE using a custom module + */ +class NetDCE_Factory: public esp_modem::dce_factory::Factory { +public: + template + static auto create(const esp_modem::dce_factory::config *cfg, Args &&... args) -> std::shared_ptr> + { + return build_generic_DCE, std::shared_ptr>>(cfg, std::forward(args)...); + } +}; + +/** + * @brief This is a null-module, doesn't define any AT commands, just passes everything to pppd + */ +class NetModule: public esp_modem::ModuleIf { +public: + explicit NetModule(std::shared_ptr dte, const esp_modem_dce_config *cfg): + dte(std::move(dte)) {} + + bool setup_data_mode() override + { + return true; + } + + bool set_mode(esp_modem::modem_mode mode) override + { + return true; + } + +private: + std::shared_ptr dte; +}; + + +std::shared_ptr> create(std::shared_ptr dte, esp_netif_t *netif) +{ + const esp_modem::dce_config config = {}; + return NetDCE_Factory::create(&config, dte, netif); +} diff --git a/components/esp_modem/test/target_ota/main/ota_test.cpp b/components/esp_modem/test/target_ota/main/ota_test.cpp new file mode 100644 index 000000000..ec0edfbb0 --- /dev/null +++ b/components/esp_modem/test/target_ota/main/ota_test.cpp @@ -0,0 +1,295 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "esp_netif.h" +#include "esp_netif_ppp.h" +#include "esp_log.h" +#include "esp_event.h" +#include "cxx_include/esp_modem_dte.hpp" +#include "esp_modem_config.h" +#include "cxx_include/esp_modem_api.hpp" +#include "esp_vfs_dev.h" // For optional VFS support +#include "vfs_resource/vfs_create.hpp" +#include "network_dce.hpp" +#include "manual_ota.hpp" +#include "mqtt_client.h" + +using namespace esp_modem; + +static const char *TAG = "ota_test"; + + +// Wrap event handlers to destruct correctly on returning from main +class StatusHandler { +public: + static constexpr auto IP_Event = SignalGroup::bit0; + static constexpr auto MQTT_Connect = SignalGroup::bit1; + static constexpr auto MQTT_Data = SignalGroup::bit2; + + StatusHandler() + { + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, on_event, this)); + } + + ~StatusHandler() + { + esp_event_handler_unregister(IP_EVENT, ESP_EVENT_ANY_ID, on_event); + } + + void handle_mqtt(esp_mqtt_client_handle_t client) + { + mqtt = client; + esp_mqtt_client_register_event(client, MQTT_EVENT_ANY, on_event, this); + } + + void remove_mqtt() + { + esp_mqtt_client_unregister_event(mqtt, MQTT_EVENT_ANY, on_event); + mqtt = nullptr; + } + + esp_err_t wait_for(decltype(IP_Event) event, int milliseconds) + { + return signal.wait_any(event, milliseconds); + } + + ip_event_t get_ip_event_type() + { + return ip_event_type; + } + +private: + static void on_event(void *arg, esp_event_base_t base, int32_t event, void *data) + { + auto *handler = static_cast(arg); + if (base == IP_EVENT) { + handler->ip_event(event, data); + } else { + handler->mqtt_event(event, data); + } + } + + void ip_event(int32_t id, void *data) + { + if (id == IP_EVENT_PPP_GOT_IP) { + auto *event = (ip_event_got_ip_t *)data; + ESP_LOGI(TAG, "IP : " IPSTR, IP2STR(&event->ip_info.ip)); + ESP_LOGI(TAG, "Netmask : " IPSTR, IP2STR(&event->ip_info.netmask)); + ESP_LOGI(TAG, "Gateway : " IPSTR, IP2STR(&event->ip_info.gw)); + signal.set(IP_Event); + } else if (id == IP_EVENT_PPP_LOST_IP) { + signal.set(IP_Event); + } + ip_event_type = static_cast(id); + } + + void mqtt_event(int32_t event, void *data) + { + if (mqtt && event == MQTT_EVENT_CONNECTED) { + signal.set(MQTT_Connect); + } else if (mqtt && event == MQTT_EVENT_DATA) { + auto event_data = static_cast(data); + ESP_LOGI(TAG, " TOPIC: %.*s", event_data->topic_len, event_data->topic); + ESP_LOGI(TAG, " DATA: %.*s", event_data->data_len, event_data->data); + signal.set(MQTT_Data); + } + } + + esp_modem::SignalGroup signal{}; + esp_mqtt_client_handle_t mqtt{}; + ip_event_t ip_event_type{}; +}; + +// Wrap MQTT operations to destroy everything on returning from main +struct PublishOnce { + esp_mqtt_client_handle_t mqtt_; + StatusHandler *events_; + + PublishOnce(StatusHandler *events) + { + esp_mqtt_client_config_t config = { }; + config.broker.address.uri = CONFIG_BROKER_URI; + mqtt_ = esp_mqtt_client_init(&config); + events_ = events; + events->handle_mqtt(mqtt_); + } + + bool Connect() + { + return esp_mqtt_client_start(mqtt_) == ESP_OK; + } + + bool SubscribePublish() + { + return esp_mqtt_client_subscribe(mqtt_, "/topic/esp-modem", 0) >= 0 && + esp_mqtt_client_publish(mqtt_, "/topic/esp-modem", "Hello modem", 0, 0, 0) >= 0; + } + + ~PublishOnce() + { + events_->remove_mqtt(); + esp_mqtt_client_destroy(mqtt_); + } +}; + + +// OTA related +static constexpr auto OTA_OK = SignalGroup::bit0; +static constexpr auto OTA_FAILED = SignalGroup::bit1; + +void ota_task(void *ctx) +{ + auto ota_done = static_cast(ctx); + manual_ota ota(CONFIG_TEST_OTA_URI); + ota.size_ = 64; + + ota.begin(); + while (true) { + if (!ota.perform()) { + break; + } + } + auto ret = ota.end(); + ota_done->set(ret ? OTA_OK : OTA_FAILED); + vTaskDelete(nullptr); +} + + +// App related +extern "C" void app_main(void) +{ + esp_log_level_set("*", ESP_LOG_INFO); + esp_log_level_set("ota_test", ESP_LOG_DEBUG); + + // Initialize system functions + ESP_ERROR_CHECK(esp_event_loop_create_default()); + ESP_ERROR_CHECK(esp_netif_init()); + + // Initialize DTE + esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG(); +#ifdef CONFIG_TEST_USE_VFS_TERM + // To code-cover the vfs layers + struct esp_modem_vfs_uart_creator uart_config = ESP_MODEM_VFS_DEFAULT_UART_CONFIG("/dev/uart/1"); + assert(vfs_create_uart(&uart_config, &dte_config.vfs_config) == true); + + auto dte = create_vfs_dte(&dte_config); + esp_vfs_dev_uart_use_driver(uart_config.uart.port_num); +#else + auto dte = create_uart_dte(&dte_config); +#endif // CONFIG_TEST_USE_VFS_TERM + assert(dte); + dte->set_error_cb([](terminal_error err) { + ESP_LOGE(TAG, "DTE reported terminal error: %d", static_cast(err)); + }); + + // Initialize PPP netif + esp_netif_config_t netif_ppp_config = ESP_NETIF_DEFAULT_PPP(); + esp_netif_t *esp_netif = esp_netif_new(&netif_ppp_config); + assert(esp_netif); + + // Initialize DCE +#ifdef CONFIG_TEST_DEVICE_PPPD_SERVER + auto dce = create(dte, esp_netif); +#else + esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG(CONFIG_TEST_MODEM_APN); + auto dce = create_generic_dce(&dce_config, dte, esp_netif); + assert(dce); +#endif + + StatusHandler handler; + +#ifndef CONFIG_TEST_DEVICE_PPPD_SERVER + if (dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_MODE) && + dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_SWAP) && + dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_DATA)) { +#else + if (dce->set_mode(esp_modem::modem_mode::DATA_MODE)) { +#endif + ESP_LOGI(TAG, "Modem has correctly entered the desired mode (CMUX/DATA/Manual CMUX)"); + } else { + ESP_LOGE(TAG, "Failed to configure multiplexed command mode... exiting"); + return; + } + + if (!handler.wait_for(StatusHandler::IP_Event, 60000)) { + ESP_LOGE(TAG, "Cannot get IP within specified timeout... exiting"); + return; + } else if (handler.get_ip_event_type() == IP_EVENT_PPP_GOT_IP) { + ESP_LOGI(TAG, "Got IP address"); + + /* When connected to network, subscribe and publish some MQTT data */ + PublishOnce publish(&handler); + if (!publish.Connect()) { + ESP_LOGE(TAG, "Failed to connect to mqtt server"); + return; + } + if (!handler.wait_for(StatusHandler::MQTT_Connect, 60000)) { + ESP_LOGE(TAG, "Cannot connect to %s within specified timeout... exiting", CONFIG_BROKER_URI); + return; + } + ESP_LOGI(TAG, "Connected"); + + if (!publish.SubscribePublish()) { + ESP_LOGE(TAG, "Failed to subscribe and publish to mqtt server"); + return; + } + + if (!handler.wait_for(StatusHandler::MQTT_Data, 60000)) { + ESP_LOGE(TAG, "Didn't receive published data within specified timeout... exiting"); + return; + } + ESP_LOGI(TAG, "Received MQTT data"); + + } else if (handler.get_ip_event_type() == IP_EVENT_PPP_LOST_IP) { + ESP_LOGE(TAG, "PPP client has lost connection... exiting"); + return; + } + + + esp_modem::SignalGroup ota_done{}; + // now stop the LCP keepalive before performing OTA + esp_netif_ppp_config_t cfg; + ESP_ERROR_CHECK(esp_netif_ppp_get_params(esp_netif, &cfg)); + cfg.ppp_lcp_echo_disabled = true; + ESP_ERROR_CHECK(esp_netif_ppp_set_params(esp_netif, &cfg)); + + // Run the OTA in a separate task to keep sending commands to the modem at the same time + xTaskCreate(ota_task, "ota_task", 8192, &ota_done, 5, nullptr); + +#ifndef CONFIG_TEST_DEVICE_PPPD_SERVER + while (true) { + std::string str; + if (dce->get_imsi(str) == esp_modem::command_result::OK) { + ESP_LOGI(TAG, "Modem IMSI number: %s", str.c_str()); + } + if (ota_done.wait_any(OTA_OK | OTA_FAILED, 100)) { + break; + } + } +#else + ota_done.wait_any(OTA_OK | OTA_FAILED, portMAX_DELAY); +#endif // CONFIG_TEST_DEVICE_PPPD_SERVER + +#ifndef CONFIG_TEST_DEVICE_PPPD_SERVER + if (dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_EXIT)) { +#else + if (dce->set_mode(esp_modem::modem_mode::COMMAND_MODE)) { +#endif + ESP_LOGI(TAG, "Modem CMUX/DATA mode exit"); + } else { + ESP_LOGE(TAG, "Failed to configure desired mode... exiting"); + return; + } + + if (ota_done.is_any(OTA_OK)) { + ESP_LOGI(TAG, "Prepare to restart system!"); + esp_restart(); + } + +} From edc3e7251f965ae58556bc2de01c0186b3fdf8ea Mon Sep 17 00:00:00 2001 From: David Cermak Date: Mon, 27 Nov 2023 20:04:04 +0100 Subject: [PATCH 2/4] fix(modem): Fixed OAT test to verify server cert and CN --- .../include/mbedtls_wrap.hpp | 1 + .../extra_tcp_transports/mbedtls_wrap.cpp | 10 + .../esp_modem/test/target_ota/README.md | 6 + .../esp_modem/test/target_ota/bin/blink.bin | Bin 0 -> 184608 bytes .../components/manual_ota/manual_ota.cpp | 236 +++++++++++------- .../components/manual_ota/manual_ota.hpp | 73 ++++-- .../manual_ota/transport_batch_tls.cpp | 35 ++- .../manual_ota/transport_batch_tls.hpp | 20 +- .../test/target_ota/main/Kconfig.projbuild | 13 + .../test/target_ota/main/ota_test.cpp | 16 +- .../esp_modem/test/target_ota/sdkconfig.ci.1 | 2 + .../esp_modem/test/target_ota/sdkconfig.ci.2 | 4 + .../esp_modem/test/target_ota/sdkconfig.ci.3 | 4 + .../test/target_ota/sdkconfig.defaults | 12 + 14 files changed, 315 insertions(+), 117 deletions(-) create mode 100644 components/esp_modem/test/target_ota/bin/blink.bin create mode 100644 components/esp_modem/test/target_ota/sdkconfig.ci.1 create mode 100644 components/esp_modem/test/target_ota/sdkconfig.ci.2 create mode 100644 components/esp_modem/test/target_ota/sdkconfig.ci.3 create mode 100644 components/esp_modem/test/target_ota/sdkconfig.defaults diff --git a/components/esp_modem/examples/modem_tcp_client/components/extra_tcp_transports/include/mbedtls_wrap.hpp b/components/esp_modem/examples/modem_tcp_client/components/extra_tcp_transports/include/mbedtls_wrap.hpp index 5590801e8..5dc1df71a 100644 --- a/components/esp_modem/examples/modem_tcp_client/components/extra_tcp_transports/include/mbedtls_wrap.hpp +++ b/components/esp_modem/examples/modem_tcp_client/components/extra_tcp_transports/include/mbedtls_wrap.hpp @@ -29,6 +29,7 @@ public: int read(unsigned char *buf, size_t len); [[nodiscard]] bool set_own_cert(const_buf crt, const_buf key); [[nodiscard]] bool set_ca_cert(const_buf crt); + bool set_hostname(const char *name); virtual int send(const unsigned char *buf, size_t len) = 0; virtual int recv(unsigned char *buf, size_t len) = 0; size_t get_available_bytes(); diff --git a/components/esp_modem/examples/modem_tcp_client/components/extra_tcp_transports/mbedtls_wrap.cpp b/components/esp_modem/examples/modem_tcp_client/components/extra_tcp_transports/mbedtls_wrap.cpp index 026f41020..eda6045fe 100644 --- a/components/esp_modem/examples/modem_tcp_client/components/extra_tcp_transports/mbedtls_wrap.cpp +++ b/components/esp_modem/examples/modem_tcp_client/components/extra_tcp_transports/mbedtls_wrap.cpp @@ -116,6 +116,16 @@ bool Tls::set_ca_cert(const_buf crt) return true; } +bool Tls::set_hostname(const char *name) +{ + int ret = mbedtls_ssl_set_hostname(&ssl_, name); + if (ret < 0) { + print_error("mbedtls_ssl_set_hostname", ret); + return false; + } + return true; +} + Tls::Tls() { mbedtls_x509_crt_init(&public_cert_); diff --git a/components/esp_modem/test/target_ota/README.md b/components/esp_modem/test/target_ota/README.md index 74b98ef4a..434fa03b1 100644 --- a/components/esp_modem/test/target_ota/README.md +++ b/components/esp_modem/test/target_ota/README.md @@ -19,3 +19,9 @@ sudo pppd /dev/ttyUSB1 115200 192.168.11.1:192.168.11.2 ms-dns 8.8.8.8 modem loc ``` * MQTT broker: Running mosquitto in the default config is enough, configuring the broker's URL to the local PPP address: `config.broker.address.uri = "mqtt://192.168.11.1";` * HTTP server: Need to support HTTP/1.1 (to support ranges). You can use the script `http_server.py` and configure the OTA endpoint as `"https://192.168.11.1:1234/esp32.bin"` + +## Potential issues + +When running this test it is expected to experience some buffer overflows or connection interruption. +The modem library should recover from these failure modes and continue and complete OTA update. +These issues are expected, since UART ISR is deliberately not placed into IRAM in the test configuration to exhibit some minor communication glitches. diff --git a/components/esp_modem/test/target_ota/bin/blink.bin b/components/esp_modem/test/target_ota/bin/blink.bin new file mode 100644 index 0000000000000000000000000000000000000000..aea9e250a080a816284bdd324ed24ff3d2d3cfe4 GIT binary patch literal 184608 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�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
    Fx{bg=H|uqcMh(<0~*X-c1QIY! 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 Z<)3kBNl8xSCZ@}hXZ21 size_) { - char *header_val = nullptr; - asprintf(&header_val, "bytes=0-%d", max_buffer_size_ - 1); - if (header_val == nullptr) { - ESP_LOGE(TAG, "Failed to allocate memory for HTTP header"); - return false; + if (!http_.set_range(0, max_buffer_size_ - 1)) { + return fail(); } - esp_http_client_set_header(http_, "Range", header_val); - free(header_val); } - esp_http_client_set_method(http_, HTTP_METHOD_GET); + esp_http_client_set_method(http_.handle_, HTTP_METHOD_GET); partition_ = esp_ota_get_next_update_partition(nullptr); if (partition_ == nullptr) { @@ -86,40 +80,42 @@ bool manual_ota::begin() bool manual_ota::perform() { if (status != state::IMAGE_CHECK && status != state::START) { - ESP_LOGE(TAG, "Invalid state"); + ESP_LOGE(TAG, "Invalid state for manual_ota::perform"); return false; } - esp_err_t err = esp_http_client_open(http_, 0); + esp_err_t err = esp_http_client_open(http_.handle_, 0); if (err != ESP_OK) { if (image_length_ == file_length_) { status = state::END; return false; } - esp_http_client_close(http_); + esp_http_client_close(http_.handle_); ESP_LOGI(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err)); if (reconnect_attempts_++ < max_reconnect_attempts_) { - if (prepare_reconnect()) { - return true; // will retry in the next iteration - } + return true; // will retry in the next iteration } - return fail_cleanup(); + return fail(); } - esp_http_client_fetch_headers(http_); + esp_http_client_fetch_headers(http_.handle_); - int batch_len = esp_transport_batch_tls_pre_read(ssl_, max_buffer_size_, timeout_ * 1000); - if (batch_len < 0) { - ESP_LOGE(TAG, "Error: Failed to pre-read plain text data!"); - return fail_cleanup(); + int batch_len = max_buffer_size_; + if (mode_ == mode::BATCH) { + batch_len = esp_transport_batch_tls_pre_read(ssl_, max_buffer_size_, timeout_ * 1000); + if (batch_len < 0) { + ESP_LOGE(TAG, "Error: Failed to pre-read plain text data!"); + fail(); + return false; + } } - int data_read = esp_http_client_read(http_, buffer_.data(), batch_len); + int data_read = esp_http_client_read(http_.handle_, buffer_.data(), batch_len); if (data_read < 0) { ESP_LOGE(TAG, "Error: SSL data read error"); - return fail_cleanup(); + return fail(); } else if (data_read > 0) { - esp_http_client_close(http_); + esp_http_client_close(http_.handle_); if (status == state::IMAGE_CHECK) { esp_app_desc_t new_app_info; @@ -146,7 +142,7 @@ bool manual_ota::perform() ESP_LOGW(TAG, "New version is the same as invalid version."); ESP_LOGW(TAG, "Previously, there was an attempt to launch the firmware with %s version, but it failed.", invalid_app_info.version); ESP_LOGW(TAG, "The firmware has been rolled back to the previous version."); - return fail_cleanup(); + return fail(); } } @@ -154,20 +150,18 @@ bool manual_ota::perform() err = esp_ota_begin(partition_, OTA_WITH_SEQUENTIAL_WRITES, &update_handle_); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ota_begin failed (%s)", esp_err_to_name(err)); - esp_ota_abort(update_handle_); - return fail_cleanup(); + return fail(); } + ota_begin = true; ESP_LOGI(TAG, "esp_ota_begin succeeded"); } else { ESP_LOGE(TAG, "Received chunk doesn't contain app descriptor"); - esp_ota_abort(update_handle_); - return fail_cleanup(); + return fail(); } } err = esp_ota_write(update_handle_, (const void *)buffer_.data(), data_read); if (err != ESP_OK) { - esp_ota_abort(update_handle_); - return fail_cleanup(); + return fail(); } file_length_ += data_read; ESP_LOGI(TAG, "Written image length %d", file_length_); @@ -178,28 +172,22 @@ bool manual_ota::perform() } if (!prepare_reconnect()) { - esp_ota_abort(update_handle_); - return fail_cleanup(); + return fail(); } } else if (data_read == 0) { if (file_length_ == 0) { // Try to handle possible HTTP redirections - int status_code = esp_http_client_get_status_code(http_); - ESP_LOGW(TAG, "Status code: %d", status_code); - err = esp_http_client_set_redirection(http_); - if (err != ESP_OK) { - ESP_LOGE(TAG, "URL redirection Failed"); - esp_ota_abort(update_handle_); - return fail_cleanup(); + if (!http_.handle_redirects()) { + return fail(); } - err = esp_http_client_open(http_, 0); + err = esp_http_client_open(http_.handle_, 0); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err)); - return fail_cleanup(); + return fail(); } - esp_http_client_fetch_headers(http_); + esp_http_client_fetch_headers(http_.handle_); } } @@ -208,26 +196,13 @@ bool manual_ota::perform() bool manual_ota::prepare_reconnect() { - esp_http_client_set_method(http_, HTTP_METHOD_GET); - char *header_val = nullptr; - if ((image_length_ - file_length_) > max_buffer_size_) { - asprintf(&header_val, "bytes=%d-%d", file_length_, (file_length_ + max_buffer_size_ - 1)); - } else { - asprintf(&header_val, "bytes=%d-", file_length_); - } - if (header_val == nullptr) { - ESP_LOGE(TAG, "Failed to allocate memory for HTTP header"); - return false; - } - esp_http_client_set_header(http_, "Range", header_val); - free(header_val); - return true; + esp_http_client_set_method(http_.handle_, HTTP_METHOD_GET); + return http_.set_range(file_length_, + (image_length_ - file_length_) > max_buffer_size_ ? (file_length_ + max_buffer_size_ - 1) : 0); } -bool manual_ota::fail_cleanup() +bool manual_ota::fail() { - esp_http_client_close(http_); - esp_http_client_cleanup(http_); status = state::FAIL; return false; } @@ -235,9 +210,9 @@ bool manual_ota::fail_cleanup() bool manual_ota::end() { if (status == state::END) { - if (!esp_http_client_is_complete_data_received(http_)) { + if (!http_.is_data_complete()) { ESP_LOGE(TAG, "Error in receiving complete file"); - return fail_cleanup(); + return fail(); } esp_err_t err = esp_ota_end(update_handle_); if (err != ESP_OK) { @@ -246,14 +221,97 @@ bool manual_ota::end() } else { ESP_LOGE(TAG, "esp_ota_end failed (%s)!", esp_err_to_name(err)); } - return fail_cleanup(); + return fail(); } + ota_begin = false; err = esp_ota_set_boot_partition(partition_); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ota_set_boot_partition failed (%s)!", esp_err_to_name(err)); - return fail_cleanup(); + return fail(); } return true; } return false; } + +manual_ota::~manual_ota() +{ + if (ota_begin) { + esp_ota_abort(update_handle_); + } +} + +bool manual_ota::http_client::handle_redirects() +{ + int status_code = esp_http_client_get_status_code(handle_); + ESP_LOGW(TAG, "Status code: %d", status_code); + esp_err_t err = esp_http_client_set_redirection(handle_); + if (err != ESP_OK) { + ESP_LOGE(TAG, "URL redirection Failed"); + return false; + } + + err = esp_http_client_open(handle_, 0); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err)); + return false; + } + esp_http_client_fetch_headers(handle_); + return true; +} + +bool manual_ota::http_client::set_range(size_t from, size_t to) +{ + char *header_val = nullptr; + if (to != 0) { + asprintf(&header_val, "bytes=%d-%d", from, to); + } else { + asprintf(&header_val, "bytes=%d-", from); + } + if (header_val == nullptr) { + ESP_LOGE(TAG, "Failed to allocate memory for HTTP header"); + return false; + } + esp_http_client_set_header(handle_, "Range", header_val); + free(header_val); + return true; +} + +bool manual_ota::http_client::is_data_complete() +{ + return esp_http_client_is_complete_data_received(handle_); +} + +manual_ota::http_client::~http_client() +{ + if (handle_) { + esp_http_client_close(handle_); + esp_http_client_cleanup(handle_); + } +} + +bool manual_ota::http_client::init() +{ + handle_ = esp_http_client_init(&config_); + return handle_ != nullptr; +} + +int64_t manual_ota::http_client::get_image_len() +{ + esp_http_client_set_method(handle_, HTTP_METHOD_HEAD); + esp_err_t err = esp_http_client_perform(handle_); + if (err == ESP_OK) { + int http_status = esp_http_client_get_status_code(handle_); + if (http_status != HttpStatus_Ok) { + ESP_LOGE(TAG, "Received incorrect http status %d", http_status); + return -1; + } + } else { + ESP_LOGE(TAG, "ESP HTTP client perform failed: %d", err); + return -1; + } + int64_t image_length = esp_http_client_get_content_length(handle_); + ESP_LOGI(TAG, "image_length = %lld", image_length); + esp_http_client_close(handle_); + return image_length; +} diff --git a/components/esp_modem/test/target_ota/components/manual_ota/manual_ota.hpp b/components/esp_modem/test/target_ota/components/manual_ota/manual_ota.hpp index ad09e748d..baa1302a9 100644 --- a/components/esp_modem/test/target_ota/components/manual_ota/manual_ota.hpp +++ b/components/esp_modem/test/target_ota/components/manual_ota/manual_ota.hpp @@ -12,23 +12,57 @@ class manual_ota { public: - enum class state { - UNDEF, - INIT, - IMAGE_CHECK, - START, - END, - FAIL, - }; + /** + * @brief Set the preferred mode + */ + enum class mode { + BATCH, /**< Read data chunk from TCP and pass it to SSL, restore session on reconnection */ + NORMAL /**< Use standard partial download, continuously passing data from TCP to mbedTLS */ + } mode_ {mode::BATCH}; + + /** + * @brief Set the OTA batch size in kB + * + * This would allocate two big buffers: + * - one for reading from TCP socket and + * - one for passing to mbedTLS for description + */ size_t size_{32}; + + /** + * @brief Set timeout in seconds + * + * This is the network timeout, so if less data than the batch size received + * the timeout (and no EOF) we should proceed with passing the data to mbedtls + */ int timeout_{2}; /** - * @brief Construct a new manual ota object - * - * @param uri URI of the binary image + * @brief Set common name of the server to verify */ - explicit manual_ota(const char *uri): uri_(uri) {} + const char *common_name_; + /** + * @brief Wrapper around the http client -- Please set the http config + */ + class http_client { + friend class manual_ota; + ~http_client(); + bool init(); + esp_http_client_handle_t handle_{nullptr}; + bool handle_redirects(); + bool set_range(size_t from, size_t to); + bool is_data_complete(); + int64_t get_image_len(); + public: + esp_http_client_config_t config_{}; /**< Configure the http connection parameters */ + } http_; + + /** + * @brief Construct a new manual ota object + */ + explicit manual_ota() {} + + ~manual_ota(); /** * @brief Start the manual OTA process @@ -53,11 +87,17 @@ public: bool end(); private: - const char *uri_{}; - esp_http_client_handle_t http_; + enum class state { + UNDEF, + INIT, + IMAGE_CHECK, + START, + END, + FAIL, + }; int64_t image_length_; size_t file_length_; - const size_t max_buffer_size_{size_ * 1024}; + size_t max_buffer_size_{size_ * 1024}; const esp_partition_t *partition_{nullptr}; state status{state::UNDEF}; std::vector buffer_{}; @@ -65,7 +105,8 @@ private: const int max_reconnect_attempts_{3}; esp_transport_handle_t ssl_; esp_ota_handle_t update_handle_{0}; + bool ota_begin; bool prepare_reconnect(); - bool fail_cleanup(); + bool fail(); }; diff --git a/components/esp_modem/test/target_ota/components/manual_ota/transport_batch_tls.cpp b/components/esp_modem/test/target_ota/components/manual_ota/transport_batch_tls.cpp index 013b4ef6b..8f9f31b91 100644 --- a/components/esp_modem/test/target_ota/components/manual_ota/transport_batch_tls.cpp +++ b/components/esp_modem/test/target_ota/components/manual_ota/transport_batch_tls.cpp @@ -12,7 +12,8 @@ class TlsTransport: public Tls { public: - explicit TlsTransport(esp_transport_handle_t parent) : Tls(), transport_(parent), read_len(0), offset(0) {} + explicit TlsTransport(esp_transport_handle_t parent): + Tls(), transport_(parent), last_timeout(0), read_len(0), offset(0) {} int send(const unsigned char *buf, size_t len) override; int recv(unsigned char *buf, size_t len) override; static bool set_func(esp_transport_handle_t tls_transport); @@ -40,11 +41,11 @@ private: esp_transport_handle_t esp_transport_tls_init(esp_transport_handle_t parent) { - esp_transport_handle_t ssl = esp_transport_init(); - auto *tls = new TlsTransport(parent); - esp_transport_set_context_data(ssl, tls); - TlsTransport::set_func(ssl); - return ssl; + esp_transport_handle_t transport_handle = esp_transport_init(); + auto *tls_context = new TlsTransport(parent); + esp_transport_set_context_data(transport_handle, tls_context); + TlsTransport::set_func(transport_handle); + return transport_handle; } int TlsTransport::send(const unsigned char *buf, size_t len) @@ -105,7 +106,7 @@ void TlsTransport::delay() int TlsTransport::transport::connect(esp_transport_handle_t t, const char *host, int port, int timeout_ms) { auto tls = static_cast(esp_transport_get_context_data(t)); - tls->init(is_server{false}, do_verify{false}); + tls->init(is_server{false}, do_verify{true}); ESP_LOGD(TAG, "TLS-connect"); auto ret = tls->connect(host, port, timeout_ms); @@ -122,7 +123,10 @@ int TlsTransport::transport::connect(esp_transport_handle_t t, const char *host, ESP_LOGI(TAG, "Failed to handshake"); return ret; } - tls->get_session(); + if (!tls->get_session()) { + // we're not able to save session, report an error and continue (next connection will be slower) + ESP_LOGW(TAG, "Failed to save session"); + } ESP_LOGI(TAG, "After handshake"); return 0; } @@ -202,6 +206,19 @@ esp_transport_handle_t esp_transport_batch_tls_init(esp_transport_handle_t paren return ssl; } +bool esp_transport_batch_set_ca_cert(esp_transport_handle_t t, const char *ca_cert, size_t cert_len = 0) +{ + auto tls = static_cast(esp_transport_get_context_data(t)); + const_buf cert((const unsigned char *)ca_cert, cert_len ? cert_len : strlen(ca_cert) + 1); + return tls->set_ca_cert(cert); +} + +bool esp_transport_batch_set_cn(esp_transport_handle_t t, const char *name) +{ + auto tls = static_cast(esp_transport_get_context_data(t)); + return tls->set_hostname(name); +} + int TlsTransport::preread(size_t len, int timeout_ms) { while (len != read_len) { @@ -225,7 +242,7 @@ bool TlsTransport::prepare_buffer(size_t max_size) return true; } -int esp_transport_batch_tls_pre_read(esp_transport_handle_t t, int len, int timeout_ms) +int esp_transport_batch_tls_pre_read(esp_transport_handle_t t, size_t len, int timeout_ms) { auto tls = static_cast(esp_transport_get_context_data(t)); return tls->preread(len, timeout_ms); diff --git a/components/esp_modem/test/target_ota/components/manual_ota/transport_batch_tls.hpp b/components/esp_modem/test/target_ota/components/manual_ota/transport_batch_tls.hpp index e91b8b62e..8528eced1 100644 --- a/components/esp_modem/test/target_ota/components/manual_ota/transport_batch_tls.hpp +++ b/components/esp_modem/test/target_ota/components/manual_ota/transport_batch_tls.hpp @@ -22,4 +22,22 @@ esp_transport_handle_t esp_transport_batch_tls_init(esp_transport_handle_t paren * @param timeout_ms Timeout in ms * @return true If read from the parent transport completed successfully */ -bool esp_transport_batch_tls_pre_read(esp_transport_handle_t t, int len, int timeout_ms); +bool esp_transport_batch_tls_pre_read(esp_transport_handle_t t, size_t len, int timeout_ms); + +/** + * @brief Set the CA Certificate to verify the server + * + * @param ca_cert Pointer to the CA Cert data + * @param cert_len CA Cert data len (set to 0 if null terminated string, i.e. PEM format) + * @return true on success + */ +bool esp_transport_batch_set_ca_cert(esp_transport_handle_t t, const char *ca_cert, size_t cert_len); + +/** + * @brief Set comman name + * @param t + * @param ca_cert + * @param cert_len + * @return + */ +bool esp_transport_batch_set_cn(esp_transport_handle_t t, const char *name); diff --git a/components/esp_modem/test/target_ota/main/Kconfig.projbuild b/components/esp_modem/test/target_ota/main/Kconfig.projbuild index 3d973fb00..3d2de8071 100644 --- a/components/esp_modem/test/target_ota/main/Kconfig.projbuild +++ b/components/esp_modem/test/target_ota/main/Kconfig.projbuild @@ -38,6 +38,19 @@ menu "Test Configuration" help HTTPS address of the update binary. + config TEST_OTA_CA_CERT + string "Server certificate" + default "---paste the server side certificate here---" + help + Insert the CA cert of the server side here. copy the base64 text between -----BEGIN CERTIFICATE----- + and -----END CERTIFICATE-----. + + config TEST_OTA_CN + string "Server common name" + default "192.168.11.1" + help + Insert the server's common name to be checked within verification of the Server side certificat + config BROKER_URI string "Broker URL" default "mqtt://test.mosquitto.org" diff --git a/components/esp_modem/test/target_ota/main/ota_test.cpp b/components/esp_modem/test/target_ota/main/ota_test.cpp index ec0edfbb0..8eeb6c3c0 100644 --- a/components/esp_modem/test/target_ota/main/ota_test.cpp +++ b/components/esp_modem/test/target_ota/main/ota_test.cpp @@ -50,7 +50,9 @@ public: void remove_mqtt() { +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) esp_mqtt_client_unregister_event(mqtt, MQTT_EVENT_ANY, on_event); +#endif mqtt = nullptr; } @@ -145,9 +147,17 @@ static constexpr auto OTA_FAILED = SignalGroup::bit1; void ota_task(void *ctx) { + static const char *ca_cert_pem = "-----BEGIN CERTIFICATE-----\n" CONFIG_TEST_OTA_CA_CERT "\n-----END CERTIFICATE-----"; auto ota_done = static_cast(ctx); - manual_ota ota(CONFIG_TEST_OTA_URI); - ota.size_ = 64; + manual_ota ota; + ota.http_.config_.url = CONFIG_TEST_OTA_URI; + ota.http_.config_.cert_pem = ca_cert_pem; + ota.size_ = 32; + ota.common_name_ = CONFIG_TEST_OTA_CN; +#ifndef CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT + // will have to use NORMAL mode, before custom transport is supported in IDF + ota.mode_ = manual_ota::mode::NORMAL; +#endif ota.begin(); while (true) { @@ -253,11 +263,13 @@ extern "C" void app_main(void) esp_modem::SignalGroup ota_done{}; +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) // now stop the LCP keepalive before performing OTA esp_netif_ppp_config_t cfg; ESP_ERROR_CHECK(esp_netif_ppp_get_params(esp_netif, &cfg)); cfg.ppp_lcp_echo_disabled = true; ESP_ERROR_CHECK(esp_netif_ppp_set_params(esp_netif, &cfg)); +#endif // Run the OTA in a separate task to keep sending commands to the modem at the same time xTaskCreate(ota_task, "ota_task", 8192, &ota_done, 5, nullptr); diff --git a/components/esp_modem/test/target_ota/sdkconfig.ci.1 b/components/esp_modem/test/target_ota/sdkconfig.ci.1 new file mode 100644 index 000000000..7dc50a589 --- /dev/null +++ b/components/esp_modem/test/target_ota/sdkconfig.ci.1 @@ -0,0 +1,2 @@ +CONFIG_TEST_DEVICE_PPPD_SERVER=y +CONFIG_ESP_MODEM_CMUX_DEFRAGMENT_PAYLOAD=n diff --git a/components/esp_modem/test/target_ota/sdkconfig.ci.2 b/components/esp_modem/test/target_ota/sdkconfig.ci.2 new file mode 100644 index 000000000..da6e474f7 --- /dev/null +++ b/components/esp_modem/test/target_ota/sdkconfig.ci.2 @@ -0,0 +1,4 @@ +CONFIG_TEST_DEVICE_MODEM_GENERIC=y +CONFIG_TEST_OTA_URI="https://raw.githubusercontent.com/espressif/esp-protocols/master/components/esp_modem/test/target_ota/bin/blink.bin" +CONFIG_TEST_OTA_CA_CERT="MIIEvjCCA6agAwIBAgIQBtjZBNVYQ0b2ii+nVCJ+xDANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0yMTA0MTQwMDAwMDBaFw0zMTA0MTMyMzU5NTlaME8xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxKTAnBgNVBAMTIERpZ2lDZXJ0IFRMUyBSU0EgU0hBMjU2IDIwMjAgQ0ExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwUuzZUdwvN1PWNvsnO3DZuUfMRNUrUpmRh8sCuxkB+Uu3Ny5CiDt3+PE0J6aqXodgojlEVbbHp9YwlHnLDQNLtKS4VbL8Xlfs7uHyiUDe5pSQWYQYE9XE0nw6Ddng9/n00tnTCJRpt8OmRDtV1F0JuJ9x8piLhMbfyOIJVNvwTRYAIuE//i+p1hJInuWraKImxW8oHzf6VGo1bDtN+I2tIJLYrVJmuzHZ9bjPvXj1hJeRPG/cUJ9WIQDgLGBAfr5yjK7tI4nhyfFK3TUqNaX3sNk+crOU6JWvHgXjkkDKa77SU+kFbnO8lwZV21reacroicgE7XQPUDTITAHk+qZ9QIDAQABo4IBgjCCAX4wEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUt2ui6qiqhIx56rTaD5iyxZV2ufQwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjB2BggrBgEFBQcBAQRqMGgwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBABggrBgEFBQcwAoY0aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNydDBCBgNVHR8EOzA5MDegNaAzhjFodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9iYWxSb290Q0EuY3JsMD0GA1UdIAQ2MDQwCwYJYIZIAYb9bAIBMAcGBWeBDAEBMAgGBmeBDAECATAIBgZngQwBAgIwCAYGZ4EMAQIDMA0GCSqGSIb3DQEBCwUAA4IBAQCAMs5eC91uWg0Kr+HWhMvAjvqFcO3aXbMM9yt1QP6FCvrzMXi3cEsaiVi6gL3zax3pfs8LulicWdSQ0/1s/dCYbbdxglvPbQtaCdB73sRD2Cqk3p5BJl+7j5nL3a7hqG+fh/50tx8bIKuxT8b1Z11dmzzp/2n3YWzW2fP9NsarA4h20ksudYbj/NhVfSbCEXffPgK2fPOre3qGNm+499iTcc+G33Mw+nur7SpZyEKEOxEXGlLzyQ4UfaJbcme6ce1XR2bFuAJKZTRei9AqPCCcUZlM51Ke92sRKw2Sfh3oius2FkOH6ipjv3U/697EA7sKPPcw7+uvTPyLNhBzPvOk" +CONFIG_TEST_OTA_CN="github.com" diff --git a/components/esp_modem/test/target_ota/sdkconfig.ci.3 b/components/esp_modem/test/target_ota/sdkconfig.ci.3 new file mode 100644 index 000000000..01d7661de --- /dev/null +++ b/components/esp_modem/test/target_ota/sdkconfig.ci.3 @@ -0,0 +1,4 @@ +CONFIG_TEST_DEVICE_PPPD_SERVER=y +CONFIG_ESP_MODEM_CMUX_DEFRAGMENT_PAYLOAD=y +CONFIG_ESP_MODEM_USE_INFLATABLE_BUFFER_IF_NEEDED=y +CONFIG_TEST_USE_VFS_TERM=y diff --git a/components/esp_modem/test/target_ota/sdkconfig.defaults b/components/esp_modem/test/target_ota/sdkconfig.defaults new file mode 100644 index 000000000..93b9c1850 --- /dev/null +++ b/components/esp_modem/test/target_ota/sdkconfig.defaults @@ -0,0 +1,12 @@ +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_PARTITION_TABLE_TWO_OTA=y +CONFIG_COMPILER_CXX_EXCEPTIONS=y +# This is not supported in IDF yet +# CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT=y +CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT=y +CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192 +CONFIG_LWIP_PPP_SUPPORT=y +CONFIG_LWIP_PPP_ENABLE_IPV6=n +CONFIG_LWIP_ENABLE_LCP_ECHO=y +CONFIG_LWIP_LCP_ECHOINTERVAL=1 +CONFIG_LWIP_LCP_MAXECHOFAILS=2 From 10bb738911ece00960dc90f30b7dfde4007e7ac0 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Tue, 28 Nov 2023 12:23:01 +0100 Subject: [PATCH 3/4] fix(modem): Added CI jobs to build all tests for all targets --- .github/workflows/modem__build-host-tests.yml | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/.github/workflows/modem__build-host-tests.yml b/.github/workflows/modem__build-host-tests.yml index d49ac0e05..ba8b6ea0e 100644 --- a/.github/workflows/modem__build-host-tests.yml +++ b/.github/workflows/modem__build-host-tests.yml @@ -8,9 +8,9 @@ on: types: [opened, synchronize, reopened, labeled] jobs: - build_esp_modem: + build_esp_modem_examples: if: contains(github.event.pull_request.labels.*.name, 'modem') || github.event_name == 'push' - name: Build + name: Build examples strategy: matrix: idf_ver: ["latest", "release-v4.2", "release-v4.3", "release-v4.4", "release-v5.0"] @@ -50,7 +50,33 @@ jobs: . ${IDF_PATH}/export.sh python -m pip install idf-build-apps cd $GITHUB_WORKSPACE/protocols - python ./ci/build_apps.py components/esp_modem/examples/${{ matrix.example }} -m components/esp_modem/examples/.build-test-rules.yml + python ./ci/build_apps.py components/esp_modem/examples/${{ matrix.example }} -m components/esp_modem/.build-test-rules.yml + + build_esp_modem_tests: + if: contains(github.event.pull_request.labels.*.name, 'modem') || github.event_name == 'push' + name: Build tests + strategy: + matrix: + idf_ver: ["release-v5.0", "release-v5.1", "latest"] + test: ["target", "target_ota"] + + runs-on: ubuntu-20.04 + container: espressif/idf:${{ matrix.idf_ver }} + steps: + - name: Checkout esp-protocols + uses: actions/checkout@v3 + with: + path: protocols + - name: Build ${{ matrix.test }} with IDF-${{ matrix.idf_ver }} + env: + EXPECTED_WARNING: ${{ matrix.warning }} + shell: bash + run: | + . ${IDF_PATH}/export.sh + python -m pip install idf-build-apps + cd $GITHUB_WORKSPACE/protocols + python ./ci/build_apps.py components/esp_modem/test/${{ matrix.test }} -m components/esp_modem/.build-test-rules.yml + host_test_esp_modem: if: contains(github.event.pull_request.labels.*.name, 'modem') || github.event_name == 'push' From d88cd6123bca6b51a7b119715938d86797ce93d5 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Tue, 28 Nov 2023 12:23:51 +0100 Subject: [PATCH 4/4] fix(modem): Removed CI jobs for IDF v4.2 --- .github/workflows/modem__build-host-tests.yml | 8 +------- components/esp_modem/{examples => }/.build-test-rules.yml | 0 2 files changed, 1 insertion(+), 7 deletions(-) rename components/esp_modem/{examples => }/.build-test-rules.yml (100%) diff --git a/.github/workflows/modem__build-host-tests.yml b/.github/workflows/modem__build-host-tests.yml index ba8b6ea0e..730092144 100644 --- a/.github/workflows/modem__build-host-tests.yml +++ b/.github/workflows/modem__build-host-tests.yml @@ -13,20 +13,14 @@ jobs: name: Build examples strategy: matrix: - idf_ver: ["latest", "release-v4.2", "release-v4.3", "release-v4.4", "release-v5.0"] + idf_ver: ["latest", "release-v4.3", "release-v4.4", "release-v5.0"] example: ["pppos_client", "modem_console", "modem_tcp_client", "ap_to_pppos", "simple_cmux_client"] exclude: - - idf_ver: "release-v4.2" - example: simple_cmux_client - - idf_ver: "release-v4.2" - example: modem_tcp_client - idf_ver: "release-v4.3" example: modem_tcp_client - idf_ver: "release-v4.4" example: modem_tcp_client include: - - idf_ver: "release-v4.2" - skip_config: usb - idf_ver: "release-v4.3" skip_config: usb - idf_ver: "release-v5.0" diff --git a/components/esp_modem/examples/.build-test-rules.yml b/components/esp_modem/.build-test-rules.yml similarity index 100% rename from components/esp_modem/examples/.build-test-rules.yml rename to components/esp_modem/.build-test-rules.yml