diff --git a/.github/workflows/modem__build-host-tests.yml b/.github/workflows/modem__build-host-tests.yml index d49ac0e05..730092144 100644 --- a/.github/workflows/modem__build-host-tests.yml +++ b/.github/workflows/modem__build-host-tests.yml @@ -8,25 +8,19 @@ 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"] + 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" @@ -50,7 +44,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' 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 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/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..434fa03b1 --- /dev/null +++ b/components/esp_modem/test/target_ota/README.md @@ -0,0 +1,27 @@ +# 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"` + +## 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 000000000..aea9e250a Binary files /dev/null and b/components/esp_modem/test/target_ota/bin/blink.bin differ 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..c78c9b4e4 --- /dev/null +++ b/components/esp_modem/test/target_ota/components/manual_ota/manual_ota.cpp @@ -0,0 +1,317 @@ +/* + * 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 for manual_ota::perform"); + return false; + } + 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; + } + + status = state::INIT; + max_buffer_size_ = size_ * 1024; + if (mode_ == mode::BATCH) { +#ifdef CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT + esp_transport_handle_t tcp = esp_transport_tcp_init(); + ssl_ = esp_transport_batch_tls_init(tcp, max_buffer_size_); + http_.config_.transport = ssl_; + if (!esp_transport_batch_set_ca_cert(ssl_, http_.config_.cert_pem, 0)) { + return fail(); + } + if (!esp_transport_batch_set_cn(ssl_, common_name_)) { + return fail(); + } +#else + ESP_LOGE(TAG, "mode::BATCH Cannot be used without CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT"); + return false; +#endif + } + + if (!http_.init()) { + return fail(); + } + + image_length_ = http_.get_image_len(); + if (image_length_ <= 0) { + return fail(); + } + + if (image_length_ > size_) { + if (!http_.set_range(0, max_buffer_size_ - 1)) { + return fail(); + } + } + esp_http_client_set_method(http_.handle_, 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 for manual_ota::perform"); + return false; + } + 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_.handle_); + ESP_LOGI(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err)); + if (reconnect_attempts_++ < max_reconnect_attempts_) { + return true; // will retry in the next iteration + } + return fail(); + } + esp_http_client_fetch_headers(http_.handle_); + + 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_.handle_, buffer_.data(), batch_len); + + if (data_read < 0) { + ESP_LOGE(TAG, "Error: SSL data read error"); + return fail(); + } else if (data_read > 0) { + esp_http_client_close(http_.handle_); + + 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(); + } + } + + 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)); + return fail(); + } + ota_begin = true; + ESP_LOGI(TAG, "esp_ota_begin succeeded"); + } else { + ESP_LOGE(TAG, "Received chunk doesn't contain app descriptor"); + return fail(); + } + } + err = esp_ota_write(update_handle_, (const void *)buffer_.data(), data_read); + if (err != ESP_OK) { + return fail(); + } + 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()) { + return fail(); + } + + } else if (data_read == 0) { + if (file_length_ == 0) { + // Try to handle possible HTTP redirections + if (!http_.handle_redirects()) { + return fail(); + } + + 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(); + } + esp_http_client_fetch_headers(http_.handle_); + } + } + + return true; +} + +bool manual_ota::prepare_reconnect() +{ + 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() +{ + status = state::FAIL; + return false; +} + +bool manual_ota::end() +{ + if (status == state::END) { + if (!http_.is_data_complete()) { + ESP_LOGE(TAG, "Error in receiving complete file"); + return fail(); + } + 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(); + } + 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(); + } + 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 new file mode 100644 index 000000000..baa1302a9 --- /dev/null +++ b/components/esp_modem/test/target_ota/components/manual_ota/manual_ota.hpp @@ -0,0 +1,112 @@ +/* + * 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: + /** + * @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 Set common name of the server to verify + */ + 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 + * + * @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: + enum class state { + UNDEF, + INIT, + IMAGE_CHECK, + START, + END, + FAIL, + }; + int64_t image_length_; + size_t file_length_; + 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 ota_begin; + + bool prepare_reconnect(); + 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 new file mode 100644 index 000000000..8f9f31b91 --- /dev/null +++ b/components/esp_modem/test/target_ota/components/manual_ota/transport_batch_tls.cpp @@ -0,0 +1,249 @@ +/* + * 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), 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); + 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 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) +{ + 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{true}); + + 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; + } + 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; +} + +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; +} + +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) { + 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, 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 new file mode 100644 index 000000000..8528eced1 --- /dev/null +++ b/components/esp_modem/test/target_ota/components/manual_ota/transport_batch_tls.hpp @@ -0,0 +1,43 @@ +/* + * 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, 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/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..3d2de8071 --- /dev/null +++ b/components/esp_modem/test/target_ota/main/Kconfig.projbuild @@ -0,0 +1,60 @@ +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 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" + 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..8eeb6c3c0 --- /dev/null +++ b/components/esp_modem/test/target_ota/main/ota_test.cpp @@ -0,0 +1,307 @@ +/* + * 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() + { +#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; + } + + 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) +{ + 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; + 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) { + 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{}; +#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); + +#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(); + } + +} 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