From 421d6128b92184d66069c10f2f973a34348e89b8 Mon Sep 17 00:00:00 2001 From: Joel Guittet Date: Tue, 18 Jul 2023 00:05:38 +0200 Subject: [PATCH] add-ons: add troubleshoot add-on --- .github/workflows/build_tests.sh | 12 +- .github/workflows/ci.yml | 9 +- add-ons/src/mender-troubleshoot.c | 1602 +++++++++++++++++++ core/src/mender-api.c | 128 ++ core/src/mender-utils.c | 96 +- esp-idf/CMakeLists.txt | 20 + esp-idf/Kconfig | 20 +- include/mender-api.h | 28 + include/mender-troubleshoot.h | 97 ++ include/mender-utils.h | 9 + include/mender-websocket.h | 114 ++ platform/net/curl/src/mender-websocket.c | 376 +++++ platform/net/esp-idf/src/mender-websocket.c | 288 ++++ tests/CMakeLists.txt | 10 + tests/src/main.c | 82 +- zephyr/CMakeLists.txt | 4 + zephyr/Kconfig | 22 +- 17 files changed, 2906 insertions(+), 11 deletions(-) create mode 100644 add-ons/src/mender-troubleshoot.c create mode 100644 include/mender-troubleshoot.h create mode 100644 include/mender-websocket.h create mode 100644 platform/net/curl/src/mender-websocket.c create mode 100644 platform/net/esp-idf/src/mender-websocket.c diff --git a/.github/workflows/build_tests.sh b/.github/workflows/build_tests.sh index b927e3b..5598291 100755 --- a/.github/workflows/build_tests.sh +++ b/.github/workflows/build_tests.sh @@ -29,19 +29,19 @@ mkdir build cd build # Build ESP-IDF use case -cmake .. -G "Unix Makefiles" -DCONFIG_MENDER_MCU_CLIENT_BOARD_TYPE="esp-idf" -DCONFIG_MENDER_MCU_CLIENT_NET_TYPE="esp-idf" -DCONFIG_MENDER_MCU_CLIENT_RTOS_TYPE="freertos" -DCONFIG_MENDER_MCU_CLIENT_TLS_TYPE="mbedtls" -DCONFIG_MENDER_CLIENT_ADD_ON_CONFIGURE=ON -DCONFIG_MENDER_CLIENT_CONFIGURE_STORAGE=ON -DCONFIG_MENDER_CLIENT_ADD_ON_INVENTORY=ON +cmake .. -G "Unix Makefiles" -DCONFIG_MENDER_MCU_CLIENT_BOARD_TYPE="esp-idf" -DCONFIG_MENDER_MCU_CLIENT_NET_TYPE="esp-idf" -DCONFIG_MENDER_MCU_CLIENT_RTOS_TYPE="freertos" -DCONFIG_MENDER_MCU_CLIENT_TLS_TYPE="mbedtls" -DCONFIG_MENDER_CLIENT_ADD_ON_CONFIGURE=ON -DCONFIG_MENDER_CLIENT_CONFIGURE_STORAGE=ON -DCONFIG_MENDER_CLIENT_ADD_ON_INVENTORY=ON -DCONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT=ON make -j$(nproc) -cmake .. -G "Unix Makefiles" -DCONFIG_MENDER_MCU_CLIENT_BOARD_TYPE="esp-idf" -DCONFIG_MENDER_MCU_CLIENT_NET_TYPE="esp-idf" -DCONFIG_MENDER_MCU_CLIENT_RTOS_TYPE="freertos" -DCONFIG_MENDER_MCU_CLIENT_TLS_TYPE="mbedtls" -DCONFIG_MENDER_CLIENT_ADD_ON_CONFIGURE=ON -DCONFIG_MENDER_CLIENT_CONFIGURE_STORAGE=OFF -DCONFIG_MENDER_CLIENT_ADD_ON_INVENTORY=ON +cmake .. -G "Unix Makefiles" -DCONFIG_MENDER_MCU_CLIENT_BOARD_TYPE="esp-idf" -DCONFIG_MENDER_MCU_CLIENT_NET_TYPE="esp-idf" -DCONFIG_MENDER_MCU_CLIENT_RTOS_TYPE="freertos" -DCONFIG_MENDER_MCU_CLIENT_TLS_TYPE="mbedtls" -DCONFIG_MENDER_CLIENT_ADD_ON_CONFIGURE=ON -DCONFIG_MENDER_CLIENT_CONFIGURE_STORAGE=OFF -DCONFIG_MENDER_CLIENT_ADD_ON_INVENTORY=ON -DCONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT=ON make -j$(nproc) # Build Zephyr use case -cmake .. -G "Unix Makefiles" -DCONFIG_MENDER_MCU_CLIENT_BOARD_TYPE="zephyr" -DCONFIG_MENDER_MCU_CLIENT_NET_TYPE="zephyr" -DCONFIG_MENDER_MCU_CLIENT_RTOS_TYPE="zephyr" -DCONFIG_MENDER_MCU_CLIENT_TLS_TYPE="mbedtls" -DCONFIG_MENDER_CLIENT_ADD_ON_CONFIGURE=ON -DCONFIG_MENDER_CLIENT_CONFIGURE_STORAGE=ON -DCONFIG_MENDER_CLIENT_ADD_ON_INVENTORY=ON +cmake .. -G "Unix Makefiles" -DCONFIG_MENDER_MCU_CLIENT_BOARD_TYPE="zephyr" -DCONFIG_MENDER_MCU_CLIENT_NET_TYPE="zephyr" -DCONFIG_MENDER_MCU_CLIENT_RTOS_TYPE="zephyr" -DCONFIG_MENDER_MCU_CLIENT_TLS_TYPE="mbedtls" -DCONFIG_MENDER_CLIENT_ADD_ON_CONFIGURE=ON -DCONFIG_MENDER_CLIENT_CONFIGURE_STORAGE=ON -DCONFIG_MENDER_CLIENT_ADD_ON_INVENTORY=ON -DCONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT=ON make -j$(nproc) -cmake .. -G "Unix Makefiles" -DCONFIG_MENDER_MCU_CLIENT_BOARD_TYPE="zephyr" -DCONFIG_MENDER_MCU_CLIENT_NET_TYPE="zephyr" -DCONFIG_MENDER_MCU_CLIENT_RTOS_TYPE="zephyr" -DCONFIG_MENDER_MCU_CLIENT_TLS_TYPE="mbedtls" -DCONFIG_MENDER_CLIENT_ADD_ON_CONFIGURE=ON -DCONFIG_MENDER_CLIENT_CONFIGURE_STORAGE=OFF -DCONFIG_MENDER_CLIENT_ADD_ON_INVENTORY=ON +cmake .. -G "Unix Makefiles" -DCONFIG_MENDER_MCU_CLIENT_BOARD_TYPE="zephyr" -DCONFIG_MENDER_MCU_CLIENT_NET_TYPE="zephyr" -DCONFIG_MENDER_MCU_CLIENT_RTOS_TYPE="zephyr" -DCONFIG_MENDER_MCU_CLIENT_TLS_TYPE="mbedtls" -DCONFIG_MENDER_CLIENT_ADD_ON_CONFIGURE=ON -DCONFIG_MENDER_CLIENT_CONFIGURE_STORAGE=OFF -DCONFIG_MENDER_CLIENT_ADD_ON_INVENTORY=ON -DCONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT=ON make -j$(nproc) # Build Posix use case -cmake .. -G "Unix Makefiles" -DCONFIG_MENDER_MCU_CLIENT_BOARD_TYPE="posix" -DCONFIG_MENDER_MCU_CLIENT_NET_TYPE="curl" -DCONFIG_MENDER_MCU_CLIENT_RTOS_TYPE="posix" -DCONFIG_MENDER_MCU_CLIENT_TLS_TYPE="mbedtls" -DCONFIG_MENDER_CLIENT_ADD_ON_CONFIGURE=ON -DCONFIG_MENDER_CLIENT_CONFIGURE_STORAGE=ON -DCONFIG_MENDER_CLIENT_ADD_ON_INVENTORY=ON +cmake .. -G "Unix Makefiles" -DCONFIG_MENDER_MCU_CLIENT_BOARD_TYPE="posix" -DCONFIG_MENDER_MCU_CLIENT_NET_TYPE="curl" -DCONFIG_MENDER_MCU_CLIENT_RTOS_TYPE="posix" -DCONFIG_MENDER_MCU_CLIENT_TLS_TYPE="mbedtls" -DCONFIG_MENDER_CLIENT_ADD_ON_CONFIGURE=ON -DCONFIG_MENDER_CLIENT_CONFIGURE_STORAGE=ON -DCONFIG_MENDER_CLIENT_ADD_ON_INVENTORY=ON -DCONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT=ON make -j$(nproc) -cmake .. -G "Unix Makefiles" -DCONFIG_MENDER_MCU_CLIENT_BOARD_TYPE="posix" -DCONFIG_MENDER_MCU_CLIENT_NET_TYPE="curl" -DCONFIG_MENDER_MCU_CLIENT_RTOS_TYPE="posix" -DCONFIG_MENDER_MCU_CLIENT_TLS_TYPE="mbedtls" -DCONFIG_MENDER_CLIENT_ADD_ON_CONFIGURE=ON -DCONFIG_MENDER_CLIENT_CONFIGURE_STORAGE=OFF -DCONFIG_MENDER_CLIENT_ADD_ON_INVENTORY=ON +cmake .. -G "Unix Makefiles" -DCONFIG_MENDER_MCU_CLIENT_BOARD_TYPE="posix" -DCONFIG_MENDER_MCU_CLIENT_NET_TYPE="curl" -DCONFIG_MENDER_MCU_CLIENT_RTOS_TYPE="posix" -DCONFIG_MENDER_MCU_CLIENT_TLS_TYPE="mbedtls" -DCONFIG_MENDER_CLIENT_ADD_ON_CONFIGURE=ON -DCONFIG_MENDER_CLIENT_CONFIGURE_STORAGE=OFF -DCONFIG_MENDER_CLIENT_ADD_ON_INVENTORY=ON -DCONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT=ON make -j$(nproc) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 258233e..d2a6d0c 100755 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,7 +67,14 @@ jobs: - name: Install libcurl run: | wget -q -O - https://github.com/curl/curl/archive/refs/tags/curl-8_1_2.tar.gz | tar xz - cd curl-curl-8_1_2 && autoreconf -fi && ./configure --with-openssl && make && sudo make install + cd curl-curl-8_1_2 && autoreconf -fi && ./configure --with-openssl --enable-websockets && make -j$(nproc) && sudo make install + - name: Install msgpack-c + run: | + wget -q -O - https://github.com/msgpack/msgpack-c/archive/refs/tags/c-6.0.0.tar.gz | tar xz + cd msgpack-c-c-6.0.0 && cmake . -DMSGPACK_BUILD_TESTS=OFF -DMSGPACK_BUILD_EXAMPLES=OFF && make -j$(nproc) && sudo make install + - name: Refresh the dynamic linker cache + run: | + sudo ldconfig - name: Run build-wrapper run: | build-wrapper-linux-x86-64 --out-dir bw_output ./.github/workflows/build_tests.sh diff --git a/add-ons/src/mender-troubleshoot.c b/add-ons/src/mender-troubleshoot.c new file mode 100644 index 0000000..c35167e --- /dev/null +++ b/add-ons/src/mender-troubleshoot.c @@ -0,0 +1,1602 @@ +/** + * @file mender-troubleshoot.c + * @brief Mender MCU Troubleshoot add-on implementation + * + * MIT License + * + * Copyright (c) 2022-2023 joelguittet and mender-mcu-client contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "mender-api.h" +#include "mender-troubleshoot.h" +#include "mender-log.h" +#include "mender-rtos.h" + +#ifdef CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT + +#include + +/** + * @brief Default troubleshoot healthcheck interval (seconds) + */ +#ifndef CONFIG_MENDER_CLIENT_TROUBLESHOOT_HEALTHCHECK_INTERVAL +#define CONFIG_MENDER_CLIENT_TROUBLESHOOT_HEALTHCHECK_INTERVAL (30) +#endif /* CONFIG_MENDER_CLIENT_TROUBLESHOOT_HEALTHCHECK_INTERVAL */ + +/** + * msgpack zone chunk initialization size + */ +#define MENDER_TROUBLESHOOT_ZONE_CHUNK_INIT_SIZE 2048 + +/** + * msgpack sbuffer initialization size + */ +#define MENDER_TROUBLESHOOT_SBUFFER_INIT_SIZE 256 + +/** + * Proto type + */ +typedef enum { + MENDER_TROUBLESHOOT_PROTO_TYPE_INVALID = 0x0000, /**< Invalid */ + MENDER_TROUBLESHOOT_PROTO_TYPE_SHELL = 0x0001, /**< Shell */ + MENDER_TROUBLESHOOT_PROTO_TYPE_FILE_TRANSFER = 0x0002, /**< File transfer */ + MENDER_TROUBLESHOOT_PROTO_TYPE_PORT_FORWARD = 0x0003, /**< Port forward */ + MENDER_TROUBLESHOOT_PROTO_TYPE_MENDER_CLIENT = 0x0004, /**< Mender client */ + MENDER_TROUBLESHOOT_PROTO_TYPE_CONTROL = 0xFFFF /**< Control */ +} mender_troubleshoot_protohdr_type_t; + +/** + * Message type + */ +#define MENDER_TROUBLESHOOT_MESSAGE_TYPE_SHELL_PING "ping" +#define MENDER_TROUBLESHOOT_MESSAGE_TYPE_SHELL_PONG "pong" +#define MENDER_TROUBLESHOOT_MESSAGE_TYPE_SHELL_RESIZE "resize" +#define MENDER_TROUBLESHOOT_MESSAGE_TYPE_SHELL_SHELL "shell" +#define MENDER_TROUBLESHOOT_MESSAGE_TYPE_SHELL_SPAWN "new" +#define MENDER_TROUBLESHOOT_MESSAGE_TYPE_SHELL_STOP "stop" + +/** + * Status type + */ +typedef enum { + MENDER_TROUBLESHOOT_STATUS_TYPE_NORMAL = 0x0001, /**< Normal message */ + MENDER_TROUBLESHOOT_STATUS_TYPE_ERROR = 0x0002, /**< Error message */ + MENDER_TROUBLESHOOT_STATUS_TYPE_CONTROL = 0x0003 /**< Control message */ +} mender_troubleshoot_properties_status_t; + +/** + * Proto message header properties + */ +typedef struct { + uint16_t * terminal_height; /**< Terminal heigth */ + uint16_t * terminal_width; /**< Terminal width */ + char * user_id; /**< User ID */ + uint32_t * timeout; /**< Timeout */ + mender_troubleshoot_properties_status_t *status; /**< Status */ +} mender_troubleshoot_protohdr_properties_t; + +/** + * Proto message header + */ +typedef struct { + mender_troubleshoot_protohdr_type_t proto; /**< Proto type */ + char * typ; /**< Message type */ + char * sid; /**< Session ID */ + mender_troubleshoot_protohdr_properties_t *properties; /**< Properties */ +} mender_troubleshoot_protohdr_t; + +/** + * Proto message + */ +typedef struct { + mender_troubleshoot_protohdr_t *protohdr; /**< Header */ + char * body; /**< Body */ +} mender_troubleshoot_protomsg_t; + +/** + * @brief Mender troubleshoot configuration + */ +static mender_troubleshoot_config_t mender_troubleshoot_config; + +/** + * @brief Mender troubleshoot callbacks + */ +static mender_troubleshoot_callbacks_t mender_troubleshoot_callbacks; + +/** + * @brief Mender troubleshoot work handle + */ +static void *mender_troubleshoot_healthcheck_work_handle = NULL; + +/** + * @brief Mender troubleshoot connection handle + */ +static void *mender_troubleshoot_handle = NULL; + +/** + * @brief Mender troubleshoot shell session ID + */ +static char *mender_troubleshoot_shell_sid = NULL; + +/** + * @brief Mender troubleshoot healthcheck work function + * @return MENDER_OK if the function succeeds, error code otherwise + */ +static mender_err_t mender_troubleshoot_healthcheck_work_function(void); + +/** + * @brief Callback function to be invoked to perform the treatment of the data from the websocket + * @param data Received data + * @param length Received data length + * @return MENDER_OK if the function succeeds, error code if an error occured + */ +static mender_err_t mender_troubleshoot_data_received_callback(void *data, size_t length); + +/** + * @brief Function called to perform the treatment of the shell messages + * @param protomsg Received proto message + * @param response Response to be sent back to the server, NULL if no response to send + * @return MENDER_OK if the function succeeds, error code if an error occured + */ +static mender_err_t mender_troubleshoot_shell_message_handler(mender_troubleshoot_protomsg_t *protomsg, mender_troubleshoot_protomsg_t **response); + +/** + * @brief Function called to send shell ping protomsg + * @return MENDER_OK if the function succeeds, error code if an error occured + */ +static mender_err_t mender_troubleshoot_send_shell_ping_protomsg(void); + +/** + * @brief Function called to send shell stop protomsg + * @return MENDER_OK if the function succeeds, error code if an error occured + */ +static mender_err_t mender_troubleshoot_send_shell_stop_protomsg(void); + +/** + * @brief Unpack and decode Proto message + * @param data Packed data to be decoded + * @param length Length of the data to be decoded + * @return Proto message if the function succeeds, NULL otherwise + */ +static mender_troubleshoot_protomsg_t *mender_troubleshoot_unpack_protomsg(void *data, size_t length); + +/** + * @brief Decode Proto message object + * @param object Proto message object + * @return Proto message if the function succeeds, NULL otherwise + */ +static mender_troubleshoot_protomsg_t *mender_troubleshoot_decode_protomsg(msgpack_object *object); + +/** + * @brief Decode Proto header object + * @param object Proto header object + * @return Proto header if the function succeeds, NULL otherwise + */ +static mender_troubleshoot_protohdr_t *mender_troubleshoot_decode_protohdr(msgpack_object *object); + +/** + * @brief Decode Proto header properties object + * @param object Proto header properties object + * @return Proto header properties if the function succeeds, NULL otherwise + */ +static mender_troubleshoot_protohdr_properties_t *mender_troubleshoot_decode_protohdr_properties(msgpack_object *object); + +/** + * @brief Decode body object + * @param object Body object + * @return Body if the function succeeds, NULL otherwise + */ +static char *mender_troubleshoot_decode_body(msgpack_object *object); + +/** + * @brief Encode and pack Proto message + * @param protomsg Proto message + * @param data Packed data encoded + * @param length Length of the data encoded + * @return MENDER_OK if the function succeeds, error code otherwise + */ +static mender_err_t mender_troubleshoot_pack_protomsg(mender_troubleshoot_protomsg_t *protomsg, void **data, size_t *length); + +/** + * @brief Encode Proto message + * @param protomsg Proto message + * @param p Object key-value + * @return MENDER_OK if the function succeeds, error code otherwise + */ +static mender_err_t mender_troubleshoot_encode_protomsg(mender_troubleshoot_protomsg_t *protomsg, msgpack_object *object); + +/** + * @brief Encode Proto header + * @param protohdr Proto header + * @param p Object key-value + * @return MENDER_OK if the function succeeds, error code otherwise + */ +static mender_err_t mender_troubleshoot_encode_protohdr(mender_troubleshoot_protohdr_t *protohdr, msgpack_object_kv *p); + +/** + * @brief Encode Proto header properties + * @param properties Proto header properties + * @param p Object key-value + * @return MENDER_OK if the function succeeds, error code otherwise + */ +static mender_err_t mender_troubleshoot_encode_protohdr_properties(mender_troubleshoot_protohdr_properties_t *properties, msgpack_object_kv *p); + +/** + * @brief Encode body + * @param body Body + * @param p Object key-value + * @return MENDER_OK if the function succeeds, error code otherwise + */ +static mender_err_t mender_troubleshoot_encode_body(char *body, msgpack_object_kv *p); + +/** + * @brief Release msgpack object + * @param object msgpack object + */ +static void mender_troubleshoot_msgpack_object_release(msgpack_object *object); + +/** + * @brief Release Proto message + * @param protomsg Proto message + */ +static void mender_troubleshoot_release_protomsg(mender_troubleshoot_protomsg_t *protomsg); + +/** + * @brief Release Proto header + * @param protohdr Proto header + */ +static void mender_troubleshoot_release_protohdr(mender_troubleshoot_protohdr_t *protohdr); + +/** + * @brief Release Proto header properties + * @param properties Proto header properties + */ +static void mender_troubleshoot_release_protohdr_properties(mender_troubleshoot_protohdr_properties_t *properties); + +mender_err_t +mender_troubleshoot_init(mender_troubleshoot_config_t *config, mender_troubleshoot_callbacks_t *callbacks) { + + assert(NULL != config); + mender_err_t ret; + + /* Save configuration */ + if (0 != config->healthcheck_interval) { + mender_troubleshoot_config.healthcheck_interval = config->healthcheck_interval; + } else { + mender_troubleshoot_config.healthcheck_interval = CONFIG_MENDER_CLIENT_TROUBLESHOOT_HEALTHCHECK_INTERVAL; + } + + /* Save callbacks */ + memcpy(&mender_troubleshoot_callbacks, callbacks, sizeof(mender_troubleshoot_callbacks_t)); + + /* Create troubleshoot healthcheck work */ + mender_rtos_work_params_t healthcheck_work_params; + healthcheck_work_params.function = mender_troubleshoot_healthcheck_work_function; + healthcheck_work_params.period = mender_troubleshoot_config.healthcheck_interval; + healthcheck_work_params.name = "mender_troubleshoot_healthcheck"; + if (MENDER_OK != (ret = mender_rtos_work_create(&healthcheck_work_params, &mender_troubleshoot_healthcheck_work_handle))) { + mender_log_error("Unable to create healthcheck work"); + return ret; + } + + return ret; +} + +mender_err_t +mender_troubleshoot_activate(void) { + + mender_err_t ret; + + /* Activate troubleshoot healthcheck work */ + if (MENDER_OK != (ret = mender_rtos_work_activate(mender_troubleshoot_healthcheck_work_handle))) { + mender_log_error("Unable to activate troubleshoot healthcheck work"); + return ret; + } + + return ret; +} + +mender_err_t +mender_troubleshoot_deactivate(void) { + + mender_err_t ret = MENDER_OK; + + /* Deactivate troubleshoot healthcheck work */ + mender_rtos_work_deactivate(mender_troubleshoot_healthcheck_work_handle); + + /* Check if a session is already opened */ + if (NULL != mender_troubleshoot_shell_sid) { + + /* Invoke shell disconnected callback */ + if (NULL != mender_troubleshoot_callbacks.shell_disconnected) { + if (MENDER_OK != (ret = mender_troubleshoot_callbacks.shell_disconnected())) { + mender_log_error("An error occured"); + } + } + } + + /* Check if connection is established */ + if (NULL != mender_troubleshoot_handle) { + + /* Check if a session is already opened */ + if (NULL != mender_troubleshoot_shell_sid) { + + /* Send stop message to the server */ + if (MENDER_OK != (ret = mender_troubleshoot_send_shell_stop_protomsg())) { + mender_log_error("Unable to send stop message to the server"); + } + } + + /* Disconnect the device of the server */ + if (MENDER_OK != (ret = mender_api_troubleshoot_disconnect(mender_troubleshoot_handle))) { + mender_log_error("Unable to disconnect the device of the server"); + } + mender_troubleshoot_handle = NULL; + } + + /* Release session ID */ + if (NULL != mender_troubleshoot_shell_sid) { + free(mender_troubleshoot_shell_sid); + mender_troubleshoot_shell_sid = NULL; + } + + return ret; +} + +mender_err_t +mender_troubleshoot_shell_print(char *body) { + + assert(NULL != body); + mender_troubleshoot_protomsg_t *protomsg = NULL; + mender_err_t ret = MENDER_OK; + void * payload = NULL; + size_t length = 0; + + /* Check if a session is already opened */ + if (NULL == mender_troubleshoot_shell_sid) { + mender_log_error("No shell session opened"); + ret = MENDER_FAIL; + goto FAIL; + } + + /* Send shell body */ + if (NULL == (protomsg = (mender_troubleshoot_protomsg_t *)malloc(sizeof(mender_troubleshoot_protomsg_t)))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + memset(protomsg, 0, sizeof(mender_troubleshoot_protomsg_t)); + if (NULL == (protomsg->protohdr = (mender_troubleshoot_protohdr_t *)malloc(sizeof(mender_troubleshoot_protohdr_t)))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + memset(protomsg->protohdr, 0, sizeof(mender_troubleshoot_protohdr_t)); + protomsg->protohdr->proto = MENDER_TROUBLESHOOT_PROTO_TYPE_SHELL; + if (NULL == (protomsg->protohdr->typ = strdup(MENDER_TROUBLESHOOT_MESSAGE_TYPE_SHELL_SHELL))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + if (NULL == (protomsg->protohdr->sid = strdup(mender_troubleshoot_shell_sid))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + if (NULL == (protomsg->protohdr->properties = (mender_troubleshoot_protohdr_properties_t *)malloc(sizeof(mender_troubleshoot_protohdr_properties_t)))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + memset(protomsg->protohdr->properties, 0, sizeof(mender_troubleshoot_protohdr_properties_t)); + if (NULL == (protomsg->protohdr->properties->status = (mender_troubleshoot_properties_status_t *)malloc(sizeof(mender_troubleshoot_properties_status_t)))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + *(protomsg->protohdr->properties->status) = MENDER_TROUBLESHOOT_STATUS_TYPE_NORMAL; + if (NULL == (protomsg->body = mender_utils_str_replace(body, "\r|\n", "\r\n"))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + + /* Encode and pack the message */ + if (MENDER_OK != (ret = mender_troubleshoot_pack_protomsg(protomsg, &payload, &length))) { + mender_log_error("Unable to encode message"); + goto FAIL; + } + + /* Send message */ + if (MENDER_OK != (ret = mender_api_troubleshoot_send(mender_troubleshoot_handle, payload, length))) { + mender_log_error("Unable to send message"); + goto FAIL; + } + +FAIL: + + /* Release memory */ + mender_troubleshoot_release_protomsg(protomsg); + if (NULL != payload) { + free(payload); + } + + return ret; +} + +mender_err_t +mender_troubleshoot_exit(void) { + + /* Delete troubleshoot healthcheck work */ + mender_rtos_work_delete(mender_troubleshoot_healthcheck_work_handle); + mender_troubleshoot_healthcheck_work_handle = NULL; + + /* Release memory */ + if (NULL != mender_troubleshoot_shell_sid) { + free(mender_troubleshoot_shell_sid); + mender_troubleshoot_shell_sid = NULL; + } + mender_troubleshoot_config.healthcheck_interval = 0; + + return MENDER_OK; +} + +static mender_err_t +mender_troubleshoot_healthcheck_work_function(void) { + + mender_err_t ret = MENDER_OK; + + /* Check if connection is established */ + if (NULL != mender_troubleshoot_handle) { + + /* Check if a session is already opened */ + if (NULL != mender_troubleshoot_shell_sid) { + + /* Send healthcheck ping message over websocket connection */ + if (MENDER_OK != (ret = mender_troubleshoot_send_shell_ping_protomsg())) { + mender_log_error("Unable to send healthcheck message to the server"); + goto FAIL; + } + } + + } else { + + /* Connect the device to the server */ + if (MENDER_OK != (ret = mender_api_troubleshoot_connect(&mender_troubleshoot_data_received_callback, &mender_troubleshoot_handle))) { + mender_log_error("Unable to connect the device to the server"); + goto END; + } + } + + goto END; + +FAIL: + + /* Check if a session is already opened */ + if (NULL != mender_troubleshoot_shell_sid) { + + /* Invoke shell disconnected callback */ + if (NULL != mender_troubleshoot_callbacks.shell_disconnected) { + if (MENDER_OK != (ret = mender_troubleshoot_callbacks.shell_disconnected())) { + mender_log_error("An error occured"); + } + } + } + + /* Check if connection is established */ + if (NULL != mender_troubleshoot_handle) { + + /* Disconnect the device of the server */ + if (MENDER_OK != (ret = mender_api_troubleshoot_disconnect(mender_troubleshoot_handle))) { + mender_log_error("Unable to disconnect the device of the server"); + } + mender_troubleshoot_handle = NULL; + } + + /* Release session ID */ + if (NULL != mender_troubleshoot_shell_sid) { + free(mender_troubleshoot_shell_sid); + mender_troubleshoot_shell_sid = NULL; + } + +END: + + return ret; +} + +static mender_err_t +mender_troubleshoot_data_received_callback(void *data, size_t length) { + + assert(NULL != data); + mender_err_t ret = MENDER_OK; + mender_troubleshoot_protomsg_t *protomsg; + mender_troubleshoot_protomsg_t *response = NULL; + void * payload = NULL; + + /* Unpack and decode message */ + if (NULL == (protomsg = mender_troubleshoot_unpack_protomsg(data, length))) { + mender_log_error("Unable to decode message"); + ret = MENDER_FAIL; + goto END; + } + + /* Verify integrity of the message */ + if (NULL == protomsg->protohdr) { + mender_log_error("Invalid message received"); + ret = MENDER_FAIL; + goto END; + } + + /* Treatment of the message depending of the proto type */ + switch (protomsg->protohdr->proto) { + case MENDER_TROUBLESHOOT_PROTO_TYPE_INVALID: + mender_log_error("Invalid message received"); + ret = MENDER_FAIL; + break; + case MENDER_TROUBLESHOOT_PROTO_TYPE_SHELL: + ret = mender_troubleshoot_shell_message_handler(protomsg, &response); + break; + case MENDER_TROUBLESHOOT_PROTO_TYPE_FILE_TRANSFER: + case MENDER_TROUBLESHOOT_PROTO_TYPE_PORT_FORWARD: + case MENDER_TROUBLESHOOT_PROTO_TYPE_MENDER_CLIENT: + case MENDER_TROUBLESHOOT_PROTO_TYPE_CONTROL: + default: + mender_log_error("Unsupported message received with proto type 0x%04x", protomsg->protohdr->proto); + ret = MENDER_FAIL; + break; + } + + /* Check if response is available */ + if (NULL != response) { + + /* Encode and pack the message */ + if (MENDER_OK != (ret = mender_troubleshoot_pack_protomsg(response, &payload, &length))) { + mender_log_error("Unable to encode response"); + goto END; + } + + /* Send response */ + if (MENDER_OK != (ret = mender_api_troubleshoot_send(mender_troubleshoot_handle, payload, length))) { + mender_log_error("Unable to send response"); + goto END; + } + } + +END: + + /* Release memory */ + mender_troubleshoot_release_protomsg(protomsg); + mender_troubleshoot_release_protomsg(response); + if (NULL != payload) { + free(payload); + } + + return ret; +} + +static mender_err_t +mender_troubleshoot_shell_message_handler(mender_troubleshoot_protomsg_t *protomsg, mender_troubleshoot_protomsg_t **response) { + + assert(NULL != protomsg); + assert(NULL != protomsg->protohdr); + mender_err_t ret = MENDER_OK; + + /* Verify integrity of the message */ + if ((NULL == protomsg->protohdr->typ) || (NULL == protomsg->protohdr->sid)) { + mender_log_error("Invalid message received"); + ret = MENDER_FAIL; + goto END; + } + + /* Treatment of the message depending of the message type */ + if (!strcmp(protomsg->protohdr->typ, MENDER_TROUBLESHOOT_MESSAGE_TYPE_SHELL_PING)) { + + /* Nothing to do */ + + } else if (!strcmp(protomsg->protohdr->typ, MENDER_TROUBLESHOOT_MESSAGE_TYPE_SHELL_PONG)) { + + /* Nothing to do */ + + } else if (!strcmp(protomsg->protohdr->typ, MENDER_TROUBLESHOOT_MESSAGE_TYPE_SHELL_RESIZE)) { + + /* Verify integrity of the message */ + if ((NULL == protomsg->protohdr->properties) || (NULL == protomsg->protohdr->properties->terminal_height) + || (NULL == protomsg->protohdr->properties->terminal_width)) { + mender_log_error("Invalid message received"); + ret = MENDER_FAIL; + goto END; + } + + /* Invoke shell resized callback */ + if (NULL != mender_troubleshoot_callbacks.shell_resized) { + if (MENDER_OK + != (ret = mender_troubleshoot_callbacks.shell_resized(*protomsg->protohdr->properties->terminal_height, + *protomsg->protohdr->properties->terminal_width))) { + mender_log_error("An error occured"); + goto FAIL; + } + } + + } else if (!strcmp(protomsg->protohdr->typ, MENDER_TROUBLESHOOT_MESSAGE_TYPE_SHELL_SHELL)) { + + /* Verify integrity of the message */ + if (NULL == protomsg->body) { + mender_log_error("Invalid message received"); + ret = MENDER_FAIL; + goto END; + } + + /* Invoke shell data callback */ + if (NULL != mender_troubleshoot_callbacks.shell_data) { + if (MENDER_OK != (ret = mender_troubleshoot_callbacks.shell_data(protomsg->body))) { + mender_log_error("An error occured"); + goto FAIL; + } + } + + } else if (!strcmp(protomsg->protohdr->typ, MENDER_TROUBLESHOOT_MESSAGE_TYPE_SHELL_SPAWN)) { + + /* Verify integrity of the message */ + if ((NULL == protomsg->protohdr->properties) || (NULL == protomsg->protohdr->properties->terminal_height) + || (NULL == protomsg->protohdr->properties->terminal_width)) { + mender_log_error("Invalid message received"); + ret = MENDER_FAIL; + goto END; + } + + /* Check is a session is already opened */ + if (NULL != mender_troubleshoot_shell_sid) { + mender_log_warning("A shell session is already opened"); + goto END; + } + + /* Start shell session */ + mender_log_info("Starting a new shell session"); + + /* Save the session ID */ + if (NULL == (mender_troubleshoot_shell_sid = strdup(protomsg->protohdr->sid))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + + /* Acknowledge the message */ + if (NULL == (*response = (mender_troubleshoot_protomsg_t *)malloc(sizeof(mender_troubleshoot_protomsg_t)))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + memset(*response, 0, sizeof(mender_troubleshoot_protomsg_t)); + if (NULL == ((*response)->protohdr = (mender_troubleshoot_protohdr_t *)malloc(sizeof(mender_troubleshoot_protohdr_t)))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + memset((*response)->protohdr, 0, sizeof(mender_troubleshoot_protohdr_t)); + (*response)->protohdr->proto = MENDER_TROUBLESHOOT_PROTO_TYPE_SHELL; + if (NULL == ((*response)->protohdr->typ = strdup(MENDER_TROUBLESHOOT_MESSAGE_TYPE_SHELL_SPAWN))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + if (NULL == ((*response)->protohdr->sid = strdup(mender_troubleshoot_shell_sid))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + if (NULL + == ((*response)->protohdr->properties = (mender_troubleshoot_protohdr_properties_t *)malloc(sizeof(mender_troubleshoot_protohdr_properties_t)))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + memset((*response)->protohdr->properties, 0, sizeof(mender_troubleshoot_protohdr_properties_t)); + if (NULL + == ((*response)->protohdr->properties->status + = (mender_troubleshoot_properties_status_t *)malloc(sizeof(mender_troubleshoot_properties_status_t)))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + *((*response)->protohdr->properties->status) = MENDER_TROUBLESHOOT_STATUS_TYPE_NORMAL; + + /* Invoke shell connected callback */ + if (NULL != mender_troubleshoot_callbacks.shell_connected) { + if (MENDER_OK + != (ret = mender_troubleshoot_callbacks.shell_connected(*protomsg->protohdr->properties->terminal_height, + *protomsg->protohdr->properties->terminal_width))) { + mender_log_error("An error occured"); + goto FAIL; + } + } + + } else if (!strcmp(protomsg->protohdr->typ, MENDER_TROUBLESHOOT_MESSAGE_TYPE_SHELL_STOP)) { + + /* Check is a session is already opened */ + if (NULL == mender_troubleshoot_shell_sid) { + mender_log_warning("No shell session opened"); + goto END; + } + + /* Stop shell session */ + mender_log_info("Stopping current shell session"); + + /* Invoke shell disconnected callback */ + if (NULL != mender_troubleshoot_callbacks.shell_disconnected) { + if (MENDER_OK != (ret = mender_troubleshoot_callbacks.shell_disconnected())) { + mender_log_error("An error occured"); + } + } + + /* Acknowledge the message */ + if (NULL == (*response = (mender_troubleshoot_protomsg_t *)malloc(sizeof(mender_troubleshoot_protomsg_t)))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + memset(*response, 0, sizeof(mender_troubleshoot_protomsg_t)); + if (NULL == ((*response)->protohdr = (mender_troubleshoot_protohdr_t *)malloc(sizeof(mender_troubleshoot_protohdr_t)))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + memset((*response)->protohdr, 0, sizeof(mender_troubleshoot_protohdr_t)); + (*response)->protohdr->proto = MENDER_TROUBLESHOOT_PROTO_TYPE_SHELL; + if (NULL == ((*response)->protohdr->typ = strdup(MENDER_TROUBLESHOOT_MESSAGE_TYPE_SHELL_STOP))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + if (NULL == ((*response)->protohdr->sid = strdup(mender_troubleshoot_shell_sid))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + if (NULL + == ((*response)->protohdr->properties = (mender_troubleshoot_protohdr_properties_t *)malloc(sizeof(mender_troubleshoot_protohdr_properties_t)))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + memset((*response)->protohdr->properties, 0, sizeof(mender_troubleshoot_protohdr_properties_t)); + if (NULL + == ((*response)->protohdr->properties->status + = (mender_troubleshoot_properties_status_t *)malloc(sizeof(mender_troubleshoot_properties_status_t)))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + *((*response)->protohdr->properties->status) = MENDER_TROUBLESHOOT_STATUS_TYPE_NORMAL; + + /* Release session ID */ + if (NULL != mender_troubleshoot_shell_sid) { + free(mender_troubleshoot_shell_sid); + mender_troubleshoot_shell_sid = NULL; + } + + } else { + + mender_log_error("Unsupported message received with message type '%s'", protomsg->protohdr->typ); + ret = MENDER_FAIL; + goto FAIL; + } + +END: + + return ret; + +FAIL: + + /* Release memory */ + mender_troubleshoot_release_protomsg(*response); + *response = NULL; + + return ret; +} + +static mender_err_t +mender_troubleshoot_send_shell_ping_protomsg(void) { + + mender_troubleshoot_protomsg_t *protomsg = NULL; + mender_err_t ret = MENDER_OK; + void * payload = NULL; + size_t length = 0; + + /* Send shell ping message */ + if (NULL == (protomsg = (mender_troubleshoot_protomsg_t *)malloc(sizeof(mender_troubleshoot_protomsg_t)))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + memset(protomsg, 0, sizeof(mender_troubleshoot_protomsg_t)); + if (NULL == (protomsg->protohdr = (mender_troubleshoot_protohdr_t *)malloc(sizeof(mender_troubleshoot_protohdr_t)))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + memset(protomsg->protohdr, 0, sizeof(mender_troubleshoot_protohdr_t)); + protomsg->protohdr->proto = MENDER_TROUBLESHOOT_PROTO_TYPE_SHELL; + if (NULL == (protomsg->protohdr->typ = strdup(MENDER_TROUBLESHOOT_MESSAGE_TYPE_SHELL_PING))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + if (NULL == (protomsg->protohdr->sid = strdup(mender_troubleshoot_shell_sid))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + if (NULL == (protomsg->protohdr->properties = (mender_troubleshoot_protohdr_properties_t *)malloc(sizeof(mender_troubleshoot_protohdr_properties_t)))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + memset(protomsg->protohdr->properties, 0, sizeof(mender_troubleshoot_protohdr_properties_t)); + if (NULL == (protomsg->protohdr->properties->timeout = (uint32_t *)malloc(sizeof(uint32_t)))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + *protomsg->protohdr->properties->timeout = 2 * CONFIG_MENDER_CLIENT_TROUBLESHOOT_HEALTHCHECK_INTERVAL; + if (NULL == (protomsg->protohdr->properties->status = (mender_troubleshoot_properties_status_t *)malloc(sizeof(mender_troubleshoot_properties_status_t)))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + *protomsg->protohdr->properties->status = MENDER_TROUBLESHOOT_STATUS_TYPE_CONTROL; + + /* Encode and pack the message */ + if (MENDER_OK != (ret = mender_troubleshoot_pack_protomsg(protomsg, &payload, &length))) { + mender_log_error("Unable to encode message"); + goto FAIL; + } + + /* Send message */ + if (MENDER_OK != (ret = mender_api_troubleshoot_send(mender_troubleshoot_handle, payload, length))) { + mender_log_error("Unable to send message"); + goto FAIL; + } + +FAIL: + + /* Release memory */ + mender_troubleshoot_release_protomsg(protomsg); + if (NULL != payload) { + free(payload); + } + + return ret; +} + +static mender_err_t +mender_troubleshoot_send_shell_stop_protomsg(void) { + + mender_troubleshoot_protomsg_t *protomsg = NULL; + mender_err_t ret = MENDER_OK; + void * payload = NULL; + size_t length = 0; + + /* Send shell stop message */ + if (NULL == (protomsg = (mender_troubleshoot_protomsg_t *)malloc(sizeof(mender_troubleshoot_protomsg_t)))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + memset(protomsg, 0, sizeof(mender_troubleshoot_protomsg_t)); + if (NULL == (protomsg->protohdr = (mender_troubleshoot_protohdr_t *)malloc(sizeof(mender_troubleshoot_protohdr_t)))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + memset(protomsg->protohdr, 0, sizeof(mender_troubleshoot_protohdr_t)); + protomsg->protohdr->proto = MENDER_TROUBLESHOOT_PROTO_TYPE_SHELL; + if (NULL == (protomsg->protohdr->typ = strdup(MENDER_TROUBLESHOOT_MESSAGE_TYPE_SHELL_STOP))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + if (NULL == (protomsg->protohdr->sid = strdup(mender_troubleshoot_shell_sid))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + if (NULL == (protomsg->protohdr->properties = (mender_troubleshoot_protohdr_properties_t *)malloc(sizeof(mender_troubleshoot_protohdr_properties_t)))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + memset(protomsg->protohdr->properties, 0, sizeof(mender_troubleshoot_protohdr_properties_t)); + if (NULL == (protomsg->protohdr->properties->status = (mender_troubleshoot_properties_status_t *)malloc(sizeof(mender_troubleshoot_properties_status_t)))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + *protomsg->protohdr->properties->status = MENDER_TROUBLESHOOT_STATUS_TYPE_ERROR; + + /* Encode and pack the message */ + if (MENDER_OK != (ret = mender_troubleshoot_pack_protomsg(protomsg, &payload, &length))) { + mender_log_error("Unable to encode message"); + goto FAIL; + } + + /* Send message */ + if (MENDER_OK != (ret = mender_api_troubleshoot_send(mender_troubleshoot_handle, payload, length))) { + mender_log_error("Unable to send message"); + goto FAIL; + } + +FAIL: + + /* Release memory */ + mender_troubleshoot_release_protomsg(protomsg); + if (NULL != payload) { + free(payload); + } + + return ret; +} + +static mender_troubleshoot_protomsg_t * +mender_troubleshoot_unpack_protomsg(void *data, size_t length) { + + assert(NULL != data); + mender_troubleshoot_protomsg_t *protomsg; + msgpack_zone zone; + msgpack_object object; + + /* Initialize msgpack zone */ + if (true != msgpack_zone_init(&zone, MENDER_TROUBLESHOOT_ZONE_CHUNK_INIT_SIZE)) { + mender_log_error("Unable to initialize msgpack zone"); + return NULL; + } + + /* Unpack the message */ + if (MSGPACK_UNPACK_SUCCESS != msgpack_unpack((const char *)data, length, NULL, &zone, &object)) { + mender_log_error("Unable to unpack the message"); + goto FAIL; + } + + /* Check object type */ + if ((MSGPACK_OBJECT_MAP != object.type) || (0 == object.via.map.size)) { + mender_log_error("Invalid protomsg object"); + goto FAIL; + } + + /* Decode protomsg */ + if (NULL == (protomsg = mender_troubleshoot_decode_protomsg(&object))) { + mender_log_error("Invalid protomsg object"); + goto FAIL; + } + + /* Release memory */ + msgpack_zone_destroy(&zone); + + return protomsg; + +FAIL: + + /* Release memory */ + msgpack_zone_destroy(&zone); + + return NULL; +} + +static mender_troubleshoot_protomsg_t * +mender_troubleshoot_decode_protomsg(msgpack_object *object) { + + assert(NULL != object); + mender_troubleshoot_protomsg_t *protomsg; + + /* Create protomsg */ + if (NULL == (protomsg = (mender_troubleshoot_protomsg_t *)malloc(sizeof(mender_troubleshoot_protomsg_t)))) { + mender_log_error("Unable to allocate memory"); + return NULL; + } + memset(protomsg, 0, sizeof(mender_troubleshoot_protomsg_t)); + + /* Parse protomsg */ + msgpack_object_kv *p = object->via.map.ptr; + do { + if ((MSGPACK_OBJECT_STR == p->key.type) && (!strncmp(p->key.via.str.ptr, "hdr", p->key.via.str.size)) && (MSGPACK_OBJECT_MAP == p->val.type) + && (0 != p->val.via.map.size)) { + if (NULL == (protomsg->protohdr = mender_troubleshoot_decode_protohdr(&p->val))) { + mender_log_error("Invalid protomsg object"); + goto FAIL; + } + } else if ((MSGPACK_OBJECT_STR == p->key.type) && (!strncmp(p->key.via.str.ptr, "body", p->key.via.str.size)) && (MSGPACK_OBJECT_BIN == p->val.type) + && (0 != p->val.via.bin.size)) { + if (NULL == (protomsg->body = mender_troubleshoot_decode_body(&p->val))) { + mender_log_error("Invalid protomsg object"); + goto FAIL; + } + } + ++p; + } while (p < object->via.map.ptr + object->via.map.size); + + return protomsg; + +FAIL: + + /* Release memory */ + mender_troubleshoot_release_protomsg(protomsg); + + return NULL; +} + +static mender_troubleshoot_protohdr_t * +mender_troubleshoot_decode_protohdr(msgpack_object *object) { + + assert(NULL != object); + mender_troubleshoot_protohdr_t *protohdr = NULL; + + /* Create protohdr */ + if (NULL == (protohdr = (mender_troubleshoot_protohdr_t *)malloc(sizeof(mender_troubleshoot_protohdr_t)))) { + mender_log_error("Unable to allocate memory"); + return NULL; + } + memset(protohdr, 0, sizeof(mender_troubleshoot_protohdr_t)); + + /* Parse protohdr */ + msgpack_object_kv *p = object->via.map.ptr; + do { + if ((MSGPACK_OBJECT_STR == p->key.type) && (!strncmp(p->key.via.str.ptr, "proto", p->key.via.str.size)) + && (MSGPACK_OBJECT_POSITIVE_INTEGER == p->val.type)) { + protohdr->proto = (mender_troubleshoot_protohdr_type_t)p->val.via.u64; + } else if ((MSGPACK_OBJECT_STR == p->key.type) && (!strncmp(p->key.via.str.ptr, "typ", p->key.via.str.size)) && (MSGPACK_OBJECT_STR == p->val.type)) { + if (NULL == (protohdr->typ = (char *)malloc(p->val.via.str.size + 1))) { + mender_log_error("Unable to allocate memory"); + goto FAIL; + } + memcpy(protohdr->typ, p->val.via.str.ptr, p->val.via.str.size); + protohdr->typ[p->val.via.str.size] = '\0'; + } else if ((MSGPACK_OBJECT_STR == p->key.type) && (!strncmp(p->key.via.str.ptr, "sid", p->key.via.str.size)) && (MSGPACK_OBJECT_STR == p->val.type)) { + if (NULL == (protohdr->sid = (char *)malloc(p->val.via.str.size + 1))) { + mender_log_error("Unable to allocate memory"); + goto FAIL; + } + memcpy(protohdr->sid, p->val.via.str.ptr, p->val.via.str.size); + protohdr->sid[p->val.via.str.size] = '\0'; + } else if ((MSGPACK_OBJECT_STR == p->key.type) && (!strncmp(p->key.via.str.ptr, "props", p->key.via.str.size)) && (MSGPACK_OBJECT_MAP == p->val.type) + && (0 != p->val.via.map.size)) { + if (NULL == (protohdr->properties = mender_troubleshoot_decode_protohdr_properties(&p->val))) { + mender_log_error("Invalid protohdr properties object"); + goto FAIL; + } + } + ++p; + } while (p < object->via.map.ptr + object->via.map.size); + + return protohdr; + +FAIL: + + /* Release memory */ + mender_troubleshoot_release_protohdr(protohdr); + + return NULL; +} + +static mender_troubleshoot_protohdr_properties_t * +mender_troubleshoot_decode_protohdr_properties(msgpack_object *object) { + + assert(NULL != object); + mender_troubleshoot_protohdr_properties_t *properties = NULL; + + /* Create protohdr properties */ + if (NULL == (properties = (mender_troubleshoot_protohdr_properties_t *)malloc(sizeof(mender_troubleshoot_protohdr_properties_t)))) { + mender_log_error("Unable to allocate memory"); + return NULL; + } + memset(properties, 0, sizeof(mender_troubleshoot_protohdr_properties_t)); + + /* Parse protohdr properties */ + msgpack_object_kv *p = object->via.map.ptr; + do { + if ((MSGPACK_OBJECT_STR == p->key.type) && (!strncmp(p->key.via.str.ptr, "terminal_height", p->key.via.str.size)) + && (MSGPACK_OBJECT_POSITIVE_INTEGER == p->val.type)) { + if (NULL == (properties->terminal_height = (uint16_t *)malloc(sizeof(uint16_t)))) { + mender_log_error("Unable to allocate memory"); + goto FAIL; + } + *properties->terminal_height = (uint16_t)p->val.via.u64; + } else if ((MSGPACK_OBJECT_STR == p->key.type) && (!strncmp(p->key.via.str.ptr, "terminal_width", p->key.via.str.size)) + && (MSGPACK_OBJECT_POSITIVE_INTEGER == p->val.type)) { + if (NULL == (properties->terminal_width = (uint16_t *)malloc(sizeof(uint16_t)))) { + mender_log_error("Unable to allocate memory"); + goto FAIL; + } + *properties->terminal_width = (uint16_t)p->val.via.u64; + } else if ((MSGPACK_OBJECT_STR == p->key.type) && (!strncmp(p->key.via.str.ptr, "user_id", p->key.via.str.size)) + && (MSGPACK_OBJECT_STR == p->val.type)) { + if (NULL == (properties->user_id = (char *)malloc(p->val.via.str.size + 1))) { + mender_log_error("Unable to allocate memory"); + goto FAIL; + } + memcpy(properties->user_id, p->val.via.str.ptr, p->val.via.str.size); + properties->user_id[p->val.via.str.size] = '\0'; + } else if ((MSGPACK_OBJECT_STR == p->key.type) && (!strncmp(p->key.via.str.ptr, "timeout", p->key.via.str.size)) + && (MSGPACK_OBJECT_POSITIVE_INTEGER == p->val.type)) { + if (NULL == (properties->timeout = (uint32_t *)malloc(sizeof(uint32_t)))) { + mender_log_error("Unable to allocate memory"); + goto FAIL; + } + *properties->timeout = (uint32_t)p->val.via.u64; + } else if ((MSGPACK_OBJECT_STR == p->key.type) && (!strncmp(p->key.via.str.ptr, "status", p->key.via.str.size)) + && (MSGPACK_OBJECT_POSITIVE_INTEGER == p->val.type)) { + if (NULL == (properties->status = (mender_troubleshoot_properties_status_t *)malloc(sizeof(mender_troubleshoot_properties_status_t)))) { + mender_log_error("Unable to allocate memory"); + goto FAIL; + } + *properties->status = (mender_troubleshoot_properties_status_t)p->val.via.u64; + } + ++p; + } while (p < object->via.map.ptr + object->via.map.size); + + return properties; + +FAIL: + + /* Release memory */ + mender_troubleshoot_release_protohdr_properties(properties); + + return NULL; +} + +static char * +mender_troubleshoot_decode_body(msgpack_object *object) { + + assert(NULL != object); + char *body; + + /* Create body */ + if (NULL == (body = (char *)malloc(object->via.bin.size + 1))) { + mender_log_error("Unable to allocate memory"); + goto FAIL; + } + memcpy(body, object->via.bin.ptr, object->via.bin.size); + body[object->via.bin.size] = '\0'; + + return body; + +FAIL: + + return NULL; +} + +static mender_err_t +mender_troubleshoot_pack_protomsg(mender_troubleshoot_protomsg_t *protomsg, void **data, size_t *length) { + + assert(NULL != protomsg); + assert(NULL != data); + assert(NULL != length); + mender_err_t ret = MENDER_OK; + msgpack_sbuffer sbuffer; + msgpack_packer packer; + msgpack_object object; + + /* Initialize msgpack sbuffer */ + msgpack_sbuffer_init(&sbuffer); + sbuffer.alloc = MENDER_TROUBLESHOOT_SBUFFER_INIT_SIZE; + if (NULL == (sbuffer.data = (char *)malloc(sbuffer.alloc))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + + /* Initialize msgpack packer */ + msgpack_packer_init(&packer, &sbuffer, msgpack_sbuffer_write); + + /* Encode protomsg */ + if (MENDER_OK != (ret = mender_troubleshoot_encode_protomsg(protomsg, &object))) { + mender_log_error("Invalid protomsg object"); + goto FAIL; + } + + /* Pack the message */ + if (0 != msgpack_pack_object(&packer, object)) { + mender_log_error("Unable to pack the message"); + ret = MENDER_FAIL; + goto FAIL; + } + + /* Return sbuffer data and size */ + *data = sbuffer.data; + *length = sbuffer.size; + + /* Release memory */ + mender_troubleshoot_msgpack_object_release(&object); + + return ret; + +FAIL: + + /* Release memory */ + msgpack_sbuffer_destroy(&sbuffer); + + return ret; +} + +static mender_err_t +mender_troubleshoot_encode_protomsg(mender_troubleshoot_protomsg_t *protomsg, msgpack_object *object) { + + assert(NULL != protomsg); + assert(NULL != object); + mender_err_t ret = MENDER_OK; + msgpack_object_kv *p; + + /* Create protomsg */ + object->type = MSGPACK_OBJECT_MAP; + if (0 == (object->via.map.size = ((NULL != protomsg->protohdr) ? 1 : 0) + ((NULL != protomsg->body) ? 1 : 0))) { + goto END; + } + if (NULL == (object->via.map.ptr = (msgpack_object_kv *)malloc(object->via.map.size * sizeof(struct msgpack_object_kv)))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto END; + } + + /* Parse protomsg */ + p = object->via.map.ptr; + if (NULL != protomsg->protohdr) { + if (MENDER_OK != (ret = mender_troubleshoot_encode_protohdr(protomsg->protohdr, p))) { + mender_log_error("Unable to encode protohdr"); + goto END; + } + ++p; + } + if (NULL != protomsg->body) { + if (MENDER_OK != (ret = mender_troubleshoot_encode_body(protomsg->body, p))) { + mender_log_error("Unable to encode body"); + goto END; + } + } + +END: + + return ret; +} + +static mender_err_t +mender_troubleshoot_encode_protohdr(mender_troubleshoot_protohdr_t *protohdr, msgpack_object_kv *p) { + + assert(NULL != protohdr); + assert(NULL != p); + mender_err_t ret = MENDER_OK; + + /* Create protohdr */ + p->key.type = MSGPACK_OBJECT_STR; + if (NULL == (p->key.via.str.ptr = strdup("hdr"))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto END; + } + p->key.via.str.size = strlen("hdr"); + p->val.type = MSGPACK_OBJECT_MAP; + if (0 == (p->val.via.map.size = 1 + ((NULL != protohdr->typ) ? 1 : 0) + ((NULL != protohdr->sid) ? 1 : 0) + ((NULL != protohdr->properties) ? 1 : 0))) { + goto END; + } + if (NULL == (p->val.via.map.ptr = (msgpack_object_kv *)malloc(p->val.via.map.size * sizeof(struct msgpack_object_kv)))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto END; + } + + /* Parse protohdr */ + p = p->val.via.map.ptr; + p->key.type = MSGPACK_OBJECT_STR; + if (NULL == (p->key.via.str.ptr = strdup("proto"))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto END; + } + p->key.via.str.size = strlen("proto"); + p->val.type = MSGPACK_OBJECT_POSITIVE_INTEGER; + p->val.via.u64 = protohdr->proto; + ++p; + if (NULL != protohdr->typ) { + p->key.type = MSGPACK_OBJECT_STR; + if (NULL == (p->key.via.str.ptr = strdup("typ"))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto END; + } + p->key.via.str.size = strlen("typ"); + p->val.type = MSGPACK_OBJECT_STR; + p->val.via.str.size = strlen(protohdr->typ); + if (NULL == (p->val.via.str.ptr = (char *)malloc(p->val.via.str.size))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto END; + } + memcpy((void *)p->val.via.str.ptr, protohdr->typ, p->val.via.str.size); + ++p; + } + if (NULL != protohdr->sid) { + p->key.type = MSGPACK_OBJECT_STR; + if (NULL == (p->key.via.str.ptr = strdup("sid"))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto END; + } + p->key.via.str.size = strlen("sid"); + p->val.type = MSGPACK_OBJECT_STR; + p->val.via.str.size = strlen(protohdr->sid); + if (NULL == (p->val.via.str.ptr = (char *)malloc(p->val.via.str.size))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto END; + } + memcpy((void *)p->val.via.str.ptr, protohdr->sid, p->val.via.str.size); + ++p; + } + if (NULL != protohdr->properties) { + if (MENDER_OK != (ret = mender_troubleshoot_encode_protohdr_properties(protohdr->properties, p))) { + mender_log_error("Unable to encode protohdr properties"); + goto END; + } + } + +END: + + return ret; +} + +static mender_err_t +mender_troubleshoot_encode_protohdr_properties(mender_troubleshoot_protohdr_properties_t *properties, msgpack_object_kv *p) { + + assert(NULL != properties); + assert(NULL != p); + mender_err_t ret = MENDER_OK; + + /* Create properties */ + p->key.type = MSGPACK_OBJECT_STR; + if (NULL == (p->key.via.str.ptr = strdup("props"))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto END; + } + p->key.via.str.size = strlen("props"); + p->val.type = MSGPACK_OBJECT_MAP; + if (0 + == (p->val.via.map.size = ((NULL != properties->terminal_height) ? 1 : 0) + ((NULL != properties->terminal_width) ? 1 : 0) + + ((NULL != properties->user_id) ? 1 : 0) + ((NULL != properties->timeout) ? 1 : 0) + + ((NULL != properties->status) ? 1 : 0))) { + goto END; + } + if (NULL == (p->val.via.map.ptr = (msgpack_object_kv *)malloc(p->val.via.map.size * sizeof(struct msgpack_object_kv)))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto END; + } + + /* Parse properties */ + p = p->val.via.map.ptr; + if (NULL != properties->terminal_height) { + p->key.type = MSGPACK_OBJECT_STR; + if (NULL == (p->key.via.str.ptr = strdup("terminal_height"))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto END; + } + p->key.via.str.size = strlen("terminal_height"); + p->val.type = MSGPACK_OBJECT_POSITIVE_INTEGER; + p->val.via.u64 = *properties->terminal_height; + ++p; + } + if (NULL != properties->terminal_width) { + p->key.type = MSGPACK_OBJECT_STR; + if (NULL == (p->key.via.str.ptr = strdup("terminal_width"))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto END; + } + p->key.via.str.size = strlen("terminal_width"); + p->val.type = MSGPACK_OBJECT_POSITIVE_INTEGER; + p->val.via.u64 = *properties->terminal_width; + ++p; + } + if (NULL != properties->user_id) { + p->key.type = MSGPACK_OBJECT_STR; + if (NULL == (p->key.via.str.ptr = strdup("user_id"))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto END; + } + p->key.via.str.size = strlen("user_id"); + p->val.type = MSGPACK_OBJECT_STR; + p->val.via.str.size = strlen(properties->user_id); + if (NULL == (p->val.via.str.ptr = (char *)malloc(p->val.via.str.size))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto END; + } + memcpy((void *)p->val.via.str.ptr, properties->user_id, p->val.via.str.size); + ++p; + } + if (NULL != properties->timeout) { + p->key.type = MSGPACK_OBJECT_STR; + if (NULL == (p->key.via.str.ptr = strdup("timeout"))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto END; + } + p->key.via.str.size = strlen("timeout"); + p->val.type = MSGPACK_OBJECT_POSITIVE_INTEGER; + p->val.via.u64 = *properties->timeout; + ++p; + } + if (NULL != properties->status) { + p->key.type = MSGPACK_OBJECT_STR; + if (NULL == (p->key.via.str.ptr = strdup("status"))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto END; + } + p->key.via.str.size = strlen("status"); + p->val.type = MSGPACK_OBJECT_POSITIVE_INTEGER; + p->val.via.u64 = *properties->status; + } + +END: + + return ret; +} + +static mender_err_t +mender_troubleshoot_encode_body(char *body, msgpack_object_kv *p) { + + assert(NULL != body); + assert(NULL != p); + mender_err_t ret = MENDER_OK; + + /* Create body */ + p->key.type = MSGPACK_OBJECT_STR; + if (NULL == (p->key.via.str.ptr = strdup("body"))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto END; + } + p->key.via.str.size = strlen("body"); + p->val.type = MSGPACK_OBJECT_BIN; + p->val.via.bin.size = strlen(body); + if (NULL == (p->val.via.bin.ptr = (char *)malloc(p->val.via.bin.size * sizeof(uint8_t)))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto END; + } + memcpy((void *)p->val.via.bin.ptr, body, p->val.via.bin.size); + +END: + + return ret; +} + +static void +mender_troubleshoot_msgpack_object_release(msgpack_object *object) { + + /* Release memory */ + if (NULL != object) { + switch (object->type) { + case MSGPACK_OBJECT_NIL: + case MSGPACK_OBJECT_BOOLEAN: + case MSGPACK_OBJECT_POSITIVE_INTEGER: + case MSGPACK_OBJECT_NEGATIVE_INTEGER: + case MSGPACK_OBJECT_FLOAT32: + case MSGPACK_OBJECT_FLOAT64: + /* Nothing to do */ + break; + case MSGPACK_OBJECT_STR: + if (NULL != object->via.str.ptr) { + free((void *)object->via.str.ptr); + } + break; + case MSGPACK_OBJECT_BIN: + if (NULL != object->via.ext.ptr) { + free((void *)object->via.bin.ptr); + } + break; + case MSGPACK_OBJECT_EXT: + if (NULL != object->via.ext.ptr) { + free((void *)object->via.ext.ptr); + } + break; + case MSGPACK_OBJECT_ARRAY: + if (NULL != object->via.array.ptr) { + msgpack_object *p = object->via.array.ptr; + do { + mender_troubleshoot_msgpack_object_release(p); + ++p; + } while (p < object->via.array.ptr + object->via.array.size); + free(object->via.array.ptr); + } + break; + case MSGPACK_OBJECT_MAP: + if (NULL != object->via.map.ptr) { + msgpack_object_kv *p = object->via.map.ptr; + do { + mender_troubleshoot_msgpack_object_release(&(p->key)); + mender_troubleshoot_msgpack_object_release(&(p->val)); + ++p; + } while (p < object->via.map.ptr + object->via.map.size); + free(object->via.map.ptr); + } + break; + default: + /* Should not occur */ + break; + } + } +} + +static void +mender_troubleshoot_release_protomsg(mender_troubleshoot_protomsg_t *protomsg) { + + /* Release memory */ + if (NULL != protomsg) { + mender_troubleshoot_release_protohdr(protomsg->protohdr); + if (NULL != protomsg->body) { + free(protomsg->body); + } + free(protomsg); + } +} + +static void +mender_troubleshoot_release_protohdr(mender_troubleshoot_protohdr_t *protohdr) { + + /* Release memory */ + if (NULL != protohdr) { + if (NULL != protohdr->typ) { + free(protohdr->typ); + } + if (NULL != protohdr->sid) { + free(protohdr->sid); + } + mender_troubleshoot_release_protohdr_properties(protohdr->properties); + free(protohdr); + } +} + +static void +mender_troubleshoot_release_protohdr_properties(mender_troubleshoot_protohdr_properties_t *properties) { + + /* Release memory */ + if (NULL != properties) { + if (NULL != properties->terminal_height) { + free(properties->terminal_height); + } + if (NULL != properties->terminal_width) { + free(properties->terminal_width); + } + if (NULL != properties->user_id) { + free(properties->user_id); + } + if (NULL != properties->timeout) { + free(properties->timeout); + } + if (NULL != properties->status) { + free(properties->status); + } + free(properties); + } +} + +#endif /* CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT */ diff --git a/core/src/mender-api.c b/core/src/mender-api.c index e8bea69..f85446b 100644 --- a/core/src/mender-api.c +++ b/core/src/mender-api.c @@ -30,6 +30,9 @@ #include "mender-http.h" #include "mender-log.h" #include "mender-tls.h" +#ifdef CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT +#include "mender-websocket.h" +#endif /** * @brief Paths of the mender-server APIs @@ -39,6 +42,7 @@ #define MENDER_API_PATH_PUT_DEPLOYMENT_STATUS "/api/devices/v1/deployments/device/deployments/%s/status" #define MENDER_API_PATH_GET_DEVICE_CONFIGURATION "/api/devices/v1/deviceconfig/configuration" #define MENDER_API_PATH_PUT_DEVICE_CONFIGURATION "/api/devices/v1/deviceconfig/configuration" +#define MENDER_API_PATH_GET_DEVICE_CONNECT "/api/devices/v1/deviceconnect/connect" #define MENDER_API_PATH_PUT_DEVICE_ATTRIBUTES "/api/devices/v1/inventory/device/attributes" /** @@ -71,6 +75,20 @@ static mender_err_t mender_api_http_text_callback(mender_http_client_event_t eve */ static mender_err_t mender_api_http_artifact_callback(mender_http_client_event_t event, void *data, size_t data_length, void *params); +#ifdef CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT + +/** + * @brief Websocket callback used to handle websocket data + * @param event Websocket client event + * @param data Data received + * @param data_length Data length + * @param params Callback parameters + * @return MENDER_OK if the function succeeds, error code otherwise + */ +static mender_err_t mender_api_websocket_callback(mender_websocket_client_event_t event, void *data, size_t data_length, void *params); + +#endif /* CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT */ + /** * @brief Print response error * @param response HTTP response, NULL if not available @@ -97,6 +115,13 @@ mender_api_init(mender_api_config_t *config) { mender_log_error("Unable to initialize HTTP"); return ret; } +#ifdef CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT + mender_websocket_config_t mender_websocket_config = { .host = mender_api_config.host }; + if (MENDER_OK != (ret = mender_websocket_init(&mender_websocket_config))) { + mender_log_error("Unable to initialize websocket"); + return ret; + } +#endif /* CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT */ return ret; } @@ -501,6 +526,58 @@ mender_api_publish_configuration_data(mender_keystore_t *configuration) { #endif /* CONFIG_MENDER_CLIENT_ADD_ON_CONFIGURE */ +#ifdef CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT + +mender_err_t +mender_api_troubleshoot_connect(mender_err_t (*callback)(void *, size_t), void **handle) { + + mender_err_t ret; + + /* Open websocket connection */ + if (MENDER_OK != (ret = mender_websocket_connect(mender_api_jwt, MENDER_API_PATH_GET_DEVICE_CONNECT, &mender_api_websocket_callback, callback, handle))) { + mender_log_error("Unable to open websocket connection"); + goto END; + } + +END: + + return ret; +} + +mender_err_t +mender_api_troubleshoot_send(void *handle, void *payload, size_t length) { + + mender_err_t ret; + + /* Send data over websocket connection */ + if (MENDER_OK != (ret = mender_websocket_send(handle, payload, length))) { + mender_log_error("Unable to send data over websocket connection"); + goto END; + } + +END: + + return ret; +} + +mender_err_t +mender_api_troubleshoot_disconnect(void *handle) { + + mender_err_t ret; + + /* Close websocket connection */ + if (MENDER_OK != (ret = mender_websocket_disconnect(handle))) { + mender_log_error("Unable to close websocket connection"); + goto END; + } + +END: + + return ret; +} + +#endif /* CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT */ + #ifdef CONFIG_MENDER_CLIENT_ADD_ON_INVENTORY mender_err_t @@ -601,6 +678,9 @@ mender_err_t mender_api_exit(void) { /* Release all modules */ +#ifdef CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT + mender_websocket_exit(); +#endif /* CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT */ mender_http_exit(); /* Release memory */ @@ -720,6 +800,54 @@ mender_api_http_artifact_callback(mender_http_client_event_t event, void *data, return ret; } +#ifdef CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT + +static mender_err_t +mender_api_websocket_callback(mender_websocket_client_event_t event, void *data, size_t data_length, void *params) { + + assert(NULL != params); + mender_err_t (*callback)(void *, size_t) = params; + mender_err_t ret = MENDER_OK; + + /* Treatment depending of the event */ + switch (event) { + case MENDER_WEBSOCKET_EVENT_CONNECTED: + /* Nothing to do */ + mender_log_info("Websocket client connected"); + break; + case MENDER_WEBSOCKET_EVENT_DATA_RECEIVED: + /* Check input data */ + if ((NULL == data) || (0 == data_length)) { + mender_log_error("Invalid data received"); + ret = MENDER_FAIL; + break; + } + /* Process input data */ + if (MENDER_OK != (ret = callback(data, data_length))) { + mender_log_error("Unable to process data"); + break; + } + break; + case MENDER_WEBSOCKET_EVENT_DISCONNECTED: + /* Nothing to do */ + mender_log_info("Websocket client disconnected"); + break; + case MENDER_WEBSOCKET_EVENT_ERROR: + /* Websocket conection fails */ + mender_log_error("An error occurred"); + ret = MENDER_FAIL; + break; + default: + /* Should not occur */ + ret = MENDER_FAIL; + break; + } + + return ret; +} + +#endif /* CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT */ + static void mender_api_print_response_error(char *response, int status) { diff --git a/core/src/mender-utils.c b/core/src/mender-utils.c index 2f9e72a..f997c9f 100755 --- a/core/src/mender-utils.c +++ b/core/src/mender-utils.c @@ -25,6 +25,8 @@ * SOFTWARE. */ +#include +#include #include "mender-log.h" char * @@ -141,6 +143,98 @@ mender_utils_strendwith(const char *s1, const char *s2) { return (0 == strncmp(s1 + strlen(s1) - strlen(s2), s2, strlen(s2))); } +char * +mender_utils_str_replace(char *input, char *search, char *replace) { + + assert(NULL != input); + assert(NULL != search); + assert(NULL != replace); + + regex_t regex; + regmatch_t match; + char * str = input; + char * output = NULL; + size_t index = 0; + int previous_match_finish = 0; + + /* Compile expression */ + if (0 != regcomp(®ex, search, REG_EXTENDED)) { + /* Unable to compile expression */ + mender_log_error("Unable to compile expression '%s'", search); + return NULL; + } + + /* Loop until all search string are replaced */ + bool loop = true; + while (true == loop) { + + /* Search wanted string */ + if (0 != regexec(®ex, str, 1, &match, 0)) { + /* No more string to be replaced */ + loop = false; + } else { + if (match.rm_so != -1) { + + /* Beginning and ending offset of the match */ + int current_match_start = (int)(match.rm_so + (str - input)); + int current_match_finish = (int)(match.rm_eo + (str - input)); + + /* Reallocate output memory */ + char *tmp = (char *)realloc(output, index + (current_match_start - previous_match_finish) + 1); + if (NULL == tmp) { + mender_log_error("Unable to allocate memory"); + regfree(®ex); + free(output); + return NULL; + } + output = tmp; + + /* Copy string from previous match to the beginning of the current match */ + memcpy(&output[index], &input[previous_match_finish], current_match_start - previous_match_finish); + index += (current_match_start - previous_match_finish); + output[index] = 0; + + /* Reallocate output memory */ + if (NULL == (tmp = (char *)realloc(output, index + strlen(replace) + 1))) { + mender_log_error(NULL, "Unable to allocate memory"); + regfree(®ex); + free(output); + return NULL; + } + output = tmp; + + /* Copy replace string to the output */ + strcat(output, replace); + index += strlen(replace); + + /* Update previous match ending value */ + previous_match_finish = current_match_finish; + } + str += match.rm_eo; + } + } + + /* Reallocate output memory */ + char *tmp = (char *)realloc(output, index + (strlen(input) - previous_match_finish) + 1); + if (NULL == tmp) { + mender_log_error(NULL, "Unable to allocate memory"); + regfree(®ex); + free(output); + return NULL; + } + output = tmp; + + /* Copy the end of the string after the latest match */ + memcpy(&output[index], &input[previous_match_finish], strlen(input) - previous_match_finish); + index += (strlen(input) - previous_match_finish); + output[index] = 0; + + /* Release regex */ + regfree(®ex); + + return output; +} + char * mender_utils_deployment_status_to_string(mender_deployment_status_t deployment_status) { @@ -357,4 +451,4 @@ mender_utils_keystore_delete(mender_keystore_t *keystore) { } return MENDER_OK; -} \ No newline at end of file +} diff --git a/esp-idf/CMakeLists.txt b/esp-idf/CMakeLists.txt index 1851b90..1209c52 100755 --- a/esp-idf/CMakeLists.txt +++ b/esp-idf/CMakeLists.txt @@ -46,10 +46,30 @@ if(CONFIG_MENDER_CLIENT_ADD_ON_INVENTORY) "${CMAKE_CURRENT_LIST_DIR}/../add-ons/src/mender-inventory.c" ) endif() +if(CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT) + list(APPEND srcs + "${CMAKE_CURRENT_LIST_DIR}/../add-ons/src/mender-troubleshoot.c" + "${CMAKE_CURRENT_LIST_DIR}/../platform/net/esp-idf/src/mender-websocket.c" + ) +endif() + +# List of private requires +idf_build_get_property(__hack_component_targets __COMPONENT_TARGETS) +if("esp_websocket_client" IN_LIST BUILD_COMPONENTS OR "___idf_esp_websocket_client" IN_LIST __hack_component_targets OR "___idf_espressif__esp_websocket_client" IN_LIST __hack_component_targets) + list(APPEND priv + esp_websocket_client + ) +endif() +if("msgpack-c" IN_LIST BUILD_COMPONENTS OR "___idf_msgpack-c" IN_LIST __hack_component_targets OR "___idf_espressif__msgpack-c" IN_LIST __hack_component_targets) + list(APPEND priv + msgpack-c + ) +endif() # Register mender-mcu-client component idf_component_register( SRCS ${srcs} INCLUDE_DIRS "${CMAKE_CURRENT_LIST_DIR}/../include" REQUIRES app_update esp_http_client json mbedtls nvs_flash + PRIV_REQUIRES ${priv} ) diff --git a/esp-idf/Kconfig b/esp-idf/Kconfig index 4566a2d..260c5a4 100755 --- a/esp-idf/Kconfig +++ b/esp-idf/Kconfig @@ -78,7 +78,7 @@ menu "Mender client Configuration" default y help Inventory add-on permits to send inventory key-value pairs to the Mender server. - It is particurlarly used to send artifact name and device type, and it permits to see the last check-in time of the device. + It is particularly used to send artifact name and device type, and it permits to see the last check-in time of the device. if MENDER_CLIENT_ADD_ON_INVENTORY @@ -91,6 +91,24 @@ menu "Mender client Configuration" endif + config MENDER_CLIENT_ADD_ON_TROUBLESHOOT + bool "Mender client Troubleshoot" + default n + help + Troubleshoot add-on permits to perform debugging on the target from the Mender server. + It is particularly used to connect to the remote terminal of the device. + + if MENDER_CLIENT_ADD_ON_TROUBLESHOOT + + config MENDER_CLIENT_TROUBLESHOOT_HEALTHCHECK_INTERVAL + int "Mender client Troubleshoot healthcheck interval (seconds)" + range 0 60 + default 30 + help + Interval used to periodically perform healthcheck with the Mender server. + + endif + endmenu menu "Mender advanced Configuration" diff --git a/include/mender-api.h b/include/mender-api.h index 2fec248..3362b83 100755 --- a/include/mender-api.h +++ b/include/mender-api.h @@ -108,6 +108,34 @@ mender_err_t mender_api_publish_configuration_data(mender_keystore_t *configurat #endif /* CONFIG_MENDER_CLIENT_ADD_ON_CONFIGURE */ +#ifdef CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT + +/** + * @brief Connect the device and make it available to the server + * @param callback Callback function to be invoked to perform the treatment of the data from the websocket + * @param handle Connection handle + * @return MENDER_OK if the function succeeds, error code otherwise + */ +mender_err_t mender_api_troubleshoot_connect(mender_err_t (*callback)(void *, size_t), void **handle); + +/** + * @brief Send binary data to the server + * @param handle Connection handle + * @param payload Payload to send + * @param length Length of the payload + * @return MENDER_OK if the function succeeds, error code otherwise + */ +mender_err_t mender_api_troubleshoot_send(void *handle, void *payload, size_t length); + +/** + * @brief Disconnect the device + * @param handle Connection handle + * @return MENDER_OK if the function succeeds, error code otherwise + */ +mender_err_t mender_api_troubleshoot_disconnect(void *handle); + +#endif /* CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT */ + #ifdef CONFIG_MENDER_CLIENT_ADD_ON_INVENTORY /** diff --git a/include/mender-troubleshoot.h b/include/mender-troubleshoot.h new file mode 100644 index 0000000..f5c97e1 --- /dev/null +++ b/include/mender-troubleshoot.h @@ -0,0 +1,97 @@ +/** + * @file mender-troubleshoot.h + * @brief Mender MCU Troubleshoot add-on implementation + * + * MIT License + * + * Copyright (c) 2022-2023 joelguittet and mender-mcu-client contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef __MENDER_TROUBLESHOOT_H__ +#define __MENDER_TROUBLESHOOT_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#ifdef CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT + +#include "mender-utils.h" + +/** + * @brief Mender troubleshoot configuration + */ +typedef struct { + uint32_t healthcheck_interval; /**< Troubleshoot healthcheck interval, default is 30 seconds */ +} mender_troubleshoot_config_t; + +/** + * @brief Mender troubleshoot callbacks + */ +typedef struct { + mender_err_t (*shell_connected)(uint16_t, uint16_t); /**< Invoked when shell is connected */ + mender_err_t (*shell_resized)(uint16_t, uint16_t); /**< Invoked when shell is resized */ + mender_err_t (*shell_data)(char *); /**< Invoked when shell data is received */ + mender_err_t (*shell_disconnected)(void); /**< Invoked when shell is disconnected */ +} mender_troubleshoot_callbacks_t; + +/** + * @brief Initialize mender troubleshoot add-on + * @param config Mender troubleshoot configuration + * @param callbacks Mender troubleshoot callbacks + * @return MENDER_OK if the function succeeds, error code otherwise + */ +mender_err_t mender_troubleshoot_init(mender_troubleshoot_config_t *config, mender_troubleshoot_callbacks_t *callbacks); + +/** + * @brief Activate mender troubleshoot add-on + * @note This function connects the device to the server + * @return MENDER_OK if the function succeeds, error code otherwise + */ +mender_err_t mender_troubleshoot_activate(void); + +/** + * @brief Deactivate mender troubleshoot add-on + * @note This function disconnects the device of the server + * @return MENDER_OK if the function succeeds, error code otherwise + */ +mender_err_t mender_troubleshoot_deactivate(void); + +/** + * @brief Send shell data to the server + * @param body Data to send to the server for printing in the console + * @return MENDER_OK if the function succeeds, error code otherwise + */ +mender_err_t mender_troubleshoot_shell_print(char *body); + +/** + * @brief Release mender troubleshoot add-on + * @return MENDER_OK if the function succeeds, error code otherwise + */ +mender_err_t mender_troubleshoot_exit(void); + +#endif /* CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __MENDER_TROUBLESHOOT_H__ */ diff --git a/include/mender-utils.h b/include/mender-utils.h index bde60b4..9b0db08 100755 --- a/include/mender-utils.h +++ b/include/mender-utils.h @@ -114,6 +114,15 @@ bool mender_utils_strbeginwith(const char *s1, const char *s2); */ bool mender_utils_strendwith(const char *s1, const char *s2); +/** + * @brief Function used to replace a string in the input buffer + * @param input Input buffer + * @param search String to be replaced or regex expression + * @param replace Replacement string + * @return New string with replacements if the function succeeds, NULL otherwise + */ +char *mender_utils_str_replace(char *input, char *search, char *replace); + /** * @brief Function used to create a key-store * @param length Length of the key-store diff --git a/include/mender-websocket.h b/include/mender-websocket.h new file mode 100644 index 0000000..9429b72 --- /dev/null +++ b/include/mender-websocket.h @@ -0,0 +1,114 @@ +/** + * @file mender-websocket.h + * @brief Mender websocket interface + * + * MIT License + * + * Copyright (c) 2022-2023 joelguittet and mender-mcu-client contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef __MENDER_WEBSOCKET_H__ +#define __MENDER_WEBSOCKET_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#ifdef CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT + +#include "mender-utils.h" + +/** + * @brief Mender websocket configuration + */ +typedef struct { + char *host; /**< URL of the mender server */ +} mender_websocket_config_t; + +/** + * @brief Websocket opcodes + */ +typedef enum { + MENDER_WEBSOCKET_OPCODE_CONTINUATION_FRAME = 0, /**< Continuation frame */ + MENDER_WEBSOCKET_OPCODE_TEXT_FRAME = 1, /**< Text frame */ + MENDER_WEBSOCKET_OPCODE_BINARY_FRAME = 2, /**< Binary frame */ + MENDER_WEBSOCKET_OPCODE_CLOSE_CONNECTION = 8, /**< Request to close the connection */ + MENDER_WEBSOCKET_OPCODE_PING_FRAME = 9, /**< Ping frame */ + MENDER_WEBSOCKET_OPCODE_PONG_FRAME = 10 /**< Pong frame */ +} mender_websocket_opcode_t; + +/** + * @brief Websocket client events + */ +typedef enum { + MENDER_WEBSOCKET_EVENT_CONNECTED, /**< Connected to the server */ + MENDER_WEBSOCKET_EVENT_DATA_RECEIVED, /**< Data received from the server */ + MENDER_WEBSOCKET_EVENT_DISCONNECTED, /**< Disconnected from the server */ + MENDER_WEBSOCKET_EVENT_ERROR /**< An error occurred */ +} mender_websocket_client_event_t; +/** + * @brief Initialize mender websocket + * @param config Mender websocket configuration + * @return MENDER_OK if the function succeeds, error code otherwise + */ +mender_err_t mender_websocket_init(mender_websocket_config_t *config); + +/** + * @brief Perform HTTP request and upgrade connection to websocket + * @param jwt Token, NULL if not authenticated yet + * @param path Path of the request + * @param callback Callback invoked on websocket events + * @param params Parameters passed to the callback, NULL if not used + * @param handle Websocket connection handle + * @return MENDER_OK if the function succeeds, error code otherwise + */ +mender_err_t mender_websocket_connect( + char *jwt, char *path, mender_err_t (*callback)(mender_websocket_client_event_t, void *, size_t, void *), void *params, void **handle); + +/** + * @brief Send binary data over websocket connection + * @param handle Websocket connection handle + * @param payload Payload to send + * @param length Length of the payload + * @return MENDER_OK if the function succeeds, error code otherwise + */ +mender_err_t mender_websocket_send(void *handle, void *payload, size_t length); + +/** + * @brief Close the websocket connection + * @param handle Websocket connection handle + * @return MENDER_OK if the function succeeds, error code otherwise + */ +mender_err_t mender_websocket_disconnect(void *handle); + +/** + * @brief Release mender websocket + * @return MENDER_OK if the function succeeds, error code otherwise + */ +mender_err_t mender_websocket_exit(void); + +#endif /* CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __MENDER_WEBSOCKET_H__ */ diff --git a/platform/net/curl/src/mender-websocket.c b/platform/net/curl/src/mender-websocket.c new file mode 100644 index 0000000..a2d55c1 --- /dev/null +++ b/platform/net/curl/src/mender-websocket.c @@ -0,0 +1,376 @@ +/** + * @file mender-websocket.c + * @brief Mender websocket interface for curl platform, requires libcurl >= 7.86.0 + * + * MIT License + * + * Copyright (c) 2022-2023 joelguittet and mender-mcu-client contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include "mender-log.h" +#include "mender-utils.h" +#include "mender-websocket.h" + +/** + * @brief Default websocket thread stack size (kB) + */ +#ifndef CONFIG_MENDER_WEBSOCKET_THREAD_STACK_SIZE +#define CONFIG_MENDER_WEBSOCKET_THREAD_STACK_SIZE (64) +#endif /* CONFIG_MENDER_WEBSOCKET_THREAD_STACK_SIZE */ + +/** + * @brief Default websocket thread priority + */ +#ifndef CONFIG_MENDER_WEBSOCKET_THREAD_PRIORITY +#define CONFIG_MENDER_WEBSOCKET_THREAD_PRIORITY (0) +#endif /* CONFIG_MENDER_WEBSOCKET_THREAD_PRIORITY */ + +/** + * @brief Websocket handle + */ +typedef struct { + CURL * client; /**< Websocket client handle */ + struct curl_slist *headers; /**< Websocket client headers */ + pthread_t thread_handle; /**< Websocket thread handle */ + bool abort; /**< Flag used to indicate connection should be terminated */ + mender_err_t (*callback)(mender_websocket_client_event_t, + void *, + size_t, + void *); /**< Callback function to be invoked to perform the treatment of the events and data from the websocket */ + void *params; /**< Callback function parameters */ +} mender_websocket_handle_t; + +/** + * @brief Mender websocket configuration + */ +static mender_websocket_config_t mender_websocket_config; + +/** + * @brief Websocket PREREQ callback, used to inform the client is connected to the server + * @param params User data + * @param conn_primary_ip Primary IP of the remote server + * @param conn_local_ip Originating IP of the connection + * @param conn_primary_port Primary port number on the remote server + * @param conn_local_port Originating port number of the connection + * @return CURL_PREREQFUNC_OK if the function succeeds, CURL_PREREQFUNC_ABORT otherwise + */ +static int mender_websocket_prereq_callback(void *params, char *conn_primary_ip, char *conn_local_ip, int conn_primary_port, int conn_local_port); + +/** + * @brief Websocket write callback, used to retrieve data from the server + * @param data Data from the server + * @param size Size of the data + * @param nmemb Number of element + * @param params User data + * @return Real size of data if the function succeeds, -1 otherwise + */ +static size_t mender_websocket_write_callback(char *data, size_t size, size_t nmemb, void *params); + +/** + * @brief Thread used to perform connection and reception of data + * @param arg Websocket handle + * @return Not used + */ +static void *mender_websocket_thread(void *arg); + +mender_err_t +mender_websocket_init(mender_websocket_config_t *config) { + + assert(NULL != config); + assert(NULL != config->host); + + /* Save configuration */ + memcpy(&mender_websocket_config, config, sizeof(mender_websocket_config_t)); + + /* Initialization of curl */ + curl_global_init(CURL_GLOBAL_DEFAULT); + + return MENDER_OK; +} + +mender_err_t +mender_websocket_connect( + char *jwt, char *path, mender_err_t (*callback)(mender_websocket_client_event_t, void *, size_t, void *), void *params, void **handle) { + + assert(NULL != path); + assert(NULL != callback); + assert(NULL != handle); + CURLcode err_curl; + int err_pthread; + mender_err_t ret = MENDER_OK; + char * url = NULL; + char * bearer = NULL; + + /* Allocate a new handle */ + if (NULL == (*handle = malloc(sizeof(mender_websocket_handle_t)))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + memset(*handle, 0, sizeof(mender_websocket_handle_t)); + + /* Save callback and params */ + ((mender_websocket_handle_t *)*handle)->callback = callback; + ((mender_websocket_handle_t *)*handle)->params = params; + + /* Compute URL if required */ + if ((false == mender_utils_strbeginwith(path, "ws://")) && (false == mender_utils_strbeginwith(path, "wss://"))) { + if ((true == mender_utils_strbeginwith(path, "http://")) || (true == mender_utils_strbeginwith(mender_websocket_config.host, "http://"))) { + if (NULL == (url = (char *)malloc(strlen(mender_websocket_config.host) - strlen("http://") + strlen("ws://") + strlen(path) + 1))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + sprintf(url, "ws://%s%s", mender_websocket_config.host + strlen("http://"), path); + } else if ((true == mender_utils_strbeginwith(path, "https://")) || (true == mender_utils_strbeginwith(mender_websocket_config.host, "https://"))) { + if (NULL == (url = (char *)malloc(strlen(mender_websocket_config.host) - strlen("https://") + strlen("wss://") + strlen(path) + 1))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + sprintf(url, "wss://%s%s", mender_websocket_config.host + strlen("https://"), path); + } + } + + /* Initialization of the client */ + if (NULL == (((mender_websocket_handle_t *)*handle)->client = curl_easy_init())) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + + /* Configuration of the client */ + if (CURLE_OK != (err_curl = curl_easy_setopt(((mender_websocket_handle_t *)*handle)->client, CURLOPT_URL, (NULL != url) ? url : path))) { + mender_log_error("Unable to set websocket URL: %s", curl_easy_strerror(err_curl)); + ret = MENDER_FAIL; + goto FAIL; + } + if (CURLE_OK != (err_curl = curl_easy_setopt(((mender_websocket_handle_t *)*handle)->client, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2))) { + mender_log_error("Unable to set TLSv1.2: %s", curl_easy_strerror(err_curl)); + ret = MENDER_FAIL; + goto FAIL; + } + if (CURLE_OK != (err_curl = curl_easy_setopt(((mender_websocket_handle_t *)*handle)->client, CURLOPT_PREREQFUNCTION, &mender_websocket_prereq_callback))) { + mender_log_error("Unable to set websocket PREREQ function: %s", curl_easy_strerror(err_curl)); + ret = MENDER_FAIL; + goto FAIL; + } + if (CURLE_OK != (err_curl = curl_easy_setopt(((mender_websocket_handle_t *)*handle)->client, CURLOPT_PREREQDATA, *handle))) { + mender_log_error("Unable to set websocket PREREQ data: %s", curl_easy_strerror(err_curl)); + ret = MENDER_FAIL; + goto FAIL; + } + if (CURLE_OK != (err_curl = curl_easy_setopt(((mender_websocket_handle_t *)*handle)->client, CURLOPT_WRITEFUNCTION, &mender_websocket_write_callback))) { + mender_log_error("Unable to set websocket write function: %s", curl_easy_strerror(err_curl)); + ret = MENDER_FAIL; + goto FAIL; + } + if (CURLE_OK != (err_curl = curl_easy_setopt(((mender_websocket_handle_t *)*handle)->client, CURLOPT_WRITEDATA, *handle))) { + mender_log_error("Unable to set websocket write data: %s", curl_easy_strerror(err_curl)); + ret = MENDER_FAIL; + goto FAIL; + } + if (NULL != jwt) { + if (NULL == (bearer = (char *)malloc(strlen("Authorization: Bearer ") + strlen(jwt) + 1))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + sprintf(bearer, "Authorization: Bearer %s", jwt); + ((mender_websocket_handle_t *)*handle)->headers = curl_slist_append(((mender_websocket_handle_t *)*handle)->headers, bearer); + } + if (NULL != ((mender_websocket_handle_t *)*handle)->headers) { + if (CURLE_OK + != (err_curl + = curl_easy_setopt(((mender_websocket_handle_t *)*handle)->client, CURLOPT_HTTPHEADER, ((mender_websocket_handle_t *)*handle)->headers))) { + mender_log_error("Unable to set websocket HTTP headers: %s", curl_easy_strerror(err_curl)); + ret = MENDER_FAIL; + goto FAIL; + } + } + + /* Create and start websocket thread */ + pthread_attr_t pthread_attr; + if (0 != (err_pthread = pthread_attr_init(&pthread_attr))) { + mender_log_error("Unable to initialize websocket thread attributes (ret=%d)", err_pthread); + ret = MENDER_FAIL; + goto FAIL; + } + if (0 + != (err_pthread = pthread_attr_setstacksize( + &pthread_attr, ((CONFIG_MENDER_WEBSOCKET_THREAD_STACK_SIZE > 16) ? CONFIG_MENDER_WEBSOCKET_THREAD_STACK_SIZE : 16) * 1024))) { + mender_log_error("Unable to set websocket thread stack size (ret=%d)", err_pthread); + ret = MENDER_FAIL; + goto FAIL; + } + if (0 != (err_pthread = pthread_create(&((mender_websocket_handle_t *)*handle)->thread_handle, &pthread_attr, mender_websocket_thread, *handle))) { + mender_log_error("Unable to create websocket thread (ret=%d)", err_pthread); + ret = MENDER_FAIL; + goto FAIL; + } + if (0 != (err_pthread = pthread_setschedprio(((mender_websocket_handle_t *)*handle)->thread_handle, CONFIG_MENDER_WEBSOCKET_THREAD_PRIORITY))) { + mender_log_error("Unable to set websocket thread priority (ret=%d)", err_pthread); + ret = MENDER_FAIL; + goto FAIL; + } + + goto END; + +FAIL: + + /* Release memory */ + if (NULL != *handle) { + curl_easy_cleanup(((mender_websocket_handle_t *)*handle)->client); + curl_slist_free_all(((mender_websocket_handle_t *)handle)->headers); + free(*handle); + *handle = NULL; + } + +END: + + /* Release memory */ + if (NULL != bearer) { + free(bearer); + } + if (NULL != url) { + free(url); + } + + return ret; +} + +mender_err_t +mender_websocket_send(void *handle, void *payload, size_t length) { + + assert(NULL != handle); + assert(NULL != payload); + CURLcode err; + size_t sent = 0; + + /* Send binary payload */ + if (CURLE_OK != (err = curl_ws_send(((mender_websocket_handle_t *)handle)->client, payload, length, &sent, 0, CURLWS_BINARY))) { + mender_log_error("Unable to send data over websocket connection: %s", curl_easy_strerror(err)); + return MENDER_FAIL; + } + + return MENDER_OK; +} + +mender_err_t +mender_websocket_disconnect(void *handle) { + + assert(NULL != handle); + CURLcode err; + size_t sent = 0; + + /* Close websocket connection */ + ((mender_websocket_handle_t *)handle)->abort = true; + if (CURLE_OK != (err = curl_ws_send(((mender_websocket_handle_t *)handle)->client, NULL, 0, &sent, 0, CURLWS_CLOSE))) { + mender_log_error("Unable to send close payload: %s", curl_easy_strerror(err)); + return MENDER_FAIL; + } + + /* Wait end of execution of the websocket thread */ + pthread_join(((mender_websocket_handle_t *)handle)->thread_handle, NULL); + + /* Release memory */ + curl_easy_cleanup(((mender_websocket_handle_t *)handle)->client); + curl_slist_free_all(((mender_websocket_handle_t *)handle)->headers); + free(handle); + + return MENDER_OK; +} + +mender_err_t +mender_websocket_exit(void) { + + /* Cleaning */ + curl_global_cleanup(); + + return MENDER_OK; +} + +static int +mender_websocket_prereq_callback(void *params, char *conn_primary_ip, char *conn_local_ip, int conn_primary_port, int conn_local_port) { + + assert(NULL != params); + mender_websocket_handle_t *handle = (mender_websocket_handle_t *)params; + (void)conn_primary_ip; + (void)conn_local_ip; + (void)conn_primary_port; + (void)conn_local_port; + + /* Invoke connected callback */ + if (MENDER_OK != handle->callback(MENDER_WEBSOCKET_EVENT_CONNECTED, NULL, 0, handle->params)) { + mender_log_error("An error occurred"); + return CURL_PREREQFUNC_ABORT; + } + + return CURL_PREREQFUNC_OK; +} + +static size_t +mender_websocket_write_callback(char *data, size_t size, size_t nmemb, void *params) { + + assert(NULL != params); + mender_websocket_handle_t *handle = (mender_websocket_handle_t *)params; + size_t realsize = size * nmemb; + + /* Transmit data received to the upper layer */ + if (realsize > 0) { + if (MENDER_OK != handle->callback(MENDER_WEBSOCKET_EVENT_DATA_RECEIVED, (void *)data, realsize, handle->params)) { + mender_log_error("An error occurred, stop reading data"); + return -1; + } + } + + return realsize; +} + +__attribute__((noreturn)) static void * +mender_websocket_thread(void *arg) { + + assert(NULL != arg); + mender_websocket_handle_t *handle = (mender_websocket_handle_t *)arg; + CURLcode err; + + /* Perform reception of data from the websocket connection */ + while (false == handle->abort) { + err = curl_easy_perform(handle->client); + if (CURLE_OK != err) { + if (CURLE_HTTP_RETURNED_ERROR == err) { + mender_log_error("Connection has been closed"); + goto END; + } + mender_log_error("Unable to perform websocket request: %s", curl_easy_strerror(err)); + } + } + +END: + + /* Invoke disconnected callback */ + handle->callback(MENDER_WEBSOCKET_EVENT_DISCONNECTED, NULL, 0, handle->params); + + /* Terminate work queue thread */ + pthread_exit(NULL); +} diff --git a/platform/net/esp-idf/src/mender-websocket.c b/platform/net/esp-idf/src/mender-websocket.c new file mode 100644 index 0000000..a422ff4 --- /dev/null +++ b/platform/net/esp-idf/src/mender-websocket.c @@ -0,0 +1,288 @@ +/** + * @file mender-websocket.c + * @brief Mender websocket interface for curl platform, requires libcurl >= 7.86.0 + * + * MIT License + * + * Copyright (c) 2022-2023 joelguittet and mender-mcu-client contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include "mender-log.h" +#include "mender-utils.h" +#include "mender-websocket.h" + +/** + * @brief Websocket reconnect timeout (milliseconds) + */ +#define MENDER_WEBSOCKET_RECONNECT_TIMEOUT 10000 + +/** + * @brief Websocket network timeout (milliseconds) + */ +#define MENDER_WEBSOCKET_NETWORK_TIMEOUT 10000 + +/** + * @brief Websocket ping interval (seconds) + */ +#define MENDER_WEBSOCKET_PING_INTERVAL 60 + +/** + * @brief Websocket handle + */ +typedef struct { + esp_websocket_client_handle_t client; /**< Websocket client handle */ + mender_err_t (*callback)(mender_websocket_client_event_t, + void *, + size_t, + void *); /**< Callback function to be invoked to perform the treatment of the events and data from the websocket */ + void *params; /**< Callback function parameters */ +} mender_websocket_handle_t; + +/** + * @brief Mender websocket configuration + */ +static mender_websocket_config_t mender_websocket_config; + +/** + * @brief Websocket event callback + * @param handler_args + * @param base Event base + * @param event_id Event ID + * @param event_data Websocket event data + */ +static void mender_websocket_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data); + +mender_err_t +mender_websocket_init(mender_websocket_config_t *config) { + + assert(NULL != config); + assert(NULL != config->host); + + /* Save configuration */ + memcpy(&mender_websocket_config, config, sizeof(mender_websocket_config_t)); + + return MENDER_OK; +} + +mender_err_t +mender_websocket_connect( + char *jwt, char *path, mender_err_t (*callback)(mender_websocket_client_event_t, void *, size_t, void *), void *params, void **handle) { + + assert(NULL != path); + assert(NULL != callback); + assert(NULL != handle); + esp_err_t err; + mender_err_t ret = MENDER_OK; + char * url = NULL; + char * bearer = NULL; + + /* Allocate a new handle */ + if (NULL == (*handle = malloc(sizeof(mender_websocket_handle_t)))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + memset(*handle, 0, sizeof(mender_websocket_handle_t)); + + /* Save callback and params */ + ((mender_websocket_handle_t *)*handle)->callback = callback; + ((mender_websocket_handle_t *)*handle)->params = params; + + /* Compute URL if required */ + if ((false == mender_utils_strbeginwith(path, "ws://")) && (false == mender_utils_strbeginwith(path, "wss://"))) { + if ((true == mender_utils_strbeginwith(path, "http://")) || (true == mender_utils_strbeginwith(mender_websocket_config.host, "http://"))) { + if (NULL == (url = (char *)malloc(strlen(mender_websocket_config.host) - strlen("http://") + strlen("ws://") + strlen(path) + 1))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + sprintf(url, "ws://%s%s", mender_websocket_config.host + strlen("http://"), path); + } else if ((true == mender_utils_strbeginwith(path, "https://")) || (true == mender_utils_strbeginwith(mender_websocket_config.host, "https://"))) { + if (NULL == (url = (char *)malloc(strlen(mender_websocket_config.host) - strlen("https://") + strlen("wss://") + strlen(path) + 1))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto FAIL; + } + sprintf(url, "wss://%s%s", mender_websocket_config.host + strlen("https://"), path); + } + } + + /* Configuration of the client */ + esp_websocket_client_config_t config = { .uri = (NULL != url) ? url : path, + .crt_bundle_attach = esp_crt_bundle_attach, + .reconnect_timeout_ms = MENDER_WEBSOCKET_RECONNECT_TIMEOUT, + .network_timeout_ms = MENDER_WEBSOCKET_NETWORK_TIMEOUT, + .ping_interval_sec = MENDER_WEBSOCKET_PING_INTERVAL }; + if (true == mender_utils_strbeginwith(config.uri, "ws://")) { + config.transport = WEBSOCKET_TRANSPORT_OVER_TCP; + } else if (true == mender_utils_strbeginwith(config.uri, "wss://")) { + config.transport = WEBSOCKET_TRANSPORT_OVER_SSL; + } else { + config.transport = WEBSOCKET_TRANSPORT_UNKNOWN; + } + if (NULL != jwt) { + if (NULL == (bearer = (char *)malloc(strlen("Authorization: Bearer ") + strlen(jwt) + strlen("\r\n") + 1))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto END; + } + sprintf(bearer, "Authorization: Bearer %s\r\n", jwt); + } + config.headers = bearer; + + /* Initialization of the client */ + if (NULL == (((mender_websocket_handle_t *)*handle)->client = esp_websocket_client_init(&config))) { + mender_log_error("Unable to allocate memory"); + ret = MENDER_FAIL; + goto END; + } + if (ESP_OK + != (err + = esp_websocket_register_events(((mender_websocket_handle_t *)*handle)->client, WEBSOCKET_EVENT_ANY, mender_websocket_event_handler, *handle))) { + mender_log_error("Unable to register websocket callback event handler"); + ret = MENDER_FAIL; + goto END; + } + + /* Open websocket client connection */ + if (ESP_OK != (err = esp_websocket_client_start(((mender_websocket_handle_t *)*handle)->client))) { + mender_log_error("Unable to open websocket client connection: %s", esp_err_to_name(err)); + ret = MENDER_FAIL; + goto FAIL; + } + + goto END; + +FAIL: + + /* Release memory */ + if (NULL != *handle) { + if (NULL != ((mender_websocket_handle_t *)*handle)->client) { + esp_websocket_client_destroy(((mender_websocket_handle_t *)*handle)->client); + } + free(*handle); + *handle = NULL; + } + +END: + + /* Release memory */ + if (NULL != bearer) { + free(bearer); + } + if (NULL != url) { + free(url); + } + + return ret; +} + +mender_err_t +mender_websocket_send(void *handle, void *payload, size_t length) { + + assert(NULL != handle); + assert(NULL != payload); + + /* Send binary payload */ + if (length != esp_websocket_client_send_bin(((mender_websocket_handle_t *)handle)->client, payload, length, portMAX_DELAY)) { + mender_log_error("Unable to send data over websocket connection"); + return MENDER_FAIL; + } + + return MENDER_OK; +} + +mender_err_t +mender_websocket_disconnect(void *handle) { + + assert(NULL != handle); + esp_err_t err; + + /* Close websocket connection */ + if (ESP_OK != (err = esp_websocket_client_close(((mender_websocket_handle_t *)handle)->client, portMAX_DELAY))) { + mender_log_error("Unable to close websocket connection: %s", esp_err_to_name(err)); + return MENDER_FAIL; + } + + /* Release memory */ + esp_websocket_client_destroy(((mender_websocket_handle_t *)handle)->client); + free(handle); + + return MENDER_OK; +} + +mender_err_t +mender_websocket_exit(void) { + + /* Nothing to do */ + return MENDER_OK; +} + +static void +mender_websocket_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) { + assert(NULL != handler_args); + (void)base; + assert(NULL != event_data); + mender_websocket_handle_t * handle = (mender_websocket_handle_t *)handler_args; + esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data; + mender_err_t ret; + + /* Treatment depending of the event */ + switch (event_id) { + case WEBSOCKET_EVENT_BEFORE_CONNECT: + /* Nothing to do */ + break; + case WEBSOCKET_EVENT_CONNECTED: + /* Connection has been established */ + if (MENDER_OK != (ret = handle->callback(MENDER_WEBSOCKET_EVENT_CONNECTED, NULL, 0, handle->params))) { + mender_log_error("An error occurred"); + } + break; + case WEBSOCKET_EVENT_DATA: + /* Treatment of data received depending of the opcode */ + if (MENDER_WEBSOCKET_OPCODE_BINARY_FRAME == data->op_code) { + if (MENDER_OK + != (ret = handle->callback(MENDER_WEBSOCKET_EVENT_DATA_RECEIVED, (void *)data->data_ptr, (size_t)data->data_len, handle->params))) { + mender_log_error("An error occurred"); + } + } + break; + case WEBSOCKET_EVENT_DISCONNECTED: + case WEBSOCKET_EVENT_CLOSED: + /* Connection has been closed */ + if (MENDER_OK != (ret = handle->callback(MENDER_WEBSOCKET_EVENT_DISCONNECTED, NULL, 0, handle->params))) { + mender_log_error("An error occurred"); + } + break; + case WEBSOCKET_EVENT_ERROR: + /* Connection failed */ + mender_log_error("An error occurred"); + handle->callback(MENDER_WEBSOCKET_EVENT_ERROR, NULL, 0, handle->params); + break; + default: + /* Should not occur */ + mender_log_error("Unknown event occurred (event_id=%d)", event_id); + break; + } +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a9bb980..b0fb07b 100755 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -90,11 +90,18 @@ if(CONFIG_MENDER_CLIENT_ADD_ON_INVENTORY) add_compile_definitions(CONFIG_MENDER_CLIENT_INVENTORY_REFRESH_INTERVAL=${CONFIG_MENDER_CLIENT_INVENTORY_REFRESH_INTERVAL}) endif() endif() +if(CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT) + add_compile_definitions(CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT) +endif() # Add definitions depending of the target if(CONFIG_MENDER_MCU_CLIENT_BOARD_TYPE MATCHES "zephyr") add_compile_definitions(CONFIG_MENDER_STORAGE_SECTOR_COUNT=4) endif() +if(CONFIG_MENDER_MCU_CLIENT_NET_TYPE MATCHES "curl") + add_compile_definitions(CONFIG_MENDER_WEBSOCKET_THREAD_STACK_SIZE=64) + add_compile_definitions(CONFIG_MENDER_WEBSOCKET_THREAD_PRIORITY=0) +endif() if(CONFIG_MENDER_MCU_CLIENT_NET_TYPE MATCHES "zephyr") add_compile_definitions(CONFIG_NET_SOCKETS_SOCKOPT_TLS) add_compile_definitions(CONFIG_MENDER_HTTP_CA_CERTIFICATE_TAG=1) @@ -127,6 +134,9 @@ include("${CMAKE_CURRENT_LIST_DIR}/../CMakeLists.txt") # Link the executable with the mender-mcu-library target_link_libraries(${APP_EXECUTABLE_NAME} mender-mcu-client pthread) +if(CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT) + target_link_libraries(${APP_EXECUTABLE_NAME} msgpack-c) +endif() if(CONFIG_MENDER_MCU_CLIENT_NET_TYPE MATCHES "curl") target_link_libraries(${APP_EXECUTABLE_NAME} curl) endif() diff --git a/tests/src/main.c b/tests/src/main.c index 9f17d6e..c3cc8db 100644 --- a/tests/src/main.c +++ b/tests/src/main.c @@ -34,6 +34,7 @@ #include "mender-inventory.h" #include "mender-log.h" #include "mender-ota.h" +#include "mender-troubleshoot.h" /** * @brief Mender client options @@ -72,6 +73,12 @@ authentication_success_cb(void) { return ret; } #endif /* CONFIG_MENDER_CLIENT_ADD_ON_INVENTORY */ +#ifdef CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT + if (MENDER_OK != (ret = mender_troubleshoot_activate())) { + mender_log_error("Unable to activate troubleshoot add-on"); + return ret; + } +#endif /* CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT */ /* Validate the image if it is still pending */ /* Note it is possible to do multiple diagnosic tests before validating the image */ @@ -161,6 +168,65 @@ config_updated_cb(mender_keystore_t *configuration) { #endif /* CONFIG_MENDER_CLIENT_CONFIGURE_STORAGE */ #endif /* CONFIG_MENDER_CLIENT_ADD_ON_CONFIGURE */ +#ifdef CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT + +/** + * @brief Shell connected callback + * @param terminal_height Terminal height + * @param terminal_width Terminal width + * @return MENDER_OK if the function succeeds, error code otherwise + */ +static mender_err_t +shell_connected_cb(uint16_t terminal_height, uint16_t terminal_width) { + + /* Just print terminal size */ + mender_log_info("Shell connected with height=%d and width=%d", terminal_height, terminal_width); + + return MENDER_OK; +} + +/** + * @brief Shell resized callback + * @param terminal_height Terminal height + * @param terminal_width Terminal width + * @return MENDER_OK if the function succeeds, error code otherwise + */ +static mender_err_t +shell_resized_cb(uint16_t terminal_height, uint16_t terminal_width) { + + /* Just print terminal size */ + mender_log_info("Shell resized with height=%d and width=%d", terminal_height, terminal_width); + + return MENDER_OK; +} + +/** + * @brief Shell data callback + * @param body Shell data received + * @return MENDER_OK if the function succeeds, error code otherwise + */ +static mender_err_t +shell_data_cb(char *body) { + + /* Just send back the data received */ + return mender_troubleshoot_shell_print(body); +} + +/** + * @brief Shell disconnected callback + * @return MENDER_OK if the function succeeds, error code otherwise + */ +static mender_err_t +shell_disconnected_cb(void) { + + /* Just print disconnected */ + mender_log_info("Shell disconnected"); + + return MENDER_OK; +} + +#endif /* CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT */ + /** * @brief Signal handler * @param signo Signal number @@ -280,7 +346,6 @@ main(int argc, char **argv) { .ota_end = mender_ota_end, .ota_set_boot_partition = mender_ota_set_boot_partition, .restart = restart_cb }; - mender_client_init(&mender_client_config, &mender_client_callbacks); /* Initialize mender add-ons */ @@ -297,13 +362,28 @@ main(int argc, char **argv) { mender_inventory_config_t mender_inventory_config = { .refresh_interval = 0 }; mender_inventory_init(&mender_inventory_config); #endif /* CONFIG_MENDER_CLIENT_ADD_ON_INVENTORY */ +#ifdef CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT + mender_troubleshoot_config_t mender_troubleshoot_config = { .healthcheck_interval = 0 }; + mender_troubleshoot_callbacks_t mender_troubleshoot_callbacks = { + .shell_connected = shell_connected_cb, .shell_resized = shell_resized_cb, .shell_data = shell_data_cb, .shell_disconnected = shell_disconnected_cb + }; + mender_troubleshoot_init(&mender_troubleshoot_config, &mender_troubleshoot_callbacks); +#endif /* CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT */ /* Wait for mender-mcu-client events */ pthread_mutex_lock(&mender_client_events_mutex); pthread_cond_wait(&mender_client_events_cond, &mender_client_events_mutex); pthread_mutex_unlock(&mender_client_events_mutex); + /* Deactivate mender add-ons */ +#ifdef CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT + mender_troubleshoot_deactivate(); +#endif /* CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT */ + /* Release mender add-ons */ +#ifdef CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT + mender_troubleshoot_exit(); +#endif /* CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT */ #ifdef CONFIG_MENDER_CLIENT_ADD_ON_INVENTORY mender_inventory_exit(); #endif /* CONFIG_MENDER_CLIENT_ADD_ON_INVENTORY */ diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 4d23906..f12b57c 100755 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -44,4 +44,8 @@ if(CONFIG_MENDER_MCU_CLIENT) zephyr_library_sources_ifdef(CONFIG_MENDER_CLIENT_ADD_ON_INVENTORY "${CMAKE_CURRENT_LIST_DIR}/../add-ons/src/mender-inventory.c" ) + zephyr_library_sources_ifdef(CONFIG_MENDER_CLIENT_ADD_ON_TROUBLESHOOT + "${CMAKE_CURRENT_LIST_DIR}/../add-ons/src/mender-troubleshoot.c" + "${CMAKE_CURRENT_LIST_DIR}/../platform/net/zephyr/src/mender-websocket.c" + ) endif() diff --git a/zephyr/Kconfig b/zephyr/Kconfig index b5b03fe..9b44a1a 100755 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -36,6 +36,7 @@ menuconfig MENDER_MCU_CLIENT select IMG_MANAGER select MBEDTLS select MPU_ALLOW_FLASH_WRITE + select MSGPACK_C if MENDER_CLIENT_ADD_ON_TROUBLESHOOT select NETWORKING select NET_TCP select NET_SOCKETS @@ -43,6 +44,7 @@ menuconfig MENDER_MCU_CLIENT select NVS select REBOOT select STREAM_FLASH + select WEBSOCKET_CLIENT if MENDER_CLIENT_ADD_ON_TROUBLESHOOT help Secure, risk tolerant and efficient over-the-air updates for all device software @@ -114,7 +116,7 @@ if MENDER_MCU_CLIENT default y help Inventory add-on permits to send inventory key-value pairs to the Mender server. - It is particurlarly used to send artifact name and device type, and it permits to see the last check-in time of the device. + It is particularly used to send artifact name and device type, and it permits to see the last check-in time of the device. if MENDER_CLIENT_ADD_ON_INVENTORY @@ -127,6 +129,24 @@ if MENDER_MCU_CLIENT endif + config MENDER_CLIENT_ADD_ON_TROUBLESHOOT + bool "Mender client Troubleshoot" + default n + help + Troubleshoot add-on permits to perform debugging on the target from the Mender server. + It is particularly used to connect to the remote terminal of the device. + + if MENDER_CLIENT_ADD_ON_TROUBLESHOOT + + config MENDER_CLIENT_TROUBLESHOOT_HEALTHCHECK_INTERVAL + int "Mender client Troubleshoot healthcheck interval (seconds)" + range 0 60 + default 30 + help + Interval used to periodically perform healthcheck with the Mender server. + + endif + config MENDER_RTOS_WORK_QUEUE_STACK_SIZE int "Mender RTOS Work Queue Stack Size (kB)" range 0 64