/* * Copyright (C) 2019 Freie Universität Berlin * 2019 Inria * 2019 Kaspar Schleiser * * This file is subject to the terms and conditions of the GNU Lesser * General Public License v2.1. See the file LICENSE in the top level * directory for more details. */ /** * @ingroup sys_suit * @{ * * @file * @brief SUIT coap * * @author Koen Zandberg * @author Kaspar Schleiser * @author Francisco Molina * @author Alexandre Abadie * @} */ #include #include #include #include "msg.h" #include "log.h" #include "net/nanocoap.h" #include "net/nanocoap_sock.h" #include "thread.h" #include "periph/pm.h" #include "ztimer.h" #include "suit/transport/coap.h" #include "net/sock/util.h" #ifdef MODULE_RIOTBOOT_SLOT #include "riotboot/slot.h" #endif #ifdef MODULE_SUIT #include "suit.h" #include "suit/handlers.h" #include "suit/storage.h" #endif #if defined(MODULE_PROGRESS_BAR) #include "progress_bar.h" #endif #define ENABLE_DEBUG 0 #include "debug.h" #ifndef SUIT_COAP_STACKSIZE /* allocate stack needed to do manifest validation */ #define SUIT_COAP_STACKSIZE (3 * THREAD_STACKSIZE_LARGE) #endif #ifndef SUIT_COAP_PRIO #define SUIT_COAP_PRIO THREAD_PRIORITY_MAIN - 1 #endif #ifndef SUIT_URL_MAX #define SUIT_URL_MAX 128 #endif #ifndef SUIT_MANIFEST_BUFSIZE #define SUIT_MANIFEST_BUFSIZE 640 #endif #define SUIT_MSG_TRIGGER 0x12345 static char _stack[SUIT_COAP_STACKSIZE]; static char _url[SUIT_URL_MAX]; static uint8_t _manifest_buf[SUIT_MANIFEST_BUFSIZE]; #ifdef MODULE_SUIT static inline void _print_download_progress(suit_manifest_t *manifest, size_t offset, size_t len, size_t image_size) { (void)manifest; (void)offset; (void)len; DEBUG("_suit_flashwrite(): writing %u bytes at pos %u\n", len, offset); #if defined(MODULE_PROGRESS_BAR) if (image_size != 0) { char _suffix[7] = { 0 }; uint8_t _progress = 100 * (offset + len) / image_size; sprintf(_suffix, " %3d%%", _progress); progress_bar_print("Fetching firmware ", _suffix, _progress); if (_progress == 100) { puts(""); } } #else (void) image_size; #endif } #endif static kernel_pid_t _suit_coap_pid; static void _suit_handle_url(const char *url, coap_blksize_t blksize) { LOG_INFO("suit_coap: downloading \"%s\"\n", url); ssize_t size = nanocoap_get_blockwise_url_to_buf(url, blksize, _manifest_buf, SUIT_MANIFEST_BUFSIZE); if (size >= 0) { LOG_INFO("suit_coap: got manifest with size %u\n", (unsigned)size); #ifdef MODULE_SUIT suit_manifest_t manifest; memset(&manifest, 0, sizeof(manifest)); manifest.urlbuf = _url; manifest.urlbuf_len = SUIT_URL_MAX; int res; if ((res = suit_parse(&manifest, _manifest_buf, size)) != SUIT_OK) { LOG_INFO("suit_parse() failed. res=%i\n", res); return; } #endif #ifdef MODULE_SUIT_STORAGE_FLASHWRITE if (res == 0) { const riotboot_hdr_t *hdr = riotboot_slot_get_hdr( riotboot_slot_other()); riotboot_hdr_print(hdr); ztimer_sleep(ZTIMER_MSEC, 1 * MS_PER_SEC); if (riotboot_hdr_validate(hdr) == 0) { LOG_INFO("suit_coap: rebooting...\n"); pm_reboot(); } else { LOG_INFO("suit_coap: update failed, hdr invalid\n "); } } #endif } else { LOG_INFO("suit_coap: error getting manifest\n"); } } int suit_storage_helper(void *arg, size_t offset, uint8_t *buf, size_t len, int more) { suit_manifest_t *manifest = (suit_manifest_t *)arg; uint32_t image_size; nanocbor_value_t param_size; size_t total = offset + len; suit_component_t *comp = &manifest->components[manifest->component_current]; suit_param_ref_t *ref_size = &comp->param_size; /* Grab the total image size from the manifest */ if ((suit_param_ref_to_cbor(manifest, ref_size, ¶m_size) == 0) || (nanocbor_get_uint32(¶m_size, &image_size) < 0)) { /* Early exit if the total image size can't be determined */ return -1; } if (image_size < offset + len) { /* Extra newline at the start to compensate for the progress bar */ LOG_ERROR( "\n_suit_coap(): Image beyond size, offset + len=%u, " "image_size=%u\n", (unsigned)(total), (unsigned)image_size); return -1; } if (!more && image_size != total) { LOG_INFO("Incorrect size received, got %u, expected %u\n", (unsigned)total, (unsigned)image_size); return -1; } _print_download_progress(manifest, offset, len, image_size); int res = suit_storage_write(comp->storage_backend, manifest, buf, offset, len); if (!more) { LOG_INFO("Finalizing payload store\n"); /* Finalize the write if no more data available */ res = suit_storage_finish(comp->storage_backend, manifest); } return res; } static void *_suit_coap_thread(void *arg) { (void)arg; LOG_INFO("suit_coap: started.\n"); msg_t msg_queue[4]; msg_init_queue(msg_queue, 4); _suit_coap_pid = thread_getpid(); msg_t m; while (true) { msg_receive(&m); DEBUG("suit_coap: got msg with type %" PRIu32 "\n", m.content.value); switch (m.content.value) { case SUIT_MSG_TRIGGER: LOG_INFO("suit_coap: trigger received\n"); _suit_handle_url(_url, CONFIG_SUIT_COAP_BLOCKSIZE); break; default: LOG_WARNING("suit_coap: warning: unhandled msg\n"); } } return NULL; } void suit_coap_run(void) { thread_create(_stack, SUIT_COAP_STACKSIZE, SUIT_COAP_PRIO, THREAD_CREATE_STACKTEST, _suit_coap_thread, NULL, "suit_coap"); } static ssize_t _version_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, void *context) { (void)context; return coap_reply_simple(pkt, COAP_CODE_205, buf, len, COAP_FORMAT_TEXT, (uint8_t *)"NONE", 4); } #ifdef MODULE_RIOTBOOT_SLOT static ssize_t _slot_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, void *context) { /* context is passed either as NULL or 0x1 for /active or /inactive */ char c = '0'; if (context) { c += riotboot_slot_other(); } else { c += riotboot_slot_current(); } return coap_reply_simple(pkt, COAP_CODE_205, buf, len, COAP_FORMAT_TEXT, (uint8_t *)&c, 1); } #endif static ssize_t _trigger_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, void *context) { (void)context; unsigned code; size_t payload_len = pkt->payload_len; if (payload_len) { if (payload_len >= SUIT_URL_MAX) { code = COAP_CODE_REQUEST_ENTITY_TOO_LARGE; } else { code = COAP_CODE_CREATED; LOG_INFO("suit: received URL: \"%s\"\n", (char *)pkt->payload); suit_coap_trigger(pkt->payload, payload_len); } } else { code = COAP_CODE_REQUEST_ENTITY_INCOMPLETE; } return coap_reply_simple(pkt, code, buf, len, COAP_FORMAT_NONE, NULL, 0); } void suit_coap_trigger(const uint8_t *url, size_t len) { memcpy(_url, url, len); _url[len] = '\0'; msg_t m = { .content.value = SUIT_MSG_TRIGGER }; msg_send(&m, _suit_coap_pid); } static const coap_resource_t _subtree[] = { #ifdef MODULE_RIOTBOOT_SLOT { "/suit/slot/active", COAP_METHOD_GET, _slot_handler, NULL }, { "/suit/slot/inactive", COAP_METHOD_GET, _slot_handler, (void *)0x1 }, #endif { "/suit/trigger", COAP_METHOD_PUT | COAP_METHOD_POST, _trigger_handler, NULL }, { "/suit/version", COAP_METHOD_GET, _version_handler, NULL }, }; const coap_resource_subtree_t coap_resource_subtree_suit = { .resources = &_subtree[0], .resources_numof = ARRAY_SIZE(_subtree) };