diff --git a/examples/suit_update/Makefile b/examples/suit_update/Makefile index b5c09cc73aa0..a370233c430c 100644 --- a/examples/suit_update/Makefile +++ b/examples/suit_update/Makefile @@ -106,7 +106,7 @@ include $(RIOTBASE)/Makefile.include # allow to use large blocks to utilize large MTUs (802.15.4g, Ethernet, WiFi) LARGE_BLOCKS ?= 0 ifeq (1, $(LARGE_BLOCKS)) - CFLAGS += -DCONFIG_SUIT_COAP_BLOCKSIZE=COAP_BLOCKSIZE_1024 + CFLAGS += -DCONFIG_NANOCOAP_BLOCKSIZE_DEFAULT=COAP_BLOCKSIZE_1024 else # lower pktbuf size to something sufficient for this application # Set GNRC_PKTBUF_SIZE via CFLAGS if not being set via Kconfig. diff --git a/sys/Makefile.dep b/sys/Makefile.dep index 9de53e026a00..9b6e9c59a486 100644 --- a/sys/Makefile.dep +++ b/sys/Makefile.dep @@ -682,6 +682,11 @@ ifneq (,$(filter nanocoap_cache,$(USEMODULE))) USEMODULE += hashes endif +ifneq (,$(filter nanocoap_vfs,$(USEMODULE))) + USEMODULE += nanocoap_sock + USEMODULE += vfs +endif + ifneq (,$(filter nanocoap_%,$(USEMODULE))) USEMODULE += nanocoap endif diff --git a/sys/include/net/nanocoap.h b/sys/include/net/nanocoap.h index 0c61ae9a3c06..d785edd11470 100644 --- a/sys/include/net/nanocoap.h +++ b/sys/include/net/nanocoap.h @@ -148,6 +148,13 @@ extern "C" { #define CONFIG_NANOCOAP_BLOCK_SIZE_EXP_MAX (6) #endif +/** + * @brief CoAP block-wise-transfer size that should be used by default + */ +#ifndef CONFIG_NANOCOAP_BLOCKSIZE_DEFAULT +#define CONFIG_NANOCOAP_BLOCKSIZE_DEFAULT COAP_BLOCKSIZE_64 +#endif + /** @brief Maximum length of a query string written to a message */ #ifndef CONFIG_NANOCOAP_QS_MAX #define CONFIG_NANOCOAP_QS_MAX (64) diff --git a/sys/include/net/nanocoap_vfs.h b/sys/include/net/nanocoap_vfs.h new file mode 100644 index 000000000000..2ba77fb931ec --- /dev/null +++ b/sys/include/net/nanocoap_vfs.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2022 ML!PA Consulting GmbH + * + * 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 net_nanosock + * @brief VFS NanoCoAP helper functions + * + * @{ + * + * @file + * @brief VFS NanoCoAP helper functions + * + * @author Benjamin Valentin + */ +#ifndef NET_NANOCOAP_VFS_H +#define NET_NANOCOAP_VFS_H + +#include "net/nanocoap_sock.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Downloads the resource behind @p url via blockwise + * GET and stores it in the file @p dst. + * + * @param[in] url URL to the resource + * @param[in] dst Path to the destination file + * + * @returns 0 on success + * @returns <0 on error + */ +int nanocoap_vfs_get_url(const char *url, const char *dst); + +/** + * @brief Downloads the resource behind @p path via blockwise + * GET and stores it in the file @p dst. + * + * @param[in] sock Connection to the server + * @param[in] path Remote query path to the resource + * @param[in] dst Local path to the destination file + * + * @returns 0 on success + * @returns <0 on error + */ +int nanocoap_vfs_get(nanocoap_sock_t *sock, const char *path, const char *dst); + +#ifdef __cplusplus +} +#endif +#endif /* NET_NANOCOAP_VFS_H */ +/** @} */ diff --git a/sys/include/suit/transport/coap.h b/sys/include/suit/transport/coap.h index c13d30a8bb02..aaf990207269 100644 --- a/sys/include/suit/transport/coap.h +++ b/sys/include/suit/transport/coap.h @@ -76,7 +76,7 @@ extern const coap_resource_subtree_t coap_resource_subtree_suit; * @brief Coap block-wise-transfer size used for SUIT */ #ifndef CONFIG_SUIT_COAP_BLOCKSIZE -#define CONFIG_SUIT_COAP_BLOCKSIZE COAP_BLOCKSIZE_64 +#define CONFIG_SUIT_COAP_BLOCKSIZE CONFIG_NANOCOAP_BLOCKSIZE_DEFAULT #endif /** diff --git a/sys/net/application_layer/nanocoap/vfs.c b/sys/net/application_layer/nanocoap/vfs.c new file mode 100644 index 000000000000..3e1f8bfdea44 --- /dev/null +++ b/sys/net/application_layer/nanocoap/vfs.c @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2022 ML!PA Consulting GmbH + * + * 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 net_nanocoap + * @{ + * + * @file + * @brief Nanocoap VFS helpers + * + * @author Benjamin Valentin + * + * @} + */ + +#include +#include "net/nanocoap_sock.h" +#include "net/sock/util.h" +#include "vfs.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +static int _2file(void *arg, size_t offset, uint8_t *buf, size_t len, int more) +{ + (void)more; + int *fd = arg; + + vfs_lseek(*fd, offset, SEEK_SET); + return vfs_write(*fd, buf, len); +} + +static int _prepare_file(const char *dst, char *dst_tmp, size_t len) +{ + /* download to temp file, rename it later */ + if (snprintf(dst_tmp, len, "%s.t", dst) > (int)len) { + return -ENOBUFS; + } + + return vfs_open(dst_tmp, O_CREAT | O_WRONLY | O_TRUNC, 0644); +} + +static int _finalize_file(int fd, int res, const char *dst, const char *dst_tmp) +{ + vfs_close(fd); + + /* move file to it's final location */ + if (res >= 0) { + DEBUG("nanocoap: moving %s to %s\n", dst_tmp, dst); + vfs_unlink(dst); + res = vfs_rename(dst_tmp, dst); + } + + vfs_unlink(dst_tmp); + + return res; +} + +int nanocoap_vfs_get(nanocoap_sock_t *sock, const char *path, const char *dst) +{ + int fd, res; + char dst_tmp[CONFIG_SOCK_URLPATH_MAXLEN]; + + DEBUG("nanocoap: downloading %s to %s\n", path, dst_tmp); + + fd = _prepare_file(dst, dst_tmp, sizeof(dst_tmp)); + if (fd < 0) { + return fd; + } + res = nanocoap_sock_get_blockwise(sock, path, CONFIG_NANOCOAP_BLOCKSIZE_DEFAULT, + _2file, &fd); + return _finalize_file(fd, res, dst, dst_tmp); +} + +int nanocoap_vfs_get_url(const char *url, const char *dst) +{ + int fd, res; + char dst_tmp[CONFIG_SOCK_URLPATH_MAXLEN]; + + DEBUG("nanocoap: downloading %s to %s\n", url, dst_tmp); + + fd = _prepare_file(dst, dst_tmp, sizeof(dst_tmp)); + if (fd < 0) { + return fd; + } + res = nanocoap_get_blockwise_url(url, CONFIG_NANOCOAP_BLOCKSIZE_DEFAULT, + _2file, &fd); + return _finalize_file(fd, res, dst, dst_tmp); +} diff --git a/sys/shell/commands/Makefile b/sys/shell/commands/Makefile index eeddbbbb074f..e3f483556556 100644 --- a/sys/shell/commands/Makefile +++ b/sys/shell/commands/Makefile @@ -122,6 +122,10 @@ ifneq (,$(filter semtech-loramac,$(USEPKG))) SRC += sc_loramac.c endif +ifneq (,$(filter nanocoap_vfs,$(USEMODULE))) + SRC += sc_nanocoap_vfs.c +endif + ifneq (,$(filter nimble_netif,$(USEMODULE))) SRC += sc_nimble_netif.c endif diff --git a/sys/shell/commands/sc_nanocoap_vfs.c b/sys/shell/commands/sc_nanocoap_vfs.c new file mode 100644 index 000000000000..ca931adb8247 --- /dev/null +++ b/sys/shell/commands/sc_nanocoap_vfs.c @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2022 ML!PA Consulting GmbH + * + * 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_shell_commands + * @{ + * + * @file + * @brief NanoCoAP commands that interact with the filesystem + * + * @author Benjamin Valentin + * + * @} + */ + +#include +#include +#include +#include + +#include "vfs_default.h" +#include "net/nanocoap_vfs.h" +#include "net/nanocoap_sock.h" + +struct dir_list_ctx { + char *buf; + char *cur; + char *end; +}; + +static bool _is_dir(const char *url) +{ + int len = strlen(url); + return url[len - 1] == '/'; +} + +static int _print_cb(void *arg, size_t offset, uint8_t *buf, size_t len, int more) +{ + (void)arg; + (void)offset; + + write(STDOUT_FILENO, buf, len); + if (!more) { + puts(""); + } + + return 0; +} + +static int _print_dir_cb(void *arg, size_t offset, uint8_t *buf, size_t len, int more) +{ + (void)offset; + (void)more; + + struct dir_list_ctx *ctx = arg; + + char *end = (char *)buf + len; + for (char *c = (char *)buf; c < end; ++c) { + if (ctx->cur) { + if (*c == '>' || ctx->cur == ctx->end) { + *ctx->cur = 0; + puts(ctx->buf); + ctx->cur = NULL; + } else { + *ctx->cur++ = *c; + } + } else if (*c == '<') { + ctx->cur = ctx->buf; + } + } + + return 0; +} + +static int _print_dir(const char *url, char *buf, size_t len) +{ + struct dir_list_ctx ctx = { + .buf = buf, + .end = buf + len, + }; + return nanocoap_get_blockwise_url(url, CONFIG_NANOCOAP_BLOCKSIZE_DEFAULT, + _print_dir_cb, &ctx); +} + +int _nanocoap_get_handler(int argc, char **argv) +{ + int res; + char buffer[CONFIG_NANOCOAP_QS_MAX]; + char *dst, *url = argv[1]; + + if (argc < 2) { + printf("Usage: %s [destination]\n", argv[0]); + printf("Default destination: %s\n", VFS_DEFAULT_DATA); + return -EINVAL; + } + + if (_is_dir(url) && argc < 3) { + res = _print_dir(url, buffer, sizeof(buffer)); + if (res) { + printf("Request failed: %s\n", strerror(-res)); + } + return res; + } + + if (argc < 3) { + dst = strrchr(url, '/'); + if (dst == NULL) { + printf("invalid url: '%s'\n", url); + return -EINVAL; + } + if (snprintf(buffer, sizeof(buffer), "%s%s", + VFS_DEFAULT_DATA, dst) >= (int)sizeof(buffer)) { + printf("Output file path too long\n"); + return -ENOBUFS; + } + dst = buffer; + } else { + dst = argv[2]; + } + + /* alternatively write the file to stdout */ + if (strcmp(dst, "-") == 0) { + return nanocoap_get_blockwise_url(url, CONFIG_NANOCOAP_BLOCKSIZE_DEFAULT, + _print_cb, NULL); + } + + res = nanocoap_vfs_get_url(url, dst); + if (res < 0) { + printf("Download failed: %s\n", strerror(-res)); + } else { + printf("Saved as %s\n", dst); + } + return res; +} diff --git a/sys/shell/commands/shell_commands.c b/sys/shell/commands/shell_commands.c index 1b82f5aeebb4..53b09da1206d 100644 --- a/sys/shell/commands/shell_commands.c +++ b/sys/shell/commands/shell_commands.c @@ -187,6 +187,10 @@ extern int _i2c_scan(int argc, char **argv); extern int _loramac_handler(int argc, char **argv); #endif +#ifdef MODULE_NANOCOAP_VFS +extern int _nanocoap_get_handler(int argc, char **argv); +#endif + #ifdef MODULE_NICE extern int _sc_nice(int argc, char **argv); #endif @@ -289,6 +293,9 @@ const shell_command_t _shell_command_list[] = { #ifdef MODULE_SHA256SUM {"sha256sum", "Compute and check SHA256 message digest", _vfs_sha256sum_cmd}, #endif +#ifdef MODULE_NANOCOAP_VFS + {"ncget", "download a file from a CoAP server", _nanocoap_get_handler}, +#endif #ifdef MODULE_GNRC_IPV6_NIB {"nib", "Configure neighbor information base", _gnrc_ipv6_nib}, #endif diff --git a/tests/nanocoap_cli/Makefile b/tests/nanocoap_cli/Makefile index d506127f124e..e964e76531a9 100644 --- a/tests/nanocoap_cli/Makefile +++ b/tests/nanocoap_cli/Makefile @@ -15,6 +15,24 @@ USEMODULE += gnrc_ipv6_default USEMODULE += nanocoap_sock +# boards where basic nanocoap functionality fits, but no VFS +LOW_MEMORY_BOARDS := atmega1284p atxmega-a3bu-xplained derfmega128 \ + saml11-xpro bluepill saml10-xpro blackpill nucleo-f302r8 \ + stm32mp157c-dk2 stm32f7508-dk + +# Don't enable VFS functions on small boards +ifeq (,$(filter $(BOARD),$(LOW_MEMORY_BOARDS))) + USEMODULE += nanocoap_vfs + USEMODULE += vfs_default + # USEMODULE += vfs_auto_format + USEMODULE += shell_commands + + # always enable auto-format for native + ifeq ($(BOARD),native) + USEMODULE += vfs_auto_format + endif +endif + # Required by test USEMODULE += od USEMODULE += shell