diff --git a/pkg/flashdb/mtd/fal_mtd_port.c b/pkg/flashdb/mtd/fal_mtd_port.c index 248cf01859..1163aced4f 100644 --- a/pkg/flashdb/mtd/fal_mtd_port.c +++ b/pkg/flashdb/mtd/fal_mtd_port.c @@ -17,14 +17,13 @@ * @] */ +#include "macros/utils.h" #include "mtd.h" #include #include #include -#define MIN(a, b) ((a) > (b) ? (b) : (a)) - static mtd_dev_t *_mtd; static int _read(long offset, uint8_t *buf, size_t size) diff --git a/sys/Makefile.dep b/sys/Makefile.dep index 5481327be9..00afb08a25 100644 --- a/sys/Makefile.dep +++ b/sys/Makefile.dep @@ -550,6 +550,12 @@ ifneq (,$(filter nanocoap_cache,$(USEMODULE))) USEMODULE += hashes endif +ifneq (,$(filter nanocoap_fs,$(USEMODULE))) + USEMODULE += nanocoap_sock + USEMODULE += nanocoap_link_format + USEMODULE += vfs +endif + ifneq (,$(filter nanocoap_link_format,$(USEMODULE))) USEMODULE += fmt endif diff --git a/sys/include/net/nanocoap/fs.h b/sys/include/net/nanocoap/fs.h new file mode 100644 index 0000000000..f100fec47c --- /dev/null +++ b/sys/include/net/nanocoap/fs.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2024 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 nanoCoAP virtual file system + * + * @{ + * + * @file + * + * @author Benjamin Valentin + */ +#ifndef NET_NANOCOAP_FS_H +#define NET_NANOCOAP_FS_H + +#include "mutex.h" +#include "net/nanocoap_sock.h" +#include "vfs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief nanoCoAP file system configuration + */ +typedef struct { + const char *url; /**< base URL of the remote fs */ + nanocoap_sock_t sock; /**< connection to the remote server */ + mutex_t lock; /**< lock for common urlbuf */ + char urlbuf[CONFIG_SOCK_URLPATH_MAXLEN]; /**< shared url buffer */ +} nanocoap_fs_t; + +/** + * @brief nanoCoAP remote file struct + */ +typedef struct { + uint32_t offset; /**< offset into the file */ + char urlbuf[CONFIG_SOCK_URLPATH_MAXLEN]; /**< full path to the file */ +} nanocoap_fs_file_t; + +/** + * @brief nanoCoAP remote dir struct + */ +typedef struct { + uint32_t offset; /**< current directory element */ + char urlbuf[CONFIG_SOCK_URLPATH_MAXLEN]; /**< full path of the directory */ +} nanocoap_fs_dir_t; + +/** + * @brief nanoCoAP file system driver + * + * For use with vfs_mount + */ +extern const vfs_file_system_t nanocoap_fs_file_system; + +#ifdef __cplusplus +} +#endif +#endif /* NET_NANOCOAP_FS_H */ +/** @} */ diff --git a/sys/include/net/nanocoap_sock.h b/sys/include/net/nanocoap_sock.h index 9af486c206..65ef78cdb4 100644 --- a/sys/include/net/nanocoap_sock.h +++ b/sys/include/net/nanocoap_sock.h @@ -589,6 +589,25 @@ int nanocoap_sock_get_blockwise(nanocoap_sock_t *sock, const char *path, coap_blksize_t blksize, coap_blockwise_cb_t callback, void *arg); +/** + * @brief Performs a blockwise coap get request to the specified url, store + * the response in a buffer. + * + * @param[in] sock socket to use for the request + * @param[in] path Absolute URL pointer to source path + * @param[in] blksize sender suggested SZX for the COAP block request + * @param[in] offset Offset in bytes from the start of the resource + * @param[in] dst Target buffer + * @param[in] len Target buffer length + * + * @returns <0 on error + * @returns -EINVAL if an invalid url is provided + * @returns size of the response payload on success + */ +int nanocoap_sock_get_slice(nanocoap_sock_t *sock, const char *path, + coap_blksize_t blksize, size_t offset, + void *dst, size_t len); + /** * @brief Performs a blockwise coap get request to the specified url. * diff --git a/sys/include/net/sock/config.h b/sys/include/net/sock/config.h new file mode 100644 index 0000000000..33b4dffd6e --- /dev/null +++ b/sys/include/net/sock/config.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser + * 2018 Freie Universität Berlin + * + * 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. + */ + +/** + * @defgroup net_sock_util_conf SOCK utility functions compile configurations + * @ingroup net_sock_conf + * @{ + * + * @brief sock utility configuration values + * + * @author Kaspar Schleiser + * @author Hauke Petersen + */ + +#ifndef NET_SOCK_CONFIG_H +#define NET_SOCK_CONFIG_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief maximum length of the scheme part for sock_urlsplit. + * + * Ensures a hard limit on the string iterator + * */ +#ifndef CONFIG_SOCK_SCHEME_MAXLEN +#define CONFIG_SOCK_SCHEME_MAXLEN (16U) +#endif + +/** + * @brief maximum length of host:port part for sock_urlsplit() + */ +#ifndef CONFIG_SOCK_HOSTPORT_MAXLEN +#define CONFIG_SOCK_HOSTPORT_MAXLEN (64U) +#endif + +/** + * @brief maximum length path for sock_urlsplit() + */ +#ifndef CONFIG_SOCK_URLPATH_MAXLEN +#define CONFIG_SOCK_URLPATH_MAXLEN (64U) +#endif + +/** + * @brief Timeout in milliseconds for sock_dtls_establish_session() + */ +#ifndef CONFIG_SOCK_DTLS_TIMEOUT_MS +#define CONFIG_SOCK_DTLS_TIMEOUT_MS (1000U) +#endif + +/** + * @brief Number of DTLS handshake retries for sock_dtls_establish_session() + */ +#ifndef CONFIG_SOCK_DTLS_RETRIES +#define CONFIG_SOCK_DTLS_RETRIES (2) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* NET_SOCK_CONFIG_H */ +/** @} */ diff --git a/sys/include/net/sock/util.h b/sys/include/net/sock/util.h index 1ff424da29..7d32db6892 100644 --- a/sys/include/net/sock/util.h +++ b/sys/include/net/sock/util.h @@ -30,6 +30,7 @@ #include "net/sock/udp.h" #include "net/sock/tcp.h" +#include "net/sock/config.h" #ifdef MODULE_SOCK_DTLS #include "net/credman.h" @@ -296,49 +297,6 @@ int sock_dtls_establish_session(sock_udp_t *sock_udp, sock_dtls_t *sock_dtls, void *work_buf, size_t work_buf_len); #endif -/** - * @defgroup net_sock_util_conf SOCK utility functions compile configurations - * @ingroup net_sock_conf - * @{ - */ -/** - * @brief maximum length of the scheme part for sock_urlsplit. - * - * Ensures a hard limit on the string iterator - * */ -#ifndef CONFIG_SOCK_SCHEME_MAXLEN -#define CONFIG_SOCK_SCHEME_MAXLEN (16U) -#endif - -/** - * @brief maximum length of host:port part for sock_urlsplit() - */ -#ifndef CONFIG_SOCK_HOSTPORT_MAXLEN -#define CONFIG_SOCK_HOSTPORT_MAXLEN (64U) -#endif - -/** - * @brief maximum length path for sock_urlsplit() - */ -#ifndef CONFIG_SOCK_URLPATH_MAXLEN -#define CONFIG_SOCK_URLPATH_MAXLEN (64U) -#endif -/** @} */ - -/** - * @brief Timeout in milliseconds for sock_dtls_establish_session() - */ -#ifndef CONFIG_SOCK_DTLS_TIMEOUT_MS -#define CONFIG_SOCK_DTLS_TIMEOUT_MS (1000U) -#endif - -/** - * @brief Number of DTLS handshake retries for sock_dtls_establish_session() - */ -#ifndef CONFIG_SOCK_DTLS_RETRIES -#define CONFIG_SOCK_DTLS_RETRIES (2) -#endif - #ifdef __cplusplus } #endif diff --git a/sys/include/vfs.h b/sys/include/vfs.h index dd9df980ef..6aa7123da4 100644 --- a/sys/include/vfs.h +++ b/sys/include/vfs.h @@ -61,7 +61,11 @@ #include "sched.h" #include "clist.h" #include "iolist.h" +#include "macros/utils.h" #include "mtd.h" +#ifdef MODULE_NANOCOAP_FS +#include "net/sock/config.h" +#endif #include "xfa.h" #ifdef __cplusplus @@ -75,21 +79,12 @@ extern "C" { #endif /** - * @brief MAX functions for internal use - * @{ + * @brief MAX6 Function to get the largest of 6 values */ -#ifndef _MAX -#define _MAX(a, b) ((a) > (b) ? (a) : (b)) +#ifndef MAX6 +#define MAX6(a, b, c, d, e, f) MAX(MAX(MAX(MAX((a), (b)), MAX((c), (d))), (e)), (f)) #endif -#ifndef MAX5 -/** - * @brief MAX5 Function to get the largest of 5 values - */ -#define MAX5(a, b, c, d, e) _MAX(_MAX(_MAX((a), (b)), _MAX((c),(d))), (e)) -#endif -/** @} */ - /** * @brief VFS parameters for FAT * @{ @@ -208,6 +203,21 @@ extern "C" { #endif /** @} */ +/** + * @brief VFS parameters for nanoCoAP FS + * @{ + */ +#if defined(MODULE_NANOCOAP_FS) || DOXYGEN +# define NANOCOAP_FS_VFS_DIR_BUFFER_SIZE \ + (4 + CONFIG_SOCK_URLPATH_MAXLEN) /**< sizeof(nanocoap_fs_dir_t) */ +# define NANOCOAP_FS_VFS_FILE_BUFFER_SIZE \ + (4 + CONFIG_SOCK_URLPATH_MAXLEN) /**< sizeof(nanocoap_fs_file_t) */ +#else +# define NANOCOAP_FS_VFS_DIR_BUFFER_SIZE (1) +# define NANOCOAP_FS_VFS_FILE_BUFFER_SIZE (1) +#endif +/** @} */ + #ifndef VFS_MAX_OPEN_FILES /** * @brief Maximum number of simultaneous open files @@ -243,11 +253,12 @@ extern "C" { * @attention Put the check in the public header file (.h), do not put the check in the * implementation (.c) file. */ -#define VFS_DIR_BUFFER_SIZE MAX5(FATFS_VFS_DIR_BUFFER_SIZE, \ - LITTLEFS_VFS_DIR_BUFFER_SIZE, \ - LITTLEFS2_VFS_DIR_BUFFER_SIZE, \ - SPIFFS_VFS_DIR_BUFFER_SIZE, \ - LWEXT4_VFS_DIR_BUFFER_SIZE \ +#define VFS_DIR_BUFFER_SIZE MAX6(FATFS_VFS_DIR_BUFFER_SIZE, \ + LITTLEFS_VFS_DIR_BUFFER_SIZE, \ + LITTLEFS2_VFS_DIR_BUFFER_SIZE, \ + SPIFFS_VFS_DIR_BUFFER_SIZE, \ + LWEXT4_VFS_DIR_BUFFER_SIZE, \ + NANOCOAP_FS_VFS_DIR_BUFFER_SIZE \ ) #endif @@ -271,11 +282,12 @@ extern "C" { * @attention Put the check in the public header file (.h), do not put the check in the * implementation (.c) file. */ -#define VFS_FILE_BUFFER_SIZE MAX5(FATFS_VFS_FILE_BUFFER_SIZE, \ - LITTLEFS_VFS_FILE_BUFFER_SIZE, \ - LITTLEFS2_VFS_FILE_BUFFER_SIZE,\ - SPIFFS_VFS_FILE_BUFFER_SIZE, \ - LWEXT4_VFS_FILE_BUFFER_SIZE \ +#define VFS_FILE_BUFFER_SIZE MAX6(FATFS_VFS_FILE_BUFFER_SIZE, \ + LITTLEFS_VFS_FILE_BUFFER_SIZE, \ + LITTLEFS2_VFS_FILE_BUFFER_SIZE, \ + SPIFFS_VFS_FILE_BUFFER_SIZE, \ + LWEXT4_VFS_FILE_BUFFER_SIZE, \ + NANOCOAP_FS_VFS_FILE_BUFFER_SIZE \ ) #endif diff --git a/sys/net/application_layer/nanocoap/fileserver.c b/sys/net/application_layer/nanocoap/fileserver.c index 50ec708d61..a2965a14b0 100644 --- a/sys/net/application_layer/nanocoap/fileserver.c +++ b/sys/net/application_layer/nanocoap/fileserver.c @@ -61,6 +61,7 @@ struct requestoptions { bool etag :1; /**< Request carries an Etag option */ bool block2 :1; /**< Request carries a Block2 option */ bool block1 :1; /**< Request carries a Block1 option */ + bool size2 :1; /**< Request carries a Size2 option */ } exists; /**< Structure holding flags of present request options */ }; @@ -204,13 +205,15 @@ static ssize_t _get_file(coap_pkt_t *pdu, uint8_t *buf, size_t len, struct requestdata *request) { int err; - uint32_t etag; + uint32_t etag, size_total; + coap_block1_t block2 = { .szx = CONFIG_NANOCOAP_BLOCK_SIZE_EXP_MAX }; { struct stat stat; if ((err = vfs_stat(request->namebuf, &stat)) < 0) { return _error_handler(pdu, buf, len, err); } + size_total = stat.st_size; stat_etag(&stat, &etag); } if (request->options.exists.block2 && !coap_get_block2(pdu, &block2)) { @@ -240,6 +243,11 @@ static ssize_t _get_file(coap_pkt_t *pdu, uint8_t *buf, size_t len, &block2); coap_block_slicer_init(&slicer, block2.blknum, coap_szx2size(block2.szx)); coap_opt_add_block2(pdu, &slicer, true); + + if (request->options.exists.block2) { + coap_opt_add_uint(pdu, COAP_OPT_SIZE2, size_total); + } + size_t resp_len = coap_opt_finish(pdu, COAP_OPT_FINISH_PAYLOAD); err = vfs_lseek(fd, slicer.start, SEEK_SET); @@ -692,6 +700,9 @@ ssize_t nanocoap_fileserver_handler(coap_pkt_t *pdu, uint8_t *buf, size_t len, } request.options.exists.block1 = true; break; + case COAP_OPT_SIZE2: + request.options.exists.size2 = true; + break; default: if (opt.opt_num & 1) { errorcode = COAP_CODE_BAD_OPTION; diff --git a/sys/net/application_layer/nanocoap/fs.c b/sys/net/application_layer/nanocoap/fs.c new file mode 100644 index 0000000000..090aceaeb2 --- /dev/null +++ b/sys/net/application_layer/nanocoap/fs.c @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2024 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 backend + * + * @author Benjamin Valentin + * + * @} + */ + +#include + +#include "net/nanocoap_sock.h" +#include "net/nanocoap/fs.h" +#include "net/nanocoap/link_format.h" +#include "net/sock/util.h" +#include "string_utils.h" +#include "vfs.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +static int nanocoap_fs_mount(vfs_mount_t *mountp) +{ + nanocoap_fs_t *fs = mountp->private_data; + + static_assert(VFS_FILE_BUFFER_SIZE >= sizeof(nanocoap_fs_file_t), + "nanocoap_fs_file_t must fit into VFS_FILE_BUFFER_SIZE"); + static_assert(VFS_DIR_BUFFER_SIZE >= sizeof(nanocoap_fs_dir_t), + "nanocoap_fs_dir_t must fit into VFS_DIR_BUFFER_SIZE"); + + return nanocoap_sock_url_connect(fs->url, &fs->sock); +} + +static int nanocoap_fs_umount(vfs_mount_t *mountp) +{ + nanocoap_fs_t *fs = mountp->private_data; + + nanocoap_sock_close(&fs->sock); + return 0; +} + +static int _fill_urlbuf(nanocoap_fs_t *fs, char dst[CONFIG_SOCK_URLPATH_MAXLEN], + const char *name, bool dir) +{ + name += 1; /* skip leading '/' */ + + const char *extra = ""; + if (dir) { + const char *end = name + strlen(name) - 1; + if (*end != '/') { + extra = "/"; + } + } + + if ((unsigned)snprintf(dst, CONFIG_SOCK_URLPATH_MAXLEN, "%s%s%s", + sock_urlpath(fs->url), name, extra) > CONFIG_SOCK_URLPATH_MAXLEN) { + DEBUG("nanocoap_fs: %s%s > %u bytes\n", + sock_urlpath(fs->url), name, CONFIG_SOCK_URLPATH_MAXLEN); + return -ENOBUFS; + } + + return 0; +} + +static int nanocoap_fs_unlink(vfs_mount_t *mountp, const char *name) +{ + nanocoap_fs_t *fs = mountp->private_data; + int res; + + mutex_lock(&fs->lock); + + if ((res = _fill_urlbuf(fs, fs->urlbuf, name, true))) { + goto out; + } + + DEBUG("nanocoap_fs: delete %s\n", fs->urlbuf); + res = nanocoap_sock_delete(&fs->sock, fs->urlbuf); + +out: + mutex_unlock(&fs->lock); + return res; +} + +static int nanocoap_fs_open(vfs_file_t *filp, const char *name, int flags, mode_t mode) +{ + nanocoap_fs_t *fs = filp->mp->private_data; + nanocoap_fs_file_t *file = (void *)filp->private_data.buffer; + int res; + + (void)mode; + + if (flags != O_RDONLY) { + /* so far only read is implemented */ + return -ENOSYS; + } + + if ((res = _fill_urlbuf(fs, file->urlbuf, name, false))) { + return res; + } + + DEBUG("nanocoap_fs: open %s\n", file->urlbuf); + + file->offset = 0; + + return 0; +} + +static ssize_t nanocoap_fs_read(vfs_file_t *filp, void *dest, size_t nbytes) +{ + nanocoap_fs_t *fs = filp->mp->private_data; + nanocoap_fs_file_t *file = (void *)filp->private_data.buffer; + int res; + + DEBUG("nanocoap_fs: read %"PRIuSIZE" bytes\n", nbytes); + + res = nanocoap_sock_get_slice(&fs->sock, file->urlbuf, CONFIG_NANOCOAP_BLOCKSIZE_DEFAULT, + file->offset, dest, nbytes); + if (res > 0) { + file->offset += res; + } + + return res; +} + +static off_t nanocoap_fs_lseek(vfs_file_t *filp, off_t off, int whence) +{ + nanocoap_fs_file_t *file = (void *)filp->private_data.buffer; + + switch (whence) { + case SEEK_SET: + file->offset = off; + break; + case SEEK_CUR: + file->offset += off; + break; + case SEEK_END: + return -ENOSYS; + } + + return file->offset; +} + +static int _block_cb(void *arg, coap_pkt_t *pkt) +{ + struct stat *restrict buf = arg; + + if (coap_get_code_class(pkt) != COAP_CLASS_SUCCESS) { + return -ENXIO; + } + + buf->st_mode = S_IFREG; + + uint32_t size = 0; + coap_opt_get_uint(pkt, COAP_OPT_SIZE2, &size); + buf->st_size = size; + + return 0; +} + +static int _query_server(nanocoap_sock_t *sock, const char *path, + struct stat *restrict arg) +{ + uint8_t _buf[CONFIG_NANOCOAP_BLOCK_HEADER_MAX]; + uint8_t *buf = _buf; + + coap_pkt_t pkt = { + .hdr = (void *)buf, + }; + + uint16_t lastonum = 0; + + buf += coap_build_hdr(pkt.hdr, COAP_TYPE_CON, NULL, 0, COAP_METHOD_GET, + nanocoap_sock_next_msg_id(sock)); + buf += coap_opt_put_uri_pathquery(buf, &lastonum, path); + buf += coap_opt_put_uint(buf, lastonum, COAP_OPT_BLOCK2, 0); + buf += coap_opt_put_uint(buf, COAP_OPT_BLOCK2, COAP_OPT_SIZE2, 0); + + assert((uintptr_t)buf - (uintptr_t)pkt.hdr < sizeof(_buf)); + + pkt.payload = buf; + pkt.payload_len = 0; + + return nanocoap_sock_request_cb(sock, &pkt, _block_cb, arg); +} + +static int nanocoap_fs_stat(vfs_mount_t *mountp, const char *restrict path, + struct stat *restrict buf) +{ + nanocoap_fs_t *fs = mountp->private_data; + int res = 0; + + mutex_lock(&fs->lock); + + if ((res = _fill_urlbuf(fs, fs->urlbuf, path, false))) { + goto out; + } + + uint8_t dummy[4]; + if (_query_server(&fs->sock, fs->urlbuf, buf) < 0) { + if ((res = _fill_urlbuf(fs, fs->urlbuf, path, true))) { + goto out; + } + + if (nanocoap_sock_get_slice(&fs->sock, fs->urlbuf, COAP_BLOCKSIZE_16, + 0, dummy, sizeof(dummy)) >= 0) { + buf->st_mode = S_IFDIR; + } + } + +out: + mutex_unlock(&fs->lock); + return res; +} + +static int nanocoap_fs_opendir(vfs_DIR *dirp, const char *dirname) +{ + nanocoap_fs_t *fs = dirp->mp->private_data; + nanocoap_fs_dir_t *dir = (void *)dirp->private_data.buffer; + int res; + + if ((res = _fill_urlbuf(fs, dir->urlbuf, dirname, true))) { + return res; + } + + DEBUG("nanocoap_fs: opendir(%s)\n", dir->urlbuf); + + dir->offset = 0; + + return 0; +} + +struct _dir_ctx { + vfs_dirent_t *dirent; + size_t offset; +}; + +static int _dir_cb(char *entry, void *arg) +{ + struct _dir_ctx *ctx = arg; + + if (ctx->offset) { + --ctx->offset; + return 0; + } + + char *start = strchr(entry, '<'); + if (start) { + char *end = strchr(entry, '>'); + *end = '\0'; + entry = start + 1; + } + + char *end = entry + strlen(entry) - 1; + if (*end == '/') { + *end = '\0'; + } + + char *basename = strrchr(entry, '/'); + if (basename) { + entry = basename + 1; + } + + strscpy(ctx->dirent->d_name, entry, sizeof(ctx->dirent->d_name)); + return -EINTR; +} + +static int nanocoap_fs_readdir(vfs_DIR *dirp, vfs_dirent_t *entry) +{ + nanocoap_fs_t *fs = dirp->mp->private_data; + nanocoap_fs_dir_t *dir = (void *)dirp->private_data.buffer; + int res; + + entry->d_ino = dir->offset; + + struct _dir_ctx ctx = { + .dirent = entry, + .offset = dir->offset++, + }; + + res = nanocoap_link_format_get(&fs->sock, dir->urlbuf, _dir_cb, &ctx); + if (res == -EINTR) { + /* we use this to abort listing early */ + res = 1; + } + + return res; +} + +static const vfs_file_system_ops_t nanocoap_fs_ops = { + .stat = nanocoap_fs_stat, + .mount = nanocoap_fs_mount, + .umount = nanocoap_fs_umount, + .unlink = nanocoap_fs_unlink, +}; + +static const vfs_file_ops_t nanocoap_fs_file_ops = { + .lseek = nanocoap_fs_lseek, + .open = nanocoap_fs_open, + .read = nanocoap_fs_read, +}; + +static const vfs_dir_ops_t nanocoap_fs_dir_ops = { + .opendir = nanocoap_fs_opendir, + .readdir = nanocoap_fs_readdir, +}; + +const vfs_file_system_t nanocoap_fs_file_system = { + .f_op = &nanocoap_fs_file_ops, + .fs_op = &nanocoap_fs_ops, + .d_op = &nanocoap_fs_dir_ops, +}; diff --git a/sys/net/application_layer/nanocoap/sock.c b/sys/net/application_layer/nanocoap/sock.c index 157ee3097e..501b47092a 100644 --- a/sys/net/application_layer/nanocoap/sock.c +++ b/sys/net/application_layer/nanocoap/sock.c @@ -722,6 +722,107 @@ int nanocoap_sock_get_blockwise(nanocoap_sock_t *sock, const char *path, return 0; } +typedef struct { + uint8_t *ptr; + size_t len; + size_t offset; + size_t res; +} _buf_slice_t; + +static int _2buf_slice(void *arg, size_t offset, uint8_t *buf, size_t len, int more) +{ + _buf_slice_t *ctx = arg; + + if (offset + len < ctx->offset) { + return 0; + } + + if (offset > ctx->offset + ctx->len) { + return 0; + } + + if (!ctx->len) { + return 0; + } + + offset = ctx->offset - offset; + len = MIN(len - offset, ctx->len); + + memcpy(ctx->ptr, buf + offset, len); + + ctx->len -= len; + ctx->ptr += len; + ctx->offset += len; + ctx->res += len; + + DEBUG("nanocoap: got %"PRIuSIZE" bytes, %"PRIuSIZE" bytes left (offset: %"PRIuSIZE")\n", + len, ctx->len, offset); + + if (!more) { + ctx->len = 0; + } + + return 0; +} + +static unsigned _num_blks(size_t offset, size_t len, coap_blksize_t szx) +{ + uint16_t mask = coap_szx2size(szx) - 1; + uint8_t shift = szx + 4; + size_t end = offset + len; + + unsigned num_blks = ((end >> shift) + !!(end & mask)) + - ((offset >> shift) + !!(offset & mask)); + return num_blks; +} + +int nanocoap_sock_get_slice(nanocoap_sock_t *sock, const char *path, + coap_blksize_t blksize, size_t offset, + void *dst, size_t len) +{ + uint8_t buf[CONFIG_NANOCOAP_BLOCK_HEADER_MAX]; + + /* try to find optimal blocksize */ + unsigned num_blocks = _num_blks(offset, len, blksize); + for (uint8_t szx = 0; szx < blksize; ++szx) { + if (_num_blks(offset, len, szx) <= num_blocks) { + blksize = szx; + break; + } + } + + _buf_slice_t dst_ctx = { + .ptr = dst, + .len = len, + .offset = offset, + }; + + _block_ctx_t ctx = { + .callback = _2buf_slice, + .arg = &dst_ctx, + .more = true, + }; + +#if CONFIG_NANOCOAP_SOCK_BLOCK_TOKEN + random_bytes(ctx.token, sizeof(ctx.token)); +#endif + + unsigned num = offset >> (blksize + 4); + while (dst_ctx.len) { + DEBUG("nanocoap: fetching block %u\n", num); + + int res = _fetch_block(sock, buf, sizeof(buf), path, blksize, num, &ctx); + if (res < 0) { + DEBUG("nanocoap: error fetching block %u: %d\n", num, res); + return res; + } + + num += 1; + } + + return dst_ctx.res; +} + int nanocoap_sock_url_connect(const char *url, nanocoap_sock_t *sock) { char hostport[CONFIG_SOCK_HOSTPORT_MAXLEN]; diff --git a/sys/net/gnrc/sock/gnrc_sock.c b/sys/net/gnrc/sock/gnrc_sock.c index 28d579fa95..2600e97c62 100644 --- a/sys/net/gnrc/sock/gnrc_sock.c +++ b/sys/net/gnrc/sock/gnrc_sock.c @@ -19,6 +19,7 @@ #include "compiler_hints.h" #include "log.h" +#include "macros/math.h" #include "net/af.h" #include "net/gnrc/ipv6.h" #include "net/gnrc/ipv6/hdr.h" @@ -27,7 +28,7 @@ #include "net/ipv6/hdr.h" #include "net/udp.h" #include "utlist.h" -#if IS_USED(MODULE_ZTIMER_USEC) +#if IS_USED(MODULE_ZTIMER_USEC) || IS_USED(MODULE_ZTIMER_MSEC) #include "ztimer.h" #endif #if IS_USED(MODULE_XTIMER) @@ -42,7 +43,7 @@ extern gnrc_pktsnip_t *gnrc_pktbuf_fuzzptr; gnrc_pktsnip_t *gnrc_sock_prevpkt = NULL; #endif -#if IS_USED(MODULE_XTIMER) || IS_USED(MODULE_ZTIMER_USEC) +#if IS_USED(MODULE_XTIMER) || IS_USED(MODULE_ZTIMER_USEC) || IS_USED(MODULE_ZTIMER_MSEC) #define _TIMEOUT_MAGIC (0xF38A0B63U) #define _TIMEOUT_MSG_TYPE (0x8474) @@ -127,6 +128,13 @@ ssize_t gnrc_sock_recv(gnrc_sock_reg_t *reg, gnrc_pktsnip_t **pkt_out, timeout_timer.arg = reg; ztimer_set(ZTIMER_USEC, &timeout_timer, timeout); } +#elif IS_USED(MODULE_ZTIMER_MSEC) + ztimer_t timeout_timer = { .base = { .next = NULL } }; + if ((timeout != SOCK_NO_TIMEOUT) && (timeout != 0)) { + timeout_timer.callback = _callback_put; + timeout_timer.arg = reg; + ztimer_set(ZTIMER_MSEC, &timeout_timer, DIV_ROUND_INF(timeout, US_PER_MS)); + } #elif IS_USED(MODULE_XTIMER) xtimer_t timeout_timer = { .callback = NULL }; @@ -162,6 +170,8 @@ ssize_t gnrc_sock_recv(gnrc_sock_reg_t *reg, gnrc_pktsnip_t **pkt_out, } #if IS_USED(MODULE_ZTIMER_USEC) ztimer_remove(ZTIMER_USEC, &timeout_timer); +#elif IS_USED(MODULE_ZTIMER_MSEC) + ztimer_remove(ZTIMER_MSEC, &timeout_timer); #elif IS_USED(MODULE_XTIMER) xtimer_remove(&timeout_timer); #endif @@ -169,7 +179,7 @@ ssize_t gnrc_sock_recv(gnrc_sock_reg_t *reg, gnrc_pktsnip_t **pkt_out, case GNRC_NETAPI_MSG_TYPE_RCV: pkt = msg.content.ptr; break; -#if IS_USED(MODULE_XTIMER) || IS_USED(MODULE_ZTIMER_USEC) +#if IS_USED(MODULE_XTIMER) || IS_USED(MODULE_ZTIMER_USEC) || IS_USED(MODULE_ZTIMER_MSEC) case _TIMEOUT_MSG_TYPE: if (msg.content.value == _TIMEOUT_MAGIC) { return -ETIMEDOUT; diff --git a/sys/shell/cmds/vfs.c b/sys/shell/cmds/vfs.c index 5fe7375f6e..a7b7ca7643 100644 --- a/sys/shell/cmds/vfs.c +++ b/sys/shell/cmds/vfs.c @@ -647,14 +647,17 @@ static int _ls_handler(int argc, char **argv) snprintf(path_name, sizeof(path_name), "%s/%s", path, entry.d_name); vfs_stat(path_name, &stat); + + printf("%s", entry.d_name); if (stat.st_mode & S_IFDIR) { - printf("%s/\n", entry.d_name); + printf("/"); } else if (stat.st_mode & S_IFREG) { - printf("%s\t%lu B\n", entry.d_name, stat.st_size); + if (stat.st_size) { + printf("\t%lu B", stat.st_size); + } ++nfiles; - } else { - printf("%s\n", entry.d_name); } + puts(""); } if (ret == 0) { printf("total %u files\n", nfiles); diff --git a/tests/net/nanocoap_fs/Makefile b/tests/net/nanocoap_fs/Makefile new file mode 100644 index 0000000000..4625e08672 --- /dev/null +++ b/tests/net/nanocoap_fs/Makefile @@ -0,0 +1,22 @@ +include ../Makefile.net_common + +# Include packages that pull up and auto-init the link layer. +# NOTE: 6LoWPAN will be included if IEEE802.15.4 devices are present +USEMODULE += netdev_default +USEMODULE += auto_init_gnrc_netif +# Specify the mandatory networking modules +USEMODULE += gnrc_ipv6_default + +# Optionally include DNS support. This includes resolution of names at an +# upstream DNS server and the handling of RDNSS options in Router Advertisements +# to auto-configure that upstream DNS server. +# USEMODULE += sock_dns # include DNS client +# USEMODULE += gnrc_ipv6_nib_dns # include RDNSS option handling + +USEMODULE += nanocoap_fs + +# Required by test +USEMODULE += shell +USEMODULE += shell_cmds_default + +include $(RIOTBASE)/Makefile.include diff --git a/tests/net/nanocoap_fs/Makefile.ci b/tests/net/nanocoap_fs/Makefile.ci new file mode 100644 index 0000000000..f2aea054bb --- /dev/null +++ b/tests/net/nanocoap_fs/Makefile.ci @@ -0,0 +1,38 @@ +BOARD_INSUFFICIENT_MEMORY := \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-mega2560 \ + arduino-nano \ + arduino-uno \ + atmega328p \ + atmega328p-xplained-mini \ + atmega8 \ + atxmega-a3bu-xplained \ + bluepill-stm32f030c8 \ + i-nucleo-lrwan1 \ + msb-430 \ + msb-430h \ + nucleo-c031c6 \ + nucleo-f030r8 \ + nucleo-f031k6 \ + nucleo-f042k6 \ + nucleo-f303k8 \ + nucleo-f334r8 \ + nucleo-l011k4 \ + nucleo-l031k6 \ + nucleo-l053r8 \ + olimex-msp430-h1611 \ + olimex-msp430-h2618 \ + samd10-xmini \ + slstk3400a \ + stk3200 \ + stm32f030f4-demo \ + stm32f0discovery \ + stm32f7508-dk \ + stm32g0316-disco \ + stm32l0538-disco \ + telosb \ + waspmote-pro \ + weact-g030f6 \ + z1 \ + # diff --git a/tests/net/nanocoap_fs/README.md b/tests/net/nanocoap_fs/README.md new file mode 100644 index 0000000000..54af4b27d4 --- /dev/null +++ b/tests/net/nanocoap_fs/README.md @@ -0,0 +1,11 @@ +# nanoCoAP remote fs example + +This allows to mount a remote fs that was exported via e.g. `nanocoap_fileserver` +or `aiocoap-fileserver`. + +The test provides a `mount` command to mount a remote fs at a local mount point: + + mount coap://[fe80::607f:b1ff:fef7:689c]/vfs /coap + +This will mount the `vfs/` resource on the remote server as a local `/coap/` +directory. This can then be interacted with normal `vfs` commands such as `ls`. diff --git a/tests/net/nanocoap_fs/main.c b/tests/net/nanocoap_fs/main.c new file mode 100644 index 0000000000..87ac88f18e --- /dev/null +++ b/tests/net/nanocoap_fs/main.c @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2024 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 tests + * @{ + * + * @file + * @brief nanoCoAP fs test app + * + * @author Benjamin Valentin + * + * @} + */ + +#include +#include "msg.h" + +#include "net/nanocoap/fs.h" +#include "string_utils.h" +#include "shell.h" + +#define MAIN_QUEUE_SIZE (4) +static msg_t _main_msg_queue[MAIN_QUEUE_SIZE]; + +static int _cmd_mount(int argc, char **argv) +{ + static char url[64]; + static char mp[64]; + int res; + + if (argc != 3) { + printf("usage: %s \n", argv[0]); + return 1; + } + + if ((res = strscpy(mp, argv[2], sizeof(mp))) < 0) { + return res; + } + if ((res = strscpy(url, argv[1], sizeof(url))) < 0) { + return res; + } + + if (url[res] != '/') { + url[res] = '/'; + if (res == sizeof(url)) { + return -ENOBUFS; + } + url[++res] = 0; + } + + static nanocoap_fs_t nanocoap_fs_desc = { + .url = url, + }; + + static vfs_mount_t _mount = { + .fs = &nanocoap_fs_file_system, + .mount_point = mp, + .private_data = &nanocoap_fs_desc, + }; + + res = vfs_mount(&_mount); + + return res; +} + +static const shell_command_t shell_commands[] = { + { "mount", "Mount a remote fs", _cmd_mount }, + { NULL, NULL, NULL } +}; + +int main(void) +{ + /* for the thread running the shell */ + msg_init_queue(_main_msg_queue, MAIN_QUEUE_SIZE); + + /* start shell */ + puts("All up, running the shell now"); + char line_buf[SHELL_DEFAULT_BUFSIZE]; + shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE); + + /* should never be reached */ + return 0; +} diff --git a/tests/periph/hwrng/main.c b/tests/periph/hwrng/main.c index df5967006d..4949bd459d 100644 --- a/tests/periph/hwrng/main.c +++ b/tests/periph/hwrng/main.c @@ -24,22 +24,22 @@ #include "xtimer.h" #include "periph/hwrng.h" -#define LIMIT (20U) +#define NUM_BYTES (20U) int main(void) { - uint8_t buf[LIMIT]; + uint8_t buf[NUM_BYTES]; puts("\nHWRNG peripheral driver test\n"); printf("This test will print from 1 to %u random bytes about every " - "second\n\n", LIMIT); + "second\n\n", NUM_BYTES); while (1) { /* zero out buffer */ memset(buf, 0, sizeof(buf)); /* create random numbers */ - for (unsigned i = 1; i <= LIMIT; i++) { + for (unsigned i = 1; i <= NUM_BYTES; i++) { printf("generating %u random byte(s)\n", i); hwrng_read(buf, i);