diff --git a/examples/gcoap_fileserver/Makefile b/examples/gcoap_fileserver/Makefile index 96a6aac045cc..3b3e0caed4bd 100644 --- a/examples/gcoap_fileserver/Makefile +++ b/examples/gcoap_fileserver/Makefile @@ -21,6 +21,7 @@ USEMODULE += shell_commands # enable the fileserver module USEMODULE += gcoap_fileserver +USEMODULE += gcoap_fileserver_callback USEMODULE += gcoap_fileserver_delete USEMODULE += gcoap_fileserver_put diff --git a/examples/gcoap_fileserver/main.c b/examples/gcoap_fileserver/main.c index dd081e5e9f5d..e736315726b5 100644 --- a/examples/gcoap_fileserver/main.c +++ b/examples/gcoap_fileserver/main.c @@ -17,6 +17,7 @@ * @} */ +#include #include "kernel_defines.h" #include "net/gcoap.h" #include "net/gcoap/fileserver.h" @@ -45,11 +46,36 @@ static gcoap_listener_t _listener = { .resources_len = ARRAY_SIZE(_resources), }; +static void _event_cb(gcoap_fileserver_event_t event, gcoap_fileserver_event_ctx_t *ctx) +{ + switch (event) { + case GCOAP_FILESERVER_GET_FILE_START: + printf("gcoap fileserver: Download started: %s\n", ctx->path); + break; + case GCOAP_FILESERVER_GET_FILE_END: + printf("gcoap fileserver: Download finished: %s\n", ctx->path); + break; + case GCOAP_FILESERVER_PUT_FILE_START: + printf("gcoap fileserver: Upload started: %s\n", ctx->path); + break; + case GCOAP_FILESERVER_PUT_FILE_END: + printf("gcoap fileserver: Upload finished: %s\n", ctx->path); + break; + case GCOAP_FILESERVER_DELETE_FILE: + printf("gcoap fileserver: Delete %s\n", ctx->path); + break; + } +} + int main(void) { msg_init_queue(_main_msg_queue, MAIN_QUEUE_SIZE); gcoap_register_listener(&_listener); + if (IS_USED(MODULE_GCOAP_FILESERVER_CALLBACK)) { + gcoap_fileserver_set_event_cb(_event_cb, NULL); + } + char line_buf[SHELL_DEFAULT_BUFSIZE]; shell_run(NULL, line_buf, SHELL_DEFAULT_BUFSIZE); diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 8b05650bca5b..9457c3f6a585 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -67,6 +67,7 @@ PSEUDOMODULES += fatfs_vfs_format PSEUDOMODULES += fmt_% PSEUDOMODULES += gcoap_forward_proxy PSEUDOMODULES += gcoap_fileserver +PSEUDOMODULES += gcoap_fileserver_callback PSEUDOMODULES += gcoap_fileserver_delete PSEUDOMODULES += gcoap_fileserver_put PSEUDOMODULES += gcoap_dtls diff --git a/sys/include/net/gcoap/fileserver.h b/sys/include/net/gcoap/fileserver.h index 02a19ef95b11..cd0914f34fa5 100644 --- a/sys/include/net/gcoap/fileserver.h +++ b/sys/include/net/gcoap/fileserver.h @@ -90,6 +90,51 @@ extern "C" { */ #define COAPFILESERVER_DIR_DELETE_ETAG (0x6ce88b56u) +/** + * @brief GCoAP fileserver event types + * + * @note This requires the gcoap_fileserver_callback module. + */ +typedef enum { + GCOAP_FILESERVER_GET_FILE_START, /**< file download started */ + GCOAP_FILESERVER_GET_FILE_END, /**< file download finished */ + GCOAP_FILESERVER_PUT_FILE_START, /**< file upload started */ + GCOAP_FILESERVER_PUT_FILE_END, /**< file upload finished */ + GCOAP_FILESERVER_DELETE_FILE, /**< file deletion requested + (called before file is deleted) */ +} gcoap_fileserver_event_t; + +/** + * @brief GCoAP fileserver event context + */ +typedef struct { + const char *path; /**< VFS path of the affected file */ + void *user_ctx; /**< Optional user supplied context */ +} gcoap_fileserver_event_ctx_t; + +/** + * @brief GCoAP fileserver event callback type + * + * @param[in] event Type of the event + * @param[in] ctx Event context information + * + */ +typedef void (*gcoap_fileserver_event_handler_t)(gcoap_fileserver_event_t event, + gcoap_fileserver_event_ctx_t *ctx); + +/** + * @brief Register a consumer for GCoAP fileserver events + * Requires the `gcoap_fileserver_callback` module + * + * The Callback is called on each fileserver event and executed + * within the GCoAP thread. + * + * @param[in] cb The callback function to be called on events + * @param[in] arg Custom callback function context + * + */ +void gcoap_fileserver_set_event_cb(gcoap_fileserver_event_handler_t cb, void *arg); + /** * @brief File server handler * diff --git a/sys/net/application_layer/gcoap/fileserver.c b/sys/net/application_layer/gcoap/fileserver.c index eaf14a252b83..e499a6b1f92d 100644 --- a/sys/net/application_layer/gcoap/fileserver.c +++ b/sys/net/application_layer/gcoap/fileserver.c @@ -34,6 +34,21 @@ /** Maximum length of an expressible path, including the trailing 0 character. */ #define COAPFILESERVER_PATH_MAX (64) +/** + * @brief fileserver event callback, only used with `gcoap_fileserver_callback` + */ +static gcoap_fileserver_event_handler_t _event_cb; + +/** + * @brief fileserver event callback context, only used with `gcoap_fileserver_callback` + */ +static void *_event_ctx; + +/** + * @brief fileserver event mutex, protects event cb and event ctx from concurrent access + */ +static mutex_t _event_mtx; + /** * @brief Structure holding information about present options in a request */ @@ -146,6 +161,26 @@ static void _calc_szx2(coap_pkt_t *pdu, size_t reserve, coap_block1_t *block2) } } +static inline void _event_file(gcoap_fileserver_event_t event, struct requestdata *request) +{ + if (!IS_USED(MODULE_GCOAP_FILESERVER_CALLBACK)) { + return; + } + + mutex_lock(&_event_mtx); + gcoap_fileserver_event_ctx_t ctx = { + .path = request->namebuf, + .user_ctx = _event_ctx, + }; + + gcoap_fileserver_event_handler_t cb = _event_cb; + mutex_unlock(&_event_mtx); + + if (cb) { + cb(event, &ctx); + } +} + static ssize_t _get_file(coap_pkt_t *pdu, uint8_t *buf, size_t len, struct requestdata *request) { @@ -193,6 +228,10 @@ static ssize_t _get_file(coap_pkt_t *pdu, uint8_t *buf, size_t len, goto late_err; } + if (block2.blknum == 0) { + _event_file(GCOAP_FILESERVER_GET_FILE_START, request); + } + /* That'd only happen if the buffer is too small for even a 16-byte block, * or if the above calculations were wrong. * @@ -218,6 +257,10 @@ static ssize_t _get_file(coap_pkt_t *pdu, uint8_t *buf, size_t len, read -= 1; } + if (!more) { + _event_file(GCOAP_FILESERVER_GET_FILE_END, request); + } + return resp_len + read; late_err: @@ -257,6 +300,8 @@ static ssize_t _put_file(coap_pkt_t *pdu, uint8_t *buf, size_t len, ret = COAP_CODE_PRECONDITION_FAILED; goto unlink_on_error; } + + _event_file(GCOAP_FILESERVER_PUT_FILE_START, request); } if (request->options.exists.if_match) { stat_etag(&stat, &etag); /* Etag before write */ @@ -310,6 +355,9 @@ static ssize_t _put_file(coap_pkt_t *pdu, uint8_t *buf, size_t len, goto unlink_on_error; } } + + _event_file(GCOAP_FILESERVER_PUT_FILE_END, request); + stat_etag(&stat, &etag); /* Etag after write */ gcoap_resp_init(pdu, buf, len, create ? COAP_CODE_CREATED : COAP_CODE_CHANGED); coap_opt_add_opaque(pdu, COAP_OPT_ETAG, &etag, sizeof(etag)); @@ -347,6 +395,9 @@ static ssize_t _delete_file(coap_pkt_t *pdu, uint8_t *buf, size_t len, return gcoap_fileserver_error_handler(pdu, buf, len, COAP_CODE_PRECONDITION_FAILED); } } + + _event_file(GCOAP_FILESERVER_DELETE_FILE, request); + if ((ret = vfs_unlink(request->namebuf)) < 0) { return gcoap_fileserver_error_handler(pdu, buf, len, ret); } @@ -644,4 +695,16 @@ ssize_t gcoap_fileserver_handler(coap_pkt_t *pdu, uint8_t *buf, size_t len, return gcoap_response(pdu, buf, len, errorcode); } +#ifdef MODULE_GCOAP_FILESERVER_CALLBACK +void gcoap_fileserver_set_event_cb(gcoap_fileserver_event_handler_t cb, void *ctx) +{ + mutex_lock(&_event_mtx); + + _event_cb = cb; + _event_ctx = ctx; + + mutex_unlock(&_event_mtx); +} +#endif + /** @} */