diff --git a/etc/cmake/options.cmake b/etc/cmake/options.cmake index 533c5542b..e2e336353 100644 --- a/etc/cmake/options.cmake +++ b/etc/cmake/options.cmake @@ -117,6 +117,11 @@ if(OT_COAPS) target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE=1") endif() +option(OT_COAP_BLOCK "enable coap block-wise transfer (RFC7959) api support") +if(OT_COAP_BLOCK) + target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE=1") +endif() + option(OT_COAP_OBSERVE "enable coap observe (RFC7641) api support") if(OT_COAP_OBSERVE) list(APPEND OT_PRIVATE_DEFINES "OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE=1") diff --git a/examples/Makefile-simulation b/examples/Makefile-simulation index 78a04576a..c63ab5209 100644 --- a/examples/Makefile-simulation +++ b/examples/Makefile-simulation @@ -38,6 +38,7 @@ DEBUG ?= 0 BORDER_AGENT ?= 1 BORDER_ROUTER ?= 1 COAP ?= 1 +COAP_BLOCK ?= 1 COAP_OBSERVE ?= 1 COAPS ?= 1 COMMISSIONER ?= 1 diff --git a/examples/common-switches.mk b/examples/common-switches.mk index f6b20b18e..dc15f89e7 100644 --- a/examples/common-switches.mk +++ b/examples/common-switches.mk @@ -34,6 +34,7 @@ BORDER_AGENT ?= 0 BORDER_ROUTER ?= 0 BORDER_ROUTING ?= 0 COAP ?= 0 +COAP_BLOCK ?= 0 COAP_OBSERVE ?= 0 COAPS ?= 0 COMMISSIONER ?= 0 @@ -112,6 +113,10 @@ ifeq ($(COAPS),1) COMMONCFLAGS += -DOPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE=1 endif +ifeq ($(COAP_BLOCK),1) +COMMONCFLAGS += -DOPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE=1 +endif + ifeq ($(COAP_OBSERVE),1) COMMONCFLAGS += -DOPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE=1 endif diff --git a/include/openthread/coap.h b/include/openthread/coap.h index 5089df803..2214c0042 100644 --- a/include/openthread/coap.h +++ b/include/openthread/coap.h @@ -144,25 +144,12 @@ typedef enum otCoapOptionType OT_COAP_OPTION_LOCATION_QUERY = 20, ///< Location-Query OT_COAP_OPTION_BLOCK2 = 23, ///< Block2 (RFC7959) OT_COAP_OPTION_BLOCK1 = 27, ///< Block1 (RFC7959) + OT_COAP_OPTION_SIZE2 = 28, ///< Size2 (RFC7959) OT_COAP_OPTION_PROXY_URI = 35, ///< Proxy-Uri OT_COAP_OPTION_PROXY_SCHEME = 39, ///< Proxy-Scheme OT_COAP_OPTION_SIZE1 = 60, ///< Size1 } otCoapOptionType; -/** - * CoAP Block Size Exponents - */ -typedef enum otCoapBlockSize -{ - OT_COAP_BLOCK_SIZE_16 = 0, - OT_COAP_BLOCK_SIZE_32 = 1, - OT_COAP_BLOCK_SIZE_64 = 2, - OT_COAP_BLOCK_SIZE_128 = 3, - OT_COAP_BLOCK_SIZE_256 = 4, - OT_COAP_BLOCK_SIZE_512 = 5, - OT_COAP_BLOCK_SIZE_1024 = 6, -} otCoapBlockSize; - /** * This structure represents a CoAP option. * @@ -327,6 +314,20 @@ typedef enum otCoapOptionContentFormat OT_COAP_OPTION_CONTENT_FORMAT_SENSML_XML = 311 } otCoapOptionContentFormat; +/** + * CoAP Block Size Exponents + */ +typedef enum otCoapBlockSzx +{ + OT_COAP_OPTION_BLOCK_SZX_16 = 0, + OT_COAP_OPTION_BLOCK_SZX_32 = 1, + OT_COAP_OPTION_BLOCK_SZX_64 = 2, + OT_COAP_OPTION_BLOCK_SZX_128 = 3, + OT_COAP_OPTION_BLOCK_SZX_256 = 4, + OT_COAP_OPTION_BLOCK_SZX_512 = 5, + OT_COAP_OPTION_BLOCK_SZX_1024 = 6 +} otCoapBlockSzx; + /** * This function pointer is called when a CoAP response is received or on the request timeout. * @@ -355,6 +356,58 @@ typedef void (*otCoapResponseHandler)(void * aContext, */ typedef void (*otCoapRequestHandler)(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo); +/** + * This function pointer is called when a CoAP message with an block-wise transfer option is received. + * + * This function is available when OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE configuration + * is enabled. + * + * @param[in] aContext A pointer to application-specific context. + * @param[in] aBlock A pointer to the block segment. + * @param[in] aPosition The position of @p aBlock in a sequence in bytes. + * @param[in] aBlockLength The length of the block segment in bytes. + * @param[in] aMore Flag if more block segments are following. + * @param[in] aTotalLength The total length in bytes of the transfered information (indicated by a Size1 or Size2 + * option). + * + * @retval OT_ERROR_NONE Block segment was stored successfully. + * @retval OT_ERROR_NO_BUFS No more memory to store blocks. + * @retval OT_ERROR_NO_FRAME_RECEIVED Block segment missing. + * + */ +typedef otError (*otCoapBlockwiseReceiveHook)(void * aContext, + const uint8_t *aBlock, + uint32_t aPosition, + uint16_t aBlockLength, + bool aMore, + uint32_t aTotalLength); + +/** + * This function pointer is called before the next block in a block-wise transfer is sent. + * + * This function is available when OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE configuration + * is enabled. + * + * @param[in] aContext A pointer to application-specific context. + * @param[inout] aBlock A pointer to where the block segment can be written to. + * @param[in] aPosition The position in a sequence from which to obtain the block segment. + * @param[inout] aBlockLength On entry, the maximum block segment length in bytes. + * @param[out] aMore A pointer to the flag if more block segments will follow. + * + * @warning By changing the value of aBlockLength, the block size of the whole exchange is + * renegotiated. It is recommended to do this after the first block has been received as + * later changes could cause problems with other CoAP implementations. + * + * @retval OT_ERROR_NONE No error occurred. + * @retval OT_ERROR_INVALID_ARGS Block at @p aPosition does not exist. + * + */ +typedef otError (*otCoapBlockwiseTransmitHook)(void * aContext, + uint8_t * aBlock, + uint32_t aPosition, + uint16_t *aBlockLength, + bool * aMore); + /** * This structure represents a CoAP resource. * @@ -367,6 +420,30 @@ typedef struct otCoapResource struct otCoapResource *mNext; ///< The next CoAP resource in the list } otCoapResource; +/** + * This structure represents a CoAP resource with block-wise transfer. + * + */ +typedef struct otCoapBlockwiseResource +{ + const char * mUriPath; ///< The URI Path string + otCoapRequestHandler mHandler; ///< The callback for handling a received request + + /** The callback for handling incoming block-wise transfer. + * This callback is available when OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + * configuration is enabled. + */ + otCoapBlockwiseReceiveHook mReceiveHook; + + /** The callback for handling outgoing block-wise transfer. + * This callback is available when OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + * configuration is enabled. + */ + otCoapBlockwiseTransmitHook mTransmitHook; + void * mContext; ///< Application-specific context + struct otCoapBlockwiseResource *mNext; ///< The next CoAP resource in the list +} otCoapBlockwiseResource; + /** * This structure represents the CoAP transmission parameters. * @@ -539,7 +616,7 @@ otError otCoapMessageAppendUriPathOptions(otMessage *aMessage, const char *aUriP * @returns The actual size exponent value. * */ -uint16_t otCoapBlockSizeFromExponent(otCoapBlockSize aSize); +uint16_t otCoapBlockSizeFromExponent(otCoapBlockSzx aSize); /** * This function appends a Block2 option @@ -554,7 +631,7 @@ uint16_t otCoapBlockSizeFromExponent(otCoapBlockSize aSize); * @retval OT_ERROR_NO_BUFS The option length exceeds the buffer size. * */ -otError otCoapMessageAppendBlock2Option(otMessage *aMessage, uint32_t aNum, bool aMore, otCoapBlockSize aSize); +otError otCoapMessageAppendBlock2Option(otMessage *aMessage, uint32_t aNum, bool aMore, otCoapBlockSzx aSize); /** * This function appends a Block1 option @@ -569,7 +646,7 @@ otError otCoapMessageAppendBlock2Option(otMessage *aMessage, uint32_t aNum, bool * @retval OT_ERROR_NO_BUFS The option length exceeds the buffer size. * */ -otError otCoapMessageAppendBlock1Option(otMessage *aMessage, uint32_t aNum, bool aMore, otCoapBlockSize aSize); +otError otCoapMessageAppendBlock1Option(otMessage *aMessage, uint32_t aNum, bool aMore, otCoapBlockSzx aSize); /** * This function appends a Proxy-Uri option. @@ -804,6 +881,72 @@ otError otCoapSendRequestWithParameters(otInstance * aInstance, void * aContext, const otCoapTxParameters *aTxParameters); +/** + * This function sends a CoAP request block-wise with custom transmission parameters. + * + * This function is available when OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE configuration + * is enabled. + * + * If a response for a request is expected, respective function and context information should be provided. + * If the response is expected to be block-wise, a respective hook function should be provided. + * If no response is expected, these arguments should be NULL pointers. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aMessage A pointer to the message to send. + * @param[in] aMessageInfo A pointer to the message info associated with @p aMessage. + * @param[in] aHandler A function pointer that shall be called on response reception or timeout. + * @param[in] aContext A pointer to arbitrary context information. May be NULL if not used. + * @param[in] aTxParameters A pointer to transmission parameters for this request. Use NULL for defaults. + * @param[in] aTransmitHook A pointer to a hook function for outgoing block-wise transfer. + * @param[in] aReceiveHook A pointer to a hook function for incoming block-wise transfer. + * + * @retval OT_ERROR_NONE Successfully sent CoAP message. + * @retval OT_ERROR_NO_BUFS Failed to allocate retransmission data. + * + */ +otError otCoapSendRequestBlockWiseWithParameters(otInstance * aInstance, + otMessage * aMessage, + const otMessageInfo * aMessageInfo, + otCoapResponseHandler aHandler, + void * aContext, + const otCoapTxParameters * aTxParameters, + otCoapBlockwiseTransmitHook aTransmitHook, + otCoapBlockwiseReceiveHook aReceiveHook); + +/** + * This function sends a CoAP request block-wise. + * + * This function is available when OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE configuration + * is enabled. + * + * If a response for a request is expected, respective function and context information should be provided. + * If the response is expected to be block-wise, a respective hook function should be provided. + * If no response is expected, these arguments should be NULL pointers. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aMessage A pointer to the message to send. + * @param[in] aMessageInfo A pointer to the message info associated with @p aMessage. + * @param[in] aHandler A function pointer that shall be called on response reception or timeout. + * @param[in] aContext A pointer to arbitrary context information. May be NULL if not used. + * @param[in] aTransmitHook A pointer to a hook function for outgoing block-wise transfer. + * @param[in] aReceiveHook A pointer to a hook function for incoming block-wise transfer. + * + * @retval OT_ERROR_NONE Successfully sent CoAP message. + * @retval OT_ERROR_NO_BUFS Failed to allocate retransmission data. + * + */ +static inline otError otCoapSendRequestBlockWise(otInstance * aInstance, + otMessage * aMessage, + const otMessageInfo * aMessageInfo, + otCoapResponseHandler aHandler, + void * aContext, + otCoapBlockwiseTransmitHook aTransmitHook, + otCoapBlockwiseReceiveHook aReceiveHook) +{ + return otCoapSendRequestBlockWiseWithParameters(aInstance, aMessage, aMessageInfo, aHandler, aContext, nullptr, + aTransmitHook, aReceiveHook); +} + /** * This function sends a CoAP request. * @@ -870,6 +1013,24 @@ void otCoapAddResource(otInstance *aInstance, otCoapResource *aResource); */ void otCoapRemoveResource(otInstance *aInstance, otCoapResource *aResource); +/** + * This function adds a block-wise resource to the CoAP server. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aResource A pointer to the resource. + * + */ +void otCoapAddBlockWiseResource(otInstance *aInstance, otCoapBlockwiseResource *aResource); + +/** + * This function removes a block-wise resource from the CoAP server. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aResource A pointer to the resource. + * + */ +void otCoapRemoveBlockWiseResource(otInstance *aInstance, otCoapBlockwiseResource *aResource); + /** * This function sets the default handler for unhandled CoAP requests. * @@ -897,6 +1058,56 @@ otError otCoapSendResponseWithParameters(otInstance * aInstance, const otMessageInfo * aMessageInfo, const otCoapTxParameters *aTxParameters); +/** + * This function sends a CoAP response block-wise from the server with custom transmission parameters. + * + * This function is available when OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE configuration + * is enabled. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aMessage A pointer to the CoAP response to send. + * @param[in] aMessageInfo A pointer to the message info associated with @p aMessage. + * @param[in] aTxParameters A pointer to transmission parameters for this response. Use NULL for defaults. + * @param[in] aContext A pointer to arbitrary context information. May be NULL if not used. + * @param[in] aTransmitHook A pointer to a hook function for outgoing block-wise transfer. + * + * @retval OT_ERROR_NONE Successfully enqueued the CoAP response message. + * @retval OT_ERROR_NO_BUFS Insufficient buffers available to send the CoAP response. + * + */ +otError otCoapSendResponseBlockWiseWithParameters(otInstance * aInstance, + otMessage * aMessage, + const otMessageInfo * aMessageInfo, + const otCoapTxParameters * aTxParameters, + void * aContext, + otCoapBlockwiseTransmitHook aTransmitHook); + +/** + * This function sends a CoAP response block-wise from the server. + * + * This function is available when OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE configuration + * is enabled. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aMessage A pointer to the CoAP response to send. + * @param[in] aMessageInfo A pointer to the message info associated with @p aMessage. + * @param[in] aContext A pointer to arbitrary context information. May be NULL if not used. + * @param[in] aTransmitHook A pointer to a hook function for outgoing block-wise transfer. + * + * @retval OT_ERROR_NONE Successfully enqueued the CoAP response message. + * @retval OT_ERROR_NO_BUFS Insufficient buffers available to send the CoAP response. + * + */ +static inline otError otCoapSendResponseBlockWise(otInstance * aInstance, + otMessage * aMessage, + const otMessageInfo * aMessageInfo, + void * aContext, + otCoapBlockwiseTransmitHook aTransmitHook) +{ + return otCoapSendResponseBlockWiseWithParameters(aInstance, aMessage, aMessageInfo, nullptr, aContext, + aTransmitHook); +} + /** * This function sends a CoAP response from the server. * diff --git a/include/openthread/coap_secure.h b/include/openthread/coap_secure.h index 11caec54d..ee17716d7 100644 --- a/include/openthread/coap_secure.h +++ b/include/openthread/coap_secure.h @@ -229,6 +229,35 @@ bool otCoapSecureIsConnected(otInstance *aInstance); */ bool otCoapSecureIsConnectionActive(otInstance *aInstance); +/** + * This method sends a CoAP request block-wise over secure DTLS connection. + * + * This function is available when OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE configuration + * is enabled. + * + * If a response for a request is expected, respective function and context information should be provided. + * If no response is expected, these arguments should be NULL pointers. + * If Message Id was not set in the header (equal to 0), this function will assign unique Message Id to the message. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aMessage A reference to the message to send. + * @param[in] aHandler A function pointer that shall be called on response reception or time-out. + * @param[in] aContext A pointer to arbitrary context information. + * @param[in] aTransmitHook A function pointer that is called on Block1 response reception. + * @param[in] aReceiveHook A function pointer that is called on Block2 response reception. + * + * @retval OT_ERROR_NONE Successfully sent CoAP message. + * @retval OT_ERROR_NO_BUFS Failed to allocate retransmission data. + * @retval OT_ERROR_INVALID_STATE DTLS connection was not initialized. + * + */ +otError otCoapSecureSendRequestBlockWise(otInstance * aInstance, + otMessage * aMessage, + otCoapResponseHandler aHandler, + void * aContext, + otCoapBlockwiseTransmitHook aTransmitHook, + otCoapBlockwiseReceiveHook aReceiveHook); + /** * This method sends a CoAP request over secure DTLS connection. * @@ -269,6 +298,24 @@ void otCoapSecureAddResource(otInstance *aInstance, otCoapResource *aResource); */ void otCoapSecureRemoveResource(otInstance *aInstance, otCoapResource *aResource); +/** + * This function adds a block-wise resource to the CoAP Secure server. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aResource A pointer to the resource. + * + */ +void otCoapSecureAddBlockWiseResource(otInstance *aInstance, otCoapBlockwiseResource *aResource); + +/** + * This function removes a block-wise resource from the CoAP Secure server. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aResource A pointer to the resource. + * + */ +void otCoapSecureRemoveBlockWiseResource(otInstance *aInstance, otCoapBlockwiseResource *aResource); + /** * This function sets the default handler for unhandled CoAP Secure requests. * @@ -292,6 +339,28 @@ void otCoapSecureSetClientConnectedCallback(otInstance * aIns otHandleCoapSecureClientConnect aHandler, void * aContext); +/** + * This function sends a CoAP response block-wise from the CoAP Secure server. + * + * This function is available when OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE configuration + * is enabled. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aMessage A pointer to the CoAP response to send. + * @param[in] aMessageInfo A pointer to the message info associated with @p aMessage. + * @param[in] aContext A pointer to arbitrary context information. May be NULL if not used. + * @param[in] aTransmitHook A function pointer that is called on Block1 request reception. + * + * @retval OT_ERROR_NONE Successfully enqueued the CoAP response message. + * @retval OT_ERROR_NO_BUFS Insufficient buffers available to send the CoAP response. + * + */ +otError otCoapSecureSendResponseBlockWise(otInstance * aInstance, + otMessage * aMessage, + const otMessageInfo * aMessageInfo, + void * aContext, + otCoapBlockwiseTransmitHook aTransmitHook); + /** * This function sends a CoAP response from the CoAP Secure server. * diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 00876c07e..09b1a2b54 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (65) +#define OPENTHREAD_API_VERSION (66) /** * @addtogroup api-instance diff --git a/script/cmake-build b/script/cmake-build index 0489aebb2..71b8c1ab2 100755 --- a/script/cmake-build +++ b/script/cmake-build @@ -69,6 +69,7 @@ readonly OT_POSIX_SIM_COMMON_OPTIONS=( "-DOT_BORDER_AGENT=ON" "-DOT_BORDER_ROUTER=ON" "-DOT_COAP=ON" + "-DOT_COAP_BLOCK=ON" "-DOT_COAP_OBSERVE=ON" "-DOT_COAPS=ON" "-DOT_COMMISSIONER=ON" diff --git a/script/make-pretty b/script/make-pretty index 0e2200b3e..3c577fdcb 100755 --- a/script/make-pretty +++ b/script/make-pretty @@ -85,6 +85,7 @@ readonly OT_CLANG_TIDY_BUILD_OPTS=( '-DOT_CHANNEL_MONITOR=ON' '-DOT_CHILD_SUPERVISION=ON' '-DOT_COAP=ON' + '-DOT_COAP_BLOCK=ON' '-DOT_COAP_OBSERVE=ON' '-DOT_COAPS=ON' '-DOT_COMMISSIONER=ON' diff --git a/src/cli/README_COAP.md b/src/cli/README_COAP.md index 1dc86301c..755f2d5a9 100644 --- a/src/cli/README_COAP.md +++ b/src/cli/README_COAP.md @@ -115,13 +115,18 @@ Done - address: IPv6 address of the CoAP server. - uri-path: URI path of the resource. -- type: "con" for Confirmable or "non-con" for Non-confirmable (default). +- type: "con" for Confirmable or "non-con" for Non-confirmable (default). Use "block-" if the response should be transferred block-wise. ("block-16","block-32","block-64","block-128","block-256","block-512","block-1024") ```bash > coap get fdde:ad00:beef:0:2780:9423:166c:1aac test-resource Done ``` +```bash +> coap get fdde:ad00:beef:0:2780:9423:166c:1aac test-resource block-1024 +Done +``` + ### observe \ \ \[type\] This is the same a `get`, but the `Observe` parameter will be sent, set to 0 triggering a subscription request. @@ -176,26 +181,36 @@ Done - address: IPv6 address of the CoAP server. - uri-path: URI path of the resource. -- type: "con" for Confirmable or "non-con" for Non-confirmable (default). -- payload: CoAP request payload. +- type: "con" for Confirmable or "non-con" for Non-confirmable (default). Use "block-" to send blocks with random payload. ("block-16","block-32","block-64","block-128","block-256","block-512","block-1024") +- payload: CoAP request payload. If \[type\] is "block-", the amount of blocks to be sent can be set here. ```bash > coap post fdde:ad00:beef:0:2780:9423:166c:1aac test-resource con payload Done ``` +```bash +> coap post fdde:ad00:beef:0:2780:9423:166c:1aac test-resource block-1024 10 +Done +``` + ### put \ \ \[type\] \[payload\] - address: IPv6 address of the CoAP server. - uri-path: URI path of the resource. -- type: "con" for Confirmable or "non-con" for Non-confirmable (default). -- payload: CoAP request payload. +- type: "con" for Confirmable or "non-con" for Non-confirmable (default). Use "block-" to send blocks with random payload. ("block-16","block-32","block-64","block-128","block-256","block-512","block-1024") +- payload: CoAP request payload. If \[type\] is "block-", the amount of blocks to be sent can be set here. ```bash > coap put fdde:ad00:beef:0:2780:9423:166c:1aac test-resource con payload Done ``` +```bash +> coap put fdde:ad00:beef:0:2780:9423:166c:1aac test-resource block-1024 10 +Done +``` + ### resource \[uri-path\] Sets the URI path for the test resource. diff --git a/src/cli/README_COAPS.md b/src/cli/README_COAPS.md index 2d31feeb2..0cb4f99c7 100644 --- a/src/cli/README_COAPS.md +++ b/src/cli/README_COAPS.md @@ -163,24 +163,34 @@ Done ### get \ \[type\] - uri-path: URI path of the resource. -- type: "con" for Confirmable or "non-con" for Non-confirmable (default). +- type: "con" for Confirmable or "non-con" for Non-confirmable (default). Use "block-" if the response should be transferred block-wise. ("block-16","block-32","block-64","block-128","block-256","block-512","block-1024") ```bash > coaps get test-resource Done ``` +```bash +> coaps get test-resource block-1024 +Done +``` + ### post \ \[type\] \[payload\] - uri-path: URI path of the resource. -- type: "con" for Confirmable or "non-con" for Non-confirmable (default). -- payload: CoAPS request payload. +- type: "con" for Confirmable or "non-con" for Non-confirmable (default). Use "block-" to send blocks with random payload. ("block-16","block-32","block-64","block-128","block-256","block-512","block-1024") +- payload: CoAP request payload. If \[type\] is "block-", the amount of blocks to be sent can be set here. ```bash > coaps post test-resource con payload Done ``` +```bash +> coaps post test-resource block-1024 10 +Done +``` + ### psk \ \ Set DTLS ciphersuite to `TLS_PSK_WITH_AES_128_CCM_8`. @@ -196,14 +206,19 @@ Done ### put \ \[type\] \[payload\] - uri-path: URI path of the resource. -- type: "con" for Confirmable or "non-con" for Non-confirmable (default). -- payload: CoAPS request payload. +- type: "con" for Confirmable or "non-con" for Non-confirmable (default). Use "block-" to send blocks with random payload. ("block-16","block-32","block-64","block-128","block-256","block-512","block-1024") +- payload: CoAP request payload. If \[type\] is "block-", the amount of blocks to be sent can be set here. ```bash > coaps put test-resource con payload Done ``` +```bash +> coaps put test-resource block-1024 10 +Done +``` + ### resource \[uri-path\] Sets the URI path for the test resource. diff --git a/src/cli/cli_coap.cpp b/src/cli/cli_coap.cpp index d10617593..867d2800c 100644 --- a/src/cli/cli_coap.cpp +++ b/src/cli/cli_coap.cpp @@ -35,6 +35,8 @@ #if OPENTHREAD_CONFIG_COAP_API_ENABLE +#include + #include #include "cli/cli.hpp" @@ -60,6 +62,9 @@ Coap::Coap(Interpreter &aInterpreter) , mSubscriberTokenLength(0) , mSubscriberConfirmableNotifications(false) #endif +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + , mBlockCount(1) +#endif { memset(&mResource, 0, sizeof(mResource)); #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE @@ -180,8 +185,22 @@ otError Coap::ProcessResource(uint8_t aArgsLength, char *aArgs[]) mResource.mContext = this; mResource.mHandler = &Coap::HandleRequest; +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + mResource.mReceiveHook = &Coap::BlockwiseReceiveHook; + mResource.mTransmitHook = &Coap::BlockwiseTransmitHook; + + if (aArgsLength > 2) + { + SuccessOrExit(error = ParseAsUint32(aArgs[2], mBlockCount)); + } +#endif + strncpy(mUriPath, aArgs[1], sizeof(mUriPath) - 1); +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + otCoapAddBlockWiseResource(mInterpreter.mInstance, &mResource); +#else otCoapAddResource(mInterpreter.mInstance, &mResource); +#endif } else { @@ -267,7 +286,11 @@ otError Coap::ProcessStop(uint8_t aArgsLength, char *aArgs[]) OT_UNUSED_VARIABLE(aArgsLength); OT_UNUSED_VARIABLE(aArgs); +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + otCoapRemoveBlockWiseResource(mInterpreter.mInstance, &mResource); +#else otCoapRemoveResource(mInterpreter.mInstance, &mResource); +#endif return otCoapStop(mInterpreter.mInstance); } @@ -348,6 +371,11 @@ otError Coap::ProcessRequest(uint8_t aArgsLength, char *aArgs[]) #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE bool coapObserve = false; #endif +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + bool coapBlock = false; + otCoapBlockSzx coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_16; + ot::Coap::Message::BlockType coapBlockType = ot::Coap::Message::kBlockType1; +#endif VerifyOrExit(aArgsLength > 0, error = OT_ERROR_INVALID_ARGS); @@ -355,6 +383,9 @@ otError Coap::ProcessRequest(uint8_t aArgsLength, char *aArgs[]) if (strcmp(aArgs[0], "get") == 0) { coapCode = OT_COAP_CODE_GET; +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + coapBlockType = ot::Coap::Message::kBlockType2; +#endif } #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE else if (strcmp(aArgs[0], "observe") == 0) @@ -367,10 +398,16 @@ otError Coap::ProcessRequest(uint8_t aArgsLength, char *aArgs[]) else if (strcmp(aArgs[0], "post") == 0) { coapCode = OT_COAP_CODE_POST; +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + coapBlockType = ot::Coap::Message::kBlockType1; +#endif } else if (strcmp(aArgs[0], "put") == 0) { coapCode = OT_COAP_CODE_PUT; +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + coapBlockType = ot::Coap::Message::kBlockType1; +#endif } else if (strcmp(aArgs[0], "delete") == 0) { @@ -409,6 +446,50 @@ otError Coap::ProcessRequest(uint8_t aArgsLength, char *aArgs[]) { coapType = OT_COAP_TYPE_CONFIRMABLE; } +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + else if (strcmp(aArgs[3], "block-16") == 0) + { + coapType = OT_COAP_TYPE_CONFIRMABLE; + coapBlock = true; + coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_16; + } + else if (strcmp(aArgs[3], "block-32") == 0) + { + coapType = OT_COAP_TYPE_CONFIRMABLE; + coapBlock = true; + coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_32; + } + else if (strcmp(aArgs[3], "block-64") == 0) + { + coapType = OT_COAP_TYPE_CONFIRMABLE; + coapBlock = true; + coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_64; + } + else if (strcmp(aArgs[3], "block-128") == 0) + { + coapType = OT_COAP_TYPE_CONFIRMABLE; + coapBlock = true; + coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_128; + } + else if (strcmp(aArgs[3], "block-256") == 0) + { + coapType = OT_COAP_TYPE_CONFIRMABLE; + coapBlock = true; + coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_256; + } + else if (strcmp(aArgs[3], "block-512") == 0) + { + coapType = OT_COAP_TYPE_CONFIRMABLE; + coapBlock = true; + coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_512; + } + else if (strcmp(aArgs[3], "block-1024") == 0) + { + coapType = OT_COAP_TYPE_CONFIRMABLE; + coapBlock = true; + coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_1024; + } +#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE } #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE @@ -434,14 +515,39 @@ otError Coap::ProcessRequest(uint8_t aArgsLength, char *aArgs[]) SuccessOrExit(error = otCoapMessageAppendUriPathOptions(message, coapUri)); +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + if (coapBlock) + { + if (coapBlockType == ot::Coap::Message::kBlockType1) + { + SuccessOrExit(error = otCoapMessageAppendBlock1Option(message, 0, true, coapBlockSize)); + } + else + { + SuccessOrExit(error = otCoapMessageAppendBlock2Option(message, 0, false, coapBlockSize)); + } + } +#endif + if (aArgsLength > 4) { - payloadLength = static_cast(strlen(aArgs[4])); - - if (payloadLength > 0) +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + if (coapBlock) { - SuccessOrExit(error = otCoapMessageSetPayloadMarker(message)); + SuccessOrExit(error = ParseAsUint32(aArgs[4], mBlockCount)); } + else + { +#endif + payloadLength = static_cast(strlen(aArgs[4])); + + if (payloadLength > 0) + { + SuccessOrExit(error = otCoapMessageSetPayloadMarker(message)); + } +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + } +#endif } // Embed content into message if given @@ -468,8 +574,25 @@ otError Coap::ProcessRequest(uint8_t aArgsLength, char *aArgs[]) if ((coapType == OT_COAP_TYPE_CONFIRMABLE) || (coapCode == OT_COAP_CODE_GET)) { - error = otCoapSendRequestWithParameters(mInterpreter.mInstance, message, &messageInfo, &Coap::HandleResponse, - this, GetRequestTxParameters()); +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + if (coapBlock) + { + if (coapCode == OT_COAP_CODE_PUT || coapCode == OT_COAP_CODE_POST) + { + SuccessOrExit(error = otCoapMessageSetPayloadMarker(message)); + } + error = otCoapSendRequestBlockWiseWithParameters(mInterpreter.mInstance, message, &messageInfo, + &Coap::HandleResponse, this, GetRequestTxParameters(), + Coap::BlockwiseTransmitHook, Coap::BlockwiseReceiveHook); + } + else + { +#endif + error = otCoapSendRequestWithParameters(mInterpreter.mInstance, message, &messageInfo, + &Coap::HandleResponse, this, GetRequestTxParameters()); +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + } +#endif } else { @@ -514,8 +637,14 @@ void Coap::HandleRequest(otMessage *aMessage, const otMessageInfo *aMessageInfo) otMessage *responseMessage = nullptr; otCoapCode responseCode = OT_COAP_CODE_EMPTY; #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE - uint64_t observe = 0; - bool observePresent = false; + uint64_t observe = 0; + bool observePresent = false; +#endif +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + uint64_t blockValue = 0; + bool blockPresent = false; +#endif +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE || OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE otCoapOptionIterator iterator; #endif @@ -527,15 +656,24 @@ void Coap::HandleRequest(otMessage *aMessage, const otMessageInfo *aMessageInfo) { case OT_COAP_CODE_GET: mInterpreter.OutputFormat("GET"); -#if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE || OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE SuccessOrExit(error = otCoapOptionIteratorInit(&iterator, aMessage)); - if (otCoapOptionIteratorGetFirstOptionMatching(&iterator, OT_COAP_OPTION_OBSERVE) != nullptr) +#endif +#if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE + if (otCoapOptionIteratorGetFirstOptionMatching(&iterator, OT_COAP_OPTION_OBSERVE) != NULL) { SuccessOrExit(error = otCoapOptionIteratorGetOptionUintValue(&iterator, &observe)); observePresent = true; mInterpreter.OutputFormat(" OBS=%lu", static_cast(observe)); } +#endif +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + if (otCoapOptionIteratorGetFirstOptionMatching(&iterator, OT_COAP_OPTION_BLOCK2) != nullptr) + { + SuccessOrExit(error = otCoapOptionIteratorGetOptionUintValue(&iterator, &blockValue)); + blockPresent = true; + } #endif break; @@ -626,13 +764,40 @@ void Coap::HandleRequest(otMessage *aMessage, const otMessageInfo *aMessageInfo) SuccessOrExit(error = otCoapMessageAppendObserveOption(responseMessage, mObserveSerial++)); } #endif - SuccessOrExit(error = otCoapMessageSetPayloadMarker(responseMessage)); - SuccessOrExit(error = otMessageAppend(responseMessage, mResourceContent, - static_cast(strlen(mResourceContent)))); +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + if (blockPresent) + { + SuccessOrExit(error = otCoapMessageAppendBlock2Option(responseMessage, + static_cast(blockValue >> 4), true, + static_cast(blockValue & 0x7))); + SuccessOrExit(error = otCoapMessageSetPayloadMarker(responseMessage)); + } + else + { +#endif + SuccessOrExit(error = otCoapMessageSetPayloadMarker(responseMessage)); + SuccessOrExit(error = otMessageAppend(responseMessage, mResourceContent, + static_cast(strlen(mResourceContent)))); +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + } +#endif } - SuccessOrExit(error = otCoapSendResponseWithParameters(mInterpreter.mInstance, responseMessage, aMessageInfo, - GetResponseTxParameters())); +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + if (blockPresent) + { + SuccessOrExit(error = otCoapSendResponseBlockWiseWithParameters(mInterpreter.mInstance, responseMessage, + aMessageInfo, GetResponseTxParameters(), + this, mResource.mTransmitHook)); + } + else + { +#endif + SuccessOrExit(error = otCoapSendResponseWithParameters(mInterpreter.mInstance, responseMessage, + aMessageInfo, GetResponseTxParameters())); +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + } +#endif } exit: @@ -726,6 +891,77 @@ void Coap::HandleResponse(otMessage *aMessage, const otMessageInfo *aMessageInfo } } +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE +otError Coap::BlockwiseReceiveHook(void * aContext, + const uint8_t *aBlock, + uint32_t aPosition, + uint16_t aBlockLength, + bool aMore, + uint32_t aTotalLength) +{ + return static_cast(aContext)->BlockwiseReceiveHook(aBlock, aPosition, aBlockLength, aMore, aTotalLength); +} + +otError Coap::BlockwiseReceiveHook(const uint8_t *aBlock, + uint32_t aPosition, + uint16_t aBlockLength, + bool aMore, + uint32_t aTotalLength) +{ + OT_UNUSED_VARIABLE(aMore); + OT_UNUSED_VARIABLE(aTotalLength); + + mInterpreter.OutputLine("received block: Num %i Len %i", aPosition / aBlockLength, aBlockLength); + + for (uint16_t i = 0; i < aBlockLength / 16; i++) + { + mInterpreter.OutputBytes(&aBlock[i * 16], 16); + mInterpreter.OutputLine(""); + } + + return OT_ERROR_NONE; +} + +otError Coap::BlockwiseTransmitHook(void * aContext, + uint8_t * aBlock, + uint32_t aPosition, + uint16_t *aBlockLength, + bool * aMore) +{ + return static_cast(aContext)->BlockwiseTransmitHook(aBlock, aPosition, aBlockLength, aMore); +} + +otError Coap::BlockwiseTransmitHook(uint8_t *aBlock, uint32_t aPosition, uint16_t *aBlockLength, bool *aMore) +{ + static uint32_t blockCount = 0; + OT_UNUSED_VARIABLE(aPosition); + + // Send a random payload + otRandomNonCryptoFillBuffer(aBlock, *aBlockLength); + + mInterpreter.OutputLine("send block: Num %i Len %i", blockCount, *aBlockLength); + + for (uint16_t i = 0; i < *aBlockLength / 16; i++) + { + mInterpreter.OutputBytes(&aBlock[i * 16], 16); + mInterpreter.OutputLine(""); + } + + if (blockCount == mBlockCount - 1) + { + blockCount = 0; + *aMore = false; + } + else + { + *aMore = true; + blockCount++; + } + + return OT_ERROR_NONE; +} +#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + } // namespace Cli } // namespace ot diff --git a/src/cli/cli_coap.hpp b/src/cli/cli_coap.hpp index 03257818b..586386a75 100644 --- a/src/cli/cli_coap.hpp +++ b/src/cli/cli_coap.hpp @@ -115,6 +115,27 @@ private: static void HandleResponse(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo, otError aError); void HandleResponse(otMessage *aMessage, const otMessageInfo *aMessageInfo, otError aError); +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + + static otError BlockwiseReceiveHook(void * aContext, + const uint8_t *aBlock, + uint32_t aPosition, + uint16_t aBlockLength, + bool aMore, + uint32_t aTotalLength); + otError BlockwiseReceiveHook(const uint8_t *aBlock, + uint32_t aPosition, + uint16_t aBlockLength, + bool aMore, + uint32_t aTotalLength); + static otError BlockwiseTransmitHook(void * aContext, + uint8_t * aBlock, + uint32_t aPosition, + uint16_t *aBlockLength, + bool * aMore); + otError BlockwiseTransmitHook(uint8_t *aBlock, uint32_t aPosition, uint16_t *aBlockLength, bool *aMore); +#endif + const otCoapTxParameters *GetRequestTxParameters(void) const { return mUseDefaultRequestTxParameters ? nullptr : &mRequestTxParameters; @@ -154,7 +175,11 @@ private: otCoapTxParameters mRequestTxParameters; otCoapTxParameters mResponseTxParameters; +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + otCoapBlockwiseResource mResource; +#else otCoapResource mResource; +#endif #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE otIp6Address mRequestAddr; otSockAddr mSubscriberSock; @@ -170,6 +195,9 @@ private: uint8_t mSubscriberTokenLength; bool mSubscriberConfirmableNotifications; #endif +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + uint32_t mBlockCount; +#endif }; } // namespace Cli diff --git a/src/cli/cli_coap_secure.cpp b/src/cli/cli_coap_secure.cpp index 2f1b586be..880c66d92 100644 --- a/src/cli/cli_coap_secure.cpp +++ b/src/cli/cli_coap_secure.cpp @@ -37,6 +37,7 @@ #include #include +#include #include "cli/cli.hpp" #include "utils/parse_cmdline.hpp" @@ -46,6 +47,7 @@ using ot::Utils::CmdLineParser::ParseAsIp6Address; using ot::Utils::CmdLineParser::ParseAsUint16; +using ot::Utils::CmdLineParser::ParseAsUint32; namespace ot { namespace Cli { @@ -58,6 +60,9 @@ CoapSecure::CoapSecure(Interpreter &aInterpreter) , mUseCertificate(false) , mPskLength(0) , mPskIdLength(0) +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + , mBlockCount(1) +#endif { memset(&mResource, 0, sizeof(mResource)); memset(&mPsk, 0, sizeof(mPsk)); @@ -117,8 +122,22 @@ otError CoapSecure::ProcessResource(uint8_t aArgsLength, char *aArgs[]) mResource.mContext = this; mResource.mHandler = &CoapSecure::HandleRequest; +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + mResource.mReceiveHook = &CoapSecure::BlockwiseReceiveHook; + mResource.mTransmitHook = &CoapSecure::BlockwiseTransmitHook; + + if (aArgsLength > 2) + { + SuccessOrExit(error = ParseAsUint32(aArgs[2], mBlockCount)); + } +#endif + strncpy(mUriPath, aArgs[1], sizeof(mUriPath) - 1); +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + otCoapSecureAddBlockWiseResource(mInterpreter.mInstance, &mResource); +#else otCoapSecureAddResource(mInterpreter.mInstance, &mResource); +#endif } else { @@ -183,7 +202,11 @@ otError CoapSecure::ProcessStop(uint8_t aArgsLength, char *aArgs[]) OT_UNUSED_VARIABLE(aArgsLength); OT_UNUSED_VARIABLE(aArgs); +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + otCoapRemoveBlockWiseResource(mInterpreter.mInstance, &mResource); +#else otCoapRemoveResource(mInterpreter.mInstance, &mResource); +#endif if (otCoapSecureIsConnectionActive(mInterpreter.mInstance)) { @@ -211,6 +234,11 @@ otError CoapSecure::ProcessRequest(uint8_t aArgsLength, char *aArgs[]) otCoapType coapType = OT_COAP_TYPE_NON_CONFIRMABLE; otCoapCode coapCode = OT_COAP_CODE_GET; otIp6Address coapDestinationIp; +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + bool coapBlock = false; + otCoapBlockSzx coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_16; + ot::Coap::Message::BlockType coapBlockType = ot::Coap::Message::kBlockType1; +#endif VerifyOrExit(aArgsLength > 0, error = OT_ERROR_INVALID_ARGS); @@ -218,14 +246,23 @@ otError CoapSecure::ProcessRequest(uint8_t aArgsLength, char *aArgs[]) if (strcmp(aArgs[0], "get") == 0) { coapCode = OT_COAP_CODE_GET; +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + coapBlockType = ot::Coap::Message::kBlockType2; +#endif } else if (strcmp(aArgs[0], "post") == 0) { coapCode = OT_COAP_CODE_POST; +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + coapBlockType = ot::Coap::Message::kBlockType1; +#endif } else if (strcmp(aArgs[0], "put") == 0) { coapCode = OT_COAP_CODE_PUT; +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + coapBlockType = ot::Coap::Message::kBlockType1; +#endif } else if (strcmp(aArgs[0], "delete") == 0) { @@ -270,6 +307,50 @@ otError CoapSecure::ProcessRequest(uint8_t aArgsLength, char *aArgs[]) { coapType = OT_COAP_TYPE_CONFIRMABLE; } +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + else if (strcmp(aArgs[3 - indexShifter], "block-16") == 0) + { + coapType = OT_COAP_TYPE_CONFIRMABLE; + coapBlock = true; + coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_16; + } + else if (strcmp(aArgs[3 - indexShifter], "block-32") == 0) + { + coapType = OT_COAP_TYPE_CONFIRMABLE; + coapBlock = true; + coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_32; + } + else if (strcmp(aArgs[3 - indexShifter], "block-64") == 0) + { + coapType = OT_COAP_TYPE_CONFIRMABLE; + coapBlock = true; + coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_64; + } + else if (strcmp(aArgs[3 - indexShifter], "block-128") == 0) + { + coapType = OT_COAP_TYPE_CONFIRMABLE; + coapBlock = true; + coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_128; + } + else if (strcmp(aArgs[3 - indexShifter], "block-256") == 0) + { + coapType = OT_COAP_TYPE_CONFIRMABLE; + coapBlock = true; + coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_256; + } + else if (strcmp(aArgs[3 - indexShifter], "block-512") == 0) + { + coapType = OT_COAP_TYPE_CONFIRMABLE; + coapBlock = true; + coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_512; + } + else if (strcmp(aArgs[3 - indexShifter], "block-1024") == 0) + { + coapType = OT_COAP_TYPE_CONFIRMABLE; + coapBlock = true; + coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_1024; + } +#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE } message = otCoapNewMessage(mInterpreter.mInstance, nullptr); @@ -279,14 +360,39 @@ otError CoapSecure::ProcessRequest(uint8_t aArgsLength, char *aArgs[]) otCoapMessageGenerateToken(message, OT_COAP_DEFAULT_TOKEN_LENGTH); SuccessOrExit(error = otCoapMessageAppendUriPathOptions(message, coapUri)); +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + if (coapBlock) + { + if (coapBlockType == ot::Coap::Message::kBlockType1) + { + SuccessOrExit(error = otCoapMessageAppendBlock1Option(message, 0, true, coapBlockSize)); + } + else + { + SuccessOrExit(error = otCoapMessageAppendBlock2Option(message, 0, false, coapBlockSize)); + } + } +#endif + if (aArgsLength > (4 - indexShifter)) { - payloadLength = static_cast(strlen(aArgs[4 - indexShifter])); - - if (payloadLength > 0) +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + if (coapBlock) { - SuccessOrExit(error = otCoapMessageSetPayloadMarker(message)); + SuccessOrExit(error = ParseAsUint32(aArgs[4 - indexShifter], mBlockCount)); } + else + { +#endif + payloadLength = static_cast(strlen(aArgs[4 - indexShifter])); + + if (payloadLength > 0) + { + SuccessOrExit(error = otCoapMessageSetPayloadMarker(message)); + } +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + } +#endif } // add payload @@ -301,7 +407,20 @@ otError CoapSecure::ProcessRequest(uint8_t aArgsLength, char *aArgs[]) if ((coapType == OT_COAP_TYPE_CONFIRMABLE) || (coapCode == OT_COAP_CODE_GET)) { - error = otCoapSecureSendRequest(mInterpreter.mInstance, message, &CoapSecure::HandleResponse, this); +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + if (coapBlock) + { + error = + otCoapSecureSendRequestBlockWise(mInterpreter.mInstance, message, &CoapSecure::HandleResponse, this, + &CoapSecure::BlockwiseTransmitHook, &CoapSecure::BlockwiseReceiveHook); + } + else + { +#endif + error = otCoapSecureSendRequest(mInterpreter.mInstance, message, &CoapSecure::HandleResponse, this); +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + } +#endif } else { @@ -415,7 +534,11 @@ exit: void CoapSecure::Stop(void) { +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + otCoapRemoveBlockWiseResource(mInterpreter.mInstance, &mResource); +#else otCoapRemoveResource(mInterpreter.mInstance, &mResource); +#endif otCoapSecureStop(mInterpreter.mInstance); } @@ -452,6 +575,11 @@ void CoapSecure::HandleRequest(otMessage *aMessage, const otMessageInfo *aMessag otError error = OT_ERROR_NONE; otMessage *responseMessage = nullptr; otCoapCode responseCode = OT_COAP_CODE_EMPTY; +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + uint64_t blockValue = 0; + bool blockPresent = false; + otCoapOptionIterator iterator; +#endif mInterpreter.OutputFormat("coaps request from "); mInterpreter.OutputIp6Address(aMessageInfo->mPeerAddr); @@ -461,6 +589,14 @@ void CoapSecure::HandleRequest(otMessage *aMessage, const otMessageInfo *aMessag { case OT_COAP_CODE_GET: mInterpreter.OutputFormat("GET"); +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + SuccessOrExit(error = otCoapOptionIteratorInit(&iterator, aMessage)); + if (otCoapOptionIteratorGetFirstOptionMatching(&iterator, OT_COAP_OPTION_BLOCK2) != nullptr) + { + SuccessOrExit(error = otCoapOptionIteratorGetOptionUintValue(&iterator, &blockValue)); + blockPresent = true; + } +#endif break; case OT_COAP_CODE_DELETE: @@ -502,16 +638,39 @@ void CoapSecure::HandleRequest(otMessage *aMessage, const otMessageInfo *aMessag if (otCoapMessageGetCode(aMessage) == OT_COAP_CODE_GET) { +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + if (blockPresent) + { + SuccessOrExit(error = otCoapMessageAppendBlock2Option(responseMessage, + static_cast(blockValue >> 4), true, + static_cast(blockValue & 0x7))); + } +#endif SuccessOrExit(error = otCoapMessageSetPayloadMarker(responseMessage)); +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + if (!blockPresent) + { +#endif + SuccessOrExit(error = otMessageAppend(responseMessage, &mResourceContent, + static_cast(strlen(mResourceContent)))); +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + } +#endif } - if (otCoapMessageGetCode(aMessage) == OT_COAP_CODE_GET) +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + if (blockPresent) { - SuccessOrExit(error = otMessageAppend(responseMessage, &mResourceContent, - static_cast(strlen(mResourceContent)))); + SuccessOrExit(error = otCoapSecureSendResponseBlockWise(mInterpreter.mInstance, responseMessage, + aMessageInfo, this, mResource.mTransmitHook)); } - - SuccessOrExit(error = otCoapSecureSendResponse(mInterpreter.mInstance, responseMessage, aMessageInfo)); + else + { +#endif + SuccessOrExit(error = otCoapSecureSendResponse(mInterpreter.mInstance, responseMessage, aMessageInfo)); +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + } +#endif } exit: @@ -583,6 +742,78 @@ exit: } #endif // CLI_COAP_SECURE_USE_COAP_DEFAULT_HANDLER +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE +otError CoapSecure::BlockwiseReceiveHook(void * aContext, + const uint8_t *aBlock, + uint32_t aPosition, + uint16_t aBlockLength, + bool aMore, + uint32_t aTotalLength) +{ + return static_cast(aContext)->BlockwiseReceiveHook(aBlock, aPosition, aBlockLength, aMore, + aTotalLength); +} + +otError CoapSecure::BlockwiseReceiveHook(const uint8_t *aBlock, + uint32_t aPosition, + uint16_t aBlockLength, + bool aMore, + uint32_t aTotalLength) +{ + OT_UNUSED_VARIABLE(aMore); + OT_UNUSED_VARIABLE(aTotalLength); + + mInterpreter.OutputLine("received block: Num %i Len %i", aPosition / aBlockLength, aBlockLength); + + for (uint16_t i = 0; i < aBlockLength / 16; i++) + { + mInterpreter.OutputBytes(&aBlock[i * 16], 16); + mInterpreter.OutputLine(""); + } + + return OT_ERROR_NONE; +} + +otError CoapSecure::BlockwiseTransmitHook(void * aContext, + uint8_t * aBlock, + uint32_t aPosition, + uint16_t *aBlockLength, + bool * aMore) +{ + return static_cast(aContext)->BlockwiseTransmitHook(aBlock, aPosition, aBlockLength, aMore); +} + +otError CoapSecure::BlockwiseTransmitHook(uint8_t *aBlock, uint32_t aPosition, uint16_t *aBlockLength, bool *aMore) +{ + static uint32_t blockCount = 0; + OT_UNUSED_VARIABLE(aPosition); + + // Send a random payload + otRandomNonCryptoFillBuffer(aBlock, *aBlockLength); + + mInterpreter.OutputLine("send block: Num %i Len %i", blockCount, *aBlockLength); + + for (uint16_t i = 0; i < *aBlockLength / 16; i++) + { + mInterpreter.OutputBytes(&aBlock[i * 16], 16); + mInterpreter.OutputLine(""); + } + + if (blockCount == mBlockCount - 1) + { + blockCount = 0; + *aMore = false; + } + else + { + *aMore = true; + blockCount++; + } + + return OT_ERROR_NONE; +} +#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + } // namespace Cli } // namespace ot diff --git a/src/cli/cli_coap_secure.hpp b/src/cli/cli_coap_secure.hpp index 18f0d1fe5..fa52c1717 100644 --- a/src/cli/cli_coap_secure.hpp +++ b/src/cli/cli_coap_secure.hpp @@ -111,6 +111,27 @@ private: static void HandleResponse(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo, otError aError); void HandleResponse(otMessage *aMessage, const otMessageInfo *aMessageInfo, otError aError); +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + + static otError BlockwiseReceiveHook(void * aContext, + const uint8_t *aBlock, + uint32_t aPosition, + uint16_t aBlockLength, + bool aMore, + uint32_t aTotalLength); + otError BlockwiseReceiveHook(const uint8_t *aBlock, + uint32_t aPosition, + uint16_t aBlockLength, + bool aMore, + uint32_t aTotalLength); + static otError BlockwiseTransmitHook(void * aContext, + uint8_t * aBlock, + uint32_t aPosition, + uint16_t *aBlockLength, + bool * aMore); + otError BlockwiseTransmitHook(uint8_t *aBlock, uint32_t aPosition, uint16_t *aBlockLength, bool *aMore); +#endif + #if CLI_COAP_SECURE_USE_COAP_DEFAULT_HANDLER static void DefaultHandler(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo); void DefaultHandler(otMessage *aMessage, const otMessageInfo *aMessageInfo); @@ -143,9 +164,13 @@ private: Interpreter &mInterpreter; +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + otCoapBlockwiseResource mResource; +#else otCoapResource mResource; - char mUriPath[kMaxUriLength]; - char mResourceContent[kMaxBufferSize]; +#endif + char mUriPath[kMaxUriLength]; + char mResourceContent[kMaxBufferSize]; bool mShutdownFlag; bool mUseCertificate; @@ -153,6 +178,9 @@ private: uint8_t mPskLength; uint8_t mPskId[kPskIdMaxLength]; uint8_t mPskIdLength; +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + uint32_t mBlockCount; +#endif }; } // namespace Cli diff --git a/src/core/api/coap_api.cpp b/src/core/api/coap_api.cpp index b84ec1882..b6f0945f1 100644 --- a/src/core/api/coap_api.cpp +++ b/src/core/api/coap_api.cpp @@ -101,18 +101,18 @@ otError otCoapMessageAppendUriPathOptions(otMessage *aMessage, const char *aUriP return static_cast(aMessage)->AppendUriPathOptions(aUriPath); } -uint16_t otCoapBlockSizeFromExponent(otCoapBlockSize aSize) +uint16_t otCoapBlockSizeFromExponent(otCoapBlockSzx aSize) { return static_cast( 1 << (static_cast(aSize) + static_cast(Coap::Message::kBlockSzxBase))); } -otError otCoapMessageAppendBlock2Option(otMessage *aMessage, uint32_t aNum, bool aMore, otCoapBlockSize aSize) +otError otCoapMessageAppendBlock2Option(otMessage *aMessage, uint32_t aNum, bool aMore, otCoapBlockSzx aSize) { return static_cast(aMessage)->AppendBlockOption(Coap::Message::kBlockType2, aNum, aMore, aSize); } -otError otCoapMessageAppendBlock1Option(otMessage *aMessage, uint32_t aNum, bool aMore, otCoapBlockSize aSize) +otError otCoapMessageAppendBlock1Option(otMessage *aMessage, uint32_t aNum, bool aMore, otCoapBlockSzx aSize) { return static_cast(aMessage)->AppendBlockOption(Coap::Message::kBlockType1, aNum, aMore, aSize); } @@ -214,6 +214,34 @@ otError otCoapOptionIteratorGetOptionValue(otCoapOptionIterator *aIterator, void return static_cast(aIterator)->ReadOptionValue(aValue); } +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE +otError otCoapSendRequestBlockWiseWithParameters(otInstance * aInstance, + otMessage * aMessage, + const otMessageInfo * aMessageInfo, + otCoapResponseHandler aHandler, + void * aContext, + const otCoapTxParameters * aTxParameters, + otCoapBlockwiseTransmitHook aTransmitHook, + otCoapBlockwiseReceiveHook aReceiveHook) +{ + otError error; + Instance & instance = *static_cast(aInstance); + const Coap::TxParameters &txParameters = Coap::TxParameters::From(aTxParameters); + + if (aTxParameters != nullptr) + { + VerifyOrExit(txParameters.IsValid(), error = OT_ERROR_INVALID_ARGS); + } + + error = instance.GetApplicationCoap().SendMessage(*static_cast(aMessage), + *static_cast(aMessageInfo), + txParameters, aHandler, aContext, aTransmitHook, aReceiveHook); + +exit: + return error; +} +#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + otError otCoapSendRequestWithParameters(otInstance * aInstance, otMessage * aMessage, const otMessageInfo * aMessageInfo, @@ -252,6 +280,22 @@ otError otCoapStop(otInstance *aInstance) return instance.GetApplicationCoap().Stop(); } +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE +void otCoapAddBlockWiseResource(otInstance *aInstance, otCoapBlockwiseResource *aResource) +{ + Instance &instance = *static_cast(aInstance); + + instance.GetApplicationCoap().AddBlockWiseResource(*static_cast(aResource)); +} + +void otCoapRemoveBlockWiseResource(otInstance *aInstance, otCoapBlockwiseResource *aResource) +{ + Instance &instance = *static_cast(aInstance); + + instance.GetApplicationCoap().RemoveBlockWiseResource(*static_cast(aResource)); +} +#endif + void otCoapAddResource(otInstance *aInstance, otCoapResource *aResource) { Instance &instance = *static_cast(aInstance); @@ -273,6 +317,22 @@ void otCoapSetDefaultHandler(otInstance *aInstance, otCoapRequestHandler aHandle instance.GetApplicationCoap().SetDefaultHandler(aHandler, aContext); } +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE +otError otCoapSendResponseBlockWiseWithParameters(otInstance * aInstance, + otMessage * aMessage, + const otMessageInfo * aMessageInfo, + const otCoapTxParameters * aTxParameters, + void * aContext, + otCoapBlockwiseTransmitHook aTransmitHook) +{ + Instance &instance = *static_cast(aInstance); + + return instance.GetApplicationCoap().SendMessage( + *static_cast(aMessage), *static_cast(aMessageInfo), + Coap::TxParameters::From(aTxParameters), nullptr, aContext, aTransmitHook, nullptr); +} +#endif + otError otCoapSendResponseWithParameters(otInstance * aInstance, otMessage * aMessage, const otMessageInfo * aMessageInfo, diff --git a/src/core/api/coap_secure_api.cpp b/src/core/api/coap_secure_api.cpp index 942bc7851..699131697 100644 --- a/src/core/api/coap_secure_api.cpp +++ b/src/core/api/coap_secure_api.cpp @@ -151,6 +151,21 @@ void otCoapSecureStop(otInstance *aInstance) instance.GetApplicationCoapSecure().Stop(); } +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE +otError otCoapSecureSendRequestBlockWise(otInstance * aInstance, + otMessage * aMessage, + otCoapResponseHandler aHandler, + void * aContext, + otCoapBlockwiseTransmitHook aTransmitHook, + otCoapBlockwiseReceiveHook aReceiveHook) +{ + Instance &instance = *static_cast(aInstance); + + return instance.GetApplicationCoapSecure().SendMessage(*static_cast(aMessage), aHandler, aContext, + aTransmitHook, aReceiveHook); +} +#endif + otError otCoapSecureSendRequest(otInstance * aInstance, otMessage * aMessage, otCoapResponseHandler aHandler, @@ -161,6 +176,22 @@ otError otCoapSecureSendRequest(otInstance * aInstance, return instance.GetApplicationCoapSecure().SendMessage(*static_cast(aMessage), aHandler, aContext); } +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE +void otCoapSecureAddBlockWiseResource(otInstance *aInstance, otCoapBlockwiseResource *aResource) +{ + Instance &instance = *static_cast(aInstance); + + instance.GetApplicationCoapSecure().AddBlockWiseResource(*static_cast(aResource)); +} + +void otCoapSecureRemoveBlockWiseResource(otInstance *aInstance, otCoapBlockwiseResource *aResource) +{ + Instance &instance = *static_cast(aInstance); + + instance.GetApplicationCoapSecure().RemoveBlockWiseResource(*static_cast(aResource)); +} +#endif + void otCoapSecureAddResource(otInstance *aInstance, otCoapResource *aResource) { Instance &instance = *static_cast(aInstance); @@ -191,6 +222,21 @@ void otCoapSecureSetDefaultHandler(otInstance *aInstance, otCoapRequestHandler a instance.GetApplicationCoapSecure().SetDefaultHandler(aHandler, aContext); } +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE +otError otCoapSecureSendResponseBlockWise(otInstance * aInstance, + otMessage * aMessage, + const otMessageInfo * aMessageInfo, + void * aContext, + otCoapBlockwiseTransmitHook aTransmitHook) +{ + Instance &instance = *static_cast(aInstance); + + return instance.GetApplicationCoapSecure().SendMessage(*static_cast(aMessage), + *static_cast(aMessageInfo), + nullptr, aContext, aTransmitHook); +} +#endif + otError otCoapSecureSendResponse(otInstance *aInstance, otMessage *aMessage, const otMessageInfo *aMessageInfo) { Instance &instance = *static_cast(aInstance); diff --git a/src/core/coap/coap.cpp b/src/core/coap/coap.cpp index 9cce0523f..462c8d22c 100644 --- a/src/core/coap/coap.cpp +++ b/src/core/coap/coap.cpp @@ -56,6 +56,9 @@ CoapBase::CoapBase(Instance &aInstance, Sender aSender) , mDefaultHandler(nullptr) , mDefaultHandlerContext(nullptr) , mSender(aSender) +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + , mLastResponse(nullptr) +#endif { } @@ -88,6 +91,19 @@ void CoapBase::ClearRequests(const Ip6::Address *aAddress) } } +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE +void CoapBase::AddBlockWiseResource(ResourceBlockWise &aResource) +{ + IgnoreError(mBlockWiseResources.Add(aResource)); +} + +void CoapBase::RemoveBlockWiseResource(ResourceBlockWise &aResource) +{ + IgnoreError(mBlockWiseResources.Remove(aResource)); + aResource.SetNext(nullptr); +} +#endif + void CoapBase::AddResource(Resource &aResource) { IgnoreError(mResources.Add(aResource)); @@ -141,25 +157,76 @@ otError CoapBase::Send(ot::Message &aMessage, const Ip6::MessageInfo &aMessageIn return error; } +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE +otError CoapBase::SendMessage(Message & aMessage, + const Ip6::MessageInfo & aMessageInfo, + const TxParameters & aTxParameters, + ResponseHandler aHandler, + void * aContext, + otCoapBlockwiseTransmitHook aTransmitHook, + otCoapBlockwiseReceiveHook aReceiveHook) +#else otError CoapBase::SendMessage(Message & aMessage, const Ip6::MessageInfo &aMessageInfo, const TxParameters & aTxParameters, ResponseHandler aHandler, void * aContext) +#endif { otError error; Message *storedCopy = nullptr; uint16_t copyLength = 0; +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + uint8_t buf[kMaxBlockLength] = {0}; + uint16_t bufLen = kMaxBlockLength; + bool moreBlocks = false; +#endif switch (aMessage.GetType()) { case kTypeAck: +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + // Check for block-wise transfer + if ((aTransmitHook != nullptr) && (aMessage.ReadBlockOptionValues(kOptionBlock2) == OT_ERROR_NONE) && + (aMessage.GetBlockWiseBlockNumber() == 0)) + { + // Set payload for first block of the transfer + VerifyOrExit((bufLen = otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize())) <= kMaxBlockLength, + error = OT_ERROR_NO_BUFS); + SuccessOrExit(error = aTransmitHook(aContext, buf, aMessage.GetBlockWiseBlockNumber() * bufLen, &bufLen, + &moreBlocks)); + SuccessOrExit(error = aMessage.AppendBytes(buf, bufLen)); + + SuccessOrExit(error = CacheLastBlockResponse(&aMessage)); + } +#endif + mResponsesQueue.EnqueueResponse(aMessage, aMessageInfo, aTxParameters); break; case kTypeReset: OT_ASSERT(aMessage.GetCode() == kCodeEmpty); break; default: +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + // Check for block-wise transfer + if ((aTransmitHook != nullptr) && (aMessage.ReadBlockOptionValues(kOptionBlock1) == OT_ERROR_NONE) && + (aMessage.GetBlockWiseBlockNumber() == 0)) + { + // Set payload for first block of the transfer + VerifyOrExit((bufLen = otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize())) <= kMaxBlockLength, + error = OT_ERROR_NO_BUFS); + SuccessOrExit(error = aTransmitHook(aContext, buf, aMessage.GetBlockWiseBlockNumber() * bufLen, &bufLen, + &moreBlocks)); + SuccessOrExit(error = aMessage.AppendBytes(buf, bufLen)); + + // Block-Wise messages always have to be confirmable + if (aMessage.IsNonConfirmable()) + { + aMessage.SetType(kTypeConfirmable); + } + } +#endif + aMessage.SetMessageId(mMessageId++); break; } @@ -228,6 +295,10 @@ otError CoapBase::SendMessage(Message & aMessage, metadata.mHopLimit = aMessageInfo.GetHopLimit(); metadata.mIsHostInterface = aMessageInfo.IsHostInterface(); #endif +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + metadata.mBlockwiseReceiveHook = aReceiveHook; + metadata.mBlockwiseTransmitHook = aTransmitHook; +#endif #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE metadata.mObserve = observe; #endif @@ -256,7 +327,11 @@ otError CoapBase::SendMessage(Message & aMessage, ResponseHandler aHandler, void * aContext) { +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + return SendMessage(aMessage, aMessageInfo, TxParameters::GetDefault(), aHandler, aContext, nullptr, nullptr); +#else return SendMessage(aMessage, aMessageInfo, TxParameters::GetDefault(), aHandler, aContext); +#endif } otError CoapBase::SendReset(Message &aRequest, const Ip6::MessageInfo &aMessageInfo) @@ -470,6 +545,402 @@ void CoapBase::DequeueMessage(Message &aMessage) // the timer would just shoot earlier and then it'd be setup again. } +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE +void CoapBase::FreeLastBlockResponse(void) +{ + if (mLastResponse != nullptr) + { + mLastResponse->Free(); + mLastResponse = nullptr; + } +} + +otError CoapBase::CacheLastBlockResponse(Message *aResponse) +{ + otError error = OT_ERROR_NONE; + // Save last response for block-wise transfer + FreeLastBlockResponse(); + + if ((mLastResponse = aResponse->Clone()) == nullptr) + { + error = OT_ERROR_NO_BUFS; + } + + return error; +} + +otError CoapBase::PrepareNextBlockRequest(Message::BlockType aType, + bool aMoreBlocks, + Message & aRequestOld, + Message & aRequest, + Message & aMessage) +{ + otError error = OT_ERROR_NONE; + bool isOptionSet = false; + uint64_t optionBuf = 0; + uint16_t blockOption = 0; + Option::Iterator iterator; + + blockOption = (aType == Message::kBlockType1) ? kOptionBlock1 : kOptionBlock2; + + aRequest.Init(kTypeConfirmable, static_cast(aRequestOld.GetCode())); + SuccessOrExit(error = iterator.Init(aRequestOld)); + + // Copy options from last response to next message + for (; !iterator.IsDone() && iterator.GetOption()->GetLength() != 0; error = iterator.Advance()) + { + uint16_t optionNumber = iterator.GetOption()->GetNumber(); + + SuccessOrExit(error); + + // Check if option to copy next is higher than or equal to Block1 option + if (optionNumber >= blockOption && !isOptionSet) + { + // Write Block1 option to next message + SuccessOrExit(error = aRequest.AppendBlockOption(aType, aMessage.GetBlockWiseBlockNumber() + 1, aMoreBlocks, + aMessage.GetBlockWiseBlockSize())); + aRequest.SetBlockWiseBlockNumber(aMessage.GetBlockWiseBlockNumber() + 1); + aRequest.SetBlockWiseBlockSize(aMessage.GetBlockWiseBlockSize()); + aRequest.SetMoreBlocksFlag(aMoreBlocks); + + isOptionSet = true; + + // If option to copy next is Block1 or Block2 option, option is not copied + if (optionNumber == kOptionBlock1 || optionNumber == kOptionBlock2) + { + continue; + } + } + + // Copy option + SuccessOrExit(error = iterator.ReadOptionValue(&optionBuf)); + SuccessOrExit(error = aRequest.AppendOption(optionNumber, iterator.GetOption()->GetLength(), &optionBuf)); + } + + if (!isOptionSet) + { + // Write Block1 option to next message + SuccessOrExit(error = aRequest.AppendBlockOption(aType, aMessage.GetBlockWiseBlockNumber() + 1, aMoreBlocks, + aMessage.GetBlockWiseBlockSize())); + aRequest.SetBlockWiseBlockNumber(aMessage.GetBlockWiseBlockNumber() + 1); + aRequest.SetBlockWiseBlockSize(aMessage.GetBlockWiseBlockSize()); + aRequest.SetMoreBlocksFlag(aMoreBlocks); + + isOptionSet = true; + } + +exit: + return error; +} + +otError CoapBase::SendNextBlock1Request(Message & aRequest, + Message & aMessage, + const Ip6::MessageInfo &aMessageInfo, + const Metadata & aCoapMetadata) +{ + otError error = OT_ERROR_NONE; + Message *request = nullptr; + bool moreBlocks = false; + uint8_t buf[kMaxBlockLength] = {0}; + uint16_t bufLen = kMaxBlockLength; + + SuccessOrExit(error = aRequest.ReadBlockOptionValues(kOptionBlock1)); + SuccessOrExit(error = aMessage.ReadBlockOptionValues(kOptionBlock1)); + + // Conclude block-wise transfer if last block has been received + if (!aRequest.IsMoreBlocksFlagSet()) + { + FinalizeCoapTransaction(aRequest, aCoapMetadata, &aMessage, &aMessageInfo, OT_ERROR_NONE); + ExitNow(); + } + + // Get next block + VerifyOrExit((bufLen = otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize())) <= kMaxBlockLength, + error = OT_ERROR_NO_BUFS); + + SuccessOrExit( + error = aCoapMetadata.mBlockwiseTransmitHook(aCoapMetadata.mResponseContext, buf, + otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize()) * + (aMessage.GetBlockWiseBlockNumber() + 1), + &bufLen, &moreBlocks)); + + // Check if block length is valid + VerifyOrExit(bufLen <= otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize()), + error = OT_ERROR_INVALID_ARGS); + + // Init request for next block + VerifyOrExit((request = NewMessage()) != nullptr, error = OT_ERROR_NO_BUFS); + SuccessOrExit(error = PrepareNextBlockRequest(Message::kBlockType1, moreBlocks, aRequest, *request, aMessage)); + + SuccessOrExit(error = request->SetPayloadMarker()); + + SuccessOrExit(error = request->AppendBytes(buf, bufLen)); + + DequeueMessage(aRequest); + + otLogInfoCoap("Send Block1 Nr. %d, Size: %d bytes, More Blocks Flag: %d", request->GetBlockWiseBlockNumber(), + otCoapBlockSizeFromExponent(request->GetBlockWiseBlockSize()), request->IsMoreBlocksFlagSet()); + + SuccessOrExit(error = SendMessage(*request, aMessageInfo, TxParameters::GetDefault(), + aCoapMetadata.mResponseHandler, aCoapMetadata.mResponseContext, + aCoapMetadata.mBlockwiseTransmitHook, aCoapMetadata.mBlockwiseReceiveHook)); + +exit: + FreeMessageOnError(request, error); + + return error; +} + +otError CoapBase::SendNextBlock2Request(Message & aRequest, + Message & aMessage, + const Ip6::MessageInfo &aMessageInfo, + const Metadata & aCoapMetadata, + uint32_t aTotalLength, + bool aBeginBlock1Transfer) +{ + otError error = OT_ERROR_NONE; + Message *request = nullptr; + uint8_t buf[kMaxBlockLength] = {0}; + uint16_t bufLen = kMaxBlockLength; + + SuccessOrExit(error = aMessage.ReadBlockOptionValues(kOptionBlock2)); + + // Check payload and block length + VerifyOrExit((aMessage.GetLength() - aMessage.GetOffset()) <= + otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize()) && + (aMessage.GetLength() - aMessage.GetOffset()) <= kMaxBlockLength, + error = OT_ERROR_NO_BUFS); + + // Read and then forward payload to receive hook function + bufLen = aMessage.ReadBytes(aMessage.GetOffset(), buf, aMessage.GetLength() - aMessage.GetOffset()); + SuccessOrExit( + error = aCoapMetadata.mBlockwiseReceiveHook(aCoapMetadata.mResponseContext, buf, + otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize()) * + aMessage.GetBlockWiseBlockNumber(), + bufLen, aMessage.IsMoreBlocksFlagSet(), aTotalLength)); + + // CoAP Block-Wise Transfer continues + otLogInfoCoap("Received Block2 Nr. %d , Size: %d bytes, More Blocks Flag: %d", aMessage.GetBlockWiseBlockNumber(), + otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize()), aMessage.IsMoreBlocksFlagSet()); + + // Conclude block-wise transfer if last block has been received + if (!aMessage.IsMoreBlocksFlagSet()) + { + FinalizeCoapTransaction(aRequest, aCoapMetadata, &aMessage, &aMessageInfo, OT_ERROR_NONE); + ExitNow(); + } + + // Init request for next block + VerifyOrExit((request = NewMessage()) != nullptr, error = OT_ERROR_NO_BUFS); + SuccessOrExit(error = PrepareNextBlockRequest(Message::kBlockType2, aMessage.IsMoreBlocksFlagSet(), aRequest, + *request, aMessage)); + + if (!aBeginBlock1Transfer) + { + DequeueMessage(aRequest); + } + + otLogInfoCoap("Request Block2 Nr. %d, Size: %d bytes", request->GetBlockWiseBlockNumber(), + otCoapBlockSizeFromExponent(request->GetBlockWiseBlockSize())); + + SuccessOrExit(error = + SendMessage(*request, aMessageInfo, TxParameters::GetDefault(), aCoapMetadata.mResponseHandler, + aCoapMetadata.mResponseContext, nullptr, aCoapMetadata.mBlockwiseReceiveHook)); + +exit: + FreeMessageOnError(request, error); + + return error; +} + +otError CoapBase::ProcessBlock1Request(Message & aMessage, + const Ip6::MessageInfo & aMessageInfo, + const ResourceBlockWise &aResource, + uint32_t aTotalLength) +{ + otError error = OT_ERROR_NONE; + Message *response = nullptr; + uint8_t buf[kMaxBlockLength] = {0}; + uint16_t bufLen = kMaxBlockLength; + + SuccessOrExit(error = aMessage.ReadBlockOptionValues(kOptionBlock1)); + + // Read and then forward payload to receive hook function + VerifyOrExit((aMessage.GetLength() - aMessage.GetOffset()) <= kMaxBlockLength, error = OT_ERROR_NO_BUFS); + bufLen = aMessage.ReadBytes(aMessage.GetOffset(), buf, aMessage.GetLength() - aMessage.GetOffset()); + SuccessOrExit(error = aResource.HandleBlockReceive(buf, + otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize()) * + aMessage.GetBlockWiseBlockNumber(), + bufLen, aMessage.IsMoreBlocksFlagSet(), aTotalLength)); + + if (aMessage.IsMoreBlocksFlagSet()) + { + // Set up next response + VerifyOrExit((response = NewMessage()) != nullptr, error = OT_ERROR_FAILED); + response->Init(kTypeAck, kCodeContinue); + response->SetMessageId(aMessage.GetMessageId()); + IgnoreReturnValue( + response->SetToken(static_cast(aMessage).GetToken(), aMessage.GetTokenLength())); + + response->SetBlockWiseBlockNumber(aMessage.GetBlockWiseBlockNumber()); + response->SetMoreBlocksFlag(aMessage.IsMoreBlocksFlagSet()); + response->SetBlockWiseBlockSize(aMessage.GetBlockWiseBlockSize()); + + SuccessOrExit(error = response->AppendBlockOption(Message::kBlockType1, response->GetBlockWiseBlockNumber(), + response->IsMoreBlocksFlagSet(), + response->GetBlockWiseBlockSize())); + + SuccessOrExit(error = CacheLastBlockResponse(response)); + + otLogInfoCoap("Acknowledge Block1 Nr. %d, Size: %d bytes", response->GetBlockWiseBlockNumber(), + otCoapBlockSizeFromExponent(response->GetBlockWiseBlockSize())); + + SuccessOrExit(error = SendMessage(*response, aMessageInfo)); + + error = OT_ERROR_BUSY; + } + else + { + // Conclude block-wise transfer if last block has been received + FreeLastBlockResponse(); + error = OT_ERROR_NONE; + } + +exit: + if (error != OT_ERROR_NONE && error != OT_ERROR_BUSY && response != nullptr) + { + response->Free(); + } + + return error; +} + +otError CoapBase::ProcessBlock2Request(Message & aMessage, + const Ip6::MessageInfo & aMessageInfo, + const ResourceBlockWise &aResource) +{ + otError error = OT_ERROR_NONE; + Message * response = nullptr; + uint8_t buf[kMaxBlockLength] = {0}; + uint16_t bufLen = kMaxBlockLength; + bool moreBlocks = false; + uint64_t optionBuf = 0; + Option::Iterator iterator; + + SuccessOrExit(error = aMessage.ReadBlockOptionValues(kOptionBlock2)); + + otLogInfoCoap("Request for Block2 Nr. %d, Size: %d bytes received", aMessage.GetBlockWiseBlockNumber(), + otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize())); + + if (aMessage.GetBlockWiseBlockNumber() == 0) + { + aResource.HandleRequest(aMessage, aMessageInfo); + ExitNow(); + } + + // Set up next response + VerifyOrExit((response = NewMessage()) != nullptr, error = OT_ERROR_NO_BUFS); + response->Init(kTypeAck, kCodeContent); + response->SetMessageId(aMessage.GetMessageId()); + + VerifyOrExit((bufLen = otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize())) <= kMaxBlockLength, + error = OT_ERROR_NO_BUFS); + SuccessOrExit(error = aResource.HandleBlockTransmit(buf, + otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize()) * + aMessage.GetBlockWiseBlockNumber(), + &bufLen, &moreBlocks)); + + response->SetMoreBlocksFlag(moreBlocks); + if (moreBlocks) + { + switch (bufLen) + { + case 1024: + response->SetBlockWiseBlockSize(OT_COAP_OPTION_BLOCK_SZX_1024); + break; + case 512: + response->SetBlockWiseBlockSize(OT_COAP_OPTION_BLOCK_SZX_512); + break; + case 256: + response->SetBlockWiseBlockSize(OT_COAP_OPTION_BLOCK_SZX_256); + break; + case 128: + response->SetBlockWiseBlockSize(OT_COAP_OPTION_BLOCK_SZX_128); + break; + case 64: + response->SetBlockWiseBlockSize(OT_COAP_OPTION_BLOCK_SZX_64); + break; + case 32: + response->SetBlockWiseBlockSize(OT_COAP_OPTION_BLOCK_SZX_32); + break; + case 16: + response->SetBlockWiseBlockSize(OT_COAP_OPTION_BLOCK_SZX_16); + break; + default: + error = OT_ERROR_INVALID_ARGS; + ExitNow(); + break; + } + } + else + { + // Verify that buffer length is not larger than requested block size + VerifyOrExit(bufLen <= otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize()), + error = OT_ERROR_INVALID_ARGS); + response->SetBlockWiseBlockSize(aMessage.GetBlockWiseBlockSize()); + } + + response->SetBlockWiseBlockNumber( + (otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize()) * aMessage.GetBlockWiseBlockNumber()) / + (otCoapBlockSizeFromExponent(response->GetBlockWiseBlockSize()))); + + // Copy options from last response + SuccessOrExit(error = iterator.Init(*mLastResponse)); + + while (!iterator.IsDone()) + { + uint16_t optionNumber = iterator.GetOption()->GetNumber(); + + if (optionNumber == kOptionBlock2) + { + SuccessOrExit(error = response->AppendBlockOption(Message::kBlockType2, response->GetBlockWiseBlockNumber(), + response->IsMoreBlocksFlagSet(), + response->GetBlockWiseBlockSize())); + } + else if (optionNumber == kOptionBlock1) + { + SuccessOrExit(error = iterator.ReadOptionValue(&optionBuf)); + SuccessOrExit(error = response->AppendOption(optionNumber, iterator.GetOption()->GetLength(), &optionBuf)); + } + + SuccessOrExit(error = iterator.Advance()); + } + + SuccessOrExit(error = response->SetPayloadMarker()); + SuccessOrExit(error = response->AppendBytes(buf, bufLen)); + + if (response->IsMoreBlocksFlagSet()) + { + SuccessOrExit(error = CacheLastBlockResponse(response)); + } + else + { + // Conclude block-wise transfer if last block has been received + FreeLastBlockResponse(); + } + + otLogInfoCoap("Send Block2 Nr. %d, Size: %d bytes, More Blocks Flag %d", response->GetBlockWiseBlockNumber(), + otCoapBlockSizeFromExponent(response->GetBlockWiseBlockSize()), response->IsMoreBlocksFlagSet()); + + SuccessOrExit(error = SendMessage(*response, aMessageInfo)); + +exit: + FreeMessageOnError(response, error); + + return error; +} +#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + void CoapBase::SendCopy(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo) { otError error; @@ -567,6 +1038,10 @@ void CoapBase::ProcessReceivedResponse(Message &aMessage, const Ip6::MessageInfo #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE bool responseObserve = false; #endif +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + uint8_t blockOptionType = 0; + uint32_t totalTransfereSize = 0; +#endif request = FindRelatedRequest(aMessage, aMessageInfo, metadata); VerifyOrExit(request != nullptr); @@ -642,9 +1117,89 @@ void CoapBase::ProcessReceivedResponse(Message &aMessage, const Ip6::MessageInfo } else #endif +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + { + if (metadata.mBlockwiseTransmitHook != nullptr || metadata.mBlockwiseReceiveHook != nullptr) + { + // Search for CoAP Block-Wise Option [RFC7959] + Option::Iterator iterator; + + SuccessOrExit(error = iterator.Init(aMessage)); + while (!iterator.IsDone()) + { + switch (iterator.GetOption()->GetNumber()) + { + case kOptionBlock1: + blockOptionType += 1; + break; + + case kOptionBlock2: + blockOptionType += 2; + break; + + case kOptionSize2: + // ToDo: wait for method to read uint option values + totalTransfereSize = 0; + break; + + default: + break; + } + + SuccessOrExit(error = iterator.Advance()); + } + } + switch (blockOptionType) + { + case 0: + // Piggybacked response. + FinalizeCoapTransaction(*request, metadata, &aMessage, &aMessageInfo, OT_ERROR_NONE); + break; + case 1: // Block1 option + if (aMessage.GetCode() == kCodeContinue && metadata.mBlockwiseTransmitHook != nullptr) + { + error = SendNextBlock1Request(*request, aMessage, aMessageInfo, metadata); + } + + if (aMessage.GetCode() != kCodeContinue || metadata.mBlockwiseTransmitHook == nullptr || + error != OT_ERROR_NONE) + { + FinalizeCoapTransaction(*request, metadata, &aMessage, &aMessageInfo, error); + } + break; + case 2: // Block2 option + if (aMessage.GetCode() < kCodeBadRequest && metadata.mBlockwiseReceiveHook != nullptr) + { + error = SendNextBlock2Request(*request, aMessage, aMessageInfo, metadata, totalTransfereSize, + false); + } + + if (aMessage.GetCode() >= kCodeBadRequest || metadata.mBlockwiseReceiveHook == nullptr || + error != OT_ERROR_NONE) + { + FinalizeCoapTransaction(*request, metadata, &aMessage, &aMessageInfo, error); + } + break; + case 3: // Block1 & Block2 option + if (aMessage.GetCode() < kCodeBadRequest && metadata.mBlockwiseReceiveHook != nullptr) + { + error = + SendNextBlock2Request(*request, aMessage, aMessageInfo, metadata, totalTransfereSize, true); + } + + FinalizeCoapTransaction(*request, metadata, &aMessage, &aMessageInfo, error); + break; + default: + error = OT_ERROR_ABORT; + FinalizeCoapTransaction(*request, metadata, &aMessage, &aMessageInfo, error); + break; + } + } +#else // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE { FinalizeCoapTransaction(*request, metadata, &aMessage, &aMessageInfo, OT_ERROR_NONE); } +#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE } // Silently ignore acknowledgments carrying requests (RFC 7252, p. 4.2) @@ -696,6 +1251,12 @@ void CoapBase::ProcessReceivedRequest(Message &aMessage, const Ip6::MessageInfo char uriPath[Message::kMaxReceivedUriPath + 1]; Message *cachedResponse = nullptr; otError error = OT_ERROR_NOT_FOUND; +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + Option::Iterator iterator; + char * curUriPath = uriPath; + uint8_t blockOptionType = 0; + uint32_t totalTransfereSize = 0; +#endif if (mInterceptor != nullptr) { @@ -718,7 +1279,108 @@ void CoapBase::ProcessReceivedRequest(Message &aMessage, const Ip6::MessageInfo break; } +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + SuccessOrExit(error = iterator.Init(aMessage)); + + while (!iterator.IsDone()) + { + switch (iterator.GetOption()->GetNumber()) + { + case kOptionUriPath: + if (curUriPath != uriPath) + { + *curUriPath++ = '/'; + } + + VerifyOrExit(curUriPath + iterator.GetOption()->GetLength() < OT_ARRAY_END(uriPath), + error = OT_ERROR_PARSE); + + IgnoreError(iterator.ReadOptionValue(curUriPath)); + curUriPath += iterator.GetOption()->GetLength(); + break; + + case kOptionBlock1: + blockOptionType += 1; + break; + + case kOptionBlock2: + blockOptionType += 2; + break; + + case kOptionSize1: + // ToDo: wait for method to read uint option values + totalTransfereSize = 0; + break; + + default: + break; + } + + SuccessOrExit(error = iterator.Advance()); + } + + curUriPath[0] = '\0'; + + for (const ResourceBlockWise *resource = mBlockWiseResources.GetHead(); resource; resource = resource->GetNext()) + { + if (strcmp(resource->GetUriPath(), uriPath) != 0) + { + continue; + } + + if ((resource->mReceiveHook != nullptr || resource->mTransmitHook != nullptr) && blockOptionType != 0) + { + switch (blockOptionType) + { + case 1: + if (resource->mReceiveHook != nullptr) + { + switch (ProcessBlock1Request(aMessage, aMessageInfo, *resource, totalTransfereSize)) + { + case OT_ERROR_NONE: + resource->HandleRequest(aMessage, aMessageInfo); + // Fall through + case OT_ERROR_BUSY: + error = OT_ERROR_NONE; + break; + case OT_ERROR_NO_BUFS: + IgnoreReturnValue(SendHeaderResponse(kCodeRequestTooLarge, aMessage, aMessageInfo)); + error = OT_ERROR_DROP; + break; + case OT_ERROR_NO_FRAME_RECEIVED: + IgnoreReturnValue(SendHeaderResponse(kCodeRequestIncomplete, aMessage, aMessageInfo)); + error = OT_ERROR_DROP; + break; + default: + IgnoreReturnValue(SendHeaderResponse(kCodeInternalError, aMessage, aMessageInfo)); + error = OT_ERROR_DROP; + break; + } + } + break; + case 2: + if (resource->mTransmitHook != nullptr) + { + if ((error = ProcessBlock2Request(aMessage, aMessageInfo, *resource)) != OT_ERROR_NONE) + { + IgnoreReturnValue(SendHeaderResponse(kCodeInternalError, aMessage, aMessageInfo)); + error = OT_ERROR_DROP; + } + } + break; + } + ExitNow(); + } + else + { + resource->HandleRequest(aMessage, aMessageInfo); + error = OT_ERROR_NONE; + ExitNow(); + } + } +#else SuccessOrExit(error = aMessage.ReadUriPathOptions(uriPath)); +#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE for (const Resource *resource = mResources.GetHead(); resource; resource = resource->GetNext()) { diff --git a/src/core/coap/coap.hpp b/src/core/coap/coap.hpp index 8a1d983d1..03229bcf8 100644 --- a/src/core/coap/coap.hpp +++ b/src/core/coap/coap.hpp @@ -169,13 +169,108 @@ public: */ const char *GetUriPath(void) const { return mUriPath; } -private: +protected: void HandleRequest(Message &aMessage, const Ip6::MessageInfo &aMessageInfo) const { mHandler(mContext, &aMessage, &aMessageInfo); } }; +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE +/** + * This class implements CoAP block-wise resource handling. + * + */ +class ResourceBlockWise : public otCoapBlockwiseResource +{ + friend class CoapBase; + +public: + /** + * This constructor initializes the resource. + * + * @param[in] aUriPath A pointer to a NULL-terminated string for the Uri-Path. + * @param[in] aHandler A function pointer that is called when receiving a CoAP message for @p aUriPath. + * @param[in] aContext A pointer to arbitrary context information. + * @param[in] aReceiveHook A function pointer that is called when receiving a CoAP block message for @p + * aUriPath. + * @param[in] aTransmitHook A function pointer that is called when transmitting a CoAP block message from @p + * aUriPath. + */ + ResourceBlockWise(const char * aUriPath, + otCoapRequestHandler aHandler, + void * aContext, + otCoapBlockwiseReceiveHook aReceiveHook, + otCoapBlockwiseTransmitHook aTransmitHook) + { + mUriPath = aUriPath; + mHandler = aHandler; + mContext = aContext; + mReceiveHook = aReceiveHook; + mTransmitHook = aTransmitHook; + mNext = nullptr; + } + + otError HandleBlockReceive(const uint8_t *aBlock, + uint32_t aPosition, + uint16_t aBlockLength, + bool aMore, + uint32_t aTotalLength) const + { + return mReceiveHook(otCoapBlockwiseResource::mContext, aBlock, aPosition, aBlockLength, aMore, aTotalLength); + } + + otError HandleBlockTransmit(uint8_t *aBlock, uint32_t aPosition, uint16_t *aBlockLength, bool *aMore) const + { + return mTransmitHook(otCoapBlockwiseResource::mContext, aBlock, aPosition, aBlockLength, aMore); + } + + /** + * This method gets the next entry in the linked list. + * + * @returns A pointer to the next entry in the linked list or nullptr if at the end of the list. + * + */ + const ResourceBlockWise *GetNext(void) const + { + return static_cast(static_cast(this)->mNext); + } + + /** + * This method gets the next entry in the linked list. + * + * @returns A pointer to the next entry in the linked list or nullptr if at the end of the list. + * + */ + ResourceBlockWise *GetNext(void) + { + return static_cast(static_cast(this)->mNext); + } + + /** + * This method sets the next pointer on the entry. + * + * @param[in] aNext A pointer to the next entry. + * + */ + void SetNext(ResourceBlockWise *aNext) { static_cast(this)->mNext = aNext; } + + /** + * This method returns a pointer to the URI path. + * + * @returns A pointer to the URI path. + * + */ + const char *GetUriPath(void) const { return mUriPath; } + +protected: + void HandleRequest(Message &aMessage, const Ip6::MessageInfo &aMessageInfo) const + { + mHandler(mContext, &aMessage, &aMessageInfo); + } +}; +#endif + /** * This class caches CoAP responses to implement message deduplication. * @@ -269,6 +364,12 @@ class CoapBase : public InstanceLocator, private NonCopyable friend class ResponsesQueue; public: +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + enum { + kMaxBlockLength = OPENTHREAD_CONFIG_COAP_MAX_BLOCK_LENGTH, + }; +#endif + /** * This function pointer is called before CoAP server processing a CoAP message. * @@ -298,6 +399,25 @@ public: */ void ClearRequests(const Ip6::Address &aAddress); +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + + /** + * This method adds a block-wise resource to the CoAP server. + * + * @param[in] aResource A reference to the resource. + * + */ + void AddBlockWiseResource(ResourceBlockWise &aResource); + + /** + * This method removes a block-wise resource from the CoAP server. + * + * @param[in] aResource A reference to the resource. + * + */ + void RemoveBlockWiseResource(ResourceBlockWise &aResource); +#endif + /** * This method adds a resource to the CoAP server. * @@ -343,6 +463,35 @@ public: return NewMessage(Message::Settings(Message::kWithLinkSecurity, Message::kPriorityNet)); } +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + /** + * This method sends a CoAP message block-wise with custom transmission parameters. + * + * If a response for a request is expected, respective function and context information should be provided. + * If no response is expected, these arguments should be NULL pointers. + * If Message Id was not set in the header (equal to 0), this function will assign unique Message Id to the message. + * + * @param[in] aMessage A reference to the message to send. + * @param[in] aMessageInfo A reference to the message info associated with @p aMessage. + * @param[in] aTxParameters A reference to transmission parameters for this message. + * @param[in] aHandler A function pointer that shall be called on response reception or time-out. + * @param[in] aContext A pointer to arbitrary context information. + * @param[in] aTransmitHook A pointer to a hook function for outgoing block-wise transfer. + * @param[in] aReceiveHook A pointer to a hook function for incoming block-wise transfer. + * + * @retval OT_ERROR_NONE Successfully sent CoAP message. + * @retval OT_ERROR_NO_BUFS Failed to allocate retransmission data. + * + */ + otError SendMessage(Message & aMessage, + const Ip6::MessageInfo & aMessageInfo, + const TxParameters & aTxParameters, + otCoapResponseHandler aHandler = nullptr, + void * aContext = nullptr, + otCoapBlockwiseTransmitHook aTransmitHook = nullptr, + otCoapBlockwiseReceiveHook aReceiveHook = nullptr); +#else // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + /** * This method sends a CoAP message with custom transmission parameters. * @@ -365,6 +514,7 @@ public: const TxParameters & aTxParameters, ResponseHandler aHandler = nullptr, void * aContext = nullptr); +#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE /** * This method sends a CoAP message with default transmission parameters. @@ -453,6 +603,24 @@ public: */ otError SendNotFound(const Message &aRequest, const Ip6::MessageInfo &aMessageInfo); +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + /** + * This method sends a header-only CoAP message to indicate not all blocks have been sent or + * were sent out of order. + * + * @param[in] aRequest A reference to the CoAP Message that was used in CoAP request. + * @param[in] aMessageInfo The message info corresponding to the CoAP request. + * + * @retval OT_ERROR_NONE Successfully enqueued the CoAP response message. + * @retval OT_ERROR_NO_BUFS Insufficient buffers available to send the CoAP response. + * + */ + otError SendRequestEntityIncomplete(const Message &aRequest, const Ip6::MessageInfo &aMessageInfo) + { + return SendHeaderResponse(kCodeRequestIncomplete, aRequest, aMessageInfo); + } +#endif + /** * This method aborts CoAP transactions associated with given handler and context. * @@ -551,6 +719,12 @@ private: #endif #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE bool mObserve : 1; // Information that this request involves Observations. +#endif +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + otCoapBlockwiseReceiveHook mBlockwiseReceiveHook; // A function pointer that is called on Block2 + // response reception. + otCoapBlockwiseTransmitHook mBlockwiseTransmitHook; // A function pointer that is called on Block1 + // response reception. #endif }; @@ -567,9 +741,38 @@ private: const Ip6::MessageInfo *aMessageInfo, otError aResult); +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + void FreeLastBlockResponse(void); + otError CacheLastBlockResponse(Message *aResponse); + + otError PrepareNextBlockRequest(Message::BlockType aType, + bool aMoreBlocks, + Message & aRequestOld, + Message & aRequest, + Message & aMessage); + otError ProcessBlock1Request(Message & aMessage, + const Ip6::MessageInfo & aMessageInfo, + const ResourceBlockWise &aResource, + uint32_t aTotalLength); + otError ProcessBlock2Request(Message & aMessage, + const Ip6::MessageInfo & aMessageInfo, + const ResourceBlockWise &aResource); +#endif void ProcessReceivedRequest(Message &aMessage, const Ip6::MessageInfo &aMessageInfo); void ProcessReceivedResponse(Message &aMessage, const Ip6::MessageInfo &aMessageInfo); +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + otError SendNextBlock1Request(Message & aRequest, + Message & aMessage, + const Ip6::MessageInfo &aMessageInfo, + const Metadata & aCoapMetadata); + otError SendNextBlock2Request(Message & aRequest, + Message & aMessage, + const Ip6::MessageInfo &aMessageInfo, + const Metadata & aCoapMetadata, + uint32_t aTotalLength, + bool aBeginBlock1Transfer); +#endif void SendCopy(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo); otError SendEmptyMessage(Type aType, const Message &aRequest, const Ip6::MessageInfo &aMessageInfo); @@ -589,6 +792,11 @@ private: void * mDefaultHandlerContext; const Sender mSender; + +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + LinkedList mBlockWiseResources; + Message * mLastResponse; +#endif }; /** diff --git a/src/core/coap/coap_message.cpp b/src/core/coap/coap_message.cpp index 5ca789ea0..23d9cbd52 100644 --- a/src/core/coap/coap_message.cpp +++ b/src/core/coap/coap_message.cpp @@ -51,6 +51,11 @@ void Message::Init(void) GetHelpData().mHeaderLength = kMinHeaderLength; IgnoreError(SetLength(GetHelpData().mHeaderLength)); +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + SetBlockWiseBlockNumber(0); + SetMoreBlocksFlag(false); + SetBlockWiseBlockSize(OT_COAP_OPTION_BLOCK_SZX_16); +#endif } void Message::Init(Type aType, Code aCode) @@ -258,13 +263,13 @@ exit: return error; } -otError Message::AppendBlockOption(Message::BlockType aType, uint32_t aNum, bool aMore, otCoapBlockSize aSize) +otError Message::AppendBlockOption(Message::BlockType aType, uint32_t aNum, bool aMore, otCoapBlockSzx aSize) { otError error = OT_ERROR_NONE; uint32_t encoded = aSize; VerifyOrExit(aType == kBlockType1 || aType == kBlockType2, error = OT_ERROR_INVALID_ARGS); - VerifyOrExit(aSize <= OT_COAP_BLOCK_SIZE_1024, error = OT_ERROR_INVALID_ARGS); + VerifyOrExit(aSize <= OT_COAP_OPTION_BLOCK_SZX_1024, error = OT_ERROR_INVALID_ARGS); VerifyOrExit(aNum < kBlockNumMax, error = OT_ERROR_INVALID_ARGS); encoded |= static_cast(aMore << kBlockMOffset); @@ -276,6 +281,49 @@ exit: return error; } +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE +otError Message::ReadBlockOptionValues(uint16_t aBlockType) +{ + otError error = OT_ERROR_NONE; + uint8_t buf[kMaxOptionHeaderSize] = {0}; + Option::Iterator iterator; + + VerifyOrExit((aBlockType == kOptionBlock1) || (aBlockType == kOptionBlock2), error = OT_ERROR_INVALID_ARGS); + + SuccessOrExit(error = iterator.Init(*this, aBlockType)); + SuccessOrExit(error = iterator.ReadOptionValue(buf)); + + SetBlockWiseBlockNumber(0); + SetMoreBlocksFlag(false); + + switch (iterator.GetOption()->GetLength()) + { + case 0: + case 1: + SetBlockWiseBlockNumber(static_cast((buf[0] & 0xf0) >> 4)); + SetMoreBlocksFlag(static_cast((buf[0] & 0x08) >> 3 == 1)); + SetBlockWiseBlockSize(static_cast(buf[0] & 0x07)); + break; + case 2: + SetBlockWiseBlockNumber(static_cast((buf[0] << 4) + ((buf[1] & 0xf0) >> 4))); + SetMoreBlocksFlag(static_cast((buf[1] & 0x08) >> 3 == 1)); + SetBlockWiseBlockSize(static_cast(buf[1] & 0x07)); + break; + case 3: + SetBlockWiseBlockNumber(static_cast((buf[0] << 12) + (buf[1] << 4) + ((buf[2] & 0xf0) >> 4))); + SetMoreBlocksFlag(static_cast((buf[2] & 0x08) >> 3 == 1)); + SetBlockWiseBlockSize(static_cast(buf[2] & 0x07)); + break; + default: + error = OT_ERROR_INVALID_ARGS; + break; + } + +exit: + return error; +} +#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + otError Message::SetPayloadMarker(void) { otError error = OT_ERROR_NONE; diff --git a/src/core/coap/coap_message.hpp b/src/core/coap/coap_message.hpp index b8580bac1..2eee903a6 100644 --- a/src/core/coap/coap_message.hpp +++ b/src/core/coap/coap_message.hpp @@ -149,6 +149,7 @@ enum : uint16_t kOptionLocationQuery = OT_COAP_OPTION_LOCATION_QUERY, ///< Location-Query kOptionBlock2 = OT_COAP_OPTION_BLOCK2, ///< Block2 (RFC7959) kOptionBlock1 = OT_COAP_OPTION_BLOCK1, ///< Block1 (RFC7959) + kOptionSize2 = OT_COAP_OPTION_SIZE2, ///< Size2 (RFC7959) kOptionProxyUri = OT_COAP_OPTION_PROXY_URI, ///< Proxy-Uri kOptionProxyScheme = OT_COAP_OPTION_PROXY_SCHEME, ///< Proxy-Scheme kOptionSize1 = OT_COAP_OPTION_SIZE1, ///< Size1 @@ -507,7 +508,7 @@ public: * @retval OT_ERROR_NO_BUFS The option length exceeds the buffer size. * */ - otError AppendBlockOption(BlockType aType, uint32_t aNum, bool aMore, otCoapBlockSize aSize); + otError AppendBlockOption(BlockType aType, uint32_t aNum, bool aMore, otCoapBlockSzx aSize); /** * This method appends a Proxy-Uri option. @@ -558,6 +559,62 @@ public: */ otError AppendUriQueryOption(const char *aUriQuery) { return AppendStringOption(kOptionUriQuery, aUriQuery); } +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + /** + * This function reads the information contained in a Block1 or Block2 option and set it in + * the HelpData of the message. + * + * @param[in] aBlockType Block1 or Block2 option value. + * + * @retval OT_ERROR_NONE The option has been found and is valid. + * @retval OT_ERROR_NOT_FOUND The option has not been found. + * @retval OT_ERROR_INVALID_ARGS The option is invalid. + */ + otError ReadBlockOptionValues(uint16_t aBlockType); + + /** + * This method returns the current header length of a message. + * + * @returns The length of the message header. + * + */ + uint16_t GetHeaderLength(void) const { return GetHelpData().mHeaderLength; } + + /** + * This method returns the block number of a CoAP block-wise transfer message. + * + * @returns The block number. + * + */ + uint32_t GetBlockWiseBlockNumber(void) const { return GetHelpData().mBlockWiseData.mBlockNumber; } + + /** + * This method checks if the More Blocks flag is set. + * + * @retval TRUE More Blocks flag is set. + * @retval FALSE More Blocks flag is not set. + * + */ + bool IsMoreBlocksFlagSet(void) const { return GetHelpData().mBlockWiseData.mMoreBlocks; } + + /** + * This method returns the block size of a CoAP block-wise transfer message. + * + * @returns The block size. + * + */ + otCoapBlockSzx GetBlockWiseBlockSize(void) const { return GetHelpData().mBlockWiseData.mBlockSize; } +#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + + /** + * This function reads and reassembles the URI path string and fills it into @p aUriPath. + * + * @retval OT_ERROR_NONE URI path string has been reassembled. + * @retval OT_ERROR_NO_BUFS URI path string is too long. + * + */ + otError GetUriPath(char *aUriPath) const; + /** * This method adds Payload Marker indicating beginning of the payload to the CoAP header. * @@ -597,6 +654,33 @@ public: */ otError SetDefaultResponseHeader(const Message &aRequest); +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + + /** + * This method sets the block number value in the message HelpData. + * + * @param[in] aBlockNumber Block number value to set. + * + */ + void SetBlockWiseBlockNumber(uint32_t aBlockNumber) { GetHelpData().mBlockWiseData.mBlockNumber = aBlockNumber; } + + /** + * This method sets the More Blocks falg in the message HelpData. + * + * @param[in] aMoreBlocks TRUE or FALSE. + * + */ + void SetMoreBlocksFlag(bool aMoreBlocks) { GetHelpData().mBlockWiseData.mMoreBlocks = aMoreBlocks; } + + /** + * This method sets the block size value in the message HelpData. + * + * @param[in] aBlockSize Block size value to set. + * + */ + void SetBlockWiseBlockSize(otCoapBlockSzx aBlockSize) { GetHelpData().mBlockWiseData.mBlockSize = aBlockSize; } +#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + /** * This method checks if a header is an empty message header. * @@ -845,6 +929,15 @@ private: kBlockNumMax = 0xffff, }; +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + struct BlockWiseData + { + uint32_t mBlockNumber; + bool mMoreBlocks; + otCoapBlockSzx mBlockSize; + }; +#endif + /** * This structure represents a CoAP header excluding CoAP options. * @@ -868,6 +961,9 @@ private: uint16_t mOptionLast; uint16_t mHeaderOffset; ///< The byte offset for the CoAP Header uint16_t mHeaderLength; +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + BlockWiseData mBlockWiseData; +#endif }; const HelpData &GetHelpData(void) const diff --git a/src/core/coap/coap_secure.cpp b/src/core/coap/coap_secure.cpp index 06769f88b..4eab5ca9f 100644 --- a/src/core/coap/coap_secure.cpp +++ b/src/core/coap/coap_secure.cpp @@ -118,6 +118,35 @@ void CoapSecure::SetPsk(const MeshCoP::JoinerPskd &aPskd) OT_ASSERT(error == OT_ERROR_NONE); } +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE +otError CoapSecure::SendMessage(Message & aMessage, + ResponseHandler aHandler, + void * aContext, + otCoapBlockwiseTransmitHook aTransmitHook, + otCoapBlockwiseReceiveHook aReceiveHook) +{ + otError error = OT_ERROR_NONE; + + VerifyOrExit(IsConnected(), error = OT_ERROR_INVALID_STATE); + + error = CoapBase::SendMessage(aMessage, mDtls.GetMessageInfo(), TxParameters::GetDefault(), aHandler, aContext, + aTransmitHook, aReceiveHook); + +exit: + return error; +} + +otError CoapSecure::SendMessage(Message & aMessage, + const Ip6::MessageInfo & aMessageInfo, + ResponseHandler aHandler, + void * aContext, + otCoapBlockwiseTransmitHook aTransmitHook, + otCoapBlockwiseReceiveHook aReceiveHook) +{ + return CoapBase::SendMessage(aMessage, aMessageInfo, TxParameters::GetDefault(), aHandler, aContext, aTransmitHook, + aReceiveHook); +} +#else // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE otError CoapSecure::SendMessage(Message &aMessage, ResponseHandler aHandler, void *aContext) { otError error = OT_ERROR_NONE; @@ -137,6 +166,7 @@ otError CoapSecure::SendMessage(Message & aMessage, { return CoapBase::SendMessage(aMessage, aMessageInfo, aHandler, aContext); } +#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE otError CoapSecure::Send(ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo) { diff --git a/src/core/coap/coap_secure.hpp b/src/core/coap/coap_secure.hpp index 889ed74e7..2f82d0693 100644 --- a/src/core/coap/coap_secure.hpp +++ b/src/core/coap/coap_secure.hpp @@ -272,6 +272,57 @@ public: #endif // OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE +#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + /** + * This method sends a CoAP message over secure DTLS connection. + * + * If a response for a request is expected, respective function and context information should be provided. + * If no response is expected, these arguments should be NULL pointers. + * If Message Id was not set in the header (equal to 0), this function will assign unique Message Id to the message. + * + * @param[in] aMessage A reference to the message to send. + * @param[in] aHandler A function pointer that shall be called on response reception or time-out. + * @param[in] aContext A pointer to arbitrary context information. + * @param[in] aTransmitHook A pointer to a hook function for outgoing block-wise transfer. + * @param[in] aReceiveHook A pointer to a hook function for incoming block-wise transfer. + * + * @retval OT_ERROR_NONE Successfully sent CoAP message. + * @retval OT_ERROR_NO_BUFS Failed to allocate retransmission data. + * @retval OT_ERROR_INVALID_STATE DTLS connection was not initialized. + * + */ + otError SendMessage(Message & aMessage, + ResponseHandler aHandler = nullptr, + void * aContext = nullptr, + otCoapBlockwiseTransmitHook aTransmitHook = nullptr, + otCoapBlockwiseReceiveHook aReceiveHook = nullptr); + + /** + * This method sends a CoAP message over secure DTLS connection. + * + * If a response for a request is expected, respective function and context information should be provided. + * If no response is expected, these arguments should be NULL pointers. + * If Message Id was not set in the header (equal to 0), this function will assign unique Message Id to the message. + * + * @param[in] aMessage A reference to the message to send. + * @param[in] aMessageInfo A reference to the message info associated with @p aMessage. + * @param[in] aHandler A function pointer that shall be called on response reception or time-out. + * @param[in] aContext A pointer to arbitrary context information. + * @param[in] aTransmitHook A pointer to a hook function for outgoing block-wise transfer. + * @param[in] aReceiveHook A pointer to a hook function for incoming block-wise transfer. + * + * @retval OT_ERROR_NONE Successfully sent CoAP message. + * @retval OT_ERROR_NO_BUFS Failed to allocate retransmission data. + * @retval OT_ERROR_INVALID_STATE DTLS connection was not initialized. + * + */ + otError SendMessage(Message & aMessage, + const Ip6::MessageInfo & aMessageInfo, + ResponseHandler aHandler = nullptr, + void * aContext = nullptr, + otCoapBlockwiseTransmitHook aTransmitHook = nullptr, + otCoapBlockwiseReceiveHook aReceiveHook = nullptr); +#else // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE /** * This method sends a CoAP message over secure DTLS connection. * @@ -311,6 +362,7 @@ public: const Ip6::MessageInfo &aMessageInfo, ResponseHandler aHandler = nullptr, void * aContext = nullptr); +#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE /** * This method is used to pass UDP messages to the secure CoAP server. diff --git a/src/core/config/coap.h b/src/core/config/coap.h index 712c57e8d..c3ee709e4 100644 --- a/src/core/config/coap.h +++ b/src/core/config/coap.h @@ -67,6 +67,26 @@ #define OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE 0 #endif +/** + * @def OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE + * + * Define to 1 to enable the CoAP Block-Wise Transfer. + * + */ +#ifndef OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE +#define OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE 0 +#endif + +/** + * @def OPENTHREAD_CONFIG_COAP_MAX_BLOCK_LENGTH + * + * This setting configures the maximum length of one block during a CoAP Block-Wise Transfer. + * + */ +#ifndef OPENTHREAD_CONFIG_COAP_MAX_BLOCK_LENGTH +#define OPENTHREAD_CONFIG_COAP_MAX_BLOCK_LENGTH 1024 +#endif + /** * @def OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE * diff --git a/tests/scripts/thread-cert/Makefile.am b/tests/scripts/thread-cert/Makefile.am index d8f6623b9..1ed248352 100644 --- a/tests/scripts/thread-cert/Makefile.am +++ b/tests/scripts/thread-cert/Makefile.am @@ -154,6 +154,7 @@ EXTRA_DIST = \ test_coap.py \ test_coap_observe.py \ test_coaps.py \ + test_coap_block.py \ test_common.py \ test_crypto.py \ test_diag.py \ @@ -205,6 +206,7 @@ check_SCRIPTS = \ test_coap.py \ test_coap_observe.py \ test_coaps.py \ + test_coap_block.py \ test_common.py \ test_crypto.py \ test_diag.py \ diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index 5a83c213f..d9257bcda 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -1884,6 +1884,12 @@ class NodeImpl: """ return self._coap_rq('get', ipaddr, uri, con, payload) + def coap_get_block(self, ipaddr, uri, size=16, count=0): + """ + Send a GET request via CoAP. + """ + return self._coap_rq_block('get', ipaddr, uri, size, count) + def coap_observe(self, ipaddr, uri, con=False, payload=None): """ Send a GET request via CoAP with Observe set. @@ -1896,12 +1902,24 @@ class NodeImpl: """ return self._coap_rq('post', ipaddr, uri, con, payload) + def coap_post_block(self, ipaddr, uri, size=16, count=0): + """ + Send a POST request via CoAP. + """ + return self._coap_rq_block('post', ipaddr, uri, size, count) + def coap_put(self, ipaddr, uri, con=False, payload=None): """ Send a PUT request via CoAP. """ return self._coap_rq('put', ipaddr, uri, con, payload) + def coap_put_block(self, ipaddr, uri, size=16, count=0): + """ + Send a PUT request via CoAP. + """ + return self._coap_rq_block('put', ipaddr, uri, size, count) + def _coap_rq(self, method, ipaddr, uri, con=False, payload=None): """ Issue a GET/POST/PUT/DELETE/GET OBSERVE request. @@ -1918,6 +1936,20 @@ class NodeImpl: self.send_command(cmd) return self.coap_wait_response() + def _coap_rq_block(self, method, ipaddr, uri, size=16, count=0): + """ + Issue a GET/POST/PUT/DELETE/GET OBSERVE BLOCK request. + """ + cmd = 'coap %s %s %s' % (method, ipaddr, uri) + + cmd += ' block-%d' % size + + if count is not 0: + cmd += ' %d' % count + + self.send_command(cmd) + return self.coap_wait_response() + def coap_wait_response(self): """ Wait for a CoAP response, and return it. @@ -1938,7 +1970,10 @@ class NodeImpl: observe = int(observe, base=10) if payload is not None: - payload = binascii.a2b_hex(payload).decode('UTF-8') + try: + payload = binascii.a2b_hex(payload).decode('UTF-8') + except UnicodeDecodeError: + pass # Return the values received return dict(source=source, observe=observe, payload=payload) @@ -2004,6 +2039,14 @@ class NodeImpl: self.send_command(cmd) self._expect_done() + def coap_set_resource_path_block(self, path, count=0): + """ + Set the path for the CoAP resource and how many blocks can be received from this resource. + """ + cmd = 'coap resource %s %d' % (path, count) + self.send_command(cmd) + self._expect('Done') + def coap_set_content(self, content): """ Set the content of the CoAP resource. diff --git a/tests/scripts/thread-cert/test_coap_block.py b/tests/scripts/thread-cert/test_coap_block.py new file mode 100755 index 000000000..27c98bf82 --- /dev/null +++ b/tests/scripts/thread-cert/test_coap_block.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2020, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +import unittest + +import pexpect +import config +import thread_cert + +LEADER = 1 +ROUTER = 2 + + +class TestCoapBlockTransfer(thread_cert.TestCase): + """ + Test suite for CoAP Block-Wise Transfers (RFC7959). + """ + + SUPPORT_NCP = False + + TOPOLOGY = { + LEADER: { + 'mode': 'rdn', + 'panid': 0xface, + 'whitelist': [ROUTER] + }, + ROUTER: { + 'mode': 'rdn', + 'panid': 0xface, + 'router_selection_jitter': 1, + 'whitelist': [LEADER] + }, + } + + def _do_transfer_test(self, method): + self.nodes[LEADER].start() + self.simulator.go(5) + self.assertEqual(self.nodes[LEADER].get_state(), 'leader') + + self.nodes[ROUTER].start() + self.simulator.go(5) + self.assertEqual(self.nodes[ROUTER].get_state(), 'router') + + mleid = self.nodes[LEADER].get_ip6_address(config.ADDRESS_TYPE.ML_EID) + + self.nodes[LEADER].coap_start() + self.nodes[LEADER].coap_set_resource_path_block('test', 10) + + self.nodes[ROUTER].coap_start() + + if method == 'GET': + response = self.nodes[ROUTER].coap_get_block(mleid, 'test', size=32) + response_payload = response['payload'] + self.assertIsNotNone(response_payload) + + if method == 'PUT': + self.nodes[ROUTER].coap_put_block(mleid, 'test', size=256, count=2) + request = self.nodes[ROUTER].coap_wait_request() + request_payload = request['payload'] + self.assertIsNotNone(request_payload) + + if method == 'POST': + self.nodes[ROUTER].coap_post_block(mleid, 'test', size=1024, count=2) + request = self.nodes[ROUTER].coap_wait_request() + request_payload = request['payload'] + self.assertIsNotNone(request_payload) + + self.simulator.go(10) + + self.nodes[ROUTER].coap_stop() + self.nodes[LEADER].coap_stop() + + def test_get(self): + """ + Test block-wise transfer using GET method. + """ + for trial in range(0, 3): + try: + self._do_transfer_test(method='GET') + break + except (AssertionError, pexpect.exceptions.TIMEOUT): + continue + + def test_put(self): + """ + Test block-wise transfer using PUT method. + """ + for trial in range(0, 3): + try: + self._do_transfer_test(method='PUT') + break + except (AssertionError, pexpect.exceptions.TIMEOUT): + continue + + def test_post(self): + """ + Test block-wise transfer using POST method. + """ + for trial in range(0, 3): + try: + self._do_transfer_test(method='POST') + break + except (AssertionError, pexpect.exceptions.TIMEOUT): + continue + + +if __name__ == '__main__': + unittest.main()