diff --git a/sys/include/net/gcoap.h b/sys/include/net/gcoap.h index b9b4afebdd14..8955d5ffe882 100644 --- a/sys/include/net/gcoap.h +++ b/sys/include/net/gcoap.h @@ -844,6 +844,23 @@ typedef struct { gcoap_socket_t socket; /**< Transport type to observer */ } gcoap_observe_memo_t; +/** + * @brief Context for a separate response + */ +typedef struct { + event_t ev; /**< Event for the separate response */ + gcoap_socket_type_t tl; /**< Transport type on which the request was received */ + sock_udp_ep_t remote; /**< Remote to send response to */ +#if defined(MODULE_SOCK_AUX_LOCAL) || DOXYGEN + sock_udp_ep_t local; /**< Local endpoint from which to send the response */ +#endif + int result; /**< Result of precessing the request */ + uint8_t *req_buf; /**< Buffer to cache request */ + size_t req_buf_size; /**< Size of the buffer containing the request */ + uint8_t *resp_buf; /**< Buffer to write response to */ + size_t resp_buf_size; /**< Size of the buffer to write the response to */ +} gcoap_separate_response_ctx_t; + /** * @brief Initializes the gcoap thread and device * @@ -1028,6 +1045,20 @@ static inline ssize_t gcoap_req_send_tl(const uint8_t *buf, size_t len, */ int gcoap_resp_init(coap_pkt_t *pdu, uint8_t *buf, size_t len, unsigned code); +/** + * @brief Initialize a CoAP separate response packet in a buffer + * + * @param[out] pdu Response metadata + * @param[in] buf Buffer containing the PDU + * @param[in] len Length of the buffer + * @param[in] type Type of the response (CON/NON) + * @param[in] code Response code + * + * @return 0 on success + * @return < 0 on error + */ +int gcoap_separate_resp_init(coap_pkt_t *pdu, uint8_t *buf, size_t len, unsigned type, unsigned code); + /** * @brief Writes a complete CoAP response PDU when there is no payload * @@ -1047,6 +1078,41 @@ static inline ssize_t gcoap_response(coap_pkt_t *pdu, uint8_t *buf, : -1; } +/** + * @brief Prepare a context to be able to send a separate response + * + * @param[out] ctx Separate response context to prepare + * @param[in] req_buf Buffer to cache the request + * @param[in] req_buf_size Size of the request buffer + * @param[in] resp_buf Buffer to cache the request + * @param[in] resp_buf_size Size of the request buffer + * @param[in] req_pkt Request packet that initiated the separate response + * @param[in] req_ctx CoAP request context + * + * @return < 0 on error or 0 on success + */ +int gcoap_resp_prepare_separate(gcoap_separate_response_ctx_t *ctx, + void *req_buf, size_t req_buf_size, + void *resp_buf, size_t resp_buf_size, + const coap_pkt_t *req_pkt, + const coap_request_ctx_t *req_ctx); + +/** + * @brief Sends the PDU buffer containing a separate response + * + * @param[in] buf Separate response buffer + * @param[in] len Length of the buffer + * @param[in] remote Destination endpoint to reply to + * @param[in] local Local endpoint to send from, may be NULL + * @param[in] tl_type Transport type to use for the reply + * + * @return length of the packet + * @return -1 on error or 0 if cannot send + */ +ssize_t gcoap_resp_send_separate(const uint8_t *buf, size_t len, + const sock_udp_ep_t *remote, const sock_udp_ep_t *local, + gcoap_socket_type_t tl_type); + /** * @brief Initializes a CoAP Observe notification packet on a buffer, for the * observer registered for a resource diff --git a/sys/include/net/nanocoap.h b/sys/include/net/nanocoap.h index 044ea13433cc..f5cb77913c92 100644 --- a/sys/include/net/nanocoap.h +++ b/sys/include/net/nanocoap.h @@ -1982,6 +1982,23 @@ ssize_t coap_build_hdr(coap_hdr_t *hdr, unsigned type, const void *token, ssize_t coap_build_reply(coap_pkt_t *pkt, unsigned code, uint8_t *rbuf, unsigned rlen, unsigned payload_len); +/** + * @brief Initialize a separate response PDU from a request PDU + * + * @param[in,out] pkt Response PDU to initialize + * @param[in] type Response type (CON or NON) + * @param[in] code Response code + * @param[out] rbuf Response buffer + * @param[in] rlen Size of response buffer + * @param[in] payload_len Response payload length + * + * @returns 0 if no response should be sent due to a No-Response option in the request + * @returns <0 on error + * @returns -ENOSPC if @p rbuf too small + */ +ssize_t coap_build_separate_reply(coap_pkt_t *pkt, unsigned type, unsigned code, + uint8_t *rbuf, unsigned rlen, unsigned payload_len); + /** * @brief Build empty reply to CoAP request * diff --git a/sys/net/application_layer/gcoap/gcoap.c b/sys/net/application_layer/gcoap/gcoap.c index 3c7da0da84a5..e3d6a3188fa0 100644 --- a/sys/net/application_layer/gcoap/gcoap.c +++ b/sys/net/application_layer/gcoap/gcoap.c @@ -434,6 +434,7 @@ static void _process_coap_pdu(gcoap_socket_t *sock, sock_udp_ep_t *remote, sock_ memo = _find_req_memo_by_mid(remote, pdu.hdr->id); if ((memo != NULL) && (memo->send_limit != GCOAP_SEND_LIMIT_NON)) { DEBUG("gcoap: empty ACK processed, stopping retransmissions\n"); + /* for a separate CON response the memo is deleted because there is no response handler set */ _cease_retransmission(memo); } else { DEBUG("gcoap: empty ACK matches no known CON, ignoring\n"); @@ -1783,6 +1784,28 @@ int gcoap_resp_init(coap_pkt_t *pdu, uint8_t *buf, size_t len, unsigned code) return -1; } + pdu->hdr = (coap_hdr_t *)buf; + pdu->options_len = 0; + pdu->payload = buf + header_len; + pdu->payload_len = len - header_len; + + if (coap_get_observe(pdu) == COAP_OBS_REGISTER) { + _add_generated_observe_option(pdu); + } + + return 0; +} + +int gcoap_separate_resp_init(coap_pkt_t *pdu, uint8_t *buf, size_t len, unsigned type, unsigned code) +{ + int header_len = coap_build_separate_reply(pdu, type, code, buf, len, 0); + + /* request contained no-response option or not enough space for response */ + if (header_len <= 0) { + return -1; + } + + pdu->hdr = (coap_hdr_t *)buf; pdu->options_len = 0; pdu->payload = buf + header_len; pdu->payload_len = len - header_len; @@ -1954,4 +1977,124 @@ void gcoap_forward_proxy_post_event(void *arg) event_post(&_queue, arg); } +/* separate response API */ + +int gcoap_resp_prepare_separate(gcoap_separate_response_ctx_t *ctx, + void *req_buf, size_t req_buf_size, + void *resp_buf, size_t resp_buf_size, + const coap_pkt_t *req_pkt, + const coap_request_ctx_t *req_ctx) +{ + size_t len = coap_get_total_len(req_pkt); + if (req_buf_size < len) { + return -ENOSPC; + } + ctx->tl = req_ctx->tl_type; + ctx->req_buf = req_buf; + ctx->req_buf_size = req_buf_size; + ctx->resp_buf = resp_buf; + ctx->resp_buf_size = resp_buf_size; + memcpy(ctx->req_buf, req_pkt->hdr, len); + memcpy(&ctx->remote, req_ctx->remote, sizeof(ctx->remote)); +#ifdef MODULE_SOCK_AUX_LOCAL + assert(req_ctx->local); + memcpy(&ctx->local, req_ctx->local, sizeof(ctx->local)); +#endif + return 0; +} + +ssize_t gcoap_resp_send_separate(const uint8_t *buf, size_t len, + const sock_udp_ep_t *remote, const sock_udp_ep_t *local, + gcoap_socket_type_t tl_type) +{ + /* mostly copied from gcoap_req_send() */ + gcoap_socket_t socket = { 0 }; + gcoap_request_memo_t *memo = NULL; + unsigned msg_type = (*buf & 0x30) >> 4; + uint32_t timeout = 0; + + assert(remote != NULL); + + ssize_t res = _tl_init_coap_socket(&socket, tl_type); + if (res < 0) { + return -1; + } + if (msg_type == COAP_TYPE_CON) { + mutex_lock(&_coap_state.lock); + /* Find empty slot in list of open requests. */ + for (int i = 0; i < CONFIG_GCOAP_REQ_WAITING_MAX; i++) { + if (_coap_state.open_reqs[i].state == GCOAP_MEMO_UNUSED) { + memo = &_coap_state.open_reqs[i]; + memo->state = GCOAP_MEMO_WAIT; + break; + } + } + if (!memo) { + mutex_unlock(&_coap_state.lock); + DEBUG("gcoap: dropping response; no space for response tracking\n"); + return 0; + } + memo->resp_handler = NULL; + memo->context = NULL; + memcpy(&memo->remote_ep, remote, sizeof(sock_udp_ep_t)); + memo->socket = socket; + /* copy buf to resend_bufs record */ + memo->msg.data.pdu_buf = NULL; + for (int i = 0; i < CONFIG_GCOAP_RESEND_BUFS_MAX; i++) { + if (!_coap_state.resend_bufs[i][0]) { + memo->msg.data.pdu_buf = &_coap_state.resend_bufs[i][0]; + memcpy(memo->msg.data.pdu_buf, buf, CONFIG_GCOAP_PDU_BUF_SIZE); + memo->msg.data.pdu_len = len; + break; + } + } + if (memo->msg.data.pdu_buf) { + memo->send_limit = CONFIG_COAP_MAX_RETRANSMIT; + timeout = (uint32_t)CONFIG_COAP_ACK_TIMEOUT_MS; +#if CONFIG_COAP_RANDOM_FACTOR_1000 > 1000 + timeout = random_uint32_range(timeout, TIMEOUT_RANGE_END); +#endif + memo->state = GCOAP_MEMO_RETRANSMIT; + } + else { + memo->state = GCOAP_MEMO_UNUSED; + DEBUG("gcoap: no space for PDU in resend bufs\n"); + } + mutex_unlock(&_coap_state.lock); + } + + _tl_init_coap_socket(&socket, tl_type); + /* set response timeout; may be zero for non-confirmable */ + if (memo) { + if (timeout > 0) { + event_callback_init(&memo->resp_tmout_cb, _on_resp_timeout, memo); + event_timeout_ztimer_init(&memo->resp_evt_tmout, ZTIMER_MSEC, &_queue, + &memo->resp_tmout_cb.super); + event_timeout_set(&memo->resp_evt_tmout, timeout); + } + else { + memset(&memo->resp_evt_tmout, 0, sizeof(event_timeout_t)); + } + } + + sock_udp_aux_tx_t aux = { 0 }; + if (local) { + memcpy(&aux.local, local, sizeof(sock_udp_ep_t)); + aux.flags = SOCK_AUX_SET_LOCAL; + } + if ((res = _tl_send(&socket, buf, len, remote, &aux)) <= 0) { + if (memo) { + if (msg_type == COAP_TYPE_CON) { + *memo->msg.data.pdu_buf = 0; /* clear resend buffer */ + } + if (timeout > 0) { + event_timeout_clear(&memo->resp_evt_tmout); + } + memo->state = GCOAP_MEMO_UNUSED; + } + DEBUG("gcoap: sock send failed: %" PRIdSIZE "\n", res); + } + return res < 0 ? -1 : res; +} + /** @} */ diff --git a/sys/net/application_layer/nanocoap/nanocoap.c b/sys/net/application_layer/nanocoap/nanocoap.c index 08af915cd022..41bd97926886 100644 --- a/sys/net/application_layer/nanocoap/nanocoap.c +++ b/sys/net/application_layer/nanocoap/nanocoap.c @@ -692,6 +692,40 @@ ssize_t coap_build_reply(coap_pkt_t *pkt, unsigned code, return len; } +ssize_t coap_build_separate_reply(coap_pkt_t *pkt, unsigned type, unsigned code, + uint8_t *rbuf, unsigned rlen, unsigned payload_len) +{ + assert(type == COAP_TYPE_CON || type == COAP_TYPE_NON); + + unsigned tkl = coap_get_token_len(pkt); + unsigned len = sizeof(coap_hdr_t) + tkl; + + if ((len + payload_len) > rlen) { + return -ENOSPC; + } + + uint32_t no_response; + if (coap_opt_get_uint(pkt, COAP_OPT_NO_RESPONSE, &no_response) == 0) { + + const uint8_t no_response_index = (code >> 5) - 1; + /* If the handler code misbehaved here, we'd face UB otherwise */ + assert(no_response_index < 7); + + const uint8_t mask = 1 << no_response_index; + + /* option contains bitmap of disinterest */ + if (no_response & mask) { + return 0; + } + } + + coap_build_hdr((coap_hdr_t *)rbuf, type, coap_get_token(pkt), tkl, code, + ntohs(pkt->hdr->id)); + len += payload_len; + + return len; +} + ssize_t coap_build_hdr(coap_hdr_t *hdr, unsigned type, const void *token, size_t token_len, unsigned code, uint16_t id) {