2016-10-29 21:20:22 +02:00
|
|
|
/*
|
2017-07-06 13:22:11 +02:00
|
|
|
* Copyright (c) 2015-2017 Ken Bannister. All rights reserved.
|
2016-10-29 21:20:22 +02:00
|
|
|
*
|
|
|
|
* This file is subject to the terms and conditions of the GNU Lesser
|
|
|
|
* General Public License v2.1. See the file LICENSE in the top level
|
|
|
|
* directory for more details.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
2016-11-17 06:01:15 +01:00
|
|
|
* @ingroup net_gcoap
|
2016-10-29 21:20:22 +02:00
|
|
|
* @{
|
|
|
|
*
|
|
|
|
* @file
|
|
|
|
* @brief GNRC's implementation of CoAP protocol
|
|
|
|
*
|
|
|
|
* Runs a thread (_pid) to manage request/response messaging.
|
|
|
|
*
|
|
|
|
* @author Ken Bannister <kb2ma@runbox.com>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <errno.h>
|
2018-03-29 21:21:02 +02:00
|
|
|
#include <stdint.h>
|
|
|
|
#include <stdatomic.h>
|
2018-07-21 10:13:00 +02:00
|
|
|
#include <string.h>
|
2018-03-29 21:21:02 +02:00
|
|
|
|
2018-05-04 11:08:48 +02:00
|
|
|
#include "assert.h"
|
2016-11-17 06:01:15 +01:00
|
|
|
#include "net/gcoap.h"
|
2018-09-07 12:03:19 +02:00
|
|
|
#include "net/sock/util.h"
|
2018-03-29 21:21:02 +02:00
|
|
|
#include "mutex.h"
|
2016-10-29 21:20:22 +02:00
|
|
|
#include "random.h"
|
|
|
|
#include "thread.h"
|
|
|
|
|
|
|
|
#define ENABLE_DEBUG (0)
|
|
|
|
#include "debug.h"
|
|
|
|
|
2017-10-09 17:15:54 +02:00
|
|
|
/* Return values used by the _find_resource function. */
|
|
|
|
#define GCOAP_RESOURCE_FOUND 0
|
|
|
|
#define GCOAP_RESOURCE_WRONG_METHOD -1
|
|
|
|
#define GCOAP_RESOURCE_NO_PATH -2
|
|
|
|
|
2016-10-29 21:20:22 +02:00
|
|
|
/* Internal functions */
|
|
|
|
static void *_event_loop(void *arg);
|
2016-11-17 06:01:15 +01:00
|
|
|
static void _listen(sock_udp_t *sock);
|
2018-02-02 19:01:58 +01:00
|
|
|
static ssize_t _well_known_core_handler(coap_pkt_t* pdu, uint8_t *buf, size_t len, void *ctx);
|
2017-03-13 05:27:22 +01:00
|
|
|
static size_t _handle_req(coap_pkt_t *pdu, uint8_t *buf, size_t len,
|
|
|
|
sock_udp_ep_t *remote);
|
2016-10-29 21:20:22 +02:00
|
|
|
static void _expire_request(gcoap_request_memo_t *memo);
|
2017-12-13 09:22:20 +01:00
|
|
|
static void _find_req_memo(gcoap_request_memo_t **memo_ptr, coap_pkt_t *pdu,
|
|
|
|
const sock_udp_ep_t *remote);
|
2018-07-08 20:19:43 +02:00
|
|
|
static int _find_resource(coap_pkt_t *pdu, const coap_resource_t **resource_ptr,
|
2017-03-13 05:27:22 +01:00
|
|
|
gcoap_listener_t **listener_ptr);
|
|
|
|
static int _find_observer(sock_udp_ep_t **observer, sock_udp_ep_t *remote);
|
|
|
|
static int _find_obs_memo(gcoap_observe_memo_t **memo, sock_udp_ep_t *remote,
|
|
|
|
coap_pkt_t *pdu);
|
|
|
|
static void _find_obs_memo_resource(gcoap_observe_memo_t **memo,
|
|
|
|
const coap_resource_t *resource);
|
2016-10-29 21:20:22 +02:00
|
|
|
|
|
|
|
/* Internal variables */
|
|
|
|
const coap_resource_t _default_resources[] = {
|
2018-02-02 19:01:58 +01:00
|
|
|
{ "/.well-known/core", COAP_GET, _well_known_core_handler, NULL },
|
2016-10-29 21:20:22 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
static gcoap_listener_t _default_listener = {
|
2018-07-08 20:19:43 +02:00
|
|
|
&_default_resources[0],
|
2019-07-18 15:16:43 +02:00
|
|
|
ARRAY_SIZE(_default_resources),
|
2019-04-24 20:12:29 +02:00
|
|
|
NULL,
|
2016-10-29 21:20:22 +02:00
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
2018-03-29 21:21:02 +02:00
|
|
|
/* Container for the state of gcoap itself */
|
|
|
|
typedef struct {
|
|
|
|
mutex_t lock; /* Shares state attributes safely */
|
|
|
|
gcoap_listener_t *listeners; /* List of registered listeners */
|
|
|
|
gcoap_request_memo_t open_reqs[GCOAP_REQ_WAITING_MAX];
|
|
|
|
/* Storage for open requests; if first
|
|
|
|
byte of an entry is zero, the entry
|
|
|
|
is available */
|
|
|
|
atomic_uint next_message_id; /* Next message ID to use */
|
|
|
|
sock_udp_ep_t observers[GCOAP_OBS_CLIENTS_MAX];
|
|
|
|
/* Observe clients; allows reuse for
|
|
|
|
observe memos */
|
|
|
|
gcoap_observe_memo_t observe_memos[GCOAP_OBS_REGISTRATIONS_MAX];
|
|
|
|
/* Observed resource registrations */
|
|
|
|
uint8_t resend_bufs[GCOAP_RESEND_BUFS_MAX][GCOAP_PDU_BUF_SIZE];
|
|
|
|
/* Buffers for PDU for request resends;
|
|
|
|
if first byte of an entry is zero,
|
|
|
|
the entry is available */
|
|
|
|
} gcoap_state_t;
|
|
|
|
|
2016-10-29 21:20:22 +02:00
|
|
|
static gcoap_state_t _coap_state = {
|
|
|
|
.listeners = &_default_listener,
|
|
|
|
};
|
|
|
|
|
|
|
|
static kernel_pid_t _pid = KERNEL_PID_UNDEF;
|
|
|
|
static char _msg_stack[GCOAP_STACK_SIZE];
|
2018-05-06 16:40:55 +02:00
|
|
|
static msg_t _msg_queue[GCOAP_MSG_QUEUE_SIZE];
|
2016-11-17 06:01:15 +01:00
|
|
|
static sock_udp_t _sock;
|
2016-10-29 21:20:22 +02:00
|
|
|
|
|
|
|
|
|
|
|
/* Event/Message loop for gcoap _pid thread. */
|
|
|
|
static void *_event_loop(void *arg)
|
|
|
|
{
|
2018-05-06 16:40:55 +02:00
|
|
|
msg_t msg_rcvd;
|
2016-10-29 21:20:22 +02:00
|
|
|
(void)arg;
|
|
|
|
|
2018-05-06 16:40:55 +02:00
|
|
|
msg_init_queue(_msg_queue, GCOAP_MSG_QUEUE_SIZE);
|
2016-10-29 21:20:22 +02:00
|
|
|
|
2016-11-17 06:01:15 +01:00
|
|
|
sock_udp_ep_t local;
|
|
|
|
memset(&local, 0, sizeof(sock_udp_ep_t));
|
|
|
|
local.family = AF_INET6;
|
|
|
|
local.netif = SOCK_ADDR_ANY_NETIF;
|
|
|
|
local.port = GCOAP_PORT;
|
2016-10-29 21:20:22 +02:00
|
|
|
|
2016-11-17 06:01:15 +01:00
|
|
|
int res = sock_udp_create(&_sock, &local, NULL, 0);
|
|
|
|
if (res < 0) {
|
|
|
|
DEBUG("gcoap: cannot create sock: %d\n", res);
|
|
|
|
return 0;
|
|
|
|
}
|
2016-10-29 21:20:22 +02:00
|
|
|
|
2016-11-17 06:01:15 +01:00
|
|
|
while(1) {
|
|
|
|
res = msg_try_receive(&msg_rcvd);
|
2016-10-29 21:20:22 +02:00
|
|
|
|
2016-11-17 06:01:15 +01:00
|
|
|
if (res > 0) {
|
|
|
|
switch (msg_rcvd.type) {
|
2017-08-15 18:50:37 +02:00
|
|
|
case GCOAP_MSG_TYPE_TIMEOUT: {
|
|
|
|
gcoap_request_memo_t *memo = (gcoap_request_memo_t *)msg_rcvd.content.ptr;
|
|
|
|
|
|
|
|
/* no retries remaining */
|
|
|
|
if ((memo->send_limit == GCOAP_SEND_LIMIT_NON)
|
|
|
|
|| (memo->send_limit == 0)) {
|
|
|
|
_expire_request(memo);
|
|
|
|
}
|
|
|
|
/* reduce retries remaining, double timeout and resend */
|
|
|
|
else {
|
|
|
|
memo->send_limit--;
|
2019-07-03 16:35:48 +02:00
|
|
|
#ifdef GCOAP_NO_RETRANS_BACKOFF
|
|
|
|
unsigned i = 0;
|
|
|
|
#else
|
2017-08-15 18:50:37 +02:00
|
|
|
unsigned i = COAP_MAX_RETRANSMIT - memo->send_limit;
|
2019-07-03 16:35:48 +02:00
|
|
|
#endif
|
2017-08-15 18:50:37 +02:00
|
|
|
uint32_t timeout = ((uint32_t)COAP_ACK_TIMEOUT << i) * US_PER_SEC;
|
2019-07-03 15:42:16 +02:00
|
|
|
#if COAP_ACK_VARIANCE > 0
|
2017-08-15 18:50:37 +02:00
|
|
|
uint32_t variance = ((uint32_t)COAP_ACK_VARIANCE << i) * US_PER_SEC;
|
|
|
|
timeout = random_uint32_range(timeout, timeout + variance);
|
2019-07-03 15:42:16 +02:00
|
|
|
#endif
|
2017-08-15 18:50:37 +02:00
|
|
|
|
|
|
|
ssize_t bytes = sock_udp_send(&_sock, memo->msg.data.pdu_buf,
|
|
|
|
memo->msg.data.pdu_len,
|
|
|
|
&memo->remote_ep);
|
|
|
|
if (bytes > 0) {
|
|
|
|
xtimer_set_msg(&memo->response_timer, timeout,
|
|
|
|
&memo->timeout_msg, _pid);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
DEBUG("gcoap: sock resend failed: %d\n", (int)bytes);
|
|
|
|
_expire_request(memo);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
break;
|
2016-11-17 06:01:15 +01:00
|
|
|
}
|
2016-10-29 21:20:22 +02:00
|
|
|
}
|
2016-11-17 06:01:15 +01:00
|
|
|
|
|
|
|
_listen(&_sock);
|
2016-10-29 21:20:22 +02:00
|
|
|
}
|
2016-11-17 06:01:15 +01:00
|
|
|
|
2016-10-29 21:20:22 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-11-17 06:01:15 +01:00
|
|
|
/* Listen for an incoming CoAP message. */
|
|
|
|
static void _listen(sock_udp_t *sock)
|
2016-10-29 21:20:22 +02:00
|
|
|
{
|
|
|
|
coap_pkt_t pdu;
|
|
|
|
uint8_t buf[GCOAP_PDU_BUF_SIZE];
|
2016-11-17 06:01:15 +01:00
|
|
|
sock_udp_ep_t remote;
|
2016-10-29 21:20:22 +02:00
|
|
|
gcoap_request_memo_t *memo = NULL;
|
2017-05-22 13:50:59 +02:00
|
|
|
uint8_t open_reqs = gcoap_op_state();
|
2016-10-29 21:20:22 +02:00
|
|
|
|
2017-08-15 18:50:37 +02:00
|
|
|
/* We expect a -EINTR response here when unlimited waiting (SOCK_NO_TIMEOUT)
|
2019-07-03 15:00:06 +02:00
|
|
|
* is interrupted when sending a message in gcoap_req_send(). While a
|
2017-08-15 18:50:37 +02:00
|
|
|
* request is outstanding, sock_udp_recv() is called here with limited
|
|
|
|
* waiting so the request's timeout can be handled in a timely manner in
|
|
|
|
* _event_loop(). */
|
2016-11-17 06:01:15 +01:00
|
|
|
ssize_t res = sock_udp_recv(sock, buf, sizeof(buf),
|
|
|
|
open_reqs > 0 ? GCOAP_RECV_TIMEOUT : SOCK_NO_TIMEOUT,
|
|
|
|
&remote);
|
|
|
|
if (res <= 0) {
|
|
|
|
#if ENABLE_DEBUG
|
|
|
|
if (res < 0 && res != -ETIMEDOUT) {
|
|
|
|
DEBUG("gcoap: udp recv failure: %d\n", res);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
return;
|
|
|
|
}
|
2016-10-29 21:20:22 +02:00
|
|
|
|
2016-11-17 06:01:15 +01:00
|
|
|
res = coap_parse(&pdu, buf, res);
|
|
|
|
if (res < 0) {
|
2017-10-20 23:23:58 +02:00
|
|
|
DEBUG("gcoap: parse failure: %d\n", (int)res);
|
2016-10-29 21:20:22 +02:00
|
|
|
/* If a response, can't clear memo, but it will timeout later. */
|
2016-11-17 06:01:15 +01:00
|
|
|
return;
|
2016-10-29 21:20:22 +02:00
|
|
|
}
|
|
|
|
|
2017-07-28 14:15:17 +02:00
|
|
|
if (pdu.hdr->code == COAP_CODE_EMPTY) {
|
2017-06-22 18:57:34 +02:00
|
|
|
DEBUG("gcoap: empty messages not handled yet\n");
|
|
|
|
return;
|
2017-07-26 00:13:24 +02:00
|
|
|
}
|
2017-06-22 18:57:34 +02:00
|
|
|
|
2017-07-26 00:13:24 +02:00
|
|
|
/* validate class and type for incoming */
|
|
|
|
switch (coap_get_code_class(&pdu)) {
|
2016-10-29 21:20:22 +02:00
|
|
|
/* incoming request */
|
2017-07-26 00:13:24 +02:00
|
|
|
case COAP_CLASS_REQ:
|
2017-06-21 19:08:10 +02:00
|
|
|
if (coap_get_type(&pdu) == COAP_TYPE_NON
|
|
|
|
|| coap_get_type(&pdu) == COAP_TYPE_CON) {
|
|
|
|
size_t pdu_len = _handle_req(&pdu, buf, sizeof(buf), &remote);
|
|
|
|
if (pdu_len > 0) {
|
2018-02-16 12:28:32 +01:00
|
|
|
ssize_t bytes = sock_udp_send(sock, buf, pdu_len, &remote);
|
|
|
|
if (bytes <= 0) {
|
|
|
|
DEBUG("gcoap: send response failed: %d\n", (int)bytes);
|
|
|
|
}
|
2017-06-21 19:08:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
DEBUG("gcoap: illegal request type: %u\n", coap_get_type(&pdu));
|
2016-10-29 21:20:22 +02:00
|
|
|
}
|
2017-07-26 00:13:24 +02:00
|
|
|
break;
|
2017-06-22 18:57:34 +02:00
|
|
|
|
2016-10-29 21:20:22 +02:00
|
|
|
/* incoming response */
|
2017-07-26 00:13:24 +02:00
|
|
|
case COAP_CLASS_SUCCESS:
|
|
|
|
case COAP_CLASS_CLIENT_FAILURE:
|
|
|
|
case COAP_CLASS_SERVER_FAILURE:
|
2017-12-13 09:22:20 +01:00
|
|
|
_find_req_memo(&memo, &pdu, &remote);
|
2016-10-29 21:20:22 +02:00
|
|
|
if (memo) {
|
2017-07-26 00:13:24 +02:00
|
|
|
switch (coap_get_type(&pdu)) {
|
|
|
|
case COAP_TYPE_NON:
|
|
|
|
case COAP_TYPE_ACK:
|
|
|
|
xtimer_remove(&memo->response_timer);
|
|
|
|
memo->state = GCOAP_MEMO_RESP;
|
2017-07-25 11:44:46 +02:00
|
|
|
if (memo->resp_handler) {
|
|
|
|
memo->resp_handler(memo->state, &pdu, &remote);
|
|
|
|
}
|
2017-07-26 00:13:24 +02:00
|
|
|
|
|
|
|
if (memo->send_limit >= 0) { /* if confirmable */
|
|
|
|
*memo->msg.data.pdu_buf = 0; /* clear resend PDU buffer */
|
|
|
|
}
|
|
|
|
memo->state = GCOAP_MEMO_UNUSED;
|
|
|
|
break;
|
|
|
|
case COAP_TYPE_CON:
|
|
|
|
DEBUG("gcoap: separate CON response not handled yet\n");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
DEBUG("gcoap: illegal response type: %u\n", coap_get_type(&pdu));
|
|
|
|
break;
|
2017-07-10 05:36:55 +02:00
|
|
|
}
|
2016-10-29 21:20:22 +02:00
|
|
|
}
|
2017-07-26 00:13:24 +02:00
|
|
|
else {
|
|
|
|
DEBUG("gcoap: msg not found for ID: %u\n", coap_get_id(&pdu));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
DEBUG("gcoap: illegal code class: %u\n", coap_get_code_class(&pdu));
|
2016-10-29 21:20:22 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Main request handler: generates response PDU in the provided buffer.
|
|
|
|
*
|
|
|
|
* Caller must finish the PDU and send it.
|
2017-03-13 05:27:22 +01:00
|
|
|
*
|
|
|
|
* return length of response pdu, or < 0 if can't handle
|
|
|
|
*/
|
|
|
|
static size_t _handle_req(coap_pkt_t *pdu, uint8_t *buf, size_t len,
|
|
|
|
sock_udp_ep_t *remote)
|
|
|
|
{
|
2018-07-08 20:19:43 +02:00
|
|
|
const coap_resource_t *resource = NULL;
|
|
|
|
gcoap_listener_t *listener = NULL;
|
|
|
|
sock_udp_ep_t *observer = NULL;
|
|
|
|
gcoap_observe_memo_t *memo = NULL;
|
2017-03-13 05:27:22 +01:00
|
|
|
gcoap_observe_memo_t *resource_memo = NULL;
|
|
|
|
|
2017-10-09 17:15:54 +02:00
|
|
|
switch (_find_resource(pdu, &resource, &listener)) {
|
|
|
|
case GCOAP_RESOURCE_WRONG_METHOD:
|
|
|
|
return gcoap_response(pdu, buf, len, COAP_CODE_METHOD_NOT_ALLOWED);
|
|
|
|
case GCOAP_RESOURCE_NO_PATH:
|
|
|
|
return gcoap_response(pdu, buf, len, COAP_CODE_PATH_NOT_FOUND);
|
|
|
|
case GCOAP_RESOURCE_FOUND:
|
2018-06-01 06:57:36 +02:00
|
|
|
/* find observe registration for resource */
|
2017-10-09 17:15:54 +02:00
|
|
|
_find_obs_memo_resource(&resource_memo, resource);
|
|
|
|
break;
|
2017-03-13 05:27:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (coap_get_observe(pdu) == COAP_OBS_REGISTER) {
|
2018-06-01 06:57:36 +02:00
|
|
|
/* lookup remote+token */
|
2017-03-13 05:27:22 +01:00
|
|
|
int empty_slot = _find_obs_memo(&memo, remote, pdu);
|
2018-06-01 06:57:36 +02:00
|
|
|
/* validate re-registration request */
|
|
|
|
if (resource_memo != NULL) {
|
|
|
|
if (memo != NULL) {
|
|
|
|
if (memo != resource_memo) {
|
|
|
|
/* reject token already used for a different resource */
|
|
|
|
memo = NULL;
|
|
|
|
coap_clear_observe(pdu);
|
|
|
|
DEBUG("gcoap: can't change resource for token\n");
|
|
|
|
}
|
|
|
|
/* otherwise OK to re-register resource with the same token */
|
|
|
|
}
|
2018-09-07 12:03:19 +02:00
|
|
|
else if (sock_udp_ep_equal(remote, resource_memo->observer)) {
|
2018-06-01 06:57:36 +02:00
|
|
|
/* accept new token for resource */
|
2018-05-28 07:49:08 +02:00
|
|
|
memo = resource_memo;
|
|
|
|
}
|
2018-06-01 06:57:36 +02:00
|
|
|
}
|
|
|
|
/* initialize new registration request */
|
|
|
|
if ((memo == NULL) && coap_has_observe(pdu)) {
|
|
|
|
/* verify resource not already registerered (for another endpoint) */
|
|
|
|
if ((empty_slot >= 0) && (resource_memo == NULL)) {
|
2017-03-13 05:27:22 +01:00
|
|
|
int obs_slot = _find_observer(&observer, remote);
|
|
|
|
/* cache new observer */
|
|
|
|
if (observer == NULL) {
|
|
|
|
if (obs_slot >= 0) {
|
|
|
|
observer = &_coap_state.observers[obs_slot];
|
|
|
|
memcpy(observer, remote, sizeof(sock_udp_ep_t));
|
|
|
|
} else {
|
|
|
|
DEBUG("gcoap: can't register observer\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (observer != NULL) {
|
|
|
|
memo = &_coap_state.observe_memos[empty_slot];
|
2018-06-01 06:57:36 +02:00
|
|
|
memo->observer = observer;
|
2017-03-13 05:27:22 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (memo == NULL) {
|
|
|
|
coap_clear_observe(pdu);
|
|
|
|
DEBUG("gcoap: can't register observe memo\n");
|
|
|
|
}
|
|
|
|
}
|
2018-06-01 06:57:36 +02:00
|
|
|
/* finish registration */
|
2017-03-13 05:27:22 +01:00
|
|
|
if (memo != NULL) {
|
2018-06-01 06:57:36 +02:00
|
|
|
/* resource may be assigned here if it is not already registered */
|
|
|
|
memo->resource = resource;
|
2017-03-13 05:27:22 +01:00
|
|
|
memo->token_len = coap_get_token_len(pdu);
|
|
|
|
if (memo->token_len) {
|
|
|
|
memcpy(&memo->token[0], pdu->token, memo->token_len);
|
|
|
|
}
|
|
|
|
DEBUG("gcoap: Registered observer for: %s\n", memo->resource->path);
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (coap_get_observe(pdu) == COAP_OBS_DEREGISTER) {
|
|
|
|
_find_obs_memo(&memo, remote, pdu);
|
|
|
|
/* clear memo, and clear observer if no other memos */
|
|
|
|
if (memo != NULL) {
|
|
|
|
DEBUG("gcoap: Deregistering observer for: %s\n", memo->resource->path);
|
|
|
|
memo->observer = NULL;
|
|
|
|
memo = NULL;
|
|
|
|
_find_obs_memo(&memo, remote, NULL);
|
|
|
|
if (memo == NULL) {
|
|
|
|
_find_observer(&observer, remote);
|
|
|
|
if (observer != NULL) {
|
|
|
|
observer->family = AF_UNSPEC;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
coap_clear_observe(pdu);
|
|
|
|
|
|
|
|
} else if (coap_has_observe(pdu)) {
|
|
|
|
/* bogus request; don't respond */
|
|
|
|
DEBUG("gcoap: Observe value unexpected: %" PRIu32 "\n", coap_get_observe(pdu));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2018-02-02 19:01:58 +01:00
|
|
|
ssize_t pdu_len = resource->handler(pdu, buf, len, resource->context);
|
2017-03-13 05:27:22 +01:00
|
|
|
if (pdu_len < 0) {
|
|
|
|
pdu_len = gcoap_response(pdu, buf, len,
|
|
|
|
COAP_CODE_INTERNAL_SERVER_ERROR);
|
|
|
|
}
|
|
|
|
return pdu_len;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Searches listener registrations for the resource matching the path in a PDU.
|
|
|
|
*
|
|
|
|
* param[out] resource_ptr -- found resource
|
|
|
|
* param[out] listener_ptr -- listener for found resource
|
2017-10-09 17:15:54 +02:00
|
|
|
* return `GCOAP_RESOURCE_FOUND` if the resource was found,
|
|
|
|
* `GCOAP_RESOURCE_WRONG_METHOD` if a resource was found but the method
|
|
|
|
* code didn't match and `GCOAP_RESOURCE_NO_PATH` if no matching
|
|
|
|
* resource was found.
|
2016-10-29 21:20:22 +02:00
|
|
|
*/
|
2018-07-08 20:19:43 +02:00
|
|
|
static int _find_resource(coap_pkt_t *pdu, const coap_resource_t **resource_ptr,
|
2017-03-13 05:27:22 +01:00
|
|
|
gcoap_listener_t **listener_ptr)
|
2016-10-29 21:20:22 +02:00
|
|
|
{
|
2017-10-09 17:15:54 +02:00
|
|
|
int ret = GCOAP_RESOURCE_NO_PATH;
|
2016-10-29 21:20:22 +02:00
|
|
|
unsigned method_flag = coap_method2flag(coap_get_code_detail(pdu));
|
|
|
|
|
|
|
|
/* Find path for CoAP msg among listener resources and execute callback. */
|
|
|
|
gcoap_listener_t *listener = _coap_state.listeners;
|
2018-05-21 19:01:06 +02:00
|
|
|
|
|
|
|
uint8_t uri[NANOCOAP_URI_MAX];
|
|
|
|
if (coap_get_uri_path(pdu, uri) <= 0) {
|
|
|
|
return GCOAP_RESOURCE_NO_PATH;
|
|
|
|
}
|
|
|
|
|
2016-10-29 21:20:22 +02:00
|
|
|
while (listener) {
|
2018-07-08 20:19:43 +02:00
|
|
|
const coap_resource_t *resource = listener->resources;
|
2016-10-29 21:20:22 +02:00
|
|
|
for (size_t i = 0; i < listener->resources_len; i++) {
|
|
|
|
if (i) {
|
|
|
|
resource++;
|
|
|
|
}
|
|
|
|
|
2019-03-11 13:21:52 +01:00
|
|
|
int res = coap_match_path(resource, uri);
|
2016-10-29 21:20:22 +02:00
|
|
|
if (res > 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if (res < 0) {
|
|
|
|
/* resources expected in alphabetical order */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else {
|
2017-10-09 17:15:54 +02:00
|
|
|
if (! (resource->methods & method_flag)) {
|
|
|
|
ret = GCOAP_RESOURCE_WRONG_METHOD;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-03-13 05:27:22 +01:00
|
|
|
*resource_ptr = resource;
|
|
|
|
*listener_ptr = listener;
|
2017-10-09 17:15:54 +02:00
|
|
|
return GCOAP_RESOURCE_FOUND;
|
2016-10-29 21:20:22 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
listener = listener->next;
|
|
|
|
}
|
2017-10-09 17:15:54 +02:00
|
|
|
|
|
|
|
return ret;
|
2016-10-29 21:20:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Finds the memo for an outstanding request within the _coap_state.open_reqs
|
2017-12-13 09:22:20 +01:00
|
|
|
* array. Matches on remote endpoint and token.
|
2016-10-29 21:20:22 +02:00
|
|
|
*
|
2017-07-09 13:35:42 +02:00
|
|
|
* memo_ptr[out] -- Registered request memo, or NULL if not found
|
|
|
|
* src_pdu[in] -- PDU for token to match
|
2017-12-13 09:22:20 +01:00
|
|
|
* remote[in] -- Remote endpoint to match
|
2016-10-29 21:20:22 +02:00
|
|
|
*/
|
2017-12-13 09:22:20 +01:00
|
|
|
static void _find_req_memo(gcoap_request_memo_t **memo_ptr, coap_pkt_t *src_pdu,
|
|
|
|
const sock_udp_ep_t *remote)
|
2016-10-29 21:20:22 +02:00
|
|
|
{
|
2017-07-09 13:35:42 +02:00
|
|
|
*memo_ptr = NULL;
|
|
|
|
/* no need to initialize struct; we only care about buffer contents below */
|
|
|
|
coap_pkt_t memo_pdu_data;
|
|
|
|
coap_pkt_t *memo_pdu = &memo_pdu_data;
|
|
|
|
unsigned cmplen = coap_get_token_len(src_pdu);
|
2016-10-29 21:20:22 +02:00
|
|
|
|
|
|
|
for (int i = 0; i < GCOAP_REQ_WAITING_MAX; i++) {
|
|
|
|
if (_coap_state.open_reqs[i].state == GCOAP_MEMO_UNUSED)
|
|
|
|
continue;
|
|
|
|
|
2017-07-09 13:35:42 +02:00
|
|
|
gcoap_request_memo_t *memo = &_coap_state.open_reqs[i];
|
|
|
|
if (memo->send_limit == GCOAP_SEND_LIMIT_NON) {
|
|
|
|
memo_pdu->hdr = (coap_hdr_t *) &memo->msg.hdr_buf[0];
|
2016-10-29 21:20:22 +02:00
|
|
|
}
|
2017-07-09 13:35:42 +02:00
|
|
|
else {
|
|
|
|
memo_pdu->hdr = (coap_hdr_t *) memo->msg.data.pdu_buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (coap_get_token_len(memo_pdu) == cmplen) {
|
2018-10-25 09:53:22 +02:00
|
|
|
memo_pdu->token = coap_hdr_data_ptr(memo_pdu->hdr);
|
2017-12-13 09:22:20 +01:00
|
|
|
if ((memcmp(src_pdu->token, memo_pdu->token, cmplen) == 0)
|
2018-09-07 12:03:19 +02:00
|
|
|
&& sock_udp_ep_equal(&memo->remote_ep, remote)) {
|
2016-10-29 21:20:22 +02:00
|
|
|
*memo_ptr = memo;
|
2017-07-09 13:35:42 +02:00
|
|
|
break;
|
2016-10-29 21:20:22 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Calls handler callback on receipt of a timeout message. */
|
|
|
|
static void _expire_request(gcoap_request_memo_t *memo)
|
|
|
|
{
|
|
|
|
DEBUG("coap: received timeout message\n");
|
|
|
|
if (memo->state == GCOAP_MEMO_WAIT) {
|
|
|
|
memo->state = GCOAP_MEMO_TIMEOUT;
|
|
|
|
/* Pass response to handler */
|
|
|
|
if (memo->resp_handler) {
|
2017-08-15 18:50:37 +02:00
|
|
|
coap_pkt_t req;
|
|
|
|
if (memo->send_limit == GCOAP_SEND_LIMIT_NON) {
|
|
|
|
req.hdr = (coap_hdr_t *)&memo->msg.hdr_buf[0]; /* for reference */
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
req.hdr = (coap_hdr_t *)memo->msg.data.pdu_buf;
|
|
|
|
}
|
2017-07-25 19:22:45 +02:00
|
|
|
memo->resp_handler(memo->state, &req, NULL);
|
2016-10-29 21:20:22 +02:00
|
|
|
}
|
2017-08-15 18:50:37 +02:00
|
|
|
if (memo->send_limit != GCOAP_SEND_LIMIT_NON) {
|
|
|
|
*memo->msg.data.pdu_buf = 0; /* clear resend buffer */
|
|
|
|
}
|
2016-10-29 21:20:22 +02:00
|
|
|
memo->state = GCOAP_MEMO_UNUSED;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
/* Response already handled; timeout must have fired while response */
|
|
|
|
/* was in queue. */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Handler for /.well-known/core. Lists registered handlers, except for
|
|
|
|
* /.well-known/core itself.
|
|
|
|
*/
|
2019-01-27 14:51:02 +01:00
|
|
|
static ssize_t _well_known_core_handler(coap_pkt_t* pdu, uint8_t *buf, size_t len,
|
|
|
|
void *ctx)
|
2016-10-29 21:20:22 +02:00
|
|
|
{
|
2018-02-02 19:01:58 +01:00
|
|
|
(void)ctx;
|
2019-01-27 14:51:02 +01:00
|
|
|
|
2016-10-29 21:20:22 +02:00
|
|
|
gcoap_resp_init(pdu, buf, len, COAP_CODE_CONTENT);
|
2019-01-27 14:51:02 +01:00
|
|
|
coap_opt_add_format(pdu, COAP_FORMAT_LINK);
|
|
|
|
ssize_t plen = coap_opt_finish(pdu, COAP_OPT_FINISH_PAYLOAD);
|
|
|
|
|
|
|
|
plen += gcoap_get_resource_list(pdu->payload, (size_t)pdu->payload_len,
|
|
|
|
COAP_FORMAT_LINK);
|
|
|
|
return plen;
|
2016-10-29 21:20:22 +02:00
|
|
|
}
|
|
|
|
|
2017-03-13 05:27:22 +01:00
|
|
|
/*
|
|
|
|
* Find registered observer for a remote address and port.
|
|
|
|
*
|
|
|
|
* observer[out] -- Registered observer, or NULL if not found
|
|
|
|
* remote[in] -- Endpoint to match
|
|
|
|
*
|
|
|
|
* return Index of empty slot, suitable for registering new observer; or -1
|
|
|
|
* if no empty slots. Undefined if observer found.
|
|
|
|
*/
|
|
|
|
static int _find_observer(sock_udp_ep_t **observer, sock_udp_ep_t *remote)
|
|
|
|
{
|
|
|
|
int empty_slot = -1;
|
|
|
|
*observer = NULL;
|
|
|
|
for (unsigned i = 0; i < GCOAP_OBS_CLIENTS_MAX; i++) {
|
|
|
|
|
|
|
|
if (_coap_state.observers[i].family == AF_UNSPEC) {
|
|
|
|
empty_slot = i;
|
|
|
|
}
|
2018-09-07 12:03:19 +02:00
|
|
|
else if (sock_udp_ep_equal(&_coap_state.observers[i], remote)) {
|
2017-03-13 05:27:22 +01:00
|
|
|
*observer = &_coap_state.observers[i];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return empty_slot;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Find registered observe memo for a remote address and token.
|
|
|
|
*
|
|
|
|
* memo[out] -- Registered observe memo, or NULL if not found
|
|
|
|
* remote[in] -- Endpoint for address to match
|
|
|
|
* pdu[in] -- PDU for token to match, or NULL to match only on remote address
|
|
|
|
*
|
|
|
|
* return Index of empty slot, suitable for registering new memo; or -1 if no
|
|
|
|
* empty slots. Undefined if memo found.
|
|
|
|
*/
|
|
|
|
static int _find_obs_memo(gcoap_observe_memo_t **memo, sock_udp_ep_t *remote,
|
|
|
|
coap_pkt_t *pdu)
|
|
|
|
{
|
|
|
|
int empty_slot = -1;
|
|
|
|
*memo = NULL;
|
|
|
|
|
|
|
|
sock_udp_ep_t *remote_observer = NULL;
|
|
|
|
_find_observer(&remote_observer, remote);
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < GCOAP_OBS_REGISTRATIONS_MAX; i++) {
|
|
|
|
if (_coap_state.observe_memos[i].observer == NULL) {
|
|
|
|
empty_slot = i;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_coap_state.observe_memos[i].observer == remote_observer) {
|
|
|
|
if (pdu == NULL) {
|
|
|
|
*memo = &_coap_state.observe_memos[i];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_coap_state.observe_memos[i].token_len == coap_get_token_len(pdu)) {
|
|
|
|
unsigned cmplen = _coap_state.observe_memos[i].token_len;
|
|
|
|
if (cmplen &&
|
|
|
|
memcmp(&_coap_state.observe_memos[i].token[0], &pdu->token[0],
|
|
|
|
cmplen) == 0) {
|
|
|
|
*memo = &_coap_state.observe_memos[i];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return empty_slot;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Find registered observe memo for a resource.
|
|
|
|
*
|
|
|
|
* memo[out] -- Registered observe memo, or NULL if not found
|
|
|
|
* resource[in] -- Resource to match
|
|
|
|
*/
|
|
|
|
static void _find_obs_memo_resource(gcoap_observe_memo_t **memo,
|
|
|
|
const coap_resource_t *resource)
|
|
|
|
{
|
|
|
|
*memo = NULL;
|
|
|
|
for (int i = 0; i < GCOAP_OBS_REGISTRATIONS_MAX; i++) {
|
|
|
|
if (_coap_state.observe_memos[i].observer != NULL
|
|
|
|
&& _coap_state.observe_memos[i].resource == resource) {
|
|
|
|
*memo = &_coap_state.observe_memos[i];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-29 21:20:22 +02:00
|
|
|
/*
|
|
|
|
* gcoap interface functions
|
|
|
|
*/
|
|
|
|
|
|
|
|
kernel_pid_t gcoap_init(void)
|
|
|
|
{
|
|
|
|
if (_pid != KERNEL_PID_UNDEF) {
|
|
|
|
return -EEXIST;
|
|
|
|
}
|
|
|
|
_pid = thread_create(_msg_stack, sizeof(_msg_stack), THREAD_PRIORITY_MAIN - 1,
|
|
|
|
THREAD_CREATE_STACKTEST, _event_loop, NULL, "coap");
|
|
|
|
|
2017-06-18 12:29:43 +02:00
|
|
|
mutex_init(&_coap_state.lock);
|
2017-03-13 05:27:22 +01:00
|
|
|
/* Blank lists so we know if an entry is available. */
|
2016-10-29 21:20:22 +02:00
|
|
|
memset(&_coap_state.open_reqs[0], 0, sizeof(_coap_state.open_reqs));
|
2017-03-13 05:27:22 +01:00
|
|
|
memset(&_coap_state.observers[0], 0, sizeof(_coap_state.observers));
|
|
|
|
memset(&_coap_state.observe_memos[0], 0, sizeof(_coap_state.observe_memos));
|
2017-07-10 05:36:55 +02:00
|
|
|
memset(&_coap_state.resend_bufs[0], 0, sizeof(_coap_state.resend_bufs));
|
2016-10-29 21:20:22 +02:00
|
|
|
/* randomize initial value */
|
2017-06-18 12:16:15 +02:00
|
|
|
atomic_init(&_coap_state.next_message_id, (unsigned)random_uint32());
|
2016-10-29 21:20:22 +02:00
|
|
|
|
|
|
|
return _pid;
|
|
|
|
}
|
|
|
|
|
|
|
|
void gcoap_register_listener(gcoap_listener_t *listener)
|
|
|
|
{
|
|
|
|
/* Add the listener to the end of the linked list. */
|
|
|
|
gcoap_listener_t *_last = _coap_state.listeners;
|
2017-05-22 13:47:36 +02:00
|
|
|
while (_last->next) {
|
2016-10-29 21:20:22 +02:00
|
|
|
_last = _last->next;
|
2017-05-22 13:47:36 +02:00
|
|
|
}
|
2016-10-29 21:20:22 +02:00
|
|
|
|
|
|
|
listener->next = NULL;
|
2019-04-24 20:12:29 +02:00
|
|
|
if (!listener->link_encoder) {
|
|
|
|
listener->link_encoder = gcoap_encode_link;
|
|
|
|
}
|
2016-10-29 21:20:22 +02:00
|
|
|
_last->next = listener;
|
|
|
|
}
|
|
|
|
|
2018-05-03 13:31:55 +02:00
|
|
|
int gcoap_req_init(coap_pkt_t *pdu, uint8_t *buf, size_t len,
|
|
|
|
unsigned code, const char *path)
|
|
|
|
{
|
2018-11-28 19:20:22 +01:00
|
|
|
assert((path == NULL) || (path[0] == '/'));
|
2018-05-04 11:08:48 +02:00
|
|
|
|
2016-10-29 21:20:22 +02:00
|
|
|
pdu->hdr = (coap_hdr_t *)buf;
|
|
|
|
|
|
|
|
/* generate token */
|
2016-12-19 18:50:31 +01:00
|
|
|
#if GCOAP_TOKENLEN
|
|
|
|
uint8_t token[GCOAP_TOKENLEN];
|
2016-10-29 21:20:22 +02:00
|
|
|
for (size_t i = 0; i < GCOAP_TOKENLEN; i += 4) {
|
|
|
|
uint32_t rand = random_uint32();
|
|
|
|
memcpy(&token[i],
|
|
|
|
&rand,
|
|
|
|
(GCOAP_TOKENLEN - i >= 4) ? 4 : GCOAP_TOKENLEN - i);
|
|
|
|
}
|
2017-06-18 12:16:15 +02:00
|
|
|
uint16_t msgid = (uint16_t)atomic_fetch_add(&_coap_state.next_message_id, 1);
|
2019-02-01 23:35:54 +01:00
|
|
|
ssize_t res = coap_build_hdr(pdu->hdr, COAP_TYPE_NON, &token[0], GCOAP_TOKENLEN,
|
|
|
|
code, msgid);
|
2016-12-19 18:50:31 +01:00
|
|
|
#else
|
2017-06-18 12:16:15 +02:00
|
|
|
uint16_t msgid = (uint16_t)atomic_fetch_add(&_coap_state.next_message_id, 1);
|
2019-02-01 23:35:54 +01:00
|
|
|
ssize_t res = coap_build_hdr(pdu->hdr, COAP_TYPE_NON, NULL, GCOAP_TOKENLEN,
|
|
|
|
code, msgid);
|
2016-12-19 18:50:31 +01:00
|
|
|
#endif
|
2016-10-29 21:20:22 +02:00
|
|
|
|
2019-02-01 23:35:54 +01:00
|
|
|
coap_pkt_init(pdu, buf, len - GCOAP_REQ_OPTIONS_BUF, res);
|
|
|
|
if (path != NULL) {
|
|
|
|
res = coap_opt_add_string(pdu, COAP_OPT_URI_PATH, path, '/');
|
2016-10-29 21:20:22 +02:00
|
|
|
}
|
2019-02-01 23:35:54 +01:00
|
|
|
return (res > 0) ? 0 : res;
|
2016-10-29 21:20:22 +02:00
|
|
|
}
|
|
|
|
|
2018-05-16 19:09:40 +02:00
|
|
|
/*
|
|
|
|
* Assumes pdu.payload_len attribute was reduced in gcoap_xxx_init() to
|
|
|
|
* ensure enough space in PDU buffer to write Content-Format option and
|
|
|
|
* payload marker here.
|
|
|
|
*/
|
2016-10-29 21:20:22 +02:00
|
|
|
ssize_t gcoap_finish(coap_pkt_t *pdu, size_t payload_len, unsigned format)
|
|
|
|
{
|
2018-05-16 19:09:40 +02:00
|
|
|
assert( !(pdu->options_len) ||
|
|
|
|
!(payload_len) ||
|
|
|
|
(format == COAP_FORMAT_NONE) ||
|
|
|
|
(pdu->options[pdu->options_len-1].opt_num < COAP_OPT_CONTENT_FORMAT));
|
|
|
|
|
|
|
|
if (payload_len) {
|
|
|
|
/* determine Content-Format option length */
|
|
|
|
unsigned format_optlen = 1;
|
|
|
|
if (format == COAP_FORMAT_NONE) {
|
|
|
|
format_optlen = 0;
|
|
|
|
}
|
|
|
|
else if (format > 255) {
|
|
|
|
format_optlen = 3;
|
|
|
|
}
|
|
|
|
else if (format > 0) {
|
|
|
|
format_optlen = 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* move payload to accommodate option and payload marker */
|
|
|
|
memmove(pdu->payload+format_optlen+1, pdu->payload, payload_len);
|
2016-10-29 21:20:22 +02:00
|
|
|
|
2018-05-16 19:09:40 +02:00
|
|
|
if (format_optlen) {
|
|
|
|
coap_opt_add_uint(pdu, COAP_OPT_CONTENT_FORMAT, format);
|
|
|
|
}
|
|
|
|
*pdu->payload++ = 0xFF;
|
|
|
|
}
|
|
|
|
/* must write option before updating PDU with actual length */
|
|
|
|
pdu->payload_len = payload_len;
|
|
|
|
|
|
|
|
return pdu->payload_len + (pdu->payload - (uint8_t *)pdu->hdr);
|
2016-10-29 21:20:22 +02:00
|
|
|
}
|
|
|
|
|
2019-04-25 13:40:32 +02:00
|
|
|
size_t gcoap_req_send(const uint8_t *buf, size_t len,
|
|
|
|
const sock_udp_ep_t *remote,
|
|
|
|
gcoap_resp_handler_t resp_handler)
|
2016-10-29 21:20:22 +02:00
|
|
|
{
|
|
|
|
gcoap_request_memo_t *memo = NULL;
|
2017-07-10 05:36:55 +02:00
|
|
|
unsigned msg_type = (*buf & 0x30) >> 4;
|
|
|
|
uint32_t timeout = 0;
|
2018-06-08 07:56:52 +02:00
|
|
|
|
|
|
|
assert(remote != NULL);
|
|
|
|
|
|
|
|
/* Only allocate memory if necessary (i.e. if user is interested in the
|
|
|
|
* response or request is confirmable) */
|
|
|
|
if ((resp_handler != NULL) || (msg_type == COAP_TYPE_CON)) {
|
|
|
|
mutex_lock(&_coap_state.lock);
|
|
|
|
/* Find empty slot in list of open requests. */
|
|
|
|
for (int i = 0; i < 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;
|
2017-07-10 05:36:55 +02:00
|
|
|
break;
|
2016-11-17 06:01:15 +01:00
|
|
|
}
|
2016-10-29 21:20:22 +02:00
|
|
|
}
|
2018-06-08 07:56:52 +02:00
|
|
|
if (!memo) {
|
|
|
|
mutex_unlock(&_coap_state.lock);
|
|
|
|
DEBUG("gcoap: dropping request; no space for response tracking\n");
|
|
|
|
return 0;
|
2017-07-10 05:36:55 +02:00
|
|
|
}
|
2018-06-08 07:56:52 +02:00
|
|
|
|
|
|
|
memo->resp_handler = resp_handler;
|
|
|
|
memcpy(&memo->remote_ep, remote, sizeof(sock_udp_ep_t));
|
|
|
|
|
|
|
|
switch (msg_type) {
|
|
|
|
case COAP_TYPE_CON:
|
|
|
|
/* copy buf to resend_bufs record */
|
|
|
|
memo->msg.data.pdu_buf = NULL;
|
|
|
|
for (int i = 0; i < 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, GCOAP_PDU_BUF_SIZE);
|
|
|
|
memo->msg.data.pdu_len = len;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (memo->msg.data.pdu_buf) {
|
|
|
|
memo->send_limit = COAP_MAX_RETRANSMIT;
|
|
|
|
timeout = (uint32_t)COAP_ACK_TIMEOUT * US_PER_SEC;
|
2019-07-03 15:42:16 +02:00
|
|
|
#if COAP_ACK_VARIANCE > 0
|
2018-06-08 07:56:52 +02:00
|
|
|
uint32_t variance = (uint32_t)COAP_ACK_VARIANCE * US_PER_SEC;
|
|
|
|
timeout = random_uint32_range(timeout, timeout + variance);
|
2019-07-03 15:42:16 +02:00
|
|
|
#endif
|
2018-06-08 07:56:52 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
memo->state = GCOAP_MEMO_UNUSED;
|
|
|
|
DEBUG("gcoap: no space for PDU in resend bufs\n");
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case COAP_TYPE_NON:
|
|
|
|
memo->send_limit = GCOAP_SEND_LIMIT_NON;
|
|
|
|
memcpy(&memo->msg.hdr_buf[0], buf, GCOAP_HEADER_MAXLEN);
|
|
|
|
timeout = GCOAP_NON_TIMEOUT;
|
|
|
|
break;
|
|
|
|
default:
|
2016-10-29 21:20:22 +02:00
|
|
|
memo->state = GCOAP_MEMO_UNUSED;
|
2018-06-08 07:56:52 +02:00
|
|
|
DEBUG("gcoap: illegal msg type %u\n", msg_type);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
mutex_unlock(&_coap_state.lock);
|
|
|
|
if (memo->state == GCOAP_MEMO_UNUSED) {
|
|
|
|
return 0;
|
2016-10-29 21:20:22 +02:00
|
|
|
}
|
|
|
|
}
|
2017-07-10 05:36:55 +02:00
|
|
|
|
|
|
|
/* Memos complete; send msg and start timer */
|
2018-02-16 12:28:32 +01:00
|
|
|
ssize_t res = sock_udp_send(&_sock, buf, len, remote);
|
2017-07-10 05:36:55 +02:00
|
|
|
|
2018-06-08 07:56:52 +02:00
|
|
|
/* timeout may be zero for non-confirmable */
|
|
|
|
if ((memo != NULL) && (res > 0) && (timeout > 0)) {
|
2019-07-03 15:00:06 +02:00
|
|
|
/* We assume gcoap_req_send() is called on some thread other than
|
2017-08-15 18:50:37 +02:00
|
|
|
* gcoap's. First, put a message in the mbox for the sock udp object,
|
|
|
|
* which will interrupt listening on the gcoap thread. (When there are
|
|
|
|
* no outstanding requests, gcoap blocks indefinitely in _listen() at
|
|
|
|
* sock_udp_recv().) While the message sent here is outstanding, the
|
|
|
|
* sock_udp_recv() call will be set to a short timeout so the request
|
|
|
|
* timer below, also on the gcoap thread, is processed in a timely
|
|
|
|
* manner. */
|
2017-07-10 05:36:55 +02:00
|
|
|
msg_t mbox_msg;
|
|
|
|
mbox_msg.type = GCOAP_MSG_TYPE_INTR;
|
|
|
|
mbox_msg.content.value = 0;
|
|
|
|
if (mbox_try_put(&_sock.reg.mbox, &mbox_msg)) {
|
2017-08-15 18:50:37 +02:00
|
|
|
/* start response wait timer on the gcoap thread */
|
2017-07-10 05:36:55 +02:00
|
|
|
memo->timeout_msg.type = GCOAP_MSG_TYPE_TIMEOUT;
|
|
|
|
memo->timeout_msg.content.ptr = (char *)memo;
|
|
|
|
xtimer_set_msg(&memo->response_timer, timeout, &memo->timeout_msg, _pid);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
res = 0;
|
|
|
|
DEBUG("gcoap: can't wake up mbox; no timeout for msg\n");
|
|
|
|
}
|
|
|
|
}
|
2018-02-16 12:28:32 +01:00
|
|
|
if (res <= 0) {
|
2018-06-08 07:56:52 +02:00
|
|
|
if (memo != NULL) {
|
|
|
|
if (msg_type == COAP_TYPE_CON) {
|
|
|
|
*memo->msg.data.pdu_buf = 0; /* clear resend buffer */
|
|
|
|
}
|
|
|
|
memo->state = GCOAP_MEMO_UNUSED;
|
2017-07-10 05:36:55 +02:00
|
|
|
}
|
|
|
|
DEBUG("gcoap: sock send failed: %d\n", (int)res);
|
|
|
|
}
|
2018-02-16 12:28:32 +01:00
|
|
|
return (size_t)((res > 0) ? res : 0);
|
2016-10-29 21:20:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
int gcoap_resp_init(coap_pkt_t *pdu, uint8_t *buf, size_t len, unsigned code)
|
|
|
|
{
|
2017-06-21 19:08:10 +02:00
|
|
|
if (coap_get_type(pdu) == COAP_TYPE_CON) {
|
|
|
|
coap_hdr_set_type(pdu->hdr, COAP_TYPE_ACK);
|
|
|
|
}
|
2016-10-29 21:20:22 +02:00
|
|
|
coap_hdr_set_code(pdu->hdr, code);
|
|
|
|
|
2018-05-16 19:09:40 +02:00
|
|
|
unsigned header_len = coap_get_total_hdr_len(pdu);
|
|
|
|
|
|
|
|
pdu->options_len = 0;
|
|
|
|
pdu->payload = buf + header_len;
|
|
|
|
pdu->payload_len = len - header_len - GCOAP_RESP_OPTIONS_BUF;
|
|
|
|
|
|
|
|
if (coap_get_observe(pdu) == COAP_OBS_REGISTER) {
|
|
|
|
/* generate initial notification value */
|
|
|
|
uint32_t now = xtimer_now_usec();
|
|
|
|
pdu->observe_value = (now >> GCOAP_OBS_TICK_EXPONENT) & 0xFFFFFF;
|
|
|
|
coap_opt_add_uint(pdu, COAP_OPT_OBSERVE, pdu->observe_value);
|
|
|
|
}
|
2016-10-29 21:20:22 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-03-13 05:27:22 +01:00
|
|
|
int gcoap_obs_init(coap_pkt_t *pdu, uint8_t *buf, size_t len,
|
|
|
|
const coap_resource_t *resource)
|
|
|
|
{
|
|
|
|
gcoap_observe_memo_t *memo = NULL;
|
|
|
|
|
|
|
|
_find_obs_memo_resource(&memo, resource);
|
|
|
|
if (memo == NULL) {
|
|
|
|
/* Unique return value to specify there is not an observer */
|
|
|
|
return GCOAP_OBS_INIT_UNUSED;
|
|
|
|
}
|
|
|
|
|
2017-06-18 12:16:15 +02:00
|
|
|
pdu->hdr = (coap_hdr_t *)buf;
|
|
|
|
uint16_t msgid = (uint16_t)atomic_fetch_add(&_coap_state.next_message_id, 1);
|
|
|
|
ssize_t hdrlen = coap_build_hdr(pdu->hdr, COAP_TYPE_NON, &memo->token[0],
|
|
|
|
memo->token_len, COAP_CODE_CONTENT, msgid);
|
|
|
|
|
2017-03-13 05:27:22 +01:00
|
|
|
if (hdrlen > 0) {
|
2018-05-16 19:09:40 +02:00
|
|
|
coap_pkt_init(pdu, buf, len - GCOAP_OBS_OPTIONS_BUF, hdrlen);
|
|
|
|
|
2017-03-13 05:27:22 +01:00
|
|
|
uint32_t now = xtimer_now_usec();
|
|
|
|
pdu->observe_value = (now >> GCOAP_OBS_TICK_EXPONENT) & 0xFFFFFF;
|
2018-05-16 19:09:40 +02:00
|
|
|
coap_opt_add_uint(pdu, COAP_OPT_OBSERVE, pdu->observe_value);
|
2017-03-13 05:27:22 +01:00
|
|
|
|
|
|
|
return GCOAP_OBS_INIT_OK;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
/* reason for negative hdrlen is not defined, so we also are vague */
|
|
|
|
return GCOAP_OBS_INIT_ERR;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-26 16:34:40 +02:00
|
|
|
size_t gcoap_obs_send(const uint8_t *buf, size_t len,
|
|
|
|
const coap_resource_t *resource)
|
2017-03-13 05:27:22 +01:00
|
|
|
{
|
|
|
|
gcoap_observe_memo_t *memo = NULL;
|
|
|
|
|
|
|
|
_find_obs_memo_resource(&memo, resource);
|
|
|
|
|
|
|
|
if (memo) {
|
2018-02-16 12:28:32 +01:00
|
|
|
ssize_t bytes = sock_udp_send(&_sock, buf, len, memo->observer);
|
|
|
|
return (size_t)((bytes > 0) ? bytes : 0);
|
2017-03-13 05:27:22 +01:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-22 13:50:59 +02:00
|
|
|
uint8_t gcoap_op_state(void)
|
2016-10-29 21:20:22 +02:00
|
|
|
{
|
|
|
|
uint8_t count = 0;
|
|
|
|
for (int i = 0; i < GCOAP_REQ_WAITING_MAX; i++) {
|
|
|
|
if (_coap_state.open_reqs[i].state != GCOAP_MEMO_UNUSED) {
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
}
|
2017-05-22 13:50:59 +02:00
|
|
|
return count;
|
2016-10-29 21:20:22 +02:00
|
|
|
}
|
|
|
|
|
2017-07-25 20:36:24 +02:00
|
|
|
int gcoap_get_resource_list(void *buf, size_t maxlen, uint8_t cf)
|
|
|
|
{
|
2018-10-03 21:26:37 +02:00
|
|
|
assert(cf == COAP_FORMAT_LINK);
|
2017-07-25 20:36:24 +02:00
|
|
|
|
|
|
|
/* skip the first listener, gcoap itself (we skip /.well-known/core) */
|
|
|
|
gcoap_listener_t *listener = _coap_state.listeners->next;
|
|
|
|
|
|
|
|
char *out = (char *)buf;
|
|
|
|
size_t pos = 0;
|
|
|
|
|
2019-04-24 20:12:29 +02:00
|
|
|
coap_link_encoder_ctx_t ctx;
|
|
|
|
ctx.content_format = cf;
|
|
|
|
/* indicate initial link for the list */
|
|
|
|
ctx.flags = COAP_LINK_FLAG_INIT_RESLIST;
|
|
|
|
|
2017-07-25 20:36:24 +02:00
|
|
|
/* write payload */
|
|
|
|
while (listener) {
|
2019-04-24 20:12:29 +02:00
|
|
|
if (!listener->link_encoder) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
ctx.link_pos = 0;
|
2017-07-25 20:36:24 +02:00
|
|
|
|
2019-04-24 20:12:29 +02:00
|
|
|
for (; ctx.link_pos < listener->resources_len; ctx.link_pos++) {
|
|
|
|
ssize_t res;
|
2017-07-25 20:36:24 +02:00
|
|
|
if (out) {
|
2019-04-24 20:12:29 +02:00
|
|
|
res = listener->link_encoder(&listener->resources[ctx.link_pos],
|
|
|
|
&out[pos], maxlen - pos, &ctx);
|
2017-07-25 20:36:24 +02:00
|
|
|
}
|
|
|
|
else {
|
2019-04-24 20:12:29 +02:00
|
|
|
res = listener->link_encoder(&listener->resources[ctx.link_pos],
|
|
|
|
NULL, 0, &ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (res > 0) {
|
|
|
|
pos += res;
|
|
|
|
ctx.flags &= ~COAP_LINK_FLAG_INIT_RESLIST;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
break;
|
2017-07-25 20:36:24 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
listener = listener->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (int)pos;
|
|
|
|
}
|
|
|
|
|
2019-04-24 20:12:29 +02:00
|
|
|
ssize_t gcoap_encode_link(const coap_resource_t *resource, char *buf,
|
|
|
|
size_t maxlen, coap_link_encoder_ctx_t *context)
|
|
|
|
{
|
|
|
|
size_t path_len = strlen(resource->path);
|
|
|
|
/* count target separators and any link separator */
|
|
|
|
size_t exp_size = path_len + 2
|
|
|
|
+ ((context->flags & COAP_LINK_FLAG_INIT_RESLIST) ? 0 : 1);
|
|
|
|
|
|
|
|
if (buf) {
|
|
|
|
unsigned pos = 0;
|
|
|
|
if (exp_size > maxlen) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(context->flags & COAP_LINK_FLAG_INIT_RESLIST)) {
|
|
|
|
buf[pos++] = ',';
|
|
|
|
}
|
|
|
|
buf[pos++] = '<';
|
|
|
|
memcpy(&buf[pos], resource->path, path_len);
|
|
|
|
buf[pos+path_len] = '>';
|
|
|
|
}
|
|
|
|
|
|
|
|
return exp_size;
|
|
|
|
}
|
|
|
|
|
2017-07-24 17:57:53 +02:00
|
|
|
int gcoap_add_qstring(coap_pkt_t *pdu, const char *key, const char *val)
|
|
|
|
{
|
2018-05-21 19:01:06 +02:00
|
|
|
char qs[NANOCOAP_QS_MAX];
|
|
|
|
size_t len = strlen(key);
|
2017-07-24 17:57:53 +02:00
|
|
|
size_t val_len = (val) ? (strlen(val) + 1) : 0;
|
|
|
|
|
2018-05-21 19:01:06 +02:00
|
|
|
/* test if the query string fits, account for the zero termination */
|
|
|
|
if ((len + val_len + 1) >= NANOCOAP_QS_MAX) {
|
2017-07-24 17:57:53 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2018-05-21 19:01:06 +02:00
|
|
|
memcpy(&qs[0], key, len);
|
2017-07-24 17:57:53 +02:00
|
|
|
if (val) {
|
2018-05-21 19:01:06 +02:00
|
|
|
qs[len] = '=';
|
|
|
|
/* the `=` character was already counted in `val_len`, so subtract it here */
|
|
|
|
memcpy(&qs[len + 1], val, (val_len - 1));
|
|
|
|
len += val_len;
|
2017-07-24 17:57:53 +02:00
|
|
|
}
|
2018-05-21 19:01:06 +02:00
|
|
|
qs[len] = '\0';
|
2017-07-24 17:57:53 +02:00
|
|
|
|
2018-05-21 19:01:06 +02:00
|
|
|
return coap_opt_add_string(pdu, COAP_OPT_URI_QUERY, qs, '&');
|
2017-07-24 17:57:53 +02:00
|
|
|
}
|
|
|
|
|
2016-10-29 21:20:22 +02:00
|
|
|
/** @} */
|