Skip to content
Snippets Groups Projects
Commit af1eca90 authored by Ken Bannister's avatar Ken Bannister
Browse files

gcoap: Rebase messaging on sock API

parent fa815993
Branches
No related tags found
No related merge requests found
...@@ -9,14 +9,21 @@ ...@@ -9,14 +9,21 @@
/** /**
* @defgroup net_gcoap CoAP * @defgroup net_gcoap CoAP
* @ingroup net * @ingroup net
* @brief sock-based implementation of CoAP protocol, RFC 7252 * @brief High-level interface to CoAP messaging
* *
* ## Architecture ## * gcoap provides a high-level interface for writing CoAP messages via RIOT's
* Requests and responses are exchanged via an asynchronous RIOT message * sock networking API. gcoap internalizes network event processing so an
* processing thread. Depends on nanocoap for base level structs and * application only needs to focus on request/response handling. For a server,
* functionality. * gcoap accepts a list of resource paths with callbacks for writing the
* * response. For a client, gcoap provides a function to send a request, with a
* Uses a single UDP port for communication to support RFC 6282 compression. * callback for reading the server response. Generation of the request or
* response requires from one to three well-defined steps, depending on
* inclusion of a payload.
*
* gcoap allocates a RIOT message processing thread, so a single instance can
* serve multiple applications. This approach also means gcoap uses a single UDP
* port, which supports RFC 6282 compression. Internally, gcoap depends on the
* nanocoap package for base level structs and functionality.
* *
* ## Server Operation ## * ## Server Operation ##
* *
...@@ -61,11 +68,10 @@ ...@@ -61,11 +68,10 @@
* *
* ## Client Operation ## * ## Client Operation ##
* *
* gcoap uses RIOT's asynchronous messaging facility to send and receive * Client operation includes two phases: creating and sending a request, and
* messages. So, client operation includes two phases: creating and sending a * handling the response aynchronously in a client supplied callback. See
* request, and handling the response aynchronously in a client supplied * `examples/gcoap/gcoap_cli.c` for a simple example of sending a request and
* callback. See `examples/gcoap/gcoap_cli.c` for a simple example of sending * reading the response.
* a request and reading the response.
* *
* ### Creating a request ### * ### Creating a request ###
* *
...@@ -85,8 +91,8 @@ ...@@ -85,8 +91,8 @@
* as described above. The gcoap_request() function is inline, and uses those * as described above. The gcoap_request() function is inline, and uses those
* two functions. * two functions.
* *
* Finally, call gcoap_req_send() with the destination host and port, as well * Finally, call gcoap_req_send2() for the destination endpoint, as well as a
* as a callback function for the host's response. * callback function for the host's response.
* *
* ### Handling the response ### * ### Handling the response ###
* *
...@@ -114,13 +120,13 @@ ...@@ -114,13 +120,13 @@
* header and the payload. So, gcoap provides space in the buffer for them in * header and the payload. So, gcoap provides space in the buffer for them in
* the request/response ...init() function, and then writes them during * the request/response ...init() function, and then writes them during
* gcoap_finish(). We trade some inefficiency/work in the buffer for * gcoap_finish(). We trade some inefficiency/work in the buffer for
* simplicity for the user. * simplicity in the API.
* *
* ### Waiting for a response ### * ### Waiting for a response ###
* *
* We take advantage of RIOT's GNRC stack by using an xtimer to wait for a * We take advantage of RIOT's asynchronous messaging by using an xtimer to wait
* response, so the gcoap thread does not block while waiting. The user is * for a response, so the gcoap thread does not block while waiting. The user is
* notified via the same callback whether the message is received or the wait * notified via the same callback, whether the message is received or the wait
* times out. We track the response with an entry in the * times out. We track the response with an entry in the
* `_coap_state.open_reqs` array. * `_coap_state.open_reqs` array.
* *
...@@ -135,9 +141,7 @@ ...@@ -135,9 +141,7 @@
#ifndef GCOAP_H #ifndef GCOAP_H
#define GCOAP_H #define GCOAP_H
#include "net/gnrc.h" #include "net/sock/udp.h"
#include "net/gnrc/ipv6.h"
#include "net/gnrc/udp.h"
#include "nanocoap.h" #include "nanocoap.h"
#include "xtimer.h" #include "xtimer.h"
...@@ -199,15 +203,27 @@ extern "C" { ...@@ -199,15 +203,27 @@ extern "C" {
#define GCOAP_MEMO_ERR (4) /**< Error processing response packet */ #define GCOAP_MEMO_ERR (4) /**< Error processing response packet */
/** @} */ /** @} */
/** @brief Time in usec that the event loop waits for an incoming CoAP message */
#define GCOAP_RECV_TIMEOUT (1 * US_PER_SEC)
/** /**
*
* @brief Default time to wait for a non-confirmable response, in usec * @brief Default time to wait for a non-confirmable response, in usec
* *
* Set to 0 to disable timeout. * Set to 0 to disable timeout.
*/ */
#define GCOAP_NON_TIMEOUT (5000000U) #define GCOAP_NON_TIMEOUT (5000000U)
/** @brief Identifies a gcoap-specific timeout IPC message */ /** @brief Identifies waiting timed out for a response to a sent message. */
#define GCOAP_NETAPI_MSG_TYPE_TIMEOUT (0x1501) #define GCOAP_MSG_TYPE_TIMEOUT (0x1501)
/**
* @brief Identifies a request to interrupt listening for an incoming message
* on a sock.
*
* Allows the event loop to process IPC messages.
*/
#define GCOAP_MSG_TYPE_INTR (0x1502)
/** /**
* @brief A modular collection of resources for a server * @brief A modular collection of resources for a server
...@@ -243,7 +259,6 @@ typedef struct { ...@@ -243,7 +259,6 @@ typedef struct {
* @brief Container for the state of gcoap itself * @brief Container for the state of gcoap itself
*/ */
typedef struct { typedef struct {
gnrc_netreg_entry_t netreg_port; /**< Registration for IP port */
gcoap_listener_t *listeners; /**< List of registered listeners */ gcoap_listener_t *listeners; /**< List of registered listeners */
gcoap_request_memo_t open_reqs[GCOAP_REQ_WAITING_MAX]; gcoap_request_memo_t open_reqs[GCOAP_REQ_WAITING_MAX];
/**< Storage for open requests; if first /**< Storage for open requests; if first
...@@ -322,9 +337,25 @@ static inline ssize_t gcoap_request(coap_pkt_t *pdu, uint8_t *buf, size_t len, ...@@ -322,9 +337,25 @@ static inline ssize_t gcoap_request(coap_pkt_t *pdu, uint8_t *buf, size_t len,
: -1; : -1;
} }
/**
* @brief Sends a buffer containing a CoAP request to the provided endpoint.
*
* @param[in] buf Buffer containing the PDU
* @param[in] len Length of the buffer
* @param[in] remote Destination for the packet
* @param[in] resp_handler Callback when response received
*
* @return length of the packet
* @return 0 if cannot send
*/
size_t gcoap_req_send2(uint8_t *buf, size_t len, sock_udp_ep_t *remote,
gcoap_resp_handler_t resp_handler);
/** /**
* @brief Sends a buffer containing a CoAP request to the provided host/port. * @brief Sends a buffer containing a CoAP request to the provided host/port.
* *
* @deprecated Please use @ref gcoap_req_send2() instead
*
* @param[in] buf Buffer containing the PDU * @param[in] buf Buffer containing the PDU
* @param[in] len Length of the buffer * @param[in] len Length of the buffer
* @param[in] addr Destination for the packet * @param[in] addr Destination for the packet
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
*/ */
/** /**
* @ingroup net_gnrc_coap * @ingroup net_gcoap
* @{ * @{
* *
* @file * @file
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
*/ */
#include <errno.h> #include <errno.h>
#include "net/gnrc/coap.h" #include "net/gcoap.h"
#include "random.h" #include "random.h"
#include "thread.h" #include "thread.h"
...@@ -31,14 +31,11 @@ ...@@ -31,14 +31,11 @@
/* Internal functions */ /* Internal functions */
static void *_event_loop(void *arg); static void *_event_loop(void *arg);
static int _register_port(gnrc_netreg_entry_t *netreg_port, uint16_t port); static void _listen(sock_udp_t *sock);
static void _receive(gnrc_pktsnip_t *pkt, ipv6_addr_t *src, uint16_t port);
static size_t _send(gnrc_pktsnip_t *coap_snip, ipv6_addr_t *addr, uint16_t port);
static ssize_t _well_known_core_handler(coap_pkt_t* pdu, uint8_t *buf, size_t len); static ssize_t _well_known_core_handler(coap_pkt_t* pdu, uint8_t *buf, size_t len);
static ssize_t _write_options(coap_pkt_t *pdu, uint8_t *buf, size_t len); static ssize_t _write_options(coap_pkt_t *pdu, uint8_t *buf, size_t len);
static size_t _handle_req(coap_pkt_t *pdu, uint8_t *buf, size_t len); static size_t _handle_req(coap_pkt_t *pdu, uint8_t *buf, size_t len);
static ssize_t _finish_pdu(coap_pkt_t *pdu, uint8_t *buf, size_t len); static ssize_t _finish_pdu(coap_pkt_t *pdu, uint8_t *buf, size_t len);
static size_t _send_buf( uint8_t *buf, size_t len, ipv6_addr_t *src, uint16_t port);
static void _expire_request(gcoap_request_memo_t *memo); static void _expire_request(gcoap_request_memo_t *memo);
static void _find_req_memo(gcoap_request_memo_t **memo_ptr, coap_pkt_t *pdu, static void _find_req_memo(gcoap_request_memo_t **memo_ptr, coap_pkt_t *pdu,
uint8_t *buf, size_t len); uint8_t *buf, size_t len);
...@@ -55,97 +52,91 @@ static gcoap_listener_t _default_listener = { ...@@ -55,97 +52,91 @@ static gcoap_listener_t _default_listener = {
}; };
static gcoap_state_t _coap_state = { static gcoap_state_t _coap_state = {
.netreg_port = GNRC_NETREG_ENTRY_INIT_PID(0, KERNEL_PID_UNDEF),
.listeners = &_default_listener, .listeners = &_default_listener,
}; };
static kernel_pid_t _pid = KERNEL_PID_UNDEF; static kernel_pid_t _pid = KERNEL_PID_UNDEF;
static char _msg_stack[GCOAP_STACK_SIZE]; static char _msg_stack[GCOAP_STACK_SIZE];
static sock_udp_t _sock;
/* Event/Message loop for gcoap _pid thread. */ /* Event/Message loop for gcoap _pid thread. */
static void *_event_loop(void *arg) static void *_event_loop(void *arg)
{ {
msg_t msg_rcvd, msg_queue[GCOAP_MSG_QUEUE_SIZE]; msg_t msg_rcvd, msg_queue[GCOAP_MSG_QUEUE_SIZE];
gnrc_pktsnip_t *pkt, *udp_snip, *ipv6_snip;
ipv6_addr_t *src_addr;
uint16_t port;
(void)arg; (void)arg;
msg_init_queue(msg_queue, GCOAP_MSG_QUEUE_SIZE);
while (1) {
msg_receive(&msg_rcvd);
switch (msg_rcvd.type) { msg_init_queue(msg_queue, GCOAP_MSG_QUEUE_SIZE);
case GNRC_NETAPI_MSG_TYPE_RCV:
/* find client from UDP destination port */
DEBUG("coap: GNRC_NETAPI_MSG_TYPE_RCV\n");
pkt = (gnrc_pktsnip_t *)msg_rcvd.content.ptr;
if (pkt->type != GNRC_NETTYPE_UNDEF) {
gnrc_pktbuf_release(pkt);
break;
}
udp_snip = pkt->next;
if (udp_snip->type != GNRC_NETTYPE_UDP) {
gnrc_pktbuf_release(pkt);
break;
}
/* read source port and address */
port = byteorder_ntohs(((udp_hdr_t *)udp_snip->data)->src_port);
LL_SEARCH_SCALAR(udp_snip, ipv6_snip, type, GNRC_NETTYPE_IPV6); sock_udp_ep_t local;
assert(ipv6_snip != NULL); memset(&local, 0, sizeof(sock_udp_ep_t));
src_addr = &((ipv6_hdr_t *)ipv6_snip->data)->src; local.family = AF_INET6;
local.netif = SOCK_ADDR_ANY_NETIF;
local.port = GCOAP_PORT;
_receive(pkt, src_addr, port); int res = sock_udp_create(&_sock, &local, NULL, 0);
break; if (res < 0) {
DEBUG("gcoap: cannot create sock: %d\n", res);
return 0;
}
case GCOAP_NETAPI_MSG_TYPE_TIMEOUT: while(1) {
_expire_request((gcoap_request_memo_t *)msg_rcvd.content.ptr); res = msg_try_receive(&msg_rcvd);
break;
default: if (res > 0) {
break; switch (msg_rcvd.type) {
case GCOAP_MSG_TYPE_TIMEOUT:
_expire_request((gcoap_request_memo_t *)msg_rcvd.content.ptr);
break;
case GCOAP_MSG_TYPE_INTR:
/* next _listen() timeout will account for open requests */
break;
default:
break;
}
} }
_listen(&_sock);
} }
return 0; return 0;
} }
/* Handles incoming network IPC message. */ /* Listen for an incoming CoAP message. */
static void _receive(gnrc_pktsnip_t *pkt, ipv6_addr_t *src, uint16_t port) static void _listen(sock_udp_t *sock)
{ {
coap_pkt_t pdu; coap_pkt_t pdu;
uint8_t buf[GCOAP_PDU_BUF_SIZE]; uint8_t buf[GCOAP_PDU_BUF_SIZE];
size_t pdu_len = 0; sock_udp_ep_t remote;
gcoap_request_memo_t *memo = NULL; gcoap_request_memo_t *memo = NULL;
uint8_t open_reqs;
/* If too big, handle below based on request vs. response */ gcoap_op_state(&open_reqs);
size_t pkt_size = (pkt->size > sizeof(buf))
? sizeof(buf) : pkt->size;
/* Copy request into temporary buffer, and parse it as CoAP. */ ssize_t res = sock_udp_recv(sock, buf, sizeof(buf),
memcpy(buf, pkt->data, pkt_size); 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;
}
int result = coap_parse(&pdu, buf, pkt_size); res = coap_parse(&pdu, buf, res);
if (result < 0) { if (res < 0) {
DEBUG("gcoap: parse failure: %d\n", result); DEBUG("gcoap: parse failure: %d\n", res);
/* If a response, can't clear memo, but it will timeout later. */ /* If a response, can't clear memo, but it will timeout later. */
goto exit; return;
} }
/* incoming request */ /* incoming request */
if (coap_get_code_class(&pdu) == COAP_CLASS_REQ) { if (coap_get_code_class(&pdu) == COAP_CLASS_REQ) {
if (pkt->size > sizeof(buf)) { size_t pdu_len = _handle_req(&pdu, buf, sizeof(buf));
DEBUG("gcoap: request too big: %u\n", pkt->size);
pdu_len = gcoap_response(&pdu, buf, sizeof(buf),
COAP_CODE_REQUEST_ENTITY_TOO_LARGE);
} else {
pdu_len = _handle_req(&pdu, buf, sizeof(buf));
}
if (pdu_len > 0) { if (pdu_len > 0) {
_send_buf(buf, pdu_len, src, port); sock_udp_send(sock, buf, pdu_len, &remote);
} }
} }
/* incoming response */ /* incoming response */
...@@ -153,17 +144,10 @@ static void _receive(gnrc_pktsnip_t *pkt, ipv6_addr_t *src, uint16_t port) ...@@ -153,17 +144,10 @@ static void _receive(gnrc_pktsnip_t *pkt, ipv6_addr_t *src, uint16_t port)
_find_req_memo(&memo, &pdu, buf, sizeof(buf)); _find_req_memo(&memo, &pdu, buf, sizeof(buf));
if (memo) { if (memo) {
xtimer_remove(&memo->response_timer); xtimer_remove(&memo->response_timer);
if (pkt->size > sizeof(buf)) {
memo->state = GCOAP_MEMO_ERR;
DEBUG("gcoap: response too big: %u\n", pkt->size);
}
memo->resp_handler(memo->state, &pdu); memo->resp_handler(memo->state, &pdu);
memo->state = GCOAP_MEMO_UNUSED; memo->state = GCOAP_MEMO_UNUSED;
} }
} }
exit:
gnrc_pktbuf_release(pkt);
} }
/* /*
...@@ -296,77 +280,6 @@ static void _expire_request(gcoap_request_memo_t *memo) ...@@ -296,77 +280,6 @@ static void _expire_request(gcoap_request_memo_t *memo)
} }
} }
/* Registers receive/send port with GNRC registry. */
static int _register_port(gnrc_netreg_entry_t *netreg_port, uint16_t port)
{
if (!gnrc_netreg_lookup(GNRC_NETTYPE_UDP, port)) {
gnrc_netreg_entry_init_pid(netreg_port, port, _pid);
gnrc_netreg_register(GNRC_NETTYPE_UDP, netreg_port);
DEBUG("coap: registered UDP port %" PRIu32 "\n",
netreg_port->demux_ctx);
return 0;
}
else {
return -EINVAL;
}
}
/*
* Sends a CoAP message to the provided host/port.
*
* @return Length of the packet
* @return 0 if cannot send
*/
static size_t _send(gnrc_pktsnip_t *coap_snip, ipv6_addr_t *addr, uint16_t port)
{
gnrc_pktsnip_t *udp, *ip;
size_t pktlen;
/* allocate UDP header */
udp = gnrc_udp_hdr_build(coap_snip, (uint16_t)_coap_state.netreg_port.demux_ctx,
port);
if (udp == NULL) {
DEBUG("gcoap: unable to allocate UDP header\n");
gnrc_pktbuf_release(coap_snip);
return 0;
}
/* allocate IPv6 header */
ip = gnrc_ipv6_hdr_build(udp, NULL, addr);
if (ip == NULL) {
DEBUG("gcoap: unable to allocate IPv6 header\n");
gnrc_pktbuf_release(udp);
return 0;
}
pktlen = gnrc_pkt_len(ip); /* count length now; snips deallocated after send */
/* send message */
if (!gnrc_netapi_dispatch_send(GNRC_NETTYPE_UDP, GNRC_NETREG_DEMUX_CTX_ALL, ip)) {
DEBUG("gcoap: unable to locate UDP thread\n");
gnrc_pktbuf_release(ip);
return 0;
}
return pktlen;
}
/*
* Copies the request/response buffer to a pktsnip and sends it.
*
* @return Length of the packet
* @return 0 if cannot send
*/
static size_t _send_buf(uint8_t *buf, size_t len, ipv6_addr_t *src, uint16_t port)
{
gnrc_pktsnip_t *snip;
snip = gnrc_pktbuf_add(NULL, NULL, len, GNRC_NETTYPE_UNDEF);
if (!snip) {
return 0;
}
memcpy(snip->data, buf, len);
return _send(snip, src, port);
}
/* /*
* Handler for /.well-known/core. Lists registered handlers, except for * Handler for /.well-known/core. Lists registered handlers, except for
* /.well-known/core itself. * /.well-known/core itself.
...@@ -456,10 +369,6 @@ kernel_pid_t gcoap_init(void) ...@@ -456,10 +369,6 @@ kernel_pid_t gcoap_init(void)
_pid = thread_create(_msg_stack, sizeof(_msg_stack), THREAD_PRIORITY_MAIN - 1, _pid = thread_create(_msg_stack, sizeof(_msg_stack), THREAD_PRIORITY_MAIN - 1,
THREAD_CREATE_STACKTEST, _event_loop, NULL, "coap"); THREAD_CREATE_STACKTEST, _event_loop, NULL, "coap");
/* must establish pid first */
if (_register_port(&_coap_state.netreg_port, GCOAP_PORT) < 0) {
return -EINVAL;
}
/* Blank list of open requests so we know if an entry is available. */ /* Blank list of open requests so we know if an entry is available. */
memset(&_coap_state.open_reqs[0], 0, sizeof(_coap_state.open_reqs)); memset(&_coap_state.open_reqs[0], 0, sizeof(_coap_state.open_reqs));
/* randomize initial value */ /* randomize initial value */
...@@ -529,8 +438,23 @@ ssize_t gcoap_finish(coap_pkt_t *pdu, size_t payload_len, unsigned format) ...@@ -529,8 +438,23 @@ ssize_t gcoap_finish(coap_pkt_t *pdu, size_t payload_len, unsigned format)
size_t gcoap_req_send(uint8_t *buf, size_t len, ipv6_addr_t *addr, uint16_t port, size_t gcoap_req_send(uint8_t *buf, size_t len, ipv6_addr_t *addr, uint16_t port,
gcoap_resp_handler_t resp_handler) gcoap_resp_handler_t resp_handler)
{
sock_udp_ep_t remote;
remote.family = AF_INET6;
remote.netif = SOCK_ADDR_ANY_NETIF;
remote.port = port;
memcpy(&remote.addr.ipv6[0], &addr->u8[0], sizeof(addr->u8));
return gcoap_req_send2(buf, len, &remote, resp_handler);
}
size_t gcoap_req_send2(uint8_t *buf, size_t len, sock_udp_ep_t *remote,
gcoap_resp_handler_t resp_handler)
{ {
gcoap_request_memo_t *memo = NULL; gcoap_request_memo_t *memo = NULL;
assert(remote != NULL);
assert(resp_handler != NULL); assert(resp_handler != NULL);
/* Find empty slot in list of open requests. */ /* Find empty slot in list of open requests. */
...@@ -545,16 +469,28 @@ size_t gcoap_req_send(uint8_t *buf, size_t len, ipv6_addr_t *addr, uint16_t port ...@@ -545,16 +469,28 @@ size_t gcoap_req_send(uint8_t *buf, size_t len, ipv6_addr_t *addr, uint16_t port
memcpy(&memo->hdr_buf[0], buf, GCOAP_HEADER_MAXLEN); memcpy(&memo->hdr_buf[0], buf, GCOAP_HEADER_MAXLEN);
memo->resp_handler = resp_handler; memo->resp_handler = resp_handler;
size_t res = _send_buf(buf, len, addr, port); size_t res = sock_udp_send(&_sock, buf, len, remote);
if (res && (GCOAP_NON_TIMEOUT > 0)) { if (res && (GCOAP_NON_TIMEOUT > 0)) {
/* start response wait timer */ /* interrupt sock listening (to set a listen timeout) */
memo->timeout_msg.type = GCOAP_NETAPI_MSG_TYPE_TIMEOUT; msg_t mbox_msg;
memo->timeout_msg.content.ptr = (char *)memo; mbox_msg.type = GCOAP_MSG_TYPE_INTR;
xtimer_set_msg(&memo->response_timer, GCOAP_NON_TIMEOUT, mbox_msg.content.value = 0;
&memo->timeout_msg, _pid); if (mbox_try_put(&_sock.reg.mbox, &mbox_msg)) {
/* start response wait timer */
memo->timeout_msg.type = GCOAP_MSG_TYPE_TIMEOUT;
memo->timeout_msg.content.ptr = (char *)memo;
xtimer_set_msg(&memo->response_timer, GCOAP_NON_TIMEOUT,
&memo->timeout_msg, _pid);
}
else {
memo->state = GCOAP_MEMO_UNUSED;
DEBUG("gcoap: can't wake up mbox; no timeout for msg\n");
}
} }
else if (!res) { else if (!res) {
memo->state = GCOAP_MEMO_UNUSED; memo->state = GCOAP_MEMO_UNUSED;
DEBUG("gcoap: sock send failed: %d\n", res);
} }
return res; return res;
} else { } else {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment