Browse Source

gcoap: initial commit

pr/spi.typo
Ken Bannister 6 years ago
parent
commit
f3431fb49e
  1. 4
      Makefile.dep
  2. 8
      sys/auto_init/auto_init.c
  3. 388
      sys/include/net/gnrc/coap.h
  4. 3
      sys/net/gnrc/Makefile
  5. 3
      sys/net/gnrc/application_layer/coap/Makefile
  6. 598
      sys/net/gnrc/application_layer/coap/gcoap.c

4
Makefile.dep

@ -96,6 +96,10 @@ ifneq (,$(filter gnrc_zep,$(USEMODULE)))
USEMODULE += xtimer
endif
ifneq (,$(filter gcoap,$(USEMODULE)))
USEMODULE += gnrc_udp
endif
ifneq (,$(filter gnrc_tftp,$(USEMODULE)))
USEMODULE += gnrc_udp
USEMODULE += xtimer

8
sys/auto_init/auto_init.c

@ -92,6 +92,10 @@
#include "random.h"
#endif
#ifdef MODULE_GCOAP
#include "net/gnrc/coap.h"
#endif
#define ENABLE_DEBUG (0)
#include "debug.h"
@ -165,6 +169,10 @@ void auto_init(void)
DEBUG("Bootstraping lwIP.\n");
lwip_bootstrap();
#endif
#ifdef MODULE_GCOAP
DEBUG("Auto init gcoap module.\n");
gcoap_init();
#endif
/* initialize network devices */
#ifdef MODULE_AUTO_INIT_GNRC_NETIF

388
sys/include/net/gnrc/coap.h

@ -0,0 +1,388 @@
/*
* Copyright (c) 2015-2016 Ken Bannister. All rights reserved.
*
* 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.
*/
/**
* @defgroup net_gnrc_coap CoAP
* @ingroup net_gnrc
* @brief GNRC implementation of CoAP protocol, RFC 7252
*
* ## Architecture ##
* Requests and responses are exchanged via an asynchronous RIOT message
* processing thread. Depends on nanocoap for base level structs and
* functionality.
*
* Uses a single UDP port for communication to support RFC 6282 compression.
*
* ## Server Operation ##
*
* gcoap listens for requests on GCOAP_PORT, 5683 by default. You can redefine
* this by uncommenting the appropriate lines in gcoap's make file.
*
* gcoap allows an application to specify a collection of request resource paths
* it wants to be notified about. Create an array of resources, coap_resource_t
* structs. Use gcoap_register_listener() at application startup to pass in
* these resources, wrapped in a gcoap_listener_t.
*
* gcoap itself defines a resource for `/.well-known/core` discovery, which
* lists all of the registered paths.
*
* ### Creating a response ###
*
* An application resource includes a callback function, a coap_handler_t. After
* reading the request, the callback must use one or two functions provided by
* gcoap to format the response, as described below. The callback *must* read
* the request thoroughly before calling the functions, because the response
* buffer likely reuses the request buffer. See `examples/gcoap/gcoap_cli.c`
* for a simple example of a callback.
*
* Here is the expected sequence for a callback function:
*
* Read request completely and parse request payload, if any. Use the
* coap_pkt_t _payload_ and _payload_len_ attributes.
*
* If there is a payload, follow the three steps below.
*
* -# Call gcoap_resp_init() to initialize the response.
* -# Write the request payload, starting at the updated _payload_ pointer
* in the coap_pkt_t. If some error occurs, return a negative errno
* code from the handler, and gcoap will send a server error (5.00).
* -# Call gcoap_finish() to complete the PDU after writing the payload,
* and return the result. gcoap will send the message.
*
* If no payload, call only gcoap_response() to write the full response.
* Alternatively, you still can use gcoap_resp_init() and gcoap_finish(), as
* described above. In fact, the gcoap_response() function is inline, and uses
* those two functions.
*
* ## Client Operation ##
*
* gcoap uses RIOT's asynchronous messaging facility to send and receive
* messages. So, client operation includes two phases: creating and sending a
* request, and handling the response aynchronously in a client supplied
* callback. See `examples/gcoap/gcoap_cli.c` for a simple example of sending
* a request and reading the response.
*
* ### Creating a request ###
*
* Here is the expected sequence for preparing and sending a request:
*
* Allocate a buffer and a coap_pkt_t for the request.
*
* If there is a payload, follow the three steps below.
*
* -# Call gcoap_req_init() to initialize the request.
* -# Write the request payload, starting at the updated _payload_ pointer
* in the coap_pkt_t.
* -# Call gcoap_finish(), which updates the packet for the payload.
*
* If no payload, call only gcoap_request() to write the full request.
* Alternatively, you still can use gcoap_req_init() and gcoap_finish(),
* as described above. The gcoap_request() function is inline, and uses those
* two functions.
*
* Finally, call gcoap_req_send() with the destination host and port, as well
* as a callback function for the host's response.
*
* ### Handling the response ###
*
* When gcoap receives the response to a request, it executes the callback from
* the request. gcoap also executes the callback when a response is not
* received within GCOAP_RESPONSE_TIMEOUT.
*
* Here is the expected sequence for handling a response in the callback.
*
* -# Test for a server response or timeout in the _req_state_ callback
* parameter. See the GCOAP_MEMO... constants.
* -# Test the response with coap_get_code_class() and coap_get_code_detail().
* -# Test the response payload with the coap_pkt_t _payload_len_ and
* _content_type_ attributes.
* -# Read the payload, if any.
*
* ## Implementation Notes ##
*
* ### Building a packet ###
*
* The sequence and functions described above to build a request or response
* is designed to provide a relatively simple API for the user.
*
* The structure of a CoAP PDU requires that options are placed between the
* header and the payload. So, gcoap provides space in the buffer for them in
* the request/response ...init() function, and then writes them during
* gcoap_finish(). We trade some inefficiency/work in the buffer for
* simplicity for the user.
*
* ### Waiting for a response ###
*
* We take advantage of RIOT's GNRC stack by using an xtimer to wait 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
* times out. We track the response with an entry in the
* `_coap_state.open_reqs` array.
*
* @{
*
* @file
* @brief gcoap definition
*
* @author Ken Bannister <kb2ma@runbox.com>
*/
#ifndef GCOAP_H_
#define GCOAP_H_
#include "net/gnrc.h"
#include "net/gnrc/ipv6.h"
#include "net/gnrc/udp.h"
#include "nanocoap.h"
#include "xtimer.h"
#ifdef __cplusplus
extern "C" {
#endif
/** @brief Size for module message queue */
#define GCOAP_MSG_QUEUE_SIZE (4)
/** @brief Server port; use RFC 7252 default if not defined */
#ifndef GCOAP_PORT
#define GCOAP_PORT (5683)
#endif
/** @brief Size of the buffer used to build a CoAP request or response. */
#define GCOAP_PDU_BUF_SIZE (128)
/**
* @brief Size of the buffer used to write options, other than Uri-Path, in a
* request.
*
* Accommodates Content-Format.
*/
#define GCOAP_REQ_OPTIONS_BUF (8)
/**
* @brief Size of the buffer used to write options in a response.
*
* Accommodates Content-Format.
*/
#define GCOAP_RESP_OPTIONS_BUF (8)
/** @brief Maximum number of requests awaiting a response */
#define GCOAP_REQ_WAITING_MAX (2)
/** @brief Maximum length in bytes for a token */
#define GCOAP_TOKENLEN_MAX (8)
/** @brief Maximum length in bytes for a header, including the token */
#define GCOAP_HEADER_MAXLEN (sizeof(coap_hdr_t) + GCOAP_TOKENLEN_MAX)
/** @brief Length in bytes for a token; use 2 if not defined */
#ifndef GCOAP_TOKENLEN
#define GCOAP_TOKENLEN (2)
#endif
/** @brief Marks the boundary between header and payload */
#define GCOAP_PAYLOAD_MARKER (0xFF)
/**
* @name States for the memo used to track waiting for a response
* @{
*/
#define GCOAP_MEMO_UNUSED (0) /**< This memo is unused */
#define GCOAP_MEMO_WAIT (1) /**< Request sent; awaiting response */
#define GCOAP_MEMO_RESP (2) /**< Got response */
#define GCOAP_MEMO_TIMEOUT (3) /**< Timeout waiting for response */
#define GCOAP_MEMO_ERR (4) /**< Error processing response packet */
/** @} */
/**
* @brief Default time to wait for a non-confirmable response, in usec
*
* Set to 0 to disable timeout.
*/
#define GCOAP_NON_TIMEOUT (5000000U)
/** @brief Identifies a gcoap-specific timeout IPC message */
#define GCOAP_NETAPI_MSG_TYPE_TIMEOUT (0x1501)
/**
* @brief A modular collection of resources for a server
*/
typedef struct gcoap_listener {
coap_resource_t *resources; /**< First element in the array of resources;
must order alphabetically */
size_t resources_len; /**< Length of array */
struct gcoap_listener *next; /**< Next listener in list */
} gcoap_listener_t;
/**
* @brief Handler function for a server response, including the state for the
* originating request.
*
* If request timed out, the packet header is for the request.
*/
typedef void (*gcoap_resp_handler_t)(unsigned req_state, coap_pkt_t* pdu);
/**
* @brief Memo to handle a response for a request
*/
typedef struct {
unsigned state; /**< State of this memo, a GCOAP_MEMO... */
uint8_t hdr_buf[GCOAP_HEADER_MAXLEN];
/**< Stores a copy of the request header */
gcoap_resp_handler_t resp_handler; /**< Callback for the response */
xtimer_t response_timer; /**< Limits wait for response */
msg_t timeout_msg; /**< For response timer */
} gcoap_request_memo_t;
/**
* @brief Container for the state of gcoap itself
*/
typedef struct {
gnrc_netreg_entry_t netreg_port; /**< Registration for IP port */
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 */
uint16_t last_message_id; /**< Last message ID used */
} gcoap_state_t;
/**
* @brief Initializes the gcoap thread and device.
*
* Must call once before first use.
*
* @return PID of the gcoap thread on success.
* @return -EEXIST, if thread already has been created.
* @return -EINVAL, if the IP port already is in use.
*/
kernel_pid_t gcoap_init(void);
/**
* @brief Starts listening for resource paths.
*
* @param listener Listener containing the resources.
*/
void gcoap_register_listener(gcoap_listener_t *listener);
/**
* @brief Initializes a CoAP request PDU on a buffer.
*
* @param[in] pdu Request metadata
* @param[in] buf Buffer containing the PDU
* @param[in] len Length of the buffer
* @param[in] code Request code
* @param[in] path Resource path
*
* @return 0 on success
* @return < 0 on error
*/
int gcoap_req_init(coap_pkt_t *pdu, uint8_t *buf, size_t len, unsigned code,
char *path);
/**
* @brief Finishes formatting a CoAP PDU after the payload has been written.
*
* Assumes the PDU has been initialized with gcoap_req_init() or
* gcoap_resp_init().
*
* @param[in] pdu Request metadata
* @param[in] payload_len Length of the payload, or 0 if none
* @param[in] format Format code for the payload; use COAP_FORMAT_NONE if not
* specified
*
* @return size of the PDU
* @return < 0 on error
*/
ssize_t gcoap_finish(coap_pkt_t *pdu, size_t payload_len, unsigned format);
/**
* @brief Writes a complete CoAP request PDU when there is not a payload.
*
* @param[in] pdu Request metadata
* @param[in] buf Buffer containing the PDU
* @param[in] len Length of the buffer
* @param[in] code Request code
* @param[in] path Resource path
*
* @return size of the PDU within the buffer
* @return < 0 on error
*/
static inline ssize_t gcoap_request(coap_pkt_t *pdu, uint8_t *buf, size_t len,
unsigned code,
char *path)
{
return (gcoap_req_init(pdu, buf, len, code, path) == 0)
? gcoap_finish(pdu, 0, COAP_FORMAT_NONE)
: -1;
}
/**
* @brief Sends a buffer containing a CoAP request to the provided host/port.
*
* @param[in] buf Buffer containing the PDU
* @param[in] len Length of the buffer
* @param[in] addr Destination for the packet
* @param[in] port Port at the destination
* @param[in] resp_handler Callback when response received
*
* @return length of the packet
* @return 0 if cannot send
*/
size_t gcoap_req_send(uint8_t *buf, size_t len, ipv6_addr_t *addr, uint16_t port,
gcoap_resp_handler_t resp_handler);
/**
* @brief Initializes a CoAP response packet on a buffer.
*
* Initializes payload location within the buffer based on packet setup.
*
* @param[in] pdu Response metadata
* @param[in] buf Buffer containing the PDU
* @param[in] len Length of the buffer
* @param[in] code Response code
*
* @return 0 on success
* @return < 0 on error
*/
int gcoap_resp_init(coap_pkt_t *pdu, uint8_t *buf, size_t len, unsigned code);
/**
* @brief Writes a complete CoAP response PDU when there is no payload.
*
* @param[in] pdu Response metadata
* @param[in] buf Buffer containing the PDU
* @param[in] len Length of the buffer
* @param[in] code Response code
*
* @return size of the PDU within the buffer
* @return < 0 on error
*/
static inline ssize_t gcoap_response(coap_pkt_t *pdu, uint8_t *buf, size_t len,
unsigned code)
{
return (gcoap_resp_init(pdu, buf, len, code) == 0)
? gcoap_finish(pdu, 0, COAP_FORMAT_NONE)
: -1;
}
/**
* @brief Provides important operational statistics.
*
* Useful for monitoring.
*
* @param[out] open_reqs Count of unanswered requests
*/
void gcoap_op_state(uint8_t *open_reqs);
#ifdef __cplusplus
}
#endif
#endif /* GCOAP_H_ */
/** @} */

3
sys/net/gnrc/Makefile

@ -130,5 +130,8 @@ endif
ifneq (,$(filter gnrc_tftp,$(USEMODULE)))
DIRS += application_layer/tftp
endif
ifneq (,$(filter gcoap,$(USEMODULE)))
DIRS += application_layer/coap
endif
include $(RIOTBASE)/Makefile.base

3
sys/net/gnrc/application_layer/coap/Makefile

@ -0,0 +1,3 @@
MODULE = gcoap
include $(RIOTBASE)/Makefile.base

598
sys/net/gnrc/application_layer/coap/gcoap.c

@ -0,0 +1,598 @@
/*
* Copyright (c) 2015-2016 Ken Bannister. All rights reserved.
*
* 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.
*/
/**
* @ingroup net_gnrc_coap
* @{
*
* @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>
#include "net/gnrc/coap.h"
#include "random.h"
#include "thread.h"
#define ENABLE_DEBUG (0)
#include "debug.h"
/** @brief Stack size for module thread */
#if ENABLE_DEBUG
#define GCOAP_STACK_SIZE (THREAD_STACKSIZE_DEFAULT + THREAD_EXTRA_STACKSIZE_PRINTF)
#else
#define GCOAP_STACK_SIZE (THREAD_STACKSIZE_DEFAULT)
#endif
/* Internal functions */
static void *_event_loop(void *arg);
static int _register_port(gnrc_netreg_entry_t *netreg_port, uint16_t port);
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 _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 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 _find_req_memo(gcoap_request_memo_t **memo_ptr, coap_pkt_t *pdu,
uint8_t *buf, size_t len);
/* Internal variables */
const coap_resource_t _default_resources[] = {
{ "/.well-known/core", COAP_GET, _well_known_core_handler },
};
static gcoap_listener_t _default_listener = {
(coap_resource_t *)&_default_resources[0],
sizeof(_default_resources) / sizeof(_default_resources[0]),
NULL
};
static gcoap_state_t _coap_state = {
.netreg_port = {NULL, 0, KERNEL_PID_UNDEF},
.listeners = &_default_listener,
};
static kernel_pid_t _pid = KERNEL_PID_UNDEF;
static char _msg_stack[GCOAP_STACK_SIZE];
/* Event/Message loop for gcoap _pid thread. */
static void *_event_loop(void *arg)
{
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;
msg_init_queue(msg_queue, GCOAP_MSG_QUEUE_SIZE);
while (1) {
msg_receive(&msg_rcvd);
switch (msg_rcvd.type) {
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);
assert(ipv6_snip != NULL);
src_addr = &((ipv6_hdr_t *)ipv6_snip->data)->src;
_receive(pkt, src_addr, port);
break;
case GCOAP_NETAPI_MSG_TYPE_TIMEOUT:
_expire_request((gcoap_request_memo_t *)msg_rcvd.content.ptr);
break;
default:
break;
}
}
return 0;
}
/* Handles incoming network IPC message. */
static void _receive(gnrc_pktsnip_t *pkt, ipv6_addr_t *src, uint16_t port)
{
coap_pkt_t pdu;
uint8_t buf[GCOAP_PDU_BUF_SIZE];
size_t pdu_len = 0;
gcoap_request_memo_t *memo = NULL;
/* If too big, handle below based on request vs. response */
size_t pkt_size = (pkt->size > sizeof(buf))
? sizeof(buf) : pkt->size;
/* Copy request into temporary buffer, and parse it as CoAP. */
memcpy(buf, pkt->data, pkt_size);
int result = coap_parse(&pdu, buf, pkt_size);
if (result < 0) {
DEBUG("gcoap: parse failure: %d\n", result);
/* If a response, can't clear memo, but it will timeout later. */
goto exit;
}
/* incoming request */
if (coap_get_code_class(&pdu) == COAP_CLASS_REQ) {
if (pkt->size > 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) {
_send_buf(buf, pdu_len, src, port);
}
}
/* incoming response */
else {
_find_req_memo(&memo, &pdu, buf, sizeof(buf));
if (memo) {
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->state = GCOAP_MEMO_UNUSED;
}
}
exit:
gnrc_pktbuf_release(pkt);
}
/*
* Main request handler: generates response PDU in the provided buffer.
*
* Caller must finish the PDU and send it.
*/
static size_t _handle_req(coap_pkt_t *pdu, uint8_t *buf, size_t len)
{
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;
while (listener) {
coap_resource_t *resource = listener->resources;
for (size_t i = 0; i < listener->resources_len; i++) {
if (i) {
resource++;
}
if (! (resource->methods & method_flag)) {
continue;
}
int res = strcmp((char *)&pdu->url[0], resource->path);
if (res > 0) {
continue;
}
else if (res < 0) {
/* resources expected in alphabetical order */
break;
}
else {
ssize_t pdu_len = resource->handler(pdu, buf, len);
if (pdu_len < 0) {
pdu_len = gcoap_response(pdu, buf, len,
COAP_CODE_INTERNAL_SERVER_ERROR);
}
return pdu_len;
}
}
listener = listener->next;
}
/* resource not found */
return gcoap_response(pdu, buf, len, COAP_CODE_PATH_NOT_FOUND);
}
/*
* Finishes handling a PDU -- write options and reposition payload.
*
* Returns the size of the PDU within the buffer, or < 0 on error.
*/
static ssize_t _finish_pdu(coap_pkt_t *pdu, uint8_t *buf, size_t len)
{
ssize_t hdr_len = _write_options(pdu, buf, len);
DEBUG("gcoap: header length: %u\n", hdr_len);
if (hdr_len > 0) {
/* move payload over unused space after options */
if (pdu->payload_len) {
memmove(buf + hdr_len, pdu->payload, pdu->payload_len);
}
return hdr_len + pdu->payload_len;
}
else {
return -1; /* generic failure code */
}
}
/*
* Finds the memo for an outstanding request within the _coap_state.open_reqs
* array. Matches on token.
*
* src_pdu Source for the match token
*/
static void _find_req_memo(gcoap_request_memo_t **memo_ptr, coap_pkt_t *src_pdu,
uint8_t *buf, size_t len)
{
gcoap_request_memo_t *memo;
coap_pkt_t memo_pdu = { .token = NULL };
(void) buf;
(void) len;
for (int i = 0; i < GCOAP_REQ_WAITING_MAX; i++) {
if (_coap_state.open_reqs[i].state == GCOAP_MEMO_UNUSED)
continue;
/* setup memo PDU from memo header */
memo = &_coap_state.open_reqs[i];
coap_hdr_t *memo_hdr = (coap_hdr_t *) &memo->hdr_buf[0];
memo_pdu.hdr = memo_hdr;
if (coap_get_token_len(&memo_pdu)) {
memo_pdu.token = &memo_hdr->data[0];
}
/* match on token */
if (coap_get_token_len(src_pdu) == coap_get_token_len(&memo_pdu)) {
uint8_t *src_byte = src_pdu->token;
uint8_t *memo_byte = memo_pdu.token;
size_t j;
for (j = 0; j < coap_get_token_len(src_pdu); j++) {
if (*src_byte++ != *memo_byte++) {
break; /* token mismatch */
}
}
if (j == coap_get_token_len(src_pdu)) {
*memo_ptr = memo;
}
}
}
}
/* Calls handler callback on receipt of a timeout message. */
static void _expire_request(gcoap_request_memo_t *memo)
{
coap_pkt_t req;
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) {
req.hdr = (coap_hdr_t *)&memo->hdr_buf[0]; /* for reference */
memo->resp_handler(memo->state, &req);
}
memo->state = GCOAP_MEMO_UNUSED;
}
else {
/* Response already handled; timeout must have fired while response */
/* was in queue. */
}
}
/* 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)) {
netreg_port->demux_ctx = port;
netreg_port->target.pid = _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
* /.well-known/core itself.
*/
static ssize_t _well_known_core_handler(coap_pkt_t* pdu, uint8_t *buf, size_t len)
{
/* write header */
gcoap_resp_init(pdu, buf, len, COAP_CODE_CONTENT);
/* skip the first listener, gcoap itself */
gcoap_listener_t *listener = _coap_state.listeners->next;
/* write payload */
uint8_t *bufpos = pdu->payload;
while (listener) {
coap_resource_t *resource = listener->resources;
for (size_t i = 0; i < listener->resources_len; i++) {
/* Don't overwrite buffer if paths are too long. */
if (bufpos + strlen(resource->path) + 3 > buf + len) {
break;
}
if (i) {
*bufpos++ = ',';
resource++;
}
*bufpos++ = '<';
unsigned url_len = strlen(resource->path);
memcpy(bufpos, resource->path, url_len);
bufpos += url_len;
*bufpos++ = '>';
}
listener = listener->next;
}
/* response content */
return gcoap_finish(pdu, bufpos - pdu->payload, COAP_FORMAT_LINK);
}
/*
* Creates CoAP options and sets payload marker, if any.
*
* Returns length of header + options, or -EINVAL on illegal path.
*/
static ssize_t _write_options(coap_pkt_t *pdu, uint8_t *buf, size_t len)
{
uint8_t last_optnum = 0;
(void)len;
uint8_t *bufpos = buf + coap_get_total_hdr_len(pdu); /* position for write */
/* Uri-Path for request */
if (coap_get_code_class(pdu) == COAP_CLASS_REQ) {
size_t url_len = strlen((char *)pdu->url);
if (url_len) {
if (pdu->url[0] != '/') {
return -EINVAL;
}
bufpos += coap_put_option_url(bufpos, last_optnum, (char *)&pdu->url[0]);
last_optnum = COAP_OPT_URI_PATH;
}
}
/* Content-Format */
if (pdu->content_type != COAP_FORMAT_NONE) {
bufpos += coap_put_option_ct(bufpos, last_optnum, pdu->content_type);
/* uncomment when add an option after Content-Format */
/* last_optnum = COAP_OPT_CONTENT_FORMAT; */
}
/* write payload marker */
if (pdu->payload_len) {
*bufpos++ = GCOAP_PAYLOAD_MARKER;
}
return bufpos - buf;
}
/*
* 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");
/* 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. */
memset(&_coap_state.open_reqs[0], 0, sizeof(_coap_state.open_reqs));
/* randomize initial value */
_coap_state.last_message_id = random_uint32() & 0xFFFF;
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;
while (_last->next)
_last = _last->next;
listener->next = NULL;
_last->next = listener;
}
int gcoap_req_init(coap_pkt_t *pdu, uint8_t *buf, size_t len, unsigned code,
char *path) {
uint8_t token[GCOAP_TOKENLEN];
ssize_t hdrlen;
(void)len;
pdu->hdr = (coap_hdr_t *)buf;
memset(pdu->url, 0, NANOCOAP_URL_MAX);
/* generate token */
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);
}
hdrlen = coap_build_hdr(pdu->hdr, COAP_TYPE_NON, &token[0], GCOAP_TOKENLEN,
code,
++_coap_state.last_message_id);
if (hdrlen > 0) {
/* Reserve some space between the header and payload to write options later */
pdu->payload = buf + coap_get_total_hdr_len(pdu) + strlen(path)
+ GCOAP_REQ_OPTIONS_BUF;
/* Payload length really zero at this point, but we set this to the available
* length in the buffer. Allows us to reconstruct buffer length later. */
pdu->payload_len = len - (pdu->payload - buf);
pdu->content_type = COAP_FORMAT_NONE;
memcpy(&pdu->url[0], path, strlen(path));
return 0;
}
else {
/* reason for negative hdrlen is not defined, so we also are vague */
return -1;
}
}
ssize_t gcoap_finish(coap_pkt_t *pdu, size_t payload_len, unsigned format)
{
/* reconstruct full PDU buffer length */
size_t len = pdu->payload_len + (pdu->payload - (uint8_t *)pdu->hdr);
pdu->content_type = format;
pdu->payload_len = payload_len;
return _finish_pdu(pdu, (uint8_t *)pdu->hdr, len);
}
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_request_memo_t *memo = NULL;
assert(resp_handler != NULL);
/* 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;
break;
}
}
if (memo) {
memcpy(&memo->hdr_buf[0], buf, GCOAP_HEADER_MAXLEN);
memo->resp_handler = resp_handler;
size_t res = _send_buf(buf, len, addr, port);
if (res && GCOAP_NON_TIMEOUT) {
/* start response wait timer */
memo->timeout_msg.type = GCOAP_NETAPI_MSG_TYPE_TIMEOUT;
memo->timeout_msg.content.ptr = (char *)memo;
xtimer_set_msg(&memo->response_timer, GCOAP_NON_TIMEOUT,
&memo->timeout_msg, _pid);
}
else if (!res) {
memo->state = GCOAP_MEMO_UNUSED;
}
return res;
} else {
DEBUG("gcoap: dropping request; no space for response tracking\n");
return 0;
}
}
int gcoap_resp_init(coap_pkt_t *pdu, uint8_t *buf, size_t len, unsigned code)
{
/* Assume NON type request, so response type is the same. */
coap_hdr_set_code(pdu->hdr, code);
/* Create message ID since NON? */
/* Reserve some space between the header and payload to write options later */
pdu->payload = buf + coap_get_total_hdr_len(pdu) + GCOAP_RESP_OPTIONS_BUF;
/* Payload length really zero at this point, but we set this to the available
* length in the buffer. Allows us to reconstruct buffer length later. */
pdu->payload_len = len - (pdu->payload - buf);
pdu->content_type = COAP_FORMAT_NONE;
return 0;
}
void gcoap_op_state(uint8_t *open_reqs)
{
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++;
}
}
*open_reqs = count;
}
/** @} */
Loading…
Cancel
Save