
6 changed files with 1004 additions and 0 deletions
@ -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_ */ |
||||
/** @} */ |
@ -0,0 +1,3 @@
|
||||
MODULE = gcoap
|
||||
|
||||
include $(RIOTBASE)/Makefile.base |
@ -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…
Reference in new issue