From f3431fb49ea04ad7655b8212c9a1e1256f8de9ff Mon Sep 17 00:00:00 2001 From: Ken Bannister <kb2ma@runbox.com> Date: Sat, 29 Oct 2016 15:20:22 -0400 Subject: [PATCH] gcoap: initial commit --- Makefile.dep | 4 + sys/auto_init/auto_init.c | 8 + sys/include/net/gnrc/coap.h | 388 ++++++++++++ sys/net/gnrc/Makefile | 3 + sys/net/gnrc/application_layer/coap/Makefile | 3 + sys/net/gnrc/application_layer/coap/gcoap.c | 598 +++++++++++++++++++ 6 files changed, 1004 insertions(+) create mode 100644 sys/include/net/gnrc/coap.h create mode 100644 sys/net/gnrc/application_layer/coap/Makefile create mode 100644 sys/net/gnrc/application_layer/coap/gcoap.c diff --git a/Makefile.dep b/Makefile.dep index baacd4f358..4af3cd29dd 100644 --- a/Makefile.dep +++ b/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 diff --git a/sys/auto_init/auto_init.c b/sys/auto_init/auto_init.c index 9b37ffd48c..961aced7b7 100644 --- a/sys/auto_init/auto_init.c +++ b/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 diff --git a/sys/include/net/gnrc/coap.h b/sys/include/net/gnrc/coap.h new file mode 100644 index 0000000000..b7785424f3 --- /dev/null +++ b/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_ */ +/** @} */ diff --git a/sys/net/gnrc/Makefile b/sys/net/gnrc/Makefile index 0eb7f61ef7..80885f34e9 100644 --- a/sys/net/gnrc/Makefile +++ b/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 diff --git a/sys/net/gnrc/application_layer/coap/Makefile b/sys/net/gnrc/application_layer/coap/Makefile new file mode 100644 index 0000000000..df742e7364 --- /dev/null +++ b/sys/net/gnrc/application_layer/coap/Makefile @@ -0,0 +1,3 @@ +MODULE = gcoap + +include $(RIOTBASE)/Makefile.base diff --git a/sys/net/gnrc/application_layer/coap/gcoap.c b/sys/net/gnrc/application_layer/coap/gcoap.c new file mode 100644 index 0000000000..a678b682a5 --- /dev/null +++ b/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; +} + +/** @} */ -- GitLab