From f3431fb49ea04ad7655b8212c9a1e1256f8de9ff Mon Sep 17 00:00:00 2001 From: Ken Bannister Date: Sat, 29 Oct 2016 15:20:22 -0400 Subject: [PATCH 1/3] 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 + */ + +#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 + */ + +#include +#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; +} + +/** @} */ From 1783c9c6727f927ff03b09392f5adc784d1809e9 Mon Sep 17 00:00:00 2001 From: Ken Bannister Date: Sat, 29 Oct 2016 15:24:06 -0400 Subject: [PATCH 2/3] gcoap: CLI example --- examples/gcoap/Makefile | 61 ++++++++++++ examples/gcoap/Makefile.slip | 84 ++++++++++++++++ examples/gcoap/README-slip.md | 67 +++++++++++++ examples/gcoap/README.md | 69 +++++++++++++ examples/gcoap/gcoap_cli.c | 181 ++++++++++++++++++++++++++++++++++ examples/gcoap/main.c | 53 ++++++++++ examples/gcoap/slip_params.h | 39 ++++++++ 7 files changed, 554 insertions(+) create mode 100644 examples/gcoap/Makefile create mode 100644 examples/gcoap/Makefile.slip create mode 100644 examples/gcoap/README-slip.md create mode 100644 examples/gcoap/README.md create mode 100644 examples/gcoap/gcoap_cli.c create mode 100644 examples/gcoap/main.c create mode 100644 examples/gcoap/slip_params.h diff --git a/examples/gcoap/Makefile b/examples/gcoap/Makefile new file mode 100644 index 0000000000..5d8608a478 --- /dev/null +++ b/examples/gcoap/Makefile @@ -0,0 +1,61 @@ +# Default Makefile, for host native networking + +# name of your application +APPLICATION = gcoap + +# If no BOARD is found in the environment, use this default: +BOARD ?= native + +# This has to be the absolute path to the RIOT base directory: +RIOTBASE ?= $(CURDIR)/../.. + +BOARD_INSUFFICIENT_MEMORY := chronos msb-430 msb-430h nucleo-f030 nucleo-f334 \ + stm32f0discovery telosb weio wsn430-v1_3b wsn430-v1_4 \ + z1 + +# Must read nordic_softdevice_ble package before nanocoap package. However, +# can't read it explicitly here because it is read later, as a dependency for +# the nrf52dk board. +BOARD_BLACKLIST := nrf52dk + +## Uncomment to redefine port, for example use 61616 for RFC 6282 UDP compression. +#GCOAP_PORT = 5683 +#CFLAGS += -DGCOAP_PORT=$(GCOAP_PORT) + +## Uncomment to redefine request token length, max 8. +#GCOAP_TOKENLEN = 2 +#CFLAGS += -DGCOAP_TOKENLEN=$(GCOAP_TOKENLEN) + +USEPKG += nanocoap +# Required by nanocoap, but only due to issue #5959. +USEMODULE += posix +# Required by nanocoap to compile nanocoap_sock. +USEMODULE += gnrc_sock_udp + +# Include packages that pull up and auto-init the link layer. +# NOTE: 6LoWPAN will be included if IEEE802.15.4 devices are present +USEMODULE += gnrc_netdev_default +USEMODULE += auto_init_gnrc_netif +# Specify the mandatory networking modules +USEMODULE += gnrc_ipv6_default +USEMODULE += gcoap +# Additional networking modules that can be dropped if not needed +USEMODULE += gnrc_icmpv6_echo + +# Required by gcoap example +USEMODULE += od +USEMODULE += fmt +# Add also the shell, some shell commands +USEMODULE += shell +USEMODULE += shell_commands +USEMODULE += ps + +# Comment this out to disable code in RIOT that does safety checking +# which is not needed in a production environment but helps in the +# development process: +CFLAGS += -DDEVELHELP + +# Change this to 0 show compiler invocation lines by default: +QUIET ?= 1 + +include $(RIOTBASE)/Makefile.include diff --git a/examples/gcoap/Makefile.slip b/examples/gcoap/Makefile.slip new file mode 100644 index 0000000000..abd5771863 --- /dev/null +++ b/examples/gcoap/Makefile.slip @@ -0,0 +1,84 @@ +# Border router Makefile for SLIP based networking +# Assumes use of SAMR21 board + +# name of your application +APPLICATION = gcoap + +# If no BOARD is found in the environment, use this default: +BOARD ?= samr21-xpro + +# This has to be the absolute path to the RIOT base directory: +RIOTBASE ?= $(CURDIR)/../.. + +BOARD_INSUFFICIENT_MEMORY := chronos msb-430 msb-430h nucleo-f030 nucleo-f334 \ + stm32f0discovery telosb weio wsn430-v1_3b wsn430-v1_4 \ + z1 + +# Must read nordic_softdevice_ble package before nanocoap package. However, +# can't read it explicitly here because it is read later, as a dependency for +# the nrf52dk board. +BOARD_BLACKLIST := nrf52dk + +# Redefine port, for example use 61616 for RFC 6282 UDP compression. +#GCOAP_PORT = 5683 +#CFLAGS += -DGCOAP_PORT=$(GCOAP_PORT) + +# Redefine request token length, max 8. +#GCOAP_TOKENLEN = 2 +#CFLAGS += -DGCOAP_TOKENLEN=$(GCOAP_TOKENLEN) + +# Border router requirements +ifeq (,$(SLIP_UART)) + # set default (last available UART) + SLIP_UART="(UART_NUMOF-1)" +endif +ifeq (,$(SLIP_BAUDRATE)) + # set default + SLIP_BAUDRATE=115200 +endif + +GNRC_NETIF_NUMOF := 2 +INCLUDES += -I$(CURDIR) +CFLAGS += -DSLIP_UART=$(SLIP_UART) +CFLAGS += -DSLIP_BAUDRATE=$(SLIP_BAUDRATE) + +USEPKG += nanocoap +# Required by nanocoap, but only due to issue #5959. +USEMODULE += posix +# Required by nanocoap to compile nanocoap_sock. +USEMODULE += gnrc_sock_udp + +# Include packages that pull up and auto-init the link layer. +# NOTE: 6LoWPAN will be included if IEEE802.15.4 devices are present +USEMODULE += gnrc_netdev_default +USEMODULE += auto_init_gnrc_netif +# Specify the mandatory networking modules +USEMODULE += gcoap +# Add a routing protocol +USEMODULE += gnrc_rpl + +# Border router requirements +# Include SLIP package for IP over Serial communication +USEMODULE += gnrc_slip +# Specify the mandatory networking modules for 6LoWPAN border router +USEMODULE += gnrc_sixlowpan_border_router_default +# Additional networking modules that can be dropped if not needed +USEMODULE += gnrc_icmpv6_echo + +# Required by gcoap example +USEMODULE += od +USEMODULE += fmt +# Add also the shell, some shell commands +USEMODULE += shell +USEMODULE += shell_commands +USEMODULE += ps + +# Comment this out to disable code in RIOT that does safety checking +# which is not needed in a production environment but helps in the +# development process: +CFLAGS += -DDEVELHELP + +# Change this to 0 show compiler invocation lines by default: +QUIET ?= 1 + +include $(RIOTBASE)/Makefile.include diff --git a/examples/gcoap/README-slip.md b/examples/gcoap/README-slip.md new file mode 100644 index 0000000000..4abe0df5d8 --- /dev/null +++ b/examples/gcoap/README-slip.md @@ -0,0 +1,67 @@ +`Makefile.slip` assumes use of a SAMR21 Xplained Pro. This file describes how we use the board as a border router. See the [SLIP instructions][1] for the gnrc_border_router example for more background. + +## Background -- Building +Build commands, for reference: + + make clean BOARD="samr21-xpro" + + # Use -f only for border router! + make -f Makefile.slip BOARD="samr21-xpro" + + ../../dist/tools/usb-serial/list-ttys.sh + # Use -f only for border router! + make -f Makefile.slip flash BOARD="samr21-xpro" SERIAL="???" + + make term BOARD="samr21-xpro" PORT="/dev/ttyACM???" + +## USB serial port +The USB serial port requires a USB-TTL converter cable with 3.3 V output. + +Pin connections for SAMR21 board: + +* PA23 (RX) -- TXD wire +* PA22 (TX) -- RXD wire +* GND -- GND wire + +## Network configuration +We will define two networks: + +1. `bbbb` Between Border Router (BR) and Ubuntu host over TUN +2. `aaaa` Between RIOT router node and BR over 6LoWPAN + +We include two approaches to configuration below, automated via RPL, and manual via fib. + +First, define a TUN interface on an Ubuntu host with tunslip, in the `dist/tools/tunslip` directory. In the example below, the tun interface is host 1. + + cd ../../dist/tools/tunslip/ + sudo ./tunslip6 -s ttyUSB0 -t tun0 bbbb::1/64 + sudo ip -6 route add aaaa::/64 dev tun0 + +### RPL-based configuration + +Configure the interface to the `bbbb` network on the BR from the RIOT terminal. The example commands below assume SLIP is on interface 8. + + # Set address for SLIP interface + ifconfig 8 add unicast bbbb::2/64 + # Add the Ubuntu host to the neighbor cache + ncache add 8 bbbb::1 + +Next configure the interface for the `aaaa` network on the BR. Strictly speaking, it is not necessary to define this address. However, it is convenient for typing, because RIOT automatically creates a more verbose address based on the MAC address for the interface. + + ifconfig 7 add unicast aaaa::1/64 + rpl init 7 + +Next, configure the interface on the `aaaa` network on the RIOT router mote. + + ifconfig 7 add unicast aaaa::2/64 + rpl init 7 + +Finally, set the BR as the RPL DAG root. + + rpl root 1 aaaa::1 + +Ping the TUN interface from the router mote, via the BR: + + ping6 bbbb::1 + +[1]: https://github.com/RIOT-OS/RIOT/tree/master/examples/gnrc_border_router "SLIP instructions" \ No newline at end of file diff --git a/examples/gcoap/README.md b/examples/gcoap/README.md new file mode 100644 index 0000000000..46ccdcabb4 --- /dev/null +++ b/examples/gcoap/README.md @@ -0,0 +1,69 @@ +# gcoap Example + +This application provides command line access to gcoap, a GNRC implementation of CoAP. See the [CoAP spec][1] for background, and the Modules>Networking>GNRC>CoAP topic in the source documentation for the structure of the implementation. + +We support two setup options for this example: + +### Native networking +Build with the standard `Makefile`. Follow the setup [instructions][2] for the gnrc_networking example. + +### SLIP-based border router + +Build with `Makefile.slip`. Follow the setup instructions in README-slip.md, which are based on the [SLIP instructions][3] for the gnrc_border_router example. We also plan to provide or reference the ethos/UHCP instructions, but we don't have it working yet. + +## Current Status +gcoap includes server and client capability. Available features include: + +* Server and Client provide helper functions for writing the response/request. See the CoAP topic in the source documentation for details. See the gcoap example for sample implementations. +* Server allows an application to register a 'listener', which includes an array of endpoint paths and function callbacks used to write a response. +* Server listens on a port at startup; defaults to 5683. +* Client operates asynchronously; sends request and then handles response in a user provided callback. Also executes callback on timeout. +* Client generates token; length defined at compile time. +* Message Type: Supports non-confirmable (NON) messaging. +* Options: Supports Content-Format for response payload. + + +## Example Use +This example uses gcoap as a server on RIOT native. Then we send a request from a libcoap example client on the Linux host. + +### Verify setup from RIOT terminal + + > coap info + +Expected response: + + CoAP server is listening on port 5683 + CLI requests sent: 0 + CoAP open requests: 0 + +### Query from libcoap example client +gcoap does not provide any output to the CoAP terminal when it handles a request. We recommend use of Wireshark to see the request and response. You also can add some debug output in the endpoint function callback. + + ./coap-client -N -m get -p 5683 coap://[fe80::1843:8eff:fe40:4eaa%tap0]/.well-known/core + +Example response: + + v:1 t:NON c:GET i:0daa {} [ ] + + +The response shows the endpoint registered by the gcoap CLI example. + +### Send query to libcoap example server +Start the libcoap example server with the command below. + + ./coap-server + +Enter the query below in the RIOT CLI. + + > coap get fe80::d8b8:65ff:feee:121b 5683 /.well-known/core + +CLI output: + + gcoap_cli: sending msg ID 743, 75 bytes + > gcoap: response Success, code 2.05, 105 bytes + ;title="General Info";ct=0,;if="clock";rt="Ticks";title="Internal Clock";ct=0;obs,;ct=0 + + +[1]: https://tools.ietf.org/html/rfc7252 "CoAP spec" +[2]: https://github.com/RIOT-OS/RIOT/tree/master/examples/gnrc_networking "instructions" +[3]: https://github.com/RIOT-OS/RIOT/tree/master/examples/gnrc_border_router "SLIP instructions" diff --git a/examples/gcoap/gcoap_cli.c b/examples/gcoap/gcoap_cli.c new file mode 100644 index 0000000000..75de7dceee --- /dev/null +++ b/examples/gcoap/gcoap_cli.c @@ -0,0 +1,181 @@ +/* + * 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 examples + * @{ + * + * @file + * @brief gcoap CLI support + * + * @author Ken Bannister + * + * @} + */ + +#include +#include +#include +#include +#include "net/gnrc/coap.h" +#include "od.h" +#include "fmt.h" + +static void _resp_handler(unsigned req_state, coap_pkt_t* pdu); +static ssize_t _stats_handler(coap_pkt_t* pdu, uint8_t *buf, size_t len); + +/* CoAP resources */ +static const coap_resource_t _resources[] = { + { "/cli/stats", COAP_GET, _stats_handler }, +}; +static gcoap_listener_t _listener = { + (coap_resource_t *)&_resources[0], + sizeof(_resources) / sizeof(_resources[0]), + NULL +}; + +/* Counts requests sent by CLI. */ +static uint16_t req_count = 0; + +/* + * Response callback. + */ +static void _resp_handler(unsigned req_state, coap_pkt_t* pdu) +{ + if (req_state == GCOAP_MEMO_TIMEOUT) { + printf("gcoap: timeout for msg ID %02u\n", coap_get_id(pdu)); + return; + } + else if (req_state == GCOAP_MEMO_ERR) { + printf("gcoap: error in response\n"); + return; + } + + char *class_str = (coap_get_code_class(pdu) == COAP_CLASS_SUCCESS) + ? "Success" : "Error"; + printf("gcoap: response %s, code %1u.%02u", class_str, + coap_get_code_class(pdu), + coap_get_code_detail(pdu)); + if (pdu->payload_len) { + if (pdu->content_type == COAP_FORMAT_TEXT + || pdu->content_type == COAP_FORMAT_LINK + || coap_get_code_class(pdu) == COAP_CLASS_CLIENT_FAILURE + || coap_get_code_class(pdu) == COAP_CLASS_SERVER_FAILURE) { + /* Expecting diagnostic payload in failure cases */ + printf(", %u bytes\n%.*s\n", pdu->payload_len, pdu->payload_len, + (char *)pdu->payload); + } + else { + printf(", %u bytes\n", pdu->payload_len); + od_hex_dump(pdu->payload, pdu->payload_len, OD_WIDTH_DEFAULT); + } + } + else { + printf(", empty payload\n"); + } +} + +/* + * Server callback for /cli/stats. Returns the count of packets sent by the + * CLI. + */ +static ssize_t _stats_handler(coap_pkt_t* pdu, uint8_t *buf, size_t len) +{ + gcoap_resp_init(pdu, buf, len, COAP_CODE_CONTENT); + + size_t payload_len = fmt_u16_dec((char *)pdu->payload, req_count); + + return gcoap_finish(pdu, payload_len, COAP_FORMAT_TEXT); +} + +static size_t _send(uint8_t *buf, size_t len, char *addr_str, char *port_str) +{ + ipv6_addr_t addr; + uint16_t port; + size_t bytes_sent; + + /* parse destination address */ + if (ipv6_addr_from_str(&addr, addr_str) == NULL) { + puts("gcoap_cli: unable to parse destination address"); + return 0; + } + /* parse port */ + port = (uint16_t)atoi(port_str); + if (port == 0) { + puts("gcoap_cli: unable to parse destination port"); + return 0; + } + + bytes_sent = gcoap_req_send(buf, len, &addr, port, _resp_handler); + if (bytes_sent > 0) { + req_count++; + } + return bytes_sent; +} + +int gcoap_cli_cmd(int argc, char **argv) +{ + /* Ordered like the RFC method code numbers, but off by 1. GET is code 0. */ + char *method_codes[] = {"get", "post", "put"}; + uint8_t buf[GCOAP_PDU_BUF_SIZE]; + coap_pkt_t pdu; + size_t len; + + if (argc == 1) { + /* show help for main commands */ + goto end; + } + + for (size_t i = 0; i < sizeof(method_codes) / sizeof(char*); i++) { + if (strcmp(argv[1], method_codes[i]) == 0) { + if (argc == 5 || argc == 6) { + if (argc == 6) { + gcoap_req_init(&pdu, &buf[0], GCOAP_PDU_BUF_SIZE, i+1, argv[4]); + memcpy(pdu.payload, argv[5], strlen(argv[5])); + len = gcoap_finish(&pdu, strlen(argv[5]), COAP_FORMAT_TEXT); + } + else { + len = gcoap_request(&pdu, &buf[0], GCOAP_PDU_BUF_SIZE, i+1, + argv[4]); + } + printf("gcoap_cli: sending msg ID %u, %u bytes\n", coap_get_id(&pdu), + (unsigned) len); + if (!_send(&buf[0], len, argv[2], argv[3])) { + puts("gcoap_cli: msg send failed"); + } + return 0; + } + else { + printf("usage: %s [data]\n", + argv[0]); + return 1; + } + } + } + + if (strcmp(argv[1], "info") == 0) { + if (argc == 2) { + uint8_t open_reqs; + gcoap_op_state(&open_reqs); + + printf("CoAP server is listening on port %u\n", GCOAP_PORT); + printf(" CLI requests sent: %u\n", req_count); + printf("CoAP open requests: %u\n", open_reqs); + return 0; + } + } + + end: + printf("usage: %s \n", argv[0]); + return 1; +} + +void gcoap_cli_init(void) +{ + gcoap_register_listener(&_listener); +} diff --git a/examples/gcoap/main.c b/examples/gcoap/main.c new file mode 100644 index 0000000000..845d800a5a --- /dev/null +++ b/examples/gcoap/main.c @@ -0,0 +1,53 @@ +/* + * 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 examples + * @{ + * + * @file + * @brief gcoap example + * + * @author Ken Bannister + * + * @} + */ + +#include +#include "msg.h" + +#include "net/gnrc/coap.h" +#include "kernel_types.h" +#include "shell.h" + +#define MAIN_QUEUE_SIZE (4) +static msg_t _main_msg_queue[MAIN_QUEUE_SIZE]; + +extern int gcoap_cli_cmd(int argc, char **argv); +extern void gcoap_cli_init(void); + +static const shell_command_t shell_commands[] = { + { "coap", "CoAP example", gcoap_cli_cmd }, + { NULL, NULL, NULL } +}; + +int main(void) +{ + /* for the thread running the shell */ + msg_init_queue(_main_msg_queue, MAIN_QUEUE_SIZE); + gcoap_cli_init(); + puts("gcoap example app"); + + /* start shell */ + puts("All up, running the shell now"); + char line_buf[SHELL_DEFAULT_BUFSIZE]; + shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE); + + /* should never be reached */ + return 0; +} diff --git a/examples/gcoap/slip_params.h b/examples/gcoap/slip_params.h new file mode 100644 index 0000000000..3978ef7b82 --- /dev/null +++ b/examples/gcoap/slip_params.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2015 Martine Lenders + * + * 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 tests + * @{ + * + * @file + * @brief slip parameters example, used by auto_init_gnrc_netif + * + * @author Martine Lenders + */ + +#ifndef SLIP_PARAMS_H +#define SLIP_PARAMS_H + +#include "net/gnrc/slip.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static gnrc_slip_params_t gnrc_slip_params[] = { + { + .uart = SLIP_UART, + .baudrate = SLIP_BAUDRATE, + }, +}; + +#ifdef __cplusplus +} +#endif +#endif /* SLIP_PARAMS_H */ +/** @} */ From 88a3eeb574ee59569303af9f8b508cf934a9a1c2 Mon Sep 17 00:00:00 2001 From: Ken Bannister Date: Sat, 29 Oct 2016 15:26:21 -0400 Subject: [PATCH 3/3] gcoap: unit tests --- tests/unittests/tests-gcoap/Makefile | 1 + tests/unittests/tests-gcoap/Makefile.include | 9 + tests/unittests/tests-gcoap/tests-gcoap.c | 177 +++++++++++++++++++ tests/unittests/tests-gcoap/tests-gcoap.h | 40 +++++ 4 files changed, 227 insertions(+) create mode 100644 tests/unittests/tests-gcoap/Makefile create mode 100644 tests/unittests/tests-gcoap/Makefile.include create mode 100644 tests/unittests/tests-gcoap/tests-gcoap.c create mode 100644 tests/unittests/tests-gcoap/tests-gcoap.h diff --git a/tests/unittests/tests-gcoap/Makefile b/tests/unittests/tests-gcoap/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/tests/unittests/tests-gcoap/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/tests/unittests/tests-gcoap/Makefile.include b/tests/unittests/tests-gcoap/Makefile.include new file mode 100644 index 0000000000..db3b1c3527 --- /dev/null +++ b/tests/unittests/tests-gcoap/Makefile.include @@ -0,0 +1,9 @@ +USEPKG += nanocoap +# Required by nanocoap to compile nanocoap_sock. +USEMODULE += gnrc_sock_udp + +# Specify the mandatory networking modules +USEMODULE += gcoap +USEMODULE += gnrc_ipv6 + +USEMODULE += random diff --git a/tests/unittests/tests-gcoap/tests-gcoap.c b/tests/unittests/tests-gcoap/tests-gcoap.c new file mode 100644 index 0000000000..c46a368d13 --- /dev/null +++ b/tests/unittests/tests-gcoap/tests-gcoap.c @@ -0,0 +1,177 @@ +/* + * Copyright (c) 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. + */ + +/** + * @{ + * + * @file + */ +#include +#include +#include + +#include "embUnit.h" + +#include "net/gnrc/coap.h" + +#include "unittests-constants.h" +#include "tests-gcoap.h" + +/* + * Client GET request success case. Test request generation. + * Request /time resource from libcoap example + * Includes 2-byte token + */ +static void test_gcoap__client_get_req(void) +{ + uint8_t buf[GCOAP_PDU_BUF_SIZE]; + coap_pkt_t pdu; + size_t len; + char path[] = "/time"; + + uint8_t pdu_data[] = { + 0x52, 0x01, 0xe6, 0x02, 0x9b, 0xce, 0xb4, 0x74, + 0x69, 0x6d, 0x65 + }; + + len = gcoap_request(&pdu, &buf[0], GCOAP_PDU_BUF_SIZE, COAP_METHOD_GET, + &path[0]); + + TEST_ASSERT_EQUAL_INT(COAP_METHOD_GET, coap_get_code(&pdu)); + TEST_ASSERT_EQUAL_INT(GCOAP_TOKENLEN, coap_get_token_len(&pdu)); + TEST_ASSERT_EQUAL_INT(4 + GCOAP_TOKENLEN, coap_get_total_hdr_len(&pdu)); + TEST_ASSERT_EQUAL_INT(COAP_TYPE_NON, coap_get_type(&pdu)); + TEST_ASSERT_EQUAL_STRING(&path[0], (char *)&pdu.url[0]); + TEST_ASSERT_EQUAL_INT(0, pdu.payload_len); + TEST_ASSERT_EQUAL_INT(sizeof(pdu_data), len); +} + +/* + * Client GET response success case. Test parsing response. + * Response for /time resource from libcoap example + */ +static void test_gcoap__client_get_resp(void) +{ + uint8_t buf[GCOAP_PDU_BUF_SIZE]; + coap_pkt_t pdu; + int res; + char exp_payload[] = "Oct 22 10:46:48"; + + uint8_t pdu_data[] = { + 0x52, 0x45, 0xe6, 0x02, 0x9b, 0xce, 0xc0, 0x21, + 0x01, 0xff, 0x4f, 0x63, 0x74, 0x20, 0x32, 0x32, + 0x20, 0x31, 0x30, 0x3a, 0x34, 0x36, 0x3a, 0x34, + 0x38 + }; + memcpy(buf, pdu_data, sizeof(pdu_data)); + + res = coap_parse(&pdu, &buf[0], sizeof(pdu_data)); + + TEST_ASSERT_EQUAL_INT(0, res); + TEST_ASSERT_EQUAL_INT(COAP_CLASS_SUCCESS, coap_get_code_class(&pdu)); + TEST_ASSERT_EQUAL_INT(GCOAP_TOKENLEN, coap_get_token_len(&pdu)); + TEST_ASSERT_EQUAL_INT(4 + GCOAP_TOKENLEN, coap_get_total_hdr_len(&pdu)); + TEST_ASSERT_EQUAL_INT(COAP_TYPE_NON, coap_get_type(&pdu)); + TEST_ASSERT_EQUAL_INT(strlen(exp_payload), pdu.payload_len); + + for (size_t i = 0; i < strlen(exp_payload); i++) { + TEST_ASSERT_EQUAL_INT(exp_payload[i], pdu.payload[i]); + } +} + +/* + * Helper for server_get tests below. + * Request from libcoap example for gcoap_cli /cli/stats resource + * Include 2-byte token and Uri-Host option. + */ +static int _read_cli_stats_req(coap_pkt_t *pdu, uint8_t *buf) +{ + uint8_t pdu_data[] = { + 0x52, 0x01, 0x20, 0xb6, 0x35, 0x61, 0x3d, 0x10, + 0x66, 0x65, 0x38, 0x30, 0x3a, 0x3a, 0x38, 0x63, + 0x32, 0x3a, 0x31, 0x33, 0x66, 0x66, 0x3a, 0x66, + 0x65, 0x63, 0x30, 0x3a, 0x35, 0x65, 0x31, 0x32, + 0x25, 0x74, 0x61, 0x70, 0x30, 0x83, 0x63, 0x6c, + 0x69, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73 + }; + memcpy(buf, pdu_data, sizeof(pdu_data)); + + return coap_parse(pdu, buf, sizeof(pdu_data)); +} + +/* Server GET request success case. Validate request example. */ +static void test_gcoap__server_get_req(void) +{ + uint8_t buf[GCOAP_PDU_BUF_SIZE]; + coap_pkt_t pdu; + + int res = _read_cli_stats_req(&pdu, &buf[0]); + + TEST_ASSERT_EQUAL_INT(0, res); + TEST_ASSERT_EQUAL_INT(COAP_METHOD_GET, coap_get_code(&pdu)); + TEST_ASSERT_EQUAL_INT(2, coap_get_token_len(&pdu)); + TEST_ASSERT_EQUAL_INT(4 + 2, coap_get_total_hdr_len(&pdu)); + TEST_ASSERT_EQUAL_INT(COAP_TYPE_NON, coap_get_type(&pdu)); + TEST_ASSERT_EQUAL_INT(0, pdu.payload_len); + TEST_ASSERT_EQUAL_STRING("/cli/stats", (char *) &pdu.url[0]); +} + +/* + * Server GET response success case. Test writing response. + * Response for libcoap example for gcoap_cli /cli/stats resource + */ +static void test_gcoap__server_get_resp(void) +{ + uint8_t buf[GCOAP_PDU_BUF_SIZE]; + coap_pkt_t pdu; + + /* read request */ + _read_cli_stats_req(&pdu, &buf[0]); + + /* generate response */ + gcoap_resp_init(&pdu, &buf[0], sizeof(buf), COAP_CODE_CONTENT); + char resp_payload[] = "2"; + memcpy(&pdu.payload[0], &resp_payload[0], strlen(resp_payload)); + ssize_t res = gcoap_finish(&pdu, strlen(resp_payload), COAP_FORMAT_TEXT); + + uint8_t resp_data[] = { + 0x52, 0x45, 0x20, 0xb6, 0x35, 0x61, 0xc0, 0xff, + 0x32 + }; + + TEST_ASSERT_EQUAL_INT(COAP_CLASS_SUCCESS, coap_get_code_class(&pdu)); + TEST_ASSERT_EQUAL_INT(2, coap_get_token_len(&pdu)); + TEST_ASSERT_EQUAL_INT(4 + 2, coap_get_total_hdr_len(&pdu)); + TEST_ASSERT_EQUAL_INT(COAP_TYPE_NON, coap_get_type(&pdu)); + TEST_ASSERT_EQUAL_INT(strlen(resp_payload), pdu.payload_len); + TEST_ASSERT_EQUAL_INT(sizeof(resp_data), res); + + for (size_t i = 0; i < strlen(resp_payload); i++) { + TEST_ASSERT_EQUAL_INT(resp_payload[i], pdu.payload[i]); + } +} + +Test *tests_gcoap_tests(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_gcoap__client_get_req), + new_TestFixture(test_gcoap__client_get_resp), + new_TestFixture(test_gcoap__server_get_req), + new_TestFixture(test_gcoap__server_get_resp), + }; + + EMB_UNIT_TESTCALLER(gcoap_tests, NULL, NULL, fixtures); + + return (Test *)&gcoap_tests; +} + +void tests_gcoap(void) +{ + TESTS_RUN(tests_gcoap_tests()); +} +/** @} */ diff --git a/tests/unittests/tests-gcoap/tests-gcoap.h b/tests/unittests/tests-gcoap/tests-gcoap.h new file mode 100644 index 0000000000..74a3d8dfa6 --- /dev/null +++ b/tests/unittests/tests-gcoap/tests-gcoap.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 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. + */ + +/** + * @addtogroup unittests + * @{ + * + * @file + * @brief Unit tests for the gcoap module + * + * @author Ken Bannister + */ +#ifndef TESTS_GCOAP_H_ +#define TESTS_GCOAP_H_ + +#include "embUnit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief The entry point of this test suite. + * + * Includes simple success case tests to get a resource from a server, and + * to provide a resource for a client. + */ +void tests_gcoap(void); + +#ifdef __cplusplus +} +#endif + +#endif /* TESTS_GCOAP_H_ */ +/** @} */