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