diff --git a/Makefile.dep b/Makefile.dep index 9f031eb66876..a8c5177afd04 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -652,14 +652,18 @@ ifneq (,$(filter l2filter_%,$(USEMODULE))) endif ifneq (,$(filter gcoap,$(USEMODULE))) -USEPKG += nanocoap -USEMODULE += gnrc_sock_udp + USEMODULE += nanocoap + USEMODULE += gnrc_sock_udp endif ifneq (,$(filter luid,$(USEMODULE))) FEATURES_OPTIONAL += periph_cpuid endif +ifneq (,$(filter nanocoap_%,$(USEMODULE))) + USEMODULE += nanocoap +endif + # always select gpio (until explicit dependencies are sorted out) FEATURES_OPTIONAL += periph_gpio diff --git a/examples/nanocoap_server/Makefile b/examples/nanocoap_server/Makefile index 07cef0e4c975..ec2b6e1b0e09 100644 --- a/examples/nanocoap_server/Makefile +++ b/examples/nanocoap_server/Makefile @@ -22,9 +22,7 @@ USEMODULE += gnrc_sock_udp # Additional networking modules that can be dropped if not needed USEMODULE += gnrc_icmpv6_echo -USEPKG += nanocoap -# optionally enable nanocoap's debug output -#CFLAGS += -DNANOCOAP_DEBUG +USEMODULE += nanocoap_sock # include this for nicely formatting the returned internal value USEMODULE += fmt diff --git a/examples/nanocoap_server/coap_handler.c b/examples/nanocoap_server/coap_handler.c index 4eadff6ea974..5c1716a3bce8 100644 --- a/examples/nanocoap_server/coap_handler.c +++ b/examples/nanocoap_server/coap_handler.c @@ -11,7 +11,7 @@ #include #include "fmt.h" -#include "nanocoap.h" +#include "net/nanocoap.h" /* internal value that can be read/written via CoAP */ static uint8_t internal_value = 0; diff --git a/examples/nanocoap_server/main.c b/examples/nanocoap_server/main.c index c600d20fd640..91ce26ef9a51 100644 --- a/examples/nanocoap_server/main.c +++ b/examples/nanocoap_server/main.c @@ -19,8 +19,8 @@ #include -#include "nanocoap.h" -#include "nanocoap_sock.h" +#include "net/nanocoap.h" +#include "net/nanocoap_sock.h" #include "xtimer.h" diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 9bf81c929c5a..1b499d119230 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -46,6 +46,7 @@ PSEUDOMODULES += lwip_tcp PSEUDOMODULES += lwip_udp PSEUDOMODULES += lwip_udplite PSEUDOMODULES += mpu_stack_guard +PSEUDOMODULES += nanocoap_% PSEUDOMODULES += netdev_default PSEUDOMODULES += netif PSEUDOMODULES += netstats diff --git a/pkg/nanocoap/Makefile b/pkg/nanocoap/Makefile deleted file mode 100644 index d69913484677..000000000000 --- a/pkg/nanocoap/Makefile +++ /dev/null @@ -1,12 +0,0 @@ -PKG_NAME=nanocoap -PKG_URL=https://github.com/kaspar030/sock -PKG_VERSION=fc0a9470536bc760998827a6dc05d7dedf7b93ad -PKG_LICENSE=LGPL-2.1 - -.PHONY: all - -all: git-download - @cp Makefile.nanocoap $(PKG_BUILDDIR)/nanocoap/Makefile - "$(MAKE)" -C $(PKG_BUILDDIR)/nanocoap - -include $(RIOTBASE)/pkg/pkg.mk diff --git a/pkg/nanocoap/Makefile.include b/pkg/nanocoap/Makefile.include deleted file mode 100644 index 16f9db77957b..000000000000 --- a/pkg/nanocoap/Makefile.include +++ /dev/null @@ -1,2 +0,0 @@ -INCLUDES += -I$(PKGDIRBASE)/nanocoap/nanocoap -INCLUDES += -I$(RIOTBASE)/sys/posix/include diff --git a/pkg/nanocoap/Makefile.nanocoap b/pkg/nanocoap/Makefile.nanocoap deleted file mode 100644 index 8713473872ac..000000000000 --- a/pkg/nanocoap/Makefile.nanocoap +++ /dev/null @@ -1,5 +0,0 @@ -MODULE=nanocoap - -SRC := nanocoap.c nanocoap_sock.c - -include $(RIOTBASE)/Makefile.base diff --git a/pkg/nanocoap/doc.txt b/pkg/nanocoap/doc.txt deleted file mode 100644 index 7ba479a6f25a..000000000000 --- a/pkg/nanocoap/doc.txt +++ /dev/null @@ -1,7 +0,0 @@ -/** - * @defgroup pkg_nanocoap CoAP lightweight implementation - * @ingroup pkg - * @ingroup net - * @brief Provides a low-level and lightweight implementation of CoAP - * @see https://github.com/kaspar030/sock/tree/master/nanocoap - */ \ No newline at end of file diff --git a/sys/Makefile b/sys/Makefile index e170678a6145..bedb873eafe2 100644 --- a/sys/Makefile +++ b/sys/Makefile @@ -121,6 +121,9 @@ endif ifneq (,$(filter l2filter,$(USEMODULE))) DIRS += net/link_layer/l2filter endif +ifneq (,$(filter nanocoap,$(USEMODULE))) + DIRS += net/application_layer/nanocoap +endif DIRS += $(dir $(wildcard $(addsuffix /Makefile, ${USEMODULE}))) diff --git a/sys/include/net/gcoap.h b/sys/include/net/gcoap.h index 9c8cb9d91ad1..98d22adf255b 100644 --- a/sys/include/net/gcoap.h +++ b/sys/include/net/gcoap.h @@ -212,9 +212,11 @@ #include #include + +#include "net/ipv6/addr.h" #include "net/sock/udp.h" #include "mutex.h" -#include "nanocoap.h" +#include "net/nanocoap.h" #include "xtimer.h" #ifdef __cplusplus diff --git a/sys/include/net/nanocoap.h b/sys/include/net/nanocoap.h new file mode 100644 index 000000000000..77721106f471 --- /dev/null +++ b/sys/include/net/nanocoap.h @@ -0,0 +1,601 @@ +/* + * Copyright (C) 2016-17 Kaspar Schleiser + * + * 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 sys_net_nanocoap nanocoap small CoAP library + * @ingroup sys_net + * @brief Provides CoAP functionality optimized for minimal resource usage + * + * @{ + * + * @file + * @brief nanocoap API + * + * @author Kaspar Schleiser + */ + +#ifndef NET_NANOCOAP_H +#define NET_NANOCOAP_H + +#include +#include +#include +#include +#include + +#ifdef RIOT_VERSION +#include "byteorder.h" +#else +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief CoAP port to use + */ +#define COAP_PORT (5683) + +/** + * @name Nanocoap specific maximum values + * @{ + */ +#define NANOCOAP_URL_MAX (64) +#define NANOCOAP_QS_MAX (64) +/** @} */ + +/** + * @name CoAP option numbers + * @{ + */ +#define COAP_OPT_URI_HOST (3) +#define COAP_OPT_OBSERVE (6) +#define COAP_OPT_URI_PATH (11) +#define COAP_OPT_CONTENT_FORMAT (12) +#define COAP_OPT_URI_QUERY (15) +/** @} */ + +/** + * @name CoAP packet types + * @{ + */ +#define COAP_REQ (0) +#define COAP_RESP (2) +#define COAP_RST (3) + +/** + * @name Message types -- confirmable, non-confirmable, etc. + * @{ + */ +#define COAP_TYPE_CON (0) +#define COAP_TYPE_NON (1) +#define COAP_TYPE_ACK (2) +#define COAP_TYPE_RST (3) +/** @} */ + +/** + * @name CoAP method codes used in header + * @{ + */ +#define COAP_CLASS_REQ (0) +#define COAP_METHOD_GET (1) +#define COAP_METHOD_POST (2) +#define COAP_METHOD_PUT (3) +#define COAP_METHOD_DELETE (4) +/** @} */ + +/** + * @name CoAP method flags used in coap_handlers array + * @{ + */ +#define COAP_GET (0x1) +#define COAP_POST (0x2) +#define COAP_PUT (0x4) +#define COAP_DELETE (0x8) +/** @} */ + +/** + * @name Empty CoAP message code + * @{ + */ +#define COAP_CODE_EMPTY (0) +/** @} */ + +/** + * @name Response message codes: success + * @{ + */ +#define COAP_CLASS_SUCCESS (2) +#define COAP_CODE_CREATED ((2 << 5) | 1) +#define COAP_CODE_DELETED ((2 << 5) | 2) +#define COAP_CODE_VALID ((2 << 5) | 3) +#define COAP_CODE_CHANGED ((2 << 5) | 4) +#define COAP_CODE_204 ((2 << 5) | 4) +#define COAP_CODE_CONTENT ((2 << 5) | 5) +#define COAP_CODE_205 ((2 << 5) | 5) +#define COAP_CODE_231 ((2 << 5) | 31) +/** @} */ + +/** + * @name Response message codes: client error + * @{ + */ +#define COAP_CLASS_CLIENT_FAILURE (4) +#define COAP_CODE_BAD_REQUEST ((4 << 5) | 0) +#define COAP_CODE_UNAUTHORIZED ((4 << 5) | 1) +#define COAP_CODE_BAD_OPTION ((4 << 5) | 2) +#define COAP_CODE_FORBIDDEN ((4 << 5) | 3) +#define COAP_CODE_PATH_NOT_FOUND ((4 << 5) | 4) +#define COAP_CODE_404 ((4 << 5) | 4) +#define COAP_CODE_METHOD_NOT_ALLOWED ((4 << 5) | 5) +#define COAP_CODE_NOT_ACCEPTABLE ((4 << 5) | 6) +#define COAP_CODE_PRECONDITION_FAILED ((4 << 5) | 0xC) +#define COAP_CODE_REQUEST_ENTITY_TOO_LARGE ((4 << 5) | 0xD) +#define COAP_CODE_UNSUPPORTED_CONTENT_FORMAT ((4 << 5) | 0xF) +/** @} */ + +/** + * @name Response message codes: server error + * @{ + */ +#define COAP_CLASS_SERVER_FAILURE (5) +#define COAP_CODE_INTERNAL_SERVER_ERROR ((5 << 5) | 0) +#define COAP_CODE_NOT_IMPLEMENTED ((5 << 5) | 1) +#define COAP_CODE_BAD_GATEWAY ((5 << 5) | 2) +#define COAP_CODE_SERVICE_UNAVAILABLE ((5 << 5) | 3) +#define COAP_CODE_GATEWAY_TIMEOUT ((5 << 5) | 4) +#define COAP_CODE_PROXYING_NOT_SUPPORTED ((5 << 5) | 5) +/** @} */ + +/** + * @name Content types + * @{ + */ +#define COAP_CT_LINK_FORMAT (40) +#define COAP_CT_XML (41) +#define COAP_CT_OCTET_STREAM (42) +#define COAP_CT_EXI (47) +#define COAP_CT_JSON (50) +/** @} */ + +/** + * @name Content-Format option codes + * @{ + */ +#define COAP_FORMAT_TEXT (0) +#define COAP_FORMAT_LINK (40) +#define COAP_FORMAT_OCTET (42) +#define COAP_FORMAT_JSON (50) +#define COAP_FORMAT_CBOR (60) +/** @brief nanocoap-specific value to indicate no format specified. */ +#define COAP_FORMAT_NONE (65535) +/** @} */ + +/** + * @name Observe (RFC 7641) constants + * @{ + */ +#define COAP_OBS_REGISTER (0) +#define COAP_OBS_DEREGISTER (1) +/** @} */ + +/** + * @name Timing parameters + * @{ + */ +#define COAP_ACK_TIMEOUT (2U) +#define COAP_RANDOM_FACTOR (1.5) +#define COAP_MAX_RETRANSMIT (4) +#define COAP_NSTART (1) +#define COAP_DEFAULT_LEISURE (5) +/** @} */ + +/** + * @brief Raw CoAP PDU header structure + */ +typedef struct { + uint8_t ver_t_tkl; /**< version, token, token length */ + uint8_t code; /**< CoAP code (e.g.m 205) */ + uint16_t id; /**< Req/resp ID */ + uint8_t data[]; /**< convenience pointer to payload start */ +} coap_hdr_t; + +/** + * @brief CoAP option array entry + */ +typedef struct { + coap_hdr_t *hdr; /**< pointer to raw packet */ + uint8_t url[NANOCOAP_URL_MAX]; /**< parsed request URL */ + uint8_t qs[NANOCOAP_QS_MAX]; /**< parsed query string */ + uint8_t *token; /**< pointer to token */ + uint8_t *payload; /**< pointer to payload */ + unsigned payload_len; /**< length of payload */ + uint16_t content_type; /**< content type */ + uint32_t observe_value; /**< observe value */ +} coap_pkt_t; + +/** + * @brief Resource handler type + */ +typedef ssize_t (*coap_handler_t)(coap_pkt_t *pkt, uint8_t *buf, size_t len); + +/** + * @brief Type for CoAP resource entry + */ +typedef struct { + const char *path; /**< URI path of resource */ + unsigned methods; /**< OR'ed methods this resource allows */ + coap_handler_t handler; /**< ptr to resource handler */ +} coap_resource_t; + +/** + * @brief Global CoAP resource list + */ +extern const coap_resource_t coap_resources[]; + +/** + * @brief Number of entries in global CoAP resource list + */ +extern const unsigned coap_resources_numof; + +/** + * @brief Parse a CoAP PDU + * + * This function parses a raw CoAP PDU from @p buf with size @p len and fills + * the structure pointed to by @p pkt. + * @p pkt must point to a preallocated coap_pkt_t structure. + * + * @param[out] pkt structure to parse into + * @param[in] buf pointer to raw packet data + * @param[in] len length of packet at @p buf + * + * @returns 0 on success + * @returns <0 on error + */ +int coap_parse(coap_pkt_t *pkt, uint8_t *buf, size_t len); + +/** + * @brief Build reply to CoAP request + * + * This function can be used to create a reply to any CoAP request packet. It + * will create the reply packet header based on parameters from the request + * (e.g., id, token). Passing a non-zero @p payload_len will ensure the payload + * fits into the buffer along with the header. + * + * @param[in] pkt packet to reply to + * @param[in] code reply code (e.g., COAP_CODE_204) + * @param[out] rbuf buffer to write reply to + * @param[in] rlen size of @p rbuf + * @param[in] payload_len length of payload + * + * @returns size of reply packet on success + * @returns <0 on error + */ +ssize_t coap_build_reply(coap_pkt_t *pkt, unsigned code, + uint8_t *rbuf, unsigned rlen, unsigned payload_len); + +/** + * @brief Create CoAP reply (convenience function) + * + * This is a simple wrapper that allows for building CoAP replies for simple + * use-cases. + * + * The reply will be written to @p buf. Is @p payload and @p payload_len + * non-zero, the payload will be copied into the resulting reply packet. + * + * @param[in] pkt packet to reply to + * @param[in] code reply code (e.g., COAP_CODE_204) + * @param[out] buf buffer to write reply to + * @param[in] len size of @p buf + * @param[in] ct content type of payload + * @param[in] payload ptr to payload + * @param[in] payload_len length of payload + * + * @returns size of reply packet on success + * @returns <0 on error + */ +ssize_t coap_reply_simple(coap_pkt_t *pkt, + unsigned code, + uint8_t *buf, size_t len, + unsigned ct, + const uint8_t *payload, uint8_t payload_len); + +/** + * @brief Handle incoming CoAP request + * + * This function will find the correct handler, call it and write the reply + * into @p resp_buf. + * + * @param[in] pkt pointer to (parsed) CoAP packet + * @param[out] resp_buf buffer for response + * @param[in] resp_buf_len size of response buffer + * + * @returns size of reply packet on success + * @returns <0 on error + */ +ssize_t coap_handle_req(coap_pkt_t *pkt, uint8_t *resp_buf, unsigned resp_buf_len); + +/** + * @brief Builds a CoAP header + * + * Caller *must* ensure @p hdr can hold the header and the full token! + * + * @param[out] hdr hdr to fill + * @param[in] type CoAP packet type (e.g., COAP_TYPE_CON, ...) + * @param[in] token token + * @param[in] token_len length of @p token + * @param[in] code CoAP code (e.g., COAP_CODE_204, ...) + * @param[in] id CoAP request id + * + * @returns length of resulting header + */ +ssize_t coap_build_hdr(coap_hdr_t *hdr, unsigned type, uint8_t *token, + size_t token_len, unsigned code, uint16_t id); + +/** + * @brief Insert a CoAP option into buffer + * + * This function writes a CoAP option with nr. @p onum to @p buf. + * It handles calculating the option delta (from @p lastonum), encoding the + * length from @p olen and copying the option data from @p odata. + * + * @param[out] buf buffer to write to + * @param[in] lastonum number of previous option (for delta calculation), + * or 0 for first option + * @param[in] onum number of option + * @param[in] odata ptr to raw option data (or NULL) + * @param[in] olen length of @p odata (if any) + * + * @returns amount of bytes written to @p buf + */ +size_t coap_put_option(uint8_t *buf, uint16_t lastonum, uint16_t onum, uint8_t *odata, size_t olen); + +/** + * @brief Insert content type option into buffer + * + * @param[out] buf buffer to write to + * @param[in] lastonum number of previous option (for delta + * calculation), or 0 if first option + * @param[in] content_type content type to set + * + * @returns amount of bytes written to @p buf + */ +size_t coap_put_option_ct(uint8_t *buf, uint16_t lastonum, uint16_t content_type); + +/** + * @brief Insert URI encoded option into buffer + * + * @param[out] buf buffer to write to + * @param[in] lastonum number of previous option (for delta calculation), + * or 0 if first option + * @param[in] uri ptr to source URI + * @param[in] optnum option number to use (e.g., COAP_OPT_URI_PATH) + * + * @returns amount of bytes written to @p buf + */ +size_t coap_put_option_uri(uint8_t *buf, uint16_t lastonum, const char *uri, uint16_t optnum); + +/** + * @brief Get the CoAP version number + * + * @param[in] pkt CoAP packet + * + * @returns CoAP version number + */ +static inline unsigned coap_get_ver(coap_pkt_t *pkt) +{ + return (pkt->hdr->ver_t_tkl & 0x60) >> 6; +} + +/** + * @brief Get the message type + * + * @param[in] pkt CoAP packet + * + * @returns COAP_TYPE_CON + * @returns COAP_TYPE_NON + * @returns COAP_TYPE_ACK + * @returns COAP_TYPE_RST + */ +static inline unsigned coap_get_type(coap_pkt_t *pkt) +{ + return (pkt->hdr->ver_t_tkl & 0x30) >> 4; +} + +/** + * @brief Get a message's token length [in byte] + * + * @param[in] pkt CoAP packet + * + * @returns length of token in the given message (0-8 byte) + */ +static inline unsigned coap_get_token_len(coap_pkt_t *pkt) +{ + return (pkt->hdr->ver_t_tkl & 0xf); +} + +/** + * @brief Get a message's code class (3 most significant bits of code) + * + * @param[in] pkt CoAP packet + * + * @returns message code class + */ +static inline unsigned coap_get_code_class(coap_pkt_t *pkt) +{ + return pkt->hdr->code >> 5; +} + +/** + * @brief Get a message's code detail (5 least significant bits of code) + * + * @param[in] pkt CoAP packet + * + * @returns message code detail + */ +static inline unsigned coap_get_code_detail(coap_pkt_t *pkt) +{ + return pkt->hdr->code & 0x1f; +} + +/** + * @brief Get a message's raw code (class + detail) + * + * @param[in] pkt CoAP packet + * + * @returns raw message code + */ +static inline unsigned coap_get_code_raw(coap_pkt_t *pkt) +{ + return (unsigned)pkt->hdr->code; +} + +/** + * @brief Get a message's code in decimal format ((class * 100) + detail) + * + * @param[in] pkt CoAP packet + * + * @returns message code in decimal format + */ +static inline unsigned coap_get_code(coap_pkt_t *pkt) +{ + return (coap_get_code_class(pkt) * 100) + coap_get_code_detail(pkt); +} + +/** + * @brief Get the message ID of the given CoAP packet + * + * @param[in] pkt CoAP packet + * + * @returns message ID + */ +static inline unsigned coap_get_id(coap_pkt_t *pkt) +{ + return ntohs(pkt->hdr->id); +} + +/** + * @brief Get the total header length (4-byte header + token length) + * + * @param[in] pkt CoAP packet + * + * @returns total header length + */ +static inline unsigned coap_get_total_hdr_len(coap_pkt_t *pkt) +{ + return sizeof(coap_hdr_t) + coap_get_token_len(pkt); +} + +/** + * @brief Encode given code class and code detail to raw code + * + * @param[in] class message code class + * @param[in] detail message code detail + * + * @returns raw message code + */ +static inline uint8_t coap_code(unsigned class, unsigned detail) +{ + return (class << 5) | detail; +} + +/** + * @brief Write the given raw message code to given CoAP header + * + * @param[out] hdr CoAP header to write to + * @param[in] code raw message code + */ +static inline void coap_hdr_set_code(coap_hdr_t *hdr, uint8_t code) +{ + hdr->code = code; +} + +/** + * @brief Set the message type for the given CoAP header + * + * @pre (type := [0-3]) + * + * @param[out] hdr CoAP header to write + * @param[in] type message type as integer value [0-3] + */ +static inline void coap_hdr_set_type(coap_hdr_t *hdr, unsigned type) +{ + /* assert correct range of type */ + assert(!(type & ~0x3)); + + hdr->ver_t_tkl &= ~0x30; + hdr->ver_t_tkl |= type << 4; +} + +/** + * @brief Convert message code (request method) into a corresponding bit field + * + * @param[in] code request code denoting the request method + * + * @returns bit field corresponding to the given request method + */ +static inline unsigned coap_method2flag(unsigned code) +{ + return (1 << (code - 1)); +} + +/** + * @brief Identifies a packet containing an observe option + * + * @param[in] pkt CoAP packet + * + * @returns true if observe value is set + * @returns false if not + */ +static inline bool coap_has_observe(coap_pkt_t *pkt) +{ + return pkt->observe_value != UINT32_MAX; +} + +/** + * @brief Clears the observe option value from a packet + * + * @param[in] pkt CoAP packet + */ +static inline void coap_clear_observe(coap_pkt_t *pkt) +{ + pkt->observe_value = UINT32_MAX; +} + +/** + * @brief Get the value of the observe option from the given packet + * + * @param[in] pkt CoAP packet + * + * @returns value of the observe option + */ +static inline uint32_t coap_get_observe(coap_pkt_t *pkt) +{ + return pkt->observe_value; +} + +/** + * @brief Reference to the default .well-known/core handler defined by the + * application + */ +extern ssize_t coap_well_known_core_default_handler(coap_pkt_t *pkt, \ + uint8_t *buf, size_t len); + +/** + * @brief Resource definition for the default .well-known/core handler + */ +#define COAP_WELL_KNOWN_CORE_DEFAULT_HANDLER \ + { "/.well-known/core", COAP_GET, coap_well_known_core_default_handler } + +#ifdef __cplusplus +} +#endif +#endif /* NET_NANOCOAP_H */ +/** @} */ diff --git a/sys/include/net/nanocoap_sock.h b/sys/include/net/nanocoap_sock.h new file mode 100644 index 000000000000..e7b2c98bb3b0 --- /dev/null +++ b/sys/include/net/nanocoap_sock.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser + * + * 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 sys_net_nanocoap + * + * @{ + * + * @file + * @brief nanocoap high-level API + * + * @author Kaspar Schleiser + */ + +#ifndef NET_NANOCOAP_SOCK_H +#define NET_NANOCOAP_SOCK_H + +#include +#include + +#include "net/sock/udp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Start a nanocoap server instance + * + * This function only returns if there's an error binding to @p local, or if + * receiving of UDP packets fails. + * + * @param[in] local local UDP endpoint to bind to + * @param[in] buf input buffer to use + * @param[in] bufsize size of @p buf + * + * @returns -1 on error + */ +int nanocoap_server(sock_udp_ep_t *local, uint8_t *buf, size_t bufsize); + +/** + * @brief Simple synchronous CoAP get + * + * @param[in] remote remote UDP endpoint + * @param[in] path remote path + * @param[out] buf buffer to write response to + * @param[in] len length of @p buffer + * + * @returns length of response on success + * @returns <0 on error + */ +ssize_t nanocoap_get(sock_udp_ep_t *remote, const char *path, uint8_t *buf, + size_t len); + +#ifdef __cplusplus +} +#endif +#endif /* NET_NANOCOAP_SOCK_H */ +/** @} */ diff --git a/sys/net/application_layer/nanocoap/Makefile b/sys/net/application_layer/nanocoap/Makefile new file mode 100644 index 000000000000..ff222a69b508 --- /dev/null +++ b/sys/net/application_layer/nanocoap/Makefile @@ -0,0 +1,3 @@ +SRC := nanocoap.c +SUBMODULES := 1 +include $(RIOTBASE)/Makefile.base diff --git a/sys/net/application_layer/nanocoap/nanocoap.c b/sys/net/application_layer/nanocoap/nanocoap.c new file mode 100644 index 000000000000..15eca0860f74 --- /dev/null +++ b/sys/net/application_layer/nanocoap/nanocoap.c @@ -0,0 +1,406 @@ +/* + * Copyright (C) 2016-17 Kaspar Schleiser + * + * 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 sys_net_nanocoap + * @{ + * + * @file + * @brief Nanocoap implementation + * + * @author Kaspar Schleiser + * + * @} + */ + +#include +#include +#include +#include + +#include "net/nanocoap.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +static int _decode_value(unsigned val, uint8_t **pkt_pos_ptr, uint8_t *pkt_end); +static uint32_t _decode_uint(uint8_t *pkt_pos, unsigned nbytes); + +/* http://tools.ietf.org/html/rfc7252#section-3 + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |Ver| T | TKL | Code | Message ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Token (if any, TKL bytes) ... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Options (if any) ... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |1 1 1 1 1 1 1 1| Payload (if any) ... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +int coap_parse(coap_pkt_t *pkt, uint8_t *buf, size_t len) +{ + uint8_t *urlpos = pkt->url; + coap_hdr_t *hdr = (coap_hdr_t *)buf; + + pkt->hdr = hdr; + + uint8_t *pkt_pos = hdr->data; + uint8_t *pkt_end = buf + len; + + memset(pkt->url, '\0', NANOCOAP_URL_MAX); + pkt->payload_len = 0; + pkt->observe_value = UINT32_MAX; + + /* token value (tkl bytes) */ + if (coap_get_token_len(pkt)) { + pkt->token = pkt_pos; + pkt_pos += coap_get_token_len(pkt); + } + else { + pkt->token = NULL; + } + + /* parse options */ + int option_nr = 0; + while (pkt_pos != pkt_end) { + uint8_t option_byte = *pkt_pos++; + if (option_byte == 0xff) { + pkt->payload = pkt_pos; + pkt->payload_len = buf + len - pkt_pos; + DEBUG("payload len = %u\n", pkt->payload_len); + break; + } + else { + int option_delta = _decode_value(option_byte >> 4, &pkt_pos, pkt_end); + if (option_delta < 0) { + DEBUG("bad op delta\n"); + return -EBADMSG; + } + int option_len = _decode_value(option_byte & 0xf, &pkt_pos, pkt_end); + if (option_len < 0) { + DEBUG("bad op len\n"); + return -EBADMSG; + } + option_nr += option_delta; + DEBUG("option nr=%i len=%i\n", option_nr, option_len); + + switch (option_nr) { + case COAP_OPT_URI_HOST: + DEBUG("nanocoap: ignoring Uri-Host option!\n"); + break; + case COAP_OPT_URI_PATH: + *urlpos++ = '/'; + memcpy(urlpos, pkt_pos, option_len); + urlpos += option_len; + break; + case COAP_OPT_CONTENT_FORMAT: + if (option_len == 0) { + pkt->content_type = 0; + } + else if (option_len == 1) { + pkt->content_type = *pkt_pos; + } + else if (option_len == 2) { + memcpy(&pkt->content_type, pkt_pos, 2); + pkt->content_type = ntohs(pkt->content_type); + } + break; + case COAP_OPT_OBSERVE: + if (option_len < 4) { + pkt->observe_value = _decode_uint(pkt_pos, option_len); + } + else { + DEBUG("nanocoap: discarding packet with invalid option length.\n"); + return -EBADMSG; + } + break; + default: + DEBUG("nanocoap: unhandled option nr=%i len=%i critical=%u\n", option_nr, option_len, option_nr & 1); + if (option_nr & 1) { + DEBUG("nanocoap: discarding packet with unknown critical option.\n"); + return -EBADMSG; + } + } + + pkt_pos += option_len; + } + } + + DEBUG("coap pkt parsed. code=%u detail=%u payload_len=%u, 0x%02x\n", + coap_get_code_class(pkt), + coap_get_code_detail(pkt), + pkt->payload_len, hdr->code); + + return 0; +} + +ssize_t coap_handle_req(coap_pkt_t *pkt, uint8_t *resp_buf, unsigned resp_buf_len) +{ + if (coap_get_code_class(pkt) != COAP_REQ) { + DEBUG("coap_handle_req(): not a request.\n"); + return -EBADMSG; + } + + if (pkt->hdr->code == 0) { + return coap_build_reply(pkt, COAP_CODE_EMPTY, resp_buf, resp_buf_len, 0); + } + + unsigned method_flag = coap_method2flag(coap_get_code_detail(pkt)); + + for (unsigned i = 0; i < coap_resources_numof; i++) { + if (!(coap_resources[i].methods & method_flag)) { + continue; + } + + int res = strcmp((char *)pkt->url, coap_resources[i].path); + if (res > 0) { + continue; + } + else if (res < 0) { + break; + } + else { + return coap_resources[i].handler(pkt, resp_buf, resp_buf_len); + } + } + + return coap_build_reply(pkt, COAP_CODE_404, resp_buf, resp_buf_len, 0); +} + +ssize_t coap_reply_simple(coap_pkt_t *pkt, + unsigned code, + uint8_t *buf, size_t len, + unsigned ct, + const uint8_t *payload, uint8_t payload_len) +{ + uint8_t *payload_start = buf + coap_get_total_hdr_len(pkt); + uint8_t *bufpos = payload_start; + + if (payload_len) { + bufpos += coap_put_option_ct(bufpos, 0, ct); + *bufpos++ = 0xff; + + memcpy(bufpos, payload, payload_len); + bufpos += payload_len; + } + + return coap_build_reply(pkt, code, buf, len, bufpos - payload_start); +} + +ssize_t coap_build_reply(coap_pkt_t *pkt, unsigned code, + uint8_t *rbuf, unsigned rlen, unsigned payload_len) +{ + unsigned tkl = coap_get_token_len(pkt); + unsigned len = sizeof(coap_hdr_t) + tkl; + + if ((len + payload_len + 1) > rlen) { + return -ENOSPC; + } + + /* if code is COAP_CODE_EMPTY (zero), use RST as type, else RESP */ + unsigned type = code ? COAP_RESP : COAP_RST; + + coap_build_hdr((coap_hdr_t *)rbuf, type, pkt->token, tkl, code, pkt->hdr->id); + coap_hdr_set_type((coap_hdr_t *)rbuf, type); + coap_hdr_set_code((coap_hdr_t *)rbuf, code); + + len += payload_len; + + return len; +} + +ssize_t coap_build_hdr(coap_hdr_t *hdr, unsigned type, uint8_t *token, size_t token_len, unsigned code, uint16_t id) +{ + assert(!(type & ~0x3)); + assert(!(token_len & ~0x1f)); + + memset(hdr, 0, sizeof(coap_hdr_t)); + hdr->ver_t_tkl = (0x1 << 6) | (type << 4) | token_len; + hdr->code = code; + hdr->id = id; + + if (token_len) { + memcpy(hdr->data, token, token_len); + } + + return sizeof(coap_hdr_t) + token_len; +} + +static int _decode_value(unsigned val, uint8_t **pkt_pos_ptr, uint8_t *pkt_end) +{ + uint8_t *pkt_pos = *pkt_pos_ptr; + size_t left = pkt_end - pkt_pos; + int res; + + switch (val) { + case 13: + { + /* An 8-bit unsigned integer follows the initial byte and + indicates the Option Delta minus 13. */ + if (left < 1) { + return -ENOSPC; + } + uint8_t delta = *pkt_pos++; + res = delta + 13; + break; + } + case 14: + { + /* A 16-bit unsigned integer in network byte order follows + * the initial byte and indicates the Option Delta minus + * 269. */ + if (left < 2) { + return -ENOSPC; + } + uint16_t delta; + uint8_t *_tmp = (uint8_t *)δ + *_tmp++ = *pkt_pos++; + *_tmp++ = *pkt_pos++; + res = ntohs(delta) + 269; + break; + } + case 15: + /* Reserved for the Payload Marker. If the field is set to + * this value but the entire byte is not the payload + * marker, this MUST be processed as a message format + * error. */ + return -EBADMSG; + default: + res = val; + } + + *pkt_pos_ptr = pkt_pos; + return res; +} + +static uint32_t _decode_uint(uint8_t *pkt_pos, unsigned nbytes) +{ + assert(nbytes <= 4); + + uint32_t res = 0; + if (nbytes) { + memcpy(((uint8_t *)&res) + (4 - nbytes), pkt_pos, nbytes); + } + return ntohl(res); +} + +static unsigned _put_delta_optlen(uint8_t *buf, unsigned offset, unsigned shift, unsigned val) +{ + if (val < 13) { + *buf |= (val << shift); + } + else if (val < (256 + 13)) { + *buf |= (13 << shift); + buf[offset++] = (val - 13); + } + else { + *buf |= (14 << shift); + uint16_t tmp = (val - 269); + tmp = htons(tmp); + memcpy(buf + offset, &tmp, 2); + offset += 2; + } + return offset; +} + +size_t coap_put_option(uint8_t *buf, uint16_t lastonum, uint16_t onum, uint8_t *odata, size_t olen) +{ + assert(lastonum <= onum); + + unsigned delta = (onum - lastonum); + *buf = 0; + + /* write delta value to option header: 4 upper bits of header (shift 4) + + * 1 or 2 optional bytes depending on delta value) */ + unsigned n = _put_delta_optlen(buf, 1, 4, delta); + /* write option length to option header: 4 lower bits of header (shift 0) + + * 1 or 2 optional bytes depending of the length of the option */ + n = _put_delta_optlen(buf, n, 0, olen); + if (olen) { + memcpy(buf + n, odata, olen); + n += olen; + } + return (size_t)n; +} + +size_t coap_put_option_ct(uint8_t *buf, uint16_t lastonum, uint16_t content_type) +{ + if (content_type == 0) { + return coap_put_option(buf, lastonum, COAP_OPT_CONTENT_FORMAT, NULL, 0); + } + else if (content_type <= 255) { + uint8_t tmp = content_type; + return coap_put_option(buf, lastonum, COAP_OPT_CONTENT_FORMAT, &tmp, sizeof(tmp)); + } + else { + return coap_put_option(buf, lastonum, COAP_OPT_CONTENT_FORMAT, (uint8_t *)&content_type, sizeof(content_type)); + } +} + +size_t coap_put_option_uri(uint8_t *buf, uint16_t lastonum, const char *uri, uint16_t optnum) +{ + char separator = (optnum == COAP_OPT_URI_PATH) ? '/' : '&'; + size_t uri_len = strlen(uri); + + if (uri_len == 0) { + return 0; + } + + uint8_t *bufpos = buf; + char *uripos = (char *)uri; + + while (uri_len) { + size_t part_len; + uripos++; + uint8_t *part_start = (uint8_t *)uripos; + + while (uri_len--) { + if ((*uripos == separator) || (*uripos == '\0')) { + break; + } + uripos++; + } + + part_len = (uint8_t *)uripos - part_start; + + if (part_len) { + bufpos += coap_put_option(bufpos, lastonum, optnum, part_start, part_len); + lastonum = optnum; + } + } + + return bufpos - buf; +} + +ssize_t coap_well_known_core_default_handler(coap_pkt_t *pkt, uint8_t *buf, \ + size_t len) +{ + uint8_t *payload = buf + coap_get_total_hdr_len(pkt); + + uint8_t *bufpos = payload; + + bufpos += coap_put_option_ct(bufpos, 0, COAP_CT_LINK_FORMAT); + *bufpos++ = 0xff; + + for (unsigned i = 0; i < coap_resources_numof; i++) { + if (i) { + *bufpos++ = ','; + } + *bufpos++ = '<'; + unsigned url_len = strlen(coap_resources[i].path); + memcpy(bufpos, coap_resources[i].path, url_len); + bufpos += url_len; + *bufpos++ = '>'; + } + + unsigned payload_len = bufpos - payload; + + return coap_build_reply(pkt, COAP_CODE_205, buf, len, payload_len); +} diff --git a/sys/net/application_layer/nanocoap/sock.c b/sys/net/application_layer/nanocoap/sock.c new file mode 100644 index 000000000000..96c651b29829 --- /dev/null +++ b/sys/net/application_layer/nanocoap/sock.c @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2016-17 Kaspar Schleiser + * + * 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 sys_net_nanocoap + * @{ + * + * @file + * @brief Nanocoap sock helpers + * + * @author Kaspar Schleiser + * + * @} + */ + +#include +#include +#include + +#include "net/nanocoap.h" +#include "net/sock/udp.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +ssize_t nanocoap_get(sock_udp_ep_t *remote, const char *path, uint8_t *buf, size_t len) +{ + ssize_t res; + sock_udp_t sock; + + if (!remote->port) { + remote->port = COAP_PORT; + } + + res = sock_udp_create(&sock, NULL, remote, 0); + if (res < 0) { + return res; + } + + uint8_t *pktpos = buf; + pktpos += coap_build_hdr((coap_hdr_t *)pktpos, COAP_REQ, NULL, 0, COAP_METHOD_GET, 1); + pktpos += coap_put_option_uri(pktpos, 0, path, COAP_OPT_URI_PATH); + + /* TODO: timeout random between between ACK_TIMEOUT and (ACK_TIMEOUT * + * ACK_RANDOM_FACTOR) */ + uint32_t timeout = COAP_ACK_TIMEOUT * (1000000U); + int tries = 0; + while (tries++ < COAP_MAX_RETRANSMIT) { + if (!tries) { + DEBUG("nanocoap: maximum retries reached.\n"); + res = -ETIMEDOUT; + goto out; + } + + res = sock_udp_send(&sock, buf, pktpos - buf, NULL); + if (res <= 0) { + DEBUG("nanocoap: error sending coap request\n"); + goto out; + } + + res = sock_udp_recv(&sock, buf, len, timeout, NULL); + if (res <= 0) { + if (res == -ETIMEDOUT) { + DEBUG("nanocoap: timeout\n"); + + timeout *= 2; + continue; + } + DEBUG("nanocoap: error receiving coap request\n"); + break; + } + + coap_pkt_t pkt; + if (coap_parse(&pkt, (uint8_t *)buf, res) < 0) { + puts("error parsing packet"); + continue; + } + else { + res = coap_get_code(&pkt); + if (res != 205) { + res = -res; + } + else { + if (pkt.payload_len) { + memcpy(buf, pkt.payload, pkt.payload_len); + } + res = pkt.payload_len; + } + break; + } + } + +out: + sock_udp_close(&sock); + + return res; +} + +int nanocoap_server(sock_udp_ep_t *local, uint8_t *buf, size_t bufsize) +{ + sock_udp_t sock; + sock_udp_ep_t remote; + + if (!local->port) { + local->port = COAP_PORT; + } + + ssize_t res = sock_udp_create(&sock, local, NULL, 0); + if (res == -1) { + return -1; + } + + while (1) { + res = sock_udp_recv(&sock, buf, bufsize, -1, &remote); + if (res == -1) { + DEBUG("error receiving UDP packet\n"); + return -1; + } + else { + coap_pkt_t pkt; + if (coap_parse(&pkt, (uint8_t *)buf, res) < 0) { + DEBUG("error parsing packet\n"); + continue; + } + if ((res = coap_handle_req(&pkt, buf, bufsize)) > 0) { + res = sock_udp_send(&sock, buf, res, &remote); + } + } + } + + return 0; +}