diff --git a/sys/Makefile b/sys/Makefile
index e170678a6145106c11dc5f6e49366be126cfea04..bedb873eafe2d697664f19d53a072a51b6f2dd30 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/nanocoap.h b/sys/include/net/nanocoap.h
new file mode 100644
index 0000000000000000000000000000000000000000..77721106f471c49ac0e7afa2471b111aa5a0bc10
--- /dev/null
+++ b/sys/include/net/nanocoap.h
@@ -0,0 +1,601 @@
+/*
+ * Copyright (C) 2016-17 Kaspar Schleiser <kaspar@schleiser.de>
+ *
+ * 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 <kaspar@schleiser.de>
+ */
+
+#ifndef NET_NANOCOAP_H
+#define NET_NANOCOAP_H
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <unistd.h>
+
+#ifdef RIOT_VERSION
+#include "byteorder.h"
+#else
+#include <arpa/inet.h>
+#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 0000000000000000000000000000000000000000..e7b2c98bb3b08da7ed40feb9419738ee27d6c2c2
--- /dev/null
+++ b/sys/include/net/nanocoap_sock.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 Kaspar Schleiser <kaspar@schleiser.de>
+ *
+ * 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 <kaspar@schleiser.de>
+ */
+
+#ifndef NET_NANOCOAP_SOCK_H
+#define NET_NANOCOAP_SOCK_H
+
+#include <stdint.h>
+#include <unistd.h>
+
+#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 0000000000000000000000000000000000000000..48422e909a47d7cd428d10fa73825060ccc8d8c2
--- /dev/null
+++ b/sys/net/application_layer/nanocoap/Makefile
@@ -0,0 +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 0000000000000000000000000000000000000000..3751e392dd94cbf067dd673661ce33c58abb1df0
--- /dev/null
+++ b/sys/net/application_layer/nanocoap/nanocoap.c
@@ -0,0 +1,396 @@
+/*
+ * Copyright (C) 2016-17 Kaspar Schleiser <kaspar@schleiser.de>
+ *
+ * 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 <kaspar@schleiser.de>
+ *
+ * @}
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#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_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*)&delta;
+            *_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/nanocoap_sock.c b/sys/net/application_layer/nanocoap/nanocoap_sock.c
new file mode 100644
index 0000000000000000000000000000000000000000..96c651b298295c3d070dbdc2c4ec3b926198bb2c
--- /dev/null
+++ b/sys/net/application_layer/nanocoap/nanocoap_sock.c
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2016-17 Kaspar Schleiser <kaspar@schleiser.de>
+ *
+ * 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 <kaspar@schleiser.de>
+ *
+ * @}
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+#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;
+}