1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/sys/net/application_layer/gcoap/gcoap.c

876 lines
28 KiB
C
Raw Normal View History

2016-10-29 21:20:22 +02:00
/*
* 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.
*/
/**
2016-11-17 06:01:15 +01:00
* @ingroup net_gcoap
2016-10-29 21:20:22 +02:00
* @{
*
* @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>
2016-11-17 06:01:15 +01:00
#include "net/gcoap.h"
2016-10-29 21:20:22 +02:00
#include "random.h"
#include "thread.h"
#define ENABLE_DEBUG (0)
#include "debug.h"
/* Internal functions */
static void *_event_loop(void *arg);
2016-11-17 06:01:15 +01:00
static void _listen(sock_udp_t *sock);
2016-10-29 21:20:22 +02:00
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,
sock_udp_ep_t *remote);
2016-10-29 21:20:22 +02:00
static ssize_t _finish_pdu(coap_pkt_t *pdu, uint8_t *buf, size_t len);
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);
static void _find_resource(coap_pkt_t *pdu, coap_resource_t **resource_ptr,
gcoap_listener_t **listener_ptr);
static int _find_observer(sock_udp_ep_t **observer, sock_udp_ep_t *remote);
static int _find_obs_memo(gcoap_observe_memo_t **memo, sock_udp_ep_t *remote,
coap_pkt_t *pdu);
static void _find_obs_memo_resource(gcoap_observe_memo_t **memo,
const coap_resource_t *resource);
2016-10-29 21:20:22 +02:00
/* 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 = {
.listeners = &_default_listener,
};
static kernel_pid_t _pid = KERNEL_PID_UNDEF;
static char _msg_stack[GCOAP_STACK_SIZE];
2016-11-17 06:01:15 +01:00
static sock_udp_t _sock;
2016-10-29 21:20:22 +02:00
/* Event/Message loop for gcoap _pid thread. */
static void *_event_loop(void *arg)
{
msg_t msg_rcvd, msg_queue[GCOAP_MSG_QUEUE_SIZE];
(void)arg;
2016-11-17 06:01:15 +01:00
msg_init_queue(msg_queue, GCOAP_MSG_QUEUE_SIZE);
2016-10-29 21:20:22 +02:00
2016-11-17 06:01:15 +01:00
sock_udp_ep_t local;
memset(&local, 0, sizeof(sock_udp_ep_t));
local.family = AF_INET6;
local.netif = SOCK_ADDR_ANY_NETIF;
local.port = GCOAP_PORT;
2016-10-29 21:20:22 +02:00
2016-11-17 06:01:15 +01:00
int res = sock_udp_create(&_sock, &local, NULL, 0);
if (res < 0) {
DEBUG("gcoap: cannot create sock: %d\n", res);
return 0;
}
2016-10-29 21:20:22 +02:00
2016-11-17 06:01:15 +01:00
while(1) {
res = msg_try_receive(&msg_rcvd);
2016-10-29 21:20:22 +02:00
2016-11-17 06:01:15 +01:00
if (res > 0) {
switch (msg_rcvd.type) {
case GCOAP_MSG_TYPE_TIMEOUT:
_expire_request((gcoap_request_memo_t *)msg_rcvd.content.ptr);
break;
case GCOAP_MSG_TYPE_INTR:
/* next _listen() timeout will account for open requests */
break;
default:
break;
}
2016-10-29 21:20:22 +02:00
}
2016-11-17 06:01:15 +01:00
_listen(&_sock);
2016-10-29 21:20:22 +02:00
}
2016-11-17 06:01:15 +01:00
2016-10-29 21:20:22 +02:00
return 0;
}
2016-11-17 06:01:15 +01:00
/* Listen for an incoming CoAP message. */
static void _listen(sock_udp_t *sock)
2016-10-29 21:20:22 +02:00
{
coap_pkt_t pdu;
uint8_t buf[GCOAP_PDU_BUF_SIZE];
2016-11-17 06:01:15 +01:00
sock_udp_ep_t remote;
2016-10-29 21:20:22 +02:00
gcoap_request_memo_t *memo = NULL;
uint8_t open_reqs = gcoap_op_state();
2016-10-29 21:20:22 +02:00
2016-11-17 06:01:15 +01:00
ssize_t res = sock_udp_recv(sock, buf, sizeof(buf),
open_reqs > 0 ? GCOAP_RECV_TIMEOUT : SOCK_NO_TIMEOUT,
&remote);
if (res <= 0) {
#if ENABLE_DEBUG
if (res < 0 && res != -ETIMEDOUT) {
DEBUG("gcoap: udp recv failure: %d\n", res);
}
#endif
return;
}
2016-10-29 21:20:22 +02:00
2016-11-17 06:01:15 +01:00
res = coap_parse(&pdu, buf, res);
if (res < 0) {
DEBUG("gcoap: parse failure: %d\n", res);
2016-10-29 21:20:22 +02:00
/* If a response, can't clear memo, but it will timeout later. */
2016-11-17 06:01:15 +01:00
return;
2016-10-29 21:20:22 +02:00
}
if (pdu.hdr->code == COAP_CODE_EMPTY) {
DEBUG("gcoap: empty messages not handled yet\n");
return;
2016-10-29 21:20:22 +02:00
/* incoming request */
} else if (coap_get_code_class(&pdu) == COAP_CLASS_REQ) {
if (coap_get_type(&pdu) == COAP_TYPE_NON
|| coap_get_type(&pdu) == COAP_TYPE_CON) {
size_t pdu_len = _handle_req(&pdu, buf, sizeof(buf), &remote);
if (pdu_len > 0) {
sock_udp_send(sock, buf, pdu_len, &remote);
}
}
else {
DEBUG("gcoap: illegal request type: %u\n", coap_get_type(&pdu));
return;
2016-10-29 21:20:22 +02:00
}
}
2016-10-29 21:20:22 +02:00
/* incoming response */
else {
_find_req_memo(&memo, &pdu, buf, sizeof(buf));
if (memo) {
xtimer_remove(&memo->response_timer);
memo->state = GCOAP_MEMO_RESP;
memo->resp_handler(memo->state, &pdu, &remote);
2016-10-29 21:20:22 +02:00
memo->state = GCOAP_MEMO_UNUSED;
}
}
}
/*
* Main request handler: generates response PDU in the provided buffer.
*
* Caller must finish the PDU and send it.
*
* return length of response pdu, or < 0 if can't handle
*/
static size_t _handle_req(coap_pkt_t *pdu, uint8_t *buf, size_t len,
sock_udp_ep_t *remote)
{
coap_resource_t *resource;
gcoap_listener_t *listener;
sock_udp_ep_t *observer = NULL;
gcoap_observe_memo_t *memo = NULL;
gcoap_observe_memo_t *resource_memo = NULL;
_find_resource(pdu, &resource, &listener);
if (resource == NULL) {
return gcoap_response(pdu, buf, len, COAP_CODE_PATH_NOT_FOUND);
}
else {
/* used below to ensure a memo not already recorded for the resource */
_find_obs_memo_resource(&resource_memo, resource);
}
if (coap_get_observe(pdu) == COAP_OBS_REGISTER) {
int empty_slot = _find_obs_memo(&memo, remote, pdu);
/* record observe memo */
if (memo == NULL) {
if (empty_slot >= 0 && resource_memo == NULL) {
int obs_slot = _find_observer(&observer, remote);
/* cache new observer */
if (observer == NULL) {
if (obs_slot >= 0) {
observer = &_coap_state.observers[obs_slot];
memcpy(observer, remote, sizeof(sock_udp_ep_t));
} else {
DEBUG("gcoap: can't register observer\n");
}
}
if (observer != NULL) {
memo = &_coap_state.observe_memos[empty_slot];
}
}
if (memo == NULL) {
coap_clear_observe(pdu);
DEBUG("gcoap: can't register observe memo\n");
}
}
if (memo != NULL) {
memo->observer = observer;
memo->resource = resource;
memo->token_len = coap_get_token_len(pdu);
if (memo->token_len) {
memcpy(&memo->token[0], pdu->token, memo->token_len);
}
DEBUG("gcoap: Registered observer for: %s\n", memo->resource->path);
/* generate initial notification value */
uint32_t now = xtimer_now_usec();
pdu->observe_value = (now >> GCOAP_OBS_TICK_EXPONENT) & 0xFFFFFF;
}
} else if (coap_get_observe(pdu) == COAP_OBS_DEREGISTER) {
_find_obs_memo(&memo, remote, pdu);
/* clear memo, and clear observer if no other memos */
if (memo != NULL) {
DEBUG("gcoap: Deregistering observer for: %s\n", memo->resource->path);
memo->observer = NULL;
memo = NULL;
_find_obs_memo(&memo, remote, NULL);
if (memo == NULL) {
_find_observer(&observer, remote);
if (observer != NULL) {
observer->family = AF_UNSPEC;
}
}
}
coap_clear_observe(pdu);
} else if (coap_has_observe(pdu)) {
/* bogus request; don't respond */
DEBUG("gcoap: Observe value unexpected: %" PRIu32 "\n", coap_get_observe(pdu));
return -1;
}
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;
}
/*
* Searches listener registrations for the resource matching the path in a PDU.
*
* param[out] resource_ptr -- found resource
* param[out] listener_ptr -- listener for found resource
2016-10-29 21:20:22 +02:00
*/
static void _find_resource(coap_pkt_t *pdu, coap_resource_t **resource_ptr,
gcoap_listener_t **listener_ptr)
2016-10-29 21:20:22 +02:00
{
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 {
*resource_ptr = resource;
*listener_ptr = listener;
return;
2016-10-29 21:20:22 +02:00
}
}
listener = listener->next;
}
/* resource not found */
*resource_ptr = NULL;
*listener_ptr = NULL;
2016-10-29 21:20:22 +02:00
}
/*
* 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: %i\n", (int)hdr_len);
2016-10-29 21:20:22 +02:00
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, NULL);
2016-10-29 21:20:22 +02:00
}
memo->state = GCOAP_MEMO_UNUSED;
}
else {
/* Response already handled; timeout must have fired while response */
/* was in queue. */
}
}
/*
* 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);
int plen = gcoap_get_resource_list(pdu->payload, (size_t)pdu->payload_len,
COAP_FORMAT_LINK);
2016-10-29 21:20:22 +02:00
/* response content */
return gcoap_finish(pdu, (size_t)plen, COAP_FORMAT_LINK);
2016-10-29 21:20:22 +02:00
}
/*
* 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 */
/* Observe for notification or registration response */
if (coap_get_code_class(pdu) == COAP_CLASS_SUCCESS && coap_has_observe(pdu)) {
uint32_t nval = htonl(pdu->observe_value);
uint8_t *nbyte = (uint8_t *)&nval;
unsigned i;
/* find address of non-zero MSB; max 3 bytes */
for (i = 1; i < 4; i++) {
if (*(nbyte+i) > 0) {
break;
}
}
bufpos += coap_put_option(bufpos, last_optnum, COAP_OPT_OBSERVE,
nbyte+i, 4-i);
last_optnum = COAP_OPT_OBSERVE;
}
2016-10-29 21:20:22 +02:00
/* 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] != '/') {
DEBUG("gcoap: _write_options: path does not start with '/'\n");
2016-10-29 21:20:22 +02:00
return -EINVAL;
}
bufpos += coap_put_option_uri(bufpos, last_optnum, (char *)pdu->url,
COAP_OPT_URI_PATH);
2016-10-29 21:20:22 +02:00
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);
last_optnum = COAP_OPT_CONTENT_FORMAT;
}
/* Uri-query for requests */
if (coap_get_code_class(pdu) == COAP_CLASS_REQ) {
bufpos += coap_put_option_uri(bufpos, last_optnum, (char *)pdu->qs,
COAP_OPT_URI_QUERY);
/* uncomment when further options are added below ... */
/* last_optnum = COAP_OPT_URI_QUERY; */
2016-10-29 21:20:22 +02:00
}
/* write payload marker */
if (pdu->payload_len) {
*bufpos++ = GCOAP_PAYLOAD_MARKER;
}
return bufpos - buf;
}
/*
* Find registered observer for a remote address and port.
*
* observer[out] -- Registered observer, or NULL if not found
* remote[in] -- Endpoint to match
*
* return Index of empty slot, suitable for registering new observer; or -1
* if no empty slots. Undefined if observer found.
*/
static int _find_observer(sock_udp_ep_t **observer, sock_udp_ep_t *remote)
{
int empty_slot = -1;
*observer = NULL;
for (unsigned i = 0; i < GCOAP_OBS_CLIENTS_MAX; i++) {
unsigned cmplen = 0;
if (_coap_state.observers[i].family == AF_UNSPEC) {
cmplen = 0;
empty_slot = i;
}
else if (_coap_state.observers[i].family == AF_INET6) {
cmplen = 16;
}
else {
cmplen = 4;
}
if (cmplen &&
memcmp(&_coap_state.observers[i].addr.ipv6[0], &remote->addr.ipv6[0],
cmplen) == 0
&& _coap_state.observers[i].port == remote->port) {
*observer = &_coap_state.observers[i];
break;
}
}
return empty_slot;
}
/*
* Find registered observe memo for a remote address and token.
*
* memo[out] -- Registered observe memo, or NULL if not found
* remote[in] -- Endpoint for address to match
* pdu[in] -- PDU for token to match, or NULL to match only on remote address
*
* return Index of empty slot, suitable for registering new memo; or -1 if no
* empty slots. Undefined if memo found.
*/
static int _find_obs_memo(gcoap_observe_memo_t **memo, sock_udp_ep_t *remote,
coap_pkt_t *pdu)
{
int empty_slot = -1;
*memo = NULL;
sock_udp_ep_t *remote_observer = NULL;
_find_observer(&remote_observer, remote);
for (unsigned i = 0; i < GCOAP_OBS_REGISTRATIONS_MAX; i++) {
if (_coap_state.observe_memos[i].observer == NULL) {
empty_slot = i;
continue;
}
if (_coap_state.observe_memos[i].observer == remote_observer) {
if (pdu == NULL) {
*memo = &_coap_state.observe_memos[i];
break;
}
if (_coap_state.observe_memos[i].token_len == coap_get_token_len(pdu)) {
unsigned cmplen = _coap_state.observe_memos[i].token_len;
if (cmplen &&
memcmp(&_coap_state.observe_memos[i].token[0], &pdu->token[0],
cmplen) == 0) {
*memo = &_coap_state.observe_memos[i];
break;
}
}
}
}
return empty_slot;
}
/*
* Find registered observe memo for a resource.
*
* memo[out] -- Registered observe memo, or NULL if not found
* resource[in] -- Resource to match
*/
static void _find_obs_memo_resource(gcoap_observe_memo_t **memo,
const coap_resource_t *resource)
{
*memo = NULL;
for (int i = 0; i < GCOAP_OBS_REGISTRATIONS_MAX; i++) {
if (_coap_state.observe_memos[i].observer != NULL
&& _coap_state.observe_memos[i].resource == resource) {
*memo = &_coap_state.observe_memos[i];
break;
}
}
}
2016-10-29 21:20:22 +02:00
/*
* 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");
mutex_init(&_coap_state.lock);
/* Blank lists so we know if an entry is available. */
2016-10-29 21:20:22 +02:00
memset(&_coap_state.open_reqs[0], 0, sizeof(_coap_state.open_reqs));
memset(&_coap_state.observers[0], 0, sizeof(_coap_state.observers));
memset(&_coap_state.observe_memos[0], 0, sizeof(_coap_state.observe_memos));
2016-10-29 21:20:22 +02:00
/* randomize initial value */
atomic_init(&_coap_state.next_message_id, (unsigned)random_uint32());
2016-10-29 21:20:22 +02:00
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;
2017-05-22 13:47:36 +02:00
while (_last->next) {
2016-10-29 21:20:22 +02:00
_last = _last->next;
2017-05-22 13:47:36 +02:00
}
2016-10-29 21:20:22 +02:00
listener->next = NULL;
_last->next = listener;
}
int gcoap_req_init(coap_pkt_t *pdu, uint8_t *buf, size_t len, unsigned code,
char *path) {
(void)len;
pdu->hdr = (coap_hdr_t *)buf;
memset(pdu->url, 0, NANOCOAP_URL_MAX);
memset(pdu->qs, 0, NANOCOAP_QS_MAX);
2016-10-29 21:20:22 +02:00
/* generate token */
#if GCOAP_TOKENLEN
uint8_t token[GCOAP_TOKENLEN];
2016-10-29 21:20:22 +02:00
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);
}
uint16_t msgid = (uint16_t)atomic_fetch_add(&_coap_state.next_message_id, 1);
ssize_t hdrlen = coap_build_hdr(pdu->hdr, COAP_TYPE_NON, &token[0], GCOAP_TOKENLEN,
code, msgid);
#else
uint16_t msgid = (uint16_t)atomic_fetch_add(&_coap_state.next_message_id, 1);
ssize_t hdrlen = coap_build_hdr(pdu->hdr, COAP_TYPE_NON, NULL, GCOAP_TOKENLEN,
code, msgid);
#endif
2016-10-29 21:20:22 +02:00
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);
}
2017-05-26 16:34:40 +02:00
size_t gcoap_req_send(const uint8_t *buf, size_t len, const ipv6_addr_t *addr,
uint16_t port, gcoap_resp_handler_t resp_handler)
2016-11-17 06:01:15 +01:00
{
sock_udp_ep_t remote;
remote.family = AF_INET6;
remote.netif = SOCK_ADDR_ANY_NETIF;
remote.port = port;
memcpy(&remote.addr.ipv6[0], &addr->u8[0], sizeof(addr->u8));
return gcoap_req_send2(buf, len, &remote, resp_handler);
}
2017-05-26 16:34:40 +02:00
size_t gcoap_req_send2(const uint8_t *buf, size_t len,
const sock_udp_ep_t *remote,
gcoap_resp_handler_t resp_handler)
2016-10-29 21:20:22 +02:00
{
gcoap_request_memo_t *memo = NULL;
2016-11-17 06:01:15 +01:00
assert(remote != NULL);
2016-10-29 21:20:22 +02:00
assert(resp_handler != NULL);
/* Find empty slot in list of open requests. */
mutex_lock(&_coap_state.lock);
2016-10-29 21:20:22 +02:00
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;
}
}
mutex_unlock(&_coap_state.lock);
2016-10-29 21:20:22 +02:00
if (memo) {
memcpy(&memo->hdr_buf[0], buf, GCOAP_HEADER_MAXLEN);
memo->resp_handler = resp_handler;
2016-11-17 06:01:15 +01:00
size_t res = sock_udp_send(&_sock, buf, len, remote);
2016-11-10 13:29:53 +01:00
if (res && (GCOAP_NON_TIMEOUT > 0)) {
2016-11-17 06:01:15 +01:00
/* interrupt sock listening (to set a listen timeout) */
msg_t mbox_msg;
mbox_msg.type = GCOAP_MSG_TYPE_INTR;
mbox_msg.content.value = 0;
if (mbox_try_put(&_sock.reg.mbox, &mbox_msg)) {
/* start response wait timer */
memo->timeout_msg.type = GCOAP_MSG_TYPE_TIMEOUT;
memo->timeout_msg.content.ptr = (char *)memo;
xtimer_set_msg(&memo->response_timer, GCOAP_NON_TIMEOUT,
&memo->timeout_msg, _pid);
}
else {
memo->state = GCOAP_MEMO_UNUSED;
DEBUG("gcoap: can't wake up mbox; no timeout for msg\n");
}
2016-10-29 21:20:22 +02:00
}
else if (!res) {
memo->state = GCOAP_MEMO_UNUSED;
2016-11-17 06:01:15 +01:00
DEBUG("gcoap: sock send failed: %d\n", res);
2016-10-29 21:20:22 +02:00
}
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)
{
if (coap_get_type(pdu) == COAP_TYPE_CON) {
coap_hdr_set_type(pdu->hdr, COAP_TYPE_ACK);
}
2016-10-29 21:20:22 +02:00
coap_hdr_set_code(pdu->hdr, code);
/* 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;
}
int gcoap_obs_init(coap_pkt_t *pdu, uint8_t *buf, size_t len,
const coap_resource_t *resource)
{
gcoap_observe_memo_t *memo = NULL;
_find_obs_memo_resource(&memo, resource);
if (memo == NULL) {
/* Unique return value to specify there is not an observer */
return GCOAP_OBS_INIT_UNUSED;
}
pdu->hdr = (coap_hdr_t *)buf;
uint16_t msgid = (uint16_t)atomic_fetch_add(&_coap_state.next_message_id, 1);
ssize_t hdrlen = coap_build_hdr(pdu->hdr, COAP_TYPE_NON, &memo->token[0],
memo->token_len, COAP_CODE_CONTENT, msgid);
if (hdrlen > 0) {
uint32_t now = xtimer_now_usec();
pdu->observe_value = (now >> GCOAP_OBS_TICK_EXPONENT) & 0xFFFFFF;
/* Reserve some space between the header and payload to write options later */
pdu->payload = buf + coap_get_total_hdr_len(pdu) + GCOAP_OBS_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 GCOAP_OBS_INIT_OK;
}
else {
/* reason for negative hdrlen is not defined, so we also are vague */
return GCOAP_OBS_INIT_ERR;
}
}
2017-05-26 16:34:40 +02:00
size_t gcoap_obs_send(const uint8_t *buf, size_t len,
const coap_resource_t *resource)
{
gcoap_observe_memo_t *memo = NULL;
_find_obs_memo_resource(&memo, resource);
if (memo) {
return sock_udp_send(&_sock, buf, len, memo->observer);
}
else {
return 0;
}
}
uint8_t gcoap_op_state(void)
2016-10-29 21:20:22 +02:00
{
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++;
}
}
return count;
2016-10-29 21:20:22 +02:00
}
int gcoap_get_resource_list(void *buf, size_t maxlen, uint8_t cf)
{
assert(cf == COAP_CT_LINK_FORMAT);
#ifndef DEVELHELP
(void)cf;
#endif
/* skip the first listener, gcoap itself (we skip /.well-known/core) */
gcoap_listener_t *listener = _coap_state.listeners->next;
char *out = (char *)buf;
size_t pos = 0;
/* write payload */
while (listener) {
coap_resource_t *resource = listener->resources;
for (unsigned i = 0; i < listener->resources_len; i++) {
size_t path_len = strlen(resource->path);
if (out) {
/* only add new resources if there is space in the buffer */
if ((pos + path_len + 3) > maxlen) {
break;
}
if (i) {
out[pos++] = ',';
}
out[pos++] = '<';
memcpy(&out[pos], resource->path, path_len);
pos += path_len;
out[pos++] = '>';
}
else {
pos += (i) ? 3 : 2;
pos += path_len;
}
++resource;
}
listener = listener->next;
}
return (int)pos;
}
int gcoap_add_qstring(coap_pkt_t *pdu, const char *key, const char *val)
{
size_t qs_len = strlen((char *)pdu->qs);
size_t key_len = strlen(key);
size_t val_len = (val) ? (strlen(val) + 1) : 0;
/* make sure if url_len + the new query string fit into the url buffer */
if ((qs_len + key_len + val_len + 2) >= NANOCOAP_QS_MAX) {
return -1;
}
pdu->qs[qs_len++] = '&';
memcpy(&pdu->qs[qs_len], key, key_len);
qs_len += key_len;
if (val) {
pdu->qs[qs_len++] = '=';
memcpy(&pdu->qs[qs_len], val, val_len);
qs_len += val_len;
}
pdu->qs[qs_len] = '\0';
return (int)qs_len;
}
2016-10-29 21:20:22 +02:00
/** @} */