mirror of
https://github.com/obgm/libcoap.git
synced 2026-11-12 11:40:39 +03:00
the old implementation stuffed the pdu struct in front of the transport-level pbuf, and preserved it on the stack while the packages were sent. this had two advantages: * saving ram * preserving the other implementation's structure of pdu->hdr == pdu+1 (ie. starting right after the pdu struct) but two disadvantages: * bad readability and pointer juggling in the send routine * relying on the send function to block until the package is out (which works for simple implementations of lwip drivers but not dma etc) if something really assumes the contiguous memory structure, it will show (unfortunately by corrupting other pdu structs), and chances are people use ->hdr anyway.
1694 lines
46 KiB
C
1694 lines
46 KiB
C
/* net.c -- CoAP network interface
|
|
*
|
|
* Copyright (C) 2010--2014 Olaf Bergmann <bergmann@tzi.org>
|
|
*
|
|
* This file is part of the CoAP library libcoap. Please see
|
|
* README for terms of use.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <ctype.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#ifdef HAVE_LIMITS_H
|
|
#include <limits.h>
|
|
#endif
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#elif HAVE_SYS_UNISTD_H
|
|
#include <sys/unistd.h>
|
|
#endif
|
|
#include <sys/types.h>
|
|
#ifdef HAVE_SYS_SOCKET_H
|
|
#include <sys/socket.h>
|
|
#endif
|
|
#ifdef HAVE_NETINET_IN_H
|
|
#include <netinet/in.h>
|
|
#endif
|
|
#ifdef HAVE_ARPA_INET_H
|
|
#include <arpa/inet.h>
|
|
#endif
|
|
|
|
#ifdef WITH_LWIP
|
|
#include <lwip/pbuf.h>
|
|
#include <lwip/udp.h>
|
|
#include <lwip/timers.h>
|
|
#endif
|
|
|
|
#include "debug.h"
|
|
#include "mem.h"
|
|
#include "str.h"
|
|
#include "async.h"
|
|
#include "resource.h"
|
|
#include "option.h"
|
|
#include "encode.h"
|
|
#include "block.h"
|
|
#include "net.h"
|
|
|
|
#if defined(WITH_POSIX)
|
|
|
|
time_t clock_offset;
|
|
|
|
static inline coap_queue_t *
|
|
coap_malloc_node(void) {
|
|
return (coap_queue_t *)coap_malloc_type(COAP_NODE, sizeof(coap_queue_t));
|
|
}
|
|
|
|
static inline void
|
|
coap_free_node(coap_queue_t *node) {
|
|
coap_free_type(COAP_NODE, node);
|
|
}
|
|
#endif /* WITH_POSIX */
|
|
#ifdef WITH_LWIP
|
|
|
|
#include <lwip/memp.h>
|
|
|
|
static void coap_retransmittimer_execute(void *arg);
|
|
static void coap_retransmittimer_restart(coap_context_t *ctx);
|
|
|
|
static inline coap_queue_t *
|
|
coap_malloc_node() {
|
|
return (coap_queue_t *)memp_malloc(MEMP_COAP_NODE);
|
|
}
|
|
|
|
static inline void
|
|
coap_free_node(coap_queue_t *node) {
|
|
memp_free(MEMP_COAP_NODE, node);
|
|
}
|
|
|
|
#endif /* WITH_LWIP */
|
|
#ifdef WITH_CONTIKI
|
|
# ifndef DEBUG
|
|
# define DEBUG DEBUG_PRINT
|
|
# endif /* DEBUG */
|
|
|
|
#include "mem.h"
|
|
#include "memb.h"
|
|
#include "net/ip/uip-debug.h"
|
|
|
|
clock_time_t clock_offset;
|
|
|
|
#define UIP_IP_BUF ((struct uip_ip_hdr *)&uip_buf[UIP_LLH_LEN])
|
|
#define UIP_UDP_BUF ((struct uip_udp_hdr *)&uip_buf[UIP_LLIPH_LEN])
|
|
|
|
void coap_resources_init();
|
|
void coap_pdu_resources_init();
|
|
|
|
unsigned char initialized = 0;
|
|
coap_context_t the_coap_context;
|
|
|
|
MEMB(node_storage, coap_queue_t, COAP_PDU_MAXCNT);
|
|
|
|
PROCESS(coap_retransmit_process, "message retransmit process");
|
|
|
|
static inline coap_queue_t *
|
|
coap_malloc_node() {
|
|
return (coap_queue_t *)memb_alloc(&node_storage);
|
|
}
|
|
|
|
static inline void
|
|
coap_free_node(coap_queue_t *node) {
|
|
memb_free(&node_storage, node);
|
|
}
|
|
#endif /* WITH_CONTIKI */
|
|
|
|
int print_wellknown(coap_context_t *, unsigned char *, size_t *, size_t, coap_opt_t *);
|
|
|
|
void coap_handle_failed_notify(coap_context_t *, const coap_address_t *,
|
|
const str *);
|
|
|
|
unsigned int
|
|
coap_adjust_basetime(coap_context_t *ctx, coap_tick_t now) {
|
|
unsigned int result = 0;
|
|
coap_tick_diff_t delta = now - ctx->sendqueue_basetime;
|
|
|
|
if (ctx->sendqueue) {
|
|
/* delta < 0 means that the new time stamp is before the old. */
|
|
if (delta <= 0) {
|
|
ctx->sendqueue->t -= delta;
|
|
} else {
|
|
/* This case is more complex: The time must be advanced forward,
|
|
* thus possibly leading to timed out elements at the queue's
|
|
* start. For every element that has timed out, its relative
|
|
* time is set to zero and the result counter is increased. */
|
|
|
|
coap_queue_t *q = ctx->sendqueue;
|
|
coap_tick_t t = 0;
|
|
while (q && (t + q->t < (coap_tick_t)delta)) {
|
|
t += q->t;
|
|
q->t = 0;
|
|
result++;
|
|
q = q->next;
|
|
}
|
|
|
|
/* finally adjust the first element that has not expired */
|
|
if (q) {
|
|
q->t = (coap_tick_t)delta - t;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* adjust basetime */
|
|
ctx->sendqueue_basetime += delta;
|
|
|
|
return result;
|
|
}
|
|
|
|
int
|
|
coap_insert_node(coap_queue_t **queue, coap_queue_t *node) {
|
|
coap_queue_t *p, *q;
|
|
if ( !queue || !node )
|
|
return 0;
|
|
|
|
/* set queue head if empty */
|
|
if ( !*queue ) {
|
|
*queue = node;
|
|
return 1;
|
|
}
|
|
|
|
/* replace queue head if PDU's time is less than head's time */
|
|
q = *queue;
|
|
if (node->t < q->t) {
|
|
node->next = q;
|
|
*queue = node;
|
|
q->t -= node->t; /* make q->t relative to node->t */
|
|
return 1;
|
|
}
|
|
|
|
/* search for right place to insert */
|
|
do {
|
|
node->t -= q->t; /* make node-> relative to q->t */
|
|
p = q;
|
|
q = q->next;
|
|
} while (q && q->t <= node->t);
|
|
|
|
/* insert new item */
|
|
if (q) {
|
|
q->t -= node->t; /* make q->t relative to node->t */
|
|
}
|
|
node->next = q;
|
|
p->next = node;
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
coap_delete_node(coap_queue_t *node) {
|
|
if ( !node )
|
|
return 0;
|
|
|
|
coap_delete_pdu(node->pdu);
|
|
coap_free_node(node);
|
|
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
coap_delete_all(coap_queue_t *queue) {
|
|
if ( !queue )
|
|
return;
|
|
|
|
coap_delete_all( queue->next );
|
|
coap_delete_node( queue );
|
|
}
|
|
|
|
coap_queue_t *
|
|
coap_new_node(void) {
|
|
coap_queue_t *node;
|
|
node = coap_malloc_node();
|
|
|
|
if ( ! node ) {
|
|
#ifndef NDEBUG
|
|
coap_log(LOG_WARNING, "coap_new_node: malloc\n");
|
|
#endif
|
|
return NULL;
|
|
}
|
|
|
|
memset(node, 0, sizeof(*node));
|
|
return node;
|
|
}
|
|
|
|
coap_queue_t *
|
|
coap_peek_next( coap_context_t *context ) {
|
|
if ( !context || !context->sendqueue )
|
|
return NULL;
|
|
|
|
return context->sendqueue;
|
|
}
|
|
|
|
coap_queue_t *
|
|
coap_pop_next( coap_context_t *context ) {
|
|
coap_queue_t *next;
|
|
|
|
if ( !context || !context->sendqueue )
|
|
return NULL;
|
|
|
|
next = context->sendqueue;
|
|
context->sendqueue = context->sendqueue->next;
|
|
if (context->sendqueue) {
|
|
context->sendqueue->t += next->t;
|
|
}
|
|
next->next = NULL;
|
|
return next;
|
|
}
|
|
|
|
#ifdef COAP_DEFAULT_WKC_HASHKEY
|
|
/** Checks if @p Key is equal to the pre-defined hash key for.well-known/core. */
|
|
#define is_wkc(Key) \
|
|
(memcmp((Key), COAP_DEFAULT_WKC_HASHKEY, sizeof(coap_key_t)) == 0)
|
|
#else
|
|
/* Implements a singleton to store a hash key for the .wellknown/core
|
|
* resources. */
|
|
int
|
|
is_wkc(coap_key_t k) {
|
|
static coap_key_t wkc;
|
|
static unsigned char _initialized = 0;
|
|
if (!_initialized) {
|
|
_initialized = coap_hash_path((unsigned char *)COAP_DEFAULT_URI_WELLKNOWN,
|
|
sizeof(COAP_DEFAULT_URI_WELLKNOWN) - 1, wkc);
|
|
}
|
|
return memcmp(k, wkc, sizeof(coap_key_t)) == 0;
|
|
}
|
|
#endif
|
|
|
|
coap_context_t *
|
|
coap_new_context(
|
|
const coap_address_t *listen_addr) {
|
|
#ifndef WITH_CONTIKI
|
|
coap_context_t *c = coap_malloc_type(COAP_CONTEXT, sizeof( coap_context_t ) );
|
|
#endif /* not WITH_CONTIKI */
|
|
#ifdef WITH_CONTIKI
|
|
coap_context_t *c;
|
|
|
|
if (initialized)
|
|
return NULL;
|
|
#endif /* WITH_CONTIKI */
|
|
|
|
if (!listen_addr) {
|
|
coap_log(LOG_EMERG, "no listen address specified\n");
|
|
return NULL;
|
|
}
|
|
|
|
coap_clock_init();
|
|
#ifdef WITH_LWIP
|
|
prng_init(LWIP_RAND());
|
|
#endif /* WITH_LWIP */
|
|
#ifdef WITH_CONTIKI
|
|
prng_init((ptrdiff_t)listen_addr ^ clock_offset);
|
|
#endif /* WITH_LWIP */
|
|
#ifdef WITH_POSIX
|
|
prng_init((unsigned long)listen_addr ^ clock_offset);
|
|
#endif /* WITH_POSIX */
|
|
|
|
#ifndef WITH_CONTIKI
|
|
if (!c) {
|
|
#ifndef NDEBUG
|
|
coap_log(LOG_EMERG, "coap_init: malloc:\n");
|
|
#endif
|
|
return NULL;
|
|
}
|
|
#endif /* not WITH_CONTIKI */
|
|
#ifdef WITH_CONTIKI
|
|
coap_resources_init();
|
|
coap_pdu_resources_init();
|
|
coap_memory_init();
|
|
|
|
c = &the_coap_context;
|
|
initialized = 1;
|
|
#endif /* WITH_CONTIKI */
|
|
|
|
memset(c, 0, sizeof( coap_context_t ) );
|
|
|
|
/* initialize message id */
|
|
prng((unsigned char *)&c->message_id, sizeof(unsigned short));
|
|
|
|
/* register the critical options that we know */
|
|
coap_register_option(c, COAP_OPTION_IF_MATCH);
|
|
coap_register_option(c, COAP_OPTION_URI_HOST);
|
|
coap_register_option(c, COAP_OPTION_IF_NONE_MATCH);
|
|
coap_register_option(c, COAP_OPTION_URI_PORT);
|
|
coap_register_option(c, COAP_OPTION_URI_PATH);
|
|
coap_register_option(c, COAP_OPTION_URI_QUERY);
|
|
coap_register_option(c, COAP_OPTION_ACCEPT);
|
|
coap_register_option(c, COAP_OPTION_PROXY_URI);
|
|
coap_register_option(c, COAP_OPTION_PROXY_SCHEME);
|
|
coap_register_option(c, COAP_OPTION_BLOCK2);
|
|
coap_register_option(c, COAP_OPTION_BLOCK1);
|
|
|
|
c->endpoint = coap_new_endpoint(listen_addr, COAP_ENDPOINT_NOSEC);
|
|
#ifdef WITH_LWIP
|
|
c->endpoint->context = c;
|
|
#endif
|
|
if (c->endpoint == NULL) {
|
|
goto onerror;
|
|
}
|
|
#ifndef WITH_CONTIKI
|
|
#ifdef WITH_POSIX
|
|
c->sockfd = c->endpoint->handle;
|
|
#endif
|
|
|
|
return c;
|
|
|
|
onerror:
|
|
coap_free_type(COAP_CONTEXT, c);
|
|
return NULL;
|
|
|
|
#endif /* not WITH_CONTIKI */
|
|
#ifdef WITH_CONTIKI
|
|
c->conn = udp_new(NULL, 0, NULL);
|
|
udp_bind(c->conn, listen_addr->port);
|
|
|
|
process_start(&coap_retransmit_process, (char *)c);
|
|
|
|
PROCESS_CONTEXT_BEGIN(&coap_retransmit_process);
|
|
#ifndef WITHOUT_OBSERVE
|
|
etimer_set(&c->notify_timer, COAP_RESOURCE_CHECK_TIME * COAP_TICKS_PER_SECOND);
|
|
#endif /* WITHOUT_OBSERVE */
|
|
/* the retransmit timer must be initialized to some large value */
|
|
etimer_set(&the_coap_context.retransmit_timer, 0xFFFF);
|
|
PROCESS_CONTEXT_END(&coap_retransmit_process);
|
|
return c;
|
|
#endif /* WITH_CONTIKI */
|
|
}
|
|
|
|
void
|
|
coap_free_context(coap_context_t *context) {
|
|
#if defined(WITH_POSIX) || defined(WITH_LWIP)
|
|
coap_resource_t *res;
|
|
#ifndef COAP_RESOURCES_NOHASH
|
|
coap_resource_t *rtmp;
|
|
#endif
|
|
#endif /* WITH_POSIX || WITH_LWIP */
|
|
if (!context)
|
|
return;
|
|
|
|
coap_delete_all(context->sendqueue);
|
|
|
|
#ifdef WITH_LWIP
|
|
context->sendqueue = NULL;
|
|
coap_retransmittimer_restart(context);
|
|
#endif
|
|
|
|
#if defined(WITH_POSIX) || defined(WITH_LWIP)
|
|
#ifdef COAP_RESOURCES_NOHASH
|
|
LL_FOREACH(context->resources, res) {
|
|
#else
|
|
HASH_ITER(hh, context->resources, res, rtmp) {
|
|
#endif
|
|
coap_delete_resource(context, res->key);
|
|
}
|
|
#endif /* WITH_POSIX || WITH_LWIP */
|
|
|
|
coap_free_endpoint(context->endpoint);
|
|
#ifndef WITH_CONTIKI
|
|
coap_free_type(COAP_CONTEXT, context);
|
|
#endif/* not WITH_CONTIKI */
|
|
#ifdef WITH_CONTIKI
|
|
memset(&the_coap_context, 0, sizeof(coap_context_t));
|
|
initialized = 0;
|
|
#endif /* WITH_CONTIKI */
|
|
}
|
|
|
|
int
|
|
coap_option_check_critical(coap_context_t *ctx,
|
|
coap_pdu_t *pdu,
|
|
coap_opt_filter_t unknown) {
|
|
|
|
coap_opt_iterator_t opt_iter;
|
|
int ok = 1;
|
|
|
|
coap_option_iterator_init(pdu, &opt_iter, COAP_OPT_ALL);
|
|
|
|
while (coap_option_next(&opt_iter)) {
|
|
|
|
/* The following condition makes use of the fact that
|
|
* coap_option_getb() returns -1 if type exceeds the bit-vector
|
|
* filter. As the vector is supposed to be large enough to hold
|
|
* the largest known option, we know that everything beyond is
|
|
* bad.
|
|
*/
|
|
if (opt_iter.type & 0x01 &&
|
|
coap_option_getb(ctx->known_options, opt_iter.type) < 1) {
|
|
debug("unknown critical option %d\n", opt_iter.type);
|
|
|
|
ok = 0;
|
|
|
|
/* When opt_iter.type is beyond our known option range,
|
|
* coap_option_setb() will return -1 and we are safe to leave
|
|
* this loop. */
|
|
if (coap_option_setb(unknown, opt_iter.type) == -1)
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
void
|
|
coap_transaction_id(const coap_address_t *peer, const coap_pdu_t *pdu,
|
|
coap_tid_t *id) {
|
|
coap_key_t h;
|
|
|
|
memset(h, 0, sizeof(coap_key_t));
|
|
|
|
/* Compare the transport address. */
|
|
|
|
#ifdef WITH_POSIX
|
|
switch (peer->addr.sa.sa_family) {
|
|
case AF_INET:
|
|
coap_hash((const unsigned char *)&peer->addr.sin.sin_port,
|
|
sizeof(peer->addr.sin.sin_port), h);
|
|
coap_hash((const unsigned char *)&peer->addr.sin.sin_addr,
|
|
sizeof(peer->addr.sin.sin_addr), h);
|
|
break;
|
|
case AF_INET6:
|
|
coap_hash((const unsigned char *)&peer->addr.sin6.sin6_port,
|
|
sizeof(peer->addr.sin6.sin6_port), h);
|
|
coap_hash((const unsigned char *)&peer->addr.sin6.sin6_addr,
|
|
sizeof(peer->addr.sin6.sin6_addr), h);
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
#endif
|
|
#if defined(WITH_LWIP) || defined(WITH_CONTIKI)
|
|
/* FIXME: with lwip, we can do better */
|
|
coap_hash((const unsigned char *)&peer->port, sizeof(peer->port), h);
|
|
coap_hash((const unsigned char *)&peer->addr, sizeof(peer->addr), h);
|
|
#endif /* WITH_LWIP || WITH_CONTIKI */
|
|
|
|
coap_hash((const unsigned char *)&pdu->hdr->id, sizeof(unsigned short), h);
|
|
|
|
*id = ((h[0] << 8) | h[1]) ^ ((h[2] << 8) | h[3]);
|
|
}
|
|
|
|
coap_tid_t
|
|
coap_send_ack(coap_context_t *context,
|
|
const coap_endpoint_t *local_interface,
|
|
const coap_address_t *dst,
|
|
coap_pdu_t *request) {
|
|
coap_pdu_t *response;
|
|
coap_tid_t result = COAP_INVALID_TID;
|
|
|
|
if (request && request->hdr->type == COAP_MESSAGE_CON) {
|
|
response = coap_pdu_init(COAP_MESSAGE_ACK, 0, request->hdr->id,
|
|
sizeof(coap_pdu_t));
|
|
if (response) {
|
|
result = coap_send(context, local_interface, dst, response);
|
|
coap_delete_pdu(response);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#ifdef WITH_POSIX
|
|
/* releases space allocated by PDU if free_pdu is set */
|
|
coap_tid_t
|
|
coap_send_impl(coap_context_t *context,
|
|
const coap_endpoint_t *local_interface,
|
|
const coap_address_t *dst,
|
|
coap_pdu_t *pdu) {
|
|
ssize_t bytes_written;
|
|
coap_tid_t id = COAP_INVALID_TID;
|
|
|
|
if ( !context || !dst || !pdu )
|
|
return id;
|
|
|
|
bytes_written = coap_network_send(context, local_interface, dst,
|
|
(unsigned char *)pdu->hdr, pdu->length);
|
|
|
|
if (bytes_written >= 0) {
|
|
coap_transaction_id(dst, pdu, &id);
|
|
} else {
|
|
coap_log(LOG_CRIT, "coap_send_impl: %s\n", strerror(errno));
|
|
}
|
|
|
|
return id;
|
|
}
|
|
#endif /* WITH_POSIX */
|
|
#ifdef WITH_LWIP
|
|
coap_tid_t
|
|
coap_send_impl(coap_context_t *context,
|
|
const coap_endpoint_t *local_interface,
|
|
const coap_address_t *dst,
|
|
coap_pdu_t *pdu) {
|
|
coap_tid_t id = COAP_INVALID_TID;
|
|
uint8_t err;
|
|
char *data_backup;
|
|
|
|
if ( !context || !dst || !pdu )
|
|
{
|
|
return id;
|
|
}
|
|
|
|
data_backup = pdu->data;
|
|
|
|
/* FIXME: we can't check this here with the existing infrastructure, but we
|
|
* should actually check that the pdu is not held by anyone but us. the
|
|
* respective pbuf is already exclusively owned by the pdu. */
|
|
|
|
pbuf_realloc(pdu->pbuf, pdu->length);
|
|
|
|
coap_transaction_id(dst, pdu, &id);
|
|
|
|
udp_sendto(context->endpoint->pcb, pdu->pbuf,
|
|
&dst->addr, dst->port);
|
|
|
|
return id;
|
|
}
|
|
#endif /* WITH_LWIP */
|
|
|
|
coap_tid_t
|
|
coap_send(coap_context_t *context,
|
|
const coap_endpoint_t *local_interface,
|
|
const coap_address_t *dst,
|
|
coap_pdu_t *pdu) {
|
|
return coap_send_impl(context, local_interface, dst, pdu);
|
|
}
|
|
|
|
coap_tid_t
|
|
coap_send_error(coap_context_t *context,
|
|
coap_pdu_t *request,
|
|
const coap_endpoint_t *local_interface,
|
|
const coap_address_t *dst,
|
|
unsigned char code,
|
|
coap_opt_filter_t opts) {
|
|
coap_pdu_t *response;
|
|
coap_tid_t result = COAP_INVALID_TID;
|
|
|
|
assert(request);
|
|
assert(dst);
|
|
|
|
response = coap_new_error_response(request, code, opts);
|
|
if (response) {
|
|
result = coap_send(context, local_interface, dst, response);
|
|
coap_delete_pdu(response);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
coap_tid_t
|
|
coap_send_message_type(coap_context_t *context,
|
|
const coap_endpoint_t *local_interface,
|
|
const coap_address_t *dst,
|
|
coap_pdu_t *request,
|
|
unsigned char type) {
|
|
coap_pdu_t *response;
|
|
coap_tid_t result = COAP_INVALID_TID;
|
|
|
|
if (request) {
|
|
response = coap_pdu_init(type, 0, request->hdr->id, sizeof(coap_pdu_t));
|
|
if (response) {
|
|
result = coap_send(context, local_interface, dst, response);
|
|
coap_delete_pdu(response);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
coap_tid_t
|
|
coap_send_confirmed(coap_context_t *context,
|
|
const coap_endpoint_t *local_interface,
|
|
const coap_address_t *dst,
|
|
coap_pdu_t *pdu) {
|
|
coap_queue_t *node;
|
|
coap_tick_t now;
|
|
int r;
|
|
|
|
node = coap_new_node();
|
|
if (!node) {
|
|
debug("coap_send_confirmed: insufficient memory\n");
|
|
return COAP_INVALID_TID;
|
|
}
|
|
|
|
node->id = coap_send_impl(context, local_interface, dst, pdu);
|
|
if (COAP_INVALID_TID == node->id) {
|
|
debug("coap_send_confirmed: error sending pdu\n");
|
|
coap_free_node(node);
|
|
return COAP_INVALID_TID;
|
|
}
|
|
|
|
prng((unsigned char *)&r,sizeof(r));
|
|
|
|
/* add randomized RESPONSE_TIMEOUT to determine retransmission timeout */
|
|
node->timeout = COAP_DEFAULT_RESPONSE_TIMEOUT * COAP_TICKS_PER_SECOND +
|
|
(COAP_DEFAULT_RESPONSE_TIMEOUT >> 1) *
|
|
((COAP_TICKS_PER_SECOND * (r & 0xFF)) >> 8);
|
|
|
|
node->local_if = *local_interface;
|
|
memcpy(&node->remote, dst, sizeof(coap_address_t));
|
|
node->pdu = pdu;
|
|
|
|
/* Set timer for pdu retransmission. If this is the first element in
|
|
* the retransmission queue, the base time is set to the current
|
|
* time and the retransmission time is node->timeout. If there is
|
|
* already an entry in the sendqueue, we must check if this node is
|
|
* to be retransmitted earlier. Therefore, node->timeout is first
|
|
* normalized to the base time and then inserted into the queue with
|
|
* an adjusted relative time.
|
|
*/
|
|
coap_ticks(&now);
|
|
if (context->sendqueue == NULL) {
|
|
node->t = node->timeout;
|
|
context->sendqueue_basetime = now;
|
|
} else {
|
|
/* make node->t relative to context->sendqueue_basetime */
|
|
node->t = (now - context->sendqueue_basetime) + node->timeout;
|
|
}
|
|
|
|
coap_insert_node(&context->sendqueue, node);
|
|
|
|
#ifdef WITH_LWIP
|
|
if (node == context->sendqueue) /* don't bother with timer stuff if there are earlier retransmits */
|
|
coap_retransmittimer_restart(context);
|
|
#endif
|
|
|
|
#ifdef WITH_CONTIKI
|
|
{ /* (re-)initialize retransmission timer */
|
|
coap_queue_t *nextpdu;
|
|
|
|
nextpdu = coap_peek_next(context);
|
|
assert(nextpdu); /* we have just inserted a node */
|
|
|
|
/* must set timer within the context of the retransmit process */
|
|
PROCESS_CONTEXT_BEGIN(&coap_retransmit_process);
|
|
etimer_set(&context->retransmit_timer, nextpdu->t);
|
|
PROCESS_CONTEXT_END(&coap_retransmit_process);
|
|
}
|
|
#endif /* WITH_CONTIKI */
|
|
|
|
return node->id;
|
|
}
|
|
|
|
coap_tid_t
|
|
coap_retransmit(coap_context_t *context, coap_queue_t *node) {
|
|
if (!context || !node)
|
|
return COAP_INVALID_TID;
|
|
|
|
/* re-initialize timeout when maximum number of retransmissions are not reached yet */
|
|
if (node->retransmit_cnt < COAP_DEFAULT_MAX_RETRANSMIT) {
|
|
node->retransmit_cnt++;
|
|
node->t = node->timeout << node->retransmit_cnt;
|
|
coap_insert_node(&context->sendqueue, node);
|
|
#ifdef WITH_LWIP
|
|
if (node == context->sendqueue) /* don't bother with timer stuff if there are earlier retransmits */
|
|
coap_retransmittimer_restart(context);
|
|
#endif
|
|
|
|
debug("** retransmission #%d of transaction %d\n",
|
|
node->retransmit_cnt, ntohs(node->pdu->hdr->id));
|
|
|
|
node->id = coap_send_impl(context, &node->local_if,
|
|
&node->remote, node->pdu);
|
|
return node->id;
|
|
}
|
|
|
|
/* no more retransmissions, remove node from system */
|
|
|
|
#ifndef WITH_CONTIKI
|
|
debug("** removed transaction %d\n", ntohs(node->id));
|
|
#endif
|
|
|
|
#ifndef WITHOUT_OBSERVE
|
|
/* Check if subscriptions exist that should be canceled after
|
|
COAP_MAX_NOTIFY_FAILURES */
|
|
if (node->pdu->hdr->code >= 64) {
|
|
str token = { 0, NULL };
|
|
|
|
token.length = node->pdu->hdr->token_length;
|
|
token.s = node->pdu->hdr->token;
|
|
|
|
coap_handle_failed_notify(context, &node->remote, &token);
|
|
}
|
|
#endif /* WITHOUT_OBSERVE */
|
|
|
|
/* And finally delete the node */
|
|
coap_delete_node( node );
|
|
return COAP_INVALID_TID;
|
|
}
|
|
|
|
/**
|
|
* Checks if @p opt fits into the message that ends with @p maxpos.
|
|
* This function returns @c 1 on success, or @c 0 if the option @p opt
|
|
* would exceed @p maxpos.
|
|
*/
|
|
static inline int
|
|
check_opt_size(coap_opt_t *opt, unsigned char *maxpos) {
|
|
if (opt && opt < maxpos) {
|
|
if (((*opt & 0x0f) < 0x0f) || (opt + 1 < maxpos))
|
|
return opt + COAP_OPT_SIZE(opt) < maxpos;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void coap_dispatch(coap_context_t *context, coap_queue_t *rcvd);
|
|
|
|
int
|
|
coap_read( coap_context_t *ctx ) {
|
|
#ifdef WITH_CONTIKI
|
|
char *buf;
|
|
#endif
|
|
ssize_t bytes_read = -1;
|
|
coap_packet_t *packet;
|
|
coap_address_t src;
|
|
int result = -1; /* the value to be returned */
|
|
|
|
#ifdef WITH_CONTIKI
|
|
buf = uip_appdata;
|
|
#endif /* WITH_CONTIKI */
|
|
|
|
coap_address_init(&src);
|
|
|
|
#ifdef WITH_POSIX
|
|
bytes_read = coap_network_read(ctx->endpoint, &packet);
|
|
#endif /* WITH_POSIX */
|
|
#ifdef WITH_CONTIKI
|
|
if(uip_newdata()) {
|
|
uip_ipaddr_copy(&src.addr, &UIP_IP_BUF->srcipaddr);
|
|
src.port = UIP_UDP_BUF->srcport;
|
|
uip_ipaddr_copy(&dst.addr, &UIP_IP_BUF->destipaddr);
|
|
dst.port = UIP_UDP_BUF->destport;
|
|
|
|
bytes_read = uip_datalen();
|
|
((char *)uip_appdata)[bytes_read] = 0;
|
|
PRINTF("Server received %d bytes from [", (int)bytes_read);
|
|
PRINT6ADDR(&src.addr);
|
|
PRINTF("]:%d\n", uip_ntohs(src.port));
|
|
}
|
|
#endif /* WITH_CONTIKI */
|
|
|
|
if ( bytes_read < 0 ) {
|
|
warn("coap_read: recvfrom");
|
|
} else {
|
|
#ifdef WITH_POSIX
|
|
/* FIXME: make sure that dst == ctx->endpoint->addr */
|
|
/* disabled for violating the packet abstraction
|
|
if (coap_address_isany(&ctx->endpoint->addr) ||
|
|
coap_address_equals(&packet->dst, &ctx->endpoint->addr)) {
|
|
*/
|
|
result = coap_handle_message(ctx, packet);
|
|
/*
|
|
} else {
|
|
coap_log(LOG_DEBUG, "packet received on wrong interface, dropped\n");
|
|
}
|
|
*/
|
|
#endif /* WITH_POSIX */
|
|
#ifdef WITH_CONTIKI
|
|
result = coap_handle_message(ctx, &src, uip_appdata, bytes_read);
|
|
#endif /* WITH_CONTIKI */
|
|
}
|
|
|
|
coap_free_packet(packet);
|
|
|
|
return result;
|
|
}
|
|
|
|
int
|
|
coap_handle_message(coap_context_t *ctx,
|
|
coap_packet_t *packet) {
|
|
/* const coap_address_t *remote, */
|
|
/* unsigned char *msg, size_t msg_len) { */
|
|
unsigned char *msg;
|
|
size_t msg_len;
|
|
coap_queue_t *node;
|
|
|
|
/* the negated result code */
|
|
enum result_t { RESULT_OK, RESULT_ERR_EARLY, RESULT_ERR };
|
|
int result = RESULT_ERR_EARLY;
|
|
|
|
coap_packet_get_memmapped(packet, &msg, &msg_len);
|
|
|
|
if (msg_len < sizeof(coap_hdr_t)) {
|
|
debug("coap_handle_message: discarded invalid frame\n" );
|
|
goto error_early;
|
|
}
|
|
|
|
/* check version identifier */
|
|
if (((*msg >> 6) & 0x03) != COAP_DEFAULT_VERSION) {
|
|
debug("coap_handle_message: unknown protocol version %d\n", (*msg >> 6) & 0x03);
|
|
goto error_early;
|
|
}
|
|
|
|
node = coap_new_node();
|
|
if (!node) {
|
|
goto error_early;
|
|
}
|
|
|
|
/* from this point, the result code indicates that */
|
|
result = RESULT_ERR;
|
|
|
|
node->pdu = coap_pdu_init(0, 0, 0, msg_len);
|
|
if (!node->pdu) {
|
|
goto error;
|
|
}
|
|
|
|
if (!coap_pdu_parse(msg, msg_len, node->pdu)) {
|
|
warn("discard malformed PDU");
|
|
goto error;
|
|
}
|
|
|
|
coap_ticks(&node->t);
|
|
|
|
coap_packet_populate_endpoint(packet, &node->local_if);
|
|
coap_packet_copy_source(packet, &node->remote);
|
|
|
|
/* and add new node to receive queue */
|
|
coap_transaction_id(&node->remote, node->pdu, &node->id);
|
|
|
|
#ifndef NDEBUG
|
|
if (LOG_DEBUG <= coap_get_log_level()) {
|
|
#ifndef INET6_ADDRSTRLEN
|
|
#define INET6_ADDRSTRLEN 40
|
|
#endif
|
|
unsigned char addr[INET6_ADDRSTRLEN+8], localaddr[INET6_ADDRSTRLEN+8];
|
|
|
|
/** @FIXME get debug to work again **
|
|
if (coap_print_addr(remote, addr, INET6_ADDRSTRLEN+8) &&
|
|
coap_print_addr(&packet->dst, localaddr, INET6_ADDRSTRLEN+8) )
|
|
debug("** received %d bytes from %s on interface %s:\n",
|
|
(int)msg_len, addr, localaddr);
|
|
|
|
*/
|
|
coap_show_pdu(node->pdu);
|
|
}
|
|
#endif
|
|
|
|
coap_dispatch(ctx, node);
|
|
return -RESULT_OK;
|
|
|
|
error:
|
|
/* FIXME: send back RST? */
|
|
coap_delete_node(node);
|
|
return -result;
|
|
|
|
error_early:
|
|
return -result;
|
|
}
|
|
|
|
int
|
|
coap_remove_from_queue(coap_queue_t **queue, coap_tid_t id, coap_queue_t **node) {
|
|
coap_queue_t *p, *q;
|
|
|
|
if ( !queue || !*queue)
|
|
return 0;
|
|
|
|
/* replace queue head if PDU's time is less than head's time */
|
|
|
|
if ( id == (*queue)->id ) { /* found transaction */
|
|
*node = *queue;
|
|
*queue = (*queue)->next;
|
|
if (*queue) { /* adjust relative time of new queue head */
|
|
(*queue)->t += (*node)->t;
|
|
}
|
|
(*node)->next = NULL;
|
|
/* coap_delete_node( q ); */
|
|
debug("*** removed transaction %u\n", id);
|
|
return 1;
|
|
}
|
|
|
|
/* search transaction to remove (only first occurence will be removed) */
|
|
q = *queue;
|
|
do {
|
|
p = q;
|
|
q = q->next;
|
|
} while ( q && id != q->id );
|
|
|
|
if ( q ) { /* found transaction */
|
|
p->next = q->next;
|
|
if (p->next) { /* must update relative time of p->next */
|
|
p->next->t += q->t;
|
|
}
|
|
q->next = NULL;
|
|
*node = q;
|
|
/* coap_delete_node( q ); */
|
|
debug("*** removed transaction %u\n", id);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
static inline int
|
|
token_match(const unsigned char *a, size_t alen,
|
|
const unsigned char *b, size_t blen) {
|
|
return alen == blen && (alen == 0 || memcmp(a, b, alen) == 0);
|
|
}
|
|
|
|
void
|
|
coap_cancel_all_messages(coap_context_t *context, const coap_address_t *dst,
|
|
const unsigned char *token, size_t token_length) {
|
|
/* cancel all messages in sendqueue that are for dst
|
|
* and use the specified token */
|
|
coap_queue_t *p, *q;
|
|
|
|
while (context->sendqueue &&
|
|
coap_address_equals(dst, &context->sendqueue->remote) &&
|
|
token_match(token, token_length,
|
|
context->sendqueue->pdu->hdr->token,
|
|
context->sendqueue->pdu->hdr->token_length)) {
|
|
q = context->sendqueue;
|
|
context->sendqueue = q->next;
|
|
debug("**** removed transaction %d\n", ntohs(q->pdu->hdr->id));
|
|
coap_delete_node(q);
|
|
}
|
|
|
|
if (!context->sendqueue)
|
|
return;
|
|
|
|
p = context->sendqueue;
|
|
q = p->next;
|
|
|
|
/* when q is not NULL, it does not match (dst, token), so we can skip it */
|
|
while (q) {
|
|
if (coap_address_equals(dst, &q->remote) &&
|
|
token_match(token, token_length,
|
|
q->pdu->hdr->token, q->pdu->hdr->token_length)) {
|
|
p->next = q->next;
|
|
debug("**** removed transaction %d\n", ntohs(q->pdu->hdr->id));
|
|
coap_delete_node(q);
|
|
q = p->next;
|
|
} else {
|
|
p = q;
|
|
q = q->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
coap_queue_t *
|
|
coap_find_transaction(coap_queue_t *queue, coap_tid_t id) {
|
|
while (queue && queue->id != id)
|
|
queue = queue->next;
|
|
|
|
return queue;
|
|
}
|
|
|
|
coap_pdu_t *
|
|
coap_new_error_response(coap_pdu_t *request, unsigned char code,
|
|
coap_opt_filter_t opts) {
|
|
coap_opt_iterator_t opt_iter;
|
|
coap_pdu_t *response;
|
|
size_t size = sizeof(coap_hdr_t) + request->hdr->token_length;
|
|
int type;
|
|
coap_opt_t *option;
|
|
unsigned short opt_type = 0; /* used for calculating delta-storage */
|
|
|
|
#if COAP_ERROR_PHRASE_LENGTH > 0
|
|
char *phrase = coap_response_phrase(code);
|
|
|
|
/* Need some more space for the error phrase and payload start marker */
|
|
if (phrase)
|
|
size += strlen(phrase) + 1;
|
|
#endif
|
|
|
|
assert(request);
|
|
|
|
/* cannot send ACK if original request was not confirmable */
|
|
type = request->hdr->type == COAP_MESSAGE_CON
|
|
? COAP_MESSAGE_ACK
|
|
: COAP_MESSAGE_NON;
|
|
|
|
/* Estimate how much space we need for options to copy from
|
|
* request. We always need the Token, for 4.02 the unknown critical
|
|
* options must be included as well. */
|
|
coap_option_clrb(opts, COAP_OPTION_CONTENT_TYPE); /* we do not want this */
|
|
|
|
coap_option_iterator_init(request, &opt_iter, opts);
|
|
|
|
/* Add size of each unknown critical option. As known critical
|
|
options as well as elective options are not copied, the delta
|
|
value might grow.
|
|
*/
|
|
while((option = coap_option_next(&opt_iter))) {
|
|
unsigned short delta = opt_iter.type - opt_type;
|
|
/* calculate space required to encode (opt_iter.type - opt_type) */
|
|
if (delta < 13) {
|
|
size++;
|
|
} else if (delta < 269) {
|
|
size += 2;
|
|
} else {
|
|
size += 3;
|
|
}
|
|
|
|
/* add coap_opt_length(option) and the number of additional bytes
|
|
* required to encode the option length */
|
|
|
|
size += coap_opt_length(option);
|
|
switch (*option & 0x0f) {
|
|
case 0x0e:
|
|
size++;
|
|
/* fall through */
|
|
case 0x0d:
|
|
size++;
|
|
break;
|
|
default:
|
|
;
|
|
}
|
|
|
|
opt_type = opt_iter.type;
|
|
}
|
|
|
|
/* Now create the response and fill with options and payload data. */
|
|
response = coap_pdu_init(type, code, request->hdr->id, size);
|
|
if (response) {
|
|
/* copy token */
|
|
if (!coap_add_token(response, request->hdr->token_length,
|
|
request->hdr->token)) {
|
|
debug("cannot add token to error response\n");
|
|
coap_delete_pdu(response);
|
|
return NULL;
|
|
}
|
|
|
|
/* copy all options */
|
|
coap_option_iterator_init(request, &opt_iter, opts);
|
|
while((option = coap_option_next(&opt_iter)))
|
|
coap_add_option(response, opt_iter.type,
|
|
COAP_OPT_LENGTH(option),
|
|
COAP_OPT_VALUE(option));
|
|
|
|
#if COAP_ERROR_PHRASE_LENGTH > 0
|
|
/* note that diagnostic messages do not need a Content-Format option. */
|
|
if (phrase)
|
|
coap_add_data(response, strlen(phrase), (unsigned char *)phrase);
|
|
#endif
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* Quick hack to determine the size of the resource description for
|
|
* .well-known/core.
|
|
*/
|
|
static inline size_t
|
|
get_wkc_len(coap_context_t *context, coap_opt_t *query_filter) {
|
|
unsigned char buf[1];
|
|
size_t len = 0;
|
|
|
|
if (print_wellknown(context, buf, &len, UINT_MAX, query_filter)
|
|
& COAP_PRINT_STATUS_ERROR) {
|
|
warn("cannot determine length of /.well-known/core\n");
|
|
return 0;
|
|
}
|
|
|
|
debug("get_wkc_len: print_wellknown() returned %zu\n", len);
|
|
|
|
return len;
|
|
}
|
|
|
|
#define SZX_TO_BYTES(SZX) ((size_t)(1 << ((SZX) + 4)))
|
|
|
|
coap_pdu_t *
|
|
wellknown_response(coap_context_t *context, coap_pdu_t *request) {
|
|
coap_pdu_t *resp;
|
|
coap_opt_iterator_t opt_iter;
|
|
size_t len, wkc_len;
|
|
unsigned char buf[2];
|
|
int result = 0;
|
|
int need_block2 = 0; /* set to 1 if Block2 option is required */
|
|
coap_block_t block;
|
|
coap_opt_t *query_filter;
|
|
size_t offset = 0;
|
|
|
|
resp = coap_pdu_init(request->hdr->type == COAP_MESSAGE_CON
|
|
? COAP_MESSAGE_ACK
|
|
: COAP_MESSAGE_NON,
|
|
COAP_RESPONSE_CODE(205),
|
|
request->hdr->id, COAP_MAX_PDU_SIZE);
|
|
if (!resp) {
|
|
debug("wellknown_response: cannot create PDU\n");
|
|
return NULL;
|
|
}
|
|
|
|
if (!coap_add_token(resp, request->hdr->token_length, request->hdr->token)) {
|
|
debug("wellknown_response: cannot add token\n");
|
|
goto error;
|
|
}
|
|
|
|
query_filter = coap_check_option(request, COAP_OPTION_URI_QUERY, &opt_iter);
|
|
wkc_len = get_wkc_len(context, query_filter);
|
|
|
|
/* check whether the request contains the Block2 option */
|
|
if (coap_get_block(request, COAP_OPTION_BLOCK2, &block)) {
|
|
offset = block.num << (block.szx + 4);
|
|
if (block.szx > 6) { /* invalid, MUST lead to 4.00 Bad Request */
|
|
resp->hdr->code = COAP_RESPONSE_CODE(400);
|
|
return resp;
|
|
} else if (block.szx > COAP_MAX_BLOCK_SZX) {
|
|
block.szx = COAP_MAX_BLOCK_SZX;
|
|
block.num = offset >> (block.szx + 4);
|
|
}
|
|
|
|
need_block2 = 1;
|
|
}
|
|
|
|
/* Check if there is sufficient space to add Content-Format option
|
|
* and data. We do this before adding the Content-Format option to
|
|
* avoid sending error responses with that option but no actual
|
|
* content. */
|
|
if (resp->max_size <= (size_t)resp->length + 3) {
|
|
debug("wellknown_response: insufficient storage space\n");
|
|
goto error;
|
|
}
|
|
|
|
/* Add Content-Format. As we have checked for available storage,
|
|
* nothing should go wrong here. */
|
|
assert(coap_encode_var_bytes(buf,
|
|
COAP_MEDIATYPE_APPLICATION_LINK_FORMAT) == 1);
|
|
coap_add_option(resp, COAP_OPTION_CONTENT_FORMAT,
|
|
coap_encode_var_bytes(buf,
|
|
COAP_MEDIATYPE_APPLICATION_LINK_FORMAT), buf);
|
|
|
|
/* check if Block2 option is required even if not requested */
|
|
if (!need_block2 && (resp->max_size - (size_t)resp->length < wkc_len)) {
|
|
assert(resp->length <= resp->max_size);
|
|
const size_t payloadlen = resp->max_size - resp->length;
|
|
/* yes, need block-wise transfer */
|
|
block.num = 0;
|
|
block.m = 0; /* the M bit is set by coap_write_block_opt() */
|
|
block.szx = COAP_MAX_BLOCK_SZX;
|
|
while (payloadlen < SZX_TO_BYTES(block.szx)) {
|
|
if (block.szx == 0) {
|
|
debug("wellknown_response: message to small even for szx == 0\n");
|
|
goto error;
|
|
} else {
|
|
block.szx--;
|
|
}
|
|
}
|
|
|
|
need_block2 = 1;
|
|
}
|
|
|
|
/* write Block2 option if necessary */
|
|
if (need_block2) {
|
|
if (coap_write_block_opt(&block, COAP_OPTION_BLOCK2, resp, wkc_len) < 0) {
|
|
debug("wellknown_response: cannot add Block2 option\n");
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
/* Manually set payload of response to let print_wellknown() write,
|
|
* into our buffer without copying data. */
|
|
|
|
resp->data = (unsigned char *)resp->hdr + resp->length;
|
|
*resp->data = COAP_PAYLOAD_START;
|
|
resp->data++;
|
|
resp->length++;
|
|
len = need_block2 ? SZX_TO_BYTES(block.szx) : resp->max_size - resp->length;
|
|
|
|
result = print_wellknown(context, resp->data, &len, offset, query_filter);
|
|
if ((result & COAP_PRINT_STATUS_ERROR) != 0) {
|
|
debug("print_wellknown failed\n");
|
|
goto error;
|
|
}
|
|
|
|
resp->length += COAP_PRINT_OUTPUT_LENGTH(result);
|
|
return resp;
|
|
|
|
error:
|
|
/* set error code 5.03 and remove all options and data from response */
|
|
resp->hdr->code = COAP_RESPONSE_CODE(503);
|
|
resp->length = sizeof(coap_hdr_t) + resp->hdr->token_length;
|
|
return resp;
|
|
}
|
|
|
|
/**
|
|
* This function cancels outstanding messages for the remote peer and
|
|
* token specified in @p sent. Any observation relationship for
|
|
* sent->remote and the token are removed. Calling this function is
|
|
* required when receiving an RST message (usually in response to a
|
|
* notification) or a GET request with the Observe option set to 1.
|
|
*
|
|
* This function returns @c 0 when the token is unknown with this
|
|
* peer, or a value greater than zero otherwise.
|
|
*/
|
|
static int
|
|
coap_cancel(coap_context_t *context, const coap_queue_t *sent) {
|
|
#ifndef WITHOUT_OBSERVE
|
|
#ifdef WITH_CONTIKI
|
|
coap_iterator_t resource_iter;
|
|
#else
|
|
coap_resource_t *r;
|
|
#ifndef COAP_RESOURCES_NOHASH
|
|
coap_resource_t *tmp;
|
|
#endif
|
|
#endif
|
|
str token = { 0, NULL };
|
|
int num_cancelled = 0; /* the number of observers cancelled */
|
|
|
|
/* remove observer for this resource, if any
|
|
* get token from sent and try to find a matching resource. Uh!
|
|
*/
|
|
|
|
COAP_SET_STR(&token, sent->pdu->hdr->token_length, sent->pdu->hdr->token);
|
|
|
|
#ifdef WITH_CONTIKI
|
|
/* traverse resources and delete matching observers for each resource */
|
|
if (coap_resource_iterator_init(context->resources, &resource_iter) != NULL) {
|
|
|
|
while ((r = coap_resource_next(&resource_iter))) {
|
|
num_cancelled += coap_delete_observer(r, &sent->remote, &token);
|
|
coap_cancel_all_messages(context, &sent->remote, token.s, token.length);
|
|
}
|
|
|
|
}
|
|
#else
|
|
#ifdef COAP_RESOURCES_NOHASH
|
|
LL_FOREACH(context->resources, r) {
|
|
#else /* COAP_RESOURCES_NOHASH */
|
|
HASH_ITER(hh, context->resources, r, tmp) {
|
|
#endif
|
|
num_cancelled += coap_delete_observer(r, &sent->remote, &token);
|
|
coap_cancel_all_messages(context, &sent->remote, token.s, token.length);
|
|
}
|
|
#endif /* not WITH_CONTIKI */
|
|
|
|
return num_cancelled;
|
|
#else /* WITOUT_OBSERVE */
|
|
return 0;
|
|
#endif /* WITOUT_OBSERVE */
|
|
}
|
|
|
|
#define WANT_WKC(Pdu,Key) \
|
|
(((Pdu)->hdr->code == COAP_REQUEST_GET) && is_wkc(Key))
|
|
|
|
void
|
|
handle_request(coap_context_t *context, coap_queue_t *node) {
|
|
coap_method_handler_t h = NULL;
|
|
coap_pdu_t *response = NULL;
|
|
coap_opt_filter_t opt_filter;
|
|
coap_resource_t *resource;
|
|
coap_key_t key;
|
|
|
|
coap_option_filter_clear(opt_filter);
|
|
|
|
/* try to find the resource from the request URI */
|
|
coap_hash_request_uri(node->pdu, key);
|
|
resource = coap_get_resource_from_key(context, key);
|
|
|
|
if (!resource) {
|
|
/* The resource was not found. Check if the request URI happens to
|
|
* be the well-known URI. In that case, we generate a default
|
|
* response, otherwise, we return 4.04 */
|
|
|
|
switch(node->pdu->hdr->code) {
|
|
|
|
case COAP_REQUEST_GET:
|
|
if (is_wkc(key)) { /* GET request for .well-known/core */
|
|
info("create default response for %s\n", COAP_DEFAULT_URI_WELLKNOWN);
|
|
response = wellknown_response(context, node->pdu);
|
|
|
|
} else { /* GET request for any another resource, return 4.04 */
|
|
|
|
debug("GET for unknown resource 0x%02x%02x%02x%02x, return 4.04\n",
|
|
key[0], key[1], key[2], key[3]);
|
|
response =
|
|
coap_new_error_response(node->pdu, COAP_RESPONSE_CODE(404),
|
|
opt_filter);
|
|
}
|
|
break;
|
|
|
|
default: /* any other request type */
|
|
|
|
debug("unhandled request for unknown resource 0x%02x%02x%02x%02x\r\n",
|
|
key[0], key[1], key[2], key[3]);
|
|
if (!coap_mcast_interface(&node->local_if))
|
|
response = coap_new_error_response(node->pdu, COAP_RESPONSE_CODE(405),
|
|
opt_filter);
|
|
}
|
|
|
|
if (response && coap_send(context, &node->local_if,
|
|
&node->remote, response) == COAP_INVALID_TID) {
|
|
warn("cannot send response for transaction %u\n", node->id);
|
|
}
|
|
coap_delete_pdu(response);
|
|
|
|
return;
|
|
}
|
|
|
|
/* the resource was found, check if there is a registered handler */
|
|
if ((size_t)node->pdu->hdr->code - 1 <
|
|
sizeof(resource->handler)/sizeof(coap_method_handler_t))
|
|
h = resource->handler[node->pdu->hdr->code - 1];
|
|
|
|
if (h) {
|
|
debug("call custom handler for resource 0x%02x%02x%02x%02x\n",
|
|
key[0], key[1], key[2], key[3]);
|
|
response = coap_pdu_init(node->pdu->hdr->type == COAP_MESSAGE_CON
|
|
? COAP_MESSAGE_ACK
|
|
: COAP_MESSAGE_NON,
|
|
0, node->pdu->hdr->id, COAP_MAX_PDU_SIZE);
|
|
|
|
/* Implementation detail: coap_add_token() immediately returns 0
|
|
if response == NULL */
|
|
if (coap_add_token(response, node->pdu->hdr->token_length,
|
|
node->pdu->hdr->token)) {
|
|
str token = { node->pdu->hdr->token_length, node->pdu->hdr->token };
|
|
coap_opt_iterator_t opt_iter;
|
|
coap_opt_t *observe = NULL;
|
|
int observe_action = COAP_OBSERVE_CANCEL;
|
|
|
|
/* check for Observe option */
|
|
if (resource->observable) {
|
|
observe = coap_check_option(node->pdu, COAP_OPTION_OBSERVE, &opt_iter);
|
|
if (observe) {
|
|
observe_action =
|
|
coap_decode_var_bytes(coap_opt_value(observe),
|
|
coap_opt_length(observe));
|
|
|
|
if ((observe_action & COAP_OBSERVE_CANCEL) == 0) {
|
|
coap_subscription_t *subscription;
|
|
|
|
coap_log(LOG_DEBUG, "create new subscription\n");
|
|
subscription = coap_add_observer(resource, &node->local_if,
|
|
&node->remote, &token);
|
|
if (subscription) {
|
|
subscription->non = node->pdu->hdr->type == COAP_MESSAGE_NON;
|
|
coap_touch_observer(context, &node->remote, &token);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
h(context, resource, &node->local_if, &node->remote,
|
|
node->pdu, &token, response);
|
|
|
|
if (observe && ((COAP_RESPONSE_CLASS(response->hdr->code) > 2)
|
|
|| ((observe_action & COAP_OBSERVE_CANCEL) != 0))) {
|
|
coap_log(LOG_DEBUG, "removed observer");
|
|
coap_delete_observer(resource, &node->remote, &token);
|
|
}
|
|
|
|
/* If original request contained a token, and the registered
|
|
* application handler made no changes to the response, then
|
|
* this is an empty ACK with a token, which is a malformed
|
|
* PDU */
|
|
if ((response->hdr->type == COAP_MESSAGE_ACK)
|
|
&& (response->hdr->code == 0)) {
|
|
/* Remove token from otherwise-empty acknowledgment PDU */
|
|
response->hdr->token_length = 0;
|
|
response->length = sizeof(coap_hdr_t);
|
|
}
|
|
|
|
if (response->hdr->type != COAP_MESSAGE_NON ||
|
|
(response->hdr->code >= 64
|
|
&& !coap_mcast_interface(&node->local_if))) {
|
|
|
|
if (coap_send(context, &node->local_if,
|
|
&node->remote, response) == COAP_INVALID_TID) {
|
|
debug("cannot send response for message %d\n", node->pdu->hdr->id);
|
|
}
|
|
}
|
|
|
|
coap_delete_pdu(response);
|
|
} else {
|
|
warn("cannot generate response\r\n");
|
|
}
|
|
} else {
|
|
if (WANT_WKC(node->pdu, key)) {
|
|
debug("create default response for %s\n", COAP_DEFAULT_URI_WELLKNOWN);
|
|
response = wellknown_response(context, node->pdu);
|
|
} else
|
|
response = coap_new_error_response(node->pdu, COAP_RESPONSE_CODE(405),
|
|
opt_filter);
|
|
|
|
if (!response || (coap_send(context, &node->local_if, &node->remote,
|
|
response) == COAP_INVALID_TID)) {
|
|
debug("cannot send response for transaction %u\n", node->id);
|
|
}
|
|
coap_delete_pdu(response);
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
handle_response(coap_context_t *context,
|
|
coap_queue_t *sent, coap_queue_t *rcvd) {
|
|
|
|
coap_send_ack(context, &rcvd->local_if, &rcvd->remote, rcvd->pdu);
|
|
|
|
/* In a lossy context, the ACK of a separate response may have
|
|
* been lost, so we need to stop retransmitting requests with the
|
|
* same token.
|
|
*/
|
|
coap_cancel_all_messages(context, &rcvd->remote,
|
|
rcvd->pdu->hdr->token,
|
|
rcvd->pdu->hdr->token_length);
|
|
|
|
/* Call application-specific reponse handler when available. */
|
|
if (context->response_handler) {
|
|
context->response_handler(context, &rcvd->local_if,
|
|
&rcvd->remote, sent ? sent->pdu : NULL,
|
|
rcvd->pdu, rcvd->id);
|
|
}
|
|
}
|
|
|
|
static inline int
|
|
#ifdef __GNUC__
|
|
handle_locally(coap_context_t *context __attribute__ ((unused)),
|
|
coap_queue_t *node __attribute__ ((unused))) {
|
|
#else /* not a GCC */
|
|
handle_locally(coap_context_t *context, coap_queue_t *node) {
|
|
#endif /* GCC */
|
|
/* this function can be used to check if node->pdu is really for us */
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
coap_dispatch(coap_context_t *context, coap_queue_t *rcvd) {
|
|
coap_queue_t *sent = NULL;
|
|
coap_pdu_t *response;
|
|
coap_opt_filter_t opt_filter;
|
|
|
|
if (!context)
|
|
return;
|
|
|
|
memset(opt_filter, 0, sizeof(coap_opt_filter_t));
|
|
|
|
{
|
|
/* version has been checked in coap_handle_message() */
|
|
/* if ( rcvd->pdu->hdr->version != COAP_DEFAULT_VERSION ) { */
|
|
/* debug("dropped packet with unknown version %u\n", rcvd->pdu->hdr->version); */
|
|
/* goto cleanup; */
|
|
/* } */
|
|
|
|
switch (rcvd->pdu->hdr->type) {
|
|
case COAP_MESSAGE_ACK:
|
|
/* find transaction in sendqueue to stop retransmission */
|
|
coap_remove_from_queue(&context->sendqueue, rcvd->id, &sent);
|
|
|
|
if (rcvd->pdu->hdr->code == 0)
|
|
goto cleanup;
|
|
|
|
/* if sent code was >= 64 the message might have been a
|
|
* notification. Then, we must flag the observer to be alive
|
|
* by setting obs->fail_cnt = 0. */
|
|
if (sent && COAP_RESPONSE_CLASS(sent->pdu->hdr->code) == 2) {
|
|
const str token =
|
|
{ sent->pdu->hdr->token_length, sent->pdu->hdr->token };
|
|
coap_touch_observer(context, &sent->remote, &token);
|
|
}
|
|
break;
|
|
|
|
case COAP_MESSAGE_RST :
|
|
/* We have sent something the receiver disliked, so we remove
|
|
* not only the transaction but also the subscriptions we might
|
|
* have. */
|
|
|
|
#ifndef WITH_CONTIKI
|
|
coap_log(LOG_ALERT, "got RST for message %u\n", ntohs(rcvd->pdu->hdr->id));
|
|
#else /* WITH_CONTIKI */
|
|
coap_log(LOG_ALERT, "got RST for message %u\n", uip_ntohs(rcvd->pdu->hdr->id));
|
|
#endif /* WITH_CONTIKI */
|
|
|
|
/* find transaction in sendqueue to stop retransmission */
|
|
coap_remove_from_queue(&context->sendqueue, rcvd->id, &sent);
|
|
|
|
if (sent)
|
|
coap_cancel(context, sent);
|
|
goto cleanup;
|
|
|
|
case COAP_MESSAGE_NON : /* check for unknown critical options */
|
|
if (coap_option_check_critical(context, rcvd->pdu, opt_filter) == 0)
|
|
goto cleanup;
|
|
break;
|
|
|
|
case COAP_MESSAGE_CON : /* check for unknown critical options */
|
|
if (coap_option_check_critical(context, rcvd->pdu, opt_filter) == 0) {
|
|
|
|
/* FIXME: send response only if we have received a request. Otherwise,
|
|
* send RST. */
|
|
response =
|
|
coap_new_error_response(rcvd->pdu, COAP_RESPONSE_CODE(402), opt_filter);
|
|
|
|
if (!response)
|
|
warn("coap_dispatch: cannot create error reponse\n");
|
|
else {
|
|
if (coap_send(context, &rcvd->local_if, &rcvd->remote, response)
|
|
== COAP_INVALID_TID) {
|
|
warn("coap_dispatch: error sending reponse\n");
|
|
}
|
|
coap_delete_pdu(response);
|
|
}
|
|
|
|
goto cleanup;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Pass message to upper layer if a specific handler was
|
|
* registered for a request that should be handled locally. */
|
|
if (handle_locally(context, rcvd)) {
|
|
if (COAP_MESSAGE_IS_REQUEST(rcvd->pdu->hdr))
|
|
handle_request(context, rcvd);
|
|
else if (COAP_MESSAGE_IS_RESPONSE(rcvd->pdu->hdr))
|
|
handle_response(context, sent, rcvd);
|
|
else {
|
|
debug("dropped message with invalid code (%d.%02d)\n",
|
|
COAP_RESPONSE_CLASS(rcvd->pdu->hdr->code),
|
|
rcvd->pdu->hdr->code & 0x1f);
|
|
coap_send_message_type(context, &rcvd->local_if, &rcvd->remote,
|
|
rcvd->pdu, COAP_MESSAGE_RST);
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
coap_delete_node(sent);
|
|
coap_delete_node(rcvd);
|
|
}
|
|
}
|
|
|
|
int
|
|
coap_can_exit( coap_context_t *context ) {
|
|
return !context || (context->sendqueue == NULL);
|
|
}
|
|
|
|
#ifdef WITH_CONTIKI
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
/* CoAP message retransmission */
|
|
/*---------------------------------------------------------------------------*/
|
|
PROCESS_THREAD(coap_retransmit_process, ev, data)
|
|
{
|
|
coap_tick_t now;
|
|
coap_queue_t *nextpdu;
|
|
|
|
PROCESS_BEGIN();
|
|
|
|
debug("Started retransmit process\r\n");
|
|
|
|
while(1) {
|
|
PROCESS_YIELD();
|
|
if (ev == PROCESS_EVENT_TIMER) {
|
|
if (etimer_expired(&the_coap_context.retransmit_timer)) {
|
|
|
|
nextpdu = coap_peek_next(&the_coap_context);
|
|
|
|
coap_ticks(&now);
|
|
while (nextpdu && nextpdu->t <= now) {
|
|
coap_retransmit(&the_coap_context, coap_pop_next(&the_coap_context));
|
|
nextpdu = coap_peek_next(&the_coap_context);
|
|
}
|
|
|
|
/* need to set timer to some value even if no nextpdu is available */
|
|
etimer_set(&the_coap_context.retransmit_timer,
|
|
nextpdu ? nextpdu->t - now : 0xFFFF);
|
|
}
|
|
#ifndef WITHOUT_OBSERVE
|
|
if (etimer_expired(&the_coap_context.notify_timer)) {
|
|
coap_check_notify(&the_coap_context);
|
|
etimer_reset(&the_coap_context.notify_timer);
|
|
}
|
|
#endif /* WITHOUT_OBSERVE */
|
|
}
|
|
}
|
|
|
|
PROCESS_END();
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
#endif /* WITH_CONTIKI */
|
|
|
|
#ifdef WITH_LWIP
|
|
/* FIXME: retransmits that are not required any more due to incoming packages
|
|
* do *not* get cleared at the moment, the wakeup when the transmission is due
|
|
* is silently accepted. this is mainly due to the fact that the required
|
|
* checks are similar in two places in the code (when receiving ACK and RST)
|
|
* and that they cause more than one patch chunk, as it must be first checked
|
|
* whether the sendqueue item to be dropped is the next one pending, and later
|
|
* the restart function has to be called. nothing insurmountable, but it can
|
|
* also be implemented when things have stabilized, and the performance
|
|
* penality is minimal
|
|
*
|
|
* also, this completely ignores COAP_RESOURCE_CHECK_TIME.
|
|
* */
|
|
|
|
static void coap_retransmittimer_execute(void *arg)
|
|
{
|
|
coap_context_t *ctx = (coap_context_t*)arg;
|
|
coap_tick_t now;
|
|
coap_tick_t elapsed;
|
|
coap_queue_t *nextinqueue;
|
|
|
|
ctx->timer_configured = 0;
|
|
|
|
coap_ticks(&now);
|
|
|
|
elapsed = now - ctx->sendqueue_basetime; /* that's positive for sure, and unless we haven't been called for a complete wrapping cycle, did not wrap */
|
|
|
|
nextinqueue = coap_peek_next(ctx);
|
|
while (nextinqueue != NULL)
|
|
{
|
|
if (nextinqueue->t > elapsed) {
|
|
nextinqueue->t -= elapsed;
|
|
break;
|
|
} else {
|
|
elapsed -= nextinqueue->t;
|
|
coap_retransmit(ctx, coap_pop_next(ctx));
|
|
nextinqueue = coap_peek_next(ctx);
|
|
}
|
|
}
|
|
|
|
ctx->sendqueue_basetime = now;
|
|
|
|
coap_retransmittimer_restart(ctx);
|
|
}
|
|
|
|
static void coap_retransmittimer_restart(coap_context_t *ctx)
|
|
{
|
|
coap_tick_t now, elapsed, delay;
|
|
|
|
if (ctx->timer_configured)
|
|
{
|
|
printf("clearing\n");
|
|
sys_untimeout(coap_retransmittimer_execute, (void*)ctx);
|
|
ctx->timer_configured = 0;
|
|
}
|
|
if (ctx->sendqueue != NULL)
|
|
{
|
|
coap_ticks(&now);
|
|
elapsed = now - ctx->sendqueue_basetime;
|
|
if (ctx->sendqueue->t >= elapsed) {
|
|
delay = ctx->sendqueue->t - elapsed;
|
|
} else {
|
|
/* a strange situation, but not completely impossible.
|
|
*
|
|
* this happens, for example, right after
|
|
* coap_retransmittimer_execute, when a retransmission
|
|
* was *just not yet* due, and the clock ticked before
|
|
* our coap_ticks was called.
|
|
*
|
|
* not trying to retransmit anything now, as it might
|
|
* cause uncontrollable recursion; let's just try again
|
|
* with the next main loop run.
|
|
* */
|
|
delay = 0;
|
|
}
|
|
|
|
printf("scheduling for %d ticks\n", delay);
|
|
sys_timeout(delay, coap_retransmittimer_execute, (void*)ctx);
|
|
ctx->timer_configured = 1;
|
|
}
|
|
}
|
|
#endif
|