/* * Copyright (c) 2015, SICS, Swedish ICT AB. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the Institute nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /** * \file * CoAP context handling * \author * Niclas Finne * Joakim Eriksson */ #include "er-coap.h" #include "er-coap-engine.h" #include #include #define DEBUG DEBUG_NONE #include "contiki/ip/uip-debug.h" #ifndef COAP_CONTEXT_CONF_MAX_CONTEXTS #define MAX_CONTEXTS 1 #else #define MAX_CONTEXTS COAP_CONTEXT_CONF_MAX_CONTEXTS #endif /* COAP_CONTEXT_CONF_MAX_CONTEXTS */ static coap_context_t coap_contexts[MAX_CONTEXTS]; #ifdef WITH_DTLS process_event_t coap_context_event; #define STATUS_CONNECTING 1 #define STATUS_CONNECTED 2 #define STATUS_ALERT 4 PROCESS(coap_context_process, "CoAP Context Engine"); /*---------------------------------------------------------------------------*/ static int get_from_peer(struct dtls_context_t *ctx, session_t *session, uint8 *data, size_t len) { coap_context_t *coap_ctx; coap_ctx = dtls_get_app_data(ctx); if(coap_ctx != COAP_CONTEXT_NONE && coap_ctx->is_used) { uip_len(coap_ctx->buf) = len; memmove(uip_appdata(coap_ctx->buf), data, len); coap_engine_receive(coap_ctx); } return 0; } /*---------------------------------------------------------------------------*/ static int prepare_and_send_buf(coap_context_t *ctx, session_t *session, uint8_t *data, size_t len) { struct net_buf *buf; int max_data_len; /* This net_buf gets sent to network, so it is not released * by this function unless there was an error and buf was * not actually sent. */ buf = ip_buf_get_tx(ctx->net_ctx); if (!buf) { len = -ENOBUFS; goto out; } max_data_len = IP_BUF_MAX_DATA - UIP_IPUDPH_LEN; PRINTF("%s: reply to peer data %p len %d\n", __FUNCTION__, data, len); if (len > max_data_len) { PRINTF("%s: too much (%d bytes) data to send (max %d bytes)\n", __FUNCTION__, len, max_data_len); ip_buf_unref(buf); len = -EINVAL; goto out; } /* Note that we have reversed the addresses here * because net_reply() will reverse them again. */ #ifdef CONFIG_NETWORKING_WITH_IPV6 uip_ip6addr_copy(&NET_BUF_IP(buf)->destipaddr, (uip_ip6addr_t *)&ctx->my_addr.in6_addr); uip_ip6addr_copy(&NET_BUF_IP(buf)->srcipaddr, (uip_ip6addr_t *)&session->addr.ipaddr); #else uip_ip4addr_copy(&NET_BUF_IP(buf)->destipaddr, (uip_ip4addr_t *)&ctx->my_addr.in_addr); uip_ip4addr_copy(&NET_BUF_IP(buf)->srcipaddr, (uip_ip4addr_t *)&session->addr.ipaddr); #endif NET_BUF_UDP(buf)->destport = uip_ntohs(ctx->my_port); NET_BUF_UDP(buf)->srcport = session->addr.port; uip_set_udp_conn(buf) = net_context_get_udp_connection(ctx->net_ctx); memcpy(net_buf_add(buf, len), data, len); ip_buf_appdatalen(buf) = len; ip_buf_appdata(buf) = buf->data + ip_buf_reserve(buf); if (net_reply(ctx->net_ctx, buf)) { ip_buf_unref(buf); } out: return len; } /*---------------------------------------------------------------------------*/ static int send_to_peer(struct dtls_context_t *ctx, session_t *session, uint8 *data, size_t len) { int ret = 0; coap_context_t *coap_ctx; coap_ctx = dtls_get_app_data(ctx); PRINTF("%s(): ctx %p session %p data %p len %d\n", __FUNCTION__, ctx, session, data, len); if(coap_ctx != COAP_CONTEXT_NONE && coap_ctx->is_used && session != NULL) { ret = prepare_and_send_buf(coap_ctx, session, data, len); if (ret < 0) { PRINTF("%s(): cannot send data, msg discarded\n", __FUNCTION__); } } else { PRINTF("%s(): msg discarded ctx %p is_used %d buf %p udp %p session %p\n", __FUNCTION__, coap_ctx, coap_ctx ? coap_ctx->is_used : 0, coap_ctx->buf, coap_ctx ? (coap_ctx->buf ? uip_udp_conn(coap_ctx->buf) : NULL) : NULL, session); ret = -EINVAL; } return ret; } /*---------------------------------------------------------------------------*/ int coap_context_wait_data(coap_context_t *coap_ctx, int32_t ticks) { struct net_buf *buf; buf = net_receive(coap_ctx->net_ctx, ticks); if (buf) { session_t session; int ret; uip_ipaddr_copy(&session.addr.ipaddr, &UIP_IP_BUF(buf)->srcipaddr); session.addr.port = UIP_UDP_BUF(buf)->srcport; session.size = sizeof(session.addr); session.ifindex = 1; PRINTF("coap-context: got dtls message from "); PRINT6ADDR(&session.addr.ipaddr); PRINTF(":%d %u bytes\n", uip_ntohs(session.addr.port), uip_appdatalen(buf)); PRINTF("Received appdata %p appdatalen %d\n", ip_buf_appdata(buf), ip_buf_appdatalen(buf)); coap_ctx->buf = buf; ret = dtls_handle_message(coap_ctx->dtls_context, &session, ip_buf_appdata(buf), ip_buf_appdatalen(buf)); /* We always release the buffer here as this buffer is never sent * to network anyway. */ if (coap_ctx->buf) { ip_buf_unref(coap_ctx->buf); coap_ctx->buf = NULL; } return ret; } return 0; } /*---------------------------------------------------------------------------*/ static int event(struct dtls_context_t *ctx, session_t *session, dtls_alert_level_t level, unsigned short code) { coap_context_t *coap_ctx; coap_ctx = dtls_get_app_data(ctx); if(coap_ctx == COAP_CONTEXT_NONE || coap_ctx->is_used == 0) { /* The context is no longer in use */ } else if(code == DTLS_EVENT_CONNECTED) { coap_ctx->status = STATUS_CONNECTED; PRINTF("coap-context: DTLS CLIENT CONNECTED!\n"); process_post(coap_ctx->process, coap_context_event, coap_ctx); } else if(code == DTLS_EVENT_CONNECT || code == DTLS_EVENT_RENEGOTIATE) { coap_ctx->status = STATUS_CONNECTING; PRINTF("coap-context: DTLS CLIENT NOT CONNECTED!\n"); process_post(coap_ctx->process, coap_context_event, coap_ctx); } else if(level == DTLS_ALERT_LEVEL_FATAL && code < 256) { /* Fatal alert */ if (coap_ctx && coap_ctx->buf) { ip_buf_unref(coap_ctx->buf); coap_ctx->buf = NULL; } coap_ctx->status = STATUS_ALERT; PRINTF("coap-context: DTLS CLIENT ALERT %u!\n", code); process_post(coap_ctx->process, coap_context_event, coap_ctx); } return 0; } /*---------------------------------------------------------------------------*/ int coap_context_is_secure(const coap_context_t *coap_ctx) { /* Assume all contexts are secure except for the no context */ if(coap_ctx != COAP_CONTEXT_NONE) { return coap_ctx->is_used; } return 0; } /*---------------------------------------------------------------------------*/ int coap_context_is_connecting(const coap_context_t *coap_ctx) { /* Assume all non secure contexts are connected by default */ if(coap_ctx == COAP_CONTEXT_NONE || !coap_ctx->is_used) { return 0; } return coap_ctx->status == STATUS_CONNECTING; } /*---------------------------------------------------------------------------*/ int coap_context_is_connected(const coap_context_t *coap_ctx) { /* Assume all non secure contexts are connected by default */ if(coap_ctx == COAP_CONTEXT_NONE || !coap_ctx->is_used) { return 1; } return coap_ctx->status == STATUS_CONNECTED; } /*---------------------------------------------------------------------------*/ int coap_context_has_errors(const coap_context_t *coap_ctx) { /* Assume all non secure contexts are connected by default */ if(coap_ctx == COAP_CONTEXT_NONE || !coap_ctx->is_used) { return 0; } return coap_ctx->status == STATUS_ALERT; } /*---------------------------------------------------------------------------*/ coap_context_t * coap_context_new(uip_ipaddr_t *my_addr, uint16_t port) { coap_context_t *ctx = NULL; int i; for(i = 0; i < MAX_CONTEXTS; i++) { if(!coap_contexts[i].is_used) { ctx = &coap_contexts[i]; break; } } if(ctx == NULL) { PRINTF("coap-context: no free contexts\n"); return NULL; } memset(ctx, 0, sizeof(coap_context_t)); /* initialize context */ ctx->dtls_context = dtls_new_context(ctx); if(ctx->dtls_context == NULL) { PRINTF("coap-context: failed to get DTLS context\n"); uip_udp_remove(uip_udp_conn(ctx->buf)); return NULL; } ctx->dtls_handler.write = send_to_peer; ctx->dtls_handler.read = get_from_peer; ctx->dtls_handler.event = event; dtls_set_handler(ctx->dtls_context, &ctx->dtls_handler); #ifdef NETSTACK_CONF_WITH_IPV6 memcpy(&ctx->my_addr.in6_addr, my_addr, sizeof(ctx->my_addr.in6_addr)); #else memcpy(&ctx->my_addr.in_addr, my_addr, sizeof(ctx->my_addr.in_addr)); #endif ctx->my_port = port; ctx->process = PROCESS_CURRENT(); ctx->is_used = 1; PRINTF("Secure listening on port %u\n", port); return ctx; } /*---------------------------------------------------------------------------*/ void coap_context_set_key_handlers(coap_context_t *ctx, dtls_get_psk_info_t get_psk_info, dtls_get_ecdsa_key_t get_ecdsa_key, dtls_verify_ecdsa_key_t verify_ecdsa_key) { if (!ctx) { return; } #ifdef DTLS_PSK ctx->dtls_handler.get_psk_info = get_psk_info; #endif /* DTLS_PSK */ #ifdef DTLS_ECC ctx->dtls_handler.get_ecdsa_key = get_ecdsa_key; ctx->dtls_handler.verify_ecdsa_key = verify_ecdsa_key; #endif /* DTLS_ECC */ } /*---------------------------------------------------------------------------*/ void coap_context_close(coap_context_t *coap_ctx) { if(coap_ctx == NULL || coap_ctx->is_used == 0) { /* Not opened */ return; } if(coap_ctx->dtls_context != NULL) { dtls_free_context(coap_ctx->dtls_context); } if(coap_ctx->buf && uip_udp_conn(coap_ctx->buf) != NULL) { uip_udp_remove(uip_udp_conn(coap_ctx->buf)); } coap_ctx->is_used = 0; } /*---------------------------------------------------------------------------*/ int coap_context_connect(coap_context_t *coap_ctx, uip_ipaddr_t *addr, uint16_t port) { session_t session; if(coap_ctx == NULL || coap_ctx->is_used == 0) { return 0; } #ifdef NETSTACK_CONF_WITH_IPV6 memcpy(&coap_ctx->addr.in6_addr, addr, sizeof(coap_ctx->addr.in6_addr)); coap_ctx->addr.family = AF_INET6; #else memcpy(&coap_ctx->addr.in_addr, addr, sizeof(coap_ctx->addr.in_addr)); coap_ctx->addr.family = AF_INET; #endif coap_ctx->port = port; coap_ctx->net_ctx = net_context_get(IPPROTO_UDP, (const struct net_addr *)&coap_ctx->addr, coap_ctx->port, (const struct net_addr *)&coap_ctx->my_addr, coap_ctx->my_port); if (!coap_ctx->net_ctx) { PRINTF("%s: Cannot get network context\n", __FUNCTION__); return 0; } uip_ipaddr_copy(&session.addr.ipaddr, addr); session.addr.port = UIP_HTONS(port); session.size = sizeof(session.addr); session.ifindex = 1; coap_ctx->status = STATUS_CONNECTING; PRINTF("coap-context: DTLS CONNECT TO ["); PRINT6ADDR(addr); PRINTF("]:%u\n", uip_ntohs(port)); if(dtls_connect(coap_ctx->dtls_context, &session) >= 0) { return 1; } /* Failed to initiate connection */ coap_ctx->status = STATUS_ALERT; return 0; } /*---------------------------------------------------------------------------*/ int coap_context_send_message(coap_context_t *coap_ctx, uip_ipaddr_t *addr, uint16_t port, const uint8_t *data, uint16_t length) { int res; session_t sn; if(coap_ctx == NULL || coap_ctx->is_used == 0) { PRINTF("coap-context: can not send on non-used context\n"); return 0; } uip_ipaddr_copy(&sn.addr.ipaddr, addr); sn.addr.port = port; sn.size = sizeof(sn.addr); sn.ifindex = 1; res = dtls_write(coap_ctx->dtls_context, &sn, (uint8 *)data, length); if(res < 0) { PRINTF("coap-context: Failed to send with dtls (%d)\n", res); } else if (res == 0) { PRINTF("coap-context: No data sent with DTLS but connection made.\n"); } return res; } /*---------------------------------------------------------------------------*/ void coap_context_init(void) { dtls_init(); coap_context_event = process_alloc_event(); process_start(&coap_context_process, NULL); } /*---------------------------------------------------------------------------*/ PROCESS_THREAD(coap_context_process, ev, data, buf) { PROCESS_BEGIN(); PROCESS_PAUSE(); while(1) { PROCESS_WAIT_EVENT(); if(ev == tcpip_event) { /* The data reading in Zephyr is done in coap_context_wait_data() or * equiv. so this code block is no-op atm. */ } } /* while (1) */ PROCESS_END(); } /*---------------------------------------------------------------------------*/ #else /* WITH_DTLS */ /*---------------------------------------------------------------------------*/ coap_context_t * coap_context_new(uip_ipaddr_t *my_addr, uint16_t port) { coap_context_t *ctx = NULL; int i; for(i = 0; i < MAX_CONTEXTS; i++) { if(!coap_contexts[i].is_used) { ctx = &coap_contexts[i]; break; } } if(ctx == NULL) { PRINTF("coap-context: no free contexts\n"); return NULL; } memset(ctx, 0, sizeof(coap_context_t)); #ifdef NETSTACK_CONF_WITH_IPV6 memcpy(&ctx->my_addr.in6_addr, my_addr, sizeof(ctx->my_addr.in6_addr)); #else memcpy(&ctx->my_addr.in_addr, my_addr, sizeof(ctx->my_addr.in_addr)); #endif ctx->my_port = port; ctx->is_used = 1; PRINTF("Listening on port %u\n", port); return ctx; } /*---------------------------------------------------------------------------*/ void coap_context_close(coap_context_t *coap_ctx) { if(coap_ctx == NULL || coap_ctx->is_used == 0) { /* Not opened */ return; } if(coap_ctx->buf && uip_udp_conn(coap_ctx->buf) != NULL) { uip_udp_remove(uip_udp_conn(coap_ctx->buf)); } coap_ctx->is_used = 0; } /*---------------------------------------------------------------------------*/ int coap_context_reply(coap_context_t *ctx, struct net_buf *buf) { int max_data_len, ret; max_data_len = IP_BUF_MAX_DATA - UIP_IPUDPH_LEN; PRINTF("%s: reply to peer data %p len %d\n", __FUNCTION__, ip_buf_appdata(buf), ip_buf_appdatalen(buf)); if (ip_buf_appdatalen(buf) > max_data_len) { PRINTF("%s: too much (%d bytes) data to send (max %d bytes)\n", __FUNCTION__, ip_buf_appdatalen(buf), max_data_len); ip_buf_unref(buf); ret = -EINVAL; goto out; } if (net_reply(ctx->net_ctx, buf)) { ip_buf_unref(buf); } out: return ret; } /*---------------------------------------------------------------------------*/ int coap_context_wait_data(coap_context_t *coap_ctx, int32_t ticks) { struct net_buf *buf; buf = net_receive(coap_ctx->net_ctx, ticks); if (buf) { PRINTF("coap-context: got message from "); PRINT6ADDR(&UIP_IP_BUF(buf)->srcipaddr); PRINTF(":%d %u bytes\n", uip_ntohs(UIP_UDP_BUF(buf)->srcport), uip_appdatalen(buf)); PRINTF("Received data appdata %p appdatalen %d\n", ip_buf_appdata(buf), ip_buf_appdatalen(buf)); coap_ctx->buf = buf; coap_engine_receive(coap_ctx); return 1; } return 0; } /*---------------------------------------------------------------------------*/ static int coap_context_send(coap_context_t *ctx, struct net_buf *buf) { int max_data_len, ret; max_data_len = IP_BUF_MAX_DATA - UIP_IPUDPH_LEN; PRINTF("%s: send to peer data %p len %d\n", __FUNCTION__, ip_buf_appdata(buf), ip_buf_appdatalen(buf)); if (ip_buf_appdatalen(buf) > max_data_len) { PRINTF("%s: too much (%d bytes) data to send (max %d bytes)\n", __FUNCTION__, ip_buf_appdatalen(buf), max_data_len); ip_buf_unref(buf); ret = -EINVAL; goto out; } ret = net_send(buf); if (ret < 0) { PRINT("%s: sending %d bytes failed\n", __FUNCTION__, ip_buf_appdatalen(buf)); ip_buf_unref(buf); } out: return ret; } /*---------------------------------------------------------------------------*/ int coap_context_send_message(coap_context_t *coap_ctx, uip_ipaddr_t *addr, uint16_t port, const uint8_t *data, uint16_t length) { int ret; if(coap_ctx == NULL || coap_ctx->is_used == 0) { PRINTF("coap-context: can not send on non-used context\n"); return 0; } ip_buf_appdatalen(coap_ctx->buf) = length; if (!uip_udp_conn(coap_ctx->buf)) { /* Normal send, not a reply */ ret = coap_context_send(coap_ctx, coap_ctx->buf); } else { /* We need to set the uIP buffer lengths in net_buf properly as * otherwise the final buffer length in * net_init.c:udp_prepare_and_send() will be usually incorrect * (uip_len() will be length of the received packet). So by setting * uip_len() to 0 here, we let the sending in net_init.c to * set the send buffer length correctly. */ uip_len(coap_ctx->buf) = 0; memcpy(ip_buf_appdata(coap_ctx->buf), data, length); ret = coap_context_reply(coap_ctx, coap_ctx->buf); } coap_ctx->buf = NULL; return ret; } /*---------------------------------------------------------------------------*/ int coap_context_connect(coap_context_t *coap_ctx, uip_ipaddr_t *addr, uint16_t port) { if(coap_ctx == NULL || coap_ctx->is_used == 0) { return 0; } #ifdef NETSTACK_CONF_WITH_IPV6 memcpy(&coap_ctx->addr.in6_addr, addr, sizeof(coap_ctx->addr.in6_addr)); coap_ctx->addr.family = AF_INET6; #else memcpy(&coap_ctx->addr.in_addr, addr, sizeof(coap_ctx->addr.in_addr)); coap_ctx->addr.family = AF_INET; #endif coap_ctx->port = port; coap_ctx->net_ctx = net_context_get(IPPROTO_UDP, (const struct net_addr *)&coap_ctx->addr, coap_ctx->port, (const struct net_addr *)&coap_ctx->my_addr, coap_ctx->my_port); if (!coap_ctx->net_ctx) { PRINTF("%s: Cannot get network context\n", __FUNCTION__); return 0; } PRINTF("coap-context: normal connect to ["); PRINT6ADDR(addr); PRINTF("]:%u\n", port); return 1; } #endif /* WITH_DTLS */ /*---------------------------------------------------------------------------*/ int coap_context_listen(coap_context_t *coap_ctx, uip_ipaddr_t *peer_addr, uint16_t peer_port) { if(coap_ctx == NULL || coap_ctx->is_used == 0) { return 0; } #ifdef NETSTACK_CONF_WITH_IPV6 memcpy(&coap_ctx->addr.in6_addr, peer_addr, sizeof(coap_ctx->addr.in6_addr)); coap_ctx->addr.family = AF_INET6; #else memcpy(&coap_ctx->addr.in_addr, peer_addr, sizeof(coap_ctx->addr.in_addr)); coap_ctx->addr.family = AF_INET; #endif coap_ctx->port = peer_port; coap_ctx->net_ctx = net_context_get(IPPROTO_UDP, (const struct net_addr *)&coap_ctx->addr, coap_ctx->port, (const struct net_addr *)&coap_ctx->my_addr, coap_ctx->my_port); if (!coap_ctx->net_ctx) { PRINTF("%s: Cannot get network context\n", __FUNCTION__); return 0; } return 1; }