From 799e9350a013e2fd1fa7cdb4c785ebe8830d757f Mon Sep 17 00:00:00 2001 From: Daniele Alessandrelli Date: Sun, 22 Feb 2015 17:01:46 +0100 Subject: [PATCH] er-coap: add client-side support for CoAP Observe Client-side support for CoAP observe is not compiled by default. To enable it, the COAP_OBSERVE_CLIENT macro must be defined equal to 1. --- apps/er-coap/Makefile.er-coap | 4 +- apps/er-coap/er-coap-engine.c | 10 + apps/er-coap/er-coap-engine.h | 1 + apps/er-coap/er-coap-observe-client.c | 342 ++++++++++++++++++++++++++ apps/er-coap/er-coap-observe-client.h | 121 +++++++++ 5 files changed, 477 insertions(+), 1 deletion(-) create mode 100644 apps/er-coap/er-coap-observe-client.c create mode 100644 apps/er-coap/er-coap-observe-client.h diff --git a/apps/er-coap/Makefile.er-coap b/apps/er-coap/Makefile.er-coap index 096b10e2e..23b70613e 100755 --- a/apps/er-coap/Makefile.er-coap +++ b/apps/er-coap/Makefile.er-coap @@ -1,4 +1,6 @@ -er-coap_src = er-coap.c er-coap-engine.c er-coap-transactions.c er-coap-observe.c er-coap-separate.c er-coap-res-well-known-core.c er-coap-block1.c +er-coap_src = er-coap.c er-coap-engine.c er-coap-transactions.c \ + er-coap-observe.c er-coap-separate.c er-coap-res-well-known-core.c \ + er-coap-block1.c er-coap-observe-client.c # Erbium will implement the REST Engine CFLAGS += -DREST=coap_rest_implementation diff --git a/apps/er-coap/er-coap-engine.c b/apps/er-coap/er-coap-engine.c index 3a7af5e5f..76a230545 100644 --- a/apps/er-coap/er-coap-engine.c +++ b/apps/er-coap/er-coap-engine.c @@ -253,6 +253,16 @@ coap_receive(void) } /* if(ACKed transaction) */ transaction = NULL; + +#if COAP_OBSERVE_CLIENT + /* if observe notification */ + if((message->type == COAP_TYPE_CON || message->type == COAP_TYPE_NON) + && IS_OPTION(message, COAP_OPTION_OBSERVE)) { + PRINTF("Observe [%u]\n", message->observe); + coap_handle_notification(&UIP_IP_BUF->srcipaddr, UIP_UDP_BUF->srcport, + message); + } +#endif /* COAP_OBSERVE_CLIENT */ } /* request or response */ } /* parsed correctly */ diff --git a/apps/er-coap/er-coap-engine.h b/apps/er-coap/er-coap-engine.h index d77d1fa78..c6c6ae676 100644 --- a/apps/er-coap/er-coap-engine.h +++ b/apps/er-coap/er-coap-engine.h @@ -44,6 +44,7 @@ #include "er-coap-transactions.h" #include "er-coap-observe.h" #include "er-coap-separate.h" +#include "er-coap-observe-client.h" #define SERVER_LISTEN_PORT UIP_HTONS(COAP_SERVER_PORT) diff --git a/apps/er-coap/er-coap-observe-client.c b/apps/er-coap/er-coap-observe-client.c new file mode 100644 index 000000000..21863200e --- /dev/null +++ b/apps/er-coap/er-coap-observe-client.c @@ -0,0 +1,342 @@ +/* + * Copyright (c) 2014, Daniele Alessandrelli. + * 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. + * + * This file is part of the Contiki operating system. + * + */ + +/* + * \file + * Extension to Erbium for enabling CoAP observe clients + * \author + * Daniele Alessandrelli + */ + +#include +#include + +#include "er-coap.h" +#include "er-coap-observe-client.h" + +/* Compile this code only if client-side support for CoAP Observe is required */ +#if COAP_OBSERVE_CLIENT + +#define DEBUG 1 +#if DEBUG +#define PRINTF(...) printf(__VA_ARGS__) +#define PRINT6ADDR(addr) PRINTF("[%02x%02x:%02x%02x:%02x%02x:%02x%02x:" \ + "%02x%02x:%02x%02x:%02x%02x:%02x%02x]", \ + ((uint8_t *)addr)[0], ((uint8_t *)addr)[1], \ + ((uint8_t *)addr)[2], ((uint8_t *)addr)[3], \ + ((uint8_t *)addr)[4], ((uint8_t *)addr)[5], \ + ((uint8_t *)addr)[6], ((uint8_t *)addr)[7], \ + ((uint8_t *)addr)[8], ((uint8_t *)addr)[9], \ + ((uint8_t *)addr)[10], ((uint8_t *)addr)[11], \ + ((uint8_t *)addr)[12], ((uint8_t *)addr)[13], \ + ((uint8_t *)addr)[14], ((uint8_t *)addr)[15]) +#define PRINTLLADDR(lladdr) PRINTF("[%02x:%02x:%02x:%02x:%02x:%02x]", \ + (lladdr)->addr[0], (lladdr)->addr[1], \ + (lladdr)->addr[2], (lladdr)->addr[3], \ + (lladdr)->addr[4], (lladdr)->addr[5]) +#else +#define PRINTF(...) +#define PRINT6ADDR(addr) +#define PRINTLLADDR(addr) +#endif + +MEMB(obs_subjects_memb, coap_observee_t, COAP_MAX_OBSERVEES); +LIST(obs_subjects_list); + +/*----------------------------------------------------------------------------*/ +static size_t +get_token(void *packet, const uint8_t **token) +{ + coap_packet_t *const coap_pkt = (coap_packet_t *)packet; + + *token = coap_pkt->token; + + return coap_pkt->token_len; +} +/*----------------------------------------------------------------------------*/ +static int +set_token(void *packet, const uint8_t *token, size_t token_len) +{ + coap_packet_t *const coap_pkt = (coap_packet_t *)packet; + + coap_pkt->token_len = MIN(COAP_TOKEN_LEN, token_len); + memcpy(coap_pkt->token, token, coap_pkt->token_len); + + return coap_pkt->token_len; +} +/*----------------------------------------------------------------------------*/ +coap_observee_t * +coap_obs_add_observee(uip_ipaddr_t *addr, uint16_t port, + const uint8_t *token, size_t token_len, const char *url, + notification_callback_t notification_callback, + void *data) +{ + coap_observee_t *o; + + /* Remove existing observe relationship, if any. */ + coap_obs_remove_observee_by_url(addr, port, url); + o = memb_alloc(&obs_subjects_memb); + if(o) { + o->url = url; + uip_ipaddr_copy(&o->addr, addr); + o->port = port; + o->token_len = token_len; + memcpy(o->token, token, token_len); + /* o->last_mid = 0; */ + o->notification_callback = notification_callback; + o->data = data; + /* stimer_set(&o->refresh_timer, COAP_OBSERVING_REFRESH_INTERVAL); */ + PRINTF("Adding obs_subject for /%s [0x%02X%02X]\n", o->url, o->token[0], + o->token[1]); + list_add(obs_subjects_list, o); + } + + return o; +} +/*----------------------------------------------------------------------------*/ +void +coap_obs_remove_observee(coap_observee_t *o) +{ + PRINTF("Removing obs_subject for /%s [0x%02X%02X]\n", o->url, o->token[0], + o->token[1]); + memb_free(&obs_subjects_memb, o); + list_remove(obs_subjects_list, o); +} +/*----------------------------------------------------------------------------*/ +coap_observee_t * +coap_get_obs_subject_by_token(const uint8_t *token, size_t token_len) +{ + coap_observee_t *obs = NULL; + + for(obs = (coap_observee_t *)list_head(obs_subjects_list); obs; + obs = obs->next) { + PRINTF("Looking for token 0x%02X%02X\n", token[0], token[1]); + if(obs->token_len == token_len + && memcmp(obs->token, token, token_len) == 0) { + return obs; + } + } + + return NULL; +} +/*----------------------------------------------------------------------------*/ +int +coap_obs_remove_observee_by_token(uip_ipaddr_t *addr, uint16_t port, + uint8_t *token, size_t token_len) +{ + int removed = 0; + coap_observee_t *obs = NULL; + + for(obs = (coap_observee_t *)list_head(obs_subjects_list); obs; + obs = obs->next) { + PRINTF("Remove check Token 0x%02X%02X\n", token[0], token[1]); + if(uip_ipaddr_cmp(&obs->addr, addr) + && obs->port == port + && obs->token_len == token_len + && memcmp(obs->token, token, token_len) == 0) { + coap_obs_remove_observee(obs); + removed++; + } + } + return removed; +} +/*----------------------------------------------------------------------------*/ +int +coap_obs_remove_observee_by_url(uip_ipaddr_t *addr, uint16_t port, + const char *url) +{ + int removed = 0; + coap_observee_t *obs = NULL; + + for(obs = (coap_observee_t *)list_head(obs_subjects_list); obs; + obs = obs->next) { + PRINTF("Remove check URL %s\n", url); + if(uip_ipaddr_cmp(&obs->addr, addr) + && obs->port == port + && (obs->url == url || memcmp(obs->url, url, strlen(obs->url)) == 0)) { + coap_obs_remove_observee(obs); + removed++; + } + } + return removed; +} +/*----------------------------------------------------------------------------*/ +static void +simple_reply(coap_message_type_t type, uip_ip6addr_t *addr, uint16_t port, + coap_packet_t *notification) +{ + static coap_packet_t response[1]; + size_t len; + + coap_init_message(response, type, NO_ERROR, notification->mid); + len = coap_serialize_message(response, uip_appdata); + coap_send_message(addr, port, uip_appdata, len); +} +/*----------------------------------------------------------------------------*/ +static coap_notification_flag_t +classify_notification(void *response, int first) +{ + coap_packet_t *pkt; + + pkt = (coap_packet_t *)response; + if(!pkt) { + PRINTF("no response\n"); + return NO_REPLY_FROM_SERVER; + } + PRINTF("server replied\n"); + if(!IS_RESPONSE_CODE_2_XX(pkt)) { + PRINTF("error response code\n"); + return ERROR_RESPONSE_CODE; + } + if(!IS_OPTION(pkt, COAP_OPTION_OBSERVE)) { + PRINTF("server does not support observe\n"); + return OBSERVE_NOT_SUPPORTED; + } + if(first) { + return OBSERVE_OK; + } + return NOTIFICATION_OK; +} +/*----------------------------------------------------------------------------*/ +void +coap_handle_notification(uip_ipaddr_t *addr, uint16_t port, + coap_packet_t *notification) +{ + coap_packet_t *pkt; + const uint8_t *token; + int token_len; + coap_observee_t *obs; + coap_notification_flag_t flag; + uint32_t observe; + + PRINTF("coap_handle_notification()\n"); + pkt = (coap_packet_t *)notification; + token_len = get_token(pkt, &token); + PRINTF("Getting token\n"); + if(0 == token_len) { + PRINTF("Error while handling coap observe notification: " + "no token in message\n"); + return; + } + PRINTF("Getting observee info\n"); + obs = coap_get_obs_subject_by_token(token, token_len); + if(NULL == obs) { + PRINTF("Error while handling coap observe notification: " + "no matching token found\n"); + simple_reply(COAP_TYPE_RST, addr, port, notification); + return; + } + if(notification->type == COAP_TYPE_CON) { + simple_reply(COAP_TYPE_ACK, addr, port, notification); + } + if(obs->notification_callback != NULL) { + flag = classify_notification(notification, 0); + /* TODO: the following mechanism for discarding duplicates is too trivial */ + /* refer to Observe RFC for a better solution */ + if(flag == NOTIFICATION_OK) { + coap_get_header_observe(notification, &observe); + if(observe == obs->last_observe) { + PRINTF("Discarding duplicate\n"); + return; + } + obs->last_observe = observe; + } + obs->notification_callback(obs, notification, flag); + } +} +/*----------------------------------------------------------------------------*/ +static void +handle_obs_registration_response(void *data, void *response) +{ + coap_observee_t *obs; + notification_callback_t notification_callback; + coap_notification_flag_t flag; + + PRINTF("handle_obs_registration_response(): "); + obs = (coap_observee_t *)data; + notification_callback = obs->notification_callback; + flag = classify_notification(response, 1); + if(notification_callback) { + notification_callback(obs, response, flag); + } + if(flag != OBSERVE_OK) { + coap_obs_remove_observee(obs); + } +} +/*----------------------------------------------------------------------------*/ +uint8_t +coap_generate_token(uint8_t **token_ptr) +{ + static uint8_t token = 0; + + token++; + /* FIXME: we should check that this token is not already used */ + *token_ptr = (uint8_t *)&token; + return sizeof(token); +} +/*----------------------------------------------------------------------------*/ +coap_observee_t * +coap_obs_request_registration(uip_ipaddr_t *addr, uint16_t port, char *uri, + notification_callback_t notification_callback, + void *data) +{ + coap_packet_t request[1]; + coap_transaction_t *t; + uint8_t *token; + uint8_t token_len; + coap_observee_t *obs; + + obs = NULL; + coap_init_message(request, COAP_TYPE_CON, COAP_GET, coap_get_mid()); + coap_set_header_uri_path(request, uri); + coap_set_header_observe(request, 0); + token_len = coap_generate_token(&token); + set_token(request, token, token_len); + t = coap_new_transaction(request->mid, addr, port); + if(t) { + obs = coap_obs_add_observee(addr, port, (uint8_t *)token, token_len, uri, + notification_callback, data); + if(obs) { + t->callback = handle_obs_registration_response; + t->callback_data = obs; + t->packet_len = coap_serialize_message(request, t->packet); + coap_send_transaction(t); + } else { + PRINTF("Could not allocate obs_subject resource buffer"); + coap_clear_transaction(t); + } + } else { + PRINTF("Could not allocate transaction buffer"); + } + return obs; +} +#endif /* COAP_OBSERVE_CLIENT */ diff --git a/apps/er-coap/er-coap-observe-client.h b/apps/er-coap/er-coap-observe-client.h new file mode 100644 index 000000000..d0ece32c0 --- /dev/null +++ b/apps/er-coap/er-coap-observe-client.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2014, Daniele Alessandrelli. + * 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. + * + * This file is part of the Contiki operating system. + * + */ + +/* + * \file + * Extension to Erbium for enabling CoAP observe clients + * \author + * Daniele Alessandrelli + */ + +#ifndef COAP_OBSERVING_CLIENT_H_ +#define COAP_OBSERVING_CLIENT_H_ + +#include "er-coap.h" +#include "er-coap-transactions.h" + +#ifndef COAP_OBSERVE_CLIENT +#define COAP_OBSERVE_CLIENT 0 +#endif + +#ifdef COAP_CONF_MAX_OBSERVEES +#define COAP_MAX_OBSERVEES COAP_CONF_MAX_OBSERVEES +#else +#define COAP_MAX_OBSERVEES 4 +#endif /* COAP_CONF_MAX_OBSERVEES */ + +#if COAP_MAX_OPEN_TRANSACTIONS < COAP_MAX_OBSERVEES +#warning "COAP_MAX_OPEN_TRANSACTIONS smaller than COAP_MAX_OBSERVEES: " \ + "this may be a problem" +#endif + +#define IS_RESPONSE_CODE_2_XX(message) (64 < message->code \ + && message->code < 128) + +/*----------------------------------------------------------------------------*/ +typedef enum { + OBSERVE_OK, + NOTIFICATION_OK, + OBSERVE_NOT_SUPPORTED, + ERROR_RESPONSE_CODE, + NO_REPLY_FROM_SERVER, +} coap_notification_flag_t; + +/*----------------------------------------------------------------------------*/ +typedef struct coap_observee_s coap_observee_t; + +typedef void (*notification_callback_t)(coap_observee_t *subject, + void *notification, + coap_notification_flag_t); + +struct coap_observee_s { + coap_observee_t *next; /* for LIST */ + uip_ipaddr_t addr; + uint16_t port; + const char *url; + uint8_t token_len; + uint8_t token[COAP_TOKEN_LEN]; + void *data; /* generic pointer for storing user data */ + notification_callback_t notification_callback; + uint32_t last_observe; +}; + +/*----------------------------------------------------------------------------*/ +coap_observee_t *coap_obs_add_observee(uip_ipaddr_t *addr, uint16_t port, + const uint8_t *token, size_t token_len, + const char *url, + notification_callback_t + notification_callback, void *data); + +void coap_obs_remove_observee(coap_observee_t *o); + +coap_observee_t *coap_obs_get_observee_by_token(const uint8_t *token, + size_t token_len); + +int coap_obs_remove_observee_by_token(uip_ipaddr_t *addr, uint16_t port, + uint8_t *token, size_t token_len); + +int coap_obs_remove_observee_by_url(uip_ipaddr_t *addr, uint16_t port, + const char *url); + +void coap_handle_notification(uip_ipaddr_t *, uint16_t port, + coap_packet_t *notification); + +coap_observee_t *coap_obs_request_registration(uip_ipaddr_t *addr, + uint16_t port, char *uri, + notification_callback_t + notification_callback, + void *data); +/* TODO: this function may be moved to er-coap.c */ +uint8_t coap_generate_token(uint8_t **token_ptr); + +#endif /* COAP_OBSERVING_CLIENT_H_ */