Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
src/exosite.c
- Committer:
- Patrick Barrett
- Date:
- 2015-01-07
- Revision:
- 29:004c318e63fa
- Parent:
- 27:786cdb9c3605
File content as of revision 29:004c318e63fa:
/***************************************************************************** * * exosite.c - Exosite Activator Library * Copyright (C) 2015 Exosite LLC * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of Texas Instruments Incorporated 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 COPYRIGHT HOLDERS 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 COPYRIGHT * OWNER 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. * *****************************************************************************/ #include "exosite_pal.h" #include "exosite.h" #include "coap.h" #include <stdlib.h> #include <stdbool.h> #include <time.h> #include <string.h> #include <ctype.h> // Internal Functions static void exo_process_waiting_datagrams(exo_op *op, uint8_t count); static void exo_process_active_ops(exo_op *op, uint8_t count); exo_error exo_build_msg_activate(coap_pdu *pdu, const char *vendor, const char *model, const char *serial_number); exo_error exo_build_msg_read(coap_pdu *pdu, const char *alias); exo_error exo_build_msg_observe(coap_pdu *pdu, const char *alias); exo_error exo_build_msg_write(coap_pdu *pdu, const char *alias, const char *value); exo_error exo_build_msg_rst(coap_pdu *pdu, const uint16_t mid, const uint64_t token, const uint8_t tkl); exo_error exo_build_msg_ack(coap_pdu *pdu, const uint16_t mid); uint8_t exosite_validate_cik(char *cik); // Local Variables static char cik[CIK_LENGTH + 1]; static const char *vendor; static const char *model; static const char *serial; static uint16_t message_id_counter; static exo_device_state device_state = EXO_STATE_UNINITIALIZED; // Internal Constants static const int MINIMUM_DATAGRAM_SIZE = 576; // RFC791: all hosts must accept minimum of 576 octets /*! * \brief Initializes the Exosite library * * This **MUST** be called before any other exosite library calls are called. * * \param[in] *vendor Pointer to string containing the vendor name * \param[in] *model Pointer to string containing the model name * \param[in] *serial Pointer to string containing the serial number * * \return EXO_ERROR, EXO_OK on success, else error code */ exo_error exo_init(const char *vendor_in, const char *model_in, const char *serial_in) { device_state = EXO_STATE_UNINITIALIZED; if (exopal_init() != 0) { return EXO_FATAL_ERROR_PAL; } srand(time(NULL)); message_id_counter = rand(); serial = serial_in; vendor = vendor_in; model = model_in; if (exopal_retrieve_cik(cik) > 1){ return EXO_FATAL_ERROR_PAL; } else { cik[40] = 0; } if (exopal_udp_sock() != 0) { return EXO_FATAL_ERROR_PAL; } device_state = EXO_STATE_INITIALIZED; return EXO_OK; } /*! * \brief Queues a Write to the Exosite One Platform * * Queues a request to write to a dataport. * * \param[in] *alias Alias of dataport to write to, pointer must remain * valid until the request has been sent. * \param[in] callback Function pointer to call on success * * \return EXO_ERROR, EXO_OK on success or error code * */ void exo_write(exo_op *op, const char * alias, const char * value) { op->type = EXO_WRITE; op->state = EXO_REQUEST_NEW; op->alias = alias; op->value = (char *)value; // this is kinda dirty, I know op->value_max = 0; op->mid = 0; } /*! * \brief Queues a Read from the Exosite One Platform * * Queues a request to read a dataport. If the request is successful, the * provided callback will be called with a single parameter, a pointer to the * result as a C string. * * \param[in] *alias Alias of dataport to read from, pointer must remain * valid until the request has been sent. * \param[in] callback Function pointer to call on success * * \return EXO_ERROR, EXO_OK on success or error code * */ void exo_read(exo_op *op, const char * alias, char * value, const size_t value_max) { op->type = EXO_READ; op->state = EXO_REQUEST_NEW; op->alias = alias; op->value = value; op->value_max = value_max; op->mid = 0; } /*! * \brief Subscribes to a Dataport on the Exosite One Platform * * Begins a subscription to a dataport The callback is called any time the * value is updated as well as when the subscription is started. * * \param[in] alias Alias of dataport to read from * \param[in] callback Function pointer to call on change * * \return EXO_ERROR, EXO_OK on success or error code * */ void exo_subscribe(exo_op *op, const char * alias, char * value, const size_t value_max) { op->type = EXO_SUBSCRIBE; op->state = EXO_REQUEST_NEW; op->alias = alias; op->value = value; op->value_max = value_max; op->mid = 0; } /*! * \brief Activates the Device on the Platform * * Queues an activation request. Usually only used internally. * */ void exo_activate(exo_op *op) { op->type = EXO_ACTIVATE; op->state = EXO_REQUEST_NEW; op->alias = NULL; op->value = NULL; op->value_max = 0; op->mid = 0; } void exo_op_init(exo_op *op) { op->type = EXO_NULL; op->state = EXO_REQUEST_NULL; op->alias = NULL; op->value = NULL; op->value_max = 0; op->mid = 0; op->obs_seq = 0; op->tkl = 0; op->token = 0; op->timeout = 0; op->retries = 0; } void exo_op_done(exo_op *op) { if (exo_is_op_subscribe(op)) { op->state = EXO_REQUEST_SUBSCRIBED; } else { exo_op_init(op); } } uint8_t exo_is_op_valid(exo_op *op) { return op->type != EXO_NULL; } uint8_t exo_is_op_success(exo_op *op) { return op->state == EXO_REQUEST_SUCCESS; } uint8_t exo_is_op_finished(exo_op *op) { return op->state == EXO_REQUEST_SUCCESS || op->state == EXO_REQUEST_ERROR; } uint8_t exo_is_op_read(exo_op *op) { return op->type == EXO_READ; } uint8_t exo_is_op_subscribe(exo_op *op) { return op->type == EXO_SUBSCRIBE; } uint8_t exo_is_op_write(exo_op *op) { return op->type == EXO_WRITE; } /*! * \brief Performs queued operations with the Exosite One Platform * * Queues a request to read a dataport from the Exosite One Platform. If the * request is successful the provided callback will be called with a single * parameter, a pointer to the result as a C string. * * \param[in] alias Alias of dataport to read from * \param[in] callback Function pointer to call on success * * \return EXO_STATE, EXO_OK on success or error code * */ exo_state exo_operate(exo_op *op, uint8_t count) { int i; switch (device_state){ case EXO_STATE_UNINITIALIZED: return EXO_ERROR; case EXO_STATE_INITIALIZED: case EXO_STATE_BAD_CIK: if (op[0].state == EXO_REQUEST_NULL || op[0].timeout < exopal_get_time()) exo_activate(&op[0]); case EXO_STATE_GOOD: break; } exo_process_waiting_datagrams(op, count); exo_process_active_ops(op, count); for (i = 0; i < count; i++) { if (op[i].state == EXO_REQUEST_NEW) return EXO_BUSY; } for (i = 0; i < count; i++) { if (op[i].state == EXO_REQUEST_PENDING) return EXO_WAITING; } return EXO_IDLE; } // Internal Functions static void exo_process_waiting_datagrams(exo_op *op, uint8_t count) { uint8_t buf[MINIMUM_DATAGRAM_SIZE]; coap_pdu pdu; coap_option opt; coap_payload payload; int i; pdu.buf = buf; pdu.max = MINIMUM_DATAGRAM_SIZE; pdu.len = 0; // receive a UDP packet if one or more waiting while (exopal_udp_recv(pdu.buf, pdu.max, &pdu.len) == 0) { if (coap_validate_pkt(&pdu) != CE_NONE) continue; //Invalid Packet, Ignore for (i = 0; i < count; i++) { if (coap_get_type(&pdu) == CT_CON || coap_get_type(&pdu) == CT_NON) { if (op[i].state == EXO_REQUEST_SUBSCRIBED && coap_get_token(&pdu) == op[i].token) { if (coap_get_code(&pdu) == CC_CONTENT) { uint32_t new_seq = 0; opt = coap_get_option_by_num(&pdu, CON_OBSERVE, 0); for (int j = 0; j < opt.len; j++) { new_seq = (new_seq << (8*j)) | opt.val[j]; } payload = coap_get_payload(&pdu); if (payload.len == 0) { op[i].value[0] = '\0'; } else if (payload.len+1 > op[i].value_max || op[i].value == 0) { op[i].state = EXO_REQUEST_ERROR; } else{ memcpy(op[i].value, payload.val, payload.len); op[i].value[payload.len] = 0; op[i].mid = coap_get_mid(&pdu); // TODO: User proper logic to ensure it's a new value not a different, but old one. if (op[i].obs_seq != new_seq) { op[i].state = EXO_REQUEST_SUB_ACK_NEW; op[i].obs_seq = new_seq; } else { op[i].state = EXO_REQUEST_SUB_ACK; } opt = coap_get_option_by_num(&pdu, CON_MAX_AGE, 0); uint8_t max_age = 120; // default, 2 minutes, see RFC4787 Sec 4.3 if (opt.num !=0 && opt.len == 1){ // if has Max-Age option, use it max_age = opt.val[0]; } // Set timeout between Max-Age to Max-Age + ACK_RANDOM_FACTOR (CoAP Defined) op[i].timeout = exopal_get_time() + (max_age * 1000000) + (((uint64_t)rand() % 1500000)); } } else if (coap_get_code_class(&pdu) != 2) { op[i].state = EXO_REQUEST_ERROR; } break; } } else if (coap_get_type(&pdu) == CT_ACK) { if (op[i].state == EXO_REQUEST_PENDING && op[i].mid == coap_get_mid(&pdu)) { if (coap_get_code_class(&pdu) == 2) { switch (op[i].type) { case EXO_WRITE: op[i].state = EXO_REQUEST_SUCCESS; break; case EXO_READ: payload = coap_get_payload(&pdu); if (payload.len == 0) { op[i].value[0] = '\0'; } else if (payload.len+1 > op[i].value_max || op[i].value == 0) { op[i].state = EXO_REQUEST_ERROR; } else{ memcpy(op[i].value, payload.val, payload.len); op[i].value[payload.len] = 0; op[i].state = EXO_REQUEST_SUCCESS; } break; case EXO_SUBSCRIBE: payload = coap_get_payload(&pdu); if (payload.len == 0) { op[i].value[0] = '\0'; } else if (payload.len+1 > op[i].value_max || op[i].value == 0) { op[i].state = EXO_REQUEST_ERROR; } else{ memcpy(op[i].value, payload.val, payload.len); op[i].value[payload.len] = 0; op[i].state = EXO_REQUEST_SUCCESS; opt = coap_get_option_by_num(&pdu, CON_MAX_AGE, 0); uint8_t max_age = 120; // default, 2 minutes, see RFC4787 Sec 4.3 if (opt.num !=0 && opt.len == 1){ // if has Max-Age option, use it max_age = opt.val[0]; } opt = coap_get_option_by_num(&pdu, CON_OBSERVE, 0); for (int j = 0; j < opt.len; j++) { op[i].obs_seq = (op[i].obs_seq << (8*j)) | opt.val[j]; } // Set timeout between Max-Age to Max-Age + ACK_RANDOM_FACTOR (CoAP Defined) op[i].timeout = exopal_get_time() + (max_age * 1000000) + (((uint64_t)rand() % 1500000)); } break; case EXO_ACTIVATE: payload = coap_get_payload(&pdu); if (payload.len == CIK_LENGTH) { memcpy(cik, payload.val, CIK_LENGTH); cik[CIK_LENGTH] = 0; op[i].state = EXO_REQUEST_SUCCESS; exopal_store_cik(cik); device_state = EXO_STATE_GOOD; } else { op[i].state = EXO_REQUEST_ERROR; } // We're done with this op now. exo_op_init(&op[i]); break; case EXO_NULL: // pending null request? shouldn't be possible continue; } } else { op[i].state = EXO_REQUEST_ERROR; if (coap_get_code(&pdu) == CC_UNAUTHORIZED){ //device_state = EXO_STATE_BAD_CIK; if (op[0].type == EXO_NULL || op[0].timeout < exopal_get_time()) exo_activate(&op[0]); } else if (coap_get_code(&pdu) == CC_NOT_FOUND) { device_state = EXO_STATE_GOOD; } } break; } } else if (coap_get_type(&pdu) == CT_RST) { if ((op[i].state == EXO_REQUEST_PENDING || op[i].state == EXO_REQUEST_SUBSCRIBED) && (op[i].mid == coap_get_mid(&pdu) && op[i].token == coap_get_token(&pdu))){ op[i].state = EXO_REQUEST_ERROR; break; } } } // if the above loop ends normally we don't recognize message, reply RST if (i == count){ if (coap_get_type(&pdu) == CT_CON) { // this can't fail exo_build_msg_rst(&pdu, coap_get_mid(&pdu), coap_get_token(&pdu), coap_get_tkl(&pdu)); // best effort, don't bother checking if it failed, nothing we can do it // it did anyway exopal_udp_send(pdu.buf, pdu.len); } break; } } } // process all ops that are in an active state static void exo_process_active_ops(exo_op *op, uint8_t count) { uint8_t buf[MINIMUM_DATAGRAM_SIZE]; coap_pdu pdu; int i; uint64_t now = exopal_get_time(); pdu.buf = buf; pdu.max = MINIMUM_DATAGRAM_SIZE; pdu.len = 0; for (i = 0; i < count; i++) { switch (op[i].state) { case EXO_REQUEST_NEW: // Build and Send Request switch (op[i].type) { case EXO_READ: exo_build_msg_read(&pdu, op[i].alias); break; case EXO_SUBSCRIBE: exo_build_msg_observe(&pdu, op[i].alias); break; case EXO_WRITE: exo_build_msg_write(&pdu, op[i].alias, op[i].value); break; case EXO_ACTIVATE: exo_build_msg_activate(&pdu, vendor, model, serial); break; default: op[i].type = EXO_NULL; continue; } if (exopal_udp_send(pdu.buf, pdu.len) == 0) { op[i].state = EXO_REQUEST_PENDING; op[i].timeout = exopal_get_time() + 4000000; op[i].mid = coap_get_mid(&pdu); op[i].token = coap_get_token(&pdu); } break; case EXO_REQUEST_SUBSCRIBED: case EXO_REQUEST_PENDING: // check if pending requests have reached timeout if (op[i].timeout <= now){ switch (op[i].type) { case EXO_READ: case EXO_WRITE: case EXO_ACTIVATE: if (op[i].retries < COAP_MAX_RETRANSMIT){ switch (op[i].type) { case EXO_READ: exo_build_msg_read(&pdu, op[i].alias); break; case EXO_WRITE: exo_build_msg_write(&pdu, op[i].alias, op[i].value); break; case EXO_ACTIVATE: exo_build_msg_activate(&pdu, vendor, model, serial); break; default: break; } // reuse old mid and token coap_set_mid(&pdu, op[i].mid); coap_set_token(&pdu, op[i].token, op[i].tkl); if (exopal_udp_send(pdu.buf, pdu.len) == 0) { op[i].retries++; op[i].timeout = exopal_get_time() + (op[i].retries * COAP_PROBING_RATE * 1000000) + (((uint64_t)rand() % 1500000)); } } else { op[i].state = EXO_REQUEST_ERROR; } break; case EXO_SUBSCRIBE: // force a new observe request op[i].state = EXO_REQUEST_NEW; break; default: break; } } break; case EXO_REQUEST_SUB_ACK_NEW: case EXO_REQUEST_SUB_ACK: // send ack for observe notification exo_build_msg_ack(&pdu, coap_get_mid(&pdu)); if (exopal_udp_send(pdu.buf, pdu.len) == 0) { if (op[i].state == EXO_REQUEST_SUB_ACK) op[i].state = EXO_REQUEST_SUBSCRIBED; else if (op[i].state == EXO_REQUEST_SUB_ACK_NEW) op[i].state = EXO_REQUEST_SUCCESS; } break; default: break; } } } exo_error exo_build_msg_activate(coap_pdu *pdu, const char *vendor, const char *model, const char *serial_number) { coap_error ret; coap_init_pdu(pdu); ret = coap_set_version(pdu, COAP_V1); ret |= coap_set_type(pdu, CT_CON); ret |= coap_set_code(pdu, CC_POST); ret |= coap_set_mid(pdu, message_id_counter++); ret |= coap_set_token(pdu, rand(), 2); ret |= coap_add_option(pdu, CON_URI_PATH, (uint8_t*)"provision", 9); ret |= coap_add_option(pdu, CON_URI_PATH, (uint8_t*)"activate", 8); ret |= coap_add_option(pdu, CON_URI_PATH, (uint8_t*)vendor, strlen(vendor)); ret |= coap_add_option(pdu, CON_URI_PATH, (uint8_t*)model, strlen(model)); ret |= coap_add_option(pdu, CON_URI_PATH, (uint8_t*)serial_number, strlen(serial_number)); if (ret != CE_NONE) return EXO_GENERAL_ERROR; return EXO_OK; } exo_error exo_build_msg_read(coap_pdu *pdu, const char *alias) { coap_error ret; coap_init_pdu(pdu); ret = coap_set_version(pdu, COAP_V1); ret |= coap_set_type(pdu, CT_CON); ret |= coap_set_code(pdu, CC_GET); ret |= coap_set_mid(pdu, message_id_counter++); ret |= coap_set_token(pdu, rand(), 2); ret |= coap_add_option(pdu, CON_URI_PATH, (uint8_t*)"1a", 2); ret |= coap_add_option(pdu, CON_URI_PATH, (uint8_t*)alias, strlen(alias)); ret |= coap_add_option(pdu, CON_URI_QUERY, (uint8_t*)cik, 40); if (ret != CE_NONE) return EXO_GENERAL_ERROR; return EXO_OK; } exo_error exo_build_msg_observe(coap_pdu *pdu, const char *alias) { uint8_t obs_opt = 0; coap_error ret; coap_init_pdu(pdu); ret = coap_set_version(pdu, COAP_V1); ret |= coap_set_type(pdu, CT_CON); ret |= coap_set_code(pdu, CC_GET); ret |= coap_set_mid(pdu, message_id_counter++); ret |= coap_set_token(pdu, rand(), 2); ret |= coap_add_option(pdu, CON_OBSERVE, &obs_opt, 1); ret |= coap_add_option(pdu, CON_URI_PATH, (uint8_t*)"1a", 2); ret |= coap_add_option(pdu, CON_URI_PATH, (uint8_t*)alias, strlen(alias)); ret |= coap_add_option(pdu, CON_URI_QUERY, (uint8_t*)cik, 40); if (ret != CE_NONE) return EXO_GENERAL_ERROR; return EXO_OK; } exo_error exo_build_msg_write(coap_pdu *pdu, const char *alias, const char *value) { coap_error ret; coap_init_pdu(pdu); ret = coap_set_version(pdu, COAP_V1); ret |= coap_set_type(pdu, CT_CON); ret |= coap_set_code(pdu, CC_POST); ret |= coap_set_mid(pdu, message_id_counter++); ret |= coap_set_token(pdu, rand(), 2); ret |= coap_add_option(pdu, CON_URI_PATH, (uint8_t*)"1a", 2); ret |= coap_add_option(pdu, CON_URI_PATH, (uint8_t*)alias, strlen(alias)); ret |= coap_add_option(pdu, CON_URI_QUERY, (uint8_t*)cik, 40); ret |= coap_set_payload(pdu, (uint8_t *)value, strlen(value)); if (ret != CE_NONE) return EXO_GENERAL_ERROR; return EXO_OK; } exo_error exo_build_msg_rst(coap_pdu *pdu, const uint16_t mid, const uint64_t token, const uint8_t tkl) { coap_error ret; coap_init_pdu(pdu); ret = coap_set_version(pdu, COAP_V1); ret |= coap_set_type(pdu, CT_RST); ret |= coap_set_code(pdu, CC_EMPTY); ret |= coap_set_mid(pdu, mid); ret |= coap_set_token(pdu, token, tkl); if (ret != CE_NONE) return EXO_GENERAL_ERROR; return EXO_OK; } exo_error exo_build_msg_ack(coap_pdu *pdu, const uint16_t mid) { coap_error ret; coap_init_pdu(pdu); ret = coap_set_version(pdu, COAP_V1); ret |= coap_set_type(pdu, CT_ACK); ret |= coap_set_code(pdu, CC_EMPTY); ret |= coap_set_mid(pdu, mid); if (ret != CE_NONE) return EXO_GENERAL_ERROR; return EXO_OK; } exo_error exo_do_activate() { exo_op op; exo_op_init(&op); return EXO_OK; } uint8_t exo_util_is_ascii_hex(const char *str, size_t len) { size_t i; for (i = 0; i < len; i++) { if (!((str[i] >= 'a' && str[i] <= 'f') || (str[i] >= '0' && str[i] <= '9'))) { return 1; } } return 0; }