mirror of
https://github.com/oliverschmidt/contiki.git
synced 2024-12-22 10:30:13 +00:00
fa6771f058
oma-lwm2m: Add functionality to send object instance list as core link format
1129 lines
37 KiB
C
1129 lines
37 KiB
C
/*
|
|
* Copyright (c) 2015, Yanzi Networks 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 copyright holder 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 HOLDER 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 HOLDER 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.
|
|
*/
|
|
|
|
/**
|
|
* \addtogroup oma-lwm2m
|
|
* @{
|
|
*/
|
|
|
|
/**
|
|
* \file
|
|
* Implementation of the Contiki OMA LWM2M engine
|
|
* \author
|
|
* Joakim Eriksson <joakime@sics.se>
|
|
* Niclas Finne <nfi@sics.se>
|
|
*/
|
|
|
|
#include "contiki.h"
|
|
#include "lwm2m-engine.h"
|
|
#include "lwm2m-object.h"
|
|
#include "lwm2m-device.h"
|
|
#include "lwm2m-plain-text.h"
|
|
#include "lwm2m-json.h"
|
|
#include "rest-engine.h"
|
|
#include "er-coap-constants.h"
|
|
#include "er-coap-engine.h"
|
|
#include "oma-tlv.h"
|
|
#include "oma-tlv-reader.h"
|
|
#include "oma-tlv-writer.h"
|
|
#include "net/ipv6/uip-ds6.h"
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <inttypes.h>
|
|
|
|
#if UIP_CONF_IPV6_RPL
|
|
#include "net/rpl/rpl.h"
|
|
#endif /* UIP_CONF_IPV6_RPL */
|
|
|
|
#define DEBUG DEBUG_NONE
|
|
#include "net/ip/uip-debug.h"
|
|
|
|
#ifndef LWM2M_ENGINE_CLIENT_ENDPOINT_PREFIX
|
|
#ifdef LWM2M_DEVICE_MODEL_NUMBER
|
|
#define LWM2M_ENGINE_CLIENT_ENDPOINT_PREFIX LWM2M_DEVICE_MODEL_NUMBER
|
|
#else /* LWM2M_DEVICE_MODEL_NUMBER */
|
|
#define LWM2M_ENGINE_CLIENT_ENDPOINT_PREFIX "Contiki-"
|
|
#endif /* LWM2M_DEVICE_MODEL_NUMBER */
|
|
#endif /* LWM2M_ENGINE_CLIENT_ENDPOINT_PREFIX */
|
|
|
|
#ifdef LWM2M_ENGINE_CONF_MAX_OBJECTS
|
|
#define MAX_OBJECTS LWM2M_ENGINE_CONF_MAX_OBJECTS
|
|
#else /* LWM2M_ENGINE_CONF_MAX_OBJECTS */
|
|
#define MAX_OBJECTS 10
|
|
#endif /* LWM2M_ENGINE_CONF_MAX_OBJECTS */
|
|
|
|
#define REMOTE_PORT UIP_HTONS(COAP_DEFAULT_PORT)
|
|
#define BS_REMOTE_PORT UIP_HTONS(5685)
|
|
|
|
static const lwm2m_object_t *objects[MAX_OBJECTS];
|
|
static char endpoint[32];
|
|
static char rd_data[128]; /* allocate some data for the RD */
|
|
|
|
PROCESS(lwm2m_rd_client, "LWM2M Engine");
|
|
|
|
static uip_ipaddr_t server_ipaddr;
|
|
static uint16_t server_port = REMOTE_PORT;
|
|
static uip_ipaddr_t bs_server_ipaddr;
|
|
static uint16_t bs_server_port = BS_REMOTE_PORT;
|
|
|
|
static uint8_t use_bootstrap = 0;
|
|
static uint8_t has_bootstrap_server_info = 0;
|
|
static uint8_t use_registration = 0;
|
|
static uint8_t has_registration_server_info = 0;
|
|
static uint8_t registered = 0;
|
|
static uint8_t bootstrapped = 0; /* bootstrap made... */
|
|
|
|
void lwm2m_device_init(void);
|
|
void lwm2m_security_init(void);
|
|
void lwm2m_server_init(void);
|
|
|
|
static const lwm2m_instance_t *get_first_instance_of_object(uint16_t id, lwm2m_context_t *context);
|
|
static const lwm2m_instance_t *get_instance(const lwm2m_object_t *object, lwm2m_context_t *context, int depth);
|
|
static const lwm2m_resource_t *get_resource(const lwm2m_instance_t *instance, lwm2m_context_t *context);
|
|
/*---------------------------------------------------------------------------*/
|
|
static void
|
|
client_chunk_handler(void *response)
|
|
{
|
|
#if (DEBUG) & DEBUG_PRINT
|
|
const uint8_t *chunk;
|
|
|
|
int len = coap_get_payload(response, &chunk);
|
|
|
|
PRINTF("|%.*s\n", len, (char *)chunk);
|
|
#endif /* (DEBUG) & DEBUG_PRINT */
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static int
|
|
index_of(const uint8_t *data, int offset, int len, uint8_t c)
|
|
{
|
|
if(offset < 0) {
|
|
return offset;
|
|
}
|
|
for(; offset < len; offset++) {
|
|
if(data[offset] == c) {
|
|
return offset;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static int
|
|
has_network_access(void)
|
|
{
|
|
#if UIP_CONF_IPV6_RPL
|
|
if(rpl_get_any_dag() == NULL) {
|
|
return 0;
|
|
}
|
|
#endif /* UIP_CONF_IPV6_RPL */
|
|
return 1;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
void
|
|
lwm2m_engine_use_bootstrap_server(int use)
|
|
{
|
|
use_bootstrap = use != 0;
|
|
if(use_bootstrap) {
|
|
process_poll(&lwm2m_rd_client);
|
|
}
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
void
|
|
lwm2m_engine_use_registration_server(int use)
|
|
{
|
|
use_registration = use != 0;
|
|
if(use_registration) {
|
|
process_poll(&lwm2m_rd_client);
|
|
}
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
void
|
|
lwm2m_engine_register_with_server(const uip_ipaddr_t *server, uint16_t port)
|
|
{
|
|
uip_ipaddr_copy(&server_ipaddr, server);
|
|
if(port != 0) {
|
|
server_port = port;
|
|
} else {
|
|
server_port = REMOTE_PORT;
|
|
}
|
|
has_registration_server_info = 1;
|
|
registered = 0;
|
|
if(use_registration) {
|
|
process_poll(&lwm2m_rd_client);
|
|
}
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static int
|
|
update_registration_server(void)
|
|
{
|
|
if(has_registration_server_info) {
|
|
return 1;
|
|
}
|
|
|
|
#if UIP_CONF_IPV6_RPL
|
|
{
|
|
rpl_dag_t *dag;
|
|
|
|
/* Use the DAG id as server address if no other has been specified */
|
|
dag = rpl_get_any_dag();
|
|
if(dag != NULL) {
|
|
uip_ipaddr_copy(&server_ipaddr, &dag->dag_id);
|
|
server_port = REMOTE_PORT;
|
|
return 1;
|
|
}
|
|
}
|
|
#endif /* UIP_CONF_IPV6_RPL */
|
|
|
|
return 0;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
void
|
|
lwm2m_engine_register_with_bootstrap_server(const uip_ipaddr_t *server,
|
|
uint16_t port)
|
|
{
|
|
uip_ipaddr_copy(&bs_server_ipaddr, server);
|
|
if(port != 0) {
|
|
bs_server_port = port;
|
|
} else {
|
|
bs_server_port = BS_REMOTE_PORT;
|
|
}
|
|
has_bootstrap_server_info = 1;
|
|
bootstrapped = 0;
|
|
registered = 0;
|
|
if(use_bootstrap) {
|
|
process_poll(&lwm2m_rd_client);
|
|
}
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static int
|
|
update_bootstrap_server(void)
|
|
{
|
|
if(has_bootstrap_server_info) {
|
|
return 1;
|
|
}
|
|
|
|
#if UIP_CONF_IPV6_RPL
|
|
{
|
|
rpl_dag_t *dag;
|
|
|
|
/* Use the DAG id as server address if no other has been specified */
|
|
dag = rpl_get_any_dag();
|
|
if(dag != NULL) {
|
|
uip_ipaddr_copy(&bs_server_ipaddr, &dag->dag_id);
|
|
bs_server_port = REMOTE_PORT;
|
|
return 1;
|
|
}
|
|
}
|
|
#endif /* UIP_CONF_IPV6_RPL */
|
|
|
|
return 0;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
PROCESS_THREAD(lwm2m_rd_client, ev, data)
|
|
{
|
|
static coap_packet_t request[1]; /* This way the packet can be treated as pointer as usual. */
|
|
static struct etimer et;
|
|
|
|
PROCESS_BEGIN();
|
|
|
|
printf("RD Client started with endpoint '%s'\n", endpoint);
|
|
|
|
etimer_set(&et, 15 * CLOCK_SECOND);
|
|
|
|
while(1) {
|
|
PROCESS_YIELD();
|
|
|
|
if(etimer_expired(&et)) {
|
|
if(!has_network_access()) {
|
|
/* Wait until for a network to join */
|
|
} else if(use_bootstrap && bootstrapped == 0) {
|
|
if(update_bootstrap_server()) {
|
|
/* prepare request, TID is set by COAP_BLOCKING_REQUEST() */
|
|
coap_init_message(request, COAP_TYPE_CON, COAP_POST, 0);
|
|
coap_set_header_uri_path(request, "/bs");
|
|
coap_set_header_uri_query(request, endpoint);
|
|
|
|
printf("Registering ID with bootstrap server [");
|
|
uip_debug_ipaddr_print(&bs_server_ipaddr);
|
|
printf("]:%u as '%s'\n", uip_ntohs(bs_server_port), endpoint);
|
|
|
|
COAP_BLOCKING_REQUEST(&bs_server_ipaddr, bs_server_port, request,
|
|
client_chunk_handler);
|
|
bootstrapped++;
|
|
}
|
|
} else if(use_bootstrap && bootstrapped == 1) {
|
|
lwm2m_context_t context;
|
|
const lwm2m_instance_t *instance = NULL;
|
|
const lwm2m_resource_t *rsc;
|
|
const uint8_t *first;
|
|
int len;
|
|
|
|
PRINTF("*** Bootstrap - checking for server info...\n");
|
|
|
|
/* get the security object */
|
|
instance = get_first_instance_of_object(LWM2M_OBJECT_SECURITY_ID, &context);
|
|
if(instance != NULL) {
|
|
/* get the server URI */
|
|
context.resource_id = LWM2M_SECURITY_SERVER_URI;
|
|
rsc = get_resource(instance, &context);
|
|
first = lwm2m_object_get_resource_string(rsc, &context);
|
|
len = lwm2m_object_get_resource_strlen(rsc, &context);
|
|
if(first != NULL && len > 0) {
|
|
int start, end;
|
|
uip_ipaddr_t addr;
|
|
int32_t port;
|
|
uint8_t secure = 0;
|
|
|
|
PRINTF("**** Found security instance using: %.*s\n", len, first);
|
|
/* TODO Should verify it is a URI */
|
|
|
|
/* Check if secure */
|
|
secure = strncmp((const char *)first, "coaps:", 6) == 0;
|
|
|
|
/* Only IPv6 supported */
|
|
start = index_of(first, 0, len, '[');
|
|
end = index_of(first, start, len, ']');
|
|
if(start > 0 && end > start &&
|
|
uiplib_ipaddrconv((const char *)&first[start], &addr)) {
|
|
if(first[end + 1] == ':' &&
|
|
lwm2m_plain_text_read_int(first + end + 2, len - end - 2, &port)) {
|
|
} else if(secure) {
|
|
/**
|
|
* Secure CoAP should use a different port but for now
|
|
* the same port is used.
|
|
*/
|
|
port = COAP_DEFAULT_PORT;
|
|
} else {
|
|
port = COAP_DEFAULT_PORT;
|
|
}
|
|
PRINTF("Server address ");
|
|
PRINT6ADDR(&addr);
|
|
PRINTF(" port %" PRId32 "%s\n", port, secure ? " (secure)" : "");
|
|
if(secure) {
|
|
printf("Secure CoAP requested but not supported - can not bootstrap\n");
|
|
} else {
|
|
lwm2m_engine_register_with_server(&addr,
|
|
UIP_HTONS((uint16_t)port));
|
|
bootstrapped++;
|
|
}
|
|
} else {
|
|
printf("** failed to parse URI %.*s\n", len, first);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(bootstrapped == 1) {
|
|
/* Not ready. Lets retry with the bootstrap server again */
|
|
bootstrapped = 0;
|
|
}
|
|
|
|
} else if(use_registration && !registered &&
|
|
update_registration_server()) {
|
|
int pos;
|
|
int len, i, j;
|
|
registered = 1;
|
|
|
|
/* prepare request, TID is set by COAP_BLOCKING_REQUEST() */
|
|
coap_init_message(request, COAP_TYPE_CON, COAP_POST, 0);
|
|
coap_set_header_uri_path(request, "/rd");
|
|
coap_set_header_uri_query(request, endpoint);
|
|
|
|
/* generate the rd data */
|
|
pos = 0;
|
|
for(i = 0; i < MAX_OBJECTS; i++) {
|
|
if(objects[i] != NULL) {
|
|
for(j = 0; j < objects[i]->count; j++) {
|
|
if(objects[i]->instances[j].flag & LWM2M_INSTANCE_FLAG_USED) {
|
|
len = snprintf(&rd_data[pos], sizeof(rd_data) - pos,
|
|
"%s<%d/%d>", pos > 0 ? "," : "",
|
|
objects[i]->id, objects[i]->instances[j].id);
|
|
if(len > 0 && len < sizeof(rd_data) - pos) {
|
|
pos += len;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
coap_set_payload(request, (uint8_t *)rd_data, pos);
|
|
|
|
printf("Registering with [");
|
|
uip_debug_ipaddr_print(&server_ipaddr);
|
|
printf("]:%u lwm2m endpoint '%s': '%.*s'\n", uip_ntohs(server_port),
|
|
endpoint, pos, rd_data);
|
|
COAP_BLOCKING_REQUEST(&server_ipaddr, server_port, request,
|
|
client_chunk_handler);
|
|
}
|
|
/* for now only register once... registered = 0; */
|
|
etimer_set(&et, 15 * CLOCK_SECOND);
|
|
}
|
|
}
|
|
PROCESS_END();
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
void
|
|
lwm2m_engine_init(void)
|
|
{
|
|
#ifdef LWM2M_ENGINE_CLIENT_ENDPOINT_NAME
|
|
|
|
snprintf(endpoint, sizeof(endpoint) - 1,
|
|
"?ep=" LWM2M_ENGINE_CLIENT_ENDPOINT_NAME);
|
|
|
|
#else /* LWM2M_ENGINE_CLIENT_ENDPOINT_NAME */
|
|
|
|
int len, i;
|
|
uint8_t state;
|
|
uip_ipaddr_t *ipaddr;
|
|
char client[sizeof(endpoint)];
|
|
|
|
len = strlen(LWM2M_ENGINE_CLIENT_ENDPOINT_PREFIX);
|
|
/* ensure that this fits with the hex-nums */
|
|
if(len > sizeof(client) - 13) {
|
|
len = sizeof(client) - 13;
|
|
}
|
|
memcpy(client, LWM2M_ENGINE_CLIENT_ENDPOINT_PREFIX, len);
|
|
|
|
/* pick an IP address that is PREFERRED or TENTATIVE */
|
|
ipaddr = NULL;
|
|
for(i = 0; i < UIP_DS6_ADDR_NB; i++) {
|
|
state = uip_ds6_if.addr_list[i].state;
|
|
if(uip_ds6_if.addr_list[i].isused &&
|
|
(state == ADDR_TENTATIVE || state == ADDR_PREFERRED)) {
|
|
ipaddr = &(uip_ds6_if.addr_list[i]).ipaddr;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(ipaddr != NULL) {
|
|
for(i = 0; i < 6; i++) {
|
|
/* assume IPv6 for now */
|
|
uint8_t b = ipaddr->u8[10 + i];
|
|
client[len++] = (b >> 4) > 9 ? 'A' - 10 + (b >> 4) : '0' + (b >> 4);
|
|
client[len++] = (b & 0xf) > 9 ? 'A' - 10 + (b & 0xf) : '0' + (b & 0xf);
|
|
}
|
|
}
|
|
|
|
/* a zero at end of string */
|
|
client[len] = 0;
|
|
/* create endpoint */
|
|
snprintf(endpoint, sizeof(endpoint) - 1, "?ep=%s", client);
|
|
|
|
#endif /* LWM2M_ENGINE_CLIENT_ENDPOINT_NAME */
|
|
|
|
rest_init_engine();
|
|
process_start(&lwm2m_rd_client, NULL);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
void
|
|
lwm2m_engine_register_default_objects(void)
|
|
{
|
|
lwm2m_security_init();
|
|
lwm2m_server_init();
|
|
lwm2m_device_init();
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static int
|
|
parse_next(const char **path, int *path_len, uint16_t *value)
|
|
{
|
|
char c;
|
|
*value = 0;
|
|
/* printf("parse_next: %p %d\n", *path, *path_len); */
|
|
if(*path_len == 0) {
|
|
return 0;
|
|
}
|
|
while(*path_len > 0) {
|
|
c = **path;
|
|
(*path)++;
|
|
*path_len = *path_len - 1;
|
|
if(c >= '0' && c <= '9') {
|
|
*value = *value * 10 + (c - '0');
|
|
} else if(c == '/') {
|
|
return 1;
|
|
} else {
|
|
/* error */
|
|
return -4;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
int
|
|
lwm2m_engine_parse_context(const lwm2m_object_t *object,
|
|
const char *path, int path_len,
|
|
lwm2m_context_t *context)
|
|
{
|
|
int ret;
|
|
if(context == NULL || object == NULL || path == NULL) {
|
|
return 0;
|
|
}
|
|
memset(context, 0, sizeof(lwm2m_context_t));
|
|
/* get object id */
|
|
ret = 0;
|
|
ret += parse_next(&path, &path_len, &context->object_id);
|
|
ret += parse_next(&path, &path_len, &context->object_instance_id);
|
|
ret += parse_next(&path, &path_len, &context->resource_id);
|
|
|
|
/* Set default reader/writer */
|
|
context->reader = &lwm2m_plain_text_reader;
|
|
context->writer = &oma_tlv_writer;
|
|
|
|
return ret;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
const lwm2m_object_t *
|
|
lwm2m_engine_get_object(uint16_t id)
|
|
{
|
|
int i;
|
|
for(i = 0; i < MAX_OBJECTS; i++) {
|
|
if(objects[i] != NULL && objects[i]->id == id) {
|
|
return objects[i];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
int
|
|
lwm2m_engine_register_object(const lwm2m_object_t *object)
|
|
{
|
|
int i;
|
|
int found = 0;
|
|
for(i = 0; i < MAX_OBJECTS; i++) {
|
|
if(objects[i] == NULL) {
|
|
objects[i] = object;
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
rest_activate_resource(lwm2m_object_get_coap_resource(object),
|
|
(char *)object->path);
|
|
return found;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static const lwm2m_instance_t *
|
|
get_first_instance_of_object(uint16_t id, lwm2m_context_t *context)
|
|
{
|
|
const lwm2m_object_t *object;
|
|
int i;
|
|
|
|
object = lwm2m_engine_get_object(id);
|
|
if(object == NULL) {
|
|
/* No object with the specified id found */
|
|
return NULL;
|
|
}
|
|
|
|
/* Initialize the context */
|
|
memset(context, 0, sizeof(lwm2m_context_t));
|
|
context->object_id = id;
|
|
|
|
for(i = 0; i < object->count; i++) {
|
|
if(object->instances[i].flag & LWM2M_INSTANCE_FLAG_USED) {
|
|
context->object_instance_id = object->instances[i].id;
|
|
context->object_instance_index = i;
|
|
return &object->instances[i];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static const lwm2m_instance_t *
|
|
get_instance(const lwm2m_object_t *object, lwm2m_context_t *context, int depth)
|
|
{
|
|
int i;
|
|
if(depth > 1) {
|
|
PRINTF("lwm2m: searching for instance %u\n", context->object_instance_id);
|
|
for(i = 0; i < object->count; i++) {
|
|
PRINTF(" Instance %d -> %u (used: %d)\n", i, object->instances[i].id,
|
|
(object->instances[i].flag & LWM2M_INSTANCE_FLAG_USED) != 0);
|
|
if(object->instances[i].id == context->object_instance_id &&
|
|
object->instances[i].flag & LWM2M_INSTANCE_FLAG_USED) {
|
|
context->object_instance_index = i;
|
|
return &object->instances[i];
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static const lwm2m_resource_t *
|
|
get_resource(const lwm2m_instance_t *instance, lwm2m_context_t *context)
|
|
{
|
|
int i;
|
|
if(instance != NULL) {
|
|
PRINTF("lwm2m: searching for resource %u\n", context->resource_id);
|
|
for(i = 0; i < instance->count; i++) {
|
|
PRINTF(" Resource %d -> %u\n", i, instance->resources[i].id);
|
|
if(instance->resources[i].id == context->resource_id) {
|
|
context->resource_index = i;
|
|
return &instance->resources[i];
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
/**
|
|
* @brief Write a list of object instances as a CoRE Link-format list
|
|
*/
|
|
static int
|
|
write_object_instances_link(const lwm2m_object_t *object,
|
|
char *buffer, size_t size)
|
|
{
|
|
const lwm2m_instance_t *instance;
|
|
int len, rdlen, i;
|
|
|
|
PRINTF("</%d>", object->id);
|
|
rdlen = snprintf(buffer, size, "</%d>",
|
|
object->id);
|
|
if(rdlen < 0 || rdlen >= size) {
|
|
return -1;
|
|
}
|
|
|
|
for(i = 0; i < object->count; i++) {
|
|
if((object->instances[i].flag & LWM2M_INSTANCE_FLAG_USED) == 0) {
|
|
continue;
|
|
}
|
|
instance = &object->instances[i];
|
|
PRINTF(",</%d/%d>", object->id, instance->id);
|
|
|
|
len = snprintf(&buffer[rdlen], size - rdlen,
|
|
",<%d/%d>", object->id, instance->id);
|
|
rdlen += len;
|
|
if(len < 0 || rdlen >= size) {
|
|
return -1;
|
|
}
|
|
}
|
|
return rdlen;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static int
|
|
write_rd_link_data(const lwm2m_object_t *object,
|
|
const lwm2m_instance_t *instance,
|
|
char *buffer, size_t size)
|
|
{
|
|
const lwm2m_resource_t *resource;
|
|
int len, rdlen, i;
|
|
|
|
PRINTF("<%d/%d>", object->id, instance->id);
|
|
rdlen = snprintf(buffer, size, "<%d/%d>",
|
|
object->id, instance->id);
|
|
if(rdlen < 0 || rdlen >= size) {
|
|
return -1;
|
|
}
|
|
|
|
for(i = 0; i < instance->count; i++) {
|
|
resource = &instance->resources[i];
|
|
PRINTF(",<%d/%d/%d>", object->id, instance->id, resource->id);
|
|
|
|
len = snprintf(&buffer[rdlen], size - rdlen,
|
|
",<%d/%d/%d>", object->id, instance->id, resource->id);
|
|
rdlen += len;
|
|
if(len < 0 || rdlen >= size) {
|
|
return -1;
|
|
}
|
|
}
|
|
return rdlen;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static int
|
|
write_rd_json_data(const lwm2m_context_t *context,
|
|
const lwm2m_object_t *object,
|
|
const lwm2m_instance_t *instance,
|
|
char *buffer, size_t size)
|
|
{
|
|
const lwm2m_resource_t *resource;
|
|
const char *s = "";
|
|
int len, rdlen, i;
|
|
|
|
PRINTF("{\"e\":[");
|
|
rdlen = snprintf(buffer, size, "{\"e\":[");
|
|
if(rdlen < 0 || rdlen >= size) {
|
|
return -1;
|
|
}
|
|
|
|
for(i = 0, len = 0; i < instance->count; i++) {
|
|
resource = &instance->resources[i];
|
|
len = 0;
|
|
if(lwm2m_object_is_resource_string(resource)) {
|
|
const uint8_t *value;
|
|
uint16_t slen;
|
|
value = lwm2m_object_get_resource_string(resource, context);
|
|
slen = lwm2m_object_get_resource_strlen(resource, context);
|
|
if(value != NULL) {
|
|
PRINTF("%s{\"n\":\"%u\",\"sv\":\"%.*s\"}", s,
|
|
resource->id, slen, value);
|
|
len = snprintf(&buffer[rdlen], size - rdlen,
|
|
"%s{\"n\":\"%u\",\"sv\":\"%.*s\"}", s,
|
|
resource->id, slen, value);
|
|
}
|
|
} else if(lwm2m_object_is_resource_int(resource)) {
|
|
int32_t value;
|
|
if(lwm2m_object_get_resource_int(resource, context, &value)) {
|
|
PRINTF("%s{\"n\":\"%u\",\"v\":%" PRId32 "}", s,
|
|
resource->id, value);
|
|
len = snprintf(&buffer[rdlen], size - rdlen,
|
|
"%s{\"n\":\"%u\",\"v\":%" PRId32 "}", s,
|
|
resource->id, value);
|
|
}
|
|
} else if(lwm2m_object_is_resource_floatfix(resource)) {
|
|
int32_t value;
|
|
if(lwm2m_object_get_resource_floatfix(resource, context, &value)) {
|
|
PRINTF("%s{\"n\":\"%u\",\"v\":%" PRId32 "}", s, resource->id,
|
|
value / LWM2M_FLOAT32_FRAC);
|
|
len = snprintf(&buffer[rdlen], size - rdlen,
|
|
"%s{\"n\":\"%u\",\"v\":", s, resource->id);
|
|
rdlen += len;
|
|
if(len < 0 || rdlen >= size) {
|
|
return -1;
|
|
}
|
|
|
|
len = lwm2m_plain_text_write_float32fix((uint8_t *)&buffer[rdlen],
|
|
size - rdlen,
|
|
value, LWM2M_FLOAT32_BITS);
|
|
if(len == 0) {
|
|
return -1;
|
|
}
|
|
rdlen += len;
|
|
|
|
if(rdlen < size) {
|
|
buffer[rdlen] = '}';
|
|
}
|
|
len = 1;
|
|
}
|
|
} else if(lwm2m_object_is_resource_boolean(resource)) {
|
|
int value;
|
|
if(lwm2m_object_get_resource_boolean(resource, context, &value)) {
|
|
PRINTF("%s{\"n\":\"%u\",\"bv\":%s}", s, resource->id,
|
|
value ? "true" : "false");
|
|
len = snprintf(&buffer[rdlen], size - rdlen,
|
|
"%s{\"n\":\"%u\",\"bv\":%s}", s, resource->id,
|
|
value ? "true" : "false");
|
|
}
|
|
}
|
|
rdlen += len;
|
|
if(len < 0 || rdlen >= size) {
|
|
return -1;
|
|
}
|
|
if(len > 0) {
|
|
s = ",";
|
|
}
|
|
}
|
|
PRINTF("]}\n");
|
|
len = snprintf(&buffer[rdlen], size - rdlen, "]}");
|
|
rdlen += len;
|
|
if(len < 0 || rdlen >= size) {
|
|
return -1;
|
|
}
|
|
|
|
return rdlen;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
/**
|
|
* @brief Set the writer pointer to the proper writer based on the Accept: header
|
|
*
|
|
* @param[in] context LWM2M context to operate on
|
|
* @param[in] accept Accept type number from CoAP headers
|
|
*
|
|
* @return The content type of the response if the selected writer is used
|
|
*/
|
|
static unsigned int
|
|
lwm2m_engine_select_writer(lwm2m_context_t *context, unsigned int accept)
|
|
{
|
|
switch(accept) {
|
|
case LWM2M_TLV:
|
|
context->writer = &oma_tlv_writer;
|
|
break;
|
|
case LWM2M_TEXT_PLAIN:
|
|
case TEXT_PLAIN:
|
|
context->writer = &lwm2m_plain_text_writer;
|
|
break;
|
|
case LWM2M_JSON:
|
|
case APPLICATION_JSON:
|
|
context->writer = &lwm2m_json_writer;
|
|
break;
|
|
default:
|
|
PRINTF("Unknown Accept type %u, using LWM2M plain text\n", accept);
|
|
context->writer = &lwm2m_plain_text_writer;
|
|
/* Set the response type to plain text */
|
|
accept = LWM2M_TEXT_PLAIN;
|
|
break;
|
|
}
|
|
return accept;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
/**
|
|
* @brief Set the reader pointer to the proper reader based on the Content-format: header
|
|
*
|
|
* @param[in] context LWM2M context to operate on
|
|
* @param[in] content_format Content-type type number from CoAP headers
|
|
*/
|
|
static void
|
|
lwm2m_engine_select_reader(lwm2m_context_t *context, unsigned int content_format)
|
|
{
|
|
switch(content_format) {
|
|
case LWM2M_TLV:
|
|
context->reader = &oma_tlv_reader;
|
|
break;
|
|
case LWM2M_TEXT_PLAIN:
|
|
case TEXT_PLAIN:
|
|
context->reader = &lwm2m_plain_text_reader;
|
|
break;
|
|
default:
|
|
PRINTF("Unknown content type %u, using LWM2M plain text\n", accept);
|
|
context->reader = &lwm2m_plain_text_reader;
|
|
break;
|
|
}
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
void
|
|
lwm2m_engine_handler(const lwm2m_object_t *object,
|
|
void *request, void *response,
|
|
uint8_t *buffer, uint16_t preferred_size,
|
|
int32_t *offset)
|
|
{
|
|
int len;
|
|
const char *url;
|
|
unsigned int format;
|
|
unsigned int accept;
|
|
unsigned int content_type;
|
|
int depth;
|
|
lwm2m_context_t context;
|
|
rest_resource_flags_t method;
|
|
const lwm2m_instance_t *instance;
|
|
#if (DEBUG) & DEBUG_PRINT
|
|
const char *method_str;
|
|
#endif /* (DEBUG) & DEBUG_PRINT */
|
|
|
|
method = REST.get_method_type(request);
|
|
|
|
len = REST.get_url(request, &url);
|
|
if(!REST.get_header_content_type(request, &format)) {
|
|
PRINTF("No format given. Assume text plain...\n");
|
|
format = LWM2M_TEXT_PLAIN;
|
|
} else if(format == TEXT_PLAIN) {
|
|
/* CoAP content format text plain - assume LWM2M text plain */
|
|
format = LWM2M_TEXT_PLAIN;
|
|
}
|
|
if(!REST.get_header_accept(request, &accept)) {
|
|
PRINTF("No Accept header, using same as Content-format...\n");
|
|
accept = format;
|
|
}
|
|
|
|
depth = lwm2m_engine_parse_context(object, url, len, &context);
|
|
PRINTF("Context: %u/%u/%u found: %d\n", context.object_id,
|
|
context.object_instance_id, context.resource_id, depth);
|
|
|
|
/* Select reader and writer based on provided Content type and Accept headers */
|
|
lwm2m_engine_select_reader(&context, format);
|
|
content_type = lwm2m_engine_select_writer(&context, accept);
|
|
|
|
#if (DEBUG) & DEBUG_PRINT
|
|
/* for debugging */
|
|
if(method == METHOD_GET) {
|
|
method_str = "GET";
|
|
} else if(method == METHOD_POST) {
|
|
method_str = "POST";
|
|
} else if(method == METHOD_PUT) {
|
|
method_str = "PUT";
|
|
} else if(method == METHOD_DELETE) {
|
|
method_str = "DELETE";
|
|
} else {
|
|
method_str = "UNKNOWN";
|
|
}
|
|
PRINTF("%s Called Path:%.*s Format:%d ID:%d bsize:%u\n", method_str, len,
|
|
url, format, object->id, preferred_size);
|
|
if(format == LWM2M_TEXT_PLAIN) {
|
|
/* a string */
|
|
const uint8_t *data;
|
|
int plen = REST.get_request_payload(request, &data);
|
|
if(plen > 0) {
|
|
PRINTF("Data: '%.*s'\n", plen, (char *)data);
|
|
}
|
|
}
|
|
#endif /* (DEBUG) & DEBUG_PRINT */
|
|
|
|
instance = get_instance(object, &context, depth);
|
|
|
|
/* from POST */
|
|
if(depth > 1 && instance == NULL) {
|
|
if(method != METHOD_PUT && method != METHOD_POST) {
|
|
PRINTF("Error - do not have instance %d\n", context.object_instance_id);
|
|
REST.set_response_status(response, NOT_FOUND_4_04);
|
|
return;
|
|
} else {
|
|
const uint8_t *data;
|
|
int i, len, plen, pos;
|
|
oma_tlv_t tlv;
|
|
PRINTF(">>> CREATE ? %d/%d\n", context.object_id,
|
|
context.object_instance_id);
|
|
|
|
for(i = 0; i < object->count; i++) {
|
|
if((object->instances[i].flag & LWM2M_INSTANCE_FLAG_USED) == 0) {
|
|
/* allocate this instance */
|
|
object->instances[i].flag |= LWM2M_INSTANCE_FLAG_USED;
|
|
object->instances[i].id = context.object_instance_id;
|
|
context.object_instance_index = i;
|
|
PRINTF("Created instance: %d\n", context.object_instance_id);
|
|
REST.set_response_status(response, CREATED_2_01);
|
|
instance = &object->instances[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(instance == NULL) {
|
|
/* could for some reason not create the instance */
|
|
REST.set_response_status(response, NOT_ACCEPTABLE_4_06);
|
|
return;
|
|
}
|
|
|
|
plen = REST.get_request_payload(request, &data);
|
|
if(plen == 0) {
|
|
/* do nothing more */
|
|
return;
|
|
}
|
|
PRINTF("Payload: ");
|
|
for(i = 0; i < plen; i++) {
|
|
PRINTF("%02x", data[i]);
|
|
}
|
|
PRINTF("\n");
|
|
|
|
pos = 0;
|
|
do {
|
|
len = oma_tlv_read(&tlv, (uint8_t *)&data[pos], plen - pos);
|
|
PRINTF("Found TLV type=%u id=%u len=%lu\n",
|
|
tlv.type, tlv.id, (unsigned long)tlv.length);
|
|
/* here we need to do callbacks or write value */
|
|
if(tlv.type == OMA_TLV_TYPE_RESOURCE) {
|
|
context.resource_id = tlv.id;
|
|
const lwm2m_resource_t *rsc = get_resource(instance, &context);
|
|
if(rsc != NULL) {
|
|
/* write the value to the resource */
|
|
if(lwm2m_object_is_resource_string(rsc)) {
|
|
PRINTF(" new string value for /%d/%d/%d = %.*s\n",
|
|
context.object_id, context.object_instance_id,
|
|
context.resource_id, (int)tlv.length, tlv.value);
|
|
lwm2m_object_set_resource_string(rsc, &context,
|
|
tlv.length, tlv.value);
|
|
} else if(lwm2m_object_is_resource_int(rsc)) {
|
|
PRINTF(" new int value for /%d/%d/%d = %" PRId32 "\n",
|
|
context.object_id, context.object_instance_id,
|
|
context.resource_id, oma_tlv_get_int32(&tlv));
|
|
lwm2m_object_set_resource_int(rsc, &context,
|
|
oma_tlv_get_int32(&tlv));
|
|
} else if(lwm2m_object_is_resource_floatfix(rsc)) {
|
|
int32_t value;
|
|
if(oma_tlv_float32_to_fix(&tlv, &value, LWM2M_FLOAT32_BITS)) {
|
|
PRINTF(" new float value for /%d/%d/%d = %" PRId32 "\n",
|
|
context.object_id, context.object_instance_id,
|
|
context.resource_id, value >> LWM2M_FLOAT32_BITS);
|
|
lwm2m_object_set_resource_floatfix(rsc, &context, value);
|
|
} else {
|
|
PRINTF(" new float value for /%d/%d/%d: FAILED\n",
|
|
context.object_id, context.object_instance_id,
|
|
context.resource_id);
|
|
}
|
|
} else if(lwm2m_object_is_resource_boolean(rsc)) {
|
|
PRINTF(" new boolean value for /%d/%d/%d = %" PRId32 "\n",
|
|
context.object_id, context.object_instance_id,
|
|
context.resource_id, oma_tlv_get_int32(&tlv));
|
|
lwm2m_object_set_resource_boolean(rsc, &context,
|
|
oma_tlv_get_int32(&tlv) != 0);
|
|
}
|
|
}
|
|
}
|
|
pos = pos + len;
|
|
} while(len > 0 && pos < plen);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if(depth == 3) {
|
|
const lwm2m_resource_t *resource = get_resource(instance, &context);
|
|
size_t content_len = 0;
|
|
if(resource == NULL) {
|
|
PRINTF("Error - do not have resource %d\n", context.resource_id);
|
|
REST.set_response_status(response, NOT_FOUND_4_04);
|
|
return;
|
|
}
|
|
/* HANDLE PUT */
|
|
if(method == METHOD_PUT) {
|
|
if(lwm2m_object_is_resource_callback(resource)) {
|
|
if(resource->value.callback.write != NULL) {
|
|
/* pick a reader ??? */
|
|
if(format == LWM2M_TEXT_PLAIN) {
|
|
/* a string */
|
|
const uint8_t *data;
|
|
int plen = REST.get_request_payload(request, &data);
|
|
context.reader = &lwm2m_plain_text_reader;
|
|
PRINTF("PUT Callback with data: '%.*s'\n", plen, data);
|
|
/* no specific reader for plain text */
|
|
content_len = resource->value.callback.write(&context, data, plen,
|
|
buffer, preferred_size);
|
|
PRINTF("content_len:%u\n", (unsigned int)content_len);
|
|
REST.set_response_status(response, CHANGED_2_04);
|
|
} else {
|
|
PRINTF("PUT callback with format %d\n", format);
|
|
REST.set_response_status(response, NOT_ACCEPTABLE_4_06);
|
|
}
|
|
} else {
|
|
PRINTF("PUT - no write callback\n");
|
|
REST.set_response_status(response, METHOD_NOT_ALLOWED_4_05);
|
|
}
|
|
} else {
|
|
PRINTF("PUT on non-callback resource!\n");
|
|
REST.set_response_status(response, METHOD_NOT_ALLOWED_4_05);
|
|
}
|
|
/* HANDLE GET */
|
|
} else if(method == METHOD_GET) {
|
|
if(lwm2m_object_is_resource_string(resource)) {
|
|
const uint8_t *value;
|
|
value = lwm2m_object_get_resource_string(resource, &context);
|
|
if(value != NULL) {
|
|
uint16_t len = lwm2m_object_get_resource_strlen(resource, &context);
|
|
PRINTF("Get string value: %.*s\n", (int)len, (char *)value);
|
|
content_len = context.writer->write_string(&context, buffer,
|
|
preferred_size, (const char *)value, len);
|
|
}
|
|
} else if(lwm2m_object_is_resource_int(resource)) {
|
|
int32_t value;
|
|
if(lwm2m_object_get_resource_int(resource, &context, &value)) {
|
|
content_len = context.writer->write_int(&context, buffer, preferred_size, value);
|
|
}
|
|
} else if(lwm2m_object_is_resource_floatfix(resource)) {
|
|
int32_t value;
|
|
if(lwm2m_object_get_resource_floatfix(resource, &context, &value)) {
|
|
/* export FLOATFIX */
|
|
PRINTF("Exporting %d-bit fix as float: %" PRId32 "\n",
|
|
LWM2M_FLOAT32_BITS, value);
|
|
content_len = context.writer->write_float32fix(&context, buffer,
|
|
preferred_size, value, LWM2M_FLOAT32_BITS);
|
|
}
|
|
} else if(lwm2m_object_is_resource_callback(resource)) {
|
|
if(resource->value.callback.read != NULL) {
|
|
content_len = resource->value.callback.read(&context,
|
|
buffer, preferred_size);
|
|
} else {
|
|
REST.set_response_status(response, METHOD_NOT_ALLOWED_4_05);
|
|
return;
|
|
}
|
|
}
|
|
if(content_len > 0) {
|
|
REST.set_response_payload(response, buffer, content_len);
|
|
REST.set_header_content_type(response, content_type);
|
|
} else {
|
|
/* failed to produce output - it is an internal error */
|
|
REST.set_response_status(response, INTERNAL_SERVER_ERROR_5_00);
|
|
}
|
|
/* Handle POST */
|
|
} else if(method == METHOD_POST) {
|
|
if(lwm2m_object_is_resource_callback(resource)) {
|
|
if(resource->value.callback.exec != NULL) {
|
|
const uint8_t *data;
|
|
int plen = REST.get_request_payload(request, &data);
|
|
PRINTF("Execute Callback with data: '%.*s'\n", plen, data);
|
|
content_len = resource->value.callback.exec(&context,
|
|
data, plen,
|
|
buffer, preferred_size);
|
|
REST.set_response_status(response, CHANGED_2_04);
|
|
} else {
|
|
PRINTF("Execute callback - no exec callback\n");
|
|
REST.set_response_status(response, METHOD_NOT_ALLOWED_4_05);
|
|
}
|
|
} else {
|
|
PRINTF("Resource post but no callback resource\n");
|
|
REST.set_response_status(response, METHOD_NOT_ALLOWED_4_05);
|
|
}
|
|
}
|
|
} else if(depth == 2) {
|
|
/* produce an instance response */
|
|
if(method != METHOD_GET) {
|
|
REST.set_response_status(response, METHOD_NOT_ALLOWED_4_05);
|
|
} else if(instance == NULL) {
|
|
REST.set_response_status(response, NOT_FOUND_4_04);
|
|
} else {
|
|
int rdlen;
|
|
if(accept == APPLICATION_LINK_FORMAT) {
|
|
rdlen = write_rd_link_data(object, instance,
|
|
(char *)buffer, preferred_size);
|
|
} else {
|
|
rdlen = write_rd_json_data(&context, object, instance,
|
|
(char *)buffer, preferred_size);
|
|
}
|
|
if(rdlen < 0) {
|
|
PRINTF("Failed to generate instance response\n");
|
|
REST.set_response_status(response, SERVICE_UNAVAILABLE_5_03);
|
|
return;
|
|
}
|
|
REST.set_response_payload(response, buffer, rdlen);
|
|
if(accept == APPLICATION_LINK_FORMAT) {
|
|
REST.set_header_content_type(response, REST.type.APPLICATION_LINK_FORMAT);
|
|
} else {
|
|
REST.set_header_content_type(response, LWM2M_JSON);
|
|
}
|
|
}
|
|
} else if(depth == 1) {
|
|
/* produce a list of instances */
|
|
if(method != METHOD_GET) {
|
|
REST.set_response_status(response, METHOD_NOT_ALLOWED_4_05);
|
|
} else {
|
|
int rdlen;
|
|
PRINTF("Sending instance list for object %u\n", object->id);
|
|
/* TODO: if(accept == APPLICATION_LINK_FORMAT) { */
|
|
rdlen = write_object_instances_link(object, (char *)buffer, preferred_size);
|
|
if(rdlen < 0) {
|
|
PRINTF("Failed to generate object response\n");
|
|
REST.set_response_status(response, SERVICE_UNAVAILABLE_5_03);
|
|
return;
|
|
}
|
|
REST.set_header_content_type(response, REST.type.APPLICATION_LINK_FORMAT);
|
|
REST.set_response_payload(response, buffer, rdlen);
|
|
}
|
|
}
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
void
|
|
lwm2m_engine_delete_handler(const lwm2m_object_t *object, void *request,
|
|
void *response, uint8_t *buffer,
|
|
uint16_t preferred_size, int32_t *offset)
|
|
{
|
|
int len;
|
|
const char *url;
|
|
lwm2m_context_t context;
|
|
|
|
len = REST.get_url(request, &url);
|
|
PRINTF("*** DELETE URI:'%.*s' called... - responding with DELETED.\n",
|
|
len, url);
|
|
len = lwm2m_engine_parse_context(object, url, len, &context);
|
|
PRINTF("Context: %u/%u/%u found: %d\n", context.object_id,
|
|
context.object_instance_id, context.resource_id, len);
|
|
|
|
REST.set_response_status(response, DELETED_2_02);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
/** @} */
|