Contiki 3.x
lwm2m-engine.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2015, Yanzi Networks AB.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  * notice, this list of conditions and the following disclaimer in the
12  * documentation and/or other materials provided with the distribution.
13  * 3. Neither the name of the copyright holder nor the names of its
14  * contributors may be used to endorse or promote products derived
15  * from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS
18  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
20  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
21  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
22  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
28  * OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 /**
32  * \addtogroup oma-lwm2m
33  * @{
34  */
35 
36 /**
37  * \file
38  * Implementation of the Contiki OMA LWM2M engine
39  * \author
40  * Joakim Eriksson <joakime@sics.se>
41  * Niclas Finne <nfi@sics.se>
42  */
43 
44 #include "contiki.h"
45 #include "lwm2m-engine.h"
46 #include "lwm2m-object.h"
47 #include "lwm2m-device.h"
48 #include "lwm2m-plain-text.h"
49 #include "lwm2m-json.h"
50 #include "rest-engine.h"
51 #include "er-coap-constants.h"
52 #include "er-coap-engine.h"
53 #include "oma-tlv.h"
54 #include "oma-tlv-reader.h"
55 #include "oma-tlv-writer.h"
56 #include "net/ipv6/uip-ds6.h"
57 #include <stdio.h>
58 #include <string.h>
59 #include <inttypes.h>
60 
61 #if UIP_CONF_IPV6_RPL
62 #include "net/rpl/rpl.h"
63 #endif /* UIP_CONF_IPV6_RPL */
64 
65 #define DEBUG DEBUG_NONE
66 #include "net/ip/uip-debug.h"
67 
68 #ifndef LWM2M_ENGINE_CLIENT_ENDPOINT_PREFIX
69 #ifdef LWM2M_DEVICE_MODEL_NUMBER
70 #define LWM2M_ENGINE_CLIENT_ENDPOINT_PREFIX LWM2M_DEVICE_MODEL_NUMBER
71 #else /* LWM2M_DEVICE_MODEL_NUMBER */
72 #define LWM2M_ENGINE_CLIENT_ENDPOINT_PREFIX "Contiki-"
73 #endif /* LWM2M_DEVICE_MODEL_NUMBER */
74 #endif /* LWM2M_ENGINE_CLIENT_ENDPOINT_PREFIX */
75 
76 #ifdef LWM2M_ENGINE_CONF_MAX_OBJECTS
77 #define MAX_OBJECTS LWM2M_ENGINE_CONF_MAX_OBJECTS
78 #else /* LWM2M_ENGINE_CONF_MAX_OBJECTS */
79 #define MAX_OBJECTS 10
80 #endif /* LWM2M_ENGINE_CONF_MAX_OBJECTS */
81 
82 #define REMOTE_PORT UIP_HTONS(COAP_DEFAULT_PORT)
83 #define BS_REMOTE_PORT UIP_HTONS(5685)
84 
85 static const lwm2m_object_t *objects[MAX_OBJECTS];
86 static char endpoint[32];
87 static char rd_data[128]; /* allocate some data for the RD */
88 
89 PROCESS(lwm2m_rd_client, "LWM2M Engine");
90 
91 static uip_ipaddr_t server_ipaddr;
92 static uint16_t server_port = REMOTE_PORT;
93 static uip_ipaddr_t bs_server_ipaddr;
94 static uint16_t bs_server_port = BS_REMOTE_PORT;
95 
96 static uint8_t use_bootstrap = 0;
97 static uint8_t has_bootstrap_server_info = 0;
98 static uint8_t use_registration = 0;
99 static uint8_t has_registration_server_info = 0;
100 static uint8_t registered = 0;
101 static uint8_t bootstrapped = 0; /* bootstrap made... */
102 
103 void lwm2m_device_init(void);
104 void lwm2m_security_init(void);
105 void lwm2m_server_init(void);
106 
107 static const lwm2m_instance_t *get_first_instance_of_object(uint16_t id, lwm2m_context_t *context);
108 static const lwm2m_instance_t *get_instance(const lwm2m_object_t *object, lwm2m_context_t *context, int depth);
109 static const lwm2m_resource_t *get_resource(const lwm2m_instance_t *instance, lwm2m_context_t *context);
110 /*---------------------------------------------------------------------------*/
111 static void
112 client_chunk_handler(void *response)
113 {
114 #if (DEBUG) & DEBUG_PRINT
115  const uint8_t *chunk;
116 
117  int len = coap_get_payload(response, &chunk);
118 
119  PRINTF("|%.*s\n", len, (char *)chunk);
120 #endif /* (DEBUG) & DEBUG_PRINT */
121 }
122 /*---------------------------------------------------------------------------*/
123 static int
124 index_of(const uint8_t *data, int offset, int len, uint8_t c)
125 {
126  if(offset < 0) {
127  return offset;
128  }
129  for(; offset < len; offset++) {
130  if(data[offset] == c) {
131  return offset;
132  }
133  }
134  return -1;
135 }
136 /*---------------------------------------------------------------------------*/
137 static int
138 has_network_access(void)
139 {
140 #if UIP_CONF_IPV6_RPL
141  if(rpl_get_any_dag() == NULL) {
142  return 0;
143  }
144 #endif /* UIP_CONF_IPV6_RPL */
145  return 1;
146 }
147 /*---------------------------------------------------------------------------*/
148 void
149 lwm2m_engine_use_bootstrap_server(int use)
150 {
151  use_bootstrap = use != 0;
152  if(use_bootstrap) {
153  process_poll(&lwm2m_rd_client);
154  }
155 }
156 /*---------------------------------------------------------------------------*/
157 void
158 lwm2m_engine_use_registration_server(int use)
159 {
160  use_registration = use != 0;
161  if(use_registration) {
162  process_poll(&lwm2m_rd_client);
163  }
164 }
165 /*---------------------------------------------------------------------------*/
166 void
167 lwm2m_engine_register_with_server(const uip_ipaddr_t *server, uint16_t port)
168 {
169  uip_ipaddr_copy(&server_ipaddr, server);
170  if(port != 0) {
171  server_port = port;
172  } else {
173  server_port = REMOTE_PORT;
174  }
175  has_registration_server_info = 1;
176  registered = 0;
177  if(use_registration) {
178  process_poll(&lwm2m_rd_client);
179  }
180 }
181 /*---------------------------------------------------------------------------*/
182 static int
183 update_registration_server(void)
184 {
185  if(has_registration_server_info) {
186  return 1;
187  }
188 
189 #if UIP_CONF_IPV6_RPL
190  {
191  rpl_dag_t *dag;
192 
193  /* Use the DAG id as server address if no other has been specified */
194  dag = rpl_get_any_dag();
195  if(dag != NULL) {
196  uip_ipaddr_copy(&server_ipaddr, &dag->dag_id);
197  server_port = REMOTE_PORT;
198  return 1;
199  }
200  }
201 #endif /* UIP_CONF_IPV6_RPL */
202 
203  return 0;
204 }
205 /*---------------------------------------------------------------------------*/
206 void
207 lwm2m_engine_register_with_bootstrap_server(const uip_ipaddr_t *server,
208  uint16_t port)
209 {
210  uip_ipaddr_copy(&bs_server_ipaddr, server);
211  if(port != 0) {
212  bs_server_port = port;
213  } else {
214  bs_server_port = BS_REMOTE_PORT;
215  }
216  has_bootstrap_server_info = 1;
217  bootstrapped = 0;
218  registered = 0;
219  if(use_bootstrap) {
220  process_poll(&lwm2m_rd_client);
221  }
222 }
223 /*---------------------------------------------------------------------------*/
224 static int
225 update_bootstrap_server(void)
226 {
227  if(has_bootstrap_server_info) {
228  return 1;
229  }
230 
231 #if UIP_CONF_IPV6_RPL
232  {
233  rpl_dag_t *dag;
234 
235  /* Use the DAG id as server address if no other has been specified */
236  dag = rpl_get_any_dag();
237  if(dag != NULL) {
238  uip_ipaddr_copy(&bs_server_ipaddr, &dag->dag_id);
239  bs_server_port = REMOTE_PORT;
240  return 1;
241  }
242  }
243 #endif /* UIP_CONF_IPV6_RPL */
244 
245  return 0;
246 }
247 /*---------------------------------------------------------------------------*/
248 PROCESS_THREAD(lwm2m_rd_client, ev, data)
249 {
250  static coap_packet_t request[1]; /* This way the packet can be treated as pointer as usual. */
251  static struct etimer et;
252 
253  PROCESS_BEGIN();
254 
255  printf("RD Client started with endpoint '%s'\n", endpoint);
256 
257  etimer_set(&et, 15 * CLOCK_SECOND);
258 
259  while(1) {
260  PROCESS_YIELD();
261 
262  if(etimer_expired(&et)) {
263  if(!has_network_access()) {
264  /* Wait until for a network to join */
265  } else if(use_bootstrap && bootstrapped == 0) {
266  if(update_bootstrap_server()) {
267  /* prepare request, TID is set by COAP_BLOCKING_REQUEST() */
268  coap_init_message(request, COAP_TYPE_CON, COAP_POST, 0);
269  coap_set_header_uri_path(request, "/bs");
270  coap_set_header_uri_query(request, endpoint);
271 
272  printf("Registering ID with bootstrap server [");
273  uip_debug_ipaddr_print(&bs_server_ipaddr);
274  printf("]:%u as '%s'\n", uip_ntohs(bs_server_port), endpoint);
275 
276  COAP_BLOCKING_REQUEST(&bs_server_ipaddr, bs_server_port, request,
277  client_chunk_handler);
278  bootstrapped++;
279  }
280  } else if(use_bootstrap && bootstrapped == 1) {
281  lwm2m_context_t context;
282  const lwm2m_instance_t *instance = NULL;
283  const lwm2m_resource_t *rsc;
284  const uint8_t *first;
285  int len;
286 
287  PRINTF("*** Bootstrap - checking for server info...\n");
288 
289  /* get the security object */
290  instance = get_first_instance_of_object(LWM2M_OBJECT_SECURITY_ID, &context);
291  if(instance != NULL) {
292  /* get the server URI */
293  context.resource_id = LWM2M_SECURITY_SERVER_URI;
294  rsc = get_resource(instance, &context);
295  first = lwm2m_object_get_resource_string(rsc, &context);
296  len = lwm2m_object_get_resource_strlen(rsc, &context);
297  if(first != NULL && len > 0) {
298  int start, end;
299  uip_ipaddr_t addr;
300  int32_t port;
301  uint8_t secure = 0;
302 
303  PRINTF("**** Found security instance using: %.*s\n", len, first);
304  /* TODO Should verify it is a URI */
305 
306  /* Check if secure */
307  secure = strncmp((const char *)first, "coaps:", 6) == 0;
308 
309  /* Only IPv6 supported */
310  start = index_of(first, 0, len, '[');
311  end = index_of(first, start, len, ']');
312  if(start > 0 && end > start &&
313  uiplib_ipaddrconv((const char *)&first[start], &addr)) {
314  if(first[end + 1] == ':' &&
315  lwm2m_plain_text_read_int(first + end + 2, len - end - 2, &port)) {
316  } else if(secure) {
317  /**
318  * Secure CoAP should use a different port but for now
319  * the same port is used.
320  */
321  port = COAP_DEFAULT_PORT;
322  } else {
323  port = COAP_DEFAULT_PORT;
324  }
325  PRINTF("Server address ");
326  PRINT6ADDR(&addr);
327  PRINTF(" port %" PRId32 "%s\n", port, secure ? " (secure)" : "");
328  if(secure) {
329  printf("Secure CoAP requested but not supported - can not bootstrap\n");
330  } else {
331  lwm2m_engine_register_with_server(&addr,
332  UIP_HTONS((uint16_t)port));
333  bootstrapped++;
334  }
335  } else {
336  printf("** failed to parse URI %.*s\n", len, first);
337  }
338  }
339  }
340 
341  if(bootstrapped == 1) {
342  /* Not ready. Lets retry with the bootstrap server again */
343  bootstrapped = 0;
344  }
345 
346  } else if(use_registration && !registered &&
347  update_registration_server()) {
348  int pos;
349  int len, i, j;
350  registered = 1;
351 
352  /* prepare request, TID is set by COAP_BLOCKING_REQUEST() */
353  coap_init_message(request, COAP_TYPE_CON, COAP_POST, 0);
354  coap_set_header_uri_path(request, "/rd");
355  coap_set_header_uri_query(request, endpoint);
356 
357  /* generate the rd data */
358  pos = 0;
359  for(i = 0; i < MAX_OBJECTS; i++) {
360  if(objects[i] != NULL) {
361  for(j = 0; j < objects[i]->count; j++) {
362  if(objects[i]->instances[j].flag & LWM2M_INSTANCE_FLAG_USED) {
363  len = snprintf(&rd_data[pos], sizeof(rd_data) - pos,
364  "%s<%d/%d>", pos > 0 ? "," : "",
365  objects[i]->id, objects[i]->instances[j].id);
366  if(len > 0 && len < sizeof(rd_data) - pos) {
367  pos += len;
368  }
369  }
370  }
371  }
372  }
373 
374  coap_set_payload(request, (uint8_t *)rd_data, pos);
375 
376  printf("Registering with [");
377  uip_debug_ipaddr_print(&server_ipaddr);
378  printf("]:%u lwm2m endpoint '%s': '%.*s'\n", uip_ntohs(server_port),
379  endpoint, pos, rd_data);
380  COAP_BLOCKING_REQUEST(&server_ipaddr, server_port, request,
381  client_chunk_handler);
382  }
383  /* for now only register once... registered = 0; */
384  etimer_set(&et, 15 * CLOCK_SECOND);
385  }
386  }
387  PROCESS_END();
388 }
389 /*---------------------------------------------------------------------------*/
390 void
391 lwm2m_engine_init(void)
392 {
393 #ifdef LWM2M_ENGINE_CLIENT_ENDPOINT_NAME
394 
395  snprintf(endpoint, sizeof(endpoint) - 1,
396  "?ep=" LWM2M_ENGINE_CLIENT_ENDPOINT_NAME);
397 
398 #else /* LWM2M_ENGINE_CLIENT_ENDPOINT_NAME */
399 
400  int len, i;
401  uint8_t state;
402  uip_ipaddr_t *ipaddr;
403  char client[sizeof(endpoint)];
404 
405  len = strlen(LWM2M_ENGINE_CLIENT_ENDPOINT_PREFIX);
406  /* ensure that this fits with the hex-nums */
407  if(len > sizeof(client) - 13) {
408  len = sizeof(client) - 13;
409  }
410  memcpy(client, LWM2M_ENGINE_CLIENT_ENDPOINT_PREFIX, len);
411 
412  /* pick an IP address that is PREFERRED or TENTATIVE */
413  ipaddr = NULL;
414  for(i = 0; i < UIP_DS6_ADDR_NB; i++) {
415  state = uip_ds6_if.addr_list[i].state;
416  if(uip_ds6_if.addr_list[i].isused &&
417  (state == ADDR_TENTATIVE || state == ADDR_PREFERRED)) {
418  ipaddr = &(uip_ds6_if.addr_list[i]).ipaddr;
419  break;
420  }
421  }
422 
423  if(ipaddr != NULL) {
424  for(i = 0; i < 6; i++) {
425  /* assume IPv6 for now */
426  uint8_t b = ipaddr->u8[10 + i];
427  client[len++] = (b >> 4) > 9 ? 'A' - 10 + (b >> 4) : '0' + (b >> 4);
428  client[len++] = (b & 0xf) > 9 ? 'A' - 10 + (b & 0xf) : '0' + (b & 0xf);
429  }
430  }
431 
432  /* a zero at end of string */
433  client[len] = 0;
434  /* create endpoint */
435  snprintf(endpoint, sizeof(endpoint) - 1, "?ep=%s", client);
436 
437 #endif /* LWM2M_ENGINE_CLIENT_ENDPOINT_NAME */
438 
440  process_start(&lwm2m_rd_client, NULL);
441 }
442 /*---------------------------------------------------------------------------*/
443 void
444 lwm2m_engine_register_default_objects(void)
445 {
449 }
450 /*---------------------------------------------------------------------------*/
451 static int
452 parse_next(const char **path, int *path_len, uint16_t *value)
453 {
454  char c;
455  *value = 0;
456  /* printf("parse_next: %p %d\n", *path, *path_len); */
457  if(*path_len == 0) {
458  return 0;
459  }
460  while(*path_len > 0) {
461  c = **path;
462  (*path)++;
463  *path_len = *path_len - 1;
464  if(c >= '0' && c <= '9') {
465  *value = *value * 10 + (c - '0');
466  } else if(c == '/') {
467  return 1;
468  } else {
469  /* error */
470  return -4;
471  }
472  }
473  return 1;
474 }
475 /*---------------------------------------------------------------------------*/
476 int
477 lwm2m_engine_parse_context(const lwm2m_object_t *object,
478  const char *path, int path_len,
479  lwm2m_context_t *context)
480 {
481  int ret;
482  if(context == NULL || object == NULL || path == NULL) {
483  return 0;
484  }
485  memset(context, 0, sizeof(lwm2m_context_t));
486  /* get object id */
487  ret = 0;
488  ret += parse_next(&path, &path_len, &context->object_id);
489  ret += parse_next(&path, &path_len, &context->object_instance_id);
490  ret += parse_next(&path, &path_len, &context->resource_id);
491 
492  /* Set default reader/writer */
493  context->reader = &lwm2m_plain_text_reader;
494  context->writer = &oma_tlv_writer;
495 
496  return ret;
497 }
498 /*---------------------------------------------------------------------------*/
499 const lwm2m_object_t *
500 lwm2m_engine_get_object(uint16_t id)
501 {
502  int i;
503  for(i = 0; i < MAX_OBJECTS; i++) {
504  if(objects[i] != NULL && objects[i]->id == id) {
505  return objects[i];
506  }
507  }
508  return NULL;
509 }
510 /*---------------------------------------------------------------------------*/
511 int
512 lwm2m_engine_register_object(const lwm2m_object_t *object)
513 {
514  int i;
515  int found = 0;
516  for(i = 0; i < MAX_OBJECTS; i++) {
517  if(objects[i] == NULL) {
518  objects[i] = object;
519  found = 1;
520  break;
521  }
522  }
523  rest_activate_resource(lwm2m_object_get_coap_resource(object),
524  (char *)object->path);
525  return found;
526 }
527 /*---------------------------------------------------------------------------*/
528 static const lwm2m_instance_t *
529 get_first_instance_of_object(uint16_t id, lwm2m_context_t *context)
530 {
531  const lwm2m_object_t *object;
532  int i;
533 
534  object = lwm2m_engine_get_object(id);
535  if(object == NULL) {
536  /* No object with the specified id found */
537  return NULL;
538  }
539 
540  /* Initialize the context */
541  memset(context, 0, sizeof(lwm2m_context_t));
542  context->object_id = id;
543 
544  for(i = 0; i < object->count; i++) {
545  if(object->instances[i].flag & LWM2M_INSTANCE_FLAG_USED) {
546  context->object_instance_id = object->instances[i].id;
547  context->object_instance_index = i;
548  return &object->instances[i];
549  }
550  }
551  return NULL;
552 }
553 /*---------------------------------------------------------------------------*/
554 static const lwm2m_instance_t *
555 get_instance(const lwm2m_object_t *object, lwm2m_context_t *context, int depth)
556 {
557  int i;
558  if(depth > 1) {
559  PRINTF("lwm2m: searching for instance %u\n", context->object_instance_id);
560  for(i = 0; i < object->count; i++) {
561  PRINTF(" Instance %d -> %u (used: %d)\n", i, object->instances[i].id,
562  (object->instances[i].flag & LWM2M_INSTANCE_FLAG_USED) != 0);
563  if(object->instances[i].id == context->object_instance_id &&
564  object->instances[i].flag & LWM2M_INSTANCE_FLAG_USED) {
565  context->object_instance_index = i;
566  return &object->instances[i];
567  }
568  }
569  }
570  return NULL;
571 }
572 /*---------------------------------------------------------------------------*/
573 static const lwm2m_resource_t *
574 get_resource(const lwm2m_instance_t *instance, lwm2m_context_t *context)
575 {
576  int i;
577  if(instance != NULL) {
578  PRINTF("lwm2m: searching for resource %u\n", context->resource_id);
579  for(i = 0; i < instance->count; i++) {
580  PRINTF(" Resource %d -> %u\n", i, instance->resources[i].id);
581  if(instance->resources[i].id == context->resource_id) {
582  context->resource_index = i;
583  return &instance->resources[i];
584  }
585  }
586  }
587  return NULL;
588 }
589 /*---------------------------------------------------------------------------*/
590 static int
591 write_rd_link_data(const lwm2m_object_t *object,
592  const lwm2m_instance_t *instance,
593  char *buffer, size_t size)
594 {
595  const lwm2m_resource_t *resource;
596  int len, rdlen, i;
597 
598  PRINTF("<%d/%d>", object->id, instance->id);
599  rdlen = snprintf(buffer, size, "<%d/%d>",
600  object->id, instance->id);
601  if(rdlen < 0 || rdlen >= size) {
602  return -1;
603  }
604 
605  for(i = 0; i < instance->count; i++) {
606  resource = &instance->resources[i];
607  PRINTF(",<%d/%d/%d>", object->id, instance->id, resource->id);
608 
609  len = snprintf(&buffer[rdlen], size - rdlen,
610  ",<%d/%d/%d>", object->id, instance->id, resource->id);
611  rdlen += len;
612  if(len < 0 || rdlen >= size) {
613  return -1;
614  }
615  }
616  return rdlen;
617 }
618 /*---------------------------------------------------------------------------*/
619 static int
620 write_rd_json_data(const lwm2m_context_t *context,
621  const lwm2m_object_t *object,
622  const lwm2m_instance_t *instance,
623  char *buffer, size_t size)
624 {
625  const lwm2m_resource_t *resource;
626  const char *s = "";
627  int len, rdlen, i;
628 
629  PRINTF("{\"e\":[");
630  rdlen = snprintf(buffer, size, "{\"e\":[");
631  if(rdlen < 0 || rdlen >= size) {
632  return -1;
633  }
634 
635  for(i = 0, len = 0; i < instance->count; i++) {
636  resource = &instance->resources[i];
637  len = 0;
638  if(lwm2m_object_is_resource_string(resource)) {
639  const uint8_t *value;
640  uint16_t slen;
641  value = lwm2m_object_get_resource_string(resource, context);
642  slen = lwm2m_object_get_resource_strlen(resource, context);
643  if(value != NULL) {
644  PRINTF("%s{\"n\":\"%u\",\"sv\":\"%.*s\"}", s,
645  resource->id, slen, value);
646  len = snprintf(&buffer[rdlen], size - rdlen,
647  "%s{\"n\":\"%u\",\"sv\":\"%.*s\"}", s,
648  resource->id, slen, value);
649  }
650  } else if(lwm2m_object_is_resource_int(resource)) {
651  int32_t value;
652  if(lwm2m_object_get_resource_int(resource, context, &value)) {
653  PRINTF("%s{\"n\":\"%u\",\"v\":%" PRId32 "}", s,
654  resource->id, value);
655  len = snprintf(&buffer[rdlen], size - rdlen,
656  "%s{\"n\":\"%u\",\"v\":%" PRId32 "}", s,
657  resource->id, value);
658  }
659  } else if(lwm2m_object_is_resource_floatfix(resource)) {
660  int32_t value;
661  if(lwm2m_object_get_resource_floatfix(resource, context, &value)) {
662  PRINTF("%s{\"n\":\"%u\",\"v\":%" PRId32 "}", s, resource->id,
663  value / LWM2M_FLOAT32_FRAC);
664  len = snprintf(&buffer[rdlen], size - rdlen,
665  "%s{\"n\":\"%u\",\"v\":", s, resource->id);
666  rdlen += len;
667  if(len < 0 || rdlen >= size) {
668  return -1;
669  }
670 
671  len = lwm2m_plain_text_write_float32fix((uint8_t *)&buffer[rdlen],
672  size - rdlen,
673  value, LWM2M_FLOAT32_BITS);
674  if(len == 0) {
675  return -1;
676  }
677  rdlen += len;
678 
679  if(rdlen < size) {
680  buffer[rdlen] = '}';
681  }
682  len = 1;
683  }
684  } else if(lwm2m_object_is_resource_boolean(resource)) {
685  int value;
686  if(lwm2m_object_get_resource_boolean(resource, context, &value)) {
687  PRINTF("%s{\"n\":\"%u\",\"bv\":%s}", s, resource->id,
688  value ? "true" : "false");
689  len = snprintf(&buffer[rdlen], size - rdlen,
690  "%s{\"n\":\"%u\",\"bv\":%s}", s, resource->id,
691  value ? "true" : "false");
692  }
693  }
694  rdlen += len;
695  if(len < 0 || rdlen >= size) {
696  return -1;
697  }
698  if(len > 0) {
699  s = ",";
700  }
701  }
702  PRINTF("]}\n");
703  len = snprintf(&buffer[rdlen], size - rdlen, "]}");
704  rdlen += len;
705  if(len < 0 || rdlen >= size) {
706  return -1;
707  }
708 
709  return rdlen;
710 }
711 /*---------------------------------------------------------------------------*/
712 /**
713  * @brief Set the writer pointer to the proper writer based on the Accept: header
714  *
715  * @param[in] context LWM2M context to operate on
716  * @param[in] accept Accept type number from CoAP headers
717  *
718  * @return The content type of the response if the selected writer is used
719  */
720 static unsigned int
721 lwm2m_engine_select_writer(lwm2m_context_t *context, unsigned int accept)
722 {
723  switch(accept) {
724  case LWM2M_TLV:
725  context->writer = &oma_tlv_writer;
726  break;
727  case LWM2M_TEXT_PLAIN:
728  case TEXT_PLAIN:
729  context->writer = &lwm2m_plain_text_writer;
730  break;
731  case LWM2M_JSON:
732  case APPLICATION_JSON:
733  context->writer = &lwm2m_json_writer;
734  break;
735  default:
736  PRINTF("Unknown Accept type %u, using LWM2M plain text\n", accept);
737  context->writer = &lwm2m_plain_text_writer;
738  /* Set the response type to plain text */
739  accept = LWM2M_TEXT_PLAIN;
740  break;
741  }
742  return accept;
743 }
744 /*---------------------------------------------------------------------------*/
745 /**
746  * @brief Set the reader pointer to the proper reader based on the Content-format: header
747  *
748  * @param[in] context LWM2M context to operate on
749  * @param[in] content_format Content-type type number from CoAP headers
750  */
751 static void
752 lwm2m_engine_select_reader(lwm2m_context_t *context, unsigned int content_format)
753 {
754  switch(content_format) {
755  case LWM2M_TLV:
756  context->reader = &oma_tlv_reader;
757  break;
758  case LWM2M_TEXT_PLAIN:
759  case TEXT_PLAIN:
760  context->reader = &lwm2m_plain_text_reader;
761  break;
762  default:
763  PRINTF("Unknown content type %u, using LWM2M plain text\n", accept);
764  context->reader = &lwm2m_plain_text_reader;
765  break;
766  }
767 }
768 /*---------------------------------------------------------------------------*/
769 void
770 lwm2m_engine_handler(const lwm2m_object_t *object,
771  void *request, void *response,
772  uint8_t *buffer, uint16_t preferred_size,
773  int32_t *offset)
774 {
775  int len;
776  const char *url;
777  unsigned int format;
778  unsigned int accept;
779  unsigned int content_type;
780  int depth;
781  lwm2m_context_t context;
782  rest_resource_flags_t method;
783  const lwm2m_instance_t *instance;
784 #if (DEBUG) & DEBUG_PRINT
785  const char *method_str;
786 #endif /* (DEBUG) & DEBUG_PRINT */
787 
788  method = REST.get_method_type(request);
789 
790  len = REST.get_url(request, &url);
791  if(!REST.get_header_content_type(request, &format)) {
792  PRINTF("No format given. Assume text plain...\n");
793  format = LWM2M_TEXT_PLAIN;
794  } else if(format == TEXT_PLAIN) {
795  /* CoAP content format text plain - assume LWM2M text plain */
796  format = LWM2M_TEXT_PLAIN;
797  }
798  if(!REST.get_header_accept(request, &accept)) {
799  PRINTF("No Accept header, using same as Content-format...\n");
800  accept = format;
801  }
802 
803  depth = lwm2m_engine_parse_context(object, url, len, &context);
804  PRINTF("Context: %u/%u/%u found: %d\n", context.object_id,
805  context.object_instance_id, context.resource_id, depth);
806 
807  /* Select reader and writer based on provided Content type and Accept headers */
808  lwm2m_engine_select_reader(&context, format);
809  content_type = lwm2m_engine_select_writer(&context, accept);
810 
811 #if (DEBUG) & DEBUG_PRINT
812  /* for debugging */
813  if(method == METHOD_GET) {
814  method_str = "GET";
815  } else if(method == METHOD_POST) {
816  method_str = "POST";
817  } else if(method == METHOD_PUT) {
818  method_str = "PUT";
819  } else if(method == METHOD_DELETE) {
820  method_str = "DELETE";
821  } else {
822  method_str = "UNKNOWN";
823  }
824  PRINTF("%s Called Path:%.*s Format:%d ID:%d bsize:%u\n", method_str, len,
825  url, format, object->id, preferred_size);
826  if(format == LWM2M_TEXT_PLAIN) {
827  /* a string */
828  const uint8_t *data;
829  int plen = REST.get_request_payload(request, &data);
830  if(plen > 0) {
831  PRINTF("Data: '%.*s'\n", plen, (char *)data);
832  }
833  }
834 #endif /* (DEBUG) & DEBUG_PRINT */
835 
836  instance = get_instance(object, &context, depth);
837 
838  /* from POST */
839  if(depth > 1 && instance == NULL) {
840  if(method != METHOD_PUT && method != METHOD_POST) {
841  PRINTF("Error - do not have instance %d\n", context.object_instance_id);
842  REST.set_response_status(response, NOT_FOUND_4_04);
843  return;
844  } else {
845  const uint8_t *data;
846  int i, len, plen, pos;
847  oma_tlv_t tlv;
848  PRINTF(">>> CREATE ? %d/%d\n", context.object_id,
849  context.object_instance_id);
850 
851  for(i = 0; i < object->count; i++) {
852  if((object->instances[i].flag & LWM2M_INSTANCE_FLAG_USED) == 0) {
853  /* allocate this instance */
854  object->instances[i].flag |= LWM2M_INSTANCE_FLAG_USED;
855  object->instances[i].id = context.object_instance_id;
856  context.object_instance_index = i;
857  PRINTF("Created instance: %d\n", context.object_instance_id);
858  REST.set_response_status(response, CREATED_2_01);
859  instance = &object->instances[i];
860  break;
861  }
862  }
863 
864  if(instance == NULL) {
865  /* could for some reason not create the instance */
866  REST.set_response_status(response, NOT_ACCEPTABLE_4_06);
867  return;
868  }
869 
870  plen = REST.get_request_payload(request, &data);
871  if(plen == 0) {
872  /* do nothing more */
873  return;
874  }
875  PRINTF("Payload: ");
876  for(i = 0; i < plen; i++) {
877  PRINTF("%02x", data[i]);
878  }
879  PRINTF("\n");
880 
881  pos = 0;
882  do {
883  len = oma_tlv_read(&tlv, (uint8_t *)&data[pos], plen - pos);
884  PRINTF("Found TLV type=%u id=%u len=%lu\n",
885  tlv.type, tlv.id, (unsigned long)tlv.length);
886  /* here we need to do callbacks or write value */
887  if(tlv.type == OMA_TLV_TYPE_RESOURCE) {
888  context.resource_id = tlv.id;
889  const lwm2m_resource_t *rsc = get_resource(instance, &context);
890  if(rsc != NULL) {
891  /* write the value to the resource */
892  if(lwm2m_object_is_resource_string(rsc)) {
893  PRINTF(" new string value for /%d/%d/%d = %.*s\n",
894  context.object_id, context.object_instance_id,
895  context.resource_id, (int)tlv.length, tlv.value);
896  lwm2m_object_set_resource_string(rsc, &context,
897  tlv.length, tlv.value);
898  } else if(lwm2m_object_is_resource_int(rsc)) {
899  PRINTF(" new int value for /%d/%d/%d = %" PRId32 "\n",
900  context.object_id, context.object_instance_id,
901  context.resource_id, oma_tlv_get_int32(&tlv));
902  lwm2m_object_set_resource_int(rsc, &context,
903  oma_tlv_get_int32(&tlv));
904  } else if(lwm2m_object_is_resource_floatfix(rsc)) {
905  int32_t value;
906  if(oma_tlv_float32_to_fix(&tlv, &value, LWM2M_FLOAT32_BITS)) {
907  PRINTF(" new float value for /%d/%d/%d = %" PRId32 "\n",
908  context.object_id, context.object_instance_id,
909  context.resource_id, value >> LWM2M_FLOAT32_BITS);
910  lwm2m_object_set_resource_floatfix(rsc, &context, value);
911  } else {
912  PRINTF(" new float value for /%d/%d/%d: FAILED\n",
913  context.object_id, context.object_instance_id,
914  context.resource_id);
915  }
916  } else if(lwm2m_object_is_resource_boolean(rsc)) {
917  PRINTF(" new boolean value for /%d/%d/%d = %" PRId32 "\n",
918  context.object_id, context.object_instance_id,
919  context.resource_id, oma_tlv_get_int32(&tlv));
920  lwm2m_object_set_resource_boolean(rsc, &context,
921  oma_tlv_get_int32(&tlv) != 0);
922  }
923  }
924  }
925  pos = pos + len;
926  } while(len > 0 && pos < plen);
927  }
928  return;
929  }
930 
931  if(depth == 3) {
932  const lwm2m_resource_t *resource = get_resource(instance, &context);
933  size_t content_len = 0;
934  if(resource == NULL) {
935  PRINTF("Error - do not have resource %d\n", context.resource_id);
936  REST.set_response_status(response, NOT_FOUND_4_04);
937  return;
938  }
939  /* HANDLE PUT */
940  if(method == METHOD_PUT) {
941  if(lwm2m_object_is_resource_callback(resource)) {
942  if(resource->value.callback.write != NULL) {
943  /* pick a reader ??? */
944  if(format == LWM2M_TEXT_PLAIN) {
945  /* a string */
946  const uint8_t *data;
947  int plen = REST.get_request_payload(request, &data);
948  context.reader = &lwm2m_plain_text_reader;
949  PRINTF("PUT Callback with data: '%.*s'\n", plen, data);
950  /* no specific reader for plain text */
951  content_len = resource->value.callback.write(&context, data, plen,
952  buffer, preferred_size);
953  PRINTF("content_len:%u\n", (unsigned int)content_len);
954  REST.set_response_status(response, CHANGED_2_04);
955  } else {
956  PRINTF("PUT callback with format %d\n", format);
957  REST.set_response_status(response, NOT_ACCEPTABLE_4_06);
958  }
959  } else {
960  PRINTF("PUT - no write callback\n");
961  REST.set_response_status(response, METHOD_NOT_ALLOWED_4_05);
962  }
963  } else {
964  PRINTF("PUT on non-callback resource!\n");
965  REST.set_response_status(response, METHOD_NOT_ALLOWED_4_05);
966  }
967  /* HANDLE GET */
968  } else if(method == METHOD_GET) {
969  if(lwm2m_object_is_resource_string(resource)) {
970  const uint8_t *value;
971  value = lwm2m_object_get_resource_string(resource, &context);
972  if(value != NULL) {
973  uint16_t len = lwm2m_object_get_resource_strlen(resource, &context);
974  PRINTF("Get string value: %.*s\n", (int)len, (char *)value);
975  content_len = context.writer->write_string(&context, buffer,
976  preferred_size, (const char *)value, len);
977  }
978  } else if(lwm2m_object_is_resource_int(resource)) {
979  int32_t value;
980  if(lwm2m_object_get_resource_int(resource, &context, &value)) {
981  content_len = context.writer->write_int(&context, buffer, preferred_size, value);
982  }
983  } else if(lwm2m_object_is_resource_floatfix(resource)) {
984  int32_t value;
985  if(lwm2m_object_get_resource_floatfix(resource, &context, &value)) {
986  /* export FLOATFIX */
987  PRINTF("Exporting %d-bit fix as float: %" PRId32 "\n",
988  LWM2M_FLOAT32_BITS, value);
989  content_len = context.writer->write_float32fix(&context, buffer,
990  preferred_size, value, LWM2M_FLOAT32_BITS);
991  }
992  } else if(lwm2m_object_is_resource_callback(resource)) {
993  if(resource->value.callback.read != NULL) {
994  content_len = resource->value.callback.read(&context,
995  buffer, preferred_size);
996  } else {
997  REST.set_response_status(response, METHOD_NOT_ALLOWED_4_05);
998  return;
999  }
1000  }
1001  if(content_len > 0) {
1002  REST.set_response_payload(response, buffer, content_len);
1003  REST.set_header_content_type(response, content_type);
1004  } else {
1005  /* failed to produce output - it is an internal error */
1006  REST.set_response_status(response, INTERNAL_SERVER_ERROR_5_00);
1007  }
1008  /* Handle POST */
1009  } else if(method == METHOD_POST) {
1010  if(lwm2m_object_is_resource_callback(resource)) {
1011  if(resource->value.callback.exec != NULL) {
1012  const uint8_t *data;
1013  int plen = REST.get_request_payload(request, &data);
1014  PRINTF("Execute Callback with data: '%.*s'\n", plen, data);
1015  content_len = resource->value.callback.exec(&context,
1016  data, plen,
1017  buffer, preferred_size);
1018  REST.set_response_status(response, CHANGED_2_04);
1019  } else {
1020  PRINTF("Execute callback - no exec callback\n");
1021  REST.set_response_status(response, METHOD_NOT_ALLOWED_4_05);
1022  }
1023  } else {
1024  PRINTF("Resource post but no callback resource\n");
1025  REST.set_response_status(response, METHOD_NOT_ALLOWED_4_05);
1026  }
1027  }
1028  } else if(depth == 2) {
1029  /* produce an instance response */
1030  if(method != METHOD_GET) {
1031  REST.set_response_status(response, METHOD_NOT_ALLOWED_4_05);
1032  } else if(instance == NULL) {
1033  REST.set_response_status(response, NOT_FOUND_4_04);
1034  } else {
1035  int rdlen;
1036  if(accept == APPLICATION_LINK_FORMAT) {
1037  rdlen = write_rd_link_data(object, instance,
1038  (char *)buffer, preferred_size);
1039  } else {
1040  rdlen = write_rd_json_data(&context, object, instance,
1041  (char *)buffer, preferred_size);
1042  }
1043  if(rdlen < 0) {
1044  PRINTF("Failed to generate instance response\n");
1045  REST.set_response_status(response, SERVICE_UNAVAILABLE_5_03);
1046  return;
1047  }
1048  REST.set_response_payload(response, buffer, rdlen);
1049  if(accept == APPLICATION_LINK_FORMAT) {
1050  REST.set_header_content_type(response, REST.type.APPLICATION_LINK_FORMAT);
1051  } else {
1052  REST.set_header_content_type(response, LWM2M_JSON);
1053  }
1054  }
1055  }
1056 }
1057 /*---------------------------------------------------------------------------*/
1058 void
1059 lwm2m_engine_delete_handler(const lwm2m_object_t *object, void *request,
1060  void *response, uint8_t *buffer,
1061  uint16_t preferred_size, int32_t *offset)
1062 {
1063  int len;
1064  const char *url;
1065  lwm2m_context_t context;
1066 
1067  len = REST.get_url(request, &url);
1068  PRINTF("*** DELETE URI:'%.*s' called... - responding with DELETED.\n",
1069  len, url);
1070  len = lwm2m_engine_parse_context(object, url, len, &context);
1071  PRINTF("Context: %u/%u/%u found: %d\n", context.object_id,
1072  context.object_instance_id, context.resource_id, len);
1073 
1074  REST.set_response_status(response, DELETED_2_02);
1075 }
1076 /*---------------------------------------------------------------------------*/
1077 /** @} */
void process_poll(struct process *p)
Request a process to be polled.
Definition: process.c:371
static uip_ipaddr_t ipaddr
Pointer to prefix information option in uip_buf.
Definition: uip-nd6.c:129
static void start(void)
Start measurement.
Header file for the Contiki OMA LWM2M TLV reader
Header file for the Contiki OMA LWM2M device
static uip_ds6_addr_t * addr
Pointer to a router list entry.
Definition: uip-nd6.c:124
Header file for IPv6-related data structures.
rest_resource_flags_t
Resource flags for allowed methods and special functionalities.
#define UIP_HTONS(n)
Convert 16-bit quantity from host byte order to network byte order.
Definition: uip.h:1239
void etimer_set(struct etimer *et, clock_time_t interval)
Set an event timer.
Definition: etimer.c:177
#define PROCESS_END()
Define the end of a process.
Definition: process.h:131
#define PROCESS(name, strname)
Declare a process.
Definition: process.h:307
Header file for the Contiki OMA LWM2M object API
PROCESS_THREAD(lwm2m_rd_client, ev, data)
Definition: lwm2m-engine.c:248
An abstraction layer for RESTful Web services (Erbium).
A timer.
Definition: etimer.h:76
#define uip_ipaddr_copy(dest, src)
Copy an IP address from one place to another.
Definition: uip.h:1027
static struct sicslowpan_addr_context * context
Addresses contexts for IPHC.
Definition: sicslowpan.c:501
Collection of constants specified in the CoAP standard.
Header file for the Contiki OMA LWM2M TLV writer
void rest_activate_resource(resource_t *resource, char *path)
Makes a resource available under the given URI path.
Definition: rest-engine.c:103
Header file for the Contiki OMA LWM2M engine
#define NULL
The null pointer.
static unsigned int lwm2m_engine_select_writer(lwm2m_context_t *context, unsigned int accept)
Set the writer pointer to the proper writer based on the Accept: header.
Definition: lwm2m-engine.c:721
#define PROCESS_YIELD()
Yield the currently running process.
Definition: process.h:164
int etimer_expired(struct etimer *et)
Check if an event timer has expired.
Definition: etimer.c:213
void lwm2m_security_init(void)
void rest_init_engine(void)
Initializes and starts the REST Engine process.
Definition: rest-engine.c:71
#define ADDR_TENTATIVE
Possible states for the an address (RFC 4862)
Definition: uip-ds6.h:153
#define CLOCK_SECOND
A second, measured in system clock time.
Definition: clock.h:82
Header file for the Contiki OMA LWM2M plain text reader / writer
void process_start(struct process *p, process_data_t data)
Start a process.
Definition: process.c:99
#define uiplib_ipaddrconv
Convert a textual representation of an IP address to a numerical representation.
Definition: uiplib.h:71
Header file for the Contiki OMA LWM2M TLV
A set of debugging macros for the IP stack
void lwm2m_device_init(void)
Definition: lwm2m-device.c:142
uip_ds6_netif_t uip_ds6_if
The single interface.
Definition: uip-ds6.c:71
static void lwm2m_engine_select_reader(lwm2m_context_t *context, unsigned int content_format)
Set the reader pointer to the proper reader based on the Content-format: header.
Definition: lwm2m-engine.c:752
void lwm2m_server_init(void)
Definition: lwm2m-server.c:74
static uint8_t accept(uint8_t in)
Processes an incoming or outgoing multicast message and determines whether it should be dropped or ac...
Definition: roll-tm.c:894
CoAP implementation for the REST Engine.
Header file for the Contiki OMA LWM2M JSON writer
#define PROCESS_BEGIN()
Define the beginning of a process.
Definition: process.h:120