Contiki-NG
lwm2m-engine.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2015-2018, 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 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  * Carlos Gonzalo Peces <carlosgp143@gmail.com>
43  */
44 
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 "coap-constants.h"
51 #include "coap-engine.h"
52 #include "lwm2m-tlv.h"
53 #include "lwm2m-tlv-reader.h"
54 #include "lwm2m-tlv-writer.h"
55 #include "lib/list.h"
56 #include "sys/cc.h"
57 #include <stdio.h>
58 #include <string.h>
59 #include <inttypes.h>
60 #ifndef LWM2M_ENGINE_CLIENT_ENDPOINT_NAME
61 #include "net/ipv6/uip-ds6.h"
62 #endif /* LWM2M_ENGINE_CLIENT_ENDPOINT_NAME */
63 
64 /* Log configuration */
65 #include "coap-log.h"
66 #define LOG_MODULE "lwm2m-eng"
67 #define LOG_LEVEL LOG_LEVEL_LWM2M
68 
69 #ifndef LWM2M_ENGINE_CLIENT_ENDPOINT_PREFIX
70 #ifdef LWM2M_DEVICE_MODEL_NUMBER
71 #define LWM2M_ENGINE_CLIENT_ENDPOINT_PREFIX "Contiki-NG-"LWM2M_DEVICE_MODEL_NUMBER
72 #else /* LWM2M_DEVICE_MODEL_NUMBER */
73 #define LWM2M_ENGINE_CLIENT_ENDPOINT_PREFIX "Contiki-NG"
74 #endif /* LWM2M_DEVICE_MODEL_NUMBER */
75 #endif /* LWM2M_ENGINE_CLIENT_ENDPOINT_PREFIX */
76 
77 #ifdef LWM2M_ENGINE_CONF_USE_RD_CLIENT
78 #define USE_RD_CLIENT LWM2M_ENGINE_CONF_USE_RD_CLIENT
79 #else
80 #define USE_RD_CLIENT 1
81 #endif /* LWM2M_ENGINE_CONF_USE_RD_CLIENT */
82 
83 
84 #if LWM2M_QUEUE_MODE_ENABLED
85  /* Queue Mode is handled using the RD Client and the Q-Mode object */
86 #define USE_RD_CLIENT 1
87 #endif
88 
89 #if USE_RD_CLIENT
90 #include "lwm2m-rd-client.h"
91 #endif
92 
93 #if LWM2M_QUEUE_MODE_ENABLED
94 #include "lwm2m-queue-mode.h"
96 #if LWM2M_QUEUE_MODE_OBJECT_ENABLED
98 #endif /* LWM2M_QUEUE_MODE_OBJECT_ENABLED */
99 #endif /* LWM2M_QUEUE_MODE_ENABLED */
100 
101 /* MACRO for getting out resource ID from resource array ID + flags */
102 #define RSC_ID(x) ((uint16_t)(x & 0xffff))
103 #define RSC_READABLE(x) ((x & LWM2M_RESOURCE_READ) > 0)
104 #define RSC_WRITABLE(x) ((x & LWM2M_RESOURCE_WRITE) > 0)
105 #define RSC_UNSPECIFIED(x) ((x & LWM2M_RESOURCE_OP_MASK) == 0)
106 
107 /* invalid instance ID - ffff object ID */
108 #define NO_INSTANCE 0xffffffff
109 
110 /* This is a double-buffer for generating BLOCKs in CoAP - the idea
111  is that typical LWM2M resources will fit 1 block unless they themselves
112  handle BLOCK transfer - having a double sized buffer makes it possible
113  to allow writing more than one block before sending the full block.
114  The RFC seems to indicate that all blocks execept the last one should
115  be full.
116 */
117 static uint8_t d_buf[COAP_MAX_BLOCK_SIZE * 2];
118 static lwm2m_buffer_t lwm2m_buf = {
119  .len = 0, .size = COAP_MAX_BLOCK_SIZE * 2, .buffer = d_buf
120 };
121 static lwm2m_object_instance_t instance_buffer;
122 
123 /* obj-id / ... */
124 static uint16_t lwm2m_buf_lock[4];
125 static uint64_t lwm2m_buf_lock_timeout = 0;
126 
127 static lwm2m_write_opaque_callback current_opaque_callback;
128 static int current_opaque_offset = 0;
129 
130 static coap_handler_status_t lwm2m_handler_callback(coap_message_t *request,
131  coap_message_t *response,
132  uint8_t *buffer,
133  uint16_t buffer_size,
134  int32_t *offset);
135 static lwm2m_object_instance_t *
136 next_object_instance(const lwm2m_context_t *context, lwm2m_object_t *object, lwm2m_object_instance_t *last);
137 
138 static struct {
139  uint16_t object_id;
140  uint16_t instance_id;
141  uint16_t token_len;
142  uint8_t token[COAP_TOKEN_LEN];
143  /* in the future also a timeout */
144 } created;
145 
146 COAP_HANDLER(lwm2m_handler, lwm2m_handler_callback);
147 LIST(object_list);
148 LIST(generic_object_list);
149 
150 /*---------------------------------------------------------------------------*/
151 static lwm2m_object_t *
152 get_object(uint16_t object_id)
153 {
154  lwm2m_object_t *object;
155  for(object = list_head(generic_object_list);
156  object != NULL;
157  object = object->next) {
158  if(object->impl && object->impl->object_id == object_id) {
159  return object;
160  }
161  }
162  return NULL;
163 }
164 /*---------------------------------------------------------------------------*/
165 static int
166 has_non_generic_object(uint16_t object_id)
167 {
168  lwm2m_object_instance_t *instance;
169  for(instance = list_head(object_list);
170  instance != NULL;
171  instance = instance->next) {
172  if(instance->object_id == object_id) {
173  return 1;
174  }
175  }
176 
177  return 0;
178 }
179 /*---------------------------------------------------------------------------*/
180 static lwm2m_object_instance_t *
181 get_instance(uint16_t object_id, uint16_t instance_id, lwm2m_object_t **o)
182 {
183  lwm2m_object_instance_t *instance;
184  lwm2m_object_t *object;
185 
186  if(o) {
187  *o = NULL;
188  }
189 
190  for(instance = list_head(object_list);
191  instance != NULL;
192  instance = instance->next) {
193  if(instance->object_id == object_id) {
194  if(instance->instance_id == instance_id ||
195  instance_id == LWM2M_OBJECT_INSTANCE_NONE) {
196  return instance;
197  }
198  }
199  }
200 
201  object = get_object(object_id);
202  if(object != NULL) {
203  if(o) {
204  *o = object;
205  }
206  if(instance_id == LWM2M_OBJECT_INSTANCE_NONE) {
207  return object->impl->get_first(NULL);
208  }
209  return object->impl->get_by_id(instance_id, NULL);
210  }
211 
212  return NULL;
213 }
214 /*---------------------------------------------------------------------------*/
215 static lwm2m_object_instance_t *
216 get_instance_by_context(const lwm2m_context_t *context, lwm2m_object_t **o)
217 {
218  if(context->level < 2) {
219  return get_instance(context->object_id, LWM2M_OBJECT_INSTANCE_NONE, o);
220  }
221  return get_instance(context->object_id, context->object_instance_id, o);
222 }
223 /*---------------------------------------------------------------------------*/
224 static lwm2m_status_t
225 call_instance(lwm2m_object_instance_t *instance, lwm2m_context_t *context)
226 {
227  if(context->level < 3) {
228  return LWM2M_STATUS_OPERATION_NOT_ALLOWED;
229  }
230 
231  if(instance == NULL) {
232  /* No instance */
233  return LWM2M_STATUS_NOT_FOUND;
234  }
235 
236  if(instance->callback == NULL) {
237  return LWM2M_STATUS_ERROR;
238  }
239 
240  return instance->callback(instance, context);
241 }
242 /*---------------------------------------------------------------------------*/
243 /* This is intended to switch out a block2 transfer buffer
244  * It assumes that ctx containts the double buffer and that the outbuf is to
245  * be the new buffer in ctx.
246  */
247 static int
248 double_buffer_flush(lwm2m_buffer_t *ctxbuf, lwm2m_buffer_t *outbuf, int size)
249 {
250  /* Copy the data from the double buffer in ctx to the outbuf and move data */
251  /* If the buffer is less than size - we will output all and get remaining down
252  to zero */
253  if(ctxbuf->len < size) {
254  size = ctxbuf->len;
255  }
256  if(ctxbuf->len >= size && outbuf->size >= size) {
257  LOG_DBG("Double buffer - copying out %d bytes remaining: %d\n",
258  size, ctxbuf->len - size);
259  memcpy(outbuf->buffer, ctxbuf->buffer, size);
260  memcpy(ctxbuf->buffer, &ctxbuf->buffer[size],
261  ctxbuf->len - size);
262  ctxbuf->len -= size;
263  outbuf->len = size;
264  return outbuf->len;
265  }
266  return 0;
267 }
268 /*---------------------------------------------------------------------------*/
269 static inline const char *
270 get_method_as_string(coap_resource_flags_t method)
271 {
272  if(method == METHOD_GET) {
273  return "GET";
274  } else if(method == METHOD_POST) {
275  return "POST";
276  } else if(method == METHOD_PUT) {
277  return "PUT";
278  } else if(method == METHOD_DELETE) {
279  return "DELETE";
280  } else {
281  return "UNKNOWN";
282  }
283 }
284 /*--------------------------------------------------------------------------*/
285 static const char *
286 get_status_as_string(lwm2m_status_t status)
287 {
288  static char buffer[8];
289  switch(status) {
290  case LWM2M_STATUS_OK:
291  return "OK";
292  case LWM2M_STATUS_ERROR:
293  return "ERROR";
294  case LWM2M_STATUS_WRITE_ERROR:
295  return "WRITE ERROR";
296  case LWM2M_STATUS_READ_ERROR:
297  return "READ ERROR";
298  case LWM2M_STATUS_BAD_REQUEST:
299  return "BAD REQUEST";
300  case LWM2M_STATUS_UNAUTHORIZED:
301  return "UNAUTHORIZED";
302  case LWM2M_STATUS_FORBIDDEN:
303  return "FORBIDDEN";
304  case LWM2M_STATUS_NOT_FOUND:
305  return "NOT FOUND";
306  case LWM2M_STATUS_OPERATION_NOT_ALLOWED:
307  return "OPERATION NOT ALLOWED";
308  case LWM2M_STATUS_NOT_ACCEPTABLE:
309  return "NOT ACCEPTABLE";
310  case LWM2M_STATUS_UNSUPPORTED_CONTENT_FORMAT:
311  return "UNSUPPORTED CONTENT FORMAT";
312  case LWM2M_STATUS_NOT_IMPLEMENTED:
313  return "NOT IMPLEMENTED";
314  case LWM2M_STATUS_SERVICE_UNAVAILABLE:
315  return "SERVICE UNAVAILABLE";
316  default:
317  snprintf(buffer, sizeof(buffer) - 1, "<%u>", status);
318  return buffer;
319  }
320 }
321 /*--------------------------------------------------------------------------*/
322 static int
323 parse_path(const char *path, int path_len,
324  uint16_t *oid, uint16_t *iid, uint16_t *rid)
325 {
326  int ret;
327  int pos;
328  uint16_t val;
329  char c = 0;
330 
331  /* get object id */
332  LOG_DBG("parse PATH: \"");
333  LOG_DBG_COAP_STRING(path, path_len);
334  LOG_DBG_("\"\n");
335 
336  ret = 0;
337  pos = 0;
338  do {
339  val = 0;
340  /* we should get a value first - consume all numbers */
341  while(pos < path_len && (c = path[pos]) >= '0' && c <= '9') {
342  val = val * 10 + (c - '0');
343  pos++;
344  }
345  /* Slash will mote thing forward - and the end will be when pos == pl */
346  if(c == '/' || pos == path_len) {
347  /* PRINTF("Setting %u = %u\n", ret, val); */
348  if(ret == 0) *oid = val;
349  if(ret == 1) *iid = val;
350  if(ret == 2) *rid = val;
351  ret++;
352  pos++;
353  } else {
354  /* PRINTF("Error: illegal char '%c' at pos:%d\n", c, pos); */
355  return -1;
356  }
357  } while(pos < path_len);
358  return ret;
359 }
360 /*--------------------------------------------------------------------------*/
361 static int
362 lwm2m_engine_parse_context(const char *path, int path_len,
363  coap_message_t *request, coap_message_t *response,
364  uint8_t *outbuf, size_t outsize,
365  lwm2m_context_t *context)
366 {
367  int ret;
368  if(context == NULL || path == NULL) {
369  return 0;
370  }
371 
372  ret = parse_path(path, path_len, &context->object_id,
373  &context->object_instance_id, &context->resource_id);
374 
375  if(ret > 0) {
376  context->level = ret;
377  }
378 
379  return ret;
380 }
381 
382 /*---------------------------------------------------------------------------*/
383 void lwm2m_engine_set_opaque_callback(lwm2m_context_t *ctx, lwm2m_write_opaque_callback cb)
384 {
385  /* Here we should set the callback for the opaque that we are currently generating... */
386  /* And we should in the future associate the callback with the CoAP message info - MID */
387  LOG_DBG("Setting opaque handler - offset: %"PRIu32",%d\n",
388  ctx->offset, ctx->outbuf->len);
389 
390  current_opaque_offset = 0;
391  current_opaque_callback = cb;
392 }
393 /*---------------------------------------------------------------------------*/
394 int
395 lwm2m_engine_set_rd_data(lwm2m_buffer_t *outbuf, int block)
396 {
397  /* remember things here - need to lock lwm2m buffer also!!! */
398  static lwm2m_object_t *object;
399  static lwm2m_object_instance_t *instance;
400  int len;
401  /* pick size from outbuf */
402  int maxsize = outbuf->size;
403 
404  if(lwm2m_buf_lock[0] != 0 && (lwm2m_buf_lock_timeout > coap_timer_uptime()) &&
405  ((lwm2m_buf_lock[1] != 0xffff) ||
406  (lwm2m_buf_lock[2] != 0xffff))) {
407  LOG_DBG("Set-RD: already exporting resource: %d/%d/%d\n",
408  lwm2m_buf_lock[1], lwm2m_buf_lock[2], lwm2m_buf_lock[3]);
409  /* fail - what should we return here? */
410  return 0;
411  }
412 
413  if(block == 0) {
414  LOG_DBG("Starting RD generation\n");
415  /* start with simple object instances */
416  instance = list_head(object_list);
417  object = NULL;
418 
419  if(instance == NULL) {
420  /* No simple object instances available */
421  object = list_head(generic_object_list);
422  if(object == NULL) {
423  /* No objects of any kind available */
424  return 0;
425  }
426  if(object->impl != NULL) {
427  instance = object->impl->get_first(NULL);
428  }
429  }
430 
431  lwm2m_buf_lock[0] = 1; /* lock "flag" */
432  lwm2m_buf_lock[1] = 0xffff;
433  lwm2m_buf_lock[2] = 0xffff;
434  lwm2m_buf_lock[3] = 0xffff;
435  } else {
436  /* object and instance was static... */
437  }
438 
439  lwm2m_buf_lock_timeout = coap_timer_uptime() + 1000;
440 
441  LOG_DBG("Generating RD list:");
442  while(instance != NULL || object != NULL) {
443  int pos = lwm2m_buf.len;
444  if(instance != NULL) {
445  len = snprintf((char *) &lwm2m_buf.buffer[pos],
446  lwm2m_buf.size - pos, (pos > 0 || block > 0) ? ",</%d/%d>" : "</%d/%d>",
447  instance->object_id, instance->instance_id);
448  LOG_DBG_((pos > 0 || block > 0) ? ",</%d/%d>" : "</%d/%d>",
449  instance->object_id, instance->instance_id);
450  } else if(object->impl != NULL) {
451  len = snprintf((char *) &lwm2m_buf.buffer[pos],
452  lwm2m_buf.size - pos,
453  (pos > 0 || block > 0) ? ",</%d>" : "</%d>",
454  object->impl->object_id);
455  LOG_DBG_((pos > 0 || block > 0) ? ",</%d>" : "</%d>",
456  object->impl->object_id);
457  } else {
458  len = 0;
459  }
460  lwm2m_buf.len += len;
461  if(instance != NULL) {
462  instance = next_object_instance(NULL, object, instance);
463  }
464 
465  if(instance == NULL) {
466  if(object == NULL) {
467  /*
468  * No object and no instance - we are done with simple object instances.
469  */
470  object = list_head(generic_object_list);
471  } else {
472  /*
473  * Object exists but not an instance - instances for this object are
474  * done - go to next.
475  */
476  object = object->next;
477  }
478 
479  if(object != NULL && object->impl != NULL) {
480  instance = object->impl->get_first(NULL);
481  }
482 
483  if(instance == NULL && object == NULL && lwm2m_buf.len <= maxsize) {
484  /* Data generation is done. No more messages are needed after this. */
485  break;
486  }
487  }
488 
489  if(lwm2m_buf.len >= maxsize) {
490  LOG_DBG_("\n");
491  LOG_DBG("**** CoAP MAX BLOCK Reached!!! **** SEND\n");
492  /* If the produced data is larger than a CoAP block we need to send
493  this now */
494  double_buffer_flush(&lwm2m_buf, outbuf, maxsize);
495  /* there will be more - keep lock! */
496  return 1;
497  }
498  }
499  LOG_DBG_("\n");
500  double_buffer_flush(&lwm2m_buf, outbuf, maxsize);
501  /* unlock the buffer */
502  lwm2m_buf_lock[0] = 0;
503  return 0;
504 }
505 /*---------------------------------------------------------------------------*/
506 void
507 lwm2m_engine_init(void)
508 {
509  list_init(object_list);
510  list_init(generic_object_list);
511 
512 #ifdef LWM2M_ENGINE_CLIENT_ENDPOINT_NAME
513  const char *endpoint = LWM2M_ENGINE_CLIENT_ENDPOINT_NAME;
514 
515 #else /* LWM2M_ENGINE_CLIENT_ENDPOINT_NAME */
516  static char endpoint[32];
517  int len, i;
518  uint8_t state;
519  uip_ipaddr_t *ipaddr;
520 
521  len = strlen(LWM2M_ENGINE_CLIENT_ENDPOINT_PREFIX);
522  /* ensure that this fits with the hex-nums */
523  if(len > sizeof(endpoint) - 13) {
524  len = sizeof(endpoint) - 13;
525  }
526 
527  for(i = 0; i < len; i++) {
528  if(LWM2M_ENGINE_CLIENT_ENDPOINT_PREFIX[i] == ' ') {
529  endpoint[i] = '-';
530  } else {
531  endpoint[i] = LWM2M_ENGINE_CLIENT_ENDPOINT_PREFIX[i];
532  }
533  }
534  /* pick an IP address that is PREFERRED or TENTATIVE */
535  ipaddr = NULL;
536  for(i = 0; i < UIP_DS6_ADDR_NB; i++) {
537  state = uip_ds6_if.addr_list[i].state;
538  if(uip_ds6_if.addr_list[i].isused &&
539  (state == ADDR_TENTATIVE || state == ADDR_PREFERRED)) {
540  ipaddr = &(uip_ds6_if.addr_list[i]).ipaddr;
541  break;
542  }
543  }
544 
545  if(ipaddr != NULL) {
546  for(i = 0; i < 6; i++) {
547  /* assume IPv6 for now */
548  uint8_t b = ipaddr->u8[10 + i];
549  endpoint[len++] = (b >> 4) > 9 ? 'A' - 10 + (b >> 4) : '0' + (b >> 4);
550  endpoint[len++] = (b & 0xf) > 9 ? 'A' - 10 + (b & 0xf) : '0' + (b & 0xf);
551  }
552  }
553 
554  /* a zero at end of string */
555  endpoint[len] = 0;
556 
557 #endif /* LWM2M_ENGINE_CLIENT_ENDPOINT_NAME */
558 
559  /* Initialize CoAP engine. Contiki-NG already does that from the main,
560  * but for standalone use of lwm2m, this is required here. coap_engine_init()
561  * checks for double-initialization and can be called twice safely. */
562  coap_engine_init();
563 
564  /* Register the CoAP handler for lightweight object handling */
565  coap_add_handler(&lwm2m_handler);
566 
567 #if USE_RD_CLIENT
568  lwm2m_rd_client_init(endpoint);
569 #endif
570 
571 #if LWM2M_QUEUE_MODE_ENABLED && LWM2M_QUEUE_MODE_OBJECT_ENABLED
572  lwm2m_queue_mode_object_init();
573 #endif
574 }
575 /*---------------------------------------------------------------------------*/
576 /*
577  * Set the writer pointer to the proper writer based on the Accept: header
578  *
579  * param[in] context LWM2M context to operate on
580  * param[in] accept Accept type number from CoAP headers
581  *
582  * return The content type of the response if the selected writer is used
583  */
584 static unsigned int
585 lwm2m_engine_select_writer(lwm2m_context_t *context, unsigned int accept)
586 {
587  switch(accept) {
588  case LWM2M_TLV:
589  case LWM2M_OLD_TLV:
590  context->writer = &lwm2m_tlv_writer;
591  break;
592  case LWM2M_TEXT_PLAIN:
593  case TEXT_PLAIN:
594  context->writer = &lwm2m_plain_text_writer;
595  break;
596  case LWM2M_JSON:
597  case LWM2M_OLD_JSON:
598  case APPLICATION_JSON:
599  context->writer = &lwm2m_json_writer;
600  break;
601  default:
602  LOG_WARN("Unknown Accept type %u, using LWM2M plain text\n", accept);
603  context->writer = &lwm2m_plain_text_writer;
604  /* Set the response type to plain text */
605  accept = LWM2M_TEXT_PLAIN;
606  break;
607  }
608  context->content_type = accept;
609  return accept;
610 }
611 /*---------------------------------------------------------------------------*/
612 /*
613  * Set the reader pointer to the proper reader based on the Content-format: header
614  *
615  * param[in] context LWM2M context to operate on
616  * param[in] content_format Content-type type number from CoAP headers
617  */
618 static void
619 lwm2m_engine_select_reader(lwm2m_context_t *context, unsigned int content_format)
620 {
621  switch(content_format) {
622  case LWM2M_TLV:
623  case LWM2M_OLD_TLV:
624  context->reader = &lwm2m_tlv_reader;
625  break;
626  case LWM2M_JSON:
627  case LWM2M_OLD_JSON:
628  context->reader = &lwm2m_plain_text_reader;
629  break;
630  case LWM2M_TEXT_PLAIN:
631  case TEXT_PLAIN:
632  context->reader = &lwm2m_plain_text_reader;
633  break;
634  default:
635  LOG_WARN("Unknown content type %u, using LWM2M plain text\n",
636  content_format);
637  context->reader = &lwm2m_plain_text_reader;
638  break;
639  }
640 }
641 
642 /*---------------------------------------------------------------------------*/
643 /* Lightweight object instances */
644 /*---------------------------------------------------------------------------*/
645 static uint32_t last_instance_id = NO_INSTANCE;
646 static int last_rsc_pos;
647 
648 /* Multi read will handle read of JSON / TLV or Discovery (Link Format) */
649 static lwm2m_status_t
650 perform_multi_resource_read_op(lwm2m_object_t *object,
651  lwm2m_object_instance_t *instance,
652  lwm2m_context_t *ctx)
653 {
654  int size = ctx->outbuf->size;
655  int len = 0;
656  uint8_t initialized = 0; /* used for commas, etc */
657  uint8_t num_read = 0;
658  lwm2m_buffer_t *outbuf;
659 
660  if(instance == NULL) {
661  /* No existing instance */
662  return LWM2M_STATUS_NOT_FOUND;
663  }
664 
665  if(ctx->level < 3 &&
666  (ctx->content_type == LWM2M_TEXT_PLAIN ||
667  ctx->content_type == TEXT_PLAIN ||
668  ctx->content_type == LWM2M_OLD_OPAQUE)) {
669  return LWM2M_STATUS_OPERATION_NOT_ALLOWED;
670  }
671 
672  /* copy out the out-buffer as read will use its own - will be same for disoc when
673  read is fixed */
674  outbuf = ctx->outbuf;
675 
676  /* Currently we only handle one incoming read request at a time - so we return
677  BUZY or service unavailable */
678  if(lwm2m_buf_lock[0] != 0 && (lwm2m_buf_lock_timeout > coap_timer_uptime()) &&
679  ((lwm2m_buf_lock[1] != ctx->object_id) ||
680  (lwm2m_buf_lock[2] != ctx->object_instance_id) ||
681  (lwm2m_buf_lock[3] != ctx->resource_id))) {
682  LOG_DBG("Multi-read: already exporting resource: %d/%d/%d\n",
683  lwm2m_buf_lock[1], lwm2m_buf_lock[2], lwm2m_buf_lock[3]);
684  return LWM2M_STATUS_SERVICE_UNAVAILABLE;
685  }
686 
687  LOG_DBG("MultiRead: %d/%d/%d lv:%d offset:%"PRIu32"\n",
688  ctx->object_id, ctx->object_instance_id, ctx->resource_id,
689  ctx->level, ctx->offset);
690 
691  /* Make use of the double buffer */
692  ctx->outbuf = &lwm2m_buf;
693 
694  if(ctx->offset == 0) {
695  /* First GET request - need to setup all buffers and reset things here */
696  last_instance_id =
697  ((uint32_t)instance->object_id << 16) | instance->instance_id;
698  last_rsc_pos = 0;
699  /* reset any callback */
700  current_opaque_callback = NULL;
701  /* reset lwm2m_buf_len - so that we can use the double-size buffer */
702  lwm2m_buf_lock[0] = 1; /* lock "flag" */
703  lwm2m_buf_lock[1] = ctx->object_id;
704  lwm2m_buf_lock[2] = ctx->object_instance_id;
705  lwm2m_buf_lock[3] = ctx->resource_id;
706  lwm2m_buf.len = 0;
707  /* Here we should print top node */
708  } else {
709  /* offset > 0 - assume that we are already in a disco or multi get*/
710  instance = get_instance(last_instance_id >> 16, last_instance_id & 0xffff,
711  &object);
712 
713  /* we assume that this was initialized */
714  initialized = 1;
715  ctx->writer_flags |= WRITER_OUTPUT_VALUE;
716  if(instance == NULL) {
717  ctx->offset = -1;
718  ctx->outbuf->buffer[0] = ' ';
719  }
720  }
721  lwm2m_buf_lock_timeout = coap_timer_uptime() + 1000;
722 
723  while(instance != NULL) {
724  /* Do the discovery or read */
725  if(instance->resource_ids != NULL && instance->resource_count > 0) {
726  /* show all the available resources (or read all) */
727  while(last_rsc_pos < instance->resource_count) {
728  LOG_DBG("READ: 0x%"PRIx32" 0x%x 0x%x lv:%d\n",
729  instance->resource_ids[last_rsc_pos],
730  RSC_ID(instance->resource_ids[last_rsc_pos]),
731  ctx->resource_id, ctx->level);
732 
733  /* Check if this is a object read or if it is the correct resource */
734  if(ctx->level < 3 || ctx->resource_id == RSC_ID(instance->resource_ids[last_rsc_pos])) {
735  /* ---------- Discovery operation ------------- */
736  /* If this is a discovery all the object, instance, and resource triples should be
737  generted */
738  if(ctx->operation == LWM2M_OP_DISCOVER) {
739  int dim = 0;
740  len = snprintf((char *) &ctx->outbuf->buffer[ctx->outbuf->len],
741  ctx->outbuf->size - ctx->outbuf->len,
742  (ctx->outbuf->len == 0 && ctx->offset == 0) ? "</%d/%d/%d>":",</%d/%d/%d>",
743  instance->object_id, instance->instance_id,
744  RSC_ID(instance->resource_ids[last_rsc_pos]));
745  if(instance->resource_dim_callback != NULL &&
746  (dim = instance->resource_dim_callback(instance,
747  RSC_ID(instance->resource_ids[last_rsc_pos]))) > 0) {
748  len += snprintf((char *) &ctx->outbuf->buffer[ctx->outbuf->len + len],
749  ctx->outbuf->size - ctx->outbuf->len - len, ";dim=%d", dim);
750  }
751  /* here we have "read" out something */
752  num_read++;
753  ctx->outbuf->len += len;
754  if(len < 0 || ctx->outbuf->len >= size) {
755  double_buffer_flush(ctx->outbuf, outbuf, size);
756 
757  LOG_DBG("Copied lwm2m buf - remaining: %d\n", lwm2m_buf.len);
758  /* switch buffer */
759  ctx->outbuf = outbuf;
760  ctx->writer_flags |= WRITER_HAS_MORE;
761  ctx->offset += size;
762  return LWM2M_STATUS_OK;
763  }
764  /* ---------- Read operation ------------- */
765  } else if(ctx->operation == LWM2M_OP_READ) {
766  lwm2m_status_t success = 0;
767  uint8_t lv;
768 
769  lv = ctx->level;
770 
771  /* Do not allow a read on a non-readable */
772  if(lv == 3 && !RSC_READABLE(instance->resource_ids[last_rsc_pos])) {
773  lwm2m_buf_lock[0] = 0;
774  return LWM2M_STATUS_OPERATION_NOT_ALLOWED;
775  }
776  /* Set the resource ID is ctx->level < 3 */
777  if(lv < 3) {
778  ctx->resource_id = RSC_ID(instance->resource_ids[last_rsc_pos]);
779  }
780  if(lv < 2) {
781  ctx->object_instance_id = instance->instance_id;
782  }
783 
784  if(RSC_READABLE(instance->resource_ids[last_rsc_pos])) {
785  ctx->level = 3;
786  if(!initialized) {
787  /* Now we need to initialize the object writing for this new object */
788  len = ctx->writer->init_write(ctx);
789  ctx->outbuf->len += len;
790  LOG_DBG("INIT WRITE len:%d size:%"PRIu16"\n", len, ctx->outbuf->size);
791  initialized = 1;
792  }
793 
794  if(current_opaque_callback == NULL) {
795  LOG_DBG("Doing the callback to the resource %d\n", ctx->outbuf->len);
796  /* No special opaque callback to handle - use regular callback */
797  success = instance->callback(instance, ctx);
798  LOG_DBG("After the callback to the resource %d: %s\n",
799  ctx->outbuf->len, get_status_as_string(success));
800 
801  if(success != LWM2M_STATUS_OK) {
802  /* What to do here? */
803  LOG_DBG("Callback failed: %s\n", get_status_as_string(success));
804  if(lv < 3) {
805  if(success == LWM2M_STATUS_NOT_FOUND) {
806  /* ok with a not found during a multi read - what more
807  is ok? */
808  } else {
809  lwm2m_buf_lock[0] = 0;
810  return success;
811  }
812  } else {
813  lwm2m_buf_lock[0] = 0;
814  return success;
815  }
816  }
817  }
818  if(current_opaque_callback != NULL) {
819  uint32_t old_offset = ctx->offset;
820  int num_write = COAP_MAX_BLOCK_SIZE - ctx->outbuf->len;
821  /* Check if the callback did set a opaque callback function - then
822  we should produce data via that callback until the opaque has fully
823  been handled */
824  ctx->offset = current_opaque_offset;
825  /* LOG_DBG("Calling the opaque handler %x\n", ctx->writer_flags); */
826  success = current_opaque_callback(instance, ctx, num_write);
827  if((ctx->writer_flags & WRITER_HAS_MORE) == 0) {
828  /* This opaque stream is now done! */
829  /* LOG_DBG("Setting opaque callback to null - it is done!\n"); */
830  current_opaque_callback = NULL;
831  } else if(ctx->outbuf->len < COAP_MAX_BLOCK_SIZE) {
832  lwm2m_buf_lock[0] = 0;
833  return LWM2M_STATUS_ERROR;
834  }
835  current_opaque_offset += num_write;
836  ctx->offset = old_offset;
837  /* LOG_DBG("Setting back offset to: %d\n", ctx->offset); */
838  }
839 
840  /* here we have "read" out something */
841  num_read++;
842  /* We will need to handle no-success and other things */
843  LOG_DBG("Called %u/%u/%u outlen:%u %s\n",
844  ctx->object_id, ctx->object_instance_id, ctx->resource_id,
845  ctx->outbuf->len, get_status_as_string(success));
846 
847  /* we need to handle full buffer, etc here also! */
848  ctx->level = lv;
849  } else {
850  LOG_DBG("Resource %u not readable\n",
851  RSC_ID(instance->resource_ids[last_rsc_pos]));
852  }
853  }
854  }
855  if(current_opaque_callback == NULL) {
856  /* This resource is now done - (only when the opaque is also done) */
857  last_rsc_pos++;
858  } else {
859  LOG_DBG("Opaque is set - continue with that.\n");
860  }
861 
862  if(ctx->outbuf->len >= COAP_MAX_BLOCK_SIZE) {
863  LOG_DBG("**** CoAP MAX BLOCK Reached!!! **** SEND\n");
864  /* If the produced data is larger than a CoAP block we need to send
865  this now */
866  if(ctx->outbuf->len < 2 * COAP_MAX_BLOCK_SIZE) {
867  /* We assume that size is equal to COAP_MAX_BLOCK_SIZE here */
868  double_buffer_flush(ctx->outbuf, outbuf, size);
869 
870  LOG_DBG("Copied lwm2m buf - remaining: %d\n", lwm2m_buf.len);
871  /* switch buffer */
872  ctx->outbuf = outbuf;
873  ctx->writer_flags |= WRITER_HAS_MORE;
874  ctx->offset += size;
875  /* OK - everything went well... but we have more. - keep the lock here! */
876  return LWM2M_STATUS_OK;
877  } else {
878  LOG_WARN("*** ERROR Overflow?\n");
879  return LWM2M_STATUS_ERROR;
880  }
881  }
882  }
883  }
884  instance = next_object_instance(ctx, object, instance);
885  if(instance != NULL) {
886  last_instance_id =
887  ((uint32_t)instance->object_id << 16) | instance->instance_id;
888  } else {
889  last_instance_id = NO_INSTANCE;
890  }
891  if(ctx->operation == LWM2M_OP_READ) {
892  LOG_DBG("END Writer %d ->", ctx->outbuf->len);
893  len = ctx->writer->end_write(ctx);
894  ctx->outbuf->len += len;
895  LOG_DBG("%d\n", ctx->outbuf->len);
896  }
897 
898  initialized = 0;
899  last_rsc_pos = 0;
900  }
901 
902  /* did not read anything even if we should have - on single item */
903  if(num_read == 0 && ctx->level == 3) {
904  lwm2m_buf_lock[0] = 0;
905  return LWM2M_STATUS_NOT_FOUND;
906  }
907 
908  /* seems like we are done! - flush buffer */
909  len = double_buffer_flush(ctx->outbuf, outbuf, size);
910  ctx->outbuf = outbuf;
911  ctx->offset += len;
912 
913  /* If there is still data in the double-buffer - indicate that so that we get another
914  callback */
915  if(lwm2m_buf.len > 0) {
916  ctx->writer_flags |= WRITER_HAS_MORE;
917  } else {
918  /* OK - everything went well we are done, unlock and return */
919  lwm2m_buf_lock[0] = 0;
920  }
921 
922  LOG_DBG("At END: Copied lwm2m buf %d\n", len);
923 
924  return LWM2M_STATUS_OK;
925 }
926 /*---------------------------------------------------------------------------*/
927 static lwm2m_object_instance_t *
928 create_instance(lwm2m_context_t *context, lwm2m_object_t *object)
929 {
930  lwm2m_object_instance_t *instance;
931  if(object == NULL || object->impl == NULL ||
932  object->impl->create_instance == NULL) {
933  return NULL;
934  }
935 
936  /* NOTE: context->object_instance_id needs to be set before calling */
937  instance = object->impl->create_instance(context->object_instance_id, NULL);
938  if(instance != NULL) {
939  LOG_DBG("Created instance: %u/%u\n", context->object_id, context->object_instance_id);
940  coap_set_status_code(context->response, CREATED_2_01);
941 #if USE_RD_CLIENT
942  lwm2m_rd_client_set_update_rd();
943 #endif
944  }
945  return instance;
946 }
947 /*---------------------------------------------------------------------------*/
948 #define MODE_NONE 0
949 #define MODE_INSTANCE 1
950 #define MODE_VALUE 2
951 #define MODE_READY 3
952 
953 static lwm2m_object_instance_t *
954 get_or_create_instance(lwm2m_context_t *ctx, lwm2m_object_t *object,
955  uint16_t *c)
956 {
957  lwm2m_object_instance_t *instance;
958 
959  instance = get_instance_by_context(ctx, NULL);
960  LOG_DBG("Instance: %u/%u/%u = %p\n", ctx->object_id,
961  ctx->object_instance_id, ctx->resource_id, instance);
962  /* by default we assume that the instance is not created... so we set flag to zero */
963  if(c != NULL) {
964  *c = LWM2M_OBJECT_INSTANCE_NONE;
965  }
966  if(instance == NULL) {
967  instance = create_instance(ctx, object);
968  if(instance != NULL) {
969  if(c != NULL) {
970  *c = instance->instance_id;
971  }
972  created.instance_id = instance->instance_id;
973  created.object_id = instance->object_id;
974  created.token_len = MIN(COAP_TOKEN_LEN, ctx->request->token_len);
975  memcpy(&created.token, ctx->request->token, created.token_len);
976  }
977  }
978  return instance;
979 }
980 /*---------------------------------------------------------------------------*/
981 static int
982 check_write(lwm2m_context_t *ctx, lwm2m_object_instance_t *instance, int rid)
983 {
984  int i;
985  if(instance->resource_ids != NULL && instance->resource_count > 0) {
986  int count = instance->resource_count;
987  for(i = 0; i < count; i++) {
988  if(RSC_ID(instance->resource_ids[i]) == rid) {
989  if(RSC_WRITABLE(instance->resource_ids[i])) {
990  /* yes - writable */
991  return 1;
992  }
993  if(RSC_UNSPECIFIED(instance->resource_ids[i]) &&
994  created.instance_id == instance->instance_id &&
995  created.object_id == instance->object_id &&
996  created.token_len == ctx->request->token_len &&
997  memcmp(&created.token, ctx->request->token,
998  created.token_len) == 0) {
999  /* yes - writeable at create - never otherwise - sec / srv */
1000  return 1;
1001  }
1002  break;
1003  }
1004  }
1005  }
1006  /* Resource did not exist... - Ignore to avoid problems. */
1007  if(created.instance_id == instance->instance_id &&
1008  created.object_id == instance->object_id &&
1009  created.token_len == ctx->request->token_len &&
1010  memcmp(&created.token, ctx->request->token,
1011  created.token_len) == 0) {
1012  LOG_DBG("Ignoring resource %u/%u/%d in newly created instance\n",
1013  created.object_id, created.instance_id, rid);
1014  return 1;
1015  }
1016  return 0;
1017 }
1018 /*---------------------------------------------------------------------------*/
1019 static lwm2m_status_t
1020 process_tlv_write(lwm2m_context_t *ctx, lwm2m_object_t *object,
1021  int rid, uint8_t *data, int len)
1022 {
1023  lwm2m_object_instance_t *instance;
1024  uint16_t created = LWM2M_OBJECT_INSTANCE_NONE;
1025  ctx->inbuf->buffer = data;
1026  ctx->inbuf->pos = 0;
1027  ctx->inbuf->size = len;
1028  ctx->level = 3;
1029  ctx->resource_id = rid;
1030  LOG_DBG(" Doing callback to %u/%u/%u\n", ctx->object_id,
1031  ctx->object_instance_id, ctx->resource_id);
1032  instance = get_or_create_instance(ctx, object, &created);
1033  if(instance != NULL && instance->callback != NULL) {
1034  if(check_write(ctx, instance, rid)) {
1035  return instance->callback(instance, ctx);
1036  } else {
1037  return LWM2M_STATUS_OPERATION_NOT_ALLOWED;
1038  }
1039  }
1040  return LWM2M_STATUS_ERROR;
1041 }
1042 /*---------------------------------------------------------------------------*/
1043 static int last_tlv_id = 0;
1044 
1045 static lwm2m_status_t
1046 perform_multi_resource_write_op(lwm2m_object_t *object,
1047  lwm2m_object_instance_t *instance,
1048  lwm2m_context_t *ctx, int format)
1049 {
1050  /* Only for JSON and TLV formats */
1051  uint16_t oid = 0, iid = 0, rid = 0;
1052  uint8_t olv = 0;
1053  uint8_t mode = 0;
1054  uint8_t *inbuf;
1055  int inpos;
1056  size_t insize;
1057  int i;
1058  uint16_t created = LWM2M_OBJECT_INSTANCE_NONE;
1059 
1060  olv = ctx->level;
1061  inbuf = ctx->inbuf->buffer;
1062  inpos = ctx->inbuf->pos;
1063  insize = ctx->inbuf->size;
1064 
1065  if(format == LWM2M_JSON || format == LWM2M_OLD_JSON) {
1066  struct json_data json;
1067 
1068  while(lwm2m_json_next_token(ctx, &json)) {
1069  LOG_DBG("JSON: '");
1070  for(i = 0; i < json.name_len; i++) {
1071  LOG_DBG_("%c", json.name[i]);
1072  }
1073  LOG_DBG_("':'");
1074  for(i = 0; i < json.value_len; i++) {
1075  LOG_DBG_("%c", json.value[i]);
1076  }
1077  LOG_DBG_("'\n");
1078 
1079  if(json.name[0] == 'n') {
1080  i = parse_path((const char *) json.value, json.value_len, &oid, &iid, &rid);
1081  if(i > 0) {
1082  if(ctx->level == 1) {
1083  ctx->level = 3;
1084  ctx->object_instance_id = oid;
1085  ctx->resource_id = iid;
1086 
1087  instance = get_or_create_instance(ctx, object, &created);
1088  }
1089  if(instance != NULL && instance->callback != NULL) {
1090  mode |= MODE_INSTANCE;
1091  } else {
1092  /* Failure... */
1093  return LWM2M_STATUS_ERROR;
1094  }
1095  }
1096  } else {
1097  /* HACK - assume value node - can it be anything else? */
1098  mode |= MODE_VALUE;
1099  /* update values */
1100  inbuf = ctx->inbuf->buffer;
1101  inpos = ctx->inbuf->pos;
1102 
1103  ctx->inbuf->buffer = json.value;
1104  ctx->inbuf->pos = 0;
1105  ctx->inbuf->size = json.value_len;
1106  }
1107 
1108  if(mode == MODE_READY) {
1109  /* allow write if just created - otherwise not */
1110  if(!check_write(ctx, instance, ctx->resource_id)) {
1111  return LWM2M_STATUS_OPERATION_NOT_ALLOWED;
1112  }
1113  if(instance->callback(instance, ctx) != LWM2M_STATUS_OK) {
1114  /* TODO what to do here */
1115  }
1116  mode = MODE_NONE;
1117  ctx->inbuf->buffer = inbuf;
1118  ctx->inbuf->pos = inpos;
1119  ctx->inbuf->size = insize;
1120  ctx->level = olv;
1121  }
1122  }
1123  } else if(format == LWM2M_TLV || format == LWM2M_OLD_TLV) {
1124  size_t len;
1125  lwm2m_tlv_t tlv;
1126  int tlvpos = 0;
1127  lwm2m_status_t status;
1128 
1129  /* For handling blockwise (BLOCK1) write */
1130  uint32_t num;
1131  uint8_t more;
1132  uint16_t size;
1133  uint32_t offset;
1134 
1135  /* NOTE: this assumes that a BLOCK1 non-first block is not a part of a
1136  small TLV but rather a large opaque - this needs to be fixed in the
1137  future */
1138 
1139  if(coap_get_header_block1(ctx->request, &num, &more, &size, &offset)) {
1140  LOG_DBG("CoAP BLOCK1: %"PRIu32"/%d/%d offset:%"PRIu32
1141  " LWM2M CTX->offset=%"PRIu32"\n",
1142  num, more, size, offset, ctx->offset);
1143  LOG_DBG("Last TLV ID:%d final:%d\n", last_tlv_id,
1144  lwm2m_object_is_final_incoming(ctx));
1145  if(offset > 0) {
1146  status = process_tlv_write(ctx, object, last_tlv_id,
1147  inbuf, size);
1148  return status;
1149  }
1150  }
1151 
1152  while(tlvpos < insize) {
1153  len = lwm2m_tlv_read(&tlv, &inbuf[tlvpos], insize - tlvpos);
1154  LOG_DBG("Got TLV format First is: type:%d id:%d len:%d (p:%d len:%d/%d)\n",
1155  tlv.type, tlv.id, (int) tlv.length,
1156  (int) tlvpos, (int) len, (int) insize);
1157  if(tlv.type == LWM2M_TLV_TYPE_OBJECT_INSTANCE) {
1158  lwm2m_tlv_t tlv2;
1159  int len2;
1160  int pos = 0;
1161  ctx->object_instance_id = tlv.id;
1162  if(tlv.length == 0) {
1163  /* Create only - no data */
1164  if((instance = create_instance(ctx, object)) == NULL) {
1165  return LWM2M_STATUS_ERROR;
1166  }
1167  }
1168  while(pos < tlv.length && (len2 = lwm2m_tlv_read(&tlv2, &tlv.value[pos],
1169  tlv.length - pos))) {
1170  LOG_DBG(" TLV type:%d id:%d len:%d (len:%d/%d)\n",
1171  tlv2.type, tlv2.id, (int)tlv2.length,
1172  (int)len2, (int)insize);
1173  if(tlv2.type == LWM2M_TLV_TYPE_RESOURCE) {
1174  last_tlv_id = tlv2.id;
1175  status = process_tlv_write(ctx, object, tlv2.id,
1176  (uint8_t *)&tlv.value[pos], len2);
1177  if(status != LWM2M_STATUS_OK) {
1178  return status;
1179  }
1180  }
1181  pos += len2;
1182  }
1183  } else if(tlv.type == LWM2M_TLV_TYPE_RESOURCE) {
1184  status = process_tlv_write(ctx, object, tlv.id, &inbuf[tlvpos], len);
1185  if(status != LWM2M_STATUS_OK) {
1186  return status;
1187  }
1188  coap_set_status_code(ctx->response, CHANGED_2_04);
1189  }
1190  tlvpos += len;
1191  }
1192  } else if(format == LWM2M_TEXT_PLAIN ||
1193  format == TEXT_PLAIN ||
1194  format == LWM2M_OLD_OPAQUE) {
1195  return call_instance(instance, ctx);
1196 
1197  } else {
1198  /* Unsupported format */
1199  return LWM2M_STATUS_UNSUPPORTED_CONTENT_FORMAT;
1200  }
1201 
1202  /* Here we have a success! */
1203  return LWM2M_STATUS_OK;
1204 }
1205 /*---------------------------------------------------------------------------*/
1206 lwm2m_object_instance_t *
1207 lwm2m_engine_get_instance_buffer(void)
1208 {
1209  return &instance_buffer;
1210 }
1211 /*---------------------------------------------------------------------------*/
1212 int
1213 lwm2m_engine_has_instance(uint16_t object_id, uint16_t instance_id)
1214 {
1215  return get_instance(object_id, instance_id, NULL) != NULL;
1216 }
1217 /*---------------------------------------------------------------------------*/
1218 int
1219 lwm2m_engine_add_object(lwm2m_object_instance_t *object)
1220 {
1221  lwm2m_object_instance_t *instance;
1222  uint16_t min_id = 0xffff;
1223  uint16_t max_id = 0;
1224  int found = 0;
1225 
1226  if(object == NULL || object->callback == NULL) {
1227  /* Insufficient object configuration */
1228  LOG_DBG("failed to register NULL object\n");
1229  return 0;
1230  }
1231  if(get_object(object->object_id) != NULL) {
1232  /* A generic object with this id has already been registered */
1233  LOG_DBG("object with id %u already registered\n", object->object_id);
1234  return 0;
1235  }
1236 
1237  for(instance = list_head(object_list);
1238  instance != NULL;
1239  instance = instance->next) {
1240  if(object->object_id == instance->object_id) {
1241  if(object->instance_id == instance->instance_id) {
1242  LOG_DBG("object with id %u/%u already registered\n",
1243  instance->object_id, instance->instance_id);
1244  return 0;
1245  }
1246 
1247  found++;
1248  if(instance->instance_id > max_id) {
1249  max_id = instance->instance_id;
1250  }
1251  if(instance->instance_id < min_id) {
1252  min_id = instance->instance_id;
1253  }
1254  }
1255  }
1256 
1257  if(object->instance_id == LWM2M_OBJECT_INSTANCE_NONE) {
1258  /* No instance id has been assigned yet */
1259  if(found == 0) {
1260  /* First object with this id */
1261  object->instance_id = 0;
1262  } else if(min_id > 0) {
1263  object->instance_id = min_id - 1;
1264  } else {
1265  object->instance_id = max_id + 1;
1266  }
1267  }
1268  list_add(object_list, object);
1269 #if USE_RD_CLIENT
1270  lwm2m_rd_client_set_update_rd();
1271 #endif
1272  return 1;
1273 }
1274 /*---------------------------------------------------------------------------*/
1275 void
1276 lwm2m_engine_remove_object(lwm2m_object_instance_t *object)
1277 {
1278  list_remove(object_list, object);
1279 #if USE_RD_CLIENT
1280  lwm2m_rd_client_set_update_rd();
1281 #endif
1282 }
1283 /*---------------------------------------------------------------------------*/
1284 int
1285 lwm2m_engine_add_generic_object(lwm2m_object_t *object)
1286 {
1287  if(object == NULL || object->impl == NULL
1288  || object->impl->get_first == NULL
1289  || object->impl->get_next == NULL
1290  || object->impl->get_by_id == NULL) {
1291  LOG_WARN("failed to register NULL object\n");
1292  return 0;
1293  }
1294  if(get_object(object->impl->object_id) != NULL) {
1295  /* A generic object with this id has already been registered */
1296  LOG_WARN("object with id %u already registered\n",
1297  object->impl->object_id);
1298  return 0;
1299  }
1300  if(has_non_generic_object(object->impl->object_id)) {
1301  /* An object with this id has already been registered */
1302  LOG_WARN("object with id %u already registered\n",
1303  object->impl->object_id);
1304  return 0;
1305  }
1306  list_add(generic_object_list, object);
1307 
1308 #if USE_RD_CLIENT
1309  lwm2m_rd_client_set_update_rd();
1310 #endif
1311 
1312  return 1;
1313 }
1314 /*---------------------------------------------------------------------------*/
1315 void
1316 lwm2m_engine_remove_generic_object(lwm2m_object_t *object)
1317 {
1318  list_remove(generic_object_list, object);
1319 #if USE_RD_CLIENT
1320  lwm2m_rd_client_set_update_rd();
1321 #endif
1322 }
1323 /*---------------------------------------------------------------------------*/
1324 static lwm2m_object_instance_t *
1325 next_object_instance(const lwm2m_context_t *context, lwm2m_object_t *object,
1326  lwm2m_object_instance_t *last)
1327 {
1328  if(context != NULL && context->level >= 2) {
1329  /* Only single instance */
1330  return NULL;
1331  }
1332 
1333  /* There has to be a last to get a next */
1334  if(last == NULL) {
1335  return NULL;
1336  }
1337 
1338  if(object == NULL) {
1339  for(last = last->next; last != NULL; last = last->next) {
1340  /* if no context is given - this will just give the next object */
1341  if(context == NULL || last->object_id == context->object_id) {
1342  return last;
1343  }
1344  }
1345  return NULL;
1346  }
1347  return object->impl->get_next(last, NULL);
1348 }
1349 /*---------------------------------------------------------------------------*/
1350 static coap_handler_status_t
1351 lwm2m_handler_callback(coap_message_t *request, coap_message_t *response,
1352  uint8_t *buffer, uint16_t buffer_size, int32_t *offset)
1353 {
1354  const char *url;
1355  int url_len;
1356  unsigned int format;
1357  unsigned int accept;
1358  int depth;
1359  lwm2m_context_t context;
1360  lwm2m_object_t *object;
1361  lwm2m_object_instance_t *instance;
1362  uint32_t bnum;
1363  uint8_t bmore;
1364  uint16_t bsize;
1365  uint32_t boffset;
1366  lwm2m_status_t success;
1367  lwm2m_buffer_t inbuf;
1368  lwm2m_buffer_t outbuf;
1369 
1370  /* Initialize the context */
1371  memset(&context, 0, sizeof(context));
1372  memset(&outbuf, 0, sizeof(outbuf));
1373  memset(&inbuf, 0, sizeof(inbuf));
1374 
1375  context.outbuf = &outbuf;
1376  context.inbuf = &inbuf;
1377 
1378  /* Set CoAP request/response for now */
1379  context.request = request;
1380  context.response = response;
1381 
1382  /* Set out buffer */
1383  context.outbuf->buffer = buffer;
1384  context.outbuf->size = buffer_size;
1385 
1386  /* Set input buffer */
1387  if(offset != NULL) {
1388  context.offset = *offset;
1389  }
1390  context.inbuf->size = coap_get_payload(request, (const uint8_t **)&context.inbuf->buffer);
1391  context.inbuf->pos = 0;
1392 
1393 #if LWM2M_QUEUE_MODE_ENABLED
1394 lwm2m_queue_mode_request_received();
1395 #endif /* LWM2M_QUEUE_MODE_ENABLED */
1396 
1397  /* Maybe this should be part of CoAP itself - this seems not to be working
1398  with the leshan server */
1399 #define LWM2M_CONF_ENTITY_TOO_LARGE_BLOCK1 0
1400 #if LWM2M_CONF_ENTITY_TOO_LARGE_BLOCK1
1401  if(coap_is_option(request, COAP_OPTION_BLOCK1)) {
1402  uint16_t bsize;
1403  coap_get_header_block1(request, NULL, NULL, &bsize, NULL);
1404 
1405  LOG_DBG("Block1 size:%d\n", bsize);
1406  if(bsize > COAP_MAX_BLOCK_SIZE) {
1407  LOG_WARN("Entity too large: %u...\n", bsize);
1408  coap_set_status_code(response, REQUEST_ENTITY_TOO_LARGE_4_13);
1409  coap_set_header_size1(response, COAP_MAX_BLOCK_SIZE);
1410  return COAP_HANDLER_STATUS_PROCESSED;
1411  }
1412  }
1413 #endif
1414 
1415  /* Set default reader/writer */
1416  context.reader = &lwm2m_plain_text_reader;
1417  context.writer = &lwm2m_tlv_writer;
1418 
1419 
1420  url_len = coap_get_header_uri_path(request, &url);
1421 
1422  if(url_len == 2 && strncmp("bs", url, 2) == 0) {
1423  LOG_INFO("BOOTSTRAPPED!!!\n");
1424  coap_set_status_code(response, CHANGED_2_04);
1425  return COAP_HANDLER_STATUS_PROCESSED;
1426  }
1427 
1428  depth = lwm2m_engine_parse_context(url, url_len, request, response,
1429  buffer, buffer_size, &context);
1430  if(depth < 0) {
1431  /* Not a LWM2M context */
1432  return COAP_HANDLER_STATUS_CONTINUE;
1433  }
1434 
1435  LOG_DBG("%s URL:'", get_method_as_string(coap_get_method_type(request)));
1436  LOG_DBG_COAP_STRING(url, url_len);
1437  LOG_DBG_("' CTX:%u/%u/%u dp:%u bs:%d\n", context.object_id, context.object_instance_id,
1438  context.resource_id, depth, buffer_size);
1439  /* Get format and accept */
1440  if(!coap_get_header_content_format(request, &format)) {
1441  LOG_DBG("No format given. Assume text plain...\n");
1442  format = TEXT_PLAIN;
1443  } else if(format == LWM2M_TEXT_PLAIN) {
1444  /* CoAP content format text plain - assume LWM2M text plain */
1445  format = TEXT_PLAIN;
1446  }
1447  if(!coap_get_header_accept(request, &accept)) {
1448  if(format == TEXT_PLAIN && depth < 3) {
1449  LOG_DBG("No Accept header, assume JSON\n");
1450  accept = LWM2M_JSON;
1451  } else {
1452  LOG_DBG("No Accept header, using same as content-format: %d\n", format);
1453  accept = format;
1454  }
1455  }
1456 
1457  /*
1458  * 1 => Object only
1459  * 2 => Object and Instance
1460  * 3 => Object and Instance and Resource
1461  */
1462  if(depth < 1) {
1463  /* No possible object id found in URL - ignore request unless delete all */
1464  if(coap_get_method_type(request) == METHOD_DELETE) {
1465  LOG_DBG("This is a delete all - for bootstrap...\n");
1466  context.operation = LWM2M_OP_DELETE;
1467  coap_set_status_code(response, DELETED_2_02);
1468 
1469  /* Delete all dynamic objects that can be deleted */
1470  for(object = list_head(generic_object_list);
1471  object != NULL;
1472  object = object->next) {
1473  if(object->impl != NULL && object->impl->delete_instance != NULL) {
1474  object->impl->delete_instance(LWM2M_OBJECT_INSTANCE_NONE, NULL);
1475  }
1476  }
1477 #if USE_RD_CLIENT
1478  lwm2m_rd_client_set_update_rd();
1479 #endif
1480  return COAP_HANDLER_STATUS_PROCESSED;
1481  }
1482  return COAP_HANDLER_STATUS_CONTINUE;
1483  }
1484 
1485  instance = get_instance_by_context(&context, &object);
1486 
1487  /*
1488  * Check if we found either instance or object. Instance means we found an
1489  * existing instance and generic objects means we might create an instance.
1490  */
1491  if(instance == NULL && object == NULL) {
1492  /* No matching object/instance found - ignore request */
1493  return COAP_HANDLER_STATUS_CONTINUE;
1494  }
1495 
1496  LOG_INFO("Context: %u/%u/%u found: %d\n",
1497  context.object_id, context.object_instance_id,
1498  context.resource_id, depth);
1499 
1500  /*
1501  * Select reader and writer based on provided Content type and
1502  * Accept headers.
1503  */
1504  lwm2m_engine_select_reader(&context, format);
1505  lwm2m_engine_select_writer(&context, accept);
1506 
1507  switch(coap_get_method_type(request)) {
1508  case METHOD_PUT:
1509  /* can also be write atts */
1510  context.operation = LWM2M_OP_WRITE;
1511  coap_set_status_code(response, CHANGED_2_04);
1512  break;
1513  case METHOD_POST:
1514  if(context.level < 2) {
1515  /* write to a instance */
1516  context.operation = LWM2M_OP_WRITE;
1517  coap_set_status_code(response, CHANGED_2_04);
1518  } else if(context.level == 3) {
1519  context.operation = LWM2M_OP_EXECUTE;
1520  coap_set_status_code(response, CHANGED_2_04);
1521  }
1522  break;
1523  case METHOD_GET:
1524  if(accept == APPLICATION_LINK_FORMAT) {
1525  context.operation = LWM2M_OP_DISCOVER;
1526  } else {
1527  context.operation = LWM2M_OP_READ;
1528  }
1529  coap_set_status_code(response, CONTENT_2_05);
1530  break;
1531  case METHOD_DELETE:
1532  context.operation = LWM2M_OP_DELETE;
1533  coap_set_status_code(response, DELETED_2_02);
1534  break;
1535  default:
1536  break;
1537  }
1538 
1539  if(LOG_DBG_ENABLED) {
1540  /* for debugging */
1541  LOG_DBG("[");
1542  LOG_DBG_COAP_STRING(url, url_len);
1543  LOG_DBG_("] %s Format:%d ID:%d bsize:%u offset:%"PRId32"\n",
1544  get_method_as_string(coap_get_method_type(request)),
1545  format, context.object_id, buffer_size,
1546  offset != NULL ? *offset : 0);
1547  if(format == TEXT_PLAIN) {
1548  /* a string */
1549  const uint8_t *data;
1550  int plen = coap_get_payload(request, &data);
1551  if(plen > 0) {
1552  LOG_DBG("Data: '");
1553  LOG_DBG_COAP_STRING((const char *)data, plen);
1554  LOG_DBG_("'\n");
1555  }
1556  }
1557  }
1558 
1559  /* PUT/POST - e.g. write will not send in offset here - Maybe in the future? */
1560  if((offset != NULL && *offset == 0) &&
1561  coap_is_option(request, COAP_OPTION_BLOCK1)) {
1562  coap_get_header_block1(request, &bnum, &bmore, &bsize, &boffset);
1563  context.offset = boffset;
1564  }
1565 
1566  /* This is a discovery operation */
1567  switch(context.operation) {
1568  case LWM2M_OP_DISCOVER:
1569  /* Assume only one disco at a time... */
1570  success = perform_multi_resource_read_op(object, instance, &context);
1571  break;
1572  case LWM2M_OP_READ:
1573  success = perform_multi_resource_read_op(object, instance, &context);
1574  break;
1575  case LWM2M_OP_WRITE:
1576  success = perform_multi_resource_write_op(object, instance, &context, format);
1577  break;
1578  case LWM2M_OP_EXECUTE:
1579  success = call_instance(instance, &context);
1580  break;
1581  case LWM2M_OP_DELETE:
1582  if(object != NULL && object->impl != NULL &&
1583  object->impl->delete_instance != NULL) {
1584  object->impl->delete_instance(context.object_instance_id, &success);
1585 #if USE_RD_CLIENT
1586  lwm2m_rd_client_set_update_rd();
1587 #endif
1588  } else {
1589  success = LWM2M_STATUS_OPERATION_NOT_ALLOWED;
1590  }
1591  break;
1592  default:
1593  success = LWM2M_STATUS_OPERATION_NOT_ALLOWED;
1594  break;
1595  }
1596 
1597  if(success == LWM2M_STATUS_OK) {
1598  /* Handle blockwise 1 */
1599  if(coap_is_option(request, COAP_OPTION_BLOCK1)) {
1600  LOG_DBG("Setting BLOCK 1 num:%"PRIu32" o2:%"PRIu32" o:%"PRId32"\n", bnum, boffset,
1601  (offset != NULL ? *offset : 0));
1602  coap_set_header_block1(response, bnum, 0, bsize);
1603  }
1604 
1605  if(context.outbuf->len > 0) {
1606  LOG_DBG("[");
1607  LOG_DBG_COAP_STRING(url, url_len);
1608  LOG_DBG_("] replying with %u bytes\n", context.outbuf->len);
1609  coap_set_payload(response, context.outbuf->buffer, context.outbuf->len);
1610  coap_set_header_content_format(response, context.content_type);
1611 
1612  if(offset != NULL) {
1613  LOG_DBG("Setting new offset: oo %"PRIu32
1614  ", no: %"PRIu32"\n", *offset, context.offset);
1615  if(context.writer_flags & WRITER_HAS_MORE) {
1616  *offset = context.offset;
1617  } else {
1618  /* this signals to CoAP that there is no more CoAP messages to expect */
1619  *offset = -1;
1620  }
1621  }
1622  } else {
1623  LOG_DBG("[");
1624  LOG_DBG_COAP_STRING(url, url_len);
1625  LOG_DBG_("] no data in reply\n");
1626  }
1627  } else {
1628  switch(success) {
1629  case LWM2M_STATUS_FORBIDDEN:
1630  coap_set_status_code(response, FORBIDDEN_4_03);
1631  break;
1632  case LWM2M_STATUS_NOT_FOUND:
1633  coap_set_status_code(response, NOT_FOUND_4_04);
1634  break;
1635  case LWM2M_STATUS_OPERATION_NOT_ALLOWED:
1636  coap_set_status_code(response, METHOD_NOT_ALLOWED_4_05);
1637  break;
1638  case LWM2M_STATUS_NOT_ACCEPTABLE:
1639  coap_set_status_code(response, NOT_ACCEPTABLE_4_06);
1640  break;
1641  case LWM2M_STATUS_UNSUPPORTED_CONTENT_FORMAT:
1642  coap_set_status_code(response, UNSUPPORTED_MEDIA_TYPE_4_15);
1643  break;
1644  default:
1645  /* Failed to handle the request */
1646  coap_set_status_code(response, INTERNAL_SERVER_ERROR_5_00);
1647  break;
1648  }
1649  LOG_WARN("[");
1650  LOG_WARN_COAP_STRING(url, url_len);
1651  LOG_WARN("] resource failed: %s\n", get_status_as_string(success));
1652  }
1653  return COAP_HANDLER_STATUS_PROCESSED;
1654 }
1655 /*---------------------------------------------------------------------------*/
1656 static void
1657 lwm2m_send_notification(char* path)
1658 {
1659 #if LWM2M_QUEUE_MODE_ENABLED && LWM2M_QUEUE_MODE_INCLUDE_DYNAMIC_ADAPTATION
1660  if(lwm2m_queue_mode_get_dynamic_adaptation_flag()) {
1661  lwm2m_queue_mode_set_handler_from_notification();
1662  }
1663 #endif
1664  coap_notify_observers_sub(NULL, path);
1665 }
1666 /*---------------------------------------------------------------------------*/
1667 void
1668 lwm2m_notify_object_observers(lwm2m_object_instance_t *obj,
1669  uint16_t resource)
1670 {
1671  char path[20]; /* 60000/60000/60000 */
1672  if(obj != NULL) {
1673  snprintf(path, 20, "%d/%d/%d", obj->object_id, obj->instance_id, resource);
1674  }
1675 
1676 #if LWM2M_QUEUE_MODE_ENABLED
1677 
1678  if(coap_has_observers(path)) {
1679  /* Client is sleeping -> add the notification to the list */
1680  if(!lwm2m_rd_client_is_client_awake()) {
1681  lwm2m_notification_queue_add_notification_path(obj->object_id, obj->instance_id, resource);
1682 
1683  /* if it is the first notification -> wake up and send update */
1684  if(!lwm2m_queue_mode_is_waked_up_by_notification()) {
1685  lwm2m_queue_mode_set_waked_up_by_notification();
1686  lwm2m_rd_client_fsm_execute_queue_mode_update();
1687  }
1688  /* Client is awake -> send the notification */
1689  } else {
1690  lwm2m_send_notification(path);
1691  }
1692  }
1693 #else
1694  lwm2m_send_notification(path);
1695 #endif
1696 }
1697 /*---------------------------------------------------------------------------*/
1698 /** @} */
Log support for CoAP
static uip_ipaddr_t ipaddr
Pointer to prefix information option in uip_buf.
Definition: uip-nd6.c:125
Header file for the LWM2M object API
CoAP engine implementation.
Header file for the Contiki OMA LWM2M plain text reader / writer
Header file for the Contiki OMA LWM2M TLV reader
Header file for IPv6-related data structures.
Header file for functions to manage the queue to store notifications when waiting for the respons...
Header file for the Contiki OMA LWM2M device
Header file for the Contiki OMA LWM2M JSON writer
Linked list manipulation routines.
Header file for the Contiki OMA LWM2M Queue Mode object to manage the parameters from the ...
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
void * list_head(list_t list)
Get a pointer to the first element of a list.
Definition: list.c:82
Header file for the Contiki OMA LWM2M Registration and Bootstrap Client.
static uint64_t coap_timer_uptime(void)
Get the time since boot in milliseconds.
Definition: coap-timer.h:83
Header file for the Contiki OMA LWM2M TLV writer
#define ADDR_TENTATIVE
Possible states for the an address (RFC 4862)
Definition: uip-ds6.h:155
Header file for the Contiki OMA LWM2M TLV
void list_add(list_t list, void *item)
Add an item at the end of a list.
Definition: list.c:142
Header file for the Contiki OMA LWM2M Queue Mode implementation to manage the parameters ...
void list_init(list_t list)
Initialize a list.
Definition: list.c:65
#define LIST(name)
Declare a linked list.
Definition: list.h:88
uip_ds6_netif_t uip_ds6_if
The single interface.
Definition: uip-ds6.c:75
Header file for the Contiki OMA LWM2M engine
Default definitions of C compiler quirk work-arounds.
coap_resource_flags_t
Resource flags for allowed methods and special functionalities.
void list_remove(list_t list, void *item)
Remove a specific element from a list.
Definition: list.c:237
Collection of constants specified in the CoAP standard.
static struct sicslowpan_addr_context * context
Addresses contexts for IPHC.
Definition: sicslowpan.c:504