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*/
117static uint8_t d_buf[COAP_MAX_BLOCK_SIZE * 2];
118static lwm2m_buffer_t lwm2m_buf = {
119 .len = 0, .size = COAP_MAX_BLOCK_SIZE * 2, .buffer = d_buf
120};
121static lwm2m_object_instance_t instance_buffer;
122
123/* obj-id / ... */
124static uint16_t lwm2m_buf_lock[4];
125static uint64_t lwm2m_buf_lock_timeout = 0;
126
127static lwm2m_write_opaque_callback current_opaque_callback;
128static int current_opaque_offset = 0;
129
130static 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);
135static lwm2m_object_instance_t *
136next_object_instance(const lwm2m_context_t *context, lwm2m_object_t *object, lwm2m_object_instance_t *last);
137
138static 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
146COAP_HANDLER(lwm2m_handler, lwm2m_handler_callback);
147LIST(object_list);
148LIST(generic_object_list);
149
150/*---------------------------------------------------------------------------*/
151static lwm2m_object_t *
152get_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/*---------------------------------------------------------------------------*/
165static int
166has_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/*---------------------------------------------------------------------------*/
180static lwm2m_object_instance_t *
181get_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/*---------------------------------------------------------------------------*/
215static lwm2m_object_instance_t *
216get_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/*---------------------------------------------------------------------------*/
224static lwm2m_status_t
225call_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 */
247static int
248double_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/*---------------------------------------------------------------------------*/
269static inline const char *
270get_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/*--------------------------------------------------------------------------*/
285static const char *
286get_status_as_string(lwm2m_status_t status)
287{
288 static char buffer[14];
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/*--------------------------------------------------------------------------*/
322static int
323parse_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/*--------------------------------------------------------------------------*/
361static int
362lwm2m_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/*---------------------------------------------------------------------------*/
383void 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/*---------------------------------------------------------------------------*/
394int
395lwm2m_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/*---------------------------------------------------------------------------*/
506void
507lwm2m_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 */
584static unsigned int
585lwm2m_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 */
618static void
619lwm2m_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/*---------------------------------------------------------------------------*/
645static uint32_t last_instance_id = NO_INSTANCE;
646static int last_rsc_pos;
647
648/* Multi read will handle read of JSON / TLV or Discovery (Link Format) */
649static lwm2m_status_t
650perform_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/*---------------------------------------------------------------------------*/
927static lwm2m_object_instance_t *
928create_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
953static lwm2m_object_instance_t *
954get_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/*---------------------------------------------------------------------------*/
981static int
982check_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/*---------------------------------------------------------------------------*/
1019static lwm2m_status_t
1020process_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/*---------------------------------------------------------------------------*/
1043static int last_tlv_id = 0;
1044
1045static lwm2m_status_t
1046perform_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/*---------------------------------------------------------------------------*/
1206lwm2m_object_instance_t *
1207lwm2m_engine_get_instance_buffer(void)
1208{
1209 return &instance_buffer;
1210}
1211/*---------------------------------------------------------------------------*/
1212int
1213lwm2m_engine_has_instance(uint16_t object_id, uint16_t instance_id)
1214{
1215 return get_instance(object_id, instance_id, NULL) != NULL;
1216}
1217/*---------------------------------------------------------------------------*/
1218int
1219lwm2m_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/*---------------------------------------------------------------------------*/
1275void
1276lwm2m_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/*---------------------------------------------------------------------------*/
1284int
1285lwm2m_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/*---------------------------------------------------------------------------*/
1315void
1316lwm2m_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/*---------------------------------------------------------------------------*/
1324static lwm2m_object_instance_t *
1325next_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/*---------------------------------------------------------------------------*/
1350static coap_handler_status_t
1351lwm2m_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
1394lwm2m_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/*---------------------------------------------------------------------------*/
1656static void
1657lwm2m_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/*---------------------------------------------------------------------------*/
1667void
1668lwm2m_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/** @} */
Default definitions of C compiler quirk work-arounds.
Collection of constants specified in the CoAP standard.
CoAP engine implementation.
Log support for CoAP.
static volatile uint64_t count
Num.
Definition: clock.c:50
static uint64_t coap_timer_uptime(void)
Get the time since boot in milliseconds.
Definition: coap-timer.h:83
coap_resource_flags_t
Resource flags for allowed methods and special functionalities.
void list_init(list_t list)
Initialize a list.
Definition: list.c:57
#define LIST(name)
Declare a linked list.
Definition: list.h:89
void list_add(list_t list, void *item)
Add an item at the end of a list.
Definition: list.c:89
void list_remove(list_t list, const void *item)
Remove a specific element from a list.
Definition: list.c:152
void * list_head(const_list_t list)
Get a pointer to the first element of a list.
Definition: list.c:63
static uint8_t accept(uint8_t in)
Definition: mpl.c:1391
static struct sicslowpan_addr_context * context
Addresses contexts for IPHC.
Definition: sicslowpan.c:514
#define ADDR_TENTATIVE
Possible states for the an address (RFC 4862)
Definition: uip-ds6.h:156
uip_ds6_netif_t uip_ds6_if
The single interface.
Definition: uip-ds6.c:75
Linked list manipulation routines.
Header file for the Contiki OMA LWM2M device.
Header file for the Contiki OMA LWM2M engine.
Header file for the Contiki OMA LWM2M JSON writer.
Header file for functions to manage the queue to store notifications when waiting for the response to...
Header file for the LWM2M object API.
Header file for the Contiki OMA LWM2M plain text reader / writer.
Header file for the Contiki OMA LWM2M Queue Mode object to manage the parameters from the server side...
Header file for the Contiki OMA LWM2M Queue Mode implementation to manage the parameters.
Header file for the Contiki OMA LWM2M Registration and Bootstrap Client.
Header file for the Contiki OMA LWM2M TLV reader.
Header file for the Contiki OMA LWM2M TLV writer.
Header file for the Contiki OMA LWM2M TLV.
Header file for IPv6-related data structures.
static uip_ipaddr_t ipaddr
Pointer to prefix information option in uip_buf.
Definition: uip-nd6.c:116