1 /*
2 * Copyright (c) 2022 Nordic Semiconductor ASA
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define LOG_MODULE_NAME net_lwm2m_senml_cbor
8 #define LOG_LEVEL CONFIG_LWM2M_LOG_LEVEL
9
10 #include <zephyr/logging/log.h>
11 LOG_MODULE_REGISTER(LOG_MODULE_NAME);
12
13 #include <stdio.h>
14 #include <stddef.h>
15 #include <stdint.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <inttypes.h>
19 #include <ctype.h>
20 #include <time.h>
21 #include <zephyr/sys/util.h>
22 #include <zephyr/kernel.h>
23
24 #include <zcbor_common.h>
25 #include <zcbor_decode.h>
26 #include <zcbor_encode.h>
27
28 #include "lwm2m_engine.h"
29 #include "lwm2m_object.h"
30 #include "lwm2m_rw_senml_cbor.h"
31 #include "lwm2m_senml_cbor_decode.h"
32 #include "lwm2m_senml_cbor_encode.h"
33 #include "lwm2m_senml_cbor_types.h"
34 #include "lwm2m_util.h"
35
36 #define SENML_MAX_NAME_SIZE sizeof("/65535/65535/")
37
38 struct cbor_out_fmt_data {
39 /* Data */
40 struct lwm2m_senml input;
41
42 /* Storage for basenames and names ~ sizeof("/65535/65535/") */
43 struct {
44 char names[CONFIG_LWM2M_RW_SENML_CBOR_RECORDS][SENML_MAX_NAME_SIZE];
45 size_t name_sz; /* Name buff size */
46 uint8_t name_cnt;
47 };
48
49 /* Basetime for Cached data timestamp */
50 time_t basetime;
51
52 /* Storage for object links */
53 struct {
54 char objlnk[CONFIG_LWM2M_RW_SENML_CBOR_RECORDS][sizeof("65535:65535")];
55 size_t objlnk_sz; /* Object link buff size */
56 uint8_t objlnk_cnt;
57 };
58 };
59
60 struct cbor_in_fmt_data {
61 /* Decoded data */
62 struct lwm2m_senml dcd; /* Decoded data */
63 struct record *current;
64 char basename[MAX_RESOURCE_LEN + 1]; /* Null terminated basename */
65 };
66
67 /* Statically allocated formatter data is shared between different threads */
68 static union cbor_io_fmt_data{
69 struct cbor_in_fmt_data i;
70 struct cbor_out_fmt_data o;
71 } fdio;
72
73 static int path_to_string(char *buf, size_t buf_size, const struct lwm2m_obj_path *input,
74 int level_max);
75
76 /*
77 * SEND is called from a different context than the rest of the LwM2M functionality
78 */
79 K_MUTEX_DEFINE(fd_mtx);
80
81 #define GET_CBOR_FD_NAME(fd) ((fd)->names[(fd)->name_cnt])
82 /* Get the current record */
83 #define GET_CBOR_FD_REC(fd) \
84 &((fd)->input.lwm2m_senml_record_m[(fd)->input.lwm2m_senml_record_m_count])
85 /* Get a record */
86 #define GET_IN_FD_REC_I(fd, i) &((fd)->dcd.lwm2m_senml_record_m[i])
87 /* Consume the current record */
88 #define CONSUME_CBOR_FD_REC(fd) \
89 &((fd)->input.lwm2m_senml_record_m[(fd)->input.lwm2m_senml_record_m_count++])
90 /* Get CBOR output formatter data */
91 #define LWM2M_OFD_CBOR(octx) ((struct cbor_out_fmt_data *)engine_get_out_user_data(octx))
92
setup_out_fmt_data(struct lwm2m_message * msg)93 static void setup_out_fmt_data(struct lwm2m_message *msg)
94 {
95 k_mutex_lock(&fd_mtx, K_FOREVER);
96
97 struct cbor_out_fmt_data *fd = &fdio.o;
98
99 (void)memset(fd, 0, sizeof(*fd));
100 engine_set_out_user_data(&msg->out, fd);
101 fd->name_sz = SENML_MAX_NAME_SIZE;
102 fd->basetime = 0;
103 fd->objlnk_sz = sizeof("65535:65535");
104 }
105
clear_out_fmt_data(struct lwm2m_message * msg)106 static void clear_out_fmt_data(struct lwm2m_message *msg)
107 {
108 engine_clear_out_user_data(&msg->out);
109
110 k_mutex_unlock(&fd_mtx);
111 }
112
setup_in_fmt_data(struct lwm2m_message * msg)113 static void setup_in_fmt_data(struct lwm2m_message *msg)
114 {
115 k_mutex_lock(&fd_mtx, K_FOREVER);
116
117 struct cbor_in_fmt_data *fd = &fdio.i;
118
119 (void)memset(fd, 0, sizeof(*fd));
120 engine_set_in_user_data(&msg->in, fd);
121 }
122
clear_in_fmt_data(struct lwm2m_message * msg)123 static void clear_in_fmt_data(struct lwm2m_message *msg)
124 {
125 engine_clear_in_user_data(&msg->in);
126
127 k_mutex_unlock(&fd_mtx);
128 }
129
fmt_range_check(struct cbor_out_fmt_data * fd)130 static int fmt_range_check(struct cbor_out_fmt_data *fd)
131 {
132 if (fd->name_cnt >= CONFIG_LWM2M_RW_SENML_CBOR_RECORDS ||
133 fd->objlnk_cnt >= CONFIG_LWM2M_RW_SENML_CBOR_RECORDS ||
134 fd->input.lwm2m_senml_record_m_count >= CONFIG_LWM2M_RW_SENML_CBOR_RECORDS) {
135 LOG_ERR("CONFIG_LWM2M_RW_SENML_CBOR_RECORDS too small");
136 return -ENOMEM;
137 }
138
139 return 0;
140 }
141
put_basename(struct lwm2m_output_context * out,struct lwm2m_obj_path * path)142 static int put_basename(struct lwm2m_output_context *out, struct lwm2m_obj_path *path)
143 {
144 struct cbor_out_fmt_data *fd = LWM2M_OFD_CBOR(out);
145 int len;
146 int ret;
147
148 ret = fmt_range_check(fd);
149 if (ret < 0) {
150 return ret;
151 }
152
153 char *basename = GET_CBOR_FD_NAME(fd);
154
155 len = path_to_string(basename, fd->name_sz, path, LWM2M_PATH_LEVEL_OBJECT_INST);
156
157 if (len < 0) {
158 return len;
159 }
160
161 /* Tell CBOR encoder where to find the name */
162 struct record *record = GET_CBOR_FD_REC(fd);
163
164 record->record_bn.record_bn.value = basename;
165 record->record_bn.record_bn.len = len;
166 record->record_bn_present = 1;
167
168 if ((len < sizeof("/0/0") - 1) || (len >= SENML_MAX_NAME_SIZE)) {
169 __ASSERT_NO_MSG(false);
170 return -EINVAL;
171 }
172
173 fd->name_cnt++;
174
175 return 0;
176 }
177
put_empty_array(struct lwm2m_output_context * out)178 static int put_empty_array(struct lwm2m_output_context *out)
179 {
180 int len = 1;
181
182 memset(CPKT_BUF_W_PTR(out->out_cpkt), 0x80, len); /* 80 # array(0) */
183 out->out_cpkt->offset += len;
184
185 return len;
186 }
187
put_end(struct lwm2m_output_context * out,struct lwm2m_obj_path * path)188 static int put_end(struct lwm2m_output_context *out, struct lwm2m_obj_path *path)
189 {
190 size_t len;
191 struct lwm2m_senml *input = &(LWM2M_OFD_CBOR(out)->input);
192
193 if (!input->lwm2m_senml_record_m_count) {
194 len = put_empty_array(out);
195
196 return len;
197 }
198
199 uint_fast8_t ret =
200 cbor_encode_lwm2m_senml(CPKT_BUF_W_REGION(out->out_cpkt), input, &len);
201
202 if (ret != ZCBOR_SUCCESS) {
203 LOG_ERR("unable to encode senml cbor msg");
204
205 return -E2BIG;
206 }
207
208 out->out_cpkt->offset += len;
209
210 return len;
211 }
212
put_begin_oi(struct lwm2m_output_context * out,struct lwm2m_obj_path * path)213 static int put_begin_oi(struct lwm2m_output_context *out, struct lwm2m_obj_path *path)
214 {
215 int ret;
216 uint8_t tmp = path->level;
217
218 /* In case path level is set to 'none' or 'object' and we have only default oi */
219 path->level = LWM2M_PATH_LEVEL_OBJECT_INST;
220
221 ret = put_basename(out, path);
222 path->level = tmp;
223
224 return ret;
225 }
226
put_begin_r(struct lwm2m_output_context * out,struct lwm2m_obj_path * path)227 static int put_begin_r(struct lwm2m_output_context *out, struct lwm2m_obj_path *path)
228 {
229 struct cbor_out_fmt_data *fd = LWM2M_OFD_CBOR(out);
230 int len;
231 int ret;
232
233 ret = fmt_range_check(fd);
234 if (ret < 0) {
235 return ret;
236 }
237
238 char *name = GET_CBOR_FD_NAME(fd);
239
240 /* Write resource name */
241 len = snprintk(name, sizeof("65535"), "%" PRIu16 "", path->res_id);
242
243 if (len < sizeof("0") - 1) {
244 __ASSERT_NO_MSG(false);
245 return -EINVAL;
246 }
247
248 /* Check if we could use an already existing name
249 * -> latest name slot is used as a scratchpad
250 */
251 for (int idx = 0; idx < fd->name_cnt; idx++) {
252 if (strncmp(name, fd->names[idx], len) == 0) {
253 name = fd->names[idx];
254 break;
255 }
256 }
257
258 /* Tell CBOR encoder where to find the name */
259 struct record *record = GET_CBOR_FD_REC(fd);
260
261 record->record_n.record_n.value = name;
262 record->record_n.record_n.len = len;
263 record->record_n_present = 1;
264
265 /* Makes possible to use same slot for storing r/ri name combination.
266 * No need to increase the name count if an existing name has been used
267 */
268 if (path->level < LWM2M_PATH_LEVEL_RESOURCE_INST && name == GET_CBOR_FD_NAME(fd)) {
269 fd->name_cnt++;
270 }
271
272 return 0;
273 }
274
put_data_timestamp(struct lwm2m_output_context * out,time_t value)275 static int put_data_timestamp(struct lwm2m_output_context *out, time_t value)
276 {
277 struct record *out_record;
278 struct cbor_out_fmt_data *fd = LWM2M_OFD_CBOR(out);
279 int ret;
280
281 ret = fmt_range_check(fd);
282 if (ret < 0) {
283 return ret;
284 }
285
286 /* Tell CBOR encoder where to find the name */
287 out_record = GET_CBOR_FD_REC(fd);
288
289 if (fd->basetime) {
290 out_record->record_t.record_t = value - fd->basetime;
291 out_record->record_t_present = 1;
292 } else {
293 fd->basetime = value;
294 out_record->record_bt.record_bt = value;
295 out_record->record_bt_present = 1;
296 }
297
298 return 0;
299
300 }
301
put_begin_ri(struct lwm2m_output_context * out,struct lwm2m_obj_path * path)302 static int put_begin_ri(struct lwm2m_output_context *out, struct lwm2m_obj_path *path)
303 {
304 struct cbor_out_fmt_data *fd = LWM2M_OFD_CBOR(out);
305 char *name = GET_CBOR_FD_NAME(fd);
306 struct record *record = GET_CBOR_FD_REC(fd);
307 int ret;
308
309 ret = fmt_range_check(fd);
310 if (ret < 0) {
311 return ret;
312 }
313
314 /* Forms name from resource id and resource instance id */
315 int len = snprintk(name, SENML_MAX_NAME_SIZE,
316 "%" PRIu16 "/%" PRIu16 "",
317 path->res_id, path->res_inst_id);
318
319 if (len < sizeof("0/0") - 1) {
320 __ASSERT_NO_MSG(false);
321 return -EINVAL;
322 }
323
324 /* Check if we could use an already existing name
325 * -> latest name slot is used as a scratchpad
326 */
327 for (int idx = 0; idx < fd->name_cnt; idx++) {
328 if (strncmp(name, fd->names[idx], len) == 0) {
329 name = fd->names[idx];
330 break;
331 }
332 }
333
334 /* Tell CBOR encoder where to find the name */
335 record->record_n.record_n.value = name;
336 record->record_n.record_n.len = len;
337 record->record_n_present = 1;
338
339 /* No need to increase the name count if an existing name has been used */
340 if (name == GET_CBOR_FD_NAME(fd)) {
341 fd->name_cnt++;
342 }
343
344 return 0;
345 }
346
put_name_nth_ri(struct lwm2m_output_context * out,struct lwm2m_obj_path * path)347 static int put_name_nth_ri(struct lwm2m_output_context *out, struct lwm2m_obj_path *path)
348 {
349 int ret = 0;
350 struct cbor_out_fmt_data *fd = LWM2M_OFD_CBOR(out);
351 struct record *record = GET_CBOR_FD_REC(fd);
352
353 /* With the first ri the resource name (and ri name) are already in place*/
354 if (path->res_inst_id > 0) {
355 ret = put_begin_ri(out, path);
356 } else if (record && record->record_t_present) {
357 /* Name need to be add for each time serialized record */
358 ret = put_begin_r(out, path);
359 }
360
361 return ret;
362 }
363
put_value(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,int64_t value)364 static int put_value(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, int64_t value)
365 {
366 int ret = put_name_nth_ri(out, path);
367
368 if (ret < 0) {
369 return ret;
370 }
371
372 struct record *record = CONSUME_CBOR_FD_REC(LWM2M_OFD_CBOR(out));
373
374 /* Write the value */
375 record->record_union.record_union_choice = union_vi_c;
376 record->record_union.union_vi = value;
377 record->record_union_present = 1;
378
379 return 0;
380 }
381
put_s8(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,int8_t value)382 static int put_s8(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, int8_t value)
383 {
384 return put_value(out, path, value);
385 }
386
put_s16(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,int16_t value)387 static int put_s16(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, int16_t value)
388 {
389 return put_value(out, path, value);
390 }
391
put_s32(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,int32_t value)392 static int put_s32(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, int32_t value)
393 {
394 return put_value(out, path, value);
395 }
396
put_s64(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,int64_t value)397 static int put_s64(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, int64_t value)
398 {
399 return put_value(out, path, value);
400 }
401
put_time(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,time_t value)402 static int put_time(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, time_t value)
403 {
404 int ret = put_name_nth_ri(out, path);
405
406 if (ret < 0) {
407 return ret;
408 }
409
410 struct record *record = CONSUME_CBOR_FD_REC(LWM2M_OFD_CBOR(out));
411
412 /* Write the value */
413 record->record_union.record_union_choice = union_vi_c;
414 record->record_union.union_vi = (int64_t)value;
415 record->record_union_present = 1;
416
417 return 0;
418 }
419
put_float(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,double * value)420 static int put_float(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, double *value)
421 {
422 int ret = put_name_nth_ri(out, path);
423
424 if (ret < 0) {
425 return ret;
426 }
427
428 struct record *record = CONSUME_CBOR_FD_REC(LWM2M_OFD_CBOR(out));
429
430 /* Write the value */
431 record->record_union.record_union_choice = union_vf_c;
432 record->record_union.union_vf = *value;
433 record->record_union_present = 1;
434
435 return 0;
436 }
437
put_string(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,char * buf,size_t buflen)438 static int put_string(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, char *buf,
439 size_t buflen)
440 {
441 int ret = put_name_nth_ri(out, path);
442
443 if (ret < 0) {
444 return ret;
445 }
446
447 struct record *record = CONSUME_CBOR_FD_REC(LWM2M_OFD_CBOR(out));
448
449 /* Write the value */
450 record->record_union.record_union_choice = union_vs_c;
451 record->record_union.union_vs.value = buf;
452 record->record_union.union_vs.len = buflen;
453 record->record_union_present = 1;
454
455 return 0;
456 }
457
put_bool(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,bool value)458 static int put_bool(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, bool value)
459 {
460 int ret = put_name_nth_ri(out, path);
461
462 if (ret < 0) {
463 return ret;
464 }
465
466 struct record *record = CONSUME_CBOR_FD_REC(LWM2M_OFD_CBOR(out));
467
468 /* Write the value */
469 record->record_union.record_union_choice = union_vb_c;
470 record->record_union.union_vb = value;
471 record->record_union_present = 1;
472
473 return 0;
474 }
475
put_opaque(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,char * buf,size_t buflen)476 static int put_opaque(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, char *buf,
477 size_t buflen)
478 {
479 int ret = put_name_nth_ri(out, path);
480
481 if (ret < 0) {
482 return ret;
483 }
484
485 struct record *record = CONSUME_CBOR_FD_REC(LWM2M_OFD_CBOR(out));
486
487 /* Write the value */
488 record->record_union.record_union_choice = union_vd_c;
489 record->record_union.union_vd.value = buf;
490 record->record_union.union_vd.len = buflen;
491 record->record_union_present = 1;
492
493 return 0;
494 }
495
put_objlnk(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,struct lwm2m_objlnk * value)496 static int put_objlnk(struct lwm2m_output_context *out, struct lwm2m_obj_path *path,
497 struct lwm2m_objlnk *value)
498 {
499 int ret = 0;
500 struct cbor_out_fmt_data *fd = LWM2M_OFD_CBOR(out);
501
502 ret = fmt_range_check(fd);
503 if (ret < 0) {
504 return ret;
505 }
506
507 /* Format object link */
508 int objlnk_idx = fd->objlnk_cnt;
509 char *objlink_buf = fd->objlnk[objlnk_idx];
510 int objlnk_len =
511 snprintk(objlink_buf, fd->objlnk_sz, "%u:%u", value->obj_id, value->obj_inst);
512 if (objlnk_len < 0) {
513 return -EINVAL;
514 }
515
516 ret = put_name_nth_ri(out, path);
517
518 if (ret < 0) {
519 return ret;
520 }
521
522 struct record *record = CONSUME_CBOR_FD_REC(LWM2M_OFD_CBOR(out));
523
524 /* Write the value */
525 record->record_union.record_union_choice = union_vlo_c;
526 record->record_union.union_vlo.value = objlink_buf;
527 record->record_union.union_vlo.len = objlnk_len;
528 record->record_union_present = 1;
529
530 fd->objlnk_cnt++;
531
532 return 0;
533 }
534
get_opaque(struct lwm2m_input_context * in,uint8_t * value,size_t buflen,struct lwm2m_opaque_context * opaque,bool * last_block)535 static int get_opaque(struct lwm2m_input_context *in,
536 uint8_t *value, size_t buflen,
537 struct lwm2m_opaque_context *opaque,
538 bool *last_block)
539 {
540 struct cbor_in_fmt_data *fd;
541 uint8_t *dest = NULL;
542
543 /* Get the CBOR header only on first read. */
544 if (opaque->offset == 0) {
545
546 fd = engine_get_in_user_data(in);
547 if (!fd || !fd->current) {
548 return -EINVAL;
549 }
550
551 opaque->len = fd->current->record_union.union_vd.len;
552
553 if (buflen < opaque->len) {
554 LOG_DBG("Write opaque failed, no buffer space");
555 return -ENOMEM;
556 }
557
558 dest = memcpy(value, fd->current->record_union.union_vd.value, opaque->len);
559 *last_block = true;
560 } else {
561 LOG_DBG("Blockwise transfer not supported with SenML CBOR");
562 __ASSERT_NO_MSG(false);
563 }
564
565 return dest ? opaque->len : -EINVAL;
566 }
567
get_s32(struct lwm2m_input_context * in,int32_t * value)568 static int get_s32(struct lwm2m_input_context *in, int32_t *value)
569 {
570 struct cbor_in_fmt_data *fd;
571
572 fd = engine_get_in_user_data(in);
573 if (!fd || !fd->current) {
574 return -EINVAL;
575 }
576
577 *value = fd->current->record_union.union_vi;
578 fd->current = NULL;
579
580 return 0;
581 }
582
get_s64(struct lwm2m_input_context * in,int64_t * value)583 static int get_s64(struct lwm2m_input_context *in, int64_t *value)
584 {
585 struct cbor_in_fmt_data *fd;
586
587 fd = engine_get_in_user_data(in);
588 if (!fd || !fd->current) {
589 return -EINVAL;
590 }
591
592 *value = fd->current->record_union.union_vi;
593 fd->current = NULL;
594
595 return 0;
596 }
597
get_time(struct lwm2m_input_context * in,time_t * value)598 static int get_time(struct lwm2m_input_context *in, time_t *value)
599 {
600 int64_t temp64;
601 int ret;
602
603 ret = get_s64(in, &temp64);
604 if (ret == 0) {
605 *value = (time_t)temp64;
606 }
607
608 return ret;
609 }
610
get_float(struct lwm2m_input_context * in,double * value)611 static int get_float(struct lwm2m_input_context *in, double *value)
612 {
613 struct cbor_in_fmt_data *fd;
614
615 fd = engine_get_in_user_data(in);
616 if (!fd || !fd->current) {
617 return -EINVAL;
618 }
619
620 switch (fd->current->record_union.record_union_choice) {
621 case union_vi_c:
622 *value = (double)fd->current->record_union.union_vi;
623 break;
624 case union_vf_c:
625 *value = fd->current->record_union.union_vf;
626 break;
627 default:
628 return -EINVAL;
629 }
630
631 fd->current = NULL;
632
633 return 0;
634 }
635
get_string(struct lwm2m_input_context * in,uint8_t * buf,size_t buflen)636 static int get_string(struct lwm2m_input_context *in, uint8_t *buf, size_t buflen)
637 {
638 struct cbor_in_fmt_data *fd;
639 int len;
640
641 fd = engine_get_in_user_data(in);
642 if (!fd || !fd->current) {
643 return -EINVAL;
644 }
645
646 len = fd->current->record_union.union_vs.len;
647 if (len >= buflen) {
648 return -ENOMEM;
649 }
650 memcpy(buf, fd->current->record_union.union_vs.value, len);
651 buf[len] = '\0';
652
653 fd->current = NULL;
654
655 return 0;
656 }
657
get_objlnk(struct lwm2m_input_context * in,struct lwm2m_objlnk * value)658 static int get_objlnk(struct lwm2m_input_context *in,
659 struct lwm2m_objlnk *value)
660 {
661 char objlnk[sizeof("65535:65535")] = {0};
662 unsigned long id;
663 int ret;
664
665 ret = get_string(in, objlnk, sizeof(objlnk));
666 if (ret < 0) {
667 return ret;
668 }
669
670 value->obj_id = LWM2M_OBJLNK_MAX_ID;
671 value->obj_inst = LWM2M_OBJLNK_MAX_ID;
672
673 char *end;
674 char *idp = objlnk;
675
676 for (int idx = 0; idx < 2; idx++) {
677
678 errno = 0;
679 id = strtoul(idp, &end, 10);
680
681 idp = end + 1;
682
683 if ((id == 0 && errno == ERANGE) || id > 65535) {
684 LOG_WRN("decoded id %lu out of range[0..65535]", id);
685 return -EBADMSG;
686 }
687
688 switch (idx) {
689 case 0:
690 value->obj_id = id;
691 continue;
692 case 1:
693 value->obj_inst = id;
694 continue;
695 }
696 }
697
698 if (value->obj_inst != LWM2M_OBJLNK_MAX_ID && (value->obj_id == LWM2M_OBJLNK_MAX_ID)) {
699 LOG_WRN("decoded obj inst id without obj id");
700 return -EBADMSG;
701 }
702
703 return ret;
704 }
705
get_bool(struct lwm2m_input_context * in,bool * value)706 static int get_bool(struct lwm2m_input_context *in, bool *value)
707 {
708 struct cbor_in_fmt_data *fd;
709
710 fd = engine_get_in_user_data(in);
711 if (!fd || !fd->current) {
712 return -EINVAL;
713 }
714
715 *value = fd->current->record_union.union_vb;
716 fd->current = NULL;
717
718 return 0;
719 }
720
do_write_op_item(struct lwm2m_message * msg,struct record * rec)721 static int do_write_op_item(struct lwm2m_message *msg, struct record *rec)
722 {
723 struct lwm2m_engine_obj_inst *obj_inst = NULL;
724 struct lwm2m_engine_obj_field *obj_field;
725 struct lwm2m_engine_res *res = NULL;
726 struct lwm2m_engine_res_inst *res_inst = NULL;
727 int ret;
728 uint8_t created = 0U;
729 struct cbor_in_fmt_data *fd;
730 /* Composite op - name with basename */
731 char name[SENML_MAX_NAME_SIZE] = { 0 }; /* Null terminated name */
732 int len = 0;
733 /* Compiler requires reserving space for full length basename and name even though those two
734 * combined do not exceed MAX_RESOURCE_LEN
735 */
736 char fqn[MAX_RESOURCE_LEN + SENML_MAX_NAME_SIZE + 1] = {0};
737
738 fd = engine_get_in_user_data(&msg->in);
739 if (!fd) {
740 return -EINVAL;
741 }
742
743 /* If there's no name then the basename forms the path */
744 if (rec->record_n_present) {
745 len = MIN(sizeof(name) - 1, rec->record_n.record_n.len);
746 snprintk(name, len + 1, "%s", rec->record_n.record_n.value);
747 }
748
749 /* Form fully qualified path name */
750 snprintk(fqn, sizeof(fqn), "%s%s", fd->basename, name);
751
752 /* Set path on record basis */
753 ret = lwm2m_string_to_path(fqn, &msg->path, '/');
754 if (ret < 0) {
755 __ASSERT_NO_MSG(false);
756 return ret;
757 }
758
759 fd->current = rec;
760
761 ret = lwm2m_get_or_create_engine_obj(msg, &obj_inst, &created);
762 if (ret < 0) {
763 return ret;
764 }
765
766 ret = lwm2m_engine_validate_write_access(msg, obj_inst, &obj_field);
767 if (ret < 0) {
768 return ret;
769 }
770
771 ret = lwm2m_engine_get_create_res_inst(&msg->path, &res, &res_inst);
772 if (ret < 0) {
773 /* if OPTIONAL and BOOTSTRAP-WRITE or CREATE use ENOTSUP */
774 if ((msg->ctx->bootstrap_mode ||
775 msg->operation == LWM2M_OP_CREATE) &&
776 LWM2M_HAS_PERM(obj_field, BIT(LWM2M_FLAG_OPTIONAL))) {
777 ret = -ENOTSUP;
778 return ret;
779 }
780
781 ret = -ENOENT;
782 return ret;
783 }
784
785 ret = lwm2m_write_handler(obj_inst, res, res_inst, obj_field, msg);
786 if (ret == -EACCES || ret == -ENOENT) {
787 /* if read-only or non-existent data buffer move on */
788 ret = 0;
789 }
790
791 return ret;
792 }
793
794 const struct lwm2m_writer senml_cbor_writer = {
795 .put_end = put_end,
796 .put_begin_oi = put_begin_oi,
797 .put_begin_r = put_begin_r,
798 .put_begin_ri = put_begin_ri,
799 .put_s8 = put_s8,
800 .put_s16 = put_s16,
801 .put_s32 = put_s32,
802 .put_s64 = put_s64,
803 .put_time = put_time,
804 .put_string = put_string,
805 .put_float = put_float,
806 .put_bool = put_bool,
807 .put_opaque = put_opaque,
808 .put_objlnk = put_objlnk,
809 .put_data_timestamp = put_data_timestamp,
810 };
811
812 const struct lwm2m_reader senml_cbor_reader = {
813 .get_s32 = get_s32,
814 .get_s64 = get_s64,
815 .get_time = get_time,
816 .get_string = get_string,
817 .get_float = get_float,
818 .get_bool = get_bool,
819 .get_opaque = get_opaque,
820 .get_objlnk = get_objlnk,
821 };
822
do_read_op_senml_cbor(struct lwm2m_message * msg)823 int do_read_op_senml_cbor(struct lwm2m_message *msg)
824 {
825 int ret;
826
827 setup_out_fmt_data(msg);
828
829 ret = lwm2m_perform_read_op(msg, LWM2M_FORMAT_APP_SENML_CBOR);
830
831 clear_out_fmt_data(msg);
832
833 return ret;
834 }
835
parse_composite_read_paths(struct lwm2m_message * msg,sys_slist_t * lwm2m_path_list,sys_slist_t * lwm2m_path_free_list)836 static uint8_t parse_composite_read_paths(struct lwm2m_message *msg,
837 sys_slist_t *lwm2m_path_list,
838 sys_slist_t *lwm2m_path_free_list)
839 {
840 char basename[MAX_RESOURCE_LEN + 1] = {0}; /* to include terminating null */
841 char name[MAX_RESOURCE_LEN + 1] = {0}; /* to include terminating null */
842 /* Compiler requires reserving space for full length basename and name even though those two
843 * combined do not exceed MAX_RESOURCE_LEN
844 */
845 char fqn[2 * MAX_RESOURCE_LEN + 1] = {0};
846 struct lwm2m_obj_path path;
847 struct cbor_in_fmt_data *fd;
848 uint8_t paths = 0;
849 size_t isize;
850 uint_fast8_t dret;
851 int len;
852 int ret;
853 char *payload;
854 uint16_t in_len;
855
856 setup_in_fmt_data(msg);
857
858 fd = engine_get_in_user_data(&msg->in);
859 payload = (char *)coap_packet_get_payload(msg->in.in_cpkt, &in_len);
860
861 dret = cbor_decode_lwm2m_senml(payload, in_len, &fd->dcd, &isize);
862
863 if (dret != ZCBOR_SUCCESS) {
864 __ASSERT_NO_MSG(false);
865 goto out;
866 }
867
868 msg->in.offset += isize;
869
870 for (int idx = 0; idx < fd->dcd.lwm2m_senml_record_m_count; idx++) {
871
872 /* Where to find the basenames and names */
873 struct record *record = GET_IN_FD_REC_I(fd, idx);
874
875 /* Set null terminated effective basename */
876 if (record->record_bn_present) {
877 len = MIN(sizeof(basename)-1, record->record_bn.record_bn.len);
878 snprintk(basename, len + 1, "%s", record->record_bn.record_bn.value);
879 basename[len] = '\0';
880 }
881
882 /* Best effort with read, skip if no proper name is available */
883 if (!record->record_n_present) {
884 if (strcmp(basename, "") == 0) {
885 continue;
886 }
887 }
888
889 /* Set null terminated name */
890 if (record->record_n_present) {
891 len = MIN(sizeof(name)-1, record->record_n.record_n.len);
892 snprintk(name, len + 1, "%s", record->record_n.record_n.value);
893 name[len] = '\0';
894 }
895
896 /* Form fully qualified path name */
897 snprintk(fqn, sizeof(fqn), "%s%s", basename, name);
898
899 ret = lwm2m_string_to_path(fqn, &path, '/');
900
901 /* invalid path is forgiven with read */
902 if (ret < 0) {
903 continue;
904 }
905
906 ret = lwm2m_engine_add_path_to_list(lwm2m_path_list, lwm2m_path_free_list, &path);
907
908 if (ret < 0) {
909 continue;
910 }
911
912 paths++;
913 }
914
915 out:
916 clear_in_fmt_data(msg);
917
918 return paths;
919 }
920
do_composite_read_op_for_parsed_path_senml_cbor(struct lwm2m_message * msg,sys_slist_t * lwm_path_list)921 int do_composite_read_op_for_parsed_path_senml_cbor(struct lwm2m_message *msg,
922 sys_slist_t *lwm_path_list)
923 {
924 int ret;
925
926 setup_out_fmt_data(msg);
927
928 ret = lwm2m_perform_composite_read_op(msg, LWM2M_FORMAT_APP_SENML_CBOR, lwm_path_list);
929
930 clear_out_fmt_data(msg);
931
932 return ret;
933 }
934
935
do_composite_read_op_senml_cbor(struct lwm2m_message * msg)936 int do_composite_read_op_senml_cbor(struct lwm2m_message *msg)
937 {
938 struct lwm2m_obj_path_list lwm2m_path_list_buf[CONFIG_LWM2M_COMPOSITE_PATH_LIST_SIZE];
939 sys_slist_t lwm_path_list;
940 sys_slist_t lwm_path_free_list;
941 uint8_t len;
942
943 lwm2m_engine_path_list_init(&lwm_path_list,
944 &lwm_path_free_list,
945 lwm2m_path_list_buf,
946 CONFIG_LWM2M_COMPOSITE_PATH_LIST_SIZE);
947
948 /* Parse paths */
949 len = parse_composite_read_paths(msg, &lwm_path_list, &lwm_path_free_list);
950 if (len == 0) {
951 LOG_ERR("No Valid URL at msg");
952 return -ESRCH;
953 }
954
955 lwm2m_engine_clear_duplicate_path(&lwm_path_list, &lwm_path_free_list);
956
957 return do_composite_read_op_for_parsed_list(msg, LWM2M_FORMAT_APP_SENML_CBOR,
958 &lwm_path_list);
959 }
960
do_write_op_senml_cbor(struct lwm2m_message * msg)961 int do_write_op_senml_cbor(struct lwm2m_message *msg)
962 {
963 uint_fast8_t dret;
964 int ret = 0;
965 size_t decoded_sz;
966 struct cbor_in_fmt_data *fd;
967
968 /* With block-wise transfer consecutive blocks will not carry the content header -
969 * go directly to the message processing
970 */
971 if (msg->in.block_ctx != NULL && msg->in.block_ctx->ctx.current > 0) {
972 msg->path.res_id = msg->in.block_ctx->path.res_id;
973 msg->path.level = msg->in.block_ctx->path.level;
974
975 if (msg->path.level == LWM2M_PATH_LEVEL_RESOURCE_INST) {
976 msg->path.res_inst_id = msg->in.block_ctx->path.res_inst_id;
977 }
978
979 return do_write_op_item(msg, NULL);
980 }
981
982 setup_in_fmt_data(msg);
983
984 fd = engine_get_in_user_data(&msg->in);
985
986 dret = cbor_decode_lwm2m_senml(ICTX_BUF_R_PTR(&msg->in), ICTX_BUF_R_LEFT_SZ(&msg->in),
987 &fd->dcd, &decoded_sz);
988
989 if (dret != ZCBOR_SUCCESS) {
990 ret = -EBADMSG;
991 goto error;
992 }
993
994 msg->in.offset += decoded_sz;
995
996 for (int idx = 0; idx < fd->dcd.lwm2m_senml_record_m_count; idx++) {
997
998 struct record *rec = &fd->dcd.lwm2m_senml_record_m[idx];
999
1000 /* Basename applies for current and succeeding records */
1001 if (rec->record_bn_present) {
1002 int len = MIN(sizeof(fd->basename) - 1,
1003 rec->record_bn.record_bn.len);
1004
1005 snprintk(fd->basename, len + 1, "%s", rec->record_bn.record_bn.value);
1006 goto write;
1007 }
1008
1009 /* Keys' lexicographic order differ from the default */
1010 for (int jdx = 0; jdx < rec->record_key_value_pair_m_count; jdx++) {
1011 struct key_value_pair *kvp =
1012 &(rec->record_key_value_pair_m[jdx].record_key_value_pair_m);
1013
1014 if (kvp->key_value_pair_key == lwm2m_senml_cbor_key_bn) {
1015 int len = MIN(sizeof(fd->basename) - 1,
1016 kvp->key_value_pair.value_tstr.len);
1017
1018 snprintk(fd->basename, len + 1, "%s",
1019 kvp->key_value_pair.value_tstr.value);
1020 break;
1021 }
1022 }
1023 write:
1024 ret = do_write_op_item(msg, rec);
1025
1026 /*
1027 * ignore errors for CREATE op
1028 * for OP_CREATE and BOOTSTRAP WRITE: errors on
1029 * optional resources are ignored (ENOTSUP)
1030 */
1031 if (ret < 0 && !((ret == -ENOTSUP) &&
1032 (msg->ctx->bootstrap_mode || msg->operation == LWM2M_OP_CREATE))) {
1033 goto error;
1034 }
1035 }
1036
1037 ret = 0;
1038
1039 error:
1040 clear_in_fmt_data(msg);
1041
1042 return ret;
1043 }
1044
do_composite_observe_parse_path_senml_cbor(struct lwm2m_message * msg,sys_slist_t * lwm2m_path_list,sys_slist_t * lwm2m_path_free_list)1045 int do_composite_observe_parse_path_senml_cbor(struct lwm2m_message *msg,
1046 sys_slist_t *lwm2m_path_list,
1047 sys_slist_t *lwm2m_path_free_list)
1048 {
1049 uint16_t original_offset;
1050 uint8_t len;
1051
1052 original_offset = msg->in.offset;
1053
1054 /* Parse paths */
1055 len = parse_composite_read_paths(msg, lwm2m_path_list, lwm2m_path_free_list);
1056
1057 if (len == 0) {
1058 LOG_ERR("No Valid URL at msg");
1059 return -ESRCH;
1060 }
1061
1062 msg->in.offset = original_offset;
1063 return 0;
1064 }
1065
do_send_op_senml_cbor(struct lwm2m_message * msg,sys_slist_t * lwm2m_path_list)1066 int do_send_op_senml_cbor(struct lwm2m_message *msg, sys_slist_t *lwm2m_path_list)
1067 {
1068 int ret;
1069
1070 setup_out_fmt_data(msg);
1071
1072 ret = lwm2m_perform_composite_read_op(msg, LWM2M_FORMAT_APP_SENML_CBOR, lwm2m_path_list);
1073
1074 clear_out_fmt_data(msg);
1075
1076 return ret;
1077 }
1078
path_to_string(char * buf,size_t buf_size,const struct lwm2m_obj_path * input,int level_max)1079 static int path_to_string(char *buf, size_t buf_size, const struct lwm2m_obj_path *input,
1080 int level_max)
1081 {
1082 size_t fpl = 0; /* Length of the formed path */
1083 int level;
1084 int w;
1085
1086 if (!buf || buf_size < sizeof("/") || !input) {
1087 return -EINVAL;
1088 }
1089
1090 memset(buf, '\0', buf_size);
1091
1092 level = MIN(input->level, level_max);
1093
1094 /* Write path element at a time and leave space for the terminating NULL */
1095 for (int idx = LWM2M_PATH_LEVEL_NONE; idx <= level; idx++) {
1096 switch (idx) {
1097 case LWM2M_PATH_LEVEL_NONE:
1098 w = snprintk(&(buf[fpl]), buf_size - fpl, "/");
1099 break;
1100 case LWM2M_PATH_LEVEL_OBJECT:
1101 w = snprintk(&(buf[fpl]), buf_size - fpl, "%" PRIu16 "/", input->obj_id);
1102 break;
1103 case LWM2M_PATH_LEVEL_OBJECT_INST:
1104 w = snprintk(&(buf[fpl]), buf_size - fpl, "%" PRIu16 "/",
1105 input->obj_inst_id);
1106 break;
1107 case LWM2M_PATH_LEVEL_RESOURCE:
1108 w = snprintk(&(buf[fpl]), buf_size - fpl, "%" PRIu16 "", input->res_id);
1109 break;
1110 case LWM2M_PATH_LEVEL_RESOURCE_INST:
1111 w = snprintk(&(buf[fpl]), buf_size - fpl, "/%" PRIu16 "",
1112 input->res_inst_id);
1113 break;
1114 default:
1115 __ASSERT_NO_MSG(false);
1116 return -EINVAL;
1117 }
1118
1119 if (w < 0 || w >= buf_size - fpl) {
1120 return -ENOBUFS;
1121 }
1122
1123 /* Next path element, overwrites terminating NULL */
1124 fpl += w;
1125 }
1126
1127 return fpl;
1128 }
1129