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->remaining == 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 = MIN(buflen-1, fd->current->record_union.union_vs.len);
647
648 memcpy(buf, fd->current->record_union.union_vs.value, len);
649 buf[len] = '\0';
650
651 fd->current = NULL;
652
653 return 0;
654 }
655
get_objlnk(struct lwm2m_input_context * in,struct lwm2m_objlnk * value)656 static int get_objlnk(struct lwm2m_input_context *in,
657 struct lwm2m_objlnk *value)
658 {
659 char objlnk[sizeof("65535:65535")] = {0};
660 unsigned long id;
661 int ret;
662
663 ret = get_string(in, objlnk, sizeof(objlnk));
664 if (ret < 0) {
665 return ret;
666 }
667
668 value->obj_id = LWM2M_OBJLNK_MAX_ID;
669 value->obj_inst = LWM2M_OBJLNK_MAX_ID;
670
671 char *end;
672 char *idp = objlnk;
673
674 for (int idx = 0; idx < 2; idx++) {
675
676 errno = 0;
677 id = strtoul(idp, &end, 10);
678
679 idp = end + 1;
680
681 if ((id == 0 && errno == ERANGE) || id > 65535) {
682 LOG_WRN("decoded id %lu out of range[0..65535]", id);
683 return -EBADMSG;
684 }
685
686 switch (idx) {
687 case 0:
688 value->obj_id = id;
689 continue;
690 case 1:
691 value->obj_inst = id;
692 continue;
693 }
694 }
695
696 if (value->obj_inst != LWM2M_OBJLNK_MAX_ID && (value->obj_id == LWM2M_OBJLNK_MAX_ID)) {
697 LOG_WRN("decoded obj inst id without obj id");
698 return -EBADMSG;
699 }
700
701 return ret;
702 }
703
get_bool(struct lwm2m_input_context * in,bool * value)704 static int get_bool(struct lwm2m_input_context *in, bool *value)
705 {
706 struct cbor_in_fmt_data *fd;
707
708 fd = engine_get_in_user_data(in);
709 if (!fd || !fd->current) {
710 return -EINVAL;
711 }
712
713 *value = fd->current->record_union.union_vb;
714 fd->current = NULL;
715
716 return 0;
717 }
718
do_write_op_item(struct lwm2m_message * msg,struct record * rec)719 static int do_write_op_item(struct lwm2m_message *msg, struct record *rec)
720 {
721 struct lwm2m_engine_obj_inst *obj_inst = NULL;
722 struct lwm2m_engine_obj_field *obj_field;
723 struct lwm2m_engine_res *res = NULL;
724 struct lwm2m_engine_res_inst *res_inst = NULL;
725 int ret;
726 uint8_t created = 0U;
727 struct cbor_in_fmt_data *fd;
728 /* Composite op - name with basename */
729 char name[SENML_MAX_NAME_SIZE] = { 0 }; /* Null terminated name */
730 int len = 0;
731 /* Compiler requires reserving space for full length basename and name even though those two
732 * combined do not exceed MAX_RESOURCE_LEN
733 */
734 char fqn[MAX_RESOURCE_LEN + SENML_MAX_NAME_SIZE + 1] = {0};
735
736 fd = engine_get_in_user_data(&msg->in);
737 if (!fd) {
738 return -EINVAL;
739 }
740
741 /* If there's no name then the basename forms the path */
742 if (rec->record_n_present) {
743 len = MIN(sizeof(name) - 1, rec->record_n.record_n.len);
744 snprintk(name, len + 1, "%s", rec->record_n.record_n.value);
745 }
746
747 /* Form fully qualified path name */
748 snprintk(fqn, sizeof(fqn), "%s%s", fd->basename, name);
749
750 /* Set path on record basis */
751 ret = lwm2m_string_to_path(fqn, &msg->path, '/');
752 if (ret < 0) {
753 __ASSERT_NO_MSG(false);
754 return ret;
755 }
756
757 fd->current = rec;
758
759 ret = lwm2m_get_or_create_engine_obj(msg, &obj_inst, &created);
760 if (ret < 0) {
761 return ret;
762 }
763
764 ret = lwm2m_engine_validate_write_access(msg, obj_inst, &obj_field);
765 if (ret < 0) {
766 return ret;
767 }
768
769 ret = lwm2m_engine_get_create_res_inst(&msg->path, &res, &res_inst);
770 if (ret < 0) {
771 /* if OPTIONAL and BOOTSTRAP-WRITE or CREATE use ENOTSUP */
772 if ((msg->ctx->bootstrap_mode ||
773 msg->operation == LWM2M_OP_CREATE) &&
774 LWM2M_HAS_PERM(obj_field, BIT(LWM2M_FLAG_OPTIONAL))) {
775 ret = -ENOTSUP;
776 return ret;
777 }
778
779 ret = -ENOENT;
780 return ret;
781 }
782
783 ret = lwm2m_write_handler(obj_inst, res, res_inst, obj_field, msg);
784 if (ret == -EACCES || ret == -ENOENT) {
785 /* if read-only or non-existent data buffer move on */
786 ret = 0;
787 }
788
789 return ret;
790 }
791
792 const struct lwm2m_writer senml_cbor_writer = {
793 .put_end = put_end,
794 .put_begin_oi = put_begin_oi,
795 .put_begin_r = put_begin_r,
796 .put_begin_ri = put_begin_ri,
797 .put_s8 = put_s8,
798 .put_s16 = put_s16,
799 .put_s32 = put_s32,
800 .put_s64 = put_s64,
801 .put_time = put_time,
802 .put_string = put_string,
803 .put_float = put_float,
804 .put_bool = put_bool,
805 .put_opaque = put_opaque,
806 .put_objlnk = put_objlnk,
807 .put_data_timestamp = put_data_timestamp,
808 };
809
810 const struct lwm2m_reader senml_cbor_reader = {
811 .get_s32 = get_s32,
812 .get_s64 = get_s64,
813 .get_time = get_time,
814 .get_string = get_string,
815 .get_float = get_float,
816 .get_bool = get_bool,
817 .get_opaque = get_opaque,
818 .get_objlnk = get_objlnk,
819 };
820
do_read_op_senml_cbor(struct lwm2m_message * msg)821 int do_read_op_senml_cbor(struct lwm2m_message *msg)
822 {
823 int ret;
824
825 setup_out_fmt_data(msg);
826
827 ret = lwm2m_perform_read_op(msg, LWM2M_FORMAT_APP_SENML_CBOR);
828
829 clear_out_fmt_data(msg);
830
831 return ret;
832 }
833
parse_composite_read_paths(struct lwm2m_message * msg,sys_slist_t * lwm2m_path_list,sys_slist_t * lwm2m_path_free_list)834 static uint8_t parse_composite_read_paths(struct lwm2m_message *msg,
835 sys_slist_t *lwm2m_path_list,
836 sys_slist_t *lwm2m_path_free_list)
837 {
838 char basename[MAX_RESOURCE_LEN + 1] = {0}; /* to include terminating null */
839 char name[MAX_RESOURCE_LEN + 1] = {0}; /* to include terminating null */
840 /* Compiler requires reserving space for full length basename and name even though those two
841 * combined do not exceed MAX_RESOURCE_LEN
842 */
843 char fqn[2 * MAX_RESOURCE_LEN + 1] = {0};
844 struct lwm2m_obj_path path;
845 struct cbor_in_fmt_data *fd;
846 uint8_t paths = 0;
847 size_t isize;
848 uint_fast8_t dret;
849 int len;
850 int ret;
851 char *payload;
852 uint16_t in_len;
853
854 setup_in_fmt_data(msg);
855
856 fd = engine_get_in_user_data(&msg->in);
857 payload = (char *)coap_packet_get_payload(msg->in.in_cpkt, &in_len);
858
859 dret = cbor_decode_lwm2m_senml(payload, in_len, &fd->dcd, &isize);
860
861 if (dret != ZCBOR_SUCCESS) {
862 __ASSERT_NO_MSG(false);
863 goto out;
864 }
865
866 msg->in.offset += isize;
867
868 for (int idx = 0; idx < fd->dcd.lwm2m_senml_record_m_count; idx++) {
869
870 /* Where to find the basenames and names */
871 struct record *record = GET_IN_FD_REC_I(fd, idx);
872
873 /* Set null terminated effective basename */
874 if (record->record_bn_present) {
875 len = MIN(sizeof(basename)-1, record->record_bn.record_bn.len);
876 snprintk(basename, len + 1, "%s", record->record_bn.record_bn.value);
877 basename[len] = '\0';
878 }
879
880 /* Best effort with read, skip if no proper name is available */
881 if (!record->record_n_present) {
882 if (strcmp(basename, "") == 0) {
883 continue;
884 }
885 }
886
887 /* Set null terminated name */
888 if (record->record_n_present) {
889 len = MIN(sizeof(name)-1, record->record_n.record_n.len);
890 snprintk(name, len + 1, "%s", record->record_n.record_n.value);
891 name[len] = '\0';
892 }
893
894 /* Form fully qualified path name */
895 snprintk(fqn, sizeof(fqn), "%s%s", basename, name);
896
897 ret = lwm2m_string_to_path(fqn, &path, '/');
898
899 /* invalid path is forgiven with read */
900 if (ret < 0) {
901 continue;
902 }
903
904 ret = lwm2m_engine_add_path_to_list(lwm2m_path_list, lwm2m_path_free_list, &path);
905
906 if (ret < 0) {
907 continue;
908 }
909
910 paths++;
911 }
912
913 out:
914 clear_in_fmt_data(msg);
915
916 return paths;
917 }
918
do_composite_read_op_for_parsed_path_senml_cbor(struct lwm2m_message * msg,sys_slist_t * lwm_path_list)919 int do_composite_read_op_for_parsed_path_senml_cbor(struct lwm2m_message *msg,
920 sys_slist_t *lwm_path_list)
921 {
922 int ret;
923
924 setup_out_fmt_data(msg);
925
926 ret = lwm2m_perform_composite_read_op(msg, LWM2M_FORMAT_APP_SENML_CBOR, lwm_path_list);
927
928 clear_out_fmt_data(msg);
929
930 return ret;
931 }
932
933
do_composite_read_op_senml_cbor(struct lwm2m_message * msg)934 int do_composite_read_op_senml_cbor(struct lwm2m_message *msg)
935 {
936 struct lwm2m_obj_path_list lwm2m_path_list_buf[CONFIG_LWM2M_COMPOSITE_PATH_LIST_SIZE];
937 sys_slist_t lwm_path_list;
938 sys_slist_t lwm_path_free_list;
939 uint8_t len;
940
941 lwm2m_engine_path_list_init(&lwm_path_list,
942 &lwm_path_free_list,
943 lwm2m_path_list_buf,
944 CONFIG_LWM2M_COMPOSITE_PATH_LIST_SIZE);
945
946 /* Parse paths */
947 len = parse_composite_read_paths(msg, &lwm_path_list, &lwm_path_free_list);
948 if (len == 0) {
949 LOG_ERR("No Valid URL at msg");
950 return -ESRCH;
951 }
952
953 lwm2m_engine_clear_duplicate_path(&lwm_path_list, &lwm_path_free_list);
954
955 return do_composite_read_op_for_parsed_list(msg, LWM2M_FORMAT_APP_SENML_CBOR,
956 &lwm_path_list);
957 }
958
do_write_op_senml_cbor(struct lwm2m_message * msg)959 int do_write_op_senml_cbor(struct lwm2m_message *msg)
960 {
961 uint_fast8_t dret;
962 int ret = 0;
963 size_t decoded_sz;
964 struct cbor_in_fmt_data *fd;
965
966 /* With block-wise transfer consecutive blocks will not carry the content header -
967 * go directly to the message processing
968 */
969 if (msg->in.block_ctx != NULL && msg->in.block_ctx->ctx.current > 0) {
970 msg->path.res_id = msg->in.block_ctx->path.res_id;
971 msg->path.level = msg->in.block_ctx->path.level;
972
973 if (msg->path.level == LWM2M_PATH_LEVEL_RESOURCE_INST) {
974 msg->path.res_inst_id = msg->in.block_ctx->path.res_inst_id;
975 }
976
977 return do_write_op_item(msg, NULL);
978 }
979
980 setup_in_fmt_data(msg);
981
982 fd = engine_get_in_user_data(&msg->in);
983
984 dret = cbor_decode_lwm2m_senml(ICTX_BUF_R_PTR(&msg->in), ICTX_BUF_R_LEFT_SZ(&msg->in),
985 &fd->dcd, &decoded_sz);
986
987 if (dret != ZCBOR_SUCCESS) {
988 ret = -EBADMSG;
989 goto error;
990 }
991
992 msg->in.offset += decoded_sz;
993
994 for (int idx = 0; idx < fd->dcd.lwm2m_senml_record_m_count; idx++) {
995
996 struct record *rec = &fd->dcd.lwm2m_senml_record_m[idx];
997
998 /* Basename applies for current and succeeding records */
999 if (rec->record_bn_present) {
1000 int len = MIN(sizeof(fd->basename) - 1,
1001 rec->record_bn.record_bn.len);
1002
1003 snprintk(fd->basename, len + 1, "%s", rec->record_bn.record_bn.value);
1004 goto write;
1005 }
1006
1007 /* Keys' lexicographic order differ from the default */
1008 for (int jdx = 0; jdx < rec->record_key_value_pair_m_count; jdx++) {
1009 struct key_value_pair *kvp =
1010 &(rec->record_key_value_pair_m[jdx].record_key_value_pair_m);
1011
1012 if (kvp->key_value_pair_key == lwm2m_senml_cbor_key_bn) {
1013 int len = MIN(sizeof(fd->basename) - 1,
1014 kvp->key_value_pair.value_tstr.len);
1015
1016 snprintk(fd->basename, len + 1, "%s",
1017 kvp->key_value_pair.value_tstr.value);
1018 break;
1019 }
1020 }
1021 write:
1022 ret = do_write_op_item(msg, rec);
1023
1024 /*
1025 * ignore errors for CREATE op
1026 * for OP_CREATE and BOOTSTRAP WRITE: errors on
1027 * optional resources are ignored (ENOTSUP)
1028 */
1029 if (ret < 0 && !((ret == -ENOTSUP) &&
1030 (msg->ctx->bootstrap_mode || msg->operation == LWM2M_OP_CREATE))) {
1031 goto error;
1032 }
1033 }
1034
1035 ret = 0;
1036
1037 error:
1038 clear_in_fmt_data(msg);
1039
1040 return ret;
1041 }
1042
do_composite_observe_parse_path_senml_cbor(struct lwm2m_message * msg,sys_slist_t * lwm2m_path_list,sys_slist_t * lwm2m_path_free_list)1043 int do_composite_observe_parse_path_senml_cbor(struct lwm2m_message *msg,
1044 sys_slist_t *lwm2m_path_list,
1045 sys_slist_t *lwm2m_path_free_list)
1046 {
1047 uint16_t original_offset;
1048 uint8_t len;
1049
1050 original_offset = msg->in.offset;
1051
1052 /* Parse paths */
1053 len = parse_composite_read_paths(msg, lwm2m_path_list, lwm2m_path_free_list);
1054
1055 if (len == 0) {
1056 LOG_ERR("No Valid URL at msg");
1057 return -ESRCH;
1058 }
1059
1060 msg->in.offset = original_offset;
1061 return 0;
1062 }
1063
do_send_op_senml_cbor(struct lwm2m_message * msg,sys_slist_t * lwm2m_path_list)1064 int do_send_op_senml_cbor(struct lwm2m_message *msg, sys_slist_t *lwm2m_path_list)
1065 {
1066 int ret;
1067
1068 setup_out_fmt_data(msg);
1069
1070 ret = lwm2m_perform_composite_read_op(msg, LWM2M_FORMAT_APP_SENML_CBOR, lwm2m_path_list);
1071
1072 clear_out_fmt_data(msg);
1073
1074 return ret;
1075 }
1076
path_to_string(char * buf,size_t buf_size,const struct lwm2m_obj_path * input,int level_max)1077 static int path_to_string(char *buf, size_t buf_size, const struct lwm2m_obj_path *input,
1078 int level_max)
1079 {
1080 size_t fpl = 0; /* Length of the formed path */
1081 int level;
1082 int w;
1083
1084 if (!buf || buf_size < sizeof("/") || !input) {
1085 return -EINVAL;
1086 }
1087
1088 memset(buf, '\0', buf_size);
1089
1090 level = MIN(input->level, level_max);
1091
1092 /* Write path element at a time and leave space for the terminating NULL */
1093 for (int idx = LWM2M_PATH_LEVEL_NONE; idx <= level; idx++) {
1094 switch (idx) {
1095 case LWM2M_PATH_LEVEL_NONE:
1096 w = snprintk(&(buf[fpl]), buf_size - fpl, "/");
1097 break;
1098 case LWM2M_PATH_LEVEL_OBJECT:
1099 w = snprintk(&(buf[fpl]), buf_size - fpl, "%" PRIu16 "/", input->obj_id);
1100 break;
1101 case LWM2M_PATH_LEVEL_OBJECT_INST:
1102 w = snprintk(&(buf[fpl]), buf_size - fpl, "%" PRIu16 "/",
1103 input->obj_inst_id);
1104 break;
1105 case LWM2M_PATH_LEVEL_RESOURCE:
1106 w = snprintk(&(buf[fpl]), buf_size - fpl, "%" PRIu16 "", input->res_id);
1107 break;
1108 case LWM2M_PATH_LEVEL_RESOURCE_INST:
1109 w = snprintk(&(buf[fpl]), buf_size - fpl, "/%" PRIu16 "",
1110 input->res_inst_id);
1111 break;
1112 default:
1113 __ASSERT_NO_MSG(false);
1114 return -EINVAL;
1115 }
1116
1117 if (w < 0 || w >= buf_size - fpl) {
1118 return -ENOBUFS;
1119 }
1120
1121 /* Next path element, overwrites terminating NULL */
1122 fpl += w;
1123 }
1124
1125 return fpl;
1126 }
1127