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