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