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