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