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_cbor
8 #define LOG_LEVEL CONFIG_LWM2M_LOG_LEVEL
9 
10 #undef _POSIX_C_SOURCE
11 #define _POSIX_C_SOURCE 200809L /* Required for gmtime_r */
12 
13 #include <zephyr/logging/log.h>
14 #include <zephyr/sys/util.h>
15 #include <stdio.h>
16 #include <stdint.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <time.h>
20 
21 #include <zcbor_common.h>
22 #include <zcbor_decode.h>
23 #include <zcbor_encode.h>
24 
25 #include <zephyr/net/lwm2m.h>
26 #include "lwm2m_object.h"
27 #include "lwm2m_rw_cbor.h"
28 #include "lwm2m_engine.h"
29 #include "lwm2m_util.h"
30 
31 LOG_MODULE_REGISTER(LOG_MODULE_NAME);
32 
33 #define CPKT_CBOR_W_SZ(pos, cpkt) ((size_t)(pos) - (size_t)(cpkt)->data - (size_t)(cpkt)->offset)
34 
35 #define ICTX_CBOR_R_SZ(pos, ictx) ((size_t)pos - (size_t)(ictx)->in_cpkt->data - (ictx)->offset)
36 
put_time(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,time_t value)37 static int put_time(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, time_t value)
38 {
39 	/* CBOR time output format is unspecified but SenML CBOR uses string format.
40 	 * Let's stick into the same format with plain CBOR
41 	 */
42 	struct tm dt;
43 	char time_str[sizeof("1970-01-01T00:00:00-00:00")] = { 0 };
44 	int len;
45 	int str_sz;
46 	int tag_sz;
47 	bool ret;
48 
49 	if (gmtime_r(&value, &dt) != &dt) {
50 		LOG_ERR("unable to convert from secs since Epoch to a date/time construct");
51 		return -EINVAL;
52 	}
53 
54 	/* Time construct to a string. Time in UTC, offset to local time not known */
55 	len = snprintk(time_str, sizeof(time_str),
56 			"%04d-%02d-%02dT%02d:%02d:%02d-00:00",
57 			dt.tm_year+1900,
58 			dt.tm_mon+1,
59 			dt.tm_mday,
60 			dt.tm_hour,
61 			dt.tm_min,
62 			dt.tm_sec);
63 
64 	if (len < 0 || len > sizeof(time_str) - 1) {
65 		LOG_ERR("unable to form a date/time string");
66 		return -EINVAL;
67 	}
68 
69 	ZCBOR_STATE_E(states, 0, CPKT_BUF_W_PTR(out->out_cpkt), CPKT_BUF_W_SIZE(out->out_cpkt),  1);
70 
71 	/* Are tags required? V1.1 leaves this unspecified but some servers require tags */
72 	ret = zcbor_tag_put(states, ZCBOR_TAG_TIME_TSTR);
73 
74 	if (!ret) {
75 		LOG_ERR("unable to encode date/time string tag");
76 		return -ENOMEM;
77 	}
78 
79 	tag_sz = CPKT_CBOR_W_SZ(states[0].payload, out->out_cpkt);
80 
81 	out->out_cpkt->offset += tag_sz;
82 
83 	ret = zcbor_tstr_put_term(states, time_str, sizeof(time_str));
84 	if (!ret) {
85 		LOG_ERR("unable to encode date/time string");
86 		return -ENOMEM;
87 	}
88 
89 	str_sz = CPKT_CBOR_W_SZ(states[0].payload, out->out_cpkt);
90 
91 	out->out_cpkt->offset += str_sz;
92 
93 	return tag_sz + str_sz;
94 }
95 
put_s64(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,int64_t value)96 static int put_s64(struct lwm2m_output_context *out,
97 		      struct lwm2m_obj_path *path, int64_t value)
98 {
99 	int payload_len;
100 	bool ret;
101 
102 	ZCBOR_STATE_E(states, 0, CPKT_BUF_W_PTR(out->out_cpkt), CPKT_BUF_W_SIZE(out->out_cpkt),  1);
103 
104 	ret = zcbor_int64_encode(states, &value);
105 
106 	if (ret) {
107 		payload_len = CPKT_CBOR_W_SZ(states[0].payload, out->out_cpkt);
108 		out->out_cpkt->offset += payload_len;
109 	} else {
110 		LOG_ERR("unable to encode a long long integer value");
111 		payload_len = -ENOMEM;
112 	}
113 
114 	return payload_len;
115 }
116 
put_s32(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,int32_t value)117 static int put_s32(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, int32_t value)
118 {
119 	int payload_len;
120 	bool ret;
121 
122 	ZCBOR_STATE_E(states, 0, CPKT_BUF_W_PTR(out->out_cpkt), CPKT_BUF_W_SIZE(out->out_cpkt),  1);
123 
124 	ret = zcbor_int32_encode(states, &value);
125 
126 	if (ret) {
127 		payload_len = CPKT_CBOR_W_SZ(states[0].payload, out->out_cpkt);
128 		out->out_cpkt->offset += payload_len;
129 	} else {
130 		LOG_ERR("unable to encode an integer value");
131 		payload_len = -ENOMEM;
132 	}
133 
134 	return payload_len;
135 }
136 
put_s16(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,int16_t value)137 static int put_s16(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, int16_t value)
138 {
139 	return put_s32(out, path, value);
140 }
141 
put_s8(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,int8_t value)142 static int put_s8(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, int8_t value)
143 {
144 	return put_s32(out, path, value);
145 }
146 
put_float(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,double * value)147 static int put_float(struct lwm2m_output_context *out,
148 		      struct lwm2m_obj_path *path, double *value)
149 {
150 	int payload_len;
151 	bool ret;
152 
153 	ZCBOR_STATE_E(states, 0, CPKT_BUF_W_PTR(out->out_cpkt), CPKT_BUF_W_SIZE(out->out_cpkt),  1);
154 
155 	ret = zcbor_float64_encode(states, value);
156 
157 	if (ret) {
158 		payload_len = CPKT_CBOR_W_SZ(states[0].payload, out->out_cpkt);
159 		out->out_cpkt->offset += payload_len;
160 	} else {
161 		payload_len = -ENOMEM;
162 	}
163 
164 	return payload_len;
165 }
166 
put_string(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,char * buf,size_t buflen)167 static int put_string(struct lwm2m_output_context *out, struct lwm2m_obj_path *path,
168 					  char *buf, size_t buflen)
169 {
170 	int payload_len;
171 	bool ret;
172 
173 	ZCBOR_STATE_E(states, 0, CPKT_BUF_W_PTR(out->out_cpkt), CPKT_BUF_W_SIZE(out->out_cpkt),  1);
174 
175 	ret = zcbor_tstr_encode_ptr(states, buf, buflen);
176 
177 	if (ret) {
178 		payload_len = CPKT_CBOR_W_SZ(states[0].payload, out->out_cpkt);
179 		out->out_cpkt->offset += payload_len;
180 	} else {
181 		payload_len = -ENOMEM;
182 	}
183 
184 	return payload_len;
185 }
186 
put_opaque(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,char * buf,size_t buflen)187 static int put_opaque(struct lwm2m_output_context *out, struct lwm2m_obj_path *path,
188 					  char *buf, size_t buflen)
189 {
190 	int payload_len;
191 	bool ret;
192 
193 	ZCBOR_STATE_E(states, 0, CPKT_BUF_W_PTR(out->out_cpkt), CPKT_BUF_W_SIZE(out->out_cpkt),  1);
194 
195 	ret = zcbor_bstr_encode_ptr(states, buf, buflen);
196 
197 	if (ret) {
198 		payload_len = CPKT_CBOR_W_SZ(states[0].payload, out->out_cpkt);
199 		out->out_cpkt->offset += payload_len;
200 	} else {
201 		payload_len = -ENOMEM;
202 	}
203 
204 	return payload_len;
205 }
206 
put_bool(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,bool value)207 static int put_bool(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, bool value)
208 {
209 	int payload_len;
210 	bool ret;
211 
212 	ZCBOR_STATE_E(states, 0, CPKT_BUF_W_PTR(out->out_cpkt), CPKT_BUF_W_SIZE(out->out_cpkt),  1);
213 
214 	ret = zcbor_bool_encode(states, &value);
215 
216 	if (ret) {
217 		payload_len = CPKT_CBOR_W_SZ(states[0].payload, out->out_cpkt);
218 		out->out_cpkt->offset += payload_len;
219 	} else {
220 		payload_len = -ENOMEM;
221 	}
222 
223 	return payload_len;
224 }
225 
put_objlnk(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,struct lwm2m_objlnk * value)226 static int put_objlnk(struct lwm2m_output_context *out, struct lwm2m_obj_path *path,
227 			 struct lwm2m_objlnk *value)
228 {
229 	char objlnk[sizeof("65535:65535")] = { 0 };
230 
231 	snprintk(objlnk, sizeof(objlnk), "%" PRIu16 ":%" PRIu16 "", value->obj_id, value->obj_inst);
232 
233 	return put_string(out, path, objlnk, strlen(objlnk) + 1);
234 }
235 
get_s64(struct lwm2m_input_context * in,int64_t * value)236 static int get_s64(struct lwm2m_input_context *in, int64_t *value)
237 {
238 	ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in),  1, 0);
239 
240 	states->constant_state->enforce_canonical = false;
241 
242 	if (!zcbor_int64_decode(states, value)) {
243 		LOG_WRN("unable to decode a 64-bit integer value");
244 		return -EBADMSG;
245 	}
246 
247 	int len = ICTX_CBOR_R_SZ(states[0].payload, in);
248 
249 	in->offset += len;
250 
251 	return len;
252 }
253 
get_s32(struct lwm2m_input_context * in,int32_t * value)254 static int get_s32(struct lwm2m_input_context *in, int32_t *value)
255 {
256 	ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in),  1, 0);
257 
258 	states->constant_state->enforce_canonical = false;
259 
260 	if (!zcbor_int32_decode(states, value)) {
261 		LOG_WRN("unable to decode a 32-bit integer value, err: %d",
262 				states->constant_state->error);
263 		return -EBADMSG;
264 	}
265 
266 	int len = ICTX_CBOR_R_SZ(states[0].payload, in);
267 
268 	in->offset += len;
269 
270 	return len;
271 }
272 
get_float(struct lwm2m_input_context * in,double * value)273 static int get_float(struct lwm2m_input_context *in, double *value)
274 {
275 	ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in),  1, 0);
276 
277 	states->constant_state->enforce_canonical = false;
278 
279 	if (!zcbor_float_decode(states, value)) {
280 		LOG_ERR("unable to decode a floating-point value");
281 		return -EBADMSG;
282 	}
283 
284 	int len = ICTX_CBOR_R_SZ(states[0].payload, in);
285 
286 	in->offset += len;
287 
288 	return len;
289 }
290 
get_string(struct lwm2m_input_context * in,uint8_t * value,size_t buflen)291 static int get_string(struct lwm2m_input_context *in, uint8_t *value, size_t buflen)
292 {
293 	struct zcbor_string hndl;
294 	int len;
295 
296 	ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in),  1, 0);
297 
298 	states->constant_state->enforce_canonical = false;
299 
300 	if (!zcbor_tstr_decode(states, &hndl)) {
301 		LOG_WRN("unable to decode a string");
302 		return -EBADMSG;
303 	}
304 
305 	len = hndl.len;
306 	if (len >= buflen) {
307 		return -ENOMEM;
308 	}
309 
310 	memcpy(value, hndl.value, len);
311 	value[len] = '\0';
312 
313 	len = ICTX_CBOR_R_SZ(states[0].payload, in);
314 
315 	in->offset += len;
316 
317 	return len;
318 }
319 
320 /* Gets time decoded as a numeric string.
321  *
322  * return 0 on success, -EBADMSG if decoding fails or -EFAIL if value is invalid
323  */
get_time_string(struct lwm2m_input_context * in,int64_t * value)324 static int get_time_string(struct lwm2m_input_context *in, int64_t *value)
325 {
326 	char time_str[sizeof("4294967295")] = { 0 };
327 	struct zcbor_string hndl = { .value = time_str, .len = sizeof(time_str) - 1 };
328 
329 	ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in),  1, 0);
330 
331 	states->constant_state->enforce_canonical = false;
332 
333 	if (!zcbor_tstr_decode(states, &hndl)) {
334 		return -EBADMSG;
335 	}
336 
337 	/* TODO: decode date/time string */
338 	LOG_DBG("decoding a date/time string not supported");
339 
340 	return -ENOTSUP;
341 }
342 
343 /* Gets time decoded as a numerical.
344  *
345  * return 0 on success, -EBADMSG if decoding fails
346  */
get_time_numerical(struct lwm2m_input_context * in,int64_t * value)347 static int get_time_numerical(struct lwm2m_input_context *in, int64_t *value)
348 {
349 	ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in),  1, 0);
350 
351 	states->constant_state->enforce_canonical = false;
352 
353 	if (!zcbor_int64_decode(states, value)) {
354 		LOG_WRN("unable to decode seconds since Epoch");
355 		return -EBADMSG;
356 	}
357 
358 	return 0;
359 }
360 
get_time(struct lwm2m_input_context * in,time_t * value)361 static int get_time(struct lwm2m_input_context *in, time_t *value)
362 {
363 	uint32_t tag;
364 	int tag_sz = 0;
365 	int data_len;
366 	int ret;
367 	bool success;
368 	int64_t temp64;
369 
370 	ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in),  1, 0);
371 
372 	states->constant_state->enforce_canonical = false;
373 
374 	success = zcbor_tag_decode(states, &tag);
375 
376 	if (success) {
377 		tag_sz = ICTX_CBOR_R_SZ(states[0].payload, in);
378 		in->offset += tag_sz;
379 
380 		switch (tag) {
381 		case ZCBOR_TAG_TIME_NUM:
382 			ret = get_time_numerical(in, &temp64);
383 			if (ret < 0) {
384 				goto error;
385 			}
386 			break;
387 		case ZCBOR_TAG_TIME_TSTR:
388 			ret = get_time_string(in, &temp64);
389 			if (ret < 0) {
390 				goto error;
391 			}
392 			break;
393 		default:
394 			LOG_WRN("expected tagged date/time, got tag %" PRIu32 "", tag);
395 			return -EBADMSG;
396 		}
397 	} else { /* Assumption is that tags are optional */
398 
399 		/* Let's assume numeric string but in case that fails falls go back to numerical */
400 		ret = get_time_string(in, &temp64);
401 		if (ret == -EBADMSG) {
402 			ret = get_time_numerical(in, &temp64);
403 		}
404 
405 		if (ret < 0) {
406 			goto error;
407 		}
408 	}
409 
410 	data_len = ICTX_CBOR_R_SZ(states[0].payload, in);
411 	in->offset += data_len;
412 	*value = (time_t)temp64;
413 
414 	return tag_sz + data_len;
415 
416 error:
417 	return ret;
418 }
419 
get_bool(struct lwm2m_input_context * in,bool * value)420 static int get_bool(struct lwm2m_input_context *in, bool *value)
421 {
422 	ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in),  1, 0);
423 
424 	states->constant_state->enforce_canonical = false;
425 
426 	if (!zcbor_bool_decode(states, value)) {
427 		LOG_WRN("unable to decode a boolean value");
428 		return -EBADMSG;
429 	}
430 
431 	int len = ICTX_CBOR_R_SZ(states[0].payload, in);
432 
433 	in->offset += len;
434 
435 	return len;
436 }
437 
get_opaque(struct lwm2m_input_context * in,uint8_t * value,size_t buflen,struct lwm2m_opaque_context * opaque,bool * last_block)438 static int get_opaque(struct lwm2m_input_context *in, uint8_t *value, size_t buflen,
439 			 struct lwm2m_opaque_context *opaque, bool *last_block)
440 {
441 	struct zcbor_string_fragment hndl = { 0 };
442 	int ret;
443 
444 	ZCBOR_STATE_D(states, 1, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in),  1, 0);
445 
446 	states->constant_state->enforce_canonical = false;
447 
448 	/* Get the CBOR header only on first read. */
449 	if (opaque->offset == 0) {
450 		ret = zcbor_bstr_start_decode_fragment(states, &hndl);
451 
452 		if (!ret) {
453 			LOG_WRN("unable to decode opaque data header");
454 			return -EBADMSG;
455 		}
456 
457 		opaque->len = hndl.total_len;
458 
459 		int len = ICTX_CBOR_R_SZ(states[0].payload, in);
460 
461 		in->offset += len;
462 	}
463 
464 	return lwm2m_engine_get_opaque_more(in, value, buflen, opaque, last_block);
465 }
466 
get_objlnk(struct lwm2m_input_context * in,struct lwm2m_objlnk * value)467 static int get_objlnk(struct lwm2m_input_context *in, struct lwm2m_objlnk *value)
468 {
469 	char objlnk[sizeof("65535:65535")] = { 0 };
470 	char *end;
471 	char *idp = objlnk;
472 
473 	value->obj_id = LWM2M_OBJLNK_MAX_ID;
474 	value->obj_inst = LWM2M_OBJLNK_MAX_ID;
475 
476 	int len = get_string(in, objlnk, sizeof(objlnk));
477 
478 
479 	for (int idx = 0; idx < 2; idx++) {
480 		errno = 0;
481 
482 		unsigned long id = strtoul(idp, &end, 10);
483 
484 		idp = end + 1;
485 
486 		if ((!id && errno == ERANGE) || id > LWM2M_OBJLNK_MAX_ID) {
487 			LOG_WRN("decoded id %lu out of range[0..65535]", id);
488 			return -EBADMSG;
489 		}
490 
491 		switch (idx) {
492 		case 0:
493 			value->obj_id = id;
494 			continue;
495 		case 1:
496 			value->obj_inst = id;
497 			continue;
498 		}
499 	}
500 
501 	if (value->obj_inst != LWM2M_OBJLNK_MAX_ID && (value->obj_inst == LWM2M_OBJLNK_MAX_ID)) {
502 		LOG_WRN("decoded obj inst id without obj id");
503 		return -EBADMSG;
504 	}
505 
506 	return len;
507 }
508 
509 const struct lwm2m_writer cbor_writer = {
510 	.put_s8 = put_s8,
511 	.put_s16 = put_s16,
512 	.put_s32 = put_s32,
513 	.put_s64 = put_s64,
514 	.put_string = put_string,
515 	.put_float = put_float,
516 	.put_time = put_time,
517 	.put_bool = put_bool,
518 	.put_opaque = put_opaque,
519 	.put_objlnk = put_objlnk,
520 };
521 
522 const struct lwm2m_reader cbor_reader = {
523 	.get_s32 = get_s32,
524 	.get_s64 = get_s64,
525 	.get_time = get_time,
526 	.get_string = get_string,
527 	.get_float = get_float,
528 	.get_bool = get_bool,
529 	.get_opaque = get_opaque,
530 	.get_objlnk = get_objlnk,
531 };
532 
do_read_op_cbor(struct lwm2m_message * msg)533 int do_read_op_cbor(struct lwm2m_message *msg)
534 {
535 	/* Can only return single resource */
536 	if (msg->path.level < LWM2M_PATH_LEVEL_RESOURCE) {
537 		return -EPERM;
538 	} else if (msg->path.level > LWM2M_PATH_LEVEL_RESOURCE_INST) {
539 		return -ENOENT;
540 	}
541 
542 	return lwm2m_perform_read_op(msg, LWM2M_FORMAT_APP_CBOR);
543 }
544 
do_write_op_cbor(struct lwm2m_message * msg)545 int do_write_op_cbor(struct lwm2m_message *msg)
546 {
547 	struct lwm2m_engine_obj_inst *obj_inst = NULL;
548 	struct lwm2m_engine_obj_field *obj_field;
549 	struct lwm2m_engine_res *res = NULL;
550 	struct lwm2m_engine_res_inst *res_inst = NULL;
551 	int ret;
552 	uint8_t created = 0U;
553 
554 	ret = lwm2m_get_or_create_engine_obj(msg, &obj_inst, &created);
555 	if (ret < 0) {
556 		return ret;
557 	}
558 
559 	ret = lwm2m_engine_validate_write_access(msg, obj_inst, &obj_field);
560 	if (ret < 0) {
561 		return ret;
562 	}
563 
564 	ret = lwm2m_engine_get_create_res_inst(&msg->path, &res, &res_inst);
565 	if (ret < 0) {
566 		return -ENOENT;
567 	}
568 
569 	if (msg->path.level < LWM2M_PATH_LEVEL_RESOURCE) {
570 		msg->path.level = LWM2M_PATH_LEVEL_RESOURCE;
571 	}
572 
573 	return lwm2m_write_handler(obj_inst, res, res_inst, obj_field, msg);
574 }
575