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 	if (!zcbor_int64_decode(states, value)) {
241 		LOG_WRN("unable to decode a 64-bit integer value");
242 		return -EBADMSG;
243 	}
244 
245 	int len = ICTX_CBOR_R_SZ(states[0].payload, in);
246 
247 	in->offset += len;
248 
249 	return len;
250 }
251 
get_s32(struct lwm2m_input_context * in,int32_t * value)252 static int get_s32(struct lwm2m_input_context *in, int32_t *value)
253 {
254 	ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in),  1, 0);
255 
256 	if (!zcbor_int32_decode(states, value)) {
257 		LOG_WRN("unable to decode a 32-bit integer value, err: %d",
258 				states->constant_state->error);
259 		return -EBADMSG;
260 	}
261 
262 	int len = ICTX_CBOR_R_SZ(states[0].payload, in);
263 
264 	in->offset += len;
265 
266 	return len;
267 }
268 
get_float(struct lwm2m_input_context * in,double * value)269 static int get_float(struct lwm2m_input_context *in, double *value)
270 {
271 	ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in),  1, 0);
272 
273 	if (!zcbor_float_decode(states, value)) {
274 		LOG_ERR("unable to decode a floating-point value");
275 		return -EBADMSG;
276 	}
277 
278 	int len = ICTX_CBOR_R_SZ(states[0].payload, in);
279 
280 	in->offset += len;
281 
282 	return len;
283 }
284 
get_string(struct lwm2m_input_context * in,uint8_t * value,size_t buflen)285 static int get_string(struct lwm2m_input_context *in, uint8_t *value, size_t buflen)
286 {
287 	struct zcbor_string hndl;
288 	int len;
289 
290 	ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in),  1, 0);
291 
292 	if (!zcbor_tstr_decode(states, &hndl)) {
293 		LOG_WRN("unable to decode a string");
294 		return -EBADMSG;
295 	}
296 
297 	len = MIN(buflen-1, hndl.len);
298 
299 	memcpy(value, hndl.value, len);
300 
301 	value[len] = '\0';
302 
303 	len = ICTX_CBOR_R_SZ(states[0].payload, in);
304 
305 	in->offset += len;
306 
307 	return len;
308 }
309 
310 /* Gets time decoded as a numeric string.
311  *
312  * return 0 on success, -EBADMSG if decoding fails or -EFAIL if value is invalid
313  */
get_time_string(struct lwm2m_input_context * in,int64_t * value)314 static int get_time_string(struct lwm2m_input_context *in, int64_t *value)
315 {
316 	char time_str[sizeof("4294967295")] = { 0 };
317 	struct zcbor_string hndl = { .value = time_str, .len = sizeof(time_str) - 1 };
318 
319 	ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in),  1, 0);
320 
321 	if (!zcbor_tstr_decode(states, &hndl)) {
322 		return -EBADMSG;
323 	}
324 
325 	/* TODO: decode date/time string */
326 	LOG_DBG("decoding a date/time string not supported");
327 
328 	return -ENOTSUP;
329 }
330 
331 /* Gets time decoded as a numerical.
332  *
333  * return 0 on success, -EBADMSG if decoding fails
334  */
get_time_numerical(struct lwm2m_input_context * in,int64_t * value)335 static int get_time_numerical(struct lwm2m_input_context *in, int64_t *value)
336 {
337 	ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in),  1, 0);
338 
339 	if (!zcbor_int64_decode(states, value)) {
340 		LOG_WRN("unable to decode seconds since Epoch");
341 		return -EBADMSG;
342 	}
343 
344 	return 0;
345 }
346 
get_time(struct lwm2m_input_context * in,time_t * value)347 static int get_time(struct lwm2m_input_context *in, time_t *value)
348 {
349 	uint32_t tag;
350 	int tag_sz = 0;
351 	int data_len;
352 	int ret;
353 	bool success;
354 	int64_t temp64;
355 
356 	ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in),  1, 0);
357 
358 	success = zcbor_tag_decode(states, &tag);
359 
360 	if (success) {
361 		tag_sz = ICTX_CBOR_R_SZ(states[0].payload, in);
362 		in->offset += tag_sz;
363 
364 		switch (tag) {
365 		case ZCBOR_TAG_TIME_NUM:
366 			ret = get_time_numerical(in, &temp64);
367 			if (ret < 0) {
368 				goto error;
369 			}
370 			break;
371 		case ZCBOR_TAG_TIME_TSTR:
372 			ret = get_time_string(in, &temp64);
373 			if (ret < 0) {
374 				goto error;
375 			}
376 			break;
377 		default:
378 			LOG_WRN("expected tagged date/time, got tag %" PRIu32 "", tag);
379 			return -EBADMSG;
380 		}
381 	} else { /* Assumption is that tags are optional */
382 
383 		/* Let's assume numeric string but in case that fails falls go back to numerical */
384 		ret = get_time_string(in, &temp64);
385 		if (ret == -EBADMSG) {
386 			ret = get_time_numerical(in, &temp64);
387 		}
388 
389 		if (ret < 0) {
390 			goto error;
391 		}
392 	}
393 
394 	data_len = ICTX_CBOR_R_SZ(states[0].payload, in);
395 	in->offset += data_len;
396 	*value = (time_t)temp64;
397 
398 	return tag_sz + data_len;
399 
400 error:
401 	return ret;
402 }
403 
get_bool(struct lwm2m_input_context * in,bool * value)404 static int get_bool(struct lwm2m_input_context *in, bool *value)
405 {
406 	ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in),  1, 0);
407 
408 	if (!zcbor_bool_decode(states, value)) {
409 		LOG_WRN("unable to decode a boolean value");
410 		return -EBADMSG;
411 	}
412 
413 	int len = ICTX_CBOR_R_SZ(states[0].payload, in);
414 
415 	in->offset += len;
416 
417 	return len;
418 }
419 
get_opaque(struct lwm2m_input_context * in,uint8_t * value,size_t buflen,struct lwm2m_opaque_context * opaque,bool * last_block)420 static int get_opaque(struct lwm2m_input_context *in, uint8_t *value, size_t buflen,
421 			 struct lwm2m_opaque_context *opaque, bool *last_block)
422 {
423 	struct zcbor_string_fragment hndl = { 0 };
424 	int ret;
425 
426 	ZCBOR_STATE_D(states, 1, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in),  1, 0);
427 
428 	/* Get the CBOR header only on first read. */
429 	if (opaque->remaining == 0) {
430 		ret = zcbor_bstr_start_decode_fragment(states, &hndl);
431 
432 		if (!ret) {
433 			LOG_WRN("unable to decode opaque data header");
434 			return -EBADMSG;
435 		}
436 
437 		opaque->len = hndl.total_len;
438 		opaque->remaining = hndl.total_len;
439 
440 		int len = ICTX_CBOR_R_SZ(states[0].payload, in);
441 
442 		in->offset += len;
443 	}
444 
445 	return lwm2m_engine_get_opaque_more(in, value, buflen, opaque, last_block);
446 }
447 
get_objlnk(struct lwm2m_input_context * in,struct lwm2m_objlnk * value)448 static int get_objlnk(struct lwm2m_input_context *in, struct lwm2m_objlnk *value)
449 {
450 	char objlnk[sizeof("65535:65535")] = { 0 };
451 	char *end;
452 	char *idp = objlnk;
453 
454 	value->obj_id = LWM2M_OBJLNK_MAX_ID;
455 	value->obj_inst = LWM2M_OBJLNK_MAX_ID;
456 
457 	int len = get_string(in, objlnk, sizeof(objlnk));
458 
459 
460 	for (int idx = 0; idx < 2; idx++) {
461 		errno = 0;
462 
463 		unsigned long id = strtoul(idp, &end, 10);
464 
465 		idp = end + 1;
466 
467 		if ((!id && errno == ERANGE) || id > LWM2M_OBJLNK_MAX_ID) {
468 			LOG_WRN("decoded id %lu out of range[0..65535]", id);
469 			return -EBADMSG;
470 		}
471 
472 		switch (idx) {
473 		case 0:
474 			value->obj_id = id;
475 			continue;
476 		case 1:
477 			value->obj_inst = id;
478 			continue;
479 		}
480 	}
481 
482 	if (value->obj_inst != LWM2M_OBJLNK_MAX_ID && (value->obj_inst == LWM2M_OBJLNK_MAX_ID)) {
483 		LOG_WRN("decoded obj inst id without obj id");
484 		return -EBADMSG;
485 	}
486 
487 	return len;
488 }
489 
490 const struct lwm2m_writer cbor_writer = {
491 	.put_s8 = put_s8,
492 	.put_s16 = put_s16,
493 	.put_s32 = put_s32,
494 	.put_s64 = put_s64,
495 	.put_string = put_string,
496 	.put_float = put_float,
497 	.put_time = put_time,
498 	.put_bool = put_bool,
499 	.put_opaque = put_opaque,
500 	.put_objlnk = put_objlnk,
501 };
502 
503 const struct lwm2m_reader cbor_reader = {
504 	.get_s32 = get_s32,
505 	.get_s64 = get_s64,
506 	.get_time = get_time,
507 	.get_string = get_string,
508 	.get_float = get_float,
509 	.get_bool = get_bool,
510 	.get_opaque = get_opaque,
511 	.get_objlnk = get_objlnk,
512 };
513 
do_read_op_cbor(struct lwm2m_message * msg)514 int do_read_op_cbor(struct lwm2m_message *msg)
515 {
516 	/* Can only return single resource */
517 	if (msg->path.level < LWM2M_PATH_LEVEL_RESOURCE) {
518 		return -EPERM;
519 	} else if (msg->path.level > LWM2M_PATH_LEVEL_RESOURCE_INST) {
520 		return -ENOENT;
521 	}
522 
523 	return lwm2m_perform_read_op(msg, LWM2M_FORMAT_APP_CBOR);
524 }
525 
do_write_op_cbor(struct lwm2m_message * msg)526 int do_write_op_cbor(struct lwm2m_message *msg)
527 {
528 	struct lwm2m_engine_obj_inst *obj_inst = NULL;
529 	struct lwm2m_engine_obj_field *obj_field;
530 	struct lwm2m_engine_res *res = NULL;
531 	struct lwm2m_engine_res_inst *res_inst = NULL;
532 	int ret;
533 	uint8_t created = 0U;
534 
535 	ret = lwm2m_get_or_create_engine_obj(msg, &obj_inst, &created);
536 	if (ret < 0) {
537 		return ret;
538 	}
539 
540 	ret = lwm2m_engine_validate_write_access(msg, obj_inst, &obj_field);
541 	if (ret < 0) {
542 		return ret;
543 	}
544 
545 	ret = lwm2m_engine_get_create_res_inst(&msg->path, &res, &res_inst);
546 	if (ret < 0) {
547 		return -ENOENT;
548 	}
549 
550 	if (msg->path.level < LWM2M_PATH_LEVEL_RESOURCE) {
551 		msg->path.level = LWM2M_PATH_LEVEL_RESOURCE;
552 	}
553 
554 	return lwm2m_write_handler(obj_inst, res, res_inst, obj_field, msg);
555 }
556