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