1 /*
2  * Copyright (c) 2021 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <stdint.h>
8 #include <zephyr/logging/log.h>
9 
10 #include "lwm2m_engine.h"
11 #include "lwm2m_rw_link_format.h"
12 #include "lwm2m_util.h"
13 
14 LOG_MODULE_REGISTER(net_lwm2m_link_format, CONFIG_LWM2M_LOG_LEVEL);
15 
16 #define CORELINK_BUF_SIZE 24
17 
18 #if defined(CONFIG_LWM2M_VERSION_1_1)
19 #define ENABLER_VERSION "</>;lwm2m=" LWM2M_PROTOCOL_VERSION_STRING
20 #else
21 #define ENABLER_VERSION "</>;lwm2m=\"" LWM2M_PROTOCOL_VERSION_STRING "\""
22 #endif
23 
24 /*
25  * TODO: to implement a way for clients to specify alternate path
26  * via Kconfig (LwM2M specification 8.2.2 Alternate Path)
27  */
28 
29 /*
30  * In order to inform the server about the configured LwM2M content format, we have
31  * to report 'ct=' with the content type value to the server. The root path '</>'
32  * is required in order to append the content type attribute.
33  */
34 #if defined(CONFIG_LWM2M_RW_SENML_CBOR_SUPPORT)
35 #define REG_PREFACE		"</>;ct=" STRINGIFY(LWM2M_FORMAT_APP_SENML_CBOR)
36 #elif defined(CONFIG_LWM2M_RW_SENML_JSON_SUPPORT)
37 #define REG_PREFACE		"</>;ct=" STRINGIFY(LWM2M_FORMAT_APP_SEML_JSON)
38 #elif defined(CONFIG_LWM2M_RW_JSON_SUPPORT)
39 #define REG_PREFACE		"</>;ct=" STRINGIFY(LWM2M_FORMAT_OMA_JSON)
40 #else
41 #define REG_PREFACE		""
42 #endif
43 
put_begin(struct lwm2m_output_context * out,struct lwm2m_obj_path * path)44 static int put_begin(struct lwm2m_output_context *out,
45 		     struct lwm2m_obj_path *path)
46 {
47 	char init_string[MAX(sizeof(ENABLER_VERSION), sizeof(REG_PREFACE))] = "";
48 	struct link_format_out_formatter_data *fd;
49 	int ret;
50 
51 	ARG_UNUSED(path);
52 
53 	fd = engine_get_out_user_data(out);
54 	if (fd == NULL) {
55 		return -EINVAL;
56 	}
57 
58 	switch (fd->mode) {
59 	case LINK_FORMAT_MODE_DISCOVERY:
60 		/* Nothing to add in device management mode. */
61 		return 0;
62 
63 	case LINK_FORMAT_MODE_BOOTSTRAP_DISCOVERY:
64 		strcpy(init_string, ENABLER_VERSION);
65 		break;
66 
67 	case LINK_FORMAT_MODE_REGISTER:
68 		/* No need to append content type */
69 		if (strlen(REG_PREFACE) == 0) {
70 			return 0;
71 		}
72 
73 		strcpy(init_string, REG_PREFACE);
74 		break;
75 	}
76 
77 	ret = buf_append(CPKT_BUF_WRITE(out->out_cpkt), init_string,
78 			 strlen(init_string));
79 	if (ret < 0) {
80 		return ret;
81 	}
82 
83 	fd->is_first = false;
84 
85 	return strlen(init_string);
86 }
87 
put_corelink_separator(struct lwm2m_output_context * out)88 static int put_corelink_separator(struct lwm2m_output_context *out)
89 {
90 	char comma = ',';
91 	int ret;
92 
93 	ret = buf_append(CPKT_BUF_WRITE(out->out_cpkt), &comma, sizeof(comma));
94 	if (ret < 0) {
95 		return ret;
96 	}
97 
98 	return (int)sizeof(comma);
99 }
100 
put_corelink_version(struct lwm2m_output_context * out,const struct lwm2m_engine_obj * obj,uint8_t * buf,uint16_t buflen)101 static int put_corelink_version(struct lwm2m_output_context *out,
102 				const struct lwm2m_engine_obj *obj,
103 				uint8_t *buf, uint16_t buflen)
104 {
105 	int ret, len;
106 
107 	len = snprintk(buf, buflen, ";ver=%u.%u", obj->version_major,
108 		       obj->version_minor);
109 	if (len < 0 || len >= buflen) {
110 		return -ENOMEM;
111 	}
112 
113 	ret = buf_append(CPKT_BUF_WRITE(out->out_cpkt), buf, len);
114 	if (ret < 0) {
115 		return ret;
116 	}
117 
118 	return len;
119 }
120 
put_corelink_dimension(struct lwm2m_output_context * out,const struct lwm2m_engine_res * res,uint8_t * buf,uint16_t buflen)121 static int put_corelink_dimension(struct lwm2m_output_context *out,
122 				  const struct lwm2m_engine_res *res,
123 				  uint8_t *buf, uint16_t buflen)
124 {
125 	int ret, inst_count = 0, len = 0;
126 
127 	if (res->multi_res_inst) {
128 		for (int i = 0; i < res->res_inst_count; i++) {
129 			if (res->res_instances[i].res_inst_id !=
130 			    RES_INSTANCE_NOT_CREATED) {
131 				inst_count++;
132 			}
133 		}
134 
135 		len = snprintk(buf, buflen, ";dim=%d", inst_count);
136 		if (len < 0 || len >= buflen) {
137 			return -ENOMEM;
138 		}
139 
140 		ret = buf_append(CPKT_BUF_WRITE(out->out_cpkt), buf, len);
141 		if (ret < 0) {
142 			return ret;
143 		}
144 	}
145 
146 	return len;
147 }
148 
put_attribute(struct lwm2m_output_context * out,struct lwm2m_attr * attr,uint8_t * buf,uint16_t buflen)149 static int put_attribute(struct lwm2m_output_context *out,
150 			 struct lwm2m_attr *attr, uint8_t *buf,
151 			 uint16_t buflen)
152 {
153 	int used, ret;
154 	const char *name = lwm2m_engine_get_attr_name(attr);
155 
156 	if (name == NULL) {
157 		/* Invalid attribute, ignore. */
158 		return 0;
159 	}
160 
161 	if (attr->type <= LWM2M_ATTR_PMAX) {
162 		used = snprintk(buf, buflen, ";%s=%d", name, attr->int_val);
163 	} else {
164 		uint8_t float_buf[32];
165 
166 		used = lwm2m_ftoa(&attr->float_val, float_buf,
167 				  sizeof(float_buf), 4);
168 		if (used < 0 || used >= sizeof(float_buf)) {
169 			return -ENOMEM;
170 		}
171 
172 		used = snprintk(buf, buflen, ";%s=%s", name, float_buf);
173 	}
174 
175 	if (used < 0 || used >= buflen) {
176 		return -ENOMEM;
177 	}
178 
179 	ret = buf_append(CPKT_BUF_WRITE(out->out_cpkt), buf, used);
180 	if (ret < 0) {
181 		return ret;
182 	}
183 
184 	return used;
185 }
186 
put_attributes(struct lwm2m_output_context * out,struct lwm2m_attr ** attrs,uint8_t * buf,uint16_t buflen)187 static int put_attributes(struct lwm2m_output_context *out,
188 			  struct lwm2m_attr **attrs, uint8_t *buf,
189 			  uint16_t buflen)
190 {
191 	int ret;
192 	int len = 0;
193 
194 	for (int i = 0; i < NR_LWM2M_ATTR; i++) {
195 		if (attrs[i] == NULL) {
196 			continue;
197 		}
198 
199 		ret = put_attribute(out, attrs[i], buf, buflen);
200 		if (ret < 0) {
201 			return ret;
202 		}
203 
204 		len += ret;
205 	}
206 
207 	return len;
208 }
209 
get_attributes(const void * ref,struct lwm2m_attr ** attrs)210 static void get_attributes(const void *ref, struct lwm2m_attr **attrs)
211 {
212 	struct lwm2m_attr *attr = NULL;
213 
214 	while ((attr = lwm2m_engine_get_next_attr(ref, attr)) != NULL) {
215 		if (attr->type >= NR_LWM2M_ATTR) {
216 			continue;
217 		}
218 
219 		attrs[attr->type] = attr;
220 	}
221 }
222 
put_corelink_attributes(struct lwm2m_output_context * out,const void * ref,uint8_t * buf,uint16_t buflen)223 static int put_corelink_attributes(struct lwm2m_output_context *out,
224 				   const void *ref, uint8_t *buf,
225 				   uint16_t buflen)
226 {
227 	struct lwm2m_attr *attrs[NR_LWM2M_ATTR] = { 0 };
228 
229 	get_attributes(ref, attrs);
230 
231 	return put_attributes(out, attrs, buf, buflen);
232 }
233 
234 /* Resource-level attribute request - should propagate attributes from Object
235  * and Object Instance.
236  */
put_corelink_attributes_resource(struct lwm2m_output_context * out,const struct lwm2m_obj_path * path,uint8_t * buf,uint16_t buflen)237 static int put_corelink_attributes_resource(struct lwm2m_output_context *out,
238 					    const struct lwm2m_obj_path *path,
239 					    uint8_t *buf, uint16_t buflen)
240 {
241 	struct lwm2m_attr *attrs[NR_LWM2M_ATTR] = { 0 };
242 	struct lwm2m_engine_obj *obj = lwm2m_engine_get_obj(path);
243 	struct lwm2m_engine_obj_inst *obj_inst = lwm2m_engine_get_obj_inst(path);
244 	struct lwm2m_engine_res *res = lwm2m_engine_get_res(path);
245 
246 	if (obj == NULL || obj_inst == NULL || res == NULL) {
247 		return -ENOENT;
248 	}
249 
250 	get_attributes(obj, attrs);
251 	get_attributes(obj_inst, attrs);
252 	get_attributes(res, attrs);
253 
254 	return put_attributes(out, attrs, buf, buflen);
255 }
256 
put_corelink_ssid(struct lwm2m_output_context * out,const struct lwm2m_obj_path * path,uint8_t * buf,uint16_t buflen)257 static int put_corelink_ssid(struct lwm2m_output_context *out,
258 			     const struct lwm2m_obj_path *path,
259 			     uint8_t *buf, uint16_t buflen)
260 {
261 	uint16_t server_id = 0;
262 	int ret;
263 	int len;
264 
265 	switch (path->obj_id) {
266 	case LWM2M_OBJECT_SECURITY_ID: {
267 		bool bootstrap_inst;
268 
269 		ret = lwm2m_get_bool(&LWM2M_OBJ(0, path->obj_inst_id, 1), &bootstrap_inst);
270 		if (ret < 0) {
271 			return ret;
272 		}
273 
274 		/* Bootstrap Security object instance does not have associated
275 		 * Server object instance, so do not report ssid for it.
276 		 */
277 		if (bootstrap_inst) {
278 			return 0;
279 		}
280 
281 		if (ret < 0 || ret >= buflen) {
282 			return -ENOMEM;
283 		}
284 
285 		ret = lwm2m_get_u16(&LWM2M_OBJ(0, path->obj_inst_id, 10), &server_id);
286 		if (ret < 0) {
287 			return ret;
288 		}
289 
290 		break;
291 	}
292 
293 	case LWM2M_OBJECT_SERVER_ID:
294 		ret = lwm2m_get_u16(&LWM2M_OBJ(1, path->obj_inst_id, 0), &server_id);
295 		if (ret < 0) {
296 			return ret;
297 		}
298 
299 		break;
300 
301 	default:
302 		LOG_ERR("Invalid object ID for ssid attribute: %d",
303 			path->obj_id);
304 		return -EINVAL;
305 	}
306 
307 	len = snprintk(buf, buflen, ";ssid=%d", server_id);
308 	if (len < 0 || len >= buflen) {
309 		return -ENOMEM;
310 	}
311 
312 	ret = buf_append(CPKT_BUF_WRITE(out->out_cpkt), buf, len);
313 	if (ret < 0) {
314 		return ret;
315 	}
316 
317 	return len;
318 }
319 
put_obj_corelink(struct lwm2m_output_context * out,const struct lwm2m_obj_path * path,struct link_format_out_formatter_data * fd)320 static int put_obj_corelink(struct lwm2m_output_context *out,
321 			    const struct lwm2m_obj_path *path,
322 			    struct link_format_out_formatter_data *fd)
323 {
324 	char obj_buf[CORELINK_BUF_SIZE];
325 	struct lwm2m_engine_obj *obj;
326 	int len = 0;
327 	int ret;
328 
329 	ret = snprintk(obj_buf, sizeof(obj_buf), "</%u>", path->obj_id);
330 	if (ret < 0 || ret >= sizeof(obj_buf)) {
331 		return -ENOMEM;
332 	}
333 
334 	len += ret;
335 
336 	ret = buf_append(CPKT_BUF_WRITE(out->out_cpkt), obj_buf, len);
337 	if (ret < 0) {
338 		return ret;
339 	}
340 
341 	obj = lwm2m_engine_get_obj(path);
342 	if (obj == NULL) {
343 		return -EINVAL;
344 	}
345 
346 	if (lwm2m_engine_shall_report_obj_version(obj)) {
347 		ret = put_corelink_version(out, obj, obj_buf, sizeof(obj_buf));
348 		if (ret < 0) {
349 			return ret;
350 		}
351 
352 		len += ret;
353 	}
354 
355 	if (fd->mode == LINK_FORMAT_MODE_DISCOVERY) {
356 		/* Report object attributes only in device management mode
357 		 * (5.4.2).
358 		 */
359 		ret = put_corelink_attributes(out, obj, obj_buf,
360 					      sizeof(obj_buf));
361 		if (ret < 0) {
362 			return ret;
363 		}
364 
365 		len += ret;
366 	}
367 
368 	return len;
369 }
370 
put_obj_inst_corelink(struct lwm2m_output_context * out,const struct lwm2m_obj_path * path,struct link_format_out_formatter_data * fd)371 static int put_obj_inst_corelink(struct lwm2m_output_context *out,
372 				 const struct lwm2m_obj_path *path,
373 				 struct link_format_out_formatter_data *fd)
374 {
375 	char obj_buf[CORELINK_BUF_SIZE];
376 	int len = 0;
377 	int ret;
378 
379 	ret = snprintk(obj_buf, sizeof(obj_buf), "</%u/%u>",
380 		       path->obj_id, path->obj_inst_id);
381 	if (ret < 0 || ret >= sizeof(obj_buf)) {
382 		return -ENOMEM;
383 	}
384 
385 	len += ret;
386 
387 	ret = buf_append(CPKT_BUF_WRITE(out->out_cpkt), obj_buf, len);
388 	if (ret < 0) {
389 		return ret;
390 	}
391 
392 	if (fd->mode == LINK_FORMAT_MODE_REGISTER) {
393 		return len;
394 	}
395 
396 	/* Bootstrap object instance corelink shall only contain ssid
397 	 * parameter for Security and Server objects (5.2.7.3).
398 	 */
399 	if (fd->mode == LINK_FORMAT_MODE_BOOTSTRAP_DISCOVERY) {
400 		if (path->obj_id == LWM2M_OBJECT_SECURITY_ID ||
401 		    path->obj_id == LWM2M_OBJECT_SERVER_ID) {
402 			ret = put_corelink_ssid(out, path, obj_buf,
403 						sizeof(obj_buf));
404 			if (ret < 0) {
405 				return ret;
406 			}
407 
408 			len += ret;
409 		}
410 
411 		return len;
412 	}
413 
414 	/* Report object instance attributes only when Instance
415 	 * ID was specified (5.4.2).
416 	 */
417 	if (fd->request_level == LWM2M_PATH_LEVEL_OBJECT_INST) {
418 		struct lwm2m_engine_obj_inst *obj_inst =
419 					lwm2m_engine_get_obj_inst(path);
420 
421 		if (obj_inst == NULL) {
422 			return -EINVAL;
423 		}
424 
425 		ret = put_corelink_attributes(out, obj_inst, obj_buf,
426 					      sizeof(obj_buf));
427 		if (ret < 0) {
428 			return ret;
429 		}
430 
431 		len += ret;
432 	}
433 
434 	return len;
435 }
436 
put_res_corelink(struct lwm2m_output_context * out,const struct lwm2m_obj_path * path,struct link_format_out_formatter_data * fd)437 static int put_res_corelink(struct lwm2m_output_context *out,
438 			    const struct lwm2m_obj_path *path,
439 			    struct link_format_out_formatter_data *fd)
440 {
441 	char obj_buf[CORELINK_BUF_SIZE];
442 	int len = 0;
443 	int ret;
444 
445 	if (fd->mode != LINK_FORMAT_MODE_DISCOVERY) {
446 		/* Report resources only in device management discovery. */
447 		return 0;
448 	}
449 
450 	ret = snprintk(obj_buf, sizeof(obj_buf), "</%u/%u/%u>", path->obj_id,
451 		       path->obj_inst_id, path->res_id);
452 	if (ret < 0 || ret >= sizeof(obj_buf)) {
453 		return -ENOMEM;
454 	}
455 
456 	len += ret;
457 
458 	ret = buf_append(CPKT_BUF_WRITE(out->out_cpkt), obj_buf, len);
459 	if (ret < 0) {
460 		return ret;
461 	}
462 
463 	/* Report resource attrs when at least object instance was specified
464 	 * (5.4.2).
465 	 */
466 	if (fd->request_level >= LWM2M_PATH_LEVEL_OBJECT_INST) {
467 		struct lwm2m_engine_res *res = lwm2m_engine_get_res(path);
468 
469 		if (res == NULL) {
470 			return -EINVAL;
471 		}
472 
473 		ret = put_corelink_dimension(out, res, obj_buf,
474 					     sizeof(obj_buf));
475 		if (ret < 0) {
476 			return ret;
477 		}
478 
479 		len += ret;
480 
481 		if (fd->request_level == LWM2M_PATH_LEVEL_RESOURCE) {
482 			ret = put_corelink_attributes_resource(
483 					out, path, obj_buf, sizeof(obj_buf));
484 			if (ret < 0) {
485 				return ret;
486 			}
487 		} else {
488 			ret = put_corelink_attributes(
489 					out, res, obj_buf, sizeof(obj_buf));
490 			if (ret < 0) {
491 				return ret;
492 			}
493 		}
494 
495 		len += ret;
496 	}
497 
498 	return len;
499 }
500 
put_res_inst_corelink(struct lwm2m_output_context * out,const struct lwm2m_obj_path * path,struct link_format_out_formatter_data * fd)501 static int put_res_inst_corelink(struct lwm2m_output_context *out,
502 				 const struct lwm2m_obj_path *path,
503 				 struct link_format_out_formatter_data *fd)
504 {
505 	char obj_buf[CORELINK_BUF_SIZE];
506 	int len = 0;
507 	int ret;
508 
509 	if (fd->mode != LINK_FORMAT_MODE_DISCOVERY) {
510 		/* Report resources instances only in device management
511 		 * discovery.
512 		 */
513 		return 0;
514 	}
515 
516 	ret = snprintk(obj_buf, sizeof(obj_buf), "</%u/%u/%u/%u>", path->obj_id,
517 		       path->obj_inst_id, path->res_id, path->res_inst_id);
518 	if (ret < 0 || ret >= sizeof(obj_buf)) {
519 		return -ENOMEM;
520 	}
521 
522 	len += ret;
523 
524 	ret = buf_append(CPKT_BUF_WRITE(out->out_cpkt), obj_buf, len);
525 	if (ret < 0) {
526 		return ret;
527 	}
528 
529 	/* Report resource instance attrs only when resource was specified. */
530 	if (fd->request_level == LWM2M_PATH_LEVEL_RESOURCE) {
531 		struct lwm2m_engine_res_inst *res_inst =
532 					lwm2m_engine_get_res_inst(path);
533 
534 		if (res_inst == NULL) {
535 			return -EINVAL;
536 		}
537 
538 		ret = put_corelink_attributes(out, res_inst, obj_buf,
539 					      sizeof(obj_buf));
540 		if (ret < 0) {
541 			return ret;
542 		}
543 
544 		len += ret;
545 	}
546 
547 	return len;
548 }
549 
put_corelink(struct lwm2m_output_context * out,const struct lwm2m_obj_path * path)550 static int put_corelink(struct lwm2m_output_context *out,
551 			const struct lwm2m_obj_path *path)
552 {
553 	struct link_format_out_formatter_data *fd;
554 	int len = 0;
555 	int ret;
556 
557 	fd = engine_get_out_user_data(out);
558 	if (fd == NULL) {
559 		return -EINVAL;
560 	}
561 
562 	if (fd->is_first) {
563 		fd->is_first = false;
564 	} else {
565 		ret = put_corelink_separator(out);
566 		if (ret < 0) {
567 			return ret;
568 		}
569 
570 		len += ret;
571 	}
572 
573 	switch (path->level) {
574 	case LWM2M_PATH_LEVEL_OBJECT:
575 		ret =  put_obj_corelink(out, path, fd);
576 		if (ret < 0) {
577 			return ret;
578 		}
579 
580 		len += ret;
581 		break;
582 
583 	case LWM2M_PATH_LEVEL_OBJECT_INST:
584 		ret = put_obj_inst_corelink(out, path, fd);
585 		if (ret < 0) {
586 			return ret;
587 		}
588 
589 		len += ret;
590 		break;
591 
592 	case LWM2M_PATH_LEVEL_RESOURCE:
593 		ret = put_res_corelink(out, path, fd);
594 		if (ret < 0) {
595 			return ret;
596 		}
597 
598 		len += ret;
599 		break;
600 
601 	case LWM2M_PATH_LEVEL_RESOURCE_INST:
602 		if (IS_ENABLED(CONFIG_LWM2M_VERSION_1_1)) {
603 			ret = put_res_inst_corelink(out, path, fd);
604 			if (ret < 0) {
605 				return ret;
606 			}
607 
608 			len += ret;
609 			break;
610 		}
611 
612 		__fallthrough;
613 
614 	default:
615 		LOG_ERR("Invalid corelink path level: %d", path->level);
616 		return -EINVAL;
617 	}
618 
619 	return len;
620 }
621 
622 const struct lwm2m_writer link_format_writer = {
623 	.put_begin = put_begin,
624 	.put_corelink = put_corelink,
625 };
626 
do_discover_op_link_format(struct lwm2m_message * msg,bool is_bootstrap)627 int do_discover_op_link_format(struct lwm2m_message *msg, bool is_bootstrap)
628 {
629 	struct link_format_out_formatter_data fd;
630 	int ret;
631 
632 	fd.is_first = true;
633 	fd.mode = is_bootstrap ? LINK_FORMAT_MODE_BOOTSTRAP_DISCOVERY :
634 				 LINK_FORMAT_MODE_DISCOVERY;
635 	fd.request_level = msg->path.level;
636 
637 	engine_set_out_user_data(&msg->out, &fd);
638 	ret = lwm2m_discover_handler(msg, is_bootstrap);
639 	engine_clear_out_user_data(&msg->out);
640 
641 	return ret;
642 }
643 
do_register_op_link_format(struct lwm2m_message * msg)644 int do_register_op_link_format(struct lwm2m_message *msg)
645 {
646 	struct link_format_out_formatter_data fd;
647 	int ret;
648 
649 	fd.is_first = true;
650 	fd.mode = LINK_FORMAT_MODE_REGISTER;
651 	fd.request_level = LWM2M_PATH_LEVEL_NONE;
652 
653 	engine_set_out_user_data(&msg->out, &fd);
654 	ret = lwm2m_register_payload_handler(msg);
655 	engine_clear_out_user_data(&msg->out);
656 
657 	return ret;
658 }
659