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