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