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