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