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