1 /*
2 * Copyright (c) 2022 Nordic Semiconductor ASA
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define LOG_MODULE_NAME net_lwm2m_cbor
8 #define LOG_LEVEL CONFIG_LWM2M_LOG_LEVEL
9
10 #undef _POSIX_C_SOURCE
11 #define _POSIX_C_SOURCE 200809L /* Required for gmtime_r */
12
13 #include <zephyr/logging/log.h>
14 #include <zephyr/sys/util.h>
15 #include <stdio.h>
16 #include <stdint.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <time.h>
20
21 #include <zcbor_common.h>
22 #include <zcbor_decode.h>
23 #include <zcbor_encode.h>
24
25 #include <zephyr/net/lwm2m.h>
26 #include "lwm2m_object.h"
27 #include "lwm2m_rw_cbor.h"
28 #include "lwm2m_engine.h"
29 #include "lwm2m_util.h"
30
31 LOG_MODULE_REGISTER(LOG_MODULE_NAME);
32
33 #define CPKT_CBOR_W_SZ(pos, cpkt) ((size_t)(pos) - (size_t)(cpkt)->data - (size_t)(cpkt)->offset)
34
35 #define ICTX_CBOR_R_SZ(pos, ictx) ((size_t)pos - (size_t)(ictx)->in_cpkt->data - (ictx)->offset)
36
put_time(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,time_t value)37 static int put_time(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, time_t value)
38 {
39 /* CBOR time output format is unspecified but SenML CBOR uses string format.
40 * Let's stick into the same format with plain CBOR
41 */
42 struct tm dt;
43 char time_str[sizeof("1970-01-01T00:00:00-00:00")] = { 0 };
44 int len;
45 int str_sz;
46 int tag_sz;
47 bool ret;
48
49 if (gmtime_r(&value, &dt) != &dt) {
50 LOG_ERR("unable to convert from secs since Epoch to a date/time construct");
51 return -EINVAL;
52 }
53
54 /* Time construct to a string. Time in UTC, offset to local time not known */
55 len = snprintk(time_str, sizeof(time_str),
56 "%04d-%02d-%02dT%02d:%02d:%02d-00:00",
57 dt.tm_year+1900,
58 dt.tm_mon+1,
59 dt.tm_mday,
60 dt.tm_hour,
61 dt.tm_min,
62 dt.tm_sec);
63
64 if (len < 0 || len > sizeof(time_str) - 1) {
65 LOG_ERR("unable to form a date/time string");
66 return -EINVAL;
67 }
68
69 ZCBOR_STATE_E(states, 0, CPKT_BUF_W_PTR(out->out_cpkt), CPKT_BUF_W_SIZE(out->out_cpkt), 1);
70
71 /* Are tags required? V1.1 leaves this unspecified but some servers require tags */
72 ret = zcbor_tag_put(states, ZCBOR_TAG_TIME_TSTR);
73
74 if (!ret) {
75 LOG_ERR("unable to encode date/time string tag");
76 return -ENOMEM;
77 }
78
79 tag_sz = CPKT_CBOR_W_SZ(states[0].payload, out->out_cpkt);
80
81 out->out_cpkt->offset += tag_sz;
82
83 ret = zcbor_tstr_put_term(states, time_str, sizeof(time_str));
84 if (!ret) {
85 LOG_ERR("unable to encode date/time string");
86 return -ENOMEM;
87 }
88
89 str_sz = CPKT_CBOR_W_SZ(states[0].payload, out->out_cpkt);
90
91 out->out_cpkt->offset += str_sz;
92
93 return tag_sz + str_sz;
94 }
95
put_s64(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,int64_t value)96 static int put_s64(struct lwm2m_output_context *out,
97 struct lwm2m_obj_path *path, int64_t value)
98 {
99 int payload_len;
100 bool ret;
101
102 ZCBOR_STATE_E(states, 0, CPKT_BUF_W_PTR(out->out_cpkt), CPKT_BUF_W_SIZE(out->out_cpkt), 1);
103
104 ret = zcbor_int64_encode(states, &value);
105
106 if (ret) {
107 payload_len = CPKT_CBOR_W_SZ(states[0].payload, out->out_cpkt);
108 out->out_cpkt->offset += payload_len;
109 } else {
110 LOG_ERR("unable to encode a long long integer value");
111 payload_len = -ENOMEM;
112 }
113
114 return payload_len;
115 }
116
put_s32(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,int32_t value)117 static int put_s32(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, int32_t value)
118 {
119 int payload_len;
120 bool ret;
121
122 ZCBOR_STATE_E(states, 0, CPKT_BUF_W_PTR(out->out_cpkt), CPKT_BUF_W_SIZE(out->out_cpkt), 1);
123
124 ret = zcbor_int32_encode(states, &value);
125
126 if (ret) {
127 payload_len = CPKT_CBOR_W_SZ(states[0].payload, out->out_cpkt);
128 out->out_cpkt->offset += payload_len;
129 } else {
130 LOG_ERR("unable to encode an integer value");
131 payload_len = -ENOMEM;
132 }
133
134 return payload_len;
135 }
136
put_s16(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,int16_t value)137 static int put_s16(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, int16_t value)
138 {
139 return put_s32(out, path, value);
140 }
141
put_s8(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,int8_t value)142 static int put_s8(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, int8_t value)
143 {
144 return put_s32(out, path, value);
145 }
146
put_float(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,double * value)147 static int put_float(struct lwm2m_output_context *out,
148 struct lwm2m_obj_path *path, double *value)
149 {
150 int payload_len;
151 bool ret;
152
153 ZCBOR_STATE_E(states, 0, CPKT_BUF_W_PTR(out->out_cpkt), CPKT_BUF_W_SIZE(out->out_cpkt), 1);
154
155 ret = zcbor_float64_encode(states, value);
156
157 if (ret) {
158 payload_len = CPKT_CBOR_W_SZ(states[0].payload, out->out_cpkt);
159 out->out_cpkt->offset += payload_len;
160 } else {
161 payload_len = -ENOMEM;
162 }
163
164 return payload_len;
165 }
166
put_string(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,char * buf,size_t buflen)167 static int put_string(struct lwm2m_output_context *out, struct lwm2m_obj_path *path,
168 char *buf, size_t buflen)
169 {
170 int payload_len;
171 bool ret;
172
173 ZCBOR_STATE_E(states, 0, CPKT_BUF_W_PTR(out->out_cpkt), CPKT_BUF_W_SIZE(out->out_cpkt), 1);
174
175 ret = zcbor_tstr_encode_ptr(states, buf, buflen);
176
177 if (ret) {
178 payload_len = CPKT_CBOR_W_SZ(states[0].payload, out->out_cpkt);
179 out->out_cpkt->offset += payload_len;
180 } else {
181 payload_len = -ENOMEM;
182 }
183
184 return payload_len;
185 }
186
put_opaque(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,char * buf,size_t buflen)187 static int put_opaque(struct lwm2m_output_context *out, struct lwm2m_obj_path *path,
188 char *buf, size_t buflen)
189 {
190 int payload_len;
191 bool ret;
192
193 ZCBOR_STATE_E(states, 0, CPKT_BUF_W_PTR(out->out_cpkt), CPKT_BUF_W_SIZE(out->out_cpkt), 1);
194
195 ret = zcbor_bstr_encode_ptr(states, buf, buflen);
196
197 if (ret) {
198 payload_len = CPKT_CBOR_W_SZ(states[0].payload, out->out_cpkt);
199 out->out_cpkt->offset += payload_len;
200 } else {
201 payload_len = -ENOMEM;
202 }
203
204 return payload_len;
205 }
206
put_bool(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,bool value)207 static int put_bool(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, bool value)
208 {
209 int payload_len;
210 bool ret;
211
212 ZCBOR_STATE_E(states, 0, CPKT_BUF_W_PTR(out->out_cpkt), CPKT_BUF_W_SIZE(out->out_cpkt), 1);
213
214 ret = zcbor_bool_encode(states, &value);
215
216 if (ret) {
217 payload_len = CPKT_CBOR_W_SZ(states[0].payload, out->out_cpkt);
218 out->out_cpkt->offset += payload_len;
219 } else {
220 payload_len = -ENOMEM;
221 }
222
223 return payload_len;
224 }
225
put_objlnk(struct lwm2m_output_context * out,struct lwm2m_obj_path * path,struct lwm2m_objlnk * value)226 static int put_objlnk(struct lwm2m_output_context *out, struct lwm2m_obj_path *path,
227 struct lwm2m_objlnk *value)
228 {
229 char objlnk[sizeof("65535:65535")] = { 0 };
230
231 snprintk(objlnk, sizeof(objlnk), "%" PRIu16 ":%" PRIu16 "", value->obj_id, value->obj_inst);
232
233 return put_string(out, path, objlnk, strlen(objlnk) + 1);
234 }
235
get_s64(struct lwm2m_input_context * in,int64_t * value)236 static int get_s64(struct lwm2m_input_context *in, int64_t *value)
237 {
238 ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in), 1, 0);
239
240 states->constant_state->enforce_canonical = false;
241
242 if (!zcbor_int64_decode(states, value)) {
243 LOG_WRN("unable to decode a 64-bit integer value");
244 return -EBADMSG;
245 }
246
247 int len = ICTX_CBOR_R_SZ(states[0].payload, in);
248
249 in->offset += len;
250
251 return len;
252 }
253
get_s32(struct lwm2m_input_context * in,int32_t * value)254 static int get_s32(struct lwm2m_input_context *in, int32_t *value)
255 {
256 ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in), 1, 0);
257
258 states->constant_state->enforce_canonical = false;
259
260 if (!zcbor_int32_decode(states, value)) {
261 LOG_WRN("unable to decode a 32-bit integer value, err: %d",
262 states->constant_state->error);
263 return -EBADMSG;
264 }
265
266 int len = ICTX_CBOR_R_SZ(states[0].payload, in);
267
268 in->offset += len;
269
270 return len;
271 }
272
get_float(struct lwm2m_input_context * in,double * value)273 static int get_float(struct lwm2m_input_context *in, double *value)
274 {
275 ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in), 1, 0);
276
277 states->constant_state->enforce_canonical = false;
278
279 if (!zcbor_float_decode(states, value)) {
280 LOG_ERR("unable to decode a floating-point value");
281 return -EBADMSG;
282 }
283
284 int len = ICTX_CBOR_R_SZ(states[0].payload, in);
285
286 in->offset += len;
287
288 return len;
289 }
290
get_string(struct lwm2m_input_context * in,uint8_t * value,size_t buflen)291 static int get_string(struct lwm2m_input_context *in, uint8_t *value, size_t buflen)
292 {
293 struct zcbor_string hndl;
294 int len;
295
296 ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in), 1, 0);
297
298 states->constant_state->enforce_canonical = false;
299
300 if (!zcbor_tstr_decode(states, &hndl)) {
301 LOG_WRN("unable to decode a string");
302 return -EBADMSG;
303 }
304
305 len = hndl.len;
306 if (len >= buflen) {
307 return -ENOMEM;
308 }
309
310 memcpy(value, hndl.value, len);
311 value[len] = '\0';
312
313 len = ICTX_CBOR_R_SZ(states[0].payload, in);
314
315 in->offset += len;
316
317 return len;
318 }
319
320 /* Gets time decoded as a numeric string.
321 *
322 * return 0 on success, -EBADMSG if decoding fails or -EFAIL if value is invalid
323 */
get_time_string(struct lwm2m_input_context * in,int64_t * value)324 static int get_time_string(struct lwm2m_input_context *in, int64_t *value)
325 {
326 char time_str[sizeof("4294967295")] = { 0 };
327 struct zcbor_string hndl = { .value = time_str, .len = sizeof(time_str) - 1 };
328
329 ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in), 1, 0);
330
331 states->constant_state->enforce_canonical = false;
332
333 if (!zcbor_tstr_decode(states, &hndl)) {
334 return -EBADMSG;
335 }
336
337 /* TODO: decode date/time string */
338 LOG_DBG("decoding a date/time string not supported");
339
340 return -ENOTSUP;
341 }
342
343 /* Gets time decoded as a numerical.
344 *
345 * return 0 on success, -EBADMSG if decoding fails
346 */
get_time_numerical(struct lwm2m_input_context * in,int64_t * value)347 static int get_time_numerical(struct lwm2m_input_context *in, int64_t *value)
348 {
349 ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in), 1, 0);
350
351 states->constant_state->enforce_canonical = false;
352
353 if (!zcbor_int64_decode(states, value)) {
354 LOG_WRN("unable to decode seconds since Epoch");
355 return -EBADMSG;
356 }
357
358 return 0;
359 }
360
get_time(struct lwm2m_input_context * in,time_t * value)361 static int get_time(struct lwm2m_input_context *in, time_t *value)
362 {
363 uint32_t tag;
364 int tag_sz = 0;
365 int data_len;
366 int ret;
367 bool success;
368 int64_t temp64;
369
370 ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in), 1, 0);
371
372 states->constant_state->enforce_canonical = false;
373
374 success = zcbor_tag_decode(states, &tag);
375
376 if (success) {
377 tag_sz = ICTX_CBOR_R_SZ(states[0].payload, in);
378 in->offset += tag_sz;
379
380 switch (tag) {
381 case ZCBOR_TAG_TIME_NUM:
382 ret = get_time_numerical(in, &temp64);
383 if (ret < 0) {
384 goto error;
385 }
386 break;
387 case ZCBOR_TAG_TIME_TSTR:
388 ret = get_time_string(in, &temp64);
389 if (ret < 0) {
390 goto error;
391 }
392 break;
393 default:
394 LOG_WRN("expected tagged date/time, got tag %" PRIu32 "", tag);
395 return -EBADMSG;
396 }
397 } else { /* Assumption is that tags are optional */
398
399 /* Let's assume numeric string but in case that fails falls go back to numerical */
400 ret = get_time_string(in, &temp64);
401 if (ret == -EBADMSG) {
402 ret = get_time_numerical(in, &temp64);
403 }
404
405 if (ret < 0) {
406 goto error;
407 }
408 }
409
410 data_len = ICTX_CBOR_R_SZ(states[0].payload, in);
411 in->offset += data_len;
412 *value = (time_t)temp64;
413
414 return tag_sz + data_len;
415
416 error:
417 return ret;
418 }
419
get_bool(struct lwm2m_input_context * in,bool * value)420 static int get_bool(struct lwm2m_input_context *in, bool *value)
421 {
422 ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in), 1, 0);
423
424 states->constant_state->enforce_canonical = false;
425
426 if (!zcbor_bool_decode(states, value)) {
427 LOG_WRN("unable to decode a boolean value");
428 return -EBADMSG;
429 }
430
431 int len = ICTX_CBOR_R_SZ(states[0].payload, in);
432
433 in->offset += len;
434
435 return len;
436 }
437
get_opaque(struct lwm2m_input_context * in,uint8_t * value,size_t buflen,struct lwm2m_opaque_context * opaque,bool * last_block)438 static int get_opaque(struct lwm2m_input_context *in, uint8_t *value, size_t buflen,
439 struct lwm2m_opaque_context *opaque, bool *last_block)
440 {
441 struct zcbor_string_fragment hndl = { 0 };
442 int ret;
443
444 ZCBOR_STATE_D(states, 1, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in), 1, 0);
445
446 states->constant_state->enforce_canonical = false;
447
448 /* Get the CBOR header only on first read. */
449 if (opaque->offset == 0) {
450 ret = zcbor_bstr_start_decode_fragment(states, &hndl);
451
452 if (!ret) {
453 LOG_WRN("unable to decode opaque data header");
454 return -EBADMSG;
455 }
456
457 opaque->len = hndl.total_len;
458
459 int len = ICTX_CBOR_R_SZ(states[0].payload, in);
460
461 in->offset += len;
462 }
463
464 return lwm2m_engine_get_opaque_more(in, value, buflen, opaque, last_block);
465 }
466
get_objlnk(struct lwm2m_input_context * in,struct lwm2m_objlnk * value)467 static int get_objlnk(struct lwm2m_input_context *in, struct lwm2m_objlnk *value)
468 {
469 char objlnk[sizeof("65535:65535")] = { 0 };
470 char *end;
471 char *idp = objlnk;
472
473 value->obj_id = LWM2M_OBJLNK_MAX_ID;
474 value->obj_inst = LWM2M_OBJLNK_MAX_ID;
475
476 int len = get_string(in, objlnk, sizeof(objlnk));
477
478
479 for (int idx = 0; idx < 2; idx++) {
480 errno = 0;
481
482 unsigned long id = strtoul(idp, &end, 10);
483
484 idp = end + 1;
485
486 if ((!id && errno == ERANGE) || id > LWM2M_OBJLNK_MAX_ID) {
487 LOG_WRN("decoded id %lu out of range[0..65535]", id);
488 return -EBADMSG;
489 }
490
491 switch (idx) {
492 case 0:
493 value->obj_id = id;
494 continue;
495 case 1:
496 value->obj_inst = id;
497 continue;
498 }
499 }
500
501 if (value->obj_inst != LWM2M_OBJLNK_MAX_ID && (value->obj_inst == LWM2M_OBJLNK_MAX_ID)) {
502 LOG_WRN("decoded obj inst id without obj id");
503 return -EBADMSG;
504 }
505
506 return len;
507 }
508
509 const struct lwm2m_writer cbor_writer = {
510 .put_s8 = put_s8,
511 .put_s16 = put_s16,
512 .put_s32 = put_s32,
513 .put_s64 = put_s64,
514 .put_string = put_string,
515 .put_float = put_float,
516 .put_time = put_time,
517 .put_bool = put_bool,
518 .put_opaque = put_opaque,
519 .put_objlnk = put_objlnk,
520 };
521
522 const struct lwm2m_reader cbor_reader = {
523 .get_s32 = get_s32,
524 .get_s64 = get_s64,
525 .get_time = get_time,
526 .get_string = get_string,
527 .get_float = get_float,
528 .get_bool = get_bool,
529 .get_opaque = get_opaque,
530 .get_objlnk = get_objlnk,
531 };
532
do_read_op_cbor(struct lwm2m_message * msg)533 int do_read_op_cbor(struct lwm2m_message *msg)
534 {
535 /* Can only return single resource */
536 if (msg->path.level < LWM2M_PATH_LEVEL_RESOURCE) {
537 return -EPERM;
538 } else if (msg->path.level > LWM2M_PATH_LEVEL_RESOURCE_INST) {
539 return -ENOENT;
540 }
541
542 return lwm2m_perform_read_op(msg, LWM2M_FORMAT_APP_CBOR);
543 }
544
do_write_op_cbor(struct lwm2m_message * msg)545 int do_write_op_cbor(struct lwm2m_message *msg)
546 {
547 struct lwm2m_engine_obj_inst *obj_inst = NULL;
548 struct lwm2m_engine_obj_field *obj_field;
549 struct lwm2m_engine_res *res = NULL;
550 struct lwm2m_engine_res_inst *res_inst = NULL;
551 int ret;
552 uint8_t created = 0U;
553
554 ret = lwm2m_get_or_create_engine_obj(msg, &obj_inst, &created);
555 if (ret < 0) {
556 return ret;
557 }
558
559 ret = lwm2m_engine_validate_write_access(msg, obj_inst, &obj_field);
560 if (ret < 0) {
561 return ret;
562 }
563
564 ret = lwm2m_engine_get_create_res_inst(&msg->path, &res, &res_inst);
565 if (ret < 0) {
566 return -ENOENT;
567 }
568
569 if (msg->path.level < LWM2M_PATH_LEVEL_RESOURCE) {
570 msg->path.level = LWM2M_PATH_LEVEL_RESOURCE;
571 }
572
573 return lwm2m_write_handler(obj_inst, res, res_inst, obj_field, msg);
574 }
575