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 if (!zcbor_int64_decode(states, value)) {
241 LOG_WRN("unable to decode a 64-bit integer value");
242 return -EBADMSG;
243 }
244
245 int len = ICTX_CBOR_R_SZ(states[0].payload, in);
246
247 in->offset += len;
248
249 return len;
250 }
251
get_s32(struct lwm2m_input_context * in,int32_t * value)252 static int get_s32(struct lwm2m_input_context *in, int32_t *value)
253 {
254 ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in), 1, 0);
255
256 if (!zcbor_int32_decode(states, value)) {
257 LOG_WRN("unable to decode a 32-bit integer value, err: %d",
258 states->constant_state->error);
259 return -EBADMSG;
260 }
261
262 int len = ICTX_CBOR_R_SZ(states[0].payload, in);
263
264 in->offset += len;
265
266 return len;
267 }
268
get_float(struct lwm2m_input_context * in,double * value)269 static int get_float(struct lwm2m_input_context *in, double *value)
270 {
271 ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in), 1, 0);
272
273 if (!zcbor_float_decode(states, value)) {
274 LOG_ERR("unable to decode a floating-point value");
275 return -EBADMSG;
276 }
277
278 int len = ICTX_CBOR_R_SZ(states[0].payload, in);
279
280 in->offset += len;
281
282 return len;
283 }
284
get_string(struct lwm2m_input_context * in,uint8_t * value,size_t buflen)285 static int get_string(struct lwm2m_input_context *in, uint8_t *value, size_t buflen)
286 {
287 struct zcbor_string hndl;
288 int len;
289
290 ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in), 1, 0);
291
292 if (!zcbor_tstr_decode(states, &hndl)) {
293 LOG_WRN("unable to decode a string");
294 return -EBADMSG;
295 }
296
297 len = MIN(buflen-1, hndl.len);
298
299 memcpy(value, hndl.value, len);
300
301 value[len] = '\0';
302
303 len = ICTX_CBOR_R_SZ(states[0].payload, in);
304
305 in->offset += len;
306
307 return len;
308 }
309
310 /* Gets time decoded as a numeric string.
311 *
312 * return 0 on success, -EBADMSG if decoding fails or -EFAIL if value is invalid
313 */
get_time_string(struct lwm2m_input_context * in,int64_t * value)314 static int get_time_string(struct lwm2m_input_context *in, int64_t *value)
315 {
316 char time_str[sizeof("4294967295")] = { 0 };
317 struct zcbor_string hndl = { .value = time_str, .len = sizeof(time_str) - 1 };
318
319 ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in), 1, 0);
320
321 if (!zcbor_tstr_decode(states, &hndl)) {
322 return -EBADMSG;
323 }
324
325 /* TODO: decode date/time string */
326 LOG_DBG("decoding a date/time string not supported");
327
328 return -ENOTSUP;
329 }
330
331 /* Gets time decoded as a numerical.
332 *
333 * return 0 on success, -EBADMSG if decoding fails
334 */
get_time_numerical(struct lwm2m_input_context * in,int64_t * value)335 static int get_time_numerical(struct lwm2m_input_context *in, int64_t *value)
336 {
337 ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in), 1, 0);
338
339 if (!zcbor_int64_decode(states, value)) {
340 LOG_WRN("unable to decode seconds since Epoch");
341 return -EBADMSG;
342 }
343
344 return 0;
345 }
346
get_time(struct lwm2m_input_context * in,time_t * value)347 static int get_time(struct lwm2m_input_context *in, time_t *value)
348 {
349 uint32_t tag;
350 int tag_sz = 0;
351 int data_len;
352 int ret;
353 bool success;
354 int64_t temp64;
355
356 ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in), 1, 0);
357
358 success = zcbor_tag_decode(states, &tag);
359
360 if (success) {
361 tag_sz = ICTX_CBOR_R_SZ(states[0].payload, in);
362 in->offset += tag_sz;
363
364 switch (tag) {
365 case ZCBOR_TAG_TIME_NUM:
366 ret = get_time_numerical(in, &temp64);
367 if (ret < 0) {
368 goto error;
369 }
370 break;
371 case ZCBOR_TAG_TIME_TSTR:
372 ret = get_time_string(in, &temp64);
373 if (ret < 0) {
374 goto error;
375 }
376 break;
377 default:
378 LOG_WRN("expected tagged date/time, got tag %" PRIu32 "", tag);
379 return -EBADMSG;
380 }
381 } else { /* Assumption is that tags are optional */
382
383 /* Let's assume numeric string but in case that fails falls go back to numerical */
384 ret = get_time_string(in, &temp64);
385 if (ret == -EBADMSG) {
386 ret = get_time_numerical(in, &temp64);
387 }
388
389 if (ret < 0) {
390 goto error;
391 }
392 }
393
394 data_len = ICTX_CBOR_R_SZ(states[0].payload, in);
395 in->offset += data_len;
396 *value = (time_t)temp64;
397
398 return tag_sz + data_len;
399
400 error:
401 return ret;
402 }
403
get_bool(struct lwm2m_input_context * in,bool * value)404 static int get_bool(struct lwm2m_input_context *in, bool *value)
405 {
406 ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in), 1, 0);
407
408 if (!zcbor_bool_decode(states, value)) {
409 LOG_WRN("unable to decode a boolean value");
410 return -EBADMSG;
411 }
412
413 int len = ICTX_CBOR_R_SZ(states[0].payload, in);
414
415 in->offset += len;
416
417 return len;
418 }
419
get_opaque(struct lwm2m_input_context * in,uint8_t * value,size_t buflen,struct lwm2m_opaque_context * opaque,bool * last_block)420 static int get_opaque(struct lwm2m_input_context *in, uint8_t *value, size_t buflen,
421 struct lwm2m_opaque_context *opaque, bool *last_block)
422 {
423 struct zcbor_string_fragment hndl = { 0 };
424 int ret;
425
426 ZCBOR_STATE_D(states, 1, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in), 1, 0);
427
428 /* Get the CBOR header only on first read. */
429 if (opaque->remaining == 0) {
430 ret = zcbor_bstr_start_decode_fragment(states, &hndl);
431
432 if (!ret) {
433 LOG_WRN("unable to decode opaque data header");
434 return -EBADMSG;
435 }
436
437 opaque->len = hndl.total_len;
438 opaque->remaining = hndl.total_len;
439
440 int len = ICTX_CBOR_R_SZ(states[0].payload, in);
441
442 in->offset += len;
443 }
444
445 return lwm2m_engine_get_opaque_more(in, value, buflen, opaque, last_block);
446 }
447
get_objlnk(struct lwm2m_input_context * in,struct lwm2m_objlnk * value)448 static int get_objlnk(struct lwm2m_input_context *in, struct lwm2m_objlnk *value)
449 {
450 char objlnk[sizeof("65535:65535")] = { 0 };
451 char *end;
452 char *idp = objlnk;
453
454 value->obj_id = LWM2M_OBJLNK_MAX_ID;
455 value->obj_inst = LWM2M_OBJLNK_MAX_ID;
456
457 int len = get_string(in, objlnk, sizeof(objlnk));
458
459
460 for (int idx = 0; idx < 2; idx++) {
461 errno = 0;
462
463 unsigned long id = strtoul(idp, &end, 10);
464
465 idp = end + 1;
466
467 if ((!id && errno == ERANGE) || id > LWM2M_OBJLNK_MAX_ID) {
468 LOG_WRN("decoded id %lu out of range[0..65535]", id);
469 return -EBADMSG;
470 }
471
472 switch (idx) {
473 case 0:
474 value->obj_id = id;
475 continue;
476 case 1:
477 value->obj_inst = id;
478 continue;
479 }
480 }
481
482 if (value->obj_inst != LWM2M_OBJLNK_MAX_ID && (value->obj_inst == LWM2M_OBJLNK_MAX_ID)) {
483 LOG_WRN("decoded obj inst id without obj id");
484 return -EBADMSG;
485 }
486
487 return len;
488 }
489
490 const struct lwm2m_writer cbor_writer = {
491 .put_s8 = put_s8,
492 .put_s16 = put_s16,
493 .put_s32 = put_s32,
494 .put_s64 = put_s64,
495 .put_string = put_string,
496 .put_float = put_float,
497 .put_time = put_time,
498 .put_bool = put_bool,
499 .put_opaque = put_opaque,
500 .put_objlnk = put_objlnk,
501 };
502
503 const struct lwm2m_reader cbor_reader = {
504 .get_s32 = get_s32,
505 .get_s64 = get_s64,
506 .get_time = get_time,
507 .get_string = get_string,
508 .get_float = get_float,
509 .get_bool = get_bool,
510 .get_opaque = get_opaque,
511 .get_objlnk = get_objlnk,
512 };
513
do_read_op_cbor(struct lwm2m_message * msg)514 int do_read_op_cbor(struct lwm2m_message *msg)
515 {
516 /* Can only return single resource */
517 if (msg->path.level < LWM2M_PATH_LEVEL_RESOURCE) {
518 return -EPERM;
519 } else if (msg->path.level > LWM2M_PATH_LEVEL_RESOURCE_INST) {
520 return -ENOENT;
521 }
522
523 return lwm2m_perform_read_op(msg, LWM2M_FORMAT_APP_CBOR);
524 }
525
do_write_op_cbor(struct lwm2m_message * msg)526 int do_write_op_cbor(struct lwm2m_message *msg)
527 {
528 struct lwm2m_engine_obj_inst *obj_inst = NULL;
529 struct lwm2m_engine_obj_field *obj_field;
530 struct lwm2m_engine_res *res = NULL;
531 struct lwm2m_engine_res_inst *res_inst = NULL;
532 int ret;
533 uint8_t created = 0U;
534
535 ret = lwm2m_get_or_create_engine_obj(msg, &obj_inst, &created);
536 if (ret < 0) {
537 return ret;
538 }
539
540 ret = lwm2m_engine_validate_write_access(msg, obj_inst, &obj_field);
541 if (ret < 0) {
542 return ret;
543 }
544
545 ret = lwm2m_engine_get_create_res_inst(&msg->path, &res, &res_inst);
546 if (ret < 0) {
547 return -ENOENT;
548 }
549
550 if (msg->path.level < LWM2M_PATH_LEVEL_RESOURCE) {
551 msg->path.level = LWM2M_PATH_LEVEL_RESOURCE;
552 }
553
554 return lwm2m_write_handler(obj_inst, res, res_inst, obj_field, msg);
555 }
556