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