| /* |
| * Copyright (c) 2022 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define LOG_MODULE_NAME net_lwm2m_cbor |
| #define LOG_LEVEL CONFIG_LWM2M_LOG_LEVEL |
| |
| #include <zephyr/logging/log.h> |
| #include <zephyr/sys/util.h> |
| #include <stdio.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <time.h> |
| |
| #include <zcbor_common.h> |
| #include <zcbor_decode.h> |
| #include <zcbor_encode.h> |
| |
| #include <zephyr/net/lwm2m.h> |
| #include "lwm2m_object.h" |
| #include "lwm2m_rw_cbor.h" |
| #include "lwm2m_engine.h" |
| #include "lwm2m_util.h" |
| |
| LOG_MODULE_REGISTER(LOG_MODULE_NAME); |
| |
| #define CPKT_CBOR_W_SZ(pos, cpkt) ((size_t)(pos) - (size_t)(cpkt)->data - (size_t)(cpkt)->offset) |
| |
| #define ICTX_CBOR_R_SZ(pos, ictx) ((size_t)pos - (size_t)(ictx)->in_cpkt->data - (ictx)->offset) |
| |
| static int put_time(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, int64_t value) |
| { |
| /* CBOR time output format is unspecified but SenML CBOR uses string format. |
| * Let's stick into the same format with plain CBOR |
| */ |
| struct tm dt; |
| char time_str[sizeof("1970-01-01T00:00:00-00:00")] = { 0 }; |
| int len; |
| int str_sz; |
| int tag_sz; |
| bool ret; |
| |
| if (gmtime_r((time_t *)&value, &dt) != &dt) { |
| LOG_ERR("unable to convert from secs since Epoch to a date/time construct"); |
| return -EINVAL; |
| } |
| |
| /* Time construct to a string. Time in UTC, offset to local time not known */ |
| len = snprintk(time_str, sizeof(time_str), |
| "%04d-%02d-%02dT%02d:%02d:%02d-00:00", |
| dt.tm_year+1900, |
| dt.tm_mon+1, |
| dt.tm_mday, |
| dt.tm_hour, |
| dt.tm_min, |
| dt.tm_sec); |
| |
| if (len < 0 || len > sizeof(time_str) - 1) { |
| LOG_ERR("unable to form a date/time string"); |
| return -EINVAL; |
| } |
| |
| ZCBOR_STATE_E(states, 0, CPKT_BUF_W_PTR(out->out_cpkt), CPKT_BUF_W_SIZE(out->out_cpkt), 1); |
| |
| /* Are tags required? V1.1 leaves this unspecified but some servers require tags */ |
| ret = zcbor_tag_encode(states, ZCBOR_TAG_TIME_TSTR); |
| |
| if (!ret) { |
| LOG_ERR("unable to encode date/time string tag"); |
| return -ENOMEM; |
| } |
| |
| tag_sz = CPKT_CBOR_W_SZ(states[0].payload, out->out_cpkt); |
| |
| out->out_cpkt->offset += tag_sz; |
| |
| ret = zcbor_tstr_put_term(states, time_str); |
| if (!ret) { |
| LOG_ERR("unable to encode date/time string"); |
| return -ENOMEM; |
| } |
| |
| str_sz = CPKT_CBOR_W_SZ(states[0].payload, out->out_cpkt); |
| |
| out->out_cpkt->offset += str_sz; |
| |
| return tag_sz + str_sz; |
| } |
| |
| static int put_s64(struct lwm2m_output_context *out, |
| struct lwm2m_obj_path *path, int64_t value) |
| { |
| int payload_len; |
| bool ret; |
| |
| ZCBOR_STATE_E(states, 0, CPKT_BUF_W_PTR(out->out_cpkt), CPKT_BUF_W_SIZE(out->out_cpkt), 1); |
| |
| ret = zcbor_int64_encode(states, &value); |
| |
| if (ret) { |
| payload_len = CPKT_CBOR_W_SZ(states[0].payload, out->out_cpkt); |
| out->out_cpkt->offset += payload_len; |
| } else { |
| LOG_ERR("unable to encode a long long integer value"); |
| payload_len = -ENOMEM; |
| } |
| |
| return payload_len; |
| } |
| |
| static int put_s32(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, int32_t value) |
| { |
| int payload_len; |
| bool ret; |
| |
| ZCBOR_STATE_E(states, 0, CPKT_BUF_W_PTR(out->out_cpkt), CPKT_BUF_W_SIZE(out->out_cpkt), 1); |
| |
| ret = zcbor_int32_encode(states, &value); |
| |
| if (ret) { |
| payload_len = CPKT_CBOR_W_SZ(states[0].payload, out->out_cpkt); |
| out->out_cpkt->offset += payload_len; |
| } else { |
| LOG_ERR("unable to encode an integer value"); |
| payload_len = -ENOMEM; |
| } |
| |
| return payload_len; |
| } |
| |
| static int put_s16(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, int16_t value) |
| { |
| return put_s32(out, path, value); |
| } |
| |
| static int put_s8(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, int8_t value) |
| { |
| return put_s32(out, path, value); |
| } |
| |
| static int put_float(struct lwm2m_output_context *out, |
| struct lwm2m_obj_path *path, double *value) |
| { |
| int payload_len; |
| bool ret; |
| |
| ZCBOR_STATE_E(states, 0, CPKT_BUF_W_PTR(out->out_cpkt), CPKT_BUF_W_SIZE(out->out_cpkt), 1); |
| |
| ret = zcbor_float64_encode(states, value); |
| |
| if (ret) { |
| payload_len = CPKT_CBOR_W_SZ(states[0].payload, out->out_cpkt); |
| out->out_cpkt->offset += payload_len; |
| } else { |
| payload_len = -ENOMEM; |
| } |
| |
| return payload_len; |
| } |
| |
| static int put_string(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, |
| char *buf, size_t buflen) |
| { |
| int payload_len; |
| bool ret; |
| |
| ZCBOR_STATE_E(states, 0, CPKT_BUF_W_PTR(out->out_cpkt), CPKT_BUF_W_SIZE(out->out_cpkt), 1); |
| |
| ret = zcbor_tstr_encode_ptr(states, buf, buflen); |
| |
| if (ret) { |
| payload_len = CPKT_CBOR_W_SZ(states[0].payload, out->out_cpkt); |
| out->out_cpkt->offset += payload_len; |
| } else { |
| payload_len = -ENOMEM; |
| } |
| |
| return payload_len; |
| } |
| |
| static int put_opaque(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, |
| char *buf, size_t buflen) |
| { |
| int payload_len; |
| bool ret; |
| |
| ZCBOR_STATE_E(states, 0, CPKT_BUF_W_PTR(out->out_cpkt), CPKT_BUF_W_SIZE(out->out_cpkt), 1); |
| |
| ret = zcbor_bstr_encode_ptr(states, buf, buflen); |
| |
| if (ret) { |
| payload_len = CPKT_CBOR_W_SZ(states[0].payload, out->out_cpkt); |
| out->out_cpkt->offset += payload_len; |
| } else { |
| payload_len = -ENOMEM; |
| } |
| |
| return payload_len; |
| } |
| |
| static int put_bool(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, bool value) |
| { |
| int payload_len; |
| bool ret; |
| |
| ZCBOR_STATE_E(states, 0, CPKT_BUF_W_PTR(out->out_cpkt), CPKT_BUF_W_SIZE(out->out_cpkt), 1); |
| |
| ret = zcbor_bool_encode(states, &value); |
| |
| if (ret) { |
| payload_len = CPKT_CBOR_W_SZ(states[0].payload, out->out_cpkt); |
| out->out_cpkt->offset += payload_len; |
| } else { |
| payload_len = -ENOMEM; |
| } |
| |
| return payload_len; |
| } |
| |
| static int put_objlnk(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, |
| struct lwm2m_objlnk *value) |
| { |
| char objlnk[sizeof("65535:65535")] = { 0 }; |
| |
| snprintk(objlnk, sizeof(objlnk), "%" PRIu16 ":%" PRIu16 "", value->obj_id, value->obj_inst); |
| |
| return put_string(out, path, objlnk, strlen(objlnk) + 1); |
| } |
| |
| static int get_s64(struct lwm2m_input_context *in, int64_t *value) |
| { |
| ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in), 1); |
| |
| if (!zcbor_int64_decode(states, value)) { |
| LOG_WRN("unable to decode a 64-bit integer value"); |
| return -EBADMSG; |
| } |
| |
| int len = ICTX_CBOR_R_SZ(states[0].payload, in); |
| |
| in->offset += len; |
| |
| return len; |
| } |
| |
| static int get_s32(struct lwm2m_input_context *in, int32_t *value) |
| { |
| ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in), 1); |
| |
| if (!zcbor_int32_decode(states, value)) { |
| LOG_WRN("unable to decode a 32-bit integer value, err: %d", |
| states->constant_state->error); |
| return -EBADMSG; |
| } |
| |
| int len = ICTX_CBOR_R_SZ(states[0].payload, in); |
| |
| in->offset += len; |
| |
| return len; |
| } |
| |
| static int get_float(struct lwm2m_input_context *in, double *value) |
| { |
| ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in), 1); |
| |
| if (!zcbor_float_decode(states, value)) { |
| LOG_ERR("unable to decode a floating-point value"); |
| return -EBADMSG; |
| } |
| |
| int len = ICTX_CBOR_R_SZ(states[0].payload, in); |
| |
| in->offset += len; |
| |
| return len; |
| } |
| |
| static int get_string(struct lwm2m_input_context *in, uint8_t *value, size_t buflen) |
| { |
| struct zcbor_string hndl; |
| int len; |
| |
| ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in), 1); |
| |
| if (!zcbor_tstr_decode(states, &hndl)) { |
| LOG_WRN("unable to decode a string"); |
| return -EBADMSG; |
| } |
| |
| len = MIN(buflen-1, hndl.len); |
| |
| memcpy(value, hndl.value, len); |
| |
| value[len] = '\0'; |
| |
| len = ICTX_CBOR_R_SZ(states[0].payload, in); |
| |
| in->offset += len; |
| |
| return len; |
| } |
| |
| /* Gets time decoded as a numeric string. |
| * |
| * return 0 on success, -EBADMSG if decoding fails or -EFAIL if value is invalid |
| */ |
| static int get_time_string(struct lwm2m_input_context *in, int64_t *value) |
| { |
| char time_str[sizeof("4294967295")] = { 0 }; |
| struct zcbor_string hndl = { .value = time_str, .len = sizeof(time_str) - 1 }; |
| |
| ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in), 1); |
| |
| if (!zcbor_tstr_decode(states, &hndl)) { |
| return -EBADMSG; |
| } |
| |
| /* TODO: decode date/time string */ |
| LOG_DBG("decoding a date/time string not supported"); |
| |
| return -ENOTSUP; |
| } |
| |
| /* Gets time decoded as a numerical. |
| * |
| * return 0 on success, -EBADMSG if decoding fails |
| */ |
| static int get_time_numerical(struct lwm2m_input_context *in, int64_t *value) |
| { |
| ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in), 1); |
| |
| if (!zcbor_int64_decode(states, value)) { |
| LOG_WRN("unable to decode seconds since Epoch"); |
| return -EBADMSG; |
| } |
| |
| return 0; |
| } |
| |
| static int get_time(struct lwm2m_input_context *in, int64_t *value) |
| { |
| uint32_t tag; |
| int tag_sz = 0; |
| int data_len; |
| int ret; |
| bool success; |
| |
| ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in), 1); |
| |
| success = zcbor_tag_decode(states, &tag); |
| |
| if (success) { |
| tag_sz = ICTX_CBOR_R_SZ(states[0].payload, in); |
| in->offset += tag_sz; |
| |
| switch (tag) { |
| case ZCBOR_TAG_TIME_NUM: |
| ret = get_time_numerical(in, value); |
| if (ret < 0) { |
| goto error; |
| } |
| break; |
| case ZCBOR_TAG_TIME_TSTR: |
| ret = get_time_string(in, value); |
| if (ret < 0) { |
| goto error; |
| } |
| break; |
| default: |
| LOG_WRN("expected tagged date/time, got tag %" PRIu32 "", tag); |
| return -EBADMSG; |
| } |
| } else { /* Assumption is that tags are optional */ |
| |
| /* Let's assume numeric string but in case that fails falls go back to numerical */ |
| ret = get_time_string(in, value); |
| if (ret == -EBADMSG) { |
| ret = get_time_numerical(in, value); |
| } |
| |
| if (ret < 0) { |
| goto error; |
| } |
| } |
| |
| data_len = ICTX_CBOR_R_SZ(states[0].payload, in); |
| in->offset += data_len; |
| |
| return tag_sz + data_len; |
| |
| error: |
| return ret; |
| } |
| |
| static int get_bool(struct lwm2m_input_context *in, bool *value) |
| { |
| ZCBOR_STATE_D(states, 0, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in), 1); |
| |
| if (!zcbor_bool_decode(states, value)) { |
| LOG_WRN("unable to decode a boolean value"); |
| return -EBADMSG; |
| } |
| |
| int len = ICTX_CBOR_R_SZ(states[0].payload, in); |
| |
| in->offset += len; |
| |
| return len; |
| } |
| |
| static int get_opaque(struct lwm2m_input_context *in, uint8_t *value, size_t buflen, |
| struct lwm2m_opaque_context *opaque, bool *last_block) |
| { |
| struct zcbor_string_fragment hndl = { 0 }; |
| int ret; |
| |
| ZCBOR_STATE_D(states, 1, ICTX_BUF_R_PTR(in), ICTX_BUF_R_LEFT_SZ(in), 1); |
| |
| /* Get the CBOR header only on first read. */ |
| if (opaque->remaining == 0) { |
| ret = zcbor_bstr_start_decode_fragment(states, &hndl); |
| |
| if (!ret) { |
| LOG_WRN("unable to decode opaque data header"); |
| return -EBADMSG; |
| } |
| |
| opaque->len = hndl.total_len; |
| opaque->remaining = hndl.total_len; |
| |
| int len = ICTX_CBOR_R_SZ(states[0].payload, in); |
| |
| in->offset += len; |
| } |
| |
| return lwm2m_engine_get_opaque_more(in, value, buflen, opaque, last_block); |
| } |
| |
| static int get_objlnk(struct lwm2m_input_context *in, struct lwm2m_objlnk *value) |
| { |
| char objlnk[sizeof("65535:65535")] = { 0 }; |
| char *end; |
| char *idp = objlnk; |
| |
| value->obj_id = LWM2M_OBJLNK_MAX_ID; |
| value->obj_inst = LWM2M_OBJLNK_MAX_ID; |
| |
| int len = get_string(in, objlnk, sizeof(objlnk)); |
| |
| |
| for (int idx = 0; idx < 2; idx++) { |
| errno = 0; |
| |
| unsigned long id = strtoul(idp, &end, 10); |
| |
| idp = end + 1; |
| |
| if ((!id && errno == ERANGE) || id > LWM2M_OBJLNK_MAX_ID) { |
| LOG_WRN("decoded id %lu out of range[0..65535]", id); |
| return -EBADMSG; |
| } |
| |
| switch (idx) { |
| case 0: |
| value->obj_id = id; |
| continue; |
| case 1: |
| value->obj_inst = id; |
| continue; |
| } |
| } |
| |
| if (value->obj_inst != LWM2M_OBJLNK_MAX_ID && (value->obj_inst == LWM2M_OBJLNK_MAX_ID)) { |
| LOG_WRN("decoded obj inst id without obj id"); |
| return -EBADMSG; |
| } |
| |
| return len; |
| } |
| |
| const struct lwm2m_writer cbor_writer = { |
| .put_s8 = put_s8, |
| .put_s16 = put_s16, |
| .put_s32 = put_s32, |
| .put_s64 = put_s64, |
| .put_string = put_string, |
| .put_float = put_float, |
| .put_time = put_time, |
| .put_bool = put_bool, |
| .put_opaque = put_opaque, |
| .put_objlnk = put_objlnk, |
| }; |
| |
| const struct lwm2m_reader cbor_reader = { |
| .get_s32 = get_s32, |
| .get_s64 = get_s64, |
| .get_time = get_time, |
| .get_string = get_string, |
| .get_float = get_float, |
| .get_bool = get_bool, |
| .get_opaque = get_opaque, |
| .get_objlnk = get_objlnk, |
| }; |
| |
| int do_read_op_cbor(struct lwm2m_message *msg) |
| { |
| /* Can only return single resource */ |
| if (msg->path.level < LWM2M_PATH_LEVEL_RESOURCE) { |
| return -EPERM; |
| } else if (msg->path.level > LWM2M_PATH_LEVEL_RESOURCE_INST) { |
| return -ENOENT; |
| } |
| |
| return lwm2m_perform_read_op(msg, LWM2M_FORMAT_APP_CBOR); |
| } |
| |
| int do_write_op_cbor(struct lwm2m_message *msg) |
| { |
| struct lwm2m_engine_obj_inst *obj_inst = NULL; |
| struct lwm2m_engine_obj_field *obj_field; |
| struct lwm2m_engine_res *res = NULL; |
| struct lwm2m_engine_res_inst *res_inst = NULL; |
| int ret; |
| uint8_t created = 0U; |
| |
| ret = lwm2m_get_or_create_engine_obj(msg, &obj_inst, &created); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| ret = lwm2m_engine_validate_write_access(msg, obj_inst, &obj_field); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| ret = lwm2m_engine_get_create_res_inst(&msg->path, &res, &res_inst); |
| if (ret < 0) { |
| return -ENOENT; |
| } |
| |
| if (msg->path.level < LWM2M_PATH_LEVEL_RESOURCE) { |
| msg->path.level = LWM2M_PATH_LEVEL_RESOURCE; |
| } |
| |
| return lwm2m_write_handler(obj_inst, res, res_inst, obj_field, msg); |
| } |