| /* |
| * Copyright (c) 2022 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define LOG_MODULE_NAME net_lwm2m_senml_cbor |
| #define LOG_LEVEL CONFIG_LWM2M_LOG_LEVEL |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(LOG_MODULE_NAME); |
| |
| #include <stdio.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <inttypes.h> |
| #include <ctype.h> |
| #include <time.h> |
| #include <zephyr/sys/util.h> |
| #include <zephyr/kernel.h> |
| |
| #include <zcbor_common.h> |
| #include <zcbor_decode.h> |
| #include <zcbor_encode.h> |
| |
| #include "lwm2m_engine.h" |
| #include "lwm2m_object.h" |
| #include "lwm2m_rw_senml_cbor.h" |
| #include "lwm2m_senml_cbor_decode.h" |
| #include "lwm2m_senml_cbor_encode.h" |
| #include "lwm2m_senml_cbor_types.h" |
| #include "lwm2m_util.h" |
| |
| #define SENML_MAX_NAME_SIZE sizeof("/65535/65535/") |
| |
| struct cbor_out_fmt_data { |
| /* Data */ |
| struct lwm2m_senml input; |
| |
| /* Storage for basenames and names ~ sizeof("/65535/65535/") */ |
| struct { |
| char names[CONFIG_LWM2M_RW_SENML_CBOR_RECORDS][SENML_MAX_NAME_SIZE]; |
| size_t name_sz; /* Name buff size */ |
| uint8_t name_cnt; |
| }; |
| |
| /* Basetime for Cached data timestamp */ |
| time_t basetime; |
| |
| /* Storage for object links */ |
| struct { |
| char objlnk[CONFIG_LWM2M_RW_SENML_CBOR_RECORDS][sizeof("65535:65535")]; |
| size_t objlnk_sz; /* Object link buff size */ |
| uint8_t objlnk_cnt; |
| }; |
| }; |
| |
| struct cbor_in_fmt_data { |
| /* Decoded data */ |
| struct lwm2m_senml dcd; /* Decoded data */ |
| struct record *current; |
| char basename[MAX_RESOURCE_LEN + 1]; /* Null terminated basename */ |
| }; |
| |
| /* Statically allocated formatter data is shared between different threads */ |
| static union cbor_io_fmt_data{ |
| struct cbor_in_fmt_data i; |
| struct cbor_out_fmt_data o; |
| } fdio; |
| |
| static int path_to_string(char *buf, size_t buf_size, const struct lwm2m_obj_path *input, |
| int level_max); |
| |
| /* |
| * SEND is called from a different context than the rest of the LwM2M functionality |
| */ |
| K_MUTEX_DEFINE(fd_mtx); |
| |
| #define GET_CBOR_FD_NAME(fd) ((fd)->names[(fd)->name_cnt]) |
| /* Get the current record */ |
| #define GET_CBOR_FD_REC(fd) \ |
| &((fd)->input._lwm2m_senml__record[(fd)->input._lwm2m_senml__record_count]) |
| /* Get a record */ |
| #define GET_IN_FD_REC_I(fd, i) &((fd)->dcd._lwm2m_senml__record[i]) |
| /* Consume the current record */ |
| #define CONSUME_CBOR_FD_REC(fd) \ |
| &((fd)->input._lwm2m_senml__record[(fd)->input._lwm2m_senml__record_count++]) |
| /* Get CBOR output formatter data */ |
| #define LWM2M_OFD_CBOR(octx) ((struct cbor_out_fmt_data *)engine_get_out_user_data(octx)) |
| |
| static void setup_out_fmt_data(struct lwm2m_message *msg) |
| { |
| k_mutex_lock(&fd_mtx, K_FOREVER); |
| |
| struct cbor_out_fmt_data *fd = &fdio.o; |
| |
| (void)memset(fd, 0, sizeof(*fd)); |
| engine_set_out_user_data(&msg->out, fd); |
| fd->name_sz = SENML_MAX_NAME_SIZE; |
| fd->basetime = 0; |
| fd->objlnk_sz = sizeof("65535:65535"); |
| } |
| |
| static void clear_out_fmt_data(struct lwm2m_message *msg) |
| { |
| engine_clear_out_user_data(&msg->out); |
| |
| k_mutex_unlock(&fd_mtx); |
| } |
| |
| static void setup_in_fmt_data(struct lwm2m_message *msg) |
| { |
| k_mutex_lock(&fd_mtx, K_FOREVER); |
| |
| struct cbor_in_fmt_data *fd = &fdio.i; |
| |
| (void)memset(fd, 0, sizeof(*fd)); |
| engine_set_in_user_data(&msg->in, fd); |
| } |
| |
| static void clear_in_fmt_data(struct lwm2m_message *msg) |
| { |
| engine_clear_in_user_data(&msg->in); |
| |
| k_mutex_unlock(&fd_mtx); |
| } |
| |
| static int fmt_range_check(struct cbor_out_fmt_data *fd) |
| { |
| if (fd->name_cnt >= CONFIG_LWM2M_RW_SENML_CBOR_RECORDS || |
| fd->objlnk_cnt >= CONFIG_LWM2M_RW_SENML_CBOR_RECORDS || |
| fd->input._lwm2m_senml__record_count >= CONFIG_LWM2M_RW_SENML_CBOR_RECORDS) { |
| LOG_ERR("CONFIG_LWM2M_RW_SENML_CBOR_RECORDS too small"); |
| return -ENOMEM; |
| } |
| |
| return 0; |
| } |
| |
| static int put_basename(struct lwm2m_output_context *out, struct lwm2m_obj_path *path) |
| { |
| struct cbor_out_fmt_data *fd = LWM2M_OFD_CBOR(out); |
| int len; |
| int ret; |
| |
| ret = fmt_range_check(fd); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| char *basename = GET_CBOR_FD_NAME(fd); |
| |
| len = path_to_string(basename, fd->name_sz, path, LWM2M_PATH_LEVEL_OBJECT_INST); |
| |
| if (len < 0) { |
| return len; |
| } |
| |
| /* Tell CBOR encoder where to find the name */ |
| struct record *record = GET_CBOR_FD_REC(fd); |
| |
| record->_record_bn._record_bn.value = basename; |
| record->_record_bn._record_bn.len = len; |
| record->_record_bn_present = 1; |
| |
| if ((len < sizeof("/0/0") - 1) || (len >= SENML_MAX_NAME_SIZE)) { |
| __ASSERT_NO_MSG(false); |
| return -EINVAL; |
| } |
| |
| fd->name_cnt++; |
| |
| return 0; |
| } |
| |
| static int put_empty_array(struct lwm2m_output_context *out) |
| { |
| int len = 1; |
| |
| memset(CPKT_BUF_W_PTR(out->out_cpkt), 0x80, len); /* 80 # array(0) */ |
| out->out_cpkt->offset += len; |
| |
| return len; |
| } |
| |
| static int put_end(struct lwm2m_output_context *out, struct lwm2m_obj_path *path) |
| { |
| size_t len; |
| struct lwm2m_senml *input = &(LWM2M_OFD_CBOR(out)->input); |
| |
| if (!input->_lwm2m_senml__record_count) { |
| len = put_empty_array(out); |
| |
| return len; |
| } |
| |
| uint_fast8_t ret = |
| cbor_encode_lwm2m_senml(CPKT_BUF_W_REGION(out->out_cpkt), input, &len); |
| |
| if (ret != ZCBOR_SUCCESS) { |
| LOG_ERR("unable to encode senml cbor msg"); |
| |
| return -E2BIG; |
| } |
| |
| out->out_cpkt->offset += len; |
| |
| return len; |
| } |
| |
| static int put_begin_oi(struct lwm2m_output_context *out, struct lwm2m_obj_path *path) |
| { |
| int ret; |
| uint8_t tmp = path->level; |
| |
| /* In case path level is set to 'none' or 'object' and we have only default oi */ |
| path->level = LWM2M_PATH_LEVEL_OBJECT_INST; |
| |
| ret = put_basename(out, path); |
| path->level = tmp; |
| |
| return ret; |
| } |
| |
| static int put_begin_r(struct lwm2m_output_context *out, struct lwm2m_obj_path *path) |
| { |
| struct cbor_out_fmt_data *fd = LWM2M_OFD_CBOR(out); |
| int len; |
| int ret; |
| |
| ret = fmt_range_check(fd); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| char *name = GET_CBOR_FD_NAME(fd); |
| |
| /* Write resource name */ |
| len = snprintk(name, sizeof("65535"), "%" PRIu16 "", path->res_id); |
| |
| if (len < sizeof("0") - 1) { |
| __ASSERT_NO_MSG(false); |
| return -EINVAL; |
| } |
| |
| /* Check if we could use an already existing name |
| * -> latest name slot is used as a scratchpad |
| */ |
| for (int idx = 0; idx < fd->name_cnt; idx++) { |
| if (strncmp(name, fd->names[idx], len) == 0) { |
| name = fd->names[idx]; |
| break; |
| } |
| } |
| |
| /* Tell CBOR encoder where to find the name */ |
| struct record *record = GET_CBOR_FD_REC(fd); |
| |
| record->_record_n._record_n.value = name; |
| record->_record_n._record_n.len = len; |
| record->_record_n_present = 1; |
| |
| /* Makes possible to use same slot for storing r/ri name combination. |
| * No need to increase the name count if an existing name has been used |
| */ |
| if (path->level < LWM2M_PATH_LEVEL_RESOURCE_INST && name == GET_CBOR_FD_NAME(fd)) { |
| fd->name_cnt++; |
| } |
| |
| return 0; |
| } |
| |
| static int put_data_timestamp(struct lwm2m_output_context *out, time_t value) |
| { |
| struct record *out_record; |
| struct cbor_out_fmt_data *fd = LWM2M_OFD_CBOR(out); |
| int ret; |
| |
| ret = fmt_range_check(fd); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| /* Tell CBOR encoder where to find the name */ |
| out_record = GET_CBOR_FD_REC(fd); |
| |
| if (fd->basetime) { |
| out_record->_record_t._record_t = value - fd->basetime; |
| out_record->_record_t_present = 1; |
| } else { |
| fd->basetime = value; |
| out_record->_record_bt._record_bt = value; |
| out_record->_record_bt_present = 1; |
| } |
| |
| return 0; |
| |
| } |
| |
| static int put_begin_ri(struct lwm2m_output_context *out, struct lwm2m_obj_path *path) |
| { |
| struct cbor_out_fmt_data *fd = LWM2M_OFD_CBOR(out); |
| char *name = GET_CBOR_FD_NAME(fd); |
| struct record *record = GET_CBOR_FD_REC(fd); |
| int ret; |
| |
| ret = fmt_range_check(fd); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| /* Forms name from resource id and resource instance id */ |
| int len = snprintk(name, SENML_MAX_NAME_SIZE, |
| "%" PRIu16 "/%" PRIu16 "", |
| path->res_id, path->res_inst_id); |
| |
| if (len < sizeof("0/0") - 1) { |
| __ASSERT_NO_MSG(false); |
| return -EINVAL; |
| } |
| |
| /* Check if we could use an already existing name |
| * -> latest name slot is used as a scratchpad |
| */ |
| for (int idx = 0; idx < fd->name_cnt; idx++) { |
| if (strncmp(name, fd->names[idx], len) == 0) { |
| name = fd->names[idx]; |
| break; |
| } |
| } |
| |
| /* Tell CBOR encoder where to find the name */ |
| record->_record_n._record_n.value = name; |
| record->_record_n._record_n.len = len; |
| record->_record_n_present = 1; |
| |
| /* No need to increase the name count if an existing name has been used */ |
| if (name == GET_CBOR_FD_NAME(fd)) { |
| fd->name_cnt++; |
| } |
| |
| return 0; |
| } |
| |
| static int put_name_nth_ri(struct lwm2m_output_context *out, struct lwm2m_obj_path *path) |
| { |
| int ret = 0; |
| struct cbor_out_fmt_data *fd = LWM2M_OFD_CBOR(out); |
| struct record *record = GET_CBOR_FD_REC(fd); |
| |
| /* With the first ri the resource name (and ri name) are already in place*/ |
| if (path->res_inst_id > 0) { |
| ret = put_begin_ri(out, path); |
| } else if (record && record->_record_t_present) { |
| /* Name need to be add for each time serialized record */ |
| ret = put_begin_r(out, path); |
| } |
| |
| return ret; |
| } |
| |
| static int put_value(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, int64_t value) |
| { |
| int ret = put_name_nth_ri(out, path); |
| |
| if (ret < 0) { |
| return ret; |
| } |
| |
| struct record *record = CONSUME_CBOR_FD_REC(LWM2M_OFD_CBOR(out)); |
| |
| /* Write the value */ |
| record->_record_union._record_union_choice = _union_vi; |
| record->_record_union._union_vi = value; |
| record->_record_union_present = 1; |
| |
| return 0; |
| } |
| |
| static int put_s8(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, int8_t value) |
| { |
| return put_value(out, path, value); |
| } |
| |
| static int put_s16(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, int16_t value) |
| { |
| return put_value(out, path, value); |
| } |
| |
| static int put_s32(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, int32_t value) |
| { |
| return put_value(out, path, value); |
| } |
| |
| static int put_s64(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, int64_t value) |
| { |
| return put_value(out, path, value); |
| } |
| |
| static int put_time(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, time_t value) |
| { |
| int ret = put_name_nth_ri(out, path); |
| |
| if (ret < 0) { |
| return ret; |
| } |
| |
| struct record *record = CONSUME_CBOR_FD_REC(LWM2M_OFD_CBOR(out)); |
| |
| /* Write the value */ |
| record->_record_union._record_union_choice = _union_vi; |
| record->_record_union._union_vi = (int64_t)value; |
| record->_record_union_present = 1; |
| |
| return 0; |
| } |
| |
| static int put_float(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, double *value) |
| { |
| int ret = put_name_nth_ri(out, path); |
| |
| if (ret < 0) { |
| return ret; |
| } |
| |
| struct record *record = CONSUME_CBOR_FD_REC(LWM2M_OFD_CBOR(out)); |
| |
| /* Write the value */ |
| record->_record_union._record_union_choice = _union_vf; |
| record->_record_union._union_vf = *value; |
| record->_record_union_present = 1; |
| |
| return 0; |
| } |
| |
| static int put_string(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, char *buf, |
| size_t buflen) |
| { |
| int ret = put_name_nth_ri(out, path); |
| |
| if (ret < 0) { |
| return ret; |
| } |
| |
| struct record *record = CONSUME_CBOR_FD_REC(LWM2M_OFD_CBOR(out)); |
| |
| /* Write the value */ |
| record->_record_union._record_union_choice = _union_vs; |
| record->_record_union._union_vs.value = buf; |
| record->_record_union._union_vs.len = buflen; |
| record->_record_union_present = 1; |
| |
| return 0; |
| } |
| |
| static int put_bool(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, bool value) |
| { |
| int ret = put_name_nth_ri(out, path); |
| |
| if (ret < 0) { |
| return ret; |
| } |
| |
| struct record *record = CONSUME_CBOR_FD_REC(LWM2M_OFD_CBOR(out)); |
| |
| /* Write the value */ |
| record->_record_union._record_union_choice = _union_vb; |
| record->_record_union._union_vb = value; |
| record->_record_union_present = 1; |
| |
| return 0; |
| } |
| |
| static int put_opaque(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, char *buf, |
| size_t buflen) |
| { |
| int ret = put_name_nth_ri(out, path); |
| |
| if (ret < 0) { |
| return ret; |
| } |
| |
| struct record *record = CONSUME_CBOR_FD_REC(LWM2M_OFD_CBOR(out)); |
| |
| /* Write the value */ |
| record->_record_union._record_union_choice = _union_vd; |
| record->_record_union._union_vd.value = buf; |
| record->_record_union._union_vd.len = buflen; |
| record->_record_union_present = 1; |
| |
| return 0; |
| } |
| |
| static int put_objlnk(struct lwm2m_output_context *out, struct lwm2m_obj_path *path, |
| struct lwm2m_objlnk *value) |
| { |
| int ret = 0; |
| struct cbor_out_fmt_data *fd = LWM2M_OFD_CBOR(out); |
| |
| ret = fmt_range_check(fd); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| /* Format object link */ |
| int objlnk_idx = fd->objlnk_cnt; |
| char *objlink_buf = fd->objlnk[objlnk_idx]; |
| int objlnk_len = |
| snprintk(objlink_buf, fd->objlnk_sz, "%u:%u", value->obj_id, value->obj_inst); |
| if (objlnk_len < 0) { |
| return -EINVAL; |
| } |
| |
| ret = put_name_nth_ri(out, path); |
| |
| if (ret < 0) { |
| return ret; |
| } |
| |
| struct record *record = CONSUME_CBOR_FD_REC(LWM2M_OFD_CBOR(out)); |
| |
| /* Write the value */ |
| record->_record_union._record_union_choice = _union_vlo; |
| record->_record_union._union_vlo.value = objlink_buf; |
| record->_record_union._union_vlo.len = objlnk_len; |
| record->_record_union_present = 1; |
| |
| fd->objlnk_cnt++; |
| |
| return 0; |
| } |
| |
| static int get_opaque(struct lwm2m_input_context *in, |
| uint8_t *value, size_t buflen, |
| struct lwm2m_opaque_context *opaque, |
| bool *last_block) |
| { |
| struct cbor_in_fmt_data *fd; |
| uint8_t *dest = NULL; |
| |
| /* Get the CBOR header only on first read. */ |
| if (opaque->remaining == 0) { |
| |
| fd = engine_get_in_user_data(in); |
| if (!fd || !fd->current) { |
| return -EINVAL; |
| } |
| |
| opaque->len = fd->current->_record_union._union_vd.len; |
| |
| if (buflen < opaque->len) { |
| LOG_DBG("Write opaque failed, no buffer space"); |
| return -ENOMEM; |
| } |
| |
| dest = memcpy(value, fd->current->_record_union._union_vd.value, opaque->len); |
| *last_block = true; |
| } else { |
| LOG_DBG("Blockwise transfer not supported with SenML CBOR"); |
| __ASSERT_NO_MSG(false); |
| } |
| |
| return dest ? opaque->len : -EINVAL; |
| } |
| |
| static int get_s32(struct lwm2m_input_context *in, int32_t *value) |
| { |
| struct cbor_in_fmt_data *fd; |
| |
| fd = engine_get_in_user_data(in); |
| if (!fd || !fd->current) { |
| return -EINVAL; |
| } |
| |
| *value = fd->current->_record_union._union_vi; |
| fd->current = NULL; |
| |
| return 0; |
| } |
| |
| static int get_s64(struct lwm2m_input_context *in, int64_t *value) |
| { |
| struct cbor_in_fmt_data *fd; |
| |
| fd = engine_get_in_user_data(in); |
| if (!fd || !fd->current) { |
| return -EINVAL; |
| } |
| |
| *value = fd->current->_record_union._union_vi; |
| fd->current = NULL; |
| |
| return 0; |
| } |
| |
| static int get_time(struct lwm2m_input_context *in, time_t *value) |
| { |
| int64_t temp64; |
| int ret; |
| |
| ret = get_s64(in, &temp64); |
| *value = (time_t)temp64; |
| |
| return ret; |
| } |
| |
| static int get_float(struct lwm2m_input_context *in, double *value) |
| { |
| struct cbor_in_fmt_data *fd; |
| |
| fd = engine_get_in_user_data(in); |
| if (!fd || !fd->current) { |
| return -EINVAL; |
| } |
| |
| *value = fd->current->_record_union._union_vf; |
| fd->current = NULL; |
| |
| return 0; |
| } |
| |
| static int get_string(struct lwm2m_input_context *in, uint8_t *buf, size_t buflen) |
| { |
| struct cbor_in_fmt_data *fd; |
| int len; |
| |
| fd = engine_get_in_user_data(in); |
| if (!fd || !fd->current) { |
| return -EINVAL; |
| } |
| |
| len = MIN(buflen-1, fd->current->_record_union._union_vs.len); |
| |
| memcpy(buf, fd->current->_record_union._union_vs.value, len); |
| buf[len] = '\0'; |
| |
| fd->current = NULL; |
| |
| return 0; |
| } |
| |
| static int get_objlnk(struct lwm2m_input_context *in, |
| struct lwm2m_objlnk *value) |
| { |
| char objlnk[sizeof("65535:65535")] = {0}; |
| unsigned long id; |
| int ret; |
| |
| ret = get_string(in, objlnk, sizeof(objlnk)); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| value->obj_id = LWM2M_OBJLNK_MAX_ID; |
| value->obj_inst = LWM2M_OBJLNK_MAX_ID; |
| |
| char *end; |
| char *idp = objlnk; |
| |
| for (int idx = 0; idx < 2; idx++) { |
| |
| errno = 0; |
| id = strtoul(idp, &end, 10); |
| |
| idp = end + 1; |
| |
| if ((id == 0 && errno == ERANGE) || id > 65535) { |
| 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_id == LWM2M_OBJLNK_MAX_ID)) { |
| LOG_WRN("decoded obj inst id without obj id"); |
| return -EBADMSG; |
| } |
| |
| return ret; |
| } |
| |
| static int get_bool(struct lwm2m_input_context *in, bool *value) |
| { |
| struct cbor_in_fmt_data *fd; |
| |
| fd = engine_get_in_user_data(in); |
| if (!fd || !fd->current) { |
| return -EINVAL; |
| } |
| |
| *value = fd->current->_record_union._union_vb; |
| fd->current = NULL; |
| |
| return 0; |
| } |
| |
| static int do_write_op_item(struct lwm2m_message *msg, struct record *rec) |
| { |
| 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; |
| struct cbor_in_fmt_data *fd; |
| /* Composite op - name with basename */ |
| char name[SENML_MAX_NAME_SIZE] = { 0 }; /* Null terminated name */ |
| int len = 0; |
| /* Compiler requires reserving space for full length basename and name even though those two |
| * combined do not exceed MAX_RESOURCE_LEN |
| */ |
| char fqn[MAX_RESOURCE_LEN + SENML_MAX_NAME_SIZE + 1] = {0}; |
| |
| fd = engine_get_in_user_data(&msg->in); |
| if (!fd) { |
| return -EINVAL; |
| } |
| |
| /* If there's no name then the basename forms the path */ |
| if (rec->_record_n_present) { |
| len = MIN(sizeof(name) - 1, rec->_record_n._record_n.len); |
| snprintk(name, len + 1, "%s", rec->_record_n._record_n.value); |
| } |
| |
| /* Form fully qualified path name */ |
| snprintk(fqn, sizeof(fqn), "%s%s", fd->basename, name); |
| |
| /* Set path on record basis */ |
| ret = lwm2m_string_to_path(fqn, &msg->path, '/'); |
| if (ret < 0) { |
| __ASSERT_NO_MSG(false); |
| return ret; |
| } |
| |
| fd->current = rec; |
| |
| 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) { |
| /* if OPTIONAL and BOOTSTRAP-WRITE or CREATE use ENOTSUP */ |
| if ((msg->ctx->bootstrap_mode || |
| msg->operation == LWM2M_OP_CREATE) && |
| LWM2M_HAS_PERM(obj_field, BIT(LWM2M_FLAG_OPTIONAL))) { |
| ret = -ENOTSUP; |
| return ret; |
| } |
| |
| ret = -ENOENT; |
| return ret; |
| } |
| |
| ret = lwm2m_write_handler(obj_inst, res, res_inst, obj_field, msg); |
| if (ret == -EACCES || ret == -ENOENT) { |
| /* if read-only or non-existent data buffer move on */ |
| ret = 0; |
| } |
| |
| return ret; |
| } |
| |
| const struct lwm2m_writer senml_cbor_writer = { |
| .put_end = put_end, |
| .put_begin_oi = put_begin_oi, |
| .put_begin_r = put_begin_r, |
| .put_begin_ri = put_begin_ri, |
| .put_s8 = put_s8, |
| .put_s16 = put_s16, |
| .put_s32 = put_s32, |
| .put_s64 = put_s64, |
| .put_time = put_time, |
| .put_string = put_string, |
| .put_float = put_float, |
| .put_bool = put_bool, |
| .put_opaque = put_opaque, |
| .put_objlnk = put_objlnk, |
| .put_data_timestamp = put_data_timestamp, |
| }; |
| |
| const struct lwm2m_reader senml_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_senml_cbor(struct lwm2m_message *msg) |
| { |
| int ret; |
| |
| setup_out_fmt_data(msg); |
| |
| ret = lwm2m_perform_read_op(msg, LWM2M_FORMAT_APP_SENML_CBOR); |
| |
| clear_out_fmt_data(msg); |
| |
| return ret; |
| } |
| |
| static uint8_t parse_composite_read_paths(struct lwm2m_message *msg, |
| sys_slist_t *lwm2m_path_list, |
| sys_slist_t *lwm2m_path_free_list) |
| { |
| char basename[MAX_RESOURCE_LEN + 1] = {0}; /* to include terminating null */ |
| char name[MAX_RESOURCE_LEN + 1] = {0}; /* to include terminating null */ |
| /* Compiler requires reserving space for full length basename and name even though those two |
| * combined do not exceed MAX_RESOURCE_LEN |
| */ |
| char fqn[2 * MAX_RESOURCE_LEN + 1] = {0}; |
| struct lwm2m_obj_path path; |
| struct cbor_in_fmt_data *fd; |
| uint8_t paths = 0; |
| size_t isize; |
| uint_fast8_t dret; |
| int len; |
| int ret; |
| |
| setup_in_fmt_data(msg); |
| |
| fd = engine_get_in_user_data(&msg->in); |
| |
| dret = cbor_decode_lwm2m_senml(ICTX_BUF_R_REGION(&msg->in), &fd->dcd, &isize); |
| |
| if (dret != ZCBOR_SUCCESS) { |
| __ASSERT_NO_MSG(false); |
| goto out; |
| } |
| |
| msg->in.offset += isize; |
| |
| for (int idx = 0; idx < fd->dcd._lwm2m_senml__record_count; idx++) { |
| |
| /* Where to find the basenames and names */ |
| struct record *record = GET_IN_FD_REC_I(fd, idx); |
| |
| /* Set null terminated effective basename */ |
| if (record->_record_bn_present) { |
| len = MIN(sizeof(basename)-1, record->_record_bn._record_bn.len); |
| snprintk(basename, len + 1, "%s", record->_record_bn._record_bn.value); |
| basename[len] = '\0'; |
| } |
| |
| /* Best effort with read, skip if no proper name is available */ |
| if (!record->_record_n_present) { |
| if (strcmp(basename, "") == 0) { |
| continue; |
| } |
| } |
| |
| /* Set null terminated name */ |
| if (record->_record_n_present) { |
| len = MIN(sizeof(name)-1, record->_record_n._record_n.len); |
| snprintk(name, len + 1, "%s", record->_record_n._record_n.value); |
| name[len] = '\0'; |
| } |
| |
| /* Form fully qualified path name */ |
| snprintk(fqn, sizeof(fqn), "%s%s", basename, name); |
| |
| ret = lwm2m_string_to_path(fqn, &path, '/'); |
| |
| /* invalid path is forgiven with read */ |
| if (ret < 0) { |
| continue; |
| } |
| |
| ret = lwm2m_engine_add_path_to_list(lwm2m_path_list, lwm2m_path_free_list, &path); |
| |
| if (ret < 0) { |
| continue; |
| } |
| |
| paths++; |
| } |
| |
| out: |
| clear_in_fmt_data(msg); |
| |
| return paths; |
| } |
| |
| int do_composite_read_op_for_parsed_path_senml_cbor(struct lwm2m_message *msg, |
| sys_slist_t *lwm_path_list) |
| { |
| int ret; |
| |
| setup_out_fmt_data(msg); |
| |
| ret = lwm2m_perform_composite_read_op(msg, LWM2M_FORMAT_APP_SENML_CBOR, lwm_path_list); |
| |
| clear_out_fmt_data(msg); |
| |
| return ret; |
| } |
| |
| |
| int do_composite_read_op_senml_cbor(struct lwm2m_message *msg) |
| { |
| struct lwm2m_obj_path_list lwm2m_path_list_buf[CONFIG_LWM2M_COMPOSITE_PATH_LIST_SIZE]; |
| sys_slist_t lwm_path_list; |
| sys_slist_t lwm_path_free_list; |
| uint8_t len; |
| |
| lwm2m_engine_path_list_init(&lwm_path_list, |
| &lwm_path_free_list, |
| lwm2m_path_list_buf, |
| CONFIG_LWM2M_COMPOSITE_PATH_LIST_SIZE); |
| |
| /* Parse paths */ |
| len = parse_composite_read_paths(msg, &lwm_path_list, &lwm_path_free_list); |
| if (len == 0) { |
| LOG_ERR("No Valid URL at msg"); |
| return -ESRCH; |
| } |
| |
| lwm2m_engine_clear_duplicate_path(&lwm_path_list, &lwm_path_free_list); |
| |
| return do_composite_read_op_for_parsed_path_senml_cbor(msg, &lwm_path_list); |
| } |
| |
| |
| int do_write_op_senml_cbor(struct lwm2m_message *msg) |
| { |
| uint_fast8_t dret; |
| int ret = 0; |
| size_t decoded_sz; |
| struct cbor_in_fmt_data *fd; |
| |
| /* With block-wise transfer consecutive blocks will not carry the content header - |
| * go directly to the message processing |
| */ |
| if (msg->in.block_ctx != NULL && msg->in.block_ctx->ctx.current > 0) { |
| msg->path.res_id = msg->in.block_ctx->path.res_id; |
| msg->path.level = msg->in.block_ctx->path.level; |
| |
| if (msg->path.level == LWM2M_PATH_LEVEL_RESOURCE_INST) { |
| msg->path.res_inst_id = msg->in.block_ctx->path.res_inst_id; |
| } |
| |
| return do_write_op_item(msg, NULL); |
| } |
| |
| setup_in_fmt_data(msg); |
| |
| fd = engine_get_in_user_data(&msg->in); |
| |
| dret = cbor_decode_lwm2m_senml(ICTX_BUF_R_PTR(&msg->in), ICTX_BUF_R_LEFT_SZ(&msg->in), |
| &fd->dcd, &decoded_sz); |
| |
| if (dret != ZCBOR_SUCCESS) { |
| ret = -EBADMSG; |
| goto error; |
| } |
| |
| msg->in.offset += decoded_sz; |
| |
| for (int idx = 0; idx < fd->dcd._lwm2m_senml__record_count; idx++) { |
| |
| struct record *rec = &fd->dcd._lwm2m_senml__record[idx]; |
| |
| /* Basename applies for current and succeeding records */ |
| if (rec->_record_bn_present) { |
| int len = MIN(sizeof(fd->basename) - 1, |
| rec->_record_bn._record_bn.len); |
| |
| snprintk(fd->basename, len + 1, "%s", rec->_record_bn._record_bn.value); |
| goto write; |
| } |
| |
| /* Keys' lexicographic order differ from the default */ |
| for (int jdx = 0; jdx < rec->_record__key_value_pair_count; jdx++) { |
| struct key_value_pair *kvp = |
| &(rec->_record__key_value_pair[jdx]._record__key_value_pair); |
| |
| if (kvp->_key_value_pair_key == lwm2m_senml_cbor_key_bn) { |
| int len = MIN(sizeof(fd->basename) - 1, |
| kvp->_key_value_pair._value_tstr.len); |
| |
| snprintk(fd->basename, len + 1, "%s", |
| kvp->_key_value_pair._value_tstr.value); |
| break; |
| } |
| } |
| write: |
| ret = do_write_op_item(msg, rec); |
| |
| /* |
| * ignore errors for CREATE op |
| * for OP_CREATE and BOOTSTRAP WRITE: errors on |
| * optional resources are ignored (ENOTSUP) |
| */ |
| if (ret < 0 && !((ret == -ENOTSUP) && |
| (msg->ctx->bootstrap_mode || msg->operation == LWM2M_OP_CREATE))) { |
| goto error; |
| } |
| } |
| |
| ret = 0; |
| |
| error: |
| clear_in_fmt_data(msg); |
| |
| return ret; |
| } |
| |
| int do_composite_observe_parse_path_senml_cbor(struct lwm2m_message *msg, |
| sys_slist_t *lwm2m_path_list, |
| sys_slist_t *lwm2m_path_free_list) |
| { |
| uint16_t original_offset; |
| uint8_t len; |
| |
| original_offset = msg->in.offset; |
| |
| /* Parse paths */ |
| len = parse_composite_read_paths(msg, lwm2m_path_list, lwm2m_path_free_list); |
| |
| if (len == 0) { |
| LOG_ERR("No Valid URL at msg"); |
| return -ESRCH; |
| } |
| |
| msg->in.offset = original_offset; |
| return 0; |
| } |
| |
| int do_send_op_senml_cbor(struct lwm2m_message *msg, sys_slist_t *lwm2m_path_list) |
| { |
| int ret; |
| |
| setup_out_fmt_data(msg); |
| |
| ret = lwm2m_perform_composite_read_op(msg, LWM2M_FORMAT_APP_SENML_CBOR, lwm2m_path_list); |
| |
| clear_out_fmt_data(msg); |
| |
| return ret; |
| } |
| |
| static int path_to_string(char *buf, size_t buf_size, const struct lwm2m_obj_path *input, |
| int level_max) |
| { |
| size_t fpl = 0; /* Length of the formed path */ |
| int level; |
| int w; |
| |
| if (!buf || buf_size < sizeof("/") || !input) { |
| return -EINVAL; |
| } |
| |
| memset(buf, '\0', buf_size); |
| |
| level = MIN(input->level, level_max); |
| |
| /* Write path element at a time and leave space for the terminating NULL */ |
| for (int idx = LWM2M_PATH_LEVEL_NONE; idx <= level; idx++) { |
| switch (idx) { |
| case LWM2M_PATH_LEVEL_NONE: |
| w = snprintk(&(buf[fpl]), buf_size - fpl, "/"); |
| break; |
| case LWM2M_PATH_LEVEL_OBJECT: |
| w = snprintk(&(buf[fpl]), buf_size - fpl, "%" PRIu16 "/", input->obj_id); |
| break; |
| case LWM2M_PATH_LEVEL_OBJECT_INST: |
| w = snprintk(&(buf[fpl]), buf_size - fpl, "%" PRIu16 "/", |
| input->obj_inst_id); |
| break; |
| case LWM2M_PATH_LEVEL_RESOURCE: |
| w = snprintk(&(buf[fpl]), buf_size - fpl, "%" PRIu16 "", input->res_id); |
| break; |
| case LWM2M_PATH_LEVEL_RESOURCE_INST: |
| w = snprintk(&(buf[fpl]), buf_size - fpl, "/%" PRIu16 "", |
| input->res_inst_id); |
| break; |
| default: |
| __ASSERT_NO_MSG(false); |
| return -EINVAL; |
| } |
| |
| if (w < 0 || w >= buf_size - fpl) { |
| return -ENOBUFS; |
| } |
| |
| /* Next path element, overwrites terminating NULL */ |
| fpl += w; |
| } |
| |
| return fpl; |
| } |