blob: 6c20024bbdea3212d798e68f2b2a2c4fc83e6db9 [file] [log] [blame]
/*
* 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_m[(fd)->input.lwm2m_senml_record_m_count])
/* Get a record */
#define GET_IN_FD_REC_I(fd, i) &((fd)->dcd.lwm2m_senml_record_m[i])
/* Consume the current record */
#define CONSUME_CBOR_FD_REC(fd) \
&((fd)->input.lwm2m_senml_record_m[(fd)->input.lwm2m_senml_record_m_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_m_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_m_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_c;
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_c;
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_c;
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_c;
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_c;
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_c;
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_c;
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;
char *payload;
uint16_t in_len;
setup_in_fmt_data(msg);
fd = engine_get_in_user_data(&msg->in);
payload = (char *)coap_packet_get_payload(msg->in.in_cpkt, &in_len);
dret = cbor_decode_lwm2m_senml(payload, in_len, &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_m_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_list(msg, LWM2M_FORMAT_APP_SENML_CBOR,
&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_m_count; idx++) {
struct record *rec = &fd->dcd.lwm2m_senml_record_m[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_m_count; jdx++) {
struct key_value_pair *kvp =
&(rec->record_key_value_pair_m[jdx].record_key_value_pair_m);
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;
}