net: lwm2m: Single instance read&write with CBOR

Raw CBOR content format support.

Signed-off-by: Veijo Pesonen <veijo.pesonen@nordicsemi.no>
This commit is contained in:
Veijo Pesonen 2022-01-20 13:28:37 +02:00 committed by Carles Cufí
parent 24e04a2159
commit 8fd283a788
7 changed files with 612 additions and 0 deletions

View file

@ -55,6 +55,10 @@ zephyr_library_sources_ifdef(CONFIG_LWM2M_RW_JSON_SUPPORT
zephyr_library_sources_ifdef(CONFIG_LWM2M_RW_SENML_JSON_SUPPORT
lwm2m_rw_senml_json.c
)
# CBOR support
zephyr_library_sources_ifdef(CONFIG_LWM2M_RW_CBOR_SUPPORT
lwm2m_rw_cbor.c
)
# IPSO Objects
zephyr_library_sources_ifdef(CONFIG_LWM2M_IPSO_TEMP_SENSOR

View file

@ -434,6 +434,13 @@ config LWM2M_COMPOSITE_PATH_LIST_SIZE
help
Define path list size for Composite Read and send operation.
config LWM2M_RW_CBOR_SUPPORT
bool "support for CBOR writer [EXPERIMENTAL]"
select ZCBOR
select EXPERIMENTAL
help
Include support for writing CBOR data
config LWM2M_DEVICE_PWRSRC_MAX
int "Maximum # of device power source records"
default 5

View file

@ -51,6 +51,9 @@ LOG_MODULE_REGISTER(LOG_MODULE_NAME);
#ifdef CONFIG_LWM2M_RW_JSON_SUPPORT
#include "lwm2m_rw_json.h"
#endif
#ifdef CONFIG_LWM2M_RW_CBOR_SUPPORT
#include "lwm2m_rw_cbor.h"
#endif
#ifdef CONFIG_LWM2M_RD_CLIENT_SUPPORT
#include "lwm2m_rd_client.h"
#endif
@ -1603,6 +1606,12 @@ static int select_writer(struct lwm2m_output_context *out, uint16_t accept)
break;
#endif
#ifdef CONFIG_LWM2M_RW_CBOR_SUPPORT
case LWM2M_FORMAT_APP_CBOR:
out->writer = &cbor_writer;
break;
#endif
default:
LOG_WRN("Unknown content type %u", accept);
return -ENOMSG;
@ -1642,6 +1651,12 @@ static int select_reader(struct lwm2m_input_context *in, uint16_t format)
break;
#endif
#ifdef CONFIG_LWM2M_RW_CBOR_SUPPORT
case LWM2M_FORMAT_APP_CBOR:
in->reader = &cbor_reader;
break;
#endif
default:
LOG_WRN("Unknown content type %u", format);
return -ENOMSG;
@ -3706,6 +3721,11 @@ static int do_read_op(struct lwm2m_message *msg, uint16_t content_format)
return do_read_op_senml_json(msg);
#endif
#if defined(CONFIG_LWM2M_RW_CBOR_SUPPORT)
case LWM2M_FORMAT_APP_CBOR:
return do_read_op_cbor(msg);
#endif
default:
LOG_ERR("Unsupported content-format: %u", content_format);
return -ENOMSG;
@ -4252,6 +4272,11 @@ static int do_write_op(struct lwm2m_message *msg,
return do_write_op_senml_json(msg);
#endif
#ifdef CONFIG_LWM2M_RW_CBOR_SUPPORT
case LWM2M_FORMAT_APP_CBOR:
return do_write_op_cbor(msg);
#endif
default:
LOG_ERR("Unsupported format: %u", format);
return -ENOMSG;

View file

@ -27,6 +27,7 @@
#define LWM2M_FORMAT_APP_OCTET_STREAM 42
#define LWM2M_FORMAT_APP_EXI 47
#define LWM2M_FORMAT_APP_JSON 50
#define LWM2M_FORMAT_APP_CBOR 60
#define LWM2M_FORMAT_APP_SEML_JSON 110
#define LWM2M_FORMAT_OMA_PLAIN_TEXT 1541
#define LWM2M_FORMAT_OMA_OLD_TLV 1542

View file

@ -140,6 +140,13 @@ BUILD_ASSERT(CONFIG_LWM2M_COAP_BLOCK_SIZE <= CONFIG_LWM2M_COAP_MAX_MSG_SIZE,
#define CPKT_BUF_W_PTR(cpkt) ((cpkt)->data + (cpkt)->offset)
#define CPKT_BUF_W_SIZE(cpkt) ((cpkt)->max_len - (cpkt)->offset)
#define CPKT_BUF_W_REGION(cpkt) CPKT_BUF_W_PTR(cpkt), CPKT_BUF_W_SIZE(cpkt)
/* Input context buffer util macros */
#define ICTX_BUF_R_LEFT_SZ(i_ctx) ((i_ctx)->in_cpkt->max_len - (i_ctx)->offset)
#define ICTX_BUF_R_PTR(i_ctx) ((i_ctx)->in_cpkt->data + (i_ctx)->offset)
#define ICTX_BUF_R_REGION(i_ctx) ICTX_BUF_R_PTR(i_ctx), ICTX_BUF_R_LEFT_SZ(i_ctx)
struct lwm2m_engine_obj;
struct lwm2m_message;

View file

@ -0,0 +1,550 @@
/*
* 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 <logging/log.h>
#include <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 <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) - 1);
for (int idx = 0; idx < 2; idx++) {
errno = 0;
unsigned long id = strtoul(idp, &end, 10);
idp = end;
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);
}

View file

@ -0,0 +1,18 @@
/*
* Copyright (c) 2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef LWM2M_RW_CBOR_H_
#define LWM2M_RW_CBOR_H_
#include "lwm2m_object.h"
extern const struct lwm2m_writer cbor_writer;
extern const struct lwm2m_reader cbor_reader;
int do_read_op_cbor(struct lwm2m_message *msg);
int do_write_op_cbor(struct lwm2m_message *msg);
#endif /* LWM2M_RW_CBOR_H_ */