blob: 3dd9d09e7317ef3feafaa44a44a1b06c41a6a14d [file] [log] [blame]
/*
* Copyright (c) 2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <zephyr/logging/log.h>
#include "lwm2m_engine.h"
#include "lwm2m_rw_link_format.h"
#include "lwm2m_util.h"
LOG_MODULE_REGISTER(net_lwm2m_link_format, CONFIG_LWM2M_LOG_LEVEL);
#define CORELINK_BUF_SIZE 24
#define ENABLER_VERSION "lwm2m=\"" LWM2M_PROTOCOL_VERSION_STRING "\""
/*
* TODO: to implement a way for clients to specify alternate path
* via Kconfig (LwM2M specification 8.2.2 Alternate Path)
*
* For now, in order to inform server we support JSON format, we have to
* report 'ct=11543' to the server. '</>' is required in order to append
* content attribute. And resource type attribute is appended because of
* Eclipse wakaama will reject the registration when 'rt="oma.lwm2m"' is
* missing.
*/
#if defined(CONFIG_LWM2M_RW_SENML_CBOR_SUPPORT)
#define REG_PREFACE "</>;ct=" STRINGIFY(LWM2M_FORMAT_APP_SENML_CBOR)
#elif defined(CONFIG_LWM2M_RW_SENML_JSON_SUPPORT)
#define REG_PREFACE "</>;ct=" STRINGIFY(LWM2M_FORMAT_APP_SEML_JSON)
#elif defined(CONFIG_LWM2M_RW_JSON_SUPPORT)
#define REG_PREFACE "</>;rt=\"oma.lwm2m\"" \
";ct=" STRINGIFY(LWM2M_FORMAT_OMA_JSON)
#else
#define REG_PREFACE ""
#endif
static int put_begin(struct lwm2m_output_context *out,
struct lwm2m_obj_path *path)
{
char init_string[MAX(sizeof(ENABLER_VERSION), sizeof(REG_PREFACE))] = "";
struct link_format_out_formatter_data *fd;
int ret;
ARG_UNUSED(path);
fd = engine_get_out_user_data(out);
if (fd == NULL) {
return -EINVAL;
}
switch (fd->mode) {
case LINK_FORMAT_MODE_DISCOVERY:
/* Nothing to add in device management mode. */
return 0;
case LINK_FORMAT_MODE_BOOTSTRAP_DISCOVERY:
strcpy(init_string, ENABLER_VERSION);
break;
case LINK_FORMAT_MODE_REGISTER:
/* No need to append content type */
if (strlen(REG_PREFACE) == 0) {
return 0;
}
strcpy(init_string, REG_PREFACE);
break;
}
ret = buf_append(CPKT_BUF_WRITE(out->out_cpkt), init_string,
strlen(init_string));
if (ret < 0) {
return ret;
}
fd->is_first = false;
return strlen(init_string);
}
static int put_corelink_separator(struct lwm2m_output_context *out)
{
char comma = ',';
int ret;
ret = buf_append(CPKT_BUF_WRITE(out->out_cpkt), &comma, sizeof(comma));
if (ret < 0) {
return ret;
}
return (int)sizeof(comma);
}
static int put_corelink_version(struct lwm2m_output_context *out,
const struct lwm2m_engine_obj *obj,
uint8_t *buf, uint16_t buflen)
{
int ret, len;
len = snprintk(buf, buflen, ";ver=%u.%u", obj->version_major,
obj->version_minor);
if (len < 0 || len >= buflen) {
return -ENOMEM;
}
ret = buf_append(CPKT_BUF_WRITE(out->out_cpkt), buf, len);
if (ret < 0) {
return ret;
}
return len;
}
static int put_corelink_dimension(struct lwm2m_output_context *out,
const struct lwm2m_engine_res *res,
uint8_t *buf, uint16_t buflen)
{
int ret, inst_count = 0, len = 0;
if (res->multi_res_inst) {
for (int i = 0; i < res->res_inst_count; i++) {
if (res->res_instances[i].res_inst_id !=
RES_INSTANCE_NOT_CREATED) {
inst_count++;
}
}
len = snprintk(buf, buflen, ";dim=%d", inst_count);
if (len < 0 || len >= buflen) {
return -ENOMEM;
}
ret = buf_append(CPKT_BUF_WRITE(out->out_cpkt), buf, len);
if (ret < 0) {
return ret;
}
}
return len;
}
static int put_attribute(struct lwm2m_output_context *out,
struct lwm2m_attr *attr, uint8_t *buf,
uint16_t buflen)
{
int used, ret;
const char *name = lwm2m_engine_get_attr_name(attr);
if (name == NULL) {
/* Invalid attribute, ignore. */
return 0;
}
if (attr->type <= LWM2M_ATTR_PMAX) {
used = snprintk(buf, buflen, ";%s=%d", name, attr->int_val);
} else {
uint8_t float_buf[32];
used = lwm2m_ftoa(&attr->float_val, float_buf,
sizeof(float_buf), 4);
if (used < 0 || used >= sizeof(float_buf)) {
return -ENOMEM;
}
used = snprintk(buf, buflen, ";%s=%s", name, float_buf);
}
if (used < 0 || used >= buflen) {
return -ENOMEM;
}
ret = buf_append(CPKT_BUF_WRITE(out->out_cpkt), buf, used);
if (ret < 0) {
return ret;
}
return used;
}
static int put_attributes(struct lwm2m_output_context *out,
struct lwm2m_attr **attrs, uint8_t *buf,
uint16_t buflen)
{
int ret;
int len = 0;
for (int i = 0; i < NR_LWM2M_ATTR; i++) {
if (attrs[i] == NULL) {
continue;
}
ret = put_attribute(out, attrs[i], buf, buflen);
if (ret < 0) {
return ret;
}
len += ret;
}
return len;
}
static void get_attributes(const void *ref, struct lwm2m_attr **attrs)
{
struct lwm2m_attr *attr = NULL;
while ((attr = lwm2m_engine_get_next_attr(ref, attr)) != NULL) {
if (attr->type >= NR_LWM2M_ATTR) {
continue;
}
attrs[attr->type] = attr;
}
}
static int put_corelink_attributes(struct lwm2m_output_context *out,
const void *ref, uint8_t *buf,
uint16_t buflen)
{
struct lwm2m_attr *attrs[NR_LWM2M_ATTR] = { 0 };
get_attributes(ref, attrs);
return put_attributes(out, attrs, buf, buflen);
}
/* Resource-level attribute request - should propagate attributes from Object
* and Object Instance.
*/
static int put_corelink_attributes_resource(struct lwm2m_output_context *out,
const struct lwm2m_obj_path *path,
uint8_t *buf, uint16_t buflen)
{
struct lwm2m_attr *attrs[NR_LWM2M_ATTR] = { 0 };
struct lwm2m_engine_obj *obj = lwm2m_engine_get_obj(path);
struct lwm2m_engine_obj_inst *obj_inst = lwm2m_engine_get_obj_inst(path);
struct lwm2m_engine_res *res = lwm2m_engine_get_res(path);
if (obj == NULL || obj_inst == NULL || res == NULL) {
return -ENOENT;
}
get_attributes(obj, attrs);
get_attributes(obj_inst, attrs);
get_attributes(res, attrs);
return put_attributes(out, attrs, buf, buflen);
}
static int put_corelink_ssid(struct lwm2m_output_context *out,
const struct lwm2m_obj_path *path,
uint8_t *buf, uint16_t buflen)
{
uint16_t server_id = 0;
int ret;
int len;
switch (path->obj_id) {
case LWM2M_OBJECT_SECURITY_ID: {
bool bootstrap_inst;
ret = snprintk(buf, buflen, "0/%d/1",
path->obj_inst_id);
if (ret < 0 || ret >= buflen) {
return -ENOMEM;
}
ret = lwm2m_engine_get_bool(buf, &bootstrap_inst);
if (ret < 0) {
return ret;
}
/* Bootstrap Security object instance does not have associated
* Server object instance, so do not report ssid for it.
*/
if (bootstrap_inst) {
return 0;
}
ret = snprintk(buf, buflen, "0/%d/10",
path->obj_inst_id);
if (ret < 0 || ret >= buflen) {
return -ENOMEM;
}
ret = lwm2m_engine_get_u16(buf, &server_id);
if (ret < 0) {
return ret;
}
break;
}
case LWM2M_OBJECT_SERVER_ID:
ret = snprintk(buf, buflen, "1/%d/0", path->obj_inst_id);
if (ret < 0 || ret >= buflen) {
return -ENOMEM;
}
ret = lwm2m_engine_get_u16(buf, &server_id);
if (ret < 0) {
return ret;
}
break;
default:
LOG_ERR("Invalid object ID for ssid attribute: %d",
path->obj_id);
return -EINVAL;
}
len = snprintk(buf, buflen, ";ssid=%d", server_id);
if (len < 0 || len >= buflen) {
return -ENOMEM;
}
ret = buf_append(CPKT_BUF_WRITE(out->out_cpkt), buf, len);
if (ret < 0) {
return ret;
}
return len;
}
static int put_obj_corelink(struct lwm2m_output_context *out,
const struct lwm2m_obj_path *path,
struct link_format_out_formatter_data *fd)
{
char obj_buf[CORELINK_BUF_SIZE];
struct lwm2m_engine_obj *obj;
int len = 0;
int ret;
ret = snprintk(obj_buf, sizeof(obj_buf), "</%u>", path->obj_id);
if (ret < 0 || ret >= sizeof(obj_buf)) {
return -ENOMEM;
}
len += ret;
ret = buf_append(CPKT_BUF_WRITE(out->out_cpkt), obj_buf, len);
if (ret < 0) {
return ret;
}
obj = lwm2m_engine_get_obj(path);
if (obj == NULL) {
return -EINVAL;
}
if (lwm2m_engine_shall_report_obj_version(obj)) {
ret = put_corelink_version(out, obj, obj_buf, sizeof(obj_buf));
if (ret < 0) {
return ret;
}
len += ret;
}
if (fd->mode == LINK_FORMAT_MODE_DISCOVERY) {
/* Report object attributes only in device management mode
* (5.4.2).
*/
ret = put_corelink_attributes(out, obj, obj_buf,
sizeof(obj_buf));
if (ret < 0) {
return ret;
}
len += ret;
}
return len;
}
static int put_obj_inst_corelink(struct lwm2m_output_context *out,
const struct lwm2m_obj_path *path,
struct link_format_out_formatter_data *fd)
{
char obj_buf[CORELINK_BUF_SIZE];
int len = 0;
int ret;
ret = snprintk(obj_buf, sizeof(obj_buf), "</%u/%u>",
path->obj_id, path->obj_inst_id);
if (ret < 0 || ret >= sizeof(obj_buf)) {
return -ENOMEM;
}
len += ret;
ret = buf_append(CPKT_BUF_WRITE(out->out_cpkt), obj_buf, len);
if (ret < 0) {
return ret;
}
if (fd->mode == LINK_FORMAT_MODE_REGISTER) {
return len;
}
/* Bootstrap object instance corelink shall only contain ssid
* parameter for Security and Server objects (5.2.7.3).
*/
if (fd->mode == LINK_FORMAT_MODE_BOOTSTRAP_DISCOVERY) {
if (path->obj_id == LWM2M_OBJECT_SECURITY_ID ||
path->obj_id == LWM2M_OBJECT_SERVER_ID) {
ret = put_corelink_ssid(out, path, obj_buf,
sizeof(obj_buf));
if (ret < 0) {
return ret;
}
len += ret;
}
return len;
}
/* Report object instance attributes only when Instance
* ID was specified (5.4.2).
*/
if (fd->request_level == LWM2M_PATH_LEVEL_OBJECT_INST) {
struct lwm2m_engine_obj_inst *obj_inst =
lwm2m_engine_get_obj_inst(path);
if (obj_inst == NULL) {
return -EINVAL;
}
ret = put_corelink_attributes(out, obj_inst, obj_buf,
sizeof(obj_buf));
if (ret < 0) {
return ret;
}
len += ret;
}
return len;
}
static int put_res_corelink(struct lwm2m_output_context *out,
const struct lwm2m_obj_path *path,
struct link_format_out_formatter_data *fd)
{
char obj_buf[CORELINK_BUF_SIZE];
int len = 0;
int ret;
if (fd->mode != LINK_FORMAT_MODE_DISCOVERY) {
/* Report resources only in device management discovery. */
return 0;
}
ret = snprintk(obj_buf, sizeof(obj_buf), "</%u/%u/%u>", path->obj_id,
path->obj_inst_id, path->res_id);
if (ret < 0 || ret >= sizeof(obj_buf)) {
return -ENOMEM;
}
len += ret;
ret = buf_append(CPKT_BUF_WRITE(out->out_cpkt), obj_buf, len);
if (ret < 0) {
return ret;
}
/* Report resource attrs when at least object instance was specified
* (5.4.2).
*/
if (fd->request_level >= LWM2M_PATH_LEVEL_OBJECT_INST) {
struct lwm2m_engine_res *res = lwm2m_engine_get_res(path);
if (res == NULL) {
return -EINVAL;
}
ret = put_corelink_dimension(out, res, obj_buf,
sizeof(obj_buf));
if (ret < 0) {
return ret;
}
len += ret;
if (fd->request_level == LWM2M_PATH_LEVEL_RESOURCE) {
ret = put_corelink_attributes_resource(
out, path, obj_buf, sizeof(obj_buf));
if (ret < 0) {
return ret;
}
} else {
ret = put_corelink_attributes(
out, res, obj_buf, sizeof(obj_buf));
if (ret < 0) {
return ret;
}
}
len += ret;
}
return len;
}
static int put_res_inst_corelink(struct lwm2m_output_context *out,
const struct lwm2m_obj_path *path,
struct link_format_out_formatter_data *fd)
{
char obj_buf[CORELINK_BUF_SIZE];
int len = 0;
int ret;
if (fd->mode != LINK_FORMAT_MODE_DISCOVERY) {
/* Report resources instances only in device management
* discovery.
*/
return 0;
}
ret = snprintk(obj_buf, sizeof(obj_buf), "</%u/%u/%u/%u>", path->obj_id,
path->obj_inst_id, path->res_id, path->res_inst_id);
if (ret < 0 || ret >= sizeof(obj_buf)) {
return -ENOMEM;
}
len += ret;
ret = buf_append(CPKT_BUF_WRITE(out->out_cpkt), obj_buf, len);
if (ret < 0) {
return ret;
}
/* Report resource instance attrs only when resource was specified. */
if (fd->request_level == LWM2M_PATH_LEVEL_RESOURCE) {
struct lwm2m_engine_res_inst *res_inst =
lwm2m_engine_get_res_inst(path);
if (res_inst == NULL) {
return -EINVAL;
}
ret = put_corelink_attributes(out, res_inst, obj_buf,
sizeof(obj_buf));
if (ret < 0) {
return ret;
}
len += ret;
}
return len;
}
static int put_corelink(struct lwm2m_output_context *out,
const struct lwm2m_obj_path *path)
{
struct link_format_out_formatter_data *fd;
int len = 0;
int ret;
fd = engine_get_out_user_data(out);
if (fd == NULL) {
return -EINVAL;
}
if (fd->is_first) {
fd->is_first = false;
} else {
ret = put_corelink_separator(out);
if (ret < 0) {
return ret;
}
len += ret;
}
switch (path->level) {
case LWM2M_PATH_LEVEL_OBJECT:
ret = put_obj_corelink(out, path, fd);
if (ret < 0) {
return ret;
}
len += ret;
break;
case LWM2M_PATH_LEVEL_OBJECT_INST:
ret = put_obj_inst_corelink(out, path, fd);
if (ret < 0) {
return ret;
}
len += ret;
break;
case LWM2M_PATH_LEVEL_RESOURCE:
ret = put_res_corelink(out, path, fd);
if (ret < 0) {
return ret;
}
len += ret;
break;
case LWM2M_PATH_LEVEL_RESOURCE_INST:
if (IS_ENABLED(CONFIG_LWM2M_VERSION_1_1)) {
ret = put_res_inst_corelink(out, path, fd);
if (ret < 0) {
return ret;
}
len += ret;
break;
}
__fallthrough;
default:
LOG_ERR("Invalid corelink path level: %d", path->level);
return -EINVAL;
}
return len;
}
const struct lwm2m_writer link_format_writer = {
.put_begin = put_begin,
.put_corelink = put_corelink,
};
int do_discover_op_link_format(struct lwm2m_message *msg, bool is_bootstrap)
{
struct link_format_out_formatter_data fd;
int ret;
fd.is_first = true;
fd.mode = is_bootstrap ? LINK_FORMAT_MODE_BOOTSTRAP_DISCOVERY :
LINK_FORMAT_MODE_DISCOVERY;
fd.request_level = msg->path.level;
engine_set_out_user_data(&msg->out, &fd);
ret = lwm2m_discover_handler(msg, is_bootstrap);
engine_clear_out_user_data(&msg->out);
return ret;
}
int do_register_op_link_format(struct lwm2m_message *msg)
{
struct link_format_out_formatter_data fd;
int ret;
fd.is_first = true;
fd.mode = LINK_FORMAT_MODE_REGISTER;
fd.request_level = LWM2M_PATH_LEVEL_NONE;
engine_set_out_user_data(&msg->out, &fd);
ret = lwm2m_register_payload_handler(msg);
engine_clear_out_user_data(&msg->out);
return ret;
}