/* * Copyright (c) 2021 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #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) */ /* * In order to inform the server about the configured LwM2M content format, we have * to report 'ct=' with the content type value to the server. The root path '' * is required in order to append the content type attribute. */ #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 ";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 = lwm2m_get_bool(&LWM2M_OBJ(0, path->obj_inst_id, 1), &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; } if (ret < 0 || ret >= buflen) { return -ENOMEM; } ret = lwm2m_get_u16(&LWM2M_OBJ(0, path->obj_inst_id, 10), &server_id); if (ret < 0) { return ret; } break; } case LWM2M_OBJECT_SERVER_ID: ret = lwm2m_get_u16(&LWM2M_OBJ(1, path->obj_inst_id, 0), &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), "", 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), "", 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), "", 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), "", 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; }