net: lwm2m: Add application/link-format content writer

This commit adds a new content writer, application/link-format, which
can be used during the Discovery procedure, to fill the content of the
response payload.

Introducing this new content writer, which encapsulates some of the
details like attribute handling which is different for bootstrap/regular
discovery, allows to unify the discovery handler in the lwm2m_engine,
thus it's no longer needed to have spearate handler functions for
bootstrap/regular discovery.

Signed-off-by: Robert Lubos <robert.lubos@nordicsemi.no>
This commit is contained in:
Robert Lubos 2021-01-25 13:49:33 +01:00 committed by Carles Cufí
commit b17555eb9e
6 changed files with 576 additions and 300 deletions

View file

@ -40,6 +40,7 @@ LOG_MODULE_REGISTER(LOG_MODULE_NAME);
#include "lwm2m_object.h"
#include "lwm2m_engine.h"
#include "lwm2m_rw_link_format.h"
#include "lwm2m_rw_plain_text.h"
#include "lwm2m_rw_oma_tlv.h"
#ifdef CONFIG_LWM2M_RW_JSON_SUPPORT
@ -1165,7 +1166,7 @@ static int select_writer(struct lwm2m_output_context *out, uint16_t accept)
switch (accept) {
case LWM2M_FORMAT_APP_LINK_FORMAT:
/* TODO: rewrite do_discover as content formatter */
out->writer = &link_format_writer;
break;
case LWM2M_FORMAT_PLAIN_TEXT:
@ -3246,86 +3247,22 @@ move_forward:
return ret;
}
static int print_attr(struct lwm2m_output_context *out,
uint8_t *buf, uint16_t buflen, void *ref)
int lwm2m_discover_handler(struct lwm2m_message *msg, bool is_bootstrap)
{
struct lwm2m_attr *attr;
int i, used, base, ret;
uint8_t digit;
int32_t fraction;
for (i = 0; i < CONFIG_LWM2M_NUM_ATTR; i++) {
if (ref != write_attr_pool[i].ref) {
continue;
}
attr = write_attr_pool + i;
/* assuming integer will have float_val.val2 set as 0 */
used = snprintk(buf, buflen, ";%s=%s%d%s",
LWM2M_ATTR_STR[attr->type],
attr->float_val.val1 == 0 &&
attr->float_val.val2 < 0 ? "-" : "",
attr->float_val.val1,
attr->float_val.val2 != 0 ? "." : "");
base = 100000;
fraction = attr->float_val.val2 < 0 ?
-attr->float_val.val2 : attr->float_val.val2;
while (fraction && used < buflen && base > 0) {
digit = fraction / base;
buf[used++] = '0' + digit;
fraction -= digit * base;
base /= 10;
}
ret = buf_append(CPKT_BUF_WRITE(out->out_cpkt), buf, used);
if (ret < 0) {
return ret;
}
}
return 0;
}
static int print_resource_dimension(struct lwm2m_output_context *out,
uint8_t *buf, uint16_t buflen,
struct lwm2m_engine_res *res)
{
int ret, i, inst_count = 0;
if (res->multi_res_inst) {
for (i = 0; i < res->res_inst_count; i++) {
if (res->res_instances[i].res_inst_id !=
RES_INSTANCE_NOT_CREATED) {
inst_count++;
}
}
snprintk(buf, buflen, ";dim=%d", inst_count);
ret = buf_append(CPKT_BUF_WRITE(out->out_cpkt), buf,
strlen(buf));
if (ret < 0) {
return ret;
}
}
return 0;
}
static int do_discover_op(struct lwm2m_message *msg)
{
static char disc_buf[24];
struct lwm2m_engine_obj *obj;
struct lwm2m_engine_obj_inst *obj_inst;
int ret;
bool reported = false;
/* object ID is required in Device Management Discovery (5.4.2).
*/
if (msg->path.level == 0U ||
msg->path.obj_id == LWM2M_OBJECT_SECURITY_ID) {
/* Object ID is required in Device Management Discovery (5.4.2). */
if (!is_bootstrap &&
(msg->path.level == LWM2M_PATH_LEVEL_NONE ||
msg->path.obj_id == LWM2M_OBJECT_SECURITY_ID)) {
return -EPERM;
}
/* Bootstrap discovery allows to specify at most Object ID. */
if (is_bootstrap && msg->path.level > LWM2M_PATH_LEVEL_OBJECT) {
return -EPERM;
}
@ -3343,117 +3280,117 @@ static int do_discover_op(struct lwm2m_message *msg)
return ret;
}
/* Report object attributes only when Object ID (alone) was
* provided (and do it only once in case of multiple instances).
/*
* Add required prefix for bootstrap discovery (5.2.7.3).
* For device management discovery, `engine_put_begin()` adds nothing.
*/
if (msg->path.level == 1) {
SYS_SLIST_FOR_EACH_CONTAINER(&engine_obj_list, obj, node) {
if (obj->obj_id != msg->path.obj_id) {
continue;
}
engine_put_begin(&msg->out, &msg->path);
snprintk(disc_buf, sizeof(disc_buf), "%s</%u>",
reported ? "," : "", obj->obj_id);
SYS_SLIST_FOR_EACH_CONTAINER(&engine_obj_list, obj, node) {
/* Skip unrelated objects */
if (msg->path.level > 0 && msg->path.obj_id != obj->obj_id) {
continue;
}
ret = buf_append(CPKT_BUF_WRITE(msg->out.out_cpkt),
disc_buf, strlen(disc_buf));
if (ret < 0) {
return ret;
}
/* For bootstrap discover, only report object ID when no
* instance is available.
* For device management discovery, only report object ID with
* attributes if object ID (alone) was provided.
*/
if ((is_bootstrap && obj->instance_count == 0U) ||
(!is_bootstrap && msg->path.level == LWM2M_PATH_LEVEL_OBJECT)) {
struct lwm2m_obj_path path = {
.obj_id = obj->obj_id,
.level = LWM2M_PATH_LEVEL_OBJECT,
};
/* report object attrs (5.4.2) */
ret = print_attr(&msg->out, disc_buf, sizeof(disc_buf),
obj);
ret = engine_put_corelink(&msg->out, &path);
if (ret < 0) {
return ret;
}
reported = true;
break;
}
}
SYS_SLIST_FOR_EACH_CONTAINER(&engine_obj_inst_list, obj_inst, node) {
if (obj_inst->obj->obj_id != msg->path.obj_id) {
continue;
if (is_bootstrap) {
continue;
}
}
/* skip unrelated object instance */
if (msg->path.level > 1 &&
msg->path.obj_inst_id != obj_inst->obj_inst_id) {
continue;
}
/* Report object instances only if Resource ID is missing. */
if (msg->path.level <= 2U) {
snprintk(disc_buf, sizeof(disc_buf), "%s</%u/%u>",
reported ? "," : "",
obj_inst->obj->obj_id, obj_inst->obj_inst_id);
ret = buf_append(CPKT_BUF_WRITE(msg->out.out_cpkt),
disc_buf, strlen(disc_buf));
if (ret < 0) {
return ret;
SYS_SLIST_FOR_EACH_CONTAINER(&engine_obj_inst_list,
obj_inst, node) {
if (obj_inst->obj->obj_id != obj->obj_id) {
continue;
}
/* Report object instance attributes only when Instance
* ID was specified (5.4.2).
/* Skip unrelated object instance. */
if (msg->path.level > LWM2M_PATH_LEVEL_OBJECT &&
msg->path.obj_inst_id != obj_inst->obj_inst_id) {
continue;
}
/* Report object instances only if no Resource ID is
* provided.
*/
if (msg->path.level == 2U) {
ret = print_attr(&msg->out, disc_buf,
sizeof(disc_buf), obj_inst);
if (msg->path.level <= LWM2M_PATH_LEVEL_OBJECT_INST) {
struct lwm2m_obj_path path = {
.obj_id = obj_inst->obj->obj_id,
.obj_inst_id = obj_inst->obj_inst_id,
.level = LWM2M_PATH_LEVEL_OBJECT_INST,
};
ret = engine_put_corelink(&msg->out, &path);
if (ret < 0) {
return ret;
}
reported = true;
}
reported = true;
}
for (int i = 0; i < obj_inst->resource_count; i++) {
/* skip unrelated resources */
if (msg->path.level == 3U &&
msg->path.res_id != obj_inst->resources[i].res_id) {
/* Do not report resources in bootstrap discovery. */
if (is_bootstrap) {
continue;
}
snprintk(disc_buf, sizeof(disc_buf),
"%s</%u/%u/%u>",
reported ? "," : "",
obj_inst->obj->obj_id,
obj_inst->obj_inst_id,
obj_inst->resources[i].res_id);
for (int i = 0; i < obj_inst->resource_count; i++) {
/* Skip unrelated resources. */
if (msg->path.level == LWM2M_PATH_LEVEL_RESOURCE &&
msg->path.res_id != obj_inst->resources[i].res_id) {
continue;
}
ret = buf_append(CPKT_BUF_WRITE(msg->out.out_cpkt),
disc_buf, strlen(disc_buf));
if (ret < 0) {
return ret;
}
struct lwm2m_obj_path path = {
.obj_id = obj_inst->obj->obj_id,
.obj_inst_id = obj_inst->obj_inst_id,
.res_id = obj_inst->resources[i].res_id,
.level = LWM2M_PATH_LEVEL_RESOURCE,
};
/* report resource attrs when path > 1 (5.4.2) */
if (msg->path.level > 1) {
ret = print_resource_dimension(
&msg->out, disc_buf, sizeof(disc_buf),
&obj_inst->resources[i]);
ret = engine_put_corelink(&msg->out, &path);
if (ret < 0) {
return ret;
}
ret = print_attr(&msg->out,
disc_buf, sizeof(disc_buf),
&obj_inst->resources[i]);
if (ret < 0) {
return ret;
}
reported = true;
}
reported = true;
}
}
return reported ? 0 : -ENOENT;
}
static int do_discover_op(struct lwm2m_message *msg, uint16_t content_format)
{
switch (content_format) {
case LWM2M_FORMAT_APP_LINK_FORMAT:
return do_discover_op_link_format(
msg, msg->ctx->bootstrap_mode);
default:
LOG_ERR("Unsupported format: %u", content_format);
return -ENOMSG;
}
}
int lwm2m_get_or_create_engine_obj(struct lwm2m_message *msg,
struct lwm2m_engine_obj_inst **obj_inst,
uint8_t *created)
@ -3628,149 +3565,6 @@ static int bootstrap_delete(struct lwm2m_message *msg)
return ret;
}
static inline int bs_discover_fill_enabler(struct lwm2m_message *msg)
{
char buf[sizeof("lwm2m='" LWM2M_PROTOCOL_VERSION "'")];
snprintk(buf, sizeof(buf), "lwm2m=\"%s\"", LWM2M_PROTOCOL_VERSION);
return buf_append(CPKT_BUF_WRITE(msg->out.out_cpkt), buf, strlen(buf));
}
static inline int bs_discover_fill_object(struct lwm2m_engine_obj *obj,
struct lwm2m_message *msg)
{
char buf[sizeof(",</xxxxx>")];
snprintk(buf, sizeof(buf), ",</%u>", obj->obj_id);
return buf_append(CPKT_BUF_WRITE(msg->out.out_cpkt), buf, strlen(buf));
}
static int bs_discover_fill_instance(struct lwm2m_engine_obj_inst *obj_inst,
struct lwm2m_message *msg)
{
char buf[sizeof(",</xxxxx/xxxxx>")];
bool bootstrap_inst;
uint16_t server_id;
int ret;
snprintk(buf, sizeof(buf), ",</%u/%u>", obj_inst->obj->obj_id,
obj_inst->obj_inst_id);
ret = buf_append(CPKT_BUF_WRITE(msg->out.out_cpkt), buf, strlen(buf));
if (ret < 0) {
return ret;
}
/* Security and Server instnaces shall report Short Server ID associated
* with them (but only if not bootstrap instance).
*/
if (obj_inst->obj->obj_id == LWM2M_OBJECT_SECURITY_ID) {
snprintk(buf, sizeof(buf), "0/%d/1", obj_inst->obj_inst_id);
ret = lwm2m_engine_get_bool(buf, &bootstrap_inst);
if (ret < 0) {
return ret;
}
if (!bootstrap_inst) {
snprintk(buf, sizeof(buf), "0/%d/10",
obj_inst->obj_inst_id);
ret = lwm2m_engine_get_u16(buf, &server_id);
if (ret < 0) {
return ret;
}
snprintk(buf, sizeof(buf), ";ssid=%d", server_id);
ret = buf_append(CPKT_BUF_WRITE(msg->out.out_cpkt),
buf, strlen(buf));
if (ret < 0) {
return ret;
}
}
}
if (obj_inst->obj->obj_id == LWM2M_OBJECT_SERVER_ID) {
snprintk(buf, sizeof(buf), "1/%d/0", obj_inst->obj_inst_id);
ret = lwm2m_engine_get_u16(buf, &server_id);
if (ret < 0) {
return ret;
}
snprintk(buf, sizeof(buf), ";ssid=%d", server_id);
ret = buf_append(CPKT_BUF_WRITE(msg->out.out_cpkt),
buf, strlen(buf));
if (ret < 0) {
return ret;
}
}
return 0;
}
static int bootstrap_discover(struct lwm2m_message *msg)
{
struct lwm2m_engine_obj *obj;
struct lwm2m_engine_obj_inst *obj_inst;
int ret;
bool reported = false;
/* set output content-format */
ret = coap_append_option_int(msg->out.out_cpkt,
COAP_OPTION_CONTENT_FORMAT,
LWM2M_FORMAT_APP_LINK_FORMAT);
if (ret < 0) {
LOG_ERR("Error setting response content-format: %d", ret);
return ret;
}
ret = coap_packet_append_payload_marker(msg->out.out_cpkt);
if (ret < 0) {
return ret;
}
/*
* lwm2m spec 20170208-A sec 5.2.7.3 bootstrap discover on "/"
* - prefixed w/ lwm2m enabler version. e.g. lwm2m="1.0"
* - returns object and object instances only
*/
ret = bs_discover_fill_enabler(msg);
if (ret < 0) {
return ret;
}
SYS_SLIST_FOR_EACH_CONTAINER(&engine_obj_list, obj, node) {
if (msg->path.level > 0 && msg->path.obj_id != obj->obj_id) {
continue;
}
/* Only report </OBJ_ID> when no instance available */
if (obj->instance_count == 0U) {
ret = bs_discover_fill_object(obj, msg);
if (ret < 0) {
return ret;
}
reported = true;
continue;
}
SYS_SLIST_FOR_EACH_CONTAINER(&engine_obj_inst_list,
obj_inst, node) {
if (obj_inst->obj->obj_id != obj->obj_id) {
continue;
}
ret = bs_discover_fill_instance(obj_inst, msg);
if (ret < 0) {
return ret;
}
reported = true;
}
}
return reported ? 0 : -ENOENT;
}
#endif
static int handle_request(struct coap_packet *request,
@ -4074,13 +3868,7 @@ static int handle_request(struct coap_packet *request,
break;
case LWM2M_OP_DISCOVER:
#if defined(CONFIG_LWM2M_RD_CLIENT_SUPPORT_BOOTSTRAP)
if (msg->ctx->bootstrap_mode) {
r = bootstrap_discover(msg);
break;
}
#endif
r = do_discover_op(msg);
r = do_discover_op(msg, accept);
break;
case LWM2M_OP_WRITE: