blob: ae8c1e8cc31c7cd1ca08befee0c53ec31beee4fa [file] [log] [blame]
/*
* Copyright (c) 2017 Linaro Limited
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
* Copyright (c) 2015, Yanzi Networks AB.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Original Authors:
* Joakim Eriksson <joakime@sics.se>
* Niclas Finne <nfi@sics.se>
*/
/*
* Zephyr Contribution by Michael Scott <michael.scott@linaro.org>
* - Zephyr code style changes / code cleanup
* - Move to Zephyr APIs where possible
* - Convert to Zephyr int/uint types
* - Remove engine dependency (replace with writer context)
* - Add write int64 function
*/
/*
* TODO:
* - Lots of byte-order API clean up
* - Var / parameter type cleanup
* - Replace magic #'s with defines
*/
#define SYS_LOG_DOMAIN "lib/lwm2m_oma_tlv"
#define SYS_LOG_LEVEL CONFIG_SYS_LOG_LWM2M_LEVEL
#include <logging/sys_log.h>
#include <string.h>
#include <stdint.h>
#include <misc/byteorder.h>
#include "lwm2m_rw_oma_tlv.h"
#include "lwm2m_engine.h"
enum {
OMA_TLV_TYPE_OBJECT_INSTANCE = 0,
OMA_TLV_TYPE_RESOURCE_INSTANCE = 1,
OMA_TLV_TYPE_MULTI_RESOURCE = 2,
OMA_TLV_TYPE_RESOURCE = 3
};
struct oma_tlv {
u8_t type;
u16_t id; /* can be 8-bit or 16-bit when serialized */
u32_t length;
};
static u8_t get_len_type(const struct oma_tlv *tlv)
{
if (tlv->length < 8) {
return 0;
} else if (tlv->length < 0x100) {
return 1;
} else if (tlv->length < 0x10000) {
return 2;
}
return 3;
}
static u8_t tlv_calc_type(u8_t flags)
{
return flags & WRITER_RESOURCE_INSTANCE ?
OMA_TLV_TYPE_RESOURCE_INSTANCE : OMA_TLV_TYPE_RESOURCE;
}
static u16_t tlv_calc_id(u8_t flags, struct lwm2m_obj_path *path)
{
return flags & WRITER_RESOURCE_INSTANCE ?
path->res_inst_id : path->res_id;
}
static void tlv_setup(struct oma_tlv *tlv, u8_t type, u16_t id,
u32_t buflen)
{
if (tlv) {
tlv->type = type;
tlv->id = id;
tlv->length = buflen;
}
}
static int oma_tlv_put_u8(struct lwm2m_output_context *out,
u8_t value, bool insert)
{
if (insert) {
if (!net_pkt_insert(out->out_cpkt->pkt, out->frag,
out->offset, 1, &value,
BUF_ALLOC_TIMEOUT)) {
return -ENOMEM;
}
out->offset++;
} else {
out->frag = net_pkt_write(out->out_cpkt->pkt, out->frag,
out->offset, &out->offset, 1, &value,
BUF_ALLOC_TIMEOUT);
if (!out->frag && out->offset == 0xffff) {
return -ENOMEM;
}
}
return 0;
}
static size_t oma_tlv_put(const struct oma_tlv *tlv,
struct lwm2m_output_context *out,
u8_t *value, bool insert)
{
size_t pos;
int ret, i;
u8_t len_type, tmp;
/* len_type is the same as number of bytes required for length */
len_type = get_len_type(tlv);
/* first type byte in TLV header */
tmp = (tlv->type << 6) |
(tlv->id > 255 ? (1 << 5) : 0) |
(len_type << 3) |
(len_type == 0 ? tlv->length : 0);
ret = oma_tlv_put_u8(out, tmp, insert);
if (ret < 0) {
/* TODO: Generate error? */
return 0;
}
pos = 1;
/* The ID */
if (tlv->id > 255) {
ret = oma_tlv_put_u8(out, (tlv->id >> 8) & 0xff, insert);
if (ret < 0) {
/* TODO: Generate error? */
return 0;
}
pos++;
}
ret = oma_tlv_put_u8(out, tlv->id & 0xff, insert);
if (ret < 0) {
/* TODO: Generate error? */
return 0;
}
pos++;
for (i = 2; i >= 0; i--) {
if (len_type > i) {
ret = oma_tlv_put_u8(out,
(tlv->length >> (i * 8)) & 0xff,
insert);
if (ret < 0) {
/* TODO: Generate error? */
return 0;
}
pos++;
}
}
/* finally add the value */
if (value != NULL && tlv->length > 0) {
if (insert) {
if (!net_pkt_insert(out->out_cpkt->pkt, out->frag,
out->offset, tlv->length, value,
BUF_ALLOC_TIMEOUT)) {
/* TODO: Generate error? */
return 0;
}
out->offset += tlv->length;
} else {
out->frag = net_pkt_write(out->out_cpkt->pkt, out->frag,
out->offset, &out->offset,
tlv->length, value,
BUF_ALLOC_TIMEOUT);
if (!out->frag && out->offset == 0xffff) {
/* TODO: Generate error? */
return 0;
}
}
}
return pos + tlv->length;
}
static size_t oma_tlv_get(struct oma_tlv *tlv,
struct lwm2m_input_context *in,
bool dont_advance)
{
struct net_buf *tmp_frag;
u8_t len_type;
u8_t len_pos = 1;
size_t tlv_len;
u16_t tmp_offset;
u8_t buf[2];
tmp_frag = in->frag;
tmp_offset = in->offset;
tmp_frag = net_frag_read_u8(tmp_frag, tmp_offset, &tmp_offset, &buf[0]);
if (!tmp_frag && tmp_offset == 0xffff) {
goto error;
}
tlv->type = (buf[0] >> 6) & 3;
len_type = (buf[0] >> 3) & 3;
len_pos = 1 + (((buf[0] & (1 << 5)) != 0) ? 2 : 1);
tmp_frag = net_frag_read_u8(tmp_frag, tmp_offset, &tmp_offset, &buf[1]);
if (!tmp_frag && tmp_offset == 0xffff) {
return 0;
}
tlv->id = buf[1];
/* if len_pos > 2 it means that there are more ID to read */
if (len_pos > 2) {
tmp_frag = net_frag_read_u8(tmp_frag, tmp_offset,
&tmp_offset, &buf[1]);
if (!tmp_frag && tmp_offset == 0xffff) {
goto error;
}
tlv->id = (tlv->id << 8) + buf[1];
}
if (len_type == 0) {
tlv_len = buf[0] & 7;
} else {
/* read the length */
tlv_len = 0;
while (len_type > 0) {
tmp_frag = net_frag_read_u8(tmp_frag, tmp_offset,
&tmp_offset, &buf[1]);
if (!tmp_frag && tmp_offset == 0xffff) {
goto error;
}
len_pos++;
tlv_len = tlv_len << 8 | buf[1];
len_type--;
}
}
/* and read out the data??? */
tlv->length = tlv_len;
if (!dont_advance) {
in->frag = tmp_frag;
in->offset = tmp_offset;
}
return len_pos + tlv_len;
error:
/* TODO: Generate error? */
if (!dont_advance) {
in->frag = tmp_frag;
in->offset = tmp_offset;
}
return 0;
}
static size_t put_begin_ri(struct lwm2m_output_context *out,
struct lwm2m_obj_path *path)
{
/* set some flags in state */
out->writer_flags |= WRITER_RESOURCE_INSTANCE;
/*
* store position for inserting TLV when we know the length
*/
out->mark_frag_ri = out->frag;
out->mark_pos_ri = out->offset;
return 0;
}
static size_t put_end_ri(struct lwm2m_output_context *out,
struct lwm2m_obj_path *path)
{
struct oma_tlv tlv;
struct net_buf *tmp_frag;
u16_t tmp_pos;
u32_t len = 0;
out->writer_flags &= ~WRITER_RESOURCE_INSTANCE;
/* loop through buffers and add lengths */
tmp_frag = out->mark_frag_ri;
tmp_pos = out->mark_pos_ri;
while (tmp_frag != out->frag) {
len += tmp_frag->len - tmp_pos;
tmp_pos = 0;
}
len += out->offset - tmp_pos;
/* backup out location */
tmp_frag = out->frag;
tmp_pos = out->offset;
/* use stored location */
out->frag = out->mark_frag_ri;
out->offset = out->mark_pos_ri;
/* update the length */
tlv_setup(&tlv, OMA_TLV_TYPE_MULTI_RESOURCE, path->res_id,
len);
len = oma_tlv_put(&tlv, out, NULL, true) - tlv.length;
/* restore out location + newly inserted */
out->frag = tmp_frag;
out->offset = tmp_pos + len;
return 0;
}
static size_t put_s8(struct lwm2m_output_context *out,
struct lwm2m_obj_path *path, s8_t value)
{
size_t len;
struct oma_tlv tlv;
tlv_setup(&tlv, tlv_calc_type(out->writer_flags),
tlv_calc_id(out->writer_flags, path), sizeof(value));
len = oma_tlv_put(&tlv, out, (u8_t *)&value, false);
return len;
}
static size_t put_s16(struct lwm2m_output_context *out,
struct lwm2m_obj_path *path, s16_t value)
{
size_t len;
struct oma_tlv tlv;
s16_t net_value;
net_value = sys_cpu_to_be16(value);
tlv_setup(&tlv, tlv_calc_type(out->writer_flags),
tlv_calc_id(out->writer_flags, path), sizeof(net_value));
len = oma_tlv_put(&tlv, out, (u8_t *)&net_value, false);
return len;
}
static size_t put_s32(struct lwm2m_output_context *out,
struct lwm2m_obj_path *path, s32_t value)
{
size_t len;
struct oma_tlv tlv;
s32_t net_value;
net_value = sys_cpu_to_be32(value);
tlv_setup(&tlv, tlv_calc_type(out->writer_flags),
tlv_calc_id(out->writer_flags, path), sizeof(net_value));
len = oma_tlv_put(&tlv, out, (u8_t *)&net_value, false);
return len;
}
static size_t put_s64(struct lwm2m_output_context *out,
struct lwm2m_obj_path *path, s64_t value)
{
size_t len;
struct oma_tlv tlv;
s64_t net_value;
net_value = sys_cpu_to_be64(value);
tlv_setup(&tlv, tlv_calc_type(out->writer_flags),
tlv_calc_id(out->writer_flags, path), sizeof(net_value));
len = oma_tlv_put(&tlv, out, (u8_t *)&net_value, false);
return len;
}
static size_t put_string(struct lwm2m_output_context *out,
struct lwm2m_obj_path *path,
char *buf, size_t buflen)
{
size_t len;
struct oma_tlv tlv;
tlv_setup(&tlv, tlv_calc_type(out->writer_flags),
path->res_id, (u32_t)buflen);
len = oma_tlv_put(&tlv, out, (u8_t *)buf, false);
return len;
}
/* use binary32 format */
static size_t put_float32fix(struct lwm2m_output_context *out,
struct lwm2m_obj_path *path,
float32_value_t *value)
{
size_t len;
struct oma_tlv tlv;
int e = 0;
s32_t val = 0;
s32_t v;
u8_t b[4];
/*
* TODO: Currently, there is no standard API for handling the decimal
* portion of the float32_value structure. In the future, we should use
* the value->val2 (decimal portion) to set the decimal mask and in the
* following binary float calculations.
*
* HACK BELOW: hard code the decimal mask to 0 (whole number)
*/
int bits = 0;
v = value->val1;
if (v < 0) {
v = -v;
}
while (v > 1) {
val = (val >> 1);
if (v & 1) {
val = val | (1L << 22);
}
v = (v >> 1);
e++;
}
/* convert to the thing we should have */
e = e - bits + 127;
if (value->val1 == 0) {
e = 0;
}
/* is this the right byte order? */
b[0] = (value->val1 < 0 ? 0x80 : 0) | (e >> 1);
b[1] = ((e & 1) << 7) | ((val >> 16) & 0x7f);
b[2] = (val >> 8) & 0xff;
b[3] = val & 0xff;
tlv_setup(&tlv, tlv_calc_type(out->writer_flags),
tlv_calc_id(out->writer_flags, path), 4);
len = oma_tlv_put(&tlv, out, b, false);
return len;
}
static size_t put_float64fix(struct lwm2m_output_context *out,
struct lwm2m_obj_path *path,
float64_value_t *value)
{
size_t len;
s64_t binary64 = 0, net_binary64 = 0;
struct oma_tlv tlv;
/* TODO */
net_binary64 = sys_cpu_to_be64(binary64);
tlv_setup(&tlv, tlv_calc_type(out->writer_flags),
tlv_calc_id(out->writer_flags, path),
sizeof(net_binary64));
len = oma_tlv_put(&tlv, out, (u8_t *)&net_binary64, false);
return len;
}
static size_t put_bool(struct lwm2m_output_context *out,
struct lwm2m_obj_path *path, bool value)
{
return put_s8(out, path, value != 0 ? 1 : 0);
}
static size_t get_number(struct lwm2m_input_context *in, s64_t *value,
u8_t max_len)
{
struct oma_tlv tlv;
size_t size = oma_tlv_get(&tlv, in, false);
s64_t temp;
*value = 0;
if (size > 0) {
if (tlv.length > max_len) {
SYS_LOG_ERR("invalid length: %u", tlv.length);
return 0;
}
in->frag = net_frag_read(in->frag, in->offset, &in->offset,
tlv.length, (u8_t *)&temp);
if (!in->frag && in->offset == 0xffff) {
/* TODO: Generate error? */
return 0;
}
switch (tlv.length) {
case 1:
*value = (s8_t)temp;
break;
case 2:
*value = sys_cpu_to_be16((s16_t)temp);
break;
case 4:
*value = sys_cpu_to_be32((s32_t)temp);
break;
case 8:
*value = sys_cpu_to_be64(temp);
break;
default:
SYS_LOG_ERR("invalid length: %u", tlv.length);
return 0;
}
}
return size;
}
static size_t get_s64(struct lwm2m_input_context *in, s64_t *value)
{
return get_number(in, value, 8);
}
static size_t get_s32(struct lwm2m_input_context *in, s32_t *value)
{
s64_t temp;
size_t size;
*value = 0;
size = get_number(in, &temp, 4);
if (size > 0) {
*value = (s32_t)temp;
}
return size;
}
static size_t get_string(struct lwm2m_input_context *in,
u8_t *buf, size_t buflen)
{
struct oma_tlv tlv;
size_t size = oma_tlv_get(&tlv, in, false);
if (size > 0) {
if (buflen <= tlv.length) {
/* TODO: Generate error? */
return 0;
}
in->frag = net_frag_read(in->frag, in->offset, &in->offset,
tlv.length, buf);
if (!in->frag && in->offset == 0xffff) {
/* TODO: Generate error? */
return 0;
}
buf[tlv.length] = '\0';
}
return size;
}
/* convert float to fixpoint */
static size_t get_float32fix(struct lwm2m_input_context *in,
float32_value_t *value)
{
struct oma_tlv tlv;
size_t size = oma_tlv_get(&tlv, in, false);
int e;
s32_t val;
int sign = false;
int bits = 0;
u8_t values[4];
if (size > 0) {
/* TLV needs to be 4 bytes */
in->frag = net_frag_read(in->frag, in->offset, &in->offset,
4, values);
if (!in->frag && in->offset == 0xffff) {
/* TODO: Generate error? */
return 0;
}
/* sign */
sign = (values[0] & 0x80) != 0;
e = ((values[0] << 1) & 0xff) | (values[1] >> 7);
val = (((long)values[1] & 0x7f) << 16) |
(values[2] << 8) |
values[3];
e = e - 127 + bits;
/* e is the number of times we need to roll the number */
SYS_LOG_DBG("Actual e=%d", e);
e = e - 23;
SYS_LOG_DBG("E after sub %d", e);
val = val | 1L << 23;
if (e > 0) {
val = val << e;
} else {
val = val >> -e;
}
value->val1 = sign ? -val : val;
/*
* TODO: Currently, there is no standard API for handling the
* decimal portion of the float32_value structure. In the
* future, once that is settled, we should calculate
* value->val2 in the above float calculations.
*
* HACK BELOW: hard code the decimal value 0
*/
value->val2 = 0;
}
return size;
}
static size_t get_float64fix(struct lwm2m_input_context *in,
float64_value_t *value)
{
struct oma_tlv tlv;
size_t size = oma_tlv_get(&tlv, in, false);
if (size > 0) {
if (tlv.length != 8) {
SYS_LOG_ERR("invalid length: %u (not 8)", tlv.length);
return 0;
}
/* TODO */
}
return size;
}
static size_t get_bool(struct lwm2m_input_context *in, bool *value)
{
s64_t temp;
size_t size;
*value = 0;
size = get_number(in, &temp, 2);
if (size > 0) {
*value = (temp != 0);
}
return size;
}
static size_t get_opaque(struct lwm2m_input_context *in,
u8_t *value, size_t buflen, bool *last_block)
{
struct oma_tlv tlv;
oma_tlv_get(&tlv, in, false);
in->opaque_len = tlv.length;
return lwm2m_engine_get_opaque_more(in, value, buflen, last_block);
}
const struct lwm2m_writer oma_tlv_writer = {
NULL,
NULL,
put_begin_ri,
put_end_ri,
put_s8,
put_s16,
put_s32,
put_s64,
put_string,
put_float32fix,
put_float64fix,
put_bool,
NULL
};
const struct lwm2m_reader oma_tlv_reader = {
get_s32,
get_s64,
get_string,
get_float32fix,
get_float64fix,
get_bool,
get_opaque
};
static int do_write_op_tlv_item(struct lwm2m_engine_context *context)
{
struct lwm2m_engine_obj_inst *obj_inst = NULL;
struct lwm2m_engine_res_inst *res = NULL;
struct lwm2m_engine_obj_field *obj_field = NULL;
u8_t created = 0;
int ret, i;
ret = lwm2m_get_or_create_engine_obj(context, &obj_inst, &created);
if (ret < 0) {
return ret;
}
obj_field = lwm2m_get_engine_obj_field(obj_inst->obj,
context->path->res_id);
/* if obj_field is not found, treat as an optional resource */
if (!obj_field) {
if (context->operation == LWM2M_OP_CREATE) {
return -ENOTSUP;
}
return -ENOENT;
}
if ((obj_field->permissions & LWM2M_PERM_W) != LWM2M_PERM_W) {
return -EPERM;
}
if (!obj_inst->resources || obj_inst->resource_count == 0) {
return -EINVAL;
}
for (i = 0; i < obj_inst->resource_count; i++) {
if (obj_inst->resources[i].res_id == context->path->res_id) {
res = &obj_inst->resources[i];
break;
}
}
if (!res) {
return -ENOENT;
}
return lwm2m_write_handler(obj_inst, res, obj_field, context);
}
int do_write_op_tlv(struct lwm2m_engine_obj *obj,
struct lwm2m_engine_context *context)
{
struct lwm2m_input_context *in = context->in;
struct lwm2m_obj_path *path = context->path;
struct lwm2m_engine_obj_inst *obj_inst = NULL;
size_t len;
struct oma_tlv tlv;
int ret;
while (true) {
/*
* This initial read of TLV data won't advance frag/offset.
* We need tlv.type to determine how to proceed.
*/
len = oma_tlv_get(&tlv, in, true);
if (len == 0) {
break;
}
if (tlv.type == OMA_TLV_TYPE_OBJECT_INSTANCE) {
struct oma_tlv tlv2;
int len2;
int pos = 0;
oma_tlv_get(&tlv, in, false);
path->obj_inst_id = tlv.id;
if (tlv.length == 0) {
/* Create only - no data */
ret = lwm2m_create_obj_inst(path->obj_id,
path->obj_inst_id,
&obj_inst);
if (ret < 0) {
return ret;
}
}
while (pos < tlv.length &&
(len2 = oma_tlv_get(&tlv2, in, true))) {
if (tlv2.type == OMA_TLV_TYPE_RESOURCE) {
path->res_id = tlv2.id;
path->level = 3;
ret = do_write_op_tlv_item(context);
/*
* ignore errors for CREATE op
* TODO: support BOOTSTRAP WRITE where
* optional resources are ignored
*/
if (ret < 0 &&
(context->operation !=
LWM2M_OP_CREATE ||
ret != -ENOTSUP)) {
return ret;
}
}
pos += len2;
}
} else if (tlv.type == OMA_TLV_TYPE_RESOURCE) {
path->res_id = tlv.id;
path->level = 3;
ret = do_write_op_tlv_item(context);
/*
* ignore errors for CREATE op
* TODO: support BOOTSTRAP WRITE where optional
* resources are ignored
*/
if (ret < 0 && (context->operation != LWM2M_OP_CREATE ||
ret != -ENOTSUP)) {
return ret;
}
}
}
return 0;
}