ec962246e9
React to disable executable, as well as add callback that allows disabling server for a period of time. Also add API that would find a next server candidate based on the priority and server being not-disabled. Move all server related functions into its own header. Signed-off-by: Seppo Takalo <seppo.takalo@nordicsemi.no>
1669 lines
42 KiB
C
1669 lines
42 KiB
C
/*
|
|
* Copyright (c) 2017 Linaro Limited
|
|
* Copyright (c) 2018-2019 Foundries.io
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/*
|
|
* Uses some original concepts by:
|
|
* Joakim Eriksson <joakime@sics.se>
|
|
* Niclas Finne <nfi@sics.se>
|
|
* Joel Hoglund <joel@sics.se>
|
|
*/
|
|
|
|
#define LOG_MODULE_NAME net_lwm2m_observation
|
|
#define LOG_LEVEL CONFIG_LWM2M_LOG_LEVEL
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(LOG_MODULE_NAME);
|
|
|
|
#include "lwm2m_engine.h"
|
|
#include "lwm2m_object.h"
|
|
#include "lwm2m_util.h"
|
|
#include "lwm2m_rd_client.h"
|
|
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <zephyr/init.h>
|
|
#include <zephyr/net/http/parser_url.h>
|
|
#include <zephyr/net/lwm2m.h>
|
|
#include <zephyr/net/net_ip.h>
|
|
#include <zephyr/net/socket.h>
|
|
#include <zephyr/sys/printk.h>
|
|
#include <zephyr/types.h>
|
|
#include "lwm2m_obj_server.h"
|
|
|
|
#if defined(CONFIG_LWM2M_RW_SENML_JSON_SUPPORT)
|
|
#include "lwm2m_rw_senml_json.h"
|
|
#endif
|
|
#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_RW_SENML_CBOR_SUPPORT
|
|
#include "lwm2m_rw_senml_cbor.h"
|
|
#endif
|
|
|
|
#define OBSERVE_COUNTER_START 0U
|
|
|
|
#if defined(CONFIG_COAP_EXTENDED_OPTIONS_LEN)
|
|
#define COAP_OPTION_BUF_LEN (CONFIG_COAP_EXTENDED_OPTIONS_LEN_VALUE + 1)
|
|
#else
|
|
#define COAP_OPTION_BUF_LEN 13
|
|
#endif
|
|
|
|
/* Resources */
|
|
static sys_slist_t obs_obj_path_list;
|
|
|
|
static struct observe_node observe_node_data[CONFIG_LWM2M_ENGINE_MAX_OBSERVER];
|
|
|
|
/* External resources */
|
|
struct lwm2m_ctx **lwm2m_sock_ctx(void);
|
|
|
|
int lwm2m_sock_nfds(void);
|
|
/* Resource wrappers */
|
|
sys_slist_t *lwm2m_obs_obj_path_list(void) { return &obs_obj_path_list; }
|
|
|
|
struct notification_attrs {
|
|
/* use to determine which value is set */
|
|
double gt;
|
|
double lt;
|
|
double st;
|
|
int32_t pmin;
|
|
int32_t pmax;
|
|
uint8_t flags;
|
|
};
|
|
|
|
/* write-attribute related definitions */
|
|
static const char *const LWM2M_ATTR_STR[] = {"pmin", "pmax", "gt", "lt", "st"};
|
|
static const uint8_t LWM2M_ATTR_LEN[] = {4, 4, 2, 2, 2};
|
|
|
|
static struct lwm2m_attr write_attr_pool[CONFIG_LWM2M_NUM_ATTR];
|
|
|
|
/* Forward declarations */
|
|
|
|
void lwm2m_engine_free_list(sys_slist_t *path_list, sys_slist_t *free_list);
|
|
|
|
struct lwm2m_obj_path_list *lwm2m_engine_get_from_list(sys_slist_t *path_list);
|
|
|
|
static int update_attrs(void *ref, struct notification_attrs *out)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < CONFIG_LWM2M_NUM_ATTR; i++) {
|
|
if (ref != write_attr_pool[i].ref) {
|
|
continue;
|
|
}
|
|
|
|
switch (write_attr_pool[i].type) {
|
|
case LWM2M_ATTR_PMIN:
|
|
out->pmin = write_attr_pool[i].int_val;
|
|
break;
|
|
case LWM2M_ATTR_PMAX:
|
|
out->pmax = write_attr_pool[i].int_val;
|
|
break;
|
|
case LWM2M_ATTR_LT:
|
|
out->lt = write_attr_pool[i].float_val;
|
|
break;
|
|
case LWM2M_ATTR_GT:
|
|
out->gt = write_attr_pool[i].float_val;
|
|
break;
|
|
case LWM2M_ATTR_STEP:
|
|
out->st = write_attr_pool[i].float_val;
|
|
break;
|
|
default:
|
|
LOG_ERR("Unrecognized attr: %d", write_attr_pool[i].type);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* mark as set */
|
|
out->flags |= BIT(write_attr_pool[i].type);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void clear_attrs(void *ref)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < CONFIG_LWM2M_NUM_ATTR; i++) {
|
|
if (ref == write_attr_pool[i].ref) {
|
|
(void)memset(&write_attr_pool[i], 0, sizeof(write_attr_pool[i]));
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool lwm2m_observer_path_compare(const struct lwm2m_obj_path *o_p,
|
|
const struct lwm2m_obj_path *p)
|
|
{
|
|
/* check obj id matched or not */
|
|
if (p->obj_id != o_p->obj_id) {
|
|
return false;
|
|
}
|
|
|
|
if (o_p->level >= LWM2M_PATH_LEVEL_OBJECT_INST) {
|
|
if (p->level >= LWM2M_PATH_LEVEL_OBJECT_INST &&
|
|
p->obj_inst_id != o_p->obj_inst_id) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (o_p->level >= LWM2M_PATH_LEVEL_RESOURCE) {
|
|
if (p->level >= LWM2M_PATH_LEVEL_RESOURCE && p->res_id != o_p->res_id) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_LWM2M_VERSION_1_1) && o_p->level == LWM2M_PATH_LEVEL_RESOURCE_INST) {
|
|
if (p->level == LWM2M_PATH_LEVEL_RESOURCE_INST &&
|
|
p->res_inst_id != o_p->res_inst_id) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool lwm2m_notify_observer_list(sys_slist_t *path_list, const struct lwm2m_obj_path *path)
|
|
{
|
|
struct lwm2m_obj_path_list *o_p;
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER(path_list, o_p, node) {
|
|
if (lwm2m_observer_path_compare(&o_p->path, path)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int lwm2m_notify_observer(uint16_t obj_id, uint16_t obj_inst_id, uint16_t res_id)
|
|
{
|
|
struct lwm2m_obj_path path;
|
|
|
|
path.level = LWM2M_PATH_LEVEL_RESOURCE;
|
|
path.obj_id = obj_id;
|
|
path.obj_inst_id = obj_inst_id;
|
|
path.res_id = res_id;
|
|
|
|
return lwm2m_notify_observer_path(&path);
|
|
}
|
|
|
|
static int engine_observe_get_attributes(const struct lwm2m_obj_path *path,
|
|
struct notification_attrs *attrs, uint16_t srv_obj_inst)
|
|
{
|
|
struct lwm2m_engine_obj *obj;
|
|
struct lwm2m_engine_obj_field *obj_field = NULL;
|
|
struct lwm2m_engine_obj_inst *obj_inst = NULL;
|
|
struct lwm2m_engine_res_inst *res_inst = NULL;
|
|
int ret, i;
|
|
|
|
/* defaults from server object */
|
|
attrs->pmin = lwm2m_server_get_pmin(srv_obj_inst);
|
|
attrs->pmax = lwm2m_server_get_pmax(srv_obj_inst);
|
|
attrs->flags = BIT(LWM2M_ATTR_PMIN) | BIT(LWM2M_ATTR_PMAX);
|
|
|
|
/* check if object exists */
|
|
obj = get_engine_obj(path->obj_id);
|
|
if (!obj) {
|
|
LOG_ERR("unable to find obj: %u", path->obj_id);
|
|
return -ENOENT;
|
|
}
|
|
|
|
ret = update_attrs(obj, attrs);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
/* check if object instance exists */
|
|
if (path->level >= LWM2M_PATH_LEVEL_OBJECT_INST) {
|
|
obj_inst = get_engine_obj_inst(path->obj_id, path->obj_inst_id);
|
|
if (!obj_inst) {
|
|
attrs->pmax = 0;
|
|
attrs->pmin = 0;
|
|
return 0;
|
|
}
|
|
|
|
ret = update_attrs(obj_inst, attrs);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* check if resource exists */
|
|
if (path->level >= LWM2M_PATH_LEVEL_RESOURCE) {
|
|
for (i = 0; i < obj_inst->resource_count; i++) {
|
|
if (obj_inst->resources[i].res_id == path->res_id) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == obj_inst->resource_count) {
|
|
LOG_ERR("unable to find res_id: %u/%u/%u", path->obj_id, path->obj_inst_id,
|
|
path->res_id);
|
|
return -ENOENT;
|
|
}
|
|
|
|
/* load object field data */
|
|
obj_field = lwm2m_get_engine_obj_field(obj, obj_inst->resources[i].res_id);
|
|
if (!obj_field) {
|
|
LOG_ERR("unable to find obj_field: %u/%u/%u", path->obj_id,
|
|
path->obj_inst_id, path->res_id);
|
|
return -ENOENT;
|
|
}
|
|
|
|
/* check for READ permission on matching resource */
|
|
if (!LWM2M_HAS_PERM(obj_field, LWM2M_PERM_R)) {
|
|
return -EPERM;
|
|
}
|
|
|
|
ret = update_attrs(&obj_inst->resources[i], attrs);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* check if resource instance exists */
|
|
if (IS_ENABLED(CONFIG_LWM2M_VERSION_1_1) && path->level == LWM2M_PATH_LEVEL_RESOURCE_INST) {
|
|
ret = path_to_objs(path, NULL, NULL, NULL, &res_inst);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
if (res_inst == NULL) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
ret = update_attrs(res_inst, attrs);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
attrs->pmax = (attrs->pmax >= attrs->pmin) ? (uint32_t)attrs->pmax : 0UL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int engine_observe_attribute_list_get(sys_slist_t *path_list, struct notification_attrs *nattrs,
|
|
uint16_t server_obj_inst)
|
|
{
|
|
struct lwm2m_obj_path_list *o_p;
|
|
/* Temporary compare values */
|
|
int32_t pmin = 0;
|
|
int32_t pmax = 0;
|
|
int ret;
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER(path_list, o_p, node) {
|
|
nattrs->pmin = 0;
|
|
nattrs->pmax = 0;
|
|
|
|
ret = engine_observe_get_attributes(&o_p->path, nattrs, server_obj_inst);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
if (nattrs->pmin) {
|
|
if (pmin == 0) {
|
|
pmin = nattrs->pmin;
|
|
} else {
|
|
pmin = MIN(pmin, nattrs->pmin);
|
|
}
|
|
}
|
|
|
|
if (nattrs->pmax) {
|
|
if (pmax == 0) {
|
|
pmax = nattrs->pmax;
|
|
} else {
|
|
pmax = MIN(pmax, nattrs->pmax);
|
|
}
|
|
}
|
|
}
|
|
|
|
nattrs->pmin = pmin;
|
|
nattrs->pmax = pmax;
|
|
return 0;
|
|
}
|
|
|
|
int lwm2m_notify_observer_path(const struct lwm2m_obj_path *path)
|
|
{
|
|
struct observe_node *obs;
|
|
struct notification_attrs nattrs = {0};
|
|
int64_t timestamp;
|
|
int ret = 0;
|
|
int i;
|
|
struct lwm2m_ctx **sock_ctx = lwm2m_sock_ctx();
|
|
|
|
if (path->level < LWM2M_PATH_LEVEL_OBJECT) {
|
|
return 0;
|
|
}
|
|
|
|
/* look for observers which match our resource */
|
|
for (i = 0; i < lwm2m_sock_nfds(); ++i) {
|
|
SYS_SLIST_FOR_EACH_CONTAINER(&sock_ctx[i]->observer, obs, node) {
|
|
if (lwm2m_notify_observer_list(&obs->path_list, path)) {
|
|
/* update the event time for this observer */
|
|
ret = engine_observe_attribute_list_get(&obs->path_list, &nattrs,
|
|
sock_ctx[i]->srv_obj_inst);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
if (nattrs.pmin) {
|
|
timestamp =
|
|
obs->last_timestamp + MSEC_PER_SEC * nattrs.pmin;
|
|
} else {
|
|
/* Trig immediately */
|
|
timestamp = k_uptime_get();
|
|
}
|
|
|
|
if (!obs->event_timestamp || obs->event_timestamp > timestamp) {
|
|
obs->resource_update = true;
|
|
obs->event_timestamp = timestamp;
|
|
}
|
|
|
|
LOG_DBG("NOTIFY EVENT %u/%u/%u", path->obj_id, path->obj_inst_id,
|
|
path->res_id);
|
|
ret++;
|
|
lwm2m_engine_wake_up();
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct observe_node *engine_allocate_observer(sys_slist_t *path_list, bool composite)
|
|
{
|
|
int i;
|
|
struct lwm2m_obj_path_list *entry, *tmp;
|
|
struct observe_node *obs = NULL;
|
|
|
|
/* find an unused observer index node */
|
|
for (i = 0; i < CONFIG_LWM2M_ENGINE_MAX_OBSERVER; i++) {
|
|
if (!observe_node_data[i].tkl) {
|
|
|
|
obs = &observe_node_data[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!obs) {
|
|
return NULL;
|
|
}
|
|
|
|
sys_slist_init(&obs->path_list);
|
|
obs->composite = composite;
|
|
|
|
/* Allocate and copy path */
|
|
SYS_SLIST_FOR_EACH_CONTAINER(path_list, tmp, node) {
|
|
/* Allocate path entry */
|
|
entry = lwm2m_engine_get_from_list(&obs_obj_path_list);
|
|
if (!entry) {
|
|
/* Free list */
|
|
lwm2m_engine_free_list(&obs->path_list, &obs_obj_path_list);
|
|
return NULL;
|
|
}
|
|
|
|
/* copy the values and add it to the list */
|
|
memcpy(&entry->path, &tmp->path, sizeof(tmp->path));
|
|
/* Add to last by keeping already sorted order */
|
|
sys_slist_append(&obs->path_list, &entry->node);
|
|
}
|
|
|
|
return obs;
|
|
}
|
|
|
|
static void engine_observe_node_init(struct observe_node *obs, const uint8_t *token,
|
|
struct lwm2m_ctx *ctx, uint8_t tkl, uint16_t format,
|
|
int32_t att_pmax)
|
|
{
|
|
struct lwm2m_obj_path_list *tmp;
|
|
|
|
memcpy(obs->token, token, tkl);
|
|
obs->tkl = tkl;
|
|
|
|
obs->last_timestamp = k_uptime_get();
|
|
if (att_pmax) {
|
|
obs->event_timestamp = obs->last_timestamp + MSEC_PER_SEC * att_pmax;
|
|
} else {
|
|
obs->event_timestamp = 0;
|
|
}
|
|
obs->resource_update = false;
|
|
obs->active_notify = NULL;
|
|
obs->format = format;
|
|
obs->counter = OBSERVE_COUNTER_START;
|
|
sys_slist_append(&ctx->observer, &obs->node);
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER(&obs->path_list, tmp, node) {
|
|
LOG_DBG("OBSERVER ADDED %u/%u/%u/%u(%u)", tmp->path.obj_id, tmp->path.obj_inst_id,
|
|
tmp->path.res_id, tmp->path.res_inst_id, tmp->path.level);
|
|
|
|
if (ctx->observe_cb) {
|
|
ctx->observe_cb(LWM2M_OBSERVE_EVENT_OBSERVER_ADDED, &tmp->path, ctx);
|
|
}
|
|
}
|
|
|
|
LOG_DBG("token:'%s' addr:%s", sprint_token(token, tkl),
|
|
lwm2m_sprint_ip_addr(&ctx->remote_addr));
|
|
}
|
|
|
|
static void remove_observer_path_from_list(struct lwm2m_ctx *ctx, struct observe_node *obs,
|
|
struct lwm2m_obj_path_list *o_p, sys_snode_t *prev_node)
|
|
{
|
|
char buf[LWM2M_MAX_PATH_STR_SIZE];
|
|
|
|
LOG_DBG("Removing observer %p for path %s", obs, lwm2m_path_log_buf(buf, &o_p->path));
|
|
if (ctx->observe_cb) {
|
|
ctx->observe_cb(LWM2M_OBSERVE_EVENT_OBSERVER_REMOVED, &o_p->path, NULL);
|
|
}
|
|
/* Remove from the list and add to free list */
|
|
sys_slist_remove(&obs->path_list, prev_node, &o_p->node);
|
|
sys_slist_append(&obs_obj_path_list, &o_p->node);
|
|
}
|
|
|
|
static void engine_observe_single_path_id_remove(struct lwm2m_ctx *ctx, struct observe_node *obs,
|
|
uint16_t obj_id, int32_t obj_inst_id)
|
|
{
|
|
struct lwm2m_obj_path_list *o_p, *tmp;
|
|
sys_snode_t *prev_node = NULL;
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&obs->path_list, o_p, tmp, node) {
|
|
if (o_p->path.obj_id != obj_id && o_p->path.obj_inst_id) {
|
|
prev_node = &o_p->node;
|
|
continue;
|
|
}
|
|
|
|
if (obj_inst_id == -1 || o_p->path.obj_inst_id == obj_inst_id) {
|
|
remove_observer_path_from_list(ctx, obs, o_p, prev_node);
|
|
} else {
|
|
prev_node = &o_p->node;
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool engine_compare_obs_path_list(sys_slist_t *obs_path_list, sys_slist_t *path_list,
|
|
int list_length)
|
|
{
|
|
sys_snode_t *obs_ptr, *comp_ptr;
|
|
struct lwm2m_obj_path_list *obs_path, *comp_path;
|
|
|
|
obs_ptr = sys_slist_peek_head(obs_path_list);
|
|
comp_ptr = sys_slist_peek_head(path_list);
|
|
while (list_length--) {
|
|
obs_path = (struct lwm2m_obj_path_list *)obs_ptr;
|
|
comp_path = (struct lwm2m_obj_path_list *)comp_ptr;
|
|
if (memcmp(&obs_path->path, &comp_path->path, sizeof(struct lwm2m_obj_path))) {
|
|
return false;
|
|
}
|
|
/* Read Next Info from list entry*/
|
|
obs_ptr = sys_slist_peek_next_no_check(obs_ptr);
|
|
comp_ptr = sys_slist_peek_next_no_check(comp_ptr);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int engine_path_list_size(sys_slist_t *lwm2m_path_list)
|
|
{
|
|
int list_size = 0;
|
|
struct lwm2m_obj_path_list *entry;
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER(lwm2m_path_list, entry, node) {
|
|
list_size++;
|
|
}
|
|
return list_size;
|
|
}
|
|
|
|
struct observe_node *engine_observe_node_discover(sys_slist_t *observe_node_list,
|
|
sys_snode_t **prev_node,
|
|
sys_slist_t *lwm2m_path_list,
|
|
const uint8_t *token, uint8_t tkl)
|
|
{
|
|
struct observe_node *obs;
|
|
int obs_list_size, path_list_size = 0;
|
|
|
|
if (lwm2m_path_list) {
|
|
path_list_size = engine_path_list_size(lwm2m_path_list);
|
|
}
|
|
|
|
*prev_node = NULL;
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER(observe_node_list, obs, node) {
|
|
|
|
if (lwm2m_path_list) {
|
|
/* Validate Path for discovery */
|
|
obs_list_size = engine_path_list_size(&obs->path_list);
|
|
|
|
if (obs_list_size != path_list_size) {
|
|
*prev_node = &obs->node;
|
|
continue;
|
|
}
|
|
|
|
if (!engine_compare_obs_path_list(&obs->path_list, lwm2m_path_list,
|
|
obs_list_size)) {
|
|
*prev_node = &obs->node;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (token && memcmp(obs->token, token, tkl)) {
|
|
/* Token not match */
|
|
*prev_node = &obs->node;
|
|
continue;
|
|
}
|
|
return obs;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int engine_add_observer(struct lwm2m_message *msg, const uint8_t *token, uint8_t tkl,
|
|
uint16_t format)
|
|
{
|
|
struct observe_node *obs;
|
|
struct notification_attrs attrs;
|
|
struct lwm2m_obj_path_list obs_path_list_buf;
|
|
sys_slist_t lwm2m_path_list;
|
|
sys_snode_t *prev_node = NULL;
|
|
int ret;
|
|
|
|
if (!msg || !msg->ctx) {
|
|
LOG_ERR("valid lwm2m message is required");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!token || (tkl == 0U || tkl > MAX_TOKEN_LEN)) {
|
|
LOG_ERR("token(%p) and token length(%u) must be valid.", token, tkl);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Create 1 entry linked list for message path */
|
|
memcpy(&obs_path_list_buf.path, &msg->path, sizeof(struct lwm2m_obj_path));
|
|
sys_slist_init(&lwm2m_path_list);
|
|
sys_slist_append(&lwm2m_path_list, &obs_path_list_buf.node);
|
|
|
|
obs = engine_observe_node_discover(&msg->ctx->observer, &prev_node, &lwm2m_path_list, NULL,
|
|
0);
|
|
if (obs) {
|
|
memcpy(obs->token, token, tkl);
|
|
obs->tkl = tkl;
|
|
|
|
/* Cancel ongoing notification */
|
|
if (obs->active_notify != NULL) {
|
|
lwm2m_reset_message(obs->active_notify, true);
|
|
obs->active_notify = NULL;
|
|
}
|
|
|
|
LOG_DBG("OBSERVER DUPLICATE %u/%u/%u(%u) [%s]", msg->path.obj_id,
|
|
msg->path.obj_inst_id, msg->path.res_id, msg->path.level,
|
|
lwm2m_sprint_ip_addr(&msg->ctx->remote_addr));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Read attributes and allocate new entry */
|
|
ret = engine_observe_get_attributes(&msg->path, &attrs, msg->ctx->srv_obj_inst);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
obs = engine_allocate_observer(&lwm2m_path_list, false);
|
|
if (!obs) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
engine_observe_node_init(obs, token, msg->ctx, tkl, format, attrs.pmax);
|
|
return 0;
|
|
}
|
|
|
|
int do_composite_observe_read_path_op(struct lwm2m_message *msg, uint16_t content_format,
|
|
sys_slist_t *lwm2m_path_list,
|
|
sys_slist_t *lwm2m_path_free_list)
|
|
{
|
|
switch (content_format) {
|
|
#if defined(CONFIG_LWM2M_RW_SENML_JSON_SUPPORT)
|
|
case LWM2M_FORMAT_APP_SEML_JSON:
|
|
return do_composite_observe_parse_path_senml_json(msg, lwm2m_path_list,
|
|
lwm2m_path_free_list);
|
|
#endif
|
|
|
|
#if defined(CONFIG_LWM2M_RW_SENML_CBOR_SUPPORT)
|
|
case LWM2M_FORMAT_APP_SENML_CBOR:
|
|
return do_composite_observe_parse_path_senml_cbor(msg, lwm2m_path_list,
|
|
lwm2m_path_free_list);
|
|
#endif
|
|
|
|
default:
|
|
LOG_ERR("Unsupported content-format: %u", content_format);
|
|
return -ENOMSG;
|
|
}
|
|
}
|
|
|
|
static int engine_add_composite_observer(struct lwm2m_message *msg, const uint8_t *token,
|
|
uint8_t tkl, uint16_t format)
|
|
{
|
|
struct observe_node *obs;
|
|
struct notification_attrs attrs;
|
|
struct lwm2m_obj_path_list lwm2m_path_list_buf[CONFIG_LWM2M_COMPOSITE_PATH_LIST_SIZE];
|
|
sys_slist_t lwm2m_path_list;
|
|
sys_slist_t lwm2m_path_free_list;
|
|
sys_snode_t *prev_node = NULL;
|
|
int ret;
|
|
|
|
if (!msg || !msg->ctx) {
|
|
LOG_ERR("valid lwm2m message is required");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!token || (tkl == 0U || tkl > MAX_TOKEN_LEN)) {
|
|
LOG_ERR("token(%p) and token length(%u) must be valid.", token, tkl);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Init list */
|
|
lwm2m_engine_path_list_init(&lwm2m_path_list, &lwm2m_path_free_list, lwm2m_path_list_buf,
|
|
CONFIG_LWM2M_COMPOSITE_PATH_LIST_SIZE);
|
|
|
|
/* Read attributes and allocate new entry */
|
|
ret = do_composite_observe_read_path_op(msg, format, &lwm2m_path_list,
|
|
&lwm2m_path_free_list);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
obs = engine_observe_node_discover(&msg->ctx->observer, &prev_node, &lwm2m_path_list, NULL,
|
|
0);
|
|
if (obs) {
|
|
memcpy(obs->token, token, tkl);
|
|
obs->tkl = tkl;
|
|
|
|
/* Cancel ongoing notification */
|
|
if (obs->active_notify != NULL) {
|
|
lwm2m_reset_message(obs->active_notify, true);
|
|
obs->active_notify = NULL;
|
|
}
|
|
|
|
LOG_DBG("OBSERVER Composite DUPLICATE [%s]",
|
|
lwm2m_sprint_ip_addr(&msg->ctx->remote_addr));
|
|
|
|
return do_composite_read_op_for_parsed_list(msg, format, &lwm2m_path_list);
|
|
}
|
|
|
|
ret = engine_observe_attribute_list_get(&lwm2m_path_list, &attrs, msg->ctx->srv_obj_inst);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
obs = engine_allocate_observer(&lwm2m_path_list, true);
|
|
if (!obs) {
|
|
return -ENOMEM;
|
|
}
|
|
engine_observe_node_init(obs, token, msg->ctx, tkl, format, attrs.pmax);
|
|
return do_composite_read_op_for_parsed_list(msg, format, &lwm2m_path_list);
|
|
}
|
|
|
|
void remove_observer_from_list(struct lwm2m_ctx *ctx, sys_snode_t *prev_node,
|
|
struct observe_node *obs)
|
|
{
|
|
struct lwm2m_obj_path_list *o_p, *tmp;
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&obs->path_list, o_p, tmp, node) {
|
|
remove_observer_path_from_list(ctx, obs, o_p, NULL);
|
|
}
|
|
sys_slist_remove(&ctx->observer, prev_node, &obs->node);
|
|
(void)memset(obs, 0, sizeof(*obs));
|
|
}
|
|
|
|
int engine_remove_observer_by_token(struct lwm2m_ctx *ctx, const uint8_t *token, uint8_t tkl)
|
|
{
|
|
struct observe_node *obs;
|
|
sys_snode_t *prev_node = NULL;
|
|
|
|
if (!token || (tkl == 0U || tkl > MAX_TOKEN_LEN)) {
|
|
LOG_ERR("token(%p) and token length(%u) must be valid.", token, tkl);
|
|
return -EINVAL;
|
|
}
|
|
|
|
obs = engine_observe_node_discover(&ctx->observer, &prev_node, NULL, token, tkl);
|
|
if (!obs) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
remove_observer_from_list(ctx, prev_node, obs);
|
|
|
|
LOG_DBG("observer '%s' removed", sprint_token(token, tkl));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int engine_remove_composite_observer(struct lwm2m_message *msg, const uint8_t *token,
|
|
uint8_t tkl, uint16_t format)
|
|
{
|
|
struct observe_node *obs;
|
|
sys_snode_t *prev_node = NULL;
|
|
struct lwm2m_obj_path_list lwm2m_path_list_buf[CONFIG_LWM2M_COMPOSITE_PATH_LIST_SIZE];
|
|
sys_slist_t lwm2m_path_list;
|
|
sys_slist_t lwm2m_path_free_list;
|
|
int ret;
|
|
|
|
if (!token || (tkl == 0U || tkl > MAX_TOKEN_LEN)) {
|
|
LOG_ERR("token(%p) and token length(%u) must be valid.", token, tkl);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Init list */
|
|
lwm2m_engine_path_list_init(&lwm2m_path_list, &lwm2m_path_free_list, lwm2m_path_list_buf,
|
|
CONFIG_LWM2M_COMPOSITE_PATH_LIST_SIZE);
|
|
|
|
ret = do_composite_observe_read_path_op(msg, format, &lwm2m_path_list,
|
|
&lwm2m_path_free_list);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
obs = engine_observe_node_discover(&msg->ctx->observer, &prev_node, &lwm2m_path_list, token,
|
|
tkl);
|
|
if (!obs) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
remove_observer_from_list(msg->ctx, prev_node, obs);
|
|
|
|
LOG_DBG("observer '%s' removed", sprint_token(token, tkl));
|
|
|
|
return do_composite_read_op_for_parsed_list(msg, format, &lwm2m_path_list);
|
|
}
|
|
|
|
#if defined(CONFIG_LOG)
|
|
char *lwm2m_path_log_buf(char *buf, struct lwm2m_obj_path *path)
|
|
{
|
|
size_t cur;
|
|
|
|
if (!path) {
|
|
sprintf(buf, "/");
|
|
return buf;
|
|
}
|
|
|
|
cur = sprintf(buf, "%u", path->obj_id);
|
|
|
|
if (path->level > 1) {
|
|
cur += sprintf(buf + cur, "/%u", path->obj_inst_id);
|
|
}
|
|
if (path->level > 2) {
|
|
cur += sprintf(buf + cur, "/%u", path->res_id);
|
|
}
|
|
if (path->level > 3) {
|
|
cur += sprintf(buf + cur, "/%u", path->res_inst_id);
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
#endif /* CONFIG_LOG */
|
|
|
|
#if defined(CONFIG_LWM2M_CANCEL_OBSERVE_BY_PATH)
|
|
static int engine_remove_observer_by_path(struct lwm2m_ctx *ctx, struct lwm2m_obj_path *path)
|
|
{
|
|
char buf[LWM2M_MAX_PATH_STR_SIZE];
|
|
struct observe_node *obs;
|
|
struct lwm2m_obj_path_list obs_path_list_buf;
|
|
sys_slist_t lwm2m_path_list;
|
|
sys_snode_t *prev_node = NULL;
|
|
|
|
/* Create 1 entry linked list for message path */
|
|
memcpy(&obs_path_list_buf.path, path, sizeof(struct lwm2m_obj_path));
|
|
sys_slist_init(&lwm2m_path_list);
|
|
sys_slist_append(&lwm2m_path_list, &obs_path_list_buf.node);
|
|
|
|
obs = engine_observe_node_discover(&ctx->observer, &prev_node, &lwm2m_path_list, NULL, 0);
|
|
if (!obs) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
LOG_INF("Removing observer for path %s", lwm2m_path_log_buf(buf, path));
|
|
|
|
remove_observer_from_list(ctx, prev_node, obs);
|
|
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_LWM2M_CANCEL_OBSERVE_BY_PATH */
|
|
|
|
void engine_remove_observer_by_id(uint16_t obj_id, int32_t obj_inst_id)
|
|
{
|
|
struct observe_node *obs, *tmp;
|
|
sys_snode_t *prev_node = NULL;
|
|
int i;
|
|
struct lwm2m_ctx **sock_ctx = lwm2m_sock_ctx();
|
|
|
|
/* remove observer instances accordingly */
|
|
for (i = 0; i < lwm2m_sock_nfds(); ++i) {
|
|
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&sock_ctx[i]->observer, obs, tmp, node) {
|
|
engine_observe_single_path_id_remove(sock_ctx[i], obs, obj_id, obj_inst_id);
|
|
|
|
if (sys_slist_is_empty(&obs->path_list)) {
|
|
remove_observer_from_list(sock_ctx[i], prev_node, obs);
|
|
} else {
|
|
prev_node = &obs->node;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int lwm2m_update_or_allocate_attribute(void *ref, uint8_t type, void *data)
|
|
{
|
|
struct lwm2m_attr *attr;
|
|
int i;
|
|
|
|
/* find matching attributes */
|
|
for (i = 0; i < CONFIG_LWM2M_NUM_ATTR; i++) {
|
|
if (ref != write_attr_pool[i].ref || write_attr_pool[i].type != type) {
|
|
continue;
|
|
}
|
|
|
|
attr = write_attr_pool + i;
|
|
type = attr->type;
|
|
|
|
if (type <= LWM2M_ATTR_PMAX) {
|
|
attr->int_val = *(int32_t *)data;
|
|
LOG_DBG("Update %s to %d", LWM2M_ATTR_STR[type], attr->int_val);
|
|
} else {
|
|
attr->float_val = *(double *)data;
|
|
LOG_DBG("Update %s to %f", LWM2M_ATTR_STR[type], attr->float_val);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* add attribute to obj/obj_inst/res/res_inst */
|
|
/* grab an entry for newly added attribute */
|
|
for (i = 0; i < CONFIG_LWM2M_NUM_ATTR; i++) {
|
|
if (!write_attr_pool[i].ref) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == CONFIG_LWM2M_NUM_ATTR) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
attr = write_attr_pool + i;
|
|
attr->type = type;
|
|
attr->ref = ref;
|
|
|
|
if (type <= LWM2M_ATTR_PMAX) {
|
|
attr->int_val = *(int32_t *)data;
|
|
LOG_DBG("Add %s to %d", LWM2M_ATTR_STR[type], attr->int_val);
|
|
} else {
|
|
attr->float_val = *(double *)data;
|
|
LOG_DBG("Add %s to %f", LWM2M_ATTR_STR[type], attr->float_val);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
const char *lwm2m_engine_get_attr_name(const struct lwm2m_attr *attr)
|
|
{
|
|
if (attr->type >= NR_LWM2M_ATTR) {
|
|
return NULL;
|
|
}
|
|
|
|
return LWM2M_ATTR_STR[attr->type];
|
|
}
|
|
|
|
static int lwm2m_engine_observer_timestamp_update(sys_slist_t *observer,
|
|
const struct lwm2m_obj_path *path,
|
|
uint16_t srv_obj_inst)
|
|
{
|
|
struct observe_node *obs;
|
|
struct notification_attrs nattrs = {0};
|
|
int ret;
|
|
int64_t timestamp;
|
|
|
|
/* update observe_node accordingly */
|
|
SYS_SLIST_FOR_EACH_CONTAINER(observer, obs, node) {
|
|
if (obs->resource_update) {
|
|
/* Resource Update on going skip this*/
|
|
continue;
|
|
}
|
|
/* Compare Observation node path to updated one */
|
|
if (!lwm2m_notify_observer_list(&obs->path_list, path)) {
|
|
continue;
|
|
}
|
|
|
|
/* Read Attributes after validation Path */
|
|
ret = engine_observe_attribute_list_get(&obs->path_list, &nattrs, srv_obj_inst);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
/* Update based on by PMax */
|
|
if (nattrs.pmax) {
|
|
/* Update Current */
|
|
timestamp = obs->last_timestamp + MSEC_PER_SEC * nattrs.pmax;
|
|
} else {
|
|
/* Disable Automatic Notify */
|
|
timestamp = 0;
|
|
}
|
|
obs->event_timestamp = timestamp;
|
|
|
|
(void)memset(&nattrs, 0, sizeof(nattrs));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* input / output selection */
|
|
|
|
int lwm2m_get_path_reference_ptr(struct lwm2m_engine_obj *obj, const struct lwm2m_obj_path *path,
|
|
void **ref)
|
|
{
|
|
struct lwm2m_engine_obj_inst *obj_inst;
|
|
struct lwm2m_engine_res *res;
|
|
struct lwm2m_engine_res_inst *res_inst;
|
|
int ret;
|
|
|
|
if (!obj) {
|
|
/* Discover Object */
|
|
obj = get_engine_obj(path->obj_id);
|
|
if (!obj) {
|
|
/* No matching object found - ignore request */
|
|
return -ENOENT;
|
|
}
|
|
}
|
|
|
|
if (path->level == LWM2M_PATH_LEVEL_OBJECT) {
|
|
*ref = obj;
|
|
} else if (path->level == LWM2M_PATH_LEVEL_OBJECT_INST) {
|
|
obj_inst = get_engine_obj_inst(path->obj_id, path->obj_inst_id);
|
|
if (!obj_inst) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
*ref = obj_inst;
|
|
} else if (path->level == LWM2M_PATH_LEVEL_RESOURCE) {
|
|
ret = path_to_objs(path, NULL, NULL, &res, NULL);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
*ref = res;
|
|
} else if (IS_ENABLED(CONFIG_LWM2M_VERSION_1_1) &&
|
|
path->level == LWM2M_PATH_LEVEL_RESOURCE_INST) {
|
|
|
|
ret = path_to_objs(path, NULL, NULL, NULL, &res_inst);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
*ref = res_inst;
|
|
} else {
|
|
/* bad request */
|
|
return -EEXIST;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int lwm2m_update_observer_min_period(struct lwm2m_ctx *client_ctx,
|
|
const struct lwm2m_obj_path *path, uint32_t period_s)
|
|
{
|
|
int ret;
|
|
struct notification_attrs nattrs = {0};
|
|
struct notification_attrs attrs = {0};
|
|
void *ref;
|
|
|
|
/* Read Reference pointer to attribute */
|
|
ret = lwm2m_get_path_reference_ptr(NULL, path, &ref);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
/* retrieve existing attributes */
|
|
ret = update_attrs(ref, &nattrs);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
if (nattrs.flags & BIT(LWM2M_ATTR_PMIN) && nattrs.pmin == period_s) {
|
|
/* No need to change */
|
|
return 0;
|
|
}
|
|
|
|
/* Read Pmin & Pmax values for path */
|
|
ret = engine_observe_get_attributes(path, &attrs, client_ctx->srv_obj_inst);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
if (period_s && attrs.pmax && attrs.pmax < period_s) {
|
|
LOG_DBG("New pmin (%d) > pmax (%d)", period_s, attrs.pmax);
|
|
return -EEXIST;
|
|
}
|
|
|
|
return lwm2m_update_or_allocate_attribute(ref, LWM2M_ATTR_PMIN, &period_s);
|
|
}
|
|
|
|
int lwm2m_engine_update_observer_min_period(struct lwm2m_ctx *client_ctx, const char *pathstr,
|
|
uint32_t period_s)
|
|
{
|
|
int ret;
|
|
struct lwm2m_obj_path path;
|
|
|
|
ret = lwm2m_string_to_path(pathstr, &path, '/');
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
return lwm2m_update_observer_min_period(client_ctx, &path, period_s);
|
|
}
|
|
|
|
int lwm2m_update_observer_max_period(struct lwm2m_ctx *client_ctx,
|
|
const struct lwm2m_obj_path *path, uint32_t period_s)
|
|
{
|
|
int ret;
|
|
void *ref;
|
|
struct notification_attrs nattrs = {0};
|
|
struct notification_attrs attrs = {0};
|
|
|
|
/* Read Reference pointer to attribute */
|
|
ret = lwm2m_get_path_reference_ptr(NULL, path, &ref);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
/* retrieve existing attributes */
|
|
ret = update_attrs(ref, &nattrs);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
if (nattrs.flags & BIT(LWM2M_ATTR_PMAX) && nattrs.pmax == period_s) {
|
|
/* No need to change */
|
|
return 0;
|
|
}
|
|
|
|
/* Read Pmin & Pmax values for path */
|
|
ret = engine_observe_get_attributes(path, &attrs, client_ctx->srv_obj_inst);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
if (period_s && attrs.pmin > period_s) {
|
|
LOG_DBG("pmin (%d) > new pmax (%d)", attrs.pmin, period_s);
|
|
return -EEXIST;
|
|
}
|
|
|
|
/* Update or allocate new */
|
|
ret = lwm2m_update_or_allocate_attribute(ref, LWM2M_ATTR_PMAX, &period_s);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
/* Update Observer timestamp */
|
|
return lwm2m_engine_observer_timestamp_update(&client_ctx->observer, path,
|
|
client_ctx->srv_obj_inst);
|
|
}
|
|
|
|
int lwm2m_engine_update_observer_max_period(struct lwm2m_ctx *client_ctx, const char *pathstr,
|
|
uint32_t period_s)
|
|
{
|
|
int ret;
|
|
struct lwm2m_obj_path path;
|
|
|
|
ret = lwm2m_string_to_path(pathstr, &path, '/');
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
return lwm2m_update_observer_max_period(client_ctx, &path, period_s);
|
|
}
|
|
|
|
struct lwm2m_attr *lwm2m_engine_get_next_attr(const void *ref, struct lwm2m_attr *prev)
|
|
{
|
|
struct lwm2m_attr *iter = (prev == NULL) ? write_attr_pool : prev + 1;
|
|
struct lwm2m_attr *result = NULL;
|
|
|
|
if (!PART_OF_ARRAY(write_attr_pool, iter)) {
|
|
return NULL;
|
|
}
|
|
|
|
while (iter < &write_attr_pool[ARRAY_SIZE(write_attr_pool)]) {
|
|
if (ref == iter->ref) {
|
|
result = iter;
|
|
break;
|
|
}
|
|
|
|
++iter;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
int lwm2m_write_attr_handler(struct lwm2m_engine_obj *obj, struct lwm2m_message *msg)
|
|
{
|
|
bool update_observe_node = false;
|
|
char opt_buf[COAP_OPTION_BUF_LEN];
|
|
int nr_opt, i, ret = 0;
|
|
struct coap_option options[NR_LWM2M_ATTR];
|
|
struct lwm2m_attr *attr;
|
|
struct notification_attrs nattrs = {0};
|
|
uint8_t type = 0U;
|
|
void *nattr_ptrs[NR_LWM2M_ATTR] = {&nattrs.pmin, &nattrs.pmax, &nattrs.gt, &nattrs.lt,
|
|
&nattrs.st};
|
|
void *ref;
|
|
|
|
if (!obj || !msg) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* do not expose security obj */
|
|
if (obj->obj_id == LWM2M_OBJECT_SECURITY_ID) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
nr_opt = coap_find_options(msg->in.in_cpkt, COAP_OPTION_URI_QUERY, options, NR_LWM2M_ATTR);
|
|
if (nr_opt <= 0) {
|
|
LOG_ERR("No attribute found!");
|
|
/* translate as bad request */
|
|
return -EEXIST;
|
|
}
|
|
|
|
/* get lwm2m_attr slist */
|
|
ret = lwm2m_get_path_reference_ptr(obj, &msg->path, &ref);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
/* retrieve existing attributes */
|
|
ret = update_attrs(ref, &nattrs);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
/* loop through options to parse attribute */
|
|
for (i = 0; i < nr_opt; i++) {
|
|
int limit = MIN(options[i].len, 5), plen = 0, vlen;
|
|
struct lwm2m_attr val = {0};
|
|
|
|
type = 0U;
|
|
|
|
/* search for '=' */
|
|
while (plen < limit && options[i].value[plen] != '=') {
|
|
plen += 1;
|
|
}
|
|
|
|
/* either length = 2(gt/lt/st) or = 4(pmin/pmax) */
|
|
if (plen != 2 && plen != 4) {
|
|
continue;
|
|
}
|
|
|
|
/* matching attribute name */
|
|
for (type = 0U; type < NR_LWM2M_ATTR; type++) {
|
|
if (LWM2M_ATTR_LEN[type] == plen &&
|
|
!memcmp(options[i].value, LWM2M_ATTR_STR[type], LWM2M_ATTR_LEN[type])) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* unrecognized attribute */
|
|
if (type == NR_LWM2M_ATTR) {
|
|
continue;
|
|
}
|
|
|
|
/* unset attribute when no value's given */
|
|
if (options[i].len == plen) {
|
|
nattrs.flags &= ~BIT(type);
|
|
|
|
(void)memset(nattr_ptrs[type], 0,
|
|
type <= LWM2M_ATTR_PMAX ? sizeof(int32_t) : sizeof(double));
|
|
continue;
|
|
}
|
|
|
|
/* gt/lt/st cannot be assigned to obj/obj_inst unless unset */
|
|
if (plen == 2 && msg->path.level <= 2U) {
|
|
return -EEXIST;
|
|
}
|
|
|
|
vlen = options[i].len - plen - 1;
|
|
memcpy(opt_buf, options[i].value + plen + 1, vlen);
|
|
opt_buf[vlen] = '\0';
|
|
|
|
/* convert value to integer or float */
|
|
if (plen == 4) {
|
|
char *end;
|
|
long v;
|
|
|
|
/* pmin/pmax: integer (sec 5.1.2)
|
|
* however, negative is non-sense
|
|
*/
|
|
errno = 0;
|
|
v = strtol(opt_buf, &end, 10);
|
|
if (errno || *end || v < 0) {
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
val.int_val = v;
|
|
} else {
|
|
/* gt/lt/st: type float */
|
|
ret = lwm2m_atof(opt_buf, &val.float_val);
|
|
}
|
|
|
|
if (ret < 0) {
|
|
LOG_ERR("invalid attr[%s] value", LWM2M_ATTR_STR[type]);
|
|
/* bad request */
|
|
return -EEXIST;
|
|
}
|
|
|
|
if (type <= LWM2M_ATTR_PMAX) {
|
|
*(int32_t *)nattr_ptrs[type] = val.int_val;
|
|
} else {
|
|
memcpy(nattr_ptrs[type], &val.float_val, sizeof(val.float_val));
|
|
}
|
|
|
|
nattrs.flags |= BIT(type);
|
|
}
|
|
|
|
if (((nattrs.flags & (BIT(LWM2M_ATTR_PMIN) | BIT(LWM2M_ATTR_PMAX))) ==
|
|
(BIT(LWM2M_ATTR_PMIN) | BIT(LWM2M_ATTR_PMAX))) &&
|
|
nattrs.pmin > nattrs.pmax) {
|
|
LOG_DBG("pmin (%d) > pmax (%d)", nattrs.pmin, nattrs.pmax);
|
|
return -EEXIST;
|
|
}
|
|
|
|
if ((nattrs.flags & (BIT(LWM2M_ATTR_LT) | BIT(LWM2M_ATTR_GT))) ==
|
|
(BIT(LWM2M_ATTR_LT) | BIT(LWM2M_ATTR_GT))) {
|
|
if (nattrs.lt > nattrs.gt) {
|
|
LOG_DBG("lt > gt");
|
|
return -EEXIST;
|
|
}
|
|
|
|
if (nattrs.flags & BIT(LWM2M_ATTR_STEP)) {
|
|
if (nattrs.lt + 2 * nattrs.st > nattrs.gt) {
|
|
LOG_DBG("lt + 2*st > gt");
|
|
return -EEXIST;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* find matching attributes */
|
|
for (i = 0; i < CONFIG_LWM2M_NUM_ATTR; i++) {
|
|
if (ref != write_attr_pool[i].ref) {
|
|
continue;
|
|
}
|
|
|
|
attr = write_attr_pool + i;
|
|
type = attr->type;
|
|
|
|
if (!(BIT(type) & nattrs.flags)) {
|
|
LOG_DBG("Unset attr %s", LWM2M_ATTR_STR[type]);
|
|
(void)memset(attr, 0, sizeof(*attr));
|
|
|
|
if (type <= LWM2M_ATTR_PMAX) {
|
|
update_observe_node = true;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
nattrs.flags &= ~BIT(type);
|
|
|
|
if (type <= LWM2M_ATTR_PMAX) {
|
|
if (attr->int_val == *(int32_t *)nattr_ptrs[type]) {
|
|
continue;
|
|
}
|
|
|
|
attr->int_val = *(int32_t *)nattr_ptrs[type];
|
|
update_observe_node = true;
|
|
|
|
LOG_DBG("Update %s to %d", LWM2M_ATTR_STR[type], attr->int_val);
|
|
} else {
|
|
if (attr->float_val == *(double *)nattr_ptrs[type]) {
|
|
continue;
|
|
}
|
|
|
|
attr->float_val = *(double *)nattr_ptrs[type];
|
|
|
|
LOG_DBG("Update %s to %f", LWM2M_ATTR_STR[type], attr->float_val);
|
|
}
|
|
}
|
|
|
|
/* add attribute to obj/obj_inst/res/res_inst */
|
|
for (type = 0U; nattrs.flags && type < NR_LWM2M_ATTR; type++) {
|
|
if (!(BIT(type) & nattrs.flags)) {
|
|
continue;
|
|
}
|
|
|
|
/* grab an entry for newly added attribute */
|
|
for (i = 0; i < CONFIG_LWM2M_NUM_ATTR; i++) {
|
|
if (!write_attr_pool[i].ref) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == CONFIG_LWM2M_NUM_ATTR) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
attr = write_attr_pool + i;
|
|
attr->type = type;
|
|
attr->ref = ref;
|
|
|
|
if (type <= LWM2M_ATTR_PMAX) {
|
|
attr->int_val = *(int32_t *)nattr_ptrs[type];
|
|
update_observe_node = true;
|
|
|
|
LOG_DBG("Add %s to %d", LWM2M_ATTR_STR[type], attr->int_val);
|
|
} else {
|
|
attr->float_val = *(double *)nattr_ptrs[type];
|
|
|
|
LOG_DBG("Add %s to %f", LWM2M_ATTR_STR[type], attr->float_val);
|
|
}
|
|
|
|
nattrs.flags &= ~BIT(type);
|
|
}
|
|
|
|
/* check only pmin/pmax */
|
|
if (!update_observe_node) {
|
|
return 0;
|
|
}
|
|
|
|
lwm2m_engine_observer_timestamp_update(&msg->ctx->observer, &msg->path,
|
|
msg->ctx->srv_obj_inst);
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool lwm2m_path_is_observed(const struct lwm2m_obj_path *path)
|
|
{
|
|
int i;
|
|
struct observe_node *obs;
|
|
struct lwm2m_ctx **sock_ctx = lwm2m_sock_ctx();
|
|
|
|
for (i = 0; i < lwm2m_sock_nfds(); ++i) {
|
|
SYS_SLIST_FOR_EACH_CONTAINER(&sock_ctx[i]->observer, obs, node) {
|
|
|
|
if (lwm2m_notify_observer_list(&obs->path_list, path)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool lwm2m_engine_path_is_observed(const char *pathstr)
|
|
{
|
|
int ret;
|
|
struct lwm2m_obj_path path;
|
|
|
|
ret = lwm2m_string_to_path(pathstr, &path, '/');
|
|
if (ret < 0) {
|
|
return false;
|
|
}
|
|
|
|
return lwm2m_path_is_observed(&path);
|
|
}
|
|
|
|
int lwm2m_engine_observation_handler(struct lwm2m_message *msg, int observe, uint16_t accept,
|
|
bool composite)
|
|
{
|
|
int r;
|
|
|
|
if (observe == 0) {
|
|
/* add new observer */
|
|
r = coap_append_option_int(msg->out.out_cpkt, COAP_OPTION_OBSERVE,
|
|
OBSERVE_COUNTER_START);
|
|
if (r < 0) {
|
|
LOG_ERR("OBSERVE option error: %d", r);
|
|
return r;
|
|
}
|
|
|
|
if (composite) {
|
|
r = engine_add_composite_observer(msg, msg->token, msg->tkl, accept);
|
|
} else {
|
|
r = engine_add_observer(msg, msg->token, msg->tkl, accept);
|
|
}
|
|
|
|
if (r < 0) {
|
|
LOG_ERR("add OBSERVE error: %d", r);
|
|
}
|
|
} else if (observe == 1) {
|
|
/* remove observer */
|
|
if (composite) {
|
|
r = engine_remove_composite_observer(msg, msg->token, msg->tkl, accept);
|
|
} else {
|
|
r = engine_remove_observer_by_token(msg->ctx, msg->token, msg->tkl);
|
|
if (r < 0) {
|
|
#if defined(CONFIG_LWM2M_CANCEL_OBSERVE_BY_PATH)
|
|
r = engine_remove_observer_by_path(msg->ctx, &msg->path);
|
|
if (r < 0)
|
|
#endif /* CONFIG_LWM2M_CANCEL_OBSERVE_BY_PATH */
|
|
{
|
|
LOG_ERR("remove observe error: %d", r);
|
|
r = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
} else {
|
|
r = -EINVAL;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
int64_t engine_observe_shedule_next_event(struct observe_node *obs, uint16_t srv_obj_inst,
|
|
const int64_t timestamp)
|
|
{
|
|
struct notification_attrs attrs;
|
|
int64_t t_s = 0;
|
|
int ret;
|
|
|
|
ret = engine_observe_attribute_list_get(&obs->path_list, &attrs, srv_obj_inst);
|
|
if (ret < 0) {
|
|
return 0;
|
|
}
|
|
|
|
if (attrs.pmax) {
|
|
t_s = timestamp + MSEC_PER_SEC * attrs.pmax;
|
|
}
|
|
|
|
return t_s;
|
|
}
|
|
|
|
struct lwm2m_obj_path_list *lwm2m_engine_get_from_list(sys_slist_t *path_list)
|
|
{
|
|
sys_snode_t *path_node = sys_slist_get(path_list);
|
|
struct lwm2m_obj_path_list *entry;
|
|
|
|
if (!path_node) {
|
|
return NULL;
|
|
}
|
|
|
|
entry = SYS_SLIST_CONTAINER(path_node, entry, node);
|
|
if (entry) {
|
|
memset(entry, 0, sizeof(struct lwm2m_obj_path_list));
|
|
}
|
|
return entry;
|
|
}
|
|
|
|
void lwm2m_engine_free_list(sys_slist_t *path_list, sys_slist_t *free_list)
|
|
{
|
|
sys_snode_t *node;
|
|
|
|
while (NULL != (node = sys_slist_get(path_list))) {
|
|
/* Add to free list */
|
|
sys_slist_append(free_list, node);
|
|
}
|
|
}
|
|
|
|
void lwm2m_engine_path_list_init(sys_slist_t *lwm2m_path_list, sys_slist_t *lwm2m_free_list,
|
|
struct lwm2m_obj_path_list path_object_buf[],
|
|
uint8_t path_object_size)
|
|
{
|
|
/* Init list */
|
|
sys_slist_init(lwm2m_path_list);
|
|
sys_slist_init(lwm2m_free_list);
|
|
|
|
/* Put buffer elements to free list */
|
|
for (int i = 0; i < path_object_size; i++) {
|
|
sys_slist_append(lwm2m_free_list, &path_object_buf[i].node);
|
|
}
|
|
}
|
|
|
|
int lwm2m_engine_add_path_to_list(sys_slist_t *lwm2m_path_list, sys_slist_t *lwm2m_free_list,
|
|
const struct lwm2m_obj_path *path)
|
|
{
|
|
struct lwm2m_obj_path_list *prev = NULL;
|
|
struct lwm2m_obj_path_list *entry;
|
|
struct lwm2m_obj_path_list *new_entry;
|
|
bool add_before_current = false;
|
|
|
|
if (path->level == LWM2M_PATH_LEVEL_NONE) {
|
|
/* Clear the list if we are adding the root path which includes all */
|
|
lwm2m_engine_free_list(lwm2m_path_list, lwm2m_free_list);
|
|
}
|
|
|
|
/* Check is it at list already here */
|
|
new_entry = lwm2m_engine_get_from_list(lwm2m_free_list);
|
|
if (!new_entry) {
|
|
return -1;
|
|
}
|
|
|
|
new_entry->path = *path;
|
|
if (!sys_slist_is_empty(lwm2m_path_list)) {
|
|
|
|
/* Keep list Ordered by Object ID / Object instance / resource ID /
|
|
* Resource Instance ID
|
|
*/
|
|
SYS_SLIST_FOR_EACH_CONTAINER(lwm2m_path_list, entry, node) {
|
|
if (entry->path.level == LWM2M_PATH_LEVEL_NONE ||
|
|
lwm2m_obj_path_equal(&entry->path, &new_entry->path)) {
|
|
/* Already Root request at list or current path is at list */
|
|
sys_slist_append(lwm2m_free_list, &new_entry->node);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* algorithm assumes that list is already properly sorted and that
|
|
* there are no duplicates. general idea:
|
|
* - if at any level up to new entry's path level, IDs below the level
|
|
* match and the ID of the new entry at that level is smaller,
|
|
* insert before.
|
|
* - if all IDs of the new entry match the existing entry, insert before.
|
|
* Because of sorting and no duplicates, the existing entry must have
|
|
* higher path level and come after the new entry.
|
|
*/
|
|
switch (new_entry->path.level) {
|
|
case LWM2M_PATH_LEVEL_OBJECT:
|
|
add_before_current = new_entry->path.obj_id <= entry->path.obj_id;
|
|
break;
|
|
|
|
case LWM2M_PATH_LEVEL_OBJECT_INST:
|
|
add_before_current =
|
|
(new_entry->path.obj_id < entry->path.obj_id) ||
|
|
|
|
(entry->path.level >= LWM2M_PATH_LEVEL_OBJECT_INST &&
|
|
entry->path.obj_id == new_entry->path.obj_id &&
|
|
new_entry->path.obj_inst_id <= entry->path.obj_inst_id);
|
|
break;
|
|
|
|
case LWM2M_PATH_LEVEL_RESOURCE:
|
|
add_before_current =
|
|
(new_entry->path.obj_id < entry->path.obj_id) ||
|
|
|
|
(entry->path.level >= LWM2M_PATH_LEVEL_OBJECT_INST &&
|
|
entry->path.obj_id == new_entry->path.obj_id &&
|
|
new_entry->path.obj_inst_id < entry->path.obj_inst_id) ||
|
|
|
|
(entry->path.level >= LWM2M_PATH_LEVEL_RESOURCE &&
|
|
entry->path.obj_id == new_entry->path.obj_id &&
|
|
entry->path.obj_inst_id == new_entry->path.obj_inst_id &&
|
|
new_entry->path.res_id <= entry->path.res_id);
|
|
break;
|
|
|
|
case LWM2M_PATH_LEVEL_RESOURCE_INST:
|
|
add_before_current =
|
|
(new_entry->path.obj_id < entry->path.obj_id) ||
|
|
|
|
(entry->path.level >= LWM2M_PATH_LEVEL_OBJECT_INST &&
|
|
entry->path.obj_id == new_entry->path.obj_id &&
|
|
new_entry->path.obj_inst_id < entry->path.obj_inst_id) ||
|
|
|
|
(entry->path.level >= LWM2M_PATH_LEVEL_RESOURCE &&
|
|
entry->path.obj_id == new_entry->path.obj_id &&
|
|
entry->path.obj_inst_id == new_entry->path.obj_inst_id &&
|
|
new_entry->path.res_id < entry->path.res_id) ||
|
|
|
|
(entry->path.level >= LWM2M_PATH_LEVEL_RESOURCE_INST &&
|
|
entry->path.obj_id == new_entry->path.obj_id &&
|
|
entry->path.obj_inst_id == new_entry->path.obj_inst_id &&
|
|
entry->path.res_id == new_entry->path.res_id &&
|
|
new_entry->path.res_inst_id <= entry->path.res_inst_id);
|
|
break;
|
|
}
|
|
|
|
if (add_before_current) {
|
|
if (prev) {
|
|
sys_slist_insert(lwm2m_path_list, &prev->node,
|
|
&new_entry->node);
|
|
} else {
|
|
sys_slist_prepend(lwm2m_path_list, &new_entry->node);
|
|
}
|
|
return 0;
|
|
}
|
|
prev = entry;
|
|
}
|
|
}
|
|
|
|
/* Add First or new tail entry */
|
|
sys_slist_append(lwm2m_path_list, &new_entry->node);
|
|
return 0;
|
|
}
|
|
|
|
void lwm2m_engine_clear_duplicate_path(sys_slist_t *lwm2m_path_list, sys_slist_t *lwm2m_free_list)
|
|
{
|
|
struct lwm2m_obj_path_list *prev = NULL;
|
|
struct lwm2m_obj_path_list *entry, *tmp;
|
|
bool remove_entry;
|
|
|
|
if (sys_slist_is_empty(lwm2m_path_list)) {
|
|
return;
|
|
}
|
|
|
|
/* Keep list Ordered but remove if shorter path is similar */
|
|
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(lwm2m_path_list, entry, tmp, node) {
|
|
|
|
if (prev && prev->path.level < entry->path.level) {
|
|
if (prev->path.level == LWM2M_PATH_LEVEL_OBJECT &&
|
|
entry->path.obj_id == prev->path.obj_id) {
|
|
remove_entry = true;
|
|
} else if (prev->path.level == LWM2M_PATH_LEVEL_OBJECT_INST &&
|
|
entry->path.obj_id == prev->path.obj_id &&
|
|
entry->path.obj_inst_id == prev->path.obj_inst_id) {
|
|
/* Remove current from the list */
|
|
remove_entry = true;
|
|
} else if (prev->path.level == LWM2M_PATH_LEVEL_RESOURCE &&
|
|
entry->path.obj_id == prev->path.obj_id &&
|
|
entry->path.obj_inst_id == prev->path.obj_inst_id &&
|
|
entry->path.res_id == prev->path.res_id) {
|
|
/* Remove current from the list */
|
|
remove_entry = true;
|
|
} else {
|
|
remove_entry = false;
|
|
}
|
|
|
|
if (remove_entry) {
|
|
/* Remove Current entry */
|
|
sys_slist_remove(lwm2m_path_list, &prev->node, &entry->node);
|
|
sys_slist_append(lwm2m_free_list, &entry->node);
|
|
} else {
|
|
prev = entry;
|
|
}
|
|
} else {
|
|
prev = entry;
|
|
}
|
|
}
|
|
}
|