/* * Copyright (c) 2017 Linaro Limited * Copyright (c) 2018-2019 Foundries.io * * SPDX-License-Identifier: Apache-2.0 */ /* * Uses some original concepts by: * Joakim Eriksson * Niclas Finne * Joel Hoglund */ #define LOG_MODULE_NAME net_lwm2m_engine #define LOG_LEVEL CONFIG_LWM2M_LOG_LEVEL #include LOG_MODULE_REGISTER(LOG_MODULE_NAME); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(CONFIG_LWM2M_DTLS_SUPPORT) #include #endif #if defined(CONFIG_DNS_RESOLVER) #include #endif #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" #include "lwm2m_util.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_RD_CLIENT_SUPPORT #include "lwm2m_rd_client.h" #endif #if IS_ENABLED(CONFIG_NET_TC_THREAD_COOPERATIVE) /* Lowest priority cooperative thread */ #define THREAD_PRIORITY K_PRIO_COOP(CONFIG_NUM_COOP_PRIORITIES - 1) #else #define THREAD_PRIORITY K_PRIO_PREEMPT(CONFIG_NUM_PREEMPT_PRIORITIES - 1) #endif #define ENGINE_UPDATE_INTERVAL_MS 500 #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 #define BINDING_OPT_MAX_LEN 3 /* "UQ" */ #define QUEUE_OPT_MAX_LEN 2 /* "Q" */ #define MAX_TOKEN_LEN 8 struct observe_node { sys_snode_t node; sys_slist_t path_list; /* List of Observation path */ uint8_t token[MAX_TOKEN_LEN]; /* Observation Token */ int64_t event_timestamp; /* Timestamp for trig next Notify */ int64_t last_timestamp; /* Timestamp from last Notify */ uint32_t counter; uint16_t format; uint8_t tkl; bool resource_update : 1; /* Resource is updated */ bool composite : 1; /* Composite Observation */ }; 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; }; #ifdef CONFIG_LWM2M_VERSION_1_1 #define LWM2M_ENGINE_MAX_OBSERVER_PATH CONFIG_LWM2M_ENGINE_MAX_OBSERVER * 3 #else #define LWM2M_ENGINE_MAX_OBSERVER_PATH CONFIG_LWM2M_ENGINE_MAX_OBSERVER #endif static struct lwm2m_obj_path_list observe_paths[LWM2M_ENGINE_MAX_OBSERVER_PATH]; static sys_slist_t obs_obj_path_list; static struct observe_node observe_node_data[CONFIG_LWM2M_ENGINE_MAX_OBSERVER]; #define MAX_PERIODIC_SERVICE 10 struct service_node { sys_snode_t node; k_work_handler_t service_work; uint32_t min_call_period; /* ms */ uint64_t last_timestamp; /* ms */ }; static struct service_node service_node_data[MAX_PERIODIC_SERVICE]; static sys_slist_t engine_obj_list; static sys_slist_t engine_obj_inst_list; static sys_slist_t engine_service_list; #define LWM2M_DP_CLIENT_URI "dp" static K_KERNEL_STACK_DEFINE(engine_thread_stack, CONFIG_LWM2M_ENGINE_STACK_SIZE); static struct k_thread engine_thread_data; #define MAX_POLL_FD CONFIG_NET_SOCKETS_POLL_MAX static struct lwm2m_ctx *sock_ctx[MAX_POLL_FD]; static struct pollfd sock_fds[MAX_POLL_FD]; static int sock_nfds; #define NUM_BLOCK1_CONTEXT CONFIG_LWM2M_NUM_BLOCK1_CONTEXT /* TODO: figure out what's correct value */ #define TIMEOUT_BLOCKWISE_TRANSFER_MS (MSEC_PER_SEC * 30) static struct lwm2m_block_context block1_contexts[NUM_BLOCK1_CONTEXT]; /* 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]; static struct lwm2m_engine_obj *get_engine_obj(int obj_id); static struct lwm2m_engine_obj_inst *get_engine_obj_inst(int obj_id, int obj_inst_id); /* Shared set of in-flight LwM2M messages */ static struct lwm2m_message messages[CONFIG_LWM2M_ENGINE_MAX_MESSAGES]; /* Forward declarations. */ static int path_to_objs(const struct lwm2m_obj_path *path, struct lwm2m_engine_obj_inst **obj_inst, struct lwm2m_engine_obj_field **obj_field, struct lwm2m_engine_res **res, struct lwm2m_engine_res_inst **res_inst); static struct lwm2m_obj_path *lwm2m_read_first_path_ptr(sys_slist_t *lwm2m_path_list); static struct lwm2m_obj_path_list *lwm2m_engine_get_from_list(sys_slist_t *path_list); static int do_send_op(struct lwm2m_message *msg, uint16_t content_format, sys_slist_t *lwm2m_path_list); static 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); static void lwm2m_engine_free_list(sys_slist_t *path_list, sys_slist_t *free_list); /* for debugging: to print IP addresses */ char *lwm2m_sprint_ip_addr(const struct sockaddr *addr) { static char buf[NET_IPV6_ADDR_LEN]; if (addr->sa_family == AF_INET6) { return net_addr_ntop(AF_INET6, &net_sin6(addr)->sin6_addr, buf, sizeof(buf)); } if (addr->sa_family == AF_INET) { return net_addr_ntop(AF_INET, &net_sin(addr)->sin_addr, buf, sizeof(buf)); } LOG_ERR("Unknown IP address family:%d", addr->sa_family); strcpy(buf, "unk"); return buf; } static uint8_t to_hex_digit(uint8_t digit) { if (digit >= 10U) { return digit - 10U + 'a'; } return digit + '0'; } static char *sprint_token(const uint8_t *token, uint8_t tkl) { static char buf[32]; char *ptr = buf; if (token && tkl != 0) { int i; tkl = MIN(tkl, sizeof(buf) / 2 - 1); for (i = 0; i < tkl; i++) { *ptr++ = to_hex_digit(token[i] >> 4); *ptr++ = to_hex_digit(token[i] & 0x0F); } *ptr = '\0'; } else { strcpy(buf, "[no-token]"); } return buf; } /* block-wise transfer functions */ enum coap_block_size lwm2m_default_block_size(void) { switch (CONFIG_LWM2M_COAP_BLOCK_SIZE) { case 16: return COAP_BLOCK_16; case 32: return COAP_BLOCK_32; case 64: return COAP_BLOCK_64; case 128: return COAP_BLOCK_128; case 256: return COAP_BLOCK_256; case 512: return COAP_BLOCK_512; case 1024: return COAP_BLOCK_1024; } return COAP_BLOCK_256; } static int init_block_ctx(const uint8_t *token, uint8_t tkl, struct lwm2m_block_context **ctx) { int i; int64_t timestamp; *ctx = NULL; timestamp = k_uptime_get(); for (i = 0; i < NUM_BLOCK1_CONTEXT; i++) { if (block1_contexts[i].tkl == 0U) { *ctx = &block1_contexts[i]; break; } if (timestamp - block1_contexts[i].timestamp > TIMEOUT_BLOCKWISE_TRANSFER_MS) { *ctx = &block1_contexts[i]; /* TODO: notify application for block * transfer timeout */ break; } } if (*ctx == NULL) { LOG_ERR("Cannot find free block context"); return -ENOMEM; } (*ctx)->tkl = tkl; memcpy((*ctx)->token, token, tkl); coap_block_transfer_init(&(*ctx)->ctx, lwm2m_default_block_size(), 0); (*ctx)->timestamp = timestamp; (*ctx)->expected = 0; (*ctx)->last_block = false; #if defined(CONFIG_LWM2M_RW_SENML_JSON_SUPPORT) lwm2m_senml_json_context_init(&(*ctx)->senml_json_ctx); #endif memset(&(*ctx)->opaque, 0, sizeof((*ctx)->opaque)); return 0; } static int get_block_ctx(const uint8_t *token, uint8_t tkl, struct lwm2m_block_context **ctx) { int i; *ctx = NULL; for (i = 0; i < NUM_BLOCK1_CONTEXT; i++) { if (block1_contexts[i].tkl == tkl && memcmp(token, block1_contexts[i].token, tkl) == 0) { *ctx = &block1_contexts[i]; /* refresh timestamp */ (*ctx)->timestamp = k_uptime_get(); break; } } if (*ctx == NULL) { return -ENOENT; } return 0; } static void free_block_ctx(struct lwm2m_block_context *ctx) { if (ctx == NULL) { return; } ctx->tkl = 0U; } /* observer functions */ 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; } static 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(struct lwm2m_obj_path *o_p, struct lwm2m_obj_path *p) { /* updated path is deeper than obs node, skip */ if (p->level > o_p->level) { return false; } /* 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, 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(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(struct lwm2m_obj_path *path) { struct observe_node *obs; struct notification_attrs nattrs = { 0 }; int64_t timestamp; int ret = 0; int i; if (path->level < LWM2M_PATH_LEVEL_RESOURCE) { return 0; } /* look for observers which match our resource */ for (i = 0; i < 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++; } } } 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->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, NULL); } } LOG_DBG("token:'%s' addr:%s", log_strdup(sprint_token(token, tkl)), log_strdup(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_LEN]; LOG_DBG("Removing observer %p for path %s", obs, lwm2m_path_log_strdup(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; } static 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; 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; 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, log_strdup(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; } 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; LOG_DBG("OBSERVER Composite DUPLICATE [%s]", log_strdup(lwm2m_sprint_ip_addr(&msg->ctx->remote_addr))); return 0; } 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 0; } static 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)); } static 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", log_strdup(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", log_strdup(sprint_token(token, tkl))); return 0; } #if defined(CONFIG_LOG) char *lwm2m_path_log_strdup(char *buf, struct lwm2m_obj_path *path) { size_t cur; if (!path) { sprintf(buf, "/"); return log_strdup(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 log_strdup(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_LEN]; 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_strdup(buf, path)); remove_observer_from_list(ctx, prev_node, obs); return 0; } #endif /* CONFIG_LWM2M_CANCEL_OBSERVE_BY_PATH */ static 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; /* remove observer instances accordingly */ for (i = 0; i < 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; } } } } /* engine object */ void lwm2m_register_obj(struct lwm2m_engine_obj *obj) { sys_slist_append(&engine_obj_list, &obj->node); } void lwm2m_unregister_obj(struct lwm2m_engine_obj *obj) { engine_remove_observer_by_id(obj->obj_id, -1); sys_slist_find_and_remove(&engine_obj_list, &obj->node); } static struct lwm2m_engine_obj *get_engine_obj(int obj_id) { struct lwm2m_engine_obj *obj; SYS_SLIST_FOR_EACH_CONTAINER(&engine_obj_list, obj, node) { if (obj->obj_id == obj_id) { return obj; } } return NULL; } struct lwm2m_engine_obj_field * lwm2m_get_engine_obj_field(struct lwm2m_engine_obj *obj, int res_id) { int i; if (obj && obj->fields && obj->field_count > 0) { for (i = 0; i < obj->field_count; i++) { if (obj->fields[i].res_id == res_id) { return &obj->fields[i]; } } } return NULL; } /* engine object instance */ static void engine_register_obj_inst(struct lwm2m_engine_obj_inst *obj_inst) { sys_slist_append(&engine_obj_inst_list, &obj_inst->node); } static void engine_unregister_obj_inst(struct lwm2m_engine_obj_inst *obj_inst) { engine_remove_observer_by_id( obj_inst->obj->obj_id, obj_inst->obj_inst_id); sys_slist_find_and_remove(&engine_obj_inst_list, &obj_inst->node); } static struct lwm2m_engine_obj_inst *get_engine_obj_inst(int obj_id, int obj_inst_id) { struct lwm2m_engine_obj_inst *obj_inst; SYS_SLIST_FOR_EACH_CONTAINER(&engine_obj_inst_list, obj_inst, node) { if (obj_inst->obj->obj_id == obj_id && obj_inst->obj_inst_id == obj_inst_id) { return obj_inst; } } return NULL; } static struct lwm2m_engine_obj_inst * next_engine_obj_inst(int obj_id, int obj_inst_id) { struct lwm2m_engine_obj_inst *obj_inst, *next = NULL; SYS_SLIST_FOR_EACH_CONTAINER(&engine_obj_inst_list, obj_inst, node) { if (obj_inst->obj->obj_id == obj_id && obj_inst->obj_inst_id > obj_inst_id && (!next || next->obj_inst_id > obj_inst->obj_inst_id)) { next = obj_inst; } } return next; } int lwm2m_create_obj_inst(uint16_t obj_id, uint16_t obj_inst_id, struct lwm2m_engine_obj_inst **obj_inst) { struct lwm2m_engine_obj *obj; int ret; *obj_inst = NULL; obj = get_engine_obj(obj_id); if (!obj) { LOG_ERR("unable to find obj: %u", obj_id); return -ENOENT; } if (!obj->create_cb) { LOG_ERR("obj %u has no create_cb", obj_id); return -EINVAL; } if (obj->instance_count + 1 > obj->max_instance_count) { LOG_ERR("no more instances available for obj %u", obj_id); return -ENOMEM; } *obj_inst = obj->create_cb(obj_inst_id); if (!*obj_inst) { LOG_ERR("unable to create obj %u instance %u", obj_id, obj_inst_id); /* * Already checked for instance count total. * This can only be an error if the object instance exists. */ return -EEXIST; } obj->instance_count++; (*obj_inst)->obj = obj; (*obj_inst)->obj_inst_id = obj_inst_id; engine_register_obj_inst(*obj_inst); if (obj->user_create_cb) { ret = obj->user_create_cb(obj_inst_id); if (ret < 0) { LOG_ERR("Error in user obj create %u/%u: %d", obj_id, obj_inst_id, ret); lwm2m_delete_obj_inst(obj_id, obj_inst_id); return ret; } } return 0; } int lwm2m_delete_obj_inst(uint16_t obj_id, uint16_t obj_inst_id) { int i, ret = 0; struct lwm2m_engine_obj *obj; struct lwm2m_engine_obj_inst *obj_inst; obj = get_engine_obj(obj_id); if (!obj) { return -ENOENT; } obj_inst = get_engine_obj_inst(obj_id, obj_inst_id); if (!obj_inst) { return -ENOENT; } if (obj->user_delete_cb) { ret = obj->user_delete_cb(obj_inst_id); if (ret < 0) { LOG_ERR("Error in user obj delete %u/%u: %d", obj_id, obj_inst_id, ret); /* don't return error */ } } engine_unregister_obj_inst(obj_inst); obj->instance_count--; if (obj->delete_cb) { ret = obj->delete_cb(obj_inst_id); } /* reset obj_inst and res_inst data structure */ for (i = 0; i < obj_inst->resource_count; i++) { clear_attrs(&obj_inst->resources[i]); (void)memset(obj_inst->resources + i, 0, sizeof(struct lwm2m_engine_res)); } clear_attrs(obj_inst); (void)memset(obj_inst, 0, sizeof(struct lwm2m_engine_obj_inst)); return ret; } /* utility functions */ static int coap_options_to_path(struct coap_option *opt, int options_count, struct lwm2m_obj_path *path) { uint16_t len, *id[4] = { &path->obj_id, &path->obj_inst_id, &path->res_id, &path->res_inst_id }; path->level = options_count; for (int i = 0; i < options_count; i++) { *id[i] = lwm2m_atou16(opt[i].value, opt[i].len, &len); if (len == 0U || opt[i].len != len) { path->level = i; break; } } return options_count == path->level ? 0 : -EINVAL; } static struct lwm2m_message *find_msg(struct coap_pending *pending, struct coap_reply *reply) { size_t i; if (!pending && !reply) { return NULL; } for (i = 0; i < CONFIG_LWM2M_ENGINE_MAX_MESSAGES; i++) { if (pending != NULL && messages[i].ctx && messages[i].pending == pending) { return &messages[i]; } if (reply != NULL && messages[i].ctx && messages[i].reply == reply) { return &messages[i]; } } return NULL; } struct lwm2m_message *lwm2m_get_message(struct lwm2m_ctx *client_ctx) { size_t i; for (i = 0; i < CONFIG_LWM2M_ENGINE_MAX_MESSAGES; i++) { if (!messages[i].ctx) { messages[i].ctx = client_ctx; return &messages[i]; } } return NULL; } void lwm2m_reset_message(struct lwm2m_message *msg, bool release) { if (!msg) { return; } if (msg->pending) { coap_pending_clear(msg->pending); } if (msg->reply) { /* make sure we want to clear the reply */ coap_reply_clear(msg->reply); } if (msg->ctx) { sys_slist_find_and_remove(&msg->ctx->pending_sends, &msg->node); #if defined(CONFIG_LWM2M_QUEUE_MODE_ENABLED) sys_slist_find_and_remove(&msg->ctx->queued_messages, &msg->node); #endif } if (release) { (void)memset(msg, 0, sizeof(*msg)); } else { msg->message_timeout_cb = NULL; (void)memset(&msg->cpkt, 0, sizeof(msg->cpkt)); } } int lwm2m_init_message(struct lwm2m_message *msg) { uint8_t tokenlen = 0U; uint8_t *token = NULL; int r = 0; if (!msg || !msg->ctx) { LOG_ERR("LwM2M message is invalid."); return -EINVAL; } if (msg->tkl == LWM2M_MSG_TOKEN_GENERATE_NEW) { tokenlen = 8U; token = coap_next_token(); } else if (msg->token && msg->tkl != 0) { tokenlen = msg->tkl; token = msg->token; } r = coap_packet_init(&msg->cpkt, msg->msg_data, sizeof(msg->msg_data), COAP_VERSION_1, msg->type, tokenlen, token, msg->code, msg->mid); if (r < 0) { LOG_ERR("coap packet init error (err:%d)", r); goto cleanup; } /* only TYPE_CON messages need pending tracking / reply handling */ if (msg->type != COAP_TYPE_CON) { return 0; } msg->pending = coap_pending_next_unused( msg->ctx->pendings, CONFIG_LWM2M_ENGINE_MAX_PENDING); if (!msg->pending) { LOG_ERR("Unable to find a free pending to track " "retransmissions."); r = -ENOMEM; goto cleanup; } r = coap_pending_init(msg->pending, &msg->cpkt, &msg->ctx->remote_addr, COAP_DEFAULT_MAX_RETRANSMIT); if (r < 0) { LOG_ERR("Unable to initialize a pending " "retransmission (err:%d).", r); goto cleanup; } if (msg->reply_cb) { msg->reply = coap_reply_next_unused( msg->ctx->replies, CONFIG_LWM2M_ENGINE_MAX_REPLIES); if (!msg->reply) { LOG_ERR("No resources for waiting for replies."); r = -ENOMEM; goto cleanup; } coap_reply_clear(msg->reply); coap_reply_init(msg->reply, &msg->cpkt); msg->reply->reply = msg->reply_cb; } return 0; cleanup: lwm2m_reset_message(msg, true); return r; } #if defined(CONFIG_LWM2M_QUEUE_MODE_ENABLED) int lwm2m_engine_connection_resume(struct lwm2m_ctx *client_ctx) { #ifdef CONFIG_LWM2M_DTLS_SUPPORT if (!client_ctx->use_dtls) { return 0; } if (client_ctx->connection_suspended) { client_ctx->connection_suspended = false; LOG_DBG("Resume suspended connection"); return lwm2m_socket_start(client_ctx); } #endif return 0; } #endif #if defined(CONFIG_LWM2M_QUEUE_MODE_ENABLED) int lwm2m_push_queued_buffers(struct lwm2m_ctx *client_ctx) { client_ctx->buffer_client_messages = false; while (!sys_slist_is_empty(&client_ctx->queued_messages)) { sys_snode_t *msg_node = sys_slist_get(&client_ctx->queued_messages); struct lwm2m_message *msg; if (!msg_node) { break; } msg = SYS_SLIST_CONTAINER(msg_node, msg, node); sys_slist_append(&msg->ctx->pending_sends, &msg->node); } return 0; } #endif int lwm2m_send_message_async(struct lwm2m_message *msg) { #if defined(CONFIG_LWM2M_QUEUE_MODE_ENABLED) && defined(CONFIG_LWM2M_RD_CLIENT_SUPPORT) int ret; ret = lwm2m_rd_client_connection_resume(msg->ctx); if (ret) { lwm2m_reset_message(msg, true); return ret; } #endif sys_slist_append(&msg->ctx->pending_sends, &msg->node); if (IS_ENABLED(CONFIG_LWM2M_RD_CLIENT_SUPPORT) && IS_ENABLED(CONFIG_LWM2M_QUEUE_MODE_ENABLED)) { engine_update_tx_time(); } return 0; } int lwm2m_information_interface_send(struct lwm2m_message *msg) { #if defined(CONFIG_LWM2M_QUEUE_MODE_ENABLED) && defined(CONFIG_LWM2M_RD_CLIENT_SUPPORT) int ret; ret = lwm2m_rd_client_connection_resume(msg->ctx); if (ret) { lwm2m_reset_message(msg, true); return ret; } if (msg->ctx->buffer_client_messages) { sys_slist_append(&msg->ctx->queued_messages, &msg->node); return 0; } #endif sys_slist_append(&msg->ctx->pending_sends, &msg->node); if (IS_ENABLED(CONFIG_LWM2M_RD_CLIENT_SUPPORT) && IS_ENABLED(CONFIG_LWM2M_QUEUE_MODE_ENABLED)) { engine_update_tx_time(); } return 0; } static int lwm2m_send_message(struct lwm2m_message *msg) { int rc; if (!msg || !msg->ctx) { LOG_ERR("LwM2M message is invalid."); return -EINVAL; } if (msg->type == COAP_TYPE_CON) { coap_pending_cycle(msg->pending); } rc = send(msg->ctx->sock_fd, msg->cpkt.data, msg->cpkt.offset, 0); if (rc < 0) { LOG_ERR("Failed to send packet, err %d", errno); if (msg->type != COAP_TYPE_CON) { lwm2m_reset_message(msg, true); } return -errno; } if (msg->type != COAP_TYPE_CON) { lwm2m_reset_message(msg, true); } return 0; } int lwm2m_send_empty_ack(struct lwm2m_ctx *client_ctx, uint16_t mid) { struct lwm2m_message *msg; int ret; msg = lwm2m_get_message(client_ctx); if (!msg) { LOG_ERR("Unable to get a lwm2m message!"); return -ENOMEM; } msg->type = COAP_TYPE_ACK; msg->code = COAP_CODE_EMPTY; msg->mid = mid; ret = lwm2m_init_message(msg); if (ret) { goto cleanup; } lwm2m_send_message_async(msg); return 0; cleanup: lwm2m_reset_message(msg, true); return ret; } void lwm2m_acknowledge(struct lwm2m_ctx *client_ctx) { struct lwm2m_message *request; if (client_ctx == NULL || client_ctx->processed_req == NULL) { return; } request = (struct lwm2m_message *)client_ctx->processed_req; if (request->acknowledged) { return; } if (lwm2m_send_empty_ack(client_ctx, request->mid) < 0) { return; } request->acknowledged = true; } int lwm2m_register_payload_handler(struct lwm2m_message *msg) { struct lwm2m_engine_obj *obj; struct lwm2m_engine_obj_inst *obj_inst; int ret; ret = engine_put_begin(&msg->out, NULL); if (ret < 0) { return ret; } SYS_SLIST_FOR_EACH_CONTAINER(&engine_obj_list, obj, node) { /* Security obj MUST NOT be part of registration message */ if (obj->obj_id == LWM2M_OBJECT_SECURITY_ID) { continue; } /* Only report when no instance available or it's * needed to report object version. */ if (obj->instance_count == 0U || lwm2m_engine_shall_report_obj_version(obj)) { struct lwm2m_obj_path path = { .obj_id = obj->obj_id, .level = LWM2M_PATH_LEVEL_OBJECT, }; ret = engine_put_corelink(&msg->out, &path); if (ret < 0) { return ret; } if (obj->instance_count == 0U) { continue; } } SYS_SLIST_FOR_EACH_CONTAINER(&engine_obj_inst_list, obj_inst, node) { if (obj_inst->obj->obj_id == obj->obj_id) { 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; } } } } return 0; } /* input / output selection */ static int select_writer(struct lwm2m_output_context *out, uint16_t accept) { switch (accept) { case LWM2M_FORMAT_APP_LINK_FORMAT: out->writer = &link_format_writer; break; case LWM2M_FORMAT_PLAIN_TEXT: case LWM2M_FORMAT_OMA_PLAIN_TEXT: out->writer = &plain_text_writer; break; #ifdef CONFIG_LWM2M_RW_OMA_TLV_SUPPORT case LWM2M_FORMAT_OMA_TLV: case LWM2M_FORMAT_OMA_OLD_TLV: out->writer = &oma_tlv_writer; break; #endif #ifdef CONFIG_LWM2M_RW_JSON_SUPPORT case LWM2M_FORMAT_OMA_JSON: case LWM2M_FORMAT_OMA_OLD_JSON: out->writer = &json_writer; break; #endif #if defined(CONFIG_LWM2M_RW_SENML_JSON_SUPPORT) case LWM2M_FORMAT_APP_SEML_JSON: out->writer = &senml_json_writer; break; #endif #ifdef CONFIG_LWM2M_RW_CBOR_SUPPORT case LWM2M_FORMAT_APP_CBOR: out->writer = &cbor_writer; break; #endif default: LOG_WRN("Unknown content type %u", accept); return -ENOMSG; } return 0; } static int select_reader(struct lwm2m_input_context *in, uint16_t format) { switch (format) { case LWM2M_FORMAT_APP_OCTET_STREAM: case LWM2M_FORMAT_PLAIN_TEXT: case LWM2M_FORMAT_OMA_PLAIN_TEXT: in->reader = &plain_text_reader; break; #ifdef CONFIG_LWM2M_RW_OMA_TLV_SUPPORT case LWM2M_FORMAT_OMA_TLV: case LWM2M_FORMAT_OMA_OLD_TLV: in->reader = &oma_tlv_reader; break; #endif #ifdef CONFIG_LWM2M_RW_JSON_SUPPORT case LWM2M_FORMAT_OMA_JSON: case LWM2M_FORMAT_OMA_OLD_JSON: in->reader = &json_reader; break; #endif #if defined(CONFIG_LWM2M_RW_SENML_JSON_SUPPORT) case LWM2M_FORMAT_APP_SEML_JSON: in->reader = &senml_json_reader; break; #endif #ifdef CONFIG_LWM2M_RW_CBOR_SUPPORT case LWM2M_FORMAT_APP_CBOR: in->reader = &cbor_reader; break; #endif default: LOG_WRN("Unknown content type %u", format); return -ENOMSG; } return 0; } /* user data setter functions */ static int path_to_objs(const struct lwm2m_obj_path *path, struct lwm2m_engine_obj_inst **obj_inst, struct lwm2m_engine_obj_field **obj_field, struct lwm2m_engine_res **res, struct lwm2m_engine_res_inst **res_inst) { struct lwm2m_engine_obj_inst *oi; struct lwm2m_engine_obj_field *of; struct lwm2m_engine_res *r = NULL; struct lwm2m_engine_res_inst *ri = NULL; int i; if (!path) { return -EINVAL; } oi = get_engine_obj_inst(path->obj_id, path->obj_inst_id); if (!oi) { LOG_ERR("obj instance %d/%d not found", path->obj_id, path->obj_inst_id); return -ENOENT; } if (!oi->resources || oi->resource_count == 0U) { LOG_ERR("obj instance has no resources"); return -EINVAL; } of = lwm2m_get_engine_obj_field(oi->obj, path->res_id); if (!of) { LOG_ERR("obj field %d not found", path->res_id); return -ENOENT; } for (i = 0; i < oi->resource_count; i++) { if (oi->resources[i].res_id == path->res_id) { r = &oi->resources[i]; break; } } if (!r) { LOG_ERR("resource %d not found", path->res_id); return -ENOENT; } for (i = 0; i < r->res_inst_count; i++) { if (r->res_instances[i].res_inst_id == path->res_inst_id) { ri = &r->res_instances[i]; break; } } /* specifically don't complain about missing resource instance */ if (obj_inst) { *obj_inst = oi; } if (obj_field) { *obj_field = of; } if (res) { *res = r; } if (ri && res_inst) { *res_inst = ri; } return 0; } 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; } 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]; } int lwm2m_engine_create_obj_inst(const char *pathstr) { struct lwm2m_obj_path path; struct lwm2m_engine_obj_inst *obj_inst; int ret = 0; LOG_DBG("path:%s", log_strdup(pathstr)); /* translate path -> path_obj */ ret = lwm2m_string_to_path(pathstr, &path, '/'); if (ret < 0) { return ret; } if (path.level != 2U) { LOG_ERR("path must have 2 parts"); return -EINVAL; } ret = lwm2m_create_obj_inst(path.obj_id, path.obj_inst_id, &obj_inst); if (ret < 0) { return ret; } #if defined(CONFIG_LWM2M_RD_CLIENT_SUPPORT) engine_trigger_update(true); #endif return 0; } int lwm2m_engine_delete_obj_inst(const char *pathstr) { struct lwm2m_obj_path path; int ret = 0; LOG_DBG("path: %s", log_strdup(pathstr)); /* translate path -> path_obj */ ret = lwm2m_string_to_path(pathstr, &path, '/'); if (ret < 0) { return ret; } if (path.level != 2U) { LOG_ERR("path must have 2 parts"); return -EINVAL; } ret = lwm2m_delete_obj_inst(path.obj_id, path.obj_inst_id); if (ret < 0) { return ret; } #if defined(CONFIG_LWM2M_RD_CLIENT_SUPPORT) engine_trigger_update(true); #endif return 0; } int lwm2m_engine_set_res_data(const char *pathstr, void *data_ptr, uint16_t data_len, uint8_t data_flags) { struct lwm2m_obj_path path; struct lwm2m_engine_res_inst *res_inst = NULL; int ret = 0; /* translate path -> path_obj */ ret = lwm2m_string_to_path(pathstr, &path, '/'); if (ret < 0) { return ret; } if (path.level < 3) { LOG_ERR("path must have at least 3 parts"); return -EINVAL; } /* look up resource obj */ ret = path_to_objs(&path, NULL, NULL, NULL, &res_inst); if (ret < 0) { return ret; } if (!res_inst) { LOG_ERR("res instance %d not found", path.res_inst_id); return -ENOENT; } /* assign data elements */ res_inst->data_ptr = data_ptr; res_inst->data_len = data_len; res_inst->max_data_len = data_len; res_inst->data_flags = data_flags; return ret; } static int lwm2m_engine_set(const char *pathstr, void *value, uint16_t len) { struct lwm2m_obj_path path; struct lwm2m_engine_obj_inst *obj_inst; struct lwm2m_engine_obj_field *obj_field; struct lwm2m_engine_res *res = NULL; struct lwm2m_engine_res_inst *res_inst = NULL; void *data_ptr = NULL; size_t max_data_len = 0; int ret = 0; bool changed = false; LOG_DBG("path:%s, value:%p, len:%d", log_strdup(pathstr), value, len); /* translate path -> path_obj */ ret = lwm2m_string_to_path(pathstr, &path, '/'); if (ret < 0) { return ret; } if (path.level < 3) { LOG_ERR("path must have at least 3 parts"); return -EINVAL; } /* look up resource obj */ ret = path_to_objs(&path, &obj_inst, &obj_field, &res, &res_inst); if (ret < 0) { return ret; } if (!res_inst) { LOG_ERR("res instance %d not found", path.res_inst_id); return -ENOENT; } if (LWM2M_HAS_RES_FLAG(res_inst, LWM2M_RES_DATA_FLAG_RO)) { LOG_ERR("res instance data pointer is read-only " "[%u/%u/%u/%u:%u]", path.obj_id, path.obj_inst_id, path.res_id, path.res_inst_id, path.level); return -EACCES; } /* setup initial data elements */ data_ptr = res_inst->data_ptr; max_data_len = res_inst->max_data_len; /* allow user to override data elements via callback */ if (res->pre_write_cb) { data_ptr = res->pre_write_cb(obj_inst->obj_inst_id, res->res_id, res_inst->res_inst_id, &max_data_len); } if (!data_ptr) { LOG_ERR("res instance data pointer is NULL [%u/%u/%u/%u:%u]", path.obj_id, path.obj_inst_id, path.res_id, path.res_inst_id, path.level); return -EINVAL; } /* check length (note: we add 1 to string length for NULL pad) */ if (len > max_data_len - (obj_field->data_type == LWM2M_RES_TYPE_STRING ? 1 : 0)) { LOG_ERR("length %u is too long for res instance %d data", len, path.res_id); return -ENOMEM; } if (memcmp(data_ptr, value, len) != 0) { changed = true; } #if CONFIG_LWM2M_ENGINE_VALIDATION_BUFFER_SIZE > 0 if (res->validate_cb) { ret = res->validate_cb(obj_inst->obj_inst_id, res->res_id, res_inst->res_inst_id, value, len, false, 0); if (ret < 0) { return -EINVAL; } } #endif /* CONFIG_LWM2M_ENGINE_VALIDATION_BUFFER_SIZE > 0 */ switch (obj_field->data_type) { case LWM2M_RES_TYPE_OPAQUE: memcpy((uint8_t *)data_ptr, value, len); break; case LWM2M_RES_TYPE_STRING: memcpy((uint8_t *)data_ptr, value, len); ((uint8_t *)data_ptr)[len] = '\0'; break; case LWM2M_RES_TYPE_U32: case LWM2M_RES_TYPE_TIME: *((uint32_t *)data_ptr) = *(uint32_t *)value; break; case LWM2M_RES_TYPE_U16: *((uint16_t *)data_ptr) = *(uint16_t *)value; break; case LWM2M_RES_TYPE_U8: *((uint8_t *)data_ptr) = *(uint8_t *)value; break; case LWM2M_RES_TYPE_S64: *((int64_t *)data_ptr) = *(int64_t *)value; break; case LWM2M_RES_TYPE_S32: *((int32_t *)data_ptr) = *(int32_t *)value; break; case LWM2M_RES_TYPE_S16: *((int16_t *)data_ptr) = *(int16_t *)value; break; case LWM2M_RES_TYPE_S8: *((int8_t *)data_ptr) = *(int8_t *)value; break; case LWM2M_RES_TYPE_BOOL: *((bool *)data_ptr) = *(bool *)value; break; case LWM2M_RES_TYPE_FLOAT: *(double *)data_ptr = *(double *)value; break; case LWM2M_RES_TYPE_OBJLNK: *((struct lwm2m_objlnk *)data_ptr) = *(struct lwm2m_objlnk *)value; break; default: LOG_ERR("unknown obj data_type %d", obj_field->data_type); return -EINVAL; } res_inst->data_len = len; if (res->post_write_cb) { ret = res->post_write_cb(obj_inst->obj_inst_id, res->res_id, res_inst->res_inst_id, data_ptr, len, false, 0); } if (changed && LWM2M_HAS_PERM(obj_field, LWM2M_PERM_R)) { NOTIFY_OBSERVER_PATH(&path); } return ret; } int lwm2m_engine_set_opaque(const char *pathstr, char *data_ptr, uint16_t data_len) { return lwm2m_engine_set(pathstr, data_ptr, data_len); } int lwm2m_engine_set_string(const char *pathstr, char *data_ptr) { return lwm2m_engine_set(pathstr, data_ptr, strlen(data_ptr)); } int lwm2m_engine_set_u8(const char *pathstr, uint8_t value) { return lwm2m_engine_set(pathstr, &value, 1); } int lwm2m_engine_set_u16(const char *pathstr, uint16_t value) { return lwm2m_engine_set(pathstr, &value, 2); } int lwm2m_engine_set_u32(const char *pathstr, uint32_t value) { return lwm2m_engine_set(pathstr, &value, 4); } int lwm2m_engine_set_u64(const char *pathstr, uint64_t value) { return lwm2m_engine_set(pathstr, &value, 8); } int lwm2m_engine_set_s8(const char *pathstr, int8_t value) { return lwm2m_engine_set(pathstr, &value, 1); } int lwm2m_engine_set_s16(const char *pathstr, int16_t value) { return lwm2m_engine_set(pathstr, &value, 2); } int lwm2m_engine_set_s32(const char *pathstr, int32_t value) { return lwm2m_engine_set(pathstr, &value, 4); } int lwm2m_engine_set_s64(const char *pathstr, int64_t value) { return lwm2m_engine_set(pathstr, &value, 8); } int lwm2m_engine_set_bool(const char *pathstr, bool value) { uint8_t temp = (value != 0 ? 1 : 0); return lwm2m_engine_set(pathstr, &temp, 1); } int lwm2m_engine_set_float(const char *pathstr, double *value) { return lwm2m_engine_set(pathstr, value, sizeof(double)); } int lwm2m_engine_set_objlnk(const char *pathstr, struct lwm2m_objlnk *value) { return lwm2m_engine_set(pathstr, value, sizeof(struct lwm2m_objlnk)); } /* user data getter functions */ int lwm2m_engine_get_res_data(const char *pathstr, void **data_ptr, uint16_t *data_len, uint8_t *data_flags) { struct lwm2m_obj_path path; struct lwm2m_engine_res_inst *res_inst = NULL; int ret = 0; /* translate path -> path_obj */ ret = lwm2m_string_to_path(pathstr, &path, '/'); if (ret < 0) { return ret; } if (path.level < 3) { LOG_ERR("path must have at least 3 parts"); return -EINVAL; } /* look up resource obj */ ret = path_to_objs(&path, NULL, NULL, NULL, &res_inst); if (ret < 0) { return ret; } if (!res_inst) { LOG_ERR("res instance %d not found", path.res_inst_id); return -ENOENT; } *data_ptr = res_inst->data_ptr; *data_len = res_inst->data_len; *data_flags = res_inst->data_flags; return 0; } static int lwm2m_engine_get(const char *pathstr, void *buf, uint16_t buflen) { int ret = 0; struct lwm2m_obj_path path; struct lwm2m_engine_obj_inst *obj_inst; struct lwm2m_engine_obj_field *obj_field; struct lwm2m_engine_res *res = NULL; struct lwm2m_engine_res_inst *res_inst = NULL; void *data_ptr = NULL; size_t data_len = 0; LOG_DBG("path:%s, buf:%p, buflen:%d", log_strdup(pathstr), buf, buflen); /* translate path -> path_obj */ ret = lwm2m_string_to_path(pathstr, &path, '/'); if (ret < 0) { return ret; } if (path.level < 3) { LOG_ERR("path must have at least 3 parts"); return -EINVAL; } /* look up resource obj */ ret = path_to_objs(&path, &obj_inst, &obj_field, &res, &res_inst); if (ret < 0) { return ret; } if (!res_inst) { LOG_ERR("res instance %d not found", path.res_inst_id); return -ENOENT; } /* setup initial data elements */ data_ptr = res_inst->data_ptr; data_len = res_inst->data_len; /* allow user to override data elements via callback */ if (res->read_cb) { data_ptr = res->read_cb(obj_inst->obj_inst_id, res->res_id, res_inst->res_inst_id, &data_len); } /* TODO: handle data_len > buflen case */ if (data_ptr && data_len > 0) { switch (obj_field->data_type) { case LWM2M_RES_TYPE_OPAQUE: if (data_len > buflen) { return -ENOMEM; } memcpy(buf, data_ptr, data_len); break; case LWM2M_RES_TYPE_STRING: strncpy((uint8_t *)buf, (uint8_t *)data_ptr, buflen); break; case LWM2M_RES_TYPE_U32: case LWM2M_RES_TYPE_TIME: *(uint32_t *)buf = *(uint32_t *)data_ptr; break; case LWM2M_RES_TYPE_U16: *(uint16_t *)buf = *(uint16_t *)data_ptr; break; case LWM2M_RES_TYPE_U8: *(uint8_t *)buf = *(uint8_t *)data_ptr; break; case LWM2M_RES_TYPE_S64: *(int64_t *)buf = *(int64_t *)data_ptr; break; case LWM2M_RES_TYPE_S32: *(int32_t *)buf = *(int32_t *)data_ptr; break; case LWM2M_RES_TYPE_S16: *(int16_t *)buf = *(int16_t *)data_ptr; break; case LWM2M_RES_TYPE_S8: *(int8_t *)buf = *(int8_t *)data_ptr; break; case LWM2M_RES_TYPE_BOOL: *(bool *)buf = *(bool *)data_ptr; break; case LWM2M_RES_TYPE_FLOAT: *(double *)buf = *(double *)data_ptr; break; case LWM2M_RES_TYPE_OBJLNK: *(struct lwm2m_objlnk *)buf = *(struct lwm2m_objlnk *)data_ptr; break; default: LOG_ERR("unknown obj data_type %d", obj_field->data_type); return -EINVAL; } } return 0; } int lwm2m_engine_get_opaque(const char *pathstr, void *buf, uint16_t buflen) { return lwm2m_engine_get(pathstr, buf, buflen); } int lwm2m_engine_get_string(const char *pathstr, void *buf, uint16_t buflen) { return lwm2m_engine_get(pathstr, buf, buflen); } int lwm2m_engine_get_u8(const char *pathstr, uint8_t *value) { return lwm2m_engine_get(pathstr, value, 1); } int lwm2m_engine_get_u16(const char *pathstr, uint16_t *value) { return lwm2m_engine_get(pathstr, value, 2); } int lwm2m_engine_get_u32(const char *pathstr, uint32_t *value) { return lwm2m_engine_get(pathstr, value, 4); } int lwm2m_engine_get_u64(const char *pathstr, uint64_t *value) { return lwm2m_engine_get(pathstr, value, 8); } int lwm2m_engine_get_s8(const char *pathstr, int8_t *value) { return lwm2m_engine_get(pathstr, value, 1); } int lwm2m_engine_get_s16(const char *pathstr, int16_t *value) { return lwm2m_engine_get(pathstr, value, 2); } int lwm2m_engine_get_s32(const char *pathstr, int32_t *value) { return lwm2m_engine_get(pathstr, value, 4); } int lwm2m_engine_get_s64(const char *pathstr, int64_t *value) { return lwm2m_engine_get(pathstr, value, 8); } int lwm2m_engine_get_bool(const char *pathstr, bool *value) { int ret = 0; int8_t temp = 0; ret = lwm2m_engine_get_s8(pathstr, &temp); if (!ret) { *value = temp != 0; } return ret; } int lwm2m_engine_get_float(const char *pathstr, double *buf) { return lwm2m_engine_get(pathstr, buf, sizeof(double)); } int lwm2m_engine_get_objlnk(const char *pathstr, struct lwm2m_objlnk *buf) { return lwm2m_engine_get(pathstr, buf, sizeof(struct lwm2m_objlnk)); } int lwm2m_engine_get_resource(const char *pathstr, struct lwm2m_engine_res **res) { int ret; struct lwm2m_obj_path path; ret = lwm2m_string_to_path(pathstr, &path, '/'); if (ret < 0) { return ret; } if (path.level < 3) { LOG_ERR("path must have 3 parts"); return -EINVAL; } return path_to_objs(&path, NULL, NULL, res, NULL); } 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", log_strdup(LWM2M_ATTR_STR[type]), attr->int_val); } else { attr->float_val = *(double *)data; LOG_DBG("Update %s to %f", log_strdup(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", log_strdup(LWM2M_ATTR_STR[type]), attr->int_val); } else { attr->float_val = *(double *)data; LOG_DBG("Add %s to %f", log_strdup(LWM2M_ATTR_STR[type]), attr->float_val); } return 0; } static int lwm2m_engine_observer_timestamp_update(sys_slist_t *observer, 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; } static int lwm2m_get_path_reference_ptr(struct lwm2m_engine_obj *obj, 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_engine_update_observer_min_period(struct lwm2m_ctx *client_ctx, const char *pathstr, uint32_t period_s) { int ret; struct lwm2m_obj_path path; struct notification_attrs nattrs = { 0 }; struct notification_attrs attrs = { 0 }; void *ref; ret = lwm2m_string_to_path(pathstr, &path, '/'); if (ret < 0) { return ret; } /* 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_max_period(struct lwm2m_ctx *client_ctx, const char *pathstr, uint32_t period_s) { int ret; struct lwm2m_obj_path path; void *ref; struct notification_attrs nattrs = { 0 }; struct notification_attrs attrs = { 0 }; ret = lwm2m_string_to_path(pathstr, &path, '/'); if (ret < 0) { return ret; } /* 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); } void lwm2m_engine_get_binding(char *binding) { /* Defaults to UDP. */ strncpy(binding, "U", BINDING_OPT_MAX_LEN); #if CONFIG_LWM2M_VERSION_1_0 /* In LwM2M 1.0 binding and queue mode are in same parameter */ char queue[QUEUE_OPT_MAX_LEN]; lwm2m_engine_get_queue_mode(queue); strncat(binding, queue, QUEUE_OPT_MAX_LEN); #endif } void lwm2m_engine_get_queue_mode(char *queue) { if (IS_ENABLED(CONFIG_LWM2M_QUEUE_MODE_ENABLED)) { strncpy(queue, "Q", QUEUE_OPT_MAX_LEN); } else { strncpy(queue, "", QUEUE_OPT_MAX_LEN); } } static int lwm2m_engine_allocate_resource_instance(struct lwm2m_engine_res *res, struct lwm2m_engine_res_inst **res_inst, uint8_t resource_instance_id) { int i; if (!res->res_instances || res->res_inst_count == 0) { return -ENOMEM; } for (i = 0; i < res->res_inst_count; i++) { if (res->res_instances[i].res_inst_id == RES_INSTANCE_NOT_CREATED) { break; } } if (i >= res->res_inst_count) { return -ENOMEM; } res->res_instances[i].res_inst_id = resource_instance_id; *res_inst = &res->res_instances[i]; return 0; } int lwm2m_engine_get_create_res_inst(struct lwm2m_obj_path *path, struct lwm2m_engine_res **res, struct lwm2m_engine_res_inst **res_inst) { int ret; struct lwm2m_engine_res *r = NULL; struct lwm2m_engine_res_inst *r_i = NULL; ret = path_to_objs(path, NULL, NULL, &r, &r_i); if (ret < 0) { return ret; } if (!r) { return -ENOENT; } /* Store resource pointer */ *res = r; if (!r_i) { if (path->level < LWM2M_PATH_LEVEL_RESOURCE_INST) { return -EINVAL; } ret = lwm2m_engine_allocate_resource_instance(r, &r_i, path->res_inst_id); if (ret < 0) { return ret; } } /* Store resource instance pointer */ *res_inst = r_i; return 0; } int lwm2m_engine_create_res_inst(const char *pathstr) { int ret; struct lwm2m_engine_res *res = NULL; struct lwm2m_engine_res_inst *res_inst = NULL; struct lwm2m_obj_path path; ret = lwm2m_string_to_path(pathstr, &path, '/'); if (ret < 0) { return ret; } if (path.level < 4) { LOG_ERR("path must have 4 parts"); return -EINVAL; } ret = path_to_objs(&path, NULL, NULL, &res, &res_inst); if (ret < 0) { return ret; } if (!res) { LOG_ERR("resource %u not found", path.res_id); return -ENOENT; } if (res_inst && res_inst->res_inst_id != RES_INSTANCE_NOT_CREATED) { LOG_ERR("res instance %u already exists", path.res_inst_id); return -EINVAL; } return lwm2m_engine_allocate_resource_instance(res, &res_inst, path.res_inst_id); } int lwm2m_engine_delete_res_inst(const char *pathstr) { int ret; struct lwm2m_engine_res_inst *res_inst = NULL; struct lwm2m_obj_path path; ret = lwm2m_string_to_path(pathstr, &path, '/'); if (ret < 0) { return ret; } if (path.level < 4) { LOG_ERR("path must have 4 parts"); return -EINVAL; } ret = path_to_objs(&path, NULL, NULL, NULL, &res_inst); if (ret < 0) { return ret; } if (!res_inst) { LOG_ERR("res instance %u not found", path.res_inst_id); return -ENOENT; } res_inst->data_ptr = NULL; res_inst->max_data_len = 0U; res_inst->data_len = 0U; res_inst->res_inst_id = RES_INSTANCE_NOT_CREATED; return 0; } bool lwm2m_engine_path_is_observed(const char *pathstr) { struct observe_node *obs; struct lwm2m_obj_path path; int ret; int i; ret = lwm2m_string_to_path(pathstr, &path, '/'); if (ret < 0) { return false; } for (i = 0; i < 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; } int lwm2m_engine_register_read_callback(const char *pathstr, lwm2m_engine_get_data_cb_t cb) { int ret; struct lwm2m_engine_res *res = NULL; ret = lwm2m_engine_get_resource(pathstr, &res); if (ret < 0) { return ret; } res->read_cb = cb; return 0; } int lwm2m_engine_register_pre_write_callback(const char *pathstr, lwm2m_engine_get_data_cb_t cb) { int ret; struct lwm2m_engine_res *res = NULL; ret = lwm2m_engine_get_resource(pathstr, &res); if (ret < 0) { return ret; } res->pre_write_cb = cb; return 0; } int lwm2m_engine_register_validate_callback(const char *pathstr, lwm2m_engine_set_data_cb_t cb) { #if CONFIG_LWM2M_ENGINE_VALIDATION_BUFFER_SIZE > 0 int ret; struct lwm2m_engine_res *res = NULL; ret = lwm2m_engine_get_resource(pathstr, &res); if (ret < 0) { return ret; } res->validate_cb = cb; return 0; #else ARG_UNUSED(pathstr); ARG_UNUSED(cb); LOG_ERR("Validation disabled. Set " "CONFIG_LWM2M_ENGINE_VALIDATION_BUFFER_SIZE > 0 to " "enable validation support."); return -ENOTSUP; #endif /* CONFIG_LWM2M_ENGINE_VALIDATION_BUFFER_SIZE > 0 */ } int lwm2m_engine_register_post_write_callback(const char *pathstr, lwm2m_engine_set_data_cb_t cb) { int ret; struct lwm2m_engine_res *res = NULL; ret = lwm2m_engine_get_resource(pathstr, &res); if (ret < 0) { return ret; } res->post_write_cb = cb; return 0; } int lwm2m_engine_register_exec_callback(const char *pathstr, lwm2m_engine_execute_cb_t cb) { int ret; struct lwm2m_engine_res *res = NULL; ret = lwm2m_engine_get_resource(pathstr, &res); if (ret < 0) { return ret; } res->execute_cb = cb; return 0; } int lwm2m_engine_register_create_callback(uint16_t obj_id, lwm2m_engine_user_cb_t cb) { struct lwm2m_engine_obj *obj = NULL; obj = get_engine_obj(obj_id); if (!obj) { LOG_ERR("unable to find obj: %u", obj_id); return -ENOENT; } obj->user_create_cb = cb; return 0; } int lwm2m_engine_register_delete_callback(uint16_t obj_id, lwm2m_engine_user_cb_t cb) { struct lwm2m_engine_obj *obj = NULL; obj = get_engine_obj(obj_id); if (!obj) { LOG_ERR("unable to find obj: %u", obj_id); return -ENOENT; } obj->user_delete_cb = cb; return 0; } /* generic data handlers */ static int lwm2m_read_handler(struct lwm2m_engine_obj_inst *obj_inst, struct lwm2m_engine_res *res, struct lwm2m_engine_obj_field *obj_field, struct lwm2m_message *msg) { int i, loop_max = 1, found_values = 0; uint16_t res_inst_id_tmp = 0U; void *data_ptr = NULL; size_t data_len = 0; int ret = 0; if (!obj_inst || !res || !obj_field || !msg) { return -EINVAL; } loop_max = res->res_inst_count; if (res->multi_res_inst) { /* search for valid resource instances */ for (i = 0; i < loop_max; i++) { if (res->res_instances[i].res_inst_id != RES_INSTANCE_NOT_CREATED) { found_values = 1; break; } } if (!found_values) { return -ENOENT; } ret = engine_put_begin_ri(&msg->out, &msg->path); if (ret < 0) { return ret; } res_inst_id_tmp = msg->path.res_inst_id; } for (i = 0; i < loop_max; i++) { if (res->res_instances[i].res_inst_id == RES_INSTANCE_NOT_CREATED) { continue; } if (IS_ENABLED(CONFIG_LWM2M_VERSION_1_1) && msg->path.level == LWM2M_PATH_LEVEL_RESOURCE_INST && msg->path.res_inst_id != res->res_instances[i].res_inst_id) { continue; } if (res->res_inst_count > 1) { msg->path.res_inst_id = res->res_instances[i].res_inst_id; } /* setup initial data elements */ data_ptr = res->res_instances[i].data_ptr; data_len = res->res_instances[i].data_len; /* allow user to override data elements via callback */ if (res->read_cb) { data_ptr = res->read_cb(obj_inst->obj_inst_id, res->res_id, res->res_instances[i].res_inst_id, &data_len); } if (!data_ptr || data_len == 0) { return -ENOENT; } switch (obj_field->data_type) { case LWM2M_RES_TYPE_OPAQUE: ret = engine_put_opaque(&msg->out, &msg->path, (uint8_t *)data_ptr, data_len); break; case LWM2M_RES_TYPE_STRING: ret = engine_put_string(&msg->out, &msg->path, (uint8_t *)data_ptr, strlen((uint8_t *)data_ptr)); break; case LWM2M_RES_TYPE_U32: ret = engine_put_s64(&msg->out, &msg->path, (int64_t)*(uint32_t *)data_ptr); break; case LWM2M_RES_TYPE_U16: ret = engine_put_s32(&msg->out, &msg->path, (int32_t)*(uint16_t *)data_ptr); break; case LWM2M_RES_TYPE_U8: ret = engine_put_s16(&msg->out, &msg->path, (int16_t)*(uint8_t *)data_ptr); break; case LWM2M_RES_TYPE_S64: ret = engine_put_s64(&msg->out, &msg->path, *(int64_t *)data_ptr); break; case LWM2M_RES_TYPE_S32: ret = engine_put_s32(&msg->out, &msg->path, *(int32_t *)data_ptr); break; case LWM2M_RES_TYPE_S16: ret = engine_put_s16(&msg->out, &msg->path, *(int16_t *)data_ptr); break; case LWM2M_RES_TYPE_S8: ret = engine_put_s8(&msg->out, &msg->path, *(int8_t *)data_ptr); break; case LWM2M_RES_TYPE_TIME: ret = engine_put_time(&msg->out, &msg->path, (int64_t)*(uint32_t *)data_ptr); break; case LWM2M_RES_TYPE_BOOL: ret = engine_put_bool(&msg->out, &msg->path, *(bool *)data_ptr); break; case LWM2M_RES_TYPE_FLOAT: ret = engine_put_float(&msg->out, &msg->path, (double *)data_ptr); break; case LWM2M_RES_TYPE_OBJLNK: ret = engine_put_objlnk(&msg->out, &msg->path, (struct lwm2m_objlnk *)data_ptr); break; default: LOG_ERR("unknown obj data_type %d", obj_field->data_type); return -EINVAL; } /* Validate that we really read some data */ if (ret < 0) { LOG_ERR("Read operation fail"); return -ENOMEM; } } if (res->multi_res_inst) { ret = engine_put_end_ri(&msg->out, &msg->path); if (ret < 0) { return ret; } msg->path.res_inst_id = res_inst_id_tmp; } return 0; } size_t lwm2m_engine_get_opaque_more(struct lwm2m_input_context *in, uint8_t *buf, size_t buflen, struct lwm2m_opaque_context *opaque, bool *last_block) { uint32_t in_len = opaque->remaining; uint16_t remaining = in->in_cpkt->max_len - in->offset; if (in_len > buflen) { in_len = buflen; } if (in_len > remaining) { in_len = remaining; } opaque->remaining -= in_len; remaining -= in_len; if (opaque->remaining == 0U || remaining == 0) { *last_block = true; } if (buf_read(buf, in_len, CPKT_BUF_READ(in->in_cpkt), &in->offset) < 0) { *last_block = true; return 0; } return (size_t)in_len; } static int lwm2m_write_handler_opaque(struct lwm2m_engine_obj_inst *obj_inst, struct lwm2m_engine_res *res, struct lwm2m_engine_res_inst *res_inst, struct lwm2m_message *msg, void *data_ptr, size_t data_len) { int len = 1; bool last_pkt_block = false; int ret = 0; bool last_block = true; struct lwm2m_opaque_context opaque_ctx = { 0 }; void *write_buf; size_t write_buf_len; if (msg->in.block_ctx != NULL) { last_block = msg->in.block_ctx->last_block; /* Restore the opaque context from the block context, if used. */ opaque_ctx = msg->in.block_ctx->opaque; } #if CONFIG_LWM2M_ENGINE_VALIDATION_BUFFER_SIZE > 0 /* In case validation callback is present, write data to the temporary * buffer first, for validation. Otherwise, write to the data buffer * directly. */ if (res->validate_cb) { write_buf = msg->ctx->validate_buf; write_buf_len = sizeof(msg->ctx->validate_buf); } else #endif /* CONFIG_LWM2M_ENGINE_VALIDATION_BUFFER_SIZE > 0 */ { write_buf = data_ptr; write_buf_len = data_len; } while (!last_pkt_block && len > 0) { len = engine_get_opaque(&msg->in, write_buf, MIN(data_len, write_buf_len), &opaque_ctx, &last_pkt_block); if (len <= 0) { return len; } #if CONFIG_LWM2M_ENGINE_VALIDATION_BUFFER_SIZE > 0 if (res->validate_cb) { ret = res->validate_cb( obj_inst->obj_inst_id, res->res_id, res_inst->res_inst_id, write_buf, len, last_pkt_block && last_block, opaque_ctx.len); if (ret < 0) { /* -EEXIST will generate Bad Request LWM2M response. */ return -EEXIST; } memcpy(data_ptr, write_buf, len); } #endif /* CONFIG_LWM2M_ENGINE_VALIDATION_BUFFER_SIZE > 0 */ if (res->post_write_cb) { ret = res->post_write_cb( obj_inst->obj_inst_id, res->res_id, res_inst->res_inst_id, data_ptr, len, last_pkt_block && last_block, opaque_ctx.len); if (ret < 0) { return ret; } } } if (msg->in.block_ctx != NULL) { msg->in.block_ctx->opaque = opaque_ctx; } return opaque_ctx.len; } bool lwm2m_engine_bootstrap_override(struct lwm2m_ctx *client_ctx, struct lwm2m_obj_path *path) { if (!client_ctx->bootstrap_mode) { /* Bootstrap is not active override is not possible then */ return false; } if (path->obj_id == LWM2M_OBJECT_SECURITY_ID || path->obj_id == LWM2M_OBJECT_SERVER_ID) { /* Bootstrap server have a access to Security and Server object */ return true; } return false; } /* This function is exposed for the content format writers */ int lwm2m_write_handler(struct lwm2m_engine_obj_inst *obj_inst, struct lwm2m_engine_res *res, struct lwm2m_engine_res_inst *res_inst, struct lwm2m_engine_obj_field *obj_field, struct lwm2m_message *msg) { void *data_ptr = NULL; size_t data_len = 0; size_t len = 0; size_t total_size = 0; int64_t temp64 = 0; int32_t temp32 = 0; int ret = 0; bool last_block = true; void *write_buf; size_t write_buf_len; if (!obj_inst || !res || !res_inst || !obj_field || !msg) { return -EINVAL; } if (LWM2M_HAS_RES_FLAG(res_inst, LWM2M_RES_DATA_FLAG_RO)) { return -EACCES; } /* setup initial data elements */ data_ptr = res_inst->data_ptr; data_len = res_inst->max_data_len; /* allow user to override data elements via callback */ if (res->pre_write_cb) { data_ptr = res->pre_write_cb(obj_inst->obj_inst_id, res->res_id, res_inst->res_inst_id, &data_len); } if (res->post_write_cb #if CONFIG_LWM2M_ENGINE_VALIDATION_BUFFER_SIZE > 0 || res->validate_cb #endif ) { if (msg->in.block_ctx != NULL) { /* Get block_ctx for total_size (might be zero) */ total_size = msg->in.block_ctx->ctx.total_size; LOG_DBG("BLOCK1: total:%zu current:%zu" " last:%u", msg->in.block_ctx->ctx.total_size, msg->in.block_ctx->ctx.current, msg->in.block_ctx->last_block); } } #if CONFIG_LWM2M_ENGINE_VALIDATION_BUFFER_SIZE > 0 /* In case validation callback is present, write data to the temporary * buffer first, for validation. Otherwise, write to the data buffer * directly. */ if (res->validate_cb) { write_buf = msg->ctx->validate_buf; write_buf_len = sizeof(msg->ctx->validate_buf); } else #endif /* CONFIG_LWM2M_ENGINE_VALIDATION_BUFFER_SIZE > 0 */ { write_buf = data_ptr; write_buf_len = data_len; } if (data_ptr && data_len > 0) { switch (obj_field->data_type) { case LWM2M_RES_TYPE_OPAQUE: ret = lwm2m_write_handler_opaque(obj_inst, res, res_inst, msg, data_ptr, data_len); len = ret; break; case LWM2M_RES_TYPE_STRING: ret = engine_get_string(&msg->in, write_buf, write_buf_len); if (ret < 0) { break; } len = strlen((char *)write_buf); break; case LWM2M_RES_TYPE_TIME: ret = engine_get_time(&msg->in, &temp64); if (ret < 0) { break; } *(uint32_t *)write_buf = temp64; len = 4; break; case LWM2M_RES_TYPE_U32: ret = engine_get_s64(&msg->in, &temp64); if (ret < 0) { break; } *(uint32_t *)write_buf = temp64; len = 4; break; case LWM2M_RES_TYPE_U16: ret = engine_get_s32(&msg->in, &temp32); if (ret < 0) { break; } *(uint16_t *)write_buf = temp32; len = 2; break; case LWM2M_RES_TYPE_U8: ret = engine_get_s32(&msg->in, &temp32); if (ret < 0) { break; } *(uint8_t *)write_buf = temp32; len = 1; break; case LWM2M_RES_TYPE_S64: ret = engine_get_s64(&msg->in, (int64_t *)write_buf); len = 8; break; case LWM2M_RES_TYPE_S32: ret = engine_get_s32(&msg->in, (int32_t *)write_buf); len = 4; break; case LWM2M_RES_TYPE_S16: ret = engine_get_s32(&msg->in, &temp32); if (ret < 0) { break; } *(int16_t *)write_buf = temp32; len = 2; break; case LWM2M_RES_TYPE_S8: ret = engine_get_s32(&msg->in, &temp32); if (ret < 0) { break; } *(int8_t *)write_buf = temp32; len = 1; break; case LWM2M_RES_TYPE_BOOL: ret = engine_get_bool(&msg->in, (bool *)write_buf); len = 1; break; case LWM2M_RES_TYPE_FLOAT: ret = engine_get_float(&msg->in, (double *)write_buf); len = sizeof(double); break; case LWM2M_RES_TYPE_OBJLNK: ret = engine_get_objlnk(&msg->in, (struct lwm2m_objlnk *)write_buf); len = sizeof(struct lwm2m_objlnk); break; default: LOG_ERR("unknown obj data_type %d", obj_field->data_type); return -EINVAL; } if (ret < 0) { return ret; } } else { return -ENOENT; } if (obj_field->data_type != LWM2M_RES_TYPE_OPAQUE) { #if CONFIG_LWM2M_ENGINE_VALIDATION_BUFFER_SIZE > 0 if (res->validate_cb) { ret = res->validate_cb( obj_inst->obj_inst_id, res->res_id, res_inst->res_inst_id, write_buf, len, last_block, total_size); if (ret < 0) { /* -EEXIST will generate Bad Request LWM2M response. */ return -EEXIST; } if (len > data_len) { LOG_ERR("Received data won't fit into provided " "bufffer"); return -ENOMEM; } if (obj_field->data_type == LWM2M_RES_TYPE_STRING) { strncpy(data_ptr, write_buf, data_len); } else { memcpy(data_ptr, write_buf, len); } } #endif /* CONFIG_LWM2M_ENGINE_VALIDATION_BUFFER_SIZE > 0 */ if (res->post_write_cb) { ret = res->post_write_cb( obj_inst->obj_inst_id, res->res_id, res_inst->res_inst_id, data_ptr, len, last_block, total_size); } } res_inst->data_len = len; if (LWM2M_HAS_PERM(obj_field, LWM2M_PERM_R)) { NOTIFY_OBSERVER_PATH(&msg->path); } return ret; } static 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 int 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", log_strdup(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", log_strdup(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", log_strdup(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", log_strdup(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", log_strdup(LWM2M_ATTR_STR[type]), attr->int_val); } else { attr->float_val = *(double *)nattr_ptrs[type]; LOG_DBG("Add %s to %f", log_strdup(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; } static int lwm2m_exec_handler(struct lwm2m_message *msg) { struct lwm2m_engine_obj_inst *obj_inst; struct lwm2m_engine_res *res = NULL; int ret; uint8_t *args; uint16_t args_len; if (!msg) { return -EINVAL; } ret = path_to_objs(&msg->path, &obj_inst, NULL, &res, NULL); if (ret < 0) { return ret; } args = (uint8_t *)coap_packet_get_payload(msg->in.in_cpkt, &args_len); if (res->execute_cb) { return res->execute_cb(obj_inst->obj_inst_id, args, args_len); } /* TODO: something else to handle for execute? */ return -ENOENT; } static int lwm2m_delete_handler(struct lwm2m_message *msg) { int ret; if (!msg) { return -EINVAL; } /* Device management interface is not allowed to delete Security and * Device objects instances. */ if (msg->path.obj_id == LWM2M_OBJECT_SECURITY_ID || msg->path.obj_id == LWM2M_OBJECT_DEVICE_ID) { return -EPERM; } ret = lwm2m_delete_obj_inst(msg->path.obj_id, msg->path.obj_inst_id); if (ret < 0) { return ret; } #if defined(CONFIG_LWM2M_RD_CLIENT_SUPPORT) if (!msg->ctx->bootstrap_mode) { engine_trigger_update(true); } #endif return 0; } static int do_read_op(struct lwm2m_message *msg, uint16_t content_format) { switch (content_format) { case LWM2M_FORMAT_APP_OCTET_STREAM: case LWM2M_FORMAT_PLAIN_TEXT: case LWM2M_FORMAT_OMA_PLAIN_TEXT: return do_read_op_plain_text(msg, content_format); #if defined(CONFIG_LWM2M_RW_OMA_TLV_SUPPORT) case LWM2M_FORMAT_OMA_TLV: case LWM2M_FORMAT_OMA_OLD_TLV: return do_read_op_tlv(msg, content_format); #endif #if defined(CONFIG_LWM2M_RW_JSON_SUPPORT) case LWM2M_FORMAT_OMA_JSON: case LWM2M_FORMAT_OMA_OLD_JSON: return do_read_op_json(msg, content_format); #endif #if defined(CONFIG_LWM2M_RW_SENML_JSON_SUPPORT) case LWM2M_FORMAT_APP_SEML_JSON: return do_read_op_senml_json(msg); #endif #if defined(CONFIG_LWM2M_RW_CBOR_SUPPORT) case LWM2M_FORMAT_APP_CBOR: return do_read_op_cbor(msg); #endif default: LOG_ERR("Unsupported content-format: %u", content_format); return -ENOMSG; } } static int do_composite_read_op(struct lwm2m_message *msg, uint16_t content_format) { switch (content_format) { #if defined(CONFIG_LWM2M_RW_SENML_JSON_SUPPORT) case LWM2M_FORMAT_APP_SEML_JSON: return do_composite_read_op_senml_json(msg); #endif default: LOG_ERR("Unsupported content-format: %u", content_format); return -ENOMSG; } } static 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 default: LOG_ERR("Unsupported content-format: %u", content_format); return -ENOMSG; } } static int lwm2m_perform_read_object_instance(struct lwm2m_message *msg, struct lwm2m_engine_obj_inst *obj_inst, uint8_t *num_read) { struct lwm2m_engine_res *res = NULL; struct lwm2m_engine_obj_field *obj_field; int ret = 0; while (obj_inst) { if (!obj_inst->resources || obj_inst->resource_count == 0U) { goto move_forward; } /* update the obj_inst_id as we move through the instances */ msg->path.obj_inst_id = obj_inst->obj_inst_id; ret = engine_put_begin_oi(&msg->out, &msg->path); if (ret < 0) { return ret; } for (int index = 0; index < obj_inst->resource_count; index++) { if (msg->path.level > LWM2M_PATH_LEVEL_OBJECT_INST && msg->path.res_id != obj_inst->resources[index].res_id) { continue; } res = &obj_inst->resources[index]; msg->path.res_id = res->res_id; obj_field = lwm2m_get_engine_obj_field(obj_inst->obj, res->res_id); if (!obj_field) { ret = -ENOENT; } else if (!LWM2M_HAS_PERM(obj_field, LWM2M_PERM_R)) { ret = -EPERM; } else { /* start resource formatting */ ret = engine_put_begin_r(&msg->out, &msg->path); if (ret < 0) { return ret; } /* perform read operation on this resource */ ret = lwm2m_read_handler(obj_inst, res, obj_field, msg); if (ret == -ENOMEM) { /* No point continuing if there's no * memory left in a message. */ return ret; } else if (ret < 0) { /* ignore errors unless single read */ if (msg->path.level > LWM2M_PATH_LEVEL_OBJECT_INST && !LWM2M_HAS_PERM(obj_field, BIT(LWM2M_FLAG_OPTIONAL))) { LOG_ERR("READ OP: %d", ret); } } else { *num_read += 1U; } /* end resource formatting */ ret = engine_put_end_r(&msg->out, &msg->path); if (ret < 0) { return ret; } } /* on single read break if errors */ if (ret < 0 && msg->path.level > LWM2M_PATH_LEVEL_OBJECT_INST) { break; } /* when reading multiple resources ignore return code */ ret = 0; } move_forward: ret = engine_put_end_oi(&msg->out, &msg->path); if (ret < 0) { return ret; } if (msg->path.level <= LWM2M_PATH_LEVEL_OBJECT) { /* advance to the next object instance */ obj_inst = next_engine_obj_inst(msg->path.obj_id, obj_inst->obj_inst_id); } else { obj_inst = NULL; } } return ret; } int lwm2m_perform_read_op(struct lwm2m_message *msg, uint16_t content_format) { struct lwm2m_engine_obj_inst *obj_inst = NULL; struct lwm2m_obj_path temp_path; int ret = 0; uint8_t num_read = 0U; if (msg->path.level >= LWM2M_PATH_LEVEL_OBJECT_INST) { obj_inst = get_engine_obj_inst(msg->path.obj_id, msg->path.obj_inst_id); if (!obj_inst) { /* When Object instace is indicated error have to be reported */ return -ENOENT; } } else if (msg->path.level == LWM2M_PATH_LEVEL_OBJECT) { /* find first obj_inst with path's obj_id. * Path level 1 can accept NULL. It define empty payload to response. */ obj_inst = next_engine_obj_inst(msg->path.obj_id, -1); } /* set output content-format */ ret = coap_append_option_int(msg->out.out_cpkt, COAP_OPTION_CONTENT_FORMAT, content_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) { LOG_ERR("Error appending payload marker: %d", ret); return ret; } /* store original path values so we can change them during processing */ memcpy(&temp_path, &msg->path, sizeof(temp_path)); if (engine_put_begin(&msg->out, &msg->path) < 0) { return -ENOMEM; } ret = lwm2m_perform_read_object_instance(msg, obj_inst, &num_read); if (ret < 0) { return ret; } if (engine_put_end(&msg->out, &msg->path) < 0) { return -ENOMEM; } /* restore original path values */ memcpy(&msg->path, &temp_path, sizeof(temp_path)); /* did not read anything even if we should have - on single item */ if (ret == 0 && num_read == 0U) { if (msg->path.level == LWM2M_PATH_LEVEL_RESOURCE) { return -ENOENT; } if (IS_ENABLED(CONFIG_LWM2M_VERSION_1_1) && msg->path.level == LWM2M_PATH_LEVEL_RESOURCE_INST) { return -ENOENT; } } return ret; } static int lwm2m_discover_add_res(struct lwm2m_message *msg, struct lwm2m_engine_obj_inst *obj_inst, struct lwm2m_engine_res *res) { int ret; struct lwm2m_obj_path path = { .obj_id = obj_inst->obj->obj_id, .obj_inst_id = obj_inst->obj_inst_id, .res_id = res->res_id, .level = LWM2M_PATH_LEVEL_RESOURCE, }; ret = engine_put_corelink(&msg->out, &path); if (ret < 0) { return ret; } /* Report resource instances, if applicable. */ if (IS_ENABLED(CONFIG_LWM2M_VERSION_1_1) && msg->path.level == LWM2M_PATH_LEVEL_RESOURCE && res->multi_res_inst) { for (int i = 0; i < res->res_inst_count; i++) { struct lwm2m_engine_res_inst *res_inst = &res->res_instances[i]; if (res_inst->res_inst_id == RES_INSTANCE_NOT_CREATED) { continue; } path = (struct lwm2m_obj_path){ .obj_id = obj_inst->obj->obj_id, .obj_inst_id = obj_inst->obj_inst_id, .res_id = res->res_id, .res_inst_id = res_inst->res_inst_id, .level = LWM2M_PATH_LEVEL_RESOURCE_INST, }; ret = engine_put_corelink(&msg->out, &path); if (ret < 0) { return ret; } } } return 0; } int lwm2m_discover_handler(struct lwm2m_message *msg, bool is_bootstrap) { 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 (!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; } /* 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; } /* * Add required prefix for bootstrap discovery (5.2.7.3). * For device management discovery, `engine_put_begin()` adds nothing. */ ret = engine_put_begin(&msg->out, &msg->path); if (ret < 0) { return ret; } 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; } /* For bootstrap discover, only report object ID when no * instance is available or it's needed to report object * version. * For device management discovery, only report object ID with * attributes if object ID (alone) was provided. */ if ((is_bootstrap && (obj->instance_count == 0U || lwm2m_engine_shall_report_obj_version(obj))) || (!is_bootstrap && msg->path.level == LWM2M_PATH_LEVEL_OBJECT)) { struct lwm2m_obj_path path = { .obj_id = obj->obj_id, .level = LWM2M_PATH_LEVEL_OBJECT, }; ret = engine_put_corelink(&msg->out, &path); if (ret < 0) { return ret; } reported = true; if (obj->instance_count == 0U) { continue; } } SYS_SLIST_FOR_EACH_CONTAINER(&engine_obj_inst_list, obj_inst, node) { if (obj_inst->obj->obj_id != obj->obj_id) { continue; } /* 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 <= 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; } /* Do not report resources in bootstrap discovery. */ if (is_bootstrap) { continue; } 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 = lwm2m_discover_add_res(msg, obj_inst, &obj_inst->resources[i]); if (ret < 0) { return ret; } 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) { int ret = 0; if (created) { *created = 0U; } *obj_inst = get_engine_obj_inst(msg->path.obj_id, msg->path.obj_inst_id); if (!*obj_inst) { ret = lwm2m_create_obj_inst(msg->path.obj_id, msg->path.obj_inst_id, obj_inst); if (ret < 0) { return ret; } /* set created flag to one */ if (created) { *created = 1U; } #if defined(CONFIG_LWM2M_RD_CLIENT_SUPPORT) if (!msg->ctx->bootstrap_mode) { engine_trigger_update(true); } #endif } return ret; } int lwm2m_engine_validate_write_access(struct lwm2m_message *msg, struct lwm2m_engine_obj_inst *obj_inst, struct lwm2m_engine_obj_field **obj_field) { struct lwm2m_engine_obj_field *o_f; o_f = lwm2m_get_engine_obj_field(obj_inst->obj, msg->path.res_id); if (!o_f) { return -ENOENT; } *obj_field = o_f; if (!LWM2M_HAS_PERM(o_f, LWM2M_PERM_W) && !lwm2m_engine_bootstrap_override(msg->ctx, &msg->path)) { return -EPERM; } if (!obj_inst->resources || obj_inst->resource_count == 0U) { return -EINVAL; } return 0; } struct lwm2m_engine_obj *lwm2m_engine_get_obj( const struct lwm2m_obj_path *path) { if (path->level < LWM2M_PATH_LEVEL_OBJECT) { return NULL; } return get_engine_obj(path->obj_id); } struct lwm2m_engine_obj_inst *lwm2m_engine_get_obj_inst( const struct lwm2m_obj_path *path) { if (path->level < LWM2M_PATH_LEVEL_OBJECT_INST) { return NULL; } return get_engine_obj_inst(path->obj_id, path->obj_inst_id); } struct lwm2m_engine_res *lwm2m_engine_get_res( const struct lwm2m_obj_path *path) { struct lwm2m_engine_res *res = NULL; int ret; if (path->level < LWM2M_PATH_LEVEL_RESOURCE) { return NULL; } ret = path_to_objs(path, NULL, NULL, &res, NULL); if (ret < 0) { return NULL; } return res; } struct lwm2m_engine_res_inst *lwm2m_engine_get_res_inst( const struct lwm2m_obj_path *path) { struct lwm2m_engine_res_inst *res_inst = NULL; int ret; if (path->level != LWM2M_PATH_LEVEL_RESOURCE_INST) { return NULL; } ret = path_to_objs(path, NULL, NULL, NULL, &res_inst); if (ret < 0) { return NULL; } return res_inst; } bool lwm2m_engine_shall_report_obj_version(const struct lwm2m_engine_obj *obj) { if (obj->is_core) { return obj->version_major != LWM2M_PROTOCOL_VERSION_MAJOR || obj->version_minor != LWM2M_PROTOCOL_VERSION_MINOR; } return obj->version_major != 1 || obj->version_minor != 0; } static int do_write_op(struct lwm2m_message *msg, uint16_t format) { switch (format) { case LWM2M_FORMAT_APP_OCTET_STREAM: case LWM2M_FORMAT_PLAIN_TEXT: case LWM2M_FORMAT_OMA_PLAIN_TEXT: return do_write_op_plain_text(msg); #ifdef CONFIG_LWM2M_RW_OMA_TLV_SUPPORT case LWM2M_FORMAT_OMA_TLV: case LWM2M_FORMAT_OMA_OLD_TLV: return do_write_op_tlv(msg); #endif #ifdef CONFIG_LWM2M_RW_JSON_SUPPORT case LWM2M_FORMAT_OMA_JSON: case LWM2M_FORMAT_OMA_OLD_JSON: return do_write_op_json(msg); #endif #if defined(CONFIG_LWM2M_RW_SENML_JSON_SUPPORT) case LWM2M_FORMAT_APP_SEML_JSON: return do_write_op_senml_json(msg); #endif #ifdef CONFIG_LWM2M_RW_CBOR_SUPPORT case LWM2M_FORMAT_APP_CBOR: return do_write_op_cbor(msg); #endif default: LOG_ERR("Unsupported format: %u", format); return -ENOMSG; } } static int do_composite_write_op(struct lwm2m_message *msg, uint16_t format) { switch (format) { #if defined(CONFIG_LWM2M_RW_SENML_JSON_SUPPORT) case LWM2M_FORMAT_APP_SEML_JSON: return do_write_op_senml_json(msg); #endif default: LOG_ERR("Unsupported format: %u", format); return -ENOMSG; } } #if defined(CONFIG_LWM2M_RD_CLIENT_SUPPORT_BOOTSTRAP) static bool bootstrap_delete_allowed(int obj_id, int obj_inst_id) { char pathstr[MAX_RESOURCE_LEN]; bool bootstrap_server; int ret; if (obj_id == LWM2M_OBJECT_SECURITY_ID) { snprintk(pathstr, sizeof(pathstr), "%d/%d/1", LWM2M_OBJECT_SECURITY_ID, obj_inst_id); ret = lwm2m_engine_get_bool(pathstr, &bootstrap_server); if (ret < 0) { return false; } if (bootstrap_server) { return false; } } if (obj_id == LWM2M_OBJECT_DEVICE_ID) { return false; } return true; } static int bootstrap_delete(struct lwm2m_message *msg) { struct lwm2m_engine_obj_inst *obj_inst, *tmp; int ret = 0; if (msg->path.level > 2) { return -EPERM; } if (msg->path.level == 2) { if (!bootstrap_delete_allowed(msg->path.obj_id, msg->path.obj_inst_id)) { return -EPERM; } return lwm2m_delete_obj_inst(msg->path.obj_id, msg->path.obj_inst_id); } /* DELETE all instances of a specific object or all object instances if * not specified, excluding the following exceptions (according to the * LwM2M specification v1.0.2, ch 5.2.7.5): * - LwM2M Bootstrap-Server Account (Bootstrap Security object, ID 0) * - Device object (ID 3) */ SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&engine_obj_inst_list, obj_inst, tmp, node) { if (msg->path.level == 1 && obj_inst->obj->obj_id != msg->path.obj_id) { continue; } if (!bootstrap_delete_allowed(obj_inst->obj->obj_id, obj_inst->obj_inst_id)) { continue; } ret = lwm2m_delete_obj_inst(obj_inst->obj->obj_id, obj_inst->obj_inst_id); if (ret < 0) { return ret; } } return ret; } #endif static bool lwm2m_engine_path_included(uint8_t code, bool bootstrap_mode) { switch (code & COAP_REQUEST_MASK) { #if defined(CONFIG_LWM2M_RD_CLIENT_SUPPORT_BOOTSTRAP) case COAP_METHOD_DELETE: case COAP_METHOD_GET: if (bootstrap_mode) { return false; } break; #endif case COAP_METHOD_FETCH: /* Composite Read operation */ case COAP_METHOD_IPATCH: /* Composite write operation */ return false; default: break; } return true; } static 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; } static int lwm2m_engine_default_content_format(uint16_t *accept_format) { if (IS_ENABLED(CONFIG_LWM2M_VERSION_1_1)) { /* Select content format use SenML CBOR when it possible */ if (IS_ENABLED(CONFIG_LWM2M_RW_SENML_JSON_SUPPORT)) { LOG_DBG("No accept option given. Assume SenML Json."); *accept_format = LWM2M_FORMAT_APP_SEML_JSON; } else if (IS_ENABLED(CONFIG_LWM2M_RW_CBOR_SUPPORT)) { LOG_DBG("No accept option given. Assume CBOR."); *accept_format = LWM2M_FORMAT_APP_CBOR; } else { LOG_ERR("CBOR, SenML CBOR or SenML JSON is not supported"); return -ENOTSUP; } } else if (IS_ENABLED(CONFIG_LWM2M_RW_OMA_TLV_SUPPORT)) { LOG_DBG("No accept option given. Assume OMA TLV."); *accept_format = LWM2M_FORMAT_OMA_TLV; } else { LOG_ERR("No default content format is set"); return -ENOTSUP; } return 0; } static int handle_request(struct coap_packet *request, struct lwm2m_message *msg) { int r; uint8_t code; struct coap_option options[4]; struct lwm2m_engine_obj *obj = NULL; uint8_t token[8]; uint8_t tkl = 0U; uint16_t format = LWM2M_FORMAT_NONE, accept; int observe = -1; /* default to -1, 0 = ENABLE, 1 = DISABLE */ int block_opt, block_num; struct lwm2m_block_context *block_ctx = NULL; enum coap_block_size block_size; uint16_t payload_len = 0U; bool last_block = false; bool ignore = false; const uint8_t *payload_start; /* set CoAP request / message */ msg->in.in_cpkt = request; msg->out.out_cpkt = &msg->cpkt; /* set default reader/writer */ msg->in.reader = &plain_text_reader; msg->out.writer = &plain_text_writer; code = coap_header_get_code(msg->in.in_cpkt); /* setup response token */ tkl = coap_header_get_token(msg->in.in_cpkt, token); if (tkl) { msg->tkl = tkl; msg->token = token; } /* parse the URL path into components */ r = coap_find_options(msg->in.in_cpkt, COAP_OPTION_URI_PATH, options, ARRAY_SIZE(options)); if (r < 0) { goto error; } /* Treat empty URI path option as is there were no option - this will be * represented as a level "zero" in the path structure. */ if (r == 1 && options[0].len == 0) { r = 0; } if (r == 0 && lwm2m_engine_path_included(code, msg->ctx->bootstrap_mode)) { /* No URI path or empty URI path option - allowed only during * bootstrap or CoAP Fetch or iPATCH. */ r = -EPERM; goto error; } #if defined(CONFIG_LWM2M_RD_CLIENT_SUPPORT_BOOTSTRAP) /* check for bootstrap-finish */ if ((code & COAP_REQUEST_MASK) == COAP_METHOD_POST && r == 1 && strncmp(options[0].value, "bs", options[0].len) == 0) { engine_bootstrap_finish(); msg->code = COAP_RESPONSE_CODE_CHANGED; r = lwm2m_init_message(msg); if (r < 0) { goto error; } return 0; } else #endif { r = coap_options_to_path(options, r, &msg->path); if (r < 0) { r = -ENOENT; goto error; } } /* read Content Format / setup in.reader */ r = coap_find_options(msg->in.in_cpkt, COAP_OPTION_CONTENT_FORMAT, options, 1); if (r > 0) { format = coap_option_value_to_int(&options[0]); r = select_reader(&msg->in, format); if (r < 0) { goto error; } } /* read Accept / setup out.writer */ r = coap_find_options(msg->in.in_cpkt, COAP_OPTION_ACCEPT, options, 1); if (r > 0) { accept = coap_option_value_to_int(&options[0]); } else { /* Select Default based LWM2M_VERSION */ r = lwm2m_engine_default_content_format(&accept); if (r) { goto error; } } r = select_writer(&msg->out, accept); if (r < 0) { goto error; } /* Do Only Object find if path have been parsed */ if (lwm2m_engine_path_included(code, msg->ctx->bootstrap_mode)) { if (!(msg->ctx->bootstrap_mode && msg->path.level == LWM2M_PATH_LEVEL_NONE)) { /* find registered obj */ obj = get_engine_obj(msg->path.obj_id); if (!obj) { /* No matching object found - ignore request */ r = -ENOENT; goto error; } } } /* set the operation */ switch (code & COAP_REQUEST_MASK) { case COAP_METHOD_GET: /* * LwM2M V1_0_1-20170704-A, table 25, * Discover: CoAP GET + accept=LWM2M_FORMAT_APP_LINK_FORMAT */ if (accept == LWM2M_FORMAT_APP_LINK_FORMAT) { msg->operation = LWM2M_OP_DISCOVER; accept = LWM2M_FORMAT_APP_LINK_FORMAT; } else { msg->operation = LWM2M_OP_READ; } /* check for observe */ observe = coap_get_option_int(msg->in.in_cpkt, COAP_OPTION_OBSERVE); msg->code = COAP_RESPONSE_CODE_CONTENT; break; case COAP_METHOD_FETCH: msg->operation = LWM2M_OP_READ; /* check for observe */ observe = coap_get_option_int(msg->in.in_cpkt, COAP_OPTION_OBSERVE); msg->code = COAP_RESPONSE_CODE_CONTENT; break; case COAP_METHOD_IPATCH: msg->operation = LWM2M_OP_WRITE; msg->code = COAP_RESPONSE_CODE_CHANGED; break; case COAP_METHOD_POST: if (msg->path.level == 1U) { /* create an object instance */ msg->operation = LWM2M_OP_CREATE; msg->code = COAP_RESPONSE_CODE_CREATED; } else if (msg->path.level == 2U) { /* write values to an object instance */ msg->operation = LWM2M_OP_WRITE; msg->code = COAP_RESPONSE_CODE_CHANGED; } else { msg->operation = LWM2M_OP_EXECUTE; msg->code = COAP_RESPONSE_CODE_CHANGED; } break; case COAP_METHOD_PUT: /* write attributes if content-format is absent */ if (format == LWM2M_FORMAT_NONE) { msg->operation = LWM2M_OP_WRITE_ATTR; } else { msg->operation = LWM2M_OP_WRITE; } msg->code = COAP_RESPONSE_CODE_CHANGED; break; case COAP_METHOD_DELETE: msg->operation = LWM2M_OP_DELETE; msg->code = COAP_RESPONSE_CODE_DELETED; break; default: break; } /* setup incoming data */ payload_start = coap_packet_get_payload(msg->in.in_cpkt, &payload_len); if (payload_len > 0) { msg->in.offset = payload_start - msg->in.in_cpkt->data; } else { msg->in.offset = msg->in.in_cpkt->offset; } /* Check for block transfer */ block_opt = coap_get_option_int(msg->in.in_cpkt, COAP_OPTION_BLOCK1); if (block_opt > 0) { last_block = !GET_MORE(block_opt); /* RFC7252: 4.6. Message Size */ block_size = GET_BLOCK_SIZE(block_opt); if (!last_block && coap_block_size_to_bytes(block_size) > payload_len) { LOG_DBG("Trailing payload is discarded!"); r = -EFBIG; goto error; } block_num = GET_BLOCK_NUM(block_opt); /* Try to retrieve existing block context. If one not exists, * and we've received first block, allocate new context. */ r = get_block_ctx(token, tkl, &block_ctx); if (r < 0 && block_num == 0) { r = init_block_ctx(token, tkl, &block_ctx); } if (r < 0) { LOG_ERR("Cannot find block context"); goto error; } msg->in.block_ctx = block_ctx; if (block_num < block_ctx->expected) { LOG_WRN("Block already handled %d, expected %d", block_num, block_ctx->expected); ignore = true; } else if (block_num > block_ctx->expected) { LOG_WRN("Block out of order %d, expected %d", block_num, block_ctx->expected); r = -EFAULT; goto error; } else { r = coap_update_from_block(msg->in.in_cpkt, &block_ctx->ctx); if (r < 0) { LOG_ERR("Error from block update: %d", r); goto error; } block_ctx->last_block = last_block; /* Initial block sent by the server might be larger than * our block size therefore it is needed to take this * into account when calculating next expected block * number. */ block_ctx->expected += GET_BLOCK_SIZE(block_opt) - block_ctx->ctx.block_size + 1; } /* Handle blockwise 1 (Part 1): Set response code */ if (!last_block) { msg->code = COAP_RESPONSE_CODE_CONTINUE; } } /* render CoAP packet header */ r = lwm2m_init_message(msg); if (r < 0) { goto error; } if (!ignore) { switch (msg->operation) { case LWM2M_OP_READ: if (observe >= 0) { /* Validate That Token is valid for Observation */ if (!msg->token) { LOG_ERR("OBSERVE request missing token"); r = -EINVAL; goto error; } if ((code & COAP_REQUEST_MASK) == COAP_METHOD_GET) { /* Normal Observation Request or Cancel */ r = lwm2m_engine_observation_handler(msg, observe, accept, false); if (r < 0) { goto error; } r = do_read_op(msg, accept); } else { /* Composite Observation request & cancel handler */ r = lwm2m_engine_observation_handler(msg, observe, accept, true); if (r < 0) { goto error; } r = do_composite_read_op(msg, accept); } } else { if ((code & COAP_REQUEST_MASK) == COAP_METHOD_GET) { r = do_read_op(msg, accept); } else { r = do_composite_read_op(msg, accept); } } break; case LWM2M_OP_DISCOVER: r = do_discover_op(msg, accept); break; case LWM2M_OP_WRITE: case LWM2M_OP_CREATE: if ((code & COAP_REQUEST_MASK) == COAP_METHOD_IPATCH) { /* iPATCH is for Composite purpose */ r = do_composite_write_op(msg, format); } else { /* Single resource write Operation */ r = do_write_op(msg, format); } break; case LWM2M_OP_WRITE_ATTR: r = lwm2m_write_attr_handler(obj, msg); break; case LWM2M_OP_EXECUTE: r = lwm2m_exec_handler(msg); break; case LWM2M_OP_DELETE: #if defined(CONFIG_LWM2M_RD_CLIENT_SUPPORT_BOOTSTRAP) if (msg->ctx->bootstrap_mode) { r = bootstrap_delete(msg); break; } #endif r = lwm2m_delete_handler(msg); break; default: LOG_ERR("Unknown operation: %u", msg->operation); r = -EINVAL; } if (r < 0) { goto error; } } /* Handle blockwise 1 (Part 2): Append BLOCK1 option / free context */ if (block_ctx) { if (!last_block) { /* More to come, ack with correspond block # */ r = coap_append_block1_option(msg->out.out_cpkt, &block_ctx->ctx); if (r < 0) { /* report as internal server error */ LOG_ERR("Fail adding block1 option: %d", r); r = -EINVAL; goto error; } } else { /* Free context when finished */ free_block_ctx(block_ctx); } } return 0; error: lwm2m_reset_message(msg, false); if (r == -ENOENT) { msg->code = COAP_RESPONSE_CODE_NOT_FOUND; } else if (r == -EPERM) { msg->code = COAP_RESPONSE_CODE_NOT_ALLOWED; } else if (r == -EEXIST) { msg->code = COAP_RESPONSE_CODE_BAD_REQUEST; } else if (r == -EFAULT) { msg->code = COAP_RESPONSE_CODE_INCOMPLETE; } else if (r == -EFBIG) { msg->code = COAP_RESPONSE_CODE_REQUEST_TOO_LARGE; } else if (r == -ENOTSUP) { msg->code = COAP_RESPONSE_CODE_NOT_IMPLEMENTED; } else if (r == -ENOMSG) { msg->code = COAP_RESPONSE_CODE_UNSUPPORTED_CONTENT_FORMAT; } else { /* Failed to handle the request */ msg->code = COAP_RESPONSE_CODE_INTERNAL_ERROR; } r = lwm2m_init_message(msg); if (r < 0) { LOG_ERR("Error recreating message: %d", r); } /* Free block context when error happened */ free_block_ctx(block_ctx); return 0; } static int lwm2m_response_promote_to_con(struct lwm2m_message *msg) { int ret; msg->type = COAP_TYPE_CON; msg->mid = coap_next_id(); /* Since the response CoAP packet is already generated at this point, * tweak the specific fields manually: * - CoAP message type (byte 0, bits 2 and 3) * - CoAP message id (bytes 2 and 3) */ msg->cpkt.data[0] &= ~(0x3 << 4); msg->cpkt.data[0] |= (msg->type & 0x3) << 4; msg->cpkt.data[2] = msg->mid >> 8; msg->cpkt.data[3] = (uint8_t) msg->mid; /* Add the packet to the pending list. */ msg->pending = coap_pending_next_unused( msg->ctx->pendings, CONFIG_LWM2M_ENGINE_MAX_PENDING); if (!msg->pending) { LOG_ERR("Unable to find a free pending to track " "retransmissions."); return -ENOMEM; } ret = coap_pending_init(msg->pending, &msg->cpkt, &msg->ctx->remote_addr, COAP_DEFAULT_MAX_RETRANSMIT); if (ret < 0) { LOG_ERR("Unable to initialize a pending " "retransmission (err:%d).", ret); } return ret; } static void lwm2m_udp_receive(struct lwm2m_ctx *client_ctx, uint8_t *buf, uint16_t buf_len, struct sockaddr *from_addr, udp_request_handler_cb_t udp_request_handler) { struct lwm2m_message *msg = NULL; struct coap_pending *pending; struct coap_reply *reply; struct coap_packet response; int r; uint8_t token[8]; uint8_t tkl; r = coap_packet_parse(&response, buf, buf_len, NULL, 0); if (r < 0) { LOG_ERR("Invalid data received (err:%d)", r); return; } tkl = coap_header_get_token(&response, token); pending = coap_pending_received(&response, client_ctx->pendings, CONFIG_LWM2M_ENGINE_MAX_PENDING); if (pending && coap_header_get_type(&response) == COAP_TYPE_ACK) { msg = find_msg(pending, NULL); if (msg == NULL) { LOG_DBG("Orphaned pending %p.", pending); return; } msg->acknowledged = true; if (msg->reply == NULL) { /* No response expected, release the message. */ lwm2m_reset_message(msg, true); return; } /* If the original message was a request and an empty * ACK was received, expect separate response later. */ if ((msg->code >= COAP_METHOD_GET) && (msg->code <= COAP_METHOD_DELETE) && (coap_header_get_code(&response) == COAP_CODE_EMPTY)) { LOG_DBG("Empty ACK, expect separate response."); return; } } LOG_DBG("checking for reply from [%s]", log_strdup(lwm2m_sprint_ip_addr(from_addr))); reply = coap_response_received(&response, from_addr, client_ctx->replies, CONFIG_LWM2M_ENGINE_MAX_REPLIES); if (reply) { msg = find_msg(NULL, reply); if (coap_header_get_type(&response) == COAP_TYPE_CON) { r = lwm2m_send_empty_ack(client_ctx, coap_header_get_id(&response)); if (r < 0) { LOG_ERR("Error transmitting ACK"); } } /* skip release if reply->user_data has error condition */ if (reply && reply->user_data == (void *)COAP_REPLY_STATUS_ERROR) { /* reset reply->user_data for next time */ reply->user_data = (void *)COAP_REPLY_STATUS_NONE; LOG_DBG("reply %p NOT removed", reply); return; } /* free up msg resources */ if (msg) { lwm2m_reset_message(msg, true); } LOG_DBG("reply %p handled and removed", reply); return; } /* * If no normal response handler is found, then this is * a new request coming from the server. Let's look * at registered objects to find a handler. */ if (udp_request_handler && coap_header_get_type(&response) == COAP_TYPE_CON) { msg = lwm2m_get_message(client_ctx); if (!msg) { LOG_ERR("Unable to get a lwm2m message!"); return; } /* Create a response message if we reach this point */ msg->type = COAP_TYPE_ACK; msg->code = coap_header_get_code(&response); msg->mid = coap_header_get_id(&response); /* skip token generation by default */ msg->tkl = 0; client_ctx->processed_req = msg; /* process the response to this request */ r = udp_request_handler(&response, msg); if (r < 0) { return; } if (msg->acknowledged) { r = lwm2m_response_promote_to_con(msg); if (r < 0) { LOG_ERR("Failed to promote response to CON: %d", r); lwm2m_reset_message(msg, true); return; } } client_ctx->processed_req = NULL; lwm2m_send_message_async(msg); } else { LOG_DBG("No handler for response"); } } /* returns ms until the next retransmission is due, or INT32_MAX * if no retransmissions are necessary */ static int32_t retransmit_request(struct lwm2m_ctx *client_ctx, const uint32_t timestamp) { struct lwm2m_message *msg; struct coap_pending *p; int32_t remaining, next_retransmission = INT32_MAX; int i; for (i = 0, p = client_ctx->pendings; i < CONFIG_LWM2M_ENGINE_MAX_PENDING; i++, p++) { if (!p->timeout) { continue; } remaining = p->t0 + p->timeout - timestamp; if (remaining < 0) { msg = find_msg(p, NULL); if (!msg) { LOG_ERR("pending has no valid LwM2M message!"); coap_pending_clear(p); continue; } if (!p->retries) { /* pending request has expired */ if (msg->message_timeout_cb) { msg->message_timeout_cb(msg); } lwm2m_reset_message(msg, true); continue; } if (msg->acknowledged) { /* No need to retransmit, just keep the timer running to * timeout in case no response arrives. */ coap_pending_cycle(p); continue; } lwm2m_send_message_async(msg); break; } if (remaining < next_retransmission) { next_retransmission = remaining; } } return next_retransmission; } static void notify_message_timeout_cb(struct lwm2m_message *msg) { if (msg->ctx != NULL) { struct lwm2m_ctx *client_ctx = msg->ctx; if (client_ctx->observe_cb) { client_ctx->observe_cb(LWM2M_OBSERVE_EVENT_NOTIFY_TIMEOUT, &msg->path, msg->reply->user_data); } } LOG_ERR("Notify Message Timed Out : %p", msg); } static int notify_message_reply_cb(const struct coap_packet *response, struct coap_reply *reply, const struct sockaddr *from) { int ret = 0; uint8_t type, code; struct lwm2m_message *msg; struct observe_node *obs, *found_obj = NULL; type = coap_header_get_type(response); code = coap_header_get_code(response); LOG_DBG("NOTIFY ACK type:%u code:%d.%d reply_token:'%s'", type, COAP_RESPONSE_CODE_CLASS(code), COAP_RESPONSE_CODE_DETAIL(code), log_strdup(sprint_token(reply->token, reply->tkl))); msg = find_msg(NULL, reply); /* remove observer on COAP_TYPE_RESET */ if (type == COAP_TYPE_RESET) { if (reply->tkl > 0) { ret = engine_remove_observer_by_token(msg->ctx, reply->token, reply->tkl); if (ret) { LOG_ERR("remove observe error: %d", ret); } } else { LOG_ERR("notify reply missing token -- ignored."); } } else { SYS_SLIST_FOR_EACH_CONTAINER(&msg->ctx->observer, obs, node) { if (memcmp(obs->token, reply->token, reply->tkl) == 0) { found_obj = obs; break; } } if (found_obj) { if (msg->ctx->observe_cb) { msg->ctx->observe_cb(LWM2M_OBSERVE_EVENT_NOTIFY_ACK, lwm2m_read_first_path_ptr(&obs->path_list), reply->user_data); } } } return 0; } static int generate_notify_message(struct lwm2m_ctx *ctx, struct observe_node *obs, void *user_data) { struct lwm2m_message *msg; struct lwm2m_engine_obj_inst *obj_inst; struct lwm2m_obj_path *path; int ret = 0; msg = lwm2m_get_message(ctx); if (!msg) { LOG_ERR("Unable to get a lwm2m message!"); return -ENOMEM; } if (!obs->composite) { path = lwm2m_read_first_path_ptr(&obs->path_list); if (!path) { LOG_ERR("Observation node not include path"); ret = -EINVAL; } /* copy path */ memcpy(&msg->path, path, sizeof(struct lwm2m_obj_path)); LOG_DBG("[%s] NOTIFY MSG START: %u/%u/%u(%u) token:'%s' [%s] %lld", obs->resource_update ? "MANUAL" : "AUTO", path->obj_id, path->obj_inst_id, path->res_id, path->level, log_strdup(sprint_token(obs->token, obs->tkl)), log_strdup(lwm2m_sprint_ip_addr(&ctx->remote_addr)), (long long)k_uptime_get()); obj_inst = get_engine_obj_inst(path->obj_id, path->obj_inst_id); if (!obj_inst) { LOG_ERR("unable to get engine obj for %u/%u", path->obj_id, path->obj_inst_id); ret = -EINVAL; goto cleanup; } } else { LOG_DBG("[%s] NOTIFY MSG START: (Composite)) token:'%s' [%s] %lld", obs->resource_update ? "MANUAL" : "AUTO", log_strdup(sprint_token(obs->token, obs->tkl)), log_strdup(lwm2m_sprint_ip_addr(&ctx->remote_addr)), (long long)k_uptime_get()); } msg->operation = LWM2M_OP_READ; obs->resource_update = false; msg->type = COAP_TYPE_CON; msg->code = COAP_RESPONSE_CODE_CONTENT; msg->mid = coap_next_id(); msg->token = obs->token; msg->tkl = obs->tkl; msg->reply_cb = notify_message_reply_cb; msg->message_timeout_cb = notify_message_timeout_cb; msg->out.out_cpkt = &msg->cpkt; ret = lwm2m_init_message(msg); if (ret < 0) { LOG_ERR("Unable to init lwm2m message! (err: %d)", ret); goto cleanup; } /* lwm2m_init_message() cleans the coap reply fields, so we assign our data here */ msg->reply->user_data = user_data; /* each notification should increment the obs counter */ obs->counter++; ret = coap_append_option_int(&msg->cpkt, COAP_OPTION_OBSERVE, obs->counter); if (ret < 0) { LOG_ERR("OBSERVE option error: %d", ret); goto cleanup; } /* set the output writer */ select_writer(&msg->out, obs->format); if (obs->composite) { /* Use do send which actually do Composite read operation */ ret = do_send_op(msg, obs->format, &obs->path_list); } else { ret = do_read_op(msg, obs->format); } if (ret < 0) { LOG_ERR("error in multi-format read (err:%d)", ret); goto cleanup; } lwm2m_information_interface_send(msg); LOG_DBG("NOTIFY MSG: SENT"); return 0; cleanup: lwm2m_reset_message(msg, true); return ret; } static int32_t engine_next_service_timeout_ms(uint32_t max_timeout, const int64_t timestamp) { struct service_node *srv; uint64_t time_left_ms; uint32_t timeout = max_timeout; SYS_SLIST_FOR_EACH_CONTAINER(&engine_service_list, srv, node) { time_left_ms = srv->last_timestamp + srv->min_call_period; /* service is due */ if (time_left_ms < timestamp) { return 0; } /* service timeout is less than the current timeout */ time_left_ms -= timestamp; if (time_left_ms < timeout) { timeout = time_left_ms; } } return timeout; } int lwm2m_engine_add_service(k_work_handler_t service, uint32_t period_ms) { int i; /* find an unused service index node */ for (i = 0; i < MAX_PERIODIC_SERVICE; i++) { if (!service_node_data[i].service_work) { break; } } if (i == MAX_PERIODIC_SERVICE) { return -ENOMEM; } service_node_data[i].service_work = service; service_node_data[i].min_call_period = period_ms; service_node_data[i].last_timestamp = 0U; sys_slist_append(&engine_service_list, &service_node_data[i].node); return 0; } int lwm2m_engine_update_service_period(k_work_handler_t service, uint32_t period_ms) { int i = 0; for (i = 0; i < MAX_PERIODIC_SERVICE; i++) { if (service_node_data[i].service_work == service) { service_node_data[i].min_call_period = period_ms; return 0; } } return -ENOENT; } static int32_t lwm2m_engine_service(const int64_t timestamp) { struct service_node *srv; int64_t service_due_timestamp; SYS_SLIST_FOR_EACH_CONTAINER(&engine_service_list, srv, node) { service_due_timestamp = srv->last_timestamp + srv->min_call_period; /* service is due */ if (timestamp >= service_due_timestamp) { srv->last_timestamp = k_uptime_get(); srv->service_work(NULL); } } /* calculate how long to sleep till the next service */ return engine_next_service_timeout_ms(ENGINE_UPDATE_INTERVAL_MS, timestamp); } #if defined(CONFIG_LWM2M_QUEUE_MODE_ENABLED) int lwm2m_engine_close_socket_connection(struct lwm2m_ctx *client_ctx) { int ret = 0; /* Enable Queue mode buffer store */ client_ctx->buffer_client_messages = true; #ifdef CONFIG_LWM2M_DTLS_SUPPORT if (!client_ctx->use_dtls) { return 0; } if (client_ctx->sock_fd >= 0) { ret = close(client_ctx->sock_fd); if (ret) { LOG_ERR("Failed to close socket: %d", errno); ret = -errno; return ret; } client_ctx->sock_fd = -1; client_ctx->connection_suspended = true; } /* Open socket again that Observation and re-send functionality works */ client_ctx->sock_fd = socket(client_ctx->remote_addr.sa_family, SOCK_DGRAM, IPPROTO_DTLS_1_2); if (client_ctx->sock_fd < 0) { LOG_ERR("Failed to create socket: %d", errno); return -errno; } #endif return ret; } #endif int lwm2m_engine_context_close(struct lwm2m_ctx *client_ctx) { int sock_fd = client_ctx->sock_fd; struct lwm2m_message *msg; sys_snode_t *obs_node; struct observe_node *obs; size_t i; /* Remove observes for this context */ while (!sys_slist_is_empty(&client_ctx->observer)) { obs_node = sys_slist_get_not_empty(&client_ctx->observer); obs = SYS_SLIST_CONTAINER(obs_node, obs, node); remove_observer_from_list(client_ctx, NULL, obs); } for (i = 0, msg = messages; i < ARRAY_SIZE(messages); i++, msg++) { if (msg->ctx == client_ctx) { lwm2m_reset_message(msg, true); } } coap_pendings_clear(client_ctx->pendings, CONFIG_LWM2M_ENGINE_MAX_PENDING); coap_replies_clear(client_ctx->replies, CONFIG_LWM2M_ENGINE_MAX_REPLIES); #if defined(CONFIG_LWM2M_QUEUE_MODE_ENABLED) client_ctx->connection_suspended = false; client_ctx->buffer_client_messages = true; #endif lwm2m_socket_del(client_ctx); client_ctx->sock_fd = -1; if (sock_fd >= 0) { return close(sock_fd); } else { return 0; } } void lwm2m_engine_context_init(struct lwm2m_ctx *client_ctx) { sys_slist_init(&client_ctx->pending_sends); sys_slist_init(&client_ctx->observer); #if defined(CONFIG_LWM2M_QUEUE_MODE_ENABLED) client_ctx->buffer_client_messages = true; client_ctx->connection_suspended = false; sys_slist_init(&client_ctx->queued_messages); #endif } /* LwM2M Socket Integration */ int lwm2m_socket_add(struct lwm2m_ctx *ctx) { if (sock_nfds >= MAX_POLL_FD) { return -ENOMEM; } sock_ctx[sock_nfds] = ctx; sock_fds[sock_nfds].fd = ctx->sock_fd; sock_fds[sock_nfds].events = POLLIN; sock_nfds++; return 0; } void lwm2m_socket_del(struct lwm2m_ctx *ctx) { for (int i = 0; i < sock_nfds; i++) { if (sock_ctx[i] != ctx) { continue; } sock_nfds--; /* If not last, overwrite the entry with the last one. */ if (i < sock_nfds) { sock_ctx[i] = sock_ctx[sock_nfds]; sock_fds[i].fd = sock_fds[sock_nfds].fd; sock_fds[i].events = sock_fds[sock_nfds].events; } /* Remove the last entry. */ sock_ctx[sock_nfds] = NULL; sock_fds[sock_nfds].fd = -1; break; } } static 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; } static void check_notifications(struct lwm2m_ctx *ctx, const int64_t timestamp) { struct observe_node *obs; int rc; SYS_SLIST_FOR_EACH_CONTAINER(&ctx->observer, obs, node) { if (!obs->event_timestamp || timestamp < obs->event_timestamp) { continue; } rc = generate_notify_message(ctx, obs, NULL); if (rc == -ENOMEM) { /* no memory/messages available, retry later */ return; } obs->event_timestamp = engine_observe_shedule_next_event(obs, ctx->srv_obj_inst, timestamp); obs->last_timestamp = timestamp; if (!rc) { /* create at most one notification */ return; } } } static int socket_recv_message(struct lwm2m_ctx *client_ctx) { static uint8_t in_buf[NET_IPV6_MTU]; socklen_t from_addr_len; ssize_t len; static struct sockaddr from_addr; from_addr_len = sizeof(from_addr); len = recvfrom(client_ctx->sock_fd, in_buf, sizeof(in_buf) - 1, 0, &from_addr, &from_addr_len); if (len < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { return -errno; } LOG_ERR("Error reading response: %d", errno); if (client_ctx->fault_cb != NULL) { client_ctx->fault_cb(errno); } return -errno; } if (len == 0) { LOG_ERR("Zero length recv"); return 0; } in_buf[len] = 0U; lwm2m_udp_receive(client_ctx, in_buf, len, &from_addr, handle_request); return 0; } static int socket_send_message(struct lwm2m_ctx *client_ctx) { sys_snode_t *msg_node = sys_slist_get(&client_ctx->pending_sends); struct lwm2m_message *msg; if (!msg_node) { return 0; } msg = SYS_SLIST_CONTAINER(msg_node, msg, node); return lwm2m_send_message(msg); } static void socket_reset_pollfd_events(void) { for (int i = 0; i < sock_nfds; ++i) { sock_fds[i].events = POLLIN | (sys_slist_is_empty(&sock_ctx[i]->pending_sends) ? 0 : POLLOUT); sock_fds[i].revents = 0; } } /* LwM2M main work loop */ static void socket_loop(void) { int i, rc; int64_t timestamp; int32_t timeout, next_retransmit; while (1) { timestamp = k_uptime_get(); timeout = lwm2m_engine_service(timestamp); /* wait for sockets */ if (sock_nfds < 1) { k_msleep(timeout); continue; } for (i = 0; i < sock_nfds; ++i) { if (sys_slist_is_empty(&sock_ctx[i]->pending_sends)) { next_retransmit = retransmit_request(sock_ctx[i], timestamp); if (next_retransmit < timeout) { timeout = next_retransmit; } } if (sys_slist_is_empty(&sock_ctx[i]->pending_sends)) { check_notifications(sock_ctx[i], timestamp); } } socket_reset_pollfd_events(); /* * FIXME: Currently we timeout and restart poll in case fds * were modified. */ rc = poll(sock_fds, sock_nfds, timeout); if (rc < 0) { LOG_ERR("Error in poll:%d", errno); errno = 0; k_msleep(ENGINE_UPDATE_INTERVAL_MS); continue; } for (i = 0; i < sock_nfds; i++) { if ((sock_fds[i].revents & POLLERR) || (sock_fds[i].revents & POLLNVAL) || (sock_fds[i].revents & POLLHUP)) { LOG_ERR("Poll reported a socket error, %02x.", sock_fds[i].revents); if (sock_ctx[i] != NULL && sock_ctx[i]->fault_cb != NULL) { sock_ctx[i]->fault_cb(EIO); } continue; } if (sock_fds[i].revents & POLLIN) { while (sock_ctx[i]) { rc = socket_recv_message(sock_ctx[i]); if (rc) { break; } } } if (sock_fds[i].revents & POLLOUT) { socket_send_message(sock_ctx[i]); } } } } #if defined(CONFIG_LWM2M_DTLS_SUPPORT) static int load_tls_credential(struct lwm2m_ctx *client_ctx, uint16_t res_id, enum tls_credential_type type) { int ret = 0; void *cred = NULL; uint16_t cred_len; uint8_t cred_flags; char pathstr[MAX_RESOURCE_LEN]; /* ignore error value */ tls_credential_delete(client_ctx->tls_tag, type); snprintk(pathstr, sizeof(pathstr), "0/%d/%u", client_ctx->sec_obj_inst, res_id); ret = lwm2m_engine_get_res_data(pathstr, &cred, &cred_len, &cred_flags); if (ret < 0) { LOG_ERR("Unable to get resource data for '%s'", log_strdup(pathstr)); return ret; } ret = tls_credential_add(client_ctx->tls_tag, type, cred, cred_len); if (ret < 0) { LOG_ERR("Error setting cred tag %d type %d: Error %d", client_ctx->tls_tag, type, ret); } return ret; } #endif /* CONFIG_LWM2M_DTLS_SUPPORT */ int lwm2m_socket_start(struct lwm2m_ctx *client_ctx) { socklen_t addr_len; int flags; int ret; bool allocate_socket = false; #if defined(CONFIG_LWM2M_DTLS_SUPPORT) uint8_t tmp; if (client_ctx->load_credentials) { ret = client_ctx->load_credentials(client_ctx); if (ret < 0) { return ret; } } else { ret = load_tls_credential(client_ctx, 3, TLS_CREDENTIAL_PSK_ID); if (ret < 0) { return ret; } ret = load_tls_credential(client_ctx, 5, TLS_CREDENTIAL_PSK); if (ret < 0) { return ret; } } #endif /* CONFIG_LWM2M_DTLS_SUPPORT */ if (client_ctx->sock_fd < 0) { allocate_socket = true; #if defined(CONFIG_LWM2M_DTLS_SUPPORT) if (client_ctx->use_dtls) { client_ctx->sock_fd = socket(client_ctx->remote_addr.sa_family, SOCK_DGRAM, IPPROTO_DTLS_1_2); } else #endif /* CONFIG_LWM2M_DTLS_SUPPORT */ { client_ctx->sock_fd = socket(client_ctx->remote_addr.sa_family, SOCK_DGRAM, IPPROTO_UDP); } } if (client_ctx->sock_fd < 0) { LOG_ERR("Failed to create socket: %d", errno); return -errno; } #if defined(CONFIG_LWM2M_DTLS_SUPPORT) if (client_ctx->use_dtls) { sec_tag_t tls_tag_list[] = { client_ctx->tls_tag, }; ret = setsockopt(client_ctx->sock_fd, SOL_TLS, TLS_SEC_TAG_LIST, tls_tag_list, sizeof(tls_tag_list)); if (ret < 0) { ret = -errno; LOG_ERR("Failed to set TLS_SEC_TAG_LIST option: %d", ret); goto error; } if (IS_ENABLED(CONFIG_LWM2M_TLS_SESSION_CACHING)) { int session_cache = TLS_SESSION_CACHE_ENABLED; ret = setsockopt(client_ctx->sock_fd, SOL_TLS, TLS_SESSION_CACHE, &session_cache, sizeof(session_cache)); if (ret < 0) { ret = -errno; LOG_ERR("Failed to set TLS_SESSION_CACHE option: %d", errno); goto error; } } if (client_ctx->hostname_verify && (client_ctx->desthostname != NULL)) { /** store character at len position */ tmp = client_ctx->desthostname[client_ctx->desthostnamelen]; /** change it to '\0' to pass to socket*/ client_ctx->desthostname[client_ctx->desthostnamelen] = '\0'; /** mbedtls ignores length */ ret = setsockopt(client_ctx->sock_fd, SOL_TLS, TLS_HOSTNAME, client_ctx->desthostname, client_ctx->desthostnamelen); /** restore character */ client_ctx->desthostname[client_ctx->desthostnamelen] = tmp; if (ret < 0) { ret = -errno; LOG_ERR("Failed to set TLS_HOSTNAME option: %d", ret); goto error; } } } #endif /* CONFIG_LWM2M_DTLS_SUPPORT */ if ((client_ctx->remote_addr).sa_family == AF_INET) { addr_len = sizeof(struct sockaddr_in); } else if ((client_ctx->remote_addr).sa_family == AF_INET6) { addr_len = sizeof(struct sockaddr_in6); } else { lwm2m_engine_context_close(client_ctx); return -EPROTONOSUPPORT; } if (connect(client_ctx->sock_fd, &client_ctx->remote_addr, addr_len) < 0) { ret = -errno; LOG_ERR("Cannot connect UDP (%d)", ret); goto error; } flags = fcntl(client_ctx->sock_fd, F_GETFL, 0); if (flags == -1) { ret = -errno; LOG_ERR("fcntl(F_GETFL) failed (%d)", ret); goto error; } ret = fcntl(client_ctx->sock_fd, F_SETFL, flags | O_NONBLOCK); if (ret == -1) { ret = -errno; LOG_ERR("fcntl(F_SETFL) failed (%d)", ret); goto error; } LOG_INF("Connected, sock id %d", client_ctx->sock_fd); if (allocate_socket) { return lwm2m_socket_add(client_ctx); } return 0; error: lwm2m_engine_context_close(client_ctx); return ret; } int lwm2m_parse_peerinfo(char *url, struct lwm2m_ctx *client_ctx, bool is_firmware_uri) { struct http_parser_url parser; #if defined(CONFIG_LWM2M_DNS_SUPPORT) struct addrinfo *res, hints = { 0 }; #endif int ret; uint16_t off, len; uint8_t tmp; LOG_DBG("Parse url: %s", log_strdup(url)); http_parser_url_init(&parser); ret = http_parser_parse_url(url, strlen(url), 0, &parser); if (ret < 0) { LOG_ERR("Invalid url: %s", log_strdup(url)); return -ENOTSUP; } off = parser.field_data[UF_SCHEMA].off; len = parser.field_data[UF_SCHEMA].len; /* check for supported protocol */ if (strncmp(url + off, "coaps", len) != 0) { return -EPROTONOSUPPORT; } /* check for DTLS requirement */ client_ctx->use_dtls = false; if (len == 5U && strncmp(url + off, "coaps", len) == 0) { #if defined(CONFIG_LWM2M_DTLS_SUPPORT) client_ctx->use_dtls = true; #else return -EPROTONOSUPPORT; #endif /* CONFIG_LWM2M_DTLS_SUPPORT */ } if (!(parser.field_set & (1 << UF_PORT))) { if (is_firmware_uri && client_ctx->use_dtls) { /* Set to default coaps firmware update port */ parser.port = CONFIG_LWM2M_FIRMWARE_PORT_SECURE; } else if (is_firmware_uri) { /* Set to default coap firmware update port */ parser.port = CONFIG_LWM2M_FIRMWARE_PORT_NONSECURE; } else { /* Set to default LwM2M server port */ parser.port = CONFIG_LWM2M_PEER_PORT; } } off = parser.field_data[UF_HOST].off; len = parser.field_data[UF_HOST].len; #if defined(CONFIG_LWM2M_DTLS_SUPPORT) /** copy url pointer to be used in socket */ client_ctx->desthostname = url + off; client_ctx->desthostnamelen = len; #endif /* truncate host portion */ tmp = url[off + len]; url[off + len] = '\0'; /* initialize remote_addr */ (void)memset(&client_ctx->remote_addr, 0, sizeof(client_ctx->remote_addr)); /* try and set IP address directly */ client_ctx->remote_addr.sa_family = AF_INET6; ret = net_addr_pton(AF_INET6, url + off, &((struct sockaddr_in6 *)&client_ctx->remote_addr)->sin6_addr); /* Try to parse again using AF_INET */ if (ret < 0) { client_ctx->remote_addr.sa_family = AF_INET; ret = net_addr_pton(AF_INET, url + off, &((struct sockaddr_in *)&client_ctx->remote_addr)->sin_addr); } if (ret < 0) { #if defined(CONFIG_LWM2M_DNS_SUPPORT) #if defined(CONFIG_NET_IPV6) && defined(CONFIG_NET_IPV4) hints.ai_family = AF_UNSPEC; #elif defined(CONFIG_NET_IPV6) hints.ai_family = AF_INET6; #elif defined(CONFIG_NET_IPV4) hints.ai_family = AF_INET; #else hints.ai_family = AF_UNSPEC; #endif /* defined(CONFIG_NET_IPV6) && defined(CONFIG_NET_IPV4) */ hints.ai_socktype = SOCK_DGRAM; ret = getaddrinfo(url + off, NULL, &hints, &res); if (ret != 0) { LOG_ERR("Unable to resolve address"); /* DNS error codes don't align with normal errors */ ret = -ENOENT; goto cleanup; } memcpy(&client_ctx->remote_addr, res->ai_addr, sizeof(client_ctx->remote_addr)); client_ctx->remote_addr.sa_family = res->ai_family; freeaddrinfo(res); #else goto cleanup; #endif /* CONFIG_LWM2M_DNS_SUPPORT */ } /* set port */ if (client_ctx->remote_addr.sa_family == AF_INET6) { net_sin6(&client_ctx->remote_addr)->sin6_port = htons(parser.port); } else if (client_ctx->remote_addr.sa_family == AF_INET) { net_sin(&client_ctx->remote_addr)->sin_port = htons(parser.port); } else { ret = -EPROTONOSUPPORT; } cleanup: /* restore host separator */ url[off + len] = tmp; return ret; } int lwm2m_engine_start(struct lwm2m_ctx *client_ctx) { char pathstr[MAX_RESOURCE_LEN]; char *url; uint16_t url_len; uint8_t url_data_flags; int ret = 0U; /* get the server URL */ snprintk(pathstr, sizeof(pathstr), "0/%d/0", client_ctx->sec_obj_inst); ret = lwm2m_engine_get_res_data(pathstr, (void **)&url, &url_len, &url_data_flags); if (ret < 0) { return ret; } url[url_len] = '\0'; ret = lwm2m_parse_peerinfo(url, client_ctx, false); if (ret < 0) { return ret; } lwm2m_engine_context_init(client_ctx); return lwm2m_socket_start(client_ctx); } static int lwm2m_engine_init(const struct device *dev) { int i; for (i = 0; i < LWM2M_ENGINE_MAX_OBSERVER_PATH; i++) { sys_slist_append(&obs_obj_path_list, &observe_paths[i].node); } (void)memset(block1_contexts, 0, sizeof(block1_contexts)); /* start sock receive thread */ k_thread_create(&engine_thread_data, &engine_thread_stack[0], K_KERNEL_STACK_SIZEOF(engine_thread_stack), (k_thread_entry_t)socket_loop, NULL, NULL, NULL, THREAD_PRIORITY, 0, K_NO_WAIT); k_thread_name_set(&engine_thread_data, "lwm2m-sock-recv"); LOG_DBG("LWM2M engine socket receive thread started"); return 0; } static 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; } static 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); } } static bool lwm2m_path_object_compare(struct lwm2m_obj_path *path, struct lwm2m_obj_path *compare_path) { if (path->level != compare_path->level || path->obj_id != compare_path->obj_id || path->obj_inst_id != compare_path->obj_inst_id || path->res_id != compare_path->res_id || path->res_inst_id != compare_path->res_inst_id) { return false; } return true; } static struct lwm2m_obj_path *lwm2m_read_first_path_ptr(sys_slist_t *lwm2m_path_list) { struct lwm2m_obj_path_list *entry; entry = (struct lwm2m_obj_path_list *)sys_slist_peek_head(lwm2m_path_list); return &entry->path; } 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, 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 */ SYS_SLIST_FOR_EACH_CONTAINER(lwm2m_path_list, entry, node) { if (entry->path.level == LWM2M_PATH_LEVEL_NONE || lwm2m_path_object_compare(&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; } if (entry->path.obj_id > path->obj_id) { /* New entry have smaller Object ID */ add_before_current = true; } else if (entry->path.obj_id == path->obj_id && entry->path.level > path->level) { add_before_current = true; } else if (entry->path.obj_id == path->obj_id && entry->path.level == path->level) { if (path->level >= LWM2M_PATH_LEVEL_OBJECT_INST && entry->path.obj_inst_id > path->obj_inst_id) { /* * New have same Object ID * but smaller Object Instance ID */ add_before_current = true; } else if (path->level >= LWM2M_PATH_LEVEL_RESOURCE && entry->path.obj_inst_id == path->obj_inst_id && entry->path.res_id > path->res_id) { /* * Object ID and Object Instance id same * but Resource ID is smaller */ add_before_current = true; } else if (path->level >= LWM2M_PATH_LEVEL_RESOURCE_INST && entry->path.obj_inst_id == path->obj_inst_id && entry->path.res_id == path->res_id && entry->path.res_inst_id > path->res_inst_id) { /* * Object ID, Object Instance id & Resource ID same * but Resource instance ID is smaller */ add_before_current = true; } } 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; } } } static int lwm2m_perform_composite_read_root(struct lwm2m_message *msg, uint8_t *num_read) { int ret; struct lwm2m_engine_obj *obj; struct lwm2m_engine_obj_inst *obj_inst; SYS_SLIST_FOR_EACH_CONTAINER(&engine_obj_list, obj, node) { /* Security obj MUST NOT be part of registration message */ if (obj->obj_id == LWM2M_OBJECT_SECURITY_ID) { continue; } msg->path.level = 1; msg->path.obj_id = obj->obj_id; obj_inst = next_engine_obj_inst(msg->path.obj_id, -1); if (!obj_inst) { continue; } ret = lwm2m_perform_read_object_instance(msg, obj_inst, num_read); if (ret == -ENOMEM) { return ret; } } return 0; } int lwm2m_perform_composite_read_op(struct lwm2m_message *msg, uint16_t content_format, sys_slist_t *lwm2m_path_list) { struct lwm2m_engine_obj_inst *obj_inst = NULL; struct lwm2m_obj_path_list *entry; int ret = 0; uint8_t num_read = 0U; /* set output content-format */ ret = coap_append_option_int(msg->out.out_cpkt, COAP_OPTION_CONTENT_FORMAT, content_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) { LOG_ERR("Error appending payload marker: %d", ret); return ret; } /* Add object start mark */ engine_put_begin(&msg->out, &msg->path); /* Read resource from path */ SYS_SLIST_FOR_EACH_CONTAINER(lwm2m_path_list, entry, node) { /* Copy path to message path */ memcpy(&msg->path, &entry->path, sizeof(struct lwm2m_obj_path)); if (msg->path.level >= LWM2M_PATH_LEVEL_OBJECT_INST) { obj_inst = get_engine_obj_inst(msg->path.obj_id, msg->path.obj_inst_id); } else if (msg->path.level == LWM2M_PATH_LEVEL_OBJECT) { /* find first obj_inst with path's obj_id */ obj_inst = next_engine_obj_inst(msg->path.obj_id, -1); } else { /* Read root Path */ ret = lwm2m_perform_composite_read_root(msg, &num_read); if (ret == -ENOMEM) { LOG_ERR("Supported message size is too small for read root"); return ret; } break; } if (!obj_inst) { continue; } ret = lwm2m_perform_read_object_instance(msg, obj_inst, &num_read); if (ret == -ENOMEM) { return ret; } } /* did not read anything even if we should have - on single item */ if (num_read == 0U) { return -ENOENT; } /* Add object end mark */ if (engine_put_end(&msg->out, &msg->path) < 0) { return -ENOMEM; } return 0; } static int do_send_op(struct lwm2m_message *msg, uint16_t content_format, sys_slist_t *lwm2m_path_list) { switch (content_format) { #if defined(CONFIG_LWM2M_RW_SENML_JSON_SUPPORT) case LWM2M_FORMAT_APP_SEML_JSON: return do_send_op_senml_json(msg, lwm2m_path_list); #endif default: LOG_ERR("Unsupported content-format for /dp: %u", content_format); return -ENOMSG; } } #if defined(CONFIG_LWM2M_SERVER_OBJECT_VERSION_1_1) static int do_send_reply_cb(const struct coap_packet *response, struct coap_reply *reply, const struct sockaddr *from) { uint8_t code; code = coap_header_get_code(response); LOG_DBG("Send callback (code:%u.%u)", COAP_RESPONSE_CODE_CLASS(code), COAP_RESPONSE_CODE_DETAIL(code)); if (code == COAP_RESPONSE_CODE_CHANGED) { LOG_INF("Send done!"); return 0; } LOG_ERR("Failed with code %u.%u. Not Retrying.", COAP_RESPONSE_CODE_CLASS(code), COAP_RESPONSE_CODE_DETAIL(code)); return 0; } static void do_send_timeout_cb(struct lwm2m_message *msg) { LOG_WRN("Send Timeout"); } #endif int lwm2m_engine_send(struct lwm2m_ctx *ctx, char const *path_list[], uint8_t path_list_size, bool confirmation_request) { #if defined(CONFIG_LWM2M_SERVER_OBJECT_VERSION_1_1) struct lwm2m_message *msg; int ret; uint16_t content_format; /* Path list buffer */ struct lwm2m_obj_path temp; 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; if (lwm2m_server_get_mute_send(ctx->srv_obj_inst)) { LOG_WRN("Send operation is muted by server"); return -EPERM; } /* Init list */ lwm2m_engine_path_list_init(&lwm2m_path_list, &lwm2m_path_free_list, lwm2m_path_list_buf, CONFIG_LWM2M_COMPOSITE_PATH_LIST_SIZE); if (path_list_size > CONFIG_LWM2M_COMPOSITE_PATH_LIST_SIZE) { return -E2BIG; } /* Select content format use CBOR when it possible */ if (IS_ENABLED(CONFIG_LWM2M_RW_SENML_JSON_SUPPORT)) { content_format = LWM2M_FORMAT_APP_SEML_JSON; } else { LOG_WRN("SenML CBOR or JSON is not supported"); return -ENOTSUP; } /* Parse Path to internal used object path format */ for (int i = 0; i < path_list_size; i++) { ret = lwm2m_string_to_path(path_list[i], &temp, '/'); if (ret < 0) { return ret; } /* Add to linked list */ if (lwm2m_engine_add_path_to_list(&lwm2m_path_list, &lwm2m_path_free_list, &temp)) { return -1; } } /* Clear path which are part are part of recursive path /1 will include /1/0/1 */ lwm2m_engine_clear_duplicate_path(&lwm2m_path_list, &lwm2m_path_free_list); /* Allocate Message buffer */ msg = lwm2m_get_message(ctx); if (!msg) { LOG_ERR("Unable to get a lwm2m message!"); return -ENOMEM; } if (confirmation_request) { msg->type = COAP_TYPE_CON; msg->reply_cb = do_send_reply_cb; msg->message_timeout_cb = do_send_timeout_cb; } else { msg->type = COAP_TYPE_NON_CON; msg->reply_cb = NULL; msg->message_timeout_cb = NULL; } msg->code = COAP_METHOD_POST; msg->mid = coap_next_id(); msg->tkl = LWM2M_MSG_TOKEN_GENERATE_NEW; msg->out.out_cpkt = &msg->cpkt; ret = lwm2m_init_message(msg); if (ret) { goto cleanup; } ret = select_writer(&msg->out, content_format); if (ret) { goto cleanup; } ret = coap_packet_append_option(&msg->cpkt, COAP_OPTION_URI_PATH, LWM2M_DP_CLIENT_URI, strlen(LWM2M_DP_CLIENT_URI)); if (ret < 0) { goto cleanup; } /* Write requested path data */ ret = do_send_op(msg, content_format, &lwm2m_path_list); if (ret < 0) { LOG_ERR("Send (err:%d)", ret); goto cleanup; } LOG_INF("Send op to server (/dp)"); lwm2m_information_interface_send(msg); return 0; cleanup: lwm2m_reset_message(msg, true); return ret; #else LOG_WRN("LwM2M send is only supported for CONFIG_LWM2M_SERVER_OBJECT_VERSION_1_1"); return -ENOTSUP; #endif } SYS_INIT(lwm2m_engine_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);