| /* |
| * Copyright (c) 2017 Linaro Limited |
| * Copyright (c) 2018-2019 Foundries.io |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| /* |
| * Uses some original concepts by: |
| * Joakim Eriksson <joakime@sics.se> |
| * Niclas Finne <nfi@sics.se> |
| * Joel Hoglund <joel@sics.se> |
| */ |
| |
| #define LOG_MODULE_NAME net_lwm2m_message_handling |
| #define LOG_LEVEL CONFIG_LWM2M_LOG_LEVEL |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(LOG_MODULE_NAME); |
| |
| #include <ctype.h> |
| #include <errno.h> |
| #include <stddef.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include <zephyr/init.h> |
| #include <zephyr/net/http_parser_url.h> |
| #include <zephyr/net/lwm2m.h> |
| #include <zephyr/net/net_ip.h> |
| #include <zephyr/net/socket.h> |
| #include <zephyr/sys/printk.h> |
| #include <zephyr/types.h> |
| |
| #include <fcntl.h> |
| #if defined(CONFIG_LWM2M_DTLS_SUPPORT) |
| #include <zephyr/net/tls_credentials.h> |
| #endif |
| #if defined(CONFIG_DNS_RESOLVER) |
| #include <zephyr/net/dns_resolve.h> |
| #endif |
| |
| #include "lwm2m_engine.h" |
| #include "lwm2m_object.h" |
| #include "lwm2m_obj_access_control.h" |
| #include "lwm2m_rw_link_format.h" |
| #include "lwm2m_rw_oma_tlv.h" |
| #include "lwm2m_rw_plain_text.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_RW_SENML_CBOR_SUPPORT |
| #include "lwm2m_rw_senml_cbor.h" |
| #endif |
| #ifdef CONFIG_LWM2M_RD_CLIENT_SUPPORT |
| #include "lwm2m_rd_client.h" |
| #endif |
| /* TODO: figure out what's correct value */ |
| #define TIMEOUT_BLOCKWISE_TRANSFER_MS (MSEC_PER_SEC * 30) |
| |
| #define LWM2M_DP_CLIENT_URI "dp" |
| /* Resources */ |
| |
| /* Shared set of in-flight LwM2M messages */ |
| static struct lwm2m_message messages[CONFIG_LWM2M_ENGINE_MAX_MESSAGES]; |
| |
| /* External resources */ |
| sys_slist_t *lwm2m_engine_obj_list(void); |
| |
| sys_slist_t *lwm2m_engine_obj_inst_list(void); |
| |
| struct lwm2m_block_context *lwm2m_block1_context(void); |
| /* 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 (lwm2m_block1_context()[i].tkl == 0U) { |
| *ctx = &lwm2m_block1_context()[i]; |
| break; |
| } |
| |
| if (timestamp - lwm2m_block1_context()[i].timestamp > |
| TIMEOUT_BLOCKWISE_TRANSFER_MS) { |
| *ctx = &lwm2m_block1_context()[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; |
| 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 (lwm2m_block1_context()[i].tkl == tkl && |
| memcmp(token, lwm2m_block1_context()[i].token, tkl) == 0) { |
| *ctx = &lwm2m_block1_context()[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; |
| } |
| |
| void lwm2m_engine_context_close(struct lwm2m_ctx *client_ctx) |
| { |
| 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, ARRAY_SIZE(client_ctx->pendings)); |
| coap_replies_clear(client_ctx->replies, ARRAY_SIZE(client_ctx->replies)); |
| |
| |
| client_ctx->connection_suspended = false; |
| #if defined(CONFIG_LWM2M_QUEUE_MODE_ENABLED) |
| client_ctx->buffer_client_messages = true; |
| #endif |
| } |
| |
| void lwm2m_engine_context_init(struct lwm2m_ctx *client_ctx) |
| { |
| sys_slist_init(&client_ctx->pending_sends); |
| sys_slist_init(&client_ctx->observer); |
| client_ctx->connection_suspended = false; |
| #if defined(CONFIG_LWM2M_QUEUE_MODE_ENABLED) |
| client_ctx->buffer_client_messages = true; |
| sys_slist_init(&client_ctx->queued_messages); |
| #endif |
| } |
| /* 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; |
| } |
| |
| struct lwm2m_message *find_msg(struct coap_pending *pending, struct coap_reply *reply) |
| { |
| size_t i; |
| struct lwm2m_message *msg; |
| |
| if (!pending && !reply) { |
| return NULL; |
| } |
| |
| msg = lwm2m_get_ongoing_rd_msg(); |
| if (msg) { |
| if (pending != NULL && msg->pending == pending) { |
| return msg; |
| } |
| |
| if (reply != NULL && msg->reply == reply) { |
| return msg; |
| } |
| } |
| |
| 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 lm2m_message_clear_allocations(struct lwm2m_message *msg) |
| { |
| if (msg->pending) { |
| coap_pending_clear(msg->pending); |
| msg->pending = NULL; |
| } |
| |
| if (msg->reply) { |
| /* make sure we want to clear the reply */ |
| coap_reply_clear(msg->reply); |
| msg->reply = NULL; |
| } |
| } |
| |
| void lwm2m_reset_message(struct lwm2m_message *msg, bool release) |
| { |
| if (!msg) { |
| return; |
| } |
| |
| lm2m_message_clear_allocations(msg); |
| |
| 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; |
| } |
| |
| lm2m_message_clear_allocations(msg); |
| |
| 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, ARRAY_SIZE(msg->ctx->pendings)); |
| 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, |
| CONFIG_COAP_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, ARRAY_SIZE(msg->ctx->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; |
| } |
| |
| 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; |
| } |
| |
| 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; |
| sys_slist_t *engine_obj_list = lwm2m_engine_obj_list(); |
| sys_slist_t *engine_obj_inst_list = lwm2m_engine_obj_inst_list(); |
| |
| 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 <OBJ_ID> 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; |
| } |
| |
| 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 |
| |
| #ifdef CONFIG_LWM2M_RW_SENML_CBOR_SUPPORT |
| case LWM2M_FORMAT_APP_SENML_CBOR: |
| out->writer = &senml_cbor_writer; |
| break; |
| #endif |
| |
| default: |
| LOG_WRN("Unknown content type %u", accept); |
| return -ECANCELED; |
| } |
| |
| 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 |
| |
| #ifdef CONFIG_LWM2M_RW_SENML_CBOR_SUPPORT |
| case LWM2M_FORMAT_APP_SENML_CBOR: |
| in->reader = &senml_cbor_reader; |
| break; |
| #endif |
| default: |
| LOG_WRN("Unknown content type %u", format); |
| return -ENOMSG; |
| } |
| |
| return 0; |
| } |
| |
| /* generic data handlers */ |
| 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; |
| } |
| /* 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)) { |
| lwm2m_notify_observer_path(&msg->path); |
| } |
| |
| return ret; |
| } |
| |
| 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) { |
| return -ENOENT; |
| } |
| |
| if (!data_len) { |
| if (obj_field->data_type != LWM2M_RES_TYPE_OPAQUE && |
| obj_field->data_type != LWM2M_RES_TYPE_STRING) { |
| return -ENOENT; |
| } |
| /* Only opaque and string types can be empty, and when |
| * empty, we should not give pointer to potentially uninitialized |
| * data to a content formatter. Give pointer to empty string instead. |
| */ |
| data_ptr = ""; |
| } |
| |
| 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; |
| } |
| |
| 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 |
| |
| #if defined(CONFIG_LWM2M_RW_SENML_CBOR_SUPPORT) |
| case LWM2M_FORMAT_APP_SENML_CBOR: |
| return do_read_op_senml_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 |
| |
| #if defined(CONFIG_LWM2M_RW_SENML_CBOR_SUPPORT) |
| case LWM2M_FORMAT_APP_SENML_CBOR: |
| return do_composite_read_op_senml_cbor(msg); |
| #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; |
| sys_slist_t *engine_obj_list = lwm2m_engine_obj_list(); |
| sys_slist_t *engine_obj_inst_list = lwm2m_engine_obj_inst_list(); |
| |
| /* 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; |
| } |
| } |
| |
| 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 |
| |
| #ifdef CONFIG_LWM2M_RW_SENML_CBOR_SUPPORT |
| case LWM2M_FORMAT_APP_SENML_CBOR: |
| return do_write_op_senml_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 |
| |
| #if defined(CONFIG_LWM2M_RW_SENML_CBOR_SUPPORT) |
| case LWM2M_FORMAT_APP_SENML_CBOR: |
| return do_write_op_senml_cbor(msg); |
| #endif |
| |
| default: |
| LOG_ERR("Unsupported format: %u", format); |
| return -ENOMSG; |
| } |
| } |
| |
| 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_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_CBOR_SUPPORT)) { |
| LOG_DBG("No accept option given. Assume SenML CBOR."); |
| *accept_format = LWM2M_FORMAT_APP_SENML_CBOR; |
| } else 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 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; |
| } |
| |
| 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; |
| } |
| #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) { |
| #if defined(CONFIG_LWM2M_ACCESS_CONTROL_ENABLE) |
| r = access_control_check_access(msg->path.obj_id, msg->path.obj_inst_id, |
| msg->ctx->srv_obj_inst, msg->operation, |
| msg->ctx->bootstrap_mode); |
| if (r < 0) { |
| LOG_ERR("Access denied - Server obj %u does not have proper access to " |
| "resource", |
| msg->ctx->srv_obj_inst); |
| goto error; |
| } |
| #endif |
| 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; |
| } |
| } |
| } 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); |
| } |
| #if defined(CONFIG_LWM2M_ACCESS_CONTROL_ENABLE) |
| if (msg->operation == LWM2M_OP_CREATE && r >= 0) { |
| access_control_add(msg->path.obj_id, msg->path.obj_inst_id, |
| msg->ctx->srv_obj_inst); |
| } |
| #endif |
| 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 if (r == -EACCES) { |
| msg->code = COAP_RESPONSE_CODE_UNAUTHORIZED; |
| } else if (r == -ECANCELED) { |
| msg->code = COAP_RESPONSE_CODE_NOT_ACCEPTABLE; |
| } 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; |
| |
| if (msg->pending) { |
| coap_pending_clear(msg->pending); |
| } |
| |
| /* Add the packet to the pending list. */ |
| msg->pending = coap_pending_next_unused(msg->ctx->pendings, ARRAY_SIZE(msg->ctx->pendings)); |
| 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, |
| CONFIG_COAP_MAX_RETRANSMIT); |
| if (ret < 0) { |
| LOG_ERR("Unable to initialize a pending " |
| "retransmission (err:%d).", |
| ret); |
| } |
| |
| return ret; |
| } |
| |
| 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]; |
| |
| r = coap_packet_parse(&response, buf, buf_len, NULL, 0); |
| if (r < 0) { |
| LOG_ERR("Invalid data received (err:%d)", r); |
| return; |
| } |
| |
| (void)coap_header_get_token(&response, token); |
| pending = coap_pending_received(&response, client_ctx->pendings, |
| ARRAY_SIZE(client_ctx->pendings)); |
| if (pending && coap_header_get_type(&response) == COAP_TYPE_ACK) { |
| msg = find_msg(pending, NULL); |
| if (msg == NULL) { |
| LOG_DBG("Orphaned pending %p.", pending); |
| coap_pending_clear(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]", lwm2m_sprint_ip_addr(from_addr)); |
| reply = coap_response_received(&response, from_addr, client_ctx->replies, |
| ARRAY_SIZE(client_ctx->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; |
| |
| lwm2m_registry_lock(); |
| /* process the response to this request */ |
| r = udp_request_handler(&response, msg); |
| lwm2m_registry_unlock(); |
| 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"); |
| } |
| } |
| |
| static void notify_message_timeout_cb(struct lwm2m_message *msg) |
| { |
| if (msg->ctx != NULL) { |
| struct observe_node *obs; |
| struct lwm2m_ctx *client_ctx = msg->ctx; |
| sys_snode_t *prev_node = NULL; |
| |
| obs = engine_observe_node_discover(&client_ctx->observer, &prev_node, NULL, |
| msg->token, msg->tkl); |
| |
| if (obs) { |
| obs->active_tx_operation = false; |
| if (client_ctx->observe_cb) { |
| client_ctx->observe_cb(LWM2M_OBSERVE_EVENT_NOTIFY_TIMEOUT, |
| &msg->path, msg->reply->user_data); |
| } |
| |
| lwm2m_rd_client_timeout(client_ctx); |
| } |
| } |
| |
| LOG_ERR("Notify Message Timed Out : %p", msg); |
| } |
| |
| 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; |
| } |
| |
| 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; |
| sys_snode_t *prev_node = 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), |
| 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 { |
| obs = engine_observe_node_discover(&msg->ctx->observer, &prev_node, NULL, |
| reply->token, reply->tkl); |
| |
| if (obs) { |
| obs->active_tx_operation = false; |
| 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 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 |
| |
| #if defined(CONFIG_LWM2M_RW_SENML_CBOR_SUPPORT) |
| case LWM2M_FORMAT_APP_SENML_CBOR: |
| return do_send_op_senml_cbor(msg, lwm2m_path_list); |
| #endif |
| |
| default: |
| LOG_ERR("Unsupported content-format for /dp: %u", content_format); |
| return -ENOMSG; |
| } |
| } |
| |
| 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; |
| goto cleanup; |
| } |
| /* 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, sprint_token(obs->token, obs->tkl), |
| 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", |
| sprint_token(obs->token, obs->tkl), lwm2m_sprint_ip_addr(&ctx->remote_addr), |
| (long long)k_uptime_get()); |
| } |
| |
| msg->operation = LWM2M_OP_READ; |
| 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; |
| } |
| |
| obs->active_tx_operation = true; |
| obs->resource_update = false; |
| lwm2m_information_interface_send(msg); |
| |
| LOG_DBG("NOTIFY MSG: SENT"); |
| return 0; |
| |
| cleanup: |
| lwm2m_reset_message(msg, true); |
| return ret; |
| } |
| |
| 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_t *engine_obj_list = lwm2m_engine_obj_list(); |
| |
| 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; |
| } |
| |
| 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", url); |
| |
| http_parser_url_init(&parser); |
| ret = http_parser_parse_url(url, strlen(url), 0, &parser); |
| if (ret < 0) { |
| LOG_ERR("Invalid url: %s", 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 do_composite_read_op_for_parsed_list(struct lwm2m_message *msg, uint16_t content_format, |
| sys_slist_t *path_list) |
| { |
| switch (content_format) { |
| |
| #if defined(CONFIG_LWM2M_RW_SENML_JSON_SUPPORT) |
| case LWM2M_FORMAT_APP_SEML_JSON: |
| return do_composite_read_op_for_parsed_list_senml_json(msg, path_list); |
| #endif |
| |
| #if defined(CONFIG_LWM2M_RW_SENML_CBOR_SUPPORT) |
| case LWM2M_FORMAT_APP_SENML_CBOR: |
| return do_composite_read_op_for_parsed_path_senml_cbor(msg, path_list); |
| #endif |
| |
| default: |
| LOG_ERR("Unsupported content-format: %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"); |
| lwm2m_rd_client_timeout(msg->ctx); |
| } |
| #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; |
| |
| /* Validate Connection */ |
| if (!lwm2m_rd_client_is_registred(ctx)) { |
| return -EPERM; |
| } |
| |
| 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; |
| } |
| |
| if (IS_ENABLED(CONFIG_LWM2M_RW_SENML_CBOR_SUPPORT)) { |
| content_format = LWM2M_FORMAT_APP_SENML_CBOR; |
| } else 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 |
| } |