blob: 99f673ce6b1164671525c5e8f68178e7e03ef9e9 [file] [log] [blame]
/*
* Copyright (c) 2017 Linaro Limited
* Copyright (c) 2017-2019 Foundries.io
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
* Copyright (c) 2015, Yanzi Networks AB.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Original authors:
* Joakim Eriksson <joakime@sics.se>
* Niclas Finne <nfi@sics.se>
* Joel Hoglund <joel@sics.se>
*/
#define LOG_MODULE_NAME net_lwm2m_rd_client
#define LOG_LEVEL CONFIG_LWM2M_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(LOG_MODULE_NAME);
#include <zephyr/types.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <zephyr/init.h>
#include <zephyr/sys/printk.h>
#include <zephyr/net/socket.h>
#include "lwm2m_object.h"
#include "lwm2m_engine.h"
#include "lwm2m_rd_client.h"
#include "lwm2m_rw_link_format.h"
#include "lwm2m_util.h"
#include "lwm2m_obj_server.h"
#define LWM2M_RD_CLIENT_URI "rd"
#define CLIENT_EP_LEN CONFIG_LWM2M_RD_CLIENT_ENDPOINT_NAME_MAX_LENGTH
#define CLIENT_BINDING_LEN sizeof("UQ")
#define CLIENT_QUEUE_LEN sizeof("Q")
#define DELAY_BEFORE_CLOSING (1 * MSEC_PER_SEC)
#define DELAY_FOR_ACK 100U
#define EXCHANGE_LIFETIME 247U
#define MINIMUM_PERIOD 15
#define DISABLE_TIMEOUT (K_SECONDS(CONFIG_LWM2M_RD_CLIENT_MAX_RETRIES * EXCHANGE_LIFETIME))
static void sm_handle_registration_update_failure(void);
static int sm_send_registration_msg(void);
static bool sm_is_suspended(void);
static void lwm2m_rd_client_service(struct k_work *work);
static int64_t calc_next_event(void);
static void set_sm_state_delayed(uint8_t sm_state, int64_t delay_ms);
static void set_sm_state(uint8_t sm_state);
/** Try to fallback to bootstrap. Return true if we did. */
static bool fallback_to_bootstrap(void);
/* The states for the RD client state machine */
/*
* When node is unregistered it ends up in UNREGISTERED
* and this is going to be there until use X or Y kicks it
* back into INIT again
*/
enum sm_engine_state {
ENGINE_IDLE,
ENGINE_INIT,
ENGINE_DO_BOOTSTRAP_REG,
ENGINE_BOOTSTRAP_REG_SENT,
ENGINE_BOOTSTRAP_REG_DONE,
ENGINE_BOOTSTRAP_TRANS_DONE,
ENGINE_DO_REGISTRATION,
ENGINE_SEND_REGISTRATION,
ENGINE_REGISTRATION_SENT,
ENGINE_REGISTRATION_DONE,
ENGINE_REGISTRATION_DONE_RX_OFF,
ENGINE_UPDATE_REGISTRATION,
ENGINE_UPDATE_SENT,
ENGINE_SERVER_DISABLED,
ENGINE_SUSPENDED,
ENGINE_DEREGISTER,
ENGINE_DEREGISTER_SENT,
ENGINE_DEREGISTERED,
ENGINE_NETWORK_ERROR,
};
struct lwm2m_rd_client_info {
struct k_mutex mutex;
struct lwm2m_message rd_message;
struct lwm2m_ctx *ctx;
uint32_t lifetime;
uint8_t engine_state;
uint8_t retries;
uint8_t retry_delay;
int64_t last_update;
int64_t last_tx;
int64_t next_event;
int64_t last_state_change;
char ep_name[CLIENT_EP_LEN];
char server_ep[CLIENT_EP_LEN];
bool use_bootstrap : 1;
bool trigger_update : 1;
bool update_objects : 1;
bool close_socket : 1;
bool server_disabled: 1;
} client;
/* Allocate some data for queries and updates. Make sure it's large enough to
* hold the largest query string, which in most cases will be the endpoint
* string. In other case, 32 bytes are enough to encode any other query string
* documented in the LwM2M specification.
*/
static char query_buffer[MAX(32, sizeof("ep=") + CLIENT_EP_LEN)];
static enum sm_engine_state suspended_client_state;
static struct lwm2m_message *rd_get_message(void)
{
if (client.rd_message.ctx) {
/* Free old message */
lwm2m_reset_message(&client.rd_message, true);
}
client.rd_message.ctx = client.ctx;
return &client.rd_message;
}
static void rd_client_message_free(void)
{
lwm2m_reset_message(lwm2m_get_ongoing_rd_msg(), true);
}
struct lwm2m_message *lwm2m_get_ongoing_rd_msg(void)
{
if (!client.ctx || !client.rd_message.ctx) {
return NULL;
}
return &client.rd_message;
}
void engine_update_tx_time(void)
{
client.last_tx = k_uptime_get();
}
static void next_event_at(int64_t timestamp)
{
client.next_event = timestamp;
(void)lwm2m_engine_call_at(lwm2m_rd_client_service, timestamp);
}
static void set_sm_state_delayed(uint8_t sm_state, int64_t delay_ms)
{
k_mutex_lock(&client.mutex, K_FOREVER);
enum lwm2m_rd_client_event event = LWM2M_RD_CLIENT_EVENT_NONE;
/* Determine if a callback to the app is needed */
#if defined(CONFIG_LWM2M_RD_CLIENT_SUPPORT_BOOTSTRAP)
if (sm_state == ENGINE_BOOTSTRAP_REG_DONE) {
event = LWM2M_RD_CLIENT_EVENT_BOOTSTRAP_REG_COMPLETE;
} else if (client.engine_state == ENGINE_BOOTSTRAP_TRANS_DONE &&
sm_state == ENGINE_DO_REGISTRATION) {
event = LWM2M_RD_CLIENT_EVENT_BOOTSTRAP_TRANSFER_COMPLETE;
} else
#endif
if (client.engine_state == ENGINE_UPDATE_SENT &&
(sm_state == ENGINE_REGISTRATION_DONE ||
sm_state == ENGINE_REGISTRATION_DONE_RX_OFF)) {
lwm2m_push_queued_buffers(client.ctx);
event = LWM2M_RD_CLIENT_EVENT_REG_UPDATE_COMPLETE;
} else if (sm_state == ENGINE_REGISTRATION_DONE) {
lwm2m_push_queued_buffers(client.ctx);
event = LWM2M_RD_CLIENT_EVENT_REGISTRATION_COMPLETE;
} else if (sm_state == ENGINE_REGISTRATION_DONE_RX_OFF) {
event = LWM2M_RD_CLIENT_EVENT_QUEUE_MODE_RX_OFF;
} else if (sm_state == ENGINE_DEREGISTERED &&
(client.engine_state >= ENGINE_DO_REGISTRATION &&
client.engine_state <= ENGINE_DEREGISTER_SENT) && !client.server_disabled) {
event = LWM2M_RD_CLIENT_EVENT_DISCONNECT;
} else if (sm_state == ENGINE_UPDATE_REGISTRATION) {
event = LWM2M_RD_CLIENT_EVENT_REG_UPDATE;
} else if (sm_state == ENGINE_DEREGISTER) {
if (client.server_disabled) {
event = LWM2M_RD_CLIENT_EVENT_SERVER_DISABLED;
} else {
event = LWM2M_RD_CLIENT_EVENT_DEREGISTER;
}
}
if (sm_is_suspended()) {
/* Just change the state where we are going to resume next */
suspended_client_state = sm_state;
} else {
client.engine_state = sm_state;
}
if (event > LWM2M_RD_CLIENT_EVENT_NONE && client.ctx->event_cb) {
client.ctx->event_cb(client.ctx, event);
}
/* Suspend socket after Event callback */
if (event == LWM2M_RD_CLIENT_EVENT_QUEUE_MODE_RX_OFF) {
if (IS_ENABLED(CONFIG_LWM2M_RD_CLIENT_SUSPEND_SOCKET_AT_IDLE) ||
IS_ENABLED(CONFIG_LWM2M_RD_CLIENT_STOP_POLLING_AT_IDLE)) {
lwm2m_socket_suspend(client.ctx);
} else if (IS_ENABLED(CONFIG_LWM2M_RD_CLIENT_CLOSE_SOCKET_AT_IDLE)) {
lwm2m_close_socket(client.ctx);
}
}
client.last_state_change = k_uptime_get();
next_event_at(k_uptime_get() + delay_ms);
k_mutex_unlock(&client.mutex);
}
static void set_sm_state(uint8_t sm_state)
{
set_sm_state_delayed(sm_state, 0);
}
static bool sm_is_bootstrap(void)
{
#if defined(CONFIG_LWM2M_RD_CLIENT_SUPPORT_BOOTSTRAP)
k_mutex_lock(&client.mutex, K_FOREVER);
bool is_bootstrap = (client.engine_state >= ENGINE_DO_BOOTSTRAP_REG &&
client.engine_state <= ENGINE_BOOTSTRAP_TRANS_DONE);
k_mutex_unlock(&client.mutex);
return is_bootstrap;
#else
return false;
#endif
}
static bool sm_is_registered(void)
{
k_mutex_lock(&client.mutex, K_FOREVER);
bool registered = (client.engine_state >= ENGINE_REGISTRATION_DONE &&
client.engine_state <= ENGINE_DEREGISTER_SENT);
k_mutex_unlock(&client.mutex);
return registered;
}
static bool sm_is_suspended(void)
{
k_mutex_lock(&client.mutex, K_FOREVER);
bool suspended = (client.engine_state == ENGINE_SUSPENDED);
k_mutex_unlock(&client.mutex);
return suspended;
}
static uint8_t get_sm_state(void)
{
k_mutex_lock(&client.mutex, K_FOREVER);
uint8_t state = client.engine_state;
k_mutex_unlock(&client.mutex);
return state;
}
static void sm_handle_timeout_state(enum sm_engine_state sm_state)
{
k_mutex_lock(&client.mutex, K_FOREVER);
enum lwm2m_rd_client_event event = LWM2M_RD_CLIENT_EVENT_NONE;
/* Don't send BOOTSTRAP_REG_FAILURE event, that is only emitted from
* do_network_error() once we are out of retries.
*/
if (client.engine_state == ENGINE_REGISTRATION_SENT) {
event = LWM2M_RD_CLIENT_EVENT_REG_TIMEOUT;
} else if (client.engine_state == ENGINE_UPDATE_SENT) {
event = LWM2M_RD_CLIENT_EVENT_REG_TIMEOUT;
} else if (client.engine_state == ENGINE_DEREGISTER_SENT) {
event = LWM2M_RD_CLIENT_EVENT_DEREGISTER_FAILURE;
} else {
/* TODO: unknown timeout state */
}
set_sm_state(sm_state);
if (event > LWM2M_RD_CLIENT_EVENT_NONE && client.ctx->event_cb) {
client.ctx->event_cb(client.ctx, event);
}
k_mutex_unlock(&client.mutex);
}
static void sm_handle_failure_state(enum sm_engine_state sm_state)
{
k_mutex_lock(&client.mutex, K_FOREVER);
enum lwm2m_rd_client_event event = LWM2M_RD_CLIENT_EVENT_NONE;
#if defined(CONFIG_LWM2M_RD_CLIENT_SUPPORT_BOOTSTRAP)
if (client.engine_state == ENGINE_BOOTSTRAP_REG_SENT) {
event = LWM2M_RD_CLIENT_EVENT_BOOTSTRAP_REG_FAILURE;
} else
#endif
if (client.engine_state == ENGINE_REGISTRATION_SENT) {
event = LWM2M_RD_CLIENT_EVENT_REGISTRATION_FAILURE;
} else if (client.engine_state == ENGINE_UPDATE_SENT) {
sm_handle_registration_update_failure();
k_mutex_unlock(&client.mutex);
return;
} else if (client.engine_state == ENGINE_DEREGISTER_SENT) {
event = LWM2M_RD_CLIENT_EVENT_DEREGISTER_FAILURE;
}
lwm2m_engine_stop(client.ctx);
set_sm_state(sm_state);
if (event > LWM2M_RD_CLIENT_EVENT_NONE && client.ctx->event_cb) {
client.ctx->event_cb(client.ctx, event);
}
k_mutex_unlock(&client.mutex);
}
/* force state machine restart */
static void socket_fault_cb(int error)
{
LOG_ERR("RD Client socket error: %d", error);
lwm2m_socket_close(client.ctx);
if (IS_ENABLED(CONFIG_LWM2M_RD_CLIENT_SUPPORT_BOOTSTRAP) && sm_is_bootstrap()) {
client.ctx->sec_obj_inst = -1;
/* force full registration */
client.last_update = 0;
if (get_sm_state() == ENGINE_BOOTSTRAP_TRANS_DONE) {
/* Ignore the error, some servers close the connection immediately
* after receiving Ack to Bootstrap-Finish command.
*/
return;
}
}
/* Network error state causes engine to re-register,
* so only trigger that state if we are not stopping the
* engine.
* Also when engine is going to be disabled, for a while, we might get spurious
* socket errors when closing, so ignore them.
*/
if (client.engine_state > ENGINE_IDLE &&
client.engine_state < ENGINE_SERVER_DISABLED) {
sm_handle_timeout_state(ENGINE_NETWORK_ERROR);
} else if (client.engine_state != ENGINE_SUSPENDED &&
!client.server_disabled) {
sm_handle_failure_state(ENGINE_IDLE);
}
}
/* force re-update with remote peer */
void engine_trigger_update(bool update_objects)
{
k_mutex_lock(&client.mutex, K_FOREVER);
if (client.engine_state < ENGINE_REGISTRATION_SENT ||
client.engine_state > ENGINE_UPDATE_SENT) {
k_mutex_unlock(&client.mutex);
return;
}
client.trigger_update = true;
/* short delay for Ack, then trigger an update */
next_event_at(k_uptime_get() + DELAY_FOR_ACK);
if (update_objects) {
client.update_objects = true;
}
k_mutex_unlock(&client.mutex);
}
static inline const char *code2str(uint8_t code)
{
switch (code) {
case COAP_RESPONSE_CODE_BAD_REQUEST:
return "Bad Request";
case COAP_RESPONSE_CODE_FORBIDDEN:
return "Forbidden";
case COAP_RESPONSE_CODE_NOT_FOUND:
return "Not Found";
case COAP_RESPONSE_CODE_PRECONDITION_FAILED:
return "Precondition Failed";
default:
break;
}
return "Unknown";
}
/* state machine reply callbacks */
#if defined(CONFIG_LWM2M_RD_CLIENT_SUPPORT_BOOTSTRAP)
static int do_bootstrap_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("Bootstrap callback (code:%u.%u)",
COAP_RESPONSE_CODE_CLASS(code),
COAP_RESPONSE_CODE_DETAIL(code));
if (code == COAP_RESPONSE_CODE_CHANGED) {
LOG_INF("Bootstrap registration done!");
set_sm_state(ENGINE_BOOTSTRAP_REG_DONE);
return 0;
}
LOG_ERR("Failed with code %u.%u (%s). Not Retrying.",
COAP_RESPONSE_CODE_CLASS(code), COAP_RESPONSE_CODE_DETAIL(code),
code2str(code));
sm_handle_failure_state(ENGINE_IDLE);
return 0;
}
static void do_bootstrap_reg_timeout_cb(struct lwm2m_message *msg)
{
LOG_WRN("Bootstrap Timeout");
sm_handle_timeout_state(ENGINE_NETWORK_ERROR);
}
#endif
int engine_trigger_bootstrap(void)
{
#if defined(CONFIG_LWM2M_RD_CLIENT_SUPPORT_BOOTSTRAP)
k_mutex_lock(&client.mutex, K_FOREVER);
if (client.use_bootstrap) {
/* Bootstrap is not possible to trig */
LOG_WRN("Bootstrap process ongoing");
k_mutex_unlock(&client.mutex);
return -EPERM;
}
LOG_INF("Server Initiated Bootstrap");
/* Free ongoing possible message */
rd_client_message_free();
client.use_bootstrap = true;
client.trigger_update = false;
set_sm_state_delayed(ENGINE_INIT, DELAY_BEFORE_CLOSING);
k_mutex_unlock(&client.mutex);
return 0;
#else
return -EPERM;
#endif
}
static int do_registration_reply_cb(const struct coap_packet *response,
struct coap_reply *reply,
const struct sockaddr *from)
{
struct coap_option options[2];
uint8_t code;
int ret = -EINVAL;
code = coap_header_get_code(response);
LOG_DBG("Registration callback (code:%u.%u)",
COAP_RESPONSE_CODE_CLASS(code),
COAP_RESPONSE_CODE_DETAIL(code));
/* check state and possibly set registration to done */
if (code == COAP_RESPONSE_CODE_CREATED) {
ret = coap_find_options(response, COAP_OPTION_LOCATION_PATH,
options, 2);
if (ret < 2) {
LOG_ERR("Unexpected endpoint data returned. ret = %d", ret);
ret = -EINVAL;
goto fail;
}
/* option[0] should be "rd" */
if (options[1].len + 1 > sizeof(client.server_ep)) {
LOG_ERR("Unexpected length of query: "
"%u (expected %zu)\n",
options[1].len,
sizeof(client.server_ep));
ret = -EINVAL;
goto fail;
}
/* remember the last reg time */
client.last_update = k_uptime_get();
client.server_disabled = false;
client.retries = 0;
memcpy(client.server_ep, options[1].value,
options[1].len);
client.server_ep[options[1].len] = '\0';
set_sm_state(ENGINE_REGISTRATION_DONE);
LOG_INF("Registration Done (EP='%s')",
client.server_ep);
return 0;
}
LOG_ERR("Failed with code %u.%u (%s).",
COAP_RESPONSE_CODE_CLASS(code), COAP_RESPONSE_CODE_DETAIL(code),
code2str(code));
fail:
lwm2m_server_disable(client.ctx->srv_obj_inst, DISABLE_TIMEOUT);
sm_handle_failure_state(ENGINE_NETWORK_ERROR);
return ret;
}
static void do_registration_timeout_cb(struct lwm2m_message *msg)
{
LOG_WRN("Registration Timeout");
sm_handle_timeout_state(ENGINE_NETWORK_ERROR);
}
static int do_update_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_INF("Update callback (code:%u.%u)",
COAP_RESPONSE_CODE_CLASS(code),
COAP_RESPONSE_CODE_DETAIL(code));
/* If NOT_FOUND just continue on */
if ((code == COAP_RESPONSE_CODE_CHANGED) ||
(code == COAP_RESPONSE_CODE_CREATED)) {
/* remember the last reg time */
client.last_update = k_uptime_get();
set_sm_state(ENGINE_REGISTRATION_DONE);
LOG_INF("Update Done");
return 0;
}
LOG_ERR("Failed with code %u.%u (%s). Retrying registration.",
COAP_RESPONSE_CODE_CLASS(code), COAP_RESPONSE_CODE_DETAIL(code),
code2str(code));
sm_handle_failure_state(ENGINE_DO_REGISTRATION);
return 0;
}
static void do_update_timeout_cb(struct lwm2m_message *msg)
{
LOG_WRN("Registration Update Timeout");
if (client.ctx->sock_fd > -1) {
client.close_socket = true;
}
/* Re-do registration */
sm_handle_timeout_state(ENGINE_DO_REGISTRATION);
}
static int do_deregister_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("Deregister callback (code:%u.%u)",
COAP_RESPONSE_CODE_CLASS(code),
COAP_RESPONSE_CODE_DETAIL(code));
if (code == COAP_RESPONSE_CODE_DELETED) {
LOG_INF("Deregistration success");
set_sm_state(ENGINE_DEREGISTERED);
return 0;
}
LOG_ERR("Failed with code %u.%u (%s). Not Retrying",
COAP_RESPONSE_CODE_CLASS(code), COAP_RESPONSE_CODE_DETAIL(code),
code2str(code));
sm_handle_failure_state(ENGINE_DEREGISTERED);
return 0;
}
static void do_deregister_timeout_cb(struct lwm2m_message *msg)
{
LOG_WRN("De-Registration Timeout");
sm_handle_timeout_state(ENGINE_DEREGISTERED);
}
static bool is_bootsrap_server(int sec_obj_inst)
{
bool bootstrap;
int ret;
ret = lwm2m_get_bool(&LWM2M_OBJ(0, sec_obj_inst, 1), &bootstrap);
if (ret < 0) {
LOG_WRN("Failed to check bootstrap, err %d", ret);
return false;
}
return bootstrap;
}
static bool sm_update_lifetime(int srv_obj_inst, uint32_t *lifetime)
{
uint32_t new_lifetime;
if (lwm2m_get_u32(&LWM2M_OBJ(1, srv_obj_inst, 1), &new_lifetime) < 0) {
new_lifetime = CONFIG_LWM2M_ENGINE_DEFAULT_LIFETIME;
LOG_INF("Using default lifetime: %u", new_lifetime);
}
if (new_lifetime < CONFIG_LWM2M_ENGINE_DEFAULT_LIFETIME) {
new_lifetime = CONFIG_LWM2M_ENGINE_DEFAULT_LIFETIME;
lwm2m_set_u32(&LWM2M_OBJ(1, srv_obj_inst, 1), new_lifetime);
LOG_INF("Overwrite a server lifetime with default");
}
if (new_lifetime != *lifetime) {
*lifetime = new_lifetime;
return true;
}
return false;
}
/**
* @brief Find the next security instance for bootstrapping.
*
* Search for the next security instance that has the bootstrap flag set and
* is not the same as current security instance.
*
* @param sec_obj_inst current security instance or -1.
* @return zero on success, negative on error.
*/
static int sm_next_bootstrap_inst(int *sec_obj_inst)
{
int i, obj_inst_id = -1;
if (*sec_obj_inst >= 0 && !is_bootsrap_server(*sec_obj_inst)) {
*sec_obj_inst = -1;
}
/* Iterate over all instances to find the correct one. */
for (i = 0; i < CONFIG_LWM2M_SECURITY_INSTANCE_COUNT; i++) {
obj_inst_id = lwm2m_security_index_to_inst_id(i);
if (obj_inst_id < 0) {
continue;
}
if (obj_inst_id == *sec_obj_inst) {
continue;
}
if (is_bootsrap_server(obj_inst_id)) {
*sec_obj_inst = obj_inst_id;
return 0;
}
}
LOG_WRN("No Bootstrap servers found.");
return -ENOENT;
}
/* state machine step functions */
static int sm_do_init(void)
{
lwm2m_engine_stop(client.ctx);
client.trigger_update = false;
client.lifetime = 0U;
client.last_update = 0U;
client.close_socket = false;
/* Do bootstrap or registration */
if (client.use_bootstrap && IS_ENABLED(CONFIG_LWM2M_RD_CLIENT_SUPPORT_BOOTSTRAP)) {
set_sm_state(ENGINE_DO_BOOTSTRAP_REG);
} else {
set_sm_state(ENGINE_DO_REGISTRATION);
}
return 0;
}
#if defined(CONFIG_LWM2M_RD_CLIENT_SUPPORT_BOOTSTRAP)
static int sm_send_bootstrap_registration(void)
{
struct lwm2m_message *msg;
int ret;
msg = rd_get_message();
if (!msg) {
LOG_ERR("Unable to get a lwm2m message!");
return -ENOMEM;
}
msg->type = COAP_TYPE_CON;
msg->code = COAP_METHOD_POST;
msg->mid = coap_next_id();
msg->tkl = LWM2M_MSG_TOKEN_GENERATE_NEW;
msg->reply_cb = do_bootstrap_reply_cb;
msg->message_timeout_cb = do_bootstrap_reg_timeout_cb;
ret = lwm2m_init_message(msg);
if (ret) {
goto cleanup;
}
ret = coap_packet_append_option(&msg->cpkt, COAP_OPTION_URI_PATH,
"bs", strlen("bs"));
if (ret < 0) {
goto cleanup;
}
snprintk(query_buffer, sizeof(query_buffer) - 1, "ep=%s",
client.ep_name);
ret = coap_packet_append_option(&msg->cpkt, COAP_OPTION_URI_QUERY,
query_buffer, strlen(query_buffer));
if (ret < 0) {
goto cleanup;
}
if (IS_ENABLED(CONFIG_LWM2M_VERSION_1_1)) {
int pct = LWM2M_FORMAT_OMA_TLV;
if (IS_ENABLED(CONFIG_LWM2M_RW_SENML_CBOR_SUPPORT)) {
pct = LWM2M_FORMAT_APP_SENML_CBOR;
} else if (IS_ENABLED(CONFIG_LWM2M_RW_SENML_JSON_SUPPORT)) {
pct = LWM2M_FORMAT_APP_SEML_JSON;
}
snprintk(query_buffer, sizeof(query_buffer) - 1, "pct=%d", pct);
coap_packet_append_option(&msg->cpkt, COAP_OPTION_URI_QUERY,
query_buffer, strlen(query_buffer));
}
/* log the bootstrap attempt */
LOG_DBG("Register ID with bootstrap server as '%s'",
query_buffer);
lwm2m_send_message_async(msg);
return 0;
cleanup:
lwm2m_reset_message(msg, true);
return ret;
}
static void sm_do_bootstrap_reg(void)
{
int ret;
/* clear out existing connection data */
if (client.ctx->sock_fd > -1) {
lwm2m_engine_stop(client.ctx);
}
client.ctx->bootstrap_mode = true;
ret = sm_next_bootstrap_inst(&client.ctx->sec_obj_inst);
if (ret < 0) {
set_sm_state(ENGINE_NETWORK_ERROR);
return;
}
LOG_INF("Bootstrap started with endpoint '%s' using security object %d",
client.ep_name, client.ctx->sec_obj_inst);
ret = lwm2m_engine_start(client.ctx);
if (ret < 0) {
LOG_ERR("Cannot init LWM2M engine (%d)", ret);
set_sm_state(ENGINE_NETWORK_ERROR);
return;
}
ret = sm_send_bootstrap_registration();
if (!ret) {
set_sm_state(ENGINE_BOOTSTRAP_REG_SENT);
} else {
LOG_ERR("Bootstrap registration err: %d", ret);
set_sm_state(ENGINE_NETWORK_ERROR);
}
return;
}
void engine_bootstrap_finish(void)
{
LOG_INF("Bootstrap data transfer done!");
/* Delay the state transition, so engine have some time to send ACK
* before we close the socket
*/
set_sm_state_delayed(ENGINE_BOOTSTRAP_TRANS_DONE, DELAY_BEFORE_CLOSING);
}
static int sm_bootstrap_trans_done(void)
{
/* close down context resources */
lwm2m_engine_stop(client.ctx);
/* reset security object instance */
client.ctx->sec_obj_inst = -1;
client.use_bootstrap = false;
set_sm_state(ENGINE_DO_REGISTRATION);
return 0;
}
#endif
static int sm_send_registration(bool send_obj_support_data,
coap_reply_t reply_cb,
lwm2m_message_timeout_cb_t timeout_cb)
{
struct lwm2m_message *msg;
int ret;
char binding[CLIENT_BINDING_LEN];
char queue[CLIENT_QUEUE_LEN];
msg = rd_get_message();
if (!msg) {
LOG_ERR("Unable to get a lwm2m message!");
return -ENOMEM;
}
msg->type = COAP_TYPE_CON;
msg->code = COAP_METHOD_POST;
msg->mid = coap_next_id();
msg->tkl = LWM2M_MSG_TOKEN_GENERATE_NEW;
msg->reply_cb = reply_cb;
msg->message_timeout_cb = timeout_cb;
ret = lwm2m_init_message(msg);
if (ret) {
goto cleanup;
}
ret = coap_packet_append_option(&msg->cpkt, COAP_OPTION_URI_PATH,
LWM2M_RD_CLIENT_URI,
strlen(LWM2M_RD_CLIENT_URI));
if (ret < 0) {
goto cleanup;
}
if (sm_is_registered()) {
ret = coap_packet_append_option(
&msg->cpkt, COAP_OPTION_URI_PATH,
client.server_ep, strlen(client.server_ep));
if (ret < 0) {
goto cleanup;
}
}
if (send_obj_support_data) {
ret = coap_append_option_int(
&msg->cpkt, COAP_OPTION_CONTENT_FORMAT,
LWM2M_FORMAT_APP_LINK_FORMAT);
if (ret < 0) {
goto cleanup;
}
}
if (!sm_is_registered()) {
snprintk(query_buffer, sizeof(query_buffer) - 1,
"lwm2m=%s", LWM2M_PROTOCOL_VERSION_STRING);
ret = coap_packet_append_option(
&msg->cpkt, COAP_OPTION_URI_QUERY,
query_buffer, strlen(query_buffer));
if (ret < 0) {
goto cleanup;
}
snprintk(query_buffer, sizeof(query_buffer) - 1,
"ep=%s", client.ep_name);
ret = coap_packet_append_option(
&msg->cpkt, COAP_OPTION_URI_QUERY,
query_buffer, strlen(query_buffer));
if (ret < 0) {
goto cleanup;
}
}
/* Send lifetime only if changed or on initial registration.*/
if (sm_update_lifetime(client.ctx->srv_obj_inst, &client.lifetime) ||
!sm_is_registered()) {
snprintk(query_buffer, sizeof(query_buffer) - 1,
"lt=%d", client.lifetime);
ret = coap_packet_append_option(
&msg->cpkt, COAP_OPTION_URI_QUERY,
query_buffer, strlen(query_buffer));
if (ret < 0) {
goto cleanup;
}
}
lwm2m_engine_get_binding(binding);
lwm2m_engine_get_queue_mode(queue);
/* UDP is a default binding, no need to add option if UDP without queue is used. */
if ((!sm_is_registered() && (strcmp(binding, "U") != 0 || strcmp(queue, "Q") == 0))) {
snprintk(query_buffer, sizeof(query_buffer) - 1,
"b=%s", binding);
ret = coap_packet_append_option(
&msg->cpkt, COAP_OPTION_URI_QUERY,
query_buffer, strlen(query_buffer));
if (ret < 0) {
goto cleanup;
}
#if CONFIG_LWM2M_VERSION_1_1
/* In LwM2M 1.1, queue mode is a separate parameter */
uint16_t len = strlen(queue);
if (len) {
ret = coap_packet_append_option(
&msg->cpkt, COAP_OPTION_URI_QUERY,
queue, len);
if (ret < 0) {
goto cleanup;
}
}
#endif
}
if (send_obj_support_data) {
ret = coap_packet_append_payload_marker(&msg->cpkt);
if (ret < 0) {
goto cleanup;
}
msg->out.out_cpkt = &msg->cpkt;
msg->out.writer = &link_format_writer;
ret = do_register_op_link_format(msg);
if (ret < 0) {
goto cleanup;
}
}
lwm2m_send_message_async(msg);
/* log the registration attempt */
LOG_DBG("registration sent [%s]",
lwm2m_sprint_ip_addr(&client.ctx->remote_addr));
return 0;
cleanup:
LOG_ERR("error %d when sending registration message", ret);
lwm2m_reset_message(msg, true);
return ret;
}
static void sm_handle_registration_update_failure(void)
{
k_mutex_lock(&client.mutex, K_FOREVER);
LOG_WRN("Registration Update fail -> trigger full registration");
lwm2m_engine_context_close(client.ctx);
set_sm_state(ENGINE_SEND_REGISTRATION);
k_mutex_unlock(&client.mutex);
}
static int sm_send_registration_msg(void)
{
int ret;
ret = sm_send_registration(true,
do_registration_reply_cb,
do_registration_timeout_cb);
if (!ret) {
set_sm_state(ENGINE_REGISTRATION_SENT);
} else {
LOG_ERR("Registration err: %d", ret);
set_sm_state(ENGINE_NETWORK_ERROR);
}
return ret;
}
static void sm_do_registration(void)
{
uint16_t ssid;
int ret = 0;
if (client.ctx->connection_suspended) {
if (lwm2m_engine_connection_resume(client.ctx)) {
lwm2m_engine_context_close(client.ctx);
/* perform full registration */
set_sm_state(ENGINE_DO_REGISTRATION);
return;
}
} else {
bool select_srv = true;
uint16_t srv = (uint16_t) client.ctx->srv_obj_inst;
client.last_update = 0;
client.ctx->bootstrap_mode = false;
/* clear out existing connection data */
if (client.ctx->sock_fd > -1) {
if (client.close_socket) {
/* Clear old socket connection */
client.close_socket = false;
lwm2m_engine_stop(client.ctx);
} else {
lwm2m_engine_context_close(client.ctx);
/* Keep current connection, retry registration with same server */
select_srv = false;
}
}
if (select_srv) {
/* Select next one from the list, or fail */
if (!lwm2m_server_select(&srv)) {
LOG_ERR("Unable to find a valid server instance.");
goto bootstrap_or_retry;
}
client.ctx->srv_obj_inst = srv;
sm_update_lifetime(srv, &client.lifetime);
ret = lwm2m_get_u16(&LWM2M_OBJ(1, client.ctx->srv_obj_inst, 0), &ssid);
if (ret < 0) {
LOG_ERR("Failed to read SSID");
lwm2m_server_disable(srv, K_FOREVER);
goto bootstrap_or_retry;
}
ret = lwm2m_security_short_id_to_inst(ssid);
if (ret < 0) {
LOG_ERR("Unable to find a valid security instance.");
lwm2m_server_disable(srv, K_FOREVER);
goto bootstrap_or_retry;
}
client.ctx->sec_obj_inst = (uint16_t) ret;
}
LOG_INF("RD Client started with endpoint '%s' with client lifetime %d using server "
"object %d",
client.ep_name, client.lifetime, client.ctx->srv_obj_inst);
ret = lwm2m_engine_start(client.ctx);
if (ret < 0) {
LOG_ERR("Cannot init LWM2M engine (%d)", ret);
goto bootstrap_or_retry;
}
}
sm_send_registration_msg();
return;
bootstrap_or_retry:
lwm2m_engine_stop(client.ctx);
if (!client.server_disabled && fallback_to_bootstrap()) {
return;
}
set_sm_state(ENGINE_NETWORK_ERROR);
}
static int64_t next_update(void)
{
int64_t next;
int64_t period = CONFIG_LWM2M_UPDATE_PERIOD;
int64_t early = CONFIG_LWM2M_SECONDS_TO_UPDATE_EARLY;
if (period == 0) {
period = client.lifetime;
}
if (early > client.lifetime) {
early = client.lifetime;
}
next = MIN(period, client.lifetime - early);
next = MAX(next, MINIMUM_PERIOD);
return client.last_update + next * MSEC_PER_SEC;
}
static int64_t next_rx_off(void)
{
if (IS_ENABLED(CONFIG_LWM2M_QUEUE_MODE_ENABLED)) {
return client.last_tx + CONFIG_LWM2M_QUEUE_MODE_UPTIME * MSEC_PER_SEC;
} else {
return next_update();
}
}
/** Return timestamp to next even whether it is RX_OFF or update event */
static int64_t calc_next_event(void)
{
return Z_MIN(next_update(), next_rx_off());
}
static void sm_registration_done(void)
{
k_mutex_lock(&client.mutex, K_FOREVER);
int64_t now = k_uptime_get();
if (sm_is_registered() &&
(client.trigger_update ||
now >= next_update())) {
set_sm_state_delayed(ENGINE_UPDATE_REGISTRATION, DELAY_FOR_ACK);
} else if (IS_ENABLED(CONFIG_LWM2M_QUEUE_MODE_ENABLED) &&
(client.engine_state != ENGINE_REGISTRATION_DONE_RX_OFF) &&
(now >= next_rx_off())) {
set_sm_state(ENGINE_REGISTRATION_DONE_RX_OFF);
next_event_at(next_update());
} else {
next_event_at(calc_next_event());
}
k_mutex_unlock(&client.mutex);
}
static int update_registration(void)
{
int ret;
bool update_objects;
update_objects = client.update_objects;
client.trigger_update = false;
client.update_objects = false;
ret = lwm2m_engine_connection_resume(client.ctx);
if (ret) {
return ret;
}
ret = sm_send_registration(update_objects,
do_update_reply_cb,
do_update_timeout_cb);
if (ret) {
LOG_ERR("Registration update err: %d", ret);
return ret;
}
return 0;
}
static int sm_update_registration(void)
{
int ret;
ret = update_registration();
if (ret) {
LOG_ERR("Failed to update registration. Falling back to full registration");
lwm2m_engine_stop(client.ctx);
/* perform full registration */
set_sm_state(ENGINE_DO_REGISTRATION);
return ret;
}
set_sm_state(ENGINE_UPDATE_SENT);
return 0;
}
static int sm_do_deregister(void)
{
struct lwm2m_message *msg;
int ret;
if (lwm2m_engine_connection_resume(client.ctx)) {
lwm2m_engine_context_close(client.ctx);
/* Connection failed, enter directly to deregistered state */
set_sm_state(ENGINE_DEREGISTERED);
return 0;
}
msg = rd_get_message();
if (!msg) {
LOG_ERR("Unable to get a lwm2m message!");
ret = -ENOMEM;
goto close_ctx;
}
msg->type = COAP_TYPE_CON;
msg->code = COAP_METHOD_DELETE;
msg->mid = coap_next_id();
msg->tkl = LWM2M_MSG_TOKEN_GENERATE_NEW;
msg->reply_cb = do_deregister_reply_cb;
msg->message_timeout_cb = do_deregister_timeout_cb;
ret = lwm2m_init_message(msg);
if (ret) {
goto cleanup;
}
ret = coap_packet_append_option(&msg->cpkt, COAP_OPTION_URI_PATH,
LWM2M_RD_CLIENT_URI,
strlen(LWM2M_RD_CLIENT_URI));
if (ret < 0) {
LOG_ERR("Failed to encode URI path option (err:%d).", ret);
goto cleanup;
}
/* include server endpoint in URI PATH */
ret = coap_packet_append_option(&msg->cpkt, COAP_OPTION_URI_PATH,
client.server_ep,
strlen(client.server_ep));
if (ret < 0) {
LOG_ERR("Failed to encode URI path option (err:%d).", ret);
goto cleanup;
}
LOG_INF("Deregister from '%s'", client.server_ep);
lwm2m_send_message_async(msg);
set_sm_state(ENGINE_DEREGISTER_SENT);
return 0;
cleanup:
lwm2m_reset_message(msg, true);
close_ctx:
lwm2m_engine_stop(client.ctx);
set_sm_state(ENGINE_DEREGISTERED);
return ret;
}
static bool fallback_to_bootstrap(void)
{
if (IS_ENABLED(CONFIG_LWM2M_RD_CLIENT_SUPPORT_BOOTSTRAP)) {
bool fallback = true;
(void)lwm2m_get_bool(&LWM2M_OBJ(LWM2M_OBJECT_SERVER_ID, client.ctx->srv_obj_inst,
SERVER_BOOTSTRAP_ON_REGISTRATION_FAILURE_ID),
&fallback);
if (fallback) {
client.use_bootstrap = true;
set_sm_state(ENGINE_INIT);
return true;
}
}
return false;
}
static void sm_do_network_error(void)
{
int err;
LOG_ERR("sm_do_network_error, retries %d", client.retries);
lwm2m_socket_close(client.ctx);
if (client.retry_delay) {
next_event_at(k_uptime_get() + client.retry_delay * MSEC_PER_SEC);
client.retry_delay = 0;
return;
}
client.retry_delay = 1 << client.retries;
client.retries++;
/* Stop retrying and try fallback */
if (client.retries > CONFIG_LWM2M_RD_CLIENT_MAX_RETRIES) {
LOG_ERR("Network error, max retries reached (%d)", client.retries);
/* Disable current server for a period so lwm2m_server_select() does not pick it */
if (client.ctx->srv_obj_inst > -1) {
lwm2m_server_disable(client.ctx->srv_obj_inst, DISABLE_TIMEOUT);
}
/* Are we in bootstrap? Try if we can fallback to some other BS server */
if (client.ctx->bootstrap_mode &&
IS_ENABLED(CONFIG_LWM2M_RD_CLIENT_SUPPORT_BOOTSTRAP)) {
LOG_DBG("In bootstrap, try fallback srv");
/* Do we have any other bootstrap server to back off to? */
if (sm_next_bootstrap_inst(&client.ctx->sec_obj_inst) < 0) {
/* No, we are out of options, stop engine */
goto stop_engine;
}
set_sm_state(ENGINE_INIT);
return;
}
/* Try if there are other server to fall back to,
* Only allow fallback to higher priority server (lower value, or lower id)
* if we have successfully registered before.
* This should block us from looping the same list again.
* Instead we should fallback to bootstrap.
*/
uint16_t srv;
if (lwm2m_server_select(&srv)) {
uint8_t p1, p2;
p1 = lwm2m_server_get_prio(client.ctx->srv_obj_inst);
p2 = lwm2m_server_get_prio(srv);
if (p1 < p2 || client.last_update != 0) {
set_sm_state(ENGINE_INIT);
return;
}
}
/* If we have been disabled by some server, don't fall back to bootstrap */
if (client.server_disabled) {
set_sm_state(ENGINE_SERVER_DISABLED);
return;
}
if (fallback_to_bootstrap()) {
return;
}
goto stop_engine;
}
/* Retry bootstrap */
if (IS_ENABLED(CONFIG_LWM2M_RD_CLIENT_SUPPORT_BOOTSTRAP)) {
if (client.ctx->bootstrap_mode) {
lwm2m_engine_context_close(client.ctx);
/* If we don't have fallback BS server, retry with current one */
if (sm_next_bootstrap_inst(&client.ctx->sec_obj_inst) < 0) {
client.ctx->sec_obj_inst = -1;
}
set_sm_state(ENGINE_DO_BOOTSTRAP_REG);
return;
}
}
if (!client.last_update ||
(k_uptime_get() - client.last_update) / MSEC_PER_SEC > client.lifetime) {
/* do full registration as there is no active registration or lifetime exceeded */
/* Keep the same server until out of retry */
set_sm_state(ENGINE_DO_REGISTRATION);
return;
}
/* Try if we can recover the DTLS session and try Update.
* This might fallback into full registration on sm_handle_registration_update_failure().
*/
err = lwm2m_socket_start(client.ctx);
if (err) {
LOG_ERR("Failed to start socket %d", err);
/*
* keep this state until lifetime/retry count exceeds. Renew
* sm state to set retry_delay etc ...
*/
set_sm_state(ENGINE_NETWORK_ERROR);
return;
}
set_sm_state(ENGINE_UPDATE_REGISTRATION);
return;
stop_engine:
/* We are out of options, stop engine */
if (client.ctx->event_cb) {
if (client.ctx->bootstrap_mode) {
client.ctx->event_cb(client.ctx,
LWM2M_RD_CLIENT_EVENT_BOOTSTRAP_REG_FAILURE);
} else {
client.ctx->event_cb(client.ctx, LWM2M_RD_CLIENT_EVENT_NETWORK_ERROR);
}
}
set_sm_state(ENGINE_IDLE);
}
static void lwm2m_rd_client_service(struct k_work *work)
{
k_mutex_lock(&client.mutex, K_FOREVER);
int64_t timeout = 0;
if (client.ctx) {
LOG_DBG("State: %d", get_sm_state());
client.next_event = INT64_MAX;
switch (get_sm_state()) {
case ENGINE_IDLE:
if (client.ctx->sock_fd > -1) {
lwm2m_engine_stop(client.ctx);
}
rd_client_message_free();
break;
case ENGINE_INIT:
sm_do_init();
break;
case ENGINE_SUSPENDED:
break;
#if defined(CONFIG_LWM2M_RD_CLIENT_SUPPORT_BOOTSTRAP)
case ENGINE_DO_BOOTSTRAP_REG:
sm_do_bootstrap_reg();
break;
case ENGINE_BOOTSTRAP_REG_SENT:
/* wait for bootstrap registration done */
timeout = EXCHANGE_LIFETIME;
break;
case ENGINE_BOOTSTRAP_REG_DONE:
/* wait for transfer done */
timeout = EXCHANGE_LIFETIME;
break;
case ENGINE_BOOTSTRAP_TRANS_DONE:
sm_bootstrap_trans_done();
break;
#endif
case ENGINE_DO_REGISTRATION:
sm_do_registration();
break;
case ENGINE_SEND_REGISTRATION:
sm_send_registration_msg();
break;
case ENGINE_REGISTRATION_SENT:
/* wait registration to be done or timeout */
timeout = EXCHANGE_LIFETIME;
break;
case ENGINE_REGISTRATION_DONE:
case ENGINE_REGISTRATION_DONE_RX_OFF:
sm_registration_done();
break;
case ENGINE_UPDATE_REGISTRATION:
sm_update_registration();
break;
case ENGINE_UPDATE_SENT:
/* wait update to be done or abort */
timeout = EXCHANGE_LIFETIME;
break;
case ENGINE_SERVER_DISABLED:
if (lwm2m_server_select(NULL)) {
set_sm_state(ENGINE_INIT);
} else {
/* wait for server to be enabled. */
/*
* TODO: Once engine is converted to use timepoint_t
* this should calculate the next event from the previous server.
*/
next_event_at(k_uptime_get() + SEC_PER_MIN * MSEC_PER_SEC);
}
break;
case ENGINE_DEREGISTER:
sm_do_deregister();
break;
case ENGINE_DEREGISTER_SENT:
/* wait for deregister to be done or reset */
timeout = EXCHANGE_LIFETIME;
break;
case ENGINE_DEREGISTERED:
lwm2m_engine_stop(client.ctx);
if (client.server_disabled) {
set_sm_state(ENGINE_SERVER_DISABLED);
} else {
set_sm_state(ENGINE_IDLE);
}
break;
case ENGINE_NETWORK_ERROR:
sm_do_network_error();
break;
default:
LOG_ERR("Unhandled state: %d", get_sm_state());
}
if (timeout) {
int64_t end = client.last_state_change + timeout * MSEC_PER_SEC;
if (end < k_uptime_get()) {
LOG_DBG("State machine have timed out");
sm_handle_timeout_state(ENGINE_INIT);
} else if (client.next_event > end) {
next_event_at(end);
}
}
}
k_mutex_unlock(&client.mutex);
}
int lwm2m_rd_client_start(struct lwm2m_ctx *client_ctx, const char *ep_name,
uint32_t flags, lwm2m_ctx_event_cb_t event_cb,
lwm2m_observe_cb_t observe_cb)
{
k_mutex_lock(&client.mutex, K_FOREVER);
if (!IS_ENABLED(CONFIG_LWM2M_RD_CLIENT_SUPPORT_BOOTSTRAP) &&
(flags & LWM2M_RD_CLIENT_FLAG_BOOTSTRAP)) {
LOG_ERR("Bootstrap support is disabled. Please enable "
"CONFIG_LWM2M_RD_CLIENT_SUPPORT_BOOTSTRAP.");
k_mutex_unlock(&client.mutex);
return -ENOTSUP;
}
/* Check client idle state or socket is still active */
if (client.ctx && (client.engine_state != ENGINE_IDLE || client.ctx->sock_fd != -1)) {
LOG_WRN("Client is already running. state %d ", client.engine_state);
k_mutex_unlock(&client.mutex);
return -EINPROGRESS;
}
/* Init Context */
lwm2m_server_reset_timestamps();
lwm2m_engine_context_init(client_ctx);
client.ctx = client_ctx;
client.ctx->sock_fd = -1;
client.ctx->fault_cb = socket_fault_cb;
client.ctx->observe_cb = observe_cb;
client.ctx->event_cb = event_cb;
client.use_bootstrap = flags & LWM2M_RD_CLIENT_FLAG_BOOTSTRAP;
client.ctx->srv_obj_inst = -1;
client.ctx->sec_obj_inst = -1;
client.retries = 0;
strncpy(client.ep_name, ep_name, CLIENT_EP_LEN - 1);
client.ep_name[CLIENT_EP_LEN - 1] = '\0';
LOG_INF("Start LWM2M Client: %s", client.ep_name);
set_sm_state(ENGINE_INIT);
k_mutex_unlock(&client.mutex);
return 0;
}
int lwm2m_rd_client_stop(struct lwm2m_ctx *client_ctx,
lwm2m_ctx_event_cb_t event_cb, bool deregister)
{
k_mutex_lock(&client.mutex, K_FOREVER);
if (client.ctx != client_ctx) {
k_mutex_unlock(&client.mutex);
LOG_WRN("Cannot stop. Wrong context");
return -EPERM;
}
client.ctx->event_cb = event_cb;
rd_client_message_free();
if (sm_is_registered() && deregister && !client.server_disabled) {
set_sm_state(ENGINE_DEREGISTER);
} else {
client.server_disabled = false;
set_sm_state(ENGINE_DEREGISTERED);
}
LOG_INF("Stop LWM2M Client: %s", client.ep_name);
k_mutex_unlock(&client.mutex);
return 0;
}
int lwm2m_rd_client_pause(void)
{
enum lwm2m_rd_client_event event = LWM2M_RD_CLIENT_EVENT_ENGINE_SUSPENDED;
LOG_DBG("lwm2m_rd_client_pause()");
k_mutex_lock(&client.mutex, K_FOREVER);
if (!client.ctx) {
k_mutex_unlock(&client.mutex);
LOG_ERR("Cannot pause. No context");
return -EPERM;
} else if (sm_is_suspended()) {
k_mutex_unlock(&client.mutex);
LOG_ERR("LwM2M client already suspended");
return 0;
}
LOG_INF("Suspend client");
if (client.ctx->event_cb) {
client.ctx->event_cb(client.ctx, event);
}
/* Suspend or close the socket */
if (IS_ENABLED(CONFIG_LWM2M_RD_CLIENT_CLOSE_SOCKET_AT_IDLE)) {
lwm2m_close_socket(client.ctx);
} else {
lwm2m_socket_suspend(client.ctx);
}
suspended_client_state = get_sm_state();
set_sm_state(ENGINE_SUSPENDED);
k_mutex_unlock(&client.mutex);
return 0;
}
int lwm2m_rd_client_resume(void)
{
k_mutex_lock(&client.mutex, K_FOREVER);
if (!client.ctx || !lwm2m_rd_client_is_suspended(client.ctx)) {
k_mutex_unlock(&client.mutex);
LOG_WRN("Cannot resume, state is not suspended");
return -EPERM;
}
LOG_INF("Resume Client state");
if (suspended_client_state == ENGINE_UPDATE_SENT) {
/* Set back to Registration done and trigger an update */
suspended_client_state = ENGINE_REGISTRATION_DONE;
}
/* Clear Possible pending RD Client message */
rd_client_message_free();
client.engine_state = suspended_client_state;
/* Do we need to resume the bootstrap? */
#if defined(CONFIG_LWM2M_RD_CLIENT_SUPPORT_BOOTSTRAP)
if (sm_is_bootstrap()) {
client.engine_state = ENGINE_DO_BOOTSTRAP_REG;
}
#endif
/* Or do we resume into registration state */
if (client.engine_state >= ENGINE_DO_REGISTRATION &&
client.engine_state <= ENGINE_SERVER_DISABLED) {
if (!client.last_update ||
(client.lifetime <= (k_uptime_get() - client.last_update) / MSEC_PER_SEC)) {
/* No lifetime left, register again */
client.engine_state = ENGINE_DO_REGISTRATION;
} else {
/* Resume similarly like from QUEUE mode */
client.engine_state = ENGINE_REGISTRATION_DONE_RX_OFF;
lwm2m_rd_client_connection_resume(client.ctx);
}
}
next_event_at(0);
k_mutex_unlock(&client.mutex);
return 0;
}
int lwm2m_rd_client_server_disabled(uint16_t inst_id)
{
if (client.ctx->srv_obj_inst != inst_id) {
return -EPERM;
}
k_mutex_lock(&client.mutex, K_FOREVER);
client.server_disabled = true;
if (sm_is_registered()) {
LOG_INF("Server disabled, deregister");
set_sm_state_delayed(ENGINE_DEREGISTER, DELAY_BEFORE_CLOSING);
} else {
LOG_INF("Server disabled");
set_sm_state(ENGINE_DEREGISTERED);
}
k_mutex_unlock(&client.mutex);
return 0;
}
void lwm2m_rd_client_update(void)
{
engine_trigger_update(false);
}
struct lwm2m_ctx *lwm2m_rd_client_ctx(void)
{
return client.ctx;
}
int lwm2m_rd_client_connection_resume(struct lwm2m_ctx *client_ctx)
{
if (client.ctx != client_ctx) {
return -EPERM;
}
if (client.engine_state == ENGINE_REGISTRATION_DONE_RX_OFF) {
/*
* Switch state to triggering a proper registration message
* If the socket stays open (Connection ID or no-sec), or we have TLS session cache,
* we can trigger the update, otherwise fall back to full registration.
*/
if ((IS_ENABLED(CONFIG_LWM2M_RD_CLIENT_SUSPEND_SOCKET_AT_IDLE) &&
IS_ENABLED(CONFIG_LWM2M_TLS_SESSION_CACHING)) ||
(IS_ENABLED(CONFIG_LWM2M_RD_CLIENT_STOP_POLLING_AT_IDLE) ||
IS_ENABLED(CONFIG_LWM2M_RD_CLIENT_LISTEN_AT_IDLE)) ||
!IS_ENABLED(CONFIG_LWM2M_DTLS_SUPPORT)) {
client.engine_state = ENGINE_REGISTRATION_DONE;
if (IS_ENABLED(CONFIG_LWM2M_QUEUE_MODE_NO_MSG_BUFFERING)) {
/* Force online for a short period */
engine_update_tx_time();
} else {
client.trigger_update = true;
}
} else {
client.engine_state = ENGINE_DO_REGISTRATION;
}
next_event_at(0);
}
return 0;
}
int lwm2m_rd_client_timeout(struct lwm2m_ctx *client_ctx)
{
if (client.ctx != client_ctx) {
return -EPERM;
}
if (!sm_is_registered()) {
return 0;
}
k_mutex_lock(&client.mutex, K_FOREVER);
LOG_WRN("Confirmable Timeout -> Re-connect and register");
set_sm_state(ENGINE_DO_REGISTRATION);
next_event_at(0);
k_mutex_unlock(&client.mutex);
return 0;
}
bool lwm2m_rd_client_is_registred(struct lwm2m_ctx *client_ctx)
{
if (client.ctx != client_ctx || !sm_is_registered()) {
return false;
}
return true;
}
bool lwm2m_rd_client_is_suspended(struct lwm2m_ctx *client_ctx)
{
if (client.ctx != client_ctx || !sm_is_suspended()) {
return false;
}
return true;
}
int lwm2m_rd_client_init(void)
{
client.ctx = NULL;
client.rd_message.ctx = NULL;
client.engine_state = ENGINE_IDLE;
k_mutex_init(&client.mutex);
return 0;
}
static int sys_lwm2m_rd_client_init(void)
{
return lwm2m_rd_client_init();
}
LWM2M_ENGINE_INIT(sys_lwm2m_rd_client_init);