net: lwm2m: migrate LwM2M library to BSD-sockets API
This commit removes the net_app layer from the LwM2M library and replaces it with BSD-sockets APIs. Signed-off-by: Michael Scott <mike@foundries.io>
This commit is contained in:
parent
d7f20f63d8
commit
d1cb39e7ce
5 changed files with 395 additions and 399 deletions
|
@ -8,7 +8,7 @@
|
|||
#ifndef ZEPHYR_INCLUDE_NET_LWM2M_H_
|
||||
#define ZEPHYR_INCLUDE_NET_LWM2M_H_
|
||||
|
||||
#include <net/net_app.h>
|
||||
#include <kernel.h>
|
||||
#include <net/coap_sock.h>
|
||||
|
||||
/* LWM2M Objects defined by OMA */
|
||||
|
@ -33,40 +33,19 @@
|
|||
* @details Context structure for the LwM2M high-level API.
|
||||
*
|
||||
* @param remote_addr Stored remote IP address of the LwM2M client
|
||||
* @param net_app_ctx Related network application context.
|
||||
* @param net_init_timeout Used if the net_app API needs to do some time
|
||||
* consuming operation, like resolving DNS address.
|
||||
* @param net_timeout How long to wait for the network connection before
|
||||
* giving up.
|
||||
*/
|
||||
struct lwm2m_ctx {
|
||||
/** destination address storage */
|
||||
struct sockaddr remote_addr;
|
||||
|
||||
/** Net app context structure */
|
||||
struct net_app_ctx net_app_ctx;
|
||||
s32_t net_init_timeout;
|
||||
s32_t net_timeout;
|
||||
|
||||
/** Private CoAP and networking structures */
|
||||
struct coap_pending pendings[CONFIG_LWM2M_ENGINE_MAX_PENDING];
|
||||
struct coap_reply replies[CONFIG_LWM2M_ENGINE_MAX_REPLIES];
|
||||
struct k_delayed_work retransmit_work;
|
||||
|
||||
#if defined(CONFIG_NET_APP_DTLS)
|
||||
/** Pre-Shared Key Information*/
|
||||
unsigned char *client_psk;
|
||||
size_t client_psk_len;
|
||||
char *client_psk_id;
|
||||
size_t client_psk_id_len;
|
||||
|
||||
/** DTLS support structures */
|
||||
char *cert_host;
|
||||
u8_t *dtls_result_buf;
|
||||
size_t dtls_result_buf_len;
|
||||
struct k_mem_pool *dtls_pool;
|
||||
k_thread_stack_t *dtls_stack;
|
||||
size_t dtls_stack_len;
|
||||
#if defined(CONFIG_LWM2M_DTLS_SUPPORT)
|
||||
/** DTLS settings */
|
||||
int tls_tag;
|
||||
#endif
|
||||
bool use_dtls;
|
||||
|
||||
|
@ -76,6 +55,9 @@ struct lwm2m_ctx {
|
|||
|
||||
/** Packet Flow Settings */
|
||||
bool handle_separate_response;
|
||||
|
||||
/** Socket File Descriptor */
|
||||
int sock_fd;
|
||||
};
|
||||
|
||||
typedef void *(*lwm2m_engine_get_data_cb_t)(u16_t obj_inst_id,
|
||||
|
|
|
@ -7,8 +7,9 @@
|
|||
menuconfig LWM2M
|
||||
bool "OMA LWM2M protocol stack"
|
||||
select COAP
|
||||
select NET_APP_CLIENT
|
||||
select HTTP_PARSER_URL
|
||||
select NET_SOCKETS
|
||||
select NET_SOCKETS_POSIX_NAMES
|
||||
help
|
||||
This option adds logic for managing OMA LWM2M data
|
||||
|
||||
|
@ -23,7 +24,9 @@ config LWM2M_DTLS_SUPPORT
|
|||
bool "Enable DTLS support in the LwM2M client"
|
||||
select MBEDTLS
|
||||
select MBEDTLS_ENABLE_HEAP
|
||||
select NET_APP_DTLS
|
||||
select TLS_CREDENTIALS
|
||||
select NET_SOCKETS_SOCKOPT_TLS
|
||||
select NET_SOCKETS_ENABLE_DTLS
|
||||
|
||||
config LWM2M_ENGINE_STACK_SIZE
|
||||
int "LWM2M engine stack size"
|
||||
|
|
|
@ -33,10 +33,11 @@ LOG_MODULE_REGISTER(LOG_MODULE_NAME);
|
|||
#include <init.h>
|
||||
#include <misc/printk.h>
|
||||
#include <net/net_ip.h>
|
||||
#include <net/udp.h>
|
||||
|
||||
#include <net/net_pkt.h>
|
||||
#include <net/net_app.h>
|
||||
#include <net/http_parser_url.h>
|
||||
#include <net/socket.h>
|
||||
#if defined(CONFIG_LWM2M_DTLS_SUPPORT)
|
||||
#include <net/tls_credentials.h>
|
||||
#endif
|
||||
|
||||
#include "lwm2m_object.h"
|
||||
#include "lwm2m_engine.h"
|
||||
|
@ -73,10 +74,6 @@ LOG_MODULE_REGISTER(LOG_MODULE_NAME);
|
|||
#define REG_PREFACE ""
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_NET_APP_DTLS)
|
||||
#define INSTANCE_INFO "Zephyr DTLS LwM2M-client"
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_COAP_EXTENDED_OPTIONS_LEN)
|
||||
#define COAP_OPTION_BUF_LEN (CONFIG_COAP_EXTENDED_OPTIONS_LEN_VALUE + 1)
|
||||
#else
|
||||
|
@ -131,6 +128,16 @@ static sys_slist_t engine_obj_inst_list;
|
|||
static sys_slist_t engine_observer_list;
|
||||
static sys_slist_t engine_service_list;
|
||||
|
||||
static K_THREAD_STACK_DEFINE(engine_thread_stack,
|
||||
CONFIG_LWM2M_ENGINE_STACK_SIZE);
|
||||
static struct k_thread engine_thread_data;
|
||||
|
||||
#define MAX_POLL_FD CONFIG_NET_SOCKETS_POLL_MAX
|
||||
|
||||
static struct lwm2m_ctx *sock_ctx[MAX_POLL_FD];
|
||||
static struct pollfd sock_fds[MAX_POLL_FD];
|
||||
static int sock_nfds;
|
||||
|
||||
#define NUM_BLOCK1_CONTEXT CONFIG_LWM2M_NUM_BLOCK1_CONTEXT
|
||||
|
||||
/* TODO: figure out what's correct value */
|
||||
|
@ -1014,7 +1021,6 @@ cleanup:
|
|||
|
||||
int lwm2m_send_message(struct lwm2m_message *msg)
|
||||
{
|
||||
struct net_pkt *pkt;
|
||||
int ret;
|
||||
|
||||
if (!msg || !msg->ctx) {
|
||||
|
@ -1028,30 +1034,12 @@ int lwm2m_send_message(struct lwm2m_message *msg)
|
|||
|
||||
msg->send_attempts++;
|
||||
|
||||
pkt = net_app_get_net_pkt(&msg->ctx->net_app_ctx, AF_UNSPEC,
|
||||
BUF_ALLOC_TIMEOUT);
|
||||
if (!pkt) {
|
||||
LOG_ERR("Unable to get TX packet, not enough memory.");
|
||||
ret = -ENOMEM;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!net_pkt_append(pkt, msg->cpkt.offset, msg->cpkt.data,
|
||||
BUF_ALLOC_TIMEOUT)) {
|
||||
LOG_ERR("Unable to add packet data, not enough memory.");
|
||||
ret = -ENOMEM;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ret = net_app_send_pkt(&msg->ctx->net_app_ctx, pkt,
|
||||
&msg->ctx->remote_addr,
|
||||
NET_SOCKADDR_MAX_SIZE, K_NO_WAIT, NULL);
|
||||
ret = send(msg->ctx->sock_fd, msg->cpkt.data, msg->cpkt.offset, 0);
|
||||
if (ret < 0) {
|
||||
if (msg->type == COAP_TYPE_CON) {
|
||||
coap_pending_clear(msg->pending);
|
||||
}
|
||||
|
||||
net_pkt_unref(pkt);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -1067,13 +1055,6 @@ int lwm2m_send_message(struct lwm2m_message *msg)
|
|||
lwm2m_reset_message(msg, true);
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
cleanup:
|
||||
if (pkt) {
|
||||
net_pkt_unref(pkt);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -3458,60 +3439,20 @@ error:
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void lwm2m_udp_receive(struct lwm2m_ctx *client_ctx, struct net_pkt *pkt,
|
||||
static void lwm2m_udp_receive(struct lwm2m_ctx *client_ctx,
|
||||
u8_t *buf, u16_t buf_len,
|
||||
struct sockaddr *from_addr,
|
||||
udp_request_handler_cb_t udp_request_handler)
|
||||
{
|
||||
struct lwm2m_message *msg = NULL;
|
||||
struct net_udp_hdr hdr, *udp_hdr;
|
||||
struct coap_pending *pending;
|
||||
struct coap_reply *reply;
|
||||
struct coap_packet response;
|
||||
struct sockaddr from_addr;
|
||||
static u8_t in_buf[NET_IPV6_MTU];
|
||||
size_t recv_len;
|
||||
int r;
|
||||
u8_t token[8];
|
||||
u8_t tkl;
|
||||
|
||||
udp_hdr = net_udp_get_hdr(pkt, &hdr);
|
||||
if (!udp_hdr) {
|
||||
LOG_ERR("Invalid UDP data");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Save the from address */
|
||||
#if defined(CONFIG_NET_IPV6)
|
||||
if (net_pkt_family(pkt) == AF_INET6) {
|
||||
net_ipaddr_copy(&net_sin6(&from_addr)->sin6_addr,
|
||||
&NET_IPV6_HDR(pkt)->src);
|
||||
net_sin6(&from_addr)->sin6_port = udp_hdr->src_port;
|
||||
net_sin6(&from_addr)->sin6_family = AF_INET6;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_NET_IPV4)
|
||||
if (net_pkt_family(pkt) == AF_INET) {
|
||||
net_ipaddr_copy(&net_sin(&from_addr)->sin_addr,
|
||||
&NET_IPV4_HDR(pkt)->src);
|
||||
net_sin(&from_addr)->sin_port = udp_hdr->src_port;
|
||||
net_sin(&from_addr)->sin_family = AF_INET;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* copy data from pkt */
|
||||
recv_len = net_pkt_appdatalen(pkt);
|
||||
if (recv_len > sizeof(in_buf)) {
|
||||
recv_len = sizeof(in_buf);
|
||||
}
|
||||
|
||||
(void)net_frag_linearize(in_buf, recv_len, pkt,
|
||||
net_pkt_appdata(pkt) - pkt->frags->data,
|
||||
recv_len);
|
||||
|
||||
/* now that we have data, free pkt */
|
||||
net_pkt_unref(pkt);
|
||||
|
||||
r = coap_packet_parse(&response, in_buf, recv_len, NULL, 0);
|
||||
r = coap_packet_parse(&response, buf, buf_len, NULL, 0);
|
||||
if (r < 0) {
|
||||
LOG_ERR("Invalid data received (err:%d)", r);
|
||||
return;
|
||||
|
@ -3531,8 +3472,8 @@ static void lwm2m_udp_receive(struct lwm2m_ctx *client_ctx, struct net_pkt *pkt,
|
|||
}
|
||||
|
||||
LOG_DBG("checking for reply from [%s]",
|
||||
lwm2m_sprint_ip_addr(&from_addr));
|
||||
reply = coap_response_received(&response, &from_addr,
|
||||
lwm2m_sprint_ip_addr(from_addr));
|
||||
reply = coap_response_received(&response, from_addr,
|
||||
client_ctx->replies,
|
||||
CONFIG_LWM2M_ENGINE_MAX_REPLIES);
|
||||
if (reply) {
|
||||
|
@ -3611,19 +3552,8 @@ static void lwm2m_udp_receive(struct lwm2m_ctx *client_ctx, struct net_pkt *pkt,
|
|||
}
|
||||
}
|
||||
|
||||
static void udp_receive(struct net_app_ctx *app_ctx, struct net_pkt *pkt,
|
||||
int status, void *user_data)
|
||||
{
|
||||
struct lwm2m_ctx *client_ctx = CONTAINER_OF(app_ctx,
|
||||
struct lwm2m_ctx,
|
||||
net_app_ctx);
|
||||
|
||||
lwm2m_udp_receive(client_ctx, pkt, handle_request);
|
||||
}
|
||||
|
||||
static void retransmit_request(struct k_work *work)
|
||||
{
|
||||
struct net_pkt *pkt;
|
||||
struct lwm2m_ctx *client_ctx;
|
||||
struct lwm2m_message *msg;
|
||||
struct coap_pending *pending;
|
||||
|
@ -3658,43 +3588,13 @@ static void retransmit_request(struct k_work *work)
|
|||
|
||||
LOG_DBG("Resending message: %p", msg);
|
||||
msg->send_attempts++;
|
||||
|
||||
pkt = net_app_get_net_pkt(&msg->ctx->net_app_ctx, AF_UNSPEC,
|
||||
BUF_ALLOC_TIMEOUT);
|
||||
if (!pkt) {
|
||||
LOG_ERR("Unable to get TX packet, not enough memory.");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!net_pkt_append(pkt, msg->cpkt.offset, msg->cpkt.data,
|
||||
BUF_ALLOC_TIMEOUT)) {
|
||||
LOG_ERR("Unable to add packet data, not enough memory.");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/*
|
||||
* Don't use lwm2m_send_message() because it calls
|
||||
* coap_pending_cycle() / coap_pending_cycle() in a different order
|
||||
* and under different circumstances. It also does it's own ref /
|
||||
* unref of the net_pkt. Keep it simple and call net_app_send_pkt()
|
||||
* directly here.
|
||||
*/
|
||||
r = net_app_send_pkt(&msg->ctx->net_app_ctx, pkt,
|
||||
&msg->ctx->remote_addr,
|
||||
NET_SOCKADDR_MAX_SIZE, K_NO_WAIT, NULL);
|
||||
r = send(msg->ctx->sock_fd, msg->cpkt.data, msg->cpkt.offset, 0);
|
||||
if (r < 0) {
|
||||
LOG_ERR("Error sending lwm2m message: %d", r);
|
||||
/* don't error here, retry until timeout */
|
||||
net_pkt_unref(pkt);
|
||||
}
|
||||
|
||||
k_delayed_work_submit(&client_ctx->retransmit_work, pending->timeout);
|
||||
return;
|
||||
|
||||
cleanup:
|
||||
if (pkt) {
|
||||
net_pkt_unref(pkt);
|
||||
}
|
||||
}
|
||||
|
||||
static int notify_message_reply_cb(const struct coap_packet *response,
|
||||
|
@ -3926,10 +3826,16 @@ static void lwm2m_engine_service(struct k_work *work)
|
|||
|
||||
int lwm2m_engine_context_close(struct lwm2m_ctx *client_ctx)
|
||||
{
|
||||
int sock_fd = client_ctx->sock_fd;
|
||||
|
||||
k_delayed_work_cancel(&client_ctx->retransmit_work);
|
||||
net_app_close(&client_ctx->net_app_ctx);
|
||||
net_app_release(&client_ctx->net_app_ctx);
|
||||
return 0;
|
||||
lwm2m_socket_del(client_ctx);
|
||||
client_ctx->sock_fd = -1;
|
||||
if (sock_fd >= 0) {
|
||||
return close(sock_fd);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void lwm2m_engine_context_init(struct lwm2m_ctx *client_ctx)
|
||||
|
@ -3937,162 +3843,315 @@ void lwm2m_engine_context_init(struct lwm2m_ctx *client_ctx)
|
|||
k_delayed_work_init(&client_ctx->retransmit_work, retransmit_request);
|
||||
}
|
||||
|
||||
#if defined(CONFIG_NET_APP_DTLS)
|
||||
static int setup_cert(struct net_app_ctx *app_ctx, void *cert)
|
||||
/* LwM2M Socket Integration */
|
||||
|
||||
int lwm2m_socket_add(struct lwm2m_ctx *ctx)
|
||||
{
|
||||
#if defined(MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED)
|
||||
struct lwm2m_ctx *client_ctx = CONTAINER_OF(app_ctx,
|
||||
struct lwm2m_ctx,
|
||||
net_app_ctx);
|
||||
return mbedtls_ssl_conf_psk(
|
||||
&app_ctx->tls.mbedtls.conf,
|
||||
(const unsigned char *)client_ctx->client_psk,
|
||||
client_ctx->client_psk_len,
|
||||
(const unsigned char *)client_ctx->client_psk_id,
|
||||
client_ctx->client_psk_id_len);
|
||||
#else
|
||||
int i;
|
||||
|
||||
if (sock_nfds < MAX_POLL_FD) {
|
||||
i = sock_nfds++;
|
||||
} else {
|
||||
for (i = 0; i < MAX_POLL_FD; i++) {
|
||||
if (sock_ctx[i] == NULL) {
|
||||
goto found;
|
||||
}
|
||||
}
|
||||
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
found:
|
||||
sock_ctx[i] = ctx;
|
||||
sock_fds[i].fd = ctx->sock_fd;
|
||||
sock_fds[i].events = POLLIN;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
#endif /* CONFIG_NET_APP_DTLS */
|
||||
|
||||
int lwm2m_net_app_start(struct lwm2m_ctx *client_ctx,
|
||||
char *peer_str, u16_t peer_port)
|
||||
void lwm2m_socket_del(struct lwm2m_ctx *ctx)
|
||||
{
|
||||
struct sockaddr client_addr;
|
||||
int ret;
|
||||
|
||||
/* setup the local client port */
|
||||
(void)memset(&client_addr, 0, sizeof(client_addr));
|
||||
#if defined(CONFIG_NET_IPV6)
|
||||
client_addr.sa_family = AF_INET6;
|
||||
net_sin6(&client_addr)->sin6_port = htons(CONFIG_LWM2M_LOCAL_PORT);
|
||||
#elif defined(CONFIG_NET_IPV4)
|
||||
client_addr.sa_family = AF_INET;
|
||||
net_sin(&client_addr)->sin_port = htons(CONFIG_LWM2M_LOCAL_PORT);
|
||||
#endif
|
||||
|
||||
ret = net_app_init_udp_client(&client_ctx->net_app_ctx,
|
||||
&client_addr, NULL,
|
||||
peer_str,
|
||||
peer_port,
|
||||
client_ctx->net_init_timeout,
|
||||
client_ctx);
|
||||
if (ret) {
|
||||
LOG_ERR("net_app_init_udp_client err:%d", ret);
|
||||
goto error_start;
|
||||
}
|
||||
|
||||
/* set net_app callbacks */
|
||||
ret = net_app_set_cb(&client_ctx->net_app_ctx,
|
||||
NULL, udp_receive, NULL, NULL);
|
||||
if (ret) {
|
||||
LOG_ERR("Could not set receive callback (err:%d)", ret);
|
||||
goto error_start;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_NET_APP_DTLS)
|
||||
if (client_ctx->use_dtls) {
|
||||
ret = net_app_client_tls(&client_ctx->net_app_ctx,
|
||||
client_ctx->dtls_result_buf,
|
||||
client_ctx->dtls_result_buf_len,
|
||||
INSTANCE_INFO,
|
||||
strlen(INSTANCE_INFO),
|
||||
setup_cert,
|
||||
client_ctx->cert_host,
|
||||
NULL,
|
||||
client_ctx->dtls_pool,
|
||||
client_ctx->dtls_stack,
|
||||
client_ctx->dtls_stack_len);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Cannot init DTLS (%d)", ret);
|
||||
goto error_start;
|
||||
for (int i = 0; i < sock_nfds; i++) {
|
||||
if (sock_ctx[i] == ctx) {
|
||||
sock_ctx[i] = NULL;
|
||||
sock_fds[i].fd = -1;
|
||||
sock_nfds--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
ret = net_app_connect(&client_ctx->net_app_ctx,
|
||||
client_ctx->net_timeout);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Cannot connect UDP (%d)", ret);
|
||||
goto error_start;
|
||||
/* LwM2M main work loop */
|
||||
|
||||
static void socket_receive_loop(void)
|
||||
{
|
||||
static u8_t in_buf[NET_IPV6_MTU];
|
||||
static struct sockaddr from_addr;
|
||||
socklen_t from_addr_len;
|
||||
ssize_t len;
|
||||
int i;
|
||||
|
||||
from_addr_len = sizeof(from_addr);
|
||||
while (1) {
|
||||
/* wait for sockets */
|
||||
if (sock_nfds < 1) {
|
||||
k_sleep(ENGINE_UPDATE_INTERVAL);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* FIXME: Currently we timeout and restart poll in case fds
|
||||
* were modified.
|
||||
*/
|
||||
if (poll(sock_fds, sock_nfds, ENGINE_UPDATE_INTERVAL) < 0) {
|
||||
LOG_ERR("Error in poll:%d", errno);
|
||||
errno = 0;
|
||||
k_sleep(ENGINE_UPDATE_INTERVAL);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (i = 0; i < sock_nfds; i++) {
|
||||
if (sock_fds[i].revents & POLLERR) {
|
||||
LOG_ERR("Error in poll.. waiting a moment.");
|
||||
k_sleep(ENGINE_UPDATE_INTERVAL);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(sock_fds[i].revents & POLLIN) ||
|
||||
sock_ctx[i] == NULL) {
|
||||
sock_fds[i].revents = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
sock_fds[i].revents = 0;
|
||||
len = recvfrom(sock_ctx[i]->sock_fd, in_buf,
|
||||
sizeof(in_buf) - 1, 0,
|
||||
&from_addr, &from_addr_len);
|
||||
if (errno) {
|
||||
LOG_ERR("Sock RECV error: %d", errno);
|
||||
/* TODO: handle error? */
|
||||
continue;
|
||||
}
|
||||
|
||||
if (len < 0) {
|
||||
LOG_ERR("Error reading response: %d", errno);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (len == 0) {
|
||||
LOG_ERR("Zero length recv");
|
||||
continue;
|
||||
}
|
||||
|
||||
in_buf[len] = 0;
|
||||
|
||||
lwm2m_udp_receive(sock_ctx[i], in_buf, len, &from_addr,
|
||||
handle_request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* save remote addr */
|
||||
#if defined(CONFIG_LWM2M_DTLS_SUPPORT)
|
||||
if (client_ctx->use_dtls && client_ctx->net_app_ctx.dtls.ctx) {
|
||||
memcpy(&client_ctx->remote_addr,
|
||||
&client_ctx->net_app_ctx.dtls.ctx->remote,
|
||||
sizeof(client_ctx->remote_addr));
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
memcpy(&client_ctx->remote_addr,
|
||||
&client_ctx->net_app_ctx.default_ctx->remote,
|
||||
sizeof(client_ctx->remote_addr));
|
||||
}
|
||||
return 0;
|
||||
static int load_tls_credential(struct lwm2m_ctx *client_ctx, u16_t res_id,
|
||||
enum tls_credential_type type)
|
||||
{
|
||||
int ret = 0;
|
||||
void *cred = NULL;
|
||||
u16_t cred_len;
|
||||
u8_t cred_flags;
|
||||
char pathstr[MAX_RESOURCE_LEN];
|
||||
|
||||
error_start:
|
||||
lwm2m_engine_context_close(client_ctx);
|
||||
/* ignore error value */
|
||||
tls_credential_delete(client_ctx->tls_tag, type);
|
||||
|
||||
snprintk(pathstr, sizeof(pathstr), "0/%d/%u", client_ctx->sec_obj_inst,
|
||||
res_id);
|
||||
|
||||
ret = lwm2m_engine_get_res_data(pathstr, &cred, &cred_len, &cred_flags);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Unable to get resource data for '%s'", pathstr);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Set correct PSK_ID length */
|
||||
if (type == TLS_CREDENTIAL_PSK_ID) {
|
||||
cred_len = strlen(cred);
|
||||
}
|
||||
|
||||
ret = tls_credential_add(client_ctx->tls_tag, type, cred, cred_len);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Unable to get resource data for '%s'", pathstr);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif /* CONFIG_LWM2M_DTLS_SUPPORT */
|
||||
|
||||
int lwm2m_socket_start(struct lwm2m_ctx *client_ctx)
|
||||
{
|
||||
#if defined(CONFIG_LWM2M_DTLS_SUPPORT)
|
||||
int ret;
|
||||
|
||||
ret = load_tls_credential(client_ctx, 3, TLS_CREDENTIAL_PSK_ID);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = load_tls_credential(client_ctx, 5, TLS_CREDENTIAL_PSK);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (client_ctx->use_dtls) {
|
||||
client_ctx->sock_fd = socket(client_ctx->remote_addr.sa_family,
|
||||
SOCK_DGRAM, IPPROTO_DTLS_1_2);
|
||||
} else
|
||||
#endif /* CONFIG_LWM2M_DTLS_SUPPORT */
|
||||
{
|
||||
client_ctx->sock_fd = socket(client_ctx->remote_addr.sa_family,
|
||||
SOCK_DGRAM, IPPROTO_UDP);
|
||||
}
|
||||
|
||||
if (client_ctx->sock_fd < 0) {
|
||||
LOG_ERR("Failed to create socket: %d", errno);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_LWM2M_DTLS_SUPPORT)
|
||||
if (client_ctx->use_dtls) {
|
||||
sec_tag_t tls_tag_list[] = {
|
||||
client_ctx->tls_tag,
|
||||
};
|
||||
|
||||
ret = setsockopt(client_ctx->sock_fd, SOL_TLS, TLS_SEC_TAG_LIST,
|
||||
tls_tag_list, sizeof(tls_tag_list));
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to set TLS_SEC_TAG_LIST option: %d",
|
||||
errno);
|
||||
lwm2m_engine_context_close(client_ctx);
|
||||
return -errno;
|
||||
}
|
||||
}
|
||||
#endif /* CONFIG_LWM2M_DTLS_SUPPORT */
|
||||
|
||||
if (connect(client_ctx->sock_fd, &client_ctx->remote_addr,
|
||||
NET_SOCKADDR_MAX_SIZE) < 0) {
|
||||
LOG_ERR("Cannot connect UDP (-%d)", errno);
|
||||
lwm2m_engine_context_close(client_ctx);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
lwm2m_socket_add(client_ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lwm2m_parse_peerinfo(char *url, struct sockaddr *addr, bool *use_dtls)
|
||||
{
|
||||
struct http_parser_url parser;
|
||||
int ret;
|
||||
u16_t off, len;
|
||||
u8_t tmp;
|
||||
|
||||
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 */
|
||||
*use_dtls = false;
|
||||
if (len == 5 && strncmp(url + off, "coaps", len) == 0) {
|
||||
#if defined(CONFIG_LWM2M_DTLS_SUPPORT)
|
||||
*use_dtls = true;
|
||||
#else
|
||||
return -EPROTONOSUPPORT;
|
||||
#endif /* CONFIG_LWM2M_DTLS_SUPPORT */
|
||||
}
|
||||
|
||||
if (!(parser.field_set & (1 << UF_PORT))) {
|
||||
/* Set to default port of CoAP */
|
||||
parser.port = CONFIG_LWM2M_PEER_PORT;
|
||||
}
|
||||
|
||||
off = parser.field_data[UF_HOST].off;
|
||||
len = parser.field_data[UF_HOST].len;
|
||||
|
||||
/* truncate host portion */
|
||||
tmp = url[off + len];
|
||||
url[off + len] = '\0';
|
||||
|
||||
/* initialize addr */
|
||||
(void)memset(addr, 0, sizeof(*addr));
|
||||
|
||||
/* try and set IP address directly */
|
||||
ret = -EINVAL;
|
||||
|
||||
#if defined(CONFIG_NET_IPV6)
|
||||
addr->sa_family = AF_INET6;
|
||||
ret = net_addr_pton(AF_INET6, url + off,
|
||||
&((struct sockaddr_in6 *)addr)->sin6_addr);
|
||||
#endif /* CONFIG_NET_IPV6 */
|
||||
|
||||
#if defined(CONFIG_NET_IPV4)
|
||||
if (ret < 0) {
|
||||
addr->sa_family = AF_INET;
|
||||
ret = net_addr_pton(AF_INET, url + off,
|
||||
&((struct sockaddr_in *)addr)->sin_addr);
|
||||
}
|
||||
#endif /* CONFIG_NET_IPV4 */
|
||||
|
||||
if (ret < 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* set port */
|
||||
if (addr->sa_family == AF_INET6) {
|
||||
net_sin6(addr)->sin6_port = htons(parser.port);
|
||||
} else if (addr->sa_family == AF_INET) {
|
||||
net_sin(addr)->sin_port = htons(parser.port);
|
||||
} else {
|
||||
ret = -EPROTONOSUPPORT;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
/* restore host separator */
|
||||
url[off + len] = tmp;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int lwm2m_engine_start(struct lwm2m_ctx *client_ctx)
|
||||
{
|
||||
char pathstr[MAX_RESOURCE_LEN];
|
||||
char *data_ptr, *peer_str;
|
||||
u16_t peer_strlen;
|
||||
u8_t peer_data_flags;
|
||||
char *url;
|
||||
u16_t url_len;
|
||||
u8_t url_data_flags;
|
||||
int ret = 0U;
|
||||
|
||||
/* get the server URL */
|
||||
snprintk(pathstr, sizeof(pathstr), "0/%d/0", client_ctx->sec_obj_inst);
|
||||
ret = lwm2m_engine_get_res_data(pathstr, (void **)&data_ptr,
|
||||
&peer_strlen, &peer_data_flags);
|
||||
ret = lwm2m_engine_get_res_data(pathstr, (void **)&url, &url_len,
|
||||
&url_data_flags);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* TODO: use http parser for URL to get protocol and server */
|
||||
|
||||
/* walk forward till colon shifting to lower case */
|
||||
peer_str = data_ptr;
|
||||
while (*peer_str != '\0' && *peer_str != ':') {
|
||||
*peer_str = tolower(*peer_str);
|
||||
peer_str += 1;
|
||||
url[url_len] = '\0';
|
||||
ret = lwm2m_parse_peerinfo(url, &client_ctx->remote_addr,
|
||||
&client_ctx->use_dtls);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* check to make sure there was a colon */
|
||||
if (*peer_str != ':') {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (strncmp(data_ptr, "coap:", 5) != 0 &&
|
||||
strncmp(data_ptr, "coaps:", 6) != 0) {
|
||||
return -EPROTONOSUPPORT;
|
||||
}
|
||||
|
||||
client_ctx->use_dtls = false;
|
||||
if (strncmp(data_ptr, "coaps:", 6) == 0) {
|
||||
#if defined(CONFIG_LWM2M_DTLS_SUPPORT)
|
||||
client_ctx->use_dtls = true;
|
||||
#else
|
||||
return -EPROTONOSUPPORT;
|
||||
#endif /* CONFIG_LWM2M_DTLS_SUPPORT */
|
||||
}
|
||||
|
||||
/* skip the colons and slashes */
|
||||
while (*peer_str == ':' || *peer_str == '/') {
|
||||
peer_str += 1;
|
||||
}
|
||||
|
||||
LOG_DBG("URL: %s", data_ptr);
|
||||
|
||||
lwm2m_engine_context_init(client_ctx);
|
||||
|
||||
return lwm2m_net_app_start(client_ctx, peer_str,
|
||||
CONFIG_LWM2M_PEER_PORT);
|
||||
return lwm2m_socket_start(client_ctx);
|
||||
}
|
||||
|
||||
static int lwm2m_engine_init(struct device *dev)
|
||||
|
@ -4100,6 +4159,18 @@ static int lwm2m_engine_init(struct device *dev)
|
|||
(void)memset(block1_contexts, 0,
|
||||
sizeof(struct block_context) * NUM_BLOCK1_CONTEXT);
|
||||
|
||||
/* start sock receive thread */
|
||||
k_thread_create(&engine_thread_data,
|
||||
&engine_thread_stack[0],
|
||||
K_THREAD_STACK_SIZEOF(engine_thread_stack),
|
||||
(k_thread_entry_t) socket_receive_loop,
|
||||
NULL, NULL, NULL,
|
||||
/* Lowest priority cooperative thread */
|
||||
K_PRIO_COOP(CONFIG_NUM_COOP_PRIORITIES - 1),
|
||||
0, K_NO_WAIT);
|
||||
k_thread_name_set(&engine_thread_data, "lwm2m-sock-recv");
|
||||
LOG_DBG("LWM2M engine socket receive thread started");
|
||||
|
||||
k_delayed_work_init(&periodic_work, lwm2m_engine_service);
|
||||
k_delayed_work_submit(&periodic_work, K_MSEC(2000));
|
||||
LOG_DBG("LWM2M engine periodic work started");
|
||||
|
|
|
@ -109,8 +109,10 @@ void lwm2m_firmware_set_update_result(u8_t result);
|
|||
u8_t lwm2m_firmware_get_update_result(void);
|
||||
#endif
|
||||
|
||||
/* Network API Layer */
|
||||
int lwm2m_net_app_start(struct lwm2m_ctx *client_ctx,
|
||||
char *peer_str, u16_t peer_port);
|
||||
/* Network Layer */
|
||||
int lwm2m_socket_add(struct lwm2m_ctx *ctx);
|
||||
void lwm2m_socket_del(struct lwm2m_ctx *ctx);
|
||||
int lwm2m_socket_start(struct lwm2m_ctx *client_ctx);
|
||||
int lwm2m_parse_peerinfo(char *url, struct sockaddr *addr, bool *use_dtls);
|
||||
|
||||
#endif /* LWM2M_ENGINE_H */
|
||||
|
|
|
@ -16,7 +16,7 @@ LOG_MODULE_REGISTER(LOG_MODULE_NAME);
|
|||
#include <string.h>
|
||||
|
||||
#include <net/http_parser.h>
|
||||
#include <net/net_app.h>
|
||||
#include <net/socket.h>
|
||||
|
||||
#include "lwm2m_object.h"
|
||||
#include "lwm2m_engine.h"
|
||||
|
@ -29,7 +29,6 @@ LOG_MODULE_REGISTER(LOG_MODULE_NAME);
|
|||
|
||||
static struct k_work firmware_work;
|
||||
static char firmware_uri[URI_LEN];
|
||||
static struct http_parser_url parsed_uri;
|
||||
static struct lwm2m_ctx firmware_ctx;
|
||||
static int firmware_retry;
|
||||
static struct coap_block_context firmware_block_ctx;
|
||||
|
@ -68,14 +67,11 @@ static int transfer_request(struct coap_block_context *ctx,
|
|||
{
|
||||
struct lwm2m_message *msg;
|
||||
int ret;
|
||||
u16_t off;
|
||||
u16_t len;
|
||||
char *cursor;
|
||||
#if !defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_COAP_PROXY_SUPPORT)
|
||||
int i;
|
||||
int path_len;
|
||||
#else
|
||||
char *uri_path;
|
||||
struct http_parser_url parser;
|
||||
u16_t off, len;
|
||||
char *next_slash;
|
||||
#endif
|
||||
|
||||
msg = lwm2m_get_message(&firmware_ctx);
|
||||
|
@ -99,22 +95,11 @@ static int transfer_request(struct coap_block_context *ctx,
|
|||
}
|
||||
|
||||
#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_COAP_PROXY_SUPPORT)
|
||||
/* if path is not available, off/len will be zero */
|
||||
off = parsed_uri.field_data[UF_SCHEMA].off;
|
||||
len = parsed_uri.field_data[UF_SCHEMA].len;
|
||||
cursor = firmware_uri + off;
|
||||
|
||||
/* TODO: convert to lower case */
|
||||
if (len < 4 || len > 5) {
|
||||
ret = -EPROTONOSUPPORT;
|
||||
LOG_ERR("Unsupported schema");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (strncmp(cursor, (len == 4 ? "http" : "https"), len) == 0) {
|
||||
uri_path = COAP2HTTP_PROXY_URI_PATH;
|
||||
} else if (strncmp(cursor, (len == 4 ? "coap" : "coaps"), len) == 0) {
|
||||
uri_path = COAP2COAP_PROXY_URI_PATH;
|
||||
/* TODO: shift to lower case */
|
||||
if (strncmp(firmware_uri, "http", 4) == 0) {
|
||||
cursor = COAP2HTTP_PROXY_URI_PATH;
|
||||
} else if (strncmp(firmware_uri, "coap", 4) == 0) {
|
||||
cursor = COAP2COAP_PROXY_URI_PATH;
|
||||
} else {
|
||||
ret = -EPROTONOSUPPORT;
|
||||
LOG_ERR("Unsupported schema");
|
||||
|
@ -122,50 +107,53 @@ static int transfer_request(struct coap_block_context *ctx,
|
|||
}
|
||||
|
||||
ret = coap_packet_append_option(&msg->cpkt, COAP_OPTION_URI_PATH,
|
||||
uri_path, strlen(uri_path));
|
||||
cursor, strlen(cursor));
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Error adding URI_PATH '%s'", uri_path);
|
||||
LOG_ERR("Error adding URI_PATH '%s'", cursor);
|
||||
goto cleanup;
|
||||
}
|
||||
#else
|
||||
http_parser_url_init(&parser);
|
||||
ret = http_parser_parse_url(firmware_uri, strlen(firmware_uri), 0,
|
||||
&parser);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Invalid firmware url: %s", firmware_uri);
|
||||
ret = -ENOTSUP;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* if path is not available, off/len will be zero */
|
||||
off = parsed_uri.field_data[UF_PATH].off;
|
||||
len = parsed_uri.field_data[UF_PATH].len;
|
||||
off = parser.field_data[UF_PATH].off;
|
||||
len = parser.field_data[UF_PATH].len;
|
||||
cursor = firmware_uri + off;
|
||||
path_len = 0;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
if (firmware_uri[off + i] == '/') {
|
||||
if (path_len > 0) {
|
||||
ret = coap_packet_append_option(&msg->cpkt,
|
||||
COAP_OPTION_URI_PATH,
|
||||
cursor, path_len);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Error adding URI_PATH");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
cursor += path_len + 1;
|
||||
path_len = 0;
|
||||
} else {
|
||||
/* skip current slash */
|
||||
cursor += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i == len - 1) {
|
||||
/* flush the rest */
|
||||
/* add path portions (separated by slashes) */
|
||||
while (len > 0 && (next_slash = strchr(cursor, '/')) != NULL) {
|
||||
if (next_slash != cursor) {
|
||||
ret = coap_packet_append_option(&msg->cpkt,
|
||||
COAP_OPTION_URI_PATH,
|
||||
cursor, path_len + 1);
|
||||
cursor,
|
||||
next_slash - cursor);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Error adding URI_PATH");
|
||||
goto cleanup;
|
||||
}
|
||||
break;
|
||||
}
|
||||
path_len += 1;
|
||||
|
||||
/* skip slash */
|
||||
len -= (next_slash - cursor) + 1;
|
||||
cursor = next_slash + 1;
|
||||
}
|
||||
|
||||
if (len > 0) {
|
||||
/* flush the rest */
|
||||
ret = coap_packet_append_option(&msg->cpkt,
|
||||
COAP_OPTION_URI_PATH,
|
||||
cursor, len);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Error adding URI_PATH");
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -402,15 +390,9 @@ static void do_transmit_timeout_cb(struct lwm2m_message *msg)
|
|||
|
||||
static void firmware_transfer(struct k_work *work)
|
||||
{
|
||||
int ret, family;
|
||||
u16_t off;
|
||||
u16_t len;
|
||||
char tmp;
|
||||
int ret;
|
||||
char *server_addr;
|
||||
|
||||
/* Server Peer IP information */
|
||||
family = AF_INET;
|
||||
|
||||
#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_COAP_PROXY_SUPPORT)
|
||||
server_addr = CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_COAP_PROXY_ADDR;
|
||||
if (strlen(server_addr) >= URI_LEN) {
|
||||
|
@ -426,59 +408,21 @@ static void firmware_transfer(struct k_work *work)
|
|||
server_addr = firmware_uri;
|
||||
#endif
|
||||
|
||||
http_parser_url_init(&parsed_uri);
|
||||
ret = http_parser_parse_url(server_addr,
|
||||
strlen(server_addr),
|
||||
0,
|
||||
&parsed_uri);
|
||||
if (ret != 0) {
|
||||
LOG_ERR("Invalid firmware URI: %s", server_addr);
|
||||
ret = -ENOTSUP;
|
||||
ret = lwm2m_parse_peerinfo(server_addr, &firmware_ctx.remote_addr,
|
||||
&firmware_ctx.use_dtls);
|
||||
if (ret < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Check schema and only support coap for now */
|
||||
if (!(parsed_uri.field_set & (1 << UF_SCHEMA))) {
|
||||
LOG_ERR("No schema in package uri");
|
||||
ret = -ENOTSUP;
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* TODO: enable coaps when DTLS is ready */
|
||||
off = parsed_uri.field_data[UF_SCHEMA].off;
|
||||
len = parsed_uri.field_data[UF_SCHEMA].len;
|
||||
if (len != 4 || memcmp(server_addr + off, "coap", 4)) {
|
||||
LOG_ERR("Unsupported schema");
|
||||
ret = -EPROTONOSUPPORT;
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!(parsed_uri.field_set & (1 << UF_PORT))) {
|
||||
/* Set to default port of CoAP */
|
||||
parsed_uri.port = 5683;
|
||||
}
|
||||
|
||||
off = parsed_uri.field_data[UF_HOST].off;
|
||||
len = parsed_uri.field_data[UF_HOST].len;
|
||||
|
||||
/* truncate host portion */
|
||||
tmp = server_addr[off + len];
|
||||
server_addr[off + len] = '\0';
|
||||
|
||||
lwm2m_engine_context_init(&firmware_ctx);
|
||||
firmware_ctx.handle_separate_response = true;
|
||||
|
||||
ret = lwm2m_net_app_start(&firmware_ctx, &server_addr[off],
|
||||
parsed_uri.port);
|
||||
server_addr[off + len] = tmp;
|
||||
ret = lwm2m_socket_start(&firmware_ctx);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Could not get an UDP context (err:%d)", ret);
|
||||
ret = -ENOMSG;
|
||||
LOG_ERR("Cannot start a firmware-pull connection:%d", ret);
|
||||
goto error;
|
||||
}
|
||||
|
||||
LOG_INF("Connecting to server %s, port %d", server_addr + off,
|
||||
parsed_uri.port);
|
||||
LOG_INF("Connecting to server %s", firmware_uri);
|
||||
|
||||
/* reset block transfer context */
|
||||
coap_block_transfer_init(&firmware_block_ctx,
|
||||
|
@ -486,15 +430,11 @@ static void firmware_transfer(struct k_work *work)
|
|||
ret = transfer_request(&firmware_block_ctx, coap_next_token(), 8,
|
||||
do_firmware_transfer_reply_cb);
|
||||
if (ret < 0) {
|
||||
goto cleanup;
|
||||
goto error;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
cleanup:
|
||||
net_app_close(&firmware_ctx.net_app_ctx);
|
||||
net_app_release(&firmware_ctx.net_app_ctx);
|
||||
|
||||
error:
|
||||
set_update_result_from_error(ret);
|
||||
}
|
||||
|
@ -507,16 +447,14 @@ int lwm2m_firmware_cancel_transfer(void)
|
|||
|
||||
int lwm2m_firmware_start_transfer(char *package_uri)
|
||||
{
|
||||
/* free up old context */
|
||||
if (firmware_ctx.net_app_ctx.is_init) {
|
||||
net_app_close(&firmware_ctx.net_app_ctx);
|
||||
net_app_release(&firmware_ctx.net_app_ctx);
|
||||
/* close old socket */
|
||||
if (firmware_ctx.sock_fd > 0) {
|
||||
lwm2m_socket_del(&firmware_ctx);
|
||||
close(firmware_ctx.sock_fd);
|
||||
}
|
||||
|
||||
(void)memset(&firmware_ctx, 0, sizeof(struct lwm2m_ctx));
|
||||
firmware_retry = 0;
|
||||
firmware_ctx.net_init_timeout = NETWORK_INIT_TIMEOUT;
|
||||
firmware_ctx.net_timeout = NETWORK_CONNECT_TIMEOUT;
|
||||
k_work_init(&firmware_work, firmware_transfer);
|
||||
lwm2m_firmware_set_update_state(STATE_DOWNLOADING);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue