net: lwm2m: add firmware push support

1. Add handling block1 option in handle_request(). The basic idea is
   to declare structure block_context at compiled time and use "token"
   as a key to pick up the on-going block cotext. It should be able to
   support multiple blockwise transfer concurrently
2. Use write callback implemented in lwm2m_obj_firmware to deal w/ the
   update state transition and than call the callback registered by the
   application
3. move default_block_size to lwm2m_engine.c to share between
   lwm2m_engine and lwm2m_obj_firmware_pull

Signed-off-by: Robert Chou <robert.ch.chou@acer.com>
[michael.scott@linaro.org: rebased on LwM2M net_app changes.]
Signed-off-by: Michael Scott <michael.scott@linaro.org>
This commit is contained in:
Robert Chou 2017-07-25 16:54:25 +08:00 committed by Anas Nashif
commit b93eb8dee1
7 changed files with 292 additions and 84 deletions

View file

@ -123,10 +123,10 @@ int lwm2m_device_add_err(u8_t error_code);
#define RESULT_UNSUP_PROTO 9 #define RESULT_UNSUP_PROTO 9
#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_OBJ_SUPPORT) #if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_OBJ_SUPPORT)
#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT)
void lwm2m_firmware_set_write_cb(lwm2m_engine_set_data_cb_t cb); void lwm2m_firmware_set_write_cb(lwm2m_engine_set_data_cb_t cb);
lwm2m_engine_set_data_cb_t lwm2m_firmware_get_write_cb(void); lwm2m_engine_set_data_cb_t lwm2m_firmware_get_write_cb(void);
#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT)
void lwm2m_firmware_set_update_cb(lwm2m_engine_exec_cb_t cb); void lwm2m_firmware_set_update_cb(lwm2m_engine_exec_cb_t cb);
lwm2m_engine_exec_cb_t lwm2m_firmware_get_update_cb(void); lwm2m_engine_exec_cb_t lwm2m_firmware_get_update_cb(void);
#endif #endif

View file

@ -147,6 +147,7 @@ static int device_factory_default_cb(u16_t obj_inst_id)
return 1; return 1;
} }
#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT)
static int firmware_update_cb(u16_t obj_inst_id) static int firmware_update_cb(u16_t obj_inst_id)
{ {
SYS_LOG_DBG("UPDATE"); SYS_LOG_DBG("UPDATE");
@ -160,7 +161,9 @@ static int firmware_update_cb(u16_t obj_inst_id)
lwm2m_engine_set_u8("5/0/5", RESULT_SUCCESS); lwm2m_engine_set_u8("5/0/5", RESULT_SUCCESS);
return 1; return 1;
} }
#endif
#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_OBJ_SUPPORT)
static int firmware_block_received_cb(u16_t obj_inst_id, static int firmware_block_received_cb(u16_t obj_inst_id,
u8_t *data, u16_t data_len, u8_t *data, u16_t data_len,
bool last_block, size_t total_size) bool last_block, size_t total_size)
@ -169,6 +172,7 @@ static int firmware_block_received_cb(u16_t obj_inst_id,
data_len, last_block); data_len, last_block);
return 1; return 1;
} }
#endif
static int lwm2m_setup(void) static int lwm2m_setup(void)
{ {
@ -212,10 +216,12 @@ static int lwm2m_setup(void)
/* setup FIRMWARE object */ /* setup FIRMWARE object */
lwm2m_engine_register_post_write_callback("5/0/0", #if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_OBJ_SUPPORT)
firmware_block_received_cb);
lwm2m_firmware_set_write_cb(firmware_block_received_cb); lwm2m_firmware_set_write_cb(firmware_block_received_cb);
#endif
#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT)
lwm2m_firmware_set_update_cb(firmware_update_cb); lwm2m_firmware_set_update_cb(firmware_update_cb);
#endif
/* setup TEMP SENSOR object */ /* setup TEMP SENSOR object */

View file

@ -137,17 +137,23 @@ config LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT
block transfer and "FIRMWARE PACKAGE URI" resource. This option block transfer and "FIRMWARE PACKAGE URI" resource. This option
adds another UDP context and packet handling. adds another UDP context and packet handling.
config LWM2M_FIRMWARE_UPDATE_PULL_COAP_BLOCK_SIZE config LWM2M_COAP_BLOCK_SIZE
int "Firmware Update object pull CoAP block size" int "LWM2M CoAP block-wise transfer size"
depends on LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT
default 256 default 256
default 64 if NET_L2_BT default 64 if NET_L2_BT
default 64 if NET_L2_IEEE802154 default 64 if NET_L2_IEEE802154
range 16 1024 range 16 1024
help help
CoAP block size used by firmware pull when performing block-wise CoAP block size used by LWM2M when performing block-wise
transfers. Possible values: 16, 32, 64, 128, 256, 512 and 1024. transfers. Possible values: 16, 32, 64, 128, 256, 512 and 1024.
config LWM2M_NUM_BLOCK1_CONTEXT
int "Maximum # of LWM2M block1 contexts"
default 3
help
This value sets up the maximum number of block1 contexts for
CoAP block-wise transfer we can handle at the same time.
config LWM2M_RW_JSON_SUPPORT config LWM2M_RW_JSON_SUPPORT
bool "support for JSON writer" bool "support for JSON writer"
default y default y

View file

@ -16,8 +16,6 @@
* *
* - Use server / security object instance 0 for initial connection * - Use server / security object instance 0 for initial connection
* - Add DNS support for security uri parsing * - Add DNS support for security uri parsing
* - Block-transfer support / Large response messages
* (use Block2 to limit message size to 64 bytes for 6LOWPAN compat.)
* - BOOTSTRAP/DTLS cleanup * - BOOTSTRAP/DTLS cleanup
* - Handle WRITE_ATTRIBUTES (pmin=10&pmax=60) * - Handle WRITE_ATTRIBUTES (pmin=10&pmax=60)
* - Handle Resource ObjLink type * - Handle Resource ObjLink type
@ -102,6 +100,24 @@ static sys_slist_t engine_obj_list;
static sys_slist_t engine_obj_inst_list; static sys_slist_t engine_obj_inst_list;
static sys_slist_t engine_observer_list; static sys_slist_t engine_observer_list;
#define NUM_BLOCK1_CONTEXT CONFIG_LWM2M_NUM_BLOCK1_CONTEXT
/* TODO: figure out what's correct value */
#define TIMEOUT_BLOCKWISE_TRANSFER K_SECONDS(30)
#define GET_BLOCK_NUM(v) ((v) >> 4)
#define GET_BLOCK_SIZE(v) (((v) & 0x7))
#define GET_MORE(v) (!!((v) & 0x08))
struct block_context {
struct zoap_block_context ctx;
s64_t timestamp;
u8_t token[8];
u8_t tkl;
};
static struct block_context block1_contexts[NUM_BLOCK1_CONTEXT];
/* periodic / notify / observe handling stack */ /* periodic / notify / observe handling stack */
static K_THREAD_STACK_DEFINE(engine_thread_stack, static K_THREAD_STACK_DEFINE(engine_thread_stack,
CONFIG_LWM2M_ENGINE_STACK_SIZE); CONFIG_LWM2M_ENGINE_STACK_SIZE);
@ -160,6 +176,101 @@ static char *sprint_token(const u8_t *token, u8_t tkl)
} }
#endif #endif
/* block-wise transfer functions */
enum zoap_block_size lwm2m_default_block_size(void)
{
switch (CONFIG_LWM2M_COAP_BLOCK_SIZE) {
case 16:
return ZOAP_BLOCK_16;
case 32:
return ZOAP_BLOCK_32;
case 64:
return ZOAP_BLOCK_64;
case 128:
return ZOAP_BLOCK_128;
case 256:
return ZOAP_BLOCK_256;
case 512:
return ZOAP_BLOCK_512;
case 1024:
return ZOAP_BLOCK_1024;
}
return ZOAP_BLOCK_256;
}
static int
init_block_ctx(const u8_t *token, u8_t tkl, struct block_context **ctx)
{
int i;
s64_t timestamp;
*ctx = NULL;
timestamp = k_uptime_get();
for (i = 0; i < NUM_BLOCK1_CONTEXT; i++) {
if (block1_contexts[i].tkl == 0) {
*ctx = &block1_contexts[i];
break;
}
if (timestamp - block1_contexts[i].timestamp >
TIMEOUT_BLOCKWISE_TRANSFER) {
*ctx = &block1_contexts[i];
/* TODO: notify application for block
* transfer timeout
*/
break;
}
}
if (*ctx == NULL) {
SYS_LOG_ERR("Cannot find free block context");
return -ENOMEM;
}
(*ctx)->tkl = tkl;
memcpy((*ctx)->token, token, tkl);
zoap_block_transfer_init(&(*ctx)->ctx, lwm2m_default_block_size(), 0);
(*ctx)->timestamp = timestamp;
return 0;
}
static int
get_block_ctx(const u8_t *token, u8_t tkl, struct block_context **ctx)
{
int i;
*ctx = NULL;
for (i = 0; i < NUM_BLOCK1_CONTEXT; i++) {
if (block1_contexts[i].tkl == tkl &&
memcmp(token, block1_contexts[i].token, tkl) == 0) {
*ctx = &block1_contexts[i];
/* refresh timestmap */
(*ctx)->timestamp = k_uptime_get();
break;
}
}
if (*ctx == NULL) {
SYS_LOG_ERR("Cannot find block context");
return -ENOENT;
}
return 0;
}
static void free_block_ctx(struct block_context *ctx)
{
if (ctx == NULL) {
return;
}
ctx->tkl = 0;
}
/* observer functions */ /* observer functions */
int lwm2m_notify_observer(u16_t obj_id, u16_t obj_inst_id, u16_t res_id) int lwm2m_notify_observer(u16_t obj_id, u16_t obj_inst_id, u16_t res_id)
@ -530,6 +641,20 @@ int lwm2m_delete_obj_inst(u16_t obj_id, u16_t obj_inst_id)
/* utility functions */ /* utility functions */
static int get_option_int(const struct zoap_packet *zpkt, u8_t opt)
{
struct zoap_option option = {};
u16_t count = 1;
int r;
r = zoap_find_options(zpkt, opt, &option, count);
if (r <= 0) {
return -ENOENT;
}
return zoap_option_value_to_int(&option);
}
static void engine_clear_context(struct lwm2m_engine_context *context) static void engine_clear_context(struct lwm2m_engine_context *context)
{ {
if (context->in) { if (context->in) {
@ -1698,6 +1823,12 @@ int lwm2m_write_handler(struct lwm2m_engine_obj_inst *obj_inst,
void *data_ptr = NULL; void *data_ptr = NULL;
size_t data_len = 0; size_t data_len = 0;
size_t len = 0; size_t len = 0;
size_t total_size = 0;
int ret = 0;
u8_t tkl = 0;
const u8_t *token;
bool last_block = true;
struct block_context *block_ctx = NULL;
if (!obj_inst || !res || !obj_field || !context) { if (!obj_inst || !res || !obj_field || !context) {
return -EINVAL; return -EINVAL;
@ -1719,8 +1850,6 @@ int lwm2m_write_handler(struct lwm2m_engine_obj_inst *obj_inst,
data_ptr = res->pre_write_cb(obj_inst->obj_inst_id, &data_len); data_ptr = res->pre_write_cb(obj_inst->obj_inst_id, &data_len);
} }
/* TODO: check for block transfer fields here */
if (data_ptr && data_len > 0) { if (data_ptr && data_len > 0) {
switch (obj_field->data_type) { switch (obj_field->data_type) {
@ -1808,14 +1937,30 @@ int lwm2m_write_handler(struct lwm2m_engine_obj_inst *obj_inst,
} }
if (res->post_write_cb) { if (res->post_write_cb) {
/* Get block1 option for checking MORE block flag */
ret = get_option_int(in->in_zpkt, ZOAP_OPTION_BLOCK1);
if (ret >= 0) {
last_block = !GET_MORE(ret);
/* Get block_ctx for total_size (might be zero) */
token = zoap_header_get_token(in->in_zpkt, &tkl);
if (token != NULL &&
!get_block_ctx(token, tkl, &block_ctx)) {
total_size = block_ctx->ctx.total_size;
}
}
/* ignore return value here */ /* ignore return value here */
res->post_write_cb(obj_inst->obj_inst_id, data_ptr, len, ret = res->post_write_cb(obj_inst->obj_inst_id, data_ptr, len,
false, 0); last_block, total_size);
if (ret >= 0) {
ret = 0;
}
} }
NOTIFY_OBSERVER_PATH(path); NOTIFY_OBSERVER_PATH(path);
return 0; return ret;
} }
static int lwm2m_write_attr_handler(struct lwm2m_engine_obj *obj, static int lwm2m_write_attr_handler(struct lwm2m_engine_obj *obj,
@ -2103,20 +2248,6 @@ static int do_write_op(struct lwm2m_engine_obj *obj,
} }
} }
static int get_observe_option(const struct zoap_packet *zpkt)
{
struct zoap_option option = {};
u16_t count = 1;
int r;
r = zoap_find_options(zpkt, ZOAP_OPTION_OBSERVE, &option, count);
if (r <= 0) {
return -ENOENT;
}
return zoap_option_value_to_int(&option);
}
static int handle_request(struct zoap_packet *request, static int handle_request(struct zoap_packet *request,
struct lwm2m_message *msg) struct lwm2m_message *msg)
{ {
@ -2133,6 +2264,9 @@ static int handle_request(struct zoap_packet *request,
struct lwm2m_engine_context context; struct lwm2m_engine_context context;
int observe = -1; /* default to -1, 0 = ENABLE, 1 = DISABLE */ int observe = -1; /* default to -1, 0 = ENABLE, 1 = DISABLE */
bool discover = false; bool discover = false;
struct block_context *block_ctx = NULL;
size_t block_offset = 0;
enum zoap_block_size block_size;
/* setup engine context */ /* setup engine context */
memset(&context, 0, sizeof(struct lwm2m_engine_context)); memset(&context, 0, sizeof(struct lwm2m_engine_context));
@ -2208,7 +2342,7 @@ static int handle_request(struct zoap_packet *request,
context.operation = LWM2M_OP_READ; context.operation = LWM2M_OP_READ;
} }
/* check for observe */ /* check for observe */
observe = get_observe_option(in.in_zpkt); observe = get_option_int(in.in_zpkt, ZOAP_OPTION_OBSERVE);
zoap_header_set_code(out.out_zpkt, ZOAP_RESPONSE_CODE_CONTENT); zoap_header_set_code(out.out_zpkt, ZOAP_RESPONSE_CODE_CONTENT);
break; break;
@ -2245,7 +2379,31 @@ static int handle_request(struct zoap_packet *request,
in.inpos = 0; in.inpos = 0;
in.inbuf = zoap_packet_get_payload(in.in_zpkt, &in.insize); in.inbuf = zoap_packet_get_payload(in.in_zpkt, &in.insize);
/* TODO: check for block transfer? */ /* Check for block transfer */
r = get_option_int(in.in_zpkt, ZOAP_OPTION_BLOCK1);
if (r > 0) {
/* RFC7252: 4.6. Message Size */
block_size = GET_BLOCK_SIZE(r);
if (GET_MORE(r) &&
zoap_block_size_to_bytes(block_size) > in.insize) {
SYS_LOG_DBG("Trailing payload is discarded!");
r = -EFBIG;
goto error;
}
if (GET_BLOCK_NUM(r) == 0) {
r = init_block_ctx(token, tkl, &block_ctx);
} else {
r = get_block_ctx(token, tkl, &block_ctx);
}
if (r < 0) {
goto error;
}
/* 0 will be returned if it's the last block */
block_offset = zoap_next_block(in.in_zpkt, &block_ctx->ctx);
}
switch (context.operation) { switch (context.operation) {
@ -2315,37 +2473,63 @@ static int handle_request(struct zoap_packet *request,
return -EINVAL; return -EINVAL;
} }
if (r == 0) { if (r) {
/* TODO: Handle blockwise 1 */ goto error;
}
if (out.outlen > 0) { /* Handle blockwise 1 */
SYS_LOG_DBG("replying with %u bytes", out.outlen); if (block_ctx) {
zoap_packet_set_used(out.out_zpkt, out.outlen); if (block_offset > 0) {
/* More to come, ack with correspond block # */
r = zoap_add_block1_option(
out.out_zpkt, &block_ctx->ctx);
if (r) {
/* report as internal server error */
SYS_LOG_ERR("Fail adding block1 option: %d", r);
r = -EINVAL;
goto error;
} else {
zoap_header_set_code(out.out_zpkt,
ZOAP_RESPONSE_CODE_CONTINUE);
}
} else { } else {
SYS_LOG_DBG("no data in reply"); /* Free context when finished */
} free_block_ctx(block_ctx);
} else {
if (r == -ENOENT) {
zoap_header_set_code(out.out_zpkt,
ZOAP_RESPONSE_CODE_NOT_FOUND);
r = 0;
} else if (r == -EPERM) {
zoap_header_set_code(out.out_zpkt,
ZOAP_RESPONSE_CODE_NOT_ALLOWED);
r = 0;
} else if (r == -EEXIST) {
zoap_header_set_code(out.out_zpkt,
ZOAP_RESPONSE_CODE_BAD_REQUEST);
r = 0;
} else {
/* Failed to handle the request */
zoap_header_set_code(out.out_zpkt,
ZOAP_RESPONSE_CODE_INTERNAL_ERROR);
r = 0;
} }
} }
return r; if (out.outlen > 0) {
SYS_LOG_DBG("replying with %u bytes", out.outlen);
zoap_packet_set_used(out.out_zpkt, out.outlen);
} else {
SYS_LOG_DBG("no data in reply");
}
return 0;
error:
if (r == -ENOENT) {
zoap_header_set_code(out.out_zpkt,
ZOAP_RESPONSE_CODE_NOT_FOUND);
} else if (r == -EPERM) {
zoap_header_set_code(out.out_zpkt,
ZOAP_RESPONSE_CODE_NOT_ALLOWED);
} else if (r == -EEXIST) {
zoap_header_set_code(out.out_zpkt,
ZOAP_RESPONSE_CODE_BAD_REQUEST);
} else if (r == -EFBIG) {
zoap_header_set_code(out.out_zpkt,
ZOAP_RESPONSE_CODE_REQUEST_TOO_LARGE);
} else {
/* Failed to handle the request */
zoap_header_set_code(out.out_zpkt,
ZOAP_RESPONSE_CODE_INTERNAL_ERROR);
}
/* Free block context when error happened */
free_block_ctx(block_ctx);
return 0;
} }
void lwm2m_udp_receive(struct lwm2m_ctx *client_ctx, struct net_pkt *pkt, void lwm2m_udp_receive(struct lwm2m_ctx *client_ctx, struct net_pkt *pkt,
@ -2788,6 +2972,9 @@ error_start:
static int lwm2m_engine_init(struct device *dev) static int lwm2m_engine_init(struct device *dev)
{ {
memset(block1_contexts, 0,
sizeof(struct block_context) * NUM_BLOCK1_CONTEXT);
/* start thread to handle OBSERVER / NOTIFY events */ /* start thread to handle OBSERVER / NOTIFY events */
k_thread_create(&engine_thread_data, k_thread_create(&engine_thread_data,
&engine_thread_stack[0], &engine_thread_stack[0],

View file

@ -103,6 +103,8 @@ void lwm2m_udp_receive(struct lwm2m_ctx *client_ctx, struct net_pkt *pkt,
bool handle_separate_response, bool handle_separate_response,
udp_request_handler_cb_t udp_request_handler); udp_request_handler_cb_t udp_request_handler);
enum zoap_block_size lwm2m_default_block_size(void);
#if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_OBJ_SUPPORT) #if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_OBJ_SUPPORT)
u8_t lwm2m_firmware_get_update_state(void); u8_t lwm2m_firmware_get_update_state(void);
void lwm2m_firmware_set_update_state(u8_t state); void lwm2m_firmware_set_update_state(u8_t state);

View file

@ -181,13 +181,41 @@ static int package_write_cb(u16_t obj_inst_id,
u8_t *data, u16_t data_len, u8_t *data, u16_t data_len,
bool last_block, size_t total_size) bool last_block, size_t total_size)
{ {
SYS_LOG_DBG("PACKAGE WRITE"); u8_t state;
if (write_cb) { int ret = 0;
write_cb(obj_inst_id, data, data_len, last_block, total_size);
return 1; state = lwm2m_firmware_get_update_state();
if (state == STATE_IDLE) {
/* TODO: setup timer to check download status,
* make sure it fail after timeout
*/
lwm2m_firmware_set_update_state(STATE_DOWNLOADING);
} else if (state != STATE_DOWNLOADING) {
if (data_len == 0 && state == STATE_DOWNLOADED) {
/* reset to state idle and result default */
lwm2m_firmware_set_update_result(RESULT_DEFAULT);
return 1;
}
SYS_LOG_DBG("Cannot download: state = %d", state);
return -EPERM;
} }
return 0; if (write_cb) {
ret = write_cb(obj_inst_id, data, data_len,
last_block, total_size);
if (ret < 0) {
SYS_LOG_ERR("Failed to store firmware: %d", ret);
lwm2m_firmware_set_update_result(
RESULT_INTEGRITY_FAILED);
}
}
if (last_block) {
lwm2m_firmware_set_update_state(STATE_DOWNLOADED);
}
return 1;
} }
static int package_uri_write_cb(u16_t obj_inst_id, static int package_uri_write_cb(u16_t obj_inst_id,

View file

@ -225,28 +225,6 @@ do_firmware_transfer_reply_cb(const struct zoap_packet *response,
return ret; return ret;
} }
static enum zoap_block_size default_block_size(void)
{
switch (CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_COAP_BLOCK_SIZE) {
case 16:
return ZOAP_BLOCK_16;
case 32:
return ZOAP_BLOCK_32;
case 64:
return ZOAP_BLOCK_64;
case 128:
return ZOAP_BLOCK_128;
case 256:
return ZOAP_BLOCK_256;
case 512:
return ZOAP_BLOCK_512;
case 1024:
return ZOAP_BLOCK_1024;
}
return ZOAP_BLOCK_256;
}
static void firmware_transfer(struct k_work *work) static void firmware_transfer(struct k_work *work)
{ {
int ret, family; int ret, family;
@ -325,7 +303,8 @@ static void firmware_transfer(struct k_work *work)
} }
/* reset block transfer context */ /* reset block transfer context */
zoap_block_transfer_init(&firmware_block_ctx, default_block_size(), 0); zoap_block_transfer_init(&firmware_block_ctx,
lwm2m_default_block_size(), 0);
transfer_request(&firmware_block_ctx, NULL, 0, transfer_request(&firmware_block_ctx, NULL, 0,
do_firmware_transfer_reply_cb); do_firmware_transfer_reply_cb);
return; return;