net: lwm2m: Fix opaque data transfer in block mode

When FW update in PUSH mode is used, the firmware is encapsulated in the
TLV as an opaque data, according to the LMWM2M satandard, and then
sliced into blocks and transferred block by block in several
transactions. Therefore, the TLV header is only present in the initial
message.

Current implementation did not handle this case well, reporting errors
on consecutive blocks, therefore making the FW update in PUSH mode
broken.

This commit fixes this issue with following changes:
 * The TLV is only assumed to be present in the initial block, while
   consecutive blocks will be processed directly into the appropriate
   handler,
 * 32-bit variables shall be used whenever dealing with the opaque data
   length, since the firmware size can easily exceed the 16-bit range,
 * Additional information, required for the FW block transfer to work
   properly were added to the block context structure,
 * The application shall only be notified of the actual data length, and
   not the total block size (the total TLV size including header).

Signed-off-by: Robert Lubos <robert.lubos@nordicsemi.no>
This commit is contained in:
Robert Lubos 2020-06-03 10:48:44 +02:00 committed by Carles Cufí
commit 69ca589256
3 changed files with 72 additions and 16 deletions

View file

@ -141,8 +141,11 @@ static int sock_nfds;
struct block_context {
struct coap_block_context ctx;
int64_t timestamp;
uint32_t remaining_len;
uint8_t token[8];
uint8_t tkl;
uint8_t opaque_header_len;
bool last_block : 1;
};
static struct block_context block1_contexts[NUM_BLOCK1_CONTEXT];
@ -272,6 +275,9 @@ init_block_ctx(const uint8_t *token, uint8_t tkl, struct block_context **ctx)
memcpy((*ctx)->token, token, tkl);
coap_block_transfer_init(&(*ctx)->ctx, lwm2m_default_block_size(), 0);
(*ctx)->timestamp = timestamp;
(*ctx)->remaining_len = 0;
(*ctx)->opaque_header_len = 0;
(*ctx)->last_block = false;
return 0;
}
@ -2209,14 +2215,20 @@ static int lwm2m_read_handler(struct lwm2m_engine_obj_inst *obj_inst,
size_t lwm2m_engine_get_opaque_more(struct lwm2m_input_context *in,
uint8_t *buf, size_t buflen, bool *last_block)
{
uint16_t in_len = in->opaque_len;
uint32_t in_len = in->opaque_len;
uint16_t remaining = in->in_cpkt->max_len - in->offset;
if (in_len > buflen) {
in_len = buflen;
}
if (in_len > remaining) {
in_len = remaining;
}
in->opaque_len -= in_len;
if (in->opaque_len == 0U) {
remaining -= in_len;
if (in->opaque_len == 0U || remaining == 0) {
*last_block = true;
}
@ -2234,14 +2246,23 @@ static int lwm2m_write_handler_opaque(struct lwm2m_engine_obj_inst *obj_inst,
struct lwm2m_engine_res_inst *res_inst,
struct lwm2m_input_context *in,
void *data_ptr, size_t data_len,
bool last_block, size_t total_size)
struct block_context *block_ctx)
{
size_t len = 1;
bool last_pkt_block = false, first_read = true;
int block_num = 0;
int ret = 0;
size_t total_size = 0;
bool last_block = true;
if (block_ctx != NULL) {
block_num = block_ctx->ctx.current /
coap_block_size_to_bytes(block_ctx->ctx.block_size);
last_block = block_ctx->last_block;
}
while (!last_pkt_block && len > 0) {
if (first_read) {
if (first_read && block_num == 0) {
len = engine_get_opaque(in, (uint8_t *)data_ptr,
data_len, &last_pkt_block);
if (len == 0) {
@ -2249,24 +2270,49 @@ static int lwm2m_write_handler_opaque(struct lwm2m_engine_obj_inst *obj_inst,
return 0;
}
if (block_ctx != NULL) {
block_ctx->remaining_len = in->opaque_len;
/* engine_get_opaque returns only the actual
* data length, not the header length, so we
* need to calculate it.
*/
block_ctx->opaque_header_len =
block_ctx->ctx.total_size -
(block_ctx->remaining_len + len);
}
first_read = false;
} else {
if (block_ctx != NULL) {
in->opaque_len = block_ctx->remaining_len;
}
len = lwm2m_engine_get_opaque_more(in, (uint8_t *)data_ptr,
data_len,
&last_pkt_block);
if (block_ctx != NULL) {
block_ctx->remaining_len = in->opaque_len;
}
}
if (len == 0) {
return -EINVAL;
}
if (block_ctx != NULL) {
/* We shall only report the actual total data length,
* excluding the header size (if any).
*/
total_size = block_ctx->ctx.total_size -
block_ctx->opaque_header_len;
}
if (res->post_write_cb) {
ret = res->post_write_cb(obj_inst->obj_inst_id,
res->res_id,
res_inst->res_inst_id,
data_ptr, len,
last_pkt_block && last_block,
total_size);
ret = res->post_write_cb(
obj_inst->obj_inst_id, res->res_id,
res_inst->res_inst_id, data_ptr, len,
last_pkt_block && last_block, total_size);
if (ret < 0) {
return ret;
}
@ -2318,8 +2364,6 @@ int lwm2m_write_handler(struct lwm2m_engine_obj_inst *obj_inst,
/* Get block1 option for checking MORE block flag */
ret = coap_get_option_int(msg->in.in_cpkt, COAP_OPTION_BLOCK1);
if (ret >= 0) {
last_block = !GET_MORE(ret);
/* Get block_ctx for total_size (might be zero) */
tkl = coap_header_get_token(msg->in.in_cpkt, token);
if (tkl && !get_block_ctx(token, tkl, &block_ctx)) {
@ -2328,7 +2372,7 @@ int lwm2m_write_handler(struct lwm2m_engine_obj_inst *obj_inst,
" last:%u",
block_ctx->ctx.total_size,
block_ctx->ctx.current,
last_block);
block_ctx->last_block);
}
}
}
@ -2340,8 +2384,7 @@ int lwm2m_write_handler(struct lwm2m_engine_obj_inst *obj_inst,
ret = lwm2m_write_handler_opaque(obj_inst, res,
res_inst, &msg->in,
data_ptr, data_len,
last_block,
total_size);
block_ctx);
if (ret < 0) {
return ret;
}
@ -3498,6 +3541,8 @@ static int handle_request(struct coap_packet *request,
goto error;
}
block_ctx->last_block = last_block;
/* Handle blockwise 1 (Part 1): Set response code */
if (!last_block) {
msg->code = COAP_RESPONSE_CODE_CONTINUE;

View file

@ -379,7 +379,7 @@ struct lwm2m_input_context {
uint16_t offset;
/* length of incoming opaque */
uint16_t opaque_len;
size_t opaque_len;
/* private output data */
void *user_data;

View file

@ -919,6 +919,17 @@ int do_write_op_tlv(struct lwm2m_message *msg)
struct oma_tlv tlv;
int ret;
/* In case of Firmware object Package resource go directly to the
* message processing - consecutive blocks will not carry the TLV
* header.
*/
if (msg->path.obj_id == 5 && msg->path.res_id == 0) {
ret = do_write_op_tlv_item(msg);
if (ret < 0) {
return ret;
}
}
while (true) {
/*
* This initial read of TLV data won't advance frag/offset.