Bluetooth: Mesh: Keep sending Partial Block Report message

When the BLOB server is in Pull mode and all chunks were received for
the current block, and the current block is the last block, then
according to section 5.2.4 of MshMBTv1.0 the server should stop sending
BLOB Partial Block Report messages if it determines that the client
knows that the transfer is complete:

```
While the Pull BLOB State Machine is in the All Chunks Received state,
the Pull BLOB State Machine continues to send the BLOB Partial Block
Report messages until one of the following happens:
•  The Receive BLOB Timeout timer expires.
•  If the current block is not the last block, then the client starts a
new block, in which case a new Pull BLOB State Machine is instantiated.
•  If the current block is the last block, then the server determines
that the client knows the transfer is complete. For example, a
higher-layer model may indicate that the client considers the transfer
complete.
```

We currently don't have any OOB mean (for example, API) to determine
whether the client knows that the transfer is complete. We also need to
keep in mind that the Partial Block Report message can get lost so one
transmission may not be enough. The client could immediately send BLOB
Transfer Get message to get the transfer status, but this goes against
its state machine defined in section 6.2.4.2, where a Block transmission
completes when a BLOB Partial Block Report message is received with an
empty list of requested chunks (table 6.4, figure 6.1).

Because of this, we need to keep sending Partial Block Report messages.
We can keep sending them at least until Block Report timer expires.
If the client sends BLOB Transfer Get message, then it finished with
sending the block and we can change the phase and finish the transfer.

Signed-off-by: Pavel Vasilyev <pavel.vasilyev@nordicsemi.no>
This commit is contained in:
Pavel Vasilyev 2023-10-16 13:19:29 +02:00 committed by Carles Cufí
commit 6a2c102aca

View file

@ -251,6 +251,37 @@ static void resume(struct bt_mesh_blob_srv *srv)
reset_timer(srv); reset_timer(srv);
} }
static void end(struct bt_mesh_blob_srv *srv)
{
phase_set(srv, BT_MESH_BLOB_XFER_PHASE_COMPLETE);
k_work_cancel_delayable(&srv->rx_timeout);
k_work_cancel_delayable(&srv->pull.report);
io_close(srv);
erase_state(srv);
if (srv->cb && srv->cb->end) {
srv->cb->end(srv, srv->state.xfer.id, true);
}
}
static bool all_blocks_received(struct bt_mesh_blob_srv *srv)
{
for (int i = 0; i < ARRAY_SIZE(srv->state.blocks); ++i) {
if (srv->state.blocks[i]) {
return false;
}
}
return true;
}
static bool pull_mode_xfer_complete(struct bt_mesh_blob_srv *srv)
{
return srv->state.xfer.mode == BT_MESH_BLOB_XFER_MODE_PULL &&
srv->phase == BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_CHUNK &&
all_blocks_received(srv);
}
static void timeout(struct k_work *work) static void timeout(struct k_work *work)
{ {
struct bt_mesh_blob_srv *srv = struct bt_mesh_blob_srv *srv =
@ -260,6 +291,8 @@ static void timeout(struct k_work *work)
if (srv->phase == BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START) { if (srv->phase == BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START) {
cancel(srv); cancel(srv);
} else if (pull_mode_xfer_complete(srv)) {
end(srv);
} else { } else {
suspend(srv); suspend(srv);
} }
@ -388,6 +421,15 @@ static int handle_xfer_get(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ct
struct bt_mesh_blob_srv *srv = mod->user_data; struct bt_mesh_blob_srv *srv = mod->user_data;
LOG_DBG(""); LOG_DBG("");
if (pull_mode_xfer_complete(srv)) {
/* The client requested transfer. If we are in Pull mode and all blocks were
* received, we should change the Transfer state here to Complete so that the client
* receives the correct state.
*/
end(srv);
}
xfer_status_rsp(srv, ctx, BT_MESH_BLOB_SUCCESS); xfer_status_rsp(srv, ctx, BT_MESH_BLOB_SUCCESS);
return 0; return 0;
@ -685,7 +727,7 @@ static int handle_chunk(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
struct bt_mesh_blob_chunk chunk; struct bt_mesh_blob_chunk chunk;
size_t expected_size = 0; size_t expected_size = 0;
uint16_t idx; uint16_t idx;
int i, err; int err;
idx = net_buf_simple_pull_le16(buf); idx = net_buf_simple_pull_le16(buf);
chunk.size = buf->len; chunk.size = buf->len;
@ -745,26 +787,26 @@ static int handle_chunk(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
atomic_clear_bit(srv->state.blocks, srv->block.number); atomic_clear_bit(srv->state.blocks, srv->block.number);
for (i = 0; i < ARRAY_SIZE(srv->state.blocks); ++i) { if (!all_blocks_received(srv)) {
if (!srv->state.blocks[i]) {
continue;
}
phase_set(srv, BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_BLOCK); phase_set(srv, BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_BLOCK);
store_state(srv); store_state(srv);
return 0; return 0;
} }
phase_set(srv, BT_MESH_BLOB_XFER_PHASE_COMPLETE); if (srv->state.xfer.mode == BT_MESH_BLOB_XFER_MODE_PULL) {
k_work_cancel_delayable(&srv->rx_timeout); /* By spec (section 5.2.4), the BLOB Server stops sending BLOB Partial Block Report
k_work_cancel_delayable(&srv->pull.report); * messages "If the current block is the last block, then the server determines that
io_close(srv); * the client knows the transfer is complete. For example, a higher-layer model may
erase_state(srv); * indicate that the client considers the transfer complete."
*
if (srv->cb && srv->cb->end) { * We don't have any way for higher-layer model to indicate that the transfer is
srv->cb->end(srv, srv->state.xfer.id, true); * complete. Therefore we need to keep sending Partial Block Report messages until
* the client sends BLOB Transfer Get message or the Block Timer expires.
*/
return 0;
} }
end(srv);
return 0; return 0;
} }