Bluetooth: Mesh: BLOB models
Adds support for the BLOB Models from Mesh Model specification v1.1. Co-authored-by: Pavel Vasilyev <pavel.vasilyev@nordicsemi.no> Co-authored-by: Anders Storrø <anders.storro@nordicsemi.no> Co-authored-by: Aleksandr Khromykh <aleksandr.khromykh@nordicsemi.no> Co-authored-by: Krzysztof Kopyściński <krzysztof.kopyscinski@codecoup.pl> Co-authored-by: Ludvig Samuelsen Jordet <ludvig.jordet@nordicsemi.no> Co-authored-by: Mia Koen <mia.koen@nordicsemi.no> Signed-off-by: Trond Einar Snekvik <Trond.Einar.Snekvik@nordicsemi.no> Signed-off-by: Pavel Vasilyev <pavel.vasilyev@nordicsemi.no>
This commit is contained in:
parent
ffddd9ffc0
commit
4302b568cc
20 changed files with 4321 additions and 1 deletions
130
doc/connectivity/bluetooth/api/mesh/blob.rst
Normal file
130
doc/connectivity/bluetooth/api/mesh/blob.rst
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
.. _bluetooth_mesh_blob:
|
||||
|
||||
BLOB Transfer models
|
||||
####################
|
||||
|
||||
The Binary Large Object (BLOB) Transfer models provide functionality for sending large binary objects from a single source to many Target nodes over the Bluetooth mesh network. It is the underlying transport method for the :ref:`bluetooth_mesh_dfu`, but may be used for other object transfer purposes.
|
||||
|
||||
The BLOB Transfer models support transfers of continuous binary objects of up to 4 GB (2 \ :sup:`32` bytes). The BLOB transfer protocol has built-in recovery procedures for packet losses, and sets up checkpoints to ensure that all targets have received all the data before moving on. Data transfer order is not guaranteed.
|
||||
|
||||
BLOB transfers are constrained by the transfer speed and reliability of the underlying mesh network. Under ideal conditions, the BLOBs can be transferred at a rate of up to 1 kbps, allowing a 100 kB BLOB to be transferred in 10-15 minutes. However, network conditions, transfer capabilities and other limiting factors can easily degrade the data rate by several orders of magnitude. Tuning the parameters of the transfer according to the application and network configuration, as well as scheduling it to periods with low network traffic, will offer significant improvements on the speed and reliability of the protocol. However, achieving transfer rates close to the ideal rate is unlikely in actual deployments.
|
||||
|
||||
There are two BLOB Transfer models:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
blob_srv
|
||||
blob_cli
|
||||
|
||||
The BLOB Transfer Client is instantiated on the sender node, and the BLOB Transfer Server is instantiated on the receiver nodes.
|
||||
|
||||
Concepts
|
||||
********
|
||||
|
||||
The BLOB transfer protocol introduces several new concepts to implement the BLOB transfer.
|
||||
|
||||
|
||||
BLOBs
|
||||
=====
|
||||
|
||||
BLOBs are binary objects up to 4 GB in size, that can contain any data the application would like to transfer through the mesh network. The BLOBs are continuous data objects, divided into blocks and chunks to make the transfers reliable and easy to process. No limitations are put on the contents or structure of the BLOB, and applications are free to define any encoding or compression they'd like on the data itself.
|
||||
|
||||
The BLOB transfer protocol does not provide any built-in integrity checks, encryption or authentication of the BLOB data. However, the underlying encryption of the Bluetooth mesh protocol provides data integrity checks and protects the contents of the BLOB from third parties using network and application level encryption.
|
||||
|
||||
Blocks
|
||||
------
|
||||
|
||||
The binary objects are divided into blocks, typically from a few hundred to several thousand bytes in size. Each block is transmitted separately, and the BLOB Transfer Client ensures that all BLOB Transfer Servers have received the full block before moving on to the next. The block size is determined by the transfer's ``block_size_log`` parameter, and is the same for all blocks in the transfer except the last, which may be smaller. For a BLOB stored in flash memory, the block size is typically a multiple of the flash page size of the Target devices.
|
||||
|
||||
Chunks
|
||||
------
|
||||
|
||||
Each block is divided into chunks. A chunk is the smallest data unit in the BLOB transfer, and must fit inside a single Bluetooth mesh access message excluding the opcode (379 bytes or less). The mechanism for transferring chunks depends on the transfer mode.
|
||||
|
||||
When operating in Push BLOB Transfer Mode, the chunks are sent as unacknowledged packets from the BLOB Transfer Client to all targeted BLOB Transfer Servers. Once all chunks in a block have been sent, the BLOB Transfer Client asks each BLOB Transfer Server if they're missing any chunks, and resends them. This is repeated until all BLOB Transfer Servers have received all chunks, or the BLOB Transfer Client gives up.
|
||||
|
||||
When operating in Pull BLOB Transfer Mode, the BLOB Transfer Server will request a small number of chunks from the BLOB Transfer Client at a time, and wait for the BLOB Transfer Client to send them before requesting more chunks. This repeats until all chunks have been transferred, or the BLOB Transfer Server gives up.
|
||||
|
||||
Read more about the transfer modes in :ref:`bluetooth_mesh_blob_transfer_modes` section.
|
||||
|
||||
.. _bluetooth_mesh_blob_stream:
|
||||
|
||||
BLOB streams
|
||||
============
|
||||
|
||||
In the BLOB Transfer models' APIs, the BLOB data handling is separated from the high-level transfer handling. This split allows reuse of different BLOB storage and transfer strategies for different applications. While the high level transfer is controlled directly by the application, the BLOB data itself is accessed through a *BLOB stream*.
|
||||
|
||||
The BLOB stream is comparable to a standard library file stream. Through opening, closing, reading and writing, the BLOB Transfer model gets full access to the BLOB data, whether it's kept in flash, RAM, or on a peripheral. The BLOB stream is opened with an access mode (read or write) before it's used, and the BLOB Transfer models will move around inside the BLOB's data in blocks and chunks, using the BLOB stream as an interface.
|
||||
|
||||
Interaction
|
||||
-----------
|
||||
|
||||
Before the BLOB is read or written, the stream is opened by calling its :c:member:`open <bt_mesh_blob_io.open>` callback. When used with a BLOB Transfer Server, the BLOB stream is always opened in write mode, and when used with a BLOB Transfer Client, it's always opened in read mode.
|
||||
|
||||
For each block in the BLOB, the BLOB Transfer model starts by calling :c:member:`block_start <bt_mesh_blob_io.block_start>`. Then, depending on the access mode, the BLOB stream's :c:member:`wr <bt_mesh_blob_io.wr>` or :c:member:`rd <bt_mesh_blob_io.rd>` callback is called repeatedly to move data to or from the BLOB. When the model is done processing the block, it calls :c:member:`block_end <bt_mesh_blob_io.block_end>`. When the transfer is complete, the BLOB stream is closed by calling :c:member:`close <bt_mesh_blob_io.close>`.
|
||||
|
||||
Implementations
|
||||
---------------
|
||||
|
||||
The application may implement their own BLOB stream, or use the implementations provided by Zephyr:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
blob_flash
|
||||
|
||||
|
||||
Transfer capabilities
|
||||
=====================
|
||||
|
||||
Each BLOB Transfer Server may have different transfer capabilities. The transfer capabilities of each device are controlled through the following configuration options:
|
||||
|
||||
* :kconfig:option:`CONFIG_BT_MESH_BLOB_SIZE_MAX`
|
||||
* :kconfig:option:`CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MIN`
|
||||
* :kconfig:option:`CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX`
|
||||
* :kconfig:option:`CONFIG_BT_MESH_BLOB_CHUNK_COUNT_MAX`
|
||||
|
||||
The :kconfig:option:`CONFIG_BT_MESH_BLOB_CHUNK_COUNT_MAX` option is also used by the BLOB Transfer Client and affects memory consumption by the BLOB Transfer Client model structure.
|
||||
|
||||
To ensure that the transfer can be received by as many servers as possible, the BLOB Transfer Client can retrieve the capabilities of each BLOB Transfer Server before starting the transfer. The client will transfer the BLOB with the highest possible block and chunk size.
|
||||
|
||||
.. _bluetooth_mesh_blob_transfer_modes:
|
||||
|
||||
Transfer modes
|
||||
==============
|
||||
|
||||
BLOBs can be transferred using two transfer modes, Push BLOB Transfer Mode and Pull BLOB Transfer Mode.
|
||||
In most cases, the transfer should be conducted in Push BLOB Transfer Mode.
|
||||
|
||||
In Push BLOB Transfer Mode, the send rate is controlled by the BLOB Transfer Client, which will push all the chunks of each block without any high level flow control. Push BLOB Transfer Mode supports any number of Target nodes, and should be the default transfer mode.
|
||||
|
||||
In Pull BLOB Transfer Mode, the BLOB Transfer Server will "pull" the chunks from the BLOB Transfer Client at its own rate. Pull BLOB Transfer Mode can be conducted with multiple Target nodes, and is intended for transferring BLOBs to Target nodes acting as :ref:`bluetooth_mesh_lpn`. When operating in Pull BLOB Transfer Mode, the BLOB Transfer Server will request chunks from the BLOB Transfer Client in small batches, and wait for them all to arrive before requesting more chunks. This process is repeated until the BLOB Transfer Server has received all chunks in a block. Then, the BLOB Transfer Client starts the next block, and the BLOB Transfer Server requests all chunks of that block.
|
||||
|
||||
|
||||
.. _bluetooth_mesh_blob_timeout:
|
||||
|
||||
Transfer timeout
|
||||
================
|
||||
|
||||
The timeout of the BLOB transfer is based on a Timeout Base value. Both client and server use the same Timeout Base value, but they calculate timeout differently.
|
||||
|
||||
The BLOB Transfer Server uses the following formula to calculate the BLOB transfer timeout::
|
||||
|
||||
10 * (Timeout Base + 1) seconds
|
||||
|
||||
|
||||
For the BLOB Transfer Client, the following formula is used::
|
||||
|
||||
(10000 * (Timeout Base + 2)) + (100 * TTL) milliseconds
|
||||
|
||||
where TTL is time to live value set in the transfer.
|
||||
|
||||
API reference
|
||||
*************
|
||||
|
||||
This section contains types and defines common to the BLOB Transfer models.
|
||||
|
||||
.. doxygengroup:: bt_mesh_blob
|
||||
:project: Zephyr
|
||||
:members:
|
||||
96
doc/connectivity/bluetooth/api/mesh/blob_cli.rst
Normal file
96
doc/connectivity/bluetooth/api/mesh/blob_cli.rst
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
.. _bluetooth_mesh_blob_cli:
|
||||
|
||||
BLOB Transfer Client
|
||||
####################
|
||||
|
||||
The Binary Large Object (BLOB) Transfer Client is the sender of the BLOB transfer. It supports sending BLOBs of any size to any number of Target nodes, in both Push BLOB Transfer Mode and Pull BLOB Transfer Mode.
|
||||
|
||||
Usage
|
||||
*****
|
||||
|
||||
Initialization
|
||||
==============
|
||||
|
||||
The BLOB Transfer Client is instantiated on an element with a set of event handler callbacks:
|
||||
|
||||
.. code-block:: C
|
||||
|
||||
static const struct bt_mesh_blob_cli_cb blob_cb = {
|
||||
/* Callbacks */
|
||||
};
|
||||
|
||||
static struct bt_mesh_blob_cli blob_cli = {
|
||||
.cb = &blob_cb,
|
||||
};
|
||||
|
||||
static struct bt_mesh_model models[] = {
|
||||
BT_MESH_MODEL_BLOB_CLI(&blob_cli),
|
||||
};
|
||||
|
||||
Transfer context
|
||||
================
|
||||
|
||||
Both the transfer capabilities retrieval procedure and the BLOB transfer uses an instance of a :c:type:`bt_mesh_blob_cli_inputs` to determine how to perform the transfer. The BLOB Transfer Client Inputs structure must at least be initialized with a list of targets, an application key and a time to live (TTL) value before it is used in a procedure:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
static struct bt_mesh_blob_target targets[3] = {
|
||||
{ .addr = 0x0001 },
|
||||
{ .addr = 0x0002 },
|
||||
{ .addr = 0x0003 },
|
||||
};
|
||||
static struct bt_mesh_blob_cli_inputs inputs = {
|
||||
.app_idx = MY_APP_IDX,
|
||||
.ttl = BT_MESH_TTL_DEFAULT,
|
||||
};
|
||||
|
||||
sys_slist_init(&inputs.targets);
|
||||
sys_slist_append(&inputs.targets, &targets[0].n);
|
||||
sys_slist_append(&inputs.targets, &targets[1].n);
|
||||
sys_slist_append(&inputs.targets, &targets[2].n);
|
||||
|
||||
Note that all BLOB Transfer Servers in the transfer must be bound to the chosen application key.
|
||||
|
||||
|
||||
Group address
|
||||
-------------
|
||||
|
||||
The application may additionally specify a group address in the context structure. If the group is not :c:macro:`BT_MESH_ADDR_UNASSIGNED`, the messages in the transfer will be sent to the group address, instead of being sent individually to each Target node. Mesh Manager must ensure that all Target nodes having the BLOB Transfer Server model subscribe to this group address.
|
||||
|
||||
Using group addresses for transferring the BLOBs can generally increase the transfer speed, as the BLOB Transfer Client sends each message to all Target nodes at the same time. However, sending large, segmented messages to group addresses in Bluetooth mesh is generally less reliable than sending them to unicast addresses, as there is no transport layer acknowledgment mechanism for groups. This can lead to longer recovery periods at the end of each block, and increases the risk of losing Target nodes. Using group addresses for BLOB transfers will generally only pay off if the list of Target nodes is extensive, and the effectiveness of each addressing strategy will vary heavily between different deployments and the size of the chunks.
|
||||
|
||||
Transfer timeout
|
||||
----------------
|
||||
|
||||
If a Target node fails to respond to an acknowledged message within the BLOB Transfer Client's time limit, the Target node is dropped from the transfer. The application can reduce the chances of this by giving the BLOB Transfer Client extra time through the context structure. The extra time may be set in 10-second increments, up to 182 hours, in addition to the base time of 20 seconds. The wait time scales automatically with the transfer TTL.
|
||||
|
||||
Note that the BLOB Transfer Client only moves forward with the transfer in following cases:
|
||||
|
||||
* All Target nodes have responded.
|
||||
* A node has been removed from the list of Target nodes.
|
||||
* The BLOB Transfer Client times out.
|
||||
|
||||
Increasing the wait time will increase this delay.
|
||||
|
||||
BLOB transfer capabilities retrieval
|
||||
====================================
|
||||
|
||||
It is generally recommended to retrieve BLOB transfer capabilities before starting a transfer. The procedure populates the transfer capabilities from all Target nodes with the most liberal set of parameters that allows all Target nodes to participate in the transfer. Any Target nodes that fail to respond, or respond with incompatible transfer parameters, will be dropped.
|
||||
|
||||
Target nodes are prioritized according to their order in the list of Target nodes. If a Target node is found to be incompatible with any of the previous Target nodes, for instance by reporting a non-overlapping block size range, it will be dropped. Lost Target nodes will be reported through the :c:member:`lost_target <bt_mesh_blob_cli_cb.lost_target>` callback.
|
||||
|
||||
The end of the procedure is signalled through the :c:member:`caps <bt_mesh_blob_cli_cb.caps>` callback, and the resulting capabilities can be used to determine the block and chunk sizes required for the BLOB transfer.
|
||||
|
||||
BLOB transfer
|
||||
=============
|
||||
|
||||
The BLOB transfer is started by calling :c:func:`bt_mesh_blob_cli_send` function, which (in addition to the aforementioned transfer inputs) requires a set of transfer parameters and a BLOB stream instance. The transfer parameters include the 64-bit BLOB ID, the BLOB size, the transfer mode, the block size in logarithmic representation and the chunk size. The BLOB ID is application defined, but must match the BLOB ID the BLOB Transfer Servers have been started with.
|
||||
|
||||
The transfer runs until it either completes successfully for at least one Target node, or it is cancelled. The end of the transfer is communicated to the application through the :c:member:`end <bt_mesh_blob_cli_cb.end>` callback. Lost Target nodes will be reported through the :c:member:`lost_target <bt_mesh_blob_cli_cb.lost_target>` callback.
|
||||
|
||||
API reference
|
||||
*************
|
||||
|
||||
.. doxygengroup:: bt_mesh_blob_cli
|
||||
:project: Zephyr
|
||||
:members:
|
||||
26
doc/connectivity/bluetooth/api/mesh/blob_flash.rst
Normal file
26
doc/connectivity/bluetooth/api/mesh/blob_flash.rst
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
.. _bluetooth_mesh_blob_flash:
|
||||
|
||||
BLOB Flash
|
||||
##########
|
||||
|
||||
The BLOB Flash Readers and Writers implement BLOB reading to and writing from flash partitions defined in the :ref:`flash map <flash_map_api>`.
|
||||
|
||||
|
||||
BLOB Flash Reader
|
||||
*****************
|
||||
|
||||
The BLOB Flash Reader interacts with the BLOB Transfer Client to read BLOB data directly from flash. It must be initialized by calling :c:func:`bt_mesh_blob_flash_rd_init` before being passed to the BLOB Transfer Client. Each BLOB Flash Reader only supports one transfer at the time.
|
||||
|
||||
|
||||
BLOB Flash Writer
|
||||
*****************
|
||||
|
||||
The BLOB Flash Writer interacts with the BLOB Transfer Server to write BLOB data directly to flash. It must be initialized by calling :c:func:`bt_mesh_blob_flash_rd_init` before being passed to the BLOB Transfer Server. Each BLOB Flash Writer only supports one transfer at the time, and requires a block size that is a multiple of the flash page size. If a transfer is started with a block size lower than the flash page size, the transfer will be rejected.
|
||||
|
||||
The BLOB Flash Writer copies chunk data into a buffer to accommodate chunks that are unaligned with the flash write block size. The buffer data is padded with ``0xff`` if either the start or length of the chunk is unaligned.
|
||||
|
||||
API Reference
|
||||
*************
|
||||
|
||||
.. doxygengroup:: bt_mesh_blob_io_flash
|
||||
:project: Zephyr
|
||||
70
doc/connectivity/bluetooth/api/mesh/blob_srv.rst
Normal file
70
doc/connectivity/bluetooth/api/mesh/blob_srv.rst
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
.. _bluetooth_mesh_blob_srv:
|
||||
|
||||
BLOB Transfer Server
|
||||
####################
|
||||
|
||||
The Binary Large Object (BLOB) Transfer Server model implements reliable receiving of large binary objects. It serves as the backend of the :ref:`bluetooth_mesh_dfu_srv`, but can also be used for receiving other binary images.
|
||||
|
||||
BLOBs
|
||||
*****
|
||||
|
||||
As described in :ref:`bluetooth_mesh_blob`, the binary objects transferred by the BLOB Transfer models are divided into blocks, which are divided into chunks. As the transfer is controlled by the BLOB Transfer Client model, the BLOB Transfer Server must allow blocks to come in any order. The chunks within a block may also come in any order, but all chunks in a block must be received before the next block is started.
|
||||
|
||||
The BLOB Transfer Server keeps track of the received blocks and chunks, and will process each block and chunk only once. The BLOB Transfer Server also ensures that any missing chunks are resent by the BLOB Transfer Client.
|
||||
|
||||
Usage
|
||||
*****
|
||||
|
||||
The BLOB Transfer Server is instantiated on an element with a set of event handler callbacks:
|
||||
|
||||
.. code-block:: C
|
||||
|
||||
static const struct bt_mesh_blob_srv_cb blob_cb = {
|
||||
/* Callbacks */
|
||||
};
|
||||
|
||||
static struct bt_mesh_blob_srv blob_srv = {
|
||||
.cb = &blob_cb,
|
||||
};
|
||||
|
||||
static struct bt_mesh_model models[] = {
|
||||
BT_MESH_MODEL_BLOB_SRV(&blob_srv),
|
||||
};
|
||||
|
||||
A BLOB Transfer Server is capable of receiving a single BLOB transfer at a time. Before the BLOB Transfer Server can receive a transfer, it must be prepared by the user. The transfer ID must be passed to the BLOB Transfer Server through the :c:func:`bt_mesh_blob_srv_recv` function before the transfer is started by the BLOB Transfer Client. The ID must be shared between the BLOB Transfer Client and the BLOB Transfer Server through some higher level procedure, like a vendor specific transfer management model.
|
||||
|
||||
Once the transfer has been set up on the BLOB Transfer Server, it's ready for receiving the BLOB. The application is notified of the transfer progress through the event handler callbacks, and the BLOB data is sent to the BLOB stream.
|
||||
|
||||
The interaction between the BLOB Transfer Server, BLOB stream and application is shown below:
|
||||
|
||||
.. figure:: images/blob_srv.svg
|
||||
:align: center
|
||||
:alt: BLOB Transfer Server model interaction
|
||||
|
||||
BLOB Transfer Server model interaction
|
||||
|
||||
Transfer suspension
|
||||
*******************
|
||||
|
||||
The BLOB Transfer Server keeps a running timer during the transfer, that is reset on every received message. If the BLOB Transfer Client does not send a message before the transfer timer expires, the transfer is suspended by the BLOB Transfer Server.
|
||||
|
||||
The BLOB Transfer Server notifies the user of the suspension by calling the :c:member:`suspended <bt_mesh_blob_srv_cb.suspended>` callback. If the BLOB Transfer Server is in the middle of receiving a block, this block is discarded.
|
||||
|
||||
The BLOB Transfer Client may resume a suspended transfer by starting a new block transfer. The BLOB Transfer Server notifies the user by calling the :c:member:`resume <bt_mesh_blob_srv_cb.resume>` callback.
|
||||
|
||||
Transfer recovery
|
||||
*****************
|
||||
|
||||
The state of the BLOB transfer is stored persistently. If a reboot occurs, the BLOB Transfer Server will attempt to recover the transfer. When the Bluetooth mesh subsystem is started (for instance by calling :c:func:`bt_mesh_init`), the BLOB Transfer Server will check for aborted transfers, and call the :c:member:`recover <bt_mesh_blob_srv_cb.recover>` callback if there is any. In the recover callback, the user must provide a BLOB stream to use for the rest of the transfer. If the recover callback doesn't return successfully or does not provide a BLOB stream, the transfer is abandoned. If no recover callback is implemented, transfers are always abandoned after a reboot.
|
||||
|
||||
After a transfer is successfully recovered, the BLOB Transfer Server enters the suspended state. It will stay suspended until the BLOB Transfer Client resumes the transfer, or the user cancels it.
|
||||
|
||||
.. note::
|
||||
The BLOB Transfer Client sending the transfer must support transfer recovery for the transfer to complete. If the BLOB Transfer Client has already given up the transfer, the BLOB Transfer Server will stay suspended until the application calls :c:func:`bt_mesh_blob_srv_cancel`.
|
||||
|
||||
API reference
|
||||
*************
|
||||
|
||||
.. doxygengroup:: bt_mesh_blob_srv
|
||||
:project: Zephyr
|
||||
:members:
|
||||
|
|
@ -6,6 +6,8 @@ Core
|
|||
The core provides functionality for managing the general Bluetooth mesh
|
||||
state.
|
||||
|
||||
.. _bluetooth_mesh_lpn:
|
||||
|
||||
Low Power Node
|
||||
**************
|
||||
|
||||
|
|
|
|||
3
doc/connectivity/bluetooth/api/mesh/images/blob_srv.svg
generated
Normal file
3
doc/connectivity/bluetooth/api/mesh/images/blob_srv.svg
generated
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 35 KiB |
1
doc/connectivity/bluetooth/api/mesh/images/blob_srv.xml
Normal file
1
doc/connectivity/bluetooth/api/mesh/images/blob_srv.xml
Normal file
|
|
@ -0,0 +1 @@
|
|||
<mxfile host="app.diagrams.net" modified="2020-05-29T14:09:52.315Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36" etag="l8dRUjEUGN8CH7xVsUCB" version="13.1.3" type="device"><diagram id="an1P-291YU46igf0sxVC" name="Page-1">7Zzfb6M4EMf/mkh7DycBtkl5bNN276Q97epSafftRMENaB2MgDTp/vVngkmIJzQEMMk2kfoQBv+AmU+/Hg8kIzSZrz4nbhz8w33KRpbhr0bofmRZJrasUf5n+G+F5cbAhWGWhL5stDVMw1+0MBJpXIQ+TXfaZZyzLIx3jR6PIuplOzY3Sfhyt9kLZ7uTxu5MTmhsDVPPZRQ0+x76WSBvglRa/0XDWVDObBryzNwtG0tDGrg+X1ZM6GGEJgnnWfFpvppQlvuudEvR77Hm7ObCEhplTTqQ2yf7x+K/R9+x/QeHfo9//GJ/ylFeXbaQN3z35eudsExYmI9bXHn2VrpD3EScf1zM2ZfwhbIwEkd3MU3COc1oIs4waf62td0tgzCj09j18q5LwYmwBdmciSNTfBSxy1zRJdkcM+bGafi8ntUQloR6iyQNX+m/NC0Iya18keUzTTahz43QLeU90iSjq4pJuukz5eI6kzfRZLUJYtFlg2xxuNwCsAlzUAk+MSW0roRuthl6GxfxQYbmiDARECYQmYo/Yx5G2foSyN2I3CsB4kkW8BmPXFYNEXTbu7w09iUmO640CdnjS+hKVP6D9e5Kq474KU3EbV0m8db43Ii3z4V461hfqsTb+3y5h3isy5XOR3HlXiqhJ7Eu6UB10pFmCXXnlykdGJ2bdJj4XIBHRzuznXZYji7koStv45iFnpuFPLpM4IlzdsDDLP5EwONjnakAP27Gu60LdxNK/G/qSdNpKB26XEnGH8WVuCGVSJcroSefEjdKX/LdinCnm2Sf/r7/4z3v5vcuRJvdsnAWCdszzzI+FyfWvW/zOomwRnyt1zTySwuPafQUhFHZUpZnbsrjxzC/k7Wyil6Vo1qdFb1m9L1oycSf+mVdpiZWCWViEXrdLdPsc73s+i0HrLLHt3eDDLLGlC8Sj8pe2wAKz7hvlWaS29p5NsuDMs+Wh2LELR2be+wgYwYgRqyRr4KSkTXZ5KvtiKnQ8cy49/O9YBc+bLByHaTCaUhF5V+0dHxHUGxnfwAPgQIGGo8PDFQ4oTNxSLngMonUSxwsq0wXnkfTtCtkuQQJi++mAfVlP3G6okTt8XMa0ldiqluUiCoW45asEePAQD2xhi1lHmMI1mA19GsOycnVzG6KEz6ZmiHck5qByGtSM2yeYv2E1cdpnu5oR+wwOuh06FhKxtRWnNSKkTZxqhFBvejAZL3HhVDm4u8shg3T8KZapnvFQ2qM1EpWU6hAPq8O1BNU6pOSch69UN3ohEpXdtVYqzoyhGtCcjRDQOE0MQTWziEYKuesPr1YL0eyhNCjPPWlQaThothUq/pfE01lrwUeWzdGT0lzkFqX0yRfyBoCPbg57BW9y8jcrQOENE6/jGFQw/gUqMG94SRYRD+HV7dTqJGSAyHktFWjAwP1pUY1F6wXEbi5WyafCg1ZV0e9HJjOxVGZWZ0vLWq205oWPB6GFoxOQQtMvS9YUEhfgqIOpCu9IUMgAl+wugpKF1qAoGiiBQjKELQguA+7YEEZ9yUo6kC6BGU8BCLwfZ6roHShBQiKJlrAlmcQWup31x6fx4xm9PJIcfoiRR1IFynOAKQQuPOZMJ4OjMfHKMbsYkLalpxtdGAgTbwRNEAxhuzZO0lF8n9T5sq3Gk/BnIKKbSnvEzfWOPPAQDXM9YYF3C+d+EnEGWTCWH17tG0mDAbSlAnjIRTEhpulYZ8cnMHa0poMNZfRRYa6tgxDBtwjXe42GuOettFgIE2F/nIevYjAjdF1G92FFiAommhR63LD0AK/+nPBgmL3JSjqQLqyEXsIROC3Ia+C0oUWICiaaAGCMggt11cRKh6/6UtQ1IF0CcrNEIhcX0XYLyhtaQGCookWsOUZhBb4avnFF/qJ0RMpYCBdhVejEynicPuLYUXz7c+uoYf/AQ==</diagram></mxfile>
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
.. _bluetooth_mesh_models:
|
||||
|
||||
Mesh models
|
||||
###########
|
||||
|
||||
Foundation models
|
||||
#################
|
||||
*****************
|
||||
|
||||
The Bluetooth mesh specification defines four foundation models that can be
|
||||
used by network administrators to configure and diagnose mesh nodes.
|
||||
|
|
@ -13,3 +16,13 @@ used by network administrators to configure and diagnose mesh nodes.
|
|||
cfg_cli
|
||||
health_srv
|
||||
health_cli
|
||||
|
||||
Model specification models
|
||||
**************************
|
||||
|
||||
In addition to the Foundation models defined in the Bluetooth Mesh specification, the Bluetooth Mesh Model specification defines several models, some of which are implemented in Zephyr:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
blob
|
||||
|
|
|
|||
|
|
@ -20,8 +20,11 @@
|
|||
#include <zephyr/bluetooth/mesh/cfg.h>
|
||||
#include <zephyr/bluetooth/mesh/cfg_srv.h>
|
||||
#include <zephyr/bluetooth/mesh/health_srv.h>
|
||||
#include <zephyr/bluetooth/mesh/blob_srv.h>
|
||||
#include <zephyr/bluetooth/mesh/cfg_cli.h>
|
||||
#include <zephyr/bluetooth/mesh/health_cli.h>
|
||||
#include <zephyr/bluetooth/mesh/blob_cli.h>
|
||||
#include <zephyr/bluetooth/mesh/blob_io_flash.h>
|
||||
#include <zephyr/bluetooth/mesh/proxy.h>
|
||||
#include <zephyr/bluetooth/mesh/heartbeat.h>
|
||||
#include <zephyr/bluetooth/mesh/cdb.h>
|
||||
|
|
|
|||
|
|
@ -180,6 +180,8 @@ struct bt_mesh_elem {
|
|||
#define BT_MESH_MODEL_ID_LIGHT_LC_SRV 0x130f
|
||||
#define BT_MESH_MODEL_ID_LIGHT_LC_SETUPSRV 0x1310
|
||||
#define BT_MESH_MODEL_ID_LIGHT_LC_CLI 0x1311
|
||||
#define BT_MESH_MODEL_ID_BLOB_SRV 0x1400
|
||||
#define BT_MESH_MODEL_ID_BLOB_CLI 0x1401
|
||||
|
||||
/** Model opcode handler. */
|
||||
struct bt_mesh_model_op {
|
||||
|
|
|
|||
262
include/zephyr/bluetooth/mesh/blob.h
Normal file
262
include/zephyr/bluetooth/mesh/blob.h
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @defgroup bt_mesh_blob Bluetooth mesh BLOB model API
|
||||
* @{
|
||||
* @brief API for the Bluetooth mesh Binary Large Object Transfer models.
|
||||
*/
|
||||
|
||||
#ifndef ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_H__
|
||||
#define ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_H__
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_BT_MESH_BLOB_CHUNK_COUNT_MAX
|
||||
#define CONFIG_BT_MESH_BLOB_CHUNK_COUNT_MAX 0
|
||||
#endif
|
||||
|
||||
/** BLOB transfer mode. */
|
||||
enum bt_mesh_blob_xfer_mode {
|
||||
/** No valid transfer mode. */
|
||||
BT_MESH_BLOB_XFER_MODE_NONE,
|
||||
/** Push mode (Push BLOB Transfer Mode). */
|
||||
BT_MESH_BLOB_XFER_MODE_PUSH,
|
||||
/** Pull mode (Pull BLOB Transfer Mode). */
|
||||
BT_MESH_BLOB_XFER_MODE_PULL,
|
||||
/** Both modes are valid. */
|
||||
BT_MESH_BLOB_XFER_MODE_ALL,
|
||||
};
|
||||
|
||||
/** Transfer phase. */
|
||||
enum bt_mesh_blob_xfer_phase {
|
||||
/** The BLOB Transfer Server is awaiting configuration. */
|
||||
BT_MESH_BLOB_XFER_PHASE_INACTIVE,
|
||||
/** The BLOB Transfer Server is ready to receive a BLOB transfer. */
|
||||
BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START,
|
||||
/** The BLOB Transfer Server is waiting for the next block of data. */
|
||||
BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_BLOCK,
|
||||
/** The BLOB Transfer Server is waiting for the next chunk of data. */
|
||||
BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_CHUNK,
|
||||
/** The BLOB was transferred successfully. */
|
||||
BT_MESH_BLOB_XFER_PHASE_COMPLETE,
|
||||
/** The BLOB transfer is paused. */
|
||||
BT_MESH_BLOB_XFER_PHASE_SUSPENDED,
|
||||
};
|
||||
|
||||
/** BLOB model status codes. */
|
||||
enum bt_mesh_blob_status {
|
||||
/** The message was processed successfully. */
|
||||
BT_MESH_BLOB_SUCCESS,
|
||||
/** The Block Number field value is not within the range of blocks being
|
||||
* transferred.
|
||||
*/
|
||||
BT_MESH_BLOB_ERR_INVALID_BLOCK_NUM,
|
||||
/** The block size is smaller than the size indicated by the Min Block
|
||||
* Size Log state or is larger than the size indicated by the Max Block
|
||||
* Size Log state.
|
||||
*/
|
||||
BT_MESH_BLOB_ERR_INVALID_BLOCK_SIZE,
|
||||
/** The chunk size exceeds the size indicated by the Max Chunk Size
|
||||
* state, or the number of chunks exceeds the number specified by the
|
||||
* Max Total Chunks state.
|
||||
*/
|
||||
BT_MESH_BLOB_ERR_INVALID_CHUNK_SIZE,
|
||||
/** The operation cannot be performed while the server is in the current
|
||||
* phase.
|
||||
*/
|
||||
BT_MESH_BLOB_ERR_WRONG_PHASE,
|
||||
/** A parameter value in the message cannot be accepted. */
|
||||
BT_MESH_BLOB_ERR_INVALID_PARAM,
|
||||
/** The message contains a BLOB ID value that is not expected. */
|
||||
BT_MESH_BLOB_ERR_WRONG_BLOB_ID,
|
||||
/** There is not enough space available in memory to receive the BLOB.
|
||||
*/
|
||||
BT_MESH_BLOB_ERR_BLOB_TOO_LARGE,
|
||||
/** The transfer mode is not supported by the BLOB Transfer Server
|
||||
* model.
|
||||
*/
|
||||
BT_MESH_BLOB_ERR_UNSUPPORTED_MODE,
|
||||
/** An internal error occurred on the node. */
|
||||
BT_MESH_BLOB_ERR_INTERNAL,
|
||||
/** The requested information cannot be provided while the server is in
|
||||
* the current phase.
|
||||
*/
|
||||
BT_MESH_BLOB_ERR_INFO_UNAVAILABLE,
|
||||
};
|
||||
|
||||
/** BLOB transfer data block. */
|
||||
struct bt_mesh_blob_block {
|
||||
/** Block size in bytes */
|
||||
size_t size;
|
||||
/** Offset in bytes from the start of the BLOB. */
|
||||
off_t offset;
|
||||
/** Block number */
|
||||
uint16_t number;
|
||||
/** Number of chunks in block. */
|
||||
uint16_t chunk_count;
|
||||
/** Bitmap of missing chunks. */
|
||||
uint8_t missing[ceiling_fraction(CONFIG_BT_MESH_BLOB_CHUNK_COUNT_MAX,
|
||||
8)];
|
||||
};
|
||||
|
||||
/** BLOB data chunk. */
|
||||
struct bt_mesh_blob_chunk {
|
||||
/** Offset of the chunk data from the start of the block. */
|
||||
off_t offset;
|
||||
/** Chunk data size. */
|
||||
size_t size;
|
||||
/** Chunk data. */
|
||||
uint8_t *data;
|
||||
};
|
||||
|
||||
/** BLOB transfer. */
|
||||
struct bt_mesh_blob_xfer {
|
||||
/** BLOB ID. */
|
||||
uint64_t id;
|
||||
/** Total BLOB size in bytes. */
|
||||
size_t size;
|
||||
/** BLOB transfer mode. */
|
||||
enum bt_mesh_blob_xfer_mode mode;
|
||||
/* Logarithmic representation of the block size. */
|
||||
uint8_t block_size_log;
|
||||
/** Base chunk size. May be smaller for the last chunk. */
|
||||
uint16_t chunk_size;
|
||||
};
|
||||
|
||||
/** BLOB stream interaction mode. */
|
||||
enum bt_mesh_blob_io_mode {
|
||||
/** Read data from the stream. */
|
||||
BT_MESH_BLOB_READ,
|
||||
/** Write data to the stream. */
|
||||
BT_MESH_BLOB_WRITE,
|
||||
};
|
||||
|
||||
/** BLOB stream. */
|
||||
struct bt_mesh_blob_io {
|
||||
/** @brief Open callback.
|
||||
*
|
||||
* Called when the reader is opened for reading.
|
||||
*
|
||||
* @param io BLOB stream.
|
||||
* @param xfer BLOB transfer.
|
||||
* @param mode Direction of the stream (read/write).
|
||||
*
|
||||
* @return 0 on success, or (negative) error code otherwise.
|
||||
*/
|
||||
int (*open)(const struct bt_mesh_blob_io *io,
|
||||
const struct bt_mesh_blob_xfer *xfer,
|
||||
enum bt_mesh_blob_io_mode mode);
|
||||
|
||||
/** @brief Close callback.
|
||||
*
|
||||
* Called when the reader is closed.
|
||||
*
|
||||
* @param io BLOB stream.
|
||||
* @param xfer BLOB transfer.
|
||||
*/
|
||||
void (*close)(const struct bt_mesh_blob_io *io,
|
||||
const struct bt_mesh_blob_xfer *xfer);
|
||||
|
||||
/** @brief Block start callback.
|
||||
*
|
||||
* Called when a new block is opened for sending. Each block is only
|
||||
* sent once, and are always sent in increasing order. The data chunks
|
||||
* inside a single block may be requested out of order and multiple
|
||||
* times.
|
||||
*
|
||||
* @param io BLOB stream.
|
||||
* @param xfer BLOB transfer.
|
||||
* @param block Block that was started.
|
||||
*/
|
||||
int (*block_start)(const struct bt_mesh_blob_io *io,
|
||||
const struct bt_mesh_blob_xfer *xfer,
|
||||
const struct bt_mesh_blob_block *block);
|
||||
|
||||
/** @brief Block end callback.
|
||||
*
|
||||
* Called when the current block has been transmitted in full.
|
||||
* No data from this block will be requested again, and the application
|
||||
* data associated with this block may be discarded.
|
||||
*
|
||||
* @param io BLOB stream.
|
||||
* @param xfer BLOB transfer.
|
||||
* @param block Block that finished sending.
|
||||
*/
|
||||
void (*block_end)(const struct bt_mesh_blob_io *io,
|
||||
const struct bt_mesh_blob_xfer *xfer,
|
||||
const struct bt_mesh_blob_block *block);
|
||||
|
||||
/** @brief Chunk data write callback.
|
||||
*
|
||||
* Used by the BLOB Transfer Server on incoming data.
|
||||
*
|
||||
* Each block is divided into chunks of data. This callback is called
|
||||
* when a new chunk of data is received. Chunks may be received in
|
||||
* any order within their block.
|
||||
*
|
||||
* If the callback returns successfully, this chunk will be marked as
|
||||
* received, and will not be received again unless the block is
|
||||
* restarted due to a transfer suspension. If the callback returns a
|
||||
* non-zero value, the chunk remains unreceived, and the BLOB Transfer
|
||||
* Client will attempt to resend it later.
|
||||
*
|
||||
* Note that the Client will only perform a limited number of attempts
|
||||
* at delivering a chunk before dropping a Target node from the transfer.
|
||||
* The number of retries performed by the Client is implementation
|
||||
* specific.
|
||||
*
|
||||
* @param io BLOB stream.
|
||||
* @param xfer BLOB transfer.
|
||||
* @param block Block the chunk is part of.
|
||||
* @param chunk Received chunk.
|
||||
*
|
||||
* @return 0 on success, or (negative) error code otherwise.
|
||||
*/
|
||||
int (*wr)(const struct bt_mesh_blob_io *io,
|
||||
const struct bt_mesh_blob_xfer *xfer,
|
||||
const struct bt_mesh_blob_block *block,
|
||||
const struct bt_mesh_blob_chunk *chunk);
|
||||
|
||||
/** @brief Chunk data read callback.
|
||||
*
|
||||
* Used by the BLOB Transfer Client to fetch outgoing data.
|
||||
*
|
||||
* The Client calls the chunk data request callback to populate a chunk
|
||||
* message going out to the Target nodes. The data request callback
|
||||
* may be called out of order and multiple times for each offset, and
|
||||
* cannot be used as an indication of progress.
|
||||
*
|
||||
* Returning a non-zero status code on the chunk data request callback
|
||||
* results in termination of the transfer.
|
||||
*
|
||||
* @param io BLOB stream.
|
||||
* @param xfer BLOB transfer.
|
||||
* @param block Block the chunk is part of.
|
||||
* @param chunk Chunk to get the data of. The buffer pointer to by the
|
||||
* @c data member should be filled by the callback.
|
||||
*
|
||||
* @return 0 on success, or (negative) error code otherwise.
|
||||
*/
|
||||
int (*rd)(const struct bt_mesh_blob_io *io,
|
||||
const struct bt_mesh_blob_xfer *xfer,
|
||||
const struct bt_mesh_blob_block *block,
|
||||
const struct bt_mesh_blob_chunk *chunk);
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_H__ */
|
||||
|
||||
/** @} */
|
||||
433
include/zephyr/bluetooth/mesh/blob_cli.h
Normal file
433
include/zephyr/bluetooth/mesh/blob_cli.h
Normal file
|
|
@ -0,0 +1,433 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @defgroup bt_mesh_blob_cli BLOB Transfer Client model API
|
||||
* @{
|
||||
* @brief API for the Binary Large Object Transfer Client model.
|
||||
*/
|
||||
|
||||
#ifndef ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_CLI_H_
|
||||
#define ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_CLI_H_
|
||||
|
||||
#include <zephyr/bluetooth/mesh/access.h>
|
||||
#include <zephyr/bluetooth/mesh/blob.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct bt_mesh_blob_cli;
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief BLOB Transfer Client model Composition Data entry.
|
||||
*
|
||||
* @param _cli Pointer to a @ref bt_mesh_blob_cli instance.
|
||||
*/
|
||||
#define BT_MESH_MODEL_BLOB_CLI(_cli) \
|
||||
BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_BLOB_CLI, _bt_mesh_blob_cli_op, \
|
||||
NULL, _cli, &_bt_mesh_blob_cli_cb)
|
||||
|
||||
/** Target node's Pull mode (Pull BLOB Transfer Mode) context used
|
||||
* while sending chunks to the Target node.
|
||||
*/
|
||||
struct bt_mesh_blob_target_pull {
|
||||
/** Timestamp when the Block Report Timeout Timer expires for this Target node. */
|
||||
int64_t block_report_timestamp;
|
||||
|
||||
/** Missing chunks reported by this Target node. */
|
||||
uint8_t missing[ceiling_fraction(CONFIG_BT_MESH_BLOB_CHUNK_COUNT_MAX, 8)];
|
||||
};
|
||||
|
||||
/** BLOB Transfer Client Target node. */
|
||||
struct bt_mesh_blob_target {
|
||||
/** Linked list node */
|
||||
sys_snode_t n;
|
||||
|
||||
/** Target node address. */
|
||||
uint16_t addr;
|
||||
|
||||
/** Target node's Pull mode context.
|
||||
* Needs to be initialized when sending a BLOB in Pull mode.
|
||||
*/
|
||||
struct bt_mesh_blob_target_pull *pull;
|
||||
|
||||
/** BLOB transfer status, see @ref bt_mesh_blob_status. */
|
||||
uint8_t status;
|
||||
|
||||
uint8_t procedure_complete:1, /* Procedure has been completed. */
|
||||
acked:1, /* Message has been acknowledged. Not used when sending. */
|
||||
timedout:1, /* Target node didn't respond after specified timeout. */
|
||||
skip:1; /* Skip Target node from broadcast. */
|
||||
};
|
||||
|
||||
/** BLOB transfer information.
|
||||
*
|
||||
* If @c phase is @ref BT_MESH_BLOB_XFER_PHASE_INACTIVE,
|
||||
* the fields below @c phase are not initialized.
|
||||
* If @c phase is @ref BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START,
|
||||
* the fields below @c id are not initialized.
|
||||
*/
|
||||
struct bt_mesh_blob_xfer_info {
|
||||
/** BLOB transfer status. */
|
||||
enum bt_mesh_blob_status status;
|
||||
|
||||
/** BLOB transfer mode. */
|
||||
enum bt_mesh_blob_xfer_mode mode;
|
||||
|
||||
/** BLOB transfer phase. */
|
||||
enum bt_mesh_blob_xfer_phase phase;
|
||||
|
||||
/** BLOB ID. */
|
||||
uint64_t id;
|
||||
|
||||
/** BLOB size in octets. */
|
||||
uint32_t size;
|
||||
|
||||
/** Logarithmic representation of the block size. */
|
||||
uint8_t block_size_log;
|
||||
|
||||
/** MTU size in octets. */
|
||||
uint16_t mtu_size;
|
||||
|
||||
/** Bit field indicating blocks that were not received. */
|
||||
const uint8_t *missing_blocks;
|
||||
};
|
||||
|
||||
/** BLOB Transfer Client transfer inputs. */
|
||||
struct bt_mesh_blob_cli_inputs {
|
||||
/** Linked list of Target nodes. Each node should point to @ref
|
||||
* bt_mesh_blob_target::n.
|
||||
*/
|
||||
sys_slist_t targets;
|
||||
|
||||
/** AppKey index to send with. */
|
||||
uint16_t app_idx;
|
||||
|
||||
/** Group address destination for the BLOB transfer, or @ref
|
||||
* BT_MESH_ADDR_UNASSIGNED to send every message to each Target
|
||||
* node individually.
|
||||
*/
|
||||
uint16_t group;
|
||||
|
||||
/** Time to live value of BLOB transfer messages. */
|
||||
uint8_t ttl;
|
||||
|
||||
/** Additional response time for the Target nodes, in 10-second increments.
|
||||
*
|
||||
* The extra time can be used to give the Target nodes more time to respond
|
||||
* to messages from the Client. The actual timeout will be calculated
|
||||
* according to the following formula:
|
||||
*
|
||||
* @verbatim
|
||||
* timeout = 20 seconds + (10 seconds * timeout_base) + (100 ms * TTL)
|
||||
* @endverbatim
|
||||
*
|
||||
* If a Target node fails to respond to a message from the Client within the
|
||||
* configured transfer timeout, the Target node is dropped.
|
||||
*/
|
||||
uint16_t timeout_base;
|
||||
};
|
||||
|
||||
/** Transfer capabilities of a Target node. */
|
||||
struct bt_mesh_blob_cli_caps {
|
||||
/** Max BLOB size. */
|
||||
size_t max_size;
|
||||
|
||||
/** Logarithmic representation of the minimum block size. */
|
||||
uint8_t min_block_size_log;
|
||||
|
||||
/** Logarithmic representation of the maximum block size. */
|
||||
uint8_t max_block_size_log;
|
||||
|
||||
/** Max number of chunks per block. */
|
||||
uint16_t max_chunks;
|
||||
|
||||
/** Max chunk size. */
|
||||
uint16_t max_chunk_size;
|
||||
|
||||
/** Max MTU size. */
|
||||
uint16_t mtu_size;
|
||||
|
||||
/** Supported transfer modes. */
|
||||
enum bt_mesh_blob_xfer_mode modes;
|
||||
};
|
||||
|
||||
/** BLOB Transfer Client state. */
|
||||
enum bt_mesh_blob_cli_state {
|
||||
/** No transfer is active. */
|
||||
BT_MESH_BLOB_CLI_STATE_NONE,
|
||||
/** Retrieving transfer capabilities. */
|
||||
BT_MESH_BLOB_CLI_STATE_CAPS_GET,
|
||||
/** Sending transfer start. */
|
||||
BT_MESH_BLOB_CLI_STATE_START,
|
||||
/** Sending block start. */
|
||||
BT_MESH_BLOB_CLI_STATE_BLOCK_START,
|
||||
/** Sending block chunks. */
|
||||
BT_MESH_BLOB_CLI_STATE_BLOCK_SEND,
|
||||
/** Checking block status. */
|
||||
BT_MESH_BLOB_CLI_STATE_BLOCK_CHECK,
|
||||
/** Checking transfer status. */
|
||||
BT_MESH_BLOB_CLI_STATE_XFER_CHECK,
|
||||
/** Cancelling transfer. */
|
||||
BT_MESH_BLOB_CLI_STATE_CANCEL,
|
||||
/** Transfer is suspended. */
|
||||
BT_MESH_BLOB_CLI_STATE_SUSPENDED,
|
||||
/** Checking transfer progress. */
|
||||
BT_MESH_BLOB_CLI_STATE_XFER_PROGRESS_GET,
|
||||
};
|
||||
|
||||
/** Event handler callbacks for the BLOB Transfer Client model.
|
||||
*
|
||||
* All handlers are optional.
|
||||
*/
|
||||
struct bt_mesh_blob_cli_cb {
|
||||
/** @brief Capabilities retrieval completion callback.
|
||||
*
|
||||
* Called when the capabilities retrieval procedure completes, indicating that
|
||||
* a common set of acceptable transfer parameters have been established
|
||||
* for the given list of Target nodes. All compatible Target nodes have
|
||||
* status code @ref BT_MESH_BLOB_SUCCESS.
|
||||
*
|
||||
* @param cli BLOB Transfer Client instance.
|
||||
* @param caps Safe transfer capabilities if the transfer capabilities
|
||||
* of at least one Target node has satisfied the Client, or NULL otherwise.
|
||||
*/
|
||||
void (*caps)(struct bt_mesh_blob_cli *cli,
|
||||
const struct bt_mesh_blob_cli_caps *caps);
|
||||
|
||||
/** @brief Target node loss callback.
|
||||
*
|
||||
* Called whenever a Target node has been lost due to some error in the
|
||||
* transfer. Losing a Target node is not considered a fatal error for
|
||||
* the Client until all Target nodes have been lost.
|
||||
*
|
||||
* @param cli BLOB Transfer Client instance.
|
||||
* @param target Target node that was lost.
|
||||
* @param reason Reason for the Target node loss.
|
||||
*/
|
||||
void (*lost_target)(struct bt_mesh_blob_cli *cli,
|
||||
struct bt_mesh_blob_target *target,
|
||||
enum bt_mesh_blob_status reason);
|
||||
|
||||
/** @brief Transfer is suspended.
|
||||
*
|
||||
* Called when the transfer is suspended due to response timeout from all Target nodes.
|
||||
*
|
||||
* @param cli BLOB Transfer Client instance.
|
||||
*/
|
||||
void (*suspended)(struct bt_mesh_blob_cli *cli);
|
||||
|
||||
/** @brief Transfer end callback.
|
||||
*
|
||||
* Called when the transfer ends.
|
||||
*
|
||||
* @param cli BLOB Transfer Client instance.
|
||||
* @param xfer Completed transfer.
|
||||
* @param success Status of the transfer.
|
||||
* Is @c true if at least one Target
|
||||
* node received the whole transfer.
|
||||
*/
|
||||
void (*end)(struct bt_mesh_blob_cli *cli,
|
||||
const struct bt_mesh_blob_xfer *xfer, bool success);
|
||||
|
||||
/** @brief Transfer progress callback
|
||||
*
|
||||
* The content of @c info is invalidated upon exit from the callback.
|
||||
* Therefore it needs to be copied if it is planned to be used later.
|
||||
*
|
||||
* @param cli BLOB Transfer Client instance.
|
||||
* @param target Target node that responded to the request.
|
||||
* @param info BLOB transfer information.
|
||||
*/
|
||||
void (*xfer_progress)(struct bt_mesh_blob_cli *cli,
|
||||
struct bt_mesh_blob_target *target,
|
||||
const struct bt_mesh_blob_xfer_info *info);
|
||||
|
||||
/** @brief End of Get Transfer Progress procedure.
|
||||
*
|
||||
* Called when all Target nodes have responded or the procedure timed-out.
|
||||
*
|
||||
* @param cli BLOB Transfer Client instance.
|
||||
*/
|
||||
void (*xfer_progress_complete)(struct bt_mesh_blob_cli *cli);
|
||||
};
|
||||
|
||||
/** @cond INTERNAL_HIDDEN */
|
||||
struct blob_cli_broadcast_ctx {
|
||||
/** Called for every Target node in unicast mode, or once in case of multicast mode. */
|
||||
void (*send)(struct bt_mesh_blob_cli *cli, uint16_t dst);
|
||||
/** Called after every @ref blob_cli_broadcast_ctx::send callback. */
|
||||
void (*send_complete)(struct bt_mesh_blob_cli *cli, uint16_t dst);
|
||||
/** If @ref blob_cli_broadcast_ctx::acked is true, called after all Target nodes
|
||||
* have confirmed reception by @ref blob_cli_broadcast_rsp. Otherwise, called
|
||||
* after transmission has been completed.
|
||||
*/
|
||||
void (*next)(struct bt_mesh_blob_cli *cli);
|
||||
/** If true, every transmission needs to be confirmed by @ref blob_cli_broadcast_rsp before
|
||||
* @ref blob_cli_broadcast_ctx::next is called.
|
||||
*/
|
||||
bool acked;
|
||||
/** If true, the message is always sent in a unicast way. */
|
||||
bool force_unicast;
|
||||
/** If true, non-responsive Target nodes won't be dropped after transfer has timed out. */
|
||||
bool optional;
|
||||
/** Set to true by the BLOB Transfer Client between blob_cli_broadcast
|
||||
* and broadcast_complete calls.
|
||||
*/
|
||||
bool is_inited;
|
||||
};
|
||||
/** INTERNAL_HIDDEN @endcond */
|
||||
|
||||
/** BLOB Transfer Client model instance. */
|
||||
struct bt_mesh_blob_cli {
|
||||
/** Event handler callbacks */
|
||||
const struct bt_mesh_blob_cli_cb *cb;
|
||||
|
||||
/* Runtime state */
|
||||
struct bt_mesh_model *mod;
|
||||
|
||||
struct {
|
||||
struct bt_mesh_blob_target *target;
|
||||
struct blob_cli_broadcast_ctx ctx;
|
||||
struct k_work_delayable retry;
|
||||
/* Represents Client Timeout timer in a timestamp. Used in Pull mode only. */
|
||||
int64_t cli_timestamp;
|
||||
struct k_work complete;
|
||||
uint16_t pending;
|
||||
uint8_t retries;
|
||||
uint8_t sending : 1,
|
||||
cancelled : 1;
|
||||
} tx;
|
||||
|
||||
const struct bt_mesh_blob_io *io;
|
||||
const struct bt_mesh_blob_cli_inputs *inputs;
|
||||
const struct bt_mesh_blob_xfer *xfer;
|
||||
uint16_t block_count;
|
||||
uint16_t chunk_idx;
|
||||
uint16_t mtu_size;
|
||||
enum bt_mesh_blob_cli_state state;
|
||||
struct bt_mesh_blob_block block;
|
||||
struct bt_mesh_blob_cli_caps caps;
|
||||
};
|
||||
|
||||
/** @brief Retrieve transfer capabilities for a list of Target nodes.
|
||||
*
|
||||
* Queries the availability and capabilities of all Target nodes, producing a
|
||||
* cumulative set of transfer capabilities for the Target nodes, and returning
|
||||
* it through the @ref bt_mesh_blob_cli_cb::caps callback.
|
||||
*
|
||||
* Retrieving the capabilities may take several seconds, depending on the
|
||||
* number of Target nodes and mesh network performance. The end of the procedure
|
||||
* is indicated through the @ref bt_mesh_blob_cli_cb::caps callback.
|
||||
*
|
||||
* This procedure is not required, but strongly recommended as a
|
||||
* preparation for a transfer to maximize performance and the chances of
|
||||
* success.
|
||||
*
|
||||
* @param cli BLOB Transfer Client instance.
|
||||
* @param inputs Statically allocated BLOB Transfer Client transfer inputs.
|
||||
*
|
||||
* @return 0 on success, or (negative) error code otherwise.
|
||||
*/
|
||||
int bt_mesh_blob_cli_caps_get(struct bt_mesh_blob_cli *cli,
|
||||
const struct bt_mesh_blob_cli_inputs *inputs);
|
||||
|
||||
/** @brief Perform a BLOB transfer.
|
||||
*
|
||||
* Starts sending the transfer to the Target nodes. Only Target nodes with a
|
||||
* @c status of @ref BT_MESH_BLOB_SUCCESS will be considered.
|
||||
*
|
||||
* The transfer will keep going either until all Target nodes have been dropped, or
|
||||
* the full BLOB has been sent.
|
||||
*
|
||||
* The BLOB transfer may take several minutes, depending on the number of
|
||||
* Target nodes, size of the BLOB and mesh network performance. The end of the
|
||||
* transfer is indicated through the @ref bt_mesh_blob_cli_cb::end callback.
|
||||
*
|
||||
* A Client only supports one transfer at the time.
|
||||
*
|
||||
* @param cli BLOB Transfer Client instance.
|
||||
* @param inputs Statically allocated BLOB Transfer Client transfer inputs.
|
||||
* @param xfer Statically allocated transfer parameters.
|
||||
* @param io BLOB stream to read the transfer from.
|
||||
*
|
||||
* @return 0 on success, or (negative) error code otherwise.
|
||||
*/
|
||||
int bt_mesh_blob_cli_send(struct bt_mesh_blob_cli *cli,
|
||||
const struct bt_mesh_blob_cli_inputs *inputs,
|
||||
const struct bt_mesh_blob_xfer *xfer,
|
||||
const struct bt_mesh_blob_io *io);
|
||||
|
||||
/** @brief Suspend the active transfer.
|
||||
*
|
||||
* @param cli BLOB Transfer Client instance.
|
||||
*
|
||||
* @return 0 on success, or (negative) error code otherwise.
|
||||
*/
|
||||
int bt_mesh_blob_cli_suspend(struct bt_mesh_blob_cli *cli);
|
||||
|
||||
/** @brief Resume the suspended transfer.
|
||||
*
|
||||
* @param cli BLOB Transfer Client instance.
|
||||
*
|
||||
* @return 0 on success, or (negative) error code otherwise.
|
||||
*/
|
||||
int bt_mesh_blob_cli_resume(struct bt_mesh_blob_cli *cli);
|
||||
|
||||
/** @brief Cancel an ongoing transfer.
|
||||
*
|
||||
* @param cli BLOB Transfer Client instance.
|
||||
*/
|
||||
void bt_mesh_blob_cli_cancel(struct bt_mesh_blob_cli *cli);
|
||||
|
||||
/** @brief Get the progress of BLOB transfer.
|
||||
*
|
||||
* This function can only be used if the BLOB Transfer Client is currently
|
||||
* not performing a BLOB transfer.
|
||||
* To get progress of the active BLOB transfer, use the
|
||||
* @ref bt_mesh_blob_cli_xfer_progress_active_get function.
|
||||
*
|
||||
* @param cli BLOB Transfer Client instance.
|
||||
* @param inputs Statically allocated BLOB Transfer Client transfer inputs.
|
||||
*
|
||||
* @return 0 on success, or (negative) error code otherwise.
|
||||
*/
|
||||
int bt_mesh_blob_cli_xfer_progress_get(struct bt_mesh_blob_cli *cli,
|
||||
const struct bt_mesh_blob_cli_inputs *inputs);
|
||||
|
||||
/** @brief Get the current progress of the active transfer in percent.
|
||||
*
|
||||
* @param cli BLOB Transfer Client instance.
|
||||
*
|
||||
* @return The current transfer progress, or 0 if no transfer is active.
|
||||
*/
|
||||
uint8_t bt_mesh_blob_cli_xfer_progress_active_get(struct bt_mesh_blob_cli *cli);
|
||||
|
||||
/** @brief Get the current state of the BLOB Transfer Client.
|
||||
*
|
||||
* @param cli BLOB Transfer Client instance.
|
||||
*
|
||||
* @return true if the BLOB Transfer Client is currently participating in a transfer or
|
||||
* retrieving the capabilities and false otherwise.
|
||||
*/
|
||||
bool bt_mesh_blob_cli_is_busy(struct bt_mesh_blob_cli *cli);
|
||||
|
||||
/** @cond INTERNAL_HIDDEN */
|
||||
extern const struct bt_mesh_model_op _bt_mesh_blob_cli_op[];
|
||||
extern const struct bt_mesh_model_cb _bt_mesh_blob_cli_cb;
|
||||
/** @endcond */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_CLI_H_ */
|
||||
|
||||
/** @} */
|
||||
54
include/zephyr/bluetooth/mesh/blob_io_flash.h
Normal file
54
include/zephyr/bluetooth/mesh/blob_io_flash.h
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @defgroup bt_mesh_blob_io_flash Bluetooth mesh BLOB flash stream
|
||||
* @{
|
||||
* @brief
|
||||
*/
|
||||
|
||||
#ifndef ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_IO_FLASH_H__
|
||||
#define ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_IO_FLASH_H__
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** BLOB flash stream. */
|
||||
struct bt_mesh_blob_io_flash {
|
||||
/** Flash area ID to write the BLOB to. */
|
||||
uint8_t area_id;
|
||||
/** Active stream mode. */
|
||||
enum bt_mesh_blob_io_mode mode;
|
||||
/** Offset into the flash area to place the BLOB at (in bytes). */
|
||||
off_t offset;
|
||||
|
||||
|
||||
/* Internal flash area pointer. */
|
||||
const struct flash_area *area;
|
||||
/* BLOB stream. */
|
||||
struct bt_mesh_blob_io io;
|
||||
};
|
||||
|
||||
/** @brief Initialize a flash stream.
|
||||
*
|
||||
* @param flash Flash stream.
|
||||
* @param area_id Flash partition identifier. See @ref flash_area_open.
|
||||
* @param offset Offset into the flash area, in bytes.
|
||||
*
|
||||
* @return 0 on success or (negative) error code otherwise.
|
||||
*/
|
||||
int bt_mesh_blob_io_flash_init(struct bt_mesh_blob_io_flash *flash,
|
||||
uint8_t area_id, off_t offset);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_IO_FLASH_H__ */
|
||||
|
||||
/** @} */
|
||||
221
include/zephyr/bluetooth/mesh/blob_srv.h
Normal file
221
include/zephyr/bluetooth/mesh/blob_srv.h
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @defgroup bt_mesh_blob_srv Bluetooth mesh BLOB Transfer Server model API
|
||||
* @{
|
||||
* @brief API for the Bluetooth Mesh Binary Large Object (BLOB) Transfer Server model.
|
||||
*/
|
||||
|
||||
#ifndef ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_SRV_H_
|
||||
#define ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_SRV_H_
|
||||
|
||||
#include <zephyr/bluetooth/mesh/access.h>
|
||||
#include <zephyr/bluetooth/mesh/blob.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct bt_mesh_blob_srv;
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief Max number of blocks in a single transfer.
|
||||
*/
|
||||
#if defined(CONFIG_BT_MESH_BLOB_SRV)
|
||||
#define BT_MESH_BLOB_BLOCKS_MAX \
|
||||
(ceiling_fraction(CONFIG_BT_MESH_BLOB_SIZE_MAX, \
|
||||
CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MIN))
|
||||
#else
|
||||
#define BT_MESH_BLOB_BLOCKS_MAX 1
|
||||
#endif
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief BLOB Transfer Server model composition data entry.
|
||||
*
|
||||
* @param _srv Pointer to a @ref bt_mesh_blob_srv instance.
|
||||
*/
|
||||
#define BT_MESH_MODEL_BLOB_SRV(_srv) \
|
||||
BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_BLOB_SRV, _bt_mesh_blob_srv_op, \
|
||||
NULL, _srv, &_bt_mesh_blob_srv_cb)
|
||||
|
||||
/** @brief BLOB Transfer Server model event handlers.
|
||||
*
|
||||
* All callbacks are optional.
|
||||
*/
|
||||
struct bt_mesh_blob_srv_cb {
|
||||
/** @brief Transfer start callback.
|
||||
*
|
||||
* Called when the transfer has started with the prepared BLOB ID.
|
||||
*
|
||||
* @param srv BLOB Transfer Server instance.
|
||||
* @param ctx Message context for the incoming start message. The
|
||||
* entire transfer will be sent from the same source
|
||||
* address.
|
||||
* @param xfer Transfer parameters.
|
||||
*
|
||||
* @return 0 on success, or (negative) error code to reject the
|
||||
* transfer.
|
||||
*/
|
||||
int (*start)(struct bt_mesh_blob_srv *srv, struct bt_mesh_msg_ctx *ctx,
|
||||
struct bt_mesh_blob_xfer *xfer);
|
||||
|
||||
/** @brief Transfer end callback.
|
||||
*
|
||||
* Called when the transfer ends, either because it was cancelled, or
|
||||
* because it finished successfully. A new transfer may be prepared.
|
||||
*
|
||||
* @note The transfer may end before it's started if the start
|
||||
* parameters are invalid.
|
||||
*
|
||||
* @param srv BLOB Transfer Server instance.
|
||||
* @param id BLOB ID of the cancelled transfer.
|
||||
* @param success Whether the transfer was successful.
|
||||
*/
|
||||
void (*end)(struct bt_mesh_blob_srv *srv, uint64_t id, bool success);
|
||||
|
||||
/** @brief Transfer suspended callback.
|
||||
*
|
||||
* Called if the Server timed out while waiting for a transfer packet.
|
||||
* A suspended transfer may resume later from the start of the current
|
||||
* block. Any received chunks in the current block should be discarded,
|
||||
* they will be received again if the transfer resumes.
|
||||
*
|
||||
* The transfer will call @c resumed again when resuming.
|
||||
*
|
||||
* @note The BLOB Transfer Server does not run a timer in the suspended state,
|
||||
* and it's up to the application to determine whether the
|
||||
* transfer should be permanently cancelled. Without interaction,
|
||||
* the transfer will be suspended indefinitely, and the BLOB Transfer
|
||||
* Server will not accept any new transfers.
|
||||
*
|
||||
* @param srv BLOB Transfer Server instance.
|
||||
*/
|
||||
void (*suspended)(struct bt_mesh_blob_srv *srv);
|
||||
|
||||
/** @brief Transfer resume callback.
|
||||
*
|
||||
* Called if the transfer is resumed after being suspended.
|
||||
*
|
||||
* @param srv BLOB Transfer Server instance.
|
||||
*/
|
||||
void (*resume)(struct bt_mesh_blob_srv *srv);
|
||||
|
||||
/** @brief Transfer recovery callback.
|
||||
*
|
||||
* Called when the Bluetooth mesh subsystem is started if the device is rebooted
|
||||
* in the middle of a transfer.
|
||||
*
|
||||
* Transfers will not be resumed after a reboot if this callback is not
|
||||
* defined.
|
||||
*
|
||||
* @param srv BLOB Transfer Server instance.
|
||||
* @param xfer Transfer to resume.
|
||||
* @param io BLOB stream return parameter. Must be set to a valid
|
||||
* BLOB stream by the callback.
|
||||
*
|
||||
* @return 0 on success, or (negative) error code to abandon the
|
||||
* transfer.
|
||||
*/
|
||||
int (*recover)(struct bt_mesh_blob_srv *srv,
|
||||
struct bt_mesh_blob_xfer *xfer,
|
||||
const struct bt_mesh_blob_io **io);
|
||||
};
|
||||
|
||||
/** @brief BLOB Transfer Server instance. */
|
||||
struct bt_mesh_blob_srv {
|
||||
/** Event handler callbacks. */
|
||||
const struct bt_mesh_blob_srv_cb *cb;
|
||||
|
||||
/* Runtime state: */
|
||||
const struct bt_mesh_blob_io *io;
|
||||
struct k_work_delayable rx_timeout;
|
||||
struct bt_mesh_blob_block block;
|
||||
struct bt_mesh_model *mod;
|
||||
enum bt_mesh_blob_xfer_phase phase;
|
||||
|
||||
struct bt_mesh_blob_srv_state {
|
||||
struct bt_mesh_blob_xfer xfer;
|
||||
uint16_t cli;
|
||||
uint16_t app_idx;
|
||||
uint16_t timeout_base;
|
||||
uint16_t mtu_size;
|
||||
uint8_t ttl;
|
||||
|
||||
/* Bitfield of pending blocks. */
|
||||
ATOMIC_DEFINE(blocks, BT_MESH_BLOB_BLOCKS_MAX);
|
||||
} state;
|
||||
|
||||
/* Pull mode (Pull BLOB Transfer Mode) behavior. */
|
||||
struct {
|
||||
uint16_t chunk_idx;
|
||||
struct k_work_delayable report;
|
||||
} pull;
|
||||
};
|
||||
|
||||
/** @brief Prepare BLOB Transfer Server for an incoming transfer.
|
||||
*
|
||||
* Before a BLOB Transfer Server can receive a transfer, the transfer must be prepared
|
||||
* through some application level mechanism. The BLOB Transfer Server will only accept
|
||||
* incoming transfers with a matching BLOB ID.
|
||||
*
|
||||
* @param srv BLOB Transfer Server instance.
|
||||
* @param id BLOB ID to accept.
|
||||
* @param io BLOB stream to write the incoming BLOB to.
|
||||
* @param ttl Time to live value to use in responses to the BLOB Transfer Client.
|
||||
* @param timeout_base Extra time for the Client to respond in addition to the
|
||||
* base 10 seconds, in 10-second increments.
|
||||
*
|
||||
* @return 0 on success, or (negative) error code on failure.
|
||||
*/
|
||||
int bt_mesh_blob_srv_recv(struct bt_mesh_blob_srv *srv, uint64_t id,
|
||||
const struct bt_mesh_blob_io *io, uint8_t ttl,
|
||||
uint16_t timeout_base);
|
||||
|
||||
/** @brief Cancel the current BLOB transfer.
|
||||
*
|
||||
* Tells the BLOB Transfer Client to drop this device from the list of Targets for the
|
||||
* current transfer. Note that the client may continue sending the transfer to
|
||||
* other Targets.
|
||||
*
|
||||
* @param srv BLOB Transfer Server instance.
|
||||
*
|
||||
* @return 0 on success, or (negative) error code on failure.
|
||||
*/
|
||||
int bt_mesh_blob_srv_cancel(struct bt_mesh_blob_srv *srv);
|
||||
|
||||
/** @brief Get the current state of the BLOB Transfer Server.
|
||||
*
|
||||
* @param srv BLOB Transfer Server instance.
|
||||
*
|
||||
* @return true if the BLOB Transfer Server is currently participating in a transfer,
|
||||
* false otherwise.
|
||||
*/
|
||||
bool bt_mesh_blob_srv_is_busy(const struct bt_mesh_blob_srv *srv);
|
||||
|
||||
/** @brief Get the current progress of the active transfer in percent.
|
||||
*
|
||||
* @param srv BLOB Transfer Server instance.
|
||||
*
|
||||
* @return The current transfer progress, or 0 if no transfer is active.
|
||||
*/
|
||||
uint8_t bt_mesh_blob_srv_progress(const struct bt_mesh_blob_srv *srv);
|
||||
|
||||
/** @cond INTERNAL_HIDDEN */
|
||||
extern const struct bt_mesh_model_op _bt_mesh_blob_srv_op[];
|
||||
extern const struct bt_mesh_model_cb _bt_mesh_blob_srv_cb;
|
||||
/** @endcond */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_SRV_H_ */
|
||||
|
||||
/** @} */
|
||||
|
|
@ -63,3 +63,9 @@ zephyr_library_sources_ifdef(CONFIG_BT_MESH_SELF_TEST test.c)
|
|||
zephyr_library_sources_ifdef(CONFIG_BT_MESH_CDB cdb.c)
|
||||
|
||||
add_subdirectory_ifdef(CONFIG_BT_MESH_SHELL shell)
|
||||
|
||||
zephyr_library_sources_ifdef(CONFIG_BT_MESH_BLOB_SRV blob_srv.c)
|
||||
|
||||
zephyr_library_sources_ifdef(CONFIG_BT_MESH_BLOB_CLI blob_cli.c)
|
||||
|
||||
zephyr_library_sources_ifdef(CONFIG_BT_MESH_BLOB_IO_FLASH blob_io_flash.c)
|
||||
|
|
|
|||
|
|
@ -539,6 +539,7 @@ config BT_MESH_SEG_BUFS
|
|||
|
||||
config BT_MESH_RX_SEG_MAX
|
||||
int "Maximum number of segments in incoming messages"
|
||||
default 6 if BT_MESH_BLOB_SRV || BT_MESH_BLOB_CLI
|
||||
default 3
|
||||
range 1 32
|
||||
depends on BT_MESH_RX_SEG_MSG_COUNT > 0
|
||||
|
|
@ -559,6 +560,7 @@ config BT_MESH_RX_SEG_MAX
|
|||
|
||||
config BT_MESH_TX_SEG_MAX
|
||||
int "Maximum number of segments in outgoing messages"
|
||||
default 6 if BT_MESH_BLOB_SRV || BT_MESH_BLOB_CLI
|
||||
default 3
|
||||
range 1 32
|
||||
depends on BT_MESH_TX_SEG_MSG_COUNT > 0
|
||||
|
|
@ -966,6 +968,99 @@ config BT_MESH_OOB_AUTH_REQUIRED
|
|||
bool "OOB authentication mandates to use HMAC SHA256"
|
||||
depends on BT_MESH_ECDH_P256_HMAC_SHA256_AES_CCM
|
||||
|
||||
menuconfig BT_MESH_BLOB_SRV
|
||||
bool "Support for BLOB Transfer Server model"
|
||||
help
|
||||
Enable the Binary Large Object (BLOB) Transfer Server model.
|
||||
|
||||
if BT_MESH_BLOB_SRV
|
||||
|
||||
config BT_MESH_BLOB_SRV_PULL_REQ_COUNT
|
||||
int "Number of chunks to request for each pull"
|
||||
default 4
|
||||
range 1 16
|
||||
help
|
||||
In Pull mode (Pull BLOB Transfer Mode), the BLOB Transfer Server
|
||||
requests a fixed number of chunks from the Client. Use this option to
|
||||
control the chunk count in the request. If the BLOB Transfer Server
|
||||
is instantiated on a Low Power node, the pull request count will be
|
||||
trimmed to not overflow the Friend queue.
|
||||
|
||||
config BT_MESH_BLOB_SIZE_MAX
|
||||
int "Largest BLOB size in bytes"
|
||||
default 524288
|
||||
range 1 3257617792
|
||||
help
|
||||
The maximum object size a BLOB Transfer Server can receive.
|
||||
|
||||
config BT_MESH_BLOB_BLOCK_SIZE_MIN
|
||||
int "Minimum block size"
|
||||
default 4096
|
||||
range 64 1048576 # 2^6 - 2^20
|
||||
help
|
||||
Minimum acceptable block size in a BLOB transfer. The transfer block
|
||||
size will be some number that is a power of two, and is between block
|
||||
size min and block size max. If no such number exists, a compile
|
||||
time warning will be issued.
|
||||
|
||||
config BT_MESH_BLOB_BLOCK_SIZE_MAX
|
||||
int "Maximum block size"
|
||||
default 4096
|
||||
range BT_MESH_BLOB_BLOCK_SIZE_MIN 1048576
|
||||
help
|
||||
Maximum acceptable block size in a BLOB transfer. The transfer block
|
||||
size will be some number that is a power of two, and is between block
|
||||
size min and block size max. If no such number exists, a compile
|
||||
time warning will be issued.
|
||||
|
||||
config BT_MESH_BLOB_REPORT_TIMEOUT
|
||||
int "Partial Block Report interval in Pull mode"
|
||||
default 10
|
||||
range 1 31
|
||||
help
|
||||
The timer value that Pull BLOB Transfer Server uses to report missed chunks.
|
||||
|
||||
endif # BT_MESH_BLOB_SRV
|
||||
|
||||
menuconfig BT_MESH_BLOB_CLI
|
||||
bool "Support for BLOB Transfer Client model"
|
||||
help
|
||||
Enable the Binary Large Object (BLOB) Transfer Client model.
|
||||
|
||||
if BT_MESH_BLOB_CLI
|
||||
|
||||
config BT_MESH_BLOB_CLI_BLOCK_RETRIES
|
||||
int "Number of retries per block"
|
||||
default 5
|
||||
help
|
||||
Controls the number of times the client will attempt to resend missing
|
||||
chunks to the BLOB receivers for every block.
|
||||
|
||||
endif
|
||||
|
||||
menu "BLOB models common configuration"
|
||||
visible if BT_MESH_BLOB_SRV || BT_MESH_BLOB_CLI
|
||||
|
||||
config BT_MESH_BLOB_CHUNK_COUNT_MAX
|
||||
int "Maximum chunk count per block"
|
||||
default 256
|
||||
range 1 2992
|
||||
help
|
||||
A BLOB transfer contains several blocks. Each block is made up of
|
||||
several chunks. This option controls the maximum chunk count per
|
||||
block.
|
||||
|
||||
endmenu
|
||||
|
||||
config BT_MESH_BLOB_IO_FLASH
|
||||
bool "BLOB flash stream"
|
||||
default y
|
||||
depends on BT_MESH_BLOB_SRV || BT_MESH_BLOB_CLI
|
||||
depends on FLASH_MAP
|
||||
help
|
||||
Enable the BLOB flash stream for reading and writing BLOBs directly to
|
||||
and from flash.
|
||||
|
||||
endif # BT_MESH_V1d1
|
||||
|
||||
rsource "shell/Kconfig"
|
||||
|
|
|
|||
140
subsys/bluetooth/mesh/blob.h
Normal file
140
subsys/bluetooth/mesh/blob.h
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
|
||||
#define BT_MESH_BLOB_OP_XFER_GET BT_MESH_MODEL_OP_2(0x83, 0x00)
|
||||
#define BT_MESH_BLOB_OP_XFER_START BT_MESH_MODEL_OP_2(0x83, 0x01)
|
||||
#define BT_MESH_BLOB_OP_XFER_CANCEL BT_MESH_MODEL_OP_2(0x83, 0x02)
|
||||
#define BT_MESH_BLOB_OP_XFER_STATUS BT_MESH_MODEL_OP_2(0x83, 0x03)
|
||||
#define BT_MESH_BLOB_OP_BLOCK_GET BT_MESH_MODEL_OP_2(0x83, 0x05)
|
||||
#define BT_MESH_BLOB_OP_BLOCK_START BT_MESH_MODEL_OP_2(0x83, 0x04)
|
||||
#define BT_MESH_BLOB_OP_CHUNK BT_MESH_MODEL_OP_1(0x66)
|
||||
#define BT_MESH_BLOB_OP_BLOCK_STATUS BT_MESH_MODEL_OP_1(0x67)
|
||||
#define BT_MESH_BLOB_OP_BLOCK_REPORT BT_MESH_MODEL_OP_1(0x68)
|
||||
#define BT_MESH_BLOB_OP_INFO_GET BT_MESH_MODEL_OP_2(0x83, 0x06)
|
||||
#define BT_MESH_BLOB_OP_INFO_STATUS BT_MESH_MODEL_OP_2(0x83, 0x07)
|
||||
|
||||
#define BLOB_BLOCK_NOT_SET 0xffff
|
||||
|
||||
#define BLOB_CHUNK_SDU_OVERHEAD \
|
||||
(BT_MESH_MODEL_OP_LEN(BT_MESH_BLOB_OP_CHUNK) + 2 + BT_MESH_MIC_SHORT)
|
||||
|
||||
#define BLOB_CHUNK_SIZE_MAX(sdu_max) ((sdu_max) - BLOB_CHUNK_SDU_OVERHEAD)
|
||||
#define BLOB_CHUNK_SDU_LEN(chunk_size) (BLOB_CHUNK_SDU_OVERHEAD + (chunk_size))
|
||||
|
||||
/* Utility macros for calculating log2 of a number at compile time.
|
||||
* Used to determine the log2 representation of the block size, which
|
||||
* is configured as a raw number, but encoded as log2.
|
||||
*
|
||||
* The macros expand to a series of ternary expressions, effectively
|
||||
* searching through power of twos until a match is found.
|
||||
* According to the specification, the block size cannot be larger than 2^20,
|
||||
* so we'll stop the search at 20.
|
||||
*/
|
||||
#define _BLOB_LOG_2_CEIL(l, x) ((x) <= (1U << l)) ? l :
|
||||
#define _BLOB_LOG_2_FLOOR(l, x) ((x) < (1U << (l + 1))) ? l :
|
||||
|
||||
#define BLOB_BLOCK_SIZE_LOG_CEIL(x) (LISTIFY(20, _BLOB_LOG_2_CEIL, (), x) 20)
|
||||
#define BLOB_BLOCK_SIZE_LOG_FLOOR(x) (LISTIFY(20, _BLOB_LOG_2_FLOOR, (), x) 20)
|
||||
|
||||
/* Log2 representation of the minimum block size */
|
||||
#define BLOB_BLOCK_SIZE_LOG_MIN BLOB_BLOCK_SIZE_LOG_CEIL(CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MIN)
|
||||
/* Log2 representation of the maximum block size */
|
||||
#define BLOB_BLOCK_SIZE_LOG_MAX BLOB_BLOCK_SIZE_LOG_FLOOR(CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX)
|
||||
|
||||
#if defined(CONFIG_BT_MESH_BLOB_SRV)
|
||||
#define BLOB_BLOCK_REPORT_STATUS_MSG_MAXLEN ( \
|
||||
MAX(sizeof(((struct bt_mesh_blob_block *)0)->missing), \
|
||||
CONFIG_BT_MESH_BLOB_SRV_PULL_REQ_COUNT * 3))
|
||||
#define BLOB_BLOCK_STATUS_MSG_MAXLEN (5 + \
|
||||
MAX(sizeof(((struct bt_mesh_blob_block *)0)->missing), \
|
||||
CONFIG_BT_MESH_BLOB_SRV_PULL_REQ_COUNT * 3))
|
||||
#else
|
||||
#define BLOB_BLOCK_REPORT_STATUS_MSG_MAXLEN sizeof(((struct bt_mesh_blob_srv *)0)->block.missing)
|
||||
#define BLOB_BLOCK_STATUS_MSG_MAXLEN (5 + sizeof(((struct bt_mesh_blob_srv *)0)->block.missing))
|
||||
#endif
|
||||
|
||||
#define BLOB_XFER_STATUS_MSG_MAXLEN (17 + sizeof(((struct bt_mesh_blob_srv *)0)->state.blocks))
|
||||
|
||||
enum bt_mesh_blob_chunks_missing {
|
||||
BT_MESH_BLOB_CHUNKS_MISSING_ALL,
|
||||
BT_MESH_BLOB_CHUNKS_MISSING_NONE,
|
||||
BT_MESH_BLOB_CHUNKS_MISSING_SOME,
|
||||
BT_MESH_BLOB_CHUNKS_MISSING_ENCODED,
|
||||
};
|
||||
|
||||
static inline size_t blob_block_size(size_t xfer_size, uint8_t block_size_log,
|
||||
uint32_t idx)
|
||||
{
|
||||
if (((idx + 1U) << block_size_log) <= xfer_size) {
|
||||
return (1U << block_size_log);
|
||||
}
|
||||
|
||||
return xfer_size & BIT_MASK(block_size_log);
|
||||
}
|
||||
|
||||
static inline void blob_chunk_missing_set(uint8_t *missing_chunks,
|
||||
int idx, bool missing)
|
||||
{
|
||||
WRITE_BIT(missing_chunks[idx / 8], idx % 8, missing);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
blob_chunk_missing_get(const uint8_t *missing_chunks, int idx)
|
||||
{
|
||||
return !!(missing_chunks[idx / 8] & BIT(idx % 8));
|
||||
}
|
||||
|
||||
static inline void blob_chunk_missing_set_all(struct bt_mesh_blob_block *block)
|
||||
{
|
||||
size_t bytes = block->chunk_count / 8;
|
||||
|
||||
memset(block->missing, 0xff, bytes);
|
||||
if (block->chunk_count % 8) {
|
||||
block->missing[bytes] = BIT_MASK(block->chunk_count % 8);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void blob_chunk_missing_set_none(struct bt_mesh_blob_block *block)
|
||||
{
|
||||
memset(block->missing, 0, sizeof(block->missing));
|
||||
}
|
||||
|
||||
/** @brief Perform a message broadcast to all BLOB Transfer Client Target nodes.
|
||||
*
|
||||
* Will send to a group or each Target node individually, repeating until
|
||||
* all Target nodes have responded or the retry time has run out.
|
||||
*
|
||||
* @param cli BLOB Transfer Client instance
|
||||
* @param ctx Broadcast context
|
||||
*/
|
||||
void blob_cli_broadcast(struct bt_mesh_blob_cli *cli,
|
||||
const struct blob_cli_broadcast_ctx *ctx);
|
||||
|
||||
/** @brief Register that a Target node responded to a broadcast.
|
||||
*
|
||||
* @param cli BLOB Transfer Client instance
|
||||
* @param target Target node that responded.
|
||||
*/
|
||||
void blob_cli_broadcast_rsp(struct bt_mesh_blob_cli *cli,
|
||||
struct bt_mesh_blob_target *target);
|
||||
|
||||
/** @brief Notify the BLOB Transfer Client that the requested transmission is complete.
|
||||
*
|
||||
* Should be called once for each call to the @ref blob_cli_broadcast_ctx.send
|
||||
* callback.
|
||||
*
|
||||
* @param cli BLOB Transfer Client instance.
|
||||
*/
|
||||
void blob_cli_broadcast_tx_complete(struct bt_mesh_blob_cli *cli);
|
||||
|
||||
/** @brief Aborts any ongoing BLOB Transfer Client operations.
|
||||
*
|
||||
* @param cli BLOB Transfer Client instance.
|
||||
*/
|
||||
void blob_cli_broadcast_abort(struct bt_mesh_blob_cli *cli);
|
||||
1649
subsys/bluetooth/mesh/blob_cli.c
Normal file
1649
subsys/bluetooth/mesh/blob_cli.c
Normal file
File diff suppressed because it is too large
Load diff
151
subsys/bluetooth/mesh/blob_io_flash.c
Normal file
151
subsys/bluetooth/mesh/blob_io_flash.c
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Noioic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/bluetooth/mesh.h>
|
||||
#include <zephyr/storage/flash_map.h>
|
||||
#include <zephyr/drivers/flash.h>
|
||||
#include <assert.h>
|
||||
#include "blob.h"
|
||||
#include "net.h"
|
||||
#include "transport.h"
|
||||
|
||||
#define WRITE_BLOCK_SIZE 4
|
||||
|
||||
#define FLASH_IO(_io) CONTAINER_OF(_io, struct bt_mesh_blob_io_flash, io)
|
||||
|
||||
static int test_flash_area(uint8_t area_id)
|
||||
{
|
||||
const struct flash_area *area;
|
||||
uint8_t align;
|
||||
int err;
|
||||
|
||||
err = flash_area_open(area_id, &area);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
align = flash_area_align(area);
|
||||
flash_area_close(area);
|
||||
|
||||
if (align > WRITE_BLOCK_SIZE) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int io_open(const struct bt_mesh_blob_io *io,
|
||||
const struct bt_mesh_blob_xfer *xfer,
|
||||
enum bt_mesh_blob_io_mode mode)
|
||||
{
|
||||
struct bt_mesh_blob_io_flash *flash = FLASH_IO(io);
|
||||
|
||||
return flash_area_open(flash->area_id, &flash->area);
|
||||
}
|
||||
|
||||
static void io_close(const struct bt_mesh_blob_io *io,
|
||||
const struct bt_mesh_blob_xfer *xfer)
|
||||
{
|
||||
struct bt_mesh_blob_io_flash *flash = FLASH_IO(io);
|
||||
|
||||
flash_area_close(flash->area);
|
||||
}
|
||||
|
||||
static int block_start(const struct bt_mesh_blob_io *io,
|
||||
const struct bt_mesh_blob_xfer *xfer,
|
||||
const struct bt_mesh_blob_block *block)
|
||||
{
|
||||
struct bt_mesh_blob_io_flash *flash = FLASH_IO(io);
|
||||
size_t erase_size;
|
||||
|
||||
if (flash->mode == BT_MESH_BLOB_READ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
|
||||
struct flash_pages_info page;
|
||||
const struct device *flash_dev;
|
||||
int err;
|
||||
|
||||
flash_dev = flash_area_get_device(flash->area);
|
||||
if (!flash_dev) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
err = flash_get_page_info_by_offs(flash_dev,
|
||||
flash->offset + block->offset, &page);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
erase_size = page.size * ceiling_fraction(block->size, page.size);
|
||||
#else
|
||||
erase_size = block->size;
|
||||
#endif
|
||||
|
||||
return flash_area_erase(flash->area, flash->offset + block->offset,
|
||||
erase_size);
|
||||
}
|
||||
|
||||
static int rd_chunk(const struct bt_mesh_blob_io *io,
|
||||
const struct bt_mesh_blob_xfer *xfer,
|
||||
const struct bt_mesh_blob_block *block,
|
||||
const struct bt_mesh_blob_chunk *chunk)
|
||||
{
|
||||
struct bt_mesh_blob_io_flash *flash = FLASH_IO(io);
|
||||
|
||||
return flash_area_read(flash->area,
|
||||
flash->offset + block->offset + chunk->offset,
|
||||
chunk->data, chunk->size);
|
||||
}
|
||||
|
||||
static int wr_chunk(const struct bt_mesh_blob_io *io,
|
||||
const struct bt_mesh_blob_xfer *xfer,
|
||||
const struct bt_mesh_blob_block *block,
|
||||
const struct bt_mesh_blob_chunk *chunk)
|
||||
{
|
||||
struct bt_mesh_blob_io_flash *flash = FLASH_IO(io);
|
||||
uint8_t buf[ROUND_UP(BLOB_CHUNK_SIZE_MAX(BT_MESH_RX_SDU_MAX),
|
||||
WRITE_BLOCK_SIZE)];
|
||||
off_t area_offset = flash->offset + block->offset + chunk->offset;
|
||||
int i = 0;
|
||||
|
||||
/* Write block align the chunk data */
|
||||
memset(&buf[i], 0xff, area_offset % WRITE_BLOCK_SIZE);
|
||||
i += area_offset % WRITE_BLOCK_SIZE;
|
||||
|
||||
memcpy(&buf[i], chunk->data, chunk->size);
|
||||
i += chunk->size;
|
||||
|
||||
memset(&buf[i], 0xff, ROUND_UP(i, WRITE_BLOCK_SIZE) - i);
|
||||
i = ROUND_UP(i, WRITE_BLOCK_SIZE);
|
||||
|
||||
return flash_area_write(flash->area,
|
||||
ROUND_DOWN(area_offset, WRITE_BLOCK_SIZE),
|
||||
buf, i);
|
||||
}
|
||||
|
||||
int bt_mesh_blob_io_flash_init(struct bt_mesh_blob_io_flash *flash,
|
||||
uint8_t area_id, off_t offset)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = test_flash_area(area_id);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
flash->area_id = area_id;
|
||||
flash->offset = offset;
|
||||
flash->io.open = io_open;
|
||||
flash->io.close = io_close;
|
||||
flash->io.block_start = block_start;
|
||||
flash->io.block_end = NULL;
|
||||
flash->io.rd = rd_chunk;
|
||||
flash->io.wr = wr_chunk;
|
||||
|
||||
return 0;
|
||||
}
|
||||
963
subsys/bluetooth/mesh/blob_srv.c
Normal file
963
subsys/bluetooth/mesh/blob_srv.c
Normal file
|
|
@ -0,0 +1,963 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <zephyr/bluetooth/mesh.h>
|
||||
#include <common/bt_str.h>
|
||||
#include "net.h"
|
||||
#include "access.h"
|
||||
#include "transport.h"
|
||||
#include "lpn.h"
|
||||
#include "blob.h"
|
||||
|
||||
#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(bt_mesh_blob_srv);
|
||||
|
||||
#define CHUNK_SIZE_MAX BLOB_CHUNK_SIZE_MAX(BT_MESH_RX_SDU_MAX)
|
||||
#define MTU_SIZE_MAX (BT_MESH_RX_SDU_MAX - BT_MESH_MIC_SHORT)
|
||||
|
||||
/* The Receive BLOB Timeout Timer */
|
||||
#define SERVER_TIMEOUT_SECS(srv) (10 * (1 + (srv)->state.timeout_base))
|
||||
/* The initial timer value used by an instance of the Pull BLOB State machine - T_BPI */
|
||||
#define REPORT_TIMER_TIMEOUT K_SECONDS(CONFIG_BT_MESH_BLOB_REPORT_TIMEOUT)
|
||||
|
||||
BUILD_ASSERT(BLOB_BLOCK_SIZE_LOG_MIN <= BLOB_BLOCK_SIZE_LOG_MAX,
|
||||
"The must be at least one number between the min and "
|
||||
"max block size that is the power of two.");
|
||||
|
||||
BUILD_ASSERT((BLOB_XFER_STATUS_MSG_MAXLEN + BT_MESH_MODEL_OP_LEN(BT_MESH_BLOB_OP_XFER_STATUS) +
|
||||
BT_MESH_MIC_SHORT) <= BT_MESH_TX_SDU_MAX,
|
||||
"The BLOB Transfer Status message does not fit into the maximum outgoing SDU size.");
|
||||
|
||||
BUILD_ASSERT((BLOB_BLOCK_REPORT_STATUS_MSG_MAXLEN +
|
||||
BT_MESH_MODEL_OP_LEN(BT_MESH_BLOB_OP_BLOCK_REPORT) + BT_MESH_MIC_SHORT)
|
||||
<= BT_MESH_TX_SDU_MAX,
|
||||
"The BLOB Partial Block Report message does not fit into the maximum outgoing SDU "
|
||||
"size.");
|
||||
|
||||
BUILD_ASSERT((BLOB_BLOCK_STATUS_MSG_MAXLEN + BT_MESH_MODEL_OP_LEN(BT_MESH_BLOB_OP_BLOCK_STATUS) +
|
||||
BT_MESH_MIC_SHORT) <= BT_MESH_TX_SDU_MAX,
|
||||
"The BLOB Block Status message does not fit into the maximum outgoing SDU size.");
|
||||
|
||||
static void cancel(struct bt_mesh_blob_srv *srv);
|
||||
static void suspend(struct bt_mesh_blob_srv *srv);
|
||||
|
||||
static inline uint32_t block_count_get(const struct bt_mesh_blob_srv *srv)
|
||||
{
|
||||
return ceiling_fraction(srv->state.xfer.size,
|
||||
(1U << srv->state.xfer.block_size_log));
|
||||
}
|
||||
|
||||
static inline uint32_t max_chunk_size(const struct bt_mesh_blob_srv *srv)
|
||||
{
|
||||
return MIN((srv->state.mtu_size - 2 -
|
||||
BT_MESH_MODEL_OP_LEN(BT_MESH_BLOB_OP_CHUNK)),
|
||||
CHUNK_SIZE_MAX);
|
||||
}
|
||||
|
||||
static inline uint32_t max_chunk_count(const struct bt_mesh_blob_srv *srv)
|
||||
{
|
||||
return MIN(8 * (srv->state.mtu_size - 6),
|
||||
CONFIG_BT_MESH_BLOB_CHUNK_COUNT_MAX);
|
||||
}
|
||||
|
||||
static inline uint32_t missing_chunks(const struct bt_mesh_blob_block *block)
|
||||
{
|
||||
int i;
|
||||
uint32_t count = 0;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(block->missing); ++i) {
|
||||
count += POPCOUNT(block->missing[i]);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static void store_state(const struct bt_mesh_blob_srv *srv)
|
||||
{
|
||||
if (!IS_ENABLED(CONFIG_BT_SETTINGS)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Convert bit count to byte count: */
|
||||
uint32_t block_len = ceiling_fraction(block_count_get(srv), 8);
|
||||
|
||||
bt_mesh_model_data_store(
|
||||
srv->mod, false, NULL, &srv->state,
|
||||
offsetof(struct bt_mesh_blob_srv_state, blocks) + block_len);
|
||||
}
|
||||
|
||||
static void erase_state(struct bt_mesh_blob_srv *srv)
|
||||
{
|
||||
if (!IS_ENABLED(CONFIG_BT_SETTINGS)) {
|
||||
return;
|
||||
}
|
||||
|
||||
bt_mesh_model_data_store(srv->mod, false, NULL, NULL, 0);
|
||||
}
|
||||
|
||||
static int io_open(struct bt_mesh_blob_srv *srv)
|
||||
{
|
||||
if (!srv->io->open) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return srv->io->open(srv->io, &srv->state.xfer, BT_MESH_BLOB_WRITE);
|
||||
}
|
||||
|
||||
static void io_close(struct bt_mesh_blob_srv *srv)
|
||||
{
|
||||
if (!srv->io->close) {
|
||||
return;
|
||||
}
|
||||
|
||||
srv->io->close(srv->io, &srv->state.xfer);
|
||||
}
|
||||
|
||||
static void reset_timer(struct bt_mesh_blob_srv *srv)
|
||||
{
|
||||
uint32_t timeout_secs =
|
||||
srv->state.xfer.mode == BT_MESH_BLOB_XFER_MODE_PULL ?
|
||||
MAX(SERVER_TIMEOUT_SECS(srv),
|
||||
CONFIG_BT_MESH_BLOB_REPORT_TIMEOUT + 1) :
|
||||
SERVER_TIMEOUT_SECS(srv);
|
||||
k_work_reschedule(&srv->rx_timeout, K_SECONDS(timeout_secs));
|
||||
}
|
||||
|
||||
static void buf_chunk_index_add(struct net_buf_simple *buf, uint16_t chunk)
|
||||
{
|
||||
/* utf-8 encoded: */
|
||||
if (chunk < 0x80) {
|
||||
net_buf_simple_add_u8(buf, chunk);
|
||||
} else if (chunk < 0x8000) {
|
||||
net_buf_simple_add_u8(buf, 0xc0 | chunk >> 6);
|
||||
net_buf_simple_add_u8(buf, 0x80 | (chunk & BIT_MASK(6)));
|
||||
} else {
|
||||
net_buf_simple_add_u8(buf, 0xe0 | chunk >> 12);
|
||||
net_buf_simple_add_u8(buf, 0x80 | ((chunk >> 6) & BIT_MASK(6)));
|
||||
net_buf_simple_add_u8(buf, 0x80 | (chunk & BIT_MASK(6)));
|
||||
}
|
||||
}
|
||||
|
||||
static int pull_req_max(const struct bt_mesh_blob_srv *srv)
|
||||
{
|
||||
int count = CONFIG_BT_MESH_BLOB_SRV_PULL_REQ_COUNT;
|
||||
|
||||
#if defined(CONFIG_BT_MESH_LOW_POWER)
|
||||
/* No point in requesting more than the friend node can hold: */
|
||||
if (bt_mesh_lpn_established()) {
|
||||
uint32_t segments_per_chunk = ceiling_fraction(
|
||||
BLOB_CHUNK_SDU_LEN(srv->state.xfer.chunk_size),
|
||||
BT_MESH_APP_SEG_SDU_MAX);
|
||||
|
||||
count = MIN(CONFIG_BT_MESH_BLOB_SRV_PULL_REQ_COUNT,
|
||||
bt_mesh.lpn.queue_size / segments_per_chunk);
|
||||
}
|
||||
#endif
|
||||
|
||||
return MIN(count, missing_chunks(&srv->block));
|
||||
}
|
||||
|
||||
static void report_sent(int err, void *cb_data)
|
||||
{
|
||||
struct bt_mesh_blob_srv *srv = cb_data;
|
||||
|
||||
LOG_DBG("");
|
||||
|
||||
if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER) && bt_mesh_lpn_established()) {
|
||||
bt_mesh_lpn_poll();
|
||||
}
|
||||
|
||||
if (k_work_delayable_is_pending(&srv->rx_timeout)) {
|
||||
k_work_reschedule(&srv->pull.report, REPORT_TIMER_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
static void block_report(struct bt_mesh_blob_srv *srv)
|
||||
{
|
||||
static const struct bt_mesh_send_cb report_cb = { .end = report_sent };
|
||||
struct bt_mesh_msg_ctx ctx = {
|
||||
.app_idx = srv->state.app_idx,
|
||||
.send_ttl = srv->state.ttl,
|
||||
.addr = srv->state.cli,
|
||||
};
|
||||
int count;
|
||||
int i;
|
||||
|
||||
LOG_DBG("rx BLOB Timeout Timer: %i", k_work_delayable_is_pending(&srv->rx_timeout));
|
||||
|
||||
BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_BLOB_OP_BLOCK_REPORT,
|
||||
BLOB_BLOCK_REPORT_STATUS_MSG_MAXLEN);
|
||||
bt_mesh_model_msg_init(&buf, BT_MESH_BLOB_OP_BLOCK_REPORT);
|
||||
|
||||
count = pull_req_max(srv);
|
||||
|
||||
for (i = 0; i < srv->block.chunk_count && count; ++i) {
|
||||
if (blob_chunk_missing_get(srv->block.missing, i)) {
|
||||
buf_chunk_index_add(&buf, i);
|
||||
count--;
|
||||
}
|
||||
}
|
||||
|
||||
bt_mesh_model_send(srv->mod, &ctx, &buf, &report_cb, srv);
|
||||
}
|
||||
|
||||
static void phase_set(struct bt_mesh_blob_srv *srv,
|
||||
enum bt_mesh_blob_xfer_phase phase)
|
||||
{
|
||||
srv->phase = phase;
|
||||
LOG_DBG("Phase: %u", phase);
|
||||
}
|
||||
|
||||
static void cancel(struct bt_mesh_blob_srv *srv)
|
||||
{
|
||||
/* TODO: Could this state be preserved instead somehow? Wiping the
|
||||
* entire transfer state is a bit overkill
|
||||
*/
|
||||
phase_set(srv, BT_MESH_BLOB_XFER_PHASE_INACTIVE);
|
||||
srv->state.xfer.mode = BT_MESH_BLOB_XFER_MODE_NONE;
|
||||
srv->state.ttl = BT_MESH_TTL_DEFAULT;
|
||||
srv->block.number = 0xffff;
|
||||
srv->state.xfer.chunk_size = 0xffff;
|
||||
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, false);
|
||||
}
|
||||
}
|
||||
|
||||
static void suspend(struct bt_mesh_blob_srv *srv)
|
||||
{
|
||||
LOG_DBG("");
|
||||
k_work_cancel_delayable(&srv->rx_timeout);
|
||||
k_work_cancel_delayable(&srv->pull.report);
|
||||
phase_set(srv, BT_MESH_BLOB_XFER_PHASE_SUSPENDED);
|
||||
if (srv->cb && srv->cb->suspended) {
|
||||
srv->cb->suspended(srv);
|
||||
}
|
||||
}
|
||||
|
||||
static void resume(struct bt_mesh_blob_srv *srv)
|
||||
{
|
||||
LOG_DBG("Resuming");
|
||||
|
||||
phase_set(srv, BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_BLOCK);
|
||||
reset_timer(srv);
|
||||
}
|
||||
|
||||
static void timeout(struct k_work *work)
|
||||
{
|
||||
struct bt_mesh_blob_srv *srv =
|
||||
CONTAINER_OF(work, struct bt_mesh_blob_srv, rx_timeout.work);
|
||||
|
||||
LOG_DBG("");
|
||||
|
||||
if (srv->phase == BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START) {
|
||||
cancel(srv);
|
||||
} else {
|
||||
suspend(srv);
|
||||
}
|
||||
}
|
||||
|
||||
static void report_timeout(struct k_work *work)
|
||||
{
|
||||
struct bt_mesh_blob_srv *srv =
|
||||
CONTAINER_OF(work, struct bt_mesh_blob_srv, pull.report.work);
|
||||
|
||||
LOG_DBG("");
|
||||
|
||||
if (srv->phase != BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_BLOCK &&
|
||||
srv->phase != BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_CHUNK) {
|
||||
return;
|
||||
}
|
||||
|
||||
block_report(srv);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Message handling
|
||||
******************************************************************************/
|
||||
|
||||
static void xfer_status_rsp(struct bt_mesh_blob_srv *srv,
|
||||
struct bt_mesh_msg_ctx *ctx,
|
||||
enum bt_mesh_blob_status status)
|
||||
{
|
||||
BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_BLOB_OP_XFER_STATUS,
|
||||
BLOB_XFER_STATUS_MSG_MAXLEN);
|
||||
bt_mesh_model_msg_init(&buf, BT_MESH_BLOB_OP_XFER_STATUS);
|
||||
|
||||
net_buf_simple_add_u8(&buf, ((status & BIT_MASK(4)) |
|
||||
(srv->state.xfer.mode << 6)));
|
||||
net_buf_simple_add_u8(&buf, srv->phase);
|
||||
|
||||
if (srv->phase == BT_MESH_BLOB_XFER_PHASE_INACTIVE) {
|
||||
goto send;
|
||||
}
|
||||
|
||||
net_buf_simple_add_le64(&buf, srv->state.xfer.id);
|
||||
|
||||
if (srv->phase == BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START) {
|
||||
goto send;
|
||||
}
|
||||
|
||||
net_buf_simple_add_le32(&buf, srv->state.xfer.size);
|
||||
net_buf_simple_add_u8(&buf, srv->state.xfer.block_size_log);
|
||||
net_buf_simple_add_le16(&buf, srv->state.mtu_size);
|
||||
net_buf_simple_add_mem(&buf, srv->state.blocks,
|
||||
ceiling_fraction(block_count_get(srv), 8));
|
||||
|
||||
send:
|
||||
ctx->send_ttl = srv->state.ttl;
|
||||
bt_mesh_model_send(srv->mod, ctx, &buf, NULL, NULL);
|
||||
}
|
||||
|
||||
static void block_status_rsp(struct bt_mesh_blob_srv *srv,
|
||||
struct bt_mesh_msg_ctx *ctx,
|
||||
enum bt_mesh_blob_status status)
|
||||
{
|
||||
enum bt_mesh_blob_chunks_missing format;
|
||||
uint32_t missing;
|
||||
int i;
|
||||
|
||||
BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_BLOB_OP_BLOCK_STATUS,
|
||||
BLOB_BLOCK_STATUS_MSG_MAXLEN);
|
||||
bt_mesh_model_msg_init(&buf, BT_MESH_BLOB_OP_BLOCK_STATUS);
|
||||
|
||||
if (srv->phase == BT_MESH_BLOB_XFER_PHASE_INACTIVE ||
|
||||
srv->phase == BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START) {
|
||||
missing = srv->block.chunk_count;
|
||||
} else if (srv->phase == BT_MESH_BLOB_XFER_PHASE_COMPLETE) {
|
||||
missing = 0U;
|
||||
} else {
|
||||
missing = missing_chunks(&srv->block);
|
||||
}
|
||||
|
||||
if (srv->state.xfer.mode == BT_MESH_BLOB_XFER_MODE_PULL) {
|
||||
format = BT_MESH_BLOB_CHUNKS_MISSING_ENCODED;
|
||||
} else if (missing == srv->block.chunk_count) {
|
||||
format = BT_MESH_BLOB_CHUNKS_MISSING_ALL;
|
||||
} else if (missing == 0) {
|
||||
format = BT_MESH_BLOB_CHUNKS_MISSING_NONE;
|
||||
} else {
|
||||
format = BT_MESH_BLOB_CHUNKS_MISSING_SOME;
|
||||
}
|
||||
|
||||
LOG_DBG("Status: %u, missing: %u/%u", status, missing, srv->block.chunk_count);
|
||||
|
||||
net_buf_simple_add_u8(&buf, (status & BIT_MASK(4)) | (format << 6));
|
||||
net_buf_simple_add_le16(&buf, srv->block.number);
|
||||
net_buf_simple_add_le16(&buf, srv->state.xfer.chunk_size);
|
||||
|
||||
if (format == BT_MESH_BLOB_CHUNKS_MISSING_SOME) {
|
||||
net_buf_simple_add_mem(&buf, srv->block.missing,
|
||||
ceiling_fraction(srv->block.chunk_count,
|
||||
8));
|
||||
|
||||
LOG_DBG("Bits: %s",
|
||||
bt_hex(srv->block.missing,
|
||||
ceiling_fraction(srv->block.chunk_count, 8)));
|
||||
|
||||
} else if (format == BT_MESH_BLOB_CHUNKS_MISSING_ENCODED) {
|
||||
int count = pull_req_max(srv);
|
||||
|
||||
for (i = 0; (i < srv->block.chunk_count) && count; ++i) {
|
||||
if (blob_chunk_missing_get(srv->block.missing, i)) {
|
||||
LOG_DBG("Missing %u", i);
|
||||
buf_chunk_index_add(&buf, i);
|
||||
count--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (srv->phase != BT_MESH_BLOB_XFER_PHASE_INACTIVE) {
|
||||
ctx->send_ttl = srv->state.ttl;
|
||||
}
|
||||
|
||||
bt_mesh_model_send(srv->mod, ctx, &buf, NULL, NULL);
|
||||
}
|
||||
|
||||
static int handle_xfer_get(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
|
||||
struct net_buf_simple *buf)
|
||||
{
|
||||
struct bt_mesh_blob_srv *srv = mod->user_data;
|
||||
|
||||
LOG_DBG("");
|
||||
xfer_status_rsp(srv, ctx, BT_MESH_BLOB_SUCCESS);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int handle_xfer_start(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
|
||||
struct net_buf_simple *buf)
|
||||
{
|
||||
struct bt_mesh_blob_srv *srv = mod->user_data;
|
||||
enum bt_mesh_blob_status status;
|
||||
enum bt_mesh_blob_xfer_mode mode;
|
||||
uint64_t id;
|
||||
size_t size;
|
||||
uint8_t block_size_log;
|
||||
uint32_t block_count;
|
||||
uint16_t mtu_size;
|
||||
int err;
|
||||
|
||||
mode = (net_buf_simple_pull_u8(buf) >> 6);
|
||||
id = net_buf_simple_pull_le64(buf);
|
||||
size = net_buf_simple_pull_le32(buf);
|
||||
block_size_log = net_buf_simple_pull_u8(buf);
|
||||
mtu_size = net_buf_simple_pull_le16(buf);
|
||||
|
||||
LOG_DBG("\n\tsize: %u block size: %u\n\tmtu_size: %u\n\tmode: %s",
|
||||
size, (1U << block_size_log), mtu_size,
|
||||
mode == BT_MESH_BLOB_XFER_MODE_PUSH ? "push" : "pull");
|
||||
|
||||
if (mode != BT_MESH_BLOB_XFER_MODE_PULL &&
|
||||
mode != BT_MESH_BLOB_XFER_MODE_PUSH) {
|
||||
LOG_WRN("Invalid mode 0x%x", mode);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (srv->phase == BT_MESH_BLOB_XFER_PHASE_INACTIVE) {
|
||||
status = BT_MESH_BLOB_ERR_WRONG_PHASE;
|
||||
LOG_WRN("Uninitialized");
|
||||
goto rsp;
|
||||
}
|
||||
|
||||
if (srv->state.xfer.id != id) {
|
||||
status = BT_MESH_BLOB_ERR_WRONG_BLOB_ID;
|
||||
/* bt_hex uses static array for the resulting hex string.
|
||||
* Not possible to use bt_hex in the same logging function twice.
|
||||
*/
|
||||
LOG_WRN("Invalid ID: %s", bt_hex(&id, sizeof(uint64_t)));
|
||||
LOG_WRN("Expected ID: %s", bt_hex(&srv->state.xfer.id, sizeof(uint64_t)));
|
||||
goto rsp;
|
||||
}
|
||||
|
||||
if (srv->phase != BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START) {
|
||||
if (srv->state.xfer.mode != mode ||
|
||||
srv->state.xfer.size != size ||
|
||||
srv->state.xfer.block_size_log != block_size_log ||
|
||||
srv->state.mtu_size > mtu_size) {
|
||||
status = BT_MESH_BLOB_ERR_WRONG_PHASE;
|
||||
LOG_WRN("Busy");
|
||||
goto rsp;
|
||||
}
|
||||
|
||||
if (srv->phase == BT_MESH_BLOB_XFER_PHASE_SUSPENDED) {
|
||||
resume(srv);
|
||||
store_state(srv);
|
||||
} else {
|
||||
LOG_DBG("Duplicate");
|
||||
}
|
||||
|
||||
status = BT_MESH_BLOB_SUCCESS;
|
||||
goto rsp;
|
||||
}
|
||||
|
||||
if (size > CONFIG_BT_MESH_BLOB_SIZE_MAX) {
|
||||
LOG_WRN("Too large");
|
||||
status = BT_MESH_BLOB_ERR_BLOB_TOO_LARGE;
|
||||
goto rsp;
|
||||
}
|
||||
|
||||
if (((1U << block_size_log) < CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MIN) ||
|
||||
((1U << block_size_log) > CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX)) {
|
||||
LOG_WRN("Invalid block size: %u", block_size_log);
|
||||
status = BT_MESH_BLOB_ERR_INVALID_BLOCK_SIZE;
|
||||
goto rsp;
|
||||
}
|
||||
|
||||
srv->state.cli = ctx->addr;
|
||||
srv->state.app_idx = ctx->app_idx;
|
||||
srv->state.mtu_size = MIN(mtu_size, MTU_SIZE_MAX);
|
||||
srv->state.xfer.id = id;
|
||||
srv->state.xfer.size = size;
|
||||
srv->state.xfer.mode = mode;
|
||||
srv->state.xfer.block_size_log = block_size_log;
|
||||
srv->state.xfer.chunk_size = 0xffff;
|
||||
srv->block.number = 0xffff;
|
||||
|
||||
block_count = block_count_get(srv);
|
||||
if (block_count > BT_MESH_BLOB_BLOCKS_MAX) {
|
||||
LOG_WRN("Invalid block count (%u)", block_count);
|
||||
status = BT_MESH_BLOB_ERR_INVALID_PARAM;
|
||||
cancel(srv);
|
||||
goto rsp;
|
||||
}
|
||||
|
||||
memset(srv->state.blocks, 0, sizeof(srv->state.blocks));
|
||||
for (int i = 0; i < block_count; i++) {
|
||||
atomic_set_bit(srv->state.blocks, i);
|
||||
}
|
||||
|
||||
err = io_open(srv);
|
||||
if (err) {
|
||||
LOG_ERR("Couldn't open stream (err: %d)", err);
|
||||
status = BT_MESH_BLOB_ERR_INTERNAL;
|
||||
cancel(srv);
|
||||
goto rsp;
|
||||
}
|
||||
|
||||
if (srv->cb && srv->cb->start) {
|
||||
err = srv->cb->start(srv, ctx, &srv->state.xfer);
|
||||
if (err) {
|
||||
LOG_ERR("Couldn't start transfer (err: %d)", err);
|
||||
status = BT_MESH_BLOB_ERR_INTERNAL;
|
||||
cancel(srv);
|
||||
goto rsp;
|
||||
}
|
||||
}
|
||||
|
||||
reset_timer(srv);
|
||||
phase_set(srv, BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_BLOCK);
|
||||
store_state(srv);
|
||||
status = BT_MESH_BLOB_SUCCESS;
|
||||
|
||||
rsp:
|
||||
xfer_status_rsp(srv, ctx, status);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int handle_xfer_cancel(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
|
||||
struct net_buf_simple *buf)
|
||||
{
|
||||
enum bt_mesh_blob_status status = BT_MESH_BLOB_SUCCESS;
|
||||
struct bt_mesh_blob_srv *srv = mod->user_data;
|
||||
uint64_t id;
|
||||
|
||||
id = net_buf_simple_pull_le64(buf);
|
||||
|
||||
LOG_DBG("%u", (uint32_t)id);
|
||||
|
||||
if (srv->phase == BT_MESH_BLOB_XFER_PHASE_INACTIVE) {
|
||||
goto rsp;
|
||||
}
|
||||
|
||||
if (srv->state.xfer.id != id) {
|
||||
status = BT_MESH_BLOB_ERR_WRONG_BLOB_ID;
|
||||
goto rsp;
|
||||
}
|
||||
|
||||
cancel(srv);
|
||||
|
||||
rsp:
|
||||
xfer_status_rsp(srv, ctx, status);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int handle_block_get(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
|
||||
struct net_buf_simple *buf)
|
||||
{
|
||||
enum bt_mesh_blob_status status;
|
||||
struct bt_mesh_blob_srv *srv = mod->user_data;
|
||||
|
||||
switch (srv->phase) {
|
||||
case BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_BLOCK:
|
||||
case BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_CHUNK:
|
||||
case BT_MESH_BLOB_XFER_PHASE_COMPLETE:
|
||||
status = BT_MESH_BLOB_SUCCESS;
|
||||
break;
|
||||
case BT_MESH_BLOB_XFER_PHASE_SUSPENDED:
|
||||
status = BT_MESH_BLOB_ERR_INFO_UNAVAILABLE;
|
||||
break;
|
||||
case BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START:
|
||||
case BT_MESH_BLOB_XFER_PHASE_INACTIVE:
|
||||
status = BT_MESH_BLOB_ERR_WRONG_PHASE;
|
||||
break;
|
||||
default:
|
||||
status = BT_MESH_BLOB_ERR_INTERNAL;
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_DBG("");
|
||||
|
||||
block_status_rsp(srv, ctx, status);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int handle_block_start(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
|
||||
struct net_buf_simple *buf)
|
||||
{
|
||||
struct bt_mesh_blob_srv *srv = mod->user_data;
|
||||
enum bt_mesh_blob_status status;
|
||||
uint16_t block_number, chunk_size;
|
||||
int err;
|
||||
|
||||
block_number = net_buf_simple_pull_le16(buf);
|
||||
chunk_size = net_buf_simple_pull_le16(buf);
|
||||
|
||||
if (srv->phase == BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START ||
|
||||
srv->phase == BT_MESH_BLOB_XFER_PHASE_INACTIVE) {
|
||||
status = BT_MESH_BLOB_ERR_WRONG_PHASE;
|
||||
goto rsp;
|
||||
}
|
||||
|
||||
reset_timer(srv);
|
||||
|
||||
if (srv->phase == BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_CHUNK) {
|
||||
if (block_number != srv->block.number ||
|
||||
chunk_size != srv->state.xfer.chunk_size) {
|
||||
status = BT_MESH_BLOB_ERR_WRONG_PHASE;
|
||||
} else {
|
||||
status = BT_MESH_BLOB_SUCCESS;
|
||||
}
|
||||
|
||||
goto rsp;
|
||||
}
|
||||
|
||||
if (block_number >= block_count_get(srv)) {
|
||||
status = BT_MESH_BLOB_ERR_INVALID_BLOCK_NUM;
|
||||
goto rsp;
|
||||
}
|
||||
|
||||
if (!chunk_size || chunk_size > max_chunk_size(srv) ||
|
||||
(ceiling_fraction((1 << srv->state.xfer.block_size_log), chunk_size) >
|
||||
max_chunk_count(srv))) {
|
||||
LOG_WRN("Invalid chunk size: (chunk size: %u, max: %u, ceil: %u, count: %u)",
|
||||
chunk_size, max_chunk_size(srv),
|
||||
ceiling_fraction((1 << srv->state.xfer.block_size_log), chunk_size),
|
||||
max_chunk_count(srv));
|
||||
status = BT_MESH_BLOB_ERR_INVALID_CHUNK_SIZE;
|
||||
goto rsp;
|
||||
}
|
||||
|
||||
srv->block.size = blob_block_size(
|
||||
srv->state.xfer.size, srv->state.xfer.block_size_log, block_number);
|
||||
srv->block.number = block_number;
|
||||
srv->block.chunk_count = ceiling_fraction(srv->block.size, chunk_size);
|
||||
srv->state.xfer.chunk_size = chunk_size;
|
||||
srv->block.offset = block_number * (1UL << srv->state.xfer.block_size_log);
|
||||
|
||||
if (srv->phase == BT_MESH_BLOB_XFER_PHASE_COMPLETE ||
|
||||
!atomic_test_bit(srv->state.blocks, block_number)) {
|
||||
memset(srv->block.missing, 0, sizeof(srv->block.missing));
|
||||
status = BT_MESH_BLOB_SUCCESS;
|
||||
goto rsp;
|
||||
}
|
||||
|
||||
if (srv->phase == BT_MESH_BLOB_XFER_PHASE_SUSPENDED && srv->cb &&
|
||||
srv->cb->resume) {
|
||||
srv->cb->resume(srv);
|
||||
}
|
||||
|
||||
phase_set(srv, BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_CHUNK);
|
||||
blob_chunk_missing_set_all(&srv->block);
|
||||
|
||||
LOG_DBG("%u: (%u/%u)\n\tsize: %u\n\tchunk size: %u\n\tchunk count: %u",
|
||||
srv->block.number, srv->block.number + 1, block_count_get(srv),
|
||||
srv->block.size, chunk_size, srv->block.chunk_count);
|
||||
|
||||
if (srv->io->block_start) {
|
||||
err = srv->io->block_start(srv->io, &srv->state.xfer,
|
||||
&srv->block);
|
||||
if (err) {
|
||||
cancel(srv);
|
||||
status = BT_MESH_BLOB_ERR_INTERNAL;
|
||||
goto rsp;
|
||||
}
|
||||
}
|
||||
|
||||
if (srv->state.xfer.mode == BT_MESH_BLOB_XFER_MODE_PULL) {
|
||||
/* Wait for the client to send the first chunk */
|
||||
k_work_reschedule(&srv->pull.report, REPORT_TIMER_TIMEOUT);
|
||||
}
|
||||
|
||||
status = BT_MESH_BLOB_SUCCESS;
|
||||
|
||||
rsp:
|
||||
block_status_rsp(srv, ctx, status);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int handle_chunk(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
|
||||
struct net_buf_simple *buf)
|
||||
{
|
||||
struct bt_mesh_blob_srv *srv = mod->user_data;
|
||||
struct bt_mesh_blob_chunk chunk;
|
||||
size_t expected_size = 0;
|
||||
uint16_t idx;
|
||||
int i, err;
|
||||
|
||||
idx = net_buf_simple_pull_le16(buf);
|
||||
chunk.size = buf->len;
|
||||
chunk.data = net_buf_simple_pull_mem(buf, chunk.size);
|
||||
chunk.offset = idx * srv->state.xfer.chunk_size;
|
||||
|
||||
if (srv->phase != BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_CHUNK ||
|
||||
idx >= srv->block.chunk_count) {
|
||||
LOG_ERR("Invalid phase or index (%u %u)", srv->phase,
|
||||
idx);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (idx == srv->block.chunk_count - 1) {
|
||||
expected_size = srv->block.size % srv->state.xfer.chunk_size;
|
||||
}
|
||||
|
||||
if (expected_size == 0) {
|
||||
expected_size = srv->state.xfer.chunk_size;
|
||||
}
|
||||
|
||||
if (chunk.size != expected_size) {
|
||||
LOG_ERR("Unexpected size: %u != %u", expected_size, chunk.size);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
LOG_DBG("%u/%u (%u bytes)", idx + 1, srv->block.chunk_count,
|
||||
chunk.size);
|
||||
|
||||
reset_timer(srv);
|
||||
if (srv->state.xfer.mode == BT_MESH_BLOB_XFER_MODE_PULL) {
|
||||
k_work_reschedule(&srv->pull.report, REPORT_TIMER_TIMEOUT);
|
||||
}
|
||||
|
||||
if (!blob_chunk_missing_get(srv->block.missing, idx)) {
|
||||
LOG_DBG("Duplicate chunk %u", idx);
|
||||
return -EALREADY;
|
||||
}
|
||||
|
||||
err = srv->io->wr(srv->io, &srv->state.xfer, &srv->block, &chunk);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
blob_chunk_missing_set(srv->block.missing, idx, false);
|
||||
if (missing_chunks(&srv->block)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (srv->state.xfer.mode == BT_MESH_BLOB_XFER_MODE_PULL) {
|
||||
block_report(srv);
|
||||
}
|
||||
|
||||
if (srv->io->block_end) {
|
||||
srv->io->block_end(srv->io, &srv->state.xfer, &srv->block);
|
||||
}
|
||||
|
||||
atomic_clear_bit(srv->state.blocks, srv->block.number);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(srv->state.blocks); ++i) {
|
||||
if (!srv->state.blocks[i]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
phase_set(srv, BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_BLOCK);
|
||||
store_state(srv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int handle_info_get(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
|
||||
struct net_buf_simple *buf)
|
||||
{
|
||||
struct bt_mesh_blob_srv *srv = mod->user_data;
|
||||
|
||||
LOG_DBG("");
|
||||
|
||||
BT_MESH_MODEL_BUF_DEFINE(rsp, BT_MESH_BLOB_OP_INFO_STATUS, 15);
|
||||
bt_mesh_model_msg_init(&rsp, BT_MESH_BLOB_OP_INFO_STATUS);
|
||||
net_buf_simple_add_u8(&rsp, BLOB_BLOCK_SIZE_LOG_MIN);
|
||||
net_buf_simple_add_u8(&rsp, BLOB_BLOCK_SIZE_LOG_MAX);
|
||||
net_buf_simple_add_le16(&rsp, CONFIG_BT_MESH_BLOB_CHUNK_COUNT_MAX);
|
||||
net_buf_simple_add_le16(&rsp, CHUNK_SIZE_MAX);
|
||||
net_buf_simple_add_le32(&rsp, CONFIG_BT_MESH_BLOB_SIZE_MAX);
|
||||
net_buf_simple_add_le16(&rsp, MTU_SIZE_MAX);
|
||||
net_buf_simple_add_u8(&rsp, BT_MESH_BLOB_XFER_MODE_ALL);
|
||||
|
||||
if (srv->phase != BT_MESH_BLOB_XFER_PHASE_INACTIVE) {
|
||||
ctx->send_ttl = srv->state.ttl;
|
||||
}
|
||||
|
||||
bt_mesh_model_send(srv->mod, ctx, &rsp, NULL, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct bt_mesh_model_op _bt_mesh_blob_srv_op[] = {
|
||||
{ BT_MESH_BLOB_OP_XFER_GET, BT_MESH_LEN_EXACT(0), handle_xfer_get },
|
||||
{ BT_MESH_BLOB_OP_XFER_START, BT_MESH_LEN_EXACT(16), handle_xfer_start },
|
||||
{ BT_MESH_BLOB_OP_XFER_CANCEL, BT_MESH_LEN_EXACT(8), handle_xfer_cancel },
|
||||
{ BT_MESH_BLOB_OP_BLOCK_GET, BT_MESH_LEN_EXACT(0), handle_block_get },
|
||||
{ BT_MESH_BLOB_OP_BLOCK_START, BT_MESH_LEN_EXACT(4), handle_block_start },
|
||||
{ BT_MESH_BLOB_OP_CHUNK, BT_MESH_LEN_MIN(2), handle_chunk },
|
||||
{ BT_MESH_BLOB_OP_INFO_GET, BT_MESH_LEN_EXACT(0), handle_info_get },
|
||||
BT_MESH_MODEL_OP_END,
|
||||
};
|
||||
|
||||
static int blob_srv_init(struct bt_mesh_model *mod)
|
||||
{
|
||||
struct bt_mesh_blob_srv *srv = mod->user_data;
|
||||
|
||||
srv->mod = mod;
|
||||
srv->state.ttl = BT_MESH_TTL_DEFAULT;
|
||||
srv->block.number = 0xffff;
|
||||
srv->state.xfer.chunk_size = 0xffff;
|
||||
k_work_init_delayable(&srv->rx_timeout, timeout);
|
||||
k_work_init_delayable(&srv->pull.report, report_timeout);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int blob_srv_settings_set(struct bt_mesh_model *mod, const char *name,
|
||||
size_t len_rd, settings_read_cb read_cb,
|
||||
void *cb_arg)
|
||||
{
|
||||
struct bt_mesh_blob_srv *srv = mod->user_data;
|
||||
ssize_t len;
|
||||
|
||||
if (len_rd < offsetof(struct bt_mesh_blob_srv_state, blocks)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
len = read_cb(cb_arg, &srv->state, sizeof(srv->state));
|
||||
if (len < 0) {
|
||||
return len;
|
||||
}
|
||||
|
||||
srv->block.number = 0xffff;
|
||||
srv->state.xfer.chunk_size = 0xffff;
|
||||
|
||||
if (block_count_get(srv) > BT_MESH_BLOB_BLOCKS_MAX) {
|
||||
LOG_WRN("Loaded block count too high (%u, max: %u)",
|
||||
block_count_get(srv), BT_MESH_BLOB_BLOCKS_MAX);
|
||||
return 0;
|
||||
}
|
||||
|
||||
phase_set(srv, BT_MESH_BLOB_XFER_PHASE_SUSPENDED);
|
||||
|
||||
LOG_DBG("Recovered transfer from 0x%04x (%llu)", srv->state.cli,
|
||||
srv->state.xfer.id);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int blob_srv_start(struct bt_mesh_model *mod)
|
||||
{
|
||||
struct bt_mesh_blob_srv *srv = mod->user_data;
|
||||
int err = -ENOTSUP;
|
||||
|
||||
if (srv->phase == BT_MESH_BLOB_XFER_PHASE_INACTIVE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (srv->cb && srv->cb->recover) {
|
||||
srv->io = NULL;
|
||||
err = srv->cb->recover(srv, &srv->state.xfer, &srv->io);
|
||||
if (!err && srv->io) {
|
||||
err = io_open(srv);
|
||||
}
|
||||
}
|
||||
|
||||
if (err || !srv->io) {
|
||||
LOG_WRN("Abandoning transfer.");
|
||||
phase_set(srv, BT_MESH_BLOB_XFER_PHASE_INACTIVE);
|
||||
srv->state.xfer.mode = BT_MESH_BLOB_XFER_MODE_NONE;
|
||||
srv->state.ttl = BT_MESH_TTL_DEFAULT;
|
||||
erase_state(srv);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void blob_srv_reset(struct bt_mesh_model *mod)
|
||||
{
|
||||
struct bt_mesh_blob_srv *srv = mod->user_data;
|
||||
|
||||
phase_set(srv, BT_MESH_BLOB_XFER_PHASE_INACTIVE);
|
||||
srv->state.xfer.mode = BT_MESH_BLOB_XFER_MODE_NONE;
|
||||
k_work_cancel_delayable(&srv->rx_timeout);
|
||||
k_work_cancel_delayable(&srv->pull.report);
|
||||
erase_state(srv);
|
||||
}
|
||||
|
||||
const struct bt_mesh_model_cb _bt_mesh_blob_srv_cb = {
|
||||
.init = blob_srv_init,
|
||||
.settings_set = blob_srv_settings_set,
|
||||
.start = blob_srv_start,
|
||||
.reset = blob_srv_reset,
|
||||
};
|
||||
|
||||
int bt_mesh_blob_srv_recv(struct bt_mesh_blob_srv *srv, uint64_t id,
|
||||
const struct bt_mesh_blob_io *io, uint8_t ttl,
|
||||
uint16_t timeout_base)
|
||||
{
|
||||
if (bt_mesh_blob_srv_is_busy(srv)) {
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
if (!io || !io->wr) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
srv->state.xfer.id = id;
|
||||
srv->state.ttl = ttl;
|
||||
srv->state.timeout_base = timeout_base;
|
||||
srv->io = io;
|
||||
srv->block.number = 0xffff;
|
||||
srv->state.xfer.chunk_size = 0xffff;
|
||||
phase_set(srv, BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START);
|
||||
store_state(srv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bt_mesh_blob_srv_cancel(struct bt_mesh_blob_srv *srv)
|
||||
{
|
||||
if (!bt_mesh_blob_srv_is_busy(srv)) {
|
||||
return -EALREADY;
|
||||
}
|
||||
|
||||
cancel(srv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool bt_mesh_blob_srv_is_busy(const struct bt_mesh_blob_srv *srv)
|
||||
{
|
||||
return srv->phase != BT_MESH_BLOB_XFER_PHASE_INACTIVE &&
|
||||
srv->phase != BT_MESH_BLOB_XFER_PHASE_SUSPENDED &&
|
||||
srv->phase != BT_MESH_BLOB_XFER_PHASE_COMPLETE;
|
||||
}
|
||||
|
||||
uint8_t bt_mesh_blob_srv_progress(const struct bt_mesh_blob_srv *srv)
|
||||
{
|
||||
uint32_t total;
|
||||
uint32_t received;
|
||||
|
||||
if (srv->phase == BT_MESH_BLOB_XFER_PHASE_INACTIVE ||
|
||||
srv->phase == BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
total = block_count_get(srv);
|
||||
|
||||
received = 0;
|
||||
for (int i = 0; i < total; ++i) {
|
||||
if (!atomic_test_bit(srv->state.blocks, i)) {
|
||||
received++;
|
||||
}
|
||||
}
|
||||
|
||||
return (100U * received) / total;
|
||||
}
|
||||
Loading…
Reference in a new issue