蓝牙Mesh专有DFU
- Mesh专有DFU协议介绍
- 特征
- DFU模式和类型
- 角色
- 并发传输
- 混合设备的网络
- 传输速率
- 后台操作
- 传输分区
- 内存映射
- 安全
- DFU固件ID
- Application firmware ID
- SoftDevice firmware ID
- Bootloader firmware ID
- 设备页面
- 格式
- 内容
Mesh专有DFU协议介绍
设备固件更新(Device Firmware Update, DFU)是对蓝牙mesh设备的固件进行更新的过程。
Nordic的mesh DFU协议采用了专有的nRF OpenMesh项目,并在专有的OpenMesh协议上运行。OpenMesh协议是一种基于广播的协议,类似于蓝牙mesh,但它不支持寻址、确认消息传递或加密。
特征
专有的mesh DFU协议优化为尽可能有效地更新网络中的所有设备。尽管它与nRF5 SDK Bootloader和DFU模块共享一些工具和代码模块,但在协议和操作方面存在一些差异,以便尽可能轻松地更新大量设备。
DFU模式和类型
Mesh网络支持两种DFU类型,一种是后台模式,新固件在应用运行过程中通过后台传输,同时在传输完成的时候会通知应用程序,应用程序可以在新固件准备好之后改写flash。另一种是Bootloader模式,用于应用程序无法运行时使用BootLoader进行传输,此模式主要用于在应用程序出现故障时的回退机制。
在每一种DFU模式下,都可以分别对BootLoader,Application和SoftDevice进行DFU传输。这三种固件都必须单独传输,并且每个都有自己的标识符,每个都在内存映射中有自己的区域。用于Mesh SoftDevice和Bootloader的nRF5 SDK可以独立更新。如果想在Bootloader程序中进行小的更改,这可以节省时间和内存。
角色
当设备作为DFU传输的发起者时,使用源角色。源设备控制发送报文的时间间隔。它还响应DFU数据请求包。源角色由nRF Util工具控制。
目的角色用于设备升级时使用。专有Mesh DFU模块接收新固件并在传输完成时通知应用程序。在这个角色中,专有的Mesh DFU模块也重传它接收到的DFU数据包。
中继角色用于重传从其他设备接收到的DFU数据包。在此角色下,设备不将接收到的报文保存到flash中。
并发传输
与nRF5 SDK DFU相反,整个蓝牙mesh网络可以通过并发传输同时更新。
蓝牙Mesh网络可以包含数百个设备,逐个更新所有设备需要花费大量时间。为了解决这个问题,专有的mesh DFU协议允许mesh网络设备将它们接收到的数据转发给它们的邻居。被动mesh设备和接收传输的设备都将转发所有数据包,从而确保DFU传输到网络中的所有设备。这种方法比将整个DFU传输单独传递到每个设备要快得多。
接收到DFU传输的设备将对它接收到的每条数据报文执行以下步骤:
1、验证数据包尚未被接收。
2、在flash中以传输数据的适当偏移量存储数据包的有效载荷。
3、标记接收到的数据包。
4、以指数间隔重新传输数据包的预定次数。
对传输内容不直接感兴趣的设备将只执行步骤1、3和4,以确保网络中更远的目标设备仍然接收到数据包。
混合设备的网络
通常,蓝牙mesh网络包含具有多个不同角色和固件的设备。在执行DFU传输时,能够区分这些设备是很重要的。如果灯开关设备的固件更新到灯泡上,很可能灯泡停止工作。这对于一对一DFU传输是不同的,就像nRF5 SDK DFU协议所执行的那样,发送方能够识别目标设备并向其发送正确的固件。
专有mesh DFU协议通过允许每种设备类型除了应用程序版本号外,还拥有自己的应用程序ID来处理混合设备网络。当设备收到即将到来的DFU传输的通知时,它可以将传输的应用程序ID与自己固件的应用程序ID进行比较。如果应用id匹配,并且传入的传输版本较高,则设备通常接受该传输。如果一个设备收到即将到来的DFU传输通知与自己的应用程序ID不同,它可以选择作为该传输的中继设备,或者忽略它。
传输速率
与nRF5 SDK DFU协议相比,专有mesh DFU传输速度相当慢。例如,传输100 kB的固件映像需要大约一个小时。专有的mesh DFU协议依赖于冗余来确保可靠的通信,因此与nRF5 SDK DFU相比,传播相同数量的数据需要更长的时间。DFU数据以16字节的块定期发送,每个包都有一些冗余传输,以确保所有设备都能接收到它。报文间隔由传输源设备控制。默认情况下,蓝牙mesh DFU工具每500毫秒发出一个新数据包(使传输速率为16 B/500 ms = 32 B/s),但该数字应根据蓝牙mesh网络属性进行调整。
需要考虑的一些网络特性有:
网络密度:在彼此radio范围内的设备数量对数据包接收速率有很大影响。在彼此radio距离内的设备数量越多,会导致更多的数据包冲突,从而降低总吞吐量。
网络跨度:网络中的每一跳都有一定的丢包风险,并造成流量的延迟。网络中的跳数越高,某些设备错过传输的风险就越高。
网络拓扑结构:虽然高节点密度可能会对传输成功率产生负面影响,但到达目标节点的路径太少可能会导致数据包丢失。目标节点越是依赖于多个中继设备才能成功传输,那么在传输过程中的某个时刻丢失目标节点的可能性就越高。
外部噪音:当部署在嘈杂的环境中时,专有的mesh DFU性能更差(像所有无线技术一样)。
因为这些特征对于所有的部署都是不同的,所以不可能制定一个适用于所有网络的通用规则。为了最大限度地提高DFU性能:
① 调优单个部署;
② 如果可能的话,将DFU转移安排在预期的低流量期间,噪音最少。
后台操作
由于专有mesh DFU协议的传输速率相对较慢,传输最终可能需要一个多小时,这对于许多应用程序来说是不可接受的停机时间。为了解决这个问题,专有的网格DFU实现了一个后台模式。
后台模式允许应用程序在DFU传输过程中继续正常运行。后台模式是默认的操作模式,除非设备上没有有效的应用程序,在这种情况下,它会回落到引导加载程序(BootLoader)。
传输分区
接收后台DFU传输的设备必须将传入的包存储在flash区域中未使用的分区,以避免在运行时覆盖自己。在此过程中,DFU传输数据被存储在分区中,一旦完成,应用程序可以告诉引导加载程序将分区内容复制到应用程序区域,从而有效地完成更新。尽管引导加载程序会尽快通知应用程序完成传输,但应用程序可以在任何时候自由地复制分区,甚至根本不复制。同一时间,每种传输类型只能有一个分区,并且分区不能重叠。完成分区DFU传输后将删除同一传输类型的现有扇区。
分区必须放置在一个flash区域中,该区域必须足够大,以便在传输过程中容纳整个传入的应用程序,并且不能与新应用程序或旧应用程序重叠。为了确保最大的空间来接收传输,无论是作为分区还是作为完整的应用程序,通常建议将分区的开始位置放在设备应用程序部分的正中间。如果在传输完成后,传输内容不能用于分区或应用程序,则设备必须回到引导加载程序,并在引导加载程序模式下执行传输。
内存映射
专有mesh DFU协议使用与nRF5 SDK的Bootloader和DFU模块相同的闪存映射,仅有细微差别。与将MBR参数存储放在Bootloader和Bootloader设置(在专有mesh DFU中称为设备页面)之间不同,专有mesh DFU协议中的MBR参数存储放在Bootloader和应用程序区之间。
内存映射图:
内存映射包含以下主要固件元素:
SoftDevice
Application
Bootloader
这些与前面提到的传输类型相对应。每个固件元素都可以通过DFU传输单独更新。应用程序使用引导加载程序来执行接收和中继算法步骤,即使在后台模式下工作也是如此。在初始化蓝牙mesh框架时,DFU模块初始化引导加载程序中的命令处理程序模块,该模块与应用程序一起运行。
为了能够与应用程序一起运行,BootLoader程序保留设备上最后768字节的RAM。在所有蓝牙mesh项目文件和链接器脚本中都会考虑到这个保留的RAM。未能保留这些字节将导致应用程序启动时BootLoader程序出现意外行为。
安全
与nRF5 SDK类似,专有mesh DFU不加密DFU传输的数据。
在蓝牙mesh的情况下,这是一个从OpenMesh协议继承的限制,这意味着在任何情况下都不应该将安全敏感数据(如密钥)作为DFU传输的一部分发送。然而,专有mesh DFU确实具有椭圆曲线数字签名(ECDSA)用于验证传输。虽然签名是可选的,但强烈建议对所有传输签名。在创建DFU传输时,使用私有签名密钥执行签名,并且可以使用匹配的公共签名密钥对所有蓝牙mesh设备进行预编程,以对固件进行身份验证。如果蓝牙mesh设备具有公共签名密钥,则在完成传输之前,它将始终要求签名通过。签名算法通过创建传输元数据和固件的SHA256哈希来执行。
哈希分解:
固件ID (F)的大小取决于传输的类型。
ECDSA使用NIST P-256曲线(secp256r1)创建签名:
signature = ecc_sign(curve=P-256, private_key, hash)
在目标设备上使用匹配的公钥验证签名:
authenticated = ecc_verify(curve=P-256, public_key, hash, signature)
由于哈希需要整个固件数据,所以直到整个传输完成后才会检查签名。这种设计为目标设备上的拒绝服务攻击创造了可能性,因为攻击者可能会发起错误的传输。这种错误的传输可能与正常的传输难以区分,直到最后的签名检查,此时目标设备可能已经花费了大量资源来接收传输。然而,这个攻击向量不允许攻击者在目标设备上执行任何代码,因为当签名检查失败时,目标设备将删除所有关于传输的信息。
DFU固件ID
所有专有mesh DFU传输都由固件ID标识。固件ID的结构取决于传输类型:
Application firmware ID
SoftDevice firmware ID
Bootloader firmware ID
为了决定是否接受传入的传输,所有蓝牙mesh设备都在其设备页面中携带其当前固件id。
Application firmware ID
应用程序固件ID标识应用程序。
每个应用程序都有一个ID和版本号。通常,设备应该接受DFU应用程序传输与自己的固件相同应用程序ID,并且版本号更高。版本号为32位数字,版本方案由用户自定义。
当处于引导加载程序模式时,预构建的引导加载程序强制执行严格递增的版本号。这可以防止恶意设备将固件降级到以前的版本,这种攻击可以用来重新引入固件中的旧弱点。当处于后台模式时,不强制执行此规则,但强烈建议使用此规则,因为没有其他原生方法可以防止恶意降级。
要为每个应用程序创建唯一的固件ID,应用程序ID包含一个32位的Company ID字段,用于标识设备供应商。公司ID字段可以包含:
①蓝牙SIG分配的公司ID (0xFFFF或更低),或
②随机选择的大于0xFFFF的标识符。
随机选择的标识符不能保证唯一,但它允许没有分配公司ID的供应商保持高概率的唯一性。
SoftDevice firmware ID
Nordic为每个SoftDevice版本分配一个唯一的16位SoftDevice标识符,可以从固件中读出。
设备上的SoftDevice固件ID意味着匹配这个数字(尽管这不是正确操作所明确要求的)。由于SoftDevice id代表的是发布id,而不是不断增加的版本号,因此是否接受传入SoftDevice DFU传输的策略是用户可定义的。
当处于引导加载模式时,如果它收到一个FWID信标,其中的应用程序ID代表当前应用程序的更高版本,但具有不同的SoftDevice固件ID,则引导加载程序开始请求SoftDevice更新。在后台模式下,升级策略由用户自定义。
由于引导加载程序实现的限制,只有当新的SoftDevice能够适应定义的SoftDevice区域时,才能接受SoftDevice DFU传输。
Bootloader firmware ID
Bootloader程序固件ID由一个8位的引导程序ID字段和一个8位的引导程序版本字段组成。就像应用程序一样,引导加载程序可以有不同的配置,每种配置都可以有不同的版本。
在引导加载程序模式下,当引导加载程序ID字段与其当前引导加载程序ID相同并且引导加载程序版本字段大于当前引导加载程序版本时,引导加载程序接受传入的引导加载程序DFU传输。
在后台模式下,升级策略由用户自定义。
设备页面
所有运行专有mesh DFU Bootloader程序的设备都需要在flash中保存设备页面。设备页定义设备配置,并充当Bootloader程序的操作参数。
设备页面必须在主机上生成,并在部署前在每个设备上闪现。生成设备页面的device_page_generator.py脚本可以在tools/dfu/中找到。
格式
设备页面是设备最后一个flash页面的单页flash管理区域。
内容
设备页包含引导加载程序参与DFU传输所需的所有信息:
① Flash区域:每种传输类型(SoftDevice、bootloader和application)在flash中都有一个指定的区域。这些区域在设备页面中定义,并且必须能够包含其类型中最大可能的固件块。
② 固件ID:每种传输类型(SoftDevice、bootloader和application)都有一个指定的固件ID。这些固件id用于决定是否接受传入的DFU传输,并在每次DFU传输完成后更新。
③ 公共签名密钥:用于验证DFU传输签名。
④ 状态标志:指示每个固件块(SoftDevice、bootloader和application)有效性的标志。
⑤ 分区传输:设备上的每个分区传输都有一个专门的结构来描述它。此条目类型是在每次分区传输成功后由引导加载程序生成的。每种传输类型只能有一个分区。
⑥ 固件签名:每个固件的签名(如果存在)。
下表列出了可能的条目,包括必需的和可选的。
签名公钥:用于签名验证的公钥。
固件ID:当前固件ID。固件ID条目是三个不同的专有mesh固件ID的连接。
标志:每个固件的当前状态。
SoftDevice 区域:SoftDevice 固件可以写入的Flash区域。
Bootloader 区域:Bootloader 固件可以写入的Flash区域。
Application 区域:Application 固件可以写入的Flash区域。
SoftDevice 签名:当前SoftDevice的签名。
Bootloader 签名:当前Bootloader的签名。
Application 签名:当前Application的签名。
SoftDevice 分区:SoftDevice传输分区信息。填充(Padding)是一个固定的数据字段,其值始终为0。
Bootloader 分区:Bootloader 传输分区信息。填充(Padding)是一个固定的数据字段,其值始终为0。
Application 分区:Application 传输分区信息。