蓝牙mesh协议
蓝牙mesh是基于蓝牙BLE核心规范(Bluetooth Low Energy Core Specification)实现的一种网络协议。根据蓝牙mesh系统分层架构可以看出,如图2-1:
图2-1 蓝牙mesh系统分层架构
2.1 承载层
承载层(Bearer Layer)定义了网络消息时如何在各个节点之间传输的。目的蓝牙mesh规范定义了两种承载器类型:广播承载器和GATT承载器,未来可能会定义更多类型的承载器中的一种。
2.1.1 广播承载层
当使用广播承载器时,使用蓝牙BLE的广播报文来传输蓝牙mesh的数据,蓝牙BLE广播报文中的广播类型字段(AD Type)被定义为Mesh Message,具体数据格式如表2-1所示:
表2-1 Mesh Message广播类型
| 长度 | 广播类型 | 内容 |
|---|---|---|
| 0xXX | Mesh Message | 网络层PDU(Protocol Data Unit) |
- 广播类型为Mesh Message的广播报文都必须是不可连接且不能扫描的非定向广播包。如果一个蓝牙mesh节点收到一个广播类型为Mesh Message的可连接或者可扫描广播报文,那么应该忽略这个报文。
- 如果一个设备只支持广播承载器,那么它应该使用被动扫描,且它的扫描周期应尽可能接近100%。以避免错失蓝牙mesh或者配网过程的广播报文。所有的设备都应支持蓝牙GAP规范中的观察者角色和广播者角色。
2.1.2 GATT承载器
GATT承载器能够让那些不支持广播承载的设备也能加入蓝牙mesh网络。
- GATT承载器通过在两个设备之间建立GATT连接并使用代理协议来发送和接受代理数据报文。GATT承载器用属性协议(ATT)写入一个特性或者接受通知来传输蓝牙mesh消息。GATT承载器定义了两个角色:GATT承载客户端和GATT承载服务端。GATT承载客户端应为GATT客户角色;GATT承载服务端应为GATT服务角色。GATT承载服务端应当并且只能实例化一个蓝牙mesh代理服务,GATT承载客户端应支持蓝牙mesh代理服务。
- GATT承载客户端应执行GATT主要服务发现流程,用于发现蓝牙mesh代理服务,在方式上可以使用发现所有主要服务,或者按UUID来发现主要服务。
- GATT承载客户端应使用如下两种子过程来发现服务的特征:发现服务中所有特征或通过UUID发现特性。
- GATT承载客户端应使用GATT发现所有特征描述符子过程来发现特征描述符。
- GATT承载客户端应发现Mesh Proxy Data In特性、Mesh Proxy Data Out特性及其客户端特征配置(Client Characteristic Configuration)描述符。一旦发现客户端特征配置描述符,应启用通知。
- 要发送代理PDU,GATT承载客户端应使用Write Without Response子过程通过写入Mesh Proxy Data In 特性将代理PDU写入GATT承载服务器。
- 为了接受代理PDU,GATT承载客户端应能够接受Mesh Proxy Data Out特性的多个通知。每个通知都包含一个独立的代理PDU。
2.2 网络层
网络层(Network Layer)定义了PDU(Protocol Data Unit)的格式,它允许承载层来传输底层传输层的报文。它对承载层接受的消息报文进行解密并验证,将其传给底层传输层或转发给其他节点:它同样会对底层传输层输出的消息进行加密并验证,将其传给承载层。 在网络层中,多字节数据使用“大端”格式来发送。
2.2.1 地址
网络层定义了4种基本类型的地址:未分配地址、单播地址、虚拟地址和组播地址。地址长度为16位,编码方式如表2-2所示:
表2-2 网络层地址的编码方式
| 地址类型 | 值 |
|---|---|
| 未分配地址 | 0b0000000000000000 |
| 单播地址 | 0b0xxxxxxxxxxxxxxx(除0b0000000000000000) |
| 虚拟地址 | 0b10xxxxxxxxxxxxxx |
| 组播地址 | 0b11xxxxxxxxxxxxxx |
-
未分配地址 未分配地址是尚未被配置的节点元素的地址或未分配的地址。未分配地址的值为0x0000。例如,可以通过将模型的发布地址设置为未分配地址来禁用模型的消息发布。未分配地址不得用于消息的源或目标地址字段。
-
单播地址 单播地址是分配给每个元素的唯一地址。单播地址的第15位被设置为0。单播地址的值不能为0x0000,因此单播地址的取值范围是0x0001到0x7FFF(77777)。在配网阶段,配网器会在网络节点的生命周期内为节点的每个元素分配单播地址。改地址可以由配网器取消分配,以允许被重用。 单播地址既可在消息的源地址字段中使用,也可在消息的目标地址字段中使用。发送到单播地址的消息最多只能由一个元素处理。
-
虚拟地址 虚拟地址表示一组目标地址。每个虚拟地址在逻辑上代表一个标签UUID,它是一个128位的值,无须集中管理。一个或多个元素可以配置发布或订阅同一个标签UUID。标签UUID不会被传输,应该用作上层传输层中消息完整性校验值的附加数据字段。 虚拟地址也是一个16位的值,其中第15位被设为1,第14位被设为0,第0到13位是一个由标签UUID派生出的哈希值,因此每个哈希值都可表示许多标签UUID。 当上层传输层接受具有匹配哈希值的虚拟地址的数据报文时,上层传输层将使用每个对应的标签UUID作为附加数据,且是消息验证的一部分,直到找到匹配的虚拟地址为止。但是控制报文不能使用虚拟地址。 标签UUID是随机生成的,配置客户端可以分配和追踪虚拟地址,但是两个设备也可以使用带外(Out-of-Band,OOB)机制来创建虚拟地址。与组播地址不同,这些地址可以由相关设备协商一致,不需要在集中式供应数据库中注册,因为它们不太可能被复制。虚拟地址的一个缺点是,在配置期间需要多段消息将标签UUID传输到发布节点或订阅节点。 虚拟地址的取值范围是从0x8000到0xBFFF(100000 ~ 137777)。
-
组播地址 可以将0或者多个元素的地址配置成同一个地址,这个地址就是组播地址。组播地址的第15位被设置为1,第14位被设置为1,其中从0xFF00到0xFFFF(177400 ~ 177777)的组播地址保留给固定的用途,从0xC000到0xFEFF(140000 ~ 177377)的组播地址可以用于其他用途。 组播地址只能在消息的目标地址字段使用,发送给组播地址的消息会被订阅这个组播地址的所有模型实体接收到,组播地址有两种类型:动态分配的组播地址和固定的组播地址。固定组播地址如表2-3:
表2-3 固定组播地址
地址值 固定组播地址名称 0xFF00 RFU 0xFFFC 所有代理节点 0xFFFD 所有朋友节点 0xFFFE 所有中继节点 0xFFFF 所有节点 发送到所有代理节点地址的消息应由启动代理功能的所有节点的主元素处理。发送到所有朋友节点地址的消息应由启用朋友功能的所有节点的主元素处理。发送到所有节点地址的消息应由所有节点的主元素处理。
2.2.2 网络层PDU
网络层PDU字段定义如表2-4:
表2-4 网络层PDU字段定义
| 名 称 | 位 数 | 备 注 |
|---|---|---|
| IVI | 1 | IV索引的最低有效位 |
| NID | 7 | 来自NetKey,用来标识验证此PDU的Encryption Key和Privacy Key |
| CTL | 1 | 网络控制 |
| TTL | 7 | 生存时间 |
| SEQ | 24 | 序列号 |
| SRC | 16 | 源地址 |
| DST | 16 | 目标地址 |
| TransportPDU | 8~128 | 传输协议数据单元 |
| NetMIC | 32或 64 | 网络层消息完整性校验 |
网络层PDU使用从单个网络密钥派生的密钥进行保护,由NID字段标识。
- IVI(初始化向量索引 Initialization Vector Index)
- IVI字段是IV Index的最低有效位。
- NID (网络ID Network ID)
- NID字段是一个7位网络标识符,该标识符用来方便地查找用于验证和加密此网络层PDU的加密密钥和隐私密钥。NID值是与加密密钥和隐私密钥一起从网络密钥派生出来的。对于主网络消息和朋友及其低功耗节点之间的私有网络消息,它的派生方式有所不同。
- CTL(控制 Network Control)
- CTL字段是一个1位的值,用于确定消息是控制消息的一部分还是访问消息的一部分。
- 如果将CTL字段设置为0,则NetMIC为32位值,底层传输层的PDU包含一条访问消息。
- 如果将CTL字段设置为1,则NetMIC为64位值,底层传输层的PDU包含一条控制消息。
- TTL(生存时间 Time To Live)
- TTL字段是7位的,定义了以下值
- 0:未中继且将不会中继。
- 1:可能已中继,但不会中继。
- 2~126:可能已中继,并且可以中继。
- 127:尚未中继,可以中继。
- 该字段的初值由传输层(底层传输层、上层传输层、接入层、基础模型、模型)或应用程序设置,并由网络层作为中继节点运行时使用。
- 使用0的TTL值允许节点传输此消息,且表示它是一个不会中继的网络层PDU,因此接受节点可以确定发送节点处于自己的接受范围内。使用一个更大的TTL值则不能得出该结论。
- TTL字段是7位的,定义了以下值
- SEQ(序列号 Sequence Number)
- SEQ字段是一个24位的整数,与IV Index结合使用,对于该节点发起的每个新网络层PDU,该整数应该是唯一的值。
- SRC(源地址 Source Address)
- SRC字段是一个16位的值,它标识发起这个网络层PDU的元素的地址。此地址应为单播地址。SRC字段由发包节点来设置,作为中继节点运行的节点在转发过程中不能修改该字段。
- DST(目标地址 Destination Address)
- DST字段是一个16位的值,它标识网络层PDU所指向的一个或多个元素的地址,此地址应为单播地址、组播地址或虚拟地址。DST字段由发包节点设置,作为中继节点运行的节点在转发过程中不能修改该字段。
- TransportPDU(传输协议数据单元 Transport Protocol Data Unit)
- 从网络层的角度看,TransportPDU字段是数据的字节序列。当CTL位为0时,TransportPDU字段的最大值为128位。当CTL位为1时,TransportPDU字段的最大值为96位。TransportPDU字段由发包节点的底层传输层设置,网络层不能更改。
- NetMIC(网络消息完整校验值 Message Integrity Check For Network)
- NetMIC字段是一个32位或64位的字段(取决于CTL位的值),它用于验证DST和TransportPDU是否被更改。当CTL位为0时,NetMIC字段为32位。当CTL位为1时,NetMIC字段为64位。NetMIC由网络层中每个传输节点或中继该网络层PDU的节点设置。
2.2.3 网络层接口
网络层支持通过多个承载器发送和接受消息。一个承载器可能存在多个实例。承载器的每个实例都通过网络接口连接到网络层。例如。一个节点可能有三个承载器接口:一个广播承载器的接口和两个GATT承载器的接口。 网络层接口提供输入和输出过滤器。过滤器可以通过特定的PDU进行配置,也可以通过运行在节点上的服务(如mesh代理服务)在内部配置。
- 接口输入过滤器
- 接口输入过滤器决定传入的网络消息是交付给网络层以进行进一步处理,还是将其删除。
- 接受输出过滤器
- 接口输出过滤器决定是将传出的网络层消息传递给承载层,还是将其删除。接口输出过滤器应删除所有TTL值为1的消息。
- 本地网络接口
- 本地网络接口允许在同一节点内的元素之间发送消息。每一个节点都应实现一个本地网络接口。
- 通过本地网络接口接受消息后,应该将消息发送给节点的所有元素处理。
- 广播承载器的网络接口
- 广播承载器的网络接口允许使用广播承载器发送消息。当接受网络层未被标记为中继的网络层PDU时,广播承载器的网络接口应该使用网络传输状态的配置在广播承载网络上重传网络层PDU;而当从网络层接受被标记为中继的网络层PDU时,广播承载器的网络接口应该使用中继重传状态的配置在广播承载器上重传网络层PDU。
2.2.4 网络层行为
中继功能
中继功能用于中继节点或转发结点通过广播承载器接受的网络层PDU。此功能是可选的,如果支持此功能,则可以单独启用和禁用此功能。如果支持代理特性,则必须同时支持GATT承载器和广播承载器。
代理功能
代理功能指节点在GATT承载网络和广播承载网络之间中继或转发网络层PDU来实现GATT承载网络和广播承载网络间的消息互通。此功能是可选的,如果支持此功能,可以单独启用和禁用此功能。如果支持代理特性,则同时支持GATT和广播承载。
网络层PDU处理器
- 网络层接口将报文从承载层传递到网络层。网络接口应使用其输入过滤器定义的过滤规则进行过滤,如果消息通过输入过滤器,则将其传递到网络层进行进一步处理。
- 在收到消息后,节点应检查NID字段的值是否与一个或多个已知的NID匹配。如果NID字段值和已知的NID都不匹配,则应忽略该消息。如果NID字段值与已知的NID匹配,则节点应根据匹配的每个已知网络密钥对消息进行身份验证。如果消息没有通过任何已知的网络密钥的身份验证,则应忽略该消息。如果消息通过了根据网络密钥进行的身份认证,则SRC和DST字段被认为是有效的。当消息不在网络消息缓存中时,则消息应由底层传输层处理。
- 在转发消息时应使用与接受消息时相同的IV Index。
- 如果节点的网络层收到了从广播承载层分发过来的消息,并且该消息通过了验证要上报给底层传输层,并且节点启用了中继功能,消息的TTL字段的值为2或更大,且目标地址不是此节点的任何一个元素的单播地址,那么该消息的TTL字段值应当减1,网络层PDU应标记为中继PDU,并转发到连接到广播承载层的所有网络接口。建议在接受网络层PDU和中继网络层PDU之间加入一个小的随机延迟,以避免因同时接受同一个网络层PDU的多个网络消息而发生冲突。
2.3 底层传输层
底层传输层(Lower Transport Layer)用来将上层PDU传输到另外一个节点,可以将这些PDU作为单个PDU发送出去,也可以将其拆分为多个底层传输层PDU。同样,一旦接收端收到底层传输层PDU,也需要进行组包再传给上一个处理。
2.3.1 底层传输层PDU
底层传输层PDU的第1字节的最高位是SEG字段,该字段用来确认此PDU是经过分包的还是未分包的消息。根据网络层PDU中CTL字段值和底层传输层PDU中SEG字段值不同,可分为4种不同的消息类型,如表2-5所示:
表2-5 底层传输层PDU的类型
| CTL | SEG | 底层传输层PDU的类型 |
|---|---|---|
| 0 | 0 | 未分包访问消息 |
| 0 | 1 | 分包访问消息 |
| 1 | 0 | 未分包控制消息 |
| 1 | 1 | 分包控制消息 |
1.未分包访问消息
未分包访问消息用来传输一个不用拆包的上层传输层PDU,如图2-2和表2-6所示:
图2-2 未分包访问消息
表2-6 未分包访问消息格式
| 字 段 | 长 度(bit) | 备 注 |
|---|---|---|
| SEG | 1 | 0=未分包消息 |
| AKF | 1 | 应用密钥标志位 |
| AID | 6 | 应用密钥标识 |
| 上层传输接入PDU | 40~120 | 上层传输接入PDU |
上层传输层根据应用密钥或者设备密钥来设置AKF和AID字段的值。
2.分包访问消息
分包访问消息用于传输上层传输层访问PDU中的其中一段,图2-3显示了分包访问消息的示例,表2-7显示了此消息的字段定义。
图2-3 分包访问消息
表2-7 分包访问消息格式
| 字 段 | 长 度(bit) | 备 注 |
|---|---|---|
| SEG | 1 | 1=分包消息 |
| AKF | 1 | 应用密钥标志位 |
| AID | 6 | 应用密钥标识 |
| SZMIC | 1 | TransMIC长度 |
| SeqZero | 13 | SeqAuth的低有效位 |
| SegO | 5 | 分包号 |
| SegN | 5 | 最后一包分包号 |
| Segment m | 8~96 | 上层传输层访问PDU中的Segment m |
- SZMIC字段表示上层传输层访问PDU中TransMIC的大小。若SZMIC字段为0,则TransMIC是一个32bit的值;若SZMIC字段为1,TransMIC则是一个64bit的值。
- SeqZero字段由上层传输层设置;SegO字段应被设置为该上层传输层PDU的m分包的分包号(从0开始);SegN字段应被设置为该上层传输层PDU的最后一个分包号。
- Segment m字段是分包号为m的内容,除最后一个分包外的分包Segment m,它的内容是从第12 x m字节到第12 x m + 11字节,而最后一个分包内容则是从第12 x m字节到消息结尾。
- 对于相同的上层传输层访问PDU,每个分包访问消息的AKF、AID、SZMIC、SeqZero和SegN字段应该具有相同的值。
3.未分包控制消息
未分包控制消息用于传输一个应答消息或者传输控制消息。图2-4展示了未分包控制消息的示例,表2-8展示了它的格式。
图2-4 未分包控制消息
表2-8 未分包控制消息格式
| 字 段 | 长 度(bit) | 备注 |
|---|---|---|
| SEG | 1 | 0=未分包消息 |
| Opcode | 7 | 0x00=分包应答 0x01到0x7F=传输控制消息的Opcode |
| Parameters | 0到88 | 传输控制消息参数 |
分包应答消息的Opcode字段应设为0,底层传输层使用分包应答消息来确认分包消息。Parameters字段根据Opcode字段的需要来设置,如图2-5和表2-9所示。
图2-5 分包应答消息
表2-9 分包应答消息格式
| 字 段 | 长 度(bit) | 备 注 |
|---|---|---|
| SEG | 1 | 0 = 未分包消息 |
| Opcode | 7 | 0x00 = 分包应答消息 |
| OBO | 1 | 朋友代理低功耗节点 |
| SeqZero | 13 | 上层传输层PDU的SeqZero |
| RFU | 2 | 保留 |
| BlockAck | 32 | 分包的块应答 |
- 传输控制消息中的Opcode字段设为0x00。
- OBO字段为0,表示接受信息的节点直接寻址;OBO字段为1,表示接受信息的是朋友节点,朋友节点代表低功耗节点应答此消息。
- SeqZero字段应被设置为被应答的上层传输层消息的SeqZero。
- BlockAck字段应被设置为收到的分包。最低有效位,即位0表示分包0;最高有效位,即位31表示分包31.如果位n被设置为1,则表示应答分包n。大于SegN的分包的位应被设置为0并在接受时忽略。
- 如果在接受的分包信息中TTL为0,则建议在发送分包应答消息中也将TTL设置为0。
4.分包控制消息
当传输控制消息不适合使用单个网络层PDU来传输的时候,就使用分包控制消息来传输,如图2-6和表2-10所示。
图2-6 分包控制消息
表2-10 分包控制消息格式
| 字 段 | 长 度(bit) | 备 注 |
|---|---|---|
| SEG | 1 | 1=分包消息 |
| Opcode | 7 | 0x00=保留 0x01到0x7F=传输控制消息的Opcode |
| RFU | 1 | 保留 |
| SeqZero | 13 | SeqAuth低有效位 |
| SegO | 5 | 分包号 |
| SegN | 5 | 最后一包分包号 |
| Segment m | 8到64 | 上层传输层访问PDU中的Segment m |
- Opcode 字段由上层传输层设置,0x00为保留值,收到后不得传输,直接忽略。
- SeqZero 字段应由上层传输层设置。
- SegO 字段应被设置为该消息中包含的上层传输层PDU的分包号(从0开始)。
- SegN 字段应被设置为该上层传输层PDU的最后一个分包号(从0开始)。
- Segment m字段是分包号为m的内容,除最后一个分包外的分包Segment m字段是从第8 x m 字节到第8 x m + 7 字节,而最后一个分包则从第8 x m字节到消息结束。
对于相同的上层传输层控制PDU,每个分包控制消息应具有相同的Opcode、SeqZero和SeqN值。
2.3.2 分包和组包
当传输大于15字节的上层传输层PDU时,底层传输层就需要对上层传输层PDU进行分包并重新组包。为了减少底层传输层包的数量,这里使用块应答的机制。 如图2-7示例中上层传输层访问PDU包含1字节的Opcode字段,3字节的NetKeyIndex和AppKeyIndex字段,还有16字节的AppKey字段。这意味着当使用应用密钥加密和验证时,上层传输层PDU为24字节。这被底层传输层分为两个包,既分包0和分包1。每个分包具有一个标识分包数的包头,然后被传递到网络层,在那里计算完整的网络层PDU。网络层再使用该网络层PDU的序列号加密网络层PDU,然后对这些消息进行模糊处理,最终只有NID(和IV索引)字节以明文形式可见。因此可以使用两个网络层PDU安全地传递单个访问消息。
图2-7 分包组包示例
上层传输层访问PDU和上层传输层控制PDU的分包处理过程是相同的,除非明确说明,下面的描述认为这两种PDU类型是相同的。
分包
底层传输层将上层传输层PDU分为一个或多个底层传输层PDU。同一时间底层传输层将同一个上层传输层PDU的分包访问消息或分包控制消息发送到同一目的地。只有当上一个传输层PDU的所有分包都已被应答或消息被取消时,底层传输层才可以发送另外一个上层传输层PDU。 除最后一个分包以外,上层传输层访问PDU的每个分包应为12字节,而最后一个分包长度可能更多。同样地,除最后一个分包外,上层传输层控制PDU的每个分包为8字节,而最后一个分包长度可能更多。
访问PDU和控制PDU的区别?
TODO
组包
当使用了低功耗节点功能时,消息应答由朋友节点执行,而低功率节点不会发送应答消息。在收到分包消息时,首先应检查SeqAuth以确定此消息是否正在接受或先前是否已接受。如果尚未接受,则接受设备应根据SegN字段分配足够的内存,以便存储上层传输层PDU的分包并跟踪它的分包是否被收到。
如果未使用低功耗节点功能,则该消息的目的地是单播地址,并且此时节点无法接受此上层传输层PDU,例如因为节点繁忙或资源不足以重新组装此消息,然后节点通过将BlockAck值设置为0x00000000来向源节点发信号,通知它无法接受此上层传输层PDU。
底层传输层针对每条收到的某个SeqAuth取值的所有分包消息都设置了序列认证值(Sequence Authentication Value)和块应答值(Block Acknowledgement)。
如果底层传输层收到SeqAuth值小于序列认证值的消息分包,则忽略该段。如果底层传输层收到新消息的分包,则它应将该段中的SeqAuth值保存为新的序列认证值。
如果底层传输层收到多个分包消息的其中一个分包,但此时因为它当前正忙或没有资源接受更多的分包消息,并且如果该消息的目的地是单播地址,底层传输层应回复一个BlockAck字段为0x00000000的应答消息。
当接受SeqAuth值大于序列认证值的一个分包消息时,底层传输层将启动不完成定时器,定义底层传输层接受不同分包的等待最大时间,此定时器应被设置为最少10s。
当接受SeqAuth值大于目的地为单播地址的序列认证值的分包消息时,底层传输层应启动一个应答定时器,该定时器定义底层传输层发送分包应答消息的时间,最少设置为150 + 50 x TTLms。
底层传输层应将接受的每个分包在块应答值中进行标记,该块应答值可以稍后传输回源节点。收到分包消息的所有分包之后,底层传输层将发送分包确认消息,其中BlockAck字段被设置为用于序列认证值的块应答值。它应取消未完成定时器和应答定时器,并将重新组装的消息发送到上层传输层。
当应答定时器到期时,底层传输层将为当前序列认证值包发送分包应答消息,其中BlockAck字段被设置为块应答值。
当未完成定时器到期时,底层传输层应认为正在接受的消息已经失败并取消应答定时器,之前收到的部分消息都应被忽略。
2.4 上层传输层
上层传输层(Upper Transport Layer)从访问层获取消息或内部生成上层传输层控制消息,并将这些消息传输到对端的上层传输层。对于来自访问层的消息,使用应用密钥执行消息的加密和认证;由上层传输层内部生成的传输控制消息仅在网络层加密和验证。
2.4.1 上层传输层访问PDU
当网络层PDU中的CTL字段为0时,上层传输层访问PDU包含一个访问荷载,使用应用密钥或设备密钥对访问荷载进行加密,并且将加密的访问荷载和关联的消息完整性校验值组合成上层传输层访问PDU。上层传输层访问PDU的字段和格式分别如表2-11所示。
表2-11 上层传输层访问PDU字段
| 字 段 | 字节数 | 备 注 |
|---|---|---|
| Encrypted Access Payload | 1 到 380 | 解密访问荷载 |
| TransMIC | 4 或 8 | 访问荷载的消息完整性校验值 |
图2-8 上层传输层访问PDU格式
访问荷载由访问层提供。如果TransMIC是32位,则访问荷载的长度可以从单字节380字节。如果TransMIC是64位,则访问荷载的长度可以从单字节到376字节。在上层传输层,该字段是不透明的,并且不能使用该字段内的消息。
传输的消息完整性检查(TransMIC)是一个32位或64位字段,用于验证访问荷载是否未更改。对于分包消息,其中SEG设置为1,TransMIC的大小由底层传输层PDU中的SZMIC字段的值确定。对于未分包的消息,TransMIC的大小为32位,用于数据消息。
2.4.2 上层传输层控制PDU
当CTL位为1时,上层传输层PDU包含一个传输控制消息。传输控制消息具有7位Opecode,用于确定参数的格式。该Opcode字段不包括在参数字段中,但包括在底层传输层PDU未分包控制消息中或分包控制消息的每个分包中。
上层传输层控制PDU未在上层传输层进行认证,而是依赖由网络层执行的认证。所有上层传输层控制PDU都使用64位NetMIC。底层传输层可以将消息分包为较小的PDU,以便通过网络层传递。因此,建议保留上层传输层控制PDU有效荷载大小,如表2-12所示,其中值标识最大有用参数字段大小,具体取决于数据包的数量。
表2-12 上层传输层控制PDU中最大有效荷载大小
| 封包数 | 上层传输层控制PDU荷载长度 |
|---|---|
| 1 | 11(未分包) |
| 1 | 8(分包) |
| 2 | 16 |
| 3 | 24 |
| n | n x 8 |
| 32 | 256 |
| 上层传输层控制PDU的最大长度为256字节。 |
2.4.3 上层传输层行为
所有访问消息应使用应用密钥或设备密钥进行加密,而TransMIC应设置为消息完整性校验值。每个消息都有一个序列号(SEQ),在底层传输层分包消息的上下文中,该SEQ对应于SeqAuth的24个最低位,用于接受方认证和解密访问消息。
底层传输层PDU中AKF和AID字段应根据用于加密和验证上层传输层PDU的应用密钥或设备密钥来设置。如果使用应用密钥,则AKF字段应设置为1,AID字段应被设置为应用密钥标识符(AID)。如果使用设备密钥,则AKF字段应被设置为0,AID字段应被设置为0b000000。
上层传输层不应在前一个上层传输层PDU已完成或已取消之前,将新的上层传输层PDU的分包发送到给定目的地。
在收到上层传输层访问PDU的时候,首先对访问荷载进行解密,并使用所有已知的应用密钥和设备密钥去尝试认证TransMIC,看哪个密钥的AKF和AID字段是匹配的。如果上层传输层访问PDU通过了验证和重放攻击的检查,则会将其传递给访问层,并提供此消息的上下文信息,例如源地址、目标地址、以及用于解密和验证的密钥。
在收到上层传输层控制PDU后,应根据该节点元素的单播地址检查PDU的目的地址,如果匹配,则处理该消息。
如果收到消息的节点支持且启用朋友功能,并且与低功耗节点建立了朋友关系,那么当此节点为低功耗节点维护的朋友订阅地址的列表中包含消息目的地址时,消息会被存储到节点上相应的朋友队列中。
2.4.4 朋友关系
蓝牙mesh为了优化低功耗节点的功耗,引入了朋友关系这个特性,原理就是低功耗节点不用一直监听发送给自己的消息,而是由它的朋友节点来存储发给它的消息,低功耗节点采用轮询的方式查看有没有自己的消息。这允许低功耗节点通过从朋友节点接受更新来确认何时有可接收的消息。
2.4.5 心跳消息
为了确定蓝牙mesh网络中的节点是否处于活动状态,有必要从该节点接受消息。向蓝牙mesh网络中的每个节点发送消息并接受它们的响应将非常耗电,因此每个节点可以被配置为定期发送单个节点消息。此消息称为心跳(Heartbeat)消息。
心跳消息的两个主要功能:
1. 确定节点在网状网络中是否仍处于活动状态
2. 确定节点的距离
根据配置服务器模型的配置,心跳消息将定期发送,可以发送有限次数也可以不限制发送次数。心跳消息将发送到已配置的目标,建议使用组播地址发送心跳消息,也可以使用特定的TTL值进行配置。
节点在收到心跳消息后,将对其进行计数。据收到的心跳消息数可确定网状网络是否可靠,以便从发送心跳消息的节点传递消息。
每个心跳消息都包含发送原始心跳消息时使用的初始TTL值。这允许接受设备确定该消息被重传的次数,既跳数,并且还可以使用最小和最大调数的记录来确定蓝牙mesh网络的可靠性。因此,可以使用心跳消息来确定用于寻址给定节点的最佳TTL值。
心跳消息还包括节点当前正在使用的功能。可以将节点配置为在启用或禁用各种功能时发送心跳消息。这允许确定蓝牙mesh网络内的各个节点上可用的特征。
发布心跳消息
心跳消息的发布由心跳发布(Heartbeat Publication)状态控制。当心跳发布的目的地址被设置为未分配地址时,或当心跳发布计数状态的值为0x0000时,不应发布心跳消息。只有当心跳消息在DST字段被设置为心跳发布目标状态的值并且TTL字段被设置为心跳发布TTL状态的值时才进行发布。
定期发布心跳消息也可由心跳发布计数(Heartbeat Publication Count)状态打开。发布心跳消息后,如果心跳发布计数器小于0xFFFF,则心跳发布计数器应减1。计数器为0x0000时就停止。在心跳发布时间状态已被配置为定期发布后,应尽快发布第一个心跳消息。下一个心跳消息应在心跳发布周期状态定义的秒数之后发布。
心跳发布功能(Heartbeat Publication Features)状态可设置心跳消息的触发发布。
- 如果中继位被设置为1,则在节点的中继状态变化时发布心跳消息。
- 如果代理位被设置为1,则在节点的GATT代理状态变化时发布心跳消息。
- 如果朋友位被设置为1,则在节点的朋友状态变化时发布心跳消息。
- 如果低功耗位被设置为1,则在低功耗节点建立或丢失朋友关系时发布心跳消息。
接受心跳消息
接受心跳消息由心跳订阅(Heartbeat Subscription)状态控制。
心跳订阅周期(Heartbeat Subscription Period)状态是倒数计时器,用于标识接受心跳消息时的剩余秒数。当计时器达到0时,应禁用心跳消息的接受。
收到心跳消息后,应增加心跳订阅计数(Heartbeat Subscription Count)状态的值。这个值不会循环计数,它停止在0xFFFF。
收到心跳消息后,应使用消息中的InitTTL值和收到的网络层PDU TTL字段值计算跳数值(称为RxTTL)如:hops = InitTTL - RxTTL + 1
如果跳数值小于心跳订阅最小跳数(Heartbeat Subscription Min Hops)状态,则应将其设置为心跳订阅最小跳数状态的新值。
如果跳数值高于心跳订阅最大跳数(Heartbeat Subscription Max Hops)状态,则应将其设置为心跳订阅最大跳数状态的新值。
2.5 访问层
访问层(Access Layer)负责定义应用如何使用上层传输层,定义应用数据格式,定义和控制上层传输层的应用数据加密和解密方式。
2.5.1 访问层数据包
前面介绍过上层传输层对访问层数据包(Access Payload)增加tranMIC数据完整性校验值,底层传输层负责对访问层数据包进行分包,最多可以分为16个包,每个包为12字节,也就是最大字节数为384。tranMIC有4字节和8字节两种,从而计算出访问层数据包的最大数据长度分别是380字节和376字节,访问层数据包分为Opcode和Parameters两部分,如表2-13所示。
表2-13 访问层数据包
| 字 段 | 长度(字节) | 备注 |
|---|---|---|
| Opcode | 1,2,3 | 操作码 |
| Parameters | 0 ~ 379 | 参数 |
Opcode 长度为1或者2字节,SIG已经预留定义,长度为3字节,用于各厂商定义,如表2-14所示。
表2-14 Opcode定义
| Opcode | 备 注 |
|---|---|
| 0xxxxxxx | 1-Octet Opcodes |
| 01111111 | RFU |
| 10xxxxxx xxxxxxxx | 2-Octet Opcodes |
| 11xxxxxx xxxxxxxx xxxxxxxx | 3-Octet Opcodes |
其中第1字节的最高两位决定了操作码的长度。
- 字节操作码是SIG定义的操作码,最多可定义0x00~0x7E共127个Opcode。
- 字节操作码是SIG定义的操作码,最多可定义0x8000~0xBFFF共16384个Opcode。
- 字节操作码,其中后两字节是Company ID,在Company ID不变的前提下,Vendor Opcode的取值范围是0xC0~0XFF。
2.5.2 访问层的行为
访问层消息发送
访问层所有的消息都是由模型(Model)实例来发送的,发送的消息内容包含目标地址、源地址、跳数(TTL). 源地址是发送此消息的模型所在元素(Element)的单播地址,目标地址可以是单播地址、组播地址、虚拟地址中的一种。 TTL字段定义了消息在节点间可以被转发的次数。访问层不保证消息成功送达,每个模型根据业务场景需要,决定是否重传。 当一个节点收到一条需要应答的消息时,如果多个节点同时发送应答,则会出现消息碰撞的情况。为了降低消息碰撞的概率,提高消息接受的成功率,在发送应答消息时需要增加延时。
- 当发送应答消息到某一个单播地址时,增加20~50ms的随机延时。
- 当发送应答消息到某一个组播地址或者虚拟地址时,增加20~500ms的随机延时。
访问层消息接受
访问层收到上层传输层上报的消息,对消息做合法性检查,检查通过则发给模型层处理。若以下条件都满足,则合法性检查通过。
- 操作码属于目标地址模型所属的元素。
- 目标地址属于支持该操作码的模型元素的单播地址,或者是订阅的组播地址、虚拟地址、SIG协议已经定义的组播地址。
- 模型绑定了对应的AppKey或者DeviceKey。
安全性考虑
消息是由上层传输层进行加密和认证的,节点发出的消息需要指定和模型绑定的AppKey或者DeviceKey进行加密。 相应地,应答消息需要用同样的AppKey或者DeviceKey来加密。
错误消息处理
有可能存在这样的场景:一个消息用不同的Key加密,但是在接受端网络层和传输层均通过了NetMIC和TransMIC校验。校验虽然通过了,但是解密出的数据是不能被元素中的模型处理的。 当一个元素收到一条非法的消息时,需要忽略这条消息,包含如下情况:
- 应用操作码是未知的,既这个元素下所有的模型都不支持这个操作码。
- 对应这个操作码的消息长度不合法。
- 应用参数包含了当前禁用的值。
- 当一个消息发送了一条需要响应的非法消息时,由于接收端会忽略这条消息,所以发送端也不会收到响应消息。
2.5.3 两种类型的消息
不需要应答的消息
当一个节点需要把自身的状态变化通知其他节点时,会发送一条状态消息。这个状态消息可以是一个不需要应答的消息(Unacknowledged Message),目标地址是这个模型要发布的组播地址或者单播地址。由于状态消息不需要应答,因此消息的发送端无法知道消息是否被成功送达接收端。
需要应答的消息
当发送端发送一条需要应答的消息时,接收端需要回复响应消息作为应答,通常情况下这个响应消息时状态消息。发送端发出需要应答的消息(Acknowledged Message)后,在超时时间内没有收到接收端的应答,会重新发送消息,超时时间数值由应用层控制。 当一条消息的目标地址不是单播地址时,比如发送到组播地址,发送端无法知道有多少节点会收到该消息并做应答,因此不建议发送需要应答的消息到组播地址。 如果需要应答的消息的TTL被设置为0,则应答消息的TTL也需要被设置为0。
2.5.4 订阅和发布
与订阅和发布强关联的是目标地址。若一个模型所在的元素订阅了目标地址,则该元素可以收到任何发送到目标地址的消息。若一个模型配置了状态发布,则该元素需要把模型状态变化的消息发布到对应的目标地址。
发布
发布,既一个模型主动发送未经请求的消息到目标地址的行为。这里的目标地址既发布地址。目标地址可以是单播地址、组播地址、虚拟地址三者之一,一个模型有且只有一个发布地址。
订阅
每个模型可以有一个或者多个订阅列表,每个列表中包含1个或者多个目标地址,这个目标地址决定了当前节点模型可以处理的消息范围。这里的订阅地址可以是组播地址或者虚拟地址的一种。
2.5.5 消息序列
需要应答的状态读取消息
模型的Client端发送状态读取消息,模型的Server端回应状态消息作为应答。
需要应答的状态设置消息
模型的Client端发送需要应答的状态设置消息,模型的Server端收到消息后,回复状态消息作为应答消息,再发布状态消息到配置过的组播地址,订阅了这个组播地址的节点可收到这条状态消息。如果Client也订阅了这个组播地址,那么Client会收到两条状态消息。
不需要应答的状态设置消息
模型的Client端发送了不需要应答的设置状态消息,模型的Server端收到消息后做状态转换,状态转换结束后把状态消息发布到组播地址,订阅该组播地址的节点可以收到状态消息。
需要应答的状态设置消息和周期性发布消息
Server端按照配置的发布参数周期性地向目标地址发送状态消息,当收到Client端发送的需要应答的状态设置消息时,立刻回复状态消息作为应答,再发布状态变化消息到组播地址。
2.6 基础模型层
基础模型层(Foundation Models Layer)定义了配置和管理蓝牙mesh网络所需的访问层状态、消息和模型。 节点的状态是一个复合状态,由一个或者多个状态来描述,包括成分数据、模型发布状态、心跳发布状态、心跳订阅状态、网络传输状态、网络中继重传状态等若干状态。