低功耗蓝牙BLE之报文
这篇文档是来自zzfenglin的博客。
报文
报文是链路层的基石。报文非常简单,它是带有标签的数据,由一个设备发送,一个或多个设备接收。标签指明了数据由谁发出,以及应该由哪些设备接收。
如上图所示,给出了报文的基本结构,适用于所有报文,无论其用途是什么。报文的开始是一小段交替序列,称为前导。之后是接入地址,接收机用它将报文和背景噪音区分开来。接入地址之后是报头和长度字节。再之后是报文的净荷,以及用于确保净荷正确性的循环冗余校验(CRC)。
广播与数据报文
低功耗蓝牙规范中,有两类报文:广播报文和数据报文。两类报文具有两种完全不同的用途。设备利用广播报文发现、连接其他设备。一旦连接建立之后,则开始使用数据报文。而两者的区别在于,数据报文只能被连接中的两个设备(主、从设备)所理解,而广播报文则可以广播给多个侦听设备或者只发送给某个特定的设备。
是广播报文还是数据报文由其传输所在的信道决定。低功耗蓝牙规定了3个广播信道和37个数据信道。如果报文在某个广播信道传输,则为广播报文;反之,则为数据报文。
比特序与字节
报文是一比特一比特传输的,但它们同时也是由数据的字节组成的。当数据的各个字节传输时,总是从最低位开始。因此,“0x80”是按照“00000001”发送的,而“0x01”是按照“10000000”发送的。大多数多字节域是从低字节开始发送。因此,“0x010203”会按照如下顺序发送:
110000000100000010000000
前导
报文最开始的8比特是“01010101”或者“10101010”序列。这是很简单的交替序列。接收机可以用它来配置自动增益控制,以及确定“0”、“1”比特所使用的频率。
该序列之所以非常重要,是因为芯片必须能够应对输入信号强度的可能范围。接收机需要应付“-10dBm ~ -90dBm”的信号强度,也就是80dB的动态范围。从接收机的角度讲,也就是“1pW ~ 0.1mW”的能量。自动增益控制器必须检测出输入信号的能量等级,并调整增益,使得信号刚好处于接收机能够轻松工作的范围之中。
接入地址
接入地址的第一个比特决定了前导是“01010101”还是“10101010”。如果接入地址的第一个比特位是“0”,则使用“01010101”序列;如果是“1”,则使用“10101010”序列。这保证了任一报文的前9个比特都是交替的,即要么是“101010101”,要么是“010101010”。
报文接下来的32比特是接入地址。有两种类型:
1.广播接入地址
2.数据接入地址
广播接入地址在广播数据或者广播、扫描、发起连接时使用。数据接入地址在连接建立之后的两个设备间使用。
当控制器试图接收一个报文时,它要事先知道待接收报文的接入地址。接收机开启并调谐到正确的频率,就可以收到数据。即使附近没人发送,接收机还是会收到背景辐射。考虑接收到纯随机噪音的概率,接收一段噪音与前导序列相符的可能性相对还是比较高的。通常,如果低功耗蓝牙接收机一直开着,每隔几分钟就能收到一个假的前导序列。因此,就需要利用接入地址以减少随机噪声造成伪报文接收的概率。
链路层也不知道其他设备什么时候会发送报文,因此只能保留最近 40μs 接收到的比特,并在新的比特移入到寄存器的时候检查序列是否满足前导和接入地址。这一过程称为与接入地址求相关。
对于广播信道,接入地址是固定值“0x8E89BED6”,抓包显示如下:
发送时的二进制形式如下(从左到右):
01101011011111011001000101110001
这意味着广播报文的前导是“01010101”。之所以选择这个值作为接入地址,是因为其相关性非常好。该定值使得任何低功耗蓝牙设备能通过与该值求相关获知它正在接收广播报文,即使之前可能从未从该设备接收到任何报文。
对于数据信道,接入地址是一个随机值,不同的连接有不同的值。这一随机值也需要符合一些规定,主要是为了保证接入地址有足够好的白化特性。
数据信道的接入地址与广播信道接入地址至少要有1个比特的不同。数据信道接入地址也不能有任何重复的模式,各个八位组必须互不相同。数据信道接入地址也不能有超过24次的比特翻转,防止使用逐比特交替的序列。最后一点,最后6个比特需要有至少两次比特翻转,这样可以确保报头开始前有一些比特翻转,以防报头有较长的连“0”或者连“1”而带来不好的影响。
符合以上规则的有效随机接入地址大概有231个。换句话说,20亿个低功耗蓝牙设备可以在互相可通信的范围内同时工作。这个设计看起来可能有点大材小用,但请记住低功耗蓝牙已经是成功的设计,每个部分的设计都有它的合理性考虑。数据信道的随机接入地址还有一个有用的特性,就是攻击者无法根据接入地址来确定某个连接中是哪两个设备。
报头
报文的下一个部分是报头。报头的内容取决于该报文是广播报文还是数据报文。
1.对于广播报文,如下所示:
报头包含了广播报文的类型以及一些标记位,这些标记位指出了报文使用的是公共地址还是随机地址。广播报文类型共有7种,每种类型都具有不同的净荷格式及行为:
ADV_IND:通用广播指示
ADV_DIRECT_IND:定向连接指示
ADV_NONCONN_IND:不可连接指示
ADV_SCAN_IND:可扫描指示
SCAN_REQ:主动扫描请求
SCAN_RSP:主动扫描响应
CONNECT_REQ:连接请求
2.对于数据报文,如下所示:
包含如下标记位:报文可靠传输使能、低功耗管理、净荷路由(发给控制器或是主机)。
长度
对于广播报文,长度域包含6个比特,有效值的范围是“6 ~ 37”。对于数据报文,长度域包含5个比特,有效值的范围是“0 ~ 31”。长度域之后是净荷,其长度是长度域指出的字节数。
广播报文和数据报文的长度域有所不同可能显得有些奇怪。这一设计的主要原因是,广播报文除了最多31个字节的数据之外,还要包含6个字节的广播设备地址。6个字节加31个字节导致报文长度最多为37个字节,因此需要6比特的长度域。
数据报文就简单多了。数据报文长度不是那么关键,多数待传的数据只有几个字节,因此达到最大长度的报文很少用到。这里要注意的是,如果报文被加密,则需要包含4字节的消息完整性检查域,实际的净荷数据将被减少到最多27个字节。为了使得链路层设计得到简化,未加密报文的净荷也不允许超过27个字节的限制,以减低链路层缓存的复杂度。
连接成功之后通信过程中数据报文的抓包显示如下:
从上述的截图中我们可以看到“Data Header”中的“PDU-Length”就是数据报文中净荷数据的实际长度,该值最多27个字节。净荷部分包含“L2CAP Header”和ATT,其中“L2CAP Header”占用了4个字节,而ATT中Opcode和AttHandle一共占用了3个字节,剩下的AttValue最多只能有20个字节了,而AttValue就是应用层实际发送的数据,所以我们看到很多资料中提到BLE每个包最多发送20个字节的数据,就是这个原因。
注意上述截图中“Data Header”中的“PDU-Length”显示的是十进制,也就是上述截图中实际净荷长度是13个字节即“L2CAP Header”和ATT部分实际长度是13个字节,而“L2CAP Header”中的“L2CAP-Length”是十六进制,表示的是后面ATT部分的实际数据长度,图中为9个字节,抛去ATT的Opcode和AttHandle占用了3个字节之外,剩下的AttValue刚好是6个字节。
净荷
净荷是所传输的“真实”数据,可以是关于设备的广播数据,或者是发给一定区域内所有设备的服务数据;可以是主动扫描响应的附加数据,如设备名称,实现的服务;可以是建立或保持连接所需要的信息;可以是从一个设备到另一个设备的应用层数据。
循环冗余校验
报文的最后是3个字节的循环冗余校验(CRC)。CRC对报头、长度域以及净荷域进行计算。24位的CRC强大到足以检测所有奇数位错误,以及所有2位或4位错误。这意味着低功耗蓝牙可以检测出所有的1比特,2比特,3比特,4比特,5比特,7比特,9比特等错误。
考虑到多数无线系统采用16位或32位CRC,选择24位CRC显得有点奇怪。不过,考虑到低功耗蓝牙的报文长度,32位CRC也无法检测所有6位错误并且需要浪费额外 8μs 功耗,因此并不比24位CRC更好。如果报头、长度域以及净荷域的总长度增加,超过39字节,就有必要增加CRC的长度以检测4位错误。对于336比特的总载荷,16位CRC无法检测4位错误,对于低功耗蓝牙而言就不够强壮。因此,选择24位CRC是在鲁棒性和功耗间的权衡。
该24位CRC的生成多项式如下:
CRC=x24+x10+x9+x6+x4+x3+x1+x0