2、数据帧结构基础:前导码、访问地址、PDU、CRC校验

做蓝牙抓包这么多年,我见过太多人一上来就盯着PDU里的业务数据猛看,结果连包都抓不全。为什么?因为你不懂帧结构,就不知道哪些字节该跳过、哪些该解析。

这一节,咱们就拿BLE广播信道PDU当标本,把数据帧的骨架拆开来看。你想想看,一个无线数据包在空中飞,接收端怎么知道「这是个新包」?怎么知道「这包是发给我的」?怎么验证「这包没被干扰」?答案全在帧结构里。

核心认知:BLE广播信道的数据帧由4个固定部分组成——前导码、访问地址、PDU、CRC。缺任何一个,接收端都会直接丢弃。

2.1 整体帧结构一览

先看一张总图,把四个部分的位置和长度记住。我个人习惯把帧结构画在纸上贴在工位前,调试时扫一眼就能定位问题。

BLE 广播信道数据帧结构 前导码 1 字节 访问地址 4 字节 PDU(协议数据单元) 2~257 字节 CRC 3 字节 关键说明: • 前导码:接收端用来同步时钟和增益,固定为 0xAA 或 0x55 • 访问地址:广播信道固定为 0x8E89BED6,数据信道由链路层分配 • PDU:承载实际数据,广播信道PDU有固定格式 • CRC:24位循环冗余校验,覆盖PDU全部内容

2.2 前导码——接收端的「闹钟」

前导码只有1个字节,但它的作用比你想的重要得多。说白了,它就是接收端射频电路的唤醒信号。

无线信号在空中会有衰减、多径干扰,接收端需要先知道「信号来了,准备接收」。前导码的0101交替模式,正好让接收端的自动增益控制(AGC)和时钟同步电路完成初始化。

经验之谈:前导码的值取决于PDU的第一个bit。如果PDU首bit为0,前导码是0xAA(10101010);如果首bit为1,前导码是0x55(01010101)。我刚开始做抓包工具时没注意这个细节,结果同步老失败——后来发现是前导码和PDU首bit不匹配。

2.3 访问地址——谁是「自己人」

访问地址4个字节,相当于一个网络ID。接收端收到包后,第一件事就是检查访问地址是否匹配。不匹配?直接扔掉,不浪费CPU去解析后面的PDU。

在广播信道,访问地址是固定的:0x8E89BED6。这个值由蓝牙核心规范规定,所有BLE设备在广播信道上都用同一个地址。为什么?因为广播本来就是一对多的,不需要区分连接。

信道类型 访问地址值 说明
广播信道(37/38/39) 0x8E89BED6 固定值,所有广播包共用
数据信道 随机生成 连接建立时由链路层分配,每个连接唯一

避坑指南:我曾经在调试一个私有协议时,发现设备明明在广播,但抓包器就是收不到。折腾了两天,最后发现是抓包器的访问地址过滤设成了0x8E89BED5——差一个bit,整个包就被过滤掉了。记住,访问地址匹配是硬件级别的,软件根本看不到被丢弃的包。

2.4 PDU——真正的「肉」

PDU是协议数据单元,承载着广播的实际内容。广播信道PDU有自己的一套结构,咱们重点拆解它。

广播信道PDU分为两部分:报头(Header)和有效载荷(Payload)。报头2个字节,告诉你这是什么类型的包、有没有后续包、数据长度是多少。

2.4.1 报头结构

报头16个bit,每个bit都有含义。我习惯把它画成bit位图,调试时对照着看,一目了然。

Bit位 字段名 长度 说明
0~3 PDU类型 4 bit 0x00=ADV_IND, 0x01=ADV_DIRECT_IND, 0x02=ADV_NONCONN_IND, 0x03=SCAN_REQ, 0x04=SCAN_RSP, 0x05=CONNECT_IND, 0x06=ADV_SCAN_IND
4 TxAdd 1 bit 发送地址类型:0=公共地址,1=随机地址
5 RxAdd 1 bit 接收地址类型(仅用于定向广播和连接请求)
6~7 保留 2 bit 预留,必须为0
8~13 长度 6 bit 有效载荷的字节数(0~37)
14~15 保留 2 bit 预留,必须为0

嗯,这里要注意:长度字段只有6个bit,所以最大能表示63字节。但广播信道PDU的有效载荷实际限制是37字节——这是物理层决定的,别搞混了。

2.4.2 有效载荷结构

有效载荷里装的是真正的广播数据。它由两部分组成:广播地址(6字节)和广播数据(0~31字节)。

广播地址就是发送设备的蓝牙MAC地址。广播数据则是一组AD Structure(广告数据结构),每个AD Structure包含:

  • 长度(1字节):表示后面数据域的长度
  • 类型(1字节):表示数据的类型,比如设备名、UUID、厂商自定义数据等
  • 数据(N字节):实际内容

实战技巧:解析广播数据时,我习惯先读长度字段,然后根据长度跳过整个AD Structure。这样即使遇到未知类型,也不会解析错位。我曾经见过一个设备在广播数据里塞了31字节的厂商数据,类型码是0xFF——如果你按固定偏移解析,肯定崩。

2.5 CRC校验——最后的「守门员」

CRC是24位的循环冗余校验,覆盖整个PDU(报头+有效载荷)。发送端计算CRC值并附加在包尾,接收端用同样的算法重新计算,比对是否一致。

不一致怎么办?直接丢包。这是物理层自动完成的,上层应用根本不知道曾经有个坏包来过。

CRC的生成多项式是标准化的:x^24 + x^10 + x^9 + x^6 + x^4 + x^3 + x + 1。你不需要记住它,但要知道:CRC校验能检测出所有1~3 bit的错误,以及大部分突发错误。

避坑指南:我曾经在写BLE抓包解析器时,发现有些包PDU内容完全正确,但CRC就是不对。查了半天,发现是CRC的初始值搞错了——广播信道和数据信道的CRC初始值不一样。广播信道用0x555555,数据信道用0x000000。这个细节,很多教程都不提。

2.6 完整解析示例

光说不练假把式。咱们拿一个真实的广播包来拆解。假设抓到的原始数据是:

AA D6 BE 89 8E 02 01 1A 02 0A 0C 0B 09 42 6C 75 65 74 6F 6F 74 68 04 09 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF

别慌,咱们按帧结构拆开看:

部分 字节 值 说明
前导码 1 0xAA PDU首bit为0,所以前导码是0xAA
访问地址 4 0x8E89BED6 广播信道固定值(注意小端序:D6 BE 89 8E)
PDU报头 2 0x0201 类型=0x02(ADV_NONCONN_IND),长度=0x1A(26字节)
PDU有效载荷 26 ...(略) 包含6字节广播地址 + 20字节广播数据
CRC 3 0xXXXXXX 由PDU内容计算得出

关键点:解析时一定要注意字节序。BLE协议使用小端序(Little-Endian),多字节字段的低字节在前。比如访问地址0x8E89BED6在空中的顺序是D6 BE 89 8E。我见过有人把这个搞反,结果解析出来的地址完全不对。

2.7 小结

数据帧结构是解析广播数据的基石。前导码帮你找到包的起点,访问地址帮你过滤无关包,PDU承载你关心的数据,CRC保证数据没被破坏。这四个部分缺一不可。

下次你抓到一个广播包,别急着看里面的设备名。先确认前导码对不对、访问地址是不是0x8E89BED6、CRC能不能通过。这些基本功扎实了,后面解析AD Structure才不会翻车。