1. BLE广播包解析
任务说明
本案例要求实现一个BLE广播包解析工具,能够从BLE广播信道PDU的AdvData字段中,逐一解析AD Structure(AD Structure = Length + Type + Data),并提取设备名称(Type 0x09/0x08)与完整128-bit服务UUID(Type 0x07),同时输出原始AD结构列表以便核对。
BLE广播包格式
BLE广播信道PDU(Adv_Ind)结构如下:Header(2字节)+ AdvA(6字节)+ AdvData(0~31字节)。其中AdvData由若干个AD Structure组成,每个AD Structure首字节为Length(表示后续Type+Data的总长度),第二字节为AD Type,后续为AD Data。本工具聚焦于AdvData字段的解析,输入为完整的广播包字节序列(含Header与AdvA),输出解析后的AD结构及关键信息。
代码实现
import struct
# BLE广播包解析器
class BLEAdvertiseParser:
# AD Type 常量
AD_TYPE_FLAGS = 0x01
AD_TYPE_SERVICE_UUID_16BIT = 0x03
AD_TYPE_SERVICE_UUID_32BIT = 0x05
AD_TYPE_SERVICE_UUID_128BIT = 0x07
AD_TYPE_SHORT_LOCAL_NAME = 0x08
AD_TYPE_COMPLETE_LOCAL_NAME = 0x09
AD_TYPE_MANUFACTURER_SPECIFIC = 0xFF
def __init__(self, packet: bytes):
self.packet = packet
self.adv_data = b''
self.ad_structures = [] # 存储 (type, data) 元组
self.device_name = None
self.service_uuids_128 = []
def parse(self):
"""解析广播包,提取AD结构与关键信息"""
if len(self.packet) < 7:
raise ValueError("广播包长度不足(至少需要2字节Header + 6字节AdvA)")
# 跳过2字节Header和6字节AdvA,得到AdvData
self.adv_data = self.packet[8:]
offset = 0
while offset < len(self.adv_data):
length = self.adv_data[offset]
if length == 0:
break # 填充字节,结束解析
if offset + 1 + length > len(self.adv_data):
print(f"警告: AD结构长度越界,offset={offset}, length={length}")
break
ad_type = self.adv_data[offset + 1]
ad_data = self.adv_data[offset + 2 : offset + 1 + length]
self.ad_structures.append((ad_type, ad_data))
# 提取设备名称
if ad_type == self.AD_TYPE_COMPLETE_LOCAL_NAME or ad_type == self.AD_TYPE_SHORT_LOCAL_NAME:
try:
self.device_name = ad_data.decode('utf-8')
except UnicodeDecodeError:
self.device_name = ad_data.hex()
# 提取128-bit服务UUID
if ad_type == self.AD_TYPE_SERVICE_UUID_128BIT and len(ad_data) == 16:
# UUID以小端序传输,需反转字节序
uuid_bytes = ad_data[::-1]
uuid_str = str(uuid.UUID(bytes=uuid_bytes))
self.service_uuids_128.append(uuid_str)
offset += 1 + length
return self
def print_summary(self):
"""打印解析结果摘要"""
print("=== AD Structure 列表 ===")
for idx, (ad_type, ad_data) in enumerate(self.ad_structures):
print(f"[{idx}] Type=0x{ad_type:02X}, Len={len(ad_data)}, Data={ad_data.hex()}")
print("\n=== 关键信息 ===")
print(f"设备名称: {self.device_name if self.device_name else '(未提供)'}")
if self.service_uuids_128:
for uuid in self.service_uuids_128:
print(f"128-bit服务UUID: {uuid}")
else:
print("128-bit服务UUID: (未发现)")
# 示例:构造一个包含设备名称和128-bit UUID的广播包
if __name__ == "__main__":
import uuid
# 构造测试数据
test_uuid = uuid.UUID("12345678-1234-5678-1234-567812345678")
uuid_bytes_le = test_uuid.bytes_le # 小端序字节
# 构造AD Structure
# 1. Flags (Type=0x01, Data=0x06)
flags_ad = bytes([0x02, 0x01, 0x06])
# 2. 完整设备名称 "TestDev" (Type=0x09)
name_bytes = b"TestDev"
name_ad = bytes([len(name_bytes) + 1, 0x09]) + name_bytes
# 3. 128-bit服务UUID (Type=0x07)
uuid_ad = bytes([17, 0x07]) + uuid_bytes_le
adv_data = flags_ad + name_ad + uuid_ad
# 构造完整广播包: Header(2字节) + AdvA(6字节) + AdvData
header = bytes([0x00, 0x00]) # PDU类型=Adv_Ind, 非连接
adva = bytes.fromhex("AABBCCDDEEFF") # 6字节MAC
packet = header + adva + adv_data
# 解析
parser = BLEAdvertiseParser(packet)
parser.parse()
parser.print_summary()
运行与验证
直接运行上述Python脚本(需Python 3.6+,无需第三方库)。预期输出如下:
=== AD Structure 列表 ===
[0] Type=0x01, Len=1, Data=06
[1] Type=0x09, Len=6, Data=54657374446576
[2] Type=0x07, Len=16, Data=78563412785634127856341278563412
=== 关键信息 ===
设备名称: TestDev
128-bit服务UUID: 12345678-1234-5678-1234-567812345678
验证判定标准:
- 程序能正确输出3个AD Structure,Type字段与构造数据一致;
- 设备名称正确解码为"TestDev";
- 128-bit UUID正确还原为构造时的标准字符串格式(注意字节序反转处理);
- 若输入包中不含名称或UUID,对应字段输出"(未提供)"或"(未发现)",不报错。
真实数据验证方法
如需验证真实BLE广播包,可使用nRF Connect或Wireshark抓取广播包,将Hex数据粘贴替换代码中的packet变量即可。解析结果应与抓包工具显示的AD Structure一致。