1. BLE广播包解析

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

验证判定标准:

  1. 程序能正确输出3个AD Structure,Type字段与构造数据一致;
  2. 设备名称正确解码为"TestDev";
  3. 128-bit UUID正确还原为构造时的标准字符串格式(注意字节序反转处理);
  4. 若输入包中不含名称或UUID,对应字段输出"(未提供)"或"(未发现)",不报错。

真实数据验证方法

如需验证真实BLE广播包,可使用nRF Connect或Wireshark抓取广播包,将Hex数据粘贴替换代码中的packet变量即可。解析结果应与抓包工具显示的AD Structure一致。

BLE广播包解析流程与数据布局 Header (2B) AdvA (6B) AdvData (0~31B) AD Structure #1 Length (1B) | Type (1B) | Data (Length-1 B) AD Structure #2 Length (1B) | Type (1B) | Data (Length-1 B) … 循环解析直到AdvData结束 … 解析输出 Type=0x01 → Flags Type=0x09 → 设备名称 Type=0x07 → 128-bit UUID Type=0xFF → 厂商自定义 字节序处理:128-bit UUID需反转