3. 解析广播数据包

解析广播数据包 学习卡片

任务说明

BLE广播信道上的数据包以AD Structure(Advertising Data Structure)形式承载信息。每个AD Structure由Length、Type、Data三部分组成。本案例要求编写一个解析器,从原始广播数据中提取设备名称(Type=0x09/0x08)和服务UUID(Type=0x02/0x03/0x06/0x07),并输出结构化结果。

AD Structure格式

Length 1 byte Type 1 byte Data Length-1 bytes Length 1 byte Type 1 byte Data Length-1 bytes ... Length:包含Type和Data的总长度 Type:AD类型标识 Data:具体数据内容

代码实现


#include <stdio.h>
#include <stdint.h>
#include <string.h>

#define AD_TYPE_FLAGS             0x01
#define AD_TYPE_INCOMPLETE_16UUID 0x02
#define AD_TYPE_COMPLETE_16UUID   0x03
#define AD_TYPE_INCOMPLETE_32UUID 0x04
#define AD_TYPE_COMPLETE_32UUID   0x05
#define AD_TYPE_INCOMPLETE_128UUID 0x06
#define AD_TYPE_COMPLETE_128UUID  0x07
#define AD_TYPE_SHORT_NAME        0x08
#define AD_TYPE_COMPLETE_NAME     0x09

typedef struct {
    uint8_t  length;
    uint8_t  type;
    uint8_t* data;
} ad_structure_t;

typedef struct {
    char     device_name[64];
    uint16_t uuid16[8];
    uint8_t  uuid16_count;
    uint8_t  uuid128[8][16];
    uint8_t  uuid128_count;
} parsed_ad_t;

static void parse_uuid16(uint8_t* data, uint8_t len, parsed_ad_t* out) {
    for (int i = 0; i + 1 < len; i += 2) {
        if (out->uuid16_count < 8) {
            out->uuid16[out->uuid16_count++] = data[i] | (data[i+1] << 8);
        }
    }
}

static void parse_uuid128(uint8_t* data, uint8_t len, parsed_ad_t* out) {
    if (len == 16 && out->uuid128_count < 8) {
        memcpy(out->uuid128[out->uuid128_count++], data, 16);
    }
}

int parse_ad_structures(uint8_t* adv_data, uint8_t adv_len, parsed_ad_t* out) {
    if (!adv_data || !out || adv_len == 0) return -1;

    memset(out, 0, sizeof(parsed_ad_t));
    uint8_t offset = 0;

    while (offset < adv_len) {
        uint8_t length = adv_data[offset];
        if (length == 0) break;  // 结束标志

        if (offset + 1 + length > adv_len) return -2;  // 数据越界

        ad_structure_t ad;
        ad.length = length;
        ad.type   = adv_data[offset + 1];
        ad.data   = &adv_data[offset + 2];

        switch (ad.type) {
            case AD_TYPE_COMPLETE_NAME:
            case AD_TYPE_SHORT_NAME: {
                uint8_t name_len = ad.length - 1;
                if (name_len < sizeof(out->device_name)) {
                    memcpy(out->device_name, ad.data, name_len);
                    out->device_name[name_len] = '\0';
                }
                break;
            }
            case AD_TYPE_INCOMPLETE_16UUID:
            case AD_TYPE_COMPLETE_16UUID:
                parse_uuid16(ad.data, ad.length - 1, out);
                break;
            case AD_TYPE_INCOMPLETE_128UUID:
            case AD_TYPE_COMPLETE_128UUID:
                parse_uuid128(ad.data, ad.length - 1, out);
                break;
            default:
                break;  // 其他类型忽略
        }
        offset += 1 + length;
    }
    return 0;
}

void print_result(parsed_ad_t* result) {
    printf("设备名称: %s\n", result->device_name[0] ? result->device_name : "(未提供)");

    printf("16位UUID (%d个):\n", result->uuid16_count);
    for (int i = 0; i < result->uuid16_count; i++) {
        printf("  0x%04X\n", result->uuid16[i]);
    }

    printf("128位UUID (%d个):\n", result->uuid128_count);
    for (int i = 0; i < result->uuid128_count; i++) {
        printf("  ");
        for (int j = 15; j >= 0; j--) {
            printf("%02X", result->uuid128[i][j]);
            if (j % 2 == 0 && j != 0) printf("-");
        }
        printf("\n");
    }
}

int main() {
    // 示例广播数据(十六进制)
    // 02 01 06          - Flags
    // 05 09 48 65 6C 6C - 设备名称 "Hell"
    // 03 03 0F 18       - 16位UUID 0x180F (Battery Service)
    // 11 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 128位UUID全零
    uint8_t adv_data[] = {
        0x02, 0x01, 0x06,
        0x05, 0x09, 0x48, 0x65, 0x6C, 0x6C,
        0x03, 0x03, 0x0F, 0x18,
        0x11, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
    };
    uint8_t adv_len = sizeof(adv_data);

    parsed_ad_t result;
    int ret = parse_ad_structures(adv_data, adv_len, &result);
    if (ret != 0) {
        printf("解析失败,错误码: %d\n", ret);
        return 1;
    }

    print_result(&result);
    return 0;
}

编译与运行


gcc -o ble_ad_parser ble_ad_parser.c
./ble_ad_parser

预期输出


设备名称: Hell
16位UUID (1个):
  0x180F
128位UUID (1个):
  00000000-0000-0000-0000-000000000000

验证方法

使用上述示例数据运行程序,观察输出是否与预期输出一致。若一致,说明解析逻辑正确。可进一步修改adv_data中的设备名称长度或UUID值,验证解析器能否正确提取变化后的数据。对于真实BLE设备,可通过抓包工具(如nRF Connect、Wireshark配合BLE dongle)获取广播原始字节,替换示例数据后运行程序验证。