3、QNX蓝牙GAP层开发:设备发现、配对与绑定、连接建立与断开、安全设置

好,咱们进入蓝牙开发最核心的一层——GAP层。GAP,全称Generic Access Profile,说白了就是蓝牙设备的“社交礼仪”。它管的是设备怎么被发现、怎么互相认识、怎么建立连接、怎么安全地聊天。我在QNX车机上做蓝牙开发时,GAP层是打交道最多的部分,坑也最多。

3.1 设备发现:让车机“看见”周围设备

设备发现,就是让车机去扫描周围的蓝牙设备。QNX的蓝牙协议栈基于BlueZ,但车机环境做了不少定制。我个人习惯用ioctl操作蓝牙HCI层,而不是走D-Bus那套——车机上D-Bus太重了。

先看一个典型的扫描启动流程:

// 打开HCI套接字
int hci_sock = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
if (hci_sock < 0) {
    perror("socket failed");
    return -1;
}

// 设置扫描参数
struct hci_request req;
memset(&req, 0, sizeof(req));

// 启动扫描(允许发现)
inquiry_info *info = NULL;
int num_rsp = hci_inquiry(dev_id, 8, 255, NULL, &info, 
                          IREQ_CACHE_FLUSH);
if (num_rsp < 0) {
    perror("hci_inquiry failed");
    close(hci_sock);
    return -1;
}

// 遍历发现的设备
for (int i = 0; i < num_rsp; i++) {
    char addr[18];
    ba2str(&info[i].bdaddr, addr);
    printf("发现设备: %s, RSSI: %d\n", addr, info[i].rssi);
}

嗯,这里要注意。车机环境下,扫描不能太频繁。我曾经在一个项目里,每2秒扫一次,结果车机蓝牙模块过热重启了。为什么?因为扫描时射频一直处于接收状态,功耗大、发热高。我建议扫描间隔至少5秒以上,每次扫描时长控制在10秒内。

实战技巧:车机蓝牙扫描时,优先过滤掉非手机设备。你可以通过Class of Device字段判断——手机一般是0x200408(智能手机)或0x200404(普通手机)。我在项目中加了这个过滤后,扫描列表干净多了。

3.2 配对与绑定:建立信任关系

配对,就是两个蓝牙设备交换密钥的过程。绑定,是把密钥存下来,下次不用再配。车机上,配对流程通常由手机发起,但车机需要做好响应准备。

QNX下配对的核心是处理HCI_EVENT_LINK_KEY_REQUESTHCI_EVENT_PIN_CODE_REQUEST事件。我习惯用事件驱动的方式:

// 注册配对事件回调
void pairing_event_handler(uint8_t event, void *data) {
    switch(event) {
    case HCI_EVENT_PIN_CODE_REQUEST: {
        // 车机通常使用固定PIN码(如"0000")
        pin_code_reply *req = (pin_code_reply *)data;
        char pin[] = "0000";
        hci_send_cmd(hci_sock, OGF_LINK_CTL, 
                     OCF_PIN_CODE_REPLY, 6, 
                     &req->bdaddr, 4, pin);
        break;
    }
    case HCI_EVENT_LINK_KEY_REQUEST: {
        // 检查本地是否已存储该设备的密钥
        link_key_reply *req = (link_key_reply *)data;
        if (has_stored_key(&req->bdaddr)) {
            // 使用存储的密钥回复
            hci_send_cmd(hci_sock, OGF_LINK_CTL,
                         OCF_LINK_KEY_REPLY, ...);
        } else {
            // 拒绝,触发重新配对
            hci_send_cmd(hci_sock, OGF_LINK_CTL,
                         OCF_LINK_KEY_NEG_REPLY, ...);
        }
        break;
    }
    }
}
安全警告:车机配对时,千万别用固定PIN码"0000"或"1234"!我曾经见过某款车机用"0000",结果在停车场被攻击者用蓝牙嗅探器轻松配对,然后通过OBD接口读取了车辆数据。建议使用Numeric Comparison(数字比较)或Passkey Entry(密钥输入)方式,让用户在手机上确认。

绑定数据的存储,我建议用QNX的persistent_storage API,存到/data/bluetooth/目录下。每个设备一个文件,文件名用蓝牙地址的哈希值,内容包含Link Key、随机数、设备名称等。这样即使车机断电重启,绑定关系也不会丢。

3.3 连接建立与断开:管好通信链路

连接建立,就是两个设备从“认识”到“能聊天”的过程。GAP层负责建立ACL(Asynchronous Connection-Less)链路,这是所有上层协议(如RFCOMM、AVDTP)的基础。

车机上,连接通常由车机主动发起——比如用户点击“连接手机”按钮。代码逻辑如下:

// 主动连接远程设备
int connect_to_device(bdaddr_t *remote) {
    int sock = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
    if (sock < 0) return -1;

    struct sockaddr_rc addr;
    memset(&addr, 0, sizeof(addr));
    addr.rc_family = AF_BLUETOOTH;
    addr.rc_channel = 0;  // 使用默认通道
    bacpy(&addr.rc_bdaddr, remote);

    // 设置连接超时(车机环境建议10秒)
    struct timeval timeout = {10, 0};
    setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));

    if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        close(sock);
        return -1;
    }
    return sock;
}

断开连接时,我习惯先发一个HCI_DISCONNECT命令,而不是直接关socket。为什么?因为直接关socket会导致对端收到“意外断开”的错误码,用户体验不好。正确的做法是:

// 优雅断开连接
void disconnect_device(int handle) {
    // 先发HCI断开命令
    hci_send_cmd(hci_sock, OGF_LINK_CTL, OCF_DISCONNECT,
                 3, &handle, 0x13);  // 0x13 = Remote User Terminated
    
    // 等待断开完成事件
    wait_for_event(hci_sock, HCI_EVENT_DISCONN_COMPLETE, 2000);
    
    // 再关闭socket
    close(bt_sock);
}
关键点:车机蓝牙连接数有限。我见过最多支持7个并发连接(经典蓝牙)。如果连接数满了,新设备会连不上。建议维护一个连接池,超过限制时自动断开最久未使用的设备。

3.4 安全设置:别让车机变“肉鸡”

蓝牙安全,说白了就是防偷听、防篡改、防冒充。GAP层的安全设置主要涉及三个方面:认证、加密、授权。

QNX下,安全设置通过hci_write_simple_pairing_modehci_write_authentication_enable等命令控制。我建议车机强制开启以下安全策略:

安全项 推荐设置 说明
Simple Pairing 开启 使用Secure Simple Pairing(SSP),防止PIN码暴力破解
MITM保护 开启 防止中间人攻击,配对时要求用户确认
加密强制 开启 所有音频和数据连接必须加密,拒绝未加密的连接
IO能力 DisplayYesNo 车机有屏幕,可以显示数字并让用户确认

代码实现上,我通常在蓝牙初始化阶段就设置好安全参数:

// 初始化安全设置
void init_bt_security(int hci_sock) {
    // 开启Simple Pairing
    hci_send_cmd(hci_sock, OGF_HOST_CTL, 
                 OCF_WRITE_SIMPLE_PAIRING_MODE, 1, &enable);
    
    // 设置IO能力为DisplayYesNo
    uint8_t io_cap = 0x01;  // DisplayYesNo
    hci_send_cmd(hci_sock, OGF_HOST_CTL,
                 OCF_WRITE_IO_CAPABILITY, 1, &io_cap);
    
    // 强制加密
    uint8_t encrypt_mode = 0x02;  // 强制加密
    hci_send_cmd(hci_sock, OGF_HOST_CTL,
                 OCF_WRITE_ENCRYPTION_MODE, 1, &encrypt_mode);
    
    printf("蓝牙安全初始化完成\n");
}
血的教训:我曾经在一个项目中,为了调试方便,把安全设置全关了——Simple Pairing关掉、加密关掉、MITM关掉。结果车机在停车场被攻击者用蓝牙漏洞(BlueBorne)入侵,通过蓝牙连接控制了车机的娱乐系统。从那以后,我所有项目都强制开启安全设置,调试时用白名单设备,绝不关闭安全功能。

还有一个容易被忽略的点:绑定设备的密钥管理。车机可能存储几十个手机的绑定信息,每个密钥长度16字节。如果密钥文件被读取,攻击者就能冒充已绑定的手机。我建议对存储的密钥文件进行加密,使用车机唯一的硬件ID(如TEE中的密钥)作为加密密钥。这样即使文件被拷贝,在其他设备上也无法解密。

嗯,GAP层开发就这些核心内容。说白了,设备发现要节制、配对要安全、连接要优雅、安全要强制。下一章我们讲GATT层,那是蓝牙低功耗(BLE)的主战场,车机上的蓝牙钥匙、胎压监测都用它。到时候再聊。