服务发现协议(SDP)

服务发现协议(SDP)

本笔记为作者再学习蓝牙Host协议栈的一些心得体会,如有不对的地方,请包含与谅解!

​ ------------by wsoz

服务发现协议(SDP)

SDP协议是在蓝牙连接以后,查询对方支持哪些服务(如A2DP音频、HFP通话、文件传输等)以及如何连接这些服务。

客户端和服务器模式

SDP协议是基于**客户端-服务器(Client-Server)**模式:

角色定义

角色 说明
SDP Server 服务提供方,维护本地的服务记录数据库,响应查询请求
SDP Client 服务查询方,向Server发起查询,获取服务信息

工作流程

复制代码
SDP Client                           SDP Server
    |                                    |
    |  1. 建立L2CAP连接 (PSM=0x0001)     |
    |--------- L2CAP Connect ----------->|
    |<-------- L2CAP Connect Response ---|
    |                                    |
    |  2. 发送SDP查询请求                |
    |--------- SDP Request ------------->|
    |                                    |
    |  3. 返回服务信息                   |
    |<-------- SDP Response -------------|
    |                                    |
    |  4. 断开L2CAP连接                  |
    |--------- L2CAP Disconnect -------->|

关键特点

  • 一问一答:Client发请求,Server返回响应,典型的请求-响应模式
  • 角色可互换 :同一个设备可以同时作为SDP Client和SDP Server
    • 作为Server:维护自己提供的服务列表,等待别人查询
    • 作为Client:主动查询其他设备的服务
  • 使用固定PSM :SDP固定使用L2CAP的PSM=0x0001
  • 无需认证:SDP查询通常在配对之前就可以进行

典型场景

复制代码
手机(Client)                        蓝牙耳机(Server)
    |                                      |
    |  "你支持哪些音频服务?"              |
    |--- SDP查询 (A2DP UUID) ------------>|
    |                                      |
    |<-- 返回:支持A2DP Sink,PSM=0x19 ---|
    |                                      |
    |  现在知道了,建立A2DP连接           |
    |--- L2CAP Connect (PSM=0x19) ------->|

服务记录

服务记录(Service Record) 是SDP的核心数据结构,描述一个服务的所有信息。每个蓝牙设备的SDP Server维护一个服务记录数据库,存放本机提供的所有服务。

服务记录的组成

复制代码
┌─────────────────────────────────────────┐
│           Service Record                │
│  ┌───────────────────────────────────┐  │
│  │  Service Record Handle (唯一标识)  │  │
│  ├───────────────────────────────────┤  │
│  │  Attribute 1 (ID + Value)         │  │
│  ├───────────────────────────────────┤  │
│  │  Attribute 2 (ID + Value)         │  │
│  ├───────────────────────────────────┤  │
│  │  Attribute 3 (ID + Value)         │  │
│  ├───────────────────────────────────┤  │
│  │          ...                      │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘
  • Service Record Handle:服务记录句柄,32位整数,在本机唯一标识一条服务记录
  • Attribute:服务属性,由属性ID和属性值组成,描述服务的具体信息

结构图

服务属性

服务属性(Service Attribute) 是服务记录中的具体信息项,每个属性由属性ID属性值组成。

属性结构

复制代码
+----------------+------------------+
|  Attribute ID  |  Attribute Value |
|     16bit      |       可变       |
+----------------+------------------+

常用属性ID(Universal Attributes)

Attribute ID 名称 说明
0x0000 ServiceRecordHandle 服务记录句柄
0x0001 ServiceClassIDList 服务类别UUID列表,标识这是什么服务
0x0002 ServiceRecordState 服务记录状态
0x0003 ServiceID 服务的唯一标识UUID
0x0004 ProtocolDescriptorList 协议描述符列表,描述如何连接该服务
0x0005 BrowseGroupList 浏览组列表
0x0006 LanguageBaseAttributeIDList 语言基础属性列表
0x0009 BluetoothProfileDescriptorList 蓝牙Profile描述符列表
0x0100 ServiceName 服务名称(可读字符串)
0x0101 ServiceDescription 服务描述

最重要的两个属性

① ServiceClassIDList (0x0001) :标识这是什么类型的服务 服务类UUID

复制代码
例如:A2DP Sink服务
ServiceClassIDList = [0x110B]  // AudioSink UUID

② ProtocolDescriptorList (0x0004) :告诉Client如何连接这个服务 协议类UUID

复制代码
例如:
ProtocolDescriptorList = [
    [L2CAP, PSM=0x0019],      // 先建立L2CAP连接,PSM=0x0019
    [AVDTP, Version=0x0103]   // 然后使用AVDTP协议
]

UUID

UUID(Universally Unique Identifier,通用唯一标识符) 用于标识服务类型、协议、Profile等。

UUID的三种形式

形式 大小 说明
UUID-16 2字节 蓝牙SIG定义的标准UUID,最常用
UUID-32 4字节 扩展形式,较少使用
UUID-128 16字节 完整形式,自定义服务使用

UUID-16与UUID-128的转换

蓝牙定义了一个Base UUID

复制代码
00000000-0000-1000-8000-00805F9B34FB

UUID-16转换为UUID-128的公式:

复制代码
UUID-128 = UUID-16 × 2^96 + Base UUID

例如:UUID-16 = 0x110B (AudioSink)
UUID-128 = 0000110B-0000-1000-8000-00805F9B34FB

协议UUID vs 服务类UUID

UUID分为两大类,作用完全不同:

类型 回答的问题 存放位置
协议UUID "怎么连接我?"(传输方式) ProtocolDescriptorList (0x0004)
服务类UUID "我能做什么?"(功能类型) ServiceClassIDList (0x0001)

用A2DP Sink举例

复制代码
服务记录:
├── [0x0001] ServiceClassIDList = [0x110B]    ← 服务类UUID
│             "我是什么" → 音频接收器(AudioSink)
│
└── [0x0004] ProtocolDescriptorList = [       ← 协议UUID
        [L2CAP (0x0100), PSM=0x0019],         "怎么连我" → 先建L2CAP
        [AVDTP (0x0019), Version=0x0103]       → 再用AVDTP传音频
    ]
  • 服务类UUID:标识功能("我是什么")
  • 协议UUID:标识传输方式("怎么用我")

两者配合才能完成:服务发现 → 服务连接 的完整流程。

常用UUID-16(协议)

UUID-16 名称 说明
0x0001 SDP 服务发现协议
0x0003 RFCOMM 串口仿真协议
0x000F BNEP 蓝牙网络封装协议
0x0017 AVCTP 音视频控制传输协议
0x0019 AVDTP 音视频分发传输协议
0x0100 L2CAP 逻辑链路控制协议

常用UUID-16(服务类/Profile)

UUID-16 名称 说明
0x1101 SerialPort 串口服务(SPP)
0x1105 OBEXObjectPush 对象推送服务
0x110A AudioSource 音频源(A2DP Source)
0x110B AudioSink 音频接收(A2DP Sink)
0x110C A/V RemoteControlTarget AVRCP Target
0x110E A/V RemoteControl AVRCP Controller
0x111E Handsfree 免提服务(HFP)
0x111F HandsfreeAudioGateway 免提音频网关(HFP AG)
0x1124 HumanInterfaceDevice HID设备
0x112F Phonebook Access PSE 电话簿访问服务端

服务记录示例(A2DP Sink)

复制代码
Service Record:
├── [0x0000] ServiceRecordHandle = 0x00010001
├── [0x0001] ServiceClassIDList = [0x110B]           // AudioSink UUID--服务类
├── [0x0004] ProtocolDescriptorList = [
│       [L2CAP, PSM=0x0019],                         // L2CAP连接参数
│       [AVDTP, Version=0x0103]                      // AVDTP版本
│   ]
├── [0x0009] BluetoothProfileDescriptorList = [
│       [0x110D, Version=0x0103]                     // A2DP v1.3
│   ]
└── [0x0100] ServiceName = "Audio Sink"

服务类

服务类(Service Class) 用于标识一个服务的类型,通过UUID 来表示。在服务记录中,由ServiceClassIDList(0x0001) 属性来声明该服务属于哪些服务类。

服务类的作用

复制代码
SDP Client                              SDP Server
    |                                       |
    |  "我要找支持A2DP Sink的服务"          |
    |--- 查询 UUID=0x110B ----------------->|
    |                                       |
    |  Server检查所有服务记录的              |
    |  ServiceClassIDList是否包含0x110B     |
    |                                       |
    |<-- 返回匹配的服务记录 ----------------|

常用服务类UUID

音频相关

UUID-16 服务类名称 说明
0x110A AudioSource A2DP音频源(如手机播放音乐)
0x110B AudioSink A2DP音频接收(如蓝牙耳机)
0x110C A/V RemoteControlTarget AVRCP目标端
0x110E A/V RemoteControl AVRCP控制端
0x110F A/V RemoteControlController AVRCP控制器

电话相关

UUID-16 服务类名称 说明
0x111E Handsfree 免提服务(HFP HF端,如车载)
0x111F HandsfreeAudioGateway 免提音频网关(HFP AG端,如手机)
0x1112 HeadsetAudioGateway 耳机音频网关(HSP AG端)
0x1108 Headset 耳机服务(HSP)
0x112E PhoneBookAccessPCE 电话簿访问客户端
0x112F PhoneBookAccessPSE 电话簿访问服务端

数据传输相关

UUID-16 服务类名称 说明
0x1101 SerialPort 串口服务(SPP)
0x1105 OBEXObjectPush 对象推送(OPP)
0x1106 OBEXFileTransfer 文件传输(FTP)
0x1115 PANU 个人区域网络用户
0x1116 NAP 网络接入点

输入设备相关

UUID-16 服务类名称 说明
0x1124 HumanInterfaceDevice HID设备(键盘、鼠标等)
0x1812 HID over GATT BLE HID设备(HOGP)

ServiceClassIDList可以包含多个UUID

下面讨论一下一个设备有多种功能时的UUID情况:

① 一个Service Record包含多个UUID(功能相关、共享连接):

复制代码
例如:蓝牙耳机同时支持HSP和HFP
[0x0001] ServiceClassIDList = [
    0x1108,    // Headset(HSP耳机)
    0x111E     // Handsfree(HFP免提)
]
[0x0004] ProtocolDescriptorList = [[L2CAP, PSM=0x0003], [RFCOMM, Channel=1]]
// 共享同一个PSM和连接方式

② 多个独立Service Record(功能独立、PSM不同):

此时就需要我们来新开一个服务记录,因为连接的链路不同

复制代码
例如:设备同时支持SPP和HID
Service Record 1: [0x0001] = [0x1101]  // SPP, PSM=0x0003
Service Record 2: [0x0001] = [0x1124]  // HID, PSM=0x0011
// 不同PSM,需要分别连接

服务类与Profile的关系

概念 说明
Service Class 服务记录中声明的UUID,表示"我提供什么服务"
Profile 蓝牙规范定义的完整功能集,描述"如何实现这个功能"
  • 一个Profile可能对应一个或多个Service Class
  • 例如:A2DP Profile包含AudioSource和AudioSink两个服务类

服务类与服务记录的关系(面向对象理解)

SDP概念 面向对象概念 说明
服务类(Service Class) 类(Class) 定义属性的模板
服务记录(Service Record) 实例(Instance) 服务类的一个具体实例
服务属性(Attribute) 成员属性(Property) 具体的信息项
  • 服务类定义了该类服务应该包含哪些属性(Attribute ID、格式、用法)
  • 服务记录是服务类的具体实例,包含实际的属性值
  • 每个服务记录都是某个服务类的实例

服务类的继承机制

服务类支持继承,子类继承父类的所有属性,并可定义自己特有的属性:

复制代码
         ┌─────────────────────┐
         │  基础服务类          │  定义通用属性
         │  (所有服务共有)      │  0x0000, 0x0001, 0x0004...
         └──────────┬──────────┘
                    │ 继承
         ┌──────────▼──────────┐
         │  AudioSink          │  继承父类属性
         │  (0x110B)           │  + 音频专用属性
         └──────────┬──────────┘
                    │ 继承
         ┌──────────▼──────────┐
         │  AdvancedAudioSink  │  继承父类属性
         │  (子类)             │  + 更多专用属性
         └─────────────────────┘

属性分类

属性类型 说明
通用属性 所有服务都有的属性,如ServiceRecordHandle(0x0000)、ServiceClassIDList(0x0001)
专用属性 特定服务类才有的属性,由该服务类定义

ServiceClassIDList的排列顺序

当服务类存在继承关系时,ServiceClassIDList中的UUID按照从子类到父类的顺序排列:

复制代码
[0x0001] ServiceClassIDList = [
    0x110B,    // 子类(最具体的)  ← 排在最前面
    0x1200,    // 父类
    ...        // 更高层的父类      ← 排在后面
]
  • 最前面的是最具体的服务类(子类)
  • 后面依次是父类、祖父类...
  • 查询时,用父类UUID也能匹配到子类服务

特定类属性

特定类属性(Class-Specific Attributes) 是由特定服务类定义的属性,只在该服务类的服务记录中出现,与通用属性相对。

属性ID分配范围

范围 类型 说明
0x0000 - 0x01FF 通用属性 所有服务类共用,由SDP规范定义
0x0200 - 0xFFFF 特定类属性 由各服务类自行定义

通用属性 vs 特定类属性

复制代码
服务记录:
├── [0x0000] ServiceRecordHandle     ← 通用属性(所有服务都有)
├── [0x0001] ServiceClassIDList      ← 通用属性
├── [0x0004] ProtocolDescriptorList  ← 通用属性
├── [0x0100] ServiceName             ← 通用属性
│
├── [0x0200] VersionNumberList       ← 特定类属性(某些服务类定义)
├── [0x0311] SupportedFeatures       ← 特定类属性(如HFP定义)
└── ...

常见特定类属性示例

HFP(免提服务)特定属性

Attribute ID 名称 说明
0x0311 SupportedFeatures 支持的特性(如三方通话、语音识别等)
复制代码
HFP服务记录示例:
├── [0x0001] ServiceClassIDList = [0x111E]        // Handsfree
├── [0x0004] ProtocolDescriptorList = [...]
└── [0x0311] SupportedFeatures = 0x003F           // 支持的HFP特性掩码
              ├── Bit0: EC/NR(回声消除/噪声抑制)
              ├── Bit1: 三方通话
              ├── Bit2: CLI(来电显示)
              ├── Bit3: 语音识别
              ├── Bit4: 远程音量控制
              └── Bit5: 宽带语音

A2DP(高级音频分发)特定属性

Attribute ID 名称 说明
0x0311 SupportedFeatures 支持的特性(如播放器、麦克风等)
复制代码
A2DP Sink服务记录示例:
├── [0x0001] ServiceClassIDList = [0x110B]        // AudioSink
├── [0x0004] ProtocolDescriptorList = [...]
└── [0x0311] SupportedFeatures = 0x0003
              ├── Bit0: 耳机
              └── Bit1: 扬声器

AVRCP(音视频远程控制)特定属性

Attribute ID 名称 说明
0x0311 SupportedFeatures 支持的特性
复制代码
AVRCP Controller服务记录示例:
├── [0x0001] ServiceClassIDList = [0x110E]        // A/V RemoteControl
├── [0x0004] ProtocolDescriptorList = [...]
└── [0x0311] SupportedFeatures = 0x0001
              └── Bit0: Category 1 (播放器/录音机)

SPP(串口服务)特定属性

SPP服务类相对简单,主要使用通用属性,没有太多特定类属性。

特定类属性的作用

  • 能力协商:告诉对方自己支持哪些高级特性
  • 版本兼容:标识支持的功能版本
  • 功能裁剪:让Client知道Server具体支持什么,避免调用不支持的功能

注意

  • 相同的Attribute ID(如0x0311)在不同服务类中含义可能不同
  • 解析特定类属性前,必须先确认服务类(ServiceClassIDList)

数据表示方法

SDP使用数据元素(Data Element) 来表示所有的属性值 ,这是一种TLV(Type-Length-Value) 结构。

数据元素基本结构

复制代码
+------------------+------------------+------------------+
|    Header        |    可选Size      |      Data        |
|    (1字节)       |   (0/1/2/4字节)  |     (可变)       |
+------------------+------------------+------------------+

Header字节结构

复制代码
  Bit:  7  6  5  4  3  2  1  0
       +--+--+--+--+--+--+--+--+
       | Type Descriptor | Size|
       |     (5 bit)     | Idx |
       +--+--+--+--+--+--+--+--+

Type Descriptor(类型描述符,5bit)

类型 说明
0 Nil 空值
1 Unsigned Integer 无符号整数
2 Signed Integer 有符号整数
3 UUID 通用唯一标识符
4 Text String 文本字符串
5 Boolean 布尔值
6 Data Element Sequence 数据元素序列(类似数组)
7 Data Element Alternative 数据元素选择(多选一)
8 URL 统一资源定位符

Size Index(大小索引,3bit)

Size Index 含义 附加Size字段 数据长度
0 1字节数据,如果type为0则无字节数据 1
1 2字节数据 2
2 4字节数据 4
3 8字节数据 8
4 16字节数据 16
5 后跟1字节Size大小 1字节 0-255
6 后跟2字节Size大小 2字节 0-65535
7 后跟4字节Size大小 4字节 0-2^32-1

示例解析

① UUID-16 (0x110B)

复制代码
Header: 0x19
  ├── Type = 3 (UUID)        → 00011
  └── Size Index = 1 (2字节) → 001

完整数据: 0x19 0x11 0x0B
          ↑     ↑────↑
        Header  UUID值(0x110B)

② 无符号整数 (PSM=0x0019)

复制代码
Header: 0x09
  ├── Type = 1 (Unsigned Int) → 00001
  └── Size Index = 1 (2字节)  → 001

完整数据: 0x09 0x00 0x19
          ↑     ↑────↑
        Header  值(0x0019)

③ 文本字符串 "Audio Sink"

复制代码
Header: 0x25
  ├── Type = 4 (Text String)          → 00100
  └── Size Index = 5 (后跟1字节Size)  → 101

完整数据: 0x25 0x0A "Audio Sink"
          ↑    ↑    ↑─────────↑
        Header 长度10  字符串内容

④ Data Element Sequence(序列)

复制代码
ServiceClassIDList = [0x110B]

Header: 0x35
  ├── Type = 6 (Sequence)             → 00110
  └── Size Index = 5 (后跟1字节Size)  → 101

完整数据: 0x35 0x03 0x19 0x11 0x0B
          ↑    ↑    ↑─────────↑
        Header 长度3  UUID元素(0x110B)

ProtocolDescriptorList完整示例

复制代码
ProtocolDescriptorList = [[L2CAP, PSM=0x0019], [AVDTP]]

结构:
Sequence [                          // 外层序列
    Sequence [                      // L2CAP协议描述
        UUID(0x0100),               // L2CAP UUID
        Uint16(0x0019)              // PSM值
    ],
    Sequence [                      // AVDTP协议描述
        UUID(0x0019)                // AVDTP UUID
    ]
]

二进制:
0x35 0x0C                           // 外层Sequence, 长度12
    0x35 0x06                       // 内层Sequence, 长度6
        0x19 0x01 0x00              // UUID: L2CAP (0x0100)
        0x09 0x00 0x19              // Uint16: PSM (0x0019)
    0x35 0x03                       // 内层Sequence, 长度3
        0x19 0x00 0x19              // UUID: AVDTP (0x0019)

总结

概念 说明
Data Element SDP中所有数据的表示格式
Header Type(5bit) + Size Index(3bit)
嵌套 Sequence可以包含其他Data Element,形成嵌套结构

服务记录的编码格式

服务记录在二进制层面完全由Data Element格式编码,两者的关系是:

  • Data Element:编码规则(怎么表示数据)
  • 服务记录:用Data Element规则组织的具体内容
属性的编码结构

每一条属性在二进制中的结构是:

复制代码
DES(外层容器)
  ├── UINT16(属性ID)
  └── 属性值(基本类型 或 嵌套DES)

DES是大框架,用来把属性ID和属性值包在一起;属性值简单时用基本类型(UUID16/UINT8等),复杂时再套DES。

示例:ServiceClassIDList(0x0001)

复制代码
SDP_DES_SIZE8, 0x08,          // DES容器,长度8字节
SDP_UINT16, 0x00, 0x01,       // 属性ID = 0x0001
SDP_DES_SIZE8, 0x03,          // 属性值:DES容器,长度3字节
SDP_UUID16, 0x11, 0x24        // UUID = 0x1124(HID服务)
为什么ProtocolDescriptorList要多层嵌套

ProtocolDescriptorList描述"协议栈",每一层协议都需要独立描述,因此必须分开写:

复制代码
DES(整个属性)
  ├── UINT16(属性ID = 0x0004)
  └── DES(协议列表)
        ├── DES(底层:L2CAP)      ← 说明"用L2CAP,监听哪个PSM"
        │     ├── UUID16(L2CAP)
        │     └── UINT16(PSM)
        └── DES(上层:HID/AVDTP等) ← 说明"L2CAP上跑什么协议"
              └── UUID16(协议UUID)

两个并列DES语义不同,不能合并:Host解析时按固定格式取PSM和上层协议,合并后无法区分。

总结:三者关系
概念 角色 说明
Data Element 编码规则 所有数据的表示格式(TLV)
DES(Sequence) 结构容器 组织嵌套结构的框架
服务记录 具体内容 用Data Element编码的属性集合

简单记:服务记录 = 一堆属性;每个属性 = DES包裹(ID + 值);值复杂时 = 继续嵌套DES。

SDP协议

协议要求

传输层要求

  • SDP基于L2CAP 传输,使用固定PSM = 0x0001
  • SDP使用Basic L2CAP Mode(基本模式,不使用ERTM)
  • 最小MTU要求:48字节
  • 大端数据传输

协议特点

  • 请求-响应模式:Client发请求,Server必须响应
  • 事务ID匹配:每个请求有唯一的Transaction ID,响应必须使用相同的ID
  • 支持分片:大量数据可以通过Continuation State分多次传输
  • 嵌套格式:协议Data Element均是通过数据格式进行嵌套进行表示
PDU格式

SDP PDU通用结构

复制代码
+----------+----------------+------------------+------------------+
|  PDU ID  | Transaction ID | Parameter Length |   Parameters     |
|  (1字节) |    (2字节)     |     (2字节)      |     (可变)       |
+----------+----------------+------------------+------------------+
字段 大小 说明
PDU ID 1字节 PDU类型标识
Transaction ID 2字节 事务标识符,请求和响应必须匹配
Parameter Length 2字节 Parameters字段的长度
Parameters 可变 PDU的具体参数,取决于PDU类型

PDU类型

PDU ID 名称 方向 说明
0x01 SDP_ErrorResponse S→C 错误响应
0x02 SDP_ServiceSearchRequest C→S 服务搜索请求
0x03 SDP_ServiceSearchResponse S→C 服务搜索响应
0x04 SDP_ServiceAttributeRequest C→S 属性查询请求
0x05 SDP_ServiceAttributeResponse S→C 属性查询响应
0x06 SDP_ServiceSearchAttributeRequest C→S 服务搜索+属性查询请求
0x07 SDP_ServiceSearchAttributeResponse S→C 服务搜索+属性查询响应
各PDU详解

① SDP_ErrorResponse (0x01)

当Server无法处理请求时返回错误响应。

复制代码
Parameters:
+------------+------------------+
| ErrorCode  |    ErrorInfo     |
|  (2字节)   |     (可变)       |
+------------+------------------+
ErrorCode 含义
0x0001 Invalid SDP Version
0x0002 Invalid Service Record Handle
0x0003 Invalid Request Syntax
0x0004 Invalid PDU Size
0x0005 Invalid Continuation State
0x0006 Insufficient Resources

② SDP_ServiceSearchRequest (0x02)

根据UUID搜索匹配的服务,返回Service Record Handle列表。服务器会去自己的服务记录中去查询有无对应的UUID进行匹配

复制代码
Parameters:
+---------------------+----------------------+--------------------+
| ServiceSearchPattern| MaxServiceRecordCount| ContinuationState  |
|    (Data Element)   |       (2字节)        |      (可变)        |
+---------------------+----------------------+--------------------+
字段 说明
ServiceSearchPattern 要搜索的UUID列表(最多12个)
MaxServiceRecordCount 最多返回多少条记录
ContinuationState 分片继续状态(首次请求为0x00)

示例:搜索 A2DP Sink (UUID=0x110B)

复制代码
02 00 01 00 08 35 03 19 11 0B 00 10 00
↑  ↑────↑  ↑────↑  ↑───────────↑  ↑────↑  ↑
│  │       │       │              │       └─ ContinuationState=0
│  │       │       │              └─ MaxServiceRecordCount=16
│  │       │       └─ ServiceSearchPattern(嵌套结构)
│  │       └─ ParameterLength=8
│  └─ TransactionID
└─ PDU ID=0x02

ServiceSearchPattern 嵌套结构:
Sequence (35 03) ─────────────┐
└─ UUID-16 (19) ── 11 0B      │  → 0x110B
──────────────────────────────┘

③ SDP_ServiceSearchResponse (0x03)

返回匹配的Service Record Handle列表。

复制代码
Parameters:
+-------------------------+---------------------------+-------------------------+-------------------+
| TotalServiceRecordCount | CurrentServiceRecordCount | ServiceRecordHandleList | ContinuationState |
|        (2字节)          |         (2字节)           |        (可变)           |      (可变)       |
+-------------------------+---------------------------+-------------------------+-------------------+
字段 说明
TotalServiceRecordCount 匹配的总记录数
CurrentServiceRecordCount 本次返回的记录数
ServiceRecordHandleList Handle列表(每个4字节),方便后续进行对应的连接,一次可以进行多个句柄返回
ContinuationState 分片继续状态

④ SDP_ServiceAttributeRequest (0x04)

根据Service Record Handle查询指定属性。

复制代码
Parameters:
+---------------------+-----------------------+-----------------+-------------------+
| ServiceRecordHandle | MaxAttributeByteCount | AttributeIDList | ContinuationState |
|       (4字节)       |       (2字节)         | (Data Element)  |      (可变)       |
+---------------------+-----------------------+-----------------+-------------------+
字段 说明
ServiceRecordHandle 要查询的服务记录句柄
MaxAttributeByteCount 最大返回字节数,超过进行分段
AttributeIDList 要查询的属性ID列表或范围
ContinuationState 分片继续状态

AttributeIDList 格式详解

AttributeIDList 是一个 Data Element Sequence,里面可以包含两种元素:

类型 字节数 Header 说明
单个 Attribute ID 2字节 09 查询指定的单个属性
Attribute ID Range 4字节 0A 查询一个范围内的所有属性

区分依据 :通过 Data Element 的 Size Index 判断

  • 09 = Type 1 (Unsigned Int) + Size Index 1 (2字节) → 单个ID
  • 0A = Type 1 (Unsigned Int) + Size Index 2 (4字节) → Range

Attribute ID Range 格式

复制代码
+------------------+------------------+
|   起始ID (高16位) |   结束ID (低16位) |
+------------------+------------------+

示例

查询单个属性 0x0001(ServiceClassIDList):

复制代码
35 03 09 00 01
↑     ↑  ↑────↑
│     │  Attribute ID = 0x0001
│     └─ Unsigned Int, 2字节
└─ Sequence, 长度3

查询属性范围 0x0000 ~ 0xFFFF(所有属性):

复制代码
35 05 0A 00 00 FF FF
↑     ↑  ↑─────────↑
│     │  Range: 起始0x0000, 结束0xFFFF
│     └─ Unsigned Int, 4字节
└─ Sequence, 长度5

混合查询(单个ID + 范围):

复制代码
35 08 09 00 01 0A 00 04 01 00
↑     ↑──────↑ ↑───────────↑
│     │        └─ Range: 0x0004 ~ 0x0100
│     └─ 单个ID: 0x0001
└─ Sequence, 长度8

含义:查询 0x0001 + (0x0004到0x0100范围内的所有属性)

⑤ SDP_ServiceAttributeResponse (0x05)

返回请求的属性值。

复制代码
Parameters:
+------------------------+----------------+-------------------+
| AttributeListByteCount | AttributeList  | ContinuationState |
|        (2字节)         | (Data Element) |      (可变)       |
+------------------------+----------------+-------------------+

注意:AttributeListByteCount 是AttributeList 的字节长度

AttributeList 返回格式

无论请求的是单个ID 还是ID范围,返回格式都相同:

复制代码
Sequence [
    Attribute ID 1,  Attribute Value 1,
    Attribute ID 2,  Attribute Value 2,
    ...
]

注意 :只返回服务记录中实际存在的属性,不存在的不会返回。

示例:假设服务记录只有 0x0000、0x0001、0x0004 三个属性

请求 返回
单个ID 0x0001 只返回 [0x0001, Value]
范围 0x0000~0xFFFF 返回 [0x0000, Value], [0x0001, Value], [0x0004, Value]
单个ID 0x0099 返回空(该属性不存在)

返回示例(查询 0x0000~0xFFFF)

复制代码
05 00 09 00 1E 00 1B 35 19 ... 00
↑  ↑────↑  ↑────↑  ↑────↑  ↑───↑ ↑
│  │       │       │       │     └─ ContinuationState=0(无后续)
│  │       │       │       └─ AttributeList (Sequence)
│  │       │       └─ AttributeListByteCount
│  │       └─ Parameter Length
│  └─ Transaction ID
└─ PDU ID = 0x05

AttributeList 内容:
35 19                              ← Sequence, 长度25字节
├── 09 00 00                       ← Attr ID = 0x0000
│   0A 00 01 00 05                 ← Value: 0x00010005 (ServiceRecordHandle)
├── 09 00 01                       ← Attr ID = 0x0001
│   35 03 19 11 0B                 ← Value: [UUID 0x110B] (ServiceClassIDList)
└── 09 00 04                       ← Attr ID = 0x0004
    35 08 35 06 19 01 00 09 00 19  ← Value: [[L2CAP, PSM=0x19]] (ProtocolDescriptorList)

注意:数据是采用的嵌套数据格式,全部按照 Data Element 格式,同时每个元素都有自己的 Header,告诉你类型和长度。

如果数据太大:超过 MaxAttributeByteCount 时,通过 ContinuationState 分多次返回,每次返回部分属性。

⑥ SDP_ServiceSearchAttributeRequest (0x06) 最常用

最常用的请求,组合了服务搜索和属性查询,一次完成。

复制代码
Parameters:
+----------------------+-----------------------+------------------+-------------------+
| ServiceSearchPattern | MaxAttributeByteCount | AttributeIDList  | ContinuationState |
|   (Data Element)     |        (2字节)        |  (Data Element)  |      (可变)       |
+----------------------+-----------------------+------------------+-------------------+
字段 说明
ServiceSearchPattern 要搜索的UUID列表
MaxAttributeByteCount 最大返回字节数
AttributeIDList 要查询的属性ID列表
ContinuationState 分片继续状态

⑦ SDP_ServiceSearchAttributeResponse (0x07)

返回匹配服务的属性列表。

复制代码
Parameters:
+-------------------------+----------------+-------------------+
| AttributeListsByteCount | AttributeLists | ContinuationState |
|         (2字节)         | (Data Element) |      (可变)       |
+-------------------------+----------------+-------------------+
Continuation State(分片机制)

当返回数据太大时,SDP使用Continuation State进行分片传输,需要再次Request同时附上上次的ContinuationState:

复制代码
Client                                   Server
  |                                        |
  |--- Request (ContState=0x00) ---------->|  首次请求
  |<-- Response (ContState=0x05 XXXXX) ----|  数据未完,返回继续状态
  |                                        |
  |--- Request (ContState=0x05 XXXXX) ---->|  带上继续状态
  |<-- Response (ContState=0x00) ----------|  数据完毕,继续状态为空
  • ContinuationState格式:1字节长度 + N字节状态数据
  • 首次请求:ContinuationState = 0x00(1字节,表示长度为0)
  • 继续请求:把上次响应的ContinuationState原样带回
典型查询流程

使用ServiceSearchAttributeRequest(推荐)

复制代码
Client                                   Server
  |                                        |
  |  "查找A2DP Sink服务的连接信息"         |
  |                                        |
  |--- ServiceSearchAttributeRequest ----->|
  |    ServiceSearchPattern: [0x110B]      |  搜索AudioSink
  |    AttributeIDList: [0x0000-0x0100]    |  查询基本属性
  |                                        |
  |<-- ServiceSearchAttributeResponse -----|
  |    AttributeLists: [                   |
  |      [0x0000]=0x00010001,              |  Handle
  |      [0x0001]=[0x110B],                |  服务类
  |      [0x0004]=[[L2CAP,0x19],[AVDTP]]   |  协议描述
  |    ]                                   |
  |                                        |
  |  得到PSM=0x0019,可以建立连接了        |
相关推荐
Zevalin爱灰灰6 小时前
零基础入门学用物联网(ESP8266) 第一部分 基础知识篇(六)
单片机·物联网·嵌入式·esp8266
C^h7 小时前
RTthread消息队列学习
开发语言·算法·嵌入式
正点原子9 小时前
瑞芯微工业级芯加持,正点原子RK3562J开发板/核心板解锁嵌入式开发新可能!
linux·ubuntu·嵌入式
wsoz13 小时前
串口仿真协议(RFCOMM)
单片机·嵌入式·蓝牙协议栈·rfcomm
FreakStudio1 天前
不用装软件!这款MicroPython浏览器 IDE :让你在手机上也能调试树莓派 Pico
python·单片机·嵌入式·电子diy·tinyml
济6172 天前
STM32SPI实验:外部Flash掉电记忆实验---STM32 HAL库专栏
stm32·嵌入式·stm32hal库编程
busideyang2 天前
STC8H单片机delay_ms函数闪烁不准?原因是参数溢出!
c语言·单片机·嵌入式硬件·嵌入式
Hello_Embed2 天前
LVGL 入门(十五):接口优化
前端·笔记·stm32·单片机·嵌入式
GetcharZp3 天前
硬件世界的“高速缓存”:为什么手机断电后,屏幕还会闪一下?
嵌入式