Bluetooth Attribute Protocol Spec 解读
什么是Attribute protocol
简单来说,这个协议就是用来给Server和Client进行通信的协议。Server端保存有一个类似“属性数据库”的东西,包含了一系列的属性及其特性。而Client端可以通过ATT协议从Server端获取这些属性。再具体一些,Client可以查询(Discover)、读取(read)甚至配置(write)Server中保存的属性。通常是在配置之后,Server端可以实时的告知Client端属性值的变化。通知可以是无需Client应答的(notification),也可以是需要Client响应的(indication)。
Server端的Attribute
server端保存了一系列的attribute,这些attribute由四个基本元素组成:
- attribute handle
- attribute type
- attribute value
- attribute permissions
Attribute handle其实就是一个属性在server端的索引。它对每个属性来说都是唯一的,不可有重复的值,否则Client端无法准确定位到某一个属性。Handle的范围是 [0x0001, 0xFFFF)。
Attribute type是属性的类型,ATT中使用一个(16/128bits)的UUID来表示。虽然概念是属于ATT协议,但ATT本身 并没有声明具体的UUID来作为某一个attribute type。这意味着,ATT只是提供了一个类似框架的东西,具体内容怎么填,还是由上层来定义。比如GATT(Generic Attribute Profile)就定义了几个基本的attribute type:Primary Service(UUID为0x2800)、Characteristic(UUID为0x2803)。
Attribute value是一个属性的值,不过这里仅仅是ATT这一层的value,上层协议可以对其进行更具体的划分。还是以GATT为例,一个Characteristic的attribute value就由property、value handle和UUID三部分组成。
最后一个元素attribute permissions,它描述了一个属性的访问权限,包括
- read
- write
- encryption
- authentication
- authorization
比较特殊的是,这些权限由上层定义,对于ATT来说却是不可见的,Client无法获取到Server中属性的permissions。
以上结构,可以Generic Attribute Profile(GATT Profile)当中的图来诠释:
总的来说,Server端保存的attribute大致就像下面的表格所描述的那样:
几个特殊的概念
Group——Attribute的分组
如果有一组attribute,他们描述的是一个整体的不同部分,那么就可以将他们归为一组。实现这种功能的方法,就是在这组attribute的开头单独定义一个attribute,由它来说明这组属性的起止handle以及他们所共同描述的对象是什么。这里先借用下后面会提到的一个ATT协议的PDU——Read by group type response,它包含的就是Group开头的这个attribute的value:
以上,Attirbute Handle就是这组Group的起始Handle,End Group Handle就是他的最后一个attribute的handle,Attribute Value通常是一个上层定义的UUID,比如HID Service的UUID(0x1812)。下面就是一个具体的Read by group type response的HCI层视图,虽然它已经跨越了GATT,但不影响对Group的理解:
Control-Point Attributes
这是一类只能配置(write)而不能读的属性,因此它通常是提供给Client来配置Server用的。比如在HID Service当中,HOGP host可以通过HID Control Point来通知HOGP device进入或退出suspend状态。
Exchange MTU Size
故名思议,它描述的是一个改变ATT协议MTU size的动作。Default MTU size for ATT protocol是23个字节,但是它是可以由Client、Server双方协调来变大的。具体的过程就是,Client将本地的最大RX MTU告诉Server,反过来Server也将自己最大的RX MTU告知Client;双方选择两者中较小的一个,作为双向通信的MTU size。
具体在使用时,变大MTU size可以减少信息交互的时间。以HOGP中的Report Map为例,原本可能需要好几个LE connection event来完成一个Report Map的传输(主要原因是需要应用的参与,来不及在一个LE connection event内传输多个Request请求),更新MTU size之后,可以直接在一个LEconnection event中完成(上层直接将一个大号的packet送给hardware,不需要了解下面fragment的细节)。
Long Attribute Value
ATT本身是没有定义packet length这个概念的,只能通过L2CAP来推测其长度。有些属性的value短,可以直接放在一个ATT PDU中;有些则长一些,需要多个分多个ATT PDU传输。ATT专门定义了long attribute value的处理,允许Client以offset的方式,将一个attribute value分多次从Server端取出。
当然,ATT也对value的长度做出了限制,最大512 bytes。要是还不够,那该怎么办呢?目前看到HOGP里面有一种解决方案。如果一个HOGP device的Report Map长度超过了512 bytes,那么device端可以声明两个HID Service,将Report Map分到两个HID Service的定义中去。
Attribute PDU
ATT定于了六种类型的PDUs,包括Command、Request与Response、Notification、Indication与Comfirmation。总的来说,他们都具有下面的PDU format:
Name | Size(Octets) | Description |
---|---|---|
Attribute Opcode | 1 | The attribute PDU operation code bit7: Authentication Signature Flag bit6: Command Flag bit5-0: Method |
Attribute Parameters | 0 to (ATT_MTU - X) | The attribute PDU parameters X = 1 if Authentication Signature Flag of the Attribute Opcode is 0 X = 13 if Authentication Signature Flag of the Attribute Opcode is 1 |
Authentication Signature |
0 or 12 | Optional authentication signature for the Attribute Opcode and Attribute Parameters |
Opcode好理解,就是操作码。它有两个flag,而且两个都是给(Signed)Write Command来使用的(是不是有点浪费)。
Command flag表明这是个一个command,只有Write Command和Signed Write Command会将这个flag设置为1,其他Opcode的此标志位都为0。
Authorization Signature Flag就只有Write Command会用到了。它表示是否需要Authorization Signature,后者用于对数据进行认证。只有在非加密(encrypted)链路上才可以使用,毕竟两者都是为了对数据加密,没有必要两者都用。
除了Attribute Value之外,其他ATT PDU中的multi-octet字段都是使用小端字节序进行传递。Attribute Value本身保存了上层协议定义的值,因此它的字节序由高层协议来定义。
从功能来看,ATT PDU分为四类,分别是:
- 交换MTU size的Exchange MTU Request/Response
- 查询attribute handle与attribute type映射关系的Find ***系列PDUs
- 获取attribute value的Read ***系列PDUs;
- 配置attribute的Write ***系列PDUs;
- Server端用于通知value变化的Notification、Indication以及Client端的响应Comfirmation
具体各个PDU的格式,不再一一详述,可以参见Spec最后列出的Table 3.37
相对复杂、需要注意的PDU
Find By Type Value Request/Response
从格式上看,“Find By Type Value Request”是根据Type与Value来定位handle。然而,“Find By Type Value Response”的内容却是一个个“Found Attribute Handle -- Group End Handle”组成的列表信息。实际上,这里的Group End Handle只是字面上的一种情况。如果是Group,Rsp就返回真实的Group End Handle;而如果不是,则这里的“Group End Handle”将和“Found Attribute Handle”保持一致。
Read Blob Request/Response
注意到这样一个问题:ATT不包含PDU的length信息。从而,只能通过L2CAP的长度信息来确认。对于Read Blob Request来说,得确定一个attribute的完整value还没有读完,才应该继续使用这个PDU来读取。这里,ATT使用一种推测的方式,来判断是否已经获得了一个attribute的完整value。如果Rsp中的PDU长度等于MTU size,则很有可能还没有读完,需要继续;否则,则Client可以认为已经读完整个长度的value。如果刚好某个ATT PDU的长度等于MTU size,而这个attribute value的长度也到此为止了,则Client的下一个Req会收到一个长度为0的Rsp。
此外还有一种特殊的情况——value的length是可变的。这种情况下Client判断读完的依据也有两种:
- Rsp中的长度小于MTU size;
- 收到一个error code为《invalid Offset》的Error Response
Read Multiple Request/Response
有些Response的PDU会声明它所包含的每个数据项(Attribute Data)的长度,比如“Read By Type Response”和“Read By Group Type Response”;而“Read Multiple Response”就不会包含长度信息。因此使用这两个PDU的前提是,Client必须知道请求的每个attribute的value的长度。当然,这些长度都是固定的,不是可变的。
超长的Notification与Indication
这个规则给我的感觉就很另类了。ATT协议描述的是,如果一个Notification或者Indication的PDU length等于MTU size,说明它可能还有更多的数据,当前只是一部分。需要Client再调用“Read Blob Request”来获取剩下的value。
实现上,建议还是确保MTU size要大于任何attribute value的值,否则效率太低了(来回用到至少三个interval才能完成)。