从数据格式了解ATT
Opcode
- 总体分为6种大类型的Opcode
request / response
indicate / confirm
command
notify
- 所有的ATT数据,都是属于这6种类型的子类
- request 和command从client 端发起,区别是request 必须要有response ,而command 不需要,单向
- indicate和notify从server 端发起,区别是indicate必须要有confirm 返回,而notify不需要,单向
- opcode 占用1个字节
ATT 参数
att 参数主要分为三个部分,但是并不是所有ATT 数据包都需要全部包含这三部分内容
- ATT TYPE
由uuid 决定,uuid有16bit ,32bit 和128bit long uuid几种格式 ,一些常用的uuid,比如primary service,characteristic 等等由SIG 官方发布,可以查表。用户也可以自定义uuid
32bit uuid在PDU发送的时候,需要转成128bit发送,转换公式如下:
128_bit_value = 16_bit_value * 296 + Bluetooth_Base_UUID
128_bit_value = 32_bit_value * 296 + Bluetooth_Base_UUID
16bit uuid 在发送的时候,没有这个要求,ATT解析uuid 支持16bit 和128bit 两种格式
- ATT handle
所有ATT数据都是通过handle 去访问,handle 类似于数组的index,每个att 数据都有唯一的handle去与之对应,
handle 是一个1-0xFFFF直接的数据,每个handle依次增加
- ATT value
ATT 数据值是根据ATT type决定存储的格式,可以是一个字符串或者一个handle值。
根据该条ATT type,也可以解析其数据
数据的整体空间可以为定长,也可以为变化长度
数据加密
- 可以通过设置opcode bit7,需要加密的ATT数据,将会占有12个字节的空间
- 如果有打开,加密信息会附加到ATT PDU末尾,对端设备需要有key 才能读取该条ATT数据
- ATT数据的实际使用空间为 未加密:ATT_MTU-1 加密:ATT_MTU-13
从opcode了解ATT操作流程
0x01 ERROR_RSP
- 报错的opcode,handle,是什么错误(error code,1个字节)
- 经常error rsp用于表示流程结束标志
- 具体的error code ,此处不贴图,可以查阅spec
- 所占字节数1+1+2+1
0x02 EXCHANGE_MTU_REQ/ 0x03 EXCHANGE_MTU_RSP
- request ,client向server发起交换mtu 的请求,request中携带自己支持的mtu值(2字节)
- server 端收到之后,会根据自己所支持的mtu值与接收到的比较,取较小的值,回复给client端
- 在没有收到exchange rsp 之前,双方用默认值ATT_MTU=23
- ATT_MTU大小范围在23-512之间
- 如果ATT 链接建立过程中没有MTU request ,双方使用默认值
0x04 FIND_INFORMATION_REQ /0x05 FIND_INFORMATION_RSP
- request,client 发给server,大海捞针。
- client 不知道server有定义哪些服务,告诉一个区间,返回server 定义的ATT类型
- att支持的类型是有uuid 决定的,uuid有两种,16bit,128bit,response 中format 决定
- 返回的信息需要告诉handle 和uuid 成组的信息,handle-uuid成对
- requset 格式: opcode(1)+start handle(2)+end handle(2)
- response格式:opcode(1)+ format(1)+ [ handle- uudi ] +[handle -uuid]
- 一般用于discover具体charteritic 的值,搜索范围会很小,精确读取未知handle的att 类型
0x06 FIND_BY_TYPE_VALUE_REQ/0x07 FIND_BY_TYPE_VALUE_RSP
- 另外一种大海捞针的查找方式,较于find information request,多一个uuid和value 限制条件
- start ,end 定义一个查找区间
- 要查找什么类型,设置uuid过滤条件,比如primary service
- att value,设置att value过滤条件,比如0x1801,定义GAP service值
- 一般常用于discover service 开头,将范围设置为0-0xffff,返回GAP祥光的primary service,过滤条件能够减少返回的数量
- 0x04,0x05,0x06,x07主要用于初次获取对端ATT 的类型,返回值都是type类型。拿到对端的类型之后,才能通过恰当的方式去操作ATT,读取ATT携带的数据
0x08 READ_BY_TYPE_REQ/ 0x09 READ_BY_TYPE_RSP
- 需要知道server段关于目标类型的ATT 数据分布情况
- 常见的UUID type 包括:include,characteristic,PNP ID等等
- 此类UUID一般是广泛的搜索,宏观的定义
- 返回信息 handle+value,返回信息value是搜索类型的申明ATT信息,handle是指搜索类型对应的handle。
- 所有定义的类型,第一条ATT就是申明
以一个栗子说明,RSP中的数据结构
handle = 50, 该characteristic 申明对应的handle
value :包含三部分:
properties属性,包括这个特征的操作方式,每个characteristic中都包含这个部分
value handle, 指定这个charteristic实际值保存的位置,可以被操作的对象
characteristic uuid,指定这个可操作值value 的类型,可以理解为characteristic的子类
特征申明handle一般和实际value handle相邻,discover 的最终目的是通过申明找到操作value的方法,
然后通过数据所在的handle去操作
0x0A READ_REQ / 0x0B READ_RSP
-
通过read by type获取到了操作方法及value对应的handle 值,下一步就是直接去获取handle对应的value
-
在读这个handle之前,必须要先获取到对端的类型,只有这样,返回的值才能够被正确解析
-
SIG有规定部分类型的值 的数据格式,用户也可以自定义数据类型及其对应值的格式
-
reuqust: opcode(1)+handle(2)
-
response:opcode(1)+value(最长为mtu-1)
0x0C READ_BLOB_REQ / 0x0D READ_BLOB_RSP
-
如果ATT携带数据超过了MTU最大值,第一次Read 肯定读取不完,这种情况下需要使用多次读取。
-
如何判断数据超长,一次没有读取完? 第一次read response中,数据 size 等于(最大值-1),则马上改用 read
blob req读取余下的所有值, 直到response中 数据size 小于(最大值-1) -
读取的过程中,request 会传递offest=(最大值-1)的计数,保证数据完整性。每读取一次offset累加(最大值-1)
-
因此read blob request一般都在第一次read request 之后用
-
request:opcode(1)+handle(2)+offset(2)
-
response:opcode(1)+value(最长为mtu-1)
0x0e READ_MULTIPLE_REQ / 0x0f READ_MULTIPLE_RSP
- 如果遇到多个value size较小,多次读取显然不是一个明智的选择,
- read mutil request能够实现一次读取多个value值
- 但是有一些风险需要注意:
1. 任何一个handle,单独read 有error,整个rsp都会fail,如: Insufficient Authorization,Insufficient Encryption Key Size.
2. 必须要保证每个value size在上层都是已知且固定的,并且总大小不超过mtu-1,方便解析接收的数据
3. 并不要求每个value size一致
4. value总大小 = mtu-1,避免使用该命令,无法判断数据有全部读完
5. 如果value 列表总长超过了mtu-1, 只会返回第一个value值
0x10 READ_BY_GROUP_TYPE_REQ / 0x11 READ_BY_GROUP_TYPE_RSP
-
ATT group 是一个虚拟的概念,并没有指定的数据,明确表示具体的ATT属于哪一个service group
-
ATT数据结构中最大的单元是service。以service 为例, 每个service,以service declaration开头,以下一个service declaration结束,在这两个handle直接就属于同一个service group
以GATT数据框架为例说明:
每一个矩形框就是一个ATT数据,不同的ATT数据根据类型分布组成各个不同group -
rsp 返回的是改group 的start handle 和end handle
-
并没有特殊的group type 定义,一般搜索的也是宏观的uuid,例如:primary service 0x0028
-
req 中指定 搜索范围, group type 类型
opcode(1)+start handle(2)+end handle(2)+uuid(2或16) 0x0028 -
resp 如果总长超过mtu-1,只会第一组数据,每组数据格式如下:start handle(2)+end handle(2)+uuid(2或16)0x180f
返回的uuid 是服务声明的类型,具体的service 类型,例如:电量服务0x180f -
重点理解这两个uuid 的含义:
1.primary service 0x0028 服务类型,具体信息:电量服务0x180f
2.一个服务的声明,是一个ATT数据,包括一个handle,type(电量服务0x180f),服务类型(0x2800/0x2801)
0x12 WRITE_REQ / 0x13 WRITE_RSP
- 用于向已知handle 写入值
- 如果写成功,会有write RSP返回
- write 用于已经知道所操作handle 的数据类型,get db 结束之后
- opcode(1)+handle(2)+value(最多mtu-3)
0x52 WRITE_CMD / 0xD2 SIGNED_WRITE_CMD
- 用于client向 service写入数据,单向写入,没有反馈
- 典型的 点控ATT,control-point attribute
- 两者的区别,后者携带加密信息,opcode bit7 置 1,所以可携带的数据大小最多为(mtu-15)
0x16 PREPARE_WRITE_REQ/ 0x17 PREPARE_WRITE_RSP
- 针对超长ATT value的操作,可以分批次多次写入
- req 中写的写入目标handle ,offset,value
- rsp 中携带已经完成写入的handle ,offset
- 如果req 和rsp 中handle 和offset相同,则表明写入成功
- 这个命令执行完必须搭配下一个命令,才能生效,server端仅仅只是cache这个value,还没有写入。
0x18 EXECUTE_WRITE_REQ / 0x19 EXECUTE_WRITE_RSP
- 搭配上一个命令使用,包含两种状态,生效或者取消
- server接受到这个命令后,会根据状态,执行对应的操作,写入或者取消
- 在server 端,这个命令是原子操作,不可以被中断
0x1b HANDLE_VALUE_NTF
- server主动发起,notify 可以在任何时候发出,不需要client 回复
- 一个notify中携带 opcode(1)+handle(2)+value(最多mtu-3)
- 常见的hid device 都是使用这种方式发送键值信息
0x1d HANDLE_VALUE_IND / 0x1e HANDLE_VALUE_CFM
- 又一种主动从server 端发起的ATT类型
- 一个indication携带的数据 opcode(1)+handle(2)+value(最多mtu-3)
- 与notify最大的区别是,需要client回复comfirm,如果回复超时,ACL 对断开
- 如果client 接收到indication,会回复confirm,携带opcode(1)
- 一次只能发送一个indication,前一个indication对应的confirm没有拿到之前,不会发送下一个,处于block状态
0x23 MULTIPLE_HANDLE_VALUE_NTF
- 一个notify 中可以包含多个handle value值
- 一组数据中需要携带每一个handle value 长度值,告诉client 如何分割数据
- 如果对应的handle 无效,client 会忽略该值
- 任何时候server都可以发送该notify,不需要client回复
- length 长度为2个字节
- 一组数据的格式为length(2)+ handle(2)+value(length-4)
安全相关
- ATT 的访问时建立在一条已经加密过的acl 之上
- 加密的相关流程在GAP中完成
- 在air log中,输入link key,就能够解析出ATT层的具体数据信息
- 每条ATT可以设置独立的安全机制,可以设置被写或者被读
- 当client端操作一个ATT之前,server端需要先检查其具备的权限