蓝牙 4.0 ATT属性协议

时间:2024-05-18 22:32:53

转自:https://mp.weixin.qq.com/s?__biz=MjM5MzM4MDM3Mw%3D%3D&chksm=b28fa48285f82d94f2b1ac8a7486a9c7582ad88cc29b3c14d16969929142a429e7c0bd4b7177&idx=1&mid=2448221688&scene=21&sn=a0238ec6dab2b2760dc3bbff71749596

本节主要内容: ble4.0协议讲解 13 之“快递员”属性协议(ATT)

预习知识:

属性包括三种类型:服务项、特征值和描述符。三者之间存在树状包含关系,服务项包含一个或多个特征值,特征值包含一个或多个描述符,多个服务项组织在一起,构成属性规范(Attribute Profile)。对于常用的属性规范,比如体重计、心率计,BLE协会做了具体定义,这样的话,只要BLE主从设备均遵守某个Profile来进行设计,那么二者就能够优雅的通信。

BLE的属性类型是有限的,有四个大类:

  • Primary Service(首要服务项)
  • Secondary Service(次要服务项)
  • Include(包含服务项)
  • Characteristic(特征值)

这些属性类型分别对应了指定的UUID,BLE对这些UUID与属性类型的映射关系做了规定:

  • 0x1800 – 0x26FF :服务项类型
  • 0x2700 – 0x27FF :单位
  • 0x2800 – 0x28FF :属性类型
  • 0x2900 – 0x29FF :描述符类型
  • 0x2A00 – 0x7FFF :特征值类型

权限(Permission)花样比较多:

        ​​​​​​​访问权限(Access Permission)- 只读、只写、读写

        加密权限(Encryption Permission) – 加密、不加密

        认证权限(Authentication Permission) – 需要认证、无需认证

        授权权限(Authorization Permission) – 需要授权、无需授权

 

属性协议规定了客户端(Client)和服务器(Server)之间通信的方式,共六种:

  • Request(请求)
  • Response(响应)
  • Command(命令)
  • Indication(指示)
  • Confirmation(确认)
  • Notification(通知)

客户端发送Request,服务器需要返回一个Response,表明服务器收到了。

服务器发送Indication,客户端需要返回一个Confirmation,表明客户端收到了。

以上两种方式,均是单线程操作,即下一个Request/Indication操作需要在上一个操作收到Response/Confirmation之后才能开始。

客户端发送Command,服务器无需任何返回。

服务器发送Notification,客户端无需任何返回。

因此Command和Notification是不可靠的通信。当通信环境不佳,客户端频繁发送Command,可能发生服务器接收不到或丢弃的情况,Notification也类似。

 

3.4、属性协议(ATT)

上节是通过Profile搭建数据库,这节定义通信协议进行数据库的数据读写操作。通信协议主要3个方面:通信方式、通信包格式、具体通信数据包。

3.4.1、通信协议方法

属性协议主要用来发现、读写、通知和指示属性。具体如表3-12

蓝牙 4.0 ATT属性协议

这里涉及到“原子操作”,上面的通信方式中Request和Response是一对,Indication和Confirmation是一对,在没有得到对方的应答或者确认信息之前是不能进行第2包的请求或者指示数据。命令和通知因为没有应答信息,所以可以在任何时候进行包传输。

3.4.2、属性协议包格式

    协议包格式如图3-26所示。包含有1字节的操作码和其他参数,其他参数有两种格式,一种是不包含加密认证信息的参数,另一种是带有12字节的加密认证信息的协议包。

 

蓝牙 4.0 ATT属性协议

需要注意的是,如果链路本身是加密的,那么包含认证标示的包将不被发送。因为加密链路已经进行一次安全认证,不需要再次进行认证。协议原文如下:

An Attribute PDU  that includes an Authentication Signature should not be sent on an encrypted  link. Note: an encrypted link already includes authentication data on every  packet and therefore adding more authentication data is not required.

3.4.3、属性协议PDUs

属性协议数据包,是用来对属性数据库操作的实体。这一节几乎全部用图来描述。具体怎么发送数据库中的服务,下节中会讲到ATT协议怎么对应GATT服务发现。

3.4.3.1、交换MTU

    图3-27为交换MTU包格式。图3-28为交换MTU示意。图3-29为Sniffer采集空中交换MTU数据。

蓝牙 4.0 ATT属性协议

图3-27 交换MTU包格式

蓝牙 4.0 ATT属性协议

 

图3-28交换MTU示意

蓝牙 4.0 ATT属性协议

图3-29 Sniffer采集空中交换MTU数据

 

在低功耗蓝牙连接中,属性协议默认的MTU为23字节。一般客户端是不会发起这个请求的,它的默认值就是23,当双方交换的值不同时,用较小的那个作为最终使用的值。

3.4.3.2、找信息请求\应答(Find InformationRequest\Response)

    图3-30为找信息请求\应答包格式。图3-31为找信息请求\应答示意。图3-32为Sniffer采集空中找信息请求\应答数据。

蓝牙 4.0 ATT属性协议

 

蓝牙 4.0 ATT属性协议

蓝牙 4.0 ATT属性协议

蓝牙 4.0 ATT属性协议

查找信息请求和回复用来查找一系列属性的句柄和类型信息。这是唯一一个能让客户端发现任意属性类型的消息。

查找信息请求包含有两个句柄:起始句柄和结束句柄。它们定义了该请求用到的属性句柄范围。为了找到所有数值的属性,该请求的起始句柄将是0x0001,结束句柄设为0xFFFF。但是回复的信息中因为长度限制,并不能包含所有的属性,所以需要再次请求,只是将起始句柄改为查找到的最大句柄的后一个句柄开始。

查找信息响应包含句柄-类型对。它有两种格式,一种是基于16位的UUID格式,允许在一个包中最多包含有5个属性句柄-类型对;另一种是基于128位的UUID,允许包含有1个属性句柄-类型对。在同一应答包中不能包含有这两种格式。

3.4.3.3、按类型值查找请求\应答(Find By Type Value Request\Response)

     图3-33为按类型值查找请求\应答包格式。图3-34为按类型值查找请求\应答示意。图3-35为Sniffer采集空中按类型值查找请求\应答数据。

按类型值查找请求和回复可以根据给定的类型与数值查找相应的属性。该请求包含有两个句柄:起始句柄和结束句柄,规定查找范围。对于这一范围类的所有属性,如果和请求中所指定的类型和数值一样,那么这个属性就要在响应中返回。

 

蓝牙 4.0 ATT属性协议

蓝牙 4.0 ATT属性协议

蓝牙 4.0 ATT属性协议

这个命令主要用来查找特定的首要服务。发送请求时,客户端将其中的类型设置为首要服务,并将数值设为该服务的UUID。随后的响应将包含查找到的各个首要服务的实例的句柄范围。某些特殊的服务只会在服务器中实现一次,针对它们的响应只会包含一个句柄范围。

3.4.3.4、按类型读请求\应答(Read ByType Request\Response)

图3-36为按类型读请求\应答包格式。图3-37为按类型读请求\应答示意。图3-38为Sniffer采集空中按类型读请求\应答数据。

这个命令能在句柄范围内读取某个属性值。当客户端仅知道属性的类型而非句柄时可以使用该请求。请求包含有起始、结束句柄和需要读取的属性的类型。响应将给出符合的句柄和数值。

这个请求用于搜索被包含的服务,并通过特性类型来发现服务中的所有的特性。它也被用来读取已知类型的特性值。

 

蓝牙 4.0 ATT属性协议

蓝牙 4.0 ATT属性协议

蓝牙 4.0 ATT属性协议

蓝牙 4.0 ATT属性协议

蓝牙 4.0 ATT属性协议

3.4.3.5、读请求\应答(Read Request\Response)

图3-39为读请求\应答格式。图3-40为读请求\应答示意。图3-41为Sniffer采集空中读请求\应答数据。

读取请求是属性协议总最简单的请求。该请求包含一个句柄,响应将返回该句柄对应的属性值。只有在客户端已知属性句柄的情况下,才能使用该请求读取属性值。

 

蓝牙 4.0 ATT属性协议

蓝牙 4.0 ATT属性协议

3.4.3.6、大对象读请求\应答(Read BlobRequest\Response)

图3-42为大对象读请求\应答格式。图3-43为大对象读请求\应答示意。

有时属性值太长,无法装入一个读取响应,须使用大对象读取请求来获取剩余字节。大对象读取请求不光包含属性句柄,还包含属性值的这个数据中的偏移量。其响应将从属性偏移量开始,包含尽可能多的属性值。

在获得了属性值的前22个字节后,假如客户端还想获取后续的属性值,则使用大对象读取请求。下一条响应将返回第23个字节到44个字节;如此继续,直到客户端读取到完整的属性值。

该请求用于读取长特征值与长特征描述符。

蓝牙 4.0 ATT属性协议

蓝牙 4.0 ATT属性协议

3.4.3.7、多重读取请求\应答(Read Multiple Request\Response)

图3-44为多重读取请求\应答包格式。图3-45为多重读取请求\应答示意。

用来在一个操作中读取多个属性值。该请求包含一个或多个属性句柄,响应则安之请求的顺序返回相应的属性值。然而因为响应中的数值之间没有界限,因此,在请求中除了最后一个属性允许可变的长度,其他属性必须为定长的属性值。也就是说,如果客户端用一个多重读取请求来读取三个属性,前两个属性的长度必须固定,最后一个属性的长度则可以变化。

如果客户端请求读取的属性值的长度超过了响应数据包所能承载的最大长度,那么无法放入响应数据包的数值将被丢弃。

 

蓝牙 4.0 ATT属性协议

蓝牙 4.0 ATT属性协议

3.4.3.8、按组类型读取请求\应答(Read ByGroup Type Request\Response)

图3-46为按组类型读取请求\应答包格式。图3-47为按组类型读取请求\应答示意。图3-48为Sniffer采集空中按组类型读取请求\应答数据。

它和按类型读取请求类型,也包含有一个句柄范围,读取时将其视为一个属性的类型来处理,只不过属性的类型必须为分组属性。其响应包含所读取的属性句柄、属性分组中最后一个属性以及属性的数值。

这意味着,如果分组类型是首要服务,它将返回所有首要服务声明的属性句柄、该首要服务中最后一个属性以及首要服务声明的数值。因此,可以仅凭单个请求来发现设备上的所有首要服务、与之关联的属性句柄的范围以及这些服务的类型。

如同其他返回对个句柄-数值对的响应,如果返回的值长度可变,那么只有长度相同的属性值将在第一个响应中被返回。因此,客户端必须再次发起请求,更新起始句柄来发现想要获取的下一个属性。

蓝牙 4.0 ATT属性协议

蓝牙 4.0 ATT属性协议

蓝牙 4.0 ATT属性协议

3.4.3.9、写请求\应答(WriteRequest\Response)

图3-49为写请求\应答包格式。图3-50为写请求\应答示意。图3-51为Sniffer采集空中写请求\应答数据。

蓝牙 4.0 ATT属性协议

蓝牙 4.0 ATT属性协议

蓝牙 4.0 ATT属性协议

3.4.3.10、写命令(Write Command)

图3-52为写命令包格式。图3-53为写命令示意。图3-54为Sniffer采集空中写命令数据。

写入命令类似于读取请求,区别是写入命令没有响应。写入命令包含要写入的属性的句柄和要写入的数值。

当无需响应时,可以使用写入命令。此外,因为该命令可以在任何时刻发送,即使刚发送了一条请求还未收到相应的响应,对命令的发送时延有较高的要求时,该请求也适合。

蓝牙 4.0 ATT属性协议

蓝牙 4.0 ATT属性协议

 

3.4.3.11、签名写命令(Signed Write Command)

图3-55为签名写命令包格式。图3-56为签名写命令示意。

蓝牙 4.0 ATT属性协议

 

签名写命令和写命令类似,只是前者包含认证签名。通过这样的机制,发送端可以向服务器发送写入命令时认证自己,而无需加密通信连接。签名写命令适用于以下两种场合:

 发起加密将显著增加数据连接的延迟

 发起加密将显著增加简短且无需加密的数据的送达成本

认证签名由签名计数器和消息认证码构成。签名计数器对设备间发送的每一条消息赋予不同的值,不论消息发送的间隙连接中断与否。使用签名计数器可以抵御消息重放攻击,因此,假如签名写入命令的签名计数器值和先前的值相同,该命令会被忽略。消息认证码长度为64位,该码位于句柄、数值和签名计数器之后。注意,服务器需要为每一个客户端保存其最后使用的签名计数器。

3.4.3.12、准备写请求\应答(Prepare Write Request\Response) 和执行写请求\应答(ExecuteWrite Request\Response)

准备写请求和执行写请求实现两种功能:

 它们提供了长属性值的写入功能

 它们允许在一个单独执行的原子操作中写入多个值

属性服务器包含一个准备写入队列,其中保存有准备写入请求。队列的大小可独立配置,但通常它足够储存所需要准备写入的服务。只有在收到执行写入请求时,准备写入的值才会写入属性,也就是说执行写入请求给出了执行这些准备写入操作的开始信号。

准备写请求包含句柄、偏移量和部分属性值,这和大对象读取类似。这说明客户端即可以在队列中准备多个属性值,有可以准备一个长属性值的各个部分。这样,在真正执行准备队列之前,客户端可以确定某属性的所有部分都能写入服务器。

准备写入响应也包含请求中的句柄、偏移量和部分属性值。之所以这样做是为了数据传递的可靠性。客户端可以对比响应和请求的字段值,保证准备的数据被正确接收。

图3-57为准备写请求\应答包格式。图3-58为准备写请求\应答示意。

蓝牙 4.0 ATT属性协议

蓝牙 4.0 ATT属性协议

可以这么理解,准备写入请求,之所以是“准备写”,也就是没有真正的写到属性中去,上文提到准备写是写在服务器中的单独的一个缓冲器中,当所有需要写入的数据都发送给服务器后,那么就要下一个命令,即为执行写,“执行写”也就是真正的执行写入属性的操作。

完整过程是,一旦接收完所有的准备写入请求,服务器将拥有一个随时可以执行的准备写入队列。客户端发送表示位为“立刻写入”的执行写入请求,随后服务器将在一次原子操作中写入所有值。属性将按照其准备的顺序写入。如果客户端多次准备了同一个属性值,那么服务器将按照顺序向该属性写入这些值。这意味着如果使用准备队列配置硬件状态,例如硬件需要先后执行禁用、配置、重新启用操作,那么可以用一个准备队列来实现:在队列总先将对应的属性写入“禁用”,接着写入“配置”,之后在准备队列的末尾写入“启用”,随后执行该队列。这样便能在一个原子操作中实现该硬件的重新配置。

图3-59为执行写请求\应答包格式。图3-60为可靠写请求\应答示意。

蓝牙 4.0 ATT属性协议

蓝牙 4.0 ATT属性协议

 

3.4.3.13、句柄通知(Handle Value Notification)

图3-61为句柄通知包格式。图3-62为句柄通知示意。

 

当服务蓝牙 4.0 ATT属性协议器想要向客户端发送快速得属性状态更新时,可以发送一条句柄值通知。这个是服务器能够发给客户端的两种消息中的一种,并且是不要求响应的那种。服务器可以在任何时刻发送该通知,同时该通知也是不可靠的。

句柄值通知包含属性句柄和数值。因此该通知是服务器发往客户端的一条消息,用来告知某属性的当前值。它是属性协议中最重要的消息之一。它不仅让客户端能够有效地从服务器获取当前属性数据库的更新,而且也被用来通知客户端有限状态机的变化。

一般来说,服务器会为所有的绑定设备配置通知。这样做的好处在于当某客户端重新与设备建立连接时,该设备上的服务能够立刻将自己当前的状态通知该客户端。例如,将某设备的电池电量配置为通知。

因为通知不具备任何确认机制,它们可以在任何时刻发送,而不管当时正在进行什么用的操作,一次,通知适用于必须立刻向客户端发送信息的情况。

3.4.3.14、句柄指示\确认(HandleValue Indication\Confirmation)

句柄值指示类似于句柄值通知。它有着相同的属性句柄字段和数值,不同的客户端收到指示以后应回复。服务器一次只能发送一条指示,并且只有收到确认响应后才能发起下一条指示。

句柄值确认不含任何数据,主要用于流控。因为具备了确认机制,指示被视为可靠传输。一旦服务器收到确认信息,它便能确定客户端收到了该信息。

图3-63为句柄指示\确认包格式。图3-64为句柄指示\确认示意。

蓝牙 4.0 ATT属性协议

 

 

当某设备无法完成请求所要求的操作时,它可以发送错误响应。例如某设备通过发送写入请求试图写入某个属性,但该属性是只读的,那么服务器应发送错误响应来给出请求失败的原因,而不是发送写入响应正常。

错误响应包含导致错误的与请求相关的所有信息,例如请求失败的属性、导致错误的首要原因等。一旦客户端收到错误响应,它会认为该响应与其发送的最后一条请求相对应。因此,错误响应是另一种利用响应消息来终止请求操作的方法。这也说明对每一条可以发送的请求都会有两种可能的响应:失败错误响应和成功响应。

图3-65为句柄指示\确认包格式。表3-13为错误代码表。

蓝牙 4.0 ATT属性协议

蓝牙 4.0 ATT属性协议

下面对表中的代码进行一一说明:

 无效句柄  请求中的属性句柄是无线的,它在服务器中不存在。该错误是由于服务器不使用或者不具备请求读取或者写入的属性句柄。当属性句柄被设置为0x0000时也可以发送该错误。

 不允许读取  该属性不允许读取其属性值。例如当客户端试图读取某控制点的只写是属性时,可以发送该错误。

 不允许昔写入  该属性不允许写入其属性值。例如试图写入只读属性值将导致该错误。

 PDU无效  服务器不理解收到的请求。通常当客户端发送了错误格式的请求时,将导致该错误。例如,读取请求必须有两字节的句柄作为其唯一参数。当读取请求不具备两字节的参数时将导致该错误。

 认证不足  当两设备缺少相互认证时,便无法执行某属性值的读取或写入操作。为了进行认证,可以将连接加密,或是在设备还未配对时使用安全管理配对规程对设备进行配对与连接。

 请求不支持  服务器理解发送的请求但当前无法执行。当服务器未实行某个已知的请求,或是不理解某个未来的请求时,可以使用该错误。该错误可以作为设备无法执行请求时的默认错误。

 偏移量无效  请求包含偏移量,但偏移量是无效的。偏移量和属性值的长度相同不会导致该错误,但会得到一个长度为0的值。

 准备队列已满  由于用于存储等待写入队列的空间已满,准备写入请求无法被接收。

 属性不存在  属性经遍历不存在。该错误仅用于在一个属性范围内查找一种或几种特定的属性类型时。只有查找信息请求、按类型读取请求与按组类型读取请求会导致该错误。对于查找信息请求而言,该错误意味着没有找到给定的句柄范围内的属性。对按类型值查找请求、按类型读取请求和按组类型读取请求而言,该错误说明在某句柄范围内没有找到给定类型的属性。

 属性非大对象  当大对象读取请求所要读取的属性不是大对象类型时,该请求被拒绝,客户端必须改用读取请求。该错误仅和定长的属性值相关,并且该长度小于大对象读取请求当前用到的MTU长度。由此简单的做法是先使用读取请求读取属性值的前22字节,随后使用大对象读取请求读取后续的字节。

 秘钥长度不足  该错误是由于在连接已经加密、认证与授权充足的情况下,配对期间协商的秘钥长度不满足服务的要求。有些属性值需要使用高强度的秘钥来充分保证数据的机密性。

 未知错误  这或许是个最棒的错误码了。如果导致该错误的情形事先没有考虑到过,便是“未知错误”。

 加密不足  可以读取或写入属性值,但连接未加密。一些属性值需要加密连接来保证数据的机密性。

 组类型不支持  服务器不认为请求中包含的属性类型是组类型。按组类型读取请求只能使用服务器已知的组类型。

 资源不足  服务器的资源不足以接收和执行某特定的请求。例如,某服务配置某设备广播数据时,若当前广播的数据加上需要增加的广播的数据后,数据总长度过长,则会导致该错误。

 应用错误  某请求对服务中的属性执行了不允许的操作,服务分配了错误码来报告错误的所在。这是一个128-255范围内的错误,实际的含义有包含该属性的服务规格书来定义。