什么是属性协议?
属性协议(Attribute Protocol)简称ATT;
属性协议既包含了关于属性的定义,也包含了属性的操作方法,既属性协议包含了通信协议的内容,属性协议通讯操作的对象是属性实例;
属性协议定义了两种角色:服务器和客户端。
服务器:提供数据的蓝牙设备
客户端:需求数据的蓝牙设备
属性协议允许称为服务器的设备将一组属性及其关联的值公开给对端的称为客户端的设备。 服务器公开的这些属性可以由客户端发现,读取和写入,并且可以由服务器指示和通知;
属性的定义
属性是属性协议中重要的概念,低功耗蓝牙把所有事物、状态抽象成属性,属性是一条公开的带有标签的,可以被寻址的数据。属性的组成如下:
它由 4 个部分组成, 分别是:属性句柄(Attribute Handle)、属性类型(Attribute Type)、属性值(Attribute Value)以及属性许可(Attribute Permissions)。
属性类型(Attribute Type)
可以被公开的数据有许许多多的类型:温度、压强、体积、距离、功率、时间、充电状态、开关状态、状态机的状态等。所公开的数据的种类称作属性类型。为了区分如此多的数据类型,一串128位的数字被用来标识属性的类型,这个唯一的标识码就叫做通用唯一识别码(UUID)。
128位的UUID相当长,设备间为了识别数据的类型需要发送长达16个字节的数据。为了提高传输效率,蓝牙技术联盟(SIG)定义了一个称为“蓝牙UUID基数”的128为通用唯一识别码,结合一个较短的16位数使用。二者仍然遵循通用唯一识别码的分配规则,只不过在设备间传输常用UUID时,只发送较短的16位版本,接收方收到后补上蓝牙UUID基数即可。
蓝牙UUID基数如下:00000000-0000-1000-8000-00805F9B34FB
例如要发送16位识别码为0x2a01,完整的128位的UUID便是:00002a01-0000-1000-8000-00805F9B34FB
属性句柄(Attribute Handle)
UUID用于标识某一种数据类型,但使用同一种数据类型的属性可能有多个,所以需要借助属性句柄来区分每一个实例,属性句柄使用一个16位的地址,也就是属性句柄。有效的句柄范从0x0001~0xFFFF。0x0000为无效句柄,不能用于寻址属性。
属性值(Attribute Value)
属性值是一个八位字节数组,可以是固定长度也可以是可变长度。 例如,它可以是一个八位字节的值,也可以是四个八位字节的整数,或者是一个可变长度的字符串。 属性可能包含一个太大的值,无法在单个PDU中发送,并且可以使用多个PDU发送。 传输的值对于属性协议是不透明的。 这些八位字节数组的编码由属性类型定义。
属性许可(Attribute Permissions)
属性许可仅仅是对属性值的一种保护, 对句柄和类型没有用。也就是说对方设备对这个属性值的操作具有什么样的权限,也就是规定了这个属性值的安全级别,例如读写是否需要认证或者需要授权。注意:这个属性许可是不能通过属性发现协议获取到的,只能是获取对方某个属性值时,如果需要什么权限,对方就会发一个状态过来,根据状态进行下一步操作。
如果对安全属性的访问需要经过身份认证的连接,而客户端是在服务器上没有经过安全性验证的, 这时服务器发送一个错误代码
«Insufficient Authentication» 给客户端。当客户端收到这个错误代码,它可能尝试对连接进行身份验证,如果身份验证成功的,就可以访问这个属性值了。
如果对安全属性的访问需要加密连接,而连接不是加密的, 服务器发送错误代码«Insufficient Encryption»(加密不足)给客户端。当客户端收到这个错误代码时,它可能会尝试发起加密请求, 如果加密成功,它就可以访问这个属性值。
属性许可本身分 3 种类型(访问许可、身份认证许可和授权许可):
- 访问许可
访问许可定义有 3 种使用方式:
可读
可写
可读且可写
当读取属性值时,需要判断这个属性值是否可读。如果不可读。服务器会返回一个属性不可读的状态。当写入属性值时,
也需要检测这个属性是否可写,否则会收到属性值不可写的状态信息。
- 身份认证许可
认证许可有 2 种:
- 需要认证
- 不需要认证
认证是一种证明身份的方式,用来证实所连接的设备是其声称的设备。而非第三方的攻击者。举个例子,当某个用户去银行开户时,她必须出示本人的证件以证明其身份,出于对证件发行方的信任,银行可以认证用户的身份。
- 授权许可
授权许可有 2 种:
- 无授权
- 授权
授权是指分配权限做某事。
属性协议通讯
属性协议通讯采用下面六种方式之一,用于发现、读写、通知、和指示属性:
- 请求(Request) - 客户端向服务器请求数据。
- 响应(Response) - 服务器对请求的应答。
- 命令(Command) - 客户端发送命令到服务器,无应答。
- 通知(Notification) - 服务器主动发送到客户端,无应答。
- 指示(Indication) - 服务器主动向客户端发送,客户端需要确认。
- 确认(Confirmation) - 客户端对指示应答。
其中请求和命令是客户端主动发起的行为,而通知和指示是服务器将属性数值上报给客户端,以告知属性值已发生变化。
这里涉及到“原子操作”,上面的通讯方式中Request和Response是一对。Indication 和 Confirmation 是一对,在没有得到对方的应答或者确认信息之前是不能进行第 2 包的请求或者指示数据。 命令和通知因为没有应答信息,所以可以在任何时候进行传输。
属性PDU格式
属性PDU,属性协议数据单元。既属性协议通讯的数据格式,定义如下:
属性操作码(Attribute Opcode)由三个字段组成,即认证签名标记,命令标记和方法。 该方法是一个6位值,该值确定属性参数的格式和含义。
如果属性操作码的认证签名标志设置为1,则认证签名值应附加在属性PDU的末尾,并且X为13。如果属性操作码的认证签名标志设置为0,则认证签名值不得附加,并且X为1。
不应在加密的链接上发送包含身份验证签名的属性PDU。 注意:加密的链接已经在每个数据包中包含身份验证数据,因此不需要添加更多身份验证数据。
如果将属性操作码的命令标志设置为1,则PDU应被视为命令。只有写命令可以包含身份验证签名。
属性协议方法
属性协议方法总览:
属性协议方法分类情况如下:
- Error Handling
错误应答(Error Response)- MTU Exchange
交换MTU请求(Exchange MTU Request) / 交换MTU应答(Exchange MTU Response)- Find Information
找信息请求(Find Information Request) / 找信息应答(Find Information Response)
按类型值查找请求(Find By Type Value Request) / 按类型值查找应答(Find By Type Value Response)- Reading Attributes
按类型读请求(Read By Type Request) / 按类型读应答(Read By Type Response)
读请求(Read Request) / 读应答(Read Response)
大对象读请求(Read Blob Request) / 大对象读应答(Read Blob Response)
多重读取请求(Read Multiple Request) / 多重读取应答(Read Multiple Response)
按组类型读取请求(Read by Group Type Request) / 按组类型读取应答(Read by Group Type Response)- Writing Attributes
写请求(Write Request) / 写应答(Write Response)
写命令(Write Command)
签名写命令(Signed Write Command)- Queued Writes
准备写请求(Prepare Write Request) / 准备写应答(Prepare Write Response)
执行写请求(Execute Write Request) / 执行写应答(Execute Write Response)- Server Initiated
通知(Handle Value Notification)
指示(Handle Value Indication) / 确认(Handle Value Confirmation)
需要说明的是,属性协议ATT只提供了设备之间通讯最基础的应用协议,具体定义属性类型的含义和如何使用属性协议通讯方法在通用属性规范GATT中实现,通用属性规范GATT是基于属性协议ATT抽象出来的一套规范。
下面是属性协议方法的详细介绍:
-
Error Handling
错误应答(Error Response)
错误响应用于说明无法执行给定的请求,并提供原因(产生错误的Opcode,属性句柄;并提供错误码),格式如下:
Request Opcode In Error参数应设置为产生此错误的请求的属性操作码;
Attribute Handle In Error参数应设置为产生该错误的原始请求中的属性句柄。 如果原始请求中没有属性句柄或不支持该请求,则此字段应使用值0x0000;
Error Code参数应设置为以下值之一(左图来自《蓝牙规范协议文档》,右图来自《BLE4.0低功耗蓝牙协议总结》,两者内容是一样的):
-
MTU Exchange
MTU: 最大传输单元(MAXIMUM TRANSMISSION UNIT),指在一个PDU(Protocol Data Unit:协议数据单元,在一个传输单元中的有效传输数据)能够传输的最大数据量(多少字节可以一次性传输到对方)。
MTU 交换是为了在主从双方设置一个PDU中最大能够交换的数据量,通过MTU的交换和双方确认(注意这个MTU是不可以协商的,只是通知对方,双方在知道对方的极限后会选择一个较小的值作为以后的MTU,比如说,主设备发出一个150个字节的MTU请求,但是从设备回应MTU是23字节,那么今后双方要以较小的值23字节作为以后的MTU),主从双方约定每次在做数据传输时不超过这个最大数据单元。
原文链接:https://blog.csdn.net/viewtoolsz/article/details/76177465
交换MTU请求(Exchange MTU Request)
客户端使用交换MTU请求(Exchange MTU Request)将客户端的最大接收MTU大小通知服务器,并请求服务器以其最大接收MTU大小进行响应。
Client Rx MTU必须大于或等于默认的ATT_MTU,默认ATT_MTU为23字节。
该请求只能在客户端连接期间发送一次。 Client Rx MTU参数应设置为客户端可以接收的属性协议PDU的最大大小。
交换MTU应答(Exchange MTU Response)
发送交换MTU应答(Exchange MTU Response)以回复收到的交换MTU请求(Exchange MTU Request)。
Server Rx MTU参数应设置为服务器可以接收的属性协议PDU的最大大小,Server Rx MTU必须大于或等于默认的ATT_MTU。
-
Find Information
找信息请求(Find Information Request)
找信息请求(Find Information Request)和找信息应答(Find Information Response)用来查找一系列"属性句柄-属性类型"对信息。这是唯一一个能让客户端发现任意属性类型的消息。
仅返回属性句柄介于“开始句柄”参数和“结束句柄”参数之间的属性。要读取所有属性,起始句柄参数应设置为0x0001,结束句柄参数应设置为0xFFFF。起始句柄参数应小于或等于结束句柄参数。
找信息应答(Find Information Response)
Format参数可以包含下面两个值之一:
Table 3.7中Information Data字段由下面定义的数据格式的数据列表组成,具体取决于为Format选择的值:
两种格式的属性数据不可以包含在单个响应数据包中。同时因为数据量太大,所有属性信息不可能在单个响应包中传输完。在这种情况下,必须使用另一个找信息请求(Find Information Request)(新的Starting Handle、Ending Handle参数)来读取其余属性。
按类型值查找请求(Find By Type Value Request)
按类型查找请求(Find By Type Value Request ),传入16位UUID属性类型和属性值,用于获得相应属性的的属性句柄。该请求常用于查找GATT层的特定服务。
按类型值查找应答(Find By Type Value Response)
按类型值查找应答(Find By Type Value Response),格式如下:
Handles Information List包含满足条件的一个或多个Handles Information,其格式如下:
这里涉及到了属性句柄分组(Attribute Handle Grouping)的概念,分组其实是由上层(GATT层)定义,请带着疑问阅读后续章节,后面的GATT章节将详细介绍。
按类型读请求(Read By Type Request)
按类型读请求(Read By Type Request)用于已知属性类型但句柄未知的情况下获取属性值。
如果在句柄范围内具有所请求属性类型的属性对象,它们具有相同长度的属性值,则可以在单个请求中全部读取这些属性。
注意:如果在句柄范围内具有请求属性类型的属性对象具有不同长度的属性值,则必须发出多个“按类型读取”请求。
属性服务器应在响应中包括尽可能多的属性,以最小化读取相同属性类型的属性所需的PDU数量。
按类型读应答(Read By Type Response)
按类型读应答(Read By Type Response)包含已读取属性的属性句柄和属性值的列表:
Attribute Data的格式如下:
"属性句柄-属性值"对之间不应在应答包之间拆分,Length参数应设置为一个"属性句柄-属性值"对的大小,而且最大长度为255个字节。
如果属性值长度大于(ATT_MTU - 4) 或者 253字节,前(ATT_MTU - 4) 或者 253字节包含在应答包中,使用大对象读请求(Read Blob Request)读取剩余的属性值内容。
读请求(Read Request)
读请求(Read Request)使用属性句柄读取关联属性的属性值:
读应答(Read Response)
如果属性值长于(ATT_MTU-1),则该应答中应包括前(ATT_MTU-1)字节,使用大对象读请求(Read Blob Request)读取剩余的属性值内容。
大对象读请求(Read Blob Request)
大对象读请求(Read Blob Request)用于请求服务器以读取给定的偏移量处属性值的一部分,并在大对象读应答(Read Blob Response)中返回该属性值的特定部分:
如果大对象读请求(Read Blob Request)的Value Offiset等于属性值的长度,则应答中的Part Attribute Value的长度应为零。
大对象读应答(Read Blob Response)
大对象读应答(Read Blob Response)包含给定的偏移量处读取的属性值的一部分:
如果属性值的长度长于(Value Offset + ATT_MTU-1),则从Value Offset处起始(ATT_MTU-1)字节包含在应答数据包中。
多重读取请求(Read Multiple Request)
多重读取请求(Read Multiple Request)用于向服务器读取两个或多个属性值,只有具有已知固定长度的值可以读取,最后一个值的长度可变。
Set Of Handles为一组属性句柄(两个或多个),客户端请求服务器读取这些属性句柄关联的属性值。
多重读取应答(Read Multiple Response)
如果Set Of Values参数的长度大于(ATT_MTU-1),那么应答中只包含(ATT_MTU-1)字节的数据。
注意:当属性值的集合长度可能为(ATT_MTU-1)时,客户端不应使用此请求来获取属性,因为将无法确定最后一个属性值是否完整或是否溢出。
按组类型读取请求(Read by Group Type Request)
按组类型读取请求(Read by Group Type Request)用于在已知属性类型的情况下获取属性值,该属性的类型由较高层规范定义,但句柄未知。仅返回属性句柄在“开始句柄”和“结束句柄”之间(包括该句柄)且其属性类型与给定的“属性组类型”相同的属性。 为了搜索所有属性,起始句柄应设置为0x0001,结束句柄应设置为0xFFFF。
如果在句柄范围内具有所请求属性类型的属性对象,它们具有相同长度的属性值,则可以在单个请求中全部读取这些属性。
属性服务器应在应答中包括尽可能多的属性,以最大程度地减少读取相同类型属性所需的PDU数量。
注意:如果在句柄范围内具有请求类型的属性具有不同长度的属性值,则必须发出多个“按组类型读取请求”。
按组类型读取应答(Read by Group Type Response)
其中Attribute Data的格式如下:
Attribute Data最大长度为255字节,因此应答包中属性值的最大长度为(Length – 4)= 251个字节。
Attribute Data List参数应设置为在请求内的句柄范围内由属性类型标识的属性信息,如果属性值长度大于(ATT_MTU - 6) or 251 字节,前(ATT_MTU-6)或251字节包含在应答中。
注意:Read Blob Request将用于读取长属性值的其余数据。
写请求(Write Request)
写请求(Write Request)用于请求服务器写入属性值,并由服务器应答来确认写入结果。
写应答(Write Response)
写命令(Write Command)
不应响应该命令而发送错误响应或写应答,如果服务器由于某种原因无法写入此属性,则该命令将被忽略。
签名写命令(Signed Write Command)
签名写命令(Signed Write Command)用于请求服务器写入带有身份验证签名的属性值。
-
Queued Writes
队列写入的目的是将多个属性值的写入请求的保存在先进先出队列中,然后在单个原子操作中对所有这些属性执行写操作。
准备写请求(Prepare Write Request)
准备写请求(Prepare Write Request)用于请求服务器准备写入属性的值。 服务器将以准备写应答(Prepare Write Response)响应此请求,以便客户端可以验证服务器是否正确接收了需要写入的属性值。
属性协议不确定Part Attribute Value 和 the Value Offset的有效性。更高的层规范决定了数据的含义,服务器可能会限制它可以接受的准备写入请求的数量,更高的层规范应定义此限制。
发出准备写请求(Prepare Write Request)并接收到应答后,同一客户端可以向同一服务器发出任何其他命令或请求。
客户端可以向服务器发送多个准备写请求(Prepare Write Request),服务器将为每个请求保存到队列并返回应答。
每个客户端的准备队列是分开的; 一个准备队列的执行不应影响其他客户端准备队列的准备或执行。
对准备队列中存在的属性的其它任何操作不受影响,就好像准备队列不存在一样。执行写请求后,准备队列的值将写入属性,即使属性值已在之前发生了改变。
即使属性句柄与先前的准备写请求(Prepare Write Request)中的相同,每个准备写请求(Prepare Write Request)也将排队。 然后将按照接收到的顺序执行这些操作,从而导致对该属性进行多次写入。
如果丢失了链接,准备队列将被清除并且不执行任何写操作。
如果准备写请求(Prepare Write Request)无效,因此发出了错误响应,则该准备写请求(Prepare Write Request)将被视为未收到。 准备队列中的所有现有准备写入均不受此无效请求的影响。
准备写应答(Prepare Write Response)
发送准备写应答(Prepare Write Response)以响应收到的准备写请求(Prepare Write Request),确认已成功接收到该值并将其放入准备写队列中。
Attribute Handle、Value Offset和Part Attribute Value参数应和当前的准备写请求保持一致。
执行写请求(Execute Write Request)
Execute Write Request(执行写入请求)用于请求服务器从此客户端写入或取消写入当前在准备队列中保留的所有准备值。 该请求应由服务器作为原子操作处理。
当flags参数设置为0x01时,由先前的准备写请求队列的值应按照在相应的准备写请求中接收到的顺序写入。 然后应清除队列,并发送执行写应答(Execute Write Response)。
当flags参数设置为0x00时,将为此客户端丢弃所有待处理的准备值。 然后应清除队列,并发送执行写响应。
如果发生错误,准备队列将被清除,然后返回错误响应。
执行写应答(Execute Write Response)
写入属性后应发送执行写应答(Execute Write Response)。 一旦写动作完成,服务器就可以使用指示(Handle Value Indication)。
-
Server Initiated
通知(Handle Value Notification) 和指示(Handle Value Indication)是服务器主动向客户端告知属性值已经发生改变的行为。
通知(Handle Value Notification)
服务器可以随时发送属性值的通知。
服务器使用通知将目标属性的Attribute Handle和Attribute Value传达给客户端。
如果属性值的长度大于(ATT_MTU-3)字节,那么仅有(ATT_MTU-3)字节的属性值在通知中被发送。
为了获取其余的属性值的内容,客户端需要在收到通知之后,使用大对象读请求(Read Blob Request)。
如果Attribute Handle和Attribute Value无效,则在接收时将忽略此通知。
指示(Handle Value Indication)
服务器可以发送属性值的指示,客户端收到指示后需要发出确认(Handle Value Confirmation)以响应,这是为了确保客户端不遗漏服务器发出的关键信息。
服务器使用指示将目标属性的属性句柄和属性值传达给客户端。
如果属性值的长度大于(ATT_MTU-3)字节,那么仅有(ATT_MTU-3)字节的属性值在指示中被发送。
为了获取其余的属性值内容,客户端需要在收到指示之后,使用大对象读请求(Read Blob Request)。
客户应发送确认(Handle Value Confirmation)以响应指示(Handle Value Indication)。 在服务器收到确认之前,不会向该客户端发出任何进一步的指示。
如果属性句柄或属性值无效,则客户端应发送一个确认(Handle Value Confirmation)作为响应,并应从接收到的指示中丢弃该句柄和值。
确认(Handle Value Confirmation)
参考文档:
《Core_v4.2.pdf》
《BLE4.0低功耗蓝牙协议总结.pdf》
《低功耗蓝牙开发权威指南.pdf》