ATT(Attribute Protocol)屬性層是GATT和GAP的基礎,它定義了BLE協議棧上層的數據結構和組織方式。
屬性(Attribute)概念是ATT層的核心,ATT層定義了屬性的內容,規定了訪問屬性的方法和權限。以編程的眼光來看,屬性是一個數據結構,它包括了數據類型和數據值,就如同C語言結構體的概念,開發者可以設計獨特的結構,來描述外部世界實體。
屬性包括三種類型:服務項、特征值和描述符。三者之間存在樹狀包含關系,服務項包含一個或多個特征值,特征值包含一個或多個描述符,多個服務項組織在一起,構成屬性規范(Attribute Profile)。對於常用的屬性規范,比如體重計、心率計,SIG(藍牙技術聯盟)做了具體定義,這樣的話,只要BLE主從設備均遵守某個Profile來進行設計,那么二者就能夠優雅的通信。
ATT層相關的東西與開發者比較近,易於理解,但是章節內容圖表較少,闡述偏多。
一. 屬性的組成(數據結構)
屬性主要由以下四部分組成:屬性句柄(Attribute Handler)、屬性類型(Attribute Type)、屬性值(Attribute Value)、屬性權限(Attribute Permissions)。
1.1 屬性句柄
屬性句柄(Attribute Handle)猶如指向屬性實體的指針,對端設備可通過屬性句柄來訪問該屬性,它是一個2字節長度的十六進制碼,起始於0x0001,在系統初始化時候,各個屬性的句柄逐步加一,最大不超過0xFFFF。
1.2 屬性類型
作用是用以區分當前屬性是服務項或是特征值等,它用UUID來表示。UUID(universally unique identifier,通用唯一識別碼)是一個軟件構建標准,並非BLE獨有的概念,一個合法的UUID,一定是隨機的、全球唯一的,不應該出現兩個相同的UUID(出現了,就說明它們倆是同一個UUID)。標准的UUID是一串16字節十六進制字符串,如f6257d37-34e5-41dd-8f40-e308210498b4,在網上可以方便的生成一個UUID。
BLE的屬性類型是有限的,有四個大類:
- Primary Service(首要服務項)
- Secondary Service(次要服務項)
- Include(包含服務項)
- Characteristic(特征值)
這些屬性類型分別對應了指定的UUID,BLE對這些UUID與屬性類型的映射關系做了規定:
- 0x1800 – 0x26FF :服務項類型
- 0x2700 – 0x27FF :單位
- 0x2800 – 0x28FF :屬性類型
- 0x2900 – 0x29FF :描述符類型
- 0x2A00 – 0x7FFF :特征值類型
假如UUID=0x1800,就表示它是一個首要服務項。
UUID是16個字節的字符串,為什么這里只使用了2字節?
因為這些是常用的UUID,為了減少傳輸的數據量,BLE協議做了一個轉換約定,給定一個固定的16字節模板,只設置2個字節為變化量,其他為常量,2字節的UUID在系統內部會被替換,進而轉換成標准的16字節UUID。UUID模板為:
0000XXXX-0000-1000-8000-00805F9B34FB
其中從左數第3、4個字節“XXXX”就是變化位,其他為固定位。如:UUID=0x2A00在系統內部會轉換成00002A00-0000-1000-8000-00805F9B34FB。
反之,如果一個特征值的UUID是16字節的,在系統內部它的屬性類型也可能寫成第3、4字節組成的雙字節,比如UUID=1234ABCD-0000-1000-8000-00805F9B34FB,它的屬性類型在內部表示為ABCD。主機端掃描到該屬性類型,會將其當做是“用戶自定義”的類型,然后從其他位置獲取該UUID的真實值。
1.3 屬性值
用於存放數據。如果該屬性是服務項類型或者是特征值聲明類型,那么它的屬性值就是UUID等信息。如果是普通的特征值,則屬性值是用戶的數據。屬性值需要預留空間以保存用戶數據。為了方便理解,我們可以將屬性值的空間看做I2C的數據空間,操作特征值里的用戶數據,就是對那塊內存空間進行讀寫。
1.4 屬性權限
屬性權限主要有以下四種:
- 訪問權限(Access Permission)- 只讀、只寫、讀寫
- 加密權限(Encryption Permission) – 加密、不加密
- 認證權限(Authentication Permission) – 需要認證、無需認證
- 授權權限(Authorization Permission) – 需要授權、無需授權
訪問(Access)權限好理解,如果是只讀權限,就不能對其寫數據,其他類似。
加密(Encryption)權限也好理解,就是對數據進行加密。
認證(Authentication)是指相互確認對方身份。完成認證流程的兩個設備,雙方建立信任關系,二者之間的通信通道即可以認為是安全的。BLE中,“認證”過程就是配對。
授權(Authorization)是指對授信設備開放權利。
認證和授權功能容易混淆,其英文拼寫也很相似。從上面的概念上看,授權要求設備必須是可信任的,因此授權的管控等級要高於認證——認證的設備未必被授權,授權的設備一定是認證的。理解二者關系,需要引入一個概念:Trusted Device(可信任設備)一個沒有經過認證的設備,被稱為Unknown Device(未知設備);經過了認證該設備會在綁定信息中被標記為Untrusted,被稱為Untrusted Device(不可信設備);經過了認證,並且在綁定信息中被標記為Trusted的設備被稱為Trusted Device(可信設備)。
授權要求設備為Trusted Device(可信任設備)。在實際使用中,經過配對以后設備即為Untrusted Device——認證,在代碼中調用API可以設置設備為Trusted Device——授權。
二. 屬性的種類和分組(屬性的層級)
屬性大致可以分為三種類型:服務項、特征值和描述符。它們的層級關系為:最頂級為Profile, 下面是多個服務項(Service), 服務項下面是多個特征值(Characteristic), 特征值下面是多個描述符(Descriptor)。
每個設備都包含以下必要的特征值和服務項:
PROFILE
- Generic Access Service(Primary Service)
- Device Name(Characteristic)
- Appearance(Characteristic)
- Generic Attribute Service(Primary Service)
- Service Changed(Characteristic)
- CCCD(Descriptor)
- Service Changed(Characteristic)
服務項這種類型本身並不包含數據,僅僅相當於是一個容器,用來容納特征值。特征值用於保存用戶數據,但它也有自己的UUID, 有點像C語言中的變量int var=0xFF,整形變量var攜帶了用戶數據0xFF,但是它自身還有地址信息(&var),因此在使用時需要先定義再賦值兩個步驟。類似的,在處理特征值所攜帶的用戶數據之前,需要先對特征值自身進行聲明。
特征值在系統中的表達形式是:聲明 + 特征值屬性。比如Device Name,它在GATT數據庫中表示方式是:
Characteristic 聲明: 0x0002, 0x2803, access_property, 0x2A00 Characteristic 項: 0x0003, 0x2A00, access_property, data
其中第一列雙字節數代表句柄,第二列雙字節數代表屬性類型(UUID),0x2803表示該項為“特征值的聲明”,0x2A00表示該特征值是Device Name。在第一行特征值聲明中,最后一項的屬性值就是該特征值的UUID。可以注意到,聲明里面的屬性值為0x2A00,特征值自己的屬性類型也是0x2A00,顯然信息冗余了。原因是,假如特征值的UUID為自定義的16字節UUID,在特征值的屬性類型中,只能存放2個字節,而UUID的真實值,就存放在特征值聲明的屬性值項中。
BLE的屬性體系在系統中以GattDB表示,即屬性數據庫。打開CyBle_gatt.c文件,找到cyBle_gattDB變量,如下圖
const CYBLE_GATTS_DB_T cyBle_gattDB[0x10u] = { { 0x0001u, 0x2800u /* Primary service */, 0x00000001u /* */, 0x0007u, {{0x1800u, NULL}} }, { 0x0002u, 0x2803u /* Characteristic */, 0x00000201u /* rd */, 0x0003u, {{0x2A00u, NULL}} }, { 0x0003u, 0x2A00u /* Device Name */, 0x00000201u /* rd */, 0x0003u, {{0x0009u, (void *)&cyBle_attValuesLen[0]}} }, { 0x0004u, 0x2803u /* Characteristic */, 0x00000201u /* rd */, 0x0005u, {{0x2A01u, NULL}} }, { 0x0005u, 0x2A01u /* Appearance */, 0x00000201u /* rd */, 0x0005u, {{0x0002u, (void *)&cyBle_attValuesLen[1]}} }, { 0x0006u, 0x2803u /* Characteristic */, 0x00000201u /* rd */, 0x0007u, {{0x2A04u, NULL}} }, { 0x0007u, 0x2A04u /* Peripheral Preferred Connection Par */, 0x00000201u /* rd */, 0x0007u, {{0x0008u, (void *)&cyBle_attValuesLen[2]}} }, { 0x0008u, 0x2800u /* Primary service */, 0x00000001u /* */, 0x000Bu, {{0x1801u, NULL}} }, { 0x0009u, 0x2803u /* Characteristic */, 0x00002201u /* rd,ind */, 0x000Bu, {{0x2A05u, NULL}} }, { 0x000Au, 0x2A05u /* Service Changed */, 0x00002201u /* rd,ind */, 0x000Bu, {{0x0004u, (void *)&cyBle_attValuesLen[3]}} }, { 0x000Bu, 0x2902u /* Client Characteristic Configuration */, 0x00000A04u /* rd,wr */, 0x000Bu, {{0x0002u, (void *)&cyBle_attValuesLen[4]}} }, { 0x000Cu, 0x2800u /* Primary service */, 0x00000001u /* */, 0x0010u, {{0xCBBBu, NULL}} }, { 0x000Du, 0x2803u /* Characteristic */, 0x00001A01u /* rd,wr,ntf */, 0x0010u, {{0xCBB1u, NULL}} }, { 0x000Eu, 0xCBB1u /* Custom Buffer */, 0x00011A04u /* rd,wr,ntf */, 0x0010u, {{0x00C8u, (void *)&cyBle_attValuesLen[5]}} }, { 0x000Fu, 0x2901u /* Custom Descriptor */, 0x00010001u /* */, 0x000Fu, {{0x001Cu, (void *)&cyBle_attValuesLen[6]}} }, { 0x0010u, 0x2902u /* Client Characteristic Configuration */, 0x00010A04u /* rd,wr */, 0x0010u, {{0x0002u, (void *)&cyBle_attValuesLen[7]}} }, };
通過注釋可以看到,每個特征值都有聲明和特征值項兩部分組成。
由於各個屬性的句柄是遞增的,因此屬性的聲明順序會影響句柄的計算。
描述符是特征值的補充信息,掛載在特征值之下,它可以開辟一段數據空間以攜帶數據,客戶端可以像操作特征值一樣對其進行讀寫,但是描述符弱於特征值,它不具備Notify/Write等讀寫屬性,遠不如特征值靈活。有一種描述符叫CCCD(Client Characteristic Configuration Description),當對特征值設置Notify或Indication時,用以控制Notify和Indication的使能情況。
關於gattDB,值得注意的一點是,gattDB是BLE協議棧在內存中開辟的一段專有區域,它會在特定的時候寫入Flash進行保存,並在啟動時讀取出來回寫到內存中去。並非所有的BLE數據通信是操作gattDB!比如手機向BLE設備發送一串字符串,從機收到這串字符串並打印出來,這個過程中,這個字符串並沒有寫入gattDB。那么,即使開發者限制某個特征值為認證、只讀等權限,仍然不能阻止這個字符串的發送和打印,因為特征值的權限,都是針對gattDB而言的。進一步,假如BLE從機收到字符串后,希望寫入gattDB進行保存,那么就會觸發權限問題。以往有開發者說為什么我勾選了屬性需要認證,但是配對失敗后仍然能夠看到手機發來的數據,原因就在這里,就是手機端傳來的數據沒有涉及到gattDB,當嘗試寫入gattDB的時候,就會發現報錯了。
三. ATT PDU(屬性協議)
在ATT層協議框架內,擁有一組屬性的設備稱為服務端(Server),讀寫該屬性值的設備稱為客戶端(Client),Server和Client通過ATT PDU進行交互。屬性協議共有6種:
屬性PDU | 方向 | 觸發響應 |
---|---|---|
Command | Client -> Server | – |
Request | Client -> Server | Response |
Response | Server -> Client | – |
Notification | Server -> Client | – |
Indication | Server -> Client | Confirmation |
Confirmation | Client -> Server | – |
它們的區別如下:
客戶端發送Request,服務器需要返回一個Response,表明服務器收到了。
服務器發送Indication,客戶端需要返回一個Confirmation,表明客戶端收到了。
以上兩種方式,均是單線程操作,即下一個Request/Indication操作需要在上一個操作收到Response/Confirmation之后才能開始。
客戶端發送Command,服務器無需任何返回。
服務器發送Notification,客戶端無需任何返回。
因此Command和Notification是不可靠的通信。當通信環境不佳,客戶端頻繁發送Command,可能發生服務器接收不到或丟棄的情況,Notification也類似。
PDU的具體格式定義如下:
參數說明:
Opcode: bit 0-5:操作屬性的方法 bit 6:Command 標識位 bit 7:Authentication Signature標識位 Attribute Parameters: 如果Attribute Opcode中身份驗證簽名標記位為0,則X = 1; 如果Attribute Opcode中身份驗證簽名標記位為1,則X = 13; Authentication Signature: 屬性操作碼和屬性參數的可選身份驗證簽名
參考鏈接:
1. 藍牙BLE實用教程
2. BLE協議棧-ATT