藍牙BLE: ATT協議層中屬性(Attribute)

ATT(Attribute Protocol)屬性層是GATT和GAP的基礎,它定義了BLE協議棧上層的數據結構和組織方式。html

屬性(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 屬性類型ide

做用是用以區分當前屬性是服務項或是特徵值等,它用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與屬性類型的映射關係作了規定:spa

  • 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

其中從左數第三、4個字節「XXXX」就是變化位,其餘爲固定位。如:UUID=0x2A00在系統內部會轉換成00002A00-0000-1000-8000-00805F9B34FB。

反之,若是一個特徵值的UUID是16字節的,在系統內部它的屬性類型也可能寫成第三、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)

服務項這種類型自己並不包含數據,僅僅至關因而一個容器,用來容納特徵值。特徵值用於保存用戶數據,但它也有本身的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

3. BLE ATT和GATT小結

4. BLE 5 協議棧-屬性協議層(ATT)

相關文章
相關標籤/搜索