本篇文章主要介紹藍芽裝置建立連線後,如何提供或響應服務、如何發現或請求服務,這就要靠GATT(Generic Attribute Profile)來定義了,GATT 為藍芽裝置定義了Server role 和Client role 兩種角色(角色並不固定在裝置上,同一裝置不同時刻充當的角色可能不同):
Server role:接收來自使用者端的服務請求,並向用戶端返回響應資料。GATT 中的角色與GAP 中的角色相互獨立又相互相容,GAP 中的Peripheral role 或Central role 都可以充當GATT 中的Server role;
Client role:向伺服器傳送服務請求,並接收來自伺服器的響應資料。GATT 中的角色與GAP 中的角色相互獨立又相互相容,GAP 中的Peripheral role 或Central role 都可以充當GATT 中的Client role。
Server 根據設計需求會實現並向外公開一個或多個服務,Client 則根據需要發現Server 公開的部分或全部服務。Client 發現Server 提供的服務後,就可以向其請求相應的服務資料(既可以讀取伺服器狀態資訊比如溫度感測器的值,又可以改變伺服器的狀態比如控制LED燈的亮滅),Server 接收到服務請求後會向Client 響應對應的服務資料(Server 可能需要Client 通過認證與授權)。
BLE 採用「Server – Client」 架構,Server 專注於定義如何使用一個或多個屬性來實現某種特定的服務(包括可以提供的狀態資訊、可以執行的狀態切換等)以及如何存取並使用這些服務(比如使用者端請求可讀取/可寫入狀態資訊、伺服器可通知狀態更新資訊、存取服務是否需要加密/認證/授權等);Client 專注於定義如何使用一個或多個服務來滿足某種特定的應用需求(比如可以使用光強感應服務、人體感應服務、照明服務等協同實現智慧照明服務)。
Server 定義的每個單獨服務十分簡單、而且是原子化的(表示一種服務只執行不可分割的特定操作),可以讓每個服務的行為更簡單明確,同時讓不同服務之間的組合更豐富多樣。Client 使用不同的原子服務組合,可以滿足豐富多樣的場景需求,相同的服務可以在不同應用中複用,滿足高內聚、低耦合的設計原則。
Service:完成特定功能或特性的資料和相關行為的集合,由一個或多個Characteristic 構成。可分為Primary Service 和Secondary Service 兩種型別:Primary Service 是公開此裝置主要可用功能的服務(主要服務型別的16位元UUID 值0x2800);Secondary Service 是為裝置提供額外輔助資訊,但跟裝置公開的功能無關的服務,一般被包含在主要服務或另一個輔助服務中(輔助服務型別的16位元UUID 值0x2801)。Service declaration 屬性存取許可權為唯讀且不需要認證授權,屬性值為具體的服務種類,比如Heart Rate Service 的16為UUID 值為0x180D;
Include Service:將伺服器上存在的另一種服務定義參照到要定義的服務中,是一種重用其它服務的方法。當某一個服務被包含到當前服務中,當前服務就可以使用被包含服務定義的資料和行為,但不能更改被包含服務的資料和行為。Include declaration 的屬性值包含三個欄位:被包含服務宣告的屬性控制程式碼、被包含服務屬性組合中的最後一個屬性控制程式碼、被包含服務的UUID,比如前面給出的範例屬性列表中的《Include》屬性值為{0x0500, 0x0504, «Manufacturer Service»};
Primary / Include Service Discovery:Client 使用Discover All Primary Services 或Discover Primary Services By Service UUID 子過程發現Server 公開的主要服務(可以發現所有主要服務或通過UUID 發現某個特定主要服務)。待Client 發現主要服務後,可以通過Find Included Services 過程發現該主要服務所包含的其它服務,兩個過程可以讓Client 瞭解Server 公開的所有主要服務與輔助服務;
Characteristic / Descriptor Discovery:Client 使用Discover All Characteristic of a Service 或Discover Characteristic by UUID 子過程發現Server 公開的某服務的Characteristic(可以發現某服務包含的所有Characteristic或者通過UUID 發現某個特定的Characteristic,通過UUID 發現某個特徵實際上是發現所有特徵後丟棄掉與UUID 不匹配的結果)。待Client 發現Characteristic 後,可以通過Discover All Characteristic Descriptors 過程發現該Characteristic 包含的所有Descriptors。Characteristic 和Characteristic Descriptors 是構成Service 的基本元素,也是讀寫Server 公開資料或狀態資訊的載體;
Characteristic Value Read:當Client 知道想要讀取的某個Characteristic 的特徵值屬性控制程式碼時,使用Read Characteristic Value 子過程從Server 讀取該特徵值,如果Server 響應的特徵值長度大於(ATT_MTU - 1)位元組則只能返回特徵值的前半部分,Client 可以使用Read Long Characteristic Values 子過程讀取完整的特徵值。當Client 知道想要讀取的一組特徵值控制程式碼集時,可以使用Read Multiple Characteristic Values 子過程讀取該組的多個特徵值。當Client 只知道想要讀取的特徵型別UUID 而不知道特徵值控制程式碼時,可以使用Read Using Characteristic UUID 子過程讀取已發現該特徵型別的所有特徵值;
Characteristic Value Write:當Client 知道想要寫入的某個Characteristic 的特徵值屬性控制程式碼時,使用Write Characteristic Value 子過程向Server 寫入一個特徵值,如果要寫入的特徵值長度大於 (ATT_MTU – 3) 位元組則應使用Write Long Characteristic Values 子過程向Server 寫入一個比較長的完整特徵值。當Client 不需要確認寫入是否成功執行時,可以使用Write Without Response 子過程向Server 寫入特徵值,該過程可以即時傳送而不受流控約束,若Server 無法處理則靜默丟棄。當Client 需要在單個操作中按順序寫入多個值時,可以使用Characteristic Value Reliable Writes 子過程向Server 傳輸要準備寫入的多個特徵值,待Client 驗證伺服器響應的資料無誤後,對已傳輸到Server 上且經過檢驗的全部特徵值執行寫入操作;
Characteristic Descriptor Value Read / Write:Client 讀取或寫入Characteristic Descriptor Value 跟前面介紹的讀取或寫入Characteristic Value 類似,當Client 知道想要讀取或寫入的Characteristic Descriptor 屬性控制程式碼時,使用Read Characteristic Descriptors 或Write Characteristic Descriptors 子過程從Server 讀取或向Server 寫入特徵描述符。如果特徵描述符的長度大於單個請求報文容納的上限,Client 可以使用Read Long Characteristic Descriptors 或Write Long Characteristic Descriptors 子過程從Server 讀取或向Server 寫入比較長的完整特徵描述符;
本文開頭已經介紹了在Attribute protocol 與LE Controller 之間還有一層L2CAP(Logical Link Control and Adaptation Protocol and Adaptation Protocol) 協定層,L2CAP 協定層的功能架構圖如下(可以將L2CAP 協定與TCP 協定對比理解):
Segmentation and reassembly:L2CAP 上層的應用封包或服務封包SDU(Service Data Unit) 通常比較大(最大可達到64 KB),下層LE Controller 可接受的報文PDU(Protocol Data Unit)容量較小(有效負載最大為27 Bytes,擴充套件報文最大為 251 Bytes),中間的L2CAP 可以起到封包分段重組的作用,將上層Application 較大的SDU 分段為較小的PDU 後傳送給LE Controller,或將從LE Controller 接收到的多個PDU 重組為完整的SDU 再遞交給上層Application 處理;
Flow control per L2CAP channel:為了避免空中傳輸鏈路堵塞,導致資料傳輸錯誤或丟失,L2CAP 協定為每個L2CAP channel 提供了基於視窗的流量控制機制;
Error control and retransmissions:空中傳輸鏈路容易受到干擾,導致資料傳輸錯誤或丟失,L2CAP 協定為傳輸的資料提供錯誤校驗和重新傳輸機制,當發現傳輸的封包錯誤時,請求對方重新傳輸錯誤的封包,跟流量控制機制一起保證藍芽無線傳輸的可靠;
Support for Streaming:藍芽音訊服務是一個很重要的應用場景,像音訊這類資料流傳輸對時延比較敏感,對偶爾出現的少量錯誤是可以容忍的,L2CAP 協定為Streaming 流資料傳輸提供了相應的L2CAP channel,而且不對其使用流量控制和錯誤重傳機制,儘可能保證流資料傳輸的及時同步。
3.2 L2CAP PDU Format
L2CAP 既然起到多協定複用器的作用,可以將多個邏輯通道對映到同一個物理通道,為了便於區分不同的邏輯通道,L2CAP 報文需要包含Channel ID 欄位,下面給出L2CAP 的基本報文格式如下(為了實現更多上述功能,L2CAP 還有更復雜的報文格式):
前面介紹的只是Basic L2CAP Mode下的報文格式,L2CAP 為Flow Control Mode、(Enhanced) Retransmission Mode、Streaming Mode、LE (Enhanced) Credit Based Flow Control Mode 提供了更復雜的報文結構如下(本文不展開介紹了,可以參考Core_v5.2 手冊的[Vol 3] Part A, Section 3 ):
Phase 2(Long Term Key Generation):雙方成功完成配對功能交換後,通過Pairing Public Key 報文交換本裝置的Public Key(如果Slave 有鍵盤輸入能力,可通過Keypress Notification 報文通知Master 輸入的配對碼),通過Pairing Confirm 和Pairing Random 報文交換Confirm value 和Random value, 然後開始計算並生成LTK。LTK 計算完成後,雙方通過Pairing DHKey Check 報文生成並相互驗證DHKey value,如果驗證成功則表示生成LTK 的過程沒有問題;
Phase 3(Transport Specific Key Distribution):雙方建立加密連線後,開始生成並分發一些專用的金鑰。比如使用Encryption Information 報文分發LTK(用於加密對談資訊),使用Master Identification 分發EDIV(Encrypted Diversifier) 和Rand(用於計算並生成LTK),使用Identity Information 報文分發IRK(Identity Resolving Key,用於生成或解析裝置的resolvable private address),使用Identity Address Information 報文分發裝置的public device address 或static random address,使用Signing Information 報文分發CSRK(Connection Signature Resolving Key,用於對資料進行簽名或驗證接收資料的簽名資訊)。
3.2.3 L2CAP PDU format on Attribute protocol
Attribute protocol 的報文格式前面已經介紹過了,在L2CAP 層加上一個L2CAP Header,其中Channel ID 為0x0004,報文格式如下: