1. 程式人生 > 其它 >安卓藍芽協議棧中的RFCOMM狀態機分析

安卓藍芽協議棧中的RFCOMM狀態機分析

1.1 資料結構

1.1.1  tRFC_MCB

       tRFC_MCB(type of rfcomm multiplexor control block的簡寫)代表了一個多路複用器。代表了RFCOMM規範中,圖2.2中從上往下數的第2層,也就是“RFCOMM”所在的方框。一般地,兩個裝置間所有RFCOMM上層的埠都基於一個多路複用器,也就是這裡的tRFC_MCB(也就是說,兩個藍芽裝置間如果建立了RFCOMM連線,那麼就有且僅有一個tRFC_MCB資料結構在維護RFCOMM層的狀態)。

1.1.2  tPORT

       tPORT代表了一個埠,也就是代表了RFCOMM規範中,圖2.2最上面一層的內容(以數字標記成2,3…61)的那一層內容。兩個藍芽裝置間每建立一個port的連線,那麼就會分配一個tPORT來維護該連線的狀態。例如安卓手機和藍芽耳機建立了HF連線(HF基於RFCOMM),那麼該安卓手機就會分配一個tPORT來代表該port連線的狀態。不過需要注意的是,這裡的tPORT並不會維護HF本身資料協議,只是將HF的協議資料當作平凡的RFCOMM資料包來同等對待。

2.1 狀態機

2.1.1  RFCOMM多路複用器狀態機

表2.1 RFCOMM多路複用器狀態表

序號

簡寫

描述

1

RFC_MX_STATE_IDLE

空閒狀態(初始化後,未連線時)

2

RFC_MX_STATE_WAIT_CONN_CNF

等待連線應答(發出L2CAP連線請求後)

3

RFC_MX_STATE_CONFIGURE

L2CAP配置狀態

4

RFC_MX_STATE_SABME_WAIT_UA

等待回覆SABM的UA命令

5

RFC_MX_STATE_WAIT_SABME

等待SABM命令

6

RFC_MX_STATE_CONNECTED

多路複用器已連線

7

RFC_MX_STATE_DISC_WAIT_UA

等待回覆DISC的UA命令

       該狀態機有七個狀態,見表2.1中列出。每個狀態下都需要能夠處理表2.2中列出的事件;並且根據事件的不同,如果有需要那麼就會切換到下一個狀態中並且等待新的事件來處理。

2.1.1.1 RFC_MX_STATE_IDLE狀態

       處理事件RFC_MX_EVENT_START_REQ:1. 初始化RFCOMM所用的L2CAP對應的MTU長度為666位元組(L2CAP預設MTU長度672減去RFCOMM所用的header長度5,再減去1的值)。2. 呼叫L2CAP層提供的L2CAP通道連線API(L2CA_ConnectReq),使用代表RFCOMM的PSM值0x0003發起和對方裝置RFCOMM所用的L2CAP通道連線。3. 如果第二步經由L2CAP層返回的連線結果是0(代表失敗),那麼就接下來清理其他相關狀態並且通過回撥告知上層,連線失敗了;否則儲存相關狀態並且將狀態機切換到RFC_MX_STATE_WAIT_CONN_CNF狀態。

該狀態下不應該接收到事件RFC_MX_EVENT_START_RSP、RFC_MX_EVENT_CONN_CNF、RFC_MX_EVENT_CONF_IND以及RFC_MX_EVENT_CONF_CNF。

       不處理的事件是RFC_EVENT_SABME、RFC_EVENT_DM、RFC_EVENT_TIMEOUT和RFC_EVENT_UA。

       處理事件RFC_MX_EVENT_CONN_IND:1. 開啟定時器T2,不過此時超時時間設定成120秒(見5.2.1節的解釋內容)。2. 做出L2CAP連線請求的回覆,並且接受該L2CAP通道連線。3. 向對方發出配置該L2CAP通道的配置請求,期望將該L2CAP的MTU設定成1691位元組。4. 切換到狀態RFC_MX_STATE_CONFIGURE中。

       處理事件RFC_EVENT_DISC:發出DM幀作為迴應,狀態不作更改。

       處理事件RFC_EVENT_UIH:發出DM幀作為迴應,狀態不作更改。

表2.2 RFCOMM多路複用器事件

序號

簡寫

描述

1

RFC_MX_EVENT_START_REQ

要求建立RFCOMM底層的L2CAP通道連線

2

RFC_MX_EVENT_START_RSP

 

3

RFC_MX_EVENT_CLOSE_REQ

 

4

RFC_MX_EVENT_CONN_CNF

 

5

RFC_MX_EVENT_CONN_IND

 

6

RFC_MX_EVENT_CONF_CNF

 

7

RFC_MX_EVENT_CONF_IND

對端裝置要求配置L2CAP

8

RFC_MX_EVENT_QOS_VIOLATION_IND

 

9

RFC_MX_EVENT_DISC_IND

對端裝置要求斷開RFCOMM的L2CAP通道

10

RFC_MX_EVENT_TEST_CMD

 

11

RFC_MX_EVENT_TEST_RSP

 

12

RFC_MX_EVENT_FCON_CMD

 

13

RFC_MX_EVENT_FCOFF_CMD

 

14

RFC_MX_EVENT_NSC

 

15

RFC_MX_EVENT_NSC_RSP

 

16

RFC_EVENT_TIMEOUT

 

17

RFC_EVENT_SABME

 

18

RFC_EVENT_UA

收到了UA幀

2.1.1.1 RFC_MX_STATE_WAIT_CONN_CNF狀態

       該狀態中不應該收到事件RFC_MX_EVENT_START_REQ。

       處理事件RFC_MX_EVENT_CONN_CNF:1. 如果收到的L2CAP連接回復的結果碼不是成功,那麼清理之前分配的tRFC_MCB資料體,回覆使用RFCOMM的上層,告知連線失敗,並且將狀態切換至RFC_MX_STATE_IDLE。並且不處理本節描述的其他過程。2. 如果收到的結果碼是成功,那麼將狀態切換至RFC_MX_STATE_CONFIGURE。並且發出該RFCOMM的L2CAP通道的配置請求給對方,期望將該L2CAP的MTU設定成1691位元組。

       處理事件RFC_MX_EVENT_CONF_IND:正常情況下,應該先收到對方對本地裝置發出去的RFCOMM的L2CAP連線請求而作出的回覆。不過有可能因為時序的問題,沒有收到連線應答之前就收到了對方要求配置該RFCOMM的L2CAP通道的請求。不過為了相容起見,這裡假定連線已經“完成”,回覆接受該配置請求的資料包即可,不作其他處理。不過狀態仍維持在RFC_MX_STATE_WAIT_CONN_CNF。

       處理事件RFC_MX_EVENT_DISC_IND:看起來對方不願意建立RFCOMM的L2CAP連線。因此需要將狀態切換成原來的RFC_MX_STATE_IDLE;並且通知上層profile,RFCOMM連線失敗、相關的port也連線失敗了。

       處理事件RFC_EVENT_TIMEOUT:連線請求發生了超時、或者是對方不願意接受該連線而一直保持沉默狀態不回覆。將狀態切換成RFC_MX_STATE_IDLE。送出該L2CAP的斷線請求。對方不願意接受本地裝置發出去的連線請求的原因之一可能是發生了5.2.1節中介紹的連線衝突(依據是否有pending_lcid可以判斷出):此時對方堅持使用他所發起的連線。這樣的話,連線衝突就算解決了,那麼再讓狀態機執行一次再狀態RFC_MX_STATE_IDLE下的事件RFC_MX_EVENT_CONN_IND。如果沒有連線衝突,單純是對方一直不回覆本地的連線請求,那麼需要通知上層RFCOMM以及相關聯的port連線失敗。

       本狀態中不處理其他的事件。

2.1.1.2 RFC_MX_STATE_CONFIGURE狀態

該狀態下不應接收到事件RFC_MX_EVENT_START_REQ和RFC_MX_EVENT_CONN_CNF。

處理事件RFC_MX_EVENT_CONF_IND:1. 如果對方提供了它自己的L2CAP MTU,那麼儲存對端的MTU值;2. 回覆對端發過來的配置請求;3. 如果本地發出去的配置請求對方已經接受,而這裡也表示本地也已經接受了對方的配置請求,那麼如果本地是RFCOMM L2CAP連線的發起者,那麼接下來就發出SABM命令給對方以嘗試正式啟用該RFCOMM L2CAP連線(見5.2.1節);隨後開啟定時器T1(20秒),定時器超期後送出事件RFC_EVENT_TIMEOUT。4. 如果本地裝置是RFCOMM的L2CAP連線的接受者,切換到狀態RFC_MX_STATE_WAIT_SABME,並設定定時器120秒(考慮到可能需要的配對事件),並且定時器超時後送出事件RFC_EVENT_TIMEOUT。

處理事件RFC_MX_EVENT_CONF_CNF:1. 如果對方沒有同意配置,那麼本地是L2CAP發起者的話,還需要通知上層連線失敗;隨後斷開RFCOMM L2CAP連線(看起來有點武斷?)。2. 對方同意配置:如果本地裝置是RFCOMM L2CAP的發起者,那麼接下來按照5.2.1節的內容,發出SABM以開啟該RFCOMM多路複用器,並切換到狀態RFC_MX_STATE_SABME_WAIT_UA;如果本地裝置是RFCOMM L2CAP的接收者,那麼設定定時器超時時間120秒(考慮到可能觸發配對的等待或者使用者的確認),切換到狀態RFC_MX_STATE_WAIT_SABME等待對方發過來SABM。

處理事件RFC_MX_EVENT_DISC_IND:將狀態切換成原來的RFC_MX_STATE_IDLE;並且通知上層profile,RFCOMM連線失敗、相關的port也連線失敗了。

處理事件RFC_EVENT_TIMEOUT:實施處理事件RFC_MX_EVENT_DISC_IND類似的處理。

不處理其他事件。

2.1.1.3 RFC_MX_STATE_WAIT_SABME狀態

處理事件RFC_EVENT_SABME:停止定時器,並且回覆UA幀給對方;切換到狀態RFC_MX_STATE_CONNECTED。設定新的定時器2秒,如果2秒內沒有收到對方發過來的PN幀,那麼將會斷開RFCOMM所用的L2CAP通道。

處理事件RFC_MX_EVENT_DISC_IND:將狀態切換成原來的RFC_MX_STATE_IDLE;並且通知上層profile,RFCOMM連線失敗、相關的port也連線失敗了。

處理事件RFC_MX_EVENT_START_RSP

處理事件RFC_EVENT_TIMEOUT:將狀態切換至原來的狀態RFC_MX_STATE_IDLE,發起斷開RFCOMM的L2CAP連線,以及通知上層相關的連線失敗。

處理事件RFC_MX_EVENT_CONF_IND以及RFC_MX_EVENT_CONF_CNF:由於安卓協議棧目前不支援重新配置RFCOMM的L2CAP通道,因此按照處理事件RFC_EVENT_TIMEOUT一樣的處理過程即可。

在該狀態下不處理其他事件。

2.1.1.4 RFC_MX_STATE_SABME_WAIT_UA狀態

該狀態下不應該收到事件RFC_MX_EVENT_START_REQ和RFC_MX_EVENT_CONN_CNF。

處理事件RFC_MX_EVENT_DISC_IND:切換至狀態RFC_MX_STATE_IDLE;並且通知上層連線斷開了。

處理事件RFC_EVENT_UA:停止定時器,切換到狀態RFC_MX_STATE_CONNECTED;在多路控制器通道(通道號是零)上收到UA幀的裝置必定是RFCOMM L2CAP連線的發起者(因為是它發出的SABM幀並且等待UA回覆)。那麼該設備嘗試建立RFCOMM L2CAP連線的目的必定是為了建立某個RFCOMM通道的連線並且將來為某個應用層的profile所使用(例如用作HF profile)。那麼自然接下來的動作就是通過發出UIH幀(命令型別是PN)來發起配置該HF的通道了。

處理事件RFC_EVENT_DM:停止定時器。並且執行對事件RFC_EVENT_TIMEOUT所處理的內容一樣的處理。

處理事件RFC_EVENT_TIMEOUT:等待對方回覆UA發生了超時。將狀態切換至原來的狀態RFC_MX_STATE_IDLE,發起斷開RFCOMM的L2CAP連線,以及通知上層相關的連線失敗。

處理事件RFC_MX_EVENT_CONF_IND以及RFC_MX_EVENT_CONF_CNF:這裡重新收到了配置L2CAP相關的事件。由於安卓協議棧目前不支援重新配置RFCOMM的L2CAP通道,因此按照處理事件RFC_EVENT_TIMEOUT一樣的處理過程即可。

在該狀態下不處理其他事件。

2.1.1.5 RFC_MX_STATE_CONNECTED狀態

處理事件RFC_EVENT_TIMEOUT以及RFC_MX_EVENT_CLOSE_REQ:1. 發出斷開DLCI為0的控制資料包給對方。2. 開啟一個3秒的定時器,檢查斷線是否完成。3. 裝狀態切換至RFC_MX_STATE_DISC_WAIT_UA。

處理事件RFC_MX_EVENT_DISC_IND:1. 通知上層連線已經斷開。2. 將狀態切換至RFC_MX_STATE_IDLE。

處理事件RFC_EVENT_DISC:該事件的出現表明對方期望斷開RFCOMM的L2CAP連線。因此:1. 回覆UA幀。2. 如果本地裝置是RFCOMM的L2CAP的發起者,那麼還要發起斷開該L2CAP的斷線請求。3. 通知上層相關連線已經斷開。

在該狀態下不處理其他事件。

2.1.1.6 RFC_MX_STATE_DISC_WAIT_UA狀態

處理事件RFC_EVENT_UA、RFC_EVENT_DM和RFC_EVENT_TIMEOUT:1. 發出斷開該RFCOMM所用的L2CAP通道的斷線請求。2. 釋放RFCOMM多路控制器的相關資源。3. 如果通過restart_required設定了重啟L2CAP通道,那麼還需要再次發起RFCOMM的L2CAP連線請求。

處理事件RFC_EVENT_DISC:發出通道0的UA幀。

處理事件RFC_EVENT_UIH:忽略收到的資料,並且發出通道0的DM幀。

處理事件RFC_MX_EVENT_START_REQ:等待斷線應答UA期間卻收到了要求連線的請求。那麼將該請求通過標籤restart_required設定為true,等待合適的機會再次發起連線。

處理事件RFC_MX_EVENT_DISC_IND:切換到狀態RFC_MX_STATE_IDLE,並且通知上層連線斷開了。

本狀態不處理其他事件。

2.1.1.7 處理RFCOMM的L2CAP連線請求

       RFCOMM層通過L2CAP的回撥收到了來自對端裝置的RFCOMM的L2CAP連線請求之後(也就是通過L2CAP層的回撥RFCOMM_ConnectInd獲知對方裝置想要與本地裝置建立連線),隨即分配一個tRFC_MCB來維護該RFCOMM L2CAP連線的狀態。如果無法分配tRFC_MCB來處理該連線,那麼將會發出以資源不足(L2CAP_CONN_NO_RESOURCES:值為4)為由的拒絕連接回復,並且不再執行下列的處理過程。

       如果分配來的tRFC_MCB的狀態不是RFC_MX_STATE_IDLE(表示因為某種原因已經為該2裝置間分配過一次了,也就是說兩個裝置間至多隻能有一個tRFC_MCB來維護RFCOMM底層L2CAP連線)。那麼此時要按照5.2.1節中描述的連線衝突來處理(發出L2CAP連線請求之後還沒有得到回覆之前收到了對方發過來的L2CAP連線請求),設定定時器超時時間是2~12秒之間的隨機值,定時器超期後向tRFC_MCB分發RFC_EVENT_TIMEOUT事件;使用資料結構tRFC_MCB中的成員pending_lcid來儲存發生該衝突的L2CAP的LCID。

       隨後向所分配的tRFC_MCB分發事件RFC_MX_EVENT_CONN_IND(此時該tRFC_MCB的狀態是RFC_MX_STATE_IDLE)。

2.1.1.8 處理RFCOMM的L2CAP連接回復

       本地裝置發出RFCOMM的L2CAP連線請求之後,對方就會對該連線請求做出回覆(也就是通過L2CAP的回撥RFCOMM_ConnectCnf獲知遠端裝置對本地裝置發出去的連線請求作出了回覆)。本小節描述本地裝置如何處理該連接回復。

       查詢本地裝置中事先分配的tRFC_MCB資料體。如果沒有查詢到,那麼表示發生了什麼錯誤,隨機忽略來自L2CAP的該訊息並且不處理下列的操作過程。

       如果在所查詢到的tRFC_MCB資料體中找到了pending_lcid的值(非零,見9.2.1.5節),那麼代表發生了連線衝突。1. 如果連接回復中可以看出是對方裝置拒絕本地裝置發出的連線請求,那麼本地裝置就放棄之前的RFCOMM的L2CAP連線請求,並且清理相關之前分配的tRFC_MCB資料體,將該分配的tRFC_MCB的發起者狀態標識is_initiator設定成false,代表該RFCOMM的L2CAP連線是對方發起的。隨後切換至狀態RFC_MX_STATE_IDLE;向該tRFC_MCB分發事件RFC_MX_EVENT_CONN_IND讓其處理。並且隨後不作本節後續描述的執行過程。2. 如果連接回復中可以看出對方接受了本地裝置發出的連線請求,那麼代表對方裝置放棄了自己發向本地裝置的L2CAP連線請求,那麼本地裝置也就不用“客氣”了,回覆拒絕對方的L2CAP連線即可(以資源不足的理由L2CAP_CONN_NO_RESOURCES來回復)。衝突的處理的其他資訊,可以參考節。隨後清理pending_lcid的值。

       最後,向tRFC_MCB的狀態機發送事件RFC_MX_EVENT_CONN_CNF讓其處理。

2.1.1.9 處理RFCOMM的L2CAP配置請求

RFCOMM層通過L2CAP的回撥收到了來自對端裝置的RFCOMM的L2CAP配置請求之後(也就是RFCOMM_ConfigInd),在本地查詢tRFC_MCB資料體,查詢到之後向它的狀態機送入事件RFC_MX_EVENT_CONF_IND讓其處理。

2.1.1.10 處理收到的RFCOMM的L2CAP資料

RFCOMM層通過L2CAP的回撥收到了來自對端裝置的RFCOMM的L2CAP資料包之後(也就是通過RFCOMM_BufDataInd),第一步查詢所關聯的tRFC_MCB資料體來負責處理,如果沒有查詢到,那麼忽略該資料包後續不作任何處理(按理應該斷開所關聯的L2CAP通道)。第二步解析該資料包,並且提取出表10.2中的事件。如果發生了資料包校驗錯誤,那麼丟棄該包資料並且不作後續處理。第三步判斷是不是傳送至多路複用器控制通道的資料包(也就是DLCI為零)。如果是DLCI為零,那麼表示是傳送給多路複用器的通道的。