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為零,那么表示是發送給多路復用器的通道的。