安卓蓝牙协议栈中的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为零,那么表示是发送给多路复用器的通道的。
安卓蓝牙协议栈中的RFCOMM状态机分析的更多相关文章
- 蓝牙协议栈中的 OSAL
蓝牙协议栈里的操作系统叫做 OSAL(操作系统抽象层).它并非一个真正意义上的操作系统,它只是实现了操作系统的一些功能,如任务切换.内存管理. OSAL 产生的根源:基于蓝牙协议栈开发的产品,实际上是 ...
- 蓝牙 BLE 协议学习: 3种蓝牙架构实现方案(蓝牙协议栈方案)
导言 不同的蓝牙架构可以用在不同的场景中.从而协议帧的架构方案也会不同. 转载自:<三种蓝牙架构实现方案(蓝牙协议栈方案)> 蓝牙架构实现方案有哪几种?我们一般把整个蓝牙实现方案叫做蓝牙协 ...
- CVE-2019-11477:Linux 内核中TCP协议栈整数溢出漏洞详细分析 代码卫士 今天
CVE-2019-11477:Linux 内核中TCP协议栈整数溢出漏洞详细分析 代码卫士 今天
- Bluedroid: 蓝牙协议栈源码剖析
一. 基础知识介绍 1.缩略语 BTIF: Bluetooth Interface BTU : Bluetooth Upper Layer BTM: Bluetooth Manager BTE: Bl ...
- 蓝牙BLE传输性能及延迟分析
BLE传输性能主要受以下几个因素影响:操作类型,Connection Interval,每个Connection Event内发送的帧数.每一帧数据的长度.具体参见如下链接: https://devz ...
- 蓝牙接收苹果手机通知 ANCS协议分析
蓝牙接收苹果手机通知 ANCS协议分析 转载,请注明出处:http://www.cnblogs.com/alexcai/p/4321514.html 综述 现在有许多蓝牙手表.手环都能接收苹果ipho ...
- Protocol buffer序列化及其在微信蓝牙协议中的应用
Protocol buffer是Google出品的一种轻便高效的结构化数据存储格式,可对结构化数据进行序列化,并具有语言无关.平台无关等特点,在通信协议和数据存储等领域已经得到广泛的应用.目前其已经提 ...
- Linux TCP/IP 协议栈之 Socket 的实现分析(一)
内核版本:2.6.37参考[作者:kendo的文章(基于内涵版本2.6.12)] 第一部份 Socket套接字的创建 socket 并不是 TCP/IP协议的一部份. 从广义上来讲,socket 是U ...
- TCP/IP协议栈源码图解分析系列10:linux内核协议栈中对于socket相关API的实现
题记:本系列文章的目的是抛开书本从Linux内核源代码的角度详细分析TCP/IP协议栈内核相关技术 轻松搞定TCP/IP协议栈,原创文章欢迎交流, byhankswang@gmail.com linu ...
- 基于Lwip协议栈中独立模式下回调函数的使用
一.使用Lwip协议独立模式开发 最近在STM32F4上边移植了Lwip,Lwip是一个小型开源的TCP/IP协议栈,有无操作系统的支持都可以运行.我当前只测试了TCP Server功能,然后对TCP ...
随机推荐
- Docker技术知识点总纲
基本介绍的安装使用 1.Docker简要介绍 2.windows下Docker的安装 3.CentOS下Docker的安装 快速入门与常用操作 4.Docker引擎升级与存储驱动的升级 5.Docke ...
- SpringCloudEureka上篇
SpringCloudEureka上篇 本文学习自<<重新定义SpringCloud>> Eureka简介 Eureka是Netflix公司开源的一款服务发现组件,该组件提供的 ...
- 3 - 标准数据加密(DES)及其备选
标准数据加密(DES)及其备选 我的博客 原书:<Understanding Cryptography: A Text book for Students and Practitioners&g ...
- autohotkey switching within applications
class QdirManager { ppid := -1 ppath := "" __New(pathIn) { this.ppath := pathIn } __Delete ...
- firefox 安装旧版flash播放器
国内恶心的特供版flash用是不可能在用了,用旧版的火狐和旧版的flash播放器,亲测可用. 下载旧版本的火狐浏览器67.04 https://ftp.mozilla.org/pub/firefox/ ...
- APP学习4
1.Toast Toast是Android系统提供的轻量级信息提醒制度,用于向用户提示即时信息,它显示在引用程序界面的最上层,显示一段时间后自动消失,不会打断当前操作,也不获得焦点. Toast.ma ...
- SWUpdate(Suricatta) + Hawkbit Server
SWUpdate的详细介绍 https://community.nxp.com/pwmxy87654/attachments/pwmxy87654/imx-processors%40tkb/5632/ ...
- ubuntu 16.04升级到18.04 出现apt-get问题解决
0.背景 编译webrtc安卓版时要升级系统,升级到18.04之后,安装安卓环境时出现以下问题(./build/install-build-deps.sh): libasan2-armhf-cross ...
- mybatis-plus逻辑删除deleted
项目中数据库表设计原则用到了逻辑删除:数据本身没有被删除,只是将deleted字段设置为1 mybatis-plus在逻辑删除方面的设置如下: mybatis-plus: configuration: ...
- Outlook怎么合并相同邮件?设置Outlook邮件为对话模式
选择View->勾选"Show as Conversations", 这样同一个标题的邮件就是叠在一块显示了. 不蟹,bro.