蓝牙开发那些事儿(7)——l2cap层连接过程

L2cap层是连接hci和上层profile的中转站,我们之前分析包格式的时候就说过,payload header中的llid如果标示是acl-u的话,说明就是个l2cap包。

上层profile在连接的时候,都需要先建立l2cap逻辑链路,每个逻辑链路分配cid(channel id),这也是l2cap最重要的功能:协议/信道多路复用 
然后比较重要的是,l2cap提供分包和重组功能,比如说上层的包比较大,controller支持的包比较小,就有可能需要分包了,我们前文说过,payload header中的llid含有start和continue信息,这个信息哪来的呢,看一下hci acl data的格式

蓝牙开发那些事儿(7)——l2cap层连接过程_第1张图片

在handle中有两位pb flag就是用来标志这个事情的。

蓝牙开发那些事儿(7)——l2cap层连接过程_第2张图片

我们看一下btstack中的代码对于pb flag的处理:

在static void acl_handler(uint8_t *packet, int size)函数中

case 0x02: { // first fragment

           

            // sanity check

            if (conn->acl_recombination_pos) {

                log_error( "ACL First Fragment but data in buffer for handle 0x%02x, dropping stale fragments", con_handle);

                conn->acl_recombination_pos = 0;

            }



            // peek into L2CAP packet!

            uint16_t l2cap_length = READ_L2CAP_LENGTH( packet );



            // log_info( "ACL First Fragment: acl_len %u, l2cap_len %u", acl_length, l2cap_length);



            // compare fragment size to L2CAP packet size

            if (acl_length >= (l2cap_length + 4)){

                // forward fragment as L2CAP packet

                hci_emit_acl_packet(packet, acl_length + 4);

            } else {



                if (acl_length > HCI_ACL_BUFFER_SIZE){

                    log_error( "ACL First Fragment to large: fragment %u > buffer size %u for handle 0x%02x",

                        4 + acl_length, 4 + HCI_ACL_BUFFER_SIZE, con_handle);

                    return;

                }



                // store first fragment and tweak acl length for complete package

                (void)memcpy(&conn->acl_recombination_buffer[HCI_INCOMING_PRE_BUFFER_SIZE],

                             packet, acl_length + 4);

                conn->acl_recombination_pos    = acl_length + 4;

                conn->acl_recombination_length = l2cap_length;

                little_endian_store_16(conn->acl_recombination_buffer, HCI_INCOMING_PRE_BUFFER_SIZE + 2, l2cap_length +4);

            }

            break;

           

        }

如果是第一个分包,则红色字体将数据暂存到acl_recombination_buffer中去,然后、  

case 0x01: // continuation fragment

           

            // sanity checks

            if (conn->acl_recombination_pos == 0) {

                log_error( "ACL Cont Fragment but no first fragment for handle 0x%02x", con_handle);

                return;

            }

            if ((conn->acl_recombination_pos + acl_length) > (4 + HCI_ACL_BUFFER_SIZE)){

                log_error( "ACL Cont Fragment to large: combined packet %u > buffer size %u for handle 0x%02x",

                    conn->acl_recombination_pos + acl_length, 4 + HCI_ACL_BUFFER_SIZE, con_handle);

                conn->acl_recombination_pos = 0;

                return;

            }



            // append fragment payload (header already stored)

            (void)memcpy(&conn->acl_recombination_buffer[HCI_INCOMING_PRE_BUFFER_SIZE + conn->acl_recombination_pos],

                         &packet[4], acl_length);

            conn->acl_recombination_pos += acl_length;

           

            // log_error( "ACL Cont Fragment: acl_len %u, combined_len %u, l2cap_len %u", acl_length,

            //        conn->acl_recombination_pos, conn->acl_recombination_length); 

           

            // forward complete L2CAP packet if complete.

            if (conn->acl_recombination_pos >= (conn->acl_recombination_length + 4 + 4)){ // pos already incl. ACL header

                hci_emit_acl_packet(&conn->acl_recombination_buffer[HCI_INCOMING_PRE_BUFFER_SIZE], conn->acl_recombination_pos);

                // reset recombination buffer

                conn->acl_recombination_length = 0;

                conn->acl_recombination_pos = 0;

            }

            break;

如果收到continue fragment,则要比对一下数据总长度对不对,如果不对是要丢弃的,如果对的话,则和第一个包合并起来,并hci_emit_acl_packet提交给上层。

总的来说,l2cap的分包并不复杂,不像tcp协议有可能有好多个包需要合并,而且顺序打乱,l2cap的分包就是连续的两包,如果出问题则直接丢弃的。

L2cap还有一些其他功能,比如上文提到过的流控、重传,不过基本不用,还有QOS管理等等。

但是最重要的两个功能,就是协议复用和分包重组这两项。

下面我们看一下l2cap的逻辑信道是怎么连接的。

要讲连接,首先讲一下l2capcid

蓝牙开发那些事儿(7)——l2cap层连接过程_第3张图片

上图可以看出,如果是有连接的方式,上层l2cap实体间进行数据交互,也就是profile的数据交互需要建立l2cap连接后,互相绑定对方的cid

如果是无连接的方式,则是向fixed channel——2channel发送数据,无连接广播包是一类不需要建立l2cap连接的方式,类似于LEadv包。

笔者曾经实现了无连接的音乐播放,这里面挑战还是蛮大的,因为不像a2dp这种有连接的方式,传输是不可靠的,这里面涉及到底层链路设计,host层丢包处理等等,有机会可以讲一下,不过现在涉及到公司机密不太好公开的。

所谓fixed channel,是指不同于上层profile的动态分配的cid,蓝牙core spec规定了以下fixed channel

蓝牙开发那些事儿(7)——l2cap层连接过程_第4张图片

其实重要的也就是signal channel

因为所有的连接都是始于signal channel的交互。

怎么理解?

看看我们的抓包好了:

蓝牙开发那些事儿(7)——l2cap层连接过程_第5张图片

我们试图建立avdtpl2cap连接,首先我们发送l2cap connect req,这个req就是cid 01上发的,也就是signal id。在这条指令中,我们用psm表示了目前要建立的连接是avdtp,用source cid告诉对方我们这一端的cid0x40,注意这个cid是动态分配的。

蓝牙开发那些事儿(7)——l2cap层连接过程_第6张图片

然后对方成功建立l2cap连接的回复,对方告诉我们,destination cid0x4d,也就是对方给到我们的cid0x4d

然后需要进行一个配置的工作,l2cap可以配置的选项有:

蓝牙开发那些事儿(7)——l2cap层连接过程_第7张图片

这里面最重要的是MTU,最大的包长。一般也就配置这一项就可以了。

比如我们这次就告诉对方我们能支持的最大包长是672字节。

蓝牙开发那些事儿(7)——l2cap层连接过程_第8张图片

这里面进行了几次的交互,直到双方协商一致为止。

看一下btstack的代码,l2cap_signaling_handle_configure_request函数中有这么一段:

if ((option_type == L2CAP_CONFIG_OPTION_TYPE_MAX_TRANSMISSION_UNIT) && (length == 2)){

            channel->remote_mtu = little_endian_read_16(command, pos);

            log_info("Remote MTU %u", channel->remote_mtu);

            if (channel->remote_mtu > l2cap_max_mtu()){

                log_info("Remote MTU %u larger than outgoing buffer, only using MTU = %u", channel->remote_mtu, l2cap_max_mtu());

                channel->remote_mtu = l2cap_max_mtu();

            }

            channelStateVarSetFlag(channel, L2CAP_CHANNEL_STATE_VAR_SEND_CONF_RSP_MTU);

        }

最终会在remote mtu和本地的mtu中找到一个最小值作为最终的mtu的。

配置过程完成后,就可以正式在双方的绑定的cid上通信了。

也就是————更上层的profile开始工作了。

你可能感兴趣的:(蓝牙开发那些事儿)