Bluetooth LE SMP的简单流程

正如SPEC所言,安全管理协议(Security Manager Protocol,SMP )就是用来执行配对和交换link key的。在Bluetooth LE中有专门的L2CAP信道(CID = 0x0006)来传输SMP的协议信息,使得它的通信建立也十分简便,只要LE的connection建立起来,SMP就可以开始执行它的任务。这里针对Bluedroid代码,简单介绍下SMP的执行过程。


1.初始化

btu_task()==>btu_init_core()==>SMP_Init()

在SMP_Init中,它调用了smp_l2cap_if_init(),代码如下:

void smp_l2cap_if_init (void)
{
    tL2CAP_FIXED_CHNL_REG  fixed_reg;
    SMP_TRACE_EVENT0 ("SMDBG l2c smp_l2cap_if_init");
    fixed_reg.fixed_chnl_opts.mode         = L2CAP_FCR_BASIC_MODE;
    fixed_reg.fixed_chnl_opts.max_transmit = 0;
    fixed_reg.fixed_chnl_opts.rtrans_tout  = 0;
    fixed_reg.fixed_chnl_opts.mon_tout     = 0;
    fixed_reg.fixed_chnl_opts.mps          = 0;
    fixed_reg.fixed_chnl_opts.tx_win_sz    = 0;

    fixed_reg.pL2CA_FixedConn_Cb = smp_connect_cback;
    fixed_reg.pL2CA_FixedData_Cb = smp_data_ind;
    fixed_reg.default_idle_tout  = 60;      /* set 60 seconds timeout, 0xffff default idle timeout */

    /* Now, register with L2CAP */
    L2CA_RegisterFixedChannel (L2CAP_SMP_CID, &fixed_reg);
}

这里头初始化了smp的l2cap信道参数,最主要的是这两个:

    fixed_reg.pL2CA_FixedConn_Cb = smp_connect_cback;

这是smp的l2cap通道建立后的回调函数。由于在Bluetooth LE中smp使用固定的CID,因此当LE的connection建立后,smp的l2cap通道也就建立起来,从而进入该函数 。后面会提到它的一个作用,先记住它的角色。

fixed_reg.pL2CA_FixedData_Cb = smp_data_ind;
这是smp的消息处理函数,每当有来自对端的smp数据时,下层l2cap就会把数据交给它处理。

2.触发

smp用于加密并选择性的交换key,如果两个蓝牙设备曾经有过bond过程并交换了key(如Long Term Key,LTK),那么当它们俩再次建立连接时就不需要再进行smp过程,由需要加密的上层应用直接验证之前的key即可。因此,smp过程由事先没有bond过的两个设备的createBond过程开始。

从framework到jni再到Bluetooth的stack层,createBond一层层调用下来,并进入stack层的bond状态机。对于HID设备,它源于hid连接的建立;而对于一般的非HID设备,代码几经周转到了函数BTM_SecBond后,它会执行如下的过程:

#if BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE
    /* LE device, do SMP pairing */
    if (BTM_UseLeLink(bd_addr))
    {
        if (SMP_Pair(bd_addr) == SMP_STARTED)
        {
            btm_cb.pairing_state = BTM_PAIR_STATE_WAIT_AUTH_COMPLETE;
            p_dev_rec->sec_state = BTM_SEC_STATE_AUTHENTICATING;
            return BTM_CMD_STARTED;
        }
        else
            return(BTM_NO_RESOURCES);
    }
#endif
在这里发现本地为LE设备后,他便开始执行SMP_Pair。这是一个LE Create Connection的过程,代码如下:

tSMP_STATUS SMP_Pair (BD_ADDR bd_addr)
{
    tSMP_CB   *p_cb = &smp_cb;
    UINT8     status = SMP_PAIR_INTERNAL_ERR;


    BTM_TRACE_EVENT2 ("SMP_Pair state=%d flag=0x%x ", p_cb->state, p_cb->flags);
    if (p_cb->state != SMP_ST_IDLE || p_cb->flags & SMP_PAIR_FLAGS_WE_STARTED_DD)
    {
        /* pending security on going, reject this one */
        return SMP_BUSY;
    }
    else
    {
        p_cb->flags = SMP_PAIR_FLAGS_WE_STARTED_DD;


        memcpy (p_cb->pairing_bda, bd_addr, BD_ADDR_LEN);


        if (!L2CA_ConnectFixedChnl (L2CAP_SMP_CID, bd_addr))
        {
            SMP_TRACE_ERROR0("SMP_Pair: L2C connect fixed channel failed.");
            smp_sm_event(p_cb, SMP_AUTH_CMPL_EVT, &status);
            return status;
        }


        return SMP_STARTED;
    }
}
上面有几处需要注意的地方,首先是L2CA_ConnectFixedChnl函数,它的任务就是建立LE的connection。另外两处是简单的赋值,也就是memcpy函数将bd_addr赋给了全局变量smp_cb的pairing_bda成员,它决定了连接建立起来后是否真的要进入smp过程。之前提到,当连接建立起来后,会进入smp的处理函数smp_connect_cback中处理,它的代码如下:

static void smp_connect_cback (BD_ADDR bd_addr, BOOLEAN connected, UINT16 reason)
{
    tSMP_CB   *p_cb = &smp_cb;
    tSMP_INT_DATA   int_data;

    SMP_TRACE_EVENT0 ("SMDBG l2c smp_connect_cback ");

    if (memcmp(bd_addr, p_cb->pairing_bda, BD_ADDR_LEN) == 0)
    {
        SMP_TRACE_EVENT3 ("smp_connect_cback()  for pairing BDA: %08x%04x  Event: %s",
                        (bd_addr[0]<<24)+(bd_addr[1]<<16)+(bd_addr[2]<<8) + bd_addr[3],
                        (bd_addr[4]<<8)+bd_addr[5], (connected) ? "connected" : "disconnected");

        if (connected)
        {
            if(!p_cb->connect_initialized)
            {
                p_cb->connect_initialized = TRUE;
                /* initiating connection established */
                p_cb->role = L2CA_GetBleConnRole(bd_addr);

                /* initialize local i/r key to be default keys */
                p_cb->loc_r_key = p_cb->loc_i_key =  SMP_SEC_DEFAULT_KEY;
                p_cb->loc_auth_req = p_cb->peer_auth_req = SMP_DEFAULT_AUTH_REQ;
                p_cb->cb_evt = SMP_IO_CAP_REQ_EVT;
                smp_sm_event(p_cb, SMP_L2CAP_CONN_EVT, NULL);
            }
        }
        else
        {
            int_data.reason = reason;
            /* Disconnected while doing security */
            smp_sm_event(p_cb, SMP_L2CAP_DISCONN_EVT, &int_data);
        }
    }
}
这个函数一上来就进行了一次比对——当前建立连接的对端LE地址与之前SMP_Pair的地址是否相同?这里就可以看到之前在SMP_Pair中的两个赋值的含义了。只有之前在smp_cb中注册过smp的对端地址,LE连接建立起来后才会执行smp过程;否则,smp_connect_cback啥也不会做(之前已经配对过,这回由上层需要加密的应用自己去交换key)。


3.执行

进入smp_connect_cback后,它会调用smp_sm_event来处理具体的任务。实际上,smp处理就是一个smp状态机的执行过程,状态改变后就会进入smp_sm_event,根据当前状态和收到的事件(event)调用相应的处理函数,并设置下一个状态。smp_sm_event的代码如下:

void smp_sm_event(tSMP_CB *p_cb, tSMP_EVENT event, void *p_data)
{
    UINT8           curr_state = p_cb->state;
    tSMP_SM_TBL     state_table;
    UINT8           action, entry, i;
    tSMP_ENTRY_TBL  entry_table =  smp_entry_table[p_cb->role];

    ......
    /* look up the state table for the current state */
    /* lookup entry /w event & curr_state */
    /* If entry is ignore, return.
     * Otherwise, get state table (according to curr_state or all_state) */
    if ((event < SMP_MAX_EVT) && ( (entry = entry_table[event - 1][curr_state]) != SMP_SM_IGNORE ))
    {
        if (entry & SMP_ALL_TBL_MASK)
        {
            entry &= ~SMP_ALL_TBL_MASK;
            state_table = smp_all_table;
        }
        else
            state_table = smp_state_table[curr_state][p_cb->role];
    }
    else
    {
        SMP_TRACE_DEBUG4( "Ignore event [%s (%d)] in state [%s (%d)]",
                          smp_get_event_name(event), event, smp_get_state_name(curr_state), curr_state);
        return;
    }

    /* Get possible next state from state table. */

    smp_set_state(state_table[entry-1][SMP_SME_NEXT_STATE]);

    /* If action is not ignore, clear param, exec action and get next state.
     * The action function may set the Param for cback.
     * Depending on param, call cback or free buffer. */
    /* execute action */
    /* execute action functions */
    for (i = 0; i < SMP_NUM_ACTIONS; i++)
    {
        if ((action = state_table[entry-1][i]) != SMP_SM_NO_ACTION)
        {
            (*smp_sm_action[action])(p_cb, (tSMP_INT_DATA *)p_data);
        }
        else
        {
            break;
        }
    }
    SMP_TRACE_DEBUG1( "result state = %s", smp_get_state_name( p_cb->state ) ) ;
}
这里的处理过程大致是这样的:

1)首先根据本地的role(master/slave)来确定entry_table,不同的role使用的处理map是不同的;

2)根据entry_table、当前的state和收到的event确定entry值,根据当前状态确定state_table。smp有一个总的state_table即smp_state_table,包含idle、pair_req等状态的table,具体该进入那个状态由state_table 决定;而每个状态将根据不同的event有不同的子状态,entry即表示该event的子状态。子状态中包含了需要执行的操作码(一步或两步操作)以及下一个状态的状态码;

3)通过smp_set_state设置下一个状态;

4)根据2)中的state_table和entry值,执行每个操作码对应的处理函数;


在smp的处理过程中,event可能来自对端,如(本地为master)收到pair response;也可以来自smp内部,如本地comfirm value的生成过程。至于smp中具体的状态转换过程,可以打log看下,这里就不详细介绍了。

你可能感兴趣的:(bluedroid)