正如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就会把数据交给它处理。
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看下,这里就不详细介绍了。