Apple iap2协议栈在蓝牙中的移植及MFi认证的实现
资料准备:iap2协议栈源码以及MFi Accessory spec规格书
一、iap2协议栈在蓝牙中的移植:
1,rfcomm注册iap2协议栈数据收发回调函数
iap2协议栈和hfp,spp一样都是基于rfcomm实现的,因此需要在rfcomm中单独注册一个数据收发和事件的回调函数,以便iap2协议栈能够通过rfcomm收发数据与iOS通信。注册好之后,配件可以通过接收数据的回调函数解析iOS发过来的数据包,解析完成之后会设置iap2协议栈的状态机。在设置注册好相关rfcomm之后,抓包可以看到sdp到了iap2协议栈的连接,如下:
图中可以看到,rfcomm ch 11已经被注册为iap2协议的channel,至此配件可以通过ch 11与iOS进行通信。
2,iap2协议栈状态机
iap2协议栈最主要的内容就是实现了状态机,状态机能够根据接收到的数据包设置当前状态,并且通过状态机的收发函数自动收发数据,状态机需要在开机之后实时运行以便及时进行MFi认证的数据交互。只有经过MFi数据的认证后,我们的配件(蓝牙)才能够与iOS进行正常的其他数据收发(比如音乐播放控制,APP启动等)。在iap2协议栈连接上iOS之后,需要创建iap2协议栈状态机,创建调用如下:
iap2_manager.bt_iap_ran_loop = iAP2LinkRunLoopCreateAccessory(&iap2_manager.iap2_syn_param, (void *)0,(iAP2LinkSendPacketCB_t)iap_eap_tx_data_handle,(iAP2LinkDataReadyCB_t)iap_eap_rx_data_handle,(void *)0, NULL, 0, 15, nullptr);
在接口参数中,传递了iap2的链路同步载荷,状态机数据收发函数等内容。创建函数返回的指针用于运行iap2协议栈状态机。
其中我们需要比较关注的是链路的同步载荷,因为载荷参数值设置的不对可能会影响MFi认证过程,导致无法通过MFi认证,在MFi spec中链路同步载荷描述如下:
在实际使用过程中需要微调参数以便iOS无报错通过认证,我们设置的一组载荷参数如下:
static const iAP2PacketSYNData_t iap2_default_synParam = { 1, //version5, //maxOutstandingPackets30, //maxRetransmissions3, //maxCumAck650, //maxPacketSize1500, //retransmitTimeout73, //cumAckTimeout2, //numSessionInfo5, //peerMaxOutstandingPackets650, //peerMaxPacketSize{{8,0,1},{11,2,1}}
};
3,iap2会话
iap2的会话是比较重要的内容,根据MFi spec描述,iap2会话主要有控制会话,文件传输会话和EA会话,其中控制会话主要用于前期配件与iOS通信认证过程,而EA会话主要用于正常的用户数据交互,文件传输会话可以用来传输文件,一般用的比较少,区分这一点很重要,因为会话类型不对可能会导致配件无法与iOS进行正确的通信,在spec中如下描述:
当配件正确创建了状态机后,接下去便可以进行MFi的认证了。
二、MFi认证过程
根据MFi accessory spec,MFi认证过程主要分为以下几个步骤:
a>请求认证
配件连接上iOS之后,iOS会下发请求认证命令给配件,第一步是请求认证过程,当配件状态机接收到这个命令数据包之后,配件需要返回从MFi芯片读取到的认证信息给iOS平台,代码举例如下:
if (command == IAP2_RequestAuthenticationCertificate && data[0] == IAP2_SESSION_START_MSB){buffer[0] = 0x40;buffer[1] = 0x40;buffer[2] = 0x02;buffer[3] = 0x6b;buffer[4] = 0xaa;buffer[5] = 0x01;buffer[6] = 0x02;buffer[7] = 0x65;buffer[8] = 0x00;buffer[9] = 0x00;if (mfi_loadCertificateData_cb){ret = mfi_loadCertificateData_cb(&buffer[10], 609);}len = 10 + 609;ret = iAP2LinkQueueSendData(link, buffer, len, session, NULL, link_data_sent_cb);msg.id = TLK_MSG_IAP2_ReqAuthenCertificate;}
根据spec规定,配件从MFi芯片读出X.509证书返回给iOS。
b>认证挑战
iOS接收到正确的认证信息后会请求配件认证挑战,同样配件需要从MFi芯片读出响应数据返回给iOS平台,代码如下:
else if (command == IAP2_RequestAuthenticationChallengeResponse && data[0] == IAP2_SESSION_START_MSB){buffer[0] = 0x40;buffer[1] = 0x40;buffer[2] = 0x00;buffer[3] = 0x4a;buffer[4] = 0xaa;buffer[5] = 0x03;buffer[6] = 0x00;buffer[7] = 0x44;buffer[8] = 0x00;buffer[9] = 0x00;if (mfi_loadChallengeData_cb){mfi_loadChallengeData_cb(&data[10], ((data[6]<<8)|data[7])-4, &buffer[10], 64);}len = 10 + 64;ret = iAP2LinkQueueSendData(link, buffer, len, session, NULL, link_data_sent_cb);msg.id = TLK_MSG_IAP2_ReqAuthenChallengeResp;}
c>ID认证
认证挑战成功之后,iOS会请求ID认证,前面2个步骤可以认为是有人来敲门,确定了是人在敲门而不是其他,这里的ID认证就是确定你是否是我想要见到的人,亮明身份,身份哪里来?这就要求配件需要拿到MFi认证的实验室去过认证,认证过完之后实验室会给出相应的ID数据,这里代码如下所示:
else if (command == IAP2_StartIdentification && data[0] == IAP2_SESSION_START_MSB){buffer[0] = 0x40;buffer[1] = 0x40;buffer[4] = IAP2_IdentificationInformation >> 8;buffer[5] = IAP2_IdentificationInformation & 0xff;pos = 6;if (iap2_manager.iap2_add_item_cb){pos += iap2_manager.iap2_add_item_cb(&buffer[pos]);}buffer[2] = (uint08)(pos>>8);buffer[3] = (uint08)pos;len = pos;ret = iAP2LinkQueueSendData(link, buffer, len, session, NULL, link_data_sent_cb);msg.id = TLK_MSG_IAP2_StartIdentification;}
当正确的ID信息被iOS识别后,iOS会下发0x1d02的指令给到配件:
至此,认证全部完成,后续配件可以通过EA会话与iOS进行其他正常的通信了!