authmanager模块是鸿蒙为设备提供认证机制的模块。模块内的主要处理过程包括报文的接收、解密、再次封装、加密、发送的步骤。备注:该版本的鸿蒙仅实现了基于WiFi即局域网的设备身份认证机制。
本文重点介绍在设备间建立起socket连接之后,系统是如何处理接收到的新数据。处理过程主要集中在wifi_auth_manager.c文件中。
/*
函数功能:处理设备间通信(新数据到达)事件
函数参数:fd 用于通信的套接字fd
函数返回值:无
详细:
*/
void ProcessDataEvent(int fd)
{
SOFTBUS_PRINT("[AUTH] ProcessDataEvent fd = %d\n", fd);
AuthConn *conn = FindAuthConnByFd(fd);//通过fd查找认证连接链表中是否已存在该设备
if (conn == NULL) {
SOFTBUS_PRINT("ProcessDataEvent get authConn fail\n");
return;
}
//已存在,则进行下一步
if (conn->db.buf == NULL) {//如果是第一次通信,则初始化数据缓冲区
//申请用于设备间通信的数据缓冲区内存
conn->db.buf = (char *)malloc(DEFAULT_BUF_SIZE);
if (conn->db.buf == NULL) {
return;
}
//清空数据缓冲区
(void)memset_s(conn->db.buf, DEFAULT_BUF_SIZE, 0, DEFAULT_BUF_SIZE);
conn->db.size = DEFAULT_BUF_SIZE;//默认缓冲区大小1536
conn->db.used = 0;//缓冲区已使用量为0
}
//用局部变量保存数据缓冲区信息,因为参数通过地址传递,防止改变原有地址空间内容
DataBuffer *db = &conn->db;
char *buf = db->buf;
int used = db->used;
int size = db->size;
//接收设备认证过程中传输的数据,预读取size-used大小
int rc = AuthConnRecv(fd, buf, used, size - used, 0);
if (rc == 0) {//若没有数据,则返回
return;
} else if (rc < 0) {
CloseConn(conn);
return;
}
used += rc;//更新缓冲区已使用空间
int processed = ProcessPackets(conn, buf, size, used);//处理身份认证协议数据包
if (processed > 0) {
used -= processed;//处理完的数据应从缓冲区移除
if (used != 0) {
//将缓冲区中已占用的部分拷贝到缓冲区起始处
if (memmove_s(buf, processed, buf, used) != EOK) {
CloseConn(conn);
return;
}
}
} else if (processed < 0) {
CloseConn(conn);
return;
}
db->used = used;
SOFTBUS_PRINT("[AUTH] ProcessDataEvent ok\n");
}
/*
函数功能:查找该连接的套接字fd是否已在设备链表中
函数参数:
fd:建立连接的套接字fd
函数返回值:
若该设备已存在则返回设备连接信息,若不存在则返回NULL
详细:
*/
static AuthConn* FindAuthConnByFd(int fd)
{
if (g_fdMap == NULL) {
return NULL;
}
AuthConnNode *node = NULL;
List *pos = NULL;
List *tmp = NULL;
//遍历g_fdMap链表,该链表只存储认证连接设备节点地址
LIST_FOR_EACH_SAFE(pos, tmp, g_fdMap) {
node = (AuthConnNode*)pos;
if (node->aconn == NULL) {
continue;
}
if (node->aconn->fd == fd) {//若该设备已存在,则返回该设备的连接信息
return node->aconn;
}
}
return NULL;
}
/*
函数功能:接收设备认证过程中传输的数据
函数参数:
fd:用于TCP通信的套接字fd
buf:数据缓冲区首地址
offset:缓冲区数据偏移
count:预读取的数据量
timeout:超时时间
函数返回值:
成功:返回收到的数据大小
失败:返回-1
详细:
*/
int AuthConnRecv(int fd, char *buf, int offset, int count, int timeout)
{
//健壮性检查,越界检查
if ((buf == NULL) || (offset < 0) || (count <= 0) || (offset + count <= 0)) {
return -1;
}
//传入数据偏移后的地址,用于存储新收到的数据
return TcpRecvData(fd, buf + offset, count, timeout);
}
/*
函数功能:接收TCP连接的通信数据,保存到buf缓冲区中
函数参数:
fd:用于TCP通信的套接字fd;
buf:用于保存接收数据的数据缓冲区地址;
len:预读取数据长度;
timeout:超时时间
函数返回值:返回收到的数据字节数
*/
int TcpRecvData(int fd, char *buf, int len, int timeout)
{
return TcpRecvMessages(fd, buf, len, timeout, 0);//相当于read函数
}
/*
函数功能:接收TCP连接的通信数据
函数参数:
fd:用于TCP通信的套接字fd;
buf:用于保存接收数据的数据缓冲区地址;
len:预读取数据长度;
timeout:超时时间;
flags:用于recv函数的接收数据模式参数
函数返回值:
成功:返回recv函数实际读到的数据字节数
失败:返回-1
*/
static int32_t TcpRecvMessages(int fd, char *buf, uint32_t len, int timeout, int flags)
{
if (fd < 0 || buf == NULL || len == 0 || timeout < 0) {//健壮性检查
return -1;
}
errno = 0;
int32_t rc = recv(fd, buf, len, flags);//接收对端发送的数据
if ((rc == -1) && (errno == EAGAIN)) {//表示当前缓冲区没有数据可读
rc = 0;
} else if (rc <= 0) {//返回0表明对端已关闭连接
rc = -1;
}
return rc;
}
/*
函数功能:处理身份认证协议数据包
函数参数:conn——认证连接信息结构体;buf——数据缓冲区地址;size——数据缓冲区总大小;
used——数据缓冲区已使用量
函数返回值:返回已成功处理完的数据量
详细:循环解析收到的每一个数据包,先解析其头部,再解析其数据负载部分。
*/
static int ProcessPackets(AuthConn *conn, const char *buf, int size, int used)
{
int processed = 0;
while (processed + PACKET_HEAD_SIZE < used) {//循环解析数据包头部
//解析包头部
Packet *pkt = ParsePacketHead(buf, processed, used - processed, size);
if (pkt == NULL) {
SOFTBUS_PRINT("[AUTH] ProcessPackets ParsePacketHead fail\n");
return -1;
}
int len = pkt->dataLen;//获取数据负载部分的长度
//如果产生越界,跳出循环
if ((len > PACKET_DATA_SIZE) || (processed + PACKET_HEAD_SIZE + len > used)) {
free(pkt);
pkt = NULL;
break;
}
//将processed偏移一个数据包头部长度,继续处理后面的数据,即更新已处理数据量
processed += PACKET_HEAD_SIZE;
OnDataReceived(conn, pkt, buf + processed);//解析数据负载部分
//将processed偏移一个数据包负载部分的长度,继续处理后面的数据,即更新已处理数据量
processed += len;
free(pkt);
pkt = NULL;
}
return processed;
}
/*
函数功能:解析身份认证协议数据包头部,头部长度为24字节,共五个字段
函数参数:buf——接收数据缓冲区首地址;offset——认证协议各字段偏移量;len——待处理的数据长度;
size——接收数据缓冲区大小
函数返回值:解析成功则返回包含数据包头部信息的结构体地址
详细:
*/
static Packet *ParsePacketHead(const char *buf, int offset, int len, int size)
{
if ((buf == NULL) || (offset < 0) || (len < PACKET_HEAD_SIZE) ||
(offset + len <= 0) || (offset + len > size)) {
return NULL;
}
unsigned int identifier = GetIntFromBuf(buf, offset);//获取标识符字段
//如果标识符不是PKG_HEADER_IDENTIFIER,幻数,则输出错误
if (identifier != PKG_HEADER_IDENTIFIER) {
SOFTBUS_PRINT("[AUTH] ParsePacketHead invalid magic number\n");
return NULL;
}
offset += DEFAULT_INT_LEN;
int module = GetIntFromBuf(buf, offset);//获取module字段
offset += DEFAULT_INT_LEN;
long long seq = 0;
if (GetLongFromBuf(buf, offset, &seq) != 0) {//获取seq字段
return NULL;
}
offset += DEFAULT_LONG_LEN;
int flags = GetIntFromBuf(buf, offset);//获取flags字段
offset += DEFAULT_INT_LEN;
int dataLen = GetIntFromBuf(buf, offset);//获取dataLen字段
SOFTBUS_PRINT("[AUTH] ParsePacketHead module=%d, seq=%lld, flags=%d, datalen=%d\n", module, seq, flags, dataLen);
if (module < 0 || flags < 0 || dataLen < 0) {
return NULL;
}
Packet *packet = (Packet *)malloc(sizeof(Packet));//申请认证协议数据包头部所需空间
if (packet == NULL) {
return NULL;
}
//将解析出来的数据包头部字段保存在packet结构体中
packet->module = module;
packet->seq = seq;
packet->flags = flags;
packet->dataLen = dataLen;
return packet;
}
/*
函数功能:处理接收到的认证协议数据包数据负载部分
函数参数:conn——认证设备信息结构体;pkt——认证协议数据包头部结构体的地址;
data——数据负载部分的起始地址
函数返回值:无
详细:
*/
static void OnDataReceived(AuthConn *conn, const Packet *pkt, const char *data)
{
SOFTBUS_PRINT("[AUTH] OnDataReceived\n");
if ((pkt->module > MODULE_HICHAIN) && (pkt->module <= MODULE_AUTH_SDK)) {//如果module字段为MODULE_AUTH_SDK,则调用AuthInterfaceOnDataReceived继续进行处理。这里没有直接用==,是为了可扩展性
AuthInterfaceOnDataReceived(conn, pkt->module, pkt->seq, data, pkt->dataLen);//若数据包类型为MODULE_AUTH_SDK,表示对端请求创建设备身份认证环境,即该设备未进行身份认证
return;
}
//如果类型不在MODULE_HICHAIN与MODULE_AUTH_SDK之间,则进行解密处理
cJSON *msg = DecryptMessage(pkt->module, data, pkt->dataLen);//解密消息,返回cJSON格式的数据
if (msg == NULL) {
SOFTBUS_PRINT("[AUTH] OnDataReceived DecryptMessage fail\n");
return;
}
OnModuleMessageReceived(conn, pkt->module, pkt->flags, pkt->seq, msg);//根据数据包类型字段module,对接收到的cJSON数据选择不同的处理方式
cJSON_Delete(msg);
msg = NULL;
}
至此,设备间身份认证的通信管理过程结束,至于具体的身份认证协议过程,由于篇幅有限,将在下一篇文章中进行详细叙述。