OpenHarmony源码分析之分布式软总线:authmanager模块(2)/设备认证通信管理
一、 概述
authmanager模块是鸿蒙为设备提供认证机制的模块。模块内的主要处理过程包括报文的接收、解密、再次封装、加密、发送的步骤。备注:该版本的鸿蒙仅实现了基于WiFi即局域网的设备身份认证机制。
本文重点介绍在设备间建立起socket连接之后,系统是如何处理接收到的新数据。处理过程主要集中在wifi_auth_manager.c文件中。
二、 源码分析
- 当有设备发起连接请求时,首先在trans_service模块建立socket连接,建立连接之后,若有设备发送认证请求的数据,将在函数 ProcessDataEvent() 中进行处理,对该函数的阅读理解如下:
/*函数功能:处理设备间通信(新数据到达)事件函数参数: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查找认证连接链表中是否已存在该设备,若不存在则返回认证连接失败,因为之前在处理新连接时会将该设备加入设备认证连接链表中,具体在函数 FindAuthConnByFd() 中实现:
/*函数功能:查找该连接的套接字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;}
- 然后接收设备认证过程中传输的数据,在 AuthConnRecv() 中实现:
/*函数功能:接收设备认证过程中传输的数据函数参数: 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;}
- 收到数据之后,就开始处理身份认证协议数据包,具体在函数 ProcessPackets() 中实现:
/*函数功能:处理身份认证协议数据包函数参数: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;}
- 在处理认证数据包过程中,首先在函数 ParsePacketHead() 中解析数据包头部:
/*函数功能:解析身份认证协议数据包头部,头部长度为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;}
- 然后在函数 OnDataReceived() 中解析数据包负载部分:
/*函数功能:处理接收到的认证协议数据包数据负载部分函数参数: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;}
至此,设备间身份认证的通信管理过程结束,至于具体的身份认证协议过程,由于篇幅有限,将在下一篇文章中进行详细叙述。