> 文档中心 > OpenHarmony源码分析之分布式软总线:authmanager模块(2)/设备认证通信管理

OpenHarmony源码分析之分布式软总线:authmanager模块(2)/设备认证通信管理


一、 概述

authmanager模块是鸿蒙为设备提供认证机制的模块。模块内的主要处理过程包括报文的接收、解密、再次封装、加密、发送的步骤。备注:该版本的鸿蒙仅实现了基于WiFi即局域网的设备身份认证机制。
本文重点介绍在设备间建立起socket连接之后,系统是如何处理接收到的新数据。处理过程主要集中在wifi_auth_manager.c文件中。

二、 源码分析

  1. 当有设备发起连接请求时,首先在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");}
  1. 下面开始逐步分析该函数的具体内容,首先通过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;}
  1. 然后接收设备认证过程中传输的数据,在 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;}
  1. 收到数据之后,就开始处理身份认证协议数据包,具体在函数 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;}
  1. 在处理认证数据包过程中,首先在函数 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;}
  1. 然后在函数 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;}

至此,设备间身份认证的通信管理过程结束,至于具体的身份认证协议过程,由于篇幅有限,将在下一篇文章中进行详细叙述。