当前位置: 首页 > 工具软件 > AuthManager > 使用案例 >

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

滑弘扬
2023-12-01

一、 概述

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;
}

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

 类似资料: