一、 概述
authmanager模块是鸿蒙为设备提供认证机制的模块。模块内的主要处理过程包括报文的接收、解密、再次封装、加密、发送的步骤。备注:该版本的鸿蒙仅实现了基于WiFi即局域网的设备身份认证机制。 本文重点介绍在设备间建立起socket连接之后,系统是如何处理接收到的新数据。处理过程主要集中在wifi_auth_manager.c文件中。
二、 源码分析
- 当有设备发起连接请求时,首先在trans_service模块建立socket连接,建立连接之后,若有设备发送认证请求的数据,将在函数ProcessDataEvent()中进行处理,对该函数的阅读理解如下:
scss
/*
函数功能:处理设备间通信(新数据到达)事件
函数参数: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;
int rc = AuthConnRecv(fd, buf, used, size - used, 0);//接收设备认证过程中传输的数据,预读取size-used大小
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()中实现:
ini
/*
函数功能:查找该连接的套接字fd是否已在设备链表中
函数参数:
fd:建立连接的套接字fd
函数返回值:
若该设备已存在则返回设备连接信息,若不存在则返回NULL
详细:
*/
static AuthConn* FindAuthConnByFd(int fd)
{
if (g_fdMap == NULL) {
return NULL;
}
AuthConnNode *node = NULL;
List *pos = NULL;
List *tmp = NULL;
LIST_FOR_EACH_SAFE(pos, tmp, g_fdMap) {//遍历g_fdMap链表,该链表只存储认证连接设备节点地址
node = (AuthConnNode*)pos;
if (node->aconn == NULL) {
continue;
}
if (node->aconn->fd == fd) {//若该设备已存在,则返回该设备的连接信息
return node->aconn;
}
}
return NULL;
}
- 然后接收设备认证过程中传输的数据,在AuthConnRecv()中实现:
arduino
/*
函数功能:接收设备认证过程中传输的数据
函数参数:
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()中实现:
arduino
/*
函数功能:处理身份认证协议数据包
函数参数: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 += PACKET_HEAD_SIZE;//将processed偏移一个数据包头部长度,继续处理后面的数据,即更新已处理数据量
OnDataReceived(conn, pkt, buf + processed);//解析数据负载部分
processed += len;//将processed偏移一个数据包负载部分的长度,继续处理后面的数据,即更新已处理数据量
free(pkt);
pkt = NULL;
}
return processed;
}
DD一下: 欢迎大家关注公众号<程序猿百晓生>,可以了解到一下知识点。
erlang
1.OpenHarmony开发基础
2.OpenHarmony北向开发环境搭建
3.鸿蒙南向开发环境的搭建
4.鸿蒙生态应用开发白皮书V2.0 & V3.0
5.鸿蒙开发面试真题(含参考答案)
6.TypeScript入门学习手册
7.OpenHarmony 经典面试题(含参考答案)
8.OpenHarmony设备开发入门【最新版】
9.沉浸式剖析OpenHarmony源代码
10.系统定制指南
11.【OpenHarmony】Uboot 驱动加载流程
12.OpenHarmony构建系统--GN与子系统、部件、模块详解
13.ohos开机init启动流程
14.鸿蒙版性能优化指南
.......
- 在处理认证数据包过程中,首先在函数ParsePacketHead()中解析数据包头部:
arduino
/*
函数功能:解析身份认证协议数据包头部,头部长度为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);//获取标识符字段
if (identifier != PKG_HEADER_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()中解析数据包负载部分:
rust
/*
函数功能:处理接收到的认证协议数据包数据负载部分
函数参数: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;
}
至此,设备间身份认证的通信管理过程结束,至于具体的身份认证协议过程,由于篇幅有限,将在下一篇文章中进行详细叙述。