分布式软总线——再谈wifiEventTrigger(二)

OnDataReceived

这里首先回顾一下OnDataReceived的使用场景:
在这里插入图片描述
在accept接收到一个请求的socket(fd)时调用onDataEvent,内部数据的主要处理函数就是ProcessPackets,而ProcessPackets的两个主要函数一个就是ParsePacketHead用于将char*buf转换为Packet类型的结构体供OnDataReceived使用

然后看向函数onDataReceived()
在这里插入图片描述

这里可拆分程两个部分来看一个是MODULE_AUTH_SDK和其他模式,如果是MODULE_AUTH_SDK模式则调用AuthInterfaceOnDataReceived进行处理,其他的则调用DecryptMessage先将数据进行解密再调用OnModuleMessageReceived进行报文的构建和发送


这里需要清楚的是——OnDataReceived是根据packet中的module的不同来进行不同的数据处理和信息返回的,那么module都有哪些种类呢?
——在auth_conn.h中我们可以看到全部的模式:

在这里插入图片描述
而第一个要讲述的函数就是指定模式为MODULE_AUTH_SDK调用的处理函数

1. AuthInterfaceOnDataReceived

在这里插入图片描述
这里又涉及另一个管理会话的结构体g_authSessionMap——其实质是结构体AuthSession数组:
在这里插入图片描述
每个连接每次会话的信息保存在该结构体中
函数流程:
1. 通过函数AuthSessionMapInit检查g_authSessionMap是否存在,不存在则会初始化
2. 调用AuthGetAuthSessionBySeqId通过序列号从数组中找到对应的auth,如果是新连接的第一次数据则没有该auth则调用AuthGetNewAuthSession在其中添加一个auth
3. switch-case语句当模式为MODULE_AUTH_SDK调用AuthProcessReceivedData进行数据的处理

这里使用switch-case语句是方便扩展

1.1 AuthProcessReceivedData

在这里插入图片描述
函数比较简单:

  1. 当g_hcHandle不存在时调用AuthInitHiChain对g_hcHandle进行初始化;初始化失败调用AuthDelAuthSessionBySessionId删除g_authSessionMap中SessionId对应的内容
  2. 封装request——包含数据和数据大小两项内容
  3. 调用receive_data——这里没有该函数的具体内容,但是可以推测出该函数调用g_hcHandle中封装的处理函数对request进行处理并发送了回复消息

所以代码的重心落在了最开始的初始化AuthInitHiChain

1.2 AuthInitHiChain 处理函数g_hcHandler

在这里插入图片描述
重要的就是hiChainCallback的五个回调函数——其中的AuthConfirmReceiveRequest从函数名可以知道这就是回复请求确认的函数,由于代码的补全这里无法给出g_hcHandle的详细解读,留待读者阅读完善。


然后就是其他模式的通用步骤:主体数据解码——消息封装发送

2. DecryptMessage

先放上代码的注解

/*
函数功能:解密信息(信息分为密文和明文)
函数参数:module:类型;data:报文主体数据;dataLen:数据大小
函数返回:解析过后的cJSON类型的数据
*/
static cJSON *DecryptMessage(int module, const char *data, int dataLen)
{
    if (data == NULL) {
        return NULL;
    }
    //通过传入的参数module判断是否为加密密文,不是则调用解密明文函数返回cJSON类型的指针
    if (!ModuleUseCipherText(module)) {
        return DecryptPlainMessage(data, dataLen);
    }
    //是密文则先判断数据长度是否正确
    if (dataLen < MESSAGE_ENCRYPT_OVER_HEAD_LEN) {
        return NULL;
    }
    //从数据中得到keyIndex
    int index = GetKeyIndex(data, 0, MESSAGE_INDEX_LEN);
    //根据index从SessionKey链表中获取对应的*sKey
    SessionKey *sKey = AuthGetSessionKeyByIndex(index);
    if (sKey == NULL) {
        SOFTBUS_PRINT("[AUTH] DecryptMessage get session key fail\n");
        return NULL;
    }
    //获取数据的真正长度并分配空间
    unsigned int len = dataLen - MESSAGE_ENCRYPT_OVER_HEAD_LEN + 1;
    unsigned char *output = malloc(len);
    if (output == NULL) {
        return NULL;
    }
    if (memset_s((void *)output, len, 0, len) != EOK) {
        free(output);
        output = NULL;
        return NULL;
    }
    //构造密钥结构体
    AesGcmCipherKey cipherKey = {0};
    cipherKey.keybits = GCM_KEY_BITS_LEN_128;
    int ret = memcpy_s(cipherKey.key, SESSION_KEY_LENGTH, sKey->key, AUTH_SESSION_KEY_LEN);
    ret += memcpy_s(cipherKey.iv, IV_LEN, data + MESSAGE_INDEX_LEN, IV_LEN);
    if (ret != 0) {
        free(output);
        output = NULL;
        return NULL;
    }
    //调用aes_gcm.c文件中的解密函数将解析的结果放入output
    ret = DecryptTransData(&cipherKey,
        (unsigned char*)&data[MESSAGE_INDEX_LEN], dataLen - MESSAGE_INDEX_LEN, output, len);
    if (ret <= 0) {
        SOFTBUS_PRINT("[AUTH] DecryptMessage DecryptTransData fail\n");
        free(output);
        output = NULL;
        return NULL;
    }
    //通过cJSON.c中的函数将其封装为cJSON类型的数据并返回
    cJSON *msg = cJSON_Parse((char*)output);
    free(output);
    output = NULL;

    return msg;
}

主要是三个功能部分:解密前的cipherKey的构造——调用DecryptTransData在mbedtls框架下用AES_GCM模式进行解密——调用cJSON_Parse将输出转换为cJSON模式进行返回

这里的代码还是比较好理解的,需要注意三点:

  1. 在代码的开始首先调用了ModuleUseCipherText来判断当前的模式是否为加密模式
    在这里插入图片描述
    从这里我们可以看到几种不需要加密密文的模式:
#define MODULE_TRUST_ENGINE 1
#define MODULE_HICHAIN 2
#define MODULE_HICHAIN_SYNC 4
#define MODULE_AUTH_CHANNEL 8
#define MODULE_AUTH_MSG 9

所以加密模式应用于:

#define MODULE_CONNECTION 5
#define MODULE_SESSION 6
#define MODULE_SMART_COMM 7

如果是不需要加密的模式则调用DecryptPlainMessage将数据复制一遍即可

  1. 第二点就是加密参数的准备得到AesGcmCipherKey:
    在这里插入图片描述
    主要是从g_sessionKeyList中根据GetKeyIndex从data出取出的index确定的sKey存入上述结构体的char key[ ]数组中:
    在这里插入图片描述
    char iv[]数组中存入data + MESSAGE_INDEX_LEN, IV_LEN
    然后就可以将其传入DecryptTransData调用es_gcm.c文件中的解密函数将解析的结果放入output中(AES对称加密)

  2. 最后调用cJSON_Parse——cJSON一种轻量的用于网络传输的结构体,将output转换为该类msg并返回,则一个报文的解密到此结束!


最后是对报文发送客户端的相应代码

3. OnModuleMessageReceived

在这里插入图片描述
代码比较简洁,主要处理两类模式的的消息应答:

  • MODULE_TRUST_ENGINE:
  • MODULE_CONNECTION:正常的TCP连接的通信

3.1 OnMsgOpenChannelReq

在这里插入图片描述
函数流程:
1. 调用MsgGetDeviceIdUnPack从msg中解析出cmd,deviceId,authId存入conn
2. 调用BusGetLocalDeviceInfo获取当前设备的全局存储变量g_deviceInfo
3. 调用MsgGetDeviceIdPack将本地设备的CMD_TAG,deviceId存入reply
4. 调用AuthConnPostMessage进行信息的封装和发送

这里需要注意两点:

  • 调用MsgGetDeviceIdUnPack解析出来的deviceid和MsgGetDeviceIdPack存入reply的deviceId并不一样,一个是报文发送客户端的deviceId,一个是服务端本地存储的设备id也就是本地设备的deviceId,后面存入reply的deviceId是为发送回应报文需要的本地设备deviceId
  • 最后调用的AuthConnPostMessage信息推送函数在下面的OnMessageReceived中也使用到(所以在下面会详细提到),但是传入的模型参数不同,注意区分

3.2 OnMessageReceived

在这里插入图片描述
函数简单的做了两个工作:首先调用cJSON_GetObjectItem从msg中出去CODE对应的值;然后根据CODE的值选择两个不同的Verify函数进行后续处理,一个VerifyIp,另一个VerifyDeviceId

这里需要区分一下这两个函数的区别

3.2.1 OnVerifyIp

在这里插入图片描述
同样可以分为三步走:

  1. MsgVerifyIpUnPack:对于拆封的msg进行验证:更新支持的总线版本(minVersion&maxVersion);比对本地的deviceName和Type与msg中的是否一致;验证msg中是否存在AUTH_PORT和SESSION_PORT;上述的验证都通过才能进行下一步,否则则视为报文发送不正确,直接return
  2. MsgVerifyIpPack:上述验证正确后,需要重新组织发送回应的报文,以cJSON的格式组织,里面包含的内容如下:
报文内容:
CODE:CODE_VERIFY_IP
BUS_MAX_VERSION:connInfo->maxVersion
BUS_MIN_VERSION:connInfo->minVersion
AUTH_PORT:authPort
SESSION_PORT:sessionPort
CONN_CAP:DEVICE_CONN_CAP_WIFI
DEVICE_NAME:devInfo->deviceName
DEVICE_TYPE:DEVICE_TYPE_DEFAULT
DEVICE_ID:devInfo->deviceId
VERSION_TYPE:devInfo->version
  1. AuthConnPostMessage:将构造的报文reply以及模式等信息传入进行后续操作
3.2.2 OnVerifyDeviceId

相较于OnVerifyIp这里的验证和封装就显得简单的多
在这里插入图片描述

  1. GetJsonString:从request中获取deviceId
  2. MsgVerifyDeviceIdPack:封装reply:
    报文的内容:
reply:{
CODE:CODE_VERIFY_DEVID
DEVICE_ID:info->deviceId
}
  1. 调用AuthConnPostMessage进行传输

可以看到与OnVerifyIp相比,封装的信息十分有限,可以看成是它的一个简略版本(deviceId在OnVerifyIp中也有封装)


最后是发送函数

3.2.3 AuthConnPostMessage

在这里插入图片描述
函数实现两个功能:调用cJSON_PrintUnformatted将传入的cJSON格式的msg转换为char*;调用AuthConnPostBytes进行传输

3.2.4 AuthConnPostBytes

在这里插入图片描述
函数实现两个功能:首先是对packet进行打包——进行加密;然后调用AuthConnSend进行传输

3.2.4.1 AuthConnPackBytes
/*
函数功能:完成TCP中packet的打包以及发送
函数参数:module:模式;flags:FLAGS_REPLY;seqNum:报文序列号;str:上层封装的用于传输的信息;bufLen:0
函数返回:-1:发送失败;0:发送成功
*/
unsigned char* AuthConnPackBytes(int module, int flags, long long seqNum, const char *str, int *bufLen)
{
    if ((str == NULL) || (bufLen == NULL)) {
        return NULL;
    }
    bool isCipherText = ModuleUseCipherText(module);//调用上述模块函数,得知是哪种加密手段
    int dataLen = isCipherText ? (strlen(str) + MESSAGE_ENCRYPT_OVER_HEAD_LEN) : strlen(str);//三目运算符,是否为加密信息,是则需要额外的头来存储信息
    int len = dataLen + PACKET_HEAD_SIZE;//长度等于数据长度加头部标识长度
    unsigned char *buf = (unsigned char *)calloc(1, sizeof(unsigned char) * len);
    //在内存的动态存储区中分配num个长度为size的连续空间,函数返回一个指向分配起始地址的指针;如果分配不成功,返回NULL
    if (buf == NULL) {
        return NULL;
    }
    //将所有参数的信息使用安全拷贝,拷贝到data中
    unsigned char *data = buf;
    int identifier = PKG_HEADER_IDENTIFIER;
    unsigned int ret = (unsigned int)memcpy_s(data, sizeof(int), &identifier, sizeof(int));
    data += sizeof(int);
    ret |= (unsigned int)memcpy_s(data, sizeof(int), &module, sizeof(int));
    data += sizeof(int);
    ret |= (unsigned int)memcpy_s(data, sizeof(long long), &seqNum, sizeof(long long));
    data += sizeof(long long);
    ret |= (unsigned int)memcpy_s(data, sizeof(int), &flags, sizeof(int));
    data += sizeof(int);
    ret |= (unsigned int)memcpy_s(data, sizeof(int), &dataLen, sizeof(int));
    data += sizeof(int);
    if (ret != 0) {
        free(buf);
        return NULL;
    }
    //如果是密文
    if (isCipherText) {
        //需要获取密钥并将主体数据进行加密
        if (GetEncryptTransData(seqNum, str, data, dataLen, bufLen) != 0) {
            free(buf);
            return NULL;
        }
        //否则直接拷贝入data即可
    } else {
        if (memcpy_s(data, dataLen, str, dataLen) != EOK) {
            free(buf);
            return NULL;
        }
        *bufLen = len;
    }

    return buf;//返回实际数据
}

函数流程:
1. 调用ModuleUseCipherText判断当前模式是否为需要加密的模式
2. 将传输相关头部信息添加入data中包括:

PKG_HEADER_IDENTIFIER
module
seqNum
flags
dataLen

3. 如果需要加密则调用GetEncryptTransData进行加密(加密的具体步骤不详细讲解,这里需要注意的是在构造密钥结构体中的iv时,采用了randomIv+seqNum的方法),将结果存入data中,不需要加密则直接将data拷贝入data中,最后返回buf

3.2.4.2 AuthConnSend

调用TcpSendData——调用send操作将buf发送到fd对应的客户端
至此数据处理部分全部完结!

4. 数据处理流程图

最后给出整个数据处理流程由于调用十分深,涉及的函数十分庞杂,所以流程图简化了许多,仅供读者参考,更好的理解其中的调用和流程流转
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

国家一级假勤奋研究牲

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值