OpenHarmony源码分析之分布式软总线:trans_service模块(5)/TCP会话管理
一、概述
trans_service模块基于系统内核提供的socket通信,向authmanager模块提供设备认证通道管理和设备认证数据的传输;向业务模块提供session管理和基于session的数据收发功能,并且通过GCM模块的加密功能提供收发报文的加解密保护。
本文将继续介绍鸿蒙系统的会话机制的管理,承接上文OpenHarmony源码分析之分布式软总线:trans_service模块(4)/TCP会话管理的内容,本文将介绍鸿蒙系统如何处理客户端发起的请求消息。
二、源码分析
- 在上文提到的OnProcessDataAvailable() 函数中,首先判断该会话的名称是不是"softbus_Lite_unknown",若是,则表示该会话是一个新建的会话,收到的消息应是客户端发起的请求消息,因此接下来调用HandleRequestMsg() 函数进行处理,具体函数分析如下:
/*函数功能:处理会话中的请求消息函数参数: session:会话地址函数返回值: 成功:返回true 失败:返回false详细:解析认证数据包头部*/static bool HandleRequestMsg(TcpSession *session){ char data[RECIVED_BUFF_SIZE] = { 0 }; //读取接收缓冲区的数据保存到data数组中 int size = TcpRecvData(session->fd, data, AUTH_PACKET_HEAD_SIZE, 0);if (size != AUTH_PACKET_HEAD_SIZE) {//如果读到的数据小于认证数据包头部长度,返回错误return false;} int identifier = GetIntFromBuf(data, 0);//解析identifier字段 if ((unsigned int)identifier != PKG_HEADER_IDENTIFIER) { //如果头部标识符不是PKG_HEADER_IDENTIFIER,则返回false return false; } //解析dataLen字段int dataLen = GetIntFromBuf(data, AUTH_PACKET_HEAD_SIZE - sizeof(int));if (dataLen + AUTH_PACKET_HEAD_SIZE >= RECIVED_BUFF_SIZE) {//越界return false;}int total = size;//记录读到的总量int remain = dataLen;//剩余未读while (remain > 0) {//循环读取套接字接收缓冲区,直到读满dataLen字节size = TcpRecvData(session->fd, data + total, remain, 0);remain -= size;total += size;}//将第一个数据包解密后转换为cjson格式的结构体 cJSON *receiveObj = TransFirstPkg2Json(data, dataLen + AUTH_PACKET_HEAD_SIZE); if (receiveObj == NULL) { return false; } //将接收到的json对象的值赋给当前会话,如会话名字sessionName以及会话密钥sessionKey int ret = AssignValue2Session(session, receiveObj); cJSON_Delete(receiveObj); if (ret != true) { return false; } SessionListenerMap *sessionListener = GetSessionListenerByName(session->sessionName, strlen(session->sessionName));//根据会话名字在会话管理器中查找会话监听者 if (sessionListener == NULL) { return false; } if (!ResponseToClient(session)) {//响应回复客户端的请求消息 SOFTBUS_PRINT("[TRANS] HandleRequestMsg ResponseToClient fail\n"); return false; } if (sessionListener->listener == NULL) { return false; } if (sessionListener->listener->onSessionOpened == NULL) { return false; } if (sessionListener->listener->onSessionOpened(session->fd) != 0) { return false; } return true;}
- 接着看函数HandleRequestMsg() 的具体内容,首先接收数据包头部并解析,然后接收数据包负载部分,得到负载之后,在TransFirstPkg2Json() 函数中将该负载解密然后转换为cjson格式的结构体。具体实现及分析如下:
/*函数功能:将第一个数据包解密后转换为cjson格式的结构体函数参数: buffer:数据包起始地址 bufferSize:数据包总大小函数返回值: 成功:返回cJSON格式的结构体 失败:返回NULL详细:*/static cJSON *TransFirstPkg2Json(const char *buffer, int bufferSize){ if (bufferSize < AUTH_PACKET_HEAD_SIZE) {//数据包大小小于头部长度 SOFTBUS_PRINT("[TRANS] bufferSize < AUTH_PACKET_HEAD_SIZE\n"); return NULL; } int offset = AUTH_PACKET_HEAD_SIZE - sizeof(int);//为了解析获取dataLen字段 int dataLen = GetIntFromBuf(buffer, offset) - SESSION_KEY_INDEX_SIZE;//除去会话密钥索引的数据长度 if (dataLen <= 0 || dataLen > (RECIVED_BUFF_SIZE - AUTH_PACKET_HEAD_SIZE)) { //越界检查 return NULL; }//获取数据包负载中的会话密钥索引(index) int index = GetKeyIndex(buffer, AUTH_PACKET_HEAD_SIZE, MESSAGE_INDEX_LEN); //通过索引(index)获取会话密钥 SessionKey *sessionKey = AuthGetSessionKeyByIndex(index); if (sessionKey == NULL) { SOFTBUS_PRINT("[TRANS] TransFirstPkg2Json GetSessionKey fail\n"); return NULL; } char *firstDataJson = calloc(1, dataLen);//申请dataLen字节数的内存空间 if (firstDataJson == NULL) { return NULL; } unsigned char* cipherTxt = (unsigned char*)(buffer + AUTH_PACKET_HEAD_SIZE + SESSION_KEY_INDEX_SIZE);//得到密文的首地址 AesGcmCipherKey cipherKey = {0};//aes_gcm密钥 cipherKey.keybits = GCM_KEY_BITS_LEN_128;//密钥长度为128位 int ret = memcpy_s(cipherKey.key, SESSION_KEY_LENGTH, sessionKey->key, AUTH_SESSION_KEY_LEN);//将会话密钥赋值给加密密钥 //将密文的前IV_LEN个字节的值作为aes_gcm加密算法的初始iv值 ret += memcpy_s(cipherKey.iv, IV_LEN, cipherTxt, IV_LEN); if (ret != 0) { free(firstDataJson); return NULL; }//解密传输数据,解密成功则返回实际明文长度,并将明文存储在firstDataJson空间中 int plainLen = DecryptTransData(&cipherKey, cipherTxt, dataLen, (unsigned char*)firstDataJson, dataLen); if (plainLen <= 0) { free(firstDataJson); return NULL; }//json格式解析,将json字符串转换为cjson结构体 cJSON *receiveObj = cJSON_Parse(firstDataJson); free(firstDataJson); return receiveObj;}
- 然后将接收到的json对象的值赋给当前会话,如会话名字sessionName以及会话密钥sessionKey,具体在AssignValue2Session() 函数中实现,分析如下:
/*函数功能:为当前会话赋值sessionName和sessionKey,其值来自于对端发送的json数据中函数参数: session:TCP会话结构体 receiveObj:cJSON结构体对象函数返回值: 成功:返回cJSON格式的结构体 失败:返回NULL详细:*/static bool AssignValue2Session(TcpSession *session, cJSON *receiveObj){ if (receiveObj == NULL) { return false; }//获取"BUS_NAME"(key)字段的String类型的值(value) char *recvBus = GetJsonString(receiveObj, "BUS_NAME"); if (recvBus == NULL) { return false; } if (strncpy_s(session->sessionName, NAME_LENGTH, recvBus, strlen(recvBus)) != 0) { //将获取到的总线名BUS_NAME作为当前会话名sessionName return false; }//获取"SESSION_KEY"(key)字段的String类型的值(value) char *sessionKeyEncoded = GetJsonString(receiveObj, "SESSION_KEY"); if (sessionKeyEncoded == NULL) { return false; } size_t olen = 0; //将base64编码的数据解码,然后赋值给当前的会话密钥session->sessionKey int ret = mbedtls_base64_decode((unsigned char *)session->sessionKey, SESSION_KEY_LENGTH, &olen, (unsigned char *)sessionKeyEncoded, strlen(sessionKeyEncoded)); if (ret != 0) { SOFTBUS_PRINT("[TRANS] AssignValue2Session mbedtls_base64_decode error: %d\n", ret); return false; } SOFTBUS_PRINT("[TRANS] AssignValue2Session busname=%s, fd=%d\n", session->sessionName, session->fd); return true;}
- 最后回复给客户端,在ResponseToClient() 函数中实现,具体分析如下:
/*函数功能:响应回复客户端的请求消息函数参数: session:TCP会话结构体函数返回值: 成功:返回true 失败:返回false详细:*/static bool ResponseToClient(TcpSession *session){ cJSON *jsonObj = cJSON_CreateObject();//产生一个cJSON对象 if (jsonObj == NULL) { return false; } GetReplyMsg(jsonObj, session);//构造回复消息,初始化cJSON对象 char *msg = cJSON_PrintUnformatted(jsonObj);//将cJSON对象输出为无格式的字符串 if (msg == NULL) { cJSON_Delete(jsonObj); return false; } int bufLen = 0; //按字节构造认证协议数据包,返回数据包缓冲区首地址 unsigned char *buf = PackBytes(msg, &bufLen); if (buf == NULL) { SOFTBUS_PRINT("[TRANS] ResponseToClient PackBytes fail\n"); free(msg); cJSON_Delete(jsonObj); return false; } int dataLen = TcpSendData(session->fd, (char*)buf, bufLen, 0);//发送数据给对端 free(msg); cJSON_Delete(jsonObj); free(buf); if (dataLen <= 0) { SOFTBUS_PRINT("[TRANS] ResponseToClient TcpSendData fail\n"); return false; } return true;}
其中构造回复消息的函数为GetReplyMsg(),分析如下:
/*函数功能:构造回复消息,初始化cJSON对象函数参数: jsonObj:cJSON对象 session:当前TCP会话函数返回值:无详细:*/static void GetReplyMsg(cJSON *jsonObj, const TcpSession *session){//向该cJSON对象中添加数值类型的"CODE"字段,值为1 cJSON_AddNumberToObject(jsonObj, "CODE", 1); //向该cJSON对象中添加数值类型的"API_VERSION"字段,值为DEFAULT_API_VERSION cJSON_AddNumberToObject(jsonObj, "API_VERSION", DEFAULT_API_VERSION); DeviceInfo *local = BusGetLocalDeviceInfo();//获取本地设备信息 if (local == NULL) { return; } char* deviceId = local->deviceId; //向该cJSON对象中添加数值类型的"DEVICE_ID"字段,值为deviceId cJSON_AddStringToObject(jsonObj, "DEVICE_ID", deviceId); //向该cJSON对象中添加数值类型的"UID"字段,值为-1 cJSON_AddNumberToObject(jsonObj, "UID", -1); //向该cJSON对象中添加数值类型的"PID"字段,值为-1 cJSON_AddNumberToObject(jsonObj, "PID", -1); cJSON_AddStringToObject(jsonObj, "PKG_NAME", session->sessionName); cJSON_AddStringToObject(jsonObj, "CLIENT_BUS_NAME", session->sessionName); cJSON_AddStringToObject(jsonObj, "AUTH_STATE", "");//认证状态为空 cJSON_AddNumberToObject(jsonObj, "CHANNEL_TYPE", 1);//CHANNEL_TYPE为1}
至此,对于新会话的客户端请求数据的处理以及响应回复过程已结束。