前言
文件消息在业务层经过组装成帧了以后,这里分析的文件消息是指文件数据发送完后会发送文件列表到对端进行完整性核对,完整的帧会发送到传输层,进行分片、打包、加密
第一步:确认是否支持TLV打包格式以及是否需要ACK确认
函数签名
int32_t GetSupportTlvAndNeedAckById(
int32_t channelId, int32_t channelType,
bool *supportTlv, bool *needAck)
| 参数 | 含义 |
|---|---|
channelId |
通道 ID |
channelType |
通道类型(代理通道/直连通道等) |
supportTlv |
输出参数:是否支持 TLV 格式(可为 NULL,表示不关心) |
needAck |
输出参数:是否需要 ACK 确认(可为 NULL,表示不关心)(可靠传输) |
supportTlv和needAck是在通道建立时协商 的------双方在握手过程中交换能力信息,决定是否使用 TLV 新格式、是否启用可靠传输(ACK),然后存入 SessionInfo 中。这个函数只是读取协商结果。
第二步:打包加密
分支一:传统打包
流程图

1.获取加密所需信息
从通道获取加密所需的信息
int32_t seq = info->sequence;
char *sessionKey = info->sessionKey;
2.计算输出长度并分配缓冲区
outlen:输出长度 = 原始帧序列长度 + 加密开销 + 协议头大小
加密开销(OVERHEAD_LEN)= AES-GCM 的额外开销 = IV(12B) + Tag(16B) = 28B
协议头大小: sizeof(PacketHead) = 分片个数 * (int32)
dataInfo->outLen = dataInfo->inLen + OVERHEAD_LEN + sizeof(PacketHead);
dataInfo->outData = (uint8_t *)SoftBusCalloc(dataInfo->outLen);
3.准备加密密钥
cipherKey.keyLen = SESSION_KEY_LENGTH;
if (memcpy_s(cipherKey.key, SESSION_KEY_LENGTH, sessionKey, SESSION_KEY_LENGTH) != EOK) {
TRANS_LOGE(TRANS_SDK, "memcpy key error.");
SoftBusFree(dataInfo->outData);
return SOFTBUS_MEM_ERR;
}
将会话密钥拷贝到加密结构体中,供AES-GCM使用。
4.加密
char *outData = (char *)dataInfo->outData + sizeof(PacketHead);
int32_t ret = SoftBusEncryptDataWithSeq(&cipherKey,
dataInfo->inData, dataInfo->inLen, // 明文输入
(unsigned char *)outData, &outLen, // 密文输出(写在 PacketHead 之后)
seq); // 序列号参与加密
输出密文写在缓冲区 +sizeof(PacketHead) 的位置,前面留出 16 字节给协议头。
SoftBusEncryptDataWithSeq 用序列号参与加密,使得即使相同明文 + 相同密钥,不同序列号产生的密文也不同,防止重放攻击。
5.加密后立即清零密钥
安全措施,防止残存栈中
(void)memset_s(cipherKey.key, SESSION_KEY_LENGTH, 0, SESSION_KEY_LENGTH);
6.校验加密结果
校验结果必须精确等于inLen + OVERHEAD_LEN(28字节开销),否则加密出错
if (ret != SOFTBUS_OK || outLen != dataInfo->inLen + OVERHEAD_LEN) {
SoftBusFree(dataInfo->outData);
return SOFTBUS_TRANS_PROXY_SESS_ENCRYPT_ERR;
}
7.填充协议头PacketHead
将协议头填充到前面的16字节,然后转换成网络字节序(大端字节序)
PacketHead *pktHead = (PacketHead *)dataInfo->outData;
pktHead->magicNumber = MAGIC_NUMBER; // 0xBABEFACE
pktHead->seq = seq; // 序列号
pktHead->flags = flag; // 帧类型
pktHead->dataLen = outLen - sizeof(PacketHead); // 密文数据长度
ClientPackPacketHead(pktHead); // 转网络字节序
分支二:TLV打包
函数签名
/foundation/communication/dsoftbus/sdk/transmission/trans_channel/proxy/src/client_trans_proxy_manager.c
static int32_t ClientTransProxyPackTlvBytes(
int32_t channelId, ClientProxyDataInfo *dataInfo,
ProxyChannelInfoDetail *info, uint32_t dataSeq, SessionPktType flag)
参数和传统打包一致,只是内部走TLV格式。
TLV介绍
简单来说是Type-Length-Value
TLV 就是一种"自带说明书"的编码方式------每个字段前面标注"我是什么(Type)"和"我有多长(Length)",接收端不需要预先知道数据格式就能解析,遇到不认识的字段跳过即可,天然支持扩展和兼容
传统的打包包头是固定结构体
struct PacketHead {
int32_t magicNumber; // 偏移0,固定4字节
int32_t seq; // 偏移4,固定4字节
int32_t flags; // 偏移8,固定4字节
int32_t dataLen; // 偏移12,固定4字节
};
TLV是可变的结构体

在包头的结构体中每一个项都是上面那样的结构,根据type识别字段,不认识就跳过
与固定结构体的区别--可扩展
[Type=0][Len=4][seq的值]
[Type=2][Len=4][flags的值]
[Type=4][Len=4][dataLen的值]
[Type=3][Len=1][needAck的值] ← 新增字段!
[Type=1][Len=4][dataSeq的值] ← 新增字段!
1.准备参数
tlv支持加入ack确认
int32_t seq = info->sequence;
char *sessionKey = info->sessionKey;
uint32_t dataLen = dataInfo->inLen + OVERHEAD_LEN; // 加密后的数据长度
bool needAck = false;
GetSupportTlvAndNeedAckById(channelId, CHANNEL_TYPE_PROXY, NULL, &needAck);
2.构建tlv数据头
DataHead pktHead;
int32_t tlvBufferSize = 0;
ProxyBuildTlvDataHead(&pktHead, seq, flag, dataLen, &tlvBufferSize);
ProxyBuildNeedAckTlvData(&pktHead, needAck, dataSeq, &tlvBufferSize);
这两步把各个字段组装成 TLV 元素:
ProxyBuildTlvDataHead:构建基础字段(seq、flag、dataLen)ProxyBuildNeedAckTlvData:构建扩展字段(needAck、dataSeq)
3.序列化tlv头部
dataInfo->outData = TransProxyPackTlvData(&pktHead, tlvBufferSize, dataLen);
把datahead结构(内存中的tlv元素列表)序列化为连续的字节流,输出缓冲区布局为:
[magicNum(4B)][tlvCount(1B)][TLV元素1][TLV元素2]...[预留加密数据空间]
4.计算输出总长度
int32_t newDataHeadSize = MAGICNUM_SIZE + TLVCOUNT_SIZE + tlvBufferSize;
dataInfo->outLen = dataInfo->inLen + OVERHEAD_LEN + (uint32_t)newDataHeadSize;
总长度 = TLV头部大小 + 加密数据大小
= (魔数 + TLV计数 + TLV元素总长) + (原始数据 + AES-GCM开销)
5.加密
与传统打包没有太大的区别,加密结果写在头部的后面,只不过头部大小参数从原来的固定16字节变成了可变的newDataHeadSize
char *outData = (char *)dataInfo->outData + newDataHeadSize;
SoftBusEncryptDataWithSeq(&cipherKey, dataInfo->inData, dataInfo->inLen,
(unsigned char *)outData, &outLen, seq);
6.清零密钥
和传统打包没区别
回归主线
文件互传模块我之前是以为看的差不多了,直到分片看完了,我才发现,softbus SDK还是属于在应用层,还没到软总线层,把业务线设计的好复杂,不是说难,就字面意思,怎么这么多校验
总结一下目前的进度
已经看的

没看的


| 层 | 所在进程 | 核心职责 |
|---|---|---|
| 1. NAPI | 应用 | JS→C 桥接 |
| 2. Session 服务 | 应用 | 权限检查、sessionId→channelId 映射 |
| 3. 通道管理 | 应用 | 按通道类型(UDP/Proxy)分发 |
| 4. 文件管理 | 应用 | 文件锁、发送上下文、流程控制 |
| 5. 文件发送控制 | 应用 | 逐文件发送、读文件、分帧 |
| 6. 流发送 | 应用 | 查通道信息、委托打包发送 |
| 7. 打包分片 | 应用 | 加密、TLV/PacketHead、4KB 分片 |
| 8. IPC 代理 | 应用 | 序列化、Binder 跨进程 |
| 9. 服务端 | 系统服务 | 反序列化、socket 网络发送 |