文件互传在传输层的操作分析

前言

文件消息在业务层经过组装成帧了以后,这里分析的文件消息是指文件数据发送完后会发送文件列表到对端进行完整性核对,完整的帧会发送到传输层,进行分片、打包、加密

第一步:确认是否支持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 网络发送
相关推荐
吴声子夜歌3 小时前
Redis 5.x——分布式锁
redis·分布式
六bring个六1 天前
文件互传功能实现
c/c++·open harmony·文件互传
我登哥MVP1 天前
Hadoop成长史-从Nutch子项目到大数据生态王者
java·大数据·hadoop·分布式·云原生·云计算
凯云-金山HIL1 天前
打破仿真孤岛:基于凯云SimuRTS的分布式联合仿真系统架构深度解析
自动化测试·分布式·系统架构·etest·半实物仿真
Database_Cool_2 天前
HTAP数据库详解_阿里云PolarDB-X一体化行列存实现实时分析
分布式·阿里云·云计算
泰克教育官方账号2 天前
泰涨知识 | Shell脚本之一键启动和关闭Zookeeper脚本
分布式·zookeeper·云原生
添砖java_8572 天前
基于RabbitMQ实现的轻量队列测试报告
分布式·rabbitmq
Zhu7582 天前
使用腾讯CNB构建Hadoop定制容器镜像
大数据·hadoop·分布式
TTBIGDATA2 天前
【Ambari Plus】13.Spark 安装
大数据·hadoop·分布式·spark·ambari·sqoop·ambari plus