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

前言

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

第一步:确认是否支持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 网络发送
相关推荐
六bring个六5 小时前
分布式设备连接对端失败问题分析
分布式·分布式软总线
天涯明月199311 小时前
ray深度研究报告
大数据·人工智能·分布式·ray
Rain的Java大神之路21 小时前
介绍一下分布式事务
java·分布式·后端·spring·spring cloud·架构·springcloud
SendTomo1 天前
send.wang(私传网)基于网页P2P直连传输大文件的优势
javascript·网络·webrtc·html5·p2p
大菠萝爱上小西瓜1 天前
【大数据实战】Spark 2.2.2 完全分布式集群搭建
大数据·分布式·spark
肠畔码农1 天前
深度解析 RocketMQ 消费端限流与重平衡(Rebalance):分布式队列分配与流控防雪崩本质
分布式·rocketmq
海兰1 天前
【 Kafka进阶3】Apache Kafka 分布式事件流平台:架构原理与微服务解耦机制简要分析
分布式·架构·kafka
麻瓜记录Jackson2 天前
深入剖析 ZooKeeper 分布式 互斥锁:Curator 之 InterProcessMutex 源码全解(附图解)
java·spring boot·分布式·后端·zookeeper·java-zookeeper
Keystone_Onion2 天前
跨境电商架构演进:基于美国海外仓的中大件分布式仓网路由优化方案
分布式·架构
盛世宏博智慧档案2 天前
分布式机房环境监测硬件,以太网温湿度采集终端如何应用
分布式·传感器·机房·温湿度