SPECIFICATION
第 6 章 数据传输
6.1 双向数据流
DTP 实现 必须 支持以下两个独立的数据流方向:
| 方向 | 名称 | 发送方 | 接收方 |
|---|---|---|---|
| Terminal → Fay | 数据归集(Collection) | Slave | Master |
| Fay → Terminal | 数据注入(Injection) | Master | Slave |
两个方向 必须 满足:
- 使用相同的 LogicalFrame 格式与处理流程。
- 维护独立的序列号空间。
- 维护独立的续传状态(未确认 Fragment 缓存、最高已确认序列号等)。
- 互不干扰:一个方向的状态变化 不得 影响另一个方向。
6.2 数据归集流程(Terminal → Fay)
数据归集 必须 遵循以下流程:
6.2.1 发送方(DTP_Slave)流程
- 接收终端应用提交的数据。
- 构建 Fragment:
a. 生成新的 Fragment_ID(UUID v4)。
b. 设置
agreementId为当前 active 约定的 Agreement_ID。 c. 设置originTimestamp为数据实际产生时刻的 UTC 毫秒时间戳。不得 使用当前时刻。 d. 附加结构化的 ContextMetadata(参见第 4.11 节)。 e. 附加dagDependencies(可 为空数组)。 - 验证 DAG 依赖:
a. 调用 DAG Manager 验证不会形成环路(参见第 6.7 节)。
b. 如检测到环路,必须 拒绝该 Fragment 并返回
DAG_CYCLE_DETECTED错误(4001)。 - 构建 LogicalFrame:
a. 设置
frameType = "data"。 b. 应用 Agreement_ID 压缩规则(参见第 4.5 节)。 c. 分配单调递增的sequenceNumber(数据归集方向)。 d. 设置加密元数据。 - 加密 Payload:使用 CAP 预协商的密钥加密 Fragment 的
data字段。 - 序列化 LogicalFrame。
- 调用 Transport_Adapter 发送二进制数据。
- 将 Fragment 加入未确认缓存(参见第 8.2 节)。
6.2.2 接收方(DTP_Master)流程
- 接收 Transport_Adapter 传递的二进制数据。
- 反序列化为 LogicalFrame。如失败,必须 丢弃帧并返回
FRAME_DESERIALIZATION_FAILED错误(1001)。 - 验证协议版本(参见第 10 章)。
- 解析 Agreement_ID(应用第 4.5 节的压缩规则)。如关联到未知约定,必须 丢弃帧并返回
AGREEMENT_NOT_FOUND错误(3001)。 - 解密 Payload。如失败,必须 丢弃帧并返回
DECRYPTION_FAILED错误(2001)。 - 反序列化为 Fragment。
- 验证 DAG 依赖:
a. 如所有依赖目标 Fragment 已存在,必须 接受并标记为
accepted。 b. 如存在依赖目标 Fragment 尚未到达,必须 标记为pending并缓存(参见第 6.7 节)。 c. 如检测到环路,必须 拒绝并返回DAG_CYCLE_DETECTED错误(4001)。 - 更新接收状态:将该 Fragment 的 sequenceNumber 设为该方向的最高已接收序列号(如果它递增)。
- 发送确认(参见第 8 章)。
- 将 Fragment 持久化到 Personal Data Heap。
6.3 数据注入流程(Fay → Terminal)
数据注入 必须 遵循以下流程:
6.3.1 发送方(DTP_Master)流程
- 从 Personal Data Heap 查询并按约定
dataRange过滤数据,生成最小化数据集。 - 构建 Fragment(同 6.2.1 的步骤 2-3)。
- 构建 LogicalFrame、加密 Payload、序列化、发送(同 6.2.1 的步骤 4-8),但使用数据注入方向的序列号空间。
6.3.2 接收方(DTP_Slave)流程
- 反序列化、验证版本、解析 Agreement_ID、解密、反序列化 Fragment、验证 DAG(同 6.2.2 的步骤 1-7)。
- 更新接收状态、发送确认(同 6.2.2 的步骤 8-9)。
- 将 Fragment 交付给终端应用。
6.4 Agreement_ID 压缩传输
实现 必须 严格遵循第 4.5 节定义的 Agreement_ID 压缩规则。
6.4.1 压缩示例
以下是符合规范的传输序列:
Fragment 1: agreementId = "abc-123" (新约定,完整 ID)
Fragment 2: agreementId = null (沿用 "abc-123")
Fragment 3: agreementId = null (沿用 "abc-123")
Fragment 4: agreementId = "def-456" (切换到新约定,完整 ID)
Fragment 5: agreementId = null (沿用 "def-456")
6.4.2 压缩约束
实现 必须 满足:
- 可 选择不进行压缩(即所有 Fragment 都携带完整 Agreement_ID)。
- 如选择压缩,必须 严格遵循 4.5 节规则。
- 接收方 必须 同时支持压缩与非压缩两种模式。
- 上下文 Agreement_ID 在会话挂起恢复后 必须 重置为 null(即恢复后第一个 Fragment 必须 携带完整 ID)。
6.5 序列号管理
6.5.1 单调递增
每个 Fragment 的 sequenceNumber 必须 满足:
- 在单次会话内单调递增。
- 应 严格连续(即每次递增 1)。
- 不得 重复或回退。
6.5.2 双向独立
数据归集方向与数据注入方向 必须 维护独立的序列号空间:
数据归集方向 (collection): seq 1, 2, 3, 4, 5, ...
数据注入方向 (injection): seq 1, 2, 3, 4, 5, ...
实现 不得 在两个方向之间共享序列号空间。
6.5.3 重启与会话
序列号 必须 满足:
- 新会话开始时序列号 必须 重置(应 从 0 或 1 开始)。
- 会话从
Suspended恢复时,序列号 必须 保持挂起前的值,不得 重置。 - 如序列号接近实现定义的最大值,发送方 必须 主动新建会话以避免溢出。
6.6 原始时间戳保全
实现 必须 保证 Origin_Timestamp 在传输过程中不变:
- 发送方 必须 记录数据实际产生时刻为 Origin_Timestamp。
- 序列化、加密、传输、解密、反序列化 不得 修改 Origin_Timestamp。
- 接收方 不得 修改接收到的 Origin_Timestamp。
- 接收方持久化时 必须 保留 Origin_Timestamp。
实现 应 在帧头之外维护一个独立的"传输时间戳"字段(实现定义),但 不得 与 Origin_Timestamp 混淆。
6.7 DAG 依赖处理
6.7.1 添加 Fragment 到 DAG
接收方接收到 Fragment 时,必须 通过 DAG Manager 处理依赖:
- 提取 Fragment 的
dagDependencies。 - 对每条依赖: a. 检查目标 Fragment_ID 是否已在 DAG 中。 b. 检查添加该边是否会形成环路。
- 根据检查结果返回三种结果之一:
| 结果 | 含义 | 后续动作 |
|---|---|---|
accepted | 所有依赖已解析,无环路 | 必须 将 Fragment 加入 DAG |
pending | 部分依赖未解析(目标 Fragment 未到达),无环路 | 必须 缓存该 Fragment,等待依赖解析 |
rejected | 检测到环路 | 必须 拒绝该 Fragment 并返回 DAG_CYCLE_DETECTED 错误(4001) |
6.7.2 延迟解析
当 Fragment 处于 pending 状态时:
- 实现 必须 在该 Fragment 缓存中持续等待依赖。
- 当依赖目标 Fragment 到达时,必须 重新评估缓存中所有相关
pendingFragment。 - 可 设置最大缓存时间(实现定义)。超时后 应 返回
DAG_DEPENDENCY_UNRESOLVED错误(4002)并丢弃。
6.7.3 环路检测算法
实现 必须 在添加新 Fragment 前检测环路。检测算法 应 使用 DFS(深度优先搜索)或 Tarjan 算法。
具体算法:从新 Fragment 的每个依赖目标出发,进行 DFS。如能从依赖目标到达新 Fragment 自身,则形成环路。
6.8 多约定交错传输
当多个 active 约定共存时,发送方 必须:
- 在每个 Fragment 的帧头中通过 Agreement_ID 关联到正确的约定。
- 在切换约定时(即下一个 Fragment 属于不同约定),必须 在该 Fragment 中携带完整的 Agreement_ID(即不能使用 null 压缩)。
- 应 按约定的
priority调度发送顺序。
