第 6 章 数据传输

6.1 双向数据流

DTP 实现 必须 支持以下两个独立的数据流方向:

方向名称发送方接收方
Terminal → Fay数据归集(Collection)SlaveMaster
Fay → Terminal数据注入(Injection)MasterSlave

两个方向 必须 满足:

  1. 使用相同的 LogicalFrame 格式与处理流程。
  2. 维护独立的序列号空间。
  3. 维护独立的续传状态(未确认 Fragment 缓存、最高已确认序列号等)。
  4. 互不干扰:一个方向的状态变化 不得 影响另一个方向。

6.2 数据归集流程(Terminal → Fay)

数据归集 必须 遵循以下流程:

6.2.1 发送方(DTP_Slave)流程

  1. 接收终端应用提交的数据。
  2. 构建 Fragment: a. 生成新的 Fragment_ID(UUID v4)。 b. 设置 agreementId 为当前 active 约定的 Agreement_ID。 c. 设置 originTimestamp 为数据实际产生时刻的 UTC 毫秒时间戳。不得 使用当前时刻。 d. 附加结构化的 ContextMetadata(参见第 4.11 节)。 e. 附加 dagDependencies 为空数组)。
  3. 验证 DAG 依赖: a. 调用 DAG Manager 验证不会形成环路(参见第 6.7 节)。 b. 如检测到环路,必须 拒绝该 Fragment 并返回 DAG_CYCLE_DETECTED 错误(4001)。
  4. 构建 LogicalFrame: a. 设置 frameType = "data"。 b. 应用 Agreement_ID 压缩规则(参见第 4.5 节)。 c. 分配单调递增的 sequenceNumber(数据归集方向)。 d. 设置加密元数据。
  5. 加密 Payload:使用 CAP 预协商的密钥加密 Fragment 的 data 字段。
  6. 序列化 LogicalFrame。
  7. 调用 Transport_Adapter 发送二进制数据。
  8. 将 Fragment 加入未确认缓存(参见第 8.2 节)。

6.2.2 接收方(DTP_Master)流程

  1. 接收 Transport_Adapter 传递的二进制数据。
  2. 反序列化为 LogicalFrame。如失败,必须 丢弃帧并返回 FRAME_DESERIALIZATION_FAILED 错误(1001)。
  3. 验证协议版本(参见第 10 章)。
  4. 解析 Agreement_ID(应用第 4.5 节的压缩规则)。如关联到未知约定,必须 丢弃帧并返回 AGREEMENT_NOT_FOUND 错误(3001)。
  5. 解密 Payload。如失败,必须 丢弃帧并返回 DECRYPTION_FAILED 错误(2001)。
  6. 反序列化为 Fragment。
  7. 验证 DAG 依赖: a. 如所有依赖目标 Fragment 已存在,必须 接受并标记为 accepted。 b. 如存在依赖目标 Fragment 尚未到达,必须 标记为 pending 并缓存(参见第 6.7 节)。 c. 如检测到环路,必须 拒绝并返回 DAG_CYCLE_DETECTED 错误(4001)。
  8. 更新接收状态:将该 Fragment 的 sequenceNumber 设为该方向的最高已接收序列号(如果它递增)。
  9. 发送确认(参见第 8 章)。
  10. 将 Fragment 持久化到 Personal Data Heap。

6.3 数据注入流程(Fay → Terminal)

数据注入 必须 遵循以下流程:

6.3.1 发送方(DTP_Master)流程

  1. 从 Personal Data Heap 查询并按约定 dataRange 过滤数据,生成最小化数据集。
  2. 构建 Fragment(同 6.2.1 的步骤 2-3)。
  3. 构建 LogicalFrame、加密 Payload、序列化、发送(同 6.2.1 的步骤 4-8),但使用数据注入方向的序列号空间。

6.3.2 接收方(DTP_Slave)流程

  1. 反序列化、验证版本、解析 Agreement_ID、解密、反序列化 Fragment、验证 DAG(同 6.2.2 的步骤 1-7)。
  2. 更新接收状态、发送确认(同 6.2.2 的步骤 8-9)。
  3. 将 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 压缩约束

实现 必须 满足:

  1. 选择不进行压缩(即所有 Fragment 都携带完整 Agreement_ID)。
  2. 如选择压缩,必须 严格遵循 4.5 节规则。
  3. 接收方 必须 同时支持压缩与非压缩两种模式。
  4. 上下文 Agreement_ID 在会话挂起恢复后 必须 重置为 null(即恢复后第一个 Fragment 必须 携带完整 ID)。

6.5 序列号管理

6.5.1 单调递增

每个 Fragment 的 sequenceNumber 必须 满足:

  1. 在单次会话内单调递增。
  2. 严格连续(即每次递增 1)。
  3. 不得 重复或回退。

6.5.2 双向独立

数据归集方向与数据注入方向 必须 维护独立的序列号空间:

数据归集方向 (collection):  seq 1, 2, 3, 4, 5, ...
数据注入方向 (injection):   seq 1, 2, 3, 4, 5, ...

实现 不得 在两个方向之间共享序列号空间。

6.5.3 重启与会话

序列号 必须 满足:

  1. 新会话开始时序列号 必须 重置( 从 0 或 1 开始)。
  2. 会话从 Suspended 恢复时,序列号 必须 保持挂起前的值,不得 重置。
  3. 如序列号接近实现定义的最大值,发送方 必须 主动新建会话以避免溢出。

6.6 原始时间戳保全

实现 必须 保证 Origin_Timestamp 在传输过程中不变:

  1. 发送方 必须 记录数据实际产生时刻为 Origin_Timestamp。
  2. 序列化、加密、传输、解密、反序列化 不得 修改 Origin_Timestamp。
  3. 接收方 不得 修改接收到的 Origin_Timestamp。
  4. 接收方持久化时 必须 保留 Origin_Timestamp。

实现 在帧头之外维护一个独立的"传输时间戳"字段(实现定义),但 不得 与 Origin_Timestamp 混淆。

6.7 DAG 依赖处理

6.7.1 添加 Fragment 到 DAG

接收方接收到 Fragment 时,必须 通过 DAG Manager 处理依赖:

  1. 提取 Fragment 的 dagDependencies
  2. 对每条依赖: a. 检查目标 Fragment_ID 是否已在 DAG 中。 b. 检查添加该边是否会形成环路。
  3. 根据检查结果返回三种结果之一:
结果含义后续动作
accepted所有依赖已解析,无环路必须 将 Fragment 加入 DAG
pending部分依赖未解析(目标 Fragment 未到达),无环路必须 缓存该 Fragment,等待依赖解析
rejected检测到环路必须 拒绝该 Fragment 并返回 DAG_CYCLE_DETECTED 错误(4001)

6.7.2 延迟解析

当 Fragment 处于 pending 状态时:

  1. 实现 必须 在该 Fragment 缓存中持续等待依赖。
  2. 当依赖目标 Fragment 到达时,必须 重新评估缓存中所有相关 pending Fragment。
  3. 设置最大缓存时间(实现定义)。超时后 返回 DAG_DEPENDENCY_UNRESOLVED 错误(4002)并丢弃。

6.7.3 环路检测算法

实现 必须 在添加新 Fragment 前检测环路。检测算法 使用 DFS(深度优先搜索)或 Tarjan 算法。

具体算法:从新 Fragment 的每个依赖目标出发,进行 DFS。如能从依赖目标到达新 Fragment 自身,则形成环路。

6.8 多约定交错传输

当多个 active 约定共存时,发送方 必须

  1. 在每个 Fragment 的帧头中通过 Agreement_ID 关联到正确的约定。
  2. 在切换约定时(即下一个 Fragment 属于不同约定),必须 在该 Fragment 中携带完整的 Agreement_ID(即不能使用 null 压缩)。
  3. 按约定的 priority 调度发送顺序。