第 9 章 错误处理

9.1 错误处理模型

DTP 实现 必须 遵循"检测-通知-恢复"三阶段错误处理模型:

  1. 检测:识别异常情况。
  2. 通知:向对端或上层发送错误信息。
  3. 恢复:根据错误类型采取恢复措施。

实现 不得 在检测到错误时静默丢弃帧而不通知。

9.2 错误码体系

DTP 必须 使用以下错误码体系。错误码 必须 是非负整数,按功能模块划分为八个范围:

范围类别处理策略
1xxx帧处理错误丢弃帧 + 通知发送方
2xxx加密错误丢弃帧 + 通知发送方 + 可能触发密钥重协商
3xxx约定错误丢弃 Fragment + 通知发送方 + 可能触发重新协商
4xxxDAG 错误拒绝 Fragment + 通知发送方 或 缓存等待
5xxx会话错误尝试恢复会话 + 失败时关闭并通知上层
6xxx续传错误暂停发送 + 通知上层应用
7xxx版本错误发送版本不兼容通知 + 尝试降级处理
8xxx权限错误拒绝操作 + 通知请求方

9.3 错误码定义

实现 必须 使用以下规范化的错误码定义。不得 使用未列出的错误码。如需扩展,必须 在本规范的后续版本中定义。

9.3.1 帧处理错误(1xxx)

错误码名称触发条件
1001FRAME_DESERIALIZATION_FAILED接收到的二进制数据无法反序列化为 LogicalFrame
1002FRAME_INVALID_FORMATLogicalFrame 结构无效或缺少必需字段

9.3.2 加密错误(2xxx)

错误码名称触发条件
2001DECRYPTION_FAILEDPayload 解密失败(错误密钥或数据损坏)
2002KEY_NOT_READYCAP 密钥交换尚未完成,拒绝数据传输

9.3.3 约定错误(3xxx)

错误码名称触发条件
3001AGREEMENT_NOT_FOUNDFragment 引用了未知 Agreement_ID
3002AGREEMENT_EXPIRED引用的约定已过期(超过 validityPeriod)
3003AGREEMENT_NEGOTIATION_FAILED协商无法完成(超时、对端拒绝重协商等)

9.3.4 DAG 错误(4xxx)

错误码名称触发条件
4001DAG_CYCLE_DETECTED添加 Fragment 会形成 DAG 环路
4002DAG_DEPENDENCY_UNRESOLVEDFragment 依赖未在缓存超时内解析

9.3.5 会话错误(5xxx)

错误码名称触发条件
5001SESSION_NOT_FOUND引用的 Session_ID 不存在
5002SESSION_TIMEOUT会话因不活动超时
5003SESSION_RESTORE_FAILED重连后会话恢复失败

9.3.6 续传错误(6xxx)

错误码名称触发条件
6001BUFFER_FULL发送方未确认 Fragment 缓存达到容量上限
6002RETRANSMISSION_TIMEOUTFragment 重传超时未收到确认(重传次数耗尽)

9.3.7 版本错误(7xxx)

错误码名称触发条件
7001VERSION_INCOMPATIBLE接收帧的协议版本号高于自身支持版本

9.3.8 权限错误(8xxx)

错误码名称触发条件
8001PERMISSION_DENIED当前角色不允许执行该操作
8002OBSERVER_WRITE_DENIED旁观者尝试写操作(旁观者只读)

9.4 错误唯一性

实现 必须 保证错误码的唯一性:

  1. 每个错误码 必须 对应唯一的错误类型。
  2. 不同错误类型 不得 共享同一错误码。
  3. 实现 不得 重新定义本规范已分配的错误码。

9.5 错误通知机制

9.5.1 ErrorNotificationFrame

错误通知 必须 通过 ControlFrame 传递,定义如下:

interface ErrorNotificationFrame {
  frameType: "control";
  controlType: "error_notification";
  errorCode: DTPErrorCode;
  errorMessage: string;
  relatedFrameId?: FragmentID;
  relatedAgreementId?: AgreementID;
  details?: Record<string, unknown>;
}
字段规范性要求
frameType必须"control"
controlType必须"error_notification"
errorCode必须 为 9.3 节定义的错误码之一
errorMessage必须 为人类可读的错误描述。 使用对端可理解的语言
relatedFrameId可选。当错误由特定帧触发时 必须 包含该帧的 fragmentId
relatedAgreementId可选。当错误与特定约定相关时 必须 包含 Agreement_ID
details可选 包含调试用的额外信息

9.5.2 错误通知传输

ErrorNotificationFrame 必须 通过常规 LogicalFrame 通道传输,必须 加密 Payload。

如错误本身导致无法加密(例如 KEY_NOT_READY 错误),实现 通过实现定义的带外通道返回错误,但 不得 在加密通道中以明文发送错误。

9.6 关键错误处理流程

9.6.1 反序列化失败(1001)

接收到的 LogicalFrame 无法反序列化时,接收方 必须

  1. 丢弃该帧。
  2. 发送 FRAME_DESERIALIZATION_FAILED 错误通知。
  3. 不得 因反序列化失败而关闭会话。

9.6.2 解密失败(2001)

接收到的 Payload 无法解密时,接收方 必须

  1. 丢弃该帧。
  2. 发送 DECRYPTION_FAILED 错误通知。
  3. 计数连续解密失败次数。
  4. 如连续失败超过阈值(推荐 3 次), 触发 CAP 重新密钥交换。

9.6.3 DAG 环路检测(4001)

检测到 DAG 环路时:

  1. 接收方 必须 拒绝该 Fragment。
  2. 必须 返回 DAG_CYCLE_DETECTED 错误。
  3. 不得 将 Fragment 加入 DAG。
  4. 不得 影响已存在 Fragment 的状态。

9.6.4 未知约定(3001)

Fragment 引用了未知 Agreement_ID 时:

  1. 接收方 必须 丢弃 Fragment。
  2. 返回 AGREEMENT_NOT_FOUND 错误。
  3. 触发重新协商(实现定义)。

9.6.5 密钥未就绪(2002)

CAP 密钥交换尚未完成时尝试发送数据:

  1. DTP_Engine 必须 拒绝发送。
  2. 必须 返回 KEY_NOT_READY 错误给上层调用方。
  3. 不得 在通信通道中以明文形式响应。

9.6.6 缓存已满(6001)

未确认 Fragment 缓存达到上限时:

  1. 发送方 必须 暂停发送新 Fragment。
  2. 发送 BUFFER_FULL 通知给上层应用。
  3. 在确认释放缓存空间后恢复发送。
  4. 不得 丢弃已缓存的 Fragment。

9.6.7 重传超时(6002)

Fragment 重传次数耗尽时:

  1. 发送方 必须 通知上层应用 RETRANSMISSION_TIMEOUT 错误。
  2. 触发会话挂起或终止。
  3. 不得 无限重传。

9.6.8 版本不兼容(7001)

接收到协议版本不兼容的帧时(详见第 10 章):

  1. 接收方 必须 不处理该帧。
  2. 必须 返回 VERSION_INCOMPATIBLE 错误。
  3. 必须 在错误的 details 字段中包含自身支持的最高版本号。

9.6.9 权限拒绝(8001、8002)

非法的角色操作时:

  1. DTP_Engine 必须 拒绝该操作。
  2. 旁观者写操作 必须 返回 OBSERVER_WRITE_DENIED(8002)。
  3. 其他权限拒绝 必须 返回 PERMISSION_DENIED(8001)。

9.7 错误恢复策略

实现 必须 按错误类型采取相应恢复策略:

错误类别恢复策略
1xxx 帧处理丢弃帧,记录日志,继续接收后续帧
2xxx 加密单次失败丢弃帧,连续失败触发密钥重协商
3xxx 约定丢弃 Fragment,可能触发重新协商
4xxx DAG环路:拒绝;未解析:缓存等待
5xxx 会话尝试恢复会话;失败则关闭
6xxx 续传暂停发送,等待对端响应或上层介入
7xxx 版本发送版本通知;尝试降级处理
8xxx 权限拒绝操作,不得 自动重试

实现 不得 在权限错误(8xxx)发生时自动重试相同操作。