RFC-0033:未知字段处理和严格处理

RFC-0033:未知字段的处理和严格程度
状态已接受
领域
  • FIDL
说明

此 FTP 修正并阐明了 FIDL 解码器在遇到表、可扩展并集、枚举和位(可扩展消息)包含类型未知的字段时的行为。

作者
提交日期(年-月-日)2019-02-07
审核日期(年-月-日)2019-03-07

总结

此 FTP 修订并阐明了 FIDL 解码器在遇到表、可扩展联合、枚举和位(可扩展消息[^1])时包含类型未知的字段时的行为。

具体来说,我们提出了以下建议:

  • 为可扩展消息定义严格灵活的行为,指定遇到未知字段(包括句柄)的解码器的行为方式;
  • 可以作为可扩展消息声明的前缀的 strict 关键字。这样可以在验证过程中拒绝消息,从而确保接收的消息不包含未知字段。
  • 默认可扩展的消息具有灵活性,即允许未知值并通过绑定公开;
  • 定义并推荐绑定为客户端提供的 API,以检查包含未知字段的消息

与其他 RFC 的关系

此 RFC 修订者:

设计初衷

可扩展消息是一种有价值的机制,可让数据交换格式不断演变,而不会破坏有线格式(二进制)兼容性。但是,更改架构会对 FIDL 解码器做出设计决策,因为在如何验证、解析这些字段以及向最终用户公开这些字段时会出现问题。

虽然每种语言的数据结构访问机制和规范各不相同,但为解码器及其 API 指定行为可以通过强制执行验证行为来提高安全性,也可以通过提高各种类型和语言的一致性来改进整体工效学设计。

我们还希望为受限环境启用绑定,在这种环境中,可能无需解析未知字段即可正确操作,并且会增加不必要的性能负担。这也与已经达到成熟度且预计不会进一步发展的消息相关。

设计

未知字段是指读取器不知道其序数(表)、标记(可扩展并集)、值(枚举)或特定位(位)的字段。为简单起见,在此处,我们将使用“标记”来指代未知序数/标记/值/特定位。

  • 必须成功验证和解析包含未知标记的消息。
    • 不过,请参阅下文,了解 Strict 消息的例外情况。
  • 解码器必须处理消息中的未知句柄。
    • 默认处理行为必须是关闭所有句柄。
    • 绑定可以为客户端提供一种特殊处理未知句柄的机制。
  • 绑定必须提供一种机制来检测是否收到了未知标记作为消息的一部分。
  • 绑定应提供一种机制,用于检测带有给定标记的字段是否存在作为收到的消息的一部分。
  • 绑定可以提供一种机制,用于读取未知字段中的标记、原始数据和(非类型化)句柄。
  • 如果目标语言提供了一种在编译时详尽检查标记的机制(例如,C/C++ 中的 switch()、Rust 中的 match):

    • 该语言绑定应提供一个特殊的“未知”标记,该标记可以包含在详尽检查中,以实现无限别名(例如,C/C++ 中的 default、Rust 中的 _)可以省略。
    • 该建议的目的是防止进行正确的编译所需的无限别名地址的情况,因为如果确实如此,将来添加的标记将不会引发编译器警告。
    • 此 FTP 未定义任何应如何实现该机制的机制,因为语言之间的实现策略可能有所不同。
    • 示例:

      // Bindings SHOULD NOT offer this API:
      switch(union.Which()) {
        case Tag1: ...
        case Tag2: ...
        case Tag3: ...
        default: ...
        // no unknown tag in bindings forces handling using default case
      }
      
      // Bindings SHOULD offer this API:
      switch(union.Which()) {
        case Tag1: ...
        case Tag2: ...
        case Tag3: ...
        case Tag_Unknown: ...
        // no default case: new tags cause a non-exhaustiveness warning
      }
      

严格处理消息

  • 我们引入了 strict 关键字,可以为可扩展的消息声明(例如strict table T { ... }strict enum T { ... }
  • 包含未知字段的严格消息必须视为无效。
  • 绑定不得提供特殊的“未知”标记来对严格消息进行详尽的标记检查,前提是它们支持这种灵活消息机制。
  • 必须通过非重大源代码级 (API) 更改支持从严格消息转换为灵活消息以及从严格消息转换为灵活消息,并且可能会使用 [Transitional] 属性进行软转换。
    • 此类转换不得更改传输格式 (ABI)。
  • 严格消息不会传递。如果某一条消息被标记为“严格”,则只有该消息是“严格”消息。 该消息中包含的子消息并不严格。

  • 示例语法:

    // One simply doesn't walk into Mordor and add a new file mode, so this is
    // reasonable to be strict.
    strict bits UnixFilePermission : uint16 {
        ...
    };
    
    // It's too dangerous for clients to ignore data in this table if we
    // extend it later, but we wish to keep the wire format compatible if we
    // do change it, so it's not a struct.
    strict table SecurityPolicy {
        ...
    };
    

实施策略

  1. 更新 FIDL 兼容性测试,以验证现有语言绑定是否符合本规范。
    1. 为 (1) 仅包含已知字段、(2) 仅未知字段以及 (3) 至少一个已知字段和一个未知字段的消息添加测试用例。
  2. 确保 FIDL 兼容性测试针对所有适当类型的空消息提供了测试用例。
  3. fidlc 中添加了对严格消息的支持。
  4. 更新语言绑定以支持严格的消息。
  5. 向 FIDL 兼容性测试添加了针对严格消息的测试用例。

展望未来:使用网站修饰符

在设计阶段,除了提议的声明网站展示位置,我们还考虑允许将“严格”关键字放置在声明的使用网站中。

示例语法如下所示:

protocol Important {
    SomeMethod(...) -> (strict other.library.Message response);
}

此处,other.library.Message 可能尚未定义 strict,但我们希望始终使用它,同时需要严格的验证。

这会给绑定作者增加设计复杂性,因为在严格模式和灵活模式下都可能需要 other.library.Message

在编码/验证/解码方面,根据上下文针对同一消息公开严格模式和灵活模式与字符串或矢量的处理方式并无不同。 它们具有相同的布局,但可以具有不同的边界,具体取决于它们的使用位置。 这也类似于在可为 null 或不可为 null 的上下文中使用可扩展联合的方式。通常,绑定已经选择了一个类型架构,可以通过某种方式指示边界、是否可为 null 或(如此处所讨论的)严格模式。

为同一消息同时提供严格模式和灵活模式的第二个问题是,处理消息组合以及用户代码中的消息查询。

例如,假设有一个具有三个成员(ABC)的枚举。为了公开灵活模式,我们需要一个特殊的枚举成员“unknown”。 因此,现在可以组合不通过严格验证的枚举,使得在需要此枚举的其他上下文中,在严格上下文中,内容将在编码期间失败。同样,并行使用字符串和矢量也很重要:如果没有高度专用的 API,绑定允许创建过长并无法编码的字符串和矢量。

在同时支持严格模式和灵活模式时要遵循的策略是生成适用于灵活模式的所有额外部分,并确保在编码、解码和验证期间根据需要进行严格验证。

工效学设计

此 FTP 从以下几个方面改进了人体工程学:

  • 我们更好地设定了用户对不同语言的 FIDL 行为的期望。
  • 严格的消息可让用户避免编写不必要的代码来处理未知字段。

文档和示例

  • 需要更新严格字段的语法和语言规范。
  • 应更新 FIDL 样式指南,以指导何时将消息声明为严格消息。

向后兼容性

  • 此变更不会影响 ABI 兼容性。
  • 如果需要对解码器或绑定进行更改以符合此 FTP,这些更改可能会导致源代码级 (API) 损坏,而应根据具体情况予以解决。

性能

  • 如果强制解码器和绑定遵守此 FTP,可能会迫使解码器和绑定处理所有未知字段并关闭所有句柄,从而造成(可能不明显)的性能损失。
  • 绑定可能需要额外的间接层(因此使用额外的内存/二进制文件大小),才能提供“未知”标记以进行详尽的标记检查。

安全性

此 FTP 可提高安全性。

  • 我们会为包含未知内容的邮件指定验证行为。
  • 严格消息可让解码器在客户端进行检查之前验证并舍弃未知内容,从而降低出现错误的可能性。

测试

请参阅实现策略部分(我们计划使用 FIDL 兼容性测试)。 此外,每个语言绑定都应有自己的测试,用于断言正确的行为。

缺点、替代方案和未知情况

此 FTP 在很大程度上阐明了行为,并且会产生相关的实现费用,以确保语言绑定符合其建议。

替代方案:默认为严格模式或混合模式

严格程度应与矢量或字符串的大小边界类似;严格程度是一种独立于消息布局的约束条件,可在不破坏 ABI 的情况下进行更改。

我们希望 FIDL 作者能够明确选择限制(约束)其消息。

此外,我们也不需要混合模式,在这种模式下,某些消息(例如枚举)在默认情况下是严格模式,而其他消息(例如,表格)则不是。

替代方案:[严格] 属性,而不是新关键字

这是一个足够重要的广告创意,值得拥有专门的关键字。 对于其他语言的类似功能,有足够的先例,能够很好地将其翻译成 FIDL。

替代方案:其他关键字

在设计阶段,人们提出了几种不同的替代方案。最可能的竞争者是 final:它表示“关于主题的最后一句”,在 C++、Java、C# 中优先级更高。

不过,由于我们可能要在协议中使用关键字“ final”来表示无法在组合中使用(即传统使用“ final”的),因此我们选择用另一个关键字来表示严格验证。

这为引入以下语法做准备:

final strict protocol Important {
    MyMethod(SomeTable arg);
};

这表示协议 Important 不能组合使用,并且所有验证都必须严格。

其他探索的关键字有:sealedrigidfixedclosedknownstandardized

替代方案:仅严格限制

我们可以将所有可扩展消息定义为始终严格。目前,枚举和位只有严格限制,因此这种替代方法会将其扩展到表和可扩展的联合体。

在这种情况下,如果要更改可扩展结构(例如,添加新字段),则需要在更新写入者之前更新读取器。 这严重限制了这些可扩展数据结构的使用,对更高级别的用例而言限制太大。

此外,如果这是设计上的选择,我们不需要为表和可扩展联合使用信封(即不需要字节数和句柄)。 实际上,按照严格的严格解释,未知字段将被拒绝,否则架构将决定以类似于其余消息 FIDL 进程的方式消耗的字节数和句柄。

替代方案:仅灵活

我们可以定义所有可扩展的消息,使其始终具有灵活性。

这对于枚举(和位)来说非常令人惊讶,并且违背了预期。 这导致了两个不良的替代替代方案:

  • 为枚举(和位)设置例外情况,使其严格执行 - 如前所述,这会令人感到困惑,并且会使语言规则更难以理解。
  • 使这些消息保持灵活 - 这会违背预期,容易出错(例如,读取无效值),并且肯定会导致手动编写大量普通的验证代码,而不是通过绑定提供。

继续探索其他可扩展消息(表和可扩展联合体),需要满足条件并需要严格执行。

例如,请考虑使用定义为表的安全日志记录协议 LogEntry。此协议的实现可能需要确保客户端不会发送服务器无法理解的字段,因为担心这些客户端可能会对这些新字段可能如何控制日志条目的处理有所预期。例如,较新版本可以添加字段“pii ranges”,提供包含 PII 的日志条目的范围,并且必须专门记录这些范围(例如,替换为唯一 ID,将原始数据托管在该唯一 ID 下)。为防止旧服务器接受此类载荷以及避免对这些日志条目的不当处理,作者会为其 LogEntry 选择严格模式,从而保护自己免遭后续潜在的滥用。

早期技术和参考资料

其中部分理由由 go/proto3-unknown-fields 指导,说明了 proto3 为何不再支持保留未知字段,后来又推翻了这一决定。

  • FTP-037:交易邮件标头 v3(尚未发布)

脚注 1

枚举和位包含在可扩展消息中,因为在定义消息后可以添加或移除新成员。