RFC-0241:SDK 接口中的显式平台 / 外部拆分

RFC-0241:SDK 接口中明确的平台 / 外部拆分
状态已接受
区域
  • FIDL
说明

更精确地表达了哪些 Fuchsia 接口部分可以在 Fuchsia 平台之外实现。

问题
Gerrit 更改
作者
审核人
提交日期(年-月-日)2024-01-29
审核日期(年-月-日)2024-04-09

摘要

Fuchsia IDK 未指定外部组件应为它定义的各种 FIDL 协议实现客户端还是服务器。这使得很难正确评估对 Fuchsia API 所做更改的向后兼容性影响。此 RFC 提出了一种在 FIDL 中以人类和机器都可读取的方式表达该信息的方法。这有助于提高基于 Fuchsia 的产品的正确性和稳定性。

设计初衷

大部分 Fuchsia 系统接口都以 FIDL 的形式表示为一组协议以及通过这些协议交换的类型。协议是不对称的,具有客户端和服务器端,但作为 SDK/IDK 一部分提供的 FIDL 声明并未指定 Fuchsia 平台向产品提供的外部组件呈现的接口是用于这些协议的服务器端、客户端还是两端。

这种不精确性在很大程度上限制了在不了解 Fuchsia 平台(以及通常是外部组件)的实现的情况下,可以安全地对 Fuchsia 系统接口进行的更改类型。实际上,大多数协议在 Fuchsia 平台中只会实现服务器或客户端,但此信息无法通过工具进行检查或由最终开发者发现。

例如,如果属于系统接口的某个类型包含 string:100(采用 UTF-8 编码时,长度不超过 100 字节的字符串),但后来我们发现需要支持更长的字符串。如果该类型仅从外部组件发送到平台组件(在协议方法请求中),那么可以安全地增加长度限制,因为解码包含该字符串的消息的组件始终至少与编码该字符串的组件一样新。如果该类型可能会从平台组件发送到外部组件,则无法安全地增加长度,因为针对平台中较新的 API 级别构建的组件可能会无意中向外部组件发送格式错误的消息。

利益相关方

哪些人会受到此 RFC 是否被接受的影响?

辅导员

由 FEC 指派负责引导此 RFC 完成 RFC 流程的人员。

审核者

  • abarth@google.com
  • chaselatta@google.com
  • hjfreyer@google.com

已咨询

  • aaronwood@google.com
  • awolter@google.com
  • crjohns@google.com
  • mkember@google.com
  • pesk@google.com
  • surajmalhotra@google.com
  • wittrock@google.com

共同化

向从事平台版本控制工作的人员分发了一份单页文档,其中描述了问题和提案。

要求

本文档中的关键字“必须”“不得”“必需”“会”“不会”“应”“不应”“建议”“可以”和“可选”应按照 IETF RFC 2119 中的描述进行解释。

我们必须能够表达哪些系统接口协议的客户端或服务器或两者可能在平台或外部组件中实现。此信息必须以可供评估平台变更兼容性的工具访问的方式表达。

此信息应在软件组装时提供给运行的工具,并且可能在运行时提供给组件框架。此信息应通过生成的 API 文档和 build-time 检查向最终开发者显示。

为了使此信息有助于评估 Fuchsia 平台与基于该平台构建的组件之间的兼容性,外部组件必须基于平台支持的稳定 API 级别构建。

设计

分析

平台 ABI 表面整体的兼容性可以分解为考虑外部组件与平台组件之间交换的各个类型的兼容性,包括通过渠道进行的协议交换和带外交换的序列化类型。

组件之间的所有 FIDL 数据交换都基于以下其中一项:

  • 通过组件框架协议功能交换的 FIDL 协议。这些资源会标记 @discoverable 属性。
  • 通过非 FIDL 方式(例如进程实参)交换的 FIDL 协议,
  • 在文件(如组件清单)或自定义 IPC 传输中进行序列化和交换的 FIDL 类型。

如果能看到整个 Fuchsia 系统接口 FIDL,我们就可以针对每个根收集可能从客户端发送到服务器以及从服务器发送到客户端的类型集。

协议

如果我们知道每个“根”协议的客户端和服务器是否可以在外部组件或平台组件中实现,那么我们可以收集可能从外部组件发送到平台组件、从平台组件发送到外部组件、在平台组件之间以及在外部组件之间发送的类型集。

对于协议:

@discoverable
protocol {
    M(Req) -> (Resp);
}

鉴于我们允许客户端和服务器的实现位置,我们可以看到请求 (Req) 与响应 (Resp) 中的类型如何在外部组件 (E) 和平台组件 (P) 之间流动:

客户端 服务器 E 到 P P 到 E P to P E 到 E
E E 请求、响应
E P Req Resp
E P、E Req Resp 请求、响应
P E Resp Req
P P、E Resp Req 请求、响应
P、E E Resp Req 请求、响应
P、E P Req Resp 请求、响应
P、E P、E 请求、响应 请求、响应 请求、响应 请求、响应

平台代码保证能够使用外部代码所使用的协议版本的超集,因此我们可以就类型限制是否可以收紧或放宽做出以下判断:

发件人 接收器 约束条件
外部 平台 无法拧紧
平台 外部 无法松开
外部 外部 无法更改

因此,我们需要能够限制允许实现客户端和服务器的位置。

几乎所有根协议都是通过组件框架功能交换的。而其他问题则属于低级别问题和隐性问题。其中包括 fuchsia.io.Directoryfuchsia.posix.socket 中的一些网络套接字控制通道协议。我们不应另辟蹊径,发明一种将这些协议标记为通信根的新方法,而应扩展 @discoverable 的含义,以纳入这些协议。事实上,fuchsia.posix.socket 中包含套接字控制协议名称的常量,这些常量在标记为 @discoverable 后会自动生成。

类型

目前,任何组件(外部或平台)代码都可以从原始字节序列化或反序列化任何非资源类型。为了让兼容性工具知道哪些 FIDL 类型在某些上下文中不会序列化或反序列化,我们应明确标记在正常 FIDL IPC 之外的组件之间传递的类型。

语法

协议

可发现性属性将扩展为可表达可发现协议的实现位置。默认情况下,标记为 @discoverable 的协议可能具有由外部组件和平台组件实现的客户端和服务器。

serverclient 可选属性列出了可能实现相应端点的组件类型。默认情况下,任何组件都可以实现这两个端点。

例如:

// All servers in the platform, all clients in external components.
@discoverable(client="external", server="platform")
protocol P {};

// All servers in external components, all clients in the platform.
@discoverable(client="platform", server="external")
protocol Q {};

// Only clients allowed in external components, both clients and servers allowed in the platform.
@discoverable(client="platform,external", server="platform")
protocol R {};

// Servers are only allowed in platform components. Clients are allowed anywhere.
// If both clients and servers are allowed that argument can (and should) be omitted.
@discoverable(server="platform")
protocol S {};

类型

将向 FIDL 添加新的 @serializable 属性,以标记哪些类型可以序列化并在 FIDL 协议之外的组件之间传递。

此属性仅允许用于非 resource structtableunion

它有两个可选实参,即 readwrite。这些方法都接受以英文逗号分隔的 platformexternal 列表,用于指明平台组件或外部组件是否应读取或写入相应类型。每个实参的默认值为 "platform,external",表示可以从相应类型的组件读取和写入实参。

实现

FIDL 工具链

fidlc 将更新为接受 @discoverable 的这些实参和新属性 @serializable。FIDL 绑定生成器 (fidlgen_*) 目前不会修改。

Fuchsia 系统接口

partnerpartner_internal 库中的所有可发现的 FIDL 协议都将更新。大多数将标记为 @discoverable(server="platform"),表示外部组件应仅实现客户端,但平台组件可以实现客户端和服务器。有些(例如 fuchsia.io 中的许多)会标记为 @discoverable(),表示任何组件都可以实现客户端和服务器。少数(主要是驱动程序)将标记为 @discoverable(client="platform", server="external"),表示外部组件应仅实现服务器,平台组件应仅实现客户端。

经过一些实验和原型设计,似乎没有一种直接的方法来计算每种协议属于哪个类别。组件图的运行时检查和 CML 分片的静态评估均不清晰。相反,我们将查看外部组件的清单,了解它们使用和提供的协议功能。

对于独立数据类型,我们将查找对各种语言绑定的显式序列化 API 的调用,并相应地注释所使用的类型。

兼容性工具

我们正在开发一款工具,用于评估 Fuchsia 系统接口的 FIDL IPC 部分在不同版本之间的兼容性。此功能可针对 FIDL IR 运行,并将纳入来自可发现属性的信息。有关此工具将实现的兼容性规则的完整说明将在另一份 RFC 中提供。

未来机会

兼容性工具是促使我们增强 FIDL 语法的主要因素。不过,有关 Fuchsia 系统接口的这些更丰富的信息可能在其他方面很有用。

FIDL 绑定

FIDL 绑定生成器可以识别它们正在构建的绑定是面向平台组件还是外部组件,并调整它们生成的代码,以鼓励开发者仅在具有兼容性保证的上下文中实现协议的客户端和服务器。

阻止任何不受支持的客户端或服务器会适得其反,因为开发者需要在测试中能够伪造或模拟对等方,但或许我们可以设置防护措施,以阻止在非测试环境中使用它们。

可以更新独立序列化代码,以仅允许传入标记为 @serializable 的类型。

组件框架

在组件框架内,协议功能是无类型的。它们通常以所携带的 FIDL 协议命名,但这只是一种惯例,而且有时也会被违反。如果工具需要推断组件用于与其对等方通信的协议,则必须根据功能名称来猜测所使用的协议。

如果组件框架在其模型中添加了协议类型,这些工具会更简单、更稳健、更正确。

软件程序集

软件组装会根据配置、平台组件和外部组件生成 Fuchsia 系统映像。它可以利用有关哪些协议可能由平台组件提供服务或在外部提供服务的信息,拒绝组装违反这些规则的产品。这样可以确保产品所有者不会在无意中构建出提供 Fuchsia 不提供的兼容性保证的产品,并帮助平台开发者避免以他们不打算支持的方式公开功能。

安全

了解哪些协议端功能会跨外部/平台边界路由,可能有助于评估系统的安全属性。这可以临时完成,也可以集成到现有工具中。

性能

性能不会发生变化。

工效学设计

这需要明确系统中的一些假设,这需要在前期进行更多工作,但可以使系统更易于理解和使用。

向后兼容性

现有 FIDL 库不会受到影响。不带实参的 @discoverable 仍是有效属性。

最初,我们不会对可发现属性和可序列化属性进行版本控制,因为它们是版本控制的输入,不会影响源代码或运行时兼容性,并且需要广泛采用才能看到好处。如果我们更新 FIDL 绑定以根据可发现的实参进行更改,则必须考虑对它们进行版本控制,因为更改它们可能会破坏现有源代码。

安全注意事项

未来,我们可能会在这方面有机会来提升整体系统安全性。

隐私注意事项

测试

通过明确编码我们承诺兼容的协议的哪些端点,可以更轻松地确保我们为 Fuchsia 平台提供完整的兼容性测试

对于所有 fidlc 更改,我们都会添加新的 fidlc 测试。

在组装时验证所有组件是否遵循 FIDL 文件中表达的规则之前,我们无法确定 SDK 中表达的外部/平台拆分是否与 Fuchsia 产品中的实际情况相符。

文档

需要更新一些 FIDL 文档:

缺点、替代方案和未知因素

我们可以保持现状,但这要么会严重限制我们可以支持的更改类型,要么会限制我们的工具在检查更改的安全性方面的信心。

我们可以在 API 工具中(就像我们对 SDK 类别所做的那样)在 FIDL 语言之外表达外部 / 平台拆分,但由于这种分类是在声明级别进行的,因此感觉不太符合人体工程学,并且更有可能过时。

我们可以使用等效的属性来标记 fuchsia.io.Directory 等协议,以实现兼容性,但不支持协议功能,而不是使用 @discoverable。这感觉像是增加了不必要的复杂性。

我们可以只编写使用这些类型的占位 FIDL protocol,并使用适当的属性标记 @discoverable,以表明这些类型可能在何处读取和写入,而不是添加 @serializable 来标记带外交换的类型。这意味着,协议永远不应被说出,并且通常难以正确理解和使用。

我们曾使用 @discoverable(platform="server", external="client") 而不是 @discoverable(server="platform", client="external") 之类的语法,但目前的语法感觉更容易理解。

在先技术和参考资料

不适用