RFC-0235:组件字典 | |
---|---|
状态 | 已接受 |
区域 |
|
说明 | 提案:向组件框架中引入字典类型以进行功能捆绑。 |
问题 | |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2023-10-16 |
摘要
此 RFC 提出了运行时和声明式 API,用于创建和路由功能软件包(称为字典)。
设计初衷
目前,组件框架仅支持为其定义的 capability 进行“点对点”路由:为了将 capability C 从组件 A 路由到组件 B,每个中间组件中都必须存在路由段,以便在相邻组件之间路由 C。
许多使用情形都非常需要能够将一组功能作为单个逻辑单元进行路由。如果没有此功能,这些客户就不得不采用成本高昂、缺乏灵活性且脆弱的权宜解决方法。以下是这些用例的选段:
- 几乎每个组件都会使用
LogSink
、InspectSink
和轨迹提供程序等诊断功能,如果我们能够将它们作为一个软件包进行路由,将会大大简化路由拓扑。 fuchsia.debugdata.Publisher
等性能分析器功能与诊断功能类似,但只在特定 build 上启用。目前,我们还没有找到一种适合 build 配置需要跨整个拓扑添加的功能的方法。因此,目前只有测试领域支持启用性能分析器。- 测试 Realm 代理会向测试用例公开一个接口,测试用例会使用该接口来运行被测组件。目前,这是一个与组件连接 API 非常相似的自定义接口。此接口可以替换为测试 Realm 代理传输给测试的一组功能。这样,测试 Realm 代理就不必定义自己的抽象层,并且可以将任何组件框架功能提供给测试使用。
- 随时随地功能会通过多个层进行路由,可以通过捆绑来简化路由:
session_manager
:从core
路由到session_manager
的所有 capability 都需要由session_manager.cml
重新路由。这是一个庞大的功能集,因此难以维护,这意味着某些非平台功能会泄露到session_manager.cml
。chromium
:Chromium cml 文件包含大量重复内容,如果能够在一个名称下对功能进行分组和路由,则会大大简化这些文件。https://fxbug.dev/42072339)
- 组件框架环境是一项功能,可用于配置运行程序和解析器,以便将其提供给整个子树并进行隐式路由。如果我们可以改用通用捆绑 API 来实现此目的,则可以与组件框架路由 API 的其余部分更加协调,并缓解基于环境的隐式路由的最低特权问题。
以下也是促成功能捆绑的使用场景,但它们有一些特殊注意事项,需要在此 RFC 中提出的方案之外进行后续设计工作。
- 某些特定于开发板的驱动程序希望公开未为平台定义的自定义服务。这些服务应由
bootstrap
领域公开,因为所有驱动程序组件都位于该领域,但在平台拓扑中明确命名这些组件没有意义。如果 cml 有办法将这些服务捆绑在一起,我们就可以解决此问题。 - 使用
driver_test_realm
的测试中也会出现类似的问题。这些测试希望将不同的服务从驱动程序路由到测试。在这些测试中,驱动程序测试 realm 组件位于驱动程序和测试之间,我们希望能够在这些测试中重复使用驱动程序测试 realm,而无需对其进行修改。
最后,目前已经有几个现有的组件框架 API 涉及分组功能。不过,它们是独立的,仅适用于特定情况。示例如下:
- 命名空间是对路由到程序的所有功能(其
use
声明中的功能)进行分组。 - 公开目录是组件路由到其父级(即其公共接口)的功能的一种分组。
- 服务 capability 是一种将协议分组的方法,服务 capability 本身也可以分组,以形成“汇总”服务 capability。
我们有如此多的 API,这表明用户可以从通用抽象中受益,从而定义自己的字典并对其进行路由。
利益相关方
根据上述用例,我们已确定以下团队为利益相关方:
- 架构
- 诊断
- 测试
- 工具链
- 驱动程序框架
- 安全
教员:hjfreyer@
Reviewers:
- abarth@(架构)
- crjohns@(测试)
- markdittmer@(安全)
- miguelfrde@ (Diagnostics)
- surajmalhotra@(驱动程序)
- ypomortsev@(组件框架)
咨询了:
- phosek@
- kjharland@
- anmittal@
- wittrock@
- novinc@
社交:
在此 RFC 之前,有两个内部文档:使用情形和要求文档,以及核心设计文档。这些文档已获得利益相关方的非正式批准。
本文档中已根据需要纳入这些文档中的信息。
要求
以下是字典必须支持的操作,我们是通过分析用例并从现有分组 API 中推广得出这些操作的:
- 第一类:字典是 CF API 中的第一类概念,应表示为 capability。
- 聚合:有一个“聚合”操作,用于根据一组功能构建字典。
- 提取:有一个“提取”操作,用于从字典中提取单个 capability,该 capability 可以像任何 capability 一样进行路由和使用。这与聚合大致相反。
- 委托:可以在组件之间传递字典。
- 嵌套:由于字典是一种 capability,因此字典可以包含其他字典。
- 结构:字典会带有元数据标记,这些元数据会准确指明字典包含哪些功能。
- 扩展:有一个操作用于构建一个新字典
B'
,该字典会继承B
的内容并添加其他功能。 - 可变性:字典的内容可能会随时间而变化。不过,可能存在更高级别的政策,对特定字典的可变性施加限制。
设计
定义
字典定义为键值对的集合,其中键是 capability 名称(例如fuchsia.example.Echo
),值为 CF 功能。
capability 名称是来自集合 [A-Za-z0-9_-.]
的一个或多个字符的序列,大小介于 1 到 N 之间(目前 N = 100,但我们日后可能会将其扩展)。
运行时的字典
我们将引入一个公共 FIDL 协议,用于提供字典接口。作为 FIDL 伪代码:
library fuchsia.component;
type DictionaryEntry = resource struct {
key DictionaryKey;
value Capability;
};
protocol Dictionary {
Insert(DictionaryEntry) -> ();
Remove(DictionaryKey) -> (Capability);
Lookup(DictionaryKey) -> (Capability);
Enumerate() -> Iterator<DictionaryKey>;
Clone();
};
我们还将引入可公开发现的 FIDL 协议,以允许调用方创建一个空字典。
后续设计工作将确定 Capability
的确切类型定义。
组件声明中的字典
我们先来进行一些形式化处理,这有助于定义操作。我们将定义与每个组件关联的四个特殊字典:组件输入字典、组件输出字典、程序输入字典和程序输出字典。这些字典统称为根字典。
组件输入字典是一个字典,其中包含父级向组件 offer
的所有功能;换句话说,就是组件可以路由的所有功能 from parent
。
组件输出字典是一个字典,其中包含组件向其父级 expose
的所有功能;换句话说,它包含父级可以路由的所有功能 from #component
。
程序输入字典是一个字典,其中包含由组件 use
的所有功能。
程序输出字典是一个字典,其中包含组件的所有 capability
声明。
我们可以根据以下定义来表达 capability 路由操作:
use
、offer
、expose
和capabilities
是路由操作,用于在字典之间路由功能。use
会将 capability 从根字典路由到程序输入字典。expose
会将 capability 从根字典路由到组件输出字典。offer
会将 capability 从根字典路由到子项的组件输入字典。capabilities
会使程序输出字典中的 capability 可供路由。
采用这种设计后,我们将对其进行泛化,以允许路由操作使用任意字典作为来源,而不仅仅是根字典:
use
会将 capability 从字典路由到程序输入字典。expose
会将 capability 从字典路由到组件输出字典。offer
会将 capability 从字典路由到子项的组件输入字典 或其他字典。
声明
如需定义一个全新的空字典,请执行以下操作:
capabilities: [
{
dictionary: "diagnostics-bundle",
},
],
如需定义内容从现有字典继承的字典,请使用 extends
提供该字典的路径(请参阅扩展):
{
dictionary: "diagnostics-bundle",
extends: "parent/logging-bundle/sys",
},
汇总
如需将功能汇总到字典中,请在 offer
中使用 to
关键字将功能路由到该字典。由于此字典由此组件定义,因此包含它的根字典为 self
。所有相同的关键字都支持作为常规 offer
使用。例如,您可以使用 as
关键字更改目标字典中的 capability 的名称。
capabilities: [
{
dictionary: "diagnostics-bundle",
},
],
offer: [
{
protocol: "fuchsia.logger.LogSink",
from: "#archivist",
to: "self/diagnostics-bundle",
},
{
protocol: "fuchsia.inspect.InspectSink",
from: "#archivist",
to: "self/diagnostics-bundle",
},
{
directory: "publisher",
from: "#debugdata",
to: "self/diagnostics-bundle",
rights: [ "r*" ],
as: "coverage",
},
],
委托
委托是指路由字典:
offer: [
{
dictionary: "diagnostics-bundle",
from: "parent",
to: "#session",
},
],
与其他 capability 路线一样,您可以使用 as
关键字更改目标的名称。
offer: [
{
dictionary: "logging-bundle",
from: "parent",
to: "#session",
as: "diagnostics-bundle",
},
],
动态商品将提供等效的运行时 API。
嵌套
您可以使用汇总语法将字典路由到另一个字典,从而使字典包含其他字典:
capabilities: [
{
dictionary: "session-bundle",
},
],
offer: [
{
dictionary: "driver-services-bundle",
from: "parent",
to: "self/session-bundle",
},
],
提取
在正式化后,我们将扩展 from
关键字,使其不仅接受根字典,还接受嵌套在根字典中的字典。
如需从字典中提取 capability,请在 from
中为字典命名。此字典相对于根字典(parent
、#child
等)
offer: [
{
protocol: "fuchsia.ui.composition.Flatland",
from: "parent/session-bundle",
to: "#window_manager",
},
],
这也适用于 use
:
use: [
{
protocol: "fuchsia.ui.composition.Flatland",
from: "parent/session-bundle",
},
],
当字典嵌套在其他字典中时,提取功能也适用:
use: [
{
protocol: "fuchsia.ui.composition.Flatland",
from: "parent/session-bundle/gfx",
},
],
扩展程序
在字典定义中使用 extends
选项可从其他字典继承:
capabilities: [
{
dictionary: "session-bundle",
// `session-bundle` is initialized with the dictionary the parent
// offered to this component, also called `session-bundle`.
extends: "parent/session-bundle",
},
],
offer: [
{
dictionary: "session-bundle",
from: "self",
to: "#session-manager",
},
{
protocol: "fuchsia.ui.composition.Flatland",
from: "#ui",
to: "self/session-bundle",
},
],
可变性
以声明方式构建的字典是不可变的,这是一种有用的安全属性。如需使字典可变,必须在运行时创建该字典。
字典中功能的元数据
将 capability 放入字典后,它们会保留所有类型信息和元数据,这些信息和元数据与与字典本身关联的任何元数据是分开的。
例如,如果组件 A
将具有 optional
可用性的 capability 添加到字典中,并且组件 B
提取该 capability,则该 capability 在提取时将具有 optional
可用性,即使字典本身的可用性为 required
也是如此。
我们可能会在汇总时对播出信息施加一些限制。例如,禁止将 required
capability 放入 optional
字典可能是有意义的,因为这会违反从目标路由到来源时可用性绝不会变弱的常规不变量。
运行时和声明式字典之间的互操作性
在运行时创建的字典必须与组件声明中的字典能够互操作。如果不是这样,则会迫使用户只能选择其中一种,这将证明捆绑的概念基础不够通用,无法以类似的方式解决这两种用例。
我们将在后续提案中详细介绍互操作性设计。
此功能的主要已知用例是驱动程序框架,用于路由在运行时填充的服务软件包。
实现
cml 中的着陆页字典将遵循引入新 cml 功能的常规流水线。首先,我们将向 cml 和 component.decl 架构添加字典。然后,我们将更新 cmc
、cm_fidl_validator
、cm_rust
和 realm_builder
,以编译、验证和表示字典。审核功能也将更新为能够识别字典并验证字典路线。
我们正在努力将字典(作为 Rust 类型)集成到组件模型和路由引擎中。字典 API 的实现应基于这项工作,以便将这些字典用作公共字典 API 的后端,并用作路由字典功能的传输层。
性能
无特殊效果注意事项。路由字典的速度应与单独路由组成功能的速度一样快或更快。
工效学设计
改进构建组件拓扑的易用性是此设计的主要动机。
虽然引入新功能会自然增加 API 的复杂性,但我们认为,通过在拓扑中纳入字典,可以将这种增加的复杂性抵消得更多。
向后兼容性
cmc 尚不支持组件清单功能的版本控制,因此请务必小心,以免破坏与预构建清单的兼容性。幸运的是,为字典引入的所有新语法都与旧语法兼容,这让工作变得更轻松。例如,from
中的当前名称语法将成为新路径语法的特殊情况。
如果 capability 路线的一部分通过字典传递,则仍必须应用与该 capability 相关的所有安全政策。
安全注意事项
在字典中路由 capability 时,会丢失一些透明度,因为字典中 capability 的身份会隐藏在路线中的中间组件中。不过,您仍然可以通过沿着路线向后追溯到提供商来推断出这些信息。这是有意做出的妥协,以实现字典所带来的灵活性和强大功能。
以声明方式构建的字典是不可变的。对于这些字典,您可以通过对字典从目标到其来源的汇总路线执行深度优先搜索,获取其内容的完整说明。
如果字典取代环境,将会增强系统的安全状况,因为字典与环境不同,会以与其他功能相同的方式进行显式路由。
隐私注意事项
此提案对隐私权没有影响。
测试
我们将像测试大多数组件管理器功能一样测试此功能,在 component_manager
和 cmc
中进行单元测试,在 component_manager/tests
中进行集成测试。我们还将在审核中添加集成测试,以演练应用于包含字典的路线的字典和政策。
文档
我们会更新 //tools/lib/cml
中的 rustdoc。
向 //docs/concepts/components
添加一个页面来介绍字典。
向 //examples/components
添加了示例。
缺点、替代方案和未知情况
替代方案 1:字典的 in
和 into
关键字
声明
字典 capability 是一种 cml/component.decl capability 类型,用于授予对字典的访问权限。
您可以像声明任何其他组件框架功能一样声明字典。字典创建有两种变体,具体取决于 extends
关键字是否存在。
首先,您可以定义一个全新的空字典:
capabilities: [
{
dictionary: "diagnostics-bundle",
},
],
或者,您也可以定义一个字典,其内容从现有字典继承,方法是指定 extends
中的字典路径和 from
中的来源(请参阅扩展):
{
dictionary: "diagnostics-bundle",
extends: "logging-bundle/sys",
from: "parent",
},
汇总
如需将功能汇总到字典中,请使用 into
关键字将功能路由到该字典:
capabilities: [
{
dictionary: "diagnostics-bundle",
},
],
offer: [
{
protocol: "fuchsia.logger.LogSink",
from: "#archivist",
into: "diagnostics-bundle",
},
{
protocol: "fuchsia.inspect.InspectSink",
from: "#archivist",
into: "diagnostics-bundle",
},
{
protocol: "fuchsia.debugdata.Publisher",
from: "#debugdata",
into: "diagnostics-bundle",
},
],
委托
与主设计相同。
嵌套
只需使用汇总语法将字典路由到字典,即可使字典包含其他字典:
capabilities: [
{
dictionary: "session-bundle",
},
],
offer: [
{
dictionary: "driver-services-bundle",
from: "parent",
into: "session-bundle",
},
],
提取
我们引入了新的 in
关键字,用于指定要从中提取 capability 的字典。
存在 in
时,capability 关键字(protocol
等)是指此字典中的 capability,而不是 from
中命名组件直接提供的 capability。
in
可以是名称或路径(其中名称可以视为退化情况)。如果它是名称,则表示 from
提供的字典。如果是路径,则路径的第一个部分指定了 from
提供的字典,而路径的其余部分指定了嵌套在此字典中的字典的路径。
use
、offer
和 expose
支持 in
。这始终是可选的。
offer: [
{
protocol: "fuchsia.ui.composition.Flatland",
from: "parent",
in: "session-bundle/gfx",
to: "#window_manager",
},
],
expose: [
{
protocol: "fuchsia.ui.composition.Flatland",
from: "#scenic",
in: "gfx-bundle",
},
],
use: [
{
protocol: "fuchsia.ui.composition.Flatland",
in: "session-bundle/gfx",
},
],
扩展程序
在字典定义中使用 extends
关键字可从另一个字典继承:
capabilities: [
{
dictionary: "diagnostics-bundle",
extends: "parent/logging-bundle/sys",
},
],
offer: [
{
dictionary: "diagnostics-bundle",
from: "self",
to: "#session-manager",
},
{
protocol: "fuchsia.tracing.provider.Registry",
from: "#trace_manager",
into: "diagnostics-bundle",
},
],
方案 2:capability 标识符中的路径
路径可以是 capability 标识符 (protocol
、directory
等) 的一部分,而不是在 from
中指定字典的路径
汇总
与主设计相同。
委托
与主设计相同。
嵌套
与主设计相同。
提取
如需从字典中提取 capability,请在 capability 标识符 (protocol
、directory
等) 中指定字典中 capability 的路径。
offer: [
{
protocol: "session-bundle/fuchsia.ui.composition.Flatland",
from: "parent",
to: "#window_manager",
},
],
默认情况下,目标中的名称将是最后一个路径元素(即“dirname”)(fuchsia.ui.composition.Flatland
)。您也可以使用 as
对其进行重命名:
offer: [
{
protocol: "session-bundle/fuchsia.ui.composition.Flatland",
from: "parent",
to: "#window_manager",
as: "fuchsia.ui.composition.Flatland-windows",
},
],
这也适用于 use
,它可将字典中的 capability 提供给程序。与其他 use
声明一样,默认目标路径会将名称(最后一个路径元素)重基到 /svc
:
use: [
{
protocol: "session-bundle/fuchsia.ui.composition.Flatland",
path: "/svc/fuchsia.ui.composition.Flatland", // default
},
],
路径语法也适用于嵌套在其他字典中的字典:
use: [
{ protocol: "session-bundle/gfx/fuchsia.ui.composition.Flatland" },
],
扩展程序
在字典定义中使用 origin: #...
选项可从其他字典继承:
capabilities: [
{
dictionary: "session-bundle",
// Source of the dictionary to extend (in this case, the one named
// "session-bundle" from the parent)
origin: "#session",
from: "parent",
},
],
offer: [
{
dictionary: "session-bundle",
from: "self",
to: "#session-manager",
},
{
protocol: "fuchsia.ui.composition.Flatland",
from: "#ui",
into: "session-bundle",
},
],
方案 3:capability 标识符变为路径
名称 -> 路径
官方而言,cml 中的 capability 标识符是名称,没有固有的嵌套结构。例如:
offer: [
{
protocol: "fuchsia.fonts.Provider",
from: "#font_provider",
to: "#session-manager",
},
],
不过,capability 标识符会映射到 capabilities
和 use
部分中的路径。对于协议,这通常是隐式的:如果未提供路径,cmc 会填充默认路径 /svc/${capability-name}
。例如:
use: [
{
protocol: "fuchsia.fonts.Provider",
// path in namespace
path: "/svc/fuchsia.fonts.Provider",
},
],
capabilities: [
{
protocol: "fuchsia.fonts.Provider",
// path in outgoing directory
path: "/svc/fuchsia.fonts.Provider",
},
],
此替代方案将支持 capability 标识符中的路径。更正式地说:
- capability identifier(capability 标识符)是字符集
[A-Za-z0-9_-.]
中一个或多个名称的序列,包含 1 到 100 个字符,并以/
字符分隔。不允许使用前置/
。- 或者,使用正则表达式语法:
[A-Za-z0-9_-]{1,100}(/[A-Za-z0-9_-]{1,100})*
- 现有 capability 标识符与新语法向后兼容。
- 或者,使用正则表达式语法:
下面,我们将了解此语法如何自然地为打包奠定基础。
汇总
如需将 capability 汇总到字典中,请使用相同的路径前缀进行路由:
offer: [
{
protocol: "fuchsia.logger.LogSink",
from: "#archivist",
to: "all",
as: "diagnostics/fuchsia.logger.LogSink",
},
{
protocol: "fuchsia.inspect.InspectSink",
from: "#archivist",
to: "all",
as: "diagnostics/fuchsia.inspect.InspectSink",
},
{
protocol: "fuchsia.debugdata.Publisher",
from: "#debugdata",
to: "all",
as: "diagnostics/fuchsia.debugdata.Publisher",
},
],
委托
委托只是将字典按原样路由:
offer: [
{
dictionary: "diagnostics",
from: "parent",
to: "#session",
},
],
嵌套
您可以通过将嵌套字典的路径设为嵌套字典的前缀,使字典包含其他字典:
offer: [
{
dictionary: "driver-services-bundle",
from: "parent",
to: "#session-manager",
as: "session/driver-services",
},
],
提取
只需在要从中提取 capability 的字典中为其命名即可:
offer: [
{
protocol: "session-bundle/fuchsia.ui.composition.Flatland",
from: "parent",
to: "#window_manager",
as: "fuchsia.ui.composition.Flatland",
},
],
这也适用于 use
:
use: [
{
protocol: "session-bundle/fuchsia.ui.composition.Flatland",
path: "/svc/fuchsia.ui.composition.Flatland",
},
],
当字典嵌套在其他字典中时,提取功能也适用(TODO:示例)
扩展程序
重命名 capability,使其路径前缀与字典一致:
offer: [
{
protocol: "session-bundle",
from: "parent",
to: "#session-manager",
},
{
protocol: "fuchsia.ui.composition.Flatland",
as: "session-bundle/fuchsia.ui.composition.Flatland",
from: "#ui",
to: "#session-manager",
},
],
为什么使用字典,而不是目录?
我们可以使用 fuchsia.io
目录作为软件包的基础类型,而不是引入字典。从某种角度来看,这很有吸引力:目录已经存在,并且提供自己的分层捆绑形式。不过,反对使用目录的理由有很多:
- VFS 类型系统携带的信息与 CF 类型系统不同;例如,服务、目录和存储空间都会映射到 VFS 中的子目录,即使它们在 CF 中是不同的类型。
- 目录接口比支持 capability bundling 所需的接口要复杂得多。NODE_REFERENCE、链接、标志、属性、数据文件等功能与捆绑用例无关。
- 对于某些应用(尤其是驱动程序),VFS 库的大小太大。因此,
//sdk/lib/component/outgoing
库会链接到共享库 shim (//sdk/lib/svc
),而不是直接链接到 VFS 库,但代价是功能和透明度会降低。 - 这并不是一个 VFS 实现,而是两个单独的 C++ 实现和一个 Rust 实现。这些实现之间存在细微差异和功能差距。这不是字典的问题,因为组件运行时中只有一个字典实现。
- 目录会增加编写 codegen 绑定的难度,因为这些绑定会将程序提供或使用每项功能都表示为一个单独的语言元素。
- 目录本身并不支持“汇总”或“扩展”操作。必须通过提供一个新目录来模拟它们,其中一些节点会重定向到旧节点,这很难实现且容易出错。
后续工作
我们将单独提出配套设计,以针对驾驶员用例(仅凭此提案中的功能无法完全解决)进行优化。
这种设计为能力路由提供了更经济的语法。
我们可以将 capability 名称和 from
合并为单个路径,而不是将其作为单独的属性,其根在概念上是一个包含所有根字典的字典。例如:
offer: [
{
protocol: "#ui/fuchsia.ui.composition.Flatland",
to: "#session-manager",
},
],
这种语法有一个很好的特性:它可以自然地推广为允许路由整个根字典:
offer: [
{
// Plumb all capabilities from parent to child #session-manager
dictionary: "parent",
to: "#session-manager",
},
],
另外值得一提的是,一个更通用的版本,它可以进一步统一语法:
route: [
{
// Path to source capability in dictionary
src: "#ui/fuchsia.ui.composition.Flatland",
// Path of target capability in dictionary
dst: "#session-manager/fuchsia.ui.composition.Flatland",
},
],
route: [
{
src: "parent",
dst: "#session-manager/parent",
},
],
在先技术和参考文档
Capability bundle 是一个旧想法。许多内部前身文档都提出了类似的想法。