RFC-0222:引入了 Fuchsia 控制器

RFC-0222:引入 Fuchsia 控制器
状态已接受
区域
  • 开发者
  • FIDL
  • 测试
说明

添加了 Fuchsia 控制器,用于对 Fuchsia 设备进行主机端脚本编写和测试

问题
Gerrit 更改
作者
审核人
提交日期(年-月-日)2022-11-17
审核日期(年-月-日)2023-06-13

摘要

未来 Fuchsia 体验工具 FFX 提供了一种从宿主机与 Fuchsia 设备通信的方法。它使用 Fuchsia 接口定义语言 (FIDL) 调用各种服务以进行开发和测试。为了向该工具添加更多功能,FFX 支持子工具,这些子工具是用于扩展 FFX 的命令行界面和功能的单独二进制文件。该工具完全使用 Rust 编写。

如果用户希望在 Fuchsia 设备上访问 FIDL 协议,可以为 FFX 编写插件、使用 Fuchsia 脚本层 (SL4F),或者提交在系统上编写组件。

这些选项都不适合轻量级实验、快速迭代或灵活性。FFX 插件需要使用 Rust 编写工具并进行编译。SL4F 不仅不受支持,还依赖于不安全的传输,并且与其交互需要为计划调用的每个 FIDL 接口编写一个 Rust 外观。

这些方法都不允许用户遵循“自带运行时”原则。

此 RFC 提出了一种通过 FFX 中的现有库(称为 Fuchsia 控制器)访问设备的额外方法。这包括:

  • 通过 FIDL 句柄模拟与 Fuchsia 设备交互的稳定 ABI,无需更改 FFX 本身。
  • 对至少一种利用上述稳定 ABI 的热门脚本语言提供一流支持(当前选择为 Python)。
  • 以所述脚本语言编写的更高级别的库,针对编写关键测试用例进行了优化。

设计初衷

目前没有受支持的机制可用于通过 SDK、源代码树或其他方式将主机设备与 Fuchsia 设备之间的互动编写为脚本。

如果用户希望使用 FFX 与 Fuchsia 设备互动,则必须提供可编译到该工具中的服务定义。如果树之外定义了服务,用户将无法访问该服务。如果服务已定义,则用户必须编写 FFX 插件并进行编译。

Fuchsia 控制器可让用户以快速、高度迭代且易于自动化的方式与 Fuchsia 设备互动。支持稳定的 ABI 将允许用户自带运行时(如果他们选择实现它),因为使用 C ABI 可以扩展几乎所有流行的编程语言。

利益相关方

辅导员

待定

审核者

jeremymanson@google.com(工具) mgnb@google.com (FFX) ianloic@google.com (FIDL) chok@google.com(lacewing) jzgriffin@google.com (SL4F)

已咨询

EngProd 团队 FFX 团队 FIDL 团队

共同化

Fuchsia Controller 是 Tools、FFX、FIDL 和 EngProd 团队经过数月讨论的成果。其他团队包括组件框架团队和测试架构团队。作者还使用实现部分中概述的技术编写并演示了概念验证和高级演示。

设计

概览

Fuchsia 控制器由三部分组成:

  • FFX 客户端库。
  • 特定于语言的扩展库。
  • 使用扩展库的更高级别的一流库。

这些工具都与 FFX 协同工作,以处理与 Fuchsia 设备的连接。

替代文本:Fuchsia 控制器的图表。Fuchsia 控制器堆栈从更高级别的 Python 开始,然后馈送到主 Python 绑定,再馈送到 FFX 绑定。然后,FIDL 会传递到 FFX 守护进程监控的 Unix 网域套接字。从这里开始,FFX 守护程序使用隧道式 FIDL 连接与 Fuchsia 设备进行交互。此示例概述了一般互动,即获取 Remote Control Service 公开的组件的代理

在继续之前,我们需要先了解一些必备事项。

FFX 的背景

FFX 是一种主机端工具,(在撰写本文时)它使用“守护进程”(在本例中是指在后台运行的自身的分叉副本),该守护进程会在网络上发现 Fuchsia 设备时通过 SSH 主动连接到这些设备。发现设备的方法有很多,但不在本文档的讨论范围内。

FFX 守护程序允许 FFX 工具的用户在主机和 Fuchsia 设备之间打开模拟的 FIDL 渠道。消息从 FFX 客户端调用路由,通过守护程序,然后到达 Fuchsia 设备。

FFX 几乎完全使用 Rust 编写。除此之外,用于写入和读取宿主侧模拟 FIDL 通道的库也是用 Rust 编写的。

总而言之,FFX 允许用户通过 FIDL 与 Fuchsia 设备进行交互,使用与 FFX 实现紧密耦合的 Rust 编写的插件。

定义条款

在以下部分中,我们使用了一些定义:

  • “隔离”是指以某种方式使用 FFX,使其可能具有单独的 FFX 精灵实例,该实例可自行运行,而无需使用通用套接字位置或任何通用 FFX 配置值。FFX 精灵的实例不会与 Fuchsia 设备通信隔离(除非明确调用以进行隔离)。
  • “Daemon Protocol”(守护程序协议)是指 FFX 守护程序处理的主机端 FIDL 协议。这些功能包括软件包服务、检查网络上的 Fuchsia 设备以及 Fuchsia 设备端口转发。守护程序本身可以视为类似于公开了多个此类守护程序协议的组件。
  • 当提及渠道、句柄和套接字的“zircon”时,这些都是通过 overnet 在主机端模拟的。不过,未来这些对象可能会在 Fuchsia 设备上作为实际的 zircon 对象使用。

FFX 客户端库

FFX 客户端库通过 C ABI 公开了一些函数,以支持以下操作:

  • 创建、读取、写入和关闭 Zircon 渠道。
  • 创建、读取、写入和关闭 Zircon 套接字。
  • 信号 Zircon 句柄(主要用于事件对)。
  • 将 Zircon 渠道连接到 Fuchsia 设备上的组件。
  • 将 Zircon 渠道连接到 FFX 守护程序协议。

此外,还将支持其他功能,例如:

  • 创建 FFX Daemon 的隔离实例。
  • 声明 FFX 配置键值对。

特定于语言的扩展程序

这涉及在 FFX 客户端库之上实现一个简单的抽象层(以共享库的形式实现),然后将其用于一流的脚本库。

实现

如前所述,这需要创建两个共享库。

  • 与语言无关的“FFX 客户端”库,它将公开与 FFX 通信的函数以及 Zircon 句柄。
  • 语言绑定(在本例中为 Python)共享库。

此外,还将提供 Python 绑定,以便更轻松地使用 FIDL。

实现不涉及任何尚未包含在 Fuchsia 源代码树中的第三方依赖项。我们预计可以使用 FFX 客户端库实现对其他语言的支持,我们可能会支持该库(并且可能需要额外的第三方依赖项工作),但这超出了本 RFC 的范围。

将 Python 与 SDK 一起分发是一个单独的主题,不在本文档的讨论范围内,可能会封装在自己的 RFC 中。

FFX 客户端库

本部分中的 C 定义旨在放置在将随 Fuchsia SDK 一起交付的头文件中。由于 C 中缺少命名空间,这些函数名称将非常冗长。函数名称也不一定就是最终名称。

ABI 的模式旨在让大多数函数使用输出参数向调用方返回结构,而返回值要么为 void,要么为错误。

如果可能,将使用 zx_status_tzx_handle_t 等已建立的类型,以便与 Zircon 代码库中现有的 C 库保持一致。

用于读取频道的函数示例可能如下所示(这并非最终实现或文档来源):

extern zx_status_t ffx_channel_read(void* ctx,
                                    zx_handle_t handle,
                                    void* buf,
                                    uint32_t buf_len,
                                    zx_handle_t* handles,
                                    uint32_t handles_len
                                    uint32_t* actual_bytes_count,
                                    uint32_t* actual_handles_count);

ctx 值可以指向一个 Rust 对象,其中包含有关现有 FFX 环境的信息(运行目录、SDK 位置、配置值以及我们正在与之通信的 Fuchsia 设备)。

这与现有的 zx_channel_read 类似,只是省略了选项参数。与导出与 Zircon 系统调用相同的符号不同,ABI 将显式写入头文件中,以便清楚了解支持和不支持哪些功能(因为此代码最初仅针对主机而非 Fuchsia 设备编写,因此可能会有特殊注意事项)。

以下函数展示了如何获取句柄,该函数可用于通过远程控制 FIDL 定义连接到 Fuchsia 设备上的大多数组件。

extern zx_status_t ffx_connect_proxy(void* ctx, const char* endpoint_path,
                                     zx_handle_t* out);

这将返回一个指向相应协议的 FIDL 渠道。endpoint_path 应包含通过组件框架直接连接到设备上特定 FIDL 协议所需的信息(撰写本文时,此信息采用 "$moniker:expose:$protocol" 格式)。

通过支持套接字/通道读取和写入以及事件信号,将能够以 Python 复制 FFX 和 Overnet 目前支持的所有 FIDL 功能。

Python 语言绑定

Python 语言绑定将定义使用已建立的 create*/close*/destroy* 调用(以 C++ 编写)的对象,并围绕 FFX 客户端库提供一个精简的封装容器。它还会导出使用 ffx_error_str() 的错误类型。

点击此处可查看此(略有不同)实现的示例(Fuchsia 控制器原型)。

更高级别的绑定

对于 FIDL 协议的实际使用,将提供更高级别的库。 它将使用 importlib 挂钩到 FIDL 中间表示 (IR),导入分布式 .pyz 中或开发者 PYTHONPATH 中可用的任何 IR,或从手动提供的目录导入 IR。这些钩子将创建在运行时与 FIDL 交互的类,类似于 C++ 或 Rust 进行编译时绑定的情况。

例如:

import fidl.fuchsia_developer_ffx
from fuchsia_controller_py import Context

def main(ctx: Context):
  h = ctx.connect_daemon_protocol(fidl.fuchsia_developer_ffx.Echo.Marker)
  proxy = fidl.fuchsia_developer_ffx.Echo.Client(h)
  print(proxy.echo_string(value = 'foobar'))

if __name__ == '__main__':
  ctx = Context(None)
  main(ctx)

fidl. 进行导入会被库钩子捕获,以在现有的 FIDL IR 命名空间中搜索与 fuchsia.developer.ffx 匹配的内容,并根据其中定义的结构和协议生成类。

FIDL 线格式编码/解码

在第一遍中,实际的编码和解码可能会使用 fidl_codec 库完成,然后稍后完全在 Python 中实现。

fidl_codec 包含用于根据 FIDL IR 定义读取/写入 FIDL 有线格式的代码,以及可用于从 FIDL 消息构建对象的值访问器等实用程序。它用于 fidlcat 工具。使用访问者模式将来自 IR 生成的 Python 的 FIDL 结构体编码为线格式,从而创建一个可写入 FIDL 句柄的缓冲区。

性能

FFX 客户端库 ABI

当跨越 FFX 客户端 ABI 边界时(即通过 Unix 网域套接字读取/写入数据以及与 Fuchsia 设备进行网络通信时),会遇到最大的性能开销。

除此之外,FFX 客户端库会将 FIDL 读取操作复制两次,一次是为了跨越 Rust ABI 边界,另一次是为了将数据从 C++ 代码加载到 Python 对象中。如果这成为问题,我们可以在日后解决。

一般用例将涉及低开销的 FIDL 使用,即调用带有结构体的简单函数。在撰写本文时,主机上不支持更复杂的 FIDL(例如 VMO),因此不应担心性能问题。

工效学设计

FFX 客户端 ABI

FFX 客户端二进制文件以尽可能小的配置文件实现,以防止在其他语言中实现绑定时出现过多的样板代码。

更高级别的 Python 绑定

更高级别的 Python 绑定将使用 FIDL IR 在运行时创建类绑定。这样一来,不仅可以使用 Python 代码,使其行为方式与其他 FIDL 语言绑定(如 Rust、Dart 或 C++)保持一致,还可以在运行时试验 FIDL 协议,从而实现更具迭代性的开发。

如果 FIDL 服务是在树之外定义的,那么也可以使用 SDK 访问该服务(先生成 FIDL IR,然后将其导入 Fuchsia 控制器)。

安全注意事项

此 RFC 中的主要安全影响与 FFX 本身相关,不在本文档的讨论范围内。

测试

FIDL 线格式

FIDL 团队有一套测试(GIDL 和 DynSuite),可用于测试编码和解码。这适用于以下情况:使用 fidl_codec(因为尚未针对这些套件进行测试),或者 Python 显式写入/读取有线格式。

FFX 客户端库和 Python C 绑定

这些功能非常轻量级,因此除了验证是否满足某些异常条件之外,无需进行太多单元测试。为了测试 FFX 客户端库的 ffx_error_str(),可以使用明确定义的错误来防止依赖 FFX 本身中的特定字符串。

这些代码的大部分测试将在集成测试和/或端到端测试中进行。一个简单的示例是使用 Echo 协议进行不依赖于 Fuchsia 设备的测试,该测试会执行每一段代码。对于针对实际 Fuchsia 设备的测试,可以获取远程控制服务代理的实例并调用 IdentifyHost 函数。

文档

Fuchsia 控制器的文档将保留在 fuchsia.dev 网站上。

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

缺点

一流的 Python 支持

虽然 FFX 客户端库旨在支持其他语言的绑定,但 Python 不适合大型生产项目。可以说,Python 允许快速编写代码并快速实现功能,但代价是维护和可读性较差。

替代方案

为什么不是 Golang、Java、Rust 或 Dart?

在与端到端测试团队、面向产品的开发者和连接团队的初步讨论中,我们发现他们非常偏好提供互动性和易用性的脚本语言。这表明 Python 是第一个具有绑定的语言。

未知

FIDL IR

通过 SDK 将 FIDL IR 提供给树外的用户很难做到正确,因为它必须生成。因此,可能需要为 Bazel 添加额外的 build 规则,以简化依赖项。

SDK 中的 FFX

上述使用 FFX daemon 协议的示例在树外将无法正常运行,因为 FFX FIDL 协议尚未在 SDK 中实现。API 审核已部分完成,但仍需完成。很难估计这可能需要多长时间,因为 FFX 仍有广泛的 FIDL 表面需要覆盖,而且通常也是一个不断变化的目标。

FIDL 编解码器

在最终结果中,可能根本不需要使用 fidl_codec。 使用它还会涉及向 Python 共享库添加更多 surface,然后需要逐步淘汰这些 surface。

考虑的替代方案包括:

  • 生成的代码(例如,C++ 和 Rust 中就是这样处理的)。
  • 以 Python 实现的 fidl_codec 类比。

在先技术和参考资料