RFC-0120:单独使用 FIDL 有线格式 | |
---|---|
状态 | 已接受 |
领域 |
|
说明 | 此 RFC 规定了在无传输情况下使用(即编码和解码)FIDL 有线格式的要求。它还指定了关于绑定应如何公开此功能的评分准则。 |
问题 | |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2021-07-02 |
审核日期(年-月-日) | 2021-08-04 |
摘要
此 RFC 规范了使用(即编码和解码)FIDL 的要求 有线格式。还指定了关于绑定如何 应该公开此功能我们介绍了有线格式的概念 元数据,用于描述线上传输格式的修订版本和特性;以及 要求将其用于编码和解码 API,以便:
- 绑定正式支持在不使用传输的情况下使用 FIDL 有线格式。
- 用户必须随编码消息一起传输有线格式元数据。
- 绑定可支持以消息为前缀的持久性惯例 元数据。
设计初衷
Fuchsia 的一个核心原则是可更新。我们在 ABI 方面进行了大量投资 在 IPC 环境中使用 FIDL 时,例如两个对等体通过 Zircon 通道上的 FIDL 协议。FIDL 线的独立用例 另一方面,由于它们相对较少, 需要注意兼容性。例如,有时 仅传递 FIDL 消息的编码字节会导致 可发展的 ABI。
驱动程序元数据 RFC 和 RFC-0109: Fast datagram sockets 调用通过面向字节的接口发送 FIDL。 现在是正式确定 FIDL 有线格式的独立用途的好时机, 为其提供演变和互操作性保证。
设计
绑定必须支持对 FIDL 有线格式进行编码和解码,而不使用
transport(一种 API,其要求将在下面详细说明)。请注意,许多绑定
已有某种形式的公共编码/解码 API(例如,fidl::Encode
高级 C++ 绑定)。并且应根据上述要求进行调整
RFC。因此,RFC 的这一部分可视为核心
功能,阐明了 FIDL 的分层。
FIDL 传输格式
FIDL 有线格式的重点是二进制兼容性:一组保证
围绕架构演变,以支持读取使用其他
该架构的另一个版本例如,布局为 struct{uint8;uint8;}
的类型
可能会演变为布局 struct{uint16;}
。FIDL 则提供可扩展数据
但它们并不支持线缆格式本身的演变,
将 FIDL 表切换为更高效的表示法。两块
事务标头中的信息有助于 FIDL 的二进制文件兼容性
有线格式(通过协议和传输使用时):
- 魔数:用于标识线上传输格式的修订版本。如果接收方 因为不支持此修订版本,所以它会坚定地拒绝解码,因为 而不是对不匹配的有线格式进行错误解读。
- 标志:表示此消息中启用的任何软转换。对于 例如,在 union 到 xunion 迁移期间,标记中的某个位 用于表示并集是使用可扩展的 表示。
单独使用 FIDL 传输格式时, 编码结果。我们建议将一部分信息 编码和解码。具体而言:
- Encoding 将绑定/语言特定的领域对象转换为 FIDL 编码形式和不透明的线缆格式的 blob 元数据,用于说明 。
- 解码会使用编码形式的 FIDL 消息,并使用 线格式元数据,生成绑定/特定于语言的域对象。
在伪代码中,它们将具有以下函数签名:
function Encode<T>(object: T) -> (EncodedMessage, WireFormatMetadata);
function Decode<T>(message: EncodedMessage, metadata: WireFormatMetadata) -> T;
EncodedMessage
包含编码的字节以及
消息。大多数绑定都会定义具有类似或等效目的的类型。
传输格式元数据本身具有与 64 位架构兼容的 ABI 整数。其布局如下所示(采用伪 C 表示法):
struct fidl_wire_format_metadata_t {
uint8_t disambiguator;
uint8_t magic_number;
uint8_t at_rest_flags[2];
uint8_t reserved[4];
};
RFC-0138:处理未知交互提议
将事务头文件中的标志细分为 dynamic_flags
- 数
涉及协议的请求/响应交互模型的问题,以及
at_rest_flags
- 与线路格式相关的参数。此 RFC 假定:
但可以在不丢失密钥的情况下轻松地进行相应调整
属性。
线上传输格式元数据的对齐方式应为 8 个字节,
就地解码消息。绑定必须在外部表示元数据
不透明结构,长度为 8 个字节,对齐方式为 8 个字节
(例如,具有单个 uint64
字段的结构体)。这使得元数据本身可以
通过防止用户依赖
元数据。
绑定必须检查 reserved
字节是否为零。绑定不得依赖于
对 at_rest_flags
字节的处理具有任意特定值。绑定必须
验证 magic_number
是否代表
支持。
绑定必须检查 disambiguator
字节是否为零。在 Cloud Storage 中
元数据的前面可以防止节目将 FIDL 消息误认为
以文件形式保存消息(请参阅
数据持久性惯例)。
请注意,FIDL 事务标头中包含的信息是一个超集
在线上传输格式元数据中保留这部分内容at_rest_flags
字段的语义
和 magic_number
字段与事务标头和
线上传输格式元数据。
每条消息只能与其对应的元数据部分结合使用。在
换句话说,应用不得共享元数据(例如,使用元数据 A
来
对消息 A
和消息 B
进行解码,或交换元数据(例如使用元数据 A
)
对消息 B
进行解码,并使用元数据 B
解码消息 A
。这样,
要在运行时(如连接期间)更改的消息线格式修订版本
格式软迁移
绑定必须支持单独使用下列顶级类型:
- 结构体
- 表格
- 联合
对于任何其他数据类型,编码和解码函数必须失败。失败 应尽可能在编译时进行。
FIDL 语言未规定传输线格式元数据的方式 与经过编码的消息相关联。例如,元数据可以是 从生产环境中使用 FIDL 时的事务性消息标头衍生而来 IPC 上下文。
数据持久性惯例
为了更好地支持激励用例,我们要指定 将元数据附加到编码消息的惯例,其中字节 以元数据为前缀。绑定应支持此项 独立有线格式用法的带前缀,称为 持久性。
以下持久性用例在测试范围内:
- 将单个 FIDL 对象写入网络、磁盘或其他面向字节/数据包 界面不支持在不选择启用 Zircon 标识名的情况下, 转变为请求/响应范式换言之,数据是“静态”的。
- 支持大于 64 KiB 的消息。消息大小限制为 64 KiB Zircon 通道传输的属性。以字节形式保留消息时 则此类限制不适用。现有的 Rust 持久性 API 支持 大型消息,已用于解决通道消息大小限制, 待处理内置 FIDL 支持(通过手动保留大型消息) 复制到 VMO 中
以下用例不在范围内:
- 内置对编码同一类型的消息序列的支持。 应用可以定义自定义流式传输方法,该方法更适合 应用场景。
使用以下带前缀的 API 变种可在许多方面改善工效学设计和安全性 方式:
- 用户不必手动跟踪数据之间的关联
和元数据数据只跟随元数据的后面,可以作为一个整体发送,
单位。相比之下,带外元数据传递会增加
版本不匹配。如果接收方需要处理
多路复用到同一持久性媒介中的多个有线格式版本:
<ph type="x-smartling-placeholder">
- </ph>
- 当流式传输 API 中的发送方更改身份时,新发送方 与原始发送方说出的线路格式修订版本不同。
- 考虑使用一个从多个组件接收持久性消息的代理 并将它们存储到数据库中。通过 代理必须将带外口味转换回带前缀的口味 以便保留不同的线上传输格式修订。
- 缓冲区管理得以简化,性能可能会提高,这 如果是在热路径中使用独立的有线格式,则比较有利。例如: 绑定可以分配一个缓冲区来同时存储元数据和 载荷,或者描述单个矢量化写入,其中第一个元素指向 添加到元数据中
- 用户无需重新实现用于传递元数据的相同逻辑 支持多种语言和客户端库,因为 FIDL 已提供 实施。
持久性 API 必须支持以下顶级类型:
- 非资源结构体
- 非资源表
- 非资源并集
对于任何其他数据类型,持久性必须失败。失败应发生在 编译时尽量缩短。
在伪代码中,持久性 API 具有以下函数签名:
function Persist<T>(object: T) -> vector<uint8>;
function Unpersist<T>(bytes: vector<uint8>) -> T;
绑定可以使用 符合目标语言的要求,只要符合 API 的形状即可 从数据流角度来看
绑定可以支持支持矢量化的矢量化 Persist
变体
例如生成一个 zx_channel_iovec_t
或 zx_iovec_t
链接至
缓冲区数量,或与目标的惯用写入接口集成
语言。绑定应提供矢量化变体(如果已使用)
到这里就结束了
绑定可以支持接受矢量化的矢量化 Unpersist
变体
输入,例如使用链接到多个缓冲区的 zx_iovec_t
,或者
集成目标语言的惯用阅读器界面。
请注意,持久性与独立磁盘相比,以字节为单位 编码/解码,这可能会导致生成句柄。
绑定必须支持保留会导致编码消息的较大值 超过 64 KiB。
应更新 FIDL 样式指南和 API 评分准则,以纳入持久性 注意事项:
- 明确指出二进制 blob 使用的是持久性约定还是 自定义/带外机制来传递元数据。
FIDL 源语言
此 RFC 不会更改 FIDL 源语言。
实现
绑定应调整其独立的编码/解码 API,以与 涉及元数据的提议设计。他们不必严格遵循 函数签名,只要函数与提案要求一致即可 从数据依赖关系的角度来看例如,解码器的行为 必须通过元数据的方式进行配置
应使用相同的独立编码/解码 API 来实现消息传递, 例如通过 Zircon 通道进行事务性消息分派。
可以单独添加对持久性 API 变种的绑定支持。
已经存在持久性 API 的实现 ,但数据格式和 API 与 此 RFC。将调整 Rust 实现,以与已接受的 设计。
Rust 变更
目前,Rust 绑定提供以下函数:
fn create_persistent_header() -> PersistentHeader;
fn encode_persistent_header(header: &mut PersistentHeader) -> Result<Vec<u8>>;
fn encode_persistent<T: Persistable>(body: &mut T) -> Result<Vec<u8>>;
fn encode_persistent_body<T: Persistable>(body: &mut T, header: &PersistentHeader) -> Result<Vec<u8>>;
fn decode_persistent<T: Persistable>(bytes: &[u8]) -> Result<T>;
fn decode_persistent_header(bytes: &[u8]) -> Result<PersistentHeader>;
fn decode_persistent_body<T: Persistable>(bytes: &[u8], header: &PersistentHeader) -> Result<T>;
这些签名应替换为以下内容(确切签名可能会因 借鉴和终生微妙之处):
fn persist<T: Persistable, W: std::io::Write>(body: &mut T, writer: W) -> Result<()>;
fn unpersist<T: Persistable, R: std::io::Read>(reader: R) -> Result<T>;
fn standalone_encode<T: TopLevel, W: std::io::Write, H: core::iter::Extend<HandleDisposition>>(body: &mut T, writer: W, out_handles: &mut H) -> Result<WireMetadata>;
fn standalone_decode<T: TopLevel, R: std::io::Read>(reader: R, handles: &mut [HandleInfo], metadata: &WireMetadata) -> Result<T>;
struct WireMetadata { /* private fields */ }
TopLevel
trait 是为结构体、联合和表实现的。
特别是,用户无法再凭空创建永久性标头 并重复使用同一标头对多条消息进行编码。
此外,绑定应提供一种序列化/反序列化的方法。
WireMetadata
与字节之间,以支持带外传递元数据。
性能
独立编码和解码是 FIDL 事务用途的一部分, 而持久性 API 应共享大部分代码路径。因此,我们 可以重复使用相同的标准和效果基准。
工效学设计
设计约束条件下的工效学设计应鼓励持久性
。例如,绑定可以使用更简洁、更符合编程习惯的
函数名称来表示持久性变种(例如 fidl::persist
),并将
一个更长、更明确的函数名称,用于表示公开独立
编码/解码 API(例如 fidl::standalone::encode
)。
向后兼容性
此更改本身可向后兼容,因为它完全是累加的, Rust FIDL 持久性实现除外。据我们所知, Rust FIDL 持久性的当前读取者、写入者和存储数据始终在演变 同步。
添加线格式元数据可提高未来的向后兼容性, 敬请期待即将进行的 FIDL 有线格式迁移。
线上格式元数据包含 5 个预留字节。这些字节可能是 经过重新调整,以在将来具有其他含义。例如,我们可能会 使用一个字节来描述特定于持久性的问题。
安全注意事项
FIDL 传输格式的验证要求在此处适用, 并具有相同的安全属性
请注意,FIDL 不是一种自描述格式。已成功将 使用一种消息类型的持久化消息,并不能保证 最初使用相同的消息类型进行序列化:
程序可能会混淆 FIDL 消息是否包含前缀 或如果元数据是通过带外方式传递的,则会导致 输入解析有误。我们认为,这些错误往往 测试阶段。再加上明确的文档, 应该尽量减少用户误解
恶意行为者可能会诱使程序覆盖持久的 FIDL
Foo
类型的消息以及另一个Bar
类型的消息, 通过在路径处理过程中利用漏洞进行控制。这个 让恶意操作者能够间接影响Foo
的内容 消息。
“替代内容”部分提供了一种更复杂的格式,可缓解这种情况 使用有关邮件类型的信息扩展元数据标头。
隐私注意事项
FIDL 传输格式中的填充字节必须为零, 有助于避免泄露敏感信息。
与临时数据相比,持久性数据往往会引发更大的隐私问题 我们会尽快使用 IPC 数据 组件保留它或通过网络发送它。因此, IPC 和持久性 API 之间的隐私权问题是类似的。
值得注意的是,开发者始终可以通过 其他方式,例如 JSON 或 XML。平常 如果未来的设计提及涉及 持久性用户或其他敏感数据,无论方法是否通过 FIDL 持久化。
FIDL API 元素中的隐私权注解将简化隐私权审核和 支持更好的下游工具(例如自动隐去);CANNOT TRANSLATE 此 RFC 的范围(重点介绍传输 FIDL 的特定方法) 消息。
测试
我们将扩展 FIDL 一致性测试套件 GIDL,以测试编码和 永久性格式的解码。
文档
创建有关 FIDL 的参考页面以进行独立编码/解码,并 持久性以及两个 API 之间的关系。
将所有语言添加到
//examples/fidl/
,以演示独立编码 解码和持久性,并添加相应的教程。
缺点、替代方案和未知问题
替代方案 1:仅支持 Persistent API
我们可以在惯例的基础上更进一步,将它规定为标准: 所有元数据都必须在消息载荷之前。足以供使用 这种做法可能会使代码变得过于僵化, 。通过提供非预期的独立编码/解码 API 和自定义的持久性 API,用户能够自行挑选 与自己的设计最相符。
替代方案 2:允许共享线上传输格式的元数据
我们可以允许为会话中的所有消息共享相同的元数据。 这样一来,元数据就可在开头发送一次,然后针对 两个对等体之间的其余通信这可用于在线播放 为一个永久性媒介实例指定多个 FIDL 对象。例如: 快速数据报套接字 RFC 可以避免向 每个 UDP 数据报,其首先通过套接字发送元数据,然后发送 以编码形式显示多个对象。
在此过程中,我们采用了一个限制条件,即元数据必须与 只依赖于 FIDL 的版本, 将运行时编译到消息的生产方中这也意味着 编码器不得在运行时随意切换有线格式的表示法; 应该支持多种线上格式表示法。
元数据加收的 8 个字节的额外税费似乎并不严格 价格高昂。始终在消息中包含它可减少许多额外的元数据 来跟踪用户操作复杂性end 的值。
适用于真正需要原始性能的用例 元数据,我们可以考虑在 FIDL 中以原生方式添加流式传输功能。对于 例如,可以想象定义一种通过套接字进行的传输, 一种类型的值。可以通过实现绑定来跳过发送 元数据,并禁止更改发送者/接收者身份( 因此,每次有新的对等设备连接时,都必须重新建立 )。
@stream
protocol UdpSocketPayload over zx.socket {
Send(SendMsgPayload);
-> Recv(RecvMsgPayload);
};
通过使元数据始终特定于某个消息,这也支持在 按消息进行比对例如,我们可以使用一个标志 经过压缩处理。还可以想到,我们可以设计出更紧凑的电线 较少以 内存中的 IPC(例如,无需为指针或对齐预留空间)。通过 备用格式可以由 元数据。
替代方案 3:使用事务性消息标头
持久化期间的传输格式元数据可以与 事务性消息标头。
为此,我们将持久化构建为一种传输, 写入对象:
@persistence
type Metadata = table {
1: foo int32;
2: bar vector<int64>;
};
// desugars to
protocol MetadataSink over persistence {
// Ordinal is the hash of `mylib/MetadataSink.Metadata`.
Metadata(Metadata);
};
这种方法具有以下优点:
- 重复使用现有的 FIDL 功能。持久性与通过 只不过您要将字节写入某种其他类型的接收器(vmo、 file, socket)。我们还可以稍后添加流式传输或多消息支持 通过添加控制数据包(控制序号)来告知消息大小, 等等
- 通过重复使用序数哈希检查来减少串扰并提高安全性: 攻击者无法通过在 数据平面(载荷)。此策略与 FIDL 方法的属性。
这看起来是一个很好的泛化传输用例, 一种非常奇怪的协议,只有单向和一次性的: 只发送一种值。接收方没有机会 做出响应。这不会与我们要添加的进化功能很好地搭配使用 例如开放式和封闭式互动。
替代方案 4:使用消息类型信息扩展线上格式元数据
与替代选项 3 相比,这种方法没有那么冒险,我们可以对完全限定的 输入保留消息的名称,并将其添加到线上传输格式元数据 确定将要保留的消息的类型,而不引入提示 代表着成熟的运输技术
这可以减少串扰,但仍然存在其他细微的复杂性:
- 如何处理类型的重命名:保留类型的名称现在变为 是 ABI 的一部分,因为它会影响元数据中的哈希。
- 如何使此机制与 FIDL 的事务性 IPC 用途协调一致:主要提案 将独立的编码/解码 API 规定为较低级别的核心功能 FIDL 的代码,其中可构建事务性 IPC 功能 顶部。这种替代方案实现两个独立的功能。具体而言, 电汇格式元数据无法从事务消息派生 标头中,因为后一种方法使用的方法序哈希值对两者都相同 请求和响应类型。
总体而言,我们认为,这一替代方案所带来的安全风险 值得额外进行复杂的工作。
替代方案 5:将独立 API 限制为非资源类型
主要方案推荐了两种用于处理 FIDL 线的公共 API 格式:
- 独立编码/解码:可以对资源类型进行编码并生成句柄。 由 FIDL 事务性消息传递实现(客户端和服务器)共享 绑定)。
- 持久性:不允许资源类型;结果都是纯数据。电汇格式 元数据始终作为一个单位,
这是因为我们观察到,使用持久性惯例 FIDL 目前的所有独立用例。
将来的用例可能更适合单独传输或存储 有线格式元数据和载荷。他们可以覆盖独立广告 编码/解码 API,但有一个缺点,那就是允许在 API 中使用句柄, 这种情况并无充分依据。
另一种方法是提供三种类型的 API:
- 绑定内部独立编码/解码:可对资源类型进行编码。已使用 事务性消息传递实现
- 公开独立编码/解码:不允许资源类型。
- 持久性:不允许资源类型。始终为线上格式元数据 。
当用例需要单独提供时,这可以改进非资源保证 传输或存储有线格式元数据,但会导致 API 令人困惑, 因为我们最终有两类编码/解码 API 唯一不同的是 对资源类型的支持这与目标语言因素有关 在有时难以对公众隐藏 API 时存在局限性 。
主提案采用简化路径,并合并前两种类型的提案 。