RFC-0020:接口序数哈希

RFC-0020:接口序哈希
状态已接受
领域
  • FIDL
说明

我们提议让程序员不再为接口方法手动指定序数。编译器会根据完全限定方法名称(即库名称、接口名称和方法名称)的哈希值生成序数。

作者
提交日期(年-月-日)2018-10-26
审核日期(年-月-日)2018-11-29

“60% 的情况回答了面试问题”

摘要

我们提议取消程序员手动修改 为接口方法 1 指定序数。 相反,编译器会根据 完全限定的方法名称,即库名称、接口名称和方法 名称。方法重命名将通过新的 Selector 属性与 ABI 兼容 (请参阅下文)。

我们明确限制此 FTP,以建议接口进行序数哈希处理 ;非枚举、表和可扩展联合体。 我们认为,这些结构的应用场景大相径庭, 需要进一步调查,并使用其他 FTP。

示例

目前,FIDL 作者会写:

library foo;

interface Science {
    1: Hypothesize();
    2: Investigate();
    3: Explode();
    4: Reproduce();
};

此 FTP 会删除序数索引:

interface Science {
    Hypothesize();  // look, no ordinals!
    Investigate();
    Explode();
    Reproduce();
};

在后台,编译器会有效地生成类似于 :

interface Science {
    // ordinal = SHA-256 of the fully-qualified method name,
    // i.e. "foo.Science/MethodName", truncated to 32 bits
    0xf0b6ede8: Hypothesize();
    0x1c50e6df: Investigate();
    0xff408f25: Explode();
    0x0c2a400e: Reproduce();
};

设计初衷

  • 手动指定序数在很大程度上是一项机械操作。 如果您完全不需要考虑编写接口, 。
  • 如果使用合适的哈希,则极不可能产生 序数冲突,这是相对于人类手动书写的 序数(尤其是在使用接口继承的情况下)。请参阅 下方的“序数冲突”部分
  • 目前,程序员必须确保不同方法的序数 避免冲突这对包含很少方法的接口来说很容易, 接口有许多方法,这可能非常重要。 对于序数,有不同的编码风格和思路 这会导致编码风格不一致。
    • 大多数接口从 1 开始,并一直向上。
    • 不过,有些开发者更喜欢将不同的接口方法分组到一起, 范围内(例如,1-10、100-110 等)。
    • 删除手动编号的序数也会消除这种不一致问题 样式,因此作者无需再决定 要使用的样式。
  • 接口继承可能会导致顺序发生意外冲突。 到目前为止,我们已尝试了两次解决此问题: <ph type="x-smartling-placeholder">
      </ph>
    • FTP-010(已拒绝)提议了 OrdinalRange 属性, 界面 继承的可预测性更高;已被拒绝。
    • FragileBase 2 是当前的临时方案, 但无法解决核心问题,那就是确保序数 。
    • 如果序数经过哈希处理,且使用了接口和库名称 计算哈希值,对序数进行哈希处理不会导致冲突 序数,从而解决接口继承问题(在外部 极其罕见的哈希冲突)。

设计

哈希

经过哈希处理的序数由 SHA-256 哈希:

library name (encoded as UTF-8; no trailing \0)
".", ASCII 0x2e
interface name (encoded as UTF-8; no trailing \0)
"/", ASCII 0x2f
method name (encoded as UTF-8; no trailing \0)

例如,以下 FIDL 声明:

library foo;

interface Science {
    Hypothesize();
    Investigate();
    Explode();
    Reproduce();
};

将具有以下字节模式,用于计算序数 哈希:

foo.Science/Hypothesize
foo.Science/Investigate
foo.Science/Explode
foo.Science/Reproduce

使用 ./ 分隔符,因为 fidlc 已输出 完全限定的方法名称,采用此格式 (c.f. fidlcNameName() 方法)。

计算 SHA-256 哈希值后:

  1. 提取 SHA-256 哈希的高 32 位(例如, echo -n foo.Science.Hypothesize | shasum -a 256 | head -c8)
  2. 最高位设为 0,从而得到有效的 31 位哈希 从零填充到 32 位的值。(自 FIDL 有线格式预留了 32 位 ordinal.)

在伪代码中:

full_hash = sha256(library_name + "." + interface_name + "/" + method_name)
ordinal = full_hash[0] |
        full_hash[1] << 8 |
        full_hash[2] << 16 |
        full_hash[3] << 24;
ordinal &= 0x7fffffff;

选择器属性和方法重命名

我们定义编译器要使用的 Selector 属性 计算经过哈希处理的序数,而不是使用方法名称。 如果方法名称没有 Selector 属性,则该方法 名称将用作 Selector。(接口和库) 名称在哈希计算中仍然使用。)

Selector 可用于在不破坏 ABI 的情况下重命名方法 这也是手动指定的 序数。例如,如果要重命名 Investigate 方法添加到 Science 接口中的 Experiment,我们可以编写:

interface Science {
    [Selector="Investigate"] Experiment();
};

我们只允许在方法中使用 Selector 属性。正在重命名 库被视为罕见库,并且保留 ABI 兼容性 在这种情况下不是高优先级。对于 对接口进行重命名此外, 具有 Discoverable 属性的重命名的接口如下所示: 令人困惑:可发现的名称是什么?

序数冲突和冲突解决

如果经过哈希处理的序数导致与其他序数发生冲突或冲突, 经过哈希处理的序数,则编译器会发出一个 并依靠人工指定 Selector 属性 以解决冲突 3

例如,如果方法名称 Hypothesize 与 方法名称为 Investigate 时,我们可以将 Selector 添加到 Hypothesize,以避免冲突:

interface Science {
    [Selector="Hypothesize_"] Hypothesize();
    Investigate();  // should no longer conflict with Hypothesize_
};

我们将更新 FIDL API 评分准则 建议在后面附加“_”添加到 Selector 的方法名称 来解决冲突。fidlc 也会提供此修正建议。

请注意,序数只需在每个接口上是唯一的, 类似于手动指定的序数。如果我们要让序数 在所有接口中是唯一的,应该在另一个 FTP。

根据信包后方的计算结果显示,31 位和 100 个方法,发生碰撞的可能性为 0 .0003%, 因此我们预计哈希冲突极为罕见。

选择器自行车架

还有其他关于“Selector”的建议:

  • WireName (abarth)
  • OriginalName (ctiller)
  • Saltabarth;略有不同,因为它建议添加编译器指定的 而不是别名)
  • OrdinalName

我们之所以选择Selector,是因为我们认为它能更贴切地反映 intent 属性。

我们选择让程序员指定序数名称, 比序索引多,原因有多种:

  • 要求索引就会更繁琐(如复制和粘贴) 原始 SHA-256 哈希值的序列),
  • 指定序号名称可启用与 ABI 兼容的方法重命名, 和
  • 而指定名称而不是索引 与编程人员编写代码时一样 而不是降低一个抽象层 需要考虑序数。

零序数

零是无效序数。如果某种方法 名称哈希值为零,则编译器会将其视为哈希冲突 并要求用户指定一个不进行哈希处理的 Selector 零。

我们考虑让 fidlc 按照 确定性地对其进行转换,但感觉:

  • 任何此类算法都不明显;
  • 零大小写的情况极其罕见,

因此这种方法并不意味着 工效学设计和编译器实现。

事件

此 FTP 还包含一些事件,这些事件被视为 方法(详见 FIDL 语言文档 4)。

编译器和绑定变更

我们认为只需修改 fidlc 即可支持 序数哈希;代码生成后端不需要 。这是因为,fidlc 会计算序数, 将其在 JSON IR 中发送到后端。

绑定无需更改。

实施策略

我们打算分阶段实现此目标:

  1. 向 fidlc 添加代码以计算哈希值。
  2. 添加了对库属性的支持。
  3. 将意向转变为紫红色 eng,让他们意识到潜力 问题。 a.提议在某个特定日期弃用手动序数, 则表示下一步已完成。
  4. 在同一 CL 中: a.修改 FIDL 语法的接口方法规则,将序数指定为可选项; 详见下文。 b.忽略手动指定的序数,并将经过哈希处理的序数用于 传递给代码生成后端的序号名称。 c.通过添加 Selector 来手动修复任何现有的哈希冲突 属性。
  5. 请在两周内测试更改,以确保不存在生产问题。 a.在这段时间内编写的新 FIDL 接口不应使用序数。 b.手动序数被视为已弃用,但 fidlc 不会发出 关于此问题的警告 c.与团队合作,确保 中 没有 手动指定的序号保留 界面。 d.两周后,更新 FIDL 格式设置工具, 序数,并将其批量应用到整个紫红色的树。
  6. 取消了对手动指定的序数的支持。

以上是 软过渡; 将 fidlc 更改为使用经过哈希处理的序数(第 4b 步)应该不会破坏 Rollers,因为 Roller 是基于整个树的单个版本构建的。

jeremymanson@google.com 实施这个 FTP 的步骤中, 他选择使用手动指定的序数而非哈希序数, 这与上述第 4b 步有所不同。这样所有现有接口 使用手动指定的序数 ABI 兼容,并且仅使用 未指定序数时经过哈希处理的序数。

工效学设计

优点:

  • 编写接口应该更简单。

缺点:

  • 程序员需要了解一个新属性 Selector,它应该 有两个用途:重命名和解决冲突。
  • 更改方法名称可能并不明显会破坏 ABI 兼容性,而程序员指定的序数则不同。 用户指导(例如改进文档)可以解决此问题。
    • 请注意,其他组件系统(如 COM 和 Objective-C) 使用接口方法时,通常会破坏 ABI 兼容性 已重命名。 使用了类似的系统。
  • 无法手动控制序数可能会导致 异常情况下的可调试性,例如有多个 FIDL 接口 用于同一个 Zircon 频道上。

请注意,作者创建此 FTP 主要是出于工效学方面的考虑。

文档和示例

我们预计会更改 FIDL 属性、语法、语言和 线上格式文档。API 可读性评分准则文档也应该更新 如 Selector 部分中所述。

向后兼容性

  • 经过哈希处理的序数与手动指定的序数不兼容 ABI。 我们认为这不是问题,因为 <ph type="x-smartling-placeholder">
      </ph>
    • fidlc 更改是二进制的(经过哈希处理的 x 或手动序数会 使用),以及
    • fidlc 用于构建整个树,因此
    • 树的所有部分将始终使用所选的序数方案。
  • 经过哈希处理的序数与 API(源)兼容。 现有的源文件将保持兼容;手动序数将被 已弃用(请参阅“实现策略”)。
  • 如果两个不同的编译版本(即两个不同的软件版本, 使用平台源代码树的 build),而 FIDL 接口 用于在机器间通信作者目前没有任何用途 所以这应该不是问题

性能

我们预计 fidlc 的运行速度可以忽略不计,因为它现在必须对所有方法进行哈希处理 用于计算它们。

我们预计不会对运行时性能产生显著影响。 编译器可能已为手动指定的序数生成跳转表 以前是较小的连续搜索,现在变成二元搜索 通过稀疏序数空间进行微调。 同一机制也可能会以微不足道的方式影响二进制文件的大小。 (表驱动的调度很可能会改善大小和速度方面的问题。)

安全

我们预计不会出现运行时安全问题,因为序数哈希没有运行时 除了更改通过网络发送的序数值以外。

使用加密哈希 (SHA-256) 可能会导致一些人认为该哈希需要 安全系数高;我们认为不存在安全问题,原因如下:

  • FIDL 编译器将在编译时检查是否存在哈希冲突,并要求 人工输入的内容来解决这类问题;
  • 我们不将 SHA-256 用于加密目的 这种可能性极小可能会导致碰撞 CRC-32(甚至 strlen())也可以使用,但可能会导致 这样会带来极大的不便

SHA-256 哈希的截断可能也会引起一些问题,但同样,我们并不 认为存在安全问题,因为 FIDL 编译器会静态检查 哈希冲突5

测试

ianloic@google.com 在分析了现有的 FIDL 接口后,确定 以保证没有哈希冲突。

我们会仔细考虑如何测试实际哈希冲突的情况, 利用有效哈希人工生成哈希冲突是一项非常困难(从设计上来讲)。

否则,您需要使用单元测试、CQ 测试、 兼容性测试 和手动测试应该足以确保顺序哈希的可靠性。

缺点、替代方案和未知问题

此 FTP 有意仅处理接口的序数哈希。 它不会提议更改枚举的手动枚举序数, 或可扩展的联合。

完美哈希技术由 jeffbrown@google.com 提出,并被采纳。 FTP 作者不太熟悉完美的哈希方案,但认为 随着时间的推移,添加额外的方法会改变现有方法的哈希值, 方法,从而破坏 ABI 兼容性,导致不适合进行完美的哈希处理。 虽然或许也能进行动态完美哈希,但也会引发 而且不太为人熟知,也比标准 因此无需进一步调查。

移除手动序数的另一种方法是发送完整的方法名称 这在许多(大多数?)其他 RPC 系统(请参阅 参考文档)。这会对运行时性能产生影响 可能会与 FIDL 的预期应用场景相冲突。

我们考虑过能指定所使用的哈希值,以便日后进行更改, (如果 SHA-256 最终出现另一个哈希可以解决的问题)。 这种设计在安全应用中很常见,其中广泛使用的加密技术 之后会发现哈希存在漏洞。 不过,指定哈希值可能需要更改传输格式, 并要求所有语言绑定实现代码,以便选择哈希算法, 使编译器和绑定代码大幅复杂化。 我们认为这种取舍不值得。 我们认识到, git也对 SHA-1 持这种态度, 现在在一定程度上回溯决策过程,但我们认为我们的用例 差异足以证明对哈希算法进行硬编码的合理性。

探索

  • 利用节省空间的方法识别方法可以提高效率 方法的一级表示法,使方法成为第一类。
    • 例如,这可允许方法在 FIDL 调用中用作参数,或者 FIDL 方法会返回另一个方法。 可以说,目前已经存在此方法的用例,其中方法会返回 一个接口,其中包含一个方法作为返回实际方法的代理。
  • 所提议的 31 位哈希可以扩展为,例如64/128/53 位; SHA-256 提供了许多位。
  • ordinal 重命名为 selector,这是一个现有概念,提供 在其他语言和组件系统中的用途。
  • 可能有必要区分方法名称和接口名称, 拥有两个不同的数据 这样,可以唯一地引用接口名称和方法名称。 为此,我们可能需要超过 32 位。
  • 如上所述,枚举、表和可扩展联合并不在范围内。 尽管如此,我们确实认为此 FTP 适用于他们。 初步想法: <ph type="x-smartling-placeholder">
      </ph>
    • 我们不确定枚举是否需要此功能。 使用更简单、标准化的连续整数编号似乎就足够了。
    • 这可以按原样应用于可扩展联合体。
    • 表需要不同的传输格式才能采用序数哈希,因为 由于采用打包表示法,序数目前需要保持连续。
  • FIDL 目前保留了序数最高位,并明确指出 一定范围的最高位用于控制流等。 作者认为造成这一现象的其中一个原因也可能与 和序数冲突。 我们要再回顾一下这个问题吗?
    • 将序数空间扩展至 64 位(如上所述)可在很大程度上解决 这个。
    • abarth@google.com在 Fuchsia IPC 聊天室中建议仅限预订 0xFFFFxxxx
  • 我们可以将方法的参数类型包含在计算哈希值中, 我们日后可能会支持方法重载。
    • jeffbrown@google.com 提到,对完整的方法签名进行哈希处理可能会限制 接口扩展的机会,并且地图过载会导致 扩展到许多编程语言。
  • 由于序数哈希应解决接口继承时序数冲突的问题 FragileBase 属性,也可以移除。
    • 代码搜索会显示 FragileBase 的大约 9 种用法。
  • 作者们担心,一个已经大幅演变的界面 如果许多方法具有 Selector 属性,则可能难以读取。
    • 解决此问题的一种方法是采用 Objective-C 类别C# 部分类, 其中已存在的已声明接口可以“扩展”具有属性 添加到单独的声明中

先验技术和参考资料

有趣的是,我们不知道有没有其他任何方法调度或 RPC 系统 使用方法名称的哈希值来标识要调用的方法。

大多数 RPC 系统按名称(例如 gRPC/Protobuf 服务、Thrift、D-Bus)调用方法。 对于进程内方法调用,Objective-C 使用保证唯一的 char* 指针 值(称为选择器)来确定应对类调用的方法。 Objective-C 运行时可以将选择器映射到字符串化的方法名称,反之亦然。 对于进程外方法调用,Objective-C 分布式对象会使用方法 为调用指定名称。 COM 直接使用 C++ vtable 进行进程内调用,因此依赖于 介绍了 ABI 和编译器支持,以支持方法调度。 apang@google.com 建议对 ctiller@google.com 中的表使用序数哈希 Phickle 提案。ianloic@google.com 和 apang@google.com 在 2018 年 10 月 4 日(星期四)会面 写在白板上


  1. Mojo/FIDL1 也不要求程序员指定序数; 而是按顺序生成(类似于 FlatBuffers 的 表字段的隐式标记编号)。 

  2. 以前,您可以创建一个继承自 其他任何 FIDL 接口。 不过,接口和父接口具有相同的序数空间, 也就是说,如果您向接口添加方法, 子接口。 我们正围绕 FIDL-land 提出几项提案,以解决 但直到我们弄清楚 我们已经将接口的默认设置改为 禁止继承。 接口仍然可以选择允许使用 [FragileBase] 属性。 如果您遇到此问题,编译器应该会输出错误消息, 并附上简要说明 我 (abarth@google.com) 添加了 [FragileBase] 属性 在平台源代码树中,所有使用 FIDL 接口继承的地方 (但愿如此!)。 如果您有任何疑问或遇到任何问题,请与我联系。 --abarth@google.com 

  3. 我们相信不存在足够的顺序冲突 自动化技术额外增加的实施和认知复杂性 冲突解决。我们可以重新考虑此决定,而不中断 如果数据表明顺序冲突变得 问题。 

  4. 如果只声明结果,该方法就称为事件。 然后定义来自服务器的垃圾消息。 

  5. jln@google.com 写道:“是的,你可以截断 SHA-2,也可以。 在哪里截断也无关紧要。”