RFC-0279:支持特定于产品的 blob 格式

RFC-0279:支持特定于产品的 blob 格式
状态已接受
区域
  • 软件交付
说明

此 RFC 建议针对不同产品使用不同的 blob 类型

问题
Gerrit 更改
作者
审核人
提交日期(年-月-日)2026-02-02
审核日期(年-月-日)2026-03-10

问题陈述

不同类型的基于 Fuchsia 的产品可能会受益于以不同格式将 blob 存储在 blobfs / fxblob 上,这些格式针对其特定的工作负载或要求进行了优化。例如,可能需要尽可能高的压缩比、尽可能快的解压缩速度或最少的处理 RAM。目前,Fuchsia 中能够生成具有各种 blob 格式设置的 build 的能力非常有限。

摘要

此 RFC 提议引入任意 Blob 类型,并在本地存储、build、assembly、软件交付和服务器端基础架构中支持这些类型。它基于 RFC-276(“支持对 blob 格式进行更改的系统更新”)中已批准的想法和提案构建而成。不过,此 RFC 并未侧重于从类型 1 到类型 2 的迁移,而是通过以下方式将支持范围扩展到任意 Blob 类型:

  • 利用本地存储中内置的软件支持和软件交付,实现从类型 1 到类型 2 的迁移,以支持任意类型。
  • 利用软件组装支持在组装产品软件包时生成所需的 blob 类型。
  • 扩展了更新客户端-服务器通信,以便客户端可以请求特定的 blob 类型。
  • 扩展工具,以在所有受支持的 blob 类型之间进行转换。

熟悉 RFC-276:支持系统更新至关重要,因为此 RFC 是基于其概念和说明构建的。

利益相关方

辅导员:davemoore@google.com

审核者

  • csuter@google.com(存储)
  • jfsulliv@google.com(存储)
  • etryzelaar@google.com (SWD)
  • amituttam@google.com(产品经理)
  • awolter@google.com(软件组装)
  • markdittmer@google.com(安全)
  • gtsai@google.com(基础设施)

共同化

此 RFC 在软件交付、本地存储和基础架构团队的一系列内部设计讨论中进行了讨论。此 RFC 中的文本早期版本已与 build 和 Assembly 团队的利益相关方进行了审核。

要求

  • RFC-276 一样,对于生产环境中的每个客户端设备,始终可以软过渡到新的 blob 格式。也就是说,任何生产环境中的客户端设备都应能够迁移到新的 blob 格式,而不会对用户造成任何可见的影响。
  • 同样,未送达 blob 格式和给定 blob 的 Merkle 根计算保持不变。应避免为引入特定于产品的 blob 设置过渡阶段。
  • 产品配置应能够指定是否应将所有 BLOB 更新为指定格式(如果它们已以其他格式存储在磁盘上)。如果要更新现有 blob,该机制应能够完成将 blob 更新为预期格式,而不会因有意和无意的中断(例如:)对最终用户造成可见的影响:
    • 预期用途:受软件 / 配置控制,例如用于管理设备散热、读/写性能要求、测试和基准比较。
    • 非预期:断电、设备重启、迁移不完整(例如,由于连接性问题、系统崩溃导致重启等)。

设计

以下部分介绍了为满足上述要求而进行的技术变更。RFC-276 的大多数设计和实现选择都可以直接用于特定于产品的 blob。事实上,在起草 RFC-276 时,团队就预料到未来可能需要更多 blob 类型,因此尽可能避免做出会使类型 1 到类型 2 迁移变得特殊的选择。因此,此 RFC 重点介绍 RFC-276 中的设计差异和新增部分。

Blob 类型命名、旧版 Blob 类型、排序和后果

本部分将阐明有关 Blob 类型的重要细节。虽然与 RFC-207RFC-276 相比,含义或语义没有变化,但由于之前仅定义了传送 blob 类型 1 和 2,因此很容易误解语义。请务必注意,这些类型应理解为 enum尤其不应理解为版本分母。后果如下:

  • 类型 2 与类型 1不同。它满足了不同的使用情形,仅此而已。
    • 不会取代类型 1。
    • 不是类型 1 的后继者。
  • 没有排序。这些类型只是名称,没有其他含义。
    • 没有“最高支持类型 N”的概念:假设定义了 blob 类型 1、2、3 和 4,如果声明“产品 X 支持 blob 类型 1 和 4”,则表示也支持 2 和 3。

回过头来看,RFC-276 中应该为类型 2 另取一个名称,以避免这种误解(将其误解为整数)。

旧版 blob 类型

由于存在上一部分所述的潜在误解,我们今后会将类型 1 和类型 2 称为旧版 blob 类型

对于即将推出的类型,不再使用可能被解读为暗示顺序继任改进的类整数或单字母名称。而是使用描述性命名。这意味着,新的 blob 类型应通过其在枚举中的名称以及转换为字符串时的名称来传达其含义,例如:

pub enum DeliveryBlobType {
    ...
    /// Type 1 supports the zstd-chunked compression format. [legacy type]
    Type1 = 1,
    ...
    /// Uncompressed is a raw, uncompressed format [descriptive naming]
    Uncompressed = 17,
    /// Zstd compression, default settings [descriptive naming]
    ZstdDefault = 18,
    ...
}

请注意,上述示例中分配的整数仅用于说明。它们并不反映当前存在的代码,也不反映以这种exact方式实现的意图。

在将描述性类型转换为字符串时,此 RFC 建议,例如,上述示例中的 ZstdDefault 应转换为 vnd.fuchsia-blob.raw+zstd。这是因为这是一个有效的 MIME 类型字符串,非常适合下文所述的未来潜在扩展,该扩展允许进行客户端-服务器内容协商。

改用描述性 blob 类型

如上一部分所述,即将推出的 blob 类型都将采用描述性名称。这为在新产品中引入 Fuchsia 提供了一个机会,可以彻底避免使用这些产品的旧版 blob 类型。因此,此 RFC 提出了以下各种 Blob 类型的使用路线图:

  • 引入了 UncompressedZstdLevel_X_Chunksize_Y 的描述性 blob 类型,以提供一种有保证的未压缩原始 blob 类型,以及模仿现有类型 1 和类型 2 的类型。
    • 旧版 blob 文件系统(以 C++ 编写)目前预计不支持描述性 blob 类型,仅支持旧版类型。
    • 较新的 fxblob 预计将支持所有 blob 类型,并且了解旧类型及其描述性等效项之间的异同。
  • 每款产品都必须支持未压缩的 Blob 类型。
    • 基于旧版 blob 文件系统的产品必须支持类型 1。
    • 基于 Fxblob 的产品必须同时支持类型 1 和 Uncompressed
  • 如果客户端从服务器请求旧版 blob 类型,即使用类似“https://blob.domain.tld/blobs/LegacyTypeName/87fa73ed...”的网址,则服务器始终会返回 blob 类型 LegacyTypeName,无论是否设置了未压缩标志。
  • 如果客户端向服务器请求描述性 blob 类型,即使用“https://blob.domain.tld/blobs/DescriptiveTypeName/87fa73ed...”之类的网址,则服务器将返回 DescriptiveTypeNameUncompressed 的 blob 类型,但绝不会返回旧版类型(例如带有未压缩标志的 type1)。
    • 未来,如果添加了客户端-服务器内容协商(请参阅下文),服务器可能会返回请求中指示的任何受支持的类型。
  • 从商品中移除 1 类或 2 类内容需要采取过渡措施。

根据需要引入新的交付 blob 类型 <T>

RFC-207(“离线 Blob 压缩”)中讨论了交付 Blob 的引入。这些 blob 旨在实现从 blob 服务器到设备上的 fxblob/blobfs 的高效数据传输。交付 blob 包括原始数据和共享元数据(用于软件交付和本地存储)。此元数据可以指定载荷压缩类型等。

与主要解决压缩比提高需求的类型 1 到类型 2 迁移不同,此 RFC 未指定即将推出的类型(在此 RFC 中通常称为 <T>)的属性。这些即将推出的类型不会要求更改本地存储、SWD 或服务器端基础设施的设计或概念实现。因此,不会再有新的 RFC 来描述符合此 RFC 中所述方案的新交付 blob 类型。

下面提供了一些示例,展示了目前预期可能会出现的新类型:

  • 压缩率高于类型 1 或类型 2。
  • 解压缩速度比类型 1 或类型 2 快。
  • 不同于类型 1 或类型 2 的压缩算法。
  • 差分编码,使用补丁格式将现有 blob 更新为较新的 blob,作为 OTA 的一部分。

虽然从技术上讲,此设计可实现上述所有功能,但这并不意味着我们会在可预见的未来实现所有这些功能。

引入新 blob 类型带来的后果是 RFC-276 中的说明的泛化:

  • Fuchsia 平台代码(本地存储、软件包管理、ffx)将能够处理先验存在的 blob 类型以及新引入的 blob 类型。
    • 如果某个 BLOB 类型不仅在未来的 build 中被停用,而且还打算完全移除,包括可以处理它的平台代码(例如从代码库中移除压缩算法),那么需要按照 RFC-276 中的说明执行适当的过渡程序。
  • 对于涉及 TUF 代码库的本地开发工作流,无法保证 fx ota 始终成功,因为目前没有基于 TUF 的机制来强制通过过渡版本进行多阶段更新。对于这些情况,开发者应通过 ffx target flash 设备来解决问题。

当特定产品的程序集配置已更改为默认创建 blob 类型 <T> 时:

  • 默认情况下,商品包将仅包含类型为 <T> 的交付 Blob。
  • 系统更新程序将开始请求类型为 <T> 的 blob。
  • 服务器端软件包提供基础架构将确保类型 <T> 和特定项目的先前默认 Blob 类型可供客户端设备使用。服务器端基础架构将可以使用 ffx 工具,根据需要动态生成所需的 blob 类型。这些源自同一“原始 blob”的不同文件是永久存储、仅缓存还是两者都不是,此 RFC 并未规定,应根据特定的后端、服务器和产品情况来决定。

从客户端的角度来看,blob 代码库的服务器端表示形式如下所示,并且可以通过“https://some_blob_repo.domain/blobs/<T>/<hash>”之类的网址进行访问:

blobs
+> 1/
   +> a68945271ac8812c85b4d7239c2ac919030ea7bc6d0bd4cec038b0643dc1e728
   +> ...
+> 2/
   +> a68945271ac8812c85b4d7239c2ac919030ea7bc6d0bd4cec038b0643dc1e728
   +> ...
+> some_other_type/
   +> a68945271ac8812c85b4d7239c2ac919030ea7bc6d0bd4cec038b0643dc1e728
   +> ...
+> yet_another_type/
   +> a68945271ac8812c85b4d7239c2ac919030ea7bc6d0bd4cec038b0643dc1e728
   +> ...

Blob 重写场景

此 RFC 旨在解决以下潜在的迁移场景:

  1. 作为 OTA 的一部分,将 blob 类型 <T> 完全迁移到类型 <U>:在这种情况下,blob 存储会确定当前存在的 blob 的类型。随后,它可以根据 RFC-276 中描述的机制,向软件包管理堆栈指示在 OTA 期间是否需要重新下载特定 blob。
  2. 更一般的情况是,BLOB 存储区目前包含多种不同 BLOB 格式的 BLOB,即迁移是从类型 <Ta, Tb, Tc, Td, ...> 到类型 <U>。不过,这同样适用上述概念和实现:BLOB 存储区将确定某个 BLOB 不属于所需格式 <U>,并向软件包管理堆栈指示需要替换该 BLOB。
  3. 逐步迁移:请务必注意,所有 blob 的迁移(如上文各要点中所述)在一般情况下不一定需要,甚至不一定理想。一个需要将完整迁移作为 OTA 的一部分的特殊示例是,当新引入的 blob 类型提供远胜于以往的压缩功能,并且已知特定产品的存储空间非常有限时。不过,由于产品的 blob 存储空间将支持之前在产品上使用过且尚未完全替换的所有 blob 类型,因此对于存储在 blobfs/fxblob 中的 blob,没有要求它们必须是同一类型。因此,在大多数情况下,只有新下载的 blob 采用所需格式。随着时间的推移,之前存储格式的 blob 数量会不断减少,因为在被具有不同哈希值(由于 blob 本身发生了一些变化)的新 blob 替换后,这些 blob 会被垃圾回收。

第一点描述了以下情形:作为 OTA 的一部分,blob 存储空间中可用的每个 blob 都将在新的交付类型中重新下载,并替换现有的 blob。后者(“逐步迁移”)不会覆盖现有 blob。这是一个重要的区别,可用于解决一个特定难题:在逐步迁移的情况下,很难推断出设备 Blob 存储在任何给定时间的可用存储空间的确切量。对于存储空间充足的设备,这通常不是问题,但对于存储空间受限的设备,这可能会成为问题。因此,迁移到任何即将推出的类型 <T> 都可以逐步进行,也可以一次性完成,具体步骤和流程请参阅 RFC-276

迁移是逐步进行还是需要完全重写,将取决于程序集的配置设置,就像为产品设置所需的 blob 类型一样。因此,blob 存储可以与软件包管理堆栈通信,以确定哪些 blob 需要以新编码重新下载,并采用 RFC-276 中定义的相同机制:

fn do_ota() {
    ...
    for hash in list_of_expected_hashes {
        let blob = match get_blob_by_hash_from_blob_storage(hash) {
            (Some(blob), info) if info.desired_type => blob,
            _ => {
                let blob = fetch_blob_from_server(hash);
                write_blob_to_blob_storage(&blob);
                blob
            }
        };
    }
    ...
}

对于逐步迁移,info.desired_type 值始终为 true。这样可以避免重新下载 Blob 存储空间中已有的任何编码格式的 Blob。

针对 Blob 逐步迁移的风险缓解措施

上文介绍了“逐步迁移”的概念,该概念在 RFC-276 中有意未纳入范围。在执行逐步迁移时,存在一个概念性难题,需要单独考虑。为了说明这一挑战,以下场景考虑了以下情况:

  • 多种 Blob 类型迁移会在较长时间内发生(类型 A -> 类型 B -> 类型 C),此外
  • 在任何时间点,Blob 存储空间中的每个 blob 都不会以类型 B 的形式存储。
  • 自动大小检查机制会根据当前系统映像和预期更新的压缩 build 制品的尺寸进行估计,以确定 OTA 是否适合设备。从概念上讲,为了便于计数,它将可用空间视为两个大小相等的范围,这两个范围必须足够大,才能容纳“旧”的一组 blob(在执行 OTA 之前)和“新”的一组 blob(系统在启动到更新后的版本后所需的 blob)。

下图展示了这种情况随时间的变化。

大小检查病理案例

导致此问题的流程如下:

  • 阶段 1:在开始首次迁移之前,系统上的每个 blob 都以类型 A 存储。在安装开始向 B 类型逐步迁移的 OTA 之前,大小检查机制会计算出设备上应有 50% 的可用存储空间。执行 OTA 后,设备会重新启动到新系统,该系统会在每次下载新 Blob 时使用新的 B 类 Blob。
  • 第 2 阶段:在多次 OTA 更新过程中,大多数 blob 的哈希值会逐渐发生变化。随后,A 类 blob 会逐渐被 B 类 blob 取代。不过,有一种特别大的 A 类 blob(图片中的“ab7fe7331...”)不会在已将该 blob 存储在 A 类中的设备上替换,因为其内容不会更改。
  • 不过,由 build 服务器生成的 build 包含类型 B 的 blob ab7fe7331(刷入新 build 的设备也包含类型 B 的 blob ab7fe7331)。如果 blob ab7fe7331 在类型 B 下所需的存储空间比在类型 A 下少(例如,由于压缩效果更好),那么大小检查机制会低估许多已部署设备上 blob ab7fe7331 的存储空间要求,因为这些设备仍以类型 A 保存 blob ab7fe7331。
  • 第 3 阶段:在多次 OTA 过程中,除了 blob ab7fe7331 之外,所有 blob 都已更新为类型 B。
  • 第 4 阶段:现在,系统会规划向 C 类型的迁移。此外,由于 blob ab7fe7331 已被具有不同哈希值的较小 blob 取代,因此不再是 OTA 的一部分。随后,整个 OTA 更新到新版本现在需要略低于 50% 的容量。
  • 第 5 阶段:由于 blob ab7fe7331 仍为 A 类型格式,并且在下次启动之前是运行系统所必需的,因此 OTA 的 blob 下载将失败,因为“blob ab7fe7331 + 所有 B 类型 blob + 所有 C 类型 blob - 以 B 类型格式为 blob ab7fe7331 预留的空间”的总大小大于 100%。

对于存储空间充足的系统,此场景预计不会成为实际问题,但对于基于 Fuchsia 的产品(其完整 OTA 大小往往接近 Blob 存储容量的 50%),此场景就变得相关了。

值得注意的是,上述情形之所以会发生,是因为所描述的大小检查机制具有渐进式特性,即它会根据“当前”和“下一个”Blob 集来计算存储空间要求。可以设计一种保守的机制,用于记录曾交付给产品群组的每个 blob 的最大压缩大小,并根据以下假设计算大小要求:设备可能具有所有应在其 blob 存储空间中提供的哈希的最大可能 blob 集。

此机制的缺点是,它必然会导致大量误报,因为很可能没有一个设备真正拥有这个最大的 blob 集。这随后会迫使开发团队在比实际需要更紧张的存储预算下工作。

由于上述存储空间受限设备存在的问题,此 RFC 建议制定一项政策,以便始终对这些设备执行完整迁移。这样一来,就可以轻松推断出可用存储空间。

如果由于产品特定要求(例如违反关键发布计划)而无法进行完整迁移,则在迁移到 C 类型之前,应完成存储空间受限设备的完整迁移,例如在上述插图的第 3 阶段。这样一来,就可以确保渐进式大小检查机制的计算结果是真实的,并且与设备上的实际分配相符。产品发布团队需要相应地规划其发布时间表和流程。

实现

此 RFC 的实现旨在与从类型 1 到类型 2 Blob 的迁移 RFC-276 并行完成。由于在许多方面,此 RFC 是对前者所述概念的概括,因此将两者一起实现会更高效。随后,尽管此 RFC 不会强制实施无条件跳板,但该实现将嵌入到 RFC-276 的三阶段方法的第一阶段中,从概念上讲,如下所示:

  • 在 Blob 存储层(本地存储)中实现对 Blob 覆盖和可更新状态的支持。
  • 在软件包管理堆栈中实现对类型为 <T> 的交付 blob (SWD) 的支持。
  • 实现对通过 ffx 将 blob 类型从 <T> 转换为 <U> 的支持。(SWD)。
  • 支持创建包含类型为 <T> 的交付 blob(程序集)的产品包。
    • 在此 RFC 中,默认设置不会发生变化。
  • 支持提供类型为 <T> 的交付 blob(基础架构)。
    • 如果客户端请求之前的默认 blob 类型,服务器端基础架构将继续能够提供该类型,必要时可通过使用 ffx 进行实时转换。
  • 支持公开 Blob 重写政策(本地存储)的状态标志。

完成此操作并确认服务器端基础架构可以提供 <T> 类型的 blob 后,系统会调整指定 <T> 类型消费者的产品配置,从而使后续 build 开始提取 <T> 类型的 blob,而不是之前的类型。

性能

此 RFC 提议的功能预计会在某些情况下对性能产生一定影响,但由于尚未定义即将推出的 Blob 类型,因此我们目前无法对其进行精确量化。不过,从定性角度来看,可以预期以下情况:

  • 如果 OTA 包含完整的 blob 类型迁移(即所有 blob 都被重写),则由于系统会重新下载并重写之前格式的所有 blob,因此所需时间会比平时更长。
  • 每当引入一种编码方式与现有 blob 类型不同的新 blob 类型时,组装期间的性能可能会发生变化。具体情况取决于具体细节,可能会变慢或变快,变化幅度可能较小,也可能较大。目前无法确定这一点,并且这超出了本 RFC 的范围。
  • 每引入一种新的交付 Blob 类型,测试数量都会增加,以确保新类型按预期运行。这会略高于线性扩展,也就是说,针对每种现有 blob 类型定期运行的测试套件现在也会针对新引入的类型运行。除此之外,还可以根据需要测试从特定类型迁移到新类型以及回退到其他类型的其他测试。
    • 根据要求,可能无需在每个产品 / 主板配置上测试每种 BLOB 类型。这需要根据具体情况来确定,并且此 RFC 特意未对最终的测试矩阵设置要求。产品和测试团队应确保其产品所选的 blob 类型受测试程序支持,并且可以选择不参与特定产品肯定不在测试范围内的测试配置。

工效学设计

如前所述,ffx 工具已获得交付 blob,最初支持类型 1,后来支持类型 2,但截至撰写本文时,对类型 2 的支持尚未完成。

此 RFC 规定,在本地存储和软件交付中引入其他 blob 类型时,必须始终在 ffx 中支持新类型。因此,对于任何给定的 blob 类型对 <T>、<U>,以下情况应始终可行。请注意,这些命令仅为概念性示例。 即将推出的实现可能会选择不同的语法、子命令命名和/或提供其他功能:

# Encoding an uncompressed blob into <T>
$ ffx package blob compress --type <T> --output <output_file> raw_input_file

# Encoding an uncompressed blob into <U>
$ ffx package blob compress --type <U> --output <output_file> raw_input_file

# Recompressing a <T> blob into <U>
$ ffx package blob decompress --output raw_blob v1_compressed_input_file
$ ffx package blob compress --type <U> --output <output_file> raw_blob

与之前的 blob 类型一样,ffx 将能够检测到它找到的 blob 格式,因此在解码时不需要显式传递 --blob-format 实参。

与之前的 RFC 不同,此 RFC 不会更改 ffx package blob compressffx product 的默认 blob 类型。如果 ffx 或程序集要使用新定义的 blob 类型之一,则需要通过命令行实参或程序集配置显式启用它们。

请注意,在撰写本文时,我们正在考虑对人体工程学进行一项小幅潜在更改:目前,如上例中所述,ffx 命令表示 blob 类型仅与压缩有关。虽然最初是这样,但现在不一定如此。例如,生成增量 BLOB(即两个 BLOB 之间的二进制差异,用于节省数据传输带宽)也是一种明显的 BLOB 类型。随后,我们考虑在未来分别调用 ffx 子命令 ffx package blob encodedecode

向后兼容性

与之前对磁盘上 blob 格式的更改一样,引入新类型 <T> 不会中断基于 Fuchsia 的产品或开发工作流程的运行。因此,设计和实施方案力求确保向后兼容性。

正式版

由于引入此 RFC 中描述的 blob 类型不一定需要将产品从其他 blob 类型迁移,因此此 RFC 并未规定新 blob 类型是否需要过渡。这取决于具体的产品情况。

不过,我们确实需要服务器端基础架构来确保产品始终可以下载它能够理解的一组 blob。如上文“设计”部分所述,当客户端请求特定格式时,服务器后端会利用 ffx 动态转换 blob 类型。

这种方法使我们能够支持几乎无限期的迁移:引入新的 BLOB 类型时,无需使用过渡方案,但完全弃用不再使用的 BLOB 类型(即放弃对它的服务器端支持)时,需要使用过渡方案。这是因为从非常旧的版本进行更新可能不知道将升级到哪种 blob 类型,因此在系统更新到当时最新的版本期间不会请求新的 blob 类型。您可以先强制更新到支持已弃用 blob 类型的最新版本,然后再更新到最新版本,这样即可解决此问题。但同样,这不一定适用于所有特定于产品的 blob 类型。不过,如果出现这种情况,则应遵循 RFC-276 中规定的程序。

开发

在迁移期间,开发主机将从发布类型 <T> 更改为类型 <U> 交付 blob。与 RFC-207RFC-276 类似,开发主机一次只能支持一种 blob 格式。因此,主机端服务器可能不会保留旧版 blob 格式以实现向后兼容性。

与生产 build 不同(如果需要,可以通过过渡 build 强制更新),开发工作流缺少通过特定 build 强制更新的机制。因此,fx ota 将依赖于设备端回退,这意味着 fxblob/blobfs 将根据传入的 blob 处理必要的操作。

安全注意事项

此 RFC 对攻击面的影响超出了 RFC-207 中说明的范围。这样,无需特殊准备和后续 RFC 即可更改 Blob 的磁盘上格式。因此,打算引入新 blob 类型的团队应与安全团队密切协调,尤其是在以下方面:

  1. 在大多数情况下,引入新的 blob 类型时,预计会同时引入新的 blob 编码器(例如压缩算法)。此类编码器过去曾被成功利用。因此,引入新编码应通过安全审核,以验证其是否适合预期用途。这适用于对格式本身、实现和发布进行审核。
  2. 需要不断更新编码器和解码器,以确保 Fuchsia 中包含所有已知漏洞的修复。
  3. 应针对所有受支持的 blob 格式执行模糊测试,以便尽早发现潜在的漏洞。

事实上的 blob 类型弃用

引入新的 blob 类型时,总是会存在新的 blob 类型事实上的弃用旧 blob 类型的问题。虽然上文明确指出,Blob 类型通常反映不同的使用情形,并且某些类型不打算取代其他类型,但实际上这种情况可能会发生。

例如,新推出的压缩算法在特定产品的所有相关指标方面可能都优于已建立的算法。如果发生这种情况,并且 Fuchsia 平台继续支持之前的算法,则必须确保处理其他算法的代码继续更新并监控安全问题,以防止降级攻击。测试部分包含有关此类攻击的测试的其他详细信息。

因此,当引入可能会导致另一个 blob 类型事实上的弃用的新 blob 类型时(例如,由于技术范围重叠),产品和发布团队应考虑引入过渡方案。此过渡步骤会在切换到新 blob 类型后停用产品上之前使用的 blob 类型,也就是说,在 OTA 期间,正式版设备将拒绝将旧 blob 类型写入 blob 存储空间。

隐私注意事项

此 RFC 不包含任何与隐私权相关的变更。自 RFC-207 以来,客户端和服务器之间就一直在传递交付 blob 类型。此外,此 RFC 未提议对数据传输进行任何其他修改。

测试

由于此 RFC 是之前 RFC 的逻辑扩展,因此 RFC-276 中描述的测试概念也适用于此 RFC。它将再次涵盖自动化测试和手动测试。

自动测试

RFC-207RFC-276 开发的自动化测试代码将进行扩展,以涵盖对新引入的 blob 类型的测试。由于后一个 RFC 在设计时已假设最终会开发和部署其他 blob 类型,因此测试概念专门设计为满足此要求的通用概念。这意味着从类型 <T> 到 <U> 的迁移等测试场景已纳入设计中,并且扩展自动化测试以包含新的 blob 类型非常简单。因此,此 RFC 未对已设计的测试概念提出任何其他更改。

Blob 降级攻击测试

在实现此 RFC 后,我们将引入对抗性测试,以验证针对 blob 降级攻击的弹性。具体来说,一个可能的场景是产品使用的某种 blob 类型被事实上的弃用,如本 RFC 前面所述:当产品从一种 blob 类型迁移到另一种时,背后是有战略原因的。为了涵盖这种情况,我们将进行高级别测试,以确认在请求类型为 <U> 的 blob 时收到类型为 <T> 的 blob 会导致错误,并且收到的类型为 <T> 的 blob 不会写入磁盘。

请注意,该论文中有一个关于未来工作的版块,建议引入以下政策:接受多种 Blob 类型、设置 Blob 类型偏好以及定义关于可接受和不可接受的 Blob 类型的政策。如果 / 何时实现此功能,则需要相应地扩展 blob 降级攻击测试,以确保完全覆盖结果案例。

手动测试

对于产品,测试团队进行的手动 OTA 测试和后续的内部测试 build 将验证自动化测试的测试结果,并确认引入新的 blob 类型不会导致意外问题。手动测试是针对具体的产品要求执行的,也就是说,不会手动验证每种可能的迁移排列。

文档

此 RFC 获得接受后,我们将审核 BlobFSOTA 更新的文档,并在必要时进行更新,以反映因引入新的 blob 类型而发生的变化。

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

替代方案

针对特定于产品的 Blob 类型的服务器端支持的替代方案

有一种可能的解决方案,可以在服务器端不知道整个交付 blob 概念的情况下,将特定于产品的 blob 交付给设备。如果 blob 哈希不是仅根据原始二进制内容计算的,而是根据元组 (content, blobType) 计算的,那么不同的 blob 类型(具有不同的设置和/或适用于指定商品)最终会具有不同的名称。在此实现中,服务器不必区分不同的类型,而是能够从看起来只包含所有 blob 的一个目录中提供所有内容:

blobs/
+> ...
+> a68945271ac8812c85b4d7239c2ac919030ea7bc6d0bd4cec038b0643dc1e728
+> 76d4f97c1aaf486fabeabdce2095aa9d736f72e1257acd654dcbbddeb4564c28
+> ...

在此示例中,列出的两个文件具有相同的载荷,但由于 blob 类型不同,它们的哈希值(因此文件名)也会不同。然后,Blob 服务器可以从同一目录中将这些文件提供给客户端。

坚持采用原始的 Fuchsia 设计,并仅从原始载荷的哈希(即不压缩或编码)派生名称,将生成看起来更像多个目录的内容,这些目录以交付 blob 类型命名:

blobs
+> 1/
   +> a68945271ac8812c85b4d7239c2ac919030ea7bc6d0bd4cec038b0643dc1e728
   +> ...
+> 2/
   +> a68945271ac8812c85b4d7239c2ac919030ea7bc6d0bd4cec038b0643dc1e728
   +> ...
+> next_generation_type/
   +> a68945271ac8812c85b4d7239c2ac919030ea7bc6d0bd4cec038b0643dc1e728
   +> ...

这两种设计完全可行,但更改当前内置于 Fuchsia 平台和工具中的设计并不会带来明显的好处,因此无法证明额外的工程工作是合理的。

因此,此 RFC 保持当前设计,并继续仅从原始内容派生文件名。从客户端的角度来看,该设计继续利用简单的路径分隔符来避免歧义。

未来的工作

用于接受和拒绝下载的 Blob 类型的政策

由于可能存在许多不同的 blob 类型,因此未来可能需要实现以下功能:如果服务器上没有所请求的 blob 类型,则自动回退到其他类型。目前,Blob 下载器要求 Blob 以指定格式提供,如果 Blob 无法以指定格式提供,则会出错。

不过,如果实现一种可接受多种类型的方案,并且客户端可以按顺序传达偏好设置(如下一部分所述),那么明确定义产品的政策会很有用。这些政策可能有助于防止降级攻击,方法是仅允许非常特定的替代类型,从而拒绝下载和存储事实上的已弃用格式。虽然此 RFC 并未规定确切的技术实现,但建议以清晰、简洁、明确的方式提供这些政策,而不是隐式地分散在多个编译单元或组件中。

请务必注意,实现此类功能需要扩展测试,以防范 blob 类型降级攻击,如测试部分中所述。

客户端和服务器之间非标量的 blob 类型协商

本文档中描述的设计依赖于客户端通过网址中的标量分隔符将所需的交付 blob 类型传达给服务器,例如以下网址:

https://some-package-server.com/blobs/next_generation_type/a68945271ac8812c85b4d7239c2ac919030ea7bc6d0bd4cec038b0643dc1e728

其优势在于请求方案非常清晰且易于理解,实现方式也十分简单。虽然目前没有已知的具体要求,但未来可能会出现这样的需求:向客户端交付哪些 blob 取决于多个因素,而不是单个标量值。例如,假设客户端可能会向更新服务器发送请求,指定首选交付类型为“AB-positive”,同时指明“B-negative”也是可接受的替代类型。这可以通过几种可能的实现方式来实现,其中两种如下:

  • 更复杂的请求网址,用于传递必要的信息。虽然这可行,但与下一个项目符号中的解决方案相比,这种做法相当不寻常。
  • 随客户端请求一起传输到服务器的其他 HTTP 标头。一种常见的实现方式是依赖于 MIME 类型和 http Accept 标头,请参阅下文。

请注意,由于目前尚无具体要求,此 RFC 并未强制要求实现,而是将此归类为潜在的未来工作。

通过 MIME 类型和 Accept 标头进行内容协商

满足此请求的一种潜在实现方式是利用特定于供应商的自定义 MIME 类型和 HTTP“Accept”标头。举个具体示例,本部分的剩余内容假设我们正在研究三种不同的 blob 类型:

  • 未压缩的 blob。
  • 使用 lz4 算法压缩的 blob。使用 zstd 算法压缩的 blob,具有可变压缩级别和块大小。

如果更新服务器实现了相应的内容协商对等项,则上述三种类型可能具有以下 MIME 类型:

  • application/vnd.fuchsia-blob.raw
  • application/vnd.fuchsia-blob.raw+lz4
  • application/vnd.fuchsia-blob.raw+zstd; level=<L>; chunk=<C>

在此实现中,Blob 类型不再使用“类型 1”等简单术语来描述。随后,blob 标头可能不再只包含一个 u32 整数来表示 blob 类型。相反,它可能会获得一个额外的字段,该字段的长度可能可变,用于包含描述 blob 类型的字符串。

然后,系统更新客户端会请求一个网址,概念上类似于:

https://some-package-server.com/blobs/<doesnt_matter>/a68945271ac8812c85b4d7239c2ac919030ea7bc6d0bd4cec038b0643dc1e728

但它会发送一个带有包含 Accept 标头的标头的 HTTP 请求,例如:

Accept: application/vnd.fuchsia-blob.raw+zstd; level=3, application/vnd.fuchsia-blob.raw+lz4; q=0.8, application/vnd.fuchsia-blob.raw; q=0.5

在此场景中,更新服务器可以基本上忽略 blobs/hash value 之间的网址段,并根据 Accept 标头中传达的偏好设置来提供哈希。

对于处理从 Blob 服务器到目标设备并进入其存储空间的 Blob 传输,有多种可能的选项似乎可行,但它们各有优缺点,尤其是以下选项:

  1. 交付 blob 格式保持不变。软件包管理堆栈会维护一个将交付 Blob 类型映射到 MIME 类型字符串的表。存储代码会维护交付 blob 类型在 blob 文件系统实际代码方面的转换。此方案的主要优点在于,无需对存储或软件包管理中的当前交付 Blob 实现进行任何更改。不同之处在于,在请求 blob 时,解析器 / blob 下载器会查询上述表格,以确定从服务器请求 blob 时相应的 MIME 类型字符串。缺点是,MIME 类型与实际代码之间的映射关系在软件包管理代码和存储代码之间拆分。每当引入或停用新的 Blob 类型时,都需要手动保持此属性同步。与下文介绍的另一种方法相比,这种方法可能更容易出错,但仍然非常简单,因此因这种拆分而导致问题的可能性很小。
  2. 传送 blob 格式可以直接封装 MIME 类型。这需要进行更改,如下例所示。在此实现中,我们目前用于映射到定义 Blob 类型的枚举的整数类型字段仍将用于旧版类型。此字段中的预留 magic 值将指示要使用 MIME 类型字段,而不是旧版类型字段。这样做的好处是,在软件包管理堆栈中,无需使用表将 MIME 类型映射到枚举,也无需从那里映射到存储中的实际功能。相反,存储空间可以立即根据 MIME 类型进行 match。缺点是,Blob 标头引入了另一个可变长度字段,这会增加复杂性,并且需要更改 Blob 类型本身。

更改后的潜在结构

此 RFC 目前并未强制要求使用 MIME 类型和 Accept 标头来实现,但如果出现内容协商的具体要求,则可展望未来可能的实现。如果确实如此,我们将在后续的 RFC 或设计文档中讨论具体实现。

机会性在线迁移

此 RFC 侧重于“服务器端提供”的迁移,假设在从类型 <T> 到类型 <U> 的迁移中,类型 <U> 的所有 blob 均通过 blob 服务器提供,并遵循 RFC-207(离线 blob 压缩)中规定的设计和软件分发机制。

不过,在某些情况下,执行在线(即在设备上)迁移可能比从服务器下载类型 <U> blob 更有利,例如:

  • 对于特定迁移方案的最重要指标(例如,最大限度地减少总体网络活动、具有特别强大的处理器的设备、非常高效的压缩算法等),最好执行在线迁移并在设备本身上重新编码 blob。
  • 作为上一点的特例,有一种情况是类型 <U> 为未编码 / 未压缩的 Blob。在这种情况下,几乎总是最好在设备本身上以在线方式执行迁移方案,而无需从 blob 服务器下载未压缩的 blob。

从概念上讲,这些迁移可能比 RFC-276 或本 RFC 中描述的迁移更简单,但本 RFC 有意不涉及这些迁移。由于至少部分此类场景可以在不改变软件向设备交付方式的情况下实现,因此在实现之前可能不一定需要后续 RFC。

在先技术和参考资料