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 等)。
    • 移除手動編號的序數也會移除這個不一致的樣式,而作者也不需決定是否要決定要使用的樣式。
  • 介面繼承可能會導致序數發生非預期的衝突。到目前為止,我們已進行過兩次嘗試來解決這個問題:
    • 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 位元核心中最重要的位元,因此請務必使用 31 位元)。

虛擬程式碼:

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 相容性,這是手動指定關係的優點之一。舉例來說,如果想在 Science 介面中將 Investigate 方法重新命名為 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)
  • Salt (abarth;由於建議新增編譯器指定的鹽,而非替代名稱,因此略有不同)
  • OrdinalName

我們選擇 Selector,因為我們認為這項屬性能更能反映屬性的意圖,而非 WireName 或 OriginalName。

我們選擇讓程式設計師指定序數名稱,而不是序數索引,原因如下:

  • 要求索引的情況更為繁瑣 (例如,如果發生衝突,請複製及貼上原始 SHA-256 雜湊值)。
  • 指定序數名稱可啟用與 ABI 相容的方法重新命名。
  • 指定名稱 (而非索引) 時,抽象層級可在程式設計人員編寫介面時維持相同的抽象層級,而不必降低某一抽象層的結構,而是讓這些抽象層成為序數的考量。

零序數

0 是無效序數。如果方法名稱雜湊為零,編譯器會將其視為雜湊衝突,並要求使用者指定不會雜湊為零的 Selector

我們考慮讓 fidlc 透過確定性轉換自動重新雜湊名稱,但認為:

  • 這類演算法皆無法察覺
  • 零大小寫的情況非常罕見

因此,這個方法不會必須同時滿足人體工學和編譯器實作的需求。

事件

這個 FTP 也涵蓋事件,這些事件經 FIDL 語言文件4 視為方法的子集。

編譯器與繫結變更

我們認為只要修改 fidlc 就能支援一般雜湊,不需要修改程式碼產生的後端。這是因為 fidlc 會計算序數,並在 JSON IR 中發出這些序數給後端。

因此不必變更繫結。

導入策略

我們打算在不同階段進行實作:

  1. 新增程式碼至 fidlc 以計算雜湊。
  2. 為程式庫新增屬性支援。
  3. 將意圖變更為 fuchsia eng 廣播,以便瞭解潛在問題。提議在特定日期終止人工判決處罰,也就是我們預計下個步驟會完成的時間。
  4. 在同一個 CL 中: a. 修改 FIDL 文法的介面方法規則,以便選用序數;詳情請參閱下文。 b.忽略手動指定的序數,並針對傳遞至產生程式碼的後端的序數名稱使用雜湊序數。 c.新增 Selector 屬性,手動修正任何現有的雜湊衝突。
  5. 請在兩週內測試各項變更,確保實際工作環境中沒有問題。 這次編寫的新 FIDL 介面不應使用序數。 b.手動序數被視為已淘汰,但 fidlc 不會發出相關警告。 c.與團隊合作,確保手動指定的序數不會保留在介面中。兩週後,請更新 FIDL 格式器以移除序數,然後大量套用至整個 Fuchsia 樹狀結構。
  6. 移除對手動指定序數的支援。

上述方式屬於軟性轉換;將 fidlc 變更為使用雜湊序數 (步驟 4b) 不應破壞滾動器,因為滾輪是從整個樹狀結構的單一版本建構。

jeremymanson@google.com 對此 FTP 的實作中,他選擇偏好手動指定的序數,而非雜湊序數 (與上面的步驟 4b 不同)。這樣做可讓所有使用手動指定序數 ABI 的現有介面與 ABI 相容,並且只會在未指定序數時使用雜湊序數。

人體工學

優點:

  • 撰寫介面應簡單明瞭。

缺點:

  • 程式設計人員必須瞭解名為 Selector 的新屬性,這有兩個用途:重新命名和衝突解決。
  • 變更方法名稱可能不容易造成 ABI 相容性問題,這與程式設計師指定的序數不同。使用者教育 (例如撰寫更好的說明文件) 可能會令人感到失望。
    • 請注意,當介面方法重新命名時,其他元件系統 (例如 COM 和 Objective-C) 通常也會破壞 ABI 相容性。因此,採用類似系統的開發人員可能十分熟悉這種行為。
  • 如果在特殊情況下失去對序數的手動控制,可能會導致偵錯能力降低,例如在同一個 Zircon 管道上使用多個 FIDL 介面。

請注意,此 FTP 的起源主要是因人體工學的原因。

說明文件與範例

我們預計會對 FIDL 屬性、文法、語言和有線格式文件進行變更。API 的可讀性評分量表文件也應更新,如 Selector 一節所述。

回溯相容性

  • 雜湊序數在設計上與手動指定序數不相容,因此與 ABI 不相容。這應該不是問題,因為
    • fidlc 變更為二進位 (會使用經過雜湊處理的 x 手動序數);以及
    • fidlc 可用來建構整個樹狀結構,因此
    • 樹狀結構的所有部分都會使用選定的序數配置。
  • 雜湊序數與 API (來源) 相容。現有來源檔案將維持相容;手動變更會遭到淘汰 (請參閱實作策略)。
  • 如果使用兩種不同的 fidlc 版本 (即平台來源樹狀結構的兩種不同版本),且 FIDL 介面用於跨機器通訊,則會發生錯誤。作者知道目前並未使用這個項目,因此應該不成問題。

效能

我們預期 fidlc 的執行速度幾乎可以忽略,因為系統現在必須在介面中對所有方法名稱進行雜湊處理,才能進行計算。

我們預期對執行階段效能產生輕微的影響。編譯器可能會為先前小而連續的手動指定序數產生跳轉資料表,在使用雜湊序數時將變為透過稀疏空間執行二進位搜尋。相同的機制也可能會以微幅的方式影響二進位檔大小。(資料表導向的調度作業可能會焦慮空間大小和速度問題)。

安全性

我們預期執行階段雜湊不會發生執行階段安全性問題,因為序數雜湊沒有變更透過 Wi-Fi 傳輸的序數值,其他執行階段均不會變更。

使用加密編譯雜湊 (SHA-256) 時,可能會誤以為雜湊需採用高強度加密;我們相信,因為:

  • FIDL 編譯器會在編譯期間檢查雜湊衝突, 需要人工輸入才能解決這些衝突
  • 我們使用 SHA-256 不進行加密,但我們希望採用的雜湊值不太可能導致發生衝突。CRC-32 (甚至是 strlen()) 也能正常運作,但可能會產生較長的衝突,造成不便。

截斷 SHA-256 雜湊可能也有某些疑慮,但再次強調,FIDL 編譯器會以靜態方式檢查雜湊衝突5,因此應該不會發生安全性問題。

測試

ianloic@google.com 已分析現有的 FIDL 介面,並判定沒有任何雜湊衝突。

我們會仔細思考如何測試實際雜湊衝突的案例,因為在設計上,很難以理想的雜湊值手動產生雜湊衝突。

否則,一般的單元測試、CQ 測試、相容性測試和手動測試應足以確保序數雜湊穩定可靠。

缺點、替代方案和未知

此 FTP 刻意僅處理介面的序數雜湊。不會提議變更列舉、資料表和擴充聯集的手動列舉序數。

jeffbrown@google.com 建議採用完美雜湊,並納入考量。FTP 作者不太熟悉正確的雜湊配置,但相信隨著時間新增額外方法會改變現有方法的雜湊,進而破壞 ABI 相容性,導致完全雜湊處理不合適。動態雜湊或許可能會用到動態完美雜湊,但會提出同樣的變更雜湊問題,而且相較於標準雜湊值,後者較不明確且複雜,這樣並不會進一步調查。

另一個移除手動序數的方法是將完整方法名稱傳送到線,後者在許多 (大部分) 其他遠端程序呼叫系統中完成 (請參閱下方的參考資料)。這會造成執行階段效能影響,可能會與 FIDL 的預期用途發生衝突。

我們認為能夠指定使用的雜湊值,方便日後變更,前提是 SHA-256 最終有其他雜湊能夠解決的問題。這種設計在安全性應用程式中很常見,因為日後廣泛使用的加密編譯雜湊值「會有安全漏洞」出現。不過,指定雜湊可能需要變更傳輸格式,並需要所有語言繫結實作程式碼來選取雜湊演算法,而會大幅簡化編譯器和繫結程式碼。我們認為權衡利弊。 我們瞭解,git 也運用了這一點與 SHA-1 技術,現在則要追蹤這方面的決策,但我們認為使用案例也有差異,足以證明透過硬式編碼方式編寫雜湊演算法。

探索

  • 有效利用空間來識別方法,可以產生高效率的第一級方法表示法,讓方法成為最優先的方法。
    • 這可能包括啟用在 FIDL 呼叫中做為引數的方法,或者讓 FIDL 方法傳回其他方法。目前可能已經有一些用途,其中方法會傳回含有單一方法的介面做為傳回實際方法的 Proxy。
  • 建議的 31 位元雜湊值可擴大延伸至64/128/53 位元;SHA-256 提供許多位元。
  • ordinal 重新命名為 selector,這是現有概念,為其他語言和元件系統提供相同用途。
  • 建議您區分方法名稱和介面名稱,因此我們有兩則不同的資料。這樣就可以分別參照介面名稱,以及不重複的方法名稱。我們可能需要 32 位元以上的資訊
  • 如上所述,列舉、資料表和可擴充的聯集不在涵蓋範圍內。不過,我們認為此 FTP 可能也適用於他們。最初想法:
    • 我們不確定列舉是否希望此功能。簡單且標準化的連續整數編號已足夠。
    • 這可依原樣套用至可擴充的聯集。
    • 資料表需要不同的傳輸格式才能採用序數雜湊,因為已封裝的表示法目前需要連續的結構。
  • FIDL 目前會保留序數,並在文件中明確指出,上位元的範圍是用於控制流程等。作者認為造成這個情況的原因之一也可能和衝突序線有關。讓我們重新學習重點嗎?
    • 將序數空間展開至 64 位元 (如上所述) 通常可以解決這個問題。
    • abarth@google.com 在 Fuchsia IPC 聊天室上建議,只建議保留 0xFFFFxxxx
  • 我們可以將方法的引數類型納入計算的雜湊值中,日後如果需要方法,就能支援方法超載。
    • jeffbrown@google.com 提到,雜湊處理完整方法簽章可能會限制介面擴充的機會,且將地圖超載於許多程式設計語言的超載問題。
  • 由於使用介面繼承時,序數雜湊應可解析序數衝突,因此也可移除 FragileBase 屬性。
    • 「程式碼搜尋」會顯示約 9 個 FragileBase 的用法。
  • 作者擔心如果介面在一段時間內經過大幅演進,如果許多方法上具有 Selector 屬性,他們可能難以閱讀。
    • 解決這個問題的一種方法是採用類似 Objective-C 類別C# 部分類別的做法,其中現有的宣告介面可以「擴充」以將屬性新增至獨立宣告中。

先前的圖片和參考資料

有趣的是,我們不知道任何其他方法分派或遠端程序呼叫 (RPC) 系統,這些系統使用方法名稱的雜湊碼識別要呼叫的方法。

大部分的遠端程序呼叫 (RPC) 系統會依名稱呼叫方法 (例如 gRPC/Protobuf 服務、Thrift、D-Bus)。針對處理中的方法呼叫,Objective-C 會使用保證專屬的字元* 指標值 (稱為選取器) 來識別應在類別呼叫的方法。Objective-C 執行階段可以將選取器對應至字串化的方法名稱,反之亦然。對於程序外的方法呼叫,Objective-C 分散式物件會使用方法名稱來叫用。COM 會直接使用 C++ vtable 進行處理中的呼叫,因此會仰賴 ABI 和編譯器支援來支援方法分派。apang@google.com 建議針對 ctiller@google.com 中的資料表提出序數雜湊。ianloic@google.com 和 apang@google.com 在 Thu 2018/white 符合這個要求


  1. Mojo/FIDL1 也不需要程式設計師指定序數,而是依序產生 (類似於 FlatBuffers 對資料表欄位的隱含標記編號方式)。 

  2. 先前,您可以建立一個繼承自其他 FIDL 介面的 FIDL 介面。不過,介面和超介面共用相同的序數空間,也就是說,如果您在介面中新增方法,可能會破壞其他遙遠的程式庫中的子介面。目前有多個建議針對 FIDL 區域展開,以解決繼承 / 序間衝突問題。但在找出解決方法之前,我們已將介面的預設值改為禁止繼承。介面仍可選擇使用 [FragileBase] 屬性允許子介面。如果遇到這個問題,編譯器應輸出錯誤訊息,並附上簡短說明。我 (abarth@google.com) 已針對「平台來源樹狀結構」中所有使用 FIDL 介面繼承的每個位置,新增 [FragileBase] 屬性 (希望如此!)。如有任何疑問或遇到任何問題,歡迎隨時與我聯絡。 --abarth@google.com

  3. 我們不相信,自動衝突解決方案會增添任何額外的實作和認知複雜度,因此不應有任何充足的判定措施。如果資料顯示序位衝突發生了問題,我們就可以重新審視這項決策,而不會破壞回溯相容性。 

  4. 如果只有宣告結果,此方法稱為事件。然後定義伺服器傳送的來路不明的訊息。 

  5. jln@google.com write,「是的,可以截斷 SHA-2 且不行,無論如何都不行。」