RFC-0033:處理未知欄位和嚴格程度

RFC-0033:處理不明欄位和嚴格程度
狀態已接受
區域
  • FIDL
說明

此 FTP 修訂並闡明瞭在接觸表格、可延伸聯集、列舉和位元 (可擴充訊息) 時,包含類型不明的欄位行為。

作者
提交日期 (年/月)2019-02-07
審查日期 (年/月)2019-03-07

摘要

這個 FTP 修訂並闡明在碰觸資料表、可擴充聯集、列舉和位元 (可擴充訊息[^1]) 時,包含類型不明的欄位行為。

具體來說,我們建議:

  • 針對擴充訊息定義嚴格且彈性的行為,指定解碼器遇到不明欄位 (包括帳號代碼) 時應如何處理。
  • 可加入可延伸訊息宣告的 strict 關鍵字。這可確保在驗證期間拒絕訊息,確保接收的訊息不含任何不明欄位。
  • 預設可擴充的訊息具有彈性,即允許透過繫結並公開未知值;
  • 定義並建議繫結提供給用戶端以檢查不明欄位訊息的 API

與其他 RFC 的關係

本 RFC 修訂者如下:

提振精神

可擴充訊息是相當實用的機制,可在不中斷傳輸格式 (二進位檔) 相容性的情況下,推動資料交換格式。但是,變更結構定義會對 FIDL 解碼器提出設計決策,因為如何驗證、剖析及公開這些欄位給使用者的問題。

雖然每種語言對於資料結構的存取權各有不同的機制和規範,但指定解碼器及其 API 的行為可透過強制執行驗證行為來提升安全性,並藉由提升各類型和語言的一致性,改善整體人體工學。

我們也希望為受限的環境啟用繫結,因為有時不需要剖析不明欄位以進行正確作業,並增加不必要的效能負擔。這也適用於已達到成熟度,且應該不會進一步改善的訊息。

設計

「不明欄位」是指讀取器無法辨識序數 (資料表)、標記 (擴充聯集)、值 (列舉) 或特定位元 (位元) 的類型。為求簡潔,我們會在這裡使用「標記」參照未知的 ordinal/代碼/值/特定位元。

  • 包含不明標記的訊息「必須」通過驗證並成功剖析。
    • 不過,您可以參閱下文「嚴格」訊息的例外狀況。
  • 解碼器「必須」處理訊息中的不明帳號代碼。
    • 預設的處理行為「必須」關閉所有帳號代碼。
    • 繫結 MAY 可能會提供一種機制,讓用戶端以特別的方式處理未知的處理方式。
  • 繫結「必須」提供一種機制,用於偵測訊息中是否收到未知的標記。
  • 繫結「應」提供一種機制,用於偵測接收訊息中是否含有具有特定標記的欄位。
  • 繫結 MAY 可提供一種機制,可讀取不明欄位中的標記、原始資料和 (未輸入) 控點。
  • 如果目標語言提供在編譯時間完整檢查標記的機制 (例如 C/C++ 中的 switch()、Rust 中的 match),請執行下列操作:

    • 這個語言繫結「應」提供特殊的「未知」標記,可包含完整檢查的一部分,以便支援通用問題 (例如C/C++ 中的 default 和 Rust 中的 _) 可以省略。
    • 這項建議的用意是防止必須全部擷取的情況下才能進行適當編譯,因為如果是日後新增的標記,就不會觸發編譯器警告。
    • 這個 FTP 不會定義填寫方式的機制,因為不同語言的實作策略可能有所不同。
    • 例子:

      // Bindings SHOULD NOT offer this API:
      switch(union.Which()) {
        case Tag1: ...
        case Tag2: ...
        case Tag3: ...
        default: ...
        // no unknown tag in bindings forces handling using default case
      }
      
      // Bindings SHOULD offer this API:
      switch(union.Which()) {
        case Tag1: ...
        case Tag2: ...
        case Tag3: ...
        case Tag_Unknown: ...
        // no default case: new tags cause a non-exhaustiveness warning
      }
      

嚴格處理訊息

  • 我們引進 strict 關鍵字,可加上可延伸訊息宣告的前置字串,例如strict table T { ... }strict enum T { ... }
  • 嚴格郵件含有不明欄位的郵件「必須」視為無效。
  • 如果繫結支援這類彈性訊息機制,繫結「不得」提供特殊的「未知」標記,用於完整檢查嚴格訊息的標記。
  • 從嚴格訊息轉換至彈性訊息,反之亦然,必須支援非破壞性來源層級 (API) 變更,可透過 [Transitional] 屬性進行柔性轉換。
    • 這種轉場效果「不得」變更線路格式 (ABI)。
  • 嚴格的郵件「不會」轉換。 如果郵件標示為嚴格郵件,則只有該郵件會套用嚴格篩選。 該郵件所含的子訊息並不嚴格。

  • 語法範例:

    // One simply doesn't walk into Mordor and add a new file mode, so this is
    // reasonable to be strict.
    strict bits UnixFilePermission : uint16 {
        ...
    };
    
    // It's too dangerous for clients to ignore data in this table if we
    // extend it later, but we wish to keep the wire format compatible if we
    // do change it, so it's not a struct.
    strict table SecurityPolicy {
        ...
    };
    

導入策略

  1. 更新 FIDL 相容性測試,驗證現有語言繫結是否符合這項規格。
    1. 針對包含 (1) 僅已知欄位的訊息、(2) 僅不明欄位,以及 (3) 至少一個已知和一個未知欄位的訊息新增測試案例。
  2. 確認 FIDL 相容性測試含有所有適當類型的空白訊息測試案例。
  3. 支援在 fidlc 中使用嚴格郵件。
  4. 更新語言繫結以支援嚴格訊息。
  5. 在 FIDL 相容性測試中新增嚴格訊息的測試案例。

未來展望:使用網站修飾符

在設計階段,除了建議的宣告網站放置位置,我們也考慮允許使用嚴格關鍵字在宣告網站中使用。

語法範例如下:

protocol Important {
    SomeMethod(...) -> (strict other.library.Message response);
}

此處的 other.library.Message 可能尚未定義 strict,但我們希望能在需要嚴格驗證時使用。

這會為繫結作者增加一些設計複雜度,因為在嚴格模式和彈性模式下可能需要 other.library.Message 才能使用。

在編碼/驗證/解碼時,視背景資訊而定,為相同訊息同時公開嚴格和彈性模式,與字串或向量的處理方式並不相同。它們的版面配置相同,但邊界可能因用途而異。這也類似於在可為空值或不可為空值的結構定義中使用可擴充聯集。一般來說,繫結會選擇類型結構定義,以某種方式表示邊界、是否可為空值,或是目前正在探索的嚴格模式。

第二項問題是處理同一訊息時同時展現嚴格和彈性模式,問題在於處理訊息的組件,以及在使用者程式碼中查詢訊息。

假設其中一個列舉包含三個成員,ABC。為了公開彈性模式,我們需要特殊列舉成員「未知」。因此,現在可以組合未通過嚴格驗證的列舉,而在嚴格來說,在必須執行這個列舉的其他情況下,在編碼過程中將會失敗。再次提醒,與字串和向量的平行是非常重要的:如果沒有高度專業的 API,繫結可讓建立過長的字串和向量而無法編碼。

當同時支援嚴格和彈性模式時,應採取的策略是為彈性模式產生所有額外部分,並確保在編碼、解碼和驗證期間套用嚴格的驗證。

人體工學

這個 FTP 可透過下列幾種方式改善人體工學:

  • 我們希望能更好地設定使用者在各種語言的 FIDL 行為。
  • 嚴格的訊息可讓使用者避免編寫不必要的程式碼來處理不明欄位。

說明文件與範例

  • 針對嚴格欄位,文法和語言規格需要更新。
  • 您應更新 FIDL 樣式指南,提供何時宣告嚴格訊息的指引。

回溯相容性

  • 這項異動不會影響 ABI 相容性。
  • 如果需要變更解碼器或繫結以符合此 FTP,這些變更可能會導致來源層級 (API) 中斷,而應依個案情況解決。

效能

  • 強制解碼器和繫結符合此 FTP 規定,可能會強迫解碼器和繫結處理所有不明欄位和關閉所有控點,進而造成效能降低 (可能會有顯著影響)。
  • 繫結可能需要額外的間接層級 (並使用額外的記憶體/二進位檔大小),以便提供「不明」標記以進行完整標記檢查。

安全性

此 FTP 可提高安全性。

  • 我們會針對不明內容的郵件指定驗證行為。
  • 嚴格訊息可讓解碼器在用戶端檢查前,驗證及捨棄不明內容,降低發生錯誤的可能性。

測試

請參閱「導入策略」一節 (我們打算使用 FIDL 相容性測試)。此外,每個語言繫結都應有專屬的測試,才能宣告正確行為。

缺點、替代方案和未知

這個 FTP 主要會釐清行為並產生相關的實作費用,以確保語言繫結符合建議標準。

替代選項:預設為嚴格模式或混合模式

以類似向量或字串大小邊界的形式,在類似的光源下檢視嚴格程度;這種限制與訊息的版面配置無關,而且可以在沒有 ABI 中斷的情況下變更。

我們希望 FIDL 作者能明確選擇限制 (限制) 自己的訊息。

此外,我們也不想混合使用各種訊息,因為部分訊息 (例如列舉) 預設為嚴格,而其他訊息 (例如資料表) 則不適用。

替代選項:[Strict] 屬性,而非新關鍵字

因此,請務必選用適當的關鍵字。 在其他語言中,已有足夠程度的類似功能可以正確轉譯至 FIDL。

替代關鍵字:其他關鍵字

在設計階段,我們提議採取幾項不同的替代方案。最相近的論點是 final:代表「主題的最後一個字」,在 C++、Java、C# 中優先於其他字詞。

不過,因為我們想要在通訊協定上使用關鍵字「最終」來表示無法在組合中使用關鍵字 (亦即傳統的「最終」用法),因此選擇另一個關鍵字來表示嚴格驗證。

這讓門口保持開放,引入如下語法:

final strict protocol Important {
    MyMethod(SomeTable arg);
};

這可能表示通訊協定 Important 無法組合,「而且」所有驗證都必須嚴格執行。

其他探索的關鍵字是:sealedrigidfixedclosedknownstandardized

替代選項:僅嚴格

我們可以將所有可擴充的訊息定義為一律嚴格。目前,列舉和位元只是嚴格,因此這個替代方法將可擴展至資料表和可擴充的聯集。

在這種情況下,如果要變更可擴充結構 (例如新增欄位),讀取者在更新寫作前必須先更新「之前」。這會嚴重限制這些擴充式資料結構的使用,而且對於較高階用途來說並不容易。

此外,如果這是設計選擇,我們就不需要為資料表和可擴充聯集使用信封 (意即不需要容量數量或控點數量)。事實上,在嚴格解讀的情況下,不明欄位會遭到拒絕,否則結構定義會以類似其餘訊息 FIDL 程序的方式決定要使用的位元組數及處理次數。

替代版本:只靈活

我們可以將所有可擴充的訊息定義為永遠具有彈性。

對列舉 (和位元) 來說,這在期望的結果十分驚人。這會導致我們遇到兩個不良的子替代項目:

  • 為列舉 (和位元) 設定嚴格例外狀況的例外狀況,如上所述,這會造成混淆,並且讓語言規則更難以理解。
  • 請保持這些訊息彈性,避免出現預期的情況、開啟大門引起錯誤 (例如讀取無效值),而且確實會造成許多純粹的驗證碼,需要手動編寫,而不是透過繫結提供。

後續探索其他可擴充訊息 (資料表和擴充聯集) 時,有一個空間而且需要嚴格執行。

舉例來說,假設安全記錄通訊協定 LogEntry 定義為資料表。這項通訊協定的實作可能會希望確保用戶端不會傳送伺服器無法理解的欄位,以擔心這些新欄位可能會如何控制記錄項目的處理方式。舉例來說,較新版本可能會新增含有 PII 的記錄項目範圍「pii ranges」欄位,並且必須特別記錄 (例如替換為專屬 ID,並將原始資料保管箱放在該專屬 ID 下)。為了避免舊伺服器接受這類酬載,並可能不當處理這些記錄項目,作者會為 LogEntry 選擇嚴格模式,以免日後可能遭到濫用。

先前的圖片和參考資料

其中部分原因是由 go/proto3-unknown-fields 提供,說明為何 proto3 為何停止支援保留不明欄位,隨後反而撤銷了這項決定。

  • FTP-037:交易訊息標頭 v3 (尚未發布)

註腳 1

可延伸訊息會包含列舉和位元,因為您可以在定義訊息後加入或移除新成員。