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

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

這個 FTP 會修正並說明 FIDL 解碼器在遇到表格、可擴充的聯集、列舉和位元 (可擴充的訊息) 時,如果欄位的類型不明,該解碼器的行為。

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

摘要

這個 FTP 會修訂並說明 FIDL 解碼器在遇到表格、可擴充的聯集、列舉和位元 (可擴充訊息[^1]) 時的行為,這些欄位包含的類型不明。

具體而言,我們建議:

  • 定義可擴充訊息的嚴格且彈性的行為,指定遇到不明欄位 (包括句柄) 時解碼器應有的行為;
  • strict 關鍵字可用於擴充訊息宣告的前置字串。這樣一來,系統就能在驗證期間拒絕不明欄位,確保收到的郵件不會有不明欄位。
  • 預設可擴充的訊息為彈性訊息,也就是允許不明值並透過繫結公開這些值;
  • 定義並推薦綁定提供的 API,讓用戶端檢查含有不明欄位的訊息

與其他 RFC 的關係

本 RFC 修訂內容如下:

提振精神

可擴充訊息是一種實用的機制,可讓資料交換格式在不會破壞線路格式 (二進位) 相容性的情況下進行演進。不過,變更結構定義會對 FIDL 解碼器造成設計決策,因為在如何驗證、剖析及向使用者公開這些欄位時,會產生疑問。

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

我們也希望為受限環境啟用繫結,因為在這些環境中,解析未知欄位可能不必要,且會為運作正確性和效能帶來不必要的負擔。這也適用於已達成熟階段的訊息,且不會再進一步演進。

設計

讀者無法辨識的欄位稱為不明欄位,其序號 (表格)、標記 (可擴充的聯集)、值 (enum) 或特定位元 (位元) 皆為不明。為簡便起見,我們會從這裡開始使用「tag」來代稱未知的序數/標記/值/特定位元。

  • 含有不明標記的訊息必須成功驗證及剖析。
    • 不過,請參閱下文,瞭解嚴格訊息的例外狀況。
  • 解碼器必須處理訊息中的不明句柄。
    • 預設處理行為必須是關閉所有句柄。
    • 繫結可能會提供機制,讓用戶端特別處理不明的句柄。
  • 繫結項必須提供機制,以偵測訊息中是否收到不明標記。
  • 繫結應提供機制,用於偵測收到的郵件中是否存在含有特定標記的欄位。
  • 繫結可能會提供機制,用於讀取未知欄位中的標記、原始資料和 (未指定類型的) 句柄。
  • 如果目標語言提供機制,可在編譯時徹底檢查標記 (例如 C/C++ 中的 switch()、Rust 中的 match):

    • 該語言繫結應提供特殊的「unknown」標記,可納入全面檢查的一部分,以便提供萬用例外狀況 (例如 default (在 C/C++ 中為 _) 可省略。
    • 這項建議的用意在於避免需要使用萬用 catch 的情況,以便正確編譯,因為如果需要使用萬用 catch,日後新增的標記就不會觸發編譯器警告。
    • 由於實作策略因語言而異,因此此 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 { ... }
  • 含有不明欄位的嚴格訊息一律視為無效。
  • 如果繫結支援彈性訊息的這種機制,則不得提供特殊的「unknown」標記,以便對嚴格訊息進行完整的標記檢查。
  • 從嚴格訊息轉換為彈性訊息,反之亦然,必須支援為非破壞性的來源層級 (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。為了公開彈性模式,我們需要特殊的列舉成員「unknown」。因此,現在可以組合不會通過嚴格驗證的列舉,例如在需要此列舉的其他情境中,在嚴格情境中,在編碼期間會失敗。同樣地,與字串和向量並行處理也很重要:如果沒有高度專業的 API,繫結會允許建立過長的字串和向量,然後無法編碼。

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

人體工學

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

  • 我們會針對不同語言的 FIDL 行為,為使用者提供更明確的預期。
  • 嚴格訊息可讓使用者避免編寫不必要的程式碼來處理不明欄位。

說明文件和範例

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

回溯相容性

  • 這項變更不會影響 ABI 相容性。
  • 如果需要變更解碼器或繫結,以符合此 FTP,這些變更可能會導致原始碼層級 (API) 中斷,因此應視情況個別處理。

成效

  • 強制解碼器和繫結項符合此 FTP 可能會造成效能 (可能不明顯) 的懲罰,因為這會強制處理所有未知欄位並關閉所有句柄。
  • 繫結可能需要額外的間接層級 (因此會使用額外的記憶體/二進位大小),才能提供「unknown」標記,進行完整的標記檢查。

安全性

這項 FTP 可提升安全性。

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

測試

請參閱「導入策略」一節 (我們預計使用 FIDL 相容性測試)。此外,每個語言繫結都應有專屬的測試,以便斷言正確的行為。

缺點、替代方案和未知事項

這個 FTP 可大幅釐清行為,並提供相關實作成本,確保語言繫結符合相關建議。

替代做法:預設為嚴格模式或混合模式

嚴格性應與向量或字串的大小限制相同;這是與訊息版面配置無關的限制,且可變更而不會導致 ABI 中斷。

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

此外,我們不希望混合模式,其中某些訊息 (例如列舉) 預設為嚴格模式,而其他訊息 (例如表格) 則不是。

替代做法:使用 [Strict] 屬性,而非新關鍵字

這是個重要的概念,值得擁有專屬關鍵字。其他語言中也有許多類似功能,因此可以將這些功能順利轉換為 FIDL。

替代方案:其他關鍵字

在設計階段,我們提出了幾種不同的替代方案。最有可能的候選字是 final:它表示「主題的最後一個字」,在 C++、Java、C# 等語言中具有優先順序。

不過,由於我們可能會在通訊協定中使用「final」關鍵字,表示無法在組合中使用該關鍵字 (即「final」的傳統用法),因此我們選擇使用其他關鍵字來表示嚴格驗證。

這會開放引入語法,例如:

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

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

其他探索過的關鍵字包括:sealedrigidfixedclosedknownstandardized

替代做法:僅嚴格

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

在這種情況下,如果要變更可擴充結構 (例如新增欄位),就必須先更新讀取器,再更新寫入器。這會嚴重限制這些可擴充資料結構的使用,且對於較高層級的用途來說過於限制。

此外,如果這是設計選擇,我們就不需要為資料表和可擴充的聯集使用信封 (也就是不需要位元組數量或句柄數量)。事實上,在嚴格解釋下,系統會拒絕未知的欄位,否則結構定義會以類似於其他訊息 Fidl 處理程序的方式,決定要使用的位元組和句柄數量。

替代方案:僅限彈性

我們可以將所有可擴充的訊息定義為一律具備彈性。

這對列舉 (和位元) 來說會非常令人意外,且與預期相反。這會導致兩個不良的次替代方案:

  • 為列舉 (和位元) 建立例外狀況,以便嚴格規定這些項目。如上所述,這會造成混淆,並使語言規則更難理解。
  • 請保持這些訊息的彈性,否則會違背預期,導致錯誤 (例如讀取無效值),並確實導致許多純粹的驗證程式碼必須手動編寫,而非由繫結提供。

繼續探索其他可擴充訊息 (表格和可擴充的聯集),我們需要嚴格執行。

舉例來說,請考慮以表格定義的安全記錄通訊協定 LogEntry。實作此通訊協定時,可能會希望確保用戶端不會傳送伺服器無法理解的欄位,因為這些用戶端可能會預期這些新欄位如何控制記錄項目的處理方式。舉例來說,較新的版本可能會新增欄位「pii ranges」,提供含有個人識別資訊的記錄項目範圍,且必須特別記錄 (例如,以不重複 ID 取代,並將原始資料儲存於該不重複 ID 下)。為避免舊伺服器接受這類酬載,並可能錯誤處理這些記錄項目,作者會為 LogEntry 選擇嚴格模式,以免日後遭到濫用。

既有技術與參考資料

這項決定的部分原因是受到 go/proto3-unknown-fields 的影響,該文件說明瞭為何 proto3 取消支援保留不明欄位,後來又撤銷了這項決定。

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

Footnote1

由於可在定義訊息後新增或移除新成員,因此可擴充訊息會包含列舉和位元。