RFC-0138:處理未知互動

RFC-0138:處理不明互動
狀態已接受
區域
  • FIDL
說明

我們擴充了 FIDL 語義,讓同儕能夠處理不明的互動。

Gerrit 變更
作者
審查人員
提交日期 (年-月-日)2021-05-25
審查日期 (年-月-日)2021-10-27

摘要

我們擴充了 FIDL 語意,讓同儕能夠處理未知的互動,例如接收未知事件或未知方法呼叫。為此,請按照下列步驟操作:

  • 我們在 FIDL 語言中導入了彈性互動嚴格互動。即使互動方式不明,對等端也能妥善處理。嚴格的互動會導致突然中斷。

  • 我們為協定導入了三種運作模式。封閉式通訊協定一律不會允許不明的互動。相反地,開放式通訊協定則允許任何不明的互動。最後,ajar 通訊協定僅支援單向不明的互動。

FIDL 支援演進功能的整體概觀

在深入探討此提案的具體內容之前,建議先瞭解 FIDL 如何解決演進問題。

這個問題有兩個層面:來源相容性 (API) 和二進位檔相容性 (ABI)。

API 相容性旨在保證在變更前針對產生的程式碼編寫的使用者程式碼,仍可在變更後針對產生的程式碼編譯。舉例來說,您可以合理預期,在 FIDL 程式庫中新增宣告 (例如定義新的 type MyNewTable = table {};) 不會導致使用此程式庫的現有程式碼無法編譯。

解決來源相容性問題的方法有三種:

  1. 盡可能讓盡可能多的變更來源相容 (例如 RFC-0057:預設無句柄);
  2. 提供明確保證 (例如 RFC-0024:強制來源相容性)。
  3. 提供版本設定 (例如 RFC-0083:FIDL 版本設定)。

另外,ABI 相容性旨在針對不同版本的程式庫建構的程式提供互通性。舉例來說,兩個程式對資料表的結構定義有不同的理解,但仍可成功通訊。

達成 ABI 相容性可分為三個部分:

  1. 在靜態相容性方面,我們關心的是如何在資料層級達成互通性,也就是說,當兩個同儕具有相同表格的不同架構時,何時可以互通?
  2. 動態相容性假設所有資料類型皆相容,並著重於在同儕使用不同版本的通訊協定 (例如不同方法) 時,達成互通性。
  3. 最後,在某些情況下,使用不同的通訊協定並非可行做法,而解決方案則是瞭解各個同級機器的功能 (協商),然後根據這些功能調整通訊 (使用哪種通訊協定)。

動態相容性特別適合用於追求「局部彈性」的情況,例如在幾乎不變的運作模式中加入小型附加元件。在其他情況下,例如 fuchsia.io1 相對於 fuchsia.io2,就需要進行網域模型轉移。需要「全球彈性」,且所尋求的解決方案屬於通訊協定協商類別。

我們在本 RFC 中特別討論的機制 (嚴格和彈性的互動) 可改善動態相容性的現況 (2)。

術語

提醒您組合式協定模型

兩個對等端之間的通訊稱為「互動」。互動會從要求開始,並可選要求回應

請求和回應都是交易訊息,以標頭 (「交易標頭」) 表示,可選擇附上酬載內容1

互動是單向的,我們分別將兩個對等端命名為「用戶端」和「伺服器」用戶端與伺服器互動的開始方式是用戶端向伺服器提出要求,並在相反方向傳回回應 (如果有)。同樣地,我們也討論伺服器與用戶端的互動

我們通常會使用「fire and forget」或「one way」一詞,指由用戶端啟動的無回應互動;而「call」或「two way」一詞則指需要回應的互動 (在目前的模型中,一律由用戶端啟動)。當伺服器是無回應互動的發起端時,通常會稱為「事件」2

通訊協定是一組互動,我們將「工作階段」定義為使用通訊協定進行用戶端與伺服器間通訊的特定例項,也就是用戶端與伺服器之間的互動序列。

應用程式錯誤是指遵循錯誤語法的錯誤。傳輸錯誤是指因核心錯誤 (例如寫入已關閉的管道) 或 FIDL 發生錯誤而發生的錯誤。

提振精神

Fuchsia 的核心原則是可更新:套件設計為可獨立更新。即使是驅動程式,也應是二進位穩定版,這樣裝置就能在保留現有驅動程式的同時,順利更新至較新版本的 Fuchsia。FIDL 在實現此更新功能上扮演著關鍵角色,其主要設計目的是定義 應用程式二進位介面 (ABI),進而為前向和回溯相容性提供堅實基礎。

具體來說,我們希望讓兩個對等端能夠安全地互通,即使對通訊協定有不同的理解也沒關係。更棒的是,我們希望能確保有強大的靜態保證,確保兩個同級機器是「相容」的。

我們已投入大量心力,為編碼和解碼 FIDL 類型提供彈性和保證,這就是所謂的「靜態相容性」。我們推出了 table 版面配置union 版面配置、選擇明確的 union 序號、推出 strictflexible 版面配置修飾符、推出 protocol 序號雜湊降低 protocol 序號雜湊的衝突機率,並改進交易訊息標頭格式,以便日後驗證。

接下來,我們將討論動態彈性和保證,也就是所謂的動態相容性。假設兩個同級節點在靜止狀態下相容,也就是說,它們用於互動的所有類型都處於靜止狀態相容,動態相容性就是指這兩個同級節點能夠成功互通,且不會因意外互動而中斷通訊。

相關人員

  • 主持人:jamesr@google.com。
  • 審查者:
    • abarth@google.com (FEC)
    • bprosnitz@google.com (FIDL)
    • ianloic@google.com (FIDL)
    • yifeit@google.com (FIDL)
  • 諮詢對象:
    • jamesr@google.com
    • jeremymanson@google.com
    • jsankey@google.com
    • tombergan@google.com
  • 社會化:我們已將 RFC 草稿提供給 FIDL 團隊,並與 Fuchsia 團隊的多位成員進行討論。這項資訊已廣泛分享至工程委員會的 Discuss 郵寄清單 (eng-council-discuss@fuchsia.dev)。

設計

我們將介紹「彈性互動」和「嚴格互動」的概念。簡單來說,即使未知,對等端仍可妥善處理彈性互動。相反地,如果接收端不知道,嚴格互動會導致接收端突然終止工作階段。我們會以互動的嚴格性來表示互動方式是彈性或嚴格。請參閱彈性和嚴格互動行為的語意

沒有防護機制時,彈性互動功能可能會在無意間用於危害隱私權的方式:

  • 舉例來說,假設您有一個專門用於演進的轉譯引擎,新版本會新增 flexible SetAlphaBlending(...); 單向互動,與意針對舊版轉譯器的較新用戶端,其設定會遭到忽略 (但大部分的轉譯作業仍會正常運作)。不過,如果新方法是關於特殊的 PII 轉譯模式 StartPIIRendering();,則舊版轉譯器必須停止處理,而非忽略這項作業,因此使用 strict 互動才是適當的做法。
  • 另一個例子是惡意同盟方試圖透過傳送各種訊息,反射性地發現公開的途徑,以便瞭解系統能否解讀這些訊息。一般來說,反射功能會帶來額外的效能成本,並導致隱私權問題 (您可能會公開比您預期更多的資訊)。根據原則,FIDL 會選擇禁止反射,或要求明確選擇加入。

因此,我們另外推出三種可讓通訊協定運作的模式:

  • 封閉式通訊協定:不允許或不預期彈性互動,因此收到彈性互動會是異常情況。
  • 開放式通訊協定允許任何彈性互動 (無論是單向或雙向)。這類通訊協定提供最高的彈性。
  • Ajar 通訊協定:允許彈性單向互動 (即發即忘的呼叫和事件),但不允許彈性雙向互動 (如果同級機器不瞭解此方法,就無法發出方法呼叫)。

詳情請參閱「通訊協定語意」。

嚴格和彈性互動行為的語意

嚴格互動的語意相當簡單:當收到不明的請求 (即收件者不知道的序號) 時,對等端會突然終止工作階段 (關閉管道)。

彈性互動的目標是讓收件者能妥善處理不明互動。這會對設計產生一些影響。

彈性互動的傳送者必須知道,收件者可能會忽略其要求 (因為不瞭解要求內容)。

收件者必須能夠判斷這項要求是否彈性 (而非嚴格),並據此採取行動。

由於雙向互動需要收件者回覆寄件者,因此如果收到不明要求,收件者必須能夠在沒有任何其他詳細資料的情況下,自行建構回應。收件者必須向寄件者說明無法理解要求。為了滿足這項要求,靈活的雙向互動回應必須是結果聯集 (請參閱詳細資料)。

根據語意,在單向互動情況下,傳送者無法判斷收件者是否知道其要求。使用彈性單向互動時,FIDL 作者應謹慎處理整體通訊協定的語意。

值得注意的是,單向互動有點像是「盡力而為」,因為傳送端無法判斷對方是否收到互動。不過,管道會提供排序保證,因此互動順序是確定且可知的。嚴格的單向互動可確保只有在理解先前的互動時,才會發生某些互動。舉例來說,記錄通訊協定可能會包含 StartPii()StopPii() 嚴格互動,確保沒有任何同儕會忽略這些互動。

如要進一步瞭解在選擇嚴格互動和彈性互動時,應考慮的取捨,請參閱以下文章:

開啟、關閉和半開通訊協定的語意

closed 通訊協定的語意受到限制,只能進行嚴格的互動,無法進行彈性的互動。如果 closed 通訊協定有任何 flexible 互動,就會發生編譯時錯誤。

ajar 通訊協定的語意允許嚴格互動和單向彈性互動。如果 ajar 通訊協定有任何 flexible 雙向互動,就會發生編譯時錯誤。

open 通訊協定沒有任何限制,無論是嚴格或彈性,都允許單向和雙向互動。

如要進一步瞭解在選擇封閉、半開放或開放通訊協定時,應考慮的取捨,請參閱以下文章:

語言變更

我們引入了修飾符 strictflexible,用於將互動標示為嚴格或彈性:


protocol Example {
    strict Shutdown();
    flexible Update(value int32) -> () error UpdateError;
    flexible -> OnShutdown(...);
};

根據預設,互動方式是彈性的。

根據樣式指南,建議您一律明確指出互動的嚴格程度,也就是應為每項互動設定嚴格程度。3

我們引入了修飾符 closedajaropen,用來標示通訊協定為關閉、半開 (部分開啟) 或開啟:


closed protocol OnlyStrictInteractions { ...
ajar protocol StrictAndOneWayFlexibleInteractions { ...
open protocol AnyInteractions { ...

在封閉式通訊協定中,無法定義彈性互動。封閉式通訊協定只能組成其他封閉式通訊協定。

在 ajar 通訊協定中,無法定義雙向彈性互動。Ajar 通訊協定只能組合已關閉或半開的通訊協定。

(開放式通訊協定沒有任何限制)。

根據預設,通訊協定會保持開放。

這個提案的舊版已將 ajar 指定為預設值。不過,如果在未明確宣告修飾符的雙向方法中,開放度修飾符的預設值 ajar 與嚴格度修飾符的預設值 flexible 發生衝突,就會導致衝突。這表示如果通訊協定或方法至少沒有一個修飾符,則含有雙向方法的通訊協定無法編譯。請參閱下方說明:開放度的預設值以粗體顯示,嚴謹度的預設值則以斜體顯示。

視覺化:以格狀圖表顯示哪些組合會在嚴格/彈性模式下編譯成開啟/半開/關閉。

為解決這個問題,我們將開放程度的預設值從「ajar」變更為「open」,讓通訊協定在通訊協定或方法上無須修飾符,即可編譯雙向方法。

根據風格指南,建議您一律明確指出通訊協定的模式,也就是應為每個通訊協定設定模式。[^default-debate]

電報格式異動:交易訊息標頭標記

我們修改交易訊息標頭,如下所示:

  • 交易 ID (uint32)
  • 靜止標記 (array<uint8>:2,即 2 個位元組)
  • 動態標記 (uint8)
  • 魔幻數字 (uint8)
  • 序數 (uint64)

也就是說,旗標位元組會分成兩個部分,靜態旗標為兩個位元組,動態旗標為一個位元組。

動態旗標位元組的結構如下:

  • 位元 7,第一個 MSB「嚴格度位元」:嚴格方法 0,彈性方法 1。
  • 位元 6 到 0,未使用,設為 0。

進一步瞭解如何使用「動態標記」:

  1. 我們在第三版交易訊息標頭中新增了標記。這些旗標原本是用於「暫時用於軟遷移」。舉例來說,在嚴格至可擴充的聯集遷移期間,會使用一個位元。不過,目前沒有任何計畫需要一次使用這麼多標記,因此我們可以將這些標記的意圖從僅用於臨時使用,改為用於線路格式的一部分。

  2. 當收件者不知道 strict 互動情形時,寄件者必須使用嚴格位元,才能向收件者指出 strict 互動情形。在這種情況下,預期的語意是通訊會突然終止。如果沒有這個嚴格位元,傳送端和接收端之間的這種偏差可能會遭到忽略。舉例來說,假設有個 ajar (或 open) 通訊協定,其中包含新加入的 strict StopSomethingImportant(); 單向互動。如果沒有嚴格位元,接收端就必須猜測未知的互動行為是嚴格還是彈性,並根據本 RFC 中所尋求的預期可進展性改善選用彈性。因此,在擴充通訊協定時,FIDL 作者將被迫依賴雙向嚴格互動。

另請參閱「在交易 ID 中放置嚴格位元」,瞭解替代表示法的討論,以及「互動模式位元」,瞭解替代表示法未來可能需要的內容。

通訊格式異動:結果聯集

目前有兩種變化版本的結果聯集 (序號 1 代表成功回應,序號 2 代表錯誤回應) 已擴充為第三種變化版本,即序號 3,其中會附帶新的列舉 fidl.TransportError,用於指出「傳輸層級」錯誤。

舉例來說,以下互動:

open protocol AreYouHere {
    flexible Ping() -> (struct { pong Pong; }) error uint32;
};

有回應酬載:

type result = union {
    1: response struct { pong Pong; };
    2: err uint32;
    3: transport_err fidl.TransportError;
};

具體來說,如果彈性方法使用 error 語法,系統會相應設定成功類型和錯誤類型 (分別為序數 1 和 2)。否則,如果彈性方法未使用 error 語法,則結果聯集的錯誤變化版本 (序數 2) 會標示為 reserved4

部分精確度:5

  • 我們選擇 transport_err 這個名稱,因為從應用程式角度來看,錯誤來源應無法區分。除了應用程式錯誤,還有「傳輸錯誤」,這是因 FIDL 編碼/解碼、FIDL 通訊協定錯誤、核心錯誤等因素而導致的錯誤。基本上,「傳輸錯誤」是指架構 (包含多個軟體層級) 中可能發生的所有錯誤。

  • 我們將 fidl.TransportErr 類型定義為嚴格的 int32 列舉,其中包含單一變數 UNKNOWN_METHOD。這個變化版本的值與 ZX_ERR_NOT_SUPPORTED 相同,即 -2:

    type TransportErr = strict enum : int32 {
      UNKNOWN_METHOD = -2;
    };
    

    向用戶端呈現傳輸錯誤時,如果繫結提供取得不明互動 transport_errzx.status 的方法,則繫結必須使用 ZX_ERR_NOT_SUPPORTED。不過,如果未知互動 transport_errzx.status 的對應方式不符合向用戶端顯示錯誤的方式,則不必使用繫結。

    另一種方法是只使用 zx.status,並一律使用 ZX_ERR_NOT_SUPPORTED 做為值,用來表示未知的方法,但這有兩個重大缺點:

    • 它需要依附程式庫 zx,而許多程式庫可能不會直接使用該程式庫。這會導致在 IR 中定義結果聯集的難度提高,因為我們需要在 zx 上自動插入依附元件,或是在 IR 中將類型降級為 int32,但產生的繫結會將其視為 zx.status

    • 但不會定義繫結如何處理非 ZX_ERR_NOT_SUPPORTEDtransport_err 值。透過指定類型為嚴格列舉,我們可明確定義繫結的語意,以便接收無法辨識的 transport_err 值,並將其視為解碼錯誤。

  • 為簡化說明,我們會以單數形式稱呼「結果聯集」,但實際上,我們描述的是共用相同結構的聯集類別,也就是三個序數、第一個變數不受限制 (成功類型可以是任何內容)、第二個變數必須是 int32uint32 或其列舉,而第三個變數則必須是 fidl.transport_err

JSON IR 的變更

我們會在 JSON IR 中公開互動的嚴格程度。在實際操作中,我們會更新 #/definitions/interface-method 類型,並將 strict 布林值新增為 ordinalnameis_composed 等的兄弟項目。

我們會在 JSON IR 中公開通訊協定的模式。實際上,我們會更新 #/definitions/interface 類型,並新增 mode 列舉,其中成員 closedajaropencomposed_protocolsmethods 等的兄弟項目。

繫結項目的變更

我們希望綁定可顯示自動處理要求的情況。舉例來說,雖然繫結項可能會自動建構要求,指出要求不明,但您仍應提出收到不明要求的訊息 (可能包含有關要求的某些中繼資料),並選擇以「request unknown」回應,或突然終止通訊。

靜態資料疑慮。

  • 在彈性互動情況下,繫結應透過相同機制 (用於呈現其他傳輸層級錯誤,例如 zx_channel_write 中的錯誤或解碼期間的錯誤),向用戶端呈現結果聯集的 transport_err 變化版本。結果聯集的 errresponse 變化版本應以相同方式呈現給用戶端,也就是說,如果方法宣告為嚴格型別,則繫結會以相同方式呈現這些類型。

    • 舉例來說,在 Rust 繫結中,Result<T, fidl::Error> 會用於呈現呼叫的其他傳輸層級錯誤,因此 transport_err 應折疊至 fidl::Error。同樣地,在低階 C++ 繫結中,fit::result<fidl::Error> 會用於傳達傳輸層級錯誤,因此 transport_err 應合併至 fidl::Errorresponseerr 變體會以與嚴格方法相同的方式傳遞。在 Rust 中,如果方法含有錯誤語法,則 Result<Result<T, ApplicationError>, fidl::Error> 會代表該方法;如果方法不含錯誤語法,則 Result<T, fidl::Error> 會代表該方法,response 值為 Terr 值為 ApplicationError

    • 針對將錯誤折疊至 zx.status 的繫結,transport_errUNKNOWN_METHOD 必須轉換為 ZX_ERR_NOT_SUPPORTED

動態疑慮。

  • 使用 zx_channel_writezx_channel_call 或其同胞兄弟傳送要求時,必須將動態標記設為以下方式:
    • 嚴格位元 (位元 7) 必須設為 0,才能進行嚴格互動,並設為 1 才能進行彈性互動。
    • 後六位元必須設為 0。
  • 收到已知互動時:
    • 與目前繫結項目的運作方式沒有任何差異。
    • 具體來說,繫結不應驗證嚴格性,以便從嚴格互動轉換為彈性互動 (或反之)。
  • 收到不明互動 (即不明序號) 時:
    • 如果互動嚴格 (如收到的嚴格標記所示):
    • 繫結必須關閉通訊 (也就是關閉管道)。
    • 如果互動是彈性的 (如收到的嚴格性標記所示):
    • 對於封閉式通訊協定,繫結必須關閉管道。
    • 如果互動是單向的 (交易 ID 為零):
      • 繫結必須將這個不明互動事件傳送至應用程式 (詳情請見下文)。
    • 如果互動是雙向的 (交易 ID 不為零):
      • 針對 ajar 通訊協定,繫結必須關閉管道。
      • 對於開放式通訊協定,繫結必須將這個未知的互動傳送至應用程式 (詳情請見下文)。
    • 提出不明互動詳細資料:
      • 如果互動是雙向的,則繫結必須傳送選取第三個變化版本的結果聯合,以及 UNKNOWN_METHODfidl.TransportErr,以回應要求。這項操作必須在未知互動事件傳送至使用者程式碼前完成。
      • 繫結應將不明互動事件傳送至應用程式,可能會透過呼叫先前註冊的處理常式 (或類似項目) 來完成。
      • 建議繫結需要註冊不明的互動處理常式,以免建構出可能遭誤解的「預設行為」。繫結可以提供「無操作處理常式」或類似項目,但建議明確使用。
      • 繫結可能會選擇為應用程式提供選項,以便在處理不明互動時關閉管道。

如果不明訊息包含句柄,伺服器必須關閉傳入訊息中的句柄。在下列情況下,伺服器必須關閉傳入訊息中的所有句柄:

  • 關閉管道 (在嚴格方法、封閉通訊協定上的彈性方法,或 ajar 通訊協定上的彈性雙向方法)
  • 回覆訊息,如果是開放式通訊協定上的彈性雙向方法
  • 在開放或 ajar 通訊協定上使用彈性單向方法時,通知使用者程式碼有關未知方法的呼叫。

同樣地,當用戶端收到含有句柄的不明事件時,用戶端必須關閉傳入訊息中的句柄。在下列情況下,用戶端必須關閉傳入訊息中的所有句柄:

  • 在嚴格事件或封閉通訊協定上的彈性事件中關閉管道。
  • 在開放或 ajar 通訊協定上,針對彈性事件通知使用者程式未知事件。

一般來說,在處理不明互動時,作業順序如下:

  1. 關閉傳入訊息中的句柄。
  2. 視情況關閉管道或傳送 UNKNOWN_METHOD 回覆。
  3. 將不明互動傳送至不明互動處理常式,或回報錯誤。

在非同步環境中,多個執行緒可能會同時嘗試在管道上傳送/接收訊息,因此在回報不明方法錯誤前,無法保證管道會關閉,這也許不切實際。因此,當互動造成致命結果時,您不必在回報不明方法或事件的錯誤前關閉管道。不過,如果是可復原的不明互動 (如本 RFC 所述),則必須先關閉句柄和回覆 (如適用),再調度不明互動處理常式。

此 RFC 的先前版本並未指定在傳入訊息中關閉句柄、回應未知的雙向方法,以及向使用者提出未知互動之間的順序。

相容性影響

ABI 相容性

將互動從 strict 變更為 flexible,或從 flexible 變更為 strict 時,ABI 不相容。

變更通訊協定模式 (例如從 closed 變更為 ajar) 與 ABI 不相容。雖然從較嚴格的模式改為較不嚴格的模式似乎可相容於 ABI,但由於協定同時定義了傳送端和接收端 (即 fire-and-forget 和事件),因此實際上並非如此。

所有變更都可以進行軟性轉換。如有需要,您可以為修飾符建立版本

來源相容性

將互動從 strict 變更為 flexible,或從 flexible 變更為 strict 可能會與來源相容。建議綁定項目提供相同的 API,無論互動嚴謹程度為何,皆可透過折疊現有的傳輸錯誤 API 來達成。

變更通訊協定模式 (例如從 closed 變更為 ajar) 與原始碼不相容。建議您根據通訊協定模式,為繫結提供專屬的 API。舉例來說,封閉式通訊協定不需要提供「不明方法」處理程序,建議不要提供這種不會使用的處理程序。

與平台版本管理的關聯

RFC-0002 的演進部分所述,我們會「在平台對 Fuchsia 系統介面的語意進行不相容回溯的變更時,變更 ABI 修訂版本」。

我們會以多快發行新的 ABI 修訂版本,來評估是否順利達成「可更新」目標。由於新增或移除彈性互動功能時,可以採用回溯相容的方式,因此這項功能有助於改善 Fuchsia 的可更新性。

實作

  • 我們可以想像,如果繫結只實作規格嚴格部分,這將是安全的,因為通訊會提早停止,就像對等端遇到其他錯誤或錯誤一樣。
  • 考量到可進化性對 FIDL 的重要性 (這是第 1 個目標),這並非理想的未來,因此我們要求繫結必須遵循此規格。
  • 為了符合繫結規格,繫結必須實作嚴格且靈活的互動意義,以及三種通訊協定模式。
  • 基於這項考量,我們會詳細說明繫結規格的變更。這會導致 ABI 中斷,也是線格格式的重大演進 (涵蓋「靜止」和「動態」兩種關切項目)。

這份 RFC 先前版本要求在新的魔術數字後,限制未知互動的推出。不過,如前述所述,未知的互動可與現有通訊協定回溯相容,因為用於表示嚴格程度的標頭位元先前未使用/保留,且線路格式只會針對靈活的雙向方法進行變更,而這類方法只能存在於開放式通訊協定中。我們不會變更魔術數字,而是會採用兩階段的推行方式,啟用未知互動的支援功能,但將預設修飾符設為 closedstrict,然後明確將這些修飾符新增至現有的 FIDL 檔案,再將預設值變更為 openflexible

效能考量

不會影響 closed 通訊協定。如「繫結的變更」一節所述,封閉式通訊協定不需要檢查嚴格位元。

ajaropen 通訊協定的影響程度不大:

  • 處理不明互動的方式與處理已知互動類似,系統會叫用預先註冊的處理常式,並執行應用程式程式碼。
  • 此外,如果是雙向不明互動 (僅限 open 通訊協定),則會由繫結建立並傳送回應。

我們預期效能考量因素不太重要,而選擇通訊協定模式時,主要會受到安全考量的影響。

人體工學

這會讓 FIDL 更難理解,但解決了至今仍是尖銳問題的進化能力相關的非常重要需求。

回溯相容性

這項功能不具回溯相容性,且需要對所有 FIDL 用戶端和伺服器進行軟遷移。

安全性考量

新增向同儕傳送不明要求的功能 (例如在彈性互動情況下) 會造成安全性疑慮。

對於特別敏感的通訊協定,由於需要非常嚴格的互動,因此可能需要先處理演進問題,因此建議使用 closed 通訊協定。Fuchsia 的大部分內部元件都會依賴 closed 通訊協定 (例如 fuchsia.ldsvc)。

在考慮 ajaropen 通訊協定時,FIDL 作者需要考量兩項事項:

  • 惡意對等端傳送含有大量酬載的不明要求。(這與使用 flexible 類型時的疑慮類似,因為這類型也可能攜帶大量不明的酬載)。如大小會影響 ABI一文所述,我們需要進一步提供功能,讓 FIDL 作者能夠控管這些功能,這項問題將在日後的作業中解決。
  • 這會讓對等端嘗試在沒有先驗知識的情況下,探索哪些方法已實作,然後製作訊息來利用所發現的方法。如果實作項目公開的方法比預期多,就可能會發生問題。舉例來說,您原本想公開父項通訊協定,但卻綁定了組成父項的子項通訊協定。請注意,攻擊向量不會因彈性互動而改變,但由於同級機器可以依序嘗試多個序數,而無需重新連線 (在某些情況下可能會造成過高的成本),因此可能更容易遭到濫用。
  • 在選擇 ajaropen 通訊協定時,請考量對等端無法判斷單向互動是否已處理或忽略,但在雙向未知互動 (open 通訊協定允許) 的情況下,處理對等端會揭露無法瞭解互動的情況,進而向惡意對等端洩漏寶貴資訊。

隱私權注意事項

開啟嗅探通訊協定功能可能會導致隱私權疑慮。如安全性考量一節所述,這項威脅模型不會因本 RFC 而有所變更,但可能更容易遭到濫用。

測試

如要開發本 RFC 中所述的新功能組合,關鍵在於確保所有繫結都遵循相同規格,且行為相似。為此,您必須能夠在測試中表達規格,例如「傳送此要求,回應正確的交易 ID,但錯誤的序號,預期傳送者管道會關閉」。根據我們的經驗,如果能進一步著重於流暢地表達規格結果,就能增加測試次數,進而提高所有繫結至規格的相容性,並強化回歸保護功能。

我們會採用與編碼和解碼相同的方法,這也是 GIDL 開發的終極目標:先手動編寫測試,盡可能使用多個繫結,然後一點一點地將可用的部分推廣至以宣告式為主的測試方法。雖然我們希望能為動態問題建立與 GIDL 類似的工具,並且會努力朝這個方向努力,但我們不會將這項工具視為最終結果,而是可能會偏好手動編寫的流暢表達測試。

說明文件

我們會為這項功能提供詳細說明文件。規格方面:

FIDL API 評量準則中將新增其他項目,涵蓋通訊協定演進。

針對特定目標語言中這項功能的具體用途,我們希望每個繫結都會更新說明文件,並提供可運作範例。

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

缺點:訊息大小上限會影響 ABI

處理未知項目的問題 (無論是未知酬載,如 flexible 類型,或是本文所述的未知互動) 的問題是,預期由同儕讀取的訊息最大大小會影響 ABI,而這項限制從未明確說明,也未經過靜態驗證。

目前沒有管道向量讀取功能,也無法執行部分讀取。因此,訊息可以傳送至符合所有要求的對等端 (例如彈性互動,當對等端預期時),但卻導致通訊失敗,進而破壞 ABI。如果問題訊息太大,以致於對等端無法讀取 (因為對等端預期訊息大小小於 1 KiB),則系統將永遠不會讀取超過該限制的新訊息,而是關閉管道,並中斷兩個對等端之間的通訊。

由於 flexible 類型已存在這類問題,因此引入彈性互動功能後,發生這類問題的可能性會增加。

以下是未來的方向:

  • 向量化管道讀取,讓收件者可以只讀取訊息的標頭,然後決定是否要讀取酬載的其餘部分,或捨棄該訊息 (這也需要新的系統呼叫)。
  • 將訊息的大小上限設為通訊協定的明確屬性,可能會使用預先定義的大小類別,例如 smallmediumlargeunbounded

替代方案:與指令模式的比較

指令模式可讓用戶端將許多要求批次處理,以便由伺服器處理。您也可以使用指令模式,實現本 RFC 中所述的演進性。

舉例來說:

open protocol AnOpenProtocol {
    flexible FirstMethod(FirstMethodRequest) -> (FirstMethodResponse);
    flexible SecondMethod(SecondMethodRequest) -> (SecondMethodResponse);
};

您可以使用以下封閉式通訊協定來模擬這種情況,也就是說,如果您想使用現有的 FIDL 功能組合來達到相同程度的可進化性,就必須採用這種做法:

closed protocol SimulateAnOpenProtocol {
    strict Call(Request) -> (Response);
};

type Request = flexible union {
    1: first FirstMethodRequest;
    2: second SecondMethodRequest;
    ...
};

type Response = flexible union {
    1: first FirstMethodResponse;
    2: second SecondMethodResponse;
    ...
    n: transport_err zx.status;
};

因此,指令模式方法並不令人滿意。

由於我們必須將每個要求與聯集中的回應配對,因此無法執行「配對組」的語法,這也導致語法位置遺失。

由於不受控的伺服器可能會以 SecondMethodResponse 回應 FirstMethodRequest,因此我們也失去了型別安全性。有人認為智慧繫結可能會注意到這個模式,或許可以利用 @command 屬性提供與方法相同的人體工學。

在線路層級,指令模式會強制使用「兩種方法判別器」排序。我們在交易訊息標頭中使用序數 (指出 Call 是互動),並使用聯集序數 (指出所選聯集的變化版本,例如 FirstMethodRequest 為 1,SecondMethodRequest 為 2)。

同樣地,有人可能會認為,如果所有方法都遵循指令模式,也就是所有方法的要求和回應都是聯集,那麼交易訊息標頭就不需要序數。基本上,上述彈性通訊協定會使用指令模式「編譯成」封閉式通訊協定。聯集的電線格式需要計算變數的位元組和句柄,並要求這些計數必須由相容的解碼器驗證。這會造成兩個問題:

  • 交易訊息標頭允許的嚴格性 (沒有酬載說明,請盡可能解碼) 與聯合線路格式不相符 (實際上是設計上的結果)。這種嚴謹性和簡單性特別適合用於 FIDL 過度旋轉的低階用途。

  • 組合模型沒有任何「通訊協定群組」的概念。這項功能非常強大,因為我們可以 (而且確實) 透過同一個管道進行多重通訊協定。我們會盡可能使用結構化組合 (即 compose 節),並且改用動態組合 (例如服務探索)。如果我們採用「所有內容都會編譯為聯集」的觀點,就會強制採用嚴格的分組。

最後,某些 FIDL 作者希望能「自動批次處理要求」。舉例來說,fuchsia.ui.scenic 程式庫以在 fuchsia.ui.scenic/Session.Enqueue 方法中使用指令模式而聞名。不過,提供「自動批次處理要求」功能可能會造成危險,因為在一個單位中處理多個指令的語意,在不同應用程式之間往往差異甚大。我們該如何處理不明指令?我們該如何處理失敗的指令?是否應忽略指令、停止執行作業,導致中止和回溯?即使是圍繞「批次工作單元」(交易) 概念設計的 RDBM 系統,也傾向於提供多種批次模式 ([隔離層級](https://en.wikipedia.org/wiki/Isolation_(database_systems)))。簡單來說,FIDL 沒有支援「自動批次處理要求」的計畫。

總而言之,雖然嚴格和彈性互動的語意表面上看起來與指令模式相同,但兩者有明顯差異,因此需要特別的語意。

替代方案:通訊協定協商

什麼是通訊協定協商?

通訊協定協商是個廣義術語,描述了一系列技術,可讓同級機器彼此互動,逐步建立彼此的背景資訊,進而讓彼此進行正確、快速且有效率的通訊。

舉例來說,假設你隨機撥打電話給某人,也許對方會先說「某某某,對吧?」您從沒有對等端的背景資訊,轉而獲得一些身分資訊。接著你可以說:「我知道了,我有理解正確嗎?」由於行銷電話相當普遍,您可能會遇到「這通電話是關於什麼?你是誰?依此類推。兩位同儕會逐漸發現彼此的身份和能力。

  • 系統可理解哪些資料元素?例如,向對等端指出所需的資料表欄位,避免對方產生大量複雜資料,卻在收到時遭到忽略。
  • 對等端支援哪些方法?在轉譯引擎中,您可以要求 alpha 混合是否可做為功能,如果無法使用,則調整與轉譯器的互動 (可能會傳送不同的內容)。
  • 應使用哪些效能特性?一般來說,您可以協商緩衝區大小,或允許的通話頻率 (例如配額)。

每種類型都需要略有不同的解決方案,但基本上都會將互動模型的抽象說明 (例如「同儕瞭解的方法集合」) 轉換為可交換的資料。

如要妥善解決通訊協定協商問題,第一步是提供一種描述這些概念的方式 (「通訊協定」、「方法 foo 的回應類型」)。由於同儕會從低層級的內容開始,也就是彼此不瞭解對方,且必須假設對方對世界的定義不同,因此概念的描述通常會依賴結構性屬性。舉例來說,「回應類型是 MyCoolType」這句話毫無意義,且需要解讀,但「回應類型是 struct { bool; }」這句話本身就具有意義,且可在無上下文的情況下解讀。

通訊協定協商與嚴格和彈性互動之間的關係

這個 RFC 中提出的嚴格且彈性的互動方式,可在演進中的通訊協定中提供一些彈性。您現在可以新增或移除方法。甚至可能更多。不過,如果濫用演進功能,就會導致通訊協定變得模糊不清,且難以從其形狀瞭解其網域。這類似於表格,因為表格會隨著時間累積大量欄位,因為它們現在代表一種「匯總結構體」,結合了多組隨著時間變化的規定。

在合約通訊協定協商中,如果使用得當,就能隔離版本管理負擔,並在經過一些動態選擇 (協商) 後,採用更簡潔且嚴謹的通訊協定 (可能是 closed 通訊協定)。

這兩種演進技巧各有其用,而且演進工具箱都需要這兩種技巧。

替代做法:在交易 ID 中放置嚴格位元

使用交易 ID 傳達嚴格和彈性互動所需的位元,有一個重大缺點。部分交易 ID 是由核心產生,也就是 zx_channel_call 會將訊息的前四個位元組視為 zx_txid_t 類型的交易 ID。將更多資訊塞入交易 ID 會迫使核心和 FIDL 之間的耦合度加強,這並非理想情況。改用交易標頭旗標,使用 zx_channel_call 的 FIDL 程式碼就能繼續在標頭中建構所有項目,除了 ID 以外。

替代做法:互動模式位元

這份 RFC 的早期版本要求新增「互動模式」位元,以區分單向互動和雙向互動,並預計擴展至更複雜的互動,例如終端互動)。

主要缺點是互動模式位元與交易 ID 中提供的資訊重複:單向互動具有零交易 ID,雙向互動則具有非零交易 ID。由於資訊重複,這會讓不同實作 (例如繫結) 使用重複位元子集,決定如何處理訊息。這會讓系統的不同部分以不同的方式解讀訊息,進而導致惡意編寫訊息的情況。

雖然我們希望為所有互動指派交易 ID,並擴充互動模式,但這兩項變更都需要額外的位元,如互動模式所述,因此我們建議將這項設計討論延後到這些功能設計完成時再討論。

替代做法:命名

這份 RFC 重複提及,我們曾經討論過如何為新引入的概念命名。我們在此簡要說明部分討論內容。

如要區分哪些互動可標示為「未知」,哪些互動則需要標示為「已知」:

  • openclosed 的原始名稱。
  • (none)required 的意思是,您的對等端必須實作方法,否則通訊協定會終止。
  • 入圍者: flexiblestrict 借用自 RFC-0033:處理未知欄位和嚴格性

如要區分永遠無法接收不明互動的通訊協定、可接收單向不明互動的通訊協定,以及可接收單向和雙向互動的通訊協定,請按照下列順序進行:

  • staticstandarddynamic 原始名稱已選取。「靜態」和「動態」的輕微缺點是,我們一直使用「靜態」和「動態」一詞來指稱 FIDL 的線路格式和訊息方面。舉例來說,本 RFC 的部分內容提到「動態問題」,而「動態」一詞在「動態」與「動態通訊協定」之間有不同的含義。
  • strict(none)flexible 再次借用 RFC-0033 的內容。
  • 請改用 sealed 取代 static,以強調通訊協定無法輕易擴充。
  • 使用 hybridmixed 取代 standard
  • 入圍者: closedajaropen。由於 open 和 closed 不會用於互動,因此我們可以將它們用於通訊協定修飾符。ajar 的定義是「部分開啟」,這正是我們要說明的概念。是的,所有相關人員都認為這有點詭異。

既有技術與參考資料

(如文中所述)。


  1. 令人困惑的是,訊息 (與交易訊息相對) 是指FIDL 值的編碼形式。 

  2. fidlc 和 JSON IR 愛好者請注意,編譯器的內部會將事件表示為 maybe_request_payload 等於 nullptr,而 maybe_response_payload 則是 present。不過,從模型的角度來看,我們稱此酬載為要求,但方向為伺服器到用戶端。我們應採用組合模型,變更 fidlc 和 JSON IR。這超出本 RFC 的範圍,但為了完整性,我們仍會提及這項資訊。 

  3. 我們偏好使用較為彈性的文法,並搭配由 Lint 強制執行的樣式指南。我們之所以做出這個設計選擇,是因為希望新手能使用更容易上手的語言,同時又能為 Fuchsia 平台提供明確 (並且冗長) 的標準。 

  4. 值得注意的是,將 error 新增至 flexible 互動時,可以做為軟性 ABI 相容變更。 

  5. 我們後來分別將 transport_errTransportErr 重新命名為 framework_errFrameworkErr。詳情請參閱 https://fxbug.dev/42061151。