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 團隊的各個成員討論。並在 Eng Council 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 { ...

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

在半開放式通訊協定中,不得定義雙向彈性互動。微開通訊協定只能組成關閉或微開通訊協定。

(開放通訊協定不受限制)。

預設為開放式通訊協定。

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

視覺化:格線,顯示開啟/半開/關閉的組合,以及嚴格/彈性編譯。

為解決這個問題,我們將開放程度的預設值從「半開」改為「開啟」,這樣通訊協定就能編譯雙向方法,而通訊協定或方法都不需要修飾符。

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

線路格式異動:交易訊息標頭旗標

我們將交易訊息標頭修改為:

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

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

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

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

進一步瞭解「動態標記」的用途:

  1. 我們在交易訊息標頭的第三個版本中新增了旗標。這些旗標「暫時用於軟性遷移」。舉例來說,在嚴格到可擴充的聯集遷移作業期間,使用了一個位元。不過,目前沒有任何方案需要一次使用這麼多旗標,因此我們可以變更這些旗標的用途,從僅供暫時使用改為用於線路格式。

  2. 如果接收者不知道有 strict 互動,傳送者就必須使用嚴格位元向接收者指出這類互動。在這種情況下,預期的語意是通訊突然終止。如果沒有這個嚴格位元,傳送端和接收端之間的這類時間差可能不會被發現。舉例來說,假設有一個 ajar (或開放) 通訊協定,其中新增了 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;
    };
    

    向用戶端顯示傳輸錯誤時,如果繫結提供方法來取得不明互動的 zx.status transport_err,則繫結必須使用 ZX_ERR_NOT_SUPPORTED。不過,如果這不符合向用戶端顯示錯誤的方式,則不需要將不明互動 transport_err 對應至 zx.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 列舉,其中包含 closedajaropen 成員,做為 composed_protocolsmethods 等的同層級項目。

繫結異動

我們希望繫結能以可見形式呈現要求的自動處理方式。舉例來說,繫結可能會自動建構要求,指出要求不明,但請務必提出收到不明要求 (可能包含要求的一些中繼資料),並選擇以「要求不明」回應或突然終止通訊。

靜態資料疑慮。

  • 如果是彈性互動,繫結應透過與呈現其他傳輸層級錯誤 (例如來自 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 通訊協定,繫結必須關閉管道。
      • 如果是開放式通訊協定,繫結必須向應用程式提出這項不明互動 (詳情請見下文)。
    • 提出不明互動的詳細資料:
      • 如果是雙向互動,繫結必須回應要求,方法是傳送選取第三個變體的結果聯集,以及 fidl.TransportErrUNKNOWN_METHOD。這項作業必須在不明互動提升至使用者程式碼之前完成。
      • 繫結應向應用程式引發不明互動,可能的方式是叫用先前註冊的處理常式 (或類似項目)。
      • 建議繫結要求註冊不明的互動處理常式,避免建構可能遭誤解的「預設行為」。繫結可以提供「無運算處理常式」或類似項目,但建議明確使用。
      • 處理不明互動時,繫結「可能」會選擇向應用程式提供關閉管道的選項。

如果不明訊息包含控制代碼,伺服器必須關閉內送訊息中的控制代碼。伺服器必須先關閉傳入訊息中的所有控制代碼,才能執行下列操作:

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

同樣地,如果用戶端收到含有控制代碼的不明事件,用戶端必須關閉傳入訊息中的控制代碼。用戶端必須先關閉所有傳入訊息中的控制代碼,才能執行下列操作:

  • 關閉管道 (如果是嚴格或彈性事件,且採用封閉通訊協定)。
  • 如果是開放或半開放通訊協定上的彈性事件,則通知使用者程式碼發生不明事件。

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

  1. 關閉來信中的控點。
  2. 如適用,請關閉管道或傳送 UNKNOWN_METHOD 回覆。
  3. 將不明互動項目傳送至不明互動項目處理常式,或回報錯誤。

在非同步環境中,多個執行緒可能會同時嘗試在管道上傳送/接收訊息,因此在回報不明方法錯誤前,可能無法或不適合保證管道已關閉。因此,當互動是嚴重錯誤時,您不需要在回報不明方法或事件的錯誤前關閉管道。不過,對於本 RFC 中指定的未知互動,必須先關閉控制代碼並回覆 (如適用),才能分派未知互動處理常式。

先前版本的 RFC 並未指定關閉傳入訊息中控制代碼的順序、回應不明的雙向方法,以及向使用者提出不明的互動。

相容性影響

ABI 相容性

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

變更通訊協定模式 (例如從 closed 變更為 ajar) 不相容於 ABI。雖然從限制較嚴格的模式變更為限制較寬鬆的模式,似乎可能與 ABI 相容,但實際上並非如此,因為通訊協定會同時定義傳送端和接收端 (即發送後即忘和事件)。

所有變更都可以軟轉換。如有需要,可以版本化修飾符。

來源相容性

將互動從 strict 變更為 flexible,或從 flexible 變更為 strict,可能與來源相容。建議繫結提供相同的 API,無論互動的嚴格程度為何,都應摺疊現有的傳輸錯誤 API。

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

與平台版本管理的關係

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

我們達成「可更新」目標的指標之一,就是我們鑄造新 ABI 修訂版本的速度。由於新增或移除彈性互動時可回溯相容,這項功能有助於提升 Fuchsia 的可更新性。

實作

  • 我們可以想像一個世界,繫結只會實作規格的嚴格部分,這樣一來,通訊會提早停止,就像對等互連端遇到其他錯誤或錯誤一樣,因此很安全。
  • 由於可演化性對 FIDL 而言至關重要 (這是首要目標),因此我們不希望出現這種情況,並要求繫結遵守這項規格。
  • 為遵守繫結規格,繫結必須實作嚴格和彈性的互動語意,以及通訊協定的三種模式。
  • 有鑑於此,我們詳細說明繫結規格的變更。這會導致 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 屬性,並提供與目前方法相同的設計。

在線路層級,指令模式會強制執行「兩種方法鑑別器」。我們在交易訊息標頭中提供序數 (識別互動),並提供聯集序數 (識別選取的聯集變體,即 FirstMethodRequest 為 1,SecondMethodRequest 為 2)。Call

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

  • 交易訊息標頭允許的嚴格程度 (沒有酬載說明,如果可以就解碼) 是聯集線路格式 (實際上是設計) 無法比擬的。這種嚴謹性和簡潔性特別適合低階用途,而 FIDL 則過度朝向這方面發展。

  • 組合模型不會有「通訊協定分組」的概念。這項功能非常強大,因為我們可以在同一個管道上多工處理多個通訊協定。我們會盡可能使用結構化組合 (即 compose 節),也會採用動態組合 (例如服務探索)。如果我們採用「所有編譯都會歸結為聯集」的觀點,就會強制執行嚴格的分組。

最後,部分 FIDL 作者希望「自動批次處理要求」。舉例來說,fuchsia.ui.scenic 程式庫以 fuchsia.ui.scenic/Session.Enqueue 方法中的指令模式而聞名。不過,提供「自動批次處理要求」是危險的功能,因為在一個單元中處理多個指令的語意,往往會因應用程式而異。如何處理不明指令?如何處理失敗的指令?是否應忽略指令、停止執行、導致中止及回溯?即使是圍繞「批次工作單元」(交易) 概念設計的 RDBMS 系統,也往往會提供許多批次模式 ([隔離層級](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 程式碼可以繼續在標頭中建構所有內容,但識別碼除外。

替代做法:互動模式位元

本 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。由於開啟和關閉不會用於互動,因此我們可以將這些狀態用於通訊協定修飾符。「半開」的定義是「部分開啟」,這正是我們想說明的概念。是的,所有相關人員都覺得這有點詭異。

既有技術和參考資料

(如內文所述)。


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

  2. 對於 fidlc 和 JSON IR 愛好者,請注意,編譯器的內部會將事件表示為 maybe_request_payload 等於 nullptrmaybe_response_payloadpresent。不過,從模型角度來看,我們將這個酬載稱為要求,但方向是從伺服器到用戶端。我們應與組合模型保持一致,變更 fidlc 和 JSON IR。這超出本 RFC 的範圍,但為了完整性,我們仍會記錄下來。 

  3. 我們偏好寬鬆的文法,以及透過 Lint 執行的樣式指南。這項設計選擇的動機是希望為新手提供更容易理解的語言,同時為 Fuchsia 平台制定非常明確 (且因此冗長) 的標準。 

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

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