FIDL 語言規格

本文件是 Fuchsia 介面定義語言 (FIDL) 的規範。

如要進一步瞭解 FIDL 的整體目的、目標和需求,請參閱總覽

另請參閱經修改的 EBNF 說明,瞭解 FIDL 文法

語法

FIDL 提供宣告資料類型和通訊協定的語法。這些宣告會收集到程式庫中,以利發布。

FIDL 宣告會儲存在 UTF-8 文字檔中。每個檔案都包含以分號分隔的宣告序列。FIDL 檔案內或程式庫內 FIDL 檔案中的宣告順序並無關聯。

留言

FIDL 註解開頭為兩個正斜線 (//),並持續到行尾。開頭為三個正斜線 (///) 的註解稱為說明文件註解,並會在產生的繫結中以註解形式傳出。

// this is a comment
/// and this one is too, but it also ends up in the generated code
type MyStruct = struct { // plain comment
    f int32; // as is this one
}; // and this is the last one!

關鍵字

下列字詞在 FIDL 中具有特殊意義:

ajar, alias, as, bits, closed, compose, const, enum, error, false, flexible,
library, open, optional, protocol, resource, service, strict, struct, table,
true, type, union, using.

不過,FIDL 沒有保留關鍵字。例如:

// Declaring a struct named "struct" is allowed, though confusing.
type struct = struct {};

// Declaring a table field named "strict" is a more reasonable example.
type Options = table {
    1: strict bool;
};

ID

程式庫名稱

FIDL 程式庫名稱標示 FIDL 程式庫。這些字串由一個或多個以點號 (.) 分隔的元件組成。每個元件都必須符合規則運算式 [a-z][a-z0-9]*。換句話說,程式庫名稱元件開頭須為小寫英文字母,可包含小寫英文字母和數字。

// A library named "foo".
library foo;

ID

FIDL 識別碼標籤宣告及其成員。必須符合正規表示式 [a-zA-Z]([a-zA-Z0-9_]*[a-zA-Z0-9])?。換句話說,識別碼開頭須為英文字母,可以包含英文字母、數字和底線,但結尾不得使用底線。

// A struct named "Foo".
type Foo = struct {};

FIDL 識別碼會區分大小寫。不過,ID 必須具有獨特的標準形式,否則 FIDL 編譯器會失敗,並顯示 fi-0035:標準名稱衝突。將 ID 轉換為 snake_case,即可取得 ID 的標準格式。

合格 ID

FIDL 一律會在目前程式庫的範圍內尋找未限定的符號。如要參照其他程式庫中的符號,您必須使用程式庫名稱或別名來限定符號。

objects.fidl:

library objects;
using textures as tex;

protocol Frob {
    // "Thing" refers to "Thing" in the "objects" library
    // "tex.Color" refers to "Color" in the "textures" library
    Paint(struct { thing Thing; color tex.Color; });
};

type Thing = struct {
    name string;
};

textures.fidl:

library textures;

type Color = struct {
    rgba uint32;
};

解析演算法

FIDL 會使用下列演算法解析 ID。如果「嘗試解決」步驟失敗,系統會繼續執行下一個步驟。如果「解析」步驟失敗,編譯器會產生錯誤。

  • 如果未符合資格:
    1. 嘗試在目前的程式庫中將其解析為宣告。
    2. 嘗試將其解析為內建宣告,例如 bool 參照 fidl.bool
    3. 以情境位元/列舉成員的形式解析,例如 zx.handle:CHANNEL 中的 CHANNEL 是指 zx.ObjType.Channel
  • 如果符合 X.Y 資格:
    1. 嘗試將 X 解析為目前程式庫中的宣告:
      1. Y 解析為 X 的元素。
    2. X 解析為程式庫名稱或別名。
      1. Y 解析為 X 中的宣告。
  • 如果它符合 x.Y.Z 的資格,其中 x 代表一或多個元件:
    1. 嘗試將 x.Y 解析為程式庫名稱或別名:
      1. Z 解析為 x.Y 中的宣告。
    2. x 解析為程式庫名稱或別名:
      1. Y 解析為 x 中的宣告:
        1. Z 解析為 x.Y 的元素。

完整名稱

FIDL 會使用完整名稱 (簡稱「FQN」) 明確指出宣告和成員。FQN 由程式庫名稱、斜線 /、宣告 ID 和點 . (選用) 和成員 ID 組成。例如:

  • fuchsia.io/MAX_BUF 是指程式庫 fuchsia.io 中的 MAX_BUF 常數。
  • fuchsia.process/Launcher.Launch 是指程式庫 fuchsia.processLauncher 通訊協定中的 Launch 方法。

FQN 會用於錯誤訊息、FIDL JSON 中介表示法、方法選取器和說明文件註解交叉參照。

常值

FIDL 支援下列類型的文字常值:

  • 布林值:truefalse
  • 整數:十進位 (123)、十六進位 (0xA1B2)、八進位 (0755)、二進位 (0b101)。
    • 只有十進制值才能為負值。
  • 浮點:1.23-0.011e52.0e-3
    • 僅限 ee-e+ 則不行。
  • 字串:"hello""\\ \" \n \r \t \u{1f642}"

數字常值 (例如 16 進位數字) 中的所有字母均不區分大小寫。

常數

FIDL 可為所有支援文字值 (布林值、整數、浮點數和字串) 的類型,以及位元和列舉,定義常數。例如:

const ENABLED_FLAG bool = true;
const OFFSET int8 = -33;
const ANSWER uint16 = 42;
const ANSWER_IN_BINARY uint16 = 0b101010;
const POPULATION_USA_2018 uint32 = 330000000;
const DIAMOND uint64 = 0x183c7effff7e3c18;
const FUCHSIA uint64 = 4054509061583223046;
const USERNAME string = "squeenze";
const MIN_TEMP float32 = -273.15;
const CONVERSION_FACTOR float64 = 1.41421358;
const MY_DRINK Beverage = Beverage.WATER;
const FEATURES InfoFeatures = InfoFeatures.WLAN | InfoFeatures.SYNTH;

常數運算式是指字面值、其他常數的參照、位元或列舉成員的參照,或是由直立線字元 (|) 分隔的位元成員組合 FIDL 不支援任何其他算術運算式,例如 1 + 2

宣告分隔符

FIDL 會使用分號 (;) 分隔檔案中的相鄰宣告,這與 C 很相似。

程式庫

程式庫是 FIDL 宣告的命名容器。

// library identifier separated by dots
library fuchsia.composition;

// "using" to import library "fuchsia.mem"
using fuchsia.mem;

// "using" to import library "fuchsia.geometry" under the alias "geo"
using fuchsia.geometry as geo;

程式庫會使用 using 宣告匯入其他程式庫。您可以使用程式庫名稱限定匯入的程式庫中的符號,如 fuchsia.mem.Range 所示。使用 using ... as 語法時,您必須使用別名限定符號,例如 geo.Rect (fuchsia.geometry.Rect 無法使用)。

libraryusing 宣告的範圍僅限於單一檔案。FIDL 程式庫中的每個檔案都必須重新宣告 library 和檔案所需的任何 using 宣告。

FIDL 編譯器不需要任何特定的目錄結構,但每個 FIDL 程式庫通常會以程式庫名稱命名,並歸入專屬目錄。含有多個檔案的程式庫通常會有一個 overview.fidl 檔案,其中只包含 library 宣告、屬性和說明文件註解。

程式庫的名稱可在語言繫結中做為命名空間使用。舉例來說,C++ 繫結產生器會將 FIDL 程式庫 fuchsia.ui 的宣告放在 C++ 命名空間 fuchsia_ui 中。同樣地,Rust 繫結產生器會產生名為 fidl_fuchsia_ui 的 Crate。

類型和類型宣告

FIDL 支援多種內建類型,以及新類型的宣告 (例如結構體、聯集、類型別名) 和通訊協定。

基本

  • 簡單的值類型。
  • 不得為選填欄位。

FIDL 支援下列原始類型:

  • 布林值 bool
  • 帶號整數 int8 int16 int32 int64
  • 無正負號的整數 uint8 uint16 uint32 uint64
  • IEEE 754 浮點 float32 float64

數字後方會加上位元大小。例如,int8 是 1 個位元組。

使用

// A record which contains fields of a few primitive types.
type Sprite = struct {
    x float32;
    y float32;
    index uint32;
    color uint32;
    visible bool;
};

位元

  • 已命名的位元類型。
  • 從基礎整數類型中選取的位元值離散子集。
  • 不得為選填欄位。
  • 位元可以是 strictflexible
  • 位元預設為 flexible
  • strict 位元至少必須包含一個成員,flexible 位元則可以為空白。

運算子

| 是位元位元 OR 運算子。

使用

type InfoFeatures = strict bits : uint8 {
    /// If present, this device represents WLAN hardware
    WLAN = 0x01;
    /// If present, this device is synthetic (not backed by h/w)
    SYNTH = 0x02;
    /// If present, this device receives all messages it sends
    LOOPBACK = 0x04;
};

// Underlying type is assumed to be uint32.
type AllowableSegments = flexible bits {
    TOLL_ROADS = 0b001;
    HIGHWAYS = 0b010;
    BIKE_PATHS = 0b100;
};

const ROADS AllowableSegments = AllowableSegments.TOLL_ROADS | AllowableSegments.HIGHWAYS;

列舉

  • 適當的列舉類型。
  • 從基礎整數類型中選擇的具名值離散子集。
  • 不得為選填欄位。
  • 列舉可以是 strictflexible
  • 列舉的預設值為 flexible
  • strict 列舉必須至少包含一個成員,flexible 列舉則可為空白。

宣告

每個列舉元素都需要指定序數。列舉的基礎類型必須為以下其中一種:int8、uint8、int16、uint16、int32、uint32、int64、uint64。如果省略,基礎類型會預設為 uint32

type Beverage = flexible enum : uint8 {
    WATER = 0;
    COFFEE = 1;
    TEA = 2;
    WHISKEY = 3;
};

// Underlying type is assumed to be uint32.
type Vessel = strict enum {
    CUP = 0;
    BOWL = 1;
    TUREEN = 2;
    JUG = 3;
};

使用

列舉類型會以其 ID 表示,必要時可加上限定條件。

// A record which contains two enum fields.
type Order = struct {
    beverage Beverage;
    vessel Vessel;
};

FIDL 食譜:列舉

enum 是一種 FIDL 資料類型,用來表示可能常數的固定清單,例如一副撲克牌中的花色,或是使用者可從下拉式選單中選取的汽車品牌。接著,系統會將這個值清單對應至基礎整數類型,每個值都會對應至所列成員之一。

在下方範例中,我們在最適合使用列舉的情況下新增了 FIDL 列舉:列舉可能因失敗的函式呼叫而產生的錯誤值。ReadError 列舉有兩個成員:NOT_FOUND 用於指出在讀取嘗試期間無法比對搜尋鍵,而 UNKNOWN 則用於所有無法明確描述的情況,用來表示 grab-bag 錯誤。請注意,這個列舉標示為 flexible,方便日後輕鬆加入新成員。

推理

原先的唯寫鍵/值儲存庫現在已擴充,可讀取儲存庫中的項目。

實作

套用至 FIDL 和 CML 定義的變更如下:

FIDL

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
library examples.keyvaluestore.addreaditem;

// Aliases for the key and value. Using aliases helps increase the readability of FIDL files and
// reduces likelihood of errors due to differing constraints.
alias Key = string:128;
alias Value = vector<byte>:64000;

/// An item in the store. The key must match the regex `^[A-z][A-z0-9_\.\/]{2,62}[A-z0-9]$`. That
/// is, it must start with a letter, end with a letter or number, contain only letters, numbers,
/// periods, and slashes, and be between 4 and 64 characters long.
type Item = struct {
    key Key;
    value Value;
};

/// An enumeration of things that may go wrong when trying to write a value to our store.
type WriteError = flexible enum {
    UNKNOWN = 1; // Avoid 0 in errors as it may be confused with success.
    INVALID_KEY = 2;
    INVALID_VALUE = 3;
    ALREADY_EXISTS = 4;
};

/// An enumeration of things that may go wrong when trying to read a value out of our store.
type ReadError = flexible enum {
    UNKNOWN = 1; // Avoid 0 in errors as it may be confused with success.
    NOT_FOUND = 2;
};

/// A very basic key-value store - so basic, in fact, that one may only write to it, never read!
@discoverable
open protocol Store {
    /// Writes an item to the store.
    flexible WriteItem(struct {
        attempt Item;
    }) -> () error WriteError;

    /// Reads an item from the store.
    flexible ReadItem(struct {
        key Key;
    }) -> (Item) error ReadError;
};

CML

用戶端

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
{
    include: [ "syslog/client.shard.cml" ],
    program: {
        runner: "elf",
        binary: "bin/client_bin",
    },
    use: [
        { protocol: "examples.keyvaluestore.addreaditem.Store" },
    ],
    config: {
        write_items: {
            type: "vector",
            max_count: 16,
            element: {
                type: "string",
                max_size: 64,
            },
        },

        read_items: {
            type: "vector",
            max_count: 16,
            element: {
                type: "string",
                max_size: 64,
            },
        },

    },
}

伺服器

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
{
    include: [ "syslog/client.shard.cml" ],
    program: {
        runner: "elf",
        binary: "bin/server_bin",
    },
    capabilities: [
        { protocol: "examples.keyvaluestore.addreaditem.Store" },
    ],
    expose: [
        {
            protocol: "examples.keyvaluestore.addreaditem.Store",
            from: "self",
        },
    ],
}

運作範圍

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
{
    children: [
        {
            name: "client",
            url: "#meta/client.cm",
        },
        {
            name: "server",
            url: "#meta/server.cm",
        },
    ],
    offer: [
        // Route the protocol under test from the server to the client.
        {
            protocol: "examples.keyvaluestore.addreaditem.Store",
            from: "#server",
            to: "#client",
        },

        // Route diagnostics support to all children.
        {
            dictionary: "diagnostics",
            from: "parent",
            to: [
                "#client",
                "#server",
            ],
        },
    ],
}

所有語言的用戶端和伺服器實作方式也都會變更:

荒漠油廠

用戶端

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use anyhow::{Context as _, Error};
use config::Config;
use fidl_examples_keyvaluestore_addreaditem::{Item, StoreMarker};
use fuchsia_component::client::connect_to_protocol;
use std::{str, thread, time};

#[fuchsia::main]
async fn main() -> Result<(), Error> {
    println!("Started");

    // Load the structured config values passed to this component at startup.
    let config = Config::take_from_startup_handle();

    // Use the Component Framework runtime to connect to the newly spun up server component. We wrap
    // our retained client end in a proxy object that lets us asynchronously send `Store` requests
    // across the channel.
    let store = connect_to_protocol::<StoreMarker>()?;
    println!("Outgoing connection enabled");

    // This client's structured config has one parameter, a vector of strings. Each string is the
    // path to a resource file whose filename is a key and whose contents are a value. We iterate
    // over them and try to write each key-value pair to the remote store.
    for key in config.write_items.into_iter() {
        let path = format!("/pkg/data/{}.txt", key);
        let value = std::fs::read_to_string(path.clone())
            .with_context(|| format!("Failed to load {path}"))?;
        match store.write_item(&Item { key: key, value: value.into_bytes() }).await? {
            Ok(_) => println!("WriteItem Success"),
            Err(err) => println!("WriteItem Error: {}", err.into_primitive()),
        }
    }

    // The structured config for this client contains `read_items`, a vector of strings, each of
    // which is meant to be read from the key-value store. We iterate over these keys, attempting to
    // read them in turn.
    for key in config.read_items.into_iter() {
        let res = store.read_item(key.as_str()).await;
        match res.unwrap() {
            Ok(val) => {
                println!("ReadItem Success: key: {}, value: {}", key, str::from_utf8(&val.1)?)
            }
            Err(err) => println!("ReadItem Error: {}", err.into_primitive()),
        }
    }

    // TODO(https://fxbug.dev/42156498): We need to sleep here to make sure all logs get drained. Once the
    // referenced bug has been resolved, we can remove the sleep.
    thread::sleep(time::Duration::from_secs(2));
    Ok(())
}

伺服器

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use {
    anyhow::{Context as _, Error},
    fidl_examples_keyvaluestore_addreaditem::{
        Item, ReadError, StoreRequest, StoreRequestStream, WriteError,
    },
    fuchsia_component::server::ServiceFs,
    futures::prelude::*,
    lazy_static::lazy_static,
    regex::Regex,
    std::cell::RefCell,
    std::collections::hash_map::Entry,
    std::collections::HashMap,
};

lazy_static! {
    static ref KEY_VALIDATION_REGEX: Regex =
        Regex::new(r"^[A-Za-z][A-Za-z0-9_\./]{2,62}[A-Za-z0-9]$")
            .expect("Key validation regex failed to compile");
}

/// Handler for the `WriteItem` method.
fn write_item(store: &mut HashMap<String, Vec<u8>>, attempt: Item) -> Result<(), WriteError> {
    // Validate the key.
    if !KEY_VALIDATION_REGEX.is_match(attempt.key.as_str()) {
        println!("Write error: INVALID_KEY, For key: {}", attempt.key);
        return Err(WriteError::InvalidKey);
    }

    // Validate the value.
    if attempt.value.is_empty() {
        println!("Write error: INVALID_VALUE, For key: {}", attempt.key);
        return Err(WriteError::InvalidValue);
    }

    // Write to the store, validating that the key did not already exist.
    match store.entry(attempt.key) {
        Entry::Occupied(entry) => {
            println!("Write error: ALREADY_EXISTS, For key: {}", entry.key());
            Err(WriteError::AlreadyExists)
        }
        Entry::Vacant(entry) => {
            println!("Wrote value at key: {}", entry.key());
            entry.insert(attempt.value);
            Ok(())
        }
    }
}

/// Creates a new instance of the server. Each server has its own bespoke, per-connection instance
/// of the key-value store.
async fn run_server(stream: StoreRequestStream) -> Result<(), Error> {
    // Create a new in-memory key-value store. The store will live for the lifetime of the
    // connection between the server and this particular client.
    let store = RefCell::new(HashMap::<String, Vec<u8>>::new());

    // Serve all requests on the protocol sequentially - a new request is not handled until its
    // predecessor has been processed.
    stream
        .map(|result| result.context("failed request"))
        .try_for_each(|request| async {
            // Match based on the method being invoked.
            match request {
                StoreRequest::WriteItem { attempt, responder } => {
                    println!("WriteItem request received");

                    // The `responder` parameter is a special struct that manages the outgoing reply
                    // to this method call. Calling `send` on the responder exactly once will send
                    // the reply.
                    responder
                        .send(write_item(&mut store.borrow_mut(), attempt))
                        .context("error sending reply")?;
                    println!("WriteItem response sent");
                }
                StoreRequest::ReadItem { key, responder } => {
                    println!("ReadItem request received");

                    // Read the item from the store, returning the appropriate error if it could not be found.
                    responder
                        .send(match store.borrow().get(&key) {
                            Some(found) => {
                                println!("Read value at key: {}", key);
                                Ok((&key, found))
                            }
                            None => {
                                println!("Read error: NOT_FOUND, For key: {}", key);
                                Err(ReadError::NotFound)
                            }
                        })
                        .context("error sending reply")?;
                    println!("ReadItem response sent");
                } //
                StoreRequest::_UnknownMethod { ordinal, .. } => {
                    println!("Received an unknown method with ordinal {ordinal}");
                }
            }
            Ok(())
        })
        .await
}

// A helper enum that allows us to treat a `Store` service instance as a value.
enum IncomingService {
    Store(StoreRequestStream),
}

#[fuchsia::main]
async fn main() -> Result<(), Error> {
    println!("Started");

    // Add a discoverable instance of our `Store` protocol - this will allow the client to see the
    // server and connect to it.
    let mut fs = ServiceFs::new_local();
    fs.dir("svc").add_fidl_service(IncomingService::Store);
    fs.take_and_serve_directory_handle()?;
    println!("Listening for incoming connections");

    // The maximum number of concurrent clients that may be served by this process.
    const MAX_CONCURRENT: usize = 10;

    // Serve each connection simultaneously, up to the `MAX_CONCURRENT` limit.
    fs.for_each_concurrent(MAX_CONCURRENT, |IncomingService::Store(stream)| {
        run_server(stream).unwrap_or_else(|e| println!("{:?}", e))
    })
    .await;

    Ok(())
}

C++ (自然)

用戶端

// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.

伺服器

// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.

C++ (Wire)

用戶端

// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.

伺服器

// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.

HLCPP

用戶端

// TODO(https://fxbug.dev/42060656): HLCPP implementation.

伺服器

// TODO(https://fxbug.dev/42060656): HLCPP implementation.

陣列

  • 同質元素的固定長度序列。
  • 元素可以是任何類型。
  • 本身不能是選用型別,但可包含選用型別。

使用

陣列的符號為 array<T, N>,其中 T 可以是任何 FIDL 類型 (包括陣列),而 N 是正整數常數運算式,可指定陣列中的元素數量。

// A record which contains some arrays.
type Arrays = struct {
    // array of exactly 16 floating point numbers
    matrix array<float32, 16>;

    // array of exactly 10 arrays of 4 strings each
    form array<array<string, 4>, 10>;
};

請注意,N 會顯示為版面配置參數,表示會影響類型的 ABI。換句話說,變更參數 _N_破壞 ABI

字串

  • 以 UTF-8 編碼表示文字的變長度位元組序列。
  • 可選填;不填入字串和空字串的情況不同。
  • 可以指定大小上限,例如 string:40 最多 40 個位元組的字串。根據預設,string 代表 string:MAX,也就是無限。
  • 字串文字支援逸出序列 \\\"\n\r\t\u{X},其中 X 是萬國碼代碼點的 1 到 6 個十六進制數字。
  • 可能包含嵌入的 NUL 位元組,這與傳統 C 字串不同。

使用

字串的表示方式如下:

  • string:必要字串 (如未提供,則會發生驗證錯誤)
  • string:optional:選用字串
  • string:N, string:<N, optional>:字串和選用字串,分別以 N 位元組為單位的長度上限
// A record which contains some strings.
type Document = struct {
    // title string, maximum of 40 bytes long
    title string:40;

    // description string, may be null, no upper bound on size
    description string:optional;
};

請注意,N 會以限制條件形式顯示 (出現在 : 之後),表示不會影響類型的 ABI。換句話說,變更參數 _N_ 並不會造成 ABI 中斷的變更。

由於繫結會強制執行有效的 UTF-8,因此不應使用字串傳遞任意二進位資料。請改為考慮使用 bytes 傳輸小型資料,或使用 fuchsia.mem.Buffer 傳輸 Blob。詳情請參閱「我應使用字串還是向量?」。

向量

  • 同質元素的變數長度序列。
  • 可為選用值;缺少向量和空向量是不同的概念。
  • 可指定大小上限,例如 vector<T>:40 可指定最多 40 個元素的向量。根據預設,vector<T> 代表 vector<T>:MAX,也就是無限。
  • 布林值向量沒有特殊情況。每個布林元素都會佔用一個位元組,這一點與以往相同。

使用

向量如下所示:

  • vector<T>:元素類型 T 的必要向量 (如果缺少,則會發生驗證錯誤)
  • vector<T>:optional:元素類型 T 的選用向量
  • vector<T>:Nvector<T>:<N, optional>:分別為向量和選用向量,長度上限為 N 個元素

T 可以是任何 FIDL 類型。

// A record which contains some vectors.
type Vectors = struct {
    // a vector of up to 10 integers
    params vector<int32>:10;

    // a vector of bytes, no upper bound on size
    blob vector<uint8>;

    // a nullable vector of up to 24 strings
    nullable_vector_of_strings vector<string>:<24, optional>;

    // a vector of nullable strings, no upper bound on size
    vector_of_nullable_strings vector<string:optional>;

    // a vector of vectors of 16-element arrays of floating point numbers
    complex vector<vector<array<float32, 16>>>;
};

帳號代碼

  • 透過句柄值轉移 Zircon 能力。
  • 以 32 位元無正負號整數的形式儲存。
  • 可選用;缺少的句柄會編碼為零值句柄。
  • 您可以選擇將句柄與特定類型和一組 Zircon 權限相關聯。

使用

帳號代碼的表示方式如下:

  • zx.Handle:必要的 Zircon 處理常式,類型不明
  • zx.Handle:optional:選用的 Zircon 處理常式,類型不詳
  • zx.Handle:H:需要 H 類型的 Zircon 處理程序
  • zx.Handle:<H, optional>:選用的 Zircon 手把,類型為 H
  • zx.Handle:<H, R>:需要 H 類型的 Zircon 句柄,且具有 R 權限
  • zx.Handle:<H, R, optional>:選用的 Zircon 句柄,類型為 H,權限為 R

H 可以是 Zircon 支援的任何物件,例如 channelthreadvmo。如需完整清單,請參閱文法

R 可以是 Zircon 支援的任何權限。權限是位元型值,在 zx FIDL 程式庫中定義,例如 zx.Rights.READ。在傳入和傳出方向中,系統會驗證句柄是否具有正確的 Zircon 物件類型,且至少具有 FIDL 中指定的權限。如果句柄擁有的權限多於 FIDL 中指定的權限,則呼叫 zx_handle_replace 會減少其權限。如需範例,請參閱「句柄的生命週期」,如需進一步瞭解詳情,請參閱「RFC-0028:句柄權利」。

包含句柄的結構體、資料表和聯集必須標示 resource 修飾符

// A record which contains some handles.
type Handles = resource struct {
    // a handle of unspecified type
    h zx.Handle;

    // an optional channel
    c zx.Handle:<CHANNEL, optional>;
};

Structs

  • 記錄類型,由一系列指定類型的欄位組成。
  • 新增或移除欄位,或變更欄位類型,通常不相容於 ABI。
  • 宣告可以包含 resource 修飾符
  • 參考資料可能會被box
  • 結構體包含零個或多個成員。

宣告

type CirclePoint = struct {
    x float32;
    y float32;
};

type Color = struct {
    r float32;
    g float32;
    b float32;
};

使用

結構體會以宣告的名稱表示 (例如 Circle):

  • Circle:必填的社交圈
  • box<Circle>:選用圓圈,離線儲存。
type Circle = struct {
    filled bool;
    center CirclePoint; // CirclePoint will be stored in-line
    radius float32;
    color box<Color>; // Color will be stored out-of-line
    dashed bool;
};

表格

  • 記錄類型,由具有序號的型別欄位序列組成。
  • 宣告可在結構定義變更時,確保向前和向後的相容性。
  • 宣告可以包含 resource 修飾符
  • 表格不得為選填項目。為避免處理雙重選用性,我們會使用空白表格來表示「缺少值」的語意,也就是所有成員都缺席的情況。
  • 表格包含零個或多個成員。

宣告

type Profile = table {
    1: locales vector<string>;
    2: calendars vector<string>;
    3: time_zones vector<string>;
};

使用

表格會以宣告的名稱表示 (例如「Profile」):

  • Profile:必要的設定檔

以下說明 Profile 如何演進,以便攜帶溫度單位。瞭解先前 Profile 定義 (不含溫度單位) 的用戶端,仍可將其設定檔傳送至已更新的伺服器,以便處理更大的欄位集合。

type TemperatureUnit = enum {
    CELSIUS = 1;
    FAHRENHEIT = 2;
};

type Profile = table {
    1: locales vector<string>;
    2: calendars vector<string>;
    3: time_zones vector<string>;
    4: temperature_unit TemperatureUnit;
};

聯合體

  • 記錄類型,包含序數和封套。
  • 序號表示成員選取,信封則會保留內容。
  • 宣告可以在部署後修改,同時維持 ABI 相容性。如需來源相容性考量,請參閱相容性指南
  • 宣告可以包含 resource 修飾符
  • 參考資料為選填項目。
  • 聯集可以是 strictflexible
  • 聯合體的預設值為 flexible
  • strict 聯集必須包含一或多個成員。沒有成員的聯合體不會有居民,因此在電報格式中沒有太大意義。不過,系統允許使用無成員的 flexible 聯集,因為仍可對無成員聯集進行解碼 (內含的資料一律為「unknown」)。

宣告

/// The result of an operation. A result is either a single number or an
/// [Error] value.
type Result = union {
    1: number float64;
    2: reserved;
    3: error Error;
};

使用

聯集會以宣告名稱 (例如 Result) 和選用性表示:

  • Result:必要結果
  • Result:optional:選用結果
type Either = strict union {
    1: left Left;
    2: right Right;
};

嚴格與彈性

FIDL 類型宣告可以具有嚴格彈性行為:

  • 除非使用 strict 修飾符宣告,否則位元、列舉和聯集都是彈性的。
  • 結構體一律具有嚴格的行為。
  • 表格一律具有彈性的行為。

只有在嚴格類型中,序列化或反序列化包含宣告中未描述資料的值,才會發生驗證錯誤

在這個例子中:

type FlexibleEither = flexible union {
    1: left Left;
    2: right Right;
};

由於 FlexibleEither 具有彈性,因此更容易進化為第三種變化版本。若是瞭解 FlexibleEither 先前定義的用戶端,而該定義沒有第三個變化版本,則仍可從已更新為包含更多變化版本的伺服器接收聯集。如果聯集是未知的變化版本,繫結可能會將其當成未知資料 (即原始位元組和句柄) 公開給使用者,並允許重新編碼未知聯集 (例如支援類似 Proxy 的用途)。繫結參考資料中詳細說明瞭提供的彈性類型互動方法,可用於與不明資料互動。

詳情請參閱 RFC-0033:處理不明欄位和嚴格性

價值與資源

每個 FIDL 類型都是值類型資源類型。資源類型包括:

  • handles
  • 通訊協定端點
  • 資源類型的別名
  • 資源類型的陣列和向量
  • 標示為 resource 修飾符的結構體、表格和聯集
  • 對上述任一類型的選用 (或封裝) 參照

所有其他類型都是值類型。

值類型不得包含資源類型。例如,以下做法是錯誤的:

type Foo = struct { // ERROR: must be "resource struct Foo"
    h zx.Handle;
};

即使類型不含句柄,也可以標示 resource 修飾符。如果您打算在日後為類型新增句柄,則應採取這項做法,因為新增或移除 resource 修飾符需要考量來源相容性。例如:

// No handles now, but we will add some in the future.
type Record = resource table {
    1: str string;
};

// "Foo" must be a resource because it contains "Record", which is a resource.
type Foo = resource struct {
    record Record;
};

詳情請參閱 RFC-0057:預設無句柄

通訊協定

  • 說明可透過管道傳送訊息來叫用的各種方法。
  • 方法會透過序數識別。編譯器的計算方式如下:
    • 取得方法的完整名稱的 SHA-256 雜湊。
    • 擷取雜湊摘要的前 8 個位元組,
    • 將這些位元組解讀為 little endian 整數
    • 將該值的較高位元 (即最後一個位元) 設為 0。
    • 如要覆寫序數,方法可以使用 @selector 屬性。如果屬性的引數是有效的 FQN,系統會使用該 FQN 取代上述 FQN。否則,它必須是有效的 ID,並在建構 FQDN 時取代方法名稱。
  • 每個方法宣告都會指出其引數和結果。

    • 如果未宣告任何結果,則該方法為單向:伺服器不會產生任何回應。
    • 如果宣告結果 (即使為空白),則該方法為雙向:每次叫用該方法都會產生伺服器的回應。
    • 如果只宣告結果,則該方法稱為事件。接著,定義伺服器傳送的非預期訊息。
    • 雙向方法可能會宣告伺服器可傳送的錯誤類型,而非回應。此類型必須是 int32uint32 或其 enum
  • 當通訊協定的伺服器即將關閉管道時,可以選擇傳送epitaph 訊息給用戶端,以表示連線的處置方式。墓誌銘文必須是透過管道傳送的最後一則訊息。輓碑訊息包含 32 位元整數值,類型為 zx_status_t。負值則是系統錯誤代碼的預留值。值 ZX_OK (0) 表示作業成功。應用程式定義的錯誤代碼 (先前定義為所有正 zx_status_t 值) 已淘汰。如要進一步瞭解墓誌銘,請參閱 RFC-0031:已定義的墓誌銘的拒絕原因。如要進一步瞭解 zx_status_t,請參閱 RFC-0085:縮減 zx_status_t 空間

宣告

type DivisionError = strict enum : uint32 {
    DIVIDE_BY_ZERO = 1;
};

protocol Calculator {
    Add(struct {
        a int32;
        b int32;
    }) -> (struct {
        sum int32;
    });
    Divide(struct {
        dividend int32;
        divisor int32;
    }) -> (struct {
        quotient int32;
        remainder int32;
    }) error DivisionError;
    Clear();
    -> OnError(struct {
        status_code uint32;
    });
};

使用

協定會以名稱、管道方向和選用性來表示:

  • client_end:Protocol:透過 FIDL 通訊協定進行通訊的管道用戶端端點
  • client_end:<Protocol, optional>:上述選用版本
  • server_end:Protocol:透過 FIDL 通訊協定通訊的管道伺服器端點
  • server_end:<Protocol, optional>:上述選用版本
// A record which contains protocol-bound channels.
type Record = resource struct {
    // client endpoint of a channel bound to the Calculator protocol
    c client_end:Calculator;

    // server endpoint of a channel bound to the Science protocol
    s server_end:Science;

    // optional client endpoint of a channel bound to the
    // RealCalculator protocol
    r client_end:<RealCalculator, optional>;
};

通訊協定組合

協定可包含其他協定的做法。這就是所謂的組合:您可以使用其他通訊協定組合一個通訊協定。

在下列情況中會使用合成功能:

  1. 您有幾個通訊協定,且這些通訊協定都具有某些共同行為
  2. 您想向不同目標對象公開不同層級的功能

常見行為

在第一種情況下,可能會有跨多個通訊協定共用的行為。舉例來說,在圖像系統中,多個不同的通訊協定可能都需要設定背景和前景顏色。您可以定義通用的通訊協定,而非讓每個通訊協定都定義自己的顏色設定方法:

protocol SceneryController {
    SetBackground(struct {
        color Color;
    });
    SetForeground(struct {
        color Color;
    });
};

接著,其他通訊協定可分享這項資訊:

protocol Drawer {
    compose SceneryController;
    Circle(struct {
        x int32;
        y int32;
        radius int32;
    });
    Square(struct {
        x int32;
        y int32;
        diagonal int32;
    });
};

protocol Writer {
    compose SceneryController;
    Text(struct {
        x int32;
        y int32;
        message string;
    });
};

上述內容包含三個通訊協定:SceneryControllerDrawerWriterDrawer 用於繪製圖形物件,例如在指定位置繪製指定大小的圓形和正方形。因為它包含 compose 關鍵字的 SceneryController 通訊協定,因此會組合 SceneryController 通訊協定的 SetBackground()SetForeground() 方法。

Writer 通訊協定用於在螢幕上寫入文字,並以相同方式納入 SceneryController 通訊協定。

DrawerWriter 現在都包含 SetBackground()SetForeground()

與讓 DrawerWriter 指定其顏色設定方法相比,這麼做有幾項優點:

  • 無論是用於繪製圓形、方形或在螢幕上顯示文字,設定背景和前景顏色的方式都相同。
  • 您可以將新的方法新增至 DrawerWriter,而無須變更定義,只要將這些方法新增至 SceneryController 通訊協定即可。

最後一點特別重要,因為這可讓我們為現有通訊協定新增功能。舉例來說,我們可能會在圖像系統中導入 Alpha 混合 (或「透明度」) 功能。您可以擴充 SceneryController 通訊協定來處理,例如:

protocol SceneryController {
    SetBackground(struct { color Color; });
    SetForeground(struct { color Color; });
    SetAlphaChannel(struct { a int; });
};

我們現在已擴充 DrawerWriter,以便支援 alpha 混合。

多個作品

組合並非一對一關係,我們可以將多個組合納入特定通訊協定,且並非所有通訊協定都需要由相同的納入通訊協定組成。

舉例來說,我們可以設定字型特徵。字型對 Drawer 通訊協定來說並沒有意義,但對 Writer 通訊協定和其他通訊協定來說,字型確實有意義。

因此,我們定義 FontController 通訊協定:

protocol FontController {
    SetPointSize(struct {
        points int32;
    });
    SetFontName(struct {
        fontname string;
    });
    Italic(struct {
        onoff bool;
    });
    Bold(struct {
        onoff bool;
    });
    Underscore(struct {
        onoff bool;
    });
    Strikethrough(struct {
        onoff bool;
    });
};

然後使用 compose 關鍵字邀請 Writer 加入:

protocol Writer {
    compose SceneryController;
    compose FontController;
    Text(struct { x int; y int; message string; });
};

在此,我們使用 FontController 通訊協定的相關方法擴充 Writer 通訊協定,而不會影響 Drawer 通訊協定 (該通訊協定不需要知道任何有關字型的資訊)。

通訊協定組合與mixin 類似。如需更多詳細資料,請參閱 RFC-0023:組合式模型

分層

在本節開頭,我們提到了組合的第二種用途,也就是向不同目標對象公開不同層級的功能。

在這個範例中,我們有兩個獨立實用的通訊協定,一個是 Clock 通訊協定,用於取得目前的時間和時區:

protocol Clock {
    Now() -> (struct {
        time Time;
    });
    CurrentTimeZone() -> (struct {
        timezone string;
    });
};

以及設定時間和時區的 Horologist 通訊協定:

protocol Horologist {
    SetTime(struct {
        time Time;
    });
    SetCurrentTimeZone(struct {
        timezone string;
    });
};

我們不一定想將較高權限的 Horologist 通訊協定公開給任何用戶端,但我們確實想將其公開給系統時鐘元件。因此,我們建立了一個通訊協定 (SystemClock),其中包含這兩種元素:

protocol SystemClock {
    compose Clock;
    compose Horologist;
};

不明互動

通訊協定可定義在收到方法呼叫或事件時,如何回應不受支援的序號。未經認證的序數通常是因為用戶端和伺服器使用不同版本的通訊協定 (可能有更多或更少方法) 而導致,但如果用戶端和伺服器在同一個管道上誤用不同通訊協定,也會發生這種情況。

如要控制發生這些不明互動時的通訊協定行為,可以將方法標示為 strictflexible,並將通訊協定標示為 closedajaropen

方法嚴謹度修飾符 strictflexible 會指定,如果傳送端不認識序號,則傳送端如何回應互動。對於單向或雙向方法,傳送端是用戶端,而事件的傳送端則是伺服器。

  • strict 表示接收端不知道互動情形應屬於錯誤。如果收到 strict 不明互動,接收器應關閉管道。
  • flexible 表示應用程式應處理不明互動。如果通訊協定允許這類不明互動,序數會傳遞至不明互動處理常式,後者可決定如何回應。通訊協定允許哪些類型的不明互動,取決於通訊協定修飾符。如果未指定嚴格性,預設值為 flexible

通訊協定開放度修飾符 closedajaropen 會控制接收端如何回應 flexible 互動 (如果不辨識序號)。對於單向或雙向方法,接收端是伺服器,而對於事件,接收端是用戶端。

  • closed 表示通訊協定不接受任何不明的互動。無論互動是 strict 還是 flexible,如果收到任何不明互動,繫結都會回報錯誤並結束通訊。
    • 所有方法和事件都必須宣告為 strict
  • ajar 表示通訊協定允許不明的 flexible 單向方法和事件。任何不明的雙向方法和 strict 單向方法或事件仍會導致錯誤,並導致繫結關閉管道。
    • 單向方法和事件可以宣告為 strictflexible
    • 雙向方法必須宣告為 strict
  • open 表示通訊協定允許任何不明的 flexible 互動。任何不明的 strict 互動仍會導致錯誤,並導致繫結關閉管道。如果未指定開放程度,預設值為「Open」。
    • 所有方法和事件都可以宣告為 strictflexible

以下摘要說明每種通訊協定類型中,哪些嚴格度修飾符可用於不同類型的程式。開放度的預設值 open斜體標示。嚴格度的預設值 flexible粗體標示。

strict M(); flexible M(); strict -> M(); 彈性 -> M(); strict M() -> (); 彈性 M() -> ();
open P 編譯 編譯 編譯 編譯 編譯 編譯
半開 P 編譯 編譯 編譯 編譯 編譯 無法編譯
已關閉 P 編譯 無法編譯 編譯 無法編譯 編譯 無法編譯

在通訊協定上使用修飾符的示例。

open protocol Moderator {
    flexible GetPosts() -> (Posts);
    strict ApplyModeration(struct {
        post Post;
        decision Decision;
    }) -> ();
};

ajar protocol Messenger {
    strict EnableSecureMode();
    flexible AddMessageContent(struct {
        content string;
    });
    strict SendPending() -> ();
    flexible -> OnReceiveMessage(Message);
};

請注意,只有在接收端無法辨識序數,且不知道互動內容為何時,才會套用未知互動處理程序。也就是說,接收端不知道互動內容是否應嚴格或彈性處理。為了讓接收端知道如何處理不明的互動,傳送端會在訊息標頭中加入一些位元,告訴接收端是否要將互動視為嚴格或彈性。因此,互動中使用的嚴格程度取決於傳送端嘗試呼叫的方法的嚴格程度,但該互動通訊協定的開放程度則取決於接收端的開放程度。

以下說明在接收端不認識方法的情況下,通訊協定如何處理不同嚴謹度的方法或事件。

strict M(); 彈性 M(); strict -> M(); 彈性 -> M(); strict M() -> (); 彈性 M() -> ();
開啟 P 自動關閉 可處理 自動關閉 可處理 自動關閉 可處理
半開 P 自動關閉 可處理 自動關閉 可處理 自動關閉 自動關閉
已關閉 P 自動關閉 自動關閉 自動關閉 自動關閉 自動關閉 自動關閉

與合成內容互動

flexible 方法和事件無法在 closed 通訊協定中宣告,flexible 雙向方法也無法在 ajar 通訊協定中宣告。為確保這些規則在通訊協定組合中一律生效,一個通訊協定只能組合至少與其同樣封閉的其他通訊協定:

  • open:可組合任何通訊協定。
  • ajar:可組合 ajarclosed 通訊協定。
  • closed:只能組合其他 closed 通訊協定。

疊頻效應

支援類型別名。例如:

const MAX_SIZE uint32 = 100;
alias StoryID = string:MAX_SIZE;
alias Chapters = vector<StoryID>:5;

在上方範例中,StoryIDstring 宣告的別名,最大大小為 MAX_SIZE。ID Chapters 是五個 StoryId 元素的向量宣告別名。

只要可使用別名定義,就可以使用 StoryIDChapters 這兩個 ID。請考慮:

type Message = struct {
    baseline StoryID;
    chapters Chapters;
};

在此,Message 結構體包含名為 baselineMAX_SIZE 位元組字串,以及名為 chaptersMAX_SIZE 向量,最多可包含 5MAX_SIZE 字串。

FIDL 食譜:別名

alias 是 FIDL 宣告,可為現有類型指派新名稱。這麼做有幾個好處:

  • 使用 alias 可確保別名類型代表的概念有單一可靠資料來源。
  • 它提供命名方式,特別是受限制的類型。
  • 對現在已建立別名的型別的不同用途,可能會連結為相同概念的例項。

請注意,別名目前不會傳遞至產生的繫結程式碼。換句話說,指派給 alias 宣告的名稱,絕不會在產生的 FIDL 程式碼中顯示為宣告名稱。

在本範例中,為 Key 新增 alias 可避免重複使用自訂名稱,同時向讀者清楚說明 Item 類型上的 key 值和 ReadItem 要求結構體中使用的 key 是刻意重複,而非巧合。

推理

原先的唯寫鍵/值儲存庫現在已擴充,可讀取儲存庫中的項目。

實作

套用至 FIDL 和 CML 定義的變更如下:

FIDL

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
library examples.keyvaluestore.addreaditem;

// Aliases for the key and value. Using aliases helps increase the readability of FIDL files and
// reduces likelihood of errors due to differing constraints.
alias Key = string:128;
alias Value = vector<byte>:64000;

/// An item in the store. The key must match the regex `^[A-z][A-z0-9_\.\/]{2,62}[A-z0-9]$`. That
/// is, it must start with a letter, end with a letter or number, contain only letters, numbers,
/// periods, and slashes, and be between 4 and 64 characters long.
type Item = struct {
    key Key;
    value Value;
};

/// An enumeration of things that may go wrong when trying to write a value to our store.
type WriteError = flexible enum {
    UNKNOWN = 1; // Avoid 0 in errors as it may be confused with success.
    INVALID_KEY = 2;
    INVALID_VALUE = 3;
    ALREADY_EXISTS = 4;
};

/// An enumeration of things that may go wrong when trying to read a value out of our store.
type ReadError = flexible enum {
    UNKNOWN = 1; // Avoid 0 in errors as it may be confused with success.
    NOT_FOUND = 2;
};

/// A very basic key-value store - so basic, in fact, that one may only write to it, never read!
@discoverable
open protocol Store {
    /// Writes an item to the store.
    flexible WriteItem(struct {
        attempt Item;
    }) -> () error WriteError;

    /// Reads an item from the store.
    flexible ReadItem(struct {
        key Key;
    }) -> (Item) error ReadError;
};

CML

用戶端

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
{
    include: [ "syslog/client.shard.cml" ],
    program: {
        runner: "elf",
        binary: "bin/client_bin",
    },
    use: [
        { protocol: "examples.keyvaluestore.addreaditem.Store" },
    ],
    config: {
        write_items: {
            type: "vector",
            max_count: 16,
            element: {
                type: "string",
                max_size: 64,
            },
        },

        read_items: {
            type: "vector",
            max_count: 16,
            element: {
                type: "string",
                max_size: 64,
            },
        },

    },
}

伺服器

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
{
    include: [ "syslog/client.shard.cml" ],
    program: {
        runner: "elf",
        binary: "bin/server_bin",
    },
    capabilities: [
        { protocol: "examples.keyvaluestore.addreaditem.Store" },
    ],
    expose: [
        {
            protocol: "examples.keyvaluestore.addreaditem.Store",
            from: "self",
        },
    ],
}

運作範圍

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
{
    children: [
        {
            name: "client",
            url: "#meta/client.cm",
        },
        {
            name: "server",
            url: "#meta/server.cm",
        },
    ],
    offer: [
        // Route the protocol under test from the server to the client.
        {
            protocol: "examples.keyvaluestore.addreaditem.Store",
            from: "#server",
            to: "#client",
        },

        // Route diagnostics support to all children.
        {
            dictionary: "diagnostics",
            from: "parent",
            to: [
                "#client",
                "#server",
            ],
        },
    ],
}

所有語言的用戶端和伺服器實作方式也都會變更:

荒漠油廠

用戶端

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use anyhow::{Context as _, Error};
use config::Config;
use fidl_examples_keyvaluestore_addreaditem::{Item, StoreMarker};
use fuchsia_component::client::connect_to_protocol;
use std::{str, thread, time};

#[fuchsia::main]
async fn main() -> Result<(), Error> {
    println!("Started");

    // Load the structured config values passed to this component at startup.
    let config = Config::take_from_startup_handle();

    // Use the Component Framework runtime to connect to the newly spun up server component. We wrap
    // our retained client end in a proxy object that lets us asynchronously send `Store` requests
    // across the channel.
    let store = connect_to_protocol::<StoreMarker>()?;
    println!("Outgoing connection enabled");

    // This client's structured config has one parameter, a vector of strings. Each string is the
    // path to a resource file whose filename is a key and whose contents are a value. We iterate
    // over them and try to write each key-value pair to the remote store.
    for key in config.write_items.into_iter() {
        let path = format!("/pkg/data/{}.txt", key);
        let value = std::fs::read_to_string(path.clone())
            .with_context(|| format!("Failed to load {path}"))?;
        match store.write_item(&Item { key: key, value: value.into_bytes() }).await? {
            Ok(_) => println!("WriteItem Success"),
            Err(err) => println!("WriteItem Error: {}", err.into_primitive()),
        }
    }

    // The structured config for this client contains `read_items`, a vector of strings, each of
    // which is meant to be read from the key-value store. We iterate over these keys, attempting to
    // read them in turn.
    for key in config.read_items.into_iter() {
        let res = store.read_item(key.as_str()).await;
        match res.unwrap() {
            Ok(val) => {
                println!("ReadItem Success: key: {}, value: {}", key, str::from_utf8(&val.1)?)
            }
            Err(err) => println!("ReadItem Error: {}", err.into_primitive()),
        }
    }

    // TODO(https://fxbug.dev/42156498): We need to sleep here to make sure all logs get drained. Once the
    // referenced bug has been resolved, we can remove the sleep.
    thread::sleep(time::Duration::from_secs(2));
    Ok(())
}

伺服器

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use {
    anyhow::{Context as _, Error},
    fidl_examples_keyvaluestore_addreaditem::{
        Item, ReadError, StoreRequest, StoreRequestStream, WriteError,
    },
    fuchsia_component::server::ServiceFs,
    futures::prelude::*,
    lazy_static::lazy_static,
    regex::Regex,
    std::cell::RefCell,
    std::collections::hash_map::Entry,
    std::collections::HashMap,
};

lazy_static! {
    static ref KEY_VALIDATION_REGEX: Regex =
        Regex::new(r"^[A-Za-z][A-Za-z0-9_\./]{2,62}[A-Za-z0-9]$")
            .expect("Key validation regex failed to compile");
}

/// Handler for the `WriteItem` method.
fn write_item(store: &mut HashMap<String, Vec<u8>>, attempt: Item) -> Result<(), WriteError> {
    // Validate the key.
    if !KEY_VALIDATION_REGEX.is_match(attempt.key.as_str()) {
        println!("Write error: INVALID_KEY, For key: {}", attempt.key);
        return Err(WriteError::InvalidKey);
    }

    // Validate the value.
    if attempt.value.is_empty() {
        println!("Write error: INVALID_VALUE, For key: {}", attempt.key);
        return Err(WriteError::InvalidValue);
    }

    // Write to the store, validating that the key did not already exist.
    match store.entry(attempt.key) {
        Entry::Occupied(entry) => {
            println!("Write error: ALREADY_EXISTS, For key: {}", entry.key());
            Err(WriteError::AlreadyExists)
        }
        Entry::Vacant(entry) => {
            println!("Wrote value at key: {}", entry.key());
            entry.insert(attempt.value);
            Ok(())
        }
    }
}

/// Creates a new instance of the server. Each server has its own bespoke, per-connection instance
/// of the key-value store.
async fn run_server(stream: StoreRequestStream) -> Result<(), Error> {
    // Create a new in-memory key-value store. The store will live for the lifetime of the
    // connection between the server and this particular client.
    let store = RefCell::new(HashMap::<String, Vec<u8>>::new());

    // Serve all requests on the protocol sequentially - a new request is not handled until its
    // predecessor has been processed.
    stream
        .map(|result| result.context("failed request"))
        .try_for_each(|request| async {
            // Match based on the method being invoked.
            match request {
                StoreRequest::WriteItem { attempt, responder } => {
                    println!("WriteItem request received");

                    // The `responder` parameter is a special struct that manages the outgoing reply
                    // to this method call. Calling `send` on the responder exactly once will send
                    // the reply.
                    responder
                        .send(write_item(&mut store.borrow_mut(), attempt))
                        .context("error sending reply")?;
                    println!("WriteItem response sent");
                }
                StoreRequest::ReadItem { key, responder } => {
                    println!("ReadItem request received");

                    // Read the item from the store, returning the appropriate error if it could not be found.
                    responder
                        .send(match store.borrow().get(&key) {
                            Some(found) => {
                                println!("Read value at key: {}", key);
                                Ok((&key, found))
                            }
                            None => {
                                println!("Read error: NOT_FOUND, For key: {}", key);
                                Err(ReadError::NotFound)
                            }
                        })
                        .context("error sending reply")?;
                    println!("ReadItem response sent");
                } //
                StoreRequest::_UnknownMethod { ordinal, .. } => {
                    println!("Received an unknown method with ordinal {ordinal}");
                }
            }
            Ok(())
        })
        .await
}

// A helper enum that allows us to treat a `Store` service instance as a value.
enum IncomingService {
    Store(StoreRequestStream),
}

#[fuchsia::main]
async fn main() -> Result<(), Error> {
    println!("Started");

    // Add a discoverable instance of our `Store` protocol - this will allow the client to see the
    // server and connect to it.
    let mut fs = ServiceFs::new_local();
    fs.dir("svc").add_fidl_service(IncomingService::Store);
    fs.take_and_serve_directory_handle()?;
    println!("Listening for incoming connections");

    // The maximum number of concurrent clients that may be served by this process.
    const MAX_CONCURRENT: usize = 10;

    // Serve each connection simultaneously, up to the `MAX_CONCURRENT` limit.
    fs.for_each_concurrent(MAX_CONCURRENT, |IncomingService::Store(stream)| {
        run_server(stream).unwrap_or_else(|e| println!("{:?}", e))
    })
    .await;

    Ok(())
}

C++ (自然)

用戶端

// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.

伺服器

// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.

C++ (Wire)

用戶端

// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.

伺服器

// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.

HLCPP

用戶端

// TODO(https://fxbug.dev/42060656): HLCPP implementation.

伺服器

// TODO(https://fxbug.dev/42060656): HLCPP implementation.

內建

FIDL 提供下列內建項目:

  • 原始類型:boolint8int16int32int64uint8uint16uint32uint64float32float64
  • 其他類型:stringclient_endserver_end
  • 類型範本:arrayvectorbox
  • 別名:byte
  • 限制:optionalMAX

以下所有內建項目都屬於 fidl 程式庫。這個程式庫一律可用,不需要透過 using 匯入。舉例來說,如果您宣告名為 string 的結構體,可以將原始字串類型稱為 fidl.string

程式庫 zx

程式庫 zx 並未內建,但會由編譯器特別處理。這個類別是在 //zircon/vdso/zx 中定義。程式庫會使用 using zx 匯入,且最常使用 zx.Handle 類型。

內嵌版面配置

您也可以在內文中指定版面配置,而非在 type 引入宣告中指定。這項功能適用於只使用一次的特定版面配置。例如,以下 FIDL:

type Options = table {
    1: reticulate_splines bool;
};

protocol Launcher {
    GenerateTerrain(struct {
        options Options;
    });
};

可以使用內嵌版面配置重寫:

protocol Launcher {
    GenerateTerrain(struct {
        options table {
            1: reticulate_splines bool;
        };
    });
};

使用內嵌版面配置時,FIDL 會根據版面配置使用的命名內容,為其保留一個保證不重複的名稱。這會產生下列保留名稱:

  • 如果內嵌版面配置用於做為外部版面配置成員的類型,則預留名稱就是對應成員的名稱。
    • 在上述範例中,Options 名稱是保留給內嵌 table 使用的。
  • 針對頂層要求/回應類型,FIDL 會連結通訊協定名稱、方法名稱,然後視類型用途而決定是否連結 "Request""Response"
    • 在上述範例中,LauncherGenerateTerrainRequest 名稱是保留給用於 GenerateTerrain 方法要求的結構體。
    • 請注意,"Request" 後置字串表示該類型用於啟動通訊;因此,事件類型會保留 "Request" 後置字串,而非 "Response" 後置字串。

實際用於產生程式碼中的名稱取決於繫結,並在個別繫結參照中說明。

針對做為版面配置成員類型的內嵌版面配置,有兩種方法可以取得不同的保留名稱:

  • 重新命名版面配置成員。
  • 使用 @generated_name 屬性覆寫預留名稱。