FIDL 語言規格

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

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

此外,請參閱 FIDL 文法 EBNF 說明的修訂版本。

語法

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 ID 標籤宣告及其成員。必須符合 [a-zA-Z]([a-zA-Z0-9_]*[a-zA-Z0-9])? 正規運算式。也就是說,ID 開頭須為英文字母,可包含英文字母、數字和底線,但結尾不得為底線。

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

FIDL ID 區分大小寫。不過,ID 必須有專屬的標準形式,否則 FIDL 編譯器會失敗,並顯示 fi-0035: Canonical name collision。如要取得 ID 的標準形式,請將 ID 轉換為 snake_case

符合資格的 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. 以「X」成員身分解除「Y」。
    2. X 解析為程式庫名稱或別名。
      1. X 中將 Y 解析為宣告。
  • 如果符合 x.Y.Z 資格,其中 x 代表一或多個元件:
    1. 嘗試將 x.Y 解析為程式庫名稱或別名:
      1. x.Y 中將 Z 解析為宣告。
    2. x 解析為程式庫名稱或別名:
      1. x 中將 Y 解析為宣告:
        1. 以「x.Y」成員身分解除「Z」。

完整名稱

FIDL 會使用完整名稱 (簡稱「FQN」) 清楚指稱宣告和成員。FQN 常數,包含程式庫名稱、斜線 /、宣告 ID,以及選用的點 . 和成員 ID。例如:

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

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

常值

FIDL 支援下列類型的常值:

  • 布林值:truefalse
  • 整數:十進位 (123)、十六進位 (0xA1B2)、八進位 (0755)、二進位 (0b101)。
    • 只有十進位常值可以是負數。
  • 浮點數:1.23-0.011e52.0e-3
    • 只能使用 ee-,不能使用 e+
  • 字串:"hello""\\ \" \n \r \t \u{1f642}"

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

常數

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 食譜:列舉

列舉是 FIDL 資料型別,代表可能常數的固定清單,例如一副撲克牌的花色,或是使用者可從下拉式選單中選取的汽車品牌。然後,這個值清單會對應至基礎整數型別,其中每個值都對應至列出的其中一個成員。

在下列範例中,我們在列舉非常適合的案例中新增 FIDL 列舉:列舉方法呼叫失敗時可能發出的錯誤值。ReadError 列舉有兩個成員:NOT_FOUND 用於指出讀取嘗試期間無法比對搜尋鍵,而 UNKNOWN 則做為所有無法明確說明的案例的錯誤收集包。請注意,這個列舉標示為 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::*,
    regex::Regex,
    std::cell::RefCell,
    std::collections::HashMap,
    std::collections::hash_map::Entry,
    std::sync::LazyLock,
};

static KEY_VALIDATION_REGEX: LazyLock<Regex> = LazyLock::new(|| {
    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++ (Natural)

用戶端

// 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.

陣列

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

使用

陣列以 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 個十六進位數字,代表 Unicode 程式碼點。
  • 可能含有內嵌的 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,也就是無界限。
  • 布林值向量沒有特殊情況。每個 bool 元素都會照常佔用一個位元組。

使用

向量的表示方式如下:

  • 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>:必要 Zircon 控制代碼,類型為 H,權限為 R
  • zx.Handle:<H, R, optional>:類型為 H 的選用 Zircon 控制代碼,具有 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 型別都是值型別資源型別。資源類型包括:

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

值類型不得包含資源類型。舉例來說,以下程式碼是錯誤的:

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 個位元組,
    • 將這些位元組解讀為小端序整數,
    • 將該值的上位元 (即最後一個位元) 設為 0。
    • 如要覆寫序數,方法可以有 @selector 屬性。如果屬性的引數是有效的 FQN,系統會使用該引數取代上述 FQN。否則,這個值必須是有效 ID,且建構 FQN 時會取代方法名稱。
  • 每個方法宣告都會說明其引數和結果。

    • 如果未宣告任何結果,這個方法就是單向方法:伺服器不會產生任何回應。
    • 如果宣告結果 (即使為空白),則方法為雙向:每次呼叫方法都會產生伺服器的回應。
    • 如果只宣告結果,則該方法稱為「事件」。然後定義伺服器傳送的未經要求訊息。
    • 雙向方法可能會宣告伺服器可傳送的錯誤類型,而非回應。這個型別必須是 int32uint32 或其 enum
  • 當通訊協定的伺服器即將關閉管道的伺服器端時,可能會選擇傳送墓誌銘訊息給用戶端,指出連線的處置方式。墓誌銘必須是透過管道傳送的最後一則訊息。墓誌銘訊息包含 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 用於繪製圖形物件,例如指定位置和大小的圓形和正方形。由於包含 SceneryController 通訊協定 (透過 compose 關鍵字),因此會組成 SceneryController 通訊協定的 SetBackground()SetForeground() 方法。

用於在螢幕上寫入文字的 Writer 通訊協定,也以相同方式包含 SceneryController 通訊協定。

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

DrawerWriter 指定各自的顏色設定方法相比,這項做法有以下優點:

  • 無論是繪製圓形、方形,還是將文字放在螢幕上,設定背景和前景顏色的方式都相同。
  • 只要將新方法新增至 SceneryController 通訊協定,即可將新方法新增至 DrawerWriter,不必變更定義。

最後一點尤其重要,因為這讓我們能夠為現有通訊協定新增功能。舉例來說,我們可能會在圖像系統中導入 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 表示通訊協定不接受任何不明互動。如果收到任何不明互動,繫結會回報錯誤並結束通訊,無論互動是 strictflexible
    • 所有方法和事件都必須宣告為 strict
  • ajar 表示通訊協定允許不明的單向方法和事件。flexible任何不明的雙向方法和 strict 單向方法或事件仍會導致錯誤,並導致繫結關閉管道。
    • 單向方法和事件可宣告為 strictflexible
    • 雙向方法必須宣告為 strict
  • open 表示通訊協定允許任何未知的 flexible 互動。 任何不明 strict 互動仍會導致錯誤,並導致繫結關閉管道。如未指定開放程度,預設值為「Open」。
    • 所有方法和事件都可以宣告為 strictflexible

下表摘要說明每種通訊協定類型中,不同方法允許的嚴格程度修飾符。開放程度的預設值為 open,以斜體標示。嚴格程度 flexible 的預設值以粗體標示。

strict M(); flexible M(); strict -> M(); flexible -> M(); strict M() -> (); flexible M() -> ();
開啟 P 編譯 編譯 編譯 編譯 編譯 編譯
ajar P 編譯 編譯 編譯 編譯 編譯 無法編譯
closed 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(); flexible M(); strict -> M(); flexible -> M(); strict M() -> (); flexible M() -> ();
開啟 P 自動關閉 handleable 自動關閉 handleable 自動關閉 handleable
ajar P 自動關閉 handleable 自動關閉 handleable 自動關閉 自動關閉
closed 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 識別項。建議考量的因素包括:

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

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

FIDL 食譜:別名

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

  • 使用 alias 可確保別名型別代表的概念只有單一可靠資料來源。
  • 這個函式提供命名事物的方式,尤其是受限型別。
  • 現在別名型別的不同用途可能會連結為相同概念的例項。

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

在本範例中,新增 KeyKey 允許我們使用自訂名稱,避免重複,同時向讀者清楚說明 Item 型別上的 key 值和 ReadItem 要求結構體中使用的 key 是相同的事物,而非只是巧合。alias

推理

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

實作

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::*,
    regex::Regex,
    std::cell::RefCell,
    std::collections::HashMap,
    std::collections::hash_map::Entry,
    std::sync::LazyLock,
};

static KEY_VALIDATION_REGEX: LazyLock<Regex> = LazyLock::new(|| {
    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++ (Natural)

用戶端

// 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.

Builtins

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 屬性覆寫預留名稱。