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;
};

标识符

库名称

FIDL 库名称用于标记 FIDL 库。它们由一个或多个组件组成,这些组件之间以点 (.) 分隔。每个组件都必须与正则表达式 [a-z][a-z0-9]* 匹配。也就是说,库名称组件必须以小写字母开头,可以包含小写字母和数字。

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

标识符

FIDL 标识符用于标记声明及其成员。它们必须与正则表达式 [a-zA-Z]([a-zA-Z0-9_]*[a-zA-Z0-9])? 匹配。也就是说,标识符必须以字母开头,可以包含字母、数字和下划线,但不能以下划线结尾。

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

FIDL 标识符区分大小写。不过,标识符必须具有唯一的规范形式,否则 FIDL 编译器将会失败并显示 fi-0035:规范名称冲突。标识符的规范形式是通过将其转换为 snake_case 获得的。

限定标识符

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 使用以下算法解析标识符。如果“尝试解决”步骤失败,系统会继续执行下一步。“解析”步骤失败时,编译器会生成错误。

  • 如果不符合条件:
    1. 尝试在当前库中将其解析为声明。
    2. 尝试将其解析为内置声明,例如 bool 引用 fidl.bool
    3. 解析为上下文位/枚举成员,例如 zx.handle:CHANNEL 中的 CHANNEL 是指 zx.ObjType.Channel
  • 如果它符合 X.Y 的条件:
    1. 尝试将 X 解析为当前库中的声明:
      1. Y 解析为 X 的成员。
    2. X 解析为库名称或别名。
      1. X 中将 Y 解析为声明。
  • 如果它符合 x.Y.Z 的条件,其中 x 代表一个或多个组件:
    1. 尝试将 x.Y 解析为库名称或别名:
      1. x.Y 中将 Z 解析为声明。
    2. x 解析为库名称或别名:
      1. x 中将 Y 解析为声明:
        1. Z 解析为 x.Y 的成员。

完全限定名称

FIDL 使用完全限定名称(简称“FQN”)来明确引用声明和成员。完全限定名称由库名称、正斜线 /、声明标识符以及可选的句点 . 和成员标识符组成。例如:

  • 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}"

数字字面量(例如十六进制数字)中的所有字母均不区分大小写。

常量

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;
};

使用

枚举类型由其标识符表示,该标识符可根据需要进行限定。

// 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",
            ],
        },
    ],
}

所有语言的客户端和服务器实现也会发生变化:

Rust

客户端

// 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 是 Unicode 码点的 1 到 6 位十六进制数字。
  • 与传统的 C 字符串不同,可能包含嵌入的 NUL 字节。

使用

字符串的表示方式如下:

  • 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 capability。
  • 存储为 32 位无符号整数。
  • 可选;缺少的句柄会编码为值为零的句柄。
  • 句柄可以选择性地与类型和一组必需的 Zircon 权限相关联。

使用

标识名表示为:

  • zx.Handle:所需的 Zircon 句柄类型未指定
  • zx.Handle:optional:类型未指定的可选 Zircon 手柄
  • zx.Handle:H:类型为 H 的必需 Zircon 句柄
  • zx.Handle:<H, optional>:类型为 H 的可选 Zircon 手柄
  • zx.Handle:<H, R>:类型为 H 且具有权限 R 的必需 Zircon 句柄
  • zx.Handle:<H, R, optional>:类型为 H 且具有权限 R 的可选 Zircon 手柄

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>;
};

结构体

  • 由一系列类型化字段组成的记录类型。
  • 添加或移除字段或更改其类型通常不兼容 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 之前定义(不含温度单位)的客户端仍可以将其配置文件发送到已更新为处理更大集合字段的服务器。

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 联合体,因为仍可以对无成员的联合体进行解码(包含的数据始终为“未知”)。

声明

/// 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 先前定义的客户端仍然可以从已更新为包含更大一组变体的服务器接收联合。如果联合体是未知变体,绑定可能会将其作为未知数据(即原始字节和句柄)公开给用户,并允许重新编码未知联合体(例如,以支持类似代理的用例)。绑定参考文档中详细介绍了用于与灵活类型的未知数据交互的方法。

如需了解详情,请参阅 RFC-0033:处理未知字段和严格性

价值与资源

每种 FIDL 类型都是值类型资源类型。资源类型包括:

  • 句柄
  • 协议端点
  • 资源类型的别名
  • 资源类型的数组和矢量
  • 标有 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 个字节,
    • 将这些字节解释为小端整数,
    • 将该值的上位(即最后一个)位设置为 0。
    • 如需替换序数,方法可以具有 @selector 属性。如果属性的参数是有效的 FQN,则系统会将其用作上述 FQN。否则,它必须是有效的标识符,并将在构建 FQN 时用作方法名称。
  • 每个方法声明都会说明其参数和结果。

    • 如果未声明任何结果,则该方法是单向的:服务器不会生成任何响应。
    • 如果声明了结果(即使为空),则该方法是双向的:每次调用该方法都会从服务器生成响应。
    • 如果仅声明了结果,则该方法称为事件。然后,它定义了来自服务器的非请求消息。
    • 双向方法可以声明服务器可以发送的错误类型,而不是响应。此类型必须是 int32uint32 或其 enum
  • 当某个协议的服务器即将关闭其端的通道时,它可以选择向客户端发送墓志铭消息,以指明连接的处理方式。墓志铭必须是通过该渠道传送的最后一条消息。墓志铭消息包含一个类型为 zx_status_t 的 32 位整数值。负值保留用于系统错误代码。值 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 协议中的 SetBackground()SetForeground() 方法,因为它包含 SceneryController 协议(通过 compose 关键字)。

用于在显示屏上写入文本的 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 协议(该协议无需了解任何有关字体的知识)。

协议组合类似于混入。如需了解详情,请参阅 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 互动仍会导致错误,并导致绑定关闭通道。如果未指定开放程度,则默认值为“开放”。
    • 所有方法和事件都可以声明为 strictflexible

下文简要介绍了每种协议类型中不同类型的方法允许使用哪些严格性修饰符。开放度的默认值 open,用斜体标记。严格性默认值 flexible 用粗体标记。

strict M(); flexible M(); strict -> M(); 灵活 -> M(); strict M() -> (); 灵活的 M() -> ();
打开 P 编译 编译 编译 编译 编译 编译
ajar 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 自动关闭 可处理 自动结束 可处理 自动结束 可处理
ajar P 自动关闭 可处理 自动结束 可处理 自动关闭 自动关闭
已关闭的 P 自动关闭 自动关闭 自动关闭 自动关闭 自动关闭 自动关闭

与组合的交互

closed 协议中不能声明 flexible 方法和事件,ajar 协议中不能声明 flexible 双向方法。为确保在协议组合中强制执行这些规则,一个协议只能组合至少与其一样封闭的其他协议:

  • open:可以组合任何协议。
  • ajar:可以组合使用 ajarclosed 协议。
  • closed:只能组合其他 closed 协议。

走样

支持类型别名。例如:

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

在上例中,标识符 StoryID 是声明大小上限为 MAX_SIZEstring 的别名。标识符 Chapters 是包含五个 StoryId 元素的矢量声明的别名。

标识符 StoryIDChapters 可在可使用其别名定义的任何位置使用。请考虑以下事项:

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

在这里,Message 结构体包含一个名为 baselineMAX_SIZE 字节字符串,以及一个包含最多 5MAX_SIZE 字符串的向量,该向量名为 chapters

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",
            ],
        },
    ],
}

所有语言的客户端和服务器实现也会发生变化:

Rust

客户端

// 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 属性替换预留名称。