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: Canonical name collision(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. X 成员的身份解决 Y 问题。
    2. X 解析为库名称或别名。
      1. Y 解析为 X 中的声明。
  • 如果限定为 x.Y.Z,其中 x 表示一个或多个组件:
    1. 尝试将 x.Y 解析为库名称或别名:
      1. Z 解析为 x.Y 中的声明。
    2. x 解析为库名称或别名:
      1. Y 解析为 x 中的声明:
        1. x.Y 成员的身份解决 Z 问题。

完全限定名称

FIDL 使用完全限定名称(缩写“FQN”)明确引用声明和成员。由库名称、斜杠 /、声明标识符以及点 . 和成员标识符(可选)组成的 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.Rectfuchsia.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 = 0;
    INVALID_KEY = 1;
    INVALID_VALUE = 2;
    ALREADY_EXISTS = 3;
};

/// An enumeration of things that may go wrong when trying to read a value out of our store.
type ReadError = flexible enum {
    UNKNOWN = 0;
    NOT_FOUND = 1;
};

/// 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.
        {
            protocol: [
                "fuchsia.inspect.InspectSink",
                "fuchsia.logger.LogSink",
            ],
            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},
    config::Config,
    fidl_examples_keyvaluestore_addreaditem::{Item, StoreMarker},
    fuchsia_component::client::connect_to_protocol,
    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++(有线)

客户端

// 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;对于 blob,请考虑使用 fuchsia.mem.Buffer。如需了解详情,请参阅我应该使用字符串还是矢量?

矢量

  • 同构元素的可变长度序列。
  • 可选填;缺少的向量和空向量是不同的。
  • 可以指定最大大小,例如,如果输入 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>:可选的 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>;
};

结构体

  • 由一系列类型化字段组成的记录类型。
  • 添加或移除字段或更改字段类型通常不兼容 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 并集,因为仍然可以解码无成员并集(包含的数据始终为“未知”)。

声明

/// 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 类型都是值类型或资源类型。资源类型包括:

  • 标识名
  • 协议端点
  • 资源类型的aliases
  • 资源类型数组和向量
  • 使用 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 时用于代替方法名称。
  • 每个方法声明都会声明其参数和结果。

    • 如果未声明任何结果,则该方法是单向的:服务器不会生成任何响应。
    • 如果声明了结果(即使结果为空),此方法是双向的:每次调用该方法都会从服务器生成响应。
    • 如果仅声明结果,则相应方法称为事件。然后定义来自服务器的垃圾消息。
    • 双向方法可能会声明服务器可以发送(而非响应)的错误类型。此类型必须是 int32uint32enum
  • 当协议的服务器即将关闭其通道一端时,它可以选择向客户端发送 epitaph 消息,以指示连接的处理方式。铭文必须是通过通道传递的最后一条消息。epitaph 消息包含类型为 zx_status_t 的 32 位整数值。系统会保留负值作为系统错误代码。值 ZX_OK (0) 表示操作成功。废弃了应用定义的错误代码(以前定义为所有 zx_status_t 正值)。如需详细了解墓碑,请参阅 RFC-0031: Typed Epitaphs 的拒绝。如需详细了解 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 协议(无需了解有关字体的任何信息)。

协议构成类似于 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粗体标记。

严格 M); 弹性 M); strict -> M); 柔性环境 -> M); 严格 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);
};

请注意,只有在接收端无法识别序数且不知道互动是什么时,未知互动处理才适用。也就是说,接收端不知道互动是严格限制还是灵活互动。为了让接收者知道如何处理未知互动,发送者在消息标头中添加了一点,告知接收者是对互动是严格遵守还是灵活处理。因此,交互中使用的严格程度取决于发送方对其尝试调用的方法的严格程度,但协议对这种交互的开放程度取决于接收方的开放程度。

下文介绍了当接收端无法识别方法时,采用每种严格程度的协议如何处理具有每种严格程度的方法或事件。

严格 M); CANNOT TRANSLATE strict -> M); 灵活 -> M); 严格 M() -> ); 弹性 M() -> );
打开 P 自动关闭 可处理 自动关闭 可处理 自动关闭 可处理
Ajar 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;

在上文中,标识符 StoryID 是声明 string 的别名,最大大小为 MAX_SIZE。标识符 Chapters 是五个 StoryId 元素的矢量声明的别名。

标识符 StoryIDChapters 可以在任何可以使用其别名定义的地方使用。请考虑:

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

此处,Message 结构体包含一个 MAX_SIZE 字节(称为 baseline)和一个向量(最多 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 = 0;
    INVALID_KEY = 1;
    INVALID_VALUE = 2;
    ALREADY_EXISTS = 3;
};

/// An enumeration of things that may go wrong when trying to read a value out of our store.
type ReadError = flexible enum {
    UNKNOWN = 0;
    NOT_FOUND = 1;
};

/// 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.
        {
            protocol: [
                "fuchsia.inspect.InspectSink",
                "fuchsia.logger.LogSink",
            ],
            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},
    config::Config,
    fidl_examples_keyvaluestore_addreaditem::{Item, StoreMarker},
    fuchsia_component::client::connect_to_protocol,
    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++(有线)

客户端

// 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" 连接起来,具体取决于类型的使用位置。
    • 在上面的示例中,为用作 GenerateTerrain 方法请求的结构体预留了名称 LauncherGenerateTerrainRequest
    • 请注意,"Request" 后缀表示相应类型用于发起通信;因此,我们将为事件类型预留 "Request" 后缀,而不是 "Response" 后缀。

生成的代码中实际使用的名称取决于绑定,各个绑定引用中都有相应说明。

对于用作布局成员类型的内嵌布局,您可以通过以下两种方式获取不同的预留名称:

  • 重命名布局成员。
  • 请使用 @generated_name 属性替换预留名称。