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 使用以下算法来解析标识符。当“尝试解决”时 步骤失败,则会继续执行下面的下一步。收到“解决”失败后, 会生成错误。

  • 如果不符合条件: <ph type="x-smartling-placeholder">
      </ph>
    1. 尝试在当前库中解析为声明。
    2. 请尝试将 resoving 作为内置声明,例如bool 是指 fidl.bool
    3. 作为上下文位/枚举成员解析,例如CHANNELzx.handle:CHANNEL 是指 zx.ObjType.Channel
  • 如果它符合 X.Y 条件: <ph type="x-smartling-placeholder">
      </ph>
    1. 尝试将 X 解析为当前库中的声明: <ph type="x-smartling-placeholder">
        </ph>
      1. X 的成员的身份解析 Y
    2. X 解析为库名称或别名。
      1. Y 解析为 X 中的声明。
  • 如果它符合 x.Y.Z 条件,其中 x 表示一个或多个组件: <ph type="x-smartling-placeholder">
      </ph>
    1. 尝试将 x.Y 解析为库名称或别名: <ph type="x-smartling-placeholder">
        </ph>
      1. Z 解析为 x.Y 中的声明。
    2. x 解析为库名称或别名: <ph type="x-smartling-placeholder">
        </ph>
      1. Y 解析为 x 中的声明: <ph type="x-smartling-placeholder">
          </ph>
        1. x.Y 的成员的身份解析 Z

完全限定名称

FIDL 使用完全限定名称(缩写为“FQN”)明确指代 和成员的方法。库名称、斜杠 /、 声明标识符,以及可选的 . 点和成员标识符。例如:

  • fuchsia.io/MAX_BUF 是指 fuchsia.io 库中的 MAX_BUF 常量。
  • fuchsia.process/Launcher.Launch 是指 Launch 方法 库 fuchsia.processLauncher 协议。

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 数据类型,表示一个固定的可能列表。 常量,例如扑克牌中的套装或用户的汽车品牌 可从下拉菜单中选择然后,该值列表会映射到 其中的每个值都对应 所列成员。

在下面的示例中,在枚举 完美契合:枚举可能由 方法调用失败。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};
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++(有线)

客户端

// 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_ 并非破坏性更改。

字符串不应用于传递任意二进制数据,因为绑定会强制执行 有效的 UTF-8。如果数据量较少,请考虑使用 bytesfuchsia.mem.Buffer 。请参阅 我应该使用字符串还是矢量? 了解详情。

矢量

  • 可变长度的同构元素序列。
  • 可以选填;不存在向量和空向量是不同的。
  • 可以指定大小上限,例如vector<T>:40(最多 40 个元素) 矢量。默认情况下,vector<T> 表示 vector<T>:MAX,即无界限。
  • 布尔值矢量没有特殊情况。每个布尔值元素 字节。

使用

向量表示如下:

  • vector<T>:必需的元素类型向量 T(如果缺失,则会出现验证错误
  • vector<T>:optional:元素类型的可选矢量 T
  • vector<T>:Nvector<T>:<N, optional>:矢量和可选的矢量 分别为 N 个元素的最大长度

T 可以是任何 FIDL 类型。

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

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

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

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

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

句柄

  • 通过句柄值传输 Zircon 功能。
  • 存储为 32 位无符号整数。
  • 可以选填;缺失的句柄会被编码为零值句柄。
  • 标识名可以选择与所需的 Zircon 类型和一组关联 权利。

使用

标识名如下:

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

H 可以是以下对象支持的任何对象: Zircon,例如channelthreadvmo。请参阅 grammar 可查看完整列表。

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 类型要么是值类型,要么是资源类型。资源 类型包括:

  • 标识名
  • 协议端点
  • 资源类型的别名
  • 资源类型的数组和矢量
  • 使用 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:默认无句柄

协议

  • 描述可通过某个通道发送消息来调用的方法。
  • 方法由其序数进行标识。编译器的计算方法如下: <ph type="x-smartling-placeholder">
      </ph>
    • 获取该方法的完全限定名称的 SHA-256 哈希值。
    • 提取哈希摘要的前 8 个字节。
    • 将这些字节解释为小端序整数,
    • 将该值的高位(即最后位)设置为 0。
    • 如需替换序数,方法可以具有 @selector 属性。如果 属性的 参数是有效的 FQN,系统将使用该参数替代 FQN 。否则,它必须是有效的标识符,并将直接使用。 方法名称的一部分。
  • 每个方法声明都会声明其参数和结果。

    • 如果没有声明任何结果,则该方法是单向的:没有响应 由服务器生成
    • 如果声明结果(即使为空),则该方法是双向的: 每次调用该方法都会从服务器生成响应。
    • 如果只声明结果,则该方法称为 event。然后定义来自服务器的垃圾消息。
    • 双向方法可以声明服务器可发送的错误类型 而不是响应。此类型必须是 int32uint32 或 其中 enum 个。
  • 当使用协议的服务器要关闭自己一端的通道时, 可能会选择向客户端发送 epitaph 消息,以指明 连接的处理方式墓碑必须是最后一条消息 通过渠道投放的内容墓记消息包含一个 32 位整数 zx_status_t 类型的值。为系统保留负值 错误代码。值 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 用于绘制图形对象,例如在指定位置绘制圆形和方形 并具有给定的尺寸 它从 Cloud Storage 中构建 SetBackground()SetForeground() 方法, SceneryController 协议,因为它包含 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 表示未知互动应由 应用。如果协议允许进行此类未知互动, 该序数会传递给一个未知的交互处理程序,然后 决定如何应对。协议的未知互动类型 allow 则由协议修饰符决定。flexible 是默认值 值。

协议开放性修饰符 closedajaropen 用于控制 如果接收端无法识别 flexible 互动, 序数。对于单向或双向方法,接收端是服务器, 对于事件,接收端是客户端。

  • closed 表示协议不接受任何未知交互。如果有 收到未知交互时,绑定报告错误并结束 通信,无论互动是strict还是 flexible
    • 所有方法和事件都必须声明为 strict
  • ajar 表示协议允许未知的 flexible 单向方法和 事件。任何未知的双向方法和 strict 单向方法或事件 仍然会引发错误,并导致绑定关闭通道。
    • 单向方法和事件可以声明为 strictflexible
    • 双向方法必须声明为 strict
  • open 表示协议允许任何未知的 flexible 互动。 任何未知的 strict 互动仍然会导致错误并导致 以及用于关闭通道的绑定如果没有开放性,则“开放”为默认值 。
    • 所有方法和事件都可以声明为 strictflexible

下面概要列出了针对不同类型可以使用哪些严格程度修饰符 每种协议类型中的方法。开放性的默认值 open,标记为 以斜体表示。严格程度的默认值 flexible 标记在 粗体

严格 M(); flexible M(); 严格 ->M(); 灵活 ->M(); strict M() ->(); flexible M() ->();
打开 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(); 柔性 M(); 严格 ->M(); 灵活 ->M(); strict M() ->(); 灵活 M() ->();
打开 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 是声明 string,大小上限为 MAX_SIZE。标识符 Chapters 是一个 五个 StoryId 元素的矢量声明的别名。

标识符 StoryIDChapters 可在任何别名地址使用 定义。 考虑:

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

此处,Message 结构体包含一个名为 baselineMAX_SIZE 字节字符串。 以及一个名为 chapters 的向量(最多 5MAX_SIZE 字符串)。

FIDL 配方:Alias

alias 是一种 FIDL 声明,用于为现有类型分配新名称。 这样做有几个好处:

  • 使用 alias 可确保概念只有一个可信来源 别名类型所代表的类型
  • 它提供了一种为内容命名的方法,尤其是受约束的类型。
  • 对现已添加别名的类型的不同使用可以作为 概念相同。

请务必注意,别名不会传递到生成的 代码编写。也就是说,分配给 alias 的名称 声明绝不会在生成的 FIDL 代码中显示为声明名称。

在此示例中,为 Key 添加 alias 可以让我们在使用 一个定制名称,同时向读者明确说明“key”的值 针对 Item 类型和 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};
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++(有线)

客户端

// 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 提供以下内置功能:

  • 基元类型:boolint8int16int32int64uint8uint16 uint32uint64float32float64
  • 其他类型: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 会为其预留一个名称,即 根据命名上下文, 布局。这会产生以下预留名称:

  • 对于用作外部布局成员类型的内嵌布局,预留的 name 只是相应成员的名称。
    • 在上面的示例中,名称 Options 已预留用于内嵌 table
  • 对于顶级请求/响应类型,FIDL 会将协议名称、 方法名称,然后是 "Request""Response",具体取决于 使用 类型的位置。
    • 在上面的示例中,名称 LauncherGenerateTerrainRequest 为 为用作 GenerateTerrain 的请求的结构体预留 方法。
    • 请注意,"Request" 后缀表示类型用于发起 通信;因此,事件类型的 "Request" 会预留后缀,而不是 "Response" 后缀。

生成的代码中实际使用的名称取决于绑定, 具体说明请参阅各个绑定参考

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

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