前提条件
本教程基于编译 FIDL 教程构建而成。如需详细了解其他 FIDL 教程,请参阅概览。
概览
通过创建一个用于演练这些数据类型的单元测试,详细了解如何使用自然语言和线程化对象。
本文档介绍了如何完成以下任务:
使用域对象示例代码
本教程中的示例代码位于 Fuchsia 检出目录中的 //examples/fidl/cpp/domain_objects
。它由单元测试组件及其包含的软件包组成。如需详细了解如何构建单元测试组件,请参阅构建组件。
您可以通过以下方式在运行的 Fuchsia 模拟器实例上构建和运行该示例:
# Add the domain objects unit test to the build. # This only needs to be done once. fx set core.x64 --with //examples/fidl/cpp/domain_objects
# Run the domain objects unit test. fx test -vo fidl-examples-domain-objects-cpp-test
将 FIDL 库的 C++ 绑定添加为 build 依赖项
GN build
对于每个 FIDL 库声明(例如编译 FIDL 中的声明),系统都会在原始目标名称后缀为 _cpp
的位置生成该库的 C++ 绑定代码:
"//examples/fidl/fuchsia.examples:fuchsia.examples_cpp",
test
目标如下所示:
test("test") {
testonly = true
output_name = "fidl_examples_domain_objects_cpp_test"
sources = [
"advanced.cc",
"main.cc",
]
deps = [
"//examples/fidl/fuchsia.examples:fuchsia.examples_cpp",
"//src/lib/fxl/test:gtest_main",
]
}
请注意,通过引用该 _cpp
目标添加对 C++ 绑定的依赖项的行。
(可选)如需查看生成的绑定,请执行以下操作:
- 使用
fx build
进行构建。 - 切换到生成的文件目录:
out/default/fidling/gen/examples/fidl/fuchsia.examples/fuchsia.examples/cpp/fidl/fuchsia.examples/cpp
,其中包含生成的文件。如果您设置了其他 build 输出目录,则可能需要更改out/default
。您可以使用cat .fx-build-dir
检查 build 输出目录。
如需详细了解如何查找生成的绑定代码,请参阅查看生成的绑定代码。
Bazel build
在依赖于 Bazel build 中的 FIDL 库时,如果 FIDL 库不是来自 SDK,则需要额外的构建规则:
# Given a FIDL library declaration like the following
fuchsia_fidl_library(
name = "fuchsia.examples",
srcs = [
"echo.test.fidl",
"types.test.fidl",
],
library = "fuchsia.examples",
visibility = ["//visibility:public"],
)
# This rule describes the generated C++ bindings code for that library
fuchsia_fidl_llcpp_library(
name = "fuchsia.examples_llcpp_cc",
library = ":fuchsia.examples",
visibility = ["//visibility:public"],
deps = ["@fuchsia_sdk//pkg/fidl_cpp_v2"],
)
如果 FIDL 库来自 Bazel SDK,则无需执行上述步骤。
FIDL 库的 C++ 绑定代码会在原始目标名称(后缀为 _llcpp_cc
)下生成:
deps = [
# Example when depending on an SDK library, `fuchsia.io`.
"@fuchsia_sdk//fidl/fuchsia.io:fuchsia.io_llcpp_cc",
# Example when depending on a local FIDL library, `fuchsia.examples`
# defined above.
# Suppose the library lives in the `//path/to/fidl/library` folder.
"//path/to/fidl/library:fuchsia.examples_llcpp_cc",
# ... other dependencies ...
]
将绑定头文件添加到代码中
添加 build 依赖项后,您可以添加绑定头文件。包含模式为 #include <fidl/my.library.name/cpp/fidl.h>
。
domain_objects/main.cc
顶部的以下 include 语句会包含绑定,并将生成的 API 提供给源代码:
#include <fidl/fuchsia.examples/cpp/fidl.h>
使用自然网域对象
自然类型是侧重于人体工学和安全性的 C++ 领域对象变种。FIDL 值树表示为具有分层所有权的 C++ 对象树。这意味着,如果某个函数收到自然类型的某个对象,则可以对整个树中的所有子对象拥有唯一的所有权。当根对象超出范围时,该树会被拆解。
从宏观上讲,自然类型包含 std::
容器和概念。例如,表表示为 std::optional<Field>
的集合。矢量是 std::vector<T>
,等等。它们还实现了惯用 C++ 移动、复制和等式。例如,资源类型仅支持移动,而值类型将同时实现复制和移动,其中移动旨在优化对象的传输。移动表不会使其为空(只是以递归方式移动字段),类似于 std::optional
。
天然零点
以严格的 fuchsia.examples/FileMode
FIDL 类型和灵活的 fuchsia.examples/FlexibleFileMode
FIDL 类型为例:
// Bits implement bitwise operators such as |, ~, &, ^.
auto flags = ~fuchsia_examples::FileMode::kRead & fuchsia_examples::FileMode::kExecute;
flags = fuchsia_examples::FileMode::kRead | fuchsia_examples::FileMode::kWrite;
// Bits implement the set difference operation (clearing bits) under -.
ASSERT_EQ(flags - fuchsia_examples::FileMode::kRead, fuchsia_examples::FileMode::kWrite);
flags -= fuchsia_examples::FileMode::kRead;
ASSERT_EQ(flags, fuchsia_examples::FileMode::kWrite);
// Bits may be explicitly casted to their underlying integer type.
flags = fuchsia_examples::FileMode::kRead | fuchsia_examples::FileMode::kWrite;
ASSERT_EQ(static_cast<uint16_t>(flags), 0b11);
// They may also be explicitly constructed from an underlying type, but
// this may result in invalid values for strict bits.
flags = fuchsia_examples::FileMode(0b11);
// A safer alternative is |TryFrom|, which constructs an instance of
// |FileMode| only if underlying primitive does not contain any unknown
// members that is not defined in the FIDL schema. Otherwise, returns
// |std::nullopt|.
std::optional<fuchsia_examples::FileMode> maybe_flags =
fuchsia_examples::FileMode::TryFrom(0b1111);
ASSERT_FALSE(maybe_flags.has_value());
// Another alternative is |TruncatingUnknown| which clears any bits not
// defined in the FIDL schema.
fuchsia_examples::FileMode truncated_flags =
fuchsia_examples::FileMode::TruncatingUnknown(0b1111);
ASSERT_EQ(truncated_flags, fuchsia_examples::FileMode(0b111));
// Bits implement bitwise-assignment.
flags |= fuchsia_examples::FileMode::kExecute;
// They also support equality and expose a |kMask| that is the
// bitwise OR of all defined bit members.
ASSERT_EQ(flags, fuchsia_examples::FileMode::kMask);
// A flexible bits type additionally supports querying the unknown bits.
fuchsia_examples::FlexibleFileMode flexible_flags = fuchsia_examples::FlexibleFileMode(0b1111);
ASSERT_TRUE(flexible_flags.has_unknown_bits());
ASSERT_EQ(static_cast<uint16_t>(flexible_flags.unknown_bits()), 0b1000);
自然枚举
以严格的 fuchsia.examples/LocationType
FIDL 类型和灵活的 fuchsia.examples/FlexibleLocationType
FIDL 类型为例:
// Enums members are scoped constants under the enum type.
fuchsia_examples::LocationType location = fuchsia_examples::LocationType::kAirport;
// They may be explicitly casted to their underlying type.
ASSERT_EQ(static_cast<uint32_t>(fuchsia_examples::LocationType::kMuseum), 1u);
// They may also be casted to their underlying type without specifying the precise type.
uint32_t strict_underlying = fidl::ToUnderlying(fuchsia_examples::LocationType::kMuseum);
ASSERT_EQ(strict_underlying, 1u);
// Enums support switch case statements.
// A strict enum can be switched exhaustively.
(void)[=] {
switch (location) {
case fuchsia_examples::LocationType::kAirport:
return 1;
case fuchsia_examples::LocationType::kMuseum:
return 2;
case fuchsia_examples::LocationType::kRestaurant:
return 3;
}
};
// A flexible enum requires a `default:` case.
fuchsia_examples::FlexibleLocationType flexible_location =
fuchsia_examples::FlexibleLocationType::kAirport;
(void)[=] {
switch (flexible_location) {
case fuchsia_examples::FlexibleLocationType::kAirport:
return 1;
case fuchsia_examples::FlexibleLocationType::kMuseum:
return 2;
case fuchsia_examples::FlexibleLocationType::kRestaurant:
return 3;
default: // Removing this branch will fail to compile.
return 4;
}
};
// A flexible enum also supports asking if the current enum value was
// not known in the FIDL schema, or marked with `@unknown`.
ASSERT_FALSE(flexible_location.IsUnknown());
// Strict enums may be uninitialized. Their value will be undefined.
fuchsia_examples::LocationType strict_location;
(void)strict_location;
// Flexible enums may be default initialized. They will either contain
// the member marked with `@unknown` in the FIDL schema if exists,
// or a compiler-reserved unknown value otherwise.
fuchsia_examples::FlexibleLocationType default_flexible_location;
ASSERT_TRUE(default_flexible_location.IsUnknown());
自然结构体
自然结构体是公开常量和可变访问器的直接记录对象。以 fuchsia.examples/Color
FIDL 类型为例:
// Structs may be default constructed with fields set to default values,
// provided that all fields are also default constructible.
fuchsia_examples::Color default_color;
ASSERT_EQ(default_color.id(), 0u);
ASSERT_EQ(default_color.name(), "red");
// They support constructing by supplying fields in a sequence.
fuchsia_examples::Color blue = {1, "blue"};
ASSERT_EQ(blue.id(), 1u);
// They also support a more readable syntax that names individual fields,
// similar to C++ designated initialization. The double brace (`{{`) syntax
// is necessary to workaround C++ limitations on aggregate initialization.
fuchsia_examples::Color red{{.id = 2, .name = "red"}};
ASSERT_EQ(red.id(), 2u);
fuchsia_examples::Color designated_1 = {{.id = 1, .name = "designated"}};
ASSERT_EQ(designated_1.id(), 1u);
fuchsia_examples::Color designated_2{{.id = 2, .name = "designated"}};
ASSERT_EQ(designated_2.id(), 2u);
// Setters take the value to be set as argument.
fuchsia_examples::Color color;
color.id(100);
color.name("green");
ASSERT_EQ(color.id(), 100u);
ASSERT_EQ(color.name(), "green");
// Setters may also be chained.
color.id(42).name("yellow");
ASSERT_EQ(color.id(), 42u);
ASSERT_EQ(color.name(), "yellow");
// Equality is implemented for value types.
ASSERT_EQ(color, fuchsia_examples::Color(42, "yellow"));
// Copies and moves.
fuchsia_examples::Color color_copy{color};
ASSERT_EQ(color_copy.name(), "yellow");
fuchsia_examples::Color color_moved{std::move(color)};
ASSERT_EQ(color_moved.name(), "yellow");
// The state of |color| is now unspecified.
自然联合体
自然联合是类似于 std::variant
的总和类型。以严格的 fuchsia.examples/JsonValue
FIDL 类型和灵活的 fuchsia.examples/FlexibleJsonValue
FIDL 类型为例:
// Factory functions are used to construct natural union objects.
// To construct a union whose active member is |int_value|, use |WithIntValue|.
auto int_val = fuchsia_examples::JsonValue::WithIntValue(1);
// |Which| obtains an enum corresponding to the active member, which may be
// used in switch cases.
ASSERT_EQ(int_val.Which(), fuchsia_examples::JsonValue::Tag::kIntValue);
// When directly accessing a field, one must first check if the field is
// active before dereferencing it.
ASSERT_TRUE(int_val.int_value().has_value());
ASSERT_TRUE(static_cast<bool>(int_val.int_value()));
ASSERT_EQ(int_val.int_value().value(), 1);
// Another example, this time activating the |string_value| member.
auto str_val = fuchsia_examples::JsonValue::WithStringValue("1");
ASSERT_EQ(str_val.Which(), fuchsia_examples::JsonValue::Tag::kStringValue);
ASSERT_TRUE(str_val.string_value().has_value());
// Unions are not default constructible, to avoid invalid states.
static_assert(!std::is_default_constructible_v<fuchsia_examples::JsonValue>,
"Unions cannot be default constructed");
fuchsia_examples::JsonValue value = fuchsia_examples::JsonValue::WithStringValue("hello");
ASSERT_FALSE(value.int_value());
ASSERT_TRUE(value.string_value());
// |value_or| returns a fallback if the corresponding member is not active.
ASSERT_EQ(value.int_value().value_or(42), 42);
// Setters take the value to be set as argument.
// Setting a field causes that field to become the active member.
value.int_value(2);
ASSERT_TRUE(value.int_value());
ASSERT_FALSE(value.string_value());
// |take| invokes the move operation on the member if it is active.
value.string_value("foo");
std::optional<std::string> str = value.string_value().take();
ASSERT_TRUE(str.has_value());
ASSERT_EQ(str.value(), "foo");
// Equality is implemented for value types.
value.string_value("bar");
ASSERT_EQ(value, fuchsia_examples::JsonValue::WithStringValue("bar"));
// Copies and moves.
fuchsia_examples::JsonValue value_copy{value};
ASSERT_EQ(value.string_value().value(), "bar");
fuchsia_examples::JsonValue value_moved{std::move(value)};
ASSERT_EQ(value_moved.string_value().value(), "bar");
// When switching over the tag from a flexible union, one must add a `default:`
// case, to handle members not understood by the FIDL schema or to handle
// newly added members in a source compatible way.
fuchsia_examples::FlexibleJsonValue flexible_value =
fuchsia_examples::FlexibleJsonValue::WithIntValue(1);
switch (flexible_value.Which()) {
case fuchsia_examples::FlexibleJsonValue::Tag::kIntValue:
ASSERT_EQ(flexible_value.int_value().value(), 1);
break;
case fuchsia_examples::FlexibleJsonValue::Tag::kStringValue:
FAIL() << "Unexpected tag. |flexible_value| was set to int";
break;
default: // Removing this branch will fail to compile.
break;
}
天然表格
自然表是指每个字段都是可选字段的记录类型。以 fuchsia.examples/User
FIDL 类型为例:
// A default constructed table is empty. That is, every field is absent.
fuchsia_examples::User user;
ASSERT_TRUE(user.IsEmpty());
// Each accessor returns a |std::optional<T>|, where |T| is the field type.
ASSERT_FALSE(user.age().has_value());
// Setters take the value to be set as argument.
user.age(100);
user.age(*user.age() + 100);
ASSERT_EQ(user.age().value(), 200);
// Setters may also be chained.
user.name("foo").age(30);
ASSERT_EQ(user.name().value(), "foo");
ASSERT_EQ(user.age().value(), 30);
// Since each field is an |std::optional<T>|, they may also be cleared.
user.name().reset();
ASSERT_FALSE(user.name().has_value());
// Assigning an |std::nullopt| also clears the field.
user.name("bar");
ASSERT_TRUE(user.name().has_value());
user.name() = std::nullopt;
ASSERT_FALSE(user.name().has_value());
// |value_or| returns a fallback if the corresponding field is absent.
ASSERT_EQ(user.name().value_or("anonymous"), "anonymous");
user.age().reset();
ASSERT_TRUE(user.IsEmpty());
// Similar to structs, tables support constructing by naming individual fields.
// Fields that are omitted from the designated initialization syntax will be
// absent from the table.
user = {{.age = 100, .name = "foo"}};
ASSERT_TRUE(user.age());
ASSERT_TRUE(user.name());
user = {{.age = 100}};
ASSERT_TRUE(user.age());
ASSERT_FALSE(user.name());
// Equality is implemented for value types.
ASSERT_EQ(user, fuchsia_examples::User{{.age = 100}});
// Copies and moves.
fuchsia_examples::User user_copy{user};
ASSERT_EQ(*user.age(), 100);
fuchsia_examples::User user_moved{std::move(user)};
ASSERT_EQ(*user_moved.age(), 100);
使用线程网域对象
线类型是面向性能的 C++ 领域对象变种。与保持分层对象所有权的自然类型不同,有线对象从不拥有其外行子项。子对象是内嵌存储还是外部存储取决于 FIDL 线格格式。
自然类型可以隐式堆分配必要的存储空间。反之,用户可以完全控制线类型的内存分配。例如,您可以从堆栈、内存池或更大的对象中分配 FIDL 矢量的元素。有线矢量类型 fidl::VectorView<T>
是一种由原始指针和长度组成的无主视图类型。通过这种类型借用元素,您可以将矢量作为 FIDL 请求的一部分发送,而无需额外的堆分配。
为了与自然类型区分开来,请在 ...::wire
嵌套的命名空间(例如 fuchsia_my_library::wire
)中定义 FIDL 库中的传输类型。
由于有大量的无所有权指针存在于线型中,因此线型虽然灵活,但非常不安全。本教程将重点介绍如何更安全地使用基于内存的电线类型。如需了解涉及不安全内存借用的一些更高级用法,请参阅线程域对象的内存所有权。
线位和枚举
由于位和枚举具有非常简单的内存布局,并且没有任何线下子项,因此 FIDL 位和枚举的线格类型与其自然类型对应项相同。为了与整个命名空间命名配置保持一致,位和枚举会别名为 fuchsia_my_library::wire
嵌套命名空间,并与线程结构体、联合体和表一起显示。
以 fuchsia.examples/FileMode
FIDL 位为例,fuchsia_examples::wire::FileMode
是 fuchsia_examples::FileMode
的类型别名。
static_assert(std::is_same<fuchsia_examples::FileMode, fuchsia_examples::wire::FileMode>::value,
"natural bits should be equivalent to wire bits");
static_assert(fuchsia_examples::FileMode::kMask == fuchsia_examples::wire::FileMode::kMask,
"natural bits should be equivalent to wire bits");
using fuchsia_examples::wire::FileMode;
auto flags = FileMode::kRead | FileMode::kWrite | FileMode::kExecute;
ASSERT_EQ(flags, FileMode::kMask);
同样,以 fuchsia.examples/LocationType
FIDL 枚举为例,fuchsia_examples::wire::LocationType
是 fuchsia_examples::LocationType
的类型别名。
static_assert(
std::is_same<fuchsia_examples::LocationType, fuchsia_examples::wire::LocationType>::value,
"natural enums should be equivalent to wire enums");
ASSERT_EQ(static_cast<uint32_t>(fuchsia_examples::wire::LocationType::kMuseum), 1u);
Wire 结构体
Wire 结构体是用于存储公共成员变量的简单 C++ 结构体。以 fuchsia.examples/Color
FIDL 类型为例:
// Wire structs are simple C++ structs with all their member fields declared
// public. One may invoke aggregate initialization:
fuchsia_examples::wire::Color blue = {1, "blue"};
ASSERT_EQ(blue.id, 1u);
ASSERT_EQ(blue.name.get(), "blue");
// ..or designated initialization.
fuchsia_examples::wire::Color blue_designated = {.id = 1, .name = "blue"};
ASSERT_EQ(blue_designated.id, 1u);
ASSERT_EQ(blue_designated.name.get(), "blue");
// A wire struct may be default constructed, but user-defined default values
// are not supported.
// Default-initializing a struct means all fields are zero-initialized.
fuchsia_examples::wire::Color default_color;
ASSERT_EQ(default_color.id, 0u);
ASSERT_TRUE(default_color.name.is_null());
ASSERT_TRUE(default_color.name.empty());
// There are no getters/setters. One simply reads or mutates the member field.
blue.id = 2;
ASSERT_EQ(blue.id, 2u);
// Here we demonstrate that wire structs do not own their out-of-line children.
// Copying a struct will not copy their out-of-line children. Pointers are
// simply aliased.
{
fuchsia_examples::wire::Color blue2 = blue;
ASSERT_EQ(blue2.name.data(), blue.name.data());
}
// Similarly, destroying a wire struct object does not destroy out-of-line
// children. Destroying |blue2| does not invalidate the string contents in |name|.
ASSERT_EQ(blue.name.get(), "blue");
线端子
线端联合体是总和类型,其内存布局类似于分辨标记,后跟对有效成员的引用。以严格的 fuchsia.examples/JsonValue
FIDL 类型和灵活的 fuchsia.examples/FlexibleJsonValue
FIDL 类型为例:
// When the active member is larger than 4 bytes, it is stored out-of-line,
// and the union will borrow the out-of-line content. The lifetimes can be
// tricky to reason about, hence the FIDL runtime provides a |fidl::AnyArena|
// interface for arena-based allocation of members. The built-in
// implementation is |fidl::Arena|.
//
// Pass the arena as the first argument to |With...| factory functions, to
// construct the member content on the arena, and have the union reference it.
fidl::Arena arena;
fuchsia_examples::wire::JsonValue str_union =
fuchsia_examples::wire::JsonValue::WithStringValue(arena, "1");
// |Which| obtains an enum corresponding to the active member, which may be
// used in switch cases.
ASSERT_EQ(str_union.Which(), fuchsia_examples::wire::JsonValue::Tag::kStringValue);
// Before accessing the |string_value| member, one should check if the union
// indeed currently holds this member, by querying |is_string_value|.
// Accessing the wrong member will cause a panic.
ASSERT_TRUE(str_union.is_string_value());
ASSERT_EQ("1", str_union.string_value().get());
// When the active member is smaller or equal to 4 bytes, such as an
// |int32_t| here, the entire member is inlined into the union object.
// In these cases, arena allocation is not necessary, and the union
// object wholly owns the member.
fuchsia_examples::wire::JsonValue int_union = fuchsia_examples::wire::JsonValue::WithIntValue(1);
ASSERT_TRUE(int_union.is_int_value());
ASSERT_EQ(1, int_union.int_value());
// A default constructed wire union is invalid.
// It must be initialized with a valid member before use.
// One is not allowed to send invalid unions through FIDL client/server APIs.
fuchsia_examples::wire::JsonValue default_union;
ASSERT_TRUE(default_union.has_invalid_tag());
default_union = fuchsia_examples::wire::JsonValue::WithStringValue(arena, "hello");
ASSERT_FALSE(default_union.has_invalid_tag());
ASSERT_TRUE(default_union.is_string_value());
ASSERT_EQ(default_union.string_value().get(), "hello");
// Optional unions are represented with |fidl::WireOptional|.
fidl::WireOptional<fuchsia_examples::wire::JsonValue> optional_json;
ASSERT_FALSE(optional_json.has_value());
optional_json = fuchsia_examples::wire::JsonValue::WithIntValue(42);
ASSERT_TRUE(optional_json.has_value());
// |fidl::WireOptional| has a |std::optional|-like API.
fuchsia_examples::wire::JsonValue& value = optional_json.value();
ASSERT_TRUE(value.is_int_value());
// When switching over the tag from a flexible union, one must add a `default:`
// case, to handle members not understood by the FIDL schema or to handle
// newly added members in a source compatible way.
fuchsia_examples::wire::FlexibleJsonValue flexible_value =
fuchsia_examples::wire::FlexibleJsonValue::WithIntValue(1);
switch (flexible_value.Which()) {
case fuchsia_examples::wire::FlexibleJsonValue::Tag::kIntValue:
ASSERT_EQ(flexible_value.int_value(), 1);
break;
case fuchsia_examples::wire::FlexibleJsonValue::Tag::kStringValue:
FAIL() << "Unexpected tag. |flexible_value| was set to int";
break;
default: // Removing this branch will fail to compile.
break;
}
线表
线表是指每个字段都是可选字段的记录类型。与自然表不同,线表不拥有任何成员字段。复制线表类似于重写(复制)指针。与指针类似,移动线表是一种反模式,因为这相当于复制。
由于线表的内存布局限制,始终使用关联的 Builder
类型创建新实例。构建表后,表就无法再添加新成员或清除现有成员。
以 fuchsia.examples/User
FIDL 类型为例:
fidl::Arena arena;
// To construct a wire table, you need to first create a corresponding
// |Builder| object, which borrows an arena. The |arena| will be used to
// allocate the table frame, a bookkeeping structure for field presence.
auto builder = fuchsia_examples::wire::User::Builder(arena);
// To set a table field, call the member function with the same name on the
// builder. The arguments will be forwarded to the field constructor, and the
// field is allocated on the initial |arena|.
builder.age(10);
// Note that only the inline portion of the field is automatically placed in
// the arena. The field itself may reference its own out-of-line content,
// such as in the case of |name| whose type is |fidl::StringView|. |name|
// will reference the "jdoe" literal, which lives in static program storage.
builder.name("jdoe");
// Call |Build| to finalize the table builder into a |User| table.
// The builder is no longer needed after this point. |user| will continue to
// reference objects allocated in the |arena|.
fuchsia_examples::wire::User user = builder.Build();
ASSERT_FALSE(user.IsEmpty());
// Before accessing a field, one should check if it is present, by querying
// |has_...|. Accessing an absent field will panic.
ASSERT_TRUE(user.has_name());
ASSERT_EQ(user.name().get(), "jdoe");
// Setters may be chained, leading to a fluent syntax.
user = fuchsia_examples::wire::User::Builder(arena).age(30).name("bob").Build();
ASSERT_FALSE(user.IsEmpty());
ASSERT_TRUE(user.has_age());
ASSERT_EQ(user.age(), 30);
ASSERT_TRUE(user.has_name());
ASSERT_EQ(user.name().get(), "bob");
// A default constructed wire table is empty.
// This is mostly useful to make requests or replies with empty tables.
fuchsia_examples::wire::User defaulted_user;
ASSERT_TRUE(defaulted_user.IsEmpty());
// In some situations it could be difficult to provide an arena when
// constructing tables. For example, here it is hard to provide constructor
// arguments to 10 tables at once. Because a default constructed wire table is
// empty, a new table instance should be built and assigned in its place.
fidl::Array<fuchsia_examples::wire::User, 10> users;
for (auto& user : users) {
ASSERT_TRUE(user.IsEmpty());
user = fuchsia_examples::wire::User::Builder(arena).age(30).Build();
ASSERT_FALSE(user.IsEmpty());
ASSERT_EQ(user.age(), 30);
}
ASSERT_EQ(users[0].age(), 30);
// Finally, tables support checking if it was received with unknown fields.
// A table created by ourselves will never have unknown fields.
ASSERT_FALSE(user.HasUnknownData());
如需详细了解这些绑定,请参阅绑定参考文档。
在自然域对象和线框域对象之间转换
为了简化互操作性,您可以调用 fidl::ToWire
和 fidl::ToNatural
函数,以便在线程和自然网域对象之间进行转换。以 fuchsia.examples/User
FIDL 类型为例:
将天然气转化为有线金属:fidl::ToWire
// Let's start with a natural table.
fuchsia_examples::User user{{.age = 100, .name = "foo"}};
// To convert it to its corresponding wire domain object, we need a
// |fidl::AnyArena| implementation to allocate the storage, here an |arena|.
fidl::Arena arena;
// Call |fidl::ToWire| with the arena and the natural domain object.
// All out-of-line fields will live on the |arena|.
fuchsia_examples::wire::User wire_user = fidl::ToWire(arena, user);
ASSERT_TRUE(wire_user.has_age());
ASSERT_EQ(wire_user.age(), 100);
ASSERT_TRUE(wire_user.has_name());
ASSERT_EQ(wire_user.name().get(), "foo");
将线条转换为自然线条:fidl::ToNatural
fidl::Arena arena;
// Let's start with a wire table.
fuchsia_examples::wire::User wire_user =
fuchsia_examples::wire::User::Builder(arena).age(30).name("bob").Build();
// Call |fidl::ToNatural| with the wire domain object.
// All child fields will be owned by |user|.
fuchsia_examples::User user = fidl::ToNatural(wire_user);
ASSERT_TRUE(user.age().has_value());
ASSERT_EQ(user.age().value(), 30);
ASSERT_TRUE(user.name().has_value());
ASSERT_EQ(user.name().value(), "bob");
保留自然域名对象和线程域名对象
您可以使用 fidl::Persist
将自然域对象或线程域对象序列化为字节矢量,主要用例是长期数据持久化。
fidl::Unpersist
会将字节序列反序列化并复制到自然域对象的某个实例中。
fidl::InplaceUnpersist
会将字节序列反序列化为线程域对象的某个实例,并在此过程中更改字节。
FIDL 方案:持久性
Persistent FIDL 是指线编码二进制 FIDL 数据,无底层传输层即可存储。而是使用文件或数据库条目等持久的字节型接口存储数据,存储时间可长达任意时间。
若要扩展键值对存储区以支持导出备份,一种简单的方法是只需添加一个新方法,用于停止世界、序列化存储区的状态,并将其作为 FIDL vector<Item>
发回。不过,这种方法有两个缺点。第一,备份的所有负担都由服务器承担 - 客户端无需支付任何费用来要求服务器执行非常昂贵的备份操作。第二点是,它涉及大量的复制操作:客户端几乎肯定会在收到生成的备份后立即将其写入某个后备数据存储区(例如文件或数据库)。让它解码这个(可能非常大的)FIDL 对象,只是为了让它能够在将其转发到将执行实际存储的任何协议时立即重新编码,非常浪费。
推理
更好的解决方案是使用 Zircon 的虚拟内存对象。我们可以铸造一个 VMO 来在客户端上存储备份数据,将其发送到服务器,然后将其转发回目标数据存储区,而无需在中间进行反序列化,而不是在存储分区序列中不断复制字节。只要目标数据存储区协议允许接受使用 VMO 传输的数据,这种方式就是执行此类高成本操作的首选方式。事实上,例如 Fuchsia 的文件系统就实现了这种模式。这种方法的一个好处是,它会在客户端请求服务器执行耗时操作时强制客户端执行一些工作,从而最大限度地减少这两者之间的工作不平衡。
您可以使用 FIDL 数据持久性二进制格式,将 FIDL 值类型持久化到任何以字节为导向的存储介质。我们将新引入的 FIDL 类型 Exportable
持久保留在 VMO 中。系统会对对象进行编码并将其写入存储空间(在本例中为 VMO,稍后可另存为文件),并在需要再次访问数据时从中对其进行解码,方式与稍后通过 IPC 使用 FIDL 时再次对消息进行编码、传输和解码的方式大致相同。
为了安全地执行此操作并遵循最小权限原则,我们应限制代表 VMO 的句柄可能拥有的权限。输入句柄权限,这是 FIDL 用于描述特定句柄类型可用的特权的一流方法。在本例中,我们允许对 Export
请求中传递到服务器的 empty
VMO 执行读取操作、查询大小、调整大小和写入操作。返回 VMO 后,我们会移除调整大小和写入权限,确保任何进程(包括某个远程组件中的恶意操作者)都无法在数据在系统中移动时修改此数据。
实现
FIDL、CML 和 Realm 接口定义如下:
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.supportexports; using zx; /// 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 string:128; value vector<byte>:64000; }; /// 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 mint an export. type ExportError = flexible enum { UNKNOWN = 0; EMPTY = 1; STORAGE_TOO_SMALL = 2; }; // A data type describing the structure of a single export. We never actually send this data type // over the wire (we use the file's VMO instead), but whenever data needs to be written to/read from // its backing storage as persistent FIDL, it will have this schema. /// /// The items should be sorted in ascending order, following lexicographic ordering of their keys. type Exportable = table { 1: items vector<Item>; }; /// 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; /// Exports the entire store as a persistent [`Exportable`] FIDL object into a VMO provided by /// the client. /// /// By having the client provide (and speculatively size) the VMO, we force the party requesting /// the relatively heavy load of generating a backup to acknowledge and bear some of the costs. /// /// This method operates by having the client supply an empty VMO, which the server then /// attempts to fill. Notice that the server removes the `zx.Rights.WRITE` and /// `zx.Rights.SET_PROPERTY` rights from the returned VMO - not even the requesting client may /// alter the backup once it has been minted by the server. flexible Export(resource struct { /// Note that the empty VMO has more rights than the filled one being returned: it has /// `zx.Rights.WRITE` (via `zx.RIGHTS_IO`) so that the VMO may be filled with exported data, /// and `zx.Rights.SET_PROPERTY` (via `zx.RIGHTS_PROPERTY`) so that it may be resized to /// truncate any remaining empty buffer. empty zx.Handle:<VMO, zx.RIGHTS_BASIC | zx.RIGHTS_PROPERTY | zx.RIGHTS_IO>; }) -> (resource struct { /// The `zx.Rights.WRITE` and `zx.Rights.SET_PROPERTY` rights have been removed from the now /// filled VMO. No one, not even the client that requested the export, is able to modify /// this VMO going forward. filled zx.Handle:<VMO, zx.RIGHTS_BASIC | zx.Rights.GET_PROPERTY | zx.Rights.READ>; }) error ExportError; };
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.supportexports.Store" }, ], config: { write_items: { type: "vector", max_count: 16, element: { type: "string", max_size: 64, }, }, // The size, in bytes, allotted to the export VMO max_export_size: { type: "uint64" }, }, }
服务器
// 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.supportexports.Store" }, ], expose: [ { protocol: "examples.keyvaluestore.supportexports.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.supportexports.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 fuchsia_component::client::connect_to_protocol; use std::{thread, time}; use fidl::unpersist; use fidl_examples_keyvaluestore_supportexports::{Exportable, Item, StoreMarker}; use zx::Vmo; #[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()), } } // If the `max_export_size` is 0, no export is possible, so just ignore this block. This check // isn't strictly necessary, but does avoid extra work down the line. if config.max_export_size > 0 { // Create a 100Kb VMO to store the resulting export. In a real implementation, we would // likely receive the VMO representing the to-be-written file from file system like vfs of // fxfs. let vmo = Vmo::create(config.max_export_size)?; // Send the VMO to the server, to be populated with the current state of the key-value // store. match store.export(vmo).await? { Err(err) => { println!("Export Error: {}", err.into_primitive()); } Ok(output) => { println!("Export Success"); // Read the exported data (encoded in byte form as persistent FIDL) from the // returned VMO. In a real implementation, instead of reading the VMO, we would // merely forward it to some other storage-handling process. Doing this using a VMO, // rather than FIDL IPC, would save us frivolous reads and writes at each hop. let content_size = output.get_content_size().unwrap(); let mut encoded_bytes = vec![0; content_size as usize]; output.read(&mut encoded_bytes, 0)?; // Decode the persistent FIDL that was just read from the file. let exportable = unpersist::<Exportable>(&encoded_bytes).unwrap(); let items = exportable.items.expect("must always be set"); // Log some information about the exported data. println!("Printing {} exported entries, which are:", items.len()); for item in items.iter() { println!(" * {}", item.key); } } }; } // 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}; use fuchsia_component::server::ServiceFs; use futures::prelude::*; use lazy_static::lazy_static; use regex::Regex; use std::cell::RefCell; use std::collections::hash_map::Entry; use std::collections::HashMap; use fidl::{persist, Vmo}; use fidl_examples_keyvaluestore_supportexports::{ ExportError, Exportable, Item, StoreRequest, StoreRequestStream, WriteError, }; lazy_static! { static ref KEY_VALIDATION_REGEX: Regex = Regex::new(r"^[A-Za-z]\w+[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(()) } } } /// Handler for the `Export` method. fn export(store: &mut HashMap<String, Vec<u8>>, vmo: Vmo) -> Result<Vmo, ExportError> { // Empty stores cannot be exported. if store.is_empty() { return Err(ExportError::Empty); } // Build the `Exportable` vector locally. That means iterating over the map, and turning it into // a vector of items instead. let mut exportable = Exportable::default(); let mut items = store .iter() .map(|entry| return Item { key: entry.0.clone(), value: entry.1.clone() }) .collect::<Vec<Item>>(); items.sort_by(|a, b| a.key.cmp(&b.key)); exportable.items = Some(items); // Encode the bytes - there is a bug in persistent FIDL if this operation fails. Even if it // succeeds, make sure to check that the VMO has enough space to handle the encoded export data. let encoded_bytes = persist(&exportable).map_err(|_| ExportError::Unknown)?; if encoded_bytes.len() as u64 > vmo.get_content_size().map_err(|_| ExportError::Unknown)? { return Err(ExportError::StorageTooSmall); } // Write the (now encoded) persistent FIDL data to the VMO. vmo.set_content_size(&(encoded_bytes.len() as u64)).map_err(|_| ExportError::Unknown)?; vmo.write(&encoded_bytes, 0).map_err(|_| ExportError::Unknown)?; Ok(vmo) } /// 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::Export { empty, responder } => { println!("Export request received"); responder .send(export(&mut store.borrow_mut(), empty)) .context("error sending reply")?; println!("Export 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++(自然)
客户端
// 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 <fidl/examples.keyvaluestore.supportexports/cpp/fidl.h> #include <lib/async-loop/cpp/loop.h> #include <lib/component/incoming/cpp/protocol.h> #include <lib/syslog/cpp/macros.h> #include <unistd.h> #include <examples/fidl/new/key_value_store/support_exports/cpp_natural/client/config.h> #include <src/lib/files/file.h> #include <src/lib/fxl/strings/string_printf.h> int main(int argc, const char** argv) { FX_LOGS(INFO) << "Started"; // Retrieve component configuration. auto conf = config::Config::TakeFromStartupHandle(); // Start up an async loop and dispatcher. async::Loop loop(&kAsyncLoopConfigNeverAttachToThread); async_dispatcher_t* dispatcher = loop.dispatcher(); // Connect to the protocol inside the component's namespace. This can fail so it's wrapped in a // |zx::result| and it must be checked for errors. zx::result client_end = component::Connect<examples_keyvaluestore_supportexports::Store>(); if (!client_end.is_ok()) { FX_LOGS(ERROR) << "Synchronous error when connecting to the |Store| protocol: " << client_end.status_string(); return -1; } // Create an asynchronous client using the newly-established connection. fidl::Client client(std::move(*client_end), dispatcher); FX_LOGS(INFO) << "Outgoing connection enabled"; for (const auto& action : conf.write_items()) { std::string text; if (!files::ReadFileToString(fxl::StringPrintf("/pkg/data/%s.txt", action.c_str()), &text)) { FX_LOGS(ERROR) << "It looks like the correct `resource` dependency has not been packaged"; break; } auto value = std::vector<uint8_t>(text.begin(), text.end()); client->WriteItem(examples_keyvaluestore_supportexports::Item(action, value)) .ThenExactlyOnce( [&](fidl::Result<examples_keyvaluestore_supportexports::Store::WriteItem> result) { // Check if the FIDL call succeeded or not. if (!result.is_ok()) { if (result.error_value().is_framework_error()) { FX_LOGS(ERROR) << "Unexpected FIDL framework error: " << result.error_value(); } else { FX_LOGS(INFO) << "WriteItem Error: " << fidl::ToUnderlying(result.error_value().domain_error()); } } else { FX_LOGS(INFO) << "WriteItem Success"; } // Quit the loop, thereby handing control back to the outer loop of actions being // iterated over. loop.Quit(); }); // Run the loop until the callback is resolved, at which point we can continue from here. loop.Run(); loop.ResetQuit(); } // If the `max_export_size` is 0, no export is possible, so just ignore this block. This check // isn't strictly necessary, but does avoid extra work down the line. if (conf.max_export_size() > 0) { // Create a 100Kb VMO to store the resulting export. In a real implementation, we would // likely receive the VMO representing the to-be-written file from file system like vfs of // fxfs. zx::vmo vmo; if (zx_status_t status = zx::vmo::create(conf.max_export_size(), 0, &vmo); status != ZX_OK) { FX_PLOGS(ERROR, status) << "Failed to create VMO"; return -1; } client->Export({std::move(vmo)}) .ThenExactlyOnce( [&](fidl::Result<examples_keyvaluestore_supportexports::Store::Export>& result) { // Quit the loop, thereby handing control back to the outer loop of actions being // iterated over, when we return from this callback. loop.Quit(); if (!result.is_ok()) { if (result.error_value().is_framework_error()) { FX_LOGS(ERROR) << "Unexpected FIDL framework error: " << result.error_value(); } else { FX_LOGS(INFO) << "Export Error: " << fidl::ToUnderlying(result.error_value().domain_error()); } return; } FX_LOGS(INFO) << "Export Success"; // Read the exported data (encoded in byte form as persistent FIDL) from the // returned VMO. In a real implementation, instead of reading the VMO, we would // merely forward it to some other storage-handling process. Doing this using a VMO, // rather than FIDL IPC, would save us frivolous reads and writes at each hop. size_t content_size = 0; zx::vmo vmo = std::move(result->filled()); if (vmo.get_prop_content_size(&content_size) != ZX_OK) { return; } std::vector<uint8_t> encoded_bytes; encoded_bytes.resize(content_size); if (vmo.read(encoded_bytes.data(), 0, content_size) != ZX_OK) { return; } // Decode the persistent FIDL that was just read from the file. fit::result exportable = fidl::Unpersist<examples_keyvaluestore_supportexports::Exportable>( cpp20::span(encoded_bytes)); if (exportable.is_error()) { FX_LOGS(ERROR) << "Failed to unpersist: " << exportable.error_value(); return; } if (!exportable->items().has_value()) { FX_LOGS(INFO) << "Expected items to be set"; return; } auto& items = exportable->items().value(); // Log some information about the exported data. FX_LOGS(INFO) << "Printing " << items.size() << " exported entries, which are:"; for (const auto& item : items) { FX_LOGS(INFO) << " * " << item.key(); } }); // Run the loop until the callback is resolved, at which point we can continue from here. loop.Run(); loop.ResetQuit(); } // 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. sleep(2); return 0; }
服务器
// 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 <fidl/examples.keyvaluestore.supportexports/cpp/fidl.h> #include <lib/async-loop/cpp/loop.h> #include <lib/async/cpp/task.h> #include <lib/component/outgoing/cpp/outgoing_directory.h> #include <lib/fidl/cpp/wire/channel.h> #include <lib/syslog/cpp/macros.h> #include <unistd.h> #include <algorithm> #include <re2/re2.h> // An implementation of the |Store| protocol. class StoreImpl final : public fidl::Server<examples_keyvaluestore_supportexports::Store> { public: // Bind this implementation to a channel. StoreImpl(async_dispatcher_t* dispatcher, fidl::ServerEnd<examples_keyvaluestore_supportexports::Store> server_end) : binding_(fidl::BindServer( dispatcher, std::move(server_end), this, [this](StoreImpl* impl, fidl::UnbindInfo info, fidl::ServerEnd<examples_keyvaluestore_supportexports::Store> server_end) { if (info.reason() != ::fidl::Reason::kPeerClosedWhileReading) { FX_LOGS(ERROR) << "Shutdown unexpectedly"; } delete this; })) {} void WriteItem(WriteItemRequest& request, WriteItemCompleter::Sync& completer) override { FX_LOGS(INFO) << "WriteItem request received"; auto key = request.attempt().key(); auto value = request.attempt().value(); // Validate the key. if (!RE2::FullMatch(key, "^[A-Za-z]\\w+[A-Za-z0-9]$")) { FX_LOGS(INFO) << "Write error: INVALID_KEY, For key: " << key; FX_LOGS(INFO) << "WriteItem response sent"; return completer.Reply( fit::error(examples_keyvaluestore_supportexports::WriteError::kInvalidKey)); } // Validate the value. if (value.empty()) { FX_LOGS(INFO) << "Write error: INVALID_VALUE, For key: " << key; FX_LOGS(INFO) << "WriteItem response sent"; return completer.Reply( fit::error(examples_keyvaluestore_supportexports::WriteError::kInvalidValue)); } if (key_value_store_.find(key) != key_value_store_.end()) { FX_LOGS(INFO) << "Write error: ALREADY_EXISTS, For key: " << key; FX_LOGS(INFO) << "WriteItem response sent"; return completer.Reply( fit::error(examples_keyvaluestore_supportexports::WriteError::kAlreadyExists)); } // Ensure that the value does not already exist in the store. key_value_store_.insert({key, value}); FX_LOGS(INFO) << "Wrote value at key: " << key; FX_LOGS(INFO) << "WriteItem response sent"; return completer.Reply(fit::ok()); } void Export(ExportRequest& request, ExportCompleter::Sync& completer) override { FX_LOGS(INFO) << "Export request received"; completer.Reply(Export(std::move(request.empty()))); FX_LOGS(INFO) << "Export response sent"; } void handle_unknown_method( fidl::UnknownMethodMetadata<examples_keyvaluestore_supportexports::Store> metadata, fidl::UnknownMethodCompleter::Sync& completer) override { FX_LOGS(WARNING) << "Received an unknown method with ordinal " << metadata.method_ordinal; } private: using ExportError = ::examples_keyvaluestore_supportexports::ExportError; using Exportable = ::examples_keyvaluestore_supportexports::Exportable; using Item = ::examples_keyvaluestore_supportexports::Item; fit::result<ExportError, zx::vmo> Export(zx::vmo vmo) { if (key_value_store_.empty()) { return fit::error(ExportError::kEmpty); } Exportable exportable; std::vector<Item> items; items.reserve(key_value_store_.size()); for (const auto& [k, v] : key_value_store_) { items.push_back(Item{{.key = k, .value = v}}); } std::sort(items.begin(), items.end(), [](const Item& a, const Item& b) { return a.key() < b.key(); }); exportable.items(std::move(items)); fit::result encoded = fidl::Persist(exportable); if (encoded.is_error()) { FX_LOGS(ERROR) << "Failed to encode in persistence convention: " << encoded.error_value(); return fit::error(ExportError::kUnknown); } size_t content_size = 0; if (vmo.get_prop_content_size(&content_size) != ZX_OK) { return fit::error(ExportError::kUnknown); } if (encoded->size() > content_size) { return fit::error(ExportError::kStorageTooSmall); } if (vmo.set_prop_content_size(encoded->size()) != ZX_OK) { return fit::error(ExportError::kUnknown); } if (vmo.write(encoded->data(), 0, encoded->size()) != ZX_OK) { return fit::error(ExportError::kUnknown); } return fit::ok(std::move(vmo)); } fidl::ServerBindingRef<examples_keyvaluestore_supportexports::Store> binding_; // The map that serves as the per-connection instance of the key-value store. std::unordered_map<std::string, std::vector<uint8_t>> key_value_store_ = {}; }; int main(int argc, char** argv) { FX_LOGS(INFO) << "Started"; // The event loop is used to asynchronously listen for incoming connections and requests from the // client. The following initializes the loop, and obtains the dispatcher, which will be used when // binding the server implementation to a channel. async::Loop loop(&kAsyncLoopConfigNeverAttachToThread); async_dispatcher_t* dispatcher = loop.dispatcher(); // Create an |OutgoingDirectory| instance. // // The |component::OutgoingDirectory| class serves the outgoing directory for our component. This // directory is where the outgoing FIDL protocols are installed so that they can be provided to // other components. component::OutgoingDirectory outgoing = component::OutgoingDirectory(dispatcher); // The `ServeFromStartupInfo()` function sets up the outgoing directory with the startup handle. // The startup handle is a handle provided to every component by the system, so that they can // serve capabilities (e.g. FIDL protocols) to other components. zx::result result = outgoing.ServeFromStartupInfo(); if (result.is_error()) { FX_LOGS(ERROR) << "Failed to serve outgoing directory: " << result.status_string(); return -1; } // Register a handler for components trying to connect to |Store|. result = outgoing.AddUnmanagedProtocol<examples_keyvaluestore_supportexports::Store>( [dispatcher](fidl::ServerEnd<examples_keyvaluestore_supportexports::Store> server_end) { // Create an instance of our StoreImpl that destroys itself when the connection closes. new StoreImpl(dispatcher, std::move(server_end)); }); if (result.is_error()) { FX_LOGS(ERROR) << "Failed to add Store protocol: " << result.status_string(); return -1; } // Everything is wired up. Sit back and run the loop until an incoming connection wakes us up. FX_LOGS(INFO) << "Listening for incoming connections"; loop.Run(); return 0; }
C++(有线)
客户端
// 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 <fidl/examples.keyvaluestore.supportexports/cpp/wire.h> #include <lib/async-loop/cpp/loop.h> #include <lib/component/incoming/cpp/protocol.h> #include <lib/syslog/cpp/macros.h> #include <unistd.h> #include <examples/fidl/new/key_value_store/support_exports/cpp_wire/client/config.h> #include <src/lib/files/file.h> #include <src/lib/fxl/strings/string_printf.h> int main(int argc, const char** argv) { FX_LOGS(INFO) << "Started"; // Retrieve component configuration. auto conf = config::Config::TakeFromStartupHandle(); // Start up an async loop and dispatcher. async::Loop loop(&kAsyncLoopConfigNeverAttachToThread); async_dispatcher_t* dispatcher = loop.dispatcher(); // Connect to the protocol inside the component's namespace. This can fail so it's wrapped in a // |zx::result| and it must be checked for errors. zx::result client_end = component::Connect<examples_keyvaluestore_supportexports::Store>(); if (!client_end.is_ok()) { FX_LOGS(ERROR) << "Synchronous error when connecting to the |Store| protocol: " << client_end.status_string(); return -1; } // Create an asynchronous client using the newly-established connection. fidl::WireClient client(std::move(*client_end), dispatcher); FX_LOGS(INFO) << "Outgoing connection enabled"; for (const auto& key : conf.write_items()) { std::string text; if (!files::ReadFileToString(fxl::StringPrintf("/pkg/data/%s.txt", key.c_str()), &text)) { FX_LOGS(ERROR) << "It looks like the correct `resource` dependency has not been packaged"; break; } auto value = std::vector<uint8_t>(text.begin(), text.end()); client ->WriteItem( {fidl::StringView::FromExternal(key), fidl::VectorView<uint8_t>::FromExternal(value)}) .ThenExactlyOnce( [&](fidl::WireUnownedResult<examples_keyvaluestore_supportexports::Store::WriteItem>& result) { if (!result.ok()) { FX_LOGS(ERROR) << "Unexpected framework error"; } else if (result->is_error()) { FX_LOGS(INFO) << "WriteItem Error: " << fidl::ToUnderlying(result->error_value()); } else { FX_LOGS(INFO) << "WriteItem Success"; } // Quit the loop, thereby handing control back to the outer loop of actions being // iterated over. loop.Quit(); }); // Run the loop until the callback is resolved, at which point we can continue from here. loop.Run(); loop.ResetQuit(); } // If the `max_export_size` is 0, no export is possible, so just ignore this block. This check // isn't strictly necessary, but does avoid extra work down the line. if (conf.max_export_size() > 0) { // Create a 100Kb VMO to store the resulting export. In a real implementation, we would // likely receive the VMO representing the to-be-written file from file system like vfs of // fxfs. zx::vmo vmo; if (zx_status_t status = zx::vmo::create(conf.max_export_size(), 0, &vmo); status != ZX_OK) { FX_PLOGS(ERROR, status) << "Failed to create VMO"; return -1; } client->Export(std::move(vmo)) .ThenExactlyOnce( [&](fidl::WireUnownedResult<examples_keyvaluestore_supportexports::Store::Export>& result) { // Quit the loop, thereby handing control back to the outer loop of actions being // iterated over, when we return from this callback. loop.Quit(); if (!result.ok()) { FX_LOGS(ERROR) << "Unexpected FIDL framework error: " << result.error(); return; } if (!result->is_ok()) { FX_LOGS(INFO) << "Export Error: " << fidl::ToUnderlying(result->error_value()); return; } FX_LOGS(INFO) << "Export Success"; // Read the exported data (encoded in byte form as persistent FIDL) from the // returned VMO. In a real implementation, instead of reading the VMO, we would // merely forward it to some other storage-handling process. Doing this using a VMO, // rather than FIDL IPC, would save us frivolous reads and writes at each hop. size_t content_size = 0; zx::vmo vmo = std::move(result->value()->filled); if (vmo.get_prop_content_size(&content_size) != ZX_OK) { return; } std::vector<uint8_t> encoded_bytes; encoded_bytes.resize(content_size); if (vmo.read(encoded_bytes.data(), 0, content_size) != ZX_OK) { return; } // Decode the persistent FIDL that was just read from the file. fit::result exportable = fidl::InplaceUnpersist<examples_keyvaluestore_supportexports::wire::Exportable>( cpp20::span(encoded_bytes)); if (exportable.is_error()) { FX_LOGS(ERROR) << "Failed to unpersist: " << exportable.error_value(); return; } if (!exportable->has_items()) { FX_LOGS(INFO) << "Expected items to be set"; return; } auto& items = exportable->items(); // Log some information about the exported data. FX_LOGS(INFO) << "Printing " << items.count() << " exported entries, which are:"; for (const auto& item : items) { FX_LOGS(INFO) << " * " << item.key.get(); } }); // Run the loop until the callback is resolved, at which point we can continue from here. loop.Run(); loop.ResetQuit(); } // 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. sleep(2); return 0; }
服务器
// 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 <fidl/examples.keyvaluestore.supportexports/cpp/wire.h> #include <lib/async-loop/cpp/loop.h> #include <lib/async/cpp/task.h> #include <lib/component/outgoing/cpp/outgoing_directory.h> #include <lib/fidl/cpp/wire/channel.h> #include <lib/syslog/cpp/macros.h> #include <unistd.h> #include <algorithm> #include <re2/re2.h> // An implementation of the |Store| protocol. class StoreImpl final : public fidl::WireServer<examples_keyvaluestore_supportexports::Store> { public: // Bind this implementation to a channel. StoreImpl(async_dispatcher_t* dispatcher, fidl::ServerEnd<examples_keyvaluestore_supportexports::Store> server_end) : binding_(fidl::BindServer( dispatcher, std::move(server_end), this, [this](StoreImpl* impl, fidl::UnbindInfo info, fidl::ServerEnd<examples_keyvaluestore_supportexports::Store> server_end) { if (info.reason() != ::fidl::Reason::kPeerClosedWhileReading) { FX_LOGS(ERROR) << "Shutdown unexpectedly"; } delete this; })) {} void WriteItem(WriteItemRequestView request, WriteItemCompleter::Sync& completer) override { FX_LOGS(INFO) << "WriteItem request received"; std::string key{request->attempt.key.get()}; std::vector<uint8_t> value{request->attempt.value.begin(), request->attempt.value.end()}; // Validate the key. if (!RE2::FullMatch(key, "^[A-Za-z]\\w+[A-Za-z0-9]$")) { FX_LOGS(INFO) << "Write error: INVALID_KEY, For key: " << key; FX_LOGS(INFO) << "WriteItem response sent"; return completer.Reply( fit::error(examples_keyvaluestore_supportexports::WriteError::kInvalidKey)); } // Validate the value. if (value.empty()) { FX_LOGS(INFO) << "Write error: INVALID_VALUE, For key: " << key; FX_LOGS(INFO) << "WriteItem response sent"; return completer.Reply( fit::error(examples_keyvaluestore_supportexports::WriteError::kInvalidValue)); } if (key_value_store_.find(key) != key_value_store_.end()) { FX_LOGS(INFO) << "Write error: ALREADY_EXISTS, For key: " << key; FX_LOGS(INFO) << "WriteItem response sent"; return completer.Reply( fit::error(examples_keyvaluestore_supportexports::WriteError::kAlreadyExists)); } // Ensure that the value does not already exist in the store. key_value_store_.insert({key, value}); FX_LOGS(INFO) << "Wrote value at key: " << key; FX_LOGS(INFO) << "WriteItem response sent"; return completer.Reply(fit::success()); } void Export(ExportRequestView request, ExportCompleter::Sync& completer) override { FX_LOGS(INFO) << "Export request received"; fit::result result = Export(std::move(request->empty)); if (result.is_ok()) { completer.ReplySuccess(std::move(result.value())); } else { completer.ReplyError(result.error_value()); } FX_LOGS(INFO) << "Export response sent"; } using ExportError = ::examples_keyvaluestore_supportexports::wire::ExportError; using Exportable = ::examples_keyvaluestore_supportexports::wire::Exportable; using Item = ::examples_keyvaluestore_supportexports::wire::Item; fit::result<ExportError, zx::vmo> Export(zx::vmo vmo) { if (key_value_store_.empty()) { return fit::error(ExportError::kEmpty); } fidl::Arena arena; fidl::VectorView<Item> items; items.Allocate(arena, key_value_store_.size()); size_t count = 0; for (auto& [k, v] : key_value_store_) { // Create a wire |Item| object that borrows from |k| and |v|. // Since |k| and |v| are references into the long living |key_value_store_|, // while |items| only live within the current function scope, // this operation is safe. items[count] = Item{ .key = fidl::StringView::FromExternal(k), .value = fidl::VectorView<uint8_t>::FromExternal(v), }; count++; } std::sort(items.begin(), items.end(), [](const Item& a, const Item& b) { return a.key.get() < b.key.get(); }); Exportable exportable = Exportable::Builder(arena).items(items).Build(); fit::result encoded = fidl::Persist(exportable); if (encoded.is_error()) { FX_LOGS(ERROR) << "Failed to encode in persistence convention: " << encoded.error_value(); return fit::error(ExportError::kUnknown); } size_t content_size = 0; if (vmo.get_prop_content_size(&content_size) != ZX_OK) { return fit::error(ExportError::kUnknown); } if (encoded->size() > content_size) { return fit::error(ExportError::kStorageTooSmall); } if (vmo.set_prop_content_size(encoded->size()) != ZX_OK) { return fit::error(ExportError::kUnknown); } if (vmo.write(encoded->data(), 0, encoded->size()) != ZX_OK) { return fit::error(ExportError::kUnknown); } return fit::ok(std::move(vmo)); } void handle_unknown_method( fidl::UnknownMethodMetadata<examples_keyvaluestore_supportexports::Store> metadata, fidl::UnknownMethodCompleter::Sync& completer) override { FX_LOGS(WARNING) << "Received an unknown method with ordinal " << metadata.method_ordinal; } private: fidl::ServerBindingRef<examples_keyvaluestore_supportexports::Store> binding_; // The map that serves as the per-connection instance of the key-value store. // // Out-of-line references in wire types are always mutable. Thus the // |const std::vector<uint8_t>| from the baseline needs to be changed to // non-const as we're making a vector view pointing to it during |Export|, // even though in practice the value is never mutated. std::unordered_map<std::string, std::vector<uint8_t>> key_value_store_ = {}; }; int main(int argc, char** argv) { FX_LOGS(INFO) << "Started"; // The event loop is used to asynchronously listen for incoming connections and requests from the // client. The following initializes the loop, and obtains the dispatcher, which will be used when // binding the server implementation to a channel. async::Loop loop(&kAsyncLoopConfigNeverAttachToThread); async_dispatcher_t* dispatcher = loop.dispatcher(); // Create an |OutgoingDirectory| instance. // // The |component::OutgoingDirectory| class serves the outgoing directory for our component. This // directory is where the outgoing FIDL protocols are installed so that they can be provided to // other components. component::OutgoingDirectory outgoing = component::OutgoingDirectory(dispatcher); // The `ServeFromStartupInfo()` function sets up the outgoing directory with the startup handle. // The startup handle is a handle provided to every component by the system, so that they can // serve capabilities (e.g. FIDL protocols) to other components. zx::result result = outgoing.ServeFromStartupInfo(); if (result.is_error()) { FX_LOGS(ERROR) << "Failed to serve outgoing directory: " << result.status_string(); return -1; } // Register a handler for components trying to connect to |Store|. result = outgoing.AddUnmanagedProtocol<examples_keyvaluestore_supportexports::Store>( [dispatcher](fidl::ServerEnd<examples_keyvaluestore_supportexports::Store> server_end) { // Create an instance of our StoreImpl that destroys itself when the connection closes. new StoreImpl(dispatcher, std::move(server_end)); }); if (result.is_error()) { FX_LOGS(ERROR) << "Failed to add Store protocol: " << result.status_string(); return -1; } // Everything is wired up. Sit back and run the loop until an incoming connection wakes us up. FX_LOGS(INFO) << "Listening for incoming connections"; loop.Run(); return 0; }
HLCPP
客户端
// TODO(https://fxbug.dev/42060656): HLCPP implementation.
服务器
// TODO(https://fxbug.dev/42060656): HLCPP implementation.