必要條件
本教學課程是以編譯 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++ 繫結新增為建構依附元件
GN 版本
針對各個 FIDL 程式庫宣告,例如:
編譯 FIDL 時,程式庫的 C++ 繫結程式碼是
產生自原始目標名稱,後置字串為 _cpp
:
"//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",
]
}
請注意,以下程式碼會參照 C++ 繫結為 C++ 繫結新增依附元件
_cpp
目標。
(選用) 如要查看產生的繫結:
- 使用
fx build
建構。 - 切換到產生的檔案目錄:
out/default/fidling/gen/examples/fidl/fuchsia.examples/fuchsia.examples/cpp/fidl/fuchsia.examples/cpp
, 產生檔案的位置你可能需要變更out/default
如果您設定了不同的建構輸出目錄。您可以檢查 輸出目錄 (cat .fx-build-dir
)。
如要進一步瞭解如何找出產生的繫結程式碼,請參閱 查看產生的繫結程式碼。
Bazel 建構作業
視 Bazel 建構作業的 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 ...
]
在程式碼中加入繫結標頭
新增建構依附元件後,即可加入繫結標頭。
包含模式為 #include <fidl/my.library.name/cpp/fidl.h>
。
以下是 domain_objects/main.cc
頂端的陳述式包括
繫結,並將產生的 API 提供給原始碼:
#include <fidl/fuchsia.examples/cpp/fidl.h>
使用自然領域物件
自然型別是以人體工學和安全性為重點的 C++ 領域變種 如需儲存大量結構化物件 建議使用 Cloud BigtableFIDL 值的樹狀結構是以 C++ 物件的樹狀結構表示, 階層擁有權。這表示函式若收到 型別,可以假設整個樹狀結構中所有子項物件的擁有權是唯一的。 當根物件超出範圍時,樹狀結構就會消失。
整體而言,自然類型會採用 std::
容器和概念。適用對象
例如資料表是以一組集合表示
std::optional<Field>
秒。向量是 std::vector<T>
,以此類推
實作慣用的 C++ 移動、複製和等式。舉例來說
resource 類型僅供移動,而值類型會同時執行
移動和移動操作的設計
可最佳化物件的移轉程序
移動表格並不會將其變成空白 (只會以遞迴方式移動欄位)
與「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());
自然結構物
自然結構體是直接的記錄物件,能公開 const 和可變動的
存取子。以 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 要求的一部分,透過借用
只要運用這個型別的元素就好。
為區分自然類型,FIDL 程式庫的電線類型是
定義於 ...::wire
巢狀命名空間中,例如fuchsia_my_library::wire
。
電線型別中無從屬的指標,因此雖然具有彈性,但卻非常有彈性 不安全。本教學課程將著重於根據 記憶領域如要進一步瞭解涉及不安全記憶體借用的進階用法,請參閱 有線網域物件的記憶體擁有權。
線位元和列舉
因為位元和列舉具有非常簡單的記憶體配置,而且沒有任何
非線子項,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);
線材結構
有線結構體是簡單的 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 方案:持久性
「永久」FIDL 是指以電匯方式儲存的二進位 FIDL 資料 基礎傳輸。將資料保存在任意時間長度 持續存取特定期間的多個資料 資料庫項目
簡單地擴充鍵/值儲存庫以支援匯出備份
只需新增一個停止世界的新方法,
並以 FIDL vector<Item>
的形式傳回。有兩個缺點
。第一個原因是
備份作業 - 用戶端無需支付要求執行備份作業的費用
對伺服器來說 成本非常高其二是牽涉到大量
複製:用戶端幾乎可以確定只會寫入最終的備份
或資料庫等備份資料儲存庫。
將其解碼 (可能非常大的) FIDL 物件
解碼器就會立即對該回應進行重新編碼
因此非常浪費
原因
更理想的做法是使用 Zircon 的虛擬記憶體 物件。與其不斷複製位元組 都屬於值區內襯,我們得以挖掘 VMO 來存放 再轉送給伺服器 而不必在兩者之間還原序列化只要目標資料 商店的通訊協定允許您接受使用 VMO 傳輸的資料, 是完成這類昂貴作業的最好方法事實上 例如 Fuchsia 的檔案系統會執行這種確切模式。這麼做的好處 這個方法就是強迫用戶端在詢問 部署高成本作業的伺服器,將兩者之間的工作差距降至最低 。
FIDL 值類型可保留至任何位元組導向儲存媒介,
FIDL 資料持續性二進位格式。我們會保留
剛加入 VMO 的 Exportable
類型 FIDL。系統會將物件編碼
並寫入儲存空間 (在此例中,VMO 之後可儲存為
檔案),並在需要再次存取資料時從檔案解碼
封存、傳輸及解碼訊息的方式
並透過 IPC 使用 FIDL。
為了以安全的方式執行此操作,並遵循最低權限原則,
我們就應該限制代表 VMO 可能持有的帳號代碼。
輸入帳號代碼,FIDL 第一流描述權限的方法
分別適用於特定帳號代碼類型在這個範例中,我們允許 empty
VMO
會透過 Export
要求傳遞至伺服器,以便從該要求讀取資料、查詢大小
寫入及寫入當 VMO 傳回時,我們會移除調整其大小和
撰寫、確保沒有任何程序,甚至是遠處的惡意發動者
元件,可以修改這些資料在系統中移動時的資料。
實作
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.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", ], }, ], }
用戶端和伺服器實作項目現在可以使用任何支援的語言編寫:
荒漠油廠
用戶端
// 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 fuchsia_zircon::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.