以下每个部分都探讨了一种可能的迭代方式, 原始键值对存储区设计,特别是:
无需依序构建,每个 提供了一种独立方式,即以一种独立方式 可以对基本案例进行修改或改进。
本页以 键值对存储区基准示例。
添加对从商店中读取的支持
推理
原始只写键值存储现已通过 读取程序从商店中重新读取的权限。
实现
应用于 FIDL 和 CML 定义的更改如下:
FIDL
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. library examples.keyvaluestore.addreaditem; // Aliases for the key and value. Using aliases helps increase the readability of FIDL files and // reduces likelihood of errors due to differing constraints. alias Key = string:128; alias Value = vector<byte>:64000; /// An item in the store. The key must match the regex `^[A-z][A-z0-9_\.\/]{2,62}[A-z0-9]$`. That /// is, it must start with a letter, end with a letter or number, contain only letters, numbers, /// periods, and slashes, and be between 4 and 64 characters long. type Item = struct { key Key; value Value; }; /// An enumeration of things that may go wrong when trying to write a value to our store. type WriteError = flexible enum { UNKNOWN = 0; INVALID_KEY = 1; INVALID_VALUE = 2; ALREADY_EXISTS = 3; }; /// An enumeration of things that may go wrong when trying to read a value out of our store. type ReadError = flexible enum { UNKNOWN = 0; NOT_FOUND = 1; }; /// A very basic key-value store - so basic, in fact, that one may only write to it, never read! @discoverable open protocol Store { /// Writes an item to the store. flexible WriteItem(struct { attempt Item; }) -> () error WriteError; /// Reads an item from the store. flexible ReadItem(struct { key Key; }) -> (Item) error ReadError; };
CML
客户端
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. { include: [ "syslog/client.shard.cml" ], program: { runner: "elf", binary: "bin/client_bin", }, use: [ { protocol: "examples.keyvaluestore.addreaditem.Store" }, ], config: { write_items: { type: "vector", max_count: 16, element: { type: "string", max_size: 64, }, }, read_items: { type: "vector", max_count: 16, element: { type: "string", max_size: 64, }, }, }, }
服务器
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. { include: [ "syslog/client.shard.cml" ], program: { runner: "elf", binary: "bin/server_bin", }, capabilities: [ { protocol: "examples.keyvaluestore.addreaditem.Store" }, ], expose: [ { protocol: "examples.keyvaluestore.addreaditem.Store", from: "self", }, ], }
大区
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. { children: [ { name: "client", url: "#meta/client.cm", }, { name: "server", url: "#meta/server.cm", }, ], offer: [ // Route the protocol under test from the server to the client. { protocol: "examples.keyvaluestore.addreaditem.Store", from: "#server", to: "#client", }, // Route diagnostics support to all children. { protocol: [ "fuchsia.inspect.InspectSink", "fuchsia.logger.LogSink", ], from: "parent", to: [ "#client", "#server", ], }, ], }
所有语言的客户端和服务器实现也会发生变化:
Rust
客户端
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. use anyhow::{Context as _, Error}; use config::Config; use fidl_examples_keyvaluestore_addreaditem::{Item, StoreMarker}; use fuchsia_component::client::connect_to_protocol; use std::{str, thread, time}; #[fuchsia::main] async fn main() -> Result<(), Error> { println!("Started"); // Load the structured config values passed to this component at startup. let config = Config::take_from_startup_handle(); // Use the Component Framework runtime to connect to the newly spun up server component. We wrap // our retained client end in a proxy object that lets us asynchronously send `Store` requests // across the channel. let store = connect_to_protocol::<StoreMarker>()?; println!("Outgoing connection enabled"); // This client's structured config has one parameter, a vector of strings. Each string is the // path to a resource file whose filename is a key and whose contents are a value. We iterate // over them and try to write each key-value pair to the remote store. for key in config.write_items.into_iter() { let path = format!("/pkg/data/{}.txt", key); let value = std::fs::read_to_string(path.clone()) .with_context(|| format!("Failed to load {path}"))?; match store.write_item(&Item { key: key, value: value.into_bytes() }).await? { Ok(_) => println!("WriteItem Success"), Err(err) => println!("WriteItem Error: {}", err.into_primitive()), } } // The structured config for this client contains `read_items`, a vector of strings, each of // which is meant to be read from the key-value store. We iterate over these keys, attempting to // read them in turn. for key in config.read_items.into_iter() { let res = store.read_item(key.as_str()).await; match res.unwrap() { Ok(val) => { println!("ReadItem Success: key: {}, value: {}", key, str::from_utf8(&val.1)?) } Err(err) => println!("ReadItem Error: {}", err.into_primitive()), } } // TODO(https://fxbug.dev/42156498): We need to sleep here to make sure all logs get drained. Once the // referenced bug has been resolved, we can remove the sleep. thread::sleep(time::Duration::from_secs(2)); Ok(()) }
服务器
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. use { anyhow::{Context as _, Error}, fidl_examples_keyvaluestore_addreaditem::{ Item, ReadError, StoreRequest, StoreRequestStream, WriteError, }, fuchsia_component::server::ServiceFs, futures::prelude::*, lazy_static::lazy_static, regex::Regex, std::cell::RefCell, std::collections::hash_map::Entry, std::collections::HashMap, }; lazy_static! { static ref KEY_VALIDATION_REGEX: Regex = Regex::new(r"^[A-Za-z][A-Za-z0-9_\./]{2,62}[A-Za-z0-9]$") .expect("Key validation regex failed to compile"); } /// Handler for the `WriteItem` method. fn write_item(store: &mut HashMap<String, Vec<u8>>, attempt: Item) -> Result<(), WriteError> { // Validate the key. if !KEY_VALIDATION_REGEX.is_match(attempt.key.as_str()) { println!("Write error: INVALID_KEY, For key: {}", attempt.key); return Err(WriteError::InvalidKey); } // Validate the value. if attempt.value.is_empty() { println!("Write error: INVALID_VALUE, For key: {}", attempt.key); return Err(WriteError::InvalidValue); } // Write to the store, validating that the key did not already exist. match store.entry(attempt.key) { Entry::Occupied(entry) => { println!("Write error: ALREADY_EXISTS, For key: {}", entry.key()); Err(WriteError::AlreadyExists) } Entry::Vacant(entry) => { println!("Wrote value at key: {}", entry.key()); entry.insert(attempt.value); Ok(()) } } } /// Creates a new instance of the server. Each server has its own bespoke, per-connection instance /// of the key-value store. async fn run_server(stream: StoreRequestStream) -> Result<(), Error> { // Create a new in-memory key-value store. The store will live for the lifetime of the // connection between the server and this particular client. let store = RefCell::new(HashMap::<String, Vec<u8>>::new()); // Serve all requests on the protocol sequentially - a new request is not handled until its // predecessor has been processed. stream .map(|result| result.context("failed request")) .try_for_each(|request| async { // Match based on the method being invoked. match request { StoreRequest::WriteItem { attempt, responder } => { println!("WriteItem request received"); // The `responder` parameter is a special struct that manages the outgoing reply // to this method call. Calling `send` on the responder exactly once will send // the reply. responder .send(write_item(&mut store.borrow_mut(), attempt)) .context("error sending reply")?; println!("WriteItem response sent"); } StoreRequest::ReadItem { key, responder } => { println!("ReadItem request received"); // Read the item from the store, returning the appropriate error if it could not be found. responder .send(match store.borrow().get(&key) { Some(found) => { println!("Read value at key: {}", key); Ok((&key, found)) } None => { println!("Read error: NOT_FOUND, For key: {}", key); Err(ReadError::NotFound) } }) .context("error sending reply")?; println!("ReadItem response sent"); } // StoreRequest::_UnknownMethod { ordinal, .. } => { println!("Received an unknown method with ordinal {ordinal}"); } } Ok(()) }) .await } // A helper enum that allows us to treat a `Store` service instance as a value. enum IncomingService { Store(StoreRequestStream), } #[fuchsia::main] async fn main() -> Result<(), Error> { println!("Started"); // Add a discoverable instance of our `Store` protocol - this will allow the client to see the // server and connect to it. let mut fs = ServiceFs::new_local(); fs.dir("svc").add_fidl_service(IncomingService::Store); fs.take_and_serve_directory_handle()?; println!("Listening for incoming connections"); // The maximum number of concurrent clients that may be served by this process. const MAX_CONCURRENT: usize = 10; // Serve each connection simultaneously, up to the `MAX_CONCURRENT` limit. fs.for_each_concurrent(MAX_CONCURRENT, |IncomingService::Store(stream)| { run_server(stream).unwrap_or_else(|e| println!("{:?}", e)) }) .await; Ok(()) }
C++(自然)
客户端
// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.
服务器
// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.
C++(有线)
客户端
// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.
服务器
// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.
HLCPP
客户端
// TODO(https://fxbug.dev/42060656): HLCPP implementation.
服务器
// TODO(https://fxbug.dev/42060656): HLCPP implementation.
使用通用值
推理
键值存储基准 示例 实施是一个很好的起点,但也存在一个主要缺点,那就是数据 存储为原始字节。FIDL 是一种丰富的类型语言。强制用于 例如,要存储为非类型字节数组的 UTF-8 字符串将清除 对于 *.fidl 文件以及 程序员使用由该文件生成的绑定
实现
这项更改的主要目标是替换基准支持请求的vector<byte>
具备 union
的 value
成员,其中存储了多种可能的类型。事实上,作为
填写一份有关 FIDL 的
value 类型已启用
优惠:
- FIDL 的所有内置标量类型都用作
Value
中的变体union
:bool
、uint8
、uint16
、uint32
、uint64
、int8
、int16
、int32
、int64
、float32
和float64
(也称为 FIDL) 基元类型),以及string
。 - 此
union
还使用了 FIDL 的内置array<T, N>
和vector<T>
类型的模板。 - FIDL 的所有类型布局,即
bits
、enum
、table
、union
和struct
,在此示例中至少使用了一次。
用于 WriteItem
的请求和响应载荷也已更改
从 struct
转换为命名的 table
和内嵌的 flexible union
。
事实上,这三种布局中的任何一种都可以用作请求/响应有效负载。通过
后两个称为表载荷和 *联合载荷,它们分别是
在除大多数对邮件大小敏感的情形下优先使用。这是因为
以后以二进制兼容的方式扩展要容易得多。
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.usegenericvalues; /// 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 Value; }; // Because the `Value` must be used both in the request and the response, we give it its own named // type. The type is a `union` of all possible data types that we take as values, and is marked // `flexible` to allow for the easy addition of new data types in the future. type Value = flexible union { // Keep the original `bytes` as one of the options in the new union. 1: bytes vector<byte>:64000; // A `string` is very similar to `vector<byte>` on the wire, with the extra constraint that // it enforces that it enforces that the byte vector in question is valid UTF-8. 2: string string:64000; // All of FIDL's primitive types. 3: bool bool; 4: uint8 uint8; 5: int8 int8; 6: uint16 uint16; 7: int16 int16; 8: uint32 uint32; 9: int32 int32; 10: float32 float32; 11: uint64 uint64; 12: int64 int64; 13: float64 float64; // FIDL does not natively support 128-bit integer types, so we have to define our own // representations. 14: uint128 array<uint64, 2>; }; // Because we now supoprt a richer range of types as values in our store, it is helpful to use a // `flexible`, and therefore evolvable, `bits` type to store write options. type WriteOptions = flexible bits : uint8 { // This flag allows us to overwrite existing data when there is a collision, rather than failing // with an `WriteError.ALREADY_EXISTS`. OVERWRITE = 0b1; // This flag allows us to concatenate to existing data when there is a collision, rather than // failing with an `WriteError.ALREADY_EXISTS`. "Concatenation" means addition for the numeric // variants and appending to the `bytes`/`string` variants. If no existing data can be found, we // "concatenate" to default values of zero and an empty vector, respectively. Attempting to // concatenate to an existing variant of a different type will return a // `WriteError.INVALID_VALUE` error. CONCAT = 0b10; }; /// 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; }; /// A very basic key-value store. @discoverable open protocol Store { /// Writes an item to the store. /// /// Since the value stored in the key-value store can now be different from the input (if the /// `WriteOptions.CONCAT` flag is set), we need to return the resulting `Value` to the /// requester. /// /// We use an (anonymous) `table` and a (named) `flexible union` as the request and response /// payload, respectively, to allow for easier future evolution. Both of these types are /// `flexible`, meaning that adding or removing members is binary-compatible. This makes them /// much easier to evolve that the `struct` types that were previously used, which cannot be /// changed after release without breaking ABI. flexible WriteItem(table { 1: attempt Item; 2: options WriteOptions; }) -> (Value) error WriteError; };
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.usegenericvalues.Store" }, ], config: { // A vector of values for every easily representible type in our key-value store. For // brevity's sake, the 8, 16, and 32 bit integer types and booleans are omitted. // // TODO(https://fxbug.dev/42178362): It would absolve individual language implementations of a great // deal of string parsing if we were able to use all FIDL constructs directly here. In // particular, floats and nested types are very difficult to represent, and have been // excluded from this example for the time being. set_concat_option: { type: "bool" }, set_overwrite_option: { type: "bool" }, write_bytes: { type: "vector", max_count: 16, element: { type: "string", max_size: 64, }, }, write_strings: { type: "vector", max_count: 16, element: { type: "string", max_size: 64, }, }, write_uint64s: { type: "vector", max_count: 16, element: { type: "uint64" }, }, write_int64s: { type: "vector", max_count: 16, element: { type: "int64" }, }, // Note: due to the limitation of structured config not allowing vectors nested in vectors, // we only set the lower half of the uint128 for simplicity's sake. write_uint128s: { type: "vector", max_count: 16, element: { 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.usegenericvalues.Store" }, ], expose: [ { protocol: "examples.keyvaluestore.usegenericvalues.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.usegenericvalues.Store", from: "#server", to: "#client", }, // Route diagnostics support to all children. { protocol: [ "fuchsia.inspect.InspectSink", "fuchsia.logger.LogSink", ], from: "parent", to: [ "#client", "#server", ], }, ], }
然后,可以使用任何受支持的语言编写客户端和服务器实现:
Rust
客户端
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. use { anyhow::{Context as _, Error}, config::Config, fidl_examples_keyvaluestore_usegenericvalues::{ Item, StoreMarker, StoreProxy, StoreWriteItemRequest, Value, WriteOptions, }, fuchsia_component::client::connect_to_protocol, std::{thread, time}, }; // A helper function to sequentially write a single item to the key-value store and print a log when // successful. async fn write_next_item( store: &StoreProxy, key: &str, value: Value, options: WriteOptions, ) -> Result<(), Error> { // Create an empty request payload using `::default()`. let mut req = StoreWriteItemRequest::default(); req.options = Some(options); // Fill in the `Item` we will be attempting to write. println!("WriteItem request sent: key: {}, value: {:?}", &key, &value); req.attempt = Some(Item { key: key.to_string(), value: value }); // Send and async `WriteItem` request to the server. match store.write_item(&req).await.context("Error sending request")? { Ok(value) => println!("WriteItem response received: {:?}", &value), Err(err) => println!("WriteItem Error: {}", err.into_primitive()), } Ok(()) } #[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"); // All of our requests will have the same bitflags set. Pull these settings from the config. let mut options = WriteOptions::empty(); options.set(WriteOptions::OVERWRITE, config.set_overwrite_option); options.set(WriteOptions::CONCAT, config.set_concat_option); // The structured config provides one input for most data types that can be stored in the data // store. Iterate through those inputs in the order we see them in the FIDL file. // // Note that FIDL unions are rendered as enums in Rust; for example, the `Value` union has now // become a `Value` Rust enum, with each member taking exactly one argument. for value in config.write_bytes.into_iter() { write_next_item(&store, "bytes", Value::Bytes(value.into()), options).await?; } for value in config.write_strings.into_iter() { write_next_item(&store, "string", Value::String(value), options).await?; } for value in config.write_uint64s.into_iter() { write_next_item(&store, "uint64", Value::Uint64(value), options).await?; } for value in config.write_int64s.into_iter() { write_next_item(&store, "int64", Value::Int64(value), options).await?; } for value in config.write_uint128s.into_iter() { write_next_item(&store, "uint128", Value::Uint128([0, value]), options).await?; } // 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_examples_keyvaluestore_usegenericvalues::{ Item, StoreRequest, StoreRequestStream, Value, WriteError, WriteOptions, }; use std::collections::hash_map::OccupiedEntry; use std::ops::Add; 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"); } /// Sums any numeric type. fn sum<T: Add + Add<Output = T> + Copy>(operands: [T; 2]) -> T { operands[0] + operands[1] } /// Clones and inserts an entry, so that the original (now concatenated) copy may be returned in the /// response. fn write(inserting: Value, mut entry: OccupiedEntry<'_, String, Value>) -> Value { entry.insert(inserting.clone()); println!("Wrote key: {}, value: {:?}", entry.key(), &inserting); inserting } /// Handler for the `WriteItem` method. fn write_item( store: &mut HashMap<String, Value>, attempt: Item, options: &WriteOptions, ) -> Result<Value, 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); } match store.entry(attempt.key) { Entry::Occupied(entry) => { // The `CONCAT` flag supersedes the `OVERWRITE` flag, so check it first. if options.contains(WriteOptions::CONCAT) { match entry.get() { Value::Bytes(old) => { if let Value::Bytes(new) = attempt.value { let mut combined = old.clone(); combined.extend(new); return Ok(write(Value::Bytes(combined), entry)); } } Value::String(old) => { if let Value::String(new) = attempt.value { return Ok(write(Value::String(format!("{}{}", old, &new)), entry)); } } Value::Uint64(old) => { if let Value::Uint64(new) = attempt.value { return Ok(write(Value::Uint64(sum([*old, new])), entry)); } } Value::Int64(old) => { if let Value::Int64(new) = attempt.value { return Ok(write(Value::Int64(sum([*old, new])), entry)); } } // Note: only works on the uint64 range in practice. Value::Uint128(old) => { if let Value::Uint128(new) = attempt.value { return Ok(write(Value::Uint128([0, sum([old[1], new[1]])]), entry)); } } _ => { panic!("actively unsupported type!") } } // Only reachable if the type of the would be concatenated value did not match the // value already occupying this entry. println!("Write error: INVALID_VALUE for key: {}", entry.key()); return Err(WriteError::InvalidValue); } // If we're not doing CONCAT, check for OVERWRITE next. if options.contains(WriteOptions::OVERWRITE) { return Ok(write(attempt.value, entry)); } println!("Write error: ALREADY_EXISTS for key: {}", entry.key()); Err(WriteError::AlreadyExists) } Entry::Vacant(entry) => { println!("Wrote key: {}, value: {:?}", entry.key(), &attempt.value); entry.insert(attempt.value.clone()); Ok(attempt.value) } } } /// 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, Value>::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 { // Because we are using a table payload, there is an extra level of indirection. The // top-level container for the table itself is always called "payload". StoreRequest::WriteItem { payload, responder } => { println!("WriteItem request received"); // Error out if either of the request table's members are not set. let attempt = payload.attempt.context("required field 'attempt' is unset")?; let options = payload.options.context("required field 'options' is unset")?; // 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, &options) .as_ref() .map_err(|e| *e), ) .context("error sending reply")?; println!("WriteItem response sent"); } // StoreRequest::_UnknownMethod { ordinal, .. } => { println!("Received an unknown method with ordinal {ordinal}"); } } Ok(()) }) .await } // A helper enum that allows us to treat a `Store` service instance as a value. enum IncomingService { Store(StoreRequestStream), } #[fuchsia::main] async fn main() -> Result<(), Error> { println!("Started"); // Add a discoverable instance of our `Store` protocol - this will allow the client to see the // server and connect to it. let mut fs = ServiceFs::new_local(); fs.dir("svc").add_fidl_service(IncomingService::Store); fs.take_and_serve_directory_handle()?; println!("Listening for incoming connections"); // The maximum number of concurrent clients that may be served by this process. const MAX_CONCURRENT: usize = 10; // Serve each connection simultaneously, up to the `MAX_CONCURRENT` limit. fs.for_each_concurrent(MAX_CONCURRENT, |IncomingService::Store(stream)| { run_server(stream).unwrap_or_else(|e| println!("{:?}", e)) }) .await; Ok(()) }
C++(自然)
客户端
// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.
服务器
// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.
C++(有线)
客户端
// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.
服务器
// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.
HLCPP
客户端
// TODO(https://fxbug.dev/42060656): HLCPP implementation.
服务器
// TODO(https://fxbug.dev/42060656): HLCPP implementation.
支持嵌套键值对存储
在此变体中,我们允许键值存储区将其他键值存储区视为
成员。简而言之,我们把它变成了一棵树。为此,我们会使用
value
的定义,其中定义使用双成员 union
:一种变体
存储使用与之前相同的 vector<byte>
类型的叶节点,而另一个
以其他嵌套存储区的形式存储分支节点。
推理
在这里,我们可以看到“可选性”的多种用途,通过它可以声明一个类型, 可能会存在。FIDL 中有三种可选性:
- 始终存储的类型
不符合要求
因此有一种内置方式可以描述“缺失”通过
null 信封。正在启用
这些类型的可选性不会影响它们所属的消息的电线形状
它只是更改对特定标签有效的值
类型。
union
、vector<T>
、client_end
、server_end
和zx.Handle
都可以通过添加:optional
约束条件使所有类型都是可选的。 通过将value
union
设置为可选,我们能够引入 null条目,采用不存在的value
的形式。这意味着,空bytes
和不存在/空的store
属性是无效值。 - 与上述类型不同,
struct
布局没有额外的空间, 可以存储 null 标头。因此,需要将其封装在 信封、更改所包含邮件的网线形状 位置为确保这种电线修改效果清晰易读,Item
struct
类型必须封装在box<T>
类型模板中。 - 最后,
table
布局始终是可选的。缺失table
只是一种 且未设置任何成员。
树状结构是自然的自引用数据结构:树中的任何节点都可以
包含具有纯数据(本例中为字符串)的叶,或具有
节点。这需要递归:Item
的定义现在以传递方式传递
需要依赖自身!在 FIDL 中表示递归类型可能有点棘手,
特别是考虑到我们目前获得的支持
受限。只要有
自引用创建的循环中至少有一个可选类型。对于
实例,在这里我们将 items
struct
成员定义为 box<Item>
,
从而打破 include 循环。
这些更改还大量使用了匿名类型,即
声明是内嵌在它们的唯一使用点,而不是直接使用,
它们自己的顶级 type
声明。默认情况下
生成的语言绑定中的类型取自其本地上下文。对于
实例中,新引入的 flexible union
会使用其所属成员的
名称为 Value
,新引入的 struct
将变为 Store
,依此类推。
由于这种启发法有时可能会导致冲突,因此 FIDL 提供了一种逃逸方法。
允许作者手动替换匿名类型生成的
名称。这是通过 @generated_name
属性完成的,该属性允许
更改后端生成的名称在这里,我们可以使用
Store
类型已重命名为 NestedStore
,以防止与
protocol
声明。
实现
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.supporttrees; /// 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 strict union { // Keep the original `bytes` as one of the options in the new union. All leaf nodes in the // tree must be `bytes`, or absent unions (representing empty). Empty byte arrays are // disallowed. 1: bytes vector<byte>:64000; // Allows a store within a store, thereby turning our flat key-value store into a tree // thereof. Note the use of `@generated_name` to prevent a type-name collision with the // `Store` protocol below, and the use of `box<T>` to ensure that there is a break in the // chain of recursion, thereby allowing `Item` to include itself in its own definition. // // This is a table so that added fields, like for example a `hash`, can be easily added in // the future. 2: store @generated_name("nested_store") table { 1: items vector<box<Item>>; }; }:optional; }; /// 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; }; /// A very basic key-value store. @discoverable open protocol Store { /// Writes an item to the store. flexible WriteItem(struct { attempt Item; }) -> () error WriteError; };
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.supporttrees.Store" }, ], config: { write_items: { type: "vector", max_count: 16, element: { type: "string", max_size: 64, }, }, // A newline separated list nested entries. The first line should be the key // for the nested store, and each subsequent entry should be a pointer to a text file // containing the string value. The name of that text file (without the `.txt` suffix) will // serve as the entries key. write_nested: { type: "vector", max_count: 16, element: { type: "string", max_size: 64, }, }, // A list of keys, all of which will be populated as null entries. write_null: { type: "vector", max_count: 16, element: { type: "string", max_size: 64, }, }, }, }
服务器
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. { include: [ "syslog/client.shard.cml" ], program: { runner: "elf", binary: "bin/server_bin", }, capabilities: [ { protocol: "examples.keyvaluestore.supporttrees.Store" }, ], expose: [ { protocol: "examples.keyvaluestore.supporttrees.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.supporttrees.Store", from: "#server", to: "#client", }, // Route diagnostics support to all children. { protocol: [ "fuchsia.inspect.InspectSink", "fuchsia.logger.LogSink", ], from: "parent", to: [ "#client", "#server", ], }, ], }
然后,可以使用任何受支持的语言编写客户端和服务器实现:
Rust
客户端
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. use { anyhow::{Context as _, Error}, config::Config, fidl_examples_keyvaluestore_supporttrees::{Item, NestedStore, StoreMarker, Value}, fuchsia_component::client::connect_to_protocol, std::{thread, time}, }; #[fuchsia::main] async fn main() -> Result<(), Error> { println!("Started"); // Load the structured config values passed to this component at startup. let config = Config::take_from_startup_handle(); // Use the Component Framework runtime to connect to the newly spun up server component. We wrap // our retained client end in a proxy object that lets us asynchronously send `Store` requests // across the channel. let store = connect_to_protocol::<StoreMarker>()?; println!("Outgoing connection enabled"); // This client's structured config has one parameter, a vector of strings. Each string is the // path to a resource file whose filename is a key and whose contents are a value. We iterate // over them and try to write each key-value pair to the remote store. for key in config.write_items.into_iter() { let path = format!("/pkg/data/{}.txt", key); let value = std::fs::read_to_string(path.clone()) .with_context(|| format!("Failed to load {path}"))?; let res = store .write_item(&Item { key: key.clone(), value: Some(Box::new(Value::Bytes(value.into_bytes()))), }) .await; match res? { Ok(_) => println!("WriteItem Success at key: {}", key), Err(err) => println!("WriteItem Error: {}", err.into_primitive()), } } // Add nested entries to the key-value store as well. The entries are strings, where the first // line is the key of the entry, and each subsequent entry should be a pointer to a text file // containing the string value. The name of that text file (without the `.txt` suffix) will // serve as the entries key. for spec in config.write_nested.into_iter() { let mut items = vec![]; let mut nested_store = NestedStore::default(); let mut lines = spec.split("\n"); let key = lines.next().unwrap(); // For each entry, make a new entry in the `NestedStore` being built. for entry in lines { let path = format!("/pkg/data/{}.txt", entry); let contents = std::fs::read_to_string(path.clone()) .with_context(|| format!("Failed to load {path}"))?; items.push(Some(Box::new(Item { key: entry.to_string(), value: Some(Box::new(Value::Bytes(contents.into()))), }))); } nested_store.items = Some(items); // Send the `NestedStore`, represented as a vector of values. let res = store .write_item(&Item { key: key.to_string(), value: Some(Box::new(Value::Store(nested_store))), }) .await; match res? { Ok(_) => println!("WriteItem Success at key: {}", key), Err(err) => println!("WriteItem Error: {}", err.into_primitive()), } } // Each entry in this list is a null value in the store. for key in config.write_null.into_iter() { match store.write_item(&Item { key: key.to_string(), value: None }).await? { Ok(_) => println!("WriteItem Success at key: {}", key), Err(err) => println!("WriteItem Error: {}", err.into_primitive()), } } // TODO(https://fxbug.dev/42156498): We need to sleep here to make sure all logs get drained. Once the // referenced bug has been resolved, we can remove the sleep. thread::sleep(time::Duration::from_secs(2)); Ok(()) }
服务器
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Note: For the clarity of this example, allow code to be unused. #![allow(dead_code)] use { anyhow::{Context as _, Error}, fidl_examples_keyvaluestore_supporttrees::{ Item, StoreRequest, StoreRequestStream, Value, WriteError, }, fuchsia_component::server::ServiceFs, futures::prelude::*, lazy_static::lazy_static, regex::Regex, std::cell::RefCell, std::collections::hash_map::Entry, std::collections::HashMap, std::str::from_utf8, }; 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"); } // A representation of a key-value store that can contain an arbitrarily deep nesting of other // key-value stores. enum StoreNode { Leaf(Option<Vec<u8>>), Branch(Box<HashMap<String, StoreNode>>), } /// Recursive item writer, which takes a `StoreNode` that may not necessarily be the root node, and /// writes an entry to it. fn write_item( store: &mut HashMap<String, StoreNode>, attempt: Item, path: &str, ) -> 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); } // 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) => { let key = format!("{}{}", &path, entry.key()); match attempt.value { // Null entries are allowed. None => { println!("Wrote value: NONE at key: {}", key); entry.insert(StoreNode::Leaf(None)); } Some(value) => match *value { // If this is a nested store, recursively make a new store to insert at this // position. Value::Store(entry_list) => { // Validate the value - absent stores, items lists with no children, or any // of the elements within that list being empty boxes, are all not allowed. if entry_list.items.is_some() { let items = entry_list.items.unwrap(); if !items.is_empty() && items.iter().all(|i| i.is_some()) { let nested_path = format!("{}/", key); let mut nested_store = HashMap::<String, StoreNode>::new(); for item in items.into_iter() { write_item(&mut nested_store, *item.unwrap(), &nested_path)?; } println!("Created branch at key: {}", key); entry.insert(StoreNode::Branch(Box::new(nested_store))); return Ok(()); } } println!("Write error: INVALID_VALUE, For key: {}", key); return Err(WriteError::InvalidValue); } // This is a simple leaf node on this branch. Value::Bytes(value) => { // Validate the value. if value.is_empty() { println!("Write error: INVALID_VALUE, For key: {}", key); return Err(WriteError::InvalidValue); } println!("Wrote key: {}, value: {:?}", key, from_utf8(&value).unwrap()); entry.insert(StoreNode::Leaf(Some(value))); } }, } Ok(()) } } } /// Creates a new instance of the server. Each server has its own bespoke, per-connection instance /// of the key-value store. async fn run_server(stream: StoreRequestStream) -> Result<(), Error> { // Create a new in-memory key-value store. The store will live for the lifetime of the // connection between the server and this particular client. let store = RefCell::new(HashMap::<String, StoreNode>::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::_UnknownMethod { ordinal, .. } => { println!("Received an unknown method with ordinal {ordinal}"); } } Ok(()) }) .await } // A helper enum that allows us to treat a `Store` service instance as a value. enum IncomingService { Store(StoreRequestStream), } #[fuchsia::main] async fn main() -> Result<(), Error> { println!("Started"); // Add a discoverable instance of our `Store` protocol - this will allow the client to see the // server and connect to it. let mut fs = ServiceFs::new_local(); fs.dir("svc").add_fidl_service(IncomingService::Store); fs.take_and_serve_directory_handle()?; println!("Listening for incoming connections"); // The maximum number of concurrent clients that may be served by this process. const MAX_CONCURRENT: usize = 10; // Serve each connection simultaneously, up to the `MAX_CONCURRENT` limit. fs.for_each_concurrent(MAX_CONCURRENT, |IncomingService::Store(stream)| { run_server(stream).unwrap_or_else(|e| println!("{:?}", e)) }) .await; Ok(()) }
C++(自然)
客户端
// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.
服务器
// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.
C++(有线)
客户端
// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.
服务器
// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.
HLCPP
客户端
// TODO(https://fxbug.dev/42060656): HLCPP implementation.
服务器
// TODO(https://fxbug.dev/42060656): HLCPP implementation.
添加对迭代商店的支持
按顺序迭代是对键值存储的有用操作: 用于返回在 让它按顺序显示
推理
在 FIDL 中,最好使用迭代器来完成,迭代器通常以 可进行此迭代的单独协议。使用单独的 因此使用单独的频道具有诸多好处,包括 通过 主协议
协议 P
的通道连接的客户端和服务器端可以是
以 FIDL 数据类型表示,以 client_end:P
和 server_end:P
的形式表示,
。这些类型统称为“协议结束”,
代表另一种(非@discoverable
)将 FIDL 客户端连接到
相应的服务器:通过现有的 FIDL 连接!
协议终止是一般 FIDL 概念的具体实例:资源 type。资源类型旨在包含 FIDL 句柄,这必不可少 对类型使用方式的额外限制。类型必须始终为 唯一,因为底层资源由其他 capability Manager 调控 (通常是 Zircon 内核)。通过简单的内存中复制此类资源 不让经理参与,是不可行的。为避免此类重复, FIDL 中的所有资源类型始终只能移动。
最后,Iterator
协议本身的 Get()
方法使用
大小限制。这限制了
在单次拉取中传输,从而可以衡量资源使用情况
控制。它还创建了自然的分页边界:而不是一个巨大的转储。
所有结果,则服务器只需在
。
实现
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.additerator; /// 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 create an iterator. type IterateConnectionError = flexible enum { /// The starting key was not found. UNKNOWN_START_AT = 1; }; /// A key-value store which supports insertion and iteration. @discoverable open protocol Store { /// Writes an item to the store. flexible WriteItem(struct { attempt Item; }) -> () error WriteError; /// Iterates over the items in the store, using lexicographic ordering over the keys. /// /// The [`iterator`] is [pipelined][pipelining] to the server, such that the client can /// immediately send requests over the new connection. /// /// [pipelining]: https://fuchsia.dev/fuchsia-src/development/api/fidl?hl=en#request-pipelining flexible Iterate(resource struct { /// If present, requests to start the iteration at this item. starting_at string:<128, optional>; /// The [`Iterator`] server endpoint. The client creates both ends of the channel and /// retains the `client_end` locally to use for pulling iteration pages, while sending the /// `server_end` off to be fulfilled by the server. iterator server_end:Iterator; }) -> () error IterateConnectionError; }; /// An iterator for the key-value store. Note that this protocol makes no guarantee of atomicity - /// the values may change between pulls from the iterator. Unlike the `Store` protocol above, this /// protocol is not `@discoverable`: it is not independently published by the component that /// implements it, but rather must have one of its two protocol ends transmitted over an existing /// FIDL connection. /// /// As is often the case with iterators, the client indicates that they are done with an instance of /// the iterator by simply closing their end of the connection. /// /// Since the iterator is associated only with the Iterate method, it is declared as closed rather /// than open. This is because changes to how iteration works are more likely to require replacing /// the Iterate method completely (which is fine because that method is flexible) rather than /// evolving the Iterator protocol. closed protocol Iterator { /// Gets the next batch of keys. /// /// The client pulls keys rather than having the server proactively push them, to implement /// [flow control][flow-control] over the messages. /// /// [flow-control]: /// https://fuchsia.dev/fuchsia-src/development/api/fidl?hl=en#prefer_pull_to_push strict Get() -> (struct { /// A list of keys. If the iterator has reached the end of iteration, the list will be /// empty. The client is expected to then close the connection. entries vector<string:128>:10; }); };
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.additerator.Store" }, ], config: { write_items: { type: "vector", max_count: 16, element: { type: "string", max_size: 64, }, }, // A key to iterate from, after all items in `write_items` have been written. iterate_from: { type: "string", max_size: 64, }, }, }
服务器
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. { include: [ "syslog/client.shard.cml" ], program: { runner: "elf", binary: "bin/server_bin", }, capabilities: [ { protocol: "examples.keyvaluestore.additerator.Store" }, ], expose: [ { protocol: "examples.keyvaluestore.additerator.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.additerator.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::endpoints::create_proxy; use fidl_examples_keyvaluestore_additerator::{Item, IteratorMarker, StoreMarker}; use futures::join; #[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 !config.iterate_from.is_empty() { // This helper creates a channel, and returns two protocol ends: the `client_end` is already // conveniently bound to the correct FIDL protocol, `Iterator`, while the `server_end` is // unbound and ready to be sent over the wire. let (iterator, server_end) = create_proxy::<IteratorMarker>()?; // There is no need to wait for the iterator to connect before sending the first `Get()` // request - since we already hold the `client_end` of the connection, we can start queuing // requests on it immediately. let connect_to_iterator = store.iterate(Some(config.iterate_from.as_str()), server_end); let first_get = iterator.get(); // Wait until both the connection and the first request resolve - an error in either case // triggers an immediate resolution of the combined future. let (connection, first_page) = join!(connect_to_iterator, first_get); // Handle any connection error. If this has occurred, it is impossible for the first `Get()` // call to have resolved successfully, so check this error first. if let Err(err) = connection.context("Could not connect to Iterator")? { println!("Iterator Connection Error: {}", err.into_primitive()); } else { println!("Iterator Connection Success"); // Consecutively repeat the `Get()` request if the previous response was not empty. let mut entries = first_page.context("Could not get page from Iterator")?; while !&entries.is_empty() { for entry in entries.iter() { println!("Iterator Entry: {}", entry); } entries = iterator.get().await.context("Could not get page from Iterator")?; } } } // 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 fidl_examples_keyvaluestore_additerator::{ Item, IterateConnectionError, IteratorRequest, IteratorRequestStream, StoreRequest, StoreRequestStream, WriteError, }; use fuchsia_async as fasync; use std::collections::btree_map::Entry; use std::collections::BTreeMap; use std::ops::Bound::*; use std::sync::{Arc, Mutex}; 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 BTreeMap<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 `Iterate` method, which deals with validating that the requested start position /// exists, and then sets up the asynchronous side channel for the actual iteration to occur over. fn iterate( store: Arc<Mutex<BTreeMap<String, Vec<u8>>>>, starting_at: Option<String>, stream: IteratorRequestStream, ) -> Result<(), IterateConnectionError> { // Validate that the starting key, if supplied, actually exists. if let Some(start_key) = starting_at.clone() { if !store.lock().unwrap().contains_key(&start_key) { return Err(IterateConnectionError::UnknownStartAt); } } // Spawn a detached task. This allows the method call to return while the iteration continues in // a separate, unawaited task. fasync::Task::spawn(async move { // Serve the iteration requests. Note that access to the underlying store is behind a // contended `Mutex`, meaning that the iteration is not atomic: page contents could shift, // change, or disappear entirely between `Get()` requests. stream .map(|result| result.context("failed request")) .try_fold( match starting_at { Some(start_key) => Included(start_key), None => Unbounded, }, |mut lower_bound, request| async { match request { IteratorRequest::Get { responder } => { println!("Iterator page request received"); // The `page_size` should be kept in sync with the size constraint on // the iterator's response, as defined in the FIDL protocol. static PAGE_SIZE: usize = 10; // An iterator, beginning at `lower_bound` and tracking the pagination's // progress through iteration as each page is pulled by a client-sent // `Get()` request. let held_store = store.lock().unwrap(); let mut entries = held_store.range((lower_bound.clone(), Unbounded)); let mut current_page = vec![]; for _ in 0..PAGE_SIZE { match entries.next() { Some(entry) => { current_page.push(entry.0.clone()); } None => break, } } // Update the `lower_bound` - either inclusive of the next item in the // iteration, or exclusive of the last seen item if the iteration has // finished. This `lower_bound` will be passed to the next request // handler as its starting point. lower_bound = match entries.next() { Some(next) => Included(next.0.clone()), None => match current_page.last() { Some(tail) => Excluded(tail.clone()), None => lower_bound, }, }; // Send the page. At the end of this scope, the `held_store` lock gets // dropped, and therefore released. responder.send(¤t_page).context("error sending reply")?; println!("Iterator page sent"); } } Ok(lower_bound) }, ) .await .ok(); }) .detach(); Ok(()) } /// Creates a new instance of the server. Each server has its own bespoke, per-connection instance /// of the key-value store. async fn run_server(stream: StoreRequestStream) -> Result<(), Error> { // Create a new in-memory key-value store. The store will live for the lifetime of the // connection between the server and this particular client. // // Note that we now use an `Arc<Mutex<BTreeMap>>`, replacing the previous `RefCell<HashMap>`. // The `BTreeMap` is used because we want an ordered map, to better facilitate iteration. The // `Arc<Mutex<...>>` is used because there are now multiple async tasks accessing the: one main // task which handles communication over the protocol, and one additional task per iterator // protocol. `Arc<Mutex<...>>` is the simplest way to synchronize concurrent access between // these racing tasks. let store = &Arc::new(Mutex::new(BTreeMap::<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.clone().lock().unwrap(), attempt)) .context("error sending reply")?; println!("WriteItem response sent"); } StoreRequest::Iterate { starting_at, iterator, responder } => { println!("Iterate request received"); // The `iterate` handler does a quick check to see that the request is valid, // then spins up a separate worker task to serve the newly minted `Iterator` // protocol instance, allowing this call to return immediately and continue the // request stream with other work. responder .send(iterate(store.clone(), starting_at, iterator.into_stream()?)) .context("error sending reply")?; println!("Iterate response sent"); } // StoreRequest::_UnknownMethod { ordinal, .. } => { println!("Received an unknown method with ordinal {ordinal}"); } } Ok(()) }) .await } // A helper enum that allows us to treat a `Store` service instance as a value. enum IncomingService { Store(StoreRequestStream), } #[fuchsia::main] async fn main() -> Result<(), Error> { println!("Started"); // Add a discoverable instance of our `Store` protocol - this will allow the client to see the // server and connect to it. let mut fs = ServiceFs::new_local(); fs.dir("svc").add_fidl_service(IncomingService::Store); fs.take_and_serve_directory_handle()?; println!("Listening for incoming connections"); // The maximum number of concurrent clients that may be served by this process. const MAX_CONCURRENT: usize = 10; // Serve each connection simultaneously, up to the `MAX_CONCURRENT` limit. fs.for_each_concurrent(MAX_CONCURRENT, |IncomingService::Store(stream)| { run_server(stream).unwrap_or_else(|e| println!("{:?}", e)) }) .await; Ok(()) }
C++(自然)
客户端
// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.
服务器
// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.
C++(有线)
客户端
// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.
服务器
// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.
HLCPP
客户端
// TODO(https://fxbug.dev/42060656): HLCPP implementation.
服务器
// TODO(https://fxbug.dev/42060656): HLCPP implementation.
启用导出备份功能
扩展键值对存储区以支持导出备份的简单方法是
只需添加一个新方法,即可让地球停止,
存储,并将其作为 FIDL vector<Item>
发回。使用 Google Cloud 时有两个缺点
这种方法。其一是它承担了
服务器上的备份 - 客户端无需支付任何费用
对服务器而言开销非常高其次,它涉及大量的
复制:客户端几乎肯定会写入生成的备份
发送到某个后备数据存储区(如文件或数据库)。
让它解码这个(可能非常大)FIDL 对象,
在将其转发到任何会执行相关协议的协议时,
会造成很大的浪费
推理
更好的解决方案是使用 zircon 的虚拟内存 对象。您不必不断将字节复制回去并 分桶作业中,我们可以创建一个 VMO 来保存 备份数据,将其发送到服务器,然后将其转发回我们的 目标数据存储区,无需在两者之间反序列化。只要目标数据 存储的协议允许接受使用 VMO 传输的数据, 是完成此类高成本操作的首选方式。事实上 例如,Fuchsia 的文件系统就可以实现此确切模式。使用 这种方法会迫使客户在询问 运行代价高昂的操作,最大限度地减少这两者之间的工作不平衡 各方。
FIDL 值类型可以使用
FIDL 数据持久性二进制格式。我们将保留
在 VMO 中新增了 FIDL 类型 Exportable
。系统会对对象进行编码
并写入该存储空间(在本例中,即是一个 VMO,稍后可保存为
并在需要再次访问数据时从该文件中解码,
其编码、传输和解码的方式与以后在发生时
通过 IPC 使用 FIDL。
为了安全地执行此操作并遵循最小权限原则,
我们应该限制代表 VMO 的句柄可能具备的权限。
输入 handle permission,这是 FIDL 用来描述权限的一流方法
可用于特定标识名类型。在本例中,我们允许 empty
VMO
在 Export
请求中传递到服务器,以便查询大小、
调整大小和写入。当 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.