1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
// Copyright 2019 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.
//! Implementations of a service endpoint.
#[cfg(test)]
mod tests;
use crate::directory::entry::{DirectoryEntry, EntryInfo, GetEntryInfo, OpenRequest};
use crate::execution_scope::ExecutionScope;
use crate::node::Node;
use crate::object_request::{ObjectRequestRef, ObjectRequestSend};
use crate::{immutable_attributes, ProtocolsExt};
use fidl::endpoints::RequestStream;
use fidl_fuchsia_io as fio;
use fuchsia_async::Channel;
use futures::future::Future;
use std::sync::Arc;
use zx_status::Status;
/// Objects that behave like services should implement this trait.
pub trait ServiceLike: Node {
/// Used to establish a new connection.
fn connect(
&self,
scope: ExecutionScope,
options: ServiceOptions,
object_request: ObjectRequestRef<'_>,
) -> Result<(), Status>;
}
pub struct ServiceOptions;
/// Constructs a node in your file system that will host a service that implements a statically
/// specified FIDL protocol. `ServerRequestStream` specifies the type of the server side of this
/// protocol.
///
/// `create_server` is a callback that is invoked when a new connection to the file system node is
/// established. The connection is reinterpreted as a `ServerRequestStream` FIDL connection and
/// passed to `create_server`. A task produces by the `create_server` callback is execution in the
/// same [`ExecutionScope`] as the one hosting current connection.
///
/// Prefer to use this method, if the type of your FIDL protocol is statically known and you want
/// to use the connection execution scope to serve the protocol requests. See [`endpoint`] for a
/// lower level version that gives you more flexibility.
pub fn host<ServerRequestStream, CreateServer, Task>(create_server: CreateServer) -> Arc<Service>
where
ServerRequestStream: RequestStream,
CreateServer: Fn(ServerRequestStream) -> Task + Send + Sync + 'static,
Task: Future<Output = ()> + Send + 'static,
{
endpoint(move |scope, channel| {
let requests = RequestStream::from_channel(channel);
let task = create_server(requests);
// There is no way to report executor failures, and if it is failing it must be shutting
// down.
let _ = scope.spawn(task);
})
}
/// Constructs a node in your file system that will host a service.
///
/// This is a lower level version of [`host`], which you should prefer if it matches your use case.
/// Unlike [`host`], `endpoint` uses a callback that will just consume the server side of the
/// channel when it is connected to the service node. It is up to the implementer of the `open`
/// callback to decide how to interpret the channel (allowing for non-static protocol selection)
/// and/or where the processing of the messages received over the channel will occur (but the
/// [`ExecutionScope`] connected to the connection is provided every time).
pub fn endpoint<Open>(open: Open) -> Arc<Service>
where
Open: Fn(ExecutionScope, Channel) + Send + Sync + 'static,
{
Arc::new(Service { open: Box::new(open) })
}
/// Represents a node in the file system that hosts a service. Opening a connection to this node
/// will switch to FIDL protocol that is different from the file system protocols, described in
/// fuchsia.io. See there for additional details.
///
/// Use [`host`] or [`endpoint`] to construct nodes of this type.
pub struct Service {
open: Box<dyn Fn(ExecutionScope, Channel) + Send + Sync>,
}
impl ServiceLike for Service {
fn connect(
&self,
scope: ExecutionScope,
_options: ServiceOptions,
object_request: ObjectRequestRef<'_>,
) -> Result<(), Status> {
if object_request.what_to_send() == ObjectRequestSend::OnOpen {
if let Ok(channel) = object_request
.take()
.into_channel_after_sending_on_open(fio::NodeInfoDeprecated::Service(fio::Service))
.map(Channel::from_channel)
{
(self.open)(scope, channel);
}
} else {
let channel = Channel::from_channel(object_request.take().into_channel());
(self.open)(scope, channel);
}
Ok(())
}
}
impl GetEntryInfo for Service {
fn entry_info(&self) -> EntryInfo {
EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Service)
}
}
impl DirectoryEntry for Service {
fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status> {
request.open_service(self)
}
}
impl Node for Service {
async fn get_attributes(
&self,
requested_attributes: fio::NodeAttributesQuery,
) -> Result<fio::NodeAttributes2, Status> {
Ok(immutable_attributes!(
requested_attributes,
Immutable {
protocols: fio::NodeProtocolKinds::CONNECTOR,
abilities: fio::Operations::GET_ATTRIBUTES | fio::Operations::CONNECT,
}
))
}
}
/// Helper to open a service or node as required.
pub fn serve(
service: Arc<impl ServiceLike>,
scope: ExecutionScope,
protocols: &impl ProtocolsExt,
object_request: ObjectRequestRef<'_>,
) -> Result<(), Status> {
if protocols.is_node() {
let options = protocols.to_node_options(service.entry_info().type_())?;
service.open_as_node(scope, options, object_request)
} else {
service.connect(scope, protocols.to_service_options()?, object_request)
}
}