Skip to main content

vfs/
service.rs

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