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