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