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)
    }
}