Skip to main content

fdf_component/
incoming.rs

1// Copyright 2024 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
5use std::marker::PhantomData;
6
7use anyhow::{Context, Error, anyhow};
8use cm_types::{IterablePath, RelativePath};
9use fdf_sys::fdf_token_transfer;
10use fidl::endpoints::{ClientEnd, DiscoverableProtocolMarker, ServiceMarker, ServiceProxy};
11use fidl_fuchsia_io as fio;
12use fidl_fuchsia_io::Flags;
13use fidl_next_bind::Service;
14use fuchsia_component::client::{Connect, connect_to_service_instance_at_dir_svc};
15use fuchsia_component::directory::{AsRefDirectory, Directory, open_directory_async};
16use fuchsia_component::{DEFAULT_SERVICE_INSTANCE, SVC_DIR};
17use log::error;
18use namespace::{Entry, Namespace};
19use zx::Status;
20
21/// Implements access to the incoming namespace for a driver. It provides methods
22/// for accessing incoming protocols and services by either their marker or proxy
23/// types, and can be used as a [`Directory`] with the functions in
24/// [`fuchsia_component::client`].
25pub struct Incoming(Vec<Entry>);
26
27impl Incoming {
28    /// Connects to the protocol in the service instance's path in the incoming namespace. Logs and
29    /// returns a [`Status::CONNECTION_REFUSED`] if the service instance couldn't be opened.
30    pub fn connect_protocol<T: Connect>(&self) -> Result<T, Status> {
31        T::connect_at_dir_svc(&self).map_err(|e| {
32            error!(
33                "Failed to connect to discoverable protocol `{}`: {e}",
34                T::Protocol::PROTOCOL_NAME
35            );
36            Status::CONNECTION_REFUSED
37        })
38    }
39
40    /// Connects to the protocol in the service instance's path in the given directory, with
41    /// zx::Channel. Logs and returns a [`Status::CONNECTION_REFUSED`] if the service instance
42    /// couldn't be opened.
43    /// Connects to the protocol in the service instance's path in the incoming namespace, with
44    /// `libasync_fidl::AsyncChannel`. Logs and returns a [`Status::CONNECTION_REFUSED`] if the
45    /// service instance couldn't be opened.
46    pub fn connect_protocol_libasync_next<P: fidl_next::Discoverable, D>(
47        &self,
48    ) -> Result<fidl_next::ClientEnd<P, libasync_fidl::AsyncChannel<D>>, Status>
49    where
50        D: Default,
51    {
52        let path = format!("/svc/{}", P::PROTOCOL_NAME);
53        Self::connect_protocol_libasync_next_at(self, &path)
54    }
55
56    /// Connects to the protocol in the service instance's path in the given directory, with
57    /// `libasync_fidl::AsyncChannel`. Logs and returns a [`Status::CONNECTION_REFUSED`] if the
58    /// service instance couldn't be opened.
59    pub fn connect_protocol_libasync_next_at<P: fidl_next::Discoverable, D>(
60        dir: &impl AsRefDirectory,
61        path: &str,
62    ) -> Result<fidl_next::ClientEnd<P, libasync_fidl::AsyncChannel<D>>, Status>
63    where
64        D: Default,
65    {
66        let (client_end, server_end) = zx::Channel::create();
67        let client_end = fidl_next::ClientEnd::<P, zx::Channel>::from_untyped(client_end);
68        dir.as_ref_directory().open(path, fio::Flags::PROTOCOL_SERVICE, server_end).map_err(
69            |e| {
70                error!("Failed to connect to discoverable protocol `{}`: {e}", P::PROTOCOL_NAME);
71                Status::CONNECTION_REFUSED
72            },
73        )?;
74        Ok(libasync_fidl::AsyncChannel::<D>::client_from_zx_channel::<P>(client_end))
75    }
76
77    /// Connects to the protocol in the service instance's path in the incoming namespace, with
78    /// `zx::Channel`. Logs and returns a [`Status::CONNECTION_REFUSED`] if the service instance
79    /// couldn't be opened.
80    pub fn connect_protocol_next<P: fidl_next::Discoverable>(
81        &self,
82    ) -> Result<fidl_next::ClientEnd<P, zx::Channel>, Status> {
83        let path = format!("/svc/{}", P::PROTOCOL_NAME);
84        Self::connect_protocol_next_at(self, &path)
85    }
86
87    /// Connects to the protocol in the service instance's path in the given directory, with
88    /// `zx::Channel`. Logs and returns a [`Status::CONNECTION_REFUSED`] if the service instance
89    /// couldn't be opened.
90    pub fn connect_protocol_next_at<P: fidl_next::Discoverable>(
91        dir: &impl AsRefDirectory,
92        path: &str,
93    ) -> Result<fidl_next::ClientEnd<P, zx::Channel>, Status> {
94        let (client_end, server_end) = zx::Channel::create();
95        let client_end = fidl_next::ClientEnd::<P, zx::Channel>::from_untyped(client_end);
96        dir.as_ref_directory().open(path, fio::Flags::PROTOCOL_SERVICE, server_end).map_err(
97            |e| {
98                error!("Failed to connect to discoverable protocol `{}`: {e}", P::PROTOCOL_NAME);
99                Status::CONNECTION_REFUSED
100            },
101        )?;
102        Ok(client_end)
103    }
104
105    /// Connects to the protocol in the service instance's path in the given directory over driver
106    /// transport, with [`fdf_fidl::DriverChannel`], using `dispatcher`. Logs and returns a
107    /// [`Status::CONNECTION_REFUSED`] if the service instance couldn't be opened.
108    pub fn connect_protocol_driver_transport<P: fidl_next::Discoverable, D>(
109        &self,
110        dispatcher: D,
111    ) -> Result<fidl_next::ClientEnd<P, fdf_fidl::DriverChannel<D>>, zx::Status>
112    where
113        D: Clone,
114    {
115        let path = format!("/svc/{}", P::PROTOCOL_NAME);
116        Self::connect_protocol_driver_transport_at(self, &path, dispatcher)
117    }
118
119    /// Connects to the protocol in the service instance's path in the given directory over driver
120    /// transport, with [`fdf_fidl::DriverChannel`], using `dispatcher`. Logs and returns a
121    /// [`Status::CONNECTION_REFUSED`] if the service instance couldn't be opened.
122    pub fn connect_protocol_driver_transport_at<P: fidl_next::Discoverable, D>(
123        dir: &impl AsRefDirectory,
124        path: &str,
125        dispatcher: D,
126    ) -> Result<fidl_next::ClientEnd<P, fdf_fidl::DriverChannel<D>>, zx::Status>
127    where
128        D: Clone,
129    {
130        let (client_token, server_token) = zx::Channel::create();
131        let (client_end, server_end) = fdf_fidl::DriverChannel::create_with_dispatcher(dispatcher);
132
133        dir.as_ref_directory().open(path, fio::Flags::PROTOCOL_SERVICE, server_token).map_err(
134            |e| {
135                error!("Failed to connect to discoverable protocol `{}`: {e}", P::PROTOCOL_NAME);
136                zx::Status::CONNECTION_REFUSED
137            },
138        )?;
139        // SAFETY: client_token and server_end are valid by construction and
140        // `fdf_token_transfer` consumes both handles and does not interact with rust memory.
141        zx::Status::ok(unsafe {
142            fdf_sys::fdf_token_transfer(
143                client_token.into_raw(),
144                server_end.into_driver_handle().into_raw().get(),
145            )
146        })
147        .inspect_err(|e| {
148            error!("Failed to connect to discoverable protocol `{}`: {e}", P::PROTOCOL_NAME);
149        })?;
150
151        Ok(fidl_next::ClientEnd::<P, _>::from_untyped(client_end))
152    }
153
154    /// Creates a connector to the given service's default instance by its marker type. This can be
155    /// convenient when the compiler can't deduce the [`ServiceProxy`] type on its own.
156    ///
157    /// See [`ServiceConnector`] for more about what you can do with the connector.
158    ///
159    /// # Example
160    ///
161    /// ```ignore
162    /// let service = context.incoming.service_marker(fidl_fuchsia_hardware_i2c::ServiceMarker).connect()?;
163    /// let device = service.connect_to_device()?;
164    /// ```
165    pub fn service_marker<M: ServiceMarker>(&self, _marker: M) -> ServiceConnector<'_, M::Proxy> {
166        ServiceConnector { incoming: self, instance: DEFAULT_SERVICE_INSTANCE, _p: PhantomData }
167    }
168
169    /// Creates a connector to the given service's default instance by its proxy type. This can be
170    /// convenient when the compiler can deduce the [`ServiceProxy`] type on its own.
171    ///
172    /// See [`ServiceConnector`] for more about what you can do with the connector.
173    ///
174    /// # Example
175    ///
176    /// ```ignore
177    /// struct MyProxies {
178    ///     i2c_service: fidl_fuchsia_hardware_i2c::ServiceProxy,
179    /// }
180    /// let proxies = MyProxies {
181    ///     i2c_service: context.incoming.service().connect()?;
182    /// };
183    /// ```
184    pub fn service<P>(&self) -> ServiceConnector<'_, P> {
185        ServiceConnector { incoming: self, instance: DEFAULT_SERVICE_INSTANCE, _p: PhantomData }
186    }
187}
188
189impl From<Vec<cm_types::NamespaceEntry>> for Incoming {
190    fn from(other: Vec<cm_types::NamespaceEntry>) -> Self {
191        Self(other.into_iter().map(Into::into).collect())
192    }
193}
194
195/// A builder for connecting to an aggregated service instance in the driver's incoming namespace.
196/// By default, it will connect to the default instance, named `default`. You can override this
197/// by calling [`Self::instance`].
198pub struct ServiceConnector<'incoming, ServiceProxy> {
199    incoming: &'incoming Incoming,
200    instance: &'incoming str,
201    _p: PhantomData<ServiceProxy>,
202}
203
204impl<'a, S> ServiceConnector<'a, S> {
205    /// Overrides the instance name to connect to when [`Self::connect`] is called.
206    pub fn instance(self, instance: &'a str) -> Self {
207        let Self { incoming, _p, .. } = self;
208        Self { incoming, instance, _p }
209    }
210}
211
212impl<'a, S: ServiceProxy> ServiceConnector<'a, S>
213where
214    S::Service: ServiceMarker,
215{
216    /// Connects to the service instance's path in the incoming namespace. Logs and returns
217    /// a [`Status::CONNECTION_REFUSED`] if the service instance couldn't be opened.
218    pub fn connect(self) -> Result<S, Status> {
219        connect_to_service_instance_at_dir_svc::<S::Service>(self.incoming, self.instance).map_err(
220            |e| {
221                error!(
222                    "Failed to connect to aggregated service connector `{}`, instance `{}`: {e}",
223                    S::Service::SERVICE_NAME,
224                    self.instance
225                );
226                Status::CONNECTION_REFUSED
227            },
228        )
229    }
230}
231
232/// Used with [`ServiceHandlerAdapter`] as a connector to members of a service instance.
233pub struct ServiceMemberConnector(fio::DirectoryProxy);
234
235fn connect(
236    dir: &fio::DirectoryProxy,
237    member: &str,
238    server_end: zx::Channel,
239) -> Result<(), fidl::Error> {
240    #[cfg(fuchsia_api_level_at_least = "27")]
241    return dir.open(member, fio::Flags::PROTOCOL_SERVICE, &fio::Options::default(), server_end);
242    #[cfg(not(fuchsia_api_level_at_least = "27"))]
243    return dir.open3(member, fio::Flags::PROTOCOL_SERVICE, &fio::Options::default(), server_end);
244}
245
246impl fidl_next_protocol::ServiceConnector<zx::Channel> for ServiceMemberConnector {
247    type Error = fidl::Error;
248    fn connect_to_member(&self, member: &str, server_end: zx::Channel) -> Result<(), Self::Error> {
249        connect(&self.0, member, server_end)
250    }
251}
252
253impl fidl_next_protocol::ServiceConnector<fdf_fidl::DriverChannel> for ServiceMemberConnector {
254    type Error = Status;
255    fn connect_to_member(
256        &self,
257        member: &str,
258        server_end: fdf_fidl::DriverChannel,
259    ) -> Result<(), Self::Error> {
260        let (client_token, server_token) = zx::Channel::create();
261
262        // SAFETY: client_token and server_end are valid by construction and `fdf_token_transfer`
263        // consumes both handles and does not interact with rust memory.
264        Status::ok(unsafe {
265            fdf_token_transfer(
266                client_token.into_raw(),
267                server_end.into_driver_handle().into_raw().get(),
268            )
269        })?;
270
271        connect(&self.0, member, server_token).map_err(|err| {
272            error!("Failed to connect to service member {member}: {err:?}");
273            Status::CONNECTION_REFUSED
274        })
275    }
276}
277
278/// A type alias representing a service instance with members that can be connected to using the
279/// [`fidl_next`] bindings.
280pub type ServiceInstance<S> = fidl_next_bind::ServiceConnector<S, ServiceMemberConnector>;
281
282impl<'a, S: Service<ServiceMemberConnector>> ServiceConnector<'a, ServiceInstance<S>> {
283    /// Connects to the service instance's path in the incoming namespace with the new wire bindings.
284    /// Logs and returns a [`Status::CONNECTION_REFUSED`] if the service instance couldn't be opened.
285    pub fn connect_next(self) -> Result<ServiceInstance<S>, Status> {
286        let service_path = format!("{SVC_DIR}/{}/{}", S::SERVICE_NAME, self.instance);
287        let dir = open_directory_async(self.incoming, &service_path, fio::Rights::empty())
288            .map_err(|e| {
289                error!(
290                    "Failed to connect to aggregated service connector `{}`, instance `{}`: {e}",
291                    S::SERVICE_NAME,
292                    self.instance
293                );
294                Status::CONNECTION_REFUSED
295            })?;
296        Ok(fidl_next_bind::ServiceConnector::from_untyped(ServiceMemberConnector(dir)))
297    }
298}
299
300impl From<Namespace> for Incoming {
301    fn from(value: Namespace) -> Self {
302        Incoming(value.flatten())
303    }
304}
305
306impl From<ClientEnd<fio::DirectoryMarker>> for Incoming {
307    fn from(client: ClientEnd<fio::DirectoryMarker>) -> Self {
308        Incoming(vec![Entry {
309            path: cm_types::NamespacePath::new("/").unwrap(),
310            directory: client,
311        }])
312    }
313}
314
315/// Returns the remainder of a prefix match of `prefix` against `self` in terms of path segments.
316///
317/// For example:
318/// ```ignore
319/// match_prefix("pkg/data", "pkg") == Some("/data")
320/// match_prefix("pkg_data", "pkg") == None
321/// ```
322fn match_prefix(match_in: &impl IterablePath, prefix: &impl IterablePath) -> Option<RelativePath> {
323    let mut my_segments = match_in.iter_segments();
324    let mut prefix_segments = prefix.iter_segments();
325    for prefix in prefix_segments.by_ref() {
326        if prefix != my_segments.next()? {
327            return None;
328        }
329    }
330    if prefix_segments.next().is_some() {
331        // did not match all prefix segments
332        return None;
333    }
334    let segments = Vec::from_iter(my_segments);
335    Some(RelativePath::from(segments))
336}
337
338impl Directory for Incoming {
339    fn open(&self, path: &str, flags: Flags, server_end: zx::Channel) -> Result<(), Error> {
340        let path = path.strip_prefix("/").unwrap_or(path);
341        let path = RelativePath::new(path)?;
342
343        for entry in &self.0 {
344            if let Some(remain) = match_prefix(&path, &entry.path) {
345                return entry.directory.open(&format!("{}", remain), flags, server_end);
346            }
347        }
348        Err(Status::NOT_FOUND)
349            .with_context(|| anyhow!("Path {path} not found in incoming namespace"))
350    }
351}
352
353impl AsRefDirectory for Incoming {
354    fn as_ref_directory(&self) -> &dyn Directory {
355        self
356    }
357}
358
359#[cfg(test)]
360mod tests {
361    use super::*;
362    use fuchsia_async::Task;
363    use fuchsia_component::server::ServiceFs;
364    use futures::stream::StreamExt;
365
366    enum IncomingServices {
367        Device(fidl_fuchsia_hardware_i2c::DeviceRequestStream),
368        DefaultService(fidl_fuchsia_hardware_i2c::ServiceRequest),
369        OtherService(fidl_fuchsia_hardware_i2c::ServiceRequest),
370    }
371
372    impl IncomingServices {
373        async fn handle_device_stream(
374            stream: fidl_fuchsia_hardware_i2c::DeviceRequestStream,
375            name: &str,
376        ) {
377            stream
378                .for_each(|msg| async move {
379                    match msg.unwrap() {
380                        fidl_fuchsia_hardware_i2c::DeviceRequest::GetName { responder } => {
381                            responder.send(Ok(name)).unwrap();
382                        }
383                        _ => unimplemented!(),
384                    }
385                })
386                .await
387        }
388
389        async fn handle(self) {
390            use IncomingServices::*;
391            match self {
392                Device(stream) => Self::handle_device_stream(stream, "device").await,
393                DefaultService(fidl_fuchsia_hardware_i2c::ServiceRequest::Device(stream)) => {
394                    Self::handle_device_stream(stream, "default").await
395                }
396                OtherService(fidl_fuchsia_hardware_i2c::ServiceRequest::Device(stream)) => {
397                    Self::handle_device_stream(stream, "other").await
398                }
399            }
400        }
401    }
402
403    async fn make_incoming() -> Incoming {
404        let (client, server) = fidl::endpoints::create_endpoints();
405        let mut fs = ServiceFs::new();
406        fs.dir("svc")
407            .add_fidl_service(IncomingServices::Device)
408            .add_fidl_service_instance("default", IncomingServices::DefaultService)
409            .add_fidl_service_instance("other", IncomingServices::OtherService);
410        fs.serve_connection(server).expect("error serving handle");
411
412        Task::spawn(fs.for_each_concurrent(100, IncomingServices::handle)).detach_on_drop();
413        Incoming::from(client)
414    }
415
416    #[fuchsia::test]
417    async fn protocol_connect_present() -> anyhow::Result<()> {
418        let incoming = make_incoming().await;
419        // try a protocol that we did set up
420        incoming
421            .connect_protocol::<fidl_fuchsia_hardware_i2c::DeviceProxy>()?
422            .get_name()
423            .await?
424            .unwrap();
425        Ok(())
426    }
427
428    #[fuchsia::test]
429    async fn protocol_connect_not_present() -> anyhow::Result<()> {
430        let incoming = make_incoming().await;
431        // try one we didn't
432        incoming
433            .connect_protocol::<fidl_fuchsia_hwinfo::DeviceProxy>()?
434            .get_info()
435            .await
436            .unwrap_err();
437        Ok(())
438    }
439
440    #[fuchsia::test]
441    async fn service_connect_default_instance() -> anyhow::Result<()> {
442        let incoming = make_incoming().await;
443        // try the default service instance that we did set up
444        assert_eq!(
445            "default",
446            &incoming
447                .service_marker(fidl_fuchsia_hardware_i2c::ServiceMarker)
448                .connect()?
449                .connect_to_device()?
450                .get_name()
451                .await?
452                .unwrap()
453        );
454        assert_eq!(
455            "default",
456            &incoming
457                .service::<fidl_fuchsia_hardware_i2c::ServiceProxy>()
458                .connect()?
459                .connect_to_device()?
460                .get_name()
461                .await?
462                .unwrap()
463        );
464        Ok(())
465    }
466
467    #[fuchsia::test]
468    async fn service_connect_other_instance() -> anyhow::Result<()> {
469        let incoming = make_incoming().await;
470        // try the other service instance that we did set up
471        assert_eq!(
472            "other",
473            &incoming
474                .service_marker(fidl_fuchsia_hardware_i2c::ServiceMarker)
475                .instance("other")
476                .connect()?
477                .connect_to_device()?
478                .get_name()
479                .await?
480                .unwrap()
481        );
482        assert_eq!(
483            "other",
484            &incoming
485                .service::<fidl_fuchsia_hardware_i2c::ServiceProxy>()
486                .instance("other")
487                .connect()?
488                .connect_to_device()?
489                .get_name()
490                .await?
491                .unwrap()
492        );
493        Ok(())
494    }
495
496    #[fuchsia::test]
497    async fn service_connect_invalid_instance() -> anyhow::Result<()> {
498        let incoming = make_incoming().await;
499        // try the invalid service instance that we did not set up
500        incoming
501            .service_marker(fidl_fuchsia_hardware_i2c::ServiceMarker)
502            .instance("invalid")
503            .connect()?
504            .connect_to_device()?
505            .get_name()
506            .await
507            .unwrap_err();
508        incoming
509            .service::<fidl_fuchsia_hardware_i2c::ServiceProxy>()
510            .instance("invalid")
511            .connect()?
512            .connect_to_device()?
513            .get_name()
514            .await
515            .unwrap_err();
516        Ok(())
517    }
518}