blob: 934f09e4325aada2b133ac5187638b5d1a160e8b [file] [log] [blame]
// 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.
use crate::event::{Event, Publisher};
use crate::message::base::MessengerType;
use crate::service;
use anyhow::{format_err, Error};
use fidl::endpoints::{DiscoverableProtocolMarker, ProtocolMarker, Proxy};
use futures::future::{BoxFuture, OptionFuture};
use fuchsia_async as fasync;
use fuchsia_component::client::{connect_to_protocol, connect_to_protocol_at_path};
use glob::glob;
use fuchsia_zircon as zx;
use std::future::Future;
pub type GenerateService =
Box<dyn Fn(&str, zx::Channel) -> BoxFuture<'static, Result<(), Error>> + Send + Sync>;
/// A wrapper around service operations, allowing redirection to a nested
/// environment.
pub struct ServiceContext {
generate_service: Option<GenerateService>,
delegate: Option<service::message::Delegate>,
}
impl ServiceContext {
pub(crate) fn new(
generate_service: Option<GenerateService>,
delegate: Option<service::message::Delegate>,
) -> Self {
Self { generate_service, delegate }
}
async fn make_publisher(&self) -> Option<Publisher> {
let maybe: OptionFuture<_> = self
.delegate
.as_ref()
.map(|delegate| Publisher::create(delegate, MessengerType::Unbound))
.into();
maybe.await
}
/// Connect to a service with the given ProtocolMarker.
///
/// If a GenerateService was specified at creation, the name of the service marker will be used
/// to generate a service.
pub(crate) async fn connect<P: DiscoverableProtocolMarker>(
&self,
) -> Result<ExternalServiceProxy<P::Proxy>, Error> {
let proxy = if let Some(generate_service) = &self.generate_service {
let (client, server) = zx::Channel::create()?;
((generate_service)(P::PROTOCOL_NAME, server)).await?;
P::Proxy::from_channel(fasync::Channel::from_channel(client)?)
} else {
connect_to_protocol::<P>()?
};
Ok(ExternalServiceProxy::new(proxy, self.make_publisher().await))
}
pub(crate) async fn connect_with_publisher<P: DiscoverableProtocolMarker>(
&self,
publisher: Publisher,
) -> Result<ExternalServiceProxy<P::Proxy>, Error> {
let proxy = if let Some(generate_service) = &self.generate_service {
let (client, server) = zx::Channel::create()?;
((generate_service)(P::PROTOCOL_NAME, server)).await?;
P::Proxy::from_channel(fasync::Channel::from_channel(client)?)
} else {
connect_to_protocol::<P>()?
};
Ok(ExternalServiceProxy::new(proxy, Some(publisher)))
}
/// Connect to a service with the given name and ProtocolMarker.
///
/// If a GenerateService was specified at creation, the given name will be used to generate a
/// service.
pub(crate) async fn connect_named<P: ProtocolMarker>(
&self,
service_name: &str,
) -> Result<ExternalServiceProxy<P::Proxy>, Error> {
if let Some(generate_service) = &self.generate_service {
let (client, server) = zx::Channel::create()?;
if (generate_service)(service_name, server).await.is_err() {
return Err(format_err!("Could not handl service {:?}", service_name));
}
Ok(ExternalServiceProxy::new(
P::Proxy::from_channel(fasync::Channel::from_channel(client)?),
self.make_publisher().await,
))
} else {
Err(format_err!("No service generator"))
}
}
/// Connect to a service at the given path and ProtocolMarker.
///
/// If a GenerateService was specified at creation, the name of the service marker will be used
/// to generate a service and the path will be ignored.
pub(crate) async fn connect_path<P: ProtocolMarker>(
&self,
path: &str,
) -> Result<ExternalServiceProxy<P::Proxy>, Error> {
let (proxy, server) = fidl::endpoints::create_proxy::<P>()?;
fdio::service_connect(path, server.into_channel())?;
Ok(ExternalServiceProxy::new(proxy, self.make_publisher().await))
}
/// Connect to a service by discovering a hardware device at the given glob-style pattern.
///
/// The first discovered path will be used to connected.
///
/// If a GenerateService was specified at creation, the name of the service marker will be used
/// to generate a service and the path will be ignored.
pub(crate) async fn connect_device_path<P: DiscoverableProtocolMarker>(
&self,
glob_pattern: &str,
) -> Result<ExternalServiceProxy<P::Proxy>, Error> {
if self.generate_service.is_some() {
// If a generate_service is already specified, just connect through there
return self.connect::<P>().await;
}
let found_path = glob(glob_pattern)?
.filter_map(|entry| entry.ok())
.next()
.ok_or_else(|| format_err!("failed to enumerate devices"))?;
let path_str =
found_path.to_str().ok_or_else(|| format_err!("failed to convert path to str"))?;
Ok(ExternalServiceProxy::new(
connect_to_protocol_at_path::<P>(path_str)?,
self.make_publisher().await,
))
}
pub(crate) async fn wrap_proxy<P: Proxy>(&self, proxy: P) -> ExternalServiceProxy<P> {
ExternalServiceProxy::new(proxy, self.make_publisher().await)
}
}
/// A wrapper around a proxy, used to track disconnections.
///
/// This wraps any type implementing `Proxy`. Whenever any call returns a
/// `ClientChannelClosed` error, this wrapper publishes a closed event for
/// the wrapped proxy.
#[derive(Clone, Debug)]
pub struct ExternalServiceProxy<P>
where
P: Proxy,
{
proxy: P,
publisher: Option<Publisher>,
}
impl<P> ExternalServiceProxy<P>
where
P: Proxy,
{
pub(crate) fn new(proxy: P, publisher: Option<Publisher>) -> Self {
Self { proxy, publisher }
}
fn inspect_result<T>(&self, result: &Result<T, fidl::Error>) {
if let Err(fidl::Error::ClientChannelClosed { .. }) = result {
if let Some(p) = self.publisher.as_ref() {
p.send_event(Event::Closed(P::Protocol::DEBUG_NAME));
}
}
}
/// Make a call to a synchronous API of the wrapped proxy.
pub(crate) fn call<T, F>(&self, func: F) -> Result<T, fidl::Error>
where
F: FnOnce(&P) -> Result<T, fidl::Error>,
{
let result = func(&self.proxy);
self.inspect_result(&result);
result
}
/// Nake a call to an asynchronous API of the wrapped proxy.
pub(crate) async fn call_async<T, F, Fut>(&self, func: F) -> Result<T, fidl::Error>
where
F: FnOnce(&P) -> Fut,
Fut: Future<Output = Result<T, fidl::Error>>,
{
let result = func(&self.proxy).await;
self.inspect_result(&result);
result
}
}
/// Helper macro to simplify calls to proxy objects
#[macro_export]
macro_rules! call {
($proxy:expr => $($call:tt)+) => {
$proxy.call(|proxy| proxy.$($call)+)
}
}
/// Helper macro to simplify async calls to proxy objects
#[macro_export]
macro_rules! call_async {
($proxy:expr => $($call:tt)+) => {
$proxy.call_async(|proxy| proxy.$($call)+)
}
}