blob: 892311cf5a6da8b1c060a6b9434c2714351b0c05 [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::clock;
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 fuchsia_async as fasync;
use fuchsia_component::client::{connect_to_protocol, connect_to_protocol_at_path};
use fuchsia_zircon as zx;
use futures::future::{BoxFuture, OptionFuture};
use glob::glob;
use std::borrow::Cow;
use std::fmt::Debug;
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>()?
};
let publisher = self.make_publisher().await;
let external_proxy = ExternalServiceProxy::new(proxy, publisher.clone());
if let Some(p) = publisher {
let timestamp = clock::inspect_format_now();
p.send_event(Event::ExternalServiceEvent(ExternalServiceEvent::Created(
P::PROTOCOL_NAME,
timestamp.into(),
)));
}
Ok(external_proxy)
}
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>()?
};
let external_proxy = ExternalServiceProxy::new(proxy, Some(publisher.clone()));
publisher.send_event(Event::ExternalServiceEvent(ExternalServiceEvent::Created(
P::PROTOCOL_NAME,
clock::inspect_format_now().into(),
)));
Ok(external_proxy)
}
/// 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"))?;
let publisher = self.make_publisher().await;
let external_proxy = ExternalServiceProxy::new(
connect_to_protocol_at_path::<P>(path_str)?,
publisher.clone(),
);
if let Some(p) = publisher {
p.send_event(Event::ExternalServiceEvent(ExternalServiceEvent::Created(
P::DEBUG_NAME,
clock::inspect_format_now().into(),
)));
}
Ok(external_proxy)
}
}
/// Definition of events related to external api calls outside of the setting service.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ExternalServiceEvent {
/// Event sent when an external service proxy is created. Contains
/// the protocol name and the timestamp at which the connection
/// was created.
Created(
&'static str, // Protocol
Cow<'static, str>, // Timestamp
),
/// Event sent when a call is made on an external service proxy.
/// Contains the protocol name, the stringified request, and the
/// request timestamp.
ApiCall(
&'static str, // Protocol
Cow<'static, str>, // Request
Cow<'static, str>, // Request timestamp
),
/// Event sent when a non-error response is received on an external
/// service proxy. Contains the protocol name, the response, the
/// associated stringified request, and the request/response timestamps.
ApiResponse(
&'static str, // Protocol
Cow<'static, str>, // Response
Cow<'static, str>, // Request
Cow<'static, str>, // Request Timestamp
Cow<'static, str>, // Response timestamp
),
/// Event sent when an error is received on an external service proxy.
/// Contains the protocol name, the error message, the associated
/// stringified request, and the request/response timestamps.
ApiError(
&'static str, // Protocol
Cow<'static, str>, // Error msg
Cow<'static, str>, // Request
Cow<'static, str>, // Request timestamp
Cow<'static, str>, // Error timestamp
),
/// Event sent when an external service proxy is closed. Contains the
/// protocol name, the associated stringified request, and the
/// request/response timestamps.
Closed(
&'static str, // Protocol
Cow<'static, str>, // Request
Cow<'static, str>, // Request timestamp
Cow<'static, str>, // Response timestamp
),
}
/// 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 }
}
/// Handle the `result` of the event sent via the call or call_async methods
/// and send the corresponding information to be logged to inspect.
fn inspect_result<T>(
&self,
result: &Result<T, fidl::Error>,
arg_str: String,
req_timestamp: String,
resp_timestamp: String,
) where
T: Debug,
{
if let Some(p) = self.publisher.as_ref() {
if let Err(fidl::Error::ClientChannelClosed { .. }) = result {
p.send_event(Event::ExternalServiceEvent(ExternalServiceEvent::Closed(
P::Protocol::DEBUG_NAME,
arg_str.into(),
req_timestamp.into(),
resp_timestamp.into(),
)));
} else if let Err(e) = result {
p.send_event(Event::ExternalServiceEvent(ExternalServiceEvent::ApiError(
P::Protocol::DEBUG_NAME,
format!("{e:?}").into(),
arg_str.into(),
req_timestamp.into(),
resp_timestamp.into(),
)));
} else {
let payload = result.as_ref().expect("Could not extract external api call result");
p.send_event(Event::ExternalServiceEvent(ExternalServiceEvent::ApiResponse(
P::Protocol::DEBUG_NAME,
format!("{payload:?}").into(),
arg_str.into(),
req_timestamp.into(),
resp_timestamp.into(),
)));
}
}
}
/// Make a call to a synchronous API of the wrapped proxy. This should not be called directly,
/// only from the call macro.
pub(crate) fn call<T, F>(&self, func: F, arg_str: String) -> Result<T, fidl::Error>
where
F: FnOnce(&P) -> Result<T, fidl::Error>,
T: std::fmt::Debug,
{
let req_timestamp = clock::inspect_format_now();
if let Some(p) = self.publisher.as_ref() {
p.send_event(Event::ExternalServiceEvent(ExternalServiceEvent::ApiCall(
P::Protocol::DEBUG_NAME,
arg_str.clone().into(),
req_timestamp.clone().into(),
)));
}
let result = func(&self.proxy);
self.inspect_result(&result, arg_str, req_timestamp, clock::inspect_format_now());
result
}
/// Make a call to an asynchronous API of the wrapped proxy. This should not be called directly,
/// only from the call_async macro.
pub(crate) async fn call_async<T, F, Fut>(
&self,
func: F,
arg_str: String,
) -> Result<T, fidl::Error>
where
F: FnOnce(&P) -> Fut,
Fut: Future<Output = Result<T, fidl::Error>>,
T: std::fmt::Debug,
{
let req_timestamp = clock::inspect_format_now();
if let Some(p) = self.publisher.as_ref() {
p.send_event(Event::ExternalServiceEvent(ExternalServiceEvent::ApiCall(
P::Protocol::DEBUG_NAME,
arg_str.clone().into(),
req_timestamp.clone().into(),
)));
}
let result = func(&self.proxy).await;
self.inspect_result(&result, arg_str, req_timestamp, clock::inspect_format_now());
result
}
}
/// Helper macro to simplify calls to proxy objects.
#[macro_export]
macro_rules! call {
($proxy:expr => $($call:tt)+) => {
{
let arg_string = $crate::format_call!($($call)+);
$proxy.call(|p| p.$($call)+, arg_string)
}
};
}
/// Helper macro to simplify async calls to proxy objects.
#[macro_export]
macro_rules! call_async {
($proxy:expr => $($call:tt)+) => {
{
let arg_string = $crate::format_call!($($call)+);
$proxy.call_async(|p| p.$($call)+, arg_string)
}
};
}
/// Helper macro to parse and stringify the arguments to `call` and `call_async`.
#[macro_export]
macro_rules! format_call {
($fn_name:ident($($arg:expr),*)) => {
{
let mut s = format!("{}(", stringify!($fn_name));
$(
s += &format!("{:?}", $arg);
)*
s += ")";
s
}
};
}