use crate::event::{Event, Publisher};
use crate::message::base::MessengerType;
use crate::{clock, 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 futures::future::{LocalBoxFuture, 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) -> LocalBoxFuture<'static, Result<(), Error>>>;
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
}
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)
}
pub(crate) async fn connect_device_path<P: DiscoverableProtocolMarker>(
&self,
glob_pattern: &str,
) -> Result<ExternalServiceProxy<P::Proxy>, Error> {
if self.generate_service.is_some() {
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)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ExternalServiceEvent {
Created(
&'static str, Cow<'static, str>, ),
ApiCall(
&'static str, Cow<'static, str>, Cow<'static, str>, ),
ApiResponse(
&'static str, Cow<'static, str>, Cow<'static, str>, Cow<'static, str>, Cow<'static, str>, ),
ApiError(
&'static str, Cow<'static, str>, Cow<'static, str>, Cow<'static, str>, Cow<'static, str>, ),
Closed(
&'static str, Cow<'static, str>, Cow<'static, str>, Cow<'static, str>, ),
}
#[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>,
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(),
)));
}
}
}
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
}
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
}
}
#[macro_export]
macro_rules! call {
($proxy:expr => $($call:tt)+) => {
{
let arg_string = $crate::format_call!($($call)+);
$proxy.call(|p| p.$($call)+, arg_string)
}
};
}
#[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)
}
};
}
#[macro_export]
macro_rules! format_call {
($fn_name:ident($($arg:expr),*)) => {
{
let mut s = format!("{}(", stringify!($fn_name));
$(
s += &format!("{:?}", $arg);
)*
s += ")";
s
}
};
}