1use crate::clock;
6use crate::inspect::event::ExternalEventPublisher;
7use anyhow::{format_err, Error};
8use fidl::endpoints::{DiscoverableProtocolMarker, ProtocolMarker, Proxy};
9use fuchsia_async as fasync;
10use fuchsia_component::client::{connect_to_protocol, connect_to_protocol_at_path};
11use futures::future::LocalBoxFuture;
12use glob::glob;
13use std::borrow::Cow;
14use std::fmt::Debug;
15use std::future::Future;
16
17pub type GenerateService =
18 Box<dyn Fn(&str, zx::Channel) -> LocalBoxFuture<'static, Result<(), Error>>>;
19
20pub struct ServiceContext {
23 generate_service: Option<GenerateService>,
24}
25
26impl ServiceContext {
27 pub fn new(generate_service: Option<GenerateService>) -> Self {
28 Self { generate_service }
29 }
30
31 pub async fn connect<P: DiscoverableProtocolMarker, Publisher, F>(
32 &self,
33 gen_publisher_fn: impl Fn() -> F,
34 ) -> Result<ExternalServiceProxy<P::Proxy, Publisher>, Error>
35 where
36 Publisher: EventPublisher + Clone,
37 F: Future<Output = Option<Publisher>>,
38 {
39 let proxy = if let Some(generate_service) = &self.generate_service {
40 let (client, server) = zx::Channel::create();
41 ((generate_service)(P::PROTOCOL_NAME, server)).await?;
42 P::Proxy::from_channel(fasync::Channel::from_channel(client))
43 } else {
44 connect_to_protocol::<P>()?
45 };
46
47 let publisher = gen_publisher_fn().await;
48
49 let external_proxy = ExternalServiceProxy::new(proxy, publisher.clone());
50 if let Some(publisher) = publisher {
51 publisher.send_event(ExternalServiceEvent::Created(
52 P::PROTOCOL_NAME,
53 clock::inspect_format_now().into(),
54 ));
55 }
56
57 Ok(external_proxy)
58 }
59
60 pub async fn connect_with_publisher<P: DiscoverableProtocolMarker, Publisher>(
61 &self,
62 publisher: Publisher,
63 ) -> Result<ExternalServiceProxy<P::Proxy, Publisher>, Error>
64 where
65 Publisher: EventPublisher + Clone,
66 {
67 let proxy = if let Some(generate_service) = &self.generate_service {
68 let (client, server) = zx::Channel::create();
69 ((generate_service)(P::PROTOCOL_NAME, server)).await?;
70 P::Proxy::from_channel(fasync::Channel::from_channel(client))
71 } else {
72 connect_to_protocol::<P>()?
73 };
74
75 let external_proxy = ExternalServiceProxy::new(proxy, Some(publisher.clone()));
76 publisher.send_event(ExternalServiceEvent::Created(
77 P::PROTOCOL_NAME,
78 clock::inspect_format_now().into(),
79 ));
80
81 Ok(external_proxy)
82 }
83
84 pub async fn connect_device_path<P: DiscoverableProtocolMarker, Publisher>(
91 &self,
92 glob_pattern: &str,
93 publisher: Publisher,
94 ) -> Result<ExternalServiceProxy<P::Proxy, Publisher>, Error>
95 where
96 Publisher: EventPublisher + Clone,
97 {
98 if self.generate_service.is_some() {
99 return self.connect_with_publisher::<P, Publisher>(publisher).await;
101 }
102
103 let found_path = glob(glob_pattern)?
104 .filter_map(|entry| entry.ok())
105 .next()
106 .ok_or_else(|| format_err!("failed to enumerate devices"))?;
107
108 let path_str =
109 found_path.to_str().ok_or_else(|| format_err!("failed to convert path to str"))?;
110
111 let external_proxy = ExternalServiceProxy::new(
112 connect_to_protocol_at_path::<P>(path_str)?,
113 Some(publisher.clone()),
114 );
115 publisher.send_event(ExternalServiceEvent::Created(
116 P::DEBUG_NAME,
117 clock::inspect_format_now().into(),
118 ));
119
120 Ok(external_proxy)
121 }
122}
123
124#[derive(Clone, Debug, Eq, PartialEq)]
126pub enum ExternalServiceEvent {
127 Created(
131 &'static str, Cow<'static, str>, ),
134
135 ApiCall(
139 &'static str, Cow<'static, str>, Cow<'static, str>, ),
143
144 ApiResponse(
148 &'static str, Cow<'static, str>, Cow<'static, str>, Cow<'static, str>, Cow<'static, str>, ),
154
155 ApiError(
159 &'static str, Cow<'static, str>, Cow<'static, str>, Cow<'static, str>, Cow<'static, str>, ),
165
166 Closed(
170 &'static str, Cow<'static, str>, Cow<'static, str>, Cow<'static, str>, ),
175}
176
177pub trait EventPublisher {
178 fn send_event(&self, event: ExternalServiceEvent);
179}
180
181impl EventPublisher for ExternalEventPublisher {
182 fn send_event(&self, event: ExternalServiceEvent) {
183 let _ = self.publish(event);
184 }
185}
186
187#[derive(Clone, Debug)]
193pub struct ExternalServiceProxy<P, Publisher>
194where
195 P: Proxy,
196{
197 proxy: P,
198 publisher: Option<Publisher>,
199}
200
201impl<P, Publisher> ExternalServiceProxy<P, Publisher>
202where
203 P: Proxy,
204 Publisher: EventPublisher,
205{
206 pub fn new(proxy: P, publisher: Option<Publisher>) -> Self {
207 Self { proxy, publisher }
208 }
209
210 pub fn inspect_result<T>(
213 &self,
214 result: &Result<T, fidl::Error>,
215 arg_str: String,
216 req_timestamp: String,
217 resp_timestamp: String,
218 ) where
219 T: Debug,
220 {
221 if let Some(p) = self.publisher.as_ref() {
222 if let Err(fidl::Error::ClientChannelClosed { .. }) = result {
223 p.send_event(ExternalServiceEvent::Closed(
224 P::Protocol::DEBUG_NAME,
225 arg_str.into(),
226 req_timestamp.into(),
227 resp_timestamp.into(),
228 ));
229 } else if let Err(e) = result {
230 p.send_event(ExternalServiceEvent::ApiError(
231 P::Protocol::DEBUG_NAME,
232 format!("{e:?}").into(),
233 arg_str.into(),
234 req_timestamp.into(),
235 resp_timestamp.into(),
236 ));
237 } else {
238 let payload = result.as_ref().expect("Could not extract external api call result");
239 p.send_event(ExternalServiceEvent::ApiResponse(
240 P::Protocol::DEBUG_NAME,
241 format!("{payload:?}").into(),
242 arg_str.into(),
243 req_timestamp.into(),
244 resp_timestamp.into(),
245 ));
246 }
247 }
248 }
249
250 pub fn call<T, F>(&self, func: F, arg_str: String) -> Result<T, fidl::Error>
253 where
254 F: FnOnce(&P) -> Result<T, fidl::Error>,
255 T: std::fmt::Debug,
256 {
257 let req_timestamp = clock::inspect_format_now();
258 if let Some(p) = self.publisher.as_ref() {
259 p.send_event(ExternalServiceEvent::ApiCall(
260 P::Protocol::DEBUG_NAME,
261 arg_str.clone().into(),
262 req_timestamp.clone().into(),
263 ));
264 }
265 let result = func(&self.proxy);
266 self.inspect_result(&result, arg_str, req_timestamp, clock::inspect_format_now());
267 result
268 }
269
270 pub async fn call_async<T, F, Fut>(&self, func: F, arg_str: String) -> Result<T, fidl::Error>
273 where
274 F: FnOnce(&P) -> Fut,
275 Fut: Future<Output = Result<T, fidl::Error>>,
276 T: std::fmt::Debug,
277 {
278 let req_timestamp = clock::inspect_format_now();
279 if let Some(p) = self.publisher.as_ref() {
280 p.send_event(ExternalServiceEvent::ApiCall(
281 P::Protocol::DEBUG_NAME,
282 arg_str.clone().into(),
283 req_timestamp.clone().into(),
284 ));
285 }
286 let result = func(&self.proxy).await;
287 self.inspect_result(&result, arg_str, req_timestamp, clock::inspect_format_now());
288 result
289 }
290}
291
292#[macro_export]
294macro_rules! call {
295 ($proxy:expr => $($call:tt)+) => {
296 {
297 let arg_string = $crate::format_call!($($call)+);
298 $proxy.call(|p| p.$($call)+, arg_string)
299 }
300 };
301}
302
303#[macro_export]
305macro_rules! call_async {
306 ($proxy:expr => $($call:tt)+) => {
307 {
308 let arg_string = $crate::format_call!($($call)+);
309 $proxy.call_async(|p| p.$($call)+, arg_string)
310 }
311 };
312}
313
314#[macro_export]
316macro_rules! format_call {
317 ($fn_name:ident($($arg:expr),*)) => {
318 {
319 let mut s = format!("{}(", stringify!($fn_name));
320 $(
321 s += &format!("{:?}", $arg);
322 )*
323
324 s += ")";
325 s
326 }
327 };
328}