1use 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
21pub struct Incoming(Vec<Entry>);
26
27impl Incoming {
28 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 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 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 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 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 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 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 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 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 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
195pub 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 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 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
232pub 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 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
278pub type ServiceInstance<S> = fidl_next_bind::ServiceConnector<S, ServiceMemberConnector>;
281
282impl<'a, S: Service<ServiceMemberConnector>> ServiceConnector<'a, ServiceInstance<S>> {
283 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
315fn 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 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 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 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 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 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 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}