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_next<P: fidl_next::Discoverable, D>(
44 &self,
45 ) -> Result<fidl_next::ClientEnd<P, libasync_fidl::AsyncChannel<D>>, Status>
46 where
47 D: Default,
48 {
49 let path = format!("/svc/{}", P::PROTOCOL_NAME);
50 Self::connect_protocol_next_at(self, &path)
51 }
52
53 pub fn connect_protocol_next_at<P: fidl_next::Discoverable, D>(
57 dir: &impl AsRefDirectory,
58 path: &str,
59 ) -> Result<fidl_next::ClientEnd<P, libasync_fidl::AsyncChannel<D>>, Status>
60 where
61 D: Default,
62 {
63 let (client_end, server_end) = zx::Channel::create();
64 let client_end = fidl_next::ClientEnd::<P, zx::Channel>::from_untyped(client_end);
65 dir.as_ref_directory().open(path, fio::Flags::PROTOCOL_SERVICE, server_end).map_err(
66 |e| {
67 error!("Failed to connect to discoverable protocol `{}`: {e}", P::PROTOCOL_NAME);
68 Status::CONNECTION_REFUSED
69 },
70 )?;
71 Ok(libasync_fidl::AsyncChannel::<D>::client_from_zx_channel::<P>(client_end))
72 }
73
74 pub fn connect_protocol_driver_transport<P: fidl_next::Discoverable, D>(
78 &self,
79 dispatcher: D,
80 ) -> Result<fidl_next::ClientEnd<P, fdf_fidl::DriverChannel<D>>, zx::Status>
81 where
82 D: Clone,
83 {
84 let path = format!("/svc/{}", P::PROTOCOL_NAME);
85 Self::connect_protocol_driver_transport_at(self, &path, dispatcher)
86 }
87
88 pub fn connect_protocol_driver_transport_at<P: fidl_next::Discoverable, D>(
92 dir: &impl AsRefDirectory,
93 path: &str,
94 dispatcher: D,
95 ) -> Result<fidl_next::ClientEnd<P, fdf_fidl::DriverChannel<D>>, zx::Status>
96 where
97 D: Clone,
98 {
99 let (client_token, server_token) = zx::Channel::create();
100 let (client_end, server_end) = fdf_fidl::DriverChannel::create_with_dispatcher(dispatcher);
101
102 dir.as_ref_directory().open(path, fio::Flags::PROTOCOL_SERVICE, server_token).map_err(
103 |e| {
104 error!("Failed to connect to discoverable protocol `{}`: {e}", P::PROTOCOL_NAME);
105 zx::Status::CONNECTION_REFUSED
106 },
107 )?;
108 zx::Status::ok(unsafe {
111 fdf_sys::fdf_token_transfer(
112 client_token.into_raw(),
113 server_end.into_driver_handle().into_raw().get(),
114 )
115 })
116 .inspect_err(|e| {
117 error!("Failed to connect to discoverable protocol `{}`: {e}", P::PROTOCOL_NAME);
118 })?;
119
120 Ok(fidl_next::ClientEnd::<P, _>::from_untyped(client_end))
121 }
122
123 pub fn service_marker<M: ServiceMarker>(&self, _marker: M) -> ServiceConnector<'_, M::Proxy> {
135 ServiceConnector { incoming: self, instance: DEFAULT_SERVICE_INSTANCE, _p: PhantomData }
136 }
137
138 pub fn service<P>(&self) -> ServiceConnector<'_, P> {
154 ServiceConnector { incoming: self, instance: DEFAULT_SERVICE_INSTANCE, _p: PhantomData }
155 }
156}
157
158impl From<Vec<cm_types::NamespaceEntry>> for Incoming {
159 fn from(other: Vec<cm_types::NamespaceEntry>) -> Self {
160 Self(other.into_iter().map(Into::into).collect())
161 }
162}
163
164pub struct ServiceConnector<'incoming, ServiceProxy> {
168 incoming: &'incoming Incoming,
169 instance: &'incoming str,
170 _p: PhantomData<ServiceProxy>,
171}
172
173impl<'a, S> ServiceConnector<'a, S> {
174 pub fn instance(self, instance: &'a str) -> Self {
176 let Self { incoming, _p, .. } = self;
177 Self { incoming, instance, _p }
178 }
179}
180
181impl<'a, S: ServiceProxy> ServiceConnector<'a, S>
182where
183 S::Service: ServiceMarker,
184{
185 pub fn connect(self) -> Result<S, Status> {
188 connect_to_service_instance_at_dir_svc::<S::Service>(self.incoming, self.instance).map_err(
189 |e| {
190 error!(
191 "Failed to connect to aggregated service connector `{}`, instance `{}`: {e}",
192 S::Service::SERVICE_NAME,
193 self.instance
194 );
195 Status::CONNECTION_REFUSED
196 },
197 )
198 }
199}
200
201pub struct ServiceMemberConnector(fio::DirectoryProxy);
203
204fn connect(
205 dir: &fio::DirectoryProxy,
206 member: &str,
207 server_end: zx::Channel,
208) -> Result<(), fidl::Error> {
209 #[cfg(fuchsia_api_level_at_least = "27")]
210 return dir.open(member, fio::Flags::PROTOCOL_SERVICE, &fio::Options::default(), server_end);
211 #[cfg(not(fuchsia_api_level_at_least = "27"))]
212 return dir.open3(member, fio::Flags::PROTOCOL_SERVICE, &fio::Options::default(), server_end);
213}
214
215impl fidl_next_protocol::ServiceConnector<zx::Channel> for ServiceMemberConnector {
216 type Error = fidl::Error;
217 fn connect_to_member(&self, member: &str, server_end: zx::Channel) -> Result<(), Self::Error> {
218 connect(&self.0, member, server_end)
219 }
220}
221
222impl fidl_next_protocol::ServiceConnector<fdf_fidl::DriverChannel> for ServiceMemberConnector {
223 type Error = Status;
224 fn connect_to_member(
225 &self,
226 member: &str,
227 server_end: fdf_fidl::DriverChannel,
228 ) -> Result<(), Self::Error> {
229 let (client_token, server_token) = zx::Channel::create();
230
231 Status::ok(unsafe {
234 fdf_token_transfer(
235 client_token.into_raw(),
236 server_end.into_driver_handle().into_raw().get(),
237 )
238 })?;
239
240 connect(&self.0, member, server_token).map_err(|err| {
241 error!("Failed to connect to service member {member}: {err:?}");
242 Status::CONNECTION_REFUSED
243 })
244 }
245}
246
247pub type ServiceInstance<S> = fidl_next_bind::ServiceConnector<S, ServiceMemberConnector>;
250
251impl<'a, S: Service<ServiceMemberConnector>> ServiceConnector<'a, ServiceInstance<S>> {
252 pub fn connect_next(self) -> Result<ServiceInstance<S>, Status> {
255 let service_path = format!("{SVC_DIR}/{}/{}", S::SERVICE_NAME, self.instance);
256 let dir = open_directory_async(self.incoming, &service_path, fio::Rights::empty())
257 .map_err(|e| {
258 error!(
259 "Failed to connect to aggregated service connector `{}`, instance `{}`: {e}",
260 S::SERVICE_NAME,
261 self.instance
262 );
263 Status::CONNECTION_REFUSED
264 })?;
265 Ok(fidl_next_bind::ServiceConnector::from_untyped(ServiceMemberConnector(dir)))
266 }
267}
268
269impl From<Namespace> for Incoming {
270 fn from(value: Namespace) -> Self {
271 Incoming(value.flatten())
272 }
273}
274
275impl From<ClientEnd<fio::DirectoryMarker>> for Incoming {
276 fn from(client: ClientEnd<fio::DirectoryMarker>) -> Self {
277 Incoming(vec![Entry {
278 path: cm_types::NamespacePath::new("/").unwrap(),
279 directory: client,
280 }])
281 }
282}
283
284fn match_prefix(match_in: &impl IterablePath, prefix: &impl IterablePath) -> Option<RelativePath> {
292 let mut my_segments = match_in.iter_segments();
293 let mut prefix_segments = prefix.iter_segments();
294 for prefix in prefix_segments.by_ref() {
295 if prefix != my_segments.next()? {
296 return None;
297 }
298 }
299 if prefix_segments.next().is_some() {
300 return None;
302 }
303 let segments = Vec::from_iter(my_segments);
304 Some(RelativePath::from(segments))
305}
306
307impl Directory for Incoming {
308 fn open(&self, path: &str, flags: Flags, server_end: zx::Channel) -> Result<(), Error> {
309 let path = path.strip_prefix("/").unwrap_or(path);
310 let path = RelativePath::new(path)?;
311
312 for entry in &self.0 {
313 if let Some(remain) = match_prefix(&path, &entry.path) {
314 return entry.directory.open(&format!("{}", remain), flags, server_end);
315 }
316 }
317 Err(Status::NOT_FOUND)
318 .with_context(|| anyhow!("Path {path} not found in incoming namespace"))
319 }
320}
321
322impl AsRefDirectory for Incoming {
323 fn as_ref_directory(&self) -> &dyn Directory {
324 self
325 }
326}
327
328#[cfg(test)]
329mod tests {
330 use super::*;
331 use fuchsia_async::Task;
332 use fuchsia_component::server::ServiceFs;
333 use futures::stream::StreamExt;
334
335 enum IncomingServices {
336 Device(fidl_fuchsia_hardware_i2c::DeviceRequestStream),
337 DefaultService(fidl_fuchsia_hardware_i2c::ServiceRequest),
338 OtherService(fidl_fuchsia_hardware_i2c::ServiceRequest),
339 }
340
341 impl IncomingServices {
342 async fn handle_device_stream(
343 stream: fidl_fuchsia_hardware_i2c::DeviceRequestStream,
344 name: &str,
345 ) {
346 stream
347 .for_each(|msg| async move {
348 match msg.unwrap() {
349 fidl_fuchsia_hardware_i2c::DeviceRequest::GetName { responder } => {
350 responder.send(Ok(name)).unwrap();
351 }
352 _ => unimplemented!(),
353 }
354 })
355 .await
356 }
357
358 async fn handle(self) {
359 use IncomingServices::*;
360 match self {
361 Device(stream) => Self::handle_device_stream(stream, "device").await,
362 DefaultService(fidl_fuchsia_hardware_i2c::ServiceRequest::Device(stream)) => {
363 Self::handle_device_stream(stream, "default").await
364 }
365 OtherService(fidl_fuchsia_hardware_i2c::ServiceRequest::Device(stream)) => {
366 Self::handle_device_stream(stream, "other").await
367 }
368 }
369 }
370 }
371
372 async fn make_incoming() -> Incoming {
373 let (client, server) = fidl::endpoints::create_endpoints();
374 let mut fs = ServiceFs::new();
375 fs.dir("svc")
376 .add_fidl_service(IncomingServices::Device)
377 .add_fidl_service_instance("default", IncomingServices::DefaultService)
378 .add_fidl_service_instance("other", IncomingServices::OtherService);
379 fs.serve_connection(server).expect("error serving handle");
380
381 Task::spawn(fs.for_each_concurrent(100, IncomingServices::handle)).detach_on_drop();
382 Incoming::from(client)
383 }
384
385 #[fuchsia::test]
386 async fn protocol_connect_present() -> anyhow::Result<()> {
387 let incoming = make_incoming().await;
388 incoming
390 .connect_protocol::<fidl_fuchsia_hardware_i2c::DeviceProxy>()?
391 .get_name()
392 .await?
393 .unwrap();
394 Ok(())
395 }
396
397 #[fuchsia::test]
398 async fn protocol_connect_not_present() -> anyhow::Result<()> {
399 let incoming = make_incoming().await;
400 incoming
402 .connect_protocol::<fidl_fuchsia_hwinfo::DeviceProxy>()?
403 .get_info()
404 .await
405 .unwrap_err();
406 Ok(())
407 }
408
409 #[fuchsia::test]
410 async fn service_connect_default_instance() -> anyhow::Result<()> {
411 let incoming = make_incoming().await;
412 assert_eq!(
414 "default",
415 &incoming
416 .service_marker(fidl_fuchsia_hardware_i2c::ServiceMarker)
417 .connect()?
418 .connect_to_device()?
419 .get_name()
420 .await?
421 .unwrap()
422 );
423 assert_eq!(
424 "default",
425 &incoming
426 .service::<fidl_fuchsia_hardware_i2c::ServiceProxy>()
427 .connect()?
428 .connect_to_device()?
429 .get_name()
430 .await?
431 .unwrap()
432 );
433 Ok(())
434 }
435
436 #[fuchsia::test]
437 async fn service_connect_other_instance() -> anyhow::Result<()> {
438 let incoming = make_incoming().await;
439 assert_eq!(
441 "other",
442 &incoming
443 .service_marker(fidl_fuchsia_hardware_i2c::ServiceMarker)
444 .instance("other")
445 .connect()?
446 .connect_to_device()?
447 .get_name()
448 .await?
449 .unwrap()
450 );
451 assert_eq!(
452 "other",
453 &incoming
454 .service::<fidl_fuchsia_hardware_i2c::ServiceProxy>()
455 .instance("other")
456 .connect()?
457 .connect_to_device()?
458 .get_name()
459 .await?
460 .unwrap()
461 );
462 Ok(())
463 }
464
465 #[fuchsia::test]
466 async fn service_connect_invalid_instance() -> anyhow::Result<()> {
467 let incoming = make_incoming().await;
468 incoming
470 .service_marker(fidl_fuchsia_hardware_i2c::ServiceMarker)
471 .instance("invalid")
472 .connect()?
473 .connect_to_device()?
474 .get_name()
475 .await
476 .unwrap_err();
477 incoming
478 .service::<fidl_fuchsia_hardware_i2c::ServiceProxy>()
479 .instance("invalid")
480 .connect()?
481 .connect_to_device()?
482 .get_name()
483 .await
484 .unwrap_err();
485 Ok(())
486 }
487}