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 service_marker<M: ServiceMarker>(&self, _marker: M) -> ServiceConnector<'_, M::Proxy> {
86 ServiceConnector { incoming: self, instance: DEFAULT_SERVICE_INSTANCE, _p: PhantomData }
87 }
88
89 pub fn service<P>(&self) -> ServiceConnector<'_, P> {
105 ServiceConnector { incoming: self, instance: DEFAULT_SERVICE_INSTANCE, _p: PhantomData }
106 }
107}
108
109impl From<Vec<cm_types::NamespaceEntry>> for Incoming {
110 fn from(other: Vec<cm_types::NamespaceEntry>) -> Self {
111 Self(other.into_iter().map(Into::into).collect())
112 }
113}
114
115pub struct ServiceConnector<'incoming, ServiceProxy> {
119 incoming: &'incoming Incoming,
120 instance: &'incoming str,
121 _p: PhantomData<ServiceProxy>,
122}
123
124impl<'a, S> ServiceConnector<'a, S> {
125 pub fn instance(self, instance: &'a str) -> Self {
127 let Self { incoming, _p, .. } = self;
128 Self { incoming, instance, _p }
129 }
130}
131
132impl<'a, S: ServiceProxy> ServiceConnector<'a, S>
133where
134 S::Service: ServiceMarker,
135{
136 pub fn connect(self) -> Result<S, Status> {
139 connect_to_service_instance_at_dir_svc::<S::Service>(self.incoming, self.instance).map_err(
140 |e| {
141 error!(
142 "Failed to connect to aggregated service connector `{}`, instance `{}`: {e}",
143 S::Service::SERVICE_NAME,
144 self.instance
145 );
146 Status::CONNECTION_REFUSED
147 },
148 )
149 }
150}
151
152pub struct ServiceMemberConnector(fio::DirectoryProxy);
154
155fn connect(
156 dir: &fio::DirectoryProxy,
157 member: &str,
158 server_end: zx::Channel,
159) -> Result<(), fidl::Error> {
160 #[cfg(fuchsia_api_level_at_least = "27")]
161 return dir.open(member, fio::Flags::PROTOCOL_SERVICE, &fio::Options::default(), server_end);
162 #[cfg(not(fuchsia_api_level_at_least = "27"))]
163 return dir.open3(member, fio::Flags::PROTOCOL_SERVICE, &fio::Options::default(), server_end);
164}
165
166impl fidl_next_protocol::ServiceConnector<zx::Channel> for ServiceMemberConnector {
167 type Error = fidl::Error;
168 fn connect_to_member(&self, member: &str, server_end: zx::Channel) -> Result<(), Self::Error> {
169 connect(&self.0, member, server_end)
170 }
171}
172
173impl fidl_next_protocol::ServiceConnector<fdf_fidl::DriverChannel> for ServiceMemberConnector {
174 type Error = Status;
175 fn connect_to_member(
176 &self,
177 member: &str,
178 server_end: fdf_fidl::DriverChannel,
179 ) -> Result<(), Self::Error> {
180 let (client_token, server_token) = zx::Channel::create();
181
182 Status::ok(unsafe {
185 fdf_token_transfer(
186 client_token.into_raw(),
187 server_end.into_driver_handle().into_raw().get(),
188 )
189 })?;
190
191 connect(&self.0, member, server_token).map_err(|err| {
192 error!("Failed to connect to service member {member}: {err:?}");
193 Status::CONNECTION_REFUSED
194 })
195 }
196}
197
198pub type ServiceInstance<S> = fidl_next_bind::ServiceConnector<S, ServiceMemberConnector>;
201
202impl<'a, S: Service<ServiceMemberConnector>> ServiceConnector<'a, ServiceInstance<S>> {
203 pub fn connect_next(self) -> Result<ServiceInstance<S>, Status> {
206 let service_path = format!("{SVC_DIR}/{}/{}", S::SERVICE_NAME, self.instance);
207 let dir = open_directory_async(self.incoming, &service_path, fio::Rights::empty())
208 .map_err(|e| {
209 error!(
210 "Failed to connect to aggregated service connector `{}`, instance `{}`: {e}",
211 S::SERVICE_NAME,
212 self.instance
213 );
214 Status::CONNECTION_REFUSED
215 })?;
216 Ok(fidl_next_bind::ServiceConnector::from_untyped(ServiceMemberConnector(dir)))
217 }
218}
219
220impl From<Namespace> for Incoming {
221 fn from(value: Namespace) -> Self {
222 Incoming(value.flatten())
223 }
224}
225
226impl From<ClientEnd<fio::DirectoryMarker>> for Incoming {
227 fn from(client: ClientEnd<fio::DirectoryMarker>) -> Self {
228 Incoming(vec![Entry {
229 path: cm_types::NamespacePath::new("/").unwrap(),
230 directory: client,
231 }])
232 }
233}
234
235fn match_prefix(match_in: &impl IterablePath, prefix: &impl IterablePath) -> Option<RelativePath> {
243 let mut my_segments = match_in.iter_segments();
244 let mut prefix_segments = prefix.iter_segments();
245 for prefix in prefix_segments.by_ref() {
246 if prefix != my_segments.next()? {
247 return None;
248 }
249 }
250 if prefix_segments.next().is_some() {
251 return None;
253 }
254 let segments = Vec::from_iter(my_segments);
255 Some(RelativePath::from(segments))
256}
257
258impl Directory for Incoming {
259 fn open(&self, path: &str, flags: Flags, server_end: zx::Channel) -> Result<(), Error> {
260 let path = path.strip_prefix("/").unwrap_or(path);
261 let path = RelativePath::new(path)?;
262
263 for entry in &self.0 {
264 if let Some(remain) = match_prefix(&path, &entry.path) {
265 return entry.directory.open(&format!("{}", remain), flags, server_end);
266 }
267 }
268 Err(Status::NOT_FOUND)
269 .with_context(|| anyhow!("Path {path} not found in incoming namespace"))
270 }
271}
272
273impl AsRefDirectory for Incoming {
274 fn as_ref_directory(&self) -> &dyn Directory {
275 self
276 }
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282 use fuchsia_async::Task;
283 use fuchsia_component::server::ServiceFs;
284 use futures::stream::StreamExt;
285
286 enum IncomingServices {
287 Device(fidl_fuchsia_hardware_i2c::DeviceRequestStream),
288 DefaultService(fidl_fuchsia_hardware_i2c::ServiceRequest),
289 OtherService(fidl_fuchsia_hardware_i2c::ServiceRequest),
290 }
291
292 impl IncomingServices {
293 async fn handle_device_stream(
294 stream: fidl_fuchsia_hardware_i2c::DeviceRequestStream,
295 name: &str,
296 ) {
297 stream
298 .for_each(|msg| async move {
299 match msg.unwrap() {
300 fidl_fuchsia_hardware_i2c::DeviceRequest::GetName { responder } => {
301 responder.send(Ok(name)).unwrap();
302 }
303 _ => unimplemented!(),
304 }
305 })
306 .await
307 }
308
309 async fn handle(self) {
310 use IncomingServices::*;
311 match self {
312 Device(stream) => Self::handle_device_stream(stream, "device").await,
313 DefaultService(fidl_fuchsia_hardware_i2c::ServiceRequest::Device(stream)) => {
314 Self::handle_device_stream(stream, "default").await
315 }
316 OtherService(fidl_fuchsia_hardware_i2c::ServiceRequest::Device(stream)) => {
317 Self::handle_device_stream(stream, "other").await
318 }
319 }
320 }
321 }
322
323 async fn make_incoming() -> Incoming {
324 let (client, server) = fidl::endpoints::create_endpoints();
325 let mut fs = ServiceFs::new();
326 fs.dir("svc")
327 .add_fidl_service(IncomingServices::Device)
328 .add_fidl_service_instance("default", IncomingServices::DefaultService)
329 .add_fidl_service_instance("other", IncomingServices::OtherService);
330 fs.serve_connection(server).expect("error serving handle");
331
332 Task::spawn(fs.for_each_concurrent(100, IncomingServices::handle)).detach_on_drop();
333 Incoming::from(client)
334 }
335
336 #[fuchsia::test]
337 async fn protocol_connect_present() -> anyhow::Result<()> {
338 let incoming = make_incoming().await;
339 incoming
341 .connect_protocol::<fidl_fuchsia_hardware_i2c::DeviceProxy>()?
342 .get_name()
343 .await?
344 .unwrap();
345 Ok(())
346 }
347
348 #[fuchsia::test]
349 async fn protocol_connect_not_present() -> anyhow::Result<()> {
350 let incoming = make_incoming().await;
351 incoming
353 .connect_protocol::<fidl_fuchsia_hwinfo::DeviceProxy>()?
354 .get_info()
355 .await
356 .unwrap_err();
357 Ok(())
358 }
359
360 #[fuchsia::test]
361 async fn service_connect_default_instance() -> anyhow::Result<()> {
362 let incoming = make_incoming().await;
363 assert_eq!(
365 "default",
366 &incoming
367 .service_marker(fidl_fuchsia_hardware_i2c::ServiceMarker)
368 .connect()?
369 .connect_to_device()?
370 .get_name()
371 .await?
372 .unwrap()
373 );
374 assert_eq!(
375 "default",
376 &incoming
377 .service::<fidl_fuchsia_hardware_i2c::ServiceProxy>()
378 .connect()?
379 .connect_to_device()?
380 .get_name()
381 .await?
382 .unwrap()
383 );
384 Ok(())
385 }
386
387 #[fuchsia::test]
388 async fn service_connect_other_instance() -> anyhow::Result<()> {
389 let incoming = make_incoming().await;
390 assert_eq!(
392 "other",
393 &incoming
394 .service_marker(fidl_fuchsia_hardware_i2c::ServiceMarker)
395 .instance("other")
396 .connect()?
397 .connect_to_device()?
398 .get_name()
399 .await?
400 .unwrap()
401 );
402 assert_eq!(
403 "other",
404 &incoming
405 .service::<fidl_fuchsia_hardware_i2c::ServiceProxy>()
406 .instance("other")
407 .connect()?
408 .connect_to_device()?
409 .get_name()
410 .await?
411 .unwrap()
412 );
413 Ok(())
414 }
415
416 #[fuchsia::test]
417 async fn service_connect_invalid_instance() -> anyhow::Result<()> {
418 let incoming = make_incoming().await;
419 incoming
421 .service_marker(fidl_fuchsia_hardware_i2c::ServiceMarker)
422 .instance("invalid")
423 .connect()?
424 .connect_to_device()?
425 .get_name()
426 .await
427 .unwrap_err();
428 incoming
429 .service::<fidl_fuchsia_hardware_i2c::ServiceProxy>()
430 .instance("invalid")
431 .connect()?
432 .connect_to_device()?
433 .get_name()
434 .await
435 .unwrap_err();
436 Ok(())
437 }
438}