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