fdf_component/
incoming.rs1use std::marker::PhantomData;
6
7use anyhow::{anyhow, Context, Error};
8use cm_types::{IterablePath, RelativePath};
9use fidl::endpoints::{DiscoverableProtocolMarker, ServiceMarker, ServiceProxy};
10use fidl_fuchsia_io::Flags;
11use fuchsia_component::client::{connect_to_service_instance_at_dir_svc, Connect};
12use fuchsia_component::directory::{AsRefDirectory, Directory};
13use fuchsia_component::DEFAULT_SERVICE_INSTANCE;
14use log::error;
15use namespace::{Entry, Namespace};
16use zx::Status;
17
18pub struct Incoming(Vec<Entry>);
23
24impl Incoming {
25 pub fn connect_protocol<T: Connect>(&self) -> Result<T, Status> {
28 T::connect_at_dir_svc(&self).map_err(|e| {
29 error!(
30 "Failed to connect to discoverable protocol `{}`: {e}",
31 T::Protocol::PROTOCOL_NAME
32 );
33 Status::CONNECTION_REFUSED
34 })
35 }
36
37 pub fn service_marker<M: ServiceMarker>(&self, _marker: M) -> ServiceConnector<'_, M::Proxy> {
49 ServiceConnector { incoming: self, instance: DEFAULT_SERVICE_INSTANCE, _p: PhantomData }
50 }
51
52 pub fn service<P>(&self) -> ServiceConnector<'_, P> {
68 ServiceConnector { incoming: self, instance: DEFAULT_SERVICE_INSTANCE, _p: PhantomData }
69 }
70}
71
72pub struct ServiceConnector<'incoming, ServiceProxy> {
76 incoming: &'incoming Incoming,
77 instance: &'incoming str,
78 _p: PhantomData<ServiceProxy>,
79}
80
81impl<'a, S: ServiceProxy> ServiceConnector<'a, S>
82where
83 S::Service: ServiceMarker,
84{
85 pub fn instance(self, instance: &'a str) -> Self {
87 let Self { incoming, _p, .. } = self;
88 Self { incoming, instance, _p }
89 }
90
91 pub fn connect(self) -> Result<S, Status> {
94 connect_to_service_instance_at_dir_svc::<S::Service>(self.incoming, self.instance).map_err(
95 |e| {
96 error!(
97 "Failed to connect to aggregated service connector `{}`, instance `{}`: {e}",
98 S::Service::SERVICE_NAME,
99 self.instance
100 );
101 Status::CONNECTION_REFUSED
102 },
103 )
104 }
105}
106
107impl From<Namespace> for Incoming {
108 fn from(value: Namespace) -> Self {
109 Incoming(value.flatten())
110 }
111}
112
113fn match_prefix(match_in: &impl IterablePath, prefix: &impl IterablePath) -> Option<RelativePath> {
121 let mut my_segments = match_in.iter_segments();
122 let mut prefix_segments = prefix.iter_segments();
123 while let Some(prefix) = prefix_segments.next() {
124 if prefix != my_segments.next()? {
125 return None;
126 }
127 }
128 if prefix_segments.next().is_some() {
129 return None;
131 }
132 let segments = Vec::from_iter(my_segments);
133 Some(RelativePath::from(segments))
134}
135
136impl Directory for Incoming {
137 fn open(&self, path: &str, flags: Flags, server_end: zx::Channel) -> Result<(), Error> {
138 let path = path.strip_prefix("/").unwrap_or(path);
139 let path = RelativePath::new(path)?;
140
141 for entry in &self.0 {
142 if let Some(remain) = match_prefix(&path, &entry.path) {
143 return entry.directory.open(&format!("/{}", remain), flags, server_end);
144 }
145 }
146 Err(Status::NOT_FOUND)
147 .with_context(|| anyhow!("Path {path} not found in incoming namespace"))
148 }
149}
150
151impl AsRefDirectory for Incoming {
152 fn as_ref_directory(&self) -> &dyn Directory {
153 self
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160 use cm_types::NamespacePath;
161 use fuchsia_async::Task;
162 use fuchsia_component::server::ServiceFs;
163 use futures::stream::StreamExt;
164
165 enum IncomingServices {
166 I2cDevice(fidl_fuchsia_hardware_i2c::DeviceRequestStream),
167 I2cDefaultService(fidl_fuchsia_hardware_i2c::ServiceRequest),
168 I2cOtherService(fidl_fuchsia_hardware_i2c::ServiceRequest),
169 }
170
171 impl IncomingServices {
172 async fn handle_device_stream(
173 stream: fidl_fuchsia_hardware_i2c::DeviceRequestStream,
174 name: &str,
175 ) {
176 stream
177 .for_each(|msg| async move {
178 match msg.unwrap() {
179 fidl_fuchsia_hardware_i2c::DeviceRequest::GetName { responder } => {
180 responder.send(Ok(name)).unwrap();
181 }
182 _ => unimplemented!(),
183 }
184 })
185 .await
186 }
187
188 async fn handle(self) {
189 use fidl_fuchsia_hardware_i2c::ServiceRequest::*;
190 use IncomingServices::*;
191 match self {
192 I2cDevice(stream) => Self::handle_device_stream(stream, "device").await,
193 I2cDefaultService(Device(stream)) => {
194 Self::handle_device_stream(stream, "default").await
195 }
196 I2cOtherService(Device(stream)) => {
197 Self::handle_device_stream(stream, "other").await
198 }
199 }
200 }
201 }
202
203 async fn make_incoming() -> Incoming {
204 let (client, server) = fidl::endpoints::create_endpoints();
205 let mut fs = ServiceFs::new();
206 fs.dir("svc")
207 .add_fidl_service(IncomingServices::I2cDevice)
208 .add_fidl_service_instance("default", IncomingServices::I2cDefaultService)
209 .add_fidl_service_instance("other", IncomingServices::I2cOtherService);
210 fs.serve_connection(server).expect("error serving handle");
211
212 Task::spawn(fs.for_each_concurrent(100, IncomingServices::handle)).detach_on_drop();
213
214 Incoming(vec![Entry { path: NamespacePath::new("/").unwrap(), directory: client }])
215 }
216
217 #[fuchsia::test]
218 async fn protocol_connect_present() -> anyhow::Result<()> {
219 let incoming = make_incoming().await;
220 incoming
222 .connect_protocol::<fidl_fuchsia_hardware_i2c::DeviceProxy>()?
223 .get_name()
224 .await?
225 .unwrap();
226 Ok(())
227 }
228
229 #[fuchsia::test]
230 async fn protocol_connect_not_present() -> anyhow::Result<()> {
231 let incoming = make_incoming().await;
232 incoming
234 .connect_protocol::<fidl_fuchsia_hwinfo::DeviceProxy>()?
235 .get_info()
236 .await
237 .unwrap_err();
238 Ok(())
239 }
240
241 #[fuchsia::test]
242 async fn service_connect_default_instance() -> anyhow::Result<()> {
243 let incoming = make_incoming().await;
244 assert_eq!(
246 "default",
247 &incoming
248 .service_marker(fidl_fuchsia_hardware_i2c::ServiceMarker)
249 .connect()?
250 .connect_to_device()?
251 .get_name()
252 .await?
253 .unwrap()
254 );
255 assert_eq!(
256 "default",
257 &incoming
258 .service::<fidl_fuchsia_hardware_i2c::ServiceProxy>()
259 .connect()?
260 .connect_to_device()?
261 .get_name()
262 .await?
263 .unwrap()
264 );
265 Ok(())
266 }
267
268 #[fuchsia::test]
269 async fn service_connect_other_instance() -> anyhow::Result<()> {
270 let incoming = make_incoming().await;
271 assert_eq!(
273 "other",
274 &incoming
275 .service_marker(fidl_fuchsia_hardware_i2c::ServiceMarker)
276 .instance("other")
277 .connect()?
278 .connect_to_device()?
279 .get_name()
280 .await?
281 .unwrap()
282 );
283 assert_eq!(
284 "other",
285 &incoming
286 .service::<fidl_fuchsia_hardware_i2c::ServiceProxy>()
287 .instance("other")
288 .connect()?
289 .connect_to_device()?
290 .get_name()
291 .await?
292 .unwrap()
293 );
294 Ok(())
295 }
296
297 #[fuchsia::test]
298 async fn service_connect_invalid_instance() -> anyhow::Result<()> {
299 let incoming = make_incoming().await;
300 incoming
302 .service_marker(fidl_fuchsia_hardware_i2c::ServiceMarker)
303 .instance("invalid")
304 .connect()?
305 .connect_to_device()?
306 .get_name()
307 .await
308 .unwrap_err();
309 incoming
310 .service::<fidl_fuchsia_hardware_i2c::ServiceProxy>()
311 .instance("invalid")
312 .connect()?
313 .connect_to_device()?
314 .get_name()
315 .await
316 .unwrap_err();
317 Ok(())
318 }
319}