Skip to main content

fuchsia_driver_dev/
lib.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use anyhow::{Context, Result, anyhow, format_err};
6use fidl_fuchsia_driver_development as fdd;
7use fidl_fuchsia_driver_framework as fdf;
8
9#[derive(Debug)]
10pub struct Device(pub fdd::NodeInfo);
11
12impl Device {
13    /// Gets the full moniker name of the device.
14    pub fn get_moniker(&self) -> Result<&str> {
15        let moniker = self.0.moniker.as_ref();
16        Ok(moniker.ok_or_else(|| format_err!("Missing moniker"))?)
17    }
18
19    /// Gets the full identifying path name of the device.
20    pub fn get_full_name(&self) -> Result<&str> {
21        self.get_moniker()
22    }
23
24    /// Gets the last ordinal of the device's moniker.
25    ///
26    /// For a `moniker` value of "this.is.a.moniker.foo.bar", "bar" will be returned.
27    pub fn extract_name(&self) -> Result<&str> {
28        let moniker = self.get_moniker()?;
29        let (_, name) = moniker.rsplit_once('.').unwrap_or(("", &moniker));
30        Ok(name)
31    }
32}
33
34impl std::convert::From<fdd::NodeInfo> for Device {
35    fn from(device_info: fdd::NodeInfo) -> Device {
36        Device(device_info)
37    }
38}
39
40/// Combines pagination results into a single vector.
41pub async fn get_device_info(
42    service: &fdd::ManagerProxy,
43    device_filter: &[String],
44    exact_match: bool,
45) -> Result<Vec<fdd::NodeInfo>> {
46    let (iterator, iterator_server) =
47        fidl::endpoints::create_proxy::<fdd::NodeInfoIteratorMarker>();
48
49    service
50        .get_node_info(device_filter, iterator_server, exact_match)
51        .context("FIDL call to get device info failed")?;
52
53    let mut info_result = Vec::new();
54
55    'outer: loop {
56        // To minimize round trips we request several results in one go.
57        let device_info_futures = vec![
58            iterator.get_next(),
59            iterator.get_next(),
60            iterator.get_next(),
61            iterator.get_next(),
62            iterator.get_next(),
63            iterator.get_next(),
64            iterator.get_next(),
65            iterator.get_next(),
66            iterator.get_next(),
67            iterator.get_next(),
68            iterator.get_next(),
69            iterator.get_next(),
70        ];
71        let device_info_result = futures::future::join_all(device_info_futures).await;
72        for result in device_info_result {
73            let mut device_info = result.context("FIDL call to get device info failed")?;
74            if device_info.len() == 0 {
75                break 'outer;
76            }
77            info_result.append(&mut device_info)
78        }
79    }
80    Ok(info_result)
81}
82
83/// Combines pagination results into a single vector.
84pub async fn get_driver_info(
85    service: &fdd::ManagerProxy,
86    driver_filter: &[String],
87) -> Result<Vec<fdf::DriverInfo>> {
88    let (iterator, iterator_server) =
89        fidl::endpoints::create_proxy::<fdd::DriverInfoIteratorMarker>();
90
91    service
92        .get_driver_info(driver_filter, iterator_server)
93        .context("FIDL call to get driver info failed")?;
94
95    let mut info_result = Vec::new();
96    'outer: loop {
97        // To minimize round trips we request several results in one go.
98        let driver_info_futures = vec![
99            iterator.get_next(),
100            iterator.get_next(),
101            iterator.get_next(),
102            iterator.get_next(),
103            iterator.get_next(),
104            iterator.get_next(),
105            iterator.get_next(),
106            iterator.get_next(),
107        ];
108        let driver_info_result = futures::future::join_all(driver_info_futures).await;
109        for result in driver_info_result {
110            let mut driver_info = result.context("FIDL call to get driver info failed")?;
111            if driver_info.len() == 0 {
112                break 'outer;
113            }
114            info_result.append(&mut driver_info)
115        }
116    }
117    Ok(info_result)
118}
119
120pub async fn get_drivers_from_query(
121    query: &str,
122    driver_development_proxy: &fdd::ManagerProxy,
123) -> Result<Vec<fdf::DriverInfo>> {
124    // Try to get exactly matching driver first by treating the query as a filter.
125    // If that fails or returns multiple, we'll do manual filtering.
126    let driver_filter = [query.to_string()];
127    let driver_info = get_driver_info(driver_development_proxy, &driver_filter).await;
128
129    match driver_info {
130        Ok(drivers) if !drivers.is_empty() => Ok(drivers),
131        _ => {
132            // If direct filter didn't work, get all drivers and filter manually.
133            let empty: [String; 0] = [];
134            let all_drivers = get_driver_info(driver_development_proxy, &empty).await?;
135            Ok(all_drivers
136                .into_iter()
137                .filter(|driver| {
138                    let url_match = driver.url.as_ref().map(|u| u.contains(query)).unwrap_or(false);
139                    if url_match {
140                        return true;
141                    }
142                    driver.name.as_ref().map(|n| n.contains(query)).unwrap_or(false)
143                })
144                .collect())
145        }
146    }
147}
148
149#[derive(Debug)]
150pub enum GetSingleDriverError {
151    NotFound(String),
152    Ambiguous { query: String, drivers: String },
153    UnderlyingError(anyhow::Error),
154}
155
156impl std::fmt::Display for GetSingleDriverError {
157    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158        match self {
159            GetSingleDriverError::NotFound(query) => {
160                write!(f, "No matching driver found for query {:?}.", query)
161            }
162            GetSingleDriverError::Ambiguous { query, drivers } => {
163                write!(
164                    f,
165                    "The query {:?} matches more than one driver:\n{}\n\nTo avoid ambiguity, use a more specific query.",
166                    query, drivers
167                )
168            }
169            GetSingleDriverError::UnderlyingError(e) => write!(f, "{}", e),
170        }
171    }
172}
173
174impl std::error::Error for GetSingleDriverError {}
175
176impl From<anyhow::Error> for GetSingleDriverError {
177    fn from(e: anyhow::Error) -> Self {
178        GetSingleDriverError::UnderlyingError(e)
179    }
180}
181
182pub async fn get_single_driver_from_query(
183    query: &str,
184    driver_development_proxy: &fdd::ManagerProxy,
185) -> Result<fdf::DriverInfo, GetSingleDriverError> {
186    let mut filtered_drivers = get_drivers_from_query(query, driver_development_proxy)
187        .await
188        .map_err(anyhow::Error::from)?;
189
190    if filtered_drivers.len() > 1 {
191        let driver_names: Vec<String> = filtered_drivers
192            .iter()
193            .map(|d| {
194                d.url.as_ref().cloned().unwrap_or_else(|| {
195                    d.name.as_ref().cloned().unwrap_or_else(|| "Unknown".to_string())
196                })
197            })
198            .collect();
199        let driver_names = driver_names.join("\n");
200        return Err(GetSingleDriverError::Ambiguous {
201            query: query.to_string(),
202            drivers: driver_names,
203        });
204    }
205
206    if filtered_drivers.is_empty() {
207        return Err(GetSingleDriverError::NotFound(query.to_string()));
208    }
209
210    Ok(filtered_drivers.remove(0))
211}
212
213/// Combines pagination results into a single vector.
214pub async fn get_composite_info(
215    service: &fdd::ManagerProxy,
216) -> Result<Vec<fdd::CompositeNodeInfo>> {
217    let (iterator, iterator_server) =
218        fidl::endpoints::create_proxy::<fdd::CompositeInfoIteratorMarker>();
219
220    service
221        .get_composite_info(iterator_server)
222        .context("FIDL call to get composite info failed")?;
223
224    let mut info_result = Vec::new();
225    loop {
226        let mut info =
227            iterator.get_next().await.context("FIDL call to get composite info failed")?;
228        if info.is_empty() {
229            break;
230        }
231        info_result.append(&mut info)
232    }
233    Ok(info_result)
234}
235
236/// Combines pagination results into a single vector.
237pub async fn get_driver_host_info(service: &fdd::ManagerProxy) -> Result<Vec<fdd::DriverHostInfo>> {
238    let (iterator, iterator_server) =
239        fidl::endpoints::create_proxy::<fdd::DriverHostInfoIteratorMarker>();
240
241    service
242        .get_driver_host_info(iterator_server)
243        .context("FIDL call to get driver host info failed")?;
244
245    let mut info_result = Vec::new();
246    loop {
247        let mut info =
248            iterator.get_next().await.context("FIDL call to get driver host info failed")?;
249        if info.is_empty() {
250            break;
251        }
252        info_result.append(&mut info)
253    }
254    Ok(info_result)
255}
256
257/// Combines pagination results into a single vector.
258pub async fn get_composite_node_specs(
259    service: &fdd::ManagerProxy,
260    name_filter: Option<String>,
261) -> Result<Vec<fdf::CompositeInfo>> {
262    let (iterator, iterator_server) =
263        fidl::endpoints::create_proxy::<fdd::CompositeNodeSpecIteratorMarker>();
264
265    service
266        .get_composite_node_specs(name_filter.as_deref(), iterator_server)
267        .context("FIDL call to get node groups failed")?;
268
269    let mut info_result = Vec::new();
270    loop {
271        let mut node_groups =
272            iterator.get_next().await.context("FIDL call to get node groups failed")?;
273        if node_groups.is_empty() {
274            break;
275        }
276        info_result.append(&mut node_groups)
277    }
278    Ok(info_result)
279}
280
281/// Gets the desired DriverInfo instance.
282///
283/// Filter based on the driver's URL.
284/// For example: "fuchsia-boot://domain/#meta/foo.cm"
285///
286/// # Arguments
287/// * `driver_filter` - Filter to the driver that matches the given filter.
288pub async fn get_driver_by_filter(
289    driver_filter: &String,
290    driver_development_proxy: &fdd::ManagerProxy,
291) -> Result<fdf::DriverInfo> {
292    let filter_list: [String; 1] = [driver_filter.to_string()];
293    let driver_list = get_driver_info(&driver_development_proxy, &filter_list).await?;
294    if driver_list.len() != 1 {
295        return Err(anyhow!(
296            "There should be exactly one match for '{}'. Found {}.",
297            driver_filter,
298            driver_list.len()
299        ));
300    }
301    let mut driver_info: Option<fdf::DriverInfo> = None;
302
303    // Confirm this is the correct match.
304    let driver = &driver_list[0];
305    if let Some(ref url) = driver.url {
306        if url == driver_filter {
307            driver_info = Some(driver.clone());
308        }
309    }
310    match driver_info {
311        Some(driver) => Ok(driver),
312        _ => Err(anyhow!("Did not find matching driver for: {}", driver_filter)),
313    }
314}
315
316/// Gets the driver that is bound to the given device.
317///
318/// Is able to fuzzy match on the device's topological path, where the shortest match
319/// will be the one chosen.
320///
321/// # Arguments
322/// * `device_topo_path` - The device's topological path. e.g. sys/platform/.../device
323pub async fn get_driver_by_device(
324    device_topo_path: &String,
325    driver_development_proxy: &fdd::ManagerProxy,
326) -> Result<fdf::DriverInfo> {
327    let device_filter: [String; 1] = [device_topo_path.to_string()];
328    let mut device_list =
329        get_device_info(&driver_development_proxy, &device_filter, /* exact_match= */ true).await?;
330    if device_list.len() != 1 {
331        let fuzzy_device_list = get_device_info(
332            &driver_development_proxy,
333            &device_filter,
334            /* exact_match= */ false,
335        )
336        .await?;
337        if fuzzy_device_list.len() == 0 {
338            return Err(anyhow!("No devices matched the query: {}", device_topo_path));
339        } else if fuzzy_device_list.len() > 1 {
340            let mut builder = "Found multiple matches. Did you mean one of these?\n\n".to_string();
341            for item in fuzzy_device_list {
342                let device: Device = item.into();
343                // We don't appear to have a string builder crate in-tree.
344                builder = format!("{}{}\n", builder, device.get_full_name()?);
345            }
346            return Err(anyhow!(builder));
347        }
348        device_list = fuzzy_device_list;
349    }
350
351    let found_device = device_list.remove(0);
352    match found_device.bound_driver_url {
353        Some(ref driver_filter) => {
354            get_driver_by_filter(&driver_filter, &driver_development_proxy).await
355        }
356        _ => Err(anyhow!("Did not find driver for device {}", &device_topo_path)),
357    }
358}
359
360/// Gets the devices that are bound to the given driver.
361///
362/// Filter based on the driver's URL.
363/// For example: "fuchsia-boot://domain/#meta/foo.cm"
364///
365/// # Arguments
366/// * `driver_filter` - Filter to the driver that matches the given filter.
367pub async fn get_devices_by_driver(
368    driver_filter: &String,
369    driver_development_proxy: &fdd::ManagerProxy,
370) -> Result<Vec<Device>> {
371    let driver_info = get_driver_by_filter(driver_filter, &driver_development_proxy);
372    let empty: [String; 0] = [];
373    let device_list =
374        get_device_info(&driver_development_proxy, &empty, /* exact_match= */ false);
375
376    let (driver_info, device_list) = futures::join!(driver_info, device_list);
377    let (driver_info, device_list) = (driver_info?, device_list?);
378
379    let mut matches: Vec<Device> = Vec::new();
380    for device_item in device_list.into_iter() {
381        let device: Device = device_item.into();
382        if let (Some(bound_driver_url), Some(url)) = (&device.0.bound_driver_url, &driver_info.url)
383        {
384            if &url == &bound_driver_url {
385                matches.push(device);
386            }
387        }
388    }
389    Ok(matches)
390}