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