driver_tools/subcommands/node/
common.rs

1// Copyright 2025 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 crate::common;
6use ansi_term::Colour;
7use anyhow::{Result, anyhow, bail, format_err};
8use fidl_fuchsia_driver_development as fdd;
9use itertools::Itertools;
10use std::collections::{BTreeMap, VecDeque};
11use std::str::FromStr;
12
13/// Filters that can be applied to nodes
14#[derive(Debug, PartialEq, Clone)]
15pub enum NodeFilter {
16    /// Filters nodes that are bound to a driver or a composite parent,
17    /// or their parent is the owner.
18    Bound,
19    /// Filters nodes that are not bound to anything and have no owner.
20    Unbound,
21    /// Filters nodes that are an ancestor of the node with the given name.
22    /// Includes the named node.
23    Ancestor(String, bool),
24    /// Filters nodes that are a descendant of the node with the given name.
25    /// Includes the named node.
26    Descendant(String),
27    /// Filters node that are a relative (either an ancestor or a descendant) of the
28    /// node with the given name. Includes the named node.
29    Relative(String, bool),
30    /// Filters node that are a sibling of the node with the given name. Includes the named node.
31    Sibling(String, bool),
32}
33
34impl FromStr for NodeFilter {
35    type Err = &'static str;
36
37    fn from_str(s: &str) -> Result<Self, Self::Err> {
38        match s.to_lowercase().as_str() {
39            "bound" => Ok(NodeFilter::Bound),
40            "unbound" => Ok(NodeFilter::Unbound),
41            filter => match filter.split_once(":") {
42                Some((function, arg)) => match function {
43                    "ancestor" | "ancestors" => Ok(NodeFilter::Ancestor(arg.to_string(), false)),
44                    "primary_ancestor" | "primary_ancestors" => {
45                        Ok(NodeFilter::Ancestor(arg.to_string(), true))
46                    }
47                    "descendant" | "descendants" => Ok(NodeFilter::Descendant(arg.to_string())),
48                    "relative" | "relatives" => Ok(NodeFilter::Relative(arg.to_string(), false)),
49                    "primary_relative" | "primary_relatives" => {
50                        Ok(NodeFilter::Relative(arg.to_string(), true))
51                    }
52                    "sibling" | "siblings" => Ok(NodeFilter::Sibling(arg.to_string(), false)),
53                    "primary_sibling" | "primary_siblings" => {
54                        Ok(NodeFilter::Sibling(arg.to_string(), true))
55                    }
56                    _ => Err("unknown function for node filter."),
57                },
58                None => Err(
59                    "node filter should be 'bound', 'unbound', 'ancestors:<node_name>', 'primary_ancestors:<node_name>', 'descendants:<node_name>', 'relatives:<node_name>', 'primary_relatives:<node_name>', 'siblings:<node_name>', or 'primary_siblings:<node_name>'.",
60                ),
61            },
62        }
63    }
64}
65
66pub fn get_state_and_owner(
67    quarantined: Option<bool>,
68    url: &Option<String>,
69    with_style: bool,
70) -> (String, String) {
71    let not_found = "Driver not found".to_string();
72    let url = url.as_ref().unwrap_or(&not_found);
73    let state = if url == "unbound" {
74        common::colorized("Unbound", Colour::Yellow, with_style)
75    } else if quarantined == Some(true) {
76        common::colorized("Quarantined", Colour::Red, with_style)
77    } else {
78        common::colorized("Bound", Colour::Green, with_style)
79    };
80
81    let owner = if url == "unbound" {
82        "none".to_string()
83    } else if url == "owned by parent" {
84        "parent".to_string()
85    } else if url == "owned by composite(s)" {
86        "composite(s)".to_string()
87    } else {
88        url.clone()
89    };
90    (state, owner)
91}
92
93pub async fn get_nodes_from_query(
94    query: &str,
95    nodes: Vec<fdd::NodeInfo>,
96) -> Result<Vec<fdd::NodeInfo>> {
97    // Try and find instances that contain the query in any of the identifiers
98    // (moniker, URL).
99    let mut filtered_instances: Vec<fdd::NodeInfo> = nodes
100        .into_iter()
101        .filter(|node| {
102            let empty = "".to_string();
103            let url_match = node.bound_driver_url.as_ref().unwrap_or(&empty).contains(&query);
104            let moniker_match = node.moniker.as_ref().unwrap_or(&empty).contains(&query);
105            url_match || moniker_match
106        })
107        .collect();
108
109    // For stability sort the list by moniker.
110    filtered_instances.sort_unstable_by(|a, b| a.moniker.as_ref().cmp(&b.moniker.as_ref()));
111
112    let (exact_matches, others): (Vec<_>, Vec<_>) = filtered_instances
113        .into_iter()
114        .partition(|i| i.moniker.as_ref().map(|s| s.as_str()) == Some(query));
115
116    if !exact_matches.is_empty() { Ok(exact_matches) } else { Ok(others) }
117}
118
119pub async fn get_single_node_from_query(
120    query: &str,
121    nodes: Vec<fdd::NodeInfo>,
122) -> Result<fdd::NodeInfo> {
123    // Get all instance monikers that match the query and ensure there is only one.
124    let mut instances = get_nodes_from_query(&query, nodes).await?;
125    if instances.len() > 1 {
126        let monikers: Vec<String> = instances
127            .into_iter()
128            .map(|i| i.moniker.unwrap_or_else(|| "No moniker.".to_string()))
129            .collect();
130        let monikers = monikers.join("\n");
131        bail!(
132            "The query {:?} matches more than one node:\n{}\n\nTo avoid ambiguity, use one of the above monikers instead.",
133            query,
134            monikers
135        );
136    }
137    if instances.is_empty() {
138        bail!("No matching node found for query {:?}.", query);
139    }
140    let instance = instances.remove(0);
141    Ok(instance)
142}
143
144pub fn filter_nodes(
145    nodes: Vec<fdd::NodeInfo>,
146    filter: Option<NodeFilter>,
147) -> Result<Vec<fdd::NodeInfo>> {
148    if let Some(filter) = filter {
149        match filter {
150            NodeFilter::Bound => Ok(nodes
151                .into_iter()
152                .filter(|n| {
153                    let (state, _) = get_state_and_owner(n.quarantined, &n.bound_driver_url, false);
154                    // Note: This returns both quarantined and bound nodes.
155                    state != "Unbound"
156                })
157                .collect()),
158            NodeFilter::Unbound => Ok(nodes
159                .into_iter()
160                .filter(|n| {
161                    let (state, _) = get_state_and_owner(n.quarantined, &n.bound_driver_url, false);
162                    state == "Unbound"
163                })
164                .collect()),
165            NodeFilter::Ancestor(filter, primary_only) => {
166                filter_ancestor(&nodes, filter, primary_only)
167            }
168            NodeFilter::Descendant(filter) => filter_descendant(&nodes, filter),
169            NodeFilter::Relative(filter, primary_only) => {
170                filter_relative(&nodes, filter, primary_only)
171            }
172            NodeFilter::Sibling(filter, primary_only) => {
173                let node_map = create_node_map(&nodes)?;
174                let target = match nodes
175                    .iter()
176                    .find(|n| n.moniker.as_ref().unwrap().eq_ignore_ascii_case(&filter))
177                {
178                    Some(node) => node,
179                    None => return Err(anyhow!("Node with moniker {} not found", filter)),
180                };
181
182                let mut results: BTreeMap<u64, fdd::NodeInfo> = BTreeMap::new();
183
184                // This collects all children of direct parents of the target node.
185                // If |primary_only|, only children from the primary parent are collected.
186                if let Some(parent_ids) = &target.parent_ids {
187                    for parent_id in parent_ids {
188                        if let Some(parent) = node_map.get(parent_id) {
189                            if !primary_only
190                                || target
191                                    .moniker
192                                    .as_ref()
193                                    .unwrap()
194                                    .starts_with(parent.moniker.as_ref().unwrap())
195                            {
196                                if let Some(child_ids) = &parent.child_ids {
197                                    for child_id in child_ids {
198                                        if let Some(child) = node_map.get(child_id) {
199                                            results.insert(*child_id, child.clone());
200                                        }
201                                    }
202                                }
203                            }
204                        }
205                    }
206                }
207
208                Ok(results.into_values().sorted_by_key(|a| a.moniker.clone()).collect())
209            }
210        }
211    } else {
212        Ok(nodes)
213    }
214}
215
216pub fn create_node_map(nodes: &Vec<fdd::NodeInfo>) -> Result<BTreeMap<u64, fdd::NodeInfo>> {
217    nodes
218        .iter()
219        .map(|node| {
220            if let Some(id) = node.id {
221                Ok((id, node.clone()))
222            } else {
223                Err(format_err!("Missing node id"))
224            }
225        })
226        .collect::<Result<BTreeMap<_, _>>>()
227}
228
229fn filter_ancestor(
230    nodes: &Vec<fdd::NodeInfo>,
231    filter: String,
232    primary_only: bool,
233) -> Result<Vec<fdd::NodeInfo>> {
234    let node_map = create_node_map(nodes)?;
235    let root =
236        match nodes.iter().find(|n| n.moniker.as_ref().unwrap().eq_ignore_ascii_case(&filter)) {
237            Some(node) => node,
238            None => return Err(anyhow!("Node with moniker {} not found", filter)),
239        };
240
241    let mut results: BTreeMap<u64, fdd::NodeInfo> = BTreeMap::new();
242    let mut visit_q: VecDeque<&fdd::NodeInfo> = VecDeque::new();
243    results.insert(root.id.ok_or_else(|| format_err!("Node missing id"))?, root.clone());
244    visit_q.push_back(root);
245
246    // Run a BFS through the parents of the node topology, to collect all nodes that are accessible
247    // through parents. If |primary_only|, only traverse the primary parents of composite nodes.
248    while let Some(node) = visit_q.pop_front() {
249        if let Some(parent_ids) = &node.parent_ids {
250            for parent_id in parent_ids {
251                if let Some(parent) = node_map.get(parent_id) {
252                    if !primary_only
253                        || node
254                            .moniker
255                            .as_ref()
256                            .unwrap()
257                            .starts_with(parent.moniker.as_ref().unwrap())
258                    {
259                        if !results.contains_key(parent_id) {
260                            results.insert(*parent_id, parent.clone());
261                            visit_q.push_back(parent);
262                        }
263                    }
264                }
265            }
266        }
267    }
268
269    Ok(results.into_values().sorted_by_key(|a| a.moniker.clone()).collect())
270}
271
272fn filter_descendant(nodes: &Vec<fdd::NodeInfo>, filter: String) -> Result<Vec<fdd::NodeInfo>> {
273    let node_map = create_node_map(&nodes)?;
274    let root =
275        match nodes.iter().find(|n| n.moniker.as_ref().unwrap().eq_ignore_ascii_case(&filter)) {
276            Some(node) => node,
277            None => return Err(anyhow!("Node with moniker {} not found", filter)),
278        };
279
280    let mut results: BTreeMap<u64, fdd::NodeInfo> = BTreeMap::new();
281    let mut visit_q: VecDeque<&fdd::NodeInfo> = VecDeque::new();
282    results.insert(root.id.ok_or_else(|| format_err!("Node missing id"))?, root.clone());
283    visit_q.push_back(root);
284
285    // Run a BFS through the children of the node topology, to collect all nodes that are accessible
286    // through children.
287    while let Some(node) = visit_q.pop_front() {
288        if let Some(child_ids) = &node.child_ids {
289            for child_id in child_ids {
290                if let Some(child) = node_map.get(child_id) {
291                    if !results.contains_key(child_id) {
292                        results.insert(*child_id, child.clone());
293                        visit_q.push_back(child);
294                    }
295                }
296            }
297        }
298    }
299
300    Ok(results.into_values().sorted_by_key(|a| a.moniker.clone()).collect())
301}
302
303fn filter_relative(
304    nodes: &Vec<fdd::NodeInfo>,
305    filter: String,
306    primary_only: bool,
307) -> Result<Vec<fdd::NodeInfo>> {
308    let node_map = create_node_map(&nodes)?;
309    let root =
310        match nodes.iter().find(|n| n.moniker.as_ref().unwrap().eq_ignore_ascii_case(&filter)) {
311            Some(node) => node,
312            None => return Err(anyhow!("Node with moniker {} not found", filter)),
313        };
314
315    let mut results: BTreeMap<u64, fdd::NodeInfo> = BTreeMap::new();
316    let mut visit_q: VecDeque<&fdd::NodeInfo> = VecDeque::new();
317    results.insert(root.id.ok_or_else(|| format_err!("Node missing id"))?, root.clone());
318    visit_q.push_back(root);
319
320    // Same as filter_ancestor.
321    // Run a BFS through the parents of the node topology, to collect all nodes that are accessible
322    // through parents. If |primary_only|, only traverse the primary parents of composite nodes.
323    while let Some(node) = visit_q.pop_front() {
324        if let Some(parent_ids) = &node.parent_ids {
325            for parent_id in parent_ids {
326                if let Some(parent) = node_map.get(parent_id) {
327                    if !primary_only
328                        || node
329                            .moniker
330                            .as_ref()
331                            .unwrap()
332                            .starts_with(parent.moniker.as_ref().unwrap())
333                    {
334                        if !results.contains_key(parent_id) {
335                            results.insert(*parent_id, parent.clone());
336                            visit_q.push_back(parent);
337                        }
338                    }
339                }
340            }
341        }
342    }
343
344    // Reset our BFS structures.
345    visit_q = VecDeque::new();
346    visit_q.push_back(root);
347
348    // Same as filter_descendant.
349    // Run a BFS through the children of the node topology, to collect all nodes that are accessible
350    // through children.
351    while let Some(node) = visit_q.pop_front() {
352        if let Some(child_ids) = &node.child_ids {
353            for child_id in child_ids {
354                if let Some(child) = node_map.get(child_id) {
355                    if !results.contains_key(child_id) {
356                        results.insert(*child_id, child.clone());
357                        visit_q.push_back(child);
358                    }
359                }
360            }
361        }
362    }
363
364    Ok(results.into_values().sorted_by_key(|a| a.moniker.clone()).collect())
365}