| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use { |
| fidl_fuchsia_driver_development as fdd, |
| std::{ |
| collections::{HashMap, HashSet}, |
| rc::Rc, |
| }, |
| }; |
| |
| /// Represents either a DFv1 device or DFv2 node. |
| #[derive(Debug, PartialEq)] |
| pub struct DeviceNode { |
| pub info: Rc<fdd::NodeInfo>, |
| pub child_nodes: Vec<DeviceNode>, |
| /// The number of child devices a device has. This can be different than |
| /// `child_nodes.len()` if, for example, the call to GetDeviceInfo passes a |
| /// filter that excludes a device's children from being returned. |
| pub num_children: usize, |
| } |
| |
| impl DeviceNode { |
| pub fn new(info: Rc<fdd::NodeInfo>, child_nodes: Vec<DeviceNode>, num_children: usize) -> Self { |
| Self { info, child_nodes, num_children } |
| } |
| } |
| |
| // Used internally for quick lookup of DFv1 devices or DFv2 nodes within a |
| // hashmap. |
| struct DeviceVertex { |
| info: Rc<fdd::NodeInfo>, |
| parent_ids: HashSet<u64>, |
| child_ids: HashSet<u64>, |
| } |
| |
| impl DeviceVertex { |
| fn new(info: Rc<fdd::NodeInfo>, parent_ids: HashSet<u64>, child_ids: HashSet<u64>) -> Self { |
| Self { info, parent_ids, child_ids } |
| } |
| } |
| |
| fn device_vertex_to_device_node( |
| id: u64, |
| device_vertices: &HashMap<u64, DeviceVertex>, |
| ) -> DeviceNode { |
| let vertex = device_vertices.get(&id).expect("Invalid vertex ID"); |
| // If two devices share the same child device then multiple nodes will |
| // be made for that child. |
| let child_nodes = vertex |
| .child_ids |
| .iter() |
| .filter_map(|child_id| { |
| // Child device may or may not exist depending on the filter |
| // sent in GetDeviceInfo. |
| if device_vertices.contains_key(child_id) { |
| Some(device_vertex_to_device_node(*child_id, device_vertices)) |
| } else { |
| None |
| } |
| }) |
| .collect(); |
| DeviceNode::new(Rc::clone(&vertex.info), child_nodes, vertex.child_ids.len()) |
| } |
| |
| // DFS approach to detect cycles. |
| // `vertices` acts similar to an adjacency matrix. |
| fn is_cyclic(vertices: &HashMap<u64, DeviceVertex>) -> bool { |
| enum State { |
| Unprocessed, |
| Processing, |
| Processed, |
| } |
| |
| // Process a vertex and its children for cycles. |
| // Returns true if there's a cycle and false if there is not. |
| fn process_vertex( |
| id: u64, |
| vertices: &HashMap<u64, DeviceVertex>, |
| states: &mut HashMap<u64, State>, |
| ) -> bool { |
| states.insert(id, State::Processing); |
| if let Some(vertex) = vertices.get(&id) { |
| for child_id in vertex.child_ids.iter() { |
| match states.get(child_id) { |
| // We found another Node that's processing which means |
| // there's a cycle. |
| Some(State::Processing) => { |
| return true; |
| } |
| Some(State::Unprocessed) => { |
| // Check our unprocessed child for cycles and return if |
| // we find one. |
| if process_vertex(*child_id, vertices, states) { |
| return true; |
| } |
| } |
| _ => {} |
| } |
| } |
| } |
| |
| // We have not found any cycles so mark Node as processed. |
| states.insert(id, State::Processed); |
| false |
| } |
| |
| // Key: ID of device vertex |
| let mut states: HashMap<u64, State> = |
| vertices.keys().map(|id| (*id, State::Unprocessed)).collect(); |
| for id in vertices.keys() { |
| if let Some(State::Unprocessed) = states.get(id) { |
| if process_vertex(*id, &vertices, &mut states) { |
| return true; |
| } |
| } |
| } |
| false |
| } |
| |
| impl From<Rc<fdd::NodeInfo>> for DeviceVertex { |
| fn from(device_info: Rc<fdd::NodeInfo>) -> Self { |
| let mut parent_ids = HashSet::<u64>::new(); |
| if let Some(ids) = device_info.parent_ids.as_ref() { |
| parent_ids = ids.iter().map(|id| *id).collect(); |
| assert_eq!( |
| ids.len(), |
| parent_ids.len(), |
| "Device has the same parent ID listed multiple times" |
| ); |
| }; |
| |
| let mut child_ids = HashSet::<u64>::new(); |
| if let Some(ids) = device_info.child_ids.as_ref() { |
| child_ids = ids.iter().map(|id| *id).collect(); |
| assert_eq!( |
| ids.len(), |
| child_ids.len(), |
| "Device has the same child ID listed multiple times" |
| ); |
| }; |
| |
| Self::new(device_info, parent_ids, child_ids) |
| } |
| } |
| |
| /// Creates zero or more tree structures that reflects the DFv1 devices' or DFv2 |
| /// nodes' topology defined in `device_infos`. Note that if two devices/nodes |
| /// share a child then that child will be duplicated so that each parent has a |
| /// copy of the child. |
| /// |
| /// # Panics |
| /// |
| /// Panics if one of the resulting graphs is cyclic or if `device_infos` |
| /// contains conflicting information about the devices'/nodes' topology or if a |
| /// device's/node's ID or topological path is missing. |
| pub fn create_device_topology(device_infos: Vec<fdd::NodeInfo>) -> Vec<DeviceNode> { |
| let device_infos: Vec<Rc<fdd::NodeInfo>> = |
| device_infos.into_iter().map(|device_info| Rc::new(device_info)).collect(); |
| // Key: ID of the vertex |
| let device_vertices: HashMap<u64, DeviceVertex> = device_infos |
| .iter() |
| .map(|device_info| { |
| (device_info.id.expect("Device ID missing"), Rc::clone(&device_info).into()) |
| }) |
| .collect(); |
| assert!(!is_cyclic(&device_vertices), "Device topology is cyclic"); |
| |
| // Assert that the devices' children and parents make sense. |
| let mut root_ids: Vec<u64> = Vec::new(); |
| for device_info in device_infos { |
| let id = device_info.id.expect("Device ID missing"); |
| |
| // Assert all device's children exist. |
| if let Some(child_ids) = device_info.child_ids.as_ref() { |
| for child_id in child_ids { |
| // Child device may or may not exist depending on the filter |
| // sent in GetDeviceInfo. |
| if let Some(child_node) = device_vertices.get(child_id) { |
| assert!(child_node.parent_ids.contains(&id), "Parent device lists the ID of a child device in it's child ID's, however, the child device does not list the parent device's ID in its parent devices"); |
| } |
| } |
| } |
| |
| // Find root and assert non-root devices' parents exist. |
| if let Some(parent_ids) = device_info.parent_ids.as_ref() { |
| for parent_id in parent_ids { |
| // Parent device may or may not exist depending on the filter |
| // sent in GetDeviceInfo or if the current device is a root |
| // device. |
| if let Some(parent_node) = device_vertices.get(parent_id) { |
| assert!(parent_node.child_ids.contains(&id), "Child device lists the ID of a parent device in it's parent ID's, however, the parent device does not list the child device's ID in its child devices"); |
| } else { |
| root_ids.push(id); |
| } |
| } |
| } else { |
| root_ids.push(id); |
| } |
| } |
| |
| root_ids |
| .into_iter() |
| .map(|root_id| device_vertex_to_device_node(root_id, &device_vertices)) |
| .collect::<Vec<DeviceNode>>() |
| } |