blob: 090ecd0e155f1e58deb70fbaa68168ae2937b972 [file] [log] [blame]
// Copyright 2020 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 {
anyhow,
clonable_error::ClonableError,
component_id_index, fidl,
fidl::encoding::decode_persistent,
fidl_fuchsia_component_internal as fcomponent_internal,
moniker::{AbsoluteMoniker, MonikerError},
std::collections::{HashMap, HashSet},
thiserror::Error,
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub type ComponentInstanceId = String;
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize), serde(rename_all = "snake_case"))]
#[derive(Debug, Clone, Error)]
pub enum ComponentIdIndexError {
// The capability routing static analyzer does not report this error subtype as part of a
// routing verification result, so we don't need to serialize it.
#[cfg_attr(feature = "serde", serde(skip))]
#[error("could not read index file {}", .path)]
IndexUnreadable {
#[source]
err: ClonableError,
path: String,
},
#[error("Index error")]
IndexError(#[from] component_id_index::IndexError),
#[error("invalid moniker")]
MonikerError(#[from] MonikerError),
}
impl ComponentIdIndexError {
pub fn index_unreadable(
index_file_path: impl Into<String>,
err: impl Into<anyhow::Error>,
) -> Self {
ComponentIdIndexError::IndexUnreadable {
path: index_file_path.into(),
err: err.into().into(),
}
}
}
// Custom implementation of PartialEq in which two ComponentIdIndexError::IndexUnreadable errors are
// never equal.
impl PartialEq for ComponentIdIndexError {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::IndexError(self_err), Self::IndexError(other_err)) => self_err.eq(other_err),
(Self::MonikerError(self_err), Self::MonikerError(other_err)) => self_err.eq(other_err),
(Self::IndexUnreadable { .. }, Self::IndexUnreadable { .. }) => false,
_ => false,
}
}
}
/// ComponentIdIndex parses a given index and provides methods to look up instance IDs.
#[derive(Debug, Default)]
pub struct ComponentIdIndex {
/// Map of a moniker from the index to its instance ID.
///
/// The moniker does not contain instances, i.e. all of the ChildMonikers in the
/// path have the (moniker, not index) instance ID set to zero.
moniker_to_instance_id: HashMap<AbsoluteMoniker, ComponentInstanceId>,
/// Stores all instance IDs from the index.
/// This is used by StorageAdmin for methods that operate directly on instance IDs.
///
/// This set will currently contain all storage IDs, even of components registered with appmgr
/// instead of component_manager. This is desired because it allows the StorageAdmin protocol
/// to handle all storage on the system.
all_instance_ids: HashSet<ComponentInstanceId>,
}
impl ComponentIdIndex {
pub async fn new(index_file_path: &str) -> Result<Self, ComponentIdIndexError> {
let raw_content = std::fs::read(index_file_path)
.map_err(|err| ComponentIdIndexError::index_unreadable(index_file_path, err))?;
let fidl_index =
decode_persistent::<fcomponent_internal::ComponentIdIndex>(&raw_content)
.map_err(|err| ComponentIdIndexError::index_unreadable(index_file_path, err))?;
let index = component_id_index::Index::from_fidl(fidl_index)?;
Self::new_from_index(index)
}
pub fn new_from_index(index: component_id_index::Index) -> Result<Self, ComponentIdIndexError> {
let mut moniker_to_instance_id = HashMap::<AbsoluteMoniker, ComponentInstanceId>::new();
let mut all_instance_ids = HashSet::new();
for entry in index.instances {
let instance_id = entry
.instance_id
.as_ref()
.ok_or_else(|| {
ComponentIdIndexError::IndexError(
component_id_index::IndexError::ValidationError(
component_id_index::ValidationError::MissingInstanceIds {
entries: vec![entry.clone()],
},
),
)
})?
.clone();
all_instance_ids.insert(instance_id.clone());
if let Some(absolute_moniker) = entry.moniker {
moniker_to_instance_id.insert(absolute_moniker, instance_id);
}
}
Ok(Self { moniker_to_instance_id, all_instance_ids })
}
pub fn look_up_moniker(&self, moniker: &AbsoluteMoniker) -> Option<&ComponentInstanceId> {
self.moniker_to_instance_id.get(&moniker)
}
pub fn look_up_instance_id(&self, id: &ComponentInstanceId) -> bool {
self.all_instance_ids.contains(id)
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use moniker::{AbsoluteMonikerBase, ChildMoniker};
use routing_test_helpers::component_id_index::make_index_file;
#[fuchsia::test]
async fn look_up_moniker_no_exists() {
let index_file = make_index_file(component_id_index::Index::default()).unwrap();
let index = ComponentIdIndex::new(index_file.path().to_str().unwrap()).await.unwrap();
assert!(index.look_up_moniker(&AbsoluteMoniker::parse_str("/a/b/c").unwrap()).is_none());
}
#[fuchsia::test]
async fn look_up_moniker_exists() {
let iid = "0".repeat(64);
let index_file = make_index_file(component_id_index::Index {
instances: vec![component_id_index::InstanceIdEntry {
instance_id: Some(iid.clone()),
appmgr_moniker: None,
moniker: Some(AbsoluteMoniker::parse_str("/a/b/c").unwrap()),
}],
..component_id_index::Index::default()
})
.unwrap();
let index = ComponentIdIndex::new(index_file.path().to_str().unwrap()).await.unwrap();
assert_eq!(
Some(&iid),
index.look_up_moniker(&AbsoluteMoniker::parse_str("/a/b/c").unwrap())
);
}
#[fuchsia::test]
async fn look_up_moniker_with_instances_exists() {
let iid = "0".repeat(64);
let index_file = make_index_file(component_id_index::Index {
instances: vec![component_id_index::InstanceIdEntry {
instance_id: Some(iid.clone()),
appmgr_moniker: None,
moniker: Some(AbsoluteMoniker::parse_str("/a/coll:name").unwrap()),
}],
..component_id_index::Index::default()
})
.unwrap();
let index = ComponentIdIndex::new(index_file.path().to_str().unwrap()).await.unwrap();
assert_eq!(
Some(&iid),
index.look_up_moniker(&AbsoluteMoniker::new(vec![
ChildMoniker::new("a".to_string(), None),
ChildMoniker::new("name".to_string(), Some("coll".to_string())),
]))
);
}
#[fuchsia::test]
fn new_from_index() {
let iid = "0".repeat(64);
let inner_index = component_id_index::Index {
instances: vec![component_id_index::InstanceIdEntry {
instance_id: Some(iid.clone()),
appmgr_moniker: None,
moniker: Some(AbsoluteMoniker::parse_str("/a/b/c").unwrap()),
}],
..component_id_index::Index::default()
};
let index = ComponentIdIndex::new_from_index(inner_index)
.expect("failed to create component id index from inner index");
assert_eq!(
Some(&iid),
index.look_up_moniker(&AbsoluteMoniker::parse_str("/a/b/c").unwrap())
);
}
#[fuchsia::test]
async fn index_unreadable() {
let result = ComponentIdIndex::new("/this/path/doesnt/exist").await;
assert!(matches!(result, Err(ComponentIdIndexError::IndexUnreadable { path: _, err: _ })));
}
}