system_image/
path_hash_mapping.rsuse crate::errors::PathHashMappingError;
use fuchsia_hash::Hash;
use fuchsia_pkg::PackagePath;
use std::io::{self, BufRead as _};
use std::marker::PhantomData;
use std::str::FromStr as _;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct Static;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct Bootfs;
pub type StaticPackages = PathHashMapping<Static>;
#[derive(Debug, PartialEq, Eq)]
pub struct PathHashMapping<T> {
contents: Vec<(PackagePath, Hash)>,
phantom: PhantomData<T>,
}
impl<T> PathHashMapping<T> {
pub fn deserialize(reader: impl io::Read) -> Result<Self, PathHashMappingError> {
let reader = io::BufReader::new(reader);
let mut contents = vec![];
for line in reader.lines() {
let line = line?;
let i = line.rfind('=').ok_or_else(|| PathHashMappingError::EntryHasNoEqualsSign {
entry: line.clone(),
})?;
let hash = Hash::from_str(&line[i + 1..])?;
let path = line[..i].parse()?;
contents.push((path, hash));
}
Ok(Self { contents, phantom: PhantomData })
}
pub fn contents(&self) -> impl ExactSizeIterator<Item = &(PackagePath, Hash)> {
self.contents.iter()
}
pub fn into_contents(self) -> impl ExactSizeIterator<Item = (PackagePath, Hash)> {
self.contents.into_iter()
}
pub fn hashes(&self) -> impl Iterator<Item = &Hash> {
self.contents.iter().map(|(_, hash)| hash)
}
pub fn hash_for_package(&self, path: &PackagePath) -> Option<Hash> {
self.contents.iter().find_map(|(n, hash)| if n == path { Some(*hash) } else { None })
}
pub fn from_entries(entries: Vec<(PackagePath, Hash)>) -> Self {
Self { contents: entries, phantom: PhantomData }
}
pub fn serialize(&self, mut writer: impl io::Write) -> Result<(), PathHashMappingError> {
for entry in self.contents.iter() {
writeln!(&mut writer, "{}={}", entry.0, entry.1)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
use fuchsia_pkg::test::random_package_path;
use proptest::prelude::*;
#[test]
fn deserialize_empty_file() {
let empty = Vec::new();
let static_packages = StaticPackages::deserialize(empty.as_slice()).unwrap();
assert_eq!(static_packages.hashes().count(), 0);
}
#[test]
fn deserialize_valid_file_list_hashes() {
let bytes =
"name/variant=0000000000000000000000000000000000000000000000000000000000000000\n\
other-name/other-variant=1111111111111111111111111111111111111111111111111111111111111111\n"
.as_bytes();
let static_packages = StaticPackages::deserialize(bytes).unwrap();
assert_eq!(
static_packages.hashes().cloned().collect::<Vec<_>>(),
vec![
"0000000000000000000000000000000000000000000000000000000000000000".parse().unwrap(),
"1111111111111111111111111111111111111111111111111111111111111111".parse().unwrap()
]
);
}
#[test]
fn deserialze_rejects_invalid_package_path() {
let bytes =
"name/=0000000000000000000000000000000000000000000000000000000000000000\n".as_bytes();
let res = StaticPackages::deserialize(bytes);
assert_matches!(res, Err(PathHashMappingError::ParsePackagePath(_)));
}
#[test]
fn deserialize_rejects_invalid_hash() {
let bytes = "name/variant=invalid-hash\n".as_bytes();
let res = StaticPackages::deserialize(bytes);
assert_matches!(res, Err(PathHashMappingError::ParseHash(_)));
}
#[test]
fn deserialize_rejects_missing_equals() {
let bytes =
"name/variant~0000000000000000000000000000000000000000000000000000000000000000\n"
.as_bytes();
let res = StaticPackages::deserialize(bytes);
assert_matches!(res, Err(PathHashMappingError::EntryHasNoEqualsSign { .. }));
}
#[test]
fn from_entries_serialize() {
let static_packages = StaticPackages::from_entries(vec![(
PackagePath::from_name_and_variant("name0".parse().unwrap(), "0".parse().unwrap()),
"0000000000000000000000000000000000000000000000000000000000000000".parse().unwrap(),
)]);
let mut serialized = vec![];
static_packages.serialize(&mut serialized).unwrap();
assert_eq!(
serialized,
&b"name0/0=0000000000000000000000000000000000000000000000000000000000000000\n"[..]
);
}
#[test]
fn hash_for_package_success() {
let bytes =
"name/variant=0000000000000000000000000000000000000000000000000000000000000000\n\
"
.as_bytes();
let static_packages = StaticPackages::deserialize(bytes).unwrap();
let res = static_packages.hash_for_package(&PackagePath::from_name_and_variant(
"name".parse().unwrap(),
"variant".parse().unwrap(),
));
assert_eq!(
res,
Some(
"0000000000000000000000000000000000000000000000000000000000000000".parse().unwrap(),
)
);
}
#[test]
fn hash_for_missing_package_is_none() {
let bytes =
"name/variant=0000000000000000000000000000000000000000000000000000000000000000\n\
"
.as_bytes();
let static_packages = StaticPackages::deserialize(bytes).unwrap();
let res = static_packages.hash_for_package(&PackagePath::from_name_and_variant(
"nope".parse().unwrap(),
"variant".parse().unwrap(),
));
assert_eq!(res, None);
}
prop_compose! {
fn random_hash()(s in "[A-Fa-f0-9]{64}") -> Hash {
s.parse().unwrap()
}
}
prop_compose! {
fn random_static_packages()
(vec in prop::collection::vec(
(random_package_path(), random_hash()), 0..4)
) -> PathHashMapping<Static> {
StaticPackages::from_entries(vec)
}
}
proptest! {
#[test]
fn serialize_deserialize_identity(static_packages in random_static_packages()) {
let mut serialized = vec![];
static_packages.serialize(&mut serialized).unwrap();
let deserialized = StaticPackages::deserialize(serialized.as_slice()).unwrap();
prop_assert_eq!(
static_packages,
deserialized
);
}
}
}