system_image/
path_hash_mapping.rs1use crate::errors::PathHashMappingError;
6use buf_read_ext::BufReadExt as _;
7use fuchsia_hash::Hash;
8use fuchsia_pkg::PackagePath;
9use sorted_vec_map::SortedVecMap;
10use std::io;
11use std::marker::PhantomData;
12use std::str::FromStr as _;
13
14#[derive(Debug, PartialEq, Eq, Clone, Copy)]
16pub struct Static;
17
18#[derive(Debug, PartialEq, Eq, Clone, Copy)]
20pub struct Bootfs;
21
22pub type StaticPackages = PathHashMapping<Static>;
23
24#[derive(Debug, PartialEq, Eq)]
28pub struct PathHashMapping<T> {
29 contents: SortedVecMap<PackagePath, Hash>,
30 phantom: PhantomData<T>,
31}
32
33impl<T> PathHashMapping<T> {
34 pub fn deserialize(mut reader: impl io::BufRead) -> Result<Self, PathHashMappingError> {
37 let mut contents = SortedVecMap::builder();
38 let mut lines = reader.lending_lines();
39 while let Some(line) = lines.next() {
40 let line = line?;
41 let i = line.rfind('=').ok_or_else(|| PathHashMappingError::EntryHasNoEqualsSign {
42 entry: line.to_owned(),
43 })?;
44 let hash = Hash::from_str(&line[i + 1..])?;
45 let path = line[..i].parse()?;
46 contents.insert(path, hash);
47 }
48 Ok(Self { contents: contents.build(), phantom: PhantomData })
49 }
50
51 pub fn contents(&self) -> &SortedVecMap<PackagePath, Hash> {
53 &self.contents
54 }
55
56 pub fn into_contents(self) -> SortedVecMap<PackagePath, Hash> {
58 self.contents
59 }
60
61 pub fn hashes(&self) -> impl Iterator<Item = &Hash> {
63 self.contents.values()
64 }
65
66 pub fn hash_for_package(&self, path: &PackagePath) -> Option<Hash> {
68 self.contents.get(path).copied()
69 }
70
71 pub fn shrink_to_fit(&mut self) {
73 self.contents.shrink_to_fit();
74 }
75
76 pub fn serialize(&self, mut writer: impl io::Write) -> Result<(), PathHashMappingError> {
78 for (k, v) in &self.contents {
79 writeln!(&mut writer, "{}={}", k, v)?;
80 }
81 Ok(())
82 }
83}
84
85impl<T> FromIterator<(PackagePath, Hash)> for PathHashMapping<T> {
86 fn from_iter<I: IntoIterator<Item = (PackagePath, Hash)>>(iter: I) -> Self {
87 let contents = SortedVecMap::from_iter(iter);
88 Self { contents, phantom: PhantomData }
89 }
90}
91
92impl<T, const N: usize> From<[(PackagePath, Hash); N]> for PathHashMapping<T> {
93 fn from(entries: [(PackagePath, Hash); N]) -> Self {
94 let contents = SortedVecMap::from_iter(entries);
95 Self { contents, phantom: PhantomData }
96 }
97}
98
99impl<T> IntoIterator for PathHashMapping<T> {
100 type Item = (PackagePath, Hash);
101 type IntoIter = std::vec::IntoIter<(PackagePath, Hash)>;
102
103 fn into_iter(self) -> Self::IntoIter {
104 self.contents.into_iter()
105 }
106}
107
108impl<'a, T> IntoIterator for &'a PathHashMapping<T> {
109 type Item = (&'a PackagePath, &'a Hash);
110 type IntoIter = sorted_vec_map::sorted_vec_map::Iter<'a, PackagePath, Hash>;
111
112 fn into_iter(self) -> Self::IntoIter {
113 (&self.contents).into_iter()
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120 use assert_matches::assert_matches;
121 use fuchsia_pkg::test::random_package_path;
122 use proptest::prelude::*;
123
124 #[test]
125 fn deserialize_empty_file() {
126 let empty = Vec::new();
127 let static_packages = StaticPackages::deserialize(empty.as_slice()).unwrap();
128 assert_eq!(static_packages.hashes().count(), 0);
129 }
130
131 #[test]
132 fn deserialize_valid_file_list_hashes() {
133 let bytes =
134 "name/variant=0000000000000000000000000000000000000000000000000000000000000000\n\
135 other-name/other-variant=1111111111111111111111111111111111111111111111111111111111111111\n"
136 .as_bytes();
137 let static_packages = StaticPackages::deserialize(bytes).unwrap();
138 assert_eq!(
139 static_packages.hashes().cloned().collect::<Vec<_>>(),
140 vec![
141 "0000000000000000000000000000000000000000000000000000000000000000".parse().unwrap(),
142 "1111111111111111111111111111111111111111111111111111111111111111".parse().unwrap()
143 ]
144 );
145 }
146
147 #[test]
148 fn deserialze_rejects_invalid_package_path() {
149 let bytes =
150 "name/=0000000000000000000000000000000000000000000000000000000000000000\n".as_bytes();
151 let res = StaticPackages::deserialize(bytes);
152 assert_matches!(res, Err(PathHashMappingError::ParsePackagePath(_)));
153 }
154
155 #[test]
156 fn deserialize_rejects_invalid_hash() {
157 let bytes = "name/variant=invalid-hash\n".as_bytes();
158 let res = StaticPackages::deserialize(bytes);
159 assert_matches!(res, Err(PathHashMappingError::ParseHash(_)));
160 }
161
162 #[test]
163 fn deserialize_rejects_missing_equals() {
164 let bytes =
165 "name/variant~0000000000000000000000000000000000000000000000000000000000000000\n"
166 .as_bytes();
167 let res = StaticPackages::deserialize(bytes);
168 assert_matches!(res, Err(PathHashMappingError::EntryHasNoEqualsSign { .. }));
169 }
170
171 #[test]
172 fn from_serialize() {
173 let static_packages = StaticPackages::from([(
174 PackagePath::from_name_and_variant("name0".parse().unwrap(), "0".parse().unwrap()),
175 "0000000000000000000000000000000000000000000000000000000000000000".parse().unwrap(),
176 )]);
177
178 let mut serialized = vec![];
179 static_packages.serialize(&mut serialized).unwrap();
180 assert_eq!(
181 serialized,
182 &b"name0/0=0000000000000000000000000000000000000000000000000000000000000000\n"[..]
183 );
184 }
185
186 #[test]
187 fn hash_for_package_success() {
188 let bytes =
189 "name/variant=0000000000000000000000000000000000000000000000000000000000000000\n\
190 "
191 .as_bytes();
192 let static_packages = StaticPackages::deserialize(bytes).unwrap();
193 let res = static_packages.hash_for_package(&PackagePath::from_name_and_variant(
194 "name".parse().unwrap(),
195 "variant".parse().unwrap(),
196 ));
197 assert_eq!(
198 res,
199 Some(
200 "0000000000000000000000000000000000000000000000000000000000000000".parse().unwrap(),
201 )
202 );
203 }
204
205 #[test]
206 fn hash_for_missing_package_is_none() {
207 let bytes =
208 "name/variant=0000000000000000000000000000000000000000000000000000000000000000\n\
209 "
210 .as_bytes();
211 let static_packages = StaticPackages::deserialize(bytes).unwrap();
212 let res = static_packages.hash_for_package(&PackagePath::from_name_and_variant(
213 "nope".parse().unwrap(),
214 "variant".parse().unwrap(),
215 ));
216 assert_eq!(res, None);
217 }
218
219 prop_compose! {
220 fn random_hash()(s in "[A-Fa-f0-9]{64}") -> Hash {
221 s.parse().unwrap()
222 }
223 }
224
225 prop_compose! {
226 fn random_static_packages()
227 (vec in prop::collection::vec(
228 (random_package_path(), random_hash()), 0..4)
229 ) -> PathHashMapping<Static> {
230 StaticPackages::from_iter(vec)
231 }
232 }
233
234 proptest! {
235 #[test]
236 fn serialize_deserialize_identity(static_packages in random_static_packages()) {
237 let mut serialized = vec![];
238 static_packages.serialize(&mut serialized).unwrap();
239 let deserialized = StaticPackages::deserialize(serialized.as_slice()).unwrap();
240 prop_assert_eq!(
241 static_packages,
242 deserialized
243 );
244 }
245 }
246}