system_image/
anchored_packages.rs1use crate::errors::AnchoredPackagesError;
6use fuchsia_pkg::package_sets::{AnchoredPackageMap, AnchoredPackageSetType, PackageProperties};
7use fuchsia_url::PinnedAbsolutePackageUrl;
8use std::collections::BTreeMap;
9
10#[derive(Clone, Debug, PartialEq, Eq)]
11pub struct AnchoredPackages {
12 contents: AnchoredPackageMap,
13}
14
15impl AnchoredPackages {
16 pub fn new() -> Self {
18 Self { contents: AnchoredPackageMap::new() }
19 }
20
21 pub fn clear(&mut self, package_set_type: Option<AnchoredPackageSetType>) -> &mut Self {
24 match package_set_type {
25 None => self.contents = AnchoredPackageMap::new(),
26 Some(t) => {
27 let _ = self.contents.remove(&t);
28 }
29 }
30 self
31 }
32
33 pub fn insert(
35 &mut self,
36 package_set_type: AnchoredPackageSetType,
37 url: PinnedAbsolutePackageUrl,
38 ) -> Result<(), AnchoredPackagesError> {
39 let (u, h) = url.into_unpinned_and_hash();
40 if let Some(map) = self.contents.get_mut(&package_set_type) {
41 if map.contains_key(&u) {
42 return Err(AnchoredPackagesError::DuplicateNotSupported(u));
43 }
44 map.insert(u, PackageProperties { hash: h });
45 } else {
46 self.contents
47 .insert(package_set_type, BTreeMap::from([(u, PackageProperties { hash: h })]));
48 }
49 Ok(())
50 }
51
52 pub(crate) fn from_json(file_contents: &[u8]) -> Result<Self, AnchoredPackagesError> {
55 if file_contents.is_empty() {
56 return Ok(Self { contents: AnchoredPackageMap::new() });
57 }
58 let contents = parse_json(file_contents)?;
59 Ok(Self { contents })
60 }
61
62 pub fn as_pinned(
64 &self,
65 package_set_type: AnchoredPackageSetType,
66 ) -> Vec<PinnedAbsolutePackageUrl> {
67 match self.contents.get(&package_set_type) {
68 Some(t) => t
69 .iter()
70 .map(|x| PinnedAbsolutePackageUrl::from_unpinned(x.0.clone(), x.1.hash))
71 .collect(),
72 None => vec![],
73 }
74 }
75
76 pub fn serialize(&self, writer: impl std::io::Write) -> Result<(), serde_json::Error> {
78 if self.contents.is_empty() {
79 return Ok(());
80 }
81 serde_json::to_writer(writer, &self.contents)
82 }
83}
84
85impl Default for AnchoredPackages {
86 fn default() -> Self {
87 Self::new()
88 }
89}
90
91fn parse_json(contents: &[u8]) -> Result<AnchoredPackageMap, AnchoredPackagesError> {
92 serde_json::from_slice(contents).map_err(AnchoredPackagesError::JsonError)
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use assert_matches::assert_matches;
99 use fuchsia_hash::Hash;
100 use fuchsia_url::{AbsolutePackageUrl, UnpinnedAbsolutePackageUrl};
101 use std::str::FromStr;
102
103 fn populated_anchored_packages() -> (AnchoredPackages, &'static str) {
104 let json = r#" {
105 "anchored_on_demand": {
106 "fuchsia-pkg://fuchsia.com/package0": {
107 "hash": "0000000000000000000000000000000000000000000000000000000000000000"
108 },
109 "fuchsia-pkg://fuchsia.com/package1": {
110 "hash": "1111111111111111111111111111111111111111111111111111111111111111"
111 }
112 },
113 "anchored_permanent": {
114 "fuchsia-pkg://fuchsia.com/package2": {
115 "hash": "2222222222222222222222222222222222222222222222222222222222222222"
116 },
117 "fuchsia-pkg://fuchsia.com/package3": {
118 "hash": "3333333333333333333333333333333333333333333333333333333333333333"
119 }
120 }
121 }"#;
122
123 let mut anchored_packages_map = BTreeMap::new();
124 anchored_packages_map.insert(AnchoredPackageSetType::OnDemand, BTreeMap::new());
125 anchored_packages_map.insert(AnchoredPackageSetType::Permanent, BTreeMap::new());
126 anchored_packages_map.get_mut(&AnchoredPackageSetType::OnDemand).unwrap().insert(
127 UnpinnedAbsolutePackageUrl::from_str("fuchsia-pkg://fuchsia.com/package1").unwrap(),
128 PackageProperties {
129 hash: Hash::from_str(
130 "1111111111111111111111111111111111111111111111111111111111111111",
131 )
132 .unwrap(),
133 },
134 );
135 anchored_packages_map.get_mut(&AnchoredPackageSetType::OnDemand).unwrap().insert(
136 UnpinnedAbsolutePackageUrl::from_str("fuchsia-pkg://fuchsia.com/package0").unwrap(),
137 PackageProperties {
138 hash: Hash::from_str(
139 "0000000000000000000000000000000000000000000000000000000000000000",
140 )
141 .unwrap(),
142 },
143 );
144 anchored_packages_map.get_mut(&AnchoredPackageSetType::Permanent).unwrap().insert(
145 UnpinnedAbsolutePackageUrl::from_str("fuchsia-pkg://fuchsia.com/package3").unwrap(),
146 PackageProperties {
147 hash: Hash::from_str(
148 "3333333333333333333333333333333333333333333333333333333333333333",
149 )
150 .unwrap(),
151 },
152 );
153 anchored_packages_map.get_mut(&AnchoredPackageSetType::Permanent).unwrap().insert(
154 UnpinnedAbsolutePackageUrl::from_str("fuchsia-pkg://fuchsia.com/package2").unwrap(),
155 PackageProperties {
156 hash: Hash::from_str(
157 "2222222222222222222222222222222222222222222222222222222222222222",
158 )
159 .unwrap(),
160 },
161 );
162 (AnchoredPackages { contents: anchored_packages_map }, json)
163 }
164
165 #[test]
166 fn empty_anchored_package_list_in_json() {
167 let empty: &[u8] = &[0; 0];
168 let r = AnchoredPackages::from_json(empty);
169 assert!(r.is_ok());
170 }
171
172 #[test]
173 fn default_anchored_package_structure() {
174 let empty = AnchoredPackages::new();
175 let default = AnchoredPackages { ..Default::default() };
176 assert_eq!(empty, default);
177 }
178
179 #[test]
180 fn insert_into_anchored_packages_structure() {
181 let mut packages = AnchoredPackages::new();
182 assert!(packages.insert(AnchoredPackageSetType::Automatic,
183 PinnedAbsolutePackageUrl::parse("fuchsia-pkg://fuchsia.com/package0?hash=0000000000000000000000000000000000000000000000000000000000000000").unwrap()).is_ok());
184 }
185
186 #[test]
187 fn clear_non_empty_anchored_package_structure() {
188 let mut packages = AnchoredPackages::new();
189 assert!(packages.insert(AnchoredPackageSetType::Automatic,
190 PinnedAbsolutePackageUrl::parse("fuchsia-pkg://fuchsia.com/package0?hash=0000000000000000000000000000000000000000000000000000000000000000").unwrap()).is_ok());
191 assert!(packages.insert(AnchoredPackageSetType::OnDemand,
192 PinnedAbsolutePackageUrl::parse("fuchsia-pkg://fuchsia.com/package1?hash=1111111111111111111111111111111111111111111111111111111111111111").unwrap()).is_ok());
193 packages.clear(None);
194 let empty = AnchoredPackages::new();
195 assert_eq!(empty, packages);
196 }
197
198 #[test]
199 fn partially_clear_non_empty_anchored_package_structure() {
200 let mut packages = AnchoredPackages::new();
201 assert!(packages.insert(AnchoredPackageSetType::Automatic,
202 PinnedAbsolutePackageUrl::parse("fuchsia-pkg://fuchsia.com/package0?hash=0000000000000000000000000000000000000000000000000000000000000000").unwrap()).is_ok());
203 assert!(packages.insert(AnchoredPackageSetType::OnDemand,
204 PinnedAbsolutePackageUrl::parse("fuchsia-pkg://fuchsia.com/package1?hash=1111111111111111111111111111111111111111111111111111111111111111").unwrap()).is_ok());
205 packages.clear(Some(AnchoredPackageSetType::Automatic));
206 let mut packages_cmp = AnchoredPackages::new();
207 assert_ne!(packages_cmp, packages);
208 assert!(packages_cmp.insert(AnchoredPackageSetType::OnDemand,
209 PinnedAbsolutePackageUrl::parse("fuchsia-pkg://fuchsia.com/package1?hash=1111111111111111111111111111111111111111111111111111111111111111").unwrap()).is_ok());
210 assert_eq!(packages_cmp, packages);
211 }
212
213 #[test]
214 fn insert_into_structure_and_json_deserialize_yield_identical_mappings() {
215 let json = r#" {
216 "anchored_automatic": {
217 "fuchsia-pkg://fuchsia.com/package0": {
218 "hash": "0000000000000000000000000000000000000000000000000000000000000000"
219 }
220 },
221 "anchored_on_demand": {
222 "fuchsia-pkg://fuchsia.com/package1": {
223 "hash": "1111111111111111111111111111111111111111111111111111111111111111"
224 }
225 }
226 }"#;
227 let from_json = AnchoredPackages::from_json(json.as_bytes()).unwrap();
228 let mut from_insert = AnchoredPackages::new();
229 assert!(from_insert.insert(AnchoredPackageSetType::Automatic,
230 PinnedAbsolutePackageUrl::parse("fuchsia-pkg://fuchsia.com/package0?hash=0000000000000000000000000000000000000000000000000000000000000000").unwrap()).is_ok());
231 assert!(from_insert.insert(AnchoredPackageSetType::OnDemand,
232 PinnedAbsolutePackageUrl::parse("fuchsia-pkg://fuchsia.com/package1?hash=1111111111111111111111111111111111111111111111111111111111111111").unwrap()).is_ok());
233 assert_eq!(from_json, from_insert);
234 }
235
236 #[test]
237 fn missing_hash_in_anchored_packages() {
238 let json = r#" {
239 "anchored_automatic": {
240 "fuchsia-pkg://fuchsia.com/package0": {
241 "wrong_key": "0000000000000000000000000000000000000000000000000000000000000000"
242 },
243 "fuchsia-pkg://fuchsia.com/package1": {
244 "hash": "1111111111111111111111111111111111111111111111111111111111111111"
245 }
246 }
247 }"#;
248
249 assert_matches!(
250 AnchoredPackages::from_json(json.as_bytes()),
251 Err(AnchoredPackagesError::JsonError(_))
252 );
253 }
254
255 #[test]
256 fn pinned_package_url_in_anchored_package_list() {
257 let json = r#" {
258 "anchored_automatic": {
259 "fuchsia-pkg://fuchsia.com/package0?hash=0000000000000000000000000000000000000000000000000000000000000000": {
260 "hash": "0000000000000000000000000000000000000000000000000000000000000000"
261 }
262 }
263 }"#;
264
265 assert_matches!(
266 AnchoredPackages::from_json(json.as_bytes()),
267 Err(AnchoredPackagesError::JsonError(_))
268 );
269 }
270
271 #[test]
272 fn multiple_package_sets_in_list() {
273 let (expect, json) = populated_anchored_packages();
274 let got = AnchoredPackages::from_json(json.as_bytes()).unwrap();
275 assert_eq!(got, expect);
276 }
277
278 #[test]
279 fn additional_unused_properties_in_anchored_packages_list() {
280 let json = r#" {
281 "anchored_automatic": {
282 "fuchsia-pkg://fuchsia.com/package0": {
283 "hash": "0000000000000000000000000000000000000000000000000000000000000000",
284 "foo": "0"
285 },
286 "fuchsia-pkg://fuchsia.com/package1": {
287 "hash": "1111111111111111111111111111111111111111111111111111111111111111",
288 "bar": "1"
289 }
290 }
291 }"#;
292 let got = AnchoredPackages::from_json(json.as_bytes()).unwrap();
293 let mut expect = AnchoredPackages::new();
294 assert!(expect.insert(AnchoredPackageSetType::Automatic,
295 PinnedAbsolutePackageUrl::parse("fuchsia-pkg://fuchsia.com/package1?hash=1111111111111111111111111111111111111111111111111111111111111111").unwrap()).is_ok());
296 assert!(expect.insert(AnchoredPackageSetType::Automatic,
297 PinnedAbsolutePackageUrl::parse("fuchsia-pkg://fuchsia.com/package0?hash=0000000000000000000000000000000000000000000000000000000000000000").unwrap()).is_ok());
298 assert_eq!(got, expect);
299 }
300
301 #[test]
302 fn anchored_packages_as_pinned_urls() {
303 let (_, json) = populated_anchored_packages();
304 let mut got_on_demand = AnchoredPackages::from_json(json.as_bytes())
305 .unwrap()
306 .as_pinned(AnchoredPackageSetType::OnDemand);
307 got_on_demand.sort();
308 let mut got_permanent = AnchoredPackages::from_json(json.as_bytes())
309 .unwrap()
310 .as_pinned(AnchoredPackageSetType::Permanent);
311 got_permanent.sort();
312 let expected_on_demand = vec![
313 AbsolutePackageUrl::parse("fuchsia-pkg://fuchsia.com/package0?hash=0000000000000000000000000000000000000000000000000000000000000000").unwrap().pinned().unwrap(),
314 AbsolutePackageUrl::parse("fuchsia-pkg://fuchsia.com/package1?hash=1111111111111111111111111111111111111111111111111111111111111111").unwrap().pinned().unwrap(),
315 ];
316 let expected_permanent = vec![
317 AbsolutePackageUrl::parse("fuchsia-pkg://fuchsia.com/package2?hash=2222222222222222222222222222222222222222222222222222222222222222").unwrap().pinned().unwrap(),
318 AbsolutePackageUrl::parse("fuchsia-pkg://fuchsia.com/package3?hash=3333333333333333333333333333333333333333333333333333333333333333").unwrap().pinned().unwrap(),
319 ];
320 assert_eq!(got_on_demand, expected_on_demand);
321 assert_eq!(got_permanent, expected_permanent);
322 }
323
324 #[test]
325 fn test_serialize_deserialize_round_trip() {
326 let mut bytes = vec![];
327
328 let (packages, _) = populated_anchored_packages();
329 packages.serialize(&mut bytes).unwrap();
330
331 assert_eq!(AnchoredPackages::from_json(&bytes).unwrap(), packages);
332 }
333}