fuchsia_pkg/
package.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#[cfg(test)]
6use crate::MetaSubpackages;
7use crate::{MetaContents, MetaContentsError, MetaPackage, PackageManifest};
8use anyhow::Result;
9use fuchsia_merkle::Hash;
10use fuchsia_url::{PackageName, RelativePackageUrl};
11use std::collections::{BTreeMap, HashMap};
12#[cfg(test)]
13use std::io::{Read, Seek};
14use std::path::PathBuf;
15
16#[derive(Clone, Debug, PartialEq, Eq)]
17pub(crate) struct Package {
18    meta_contents: MetaContents,
19    meta_package: MetaPackage,
20    subpackages: Vec<SubpackageEntry>,
21    blobs: BTreeMap<String, BlobEntry>,
22}
23
24impl Package {
25    /// Get the meta_contents.
26    #[cfg(test)]
27    pub fn meta_contents(&self) -> MetaContents {
28        self.meta_contents.clone()
29    }
30
31    /// Get the meta_package.
32    pub fn meta_package(&self) -> MetaPackage {
33        self.meta_package.clone()
34    }
35
36    /// Get the meta_subpackages.
37    #[cfg(test)]
38    pub fn meta_subpackages(&self) -> MetaSubpackages {
39        MetaSubpackages::from_iter(self.subpackages.iter().map(
40            |SubpackageEntry { name, merkle, package_manifest_path: _ }| (name.clone(), *merkle),
41        ))
42    }
43
44    /// Get the subpackages (including package_manifest.json paths, if known).
45    pub fn subpackages(&self) -> Vec<SubpackageEntry> {
46        self.subpackages.clone()
47    }
48
49    /// Get the blobs.
50    pub fn blobs(&self) -> BTreeMap<String, BlobEntry> {
51        self.blobs.clone()
52    }
53
54    /// Create a new `PackageBuilder` from a package name.
55    pub(crate) fn builder(name: PackageName) -> Builder {
56        Builder::new(name)
57    }
58
59    /// Generate a Package from a meta.far file.
60    #[cfg(test)]
61    pub fn from_meta_far<R: Read + Seek>(
62        mut meta_far: R,
63        blobs: BTreeMap<String, BlobEntry>,
64        subpackages: Vec<SubpackageEntry>,
65    ) -> Result<Self> {
66        let mut meta_far = fuchsia_archive::Utf8Reader::new(&mut meta_far)?;
67        let meta_contents =
68            MetaContents::deserialize(meta_far.read_file(MetaContents::PATH)?.as_slice())?;
69        let meta_package =
70            MetaPackage::deserialize(meta_far.read_file(MetaPackage::PATH)?.as_slice())?;
71        Ok(Package { meta_contents, meta_package, subpackages, blobs })
72    }
73}
74
75pub(crate) struct Builder {
76    contents: HashMap<String, Hash>,
77    meta_package: MetaPackage,
78    subpackages: Vec<SubpackageEntry>,
79    blobs: BTreeMap<String, BlobEntry>,
80}
81
82impl Builder {
83    pub(crate) fn new(name: PackageName) -> Self {
84        Self {
85            contents: HashMap::new(),
86            meta_package: MetaPackage::from_name_and_variant_zero(name),
87            subpackages: Vec::new(),
88            blobs: BTreeMap::new(),
89        }
90    }
91
92    pub(crate) fn add_entry(
93        &mut self,
94        blob_path: String,
95        hash: Hash,
96        source_path: PathBuf,
97        size: u64,
98    ) {
99        if blob_path != PackageManifest::META_FAR_BLOB_PATH {
100            self.contents.insert(blob_path.clone(), hash);
101        }
102        self.blobs.insert(blob_path, BlobEntry { source_path, size, hash });
103    }
104
105    pub(crate) fn add_subpackage(
106        &mut self,
107        name: RelativePackageUrl,
108        merkle: Hash,
109        package_manifest_path: PathBuf,
110    ) {
111        self.subpackages.push(SubpackageEntry { name, merkle, package_manifest_path });
112    }
113
114    pub(crate) fn build(self) -> Result<Package, MetaContentsError> {
115        Ok(Package {
116            meta_contents: MetaContents::from_map(self.contents)?,
117            meta_package: self.meta_package,
118            subpackages: self.subpackages,
119            blobs: self.blobs,
120        })
121    }
122}
123
124#[derive(Clone, Debug, PartialEq, Eq)]
125pub(crate) struct BlobEntry {
126    source_path: PathBuf,
127    hash: Hash,
128    size: u64,
129}
130
131impl BlobEntry {
132    pub fn source_path(&self) -> PathBuf {
133        self.source_path.clone()
134    }
135
136    pub fn size(&self) -> u64 {
137        self.size
138    }
139
140    pub fn hash(&self) -> Hash {
141        self.hash
142    }
143}
144
145#[derive(Clone, Debug, PartialEq, Eq)]
146pub(crate) struct SubpackageEntry {
147    pub name: RelativePackageUrl,
148    pub merkle: Hash,
149    pub package_manifest_path: PathBuf,
150}
151
152#[cfg(test)]
153mod test_package {
154    use super::*;
155    use crate::build::{build_with_file_system, FileSystem};
156    use crate::PackageBuildManifest;
157
158    use maplit::{btreemap, hashmap};
159    use std::fs::File;
160    use std::io;
161    use std::str::FromStr;
162    use tempfile::tempdir;
163
164    fn zeros_hash() -> Hash {
165        "0000000000000000000000000000000000000000000000000000000000000000".parse().unwrap()
166    }
167
168    fn ones_hash() -> Hash {
169        "1111111111111111111111111111111111111111111111111111111111111111".parse().unwrap()
170    }
171
172    #[test]
173    fn test_create_package() {
174        let meta_package = MetaPackage::from_name_and_variant_zero("package-name".parse().unwrap());
175
176        let subpackages = vec![
177            SubpackageEntry {
178                name: RelativePackageUrl::parse("a_0_subpackage").unwrap(),
179                merkle: zeros_hash(),
180                package_manifest_path: PathBuf::from("some/path/to/package_manifest.json"),
181            },
182            SubpackageEntry {
183                name: RelativePackageUrl::parse("other-1-subpackage").unwrap(),
184                merkle: ones_hash(),
185                package_manifest_path: PathBuf::from("another/path/to/package_manifest.json"),
186            },
187        ];
188
189        let meta_subpackages = MetaSubpackages::from_iter(hashmap! {
190            subpackages[0].name.clone() => subpackages[0].merkle,
191            subpackages[1].name.clone() => subpackages[1].merkle,
192        });
193
194        let map = hashmap! {
195        "bin/my_prog".to_string() =>
196            Hash::from_str(
197             "0000000000000000000000000000000000000000000000000000000000000000")
198            .unwrap(),
199        "lib/mylib.so".to_string() =>
200            Hash::from_str(
201               "1111111111111111111111111111111111111111111111111111111111111111")
202            .unwrap(),
203            };
204        let meta_contents = MetaContents::from_map(map).unwrap();
205        let blob_entry = BlobEntry {
206            source_path: PathBuf::from("src/bin/my_prog"),
207            size: 1,
208            hash: Hash::from_str(
209                "0000000000000000000000000000000000000000000000000000000000000000",
210            )
211            .unwrap(),
212        };
213        let blobs = btreemap! {
214            "bin/my_prog".to_string() => blob_entry.clone(),
215            "bin/my_prog2".to_string() => blob_entry,
216        };
217        let package = Package {
218            meta_contents: meta_contents.clone(),
219            meta_package: meta_package.clone(),
220            subpackages: subpackages.clone(),
221            blobs: blobs.clone(),
222        };
223        assert_eq!(meta_package, package.meta_package());
224        assert_eq!(meta_subpackages, package.meta_subpackages());
225        assert_eq!(subpackages, package.subpackages());
226        assert_eq!(meta_contents, package.meta_contents());
227        assert_eq!(blobs, package.blobs());
228    }
229
230    struct FakeFileSystem {
231        content_map: HashMap<String, Vec<u8>>,
232    }
233
234    impl<'a> FileSystem<'a> for FakeFileSystem {
235        type File = &'a [u8];
236        fn open(&'a self, path: &str) -> Result<Self::File, io::Error> {
237            Ok(self.content_map.get(path).unwrap().as_slice())
238        }
239        fn len(&self, path: &str) -> Result<u64, io::Error> {
240            Ok(self.content_map.get(path).unwrap().len() as u64)
241        }
242        fn read(&self, path: &str) -> Result<Vec<u8>, io::Error> {
243            Ok(self.content_map.get(path).unwrap().clone())
244        }
245    }
246
247    #[test]
248    fn test_from_meta_far_valid_meta_far() {
249        let outdir = tempdir().unwrap();
250        let meta_far_path = outdir.path().join("base.far");
251
252        let creation_manifest = PackageBuildManifest::from_external_and_far_contents(
253            btreemap! {
254                "lib/mylib.so".to_string() => "host/mylib.so".to_string()
255            },
256            btreemap! {
257                "meta/my_component.cml".to_string() => "host/my_component.cml".to_string(),
258                "meta/package".to_string() => "host/meta/package".to_string()
259            },
260        )
261        .unwrap();
262        let component_manifest_contents = "my_component.cml contents";
263        let mut meta_package_json_bytes = vec![];
264        let meta_package =
265            MetaPackage::from_name_and_variant_zero("my-package-name".parse().unwrap());
266        meta_package.serialize(&mut meta_package_json_bytes).unwrap();
267
268        let subpackages = vec![
269            SubpackageEntry {
270                name: RelativePackageUrl::parse("a_0_subpackage").unwrap(),
271                merkle: zeros_hash(),
272                package_manifest_path: PathBuf::from("some/path/to/package_manifest.json"),
273            },
274            SubpackageEntry {
275                name: RelativePackageUrl::parse("other-1-subpackage").unwrap(),
276                merkle: ones_hash(),
277                package_manifest_path: PathBuf::from("another/path/to/package_manifest.json"),
278            },
279        ];
280
281        let meta_subpackages = MetaSubpackages::from_iter(hashmap! {
282            subpackages[0].name.clone() => subpackages[0].merkle,
283            subpackages[1].name.clone() => subpackages[1].merkle,
284        });
285
286        let mut meta_subpackages_json_bytes = vec![];
287        meta_subpackages.serialize(&mut meta_subpackages_json_bytes).unwrap();
288
289        let file_system = FakeFileSystem {
290            content_map: hashmap! {
291                "host/mylib.so".to_string() => "mylib.so contents".as_bytes().to_vec(),
292                "host/my_component.cml".to_string() => component_manifest_contents.as_bytes().to_vec(),
293                format!("host/{}", MetaPackage::PATH) => meta_package_json_bytes,
294                format!("host/{}", MetaSubpackages::PATH) => meta_subpackages_json_bytes,
295            },
296        };
297
298        build_with_file_system(
299            &creation_manifest,
300            &meta_far_path,
301            "my-package-name",
302            subpackages.clone(),
303            None,
304            &file_system,
305        )
306        .unwrap();
307
308        let blob_entry = BlobEntry {
309            source_path: PathBuf::from("src/bin/my_prog"),
310            size: 1,
311            hash: Hash::from_str(
312                "0000000000000000000000000000000000000000000000000000000000000000",
313            )
314            .unwrap(),
315        };
316        let blobs = btreemap! {
317            "bin/my_prog".to_string() => blob_entry.clone(),
318            "bin/my_prog2".to_string() => blob_entry,
319        };
320        let package =
321            Package::from_meta_far(File::open(&meta_far_path).unwrap(), blobs.clone(), subpackages)
322                .unwrap();
323        assert_eq!(blobs, package.blobs());
324        assert_eq!(
325            &"my-package-name".parse::<PackageName>().unwrap(),
326            package.meta_package().name()
327        );
328        assert_eq!(
329            package.meta_subpackages().subpackages().get(&"a_0_subpackage".try_into().unwrap()),
330            Some(&zeros_hash())
331        );
332        assert_eq!(
333            package.meta_subpackages().subpackages().get(&"other-1-subpackage".try_into().unwrap()),
334            Some(&ones_hash())
335        );
336    }
337
338    #[test]
339    fn test_from_meta_far_empty_meta_far() {
340        let dir = tempdir().unwrap();
341        let file_path = dir.path().join("meta.far");
342        File::create(&file_path).unwrap();
343        let file = File::open(&file_path).unwrap();
344        let blob_entry = BlobEntry {
345            source_path: PathBuf::from("src/bin/my_prog"),
346            size: 1,
347            hash: Hash::from_str(
348                "0000000000000000000000000000000000000000000000000000000000000000",
349            )
350            .unwrap(),
351        };
352        let blobs = btreemap! {
353            "bin/my_prog".to_string() => blob_entry.clone(),
354            "bin/my_prog2".to_string() => blob_entry,
355        };
356        let package = Package::from_meta_far(file, blobs, Vec::new());
357        assert!(package.is_err());
358    }
359}