fuchsia_pkg/
meta_subpackages.rs

1// Copyright 2022 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
5use crate::errors::MetaSubpackagesError;
6use fuchsia_merkle::Hash;
7use fuchsia_url::RelativePackageUrl;
8use serde::ser::Serializer;
9use serde::{Deserialize, Serialize};
10use std::collections::{BTreeMap, HashMap};
11use std::io;
12
13/// A `MetaSubpackages` represents the "meta/fuchsia.pkg/subpackages" file of a Fuchsia
14/// archive file of a Fuchsia package. It validates that all subpackage names
15/// are valid.
16#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
17#[serde(transparent)]
18pub struct MetaSubpackages(VersionedMetaSubpackages);
19
20impl MetaSubpackages {
21    pub const PATH: &'static str = "meta/fuchsia.pkg/subpackages";
22
23    fn from_v1(meta_subpackages_v1: MetaSubpackagesV1) -> Self {
24        Self(VersionedMetaSubpackages::Version1(meta_subpackages_v1))
25    }
26
27    /// Get the map from subpackage names to Merkle Tree root hashes.
28    pub fn subpackages(&self) -> &HashMap<RelativePackageUrl, Hash> {
29        match &self.0 {
30            VersionedMetaSubpackages::Version1(meta) => &meta.subpackages,
31        }
32    }
33
34    /// Take the map from subpackage names to Merkle Tree root hashes.
35    pub fn into_subpackages(self) -> HashMap<RelativePackageUrl, Hash> {
36        match self.0 {
37            VersionedMetaSubpackages::Version1(meta) => meta.subpackages,
38        }
39    }
40
41    /// Take the Merkle Tree root hashes in an iterator. The returned iterator may include
42    /// duplicates.
43    pub fn into_hashes_undeduplicated(self) -> impl Iterator<Item = Hash> {
44        self.into_subpackages().into_values()
45    }
46
47    pub fn deserialize(reader: impl io::BufRead) -> Result<Self, MetaSubpackagesError> {
48        Ok(MetaSubpackages::from_v1(serde_json::from_reader(reader)?))
49    }
50
51    pub fn serialize(&self, writer: impl io::Write) -> Result<(), MetaSubpackagesError> {
52        Ok(serde_json::to_writer(writer, &self)?)
53    }
54}
55
56impl FromIterator<(RelativePackageUrl, Hash)> for MetaSubpackages {
57    fn from_iter<T>(iter: T) -> Self
58    where
59        T: IntoIterator<Item = (RelativePackageUrl, Hash)>,
60    {
61        MetaSubpackages(VersionedMetaSubpackages::Version1(MetaSubpackagesV1 {
62            subpackages: HashMap::from_iter(iter),
63        }))
64    }
65}
66
67#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
68#[serde(tag = "version", deny_unknown_fields)]
69enum VersionedMetaSubpackages {
70    #[serde(rename = "1")]
71    Version1(MetaSubpackagesV1),
72}
73
74impl Default for VersionedMetaSubpackages {
75    fn default() -> Self {
76        VersionedMetaSubpackages::Version1(MetaSubpackagesV1::default())
77    }
78}
79
80#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
81struct MetaSubpackagesV1 {
82    subpackages: HashMap<RelativePackageUrl, Hash>,
83}
84
85impl Serialize for MetaSubpackagesV1 {
86    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
87    where
88        S: Serializer,
89    {
90        let MetaSubpackagesV1 { subpackages } = self;
91
92        // Sort the subpackages list to make sure it's in a consistent order.
93
94        #[derive(Serialize)]
95        struct Helper<'a> {
96            subpackages: BTreeMap<&'a RelativePackageUrl, &'a Hash>,
97        }
98
99        Helper { subpackages: subpackages.iter().collect() }.serialize(serializer)
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106    use crate::test::*;
107    use fuchsia_url::test::random_relative_package_url;
108    use maplit::hashmap;
109    use proptest::prelude::*;
110    use serde_json::json;
111
112    fn zeros_hash() -> Hash {
113        "0000000000000000000000000000000000000000000000000000000000000000".parse().unwrap()
114    }
115
116    fn ones_hash() -> Hash {
117        "1111111111111111111111111111111111111111111111111111111111111111".parse().unwrap()
118    }
119
120    #[test]
121    fn deserialize_known_file() {
122        let bytes = r#"{
123                "version": "1",
124                "subpackages": {
125                    "a_0_subpackage": "0000000000000000000000000000000000000000000000000000000000000000",
126                    "other-1-subpackage": "1111111111111111111111111111111111111111111111111111111111111111"
127                }
128            }"#.as_bytes();
129        let meta_subpackages = MetaSubpackages::deserialize(bytes).unwrap();
130        let expected_subpackages = hashmap! {
131            RelativePackageUrl::parse("a_0_subpackage").unwrap() => zeros_hash(),
132            RelativePackageUrl::parse("other-1-subpackage").unwrap() => ones_hash(),
133        };
134        assert_eq!(meta_subpackages.subpackages(), &expected_subpackages);
135        assert_eq!(meta_subpackages.into_subpackages(), expected_subpackages);
136    }
137
138    proptest! {
139        #![proptest_config(ProptestConfig{
140            failure_persistence: None,
141            ..Default::default()
142        })]
143
144        #[test]
145        fn serialize(
146            ref path0 in random_relative_package_url(),
147            ref hex0 in random_hash(),
148            ref path1 in random_relative_package_url(),
149            ref hex1 in random_hash())
150        {
151            prop_assume!(path0 != path1);
152            let map = hashmap! {
153                path0.clone() => *hex0,
154                path1.clone() => *hex1,
155            };
156            let meta_subpackages = MetaSubpackages::from_iter(map);
157
158            prop_assert_eq!(
159                serde_json::to_value(meta_subpackages).unwrap(),
160                json!(
161                    {
162                        "version": "1",
163                        "subpackages": {
164                            path0: hex0,
165                            path1: hex1,
166                        }
167                    }
168                )
169            );
170        }
171
172        #[test]
173        fn serialize_deserialize_is_id(
174            subpackages in prop::collection::hash_map(
175                random_relative_package_url(), random_hash(), 0..4)
176        ) {
177            let meta_subpackages = MetaSubpackages::from_iter(subpackages);
178            let deserialized = MetaSubpackages::deserialize(
179                &*serde_json::to_vec(&meta_subpackages).unwrap()
180            )
181            .unwrap();
182            prop_assert_eq!(meta_subpackages, deserialized);
183        }
184    }
185}