Skip to main content

fuchsia_repo/
test_utils.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
5use crate::repo_keys::RepoKeys;
6use crate::repository::PmRepository;
7use crate::util::copy_dir;
8use camino::{Utf8Path, Utf8PathBuf};
9use chrono::{DateTime, Utc};
10use fidl_fuchsia_pkg_ext::RepositoryKey;
11use fuchsia_hash::Hash;
12use fuchsia_pkg::{PackageBuilder, PackageManifest};
13use fuchsia_url::RelativePackageUrl;
14use futures::io::AllowStdIo;
15use maplit::hashmap;
16use std::collections::HashSet;
17use std::fs::{File, create_dir, create_dir_all};
18use std::path::{Path, PathBuf};
19use tempfile::TempDir;
20use tuf::crypto::{Ed25519PrivateKey, HashAlgorithm};
21use tuf::metadata::{Delegation, Delegations, MetadataDescription, MetadataPath, TargetPath};
22use tuf::pouf::Pouf1;
23use tuf::repo_builder::RepoBuilder;
24use tuf::repository::FileSystemRepositoryBuilder;
25
26#[cfg(not(target_os = "fuchsia"))]
27use crate::repo_client::RepoClient;
28#[cfg(not(target_os = "fuchsia"))]
29use crate::repository::{FileSystemRepository, RepoProvider};
30#[cfg(not(target_os = "fuchsia"))]
31use anyhow::anyhow;
32
33const EMPTY_REPO_PATH: &str = concat!(env!("ROOT_OUT_DIR"), "/test_data/ffx_lib_pkg/empty-repo");
34
35#[cfg(not(target_os = "fuchsia"))]
36#[cfg(test)]
37pub(crate) const PKG1_HASH: &str =
38    "2881455493b5870aaea36537d70a2adc635f516ac2092598f4b6056dabc6b25d";
39
40#[cfg(not(target_os = "fuchsia"))]
41#[cfg(test)]
42pub(crate) const PKG2_HASH: &str =
43    "050907f009ff634f9aa57bff541fb9e9c2c62b587c23578e77637cda3bd69458";
44
45#[cfg(not(target_os = "fuchsia"))]
46#[cfg(test)]
47pub(crate) const PKG1_BIN_HASH: &str =
48    "72e1e7a504f32edf4f23e7e8a3542c1d77d12541142261cfe272decfa75f542d";
49
50#[cfg(not(target_os = "fuchsia"))]
51#[cfg(test)]
52pub(crate) const PKG1_LIB_HASH: &str =
53    "8a8a5f07f935a4e8e1fd1a1eda39da09bb2438ec0adfb149679ddd6e7e1fbb4f";
54
55#[cfg(not(target_os = "fuchsia"))]
56#[cfg(test)]
57pub(crate) const PKG2_BIN_HASH: &str =
58    "548981eb310ddc4098fb5c63692e19ac4ae287b13d0e911fbd9f7819ac22491c";
59
60#[cfg(not(target_os = "fuchsia"))]
61#[cfg(test)]
62pub(crate) const PKG2_LIB_HASH: &str =
63    "ecc11f7f4b763c5a21be2b4159c9818bbe22ca7e6d8100a72f6a41d3d7b827a9";
64
65#[cfg(not(target_os = "fuchsia"))]
66#[cfg(test)]
67pub(crate) const ANONSUBPKG_HASH: &str =
68    "1d20cc5163f35cd977cf414d54f8d5b53bcb33944cff0b9560b0627a6bba7441";
69
70#[cfg(not(target_os = "fuchsia"))]
71#[cfg(test)]
72pub(crate) const ANONSUBPKG_BIN_HASH: &str =
73    "7fb2c78b2ae5ce6b591c4b017b382e5d4f1dc2a6f3e6977cac735632bbffec1f";
74
75#[cfg(not(target_os = "fuchsia"))]
76#[cfg(test)]
77pub(crate) const ANONSUBPKG_LIB_HASH: &str =
78    "8dda14b0bc837a825c71a8534e402b5c7d4dfbb7348f429c017e57d547be80df";
79
80#[cfg(not(target_os = "fuchsia"))]
81#[cfg(test)]
82pub(crate) const NAMEDSUBPKG_HASH: &str =
83    "1e67965c0601afbe00e43eb5c50509813400f1ae0ec838b44dfa018a8afcdeac";
84
85#[cfg(not(target_os = "fuchsia"))]
86#[cfg(test)]
87pub(crate) const NAMEDSUBPKG_BIN_HASH: &str =
88    "5c8e2b5f4f5be036f9694842767a226c776c89e21bf6705e89d03ab4543fea2e";
89
90#[cfg(not(target_os = "fuchsia"))]
91#[cfg(test)]
92pub(crate) const NAMEDSUBPKG_LIB_HASH: &str =
93    "208995a7397ea03e5b567a966690336d3c2cbbbf03bea95a4ee95724ac42f2e6";
94
95#[cfg(not(target_os = "fuchsia"))]
96#[cfg(test)]
97pub(crate) const SUPERPKG_HASH: &str =
98    "cb174413bb34d960976f6b82ef6e6d3d3a36ddccdec142faf3d79f76c4baa676";
99
100#[cfg(not(target_os = "fuchsia"))]
101#[cfg(test)]
102pub(crate) const SUPERPKG_BIN_HASH: &str =
103    "5ece8a1e67d2f16b861ad282667760cda93684b5ab5c80c4be6b9be47395b2b1";
104
105#[cfg(not(target_os = "fuchsia"))]
106#[cfg(test)]
107pub(crate) const SUPERPKG_LIB_HASH: &str =
108    "b54bb3adf0fc9492d5e12cce9cf0b3dc501075397e8eedb77991503139c1ad40";
109
110pub fn repo_key() -> RepositoryKey {
111    RepositoryKey::Ed25519(
112        [
113            29, 76, 86, 76, 184, 70, 108, 73, 249, 127, 4, 47, 95, 63, 36, 35, 101, 255, 212, 33,
114            10, 154, 26, 130, 117, 157, 125, 88, 175, 214, 109, 113,
115        ]
116        .to_vec(),
117    )
118}
119
120pub fn repo_private_key() -> Ed25519PrivateKey {
121    Ed25519PrivateKey::from_ed25519(&[
122        80, 121, 161, 145, 5, 165, 178, 98, 248, 146, 132, 195, 60, 32, 72, 122, 150, 223, 124,
123        216, 217, 43, 74, 9, 221, 38, 156, 113, 181, 63, 234, 98, 190, 11, 152, 63, 115, 150, 218,
124        103, 92, 64, 198, 185, 62, 71, 252, 237, 124, 30, 158, 168, 163, 42, 31, 233, 82, 186, 143,
125        81, 151, 96, 179, 7,
126    ])
127    .unwrap()
128}
129
130pub fn make_repo_keys() -> RepoKeys {
131    let keys_dir = Utf8PathBuf::from(EMPTY_REPO_PATH).join("keys");
132    let repo_keys = RepoKeys::from_dir(keys_dir.as_std_path()).unwrap();
133
134    assert_eq!(repo_keys.root_keys().len(), 1);
135    assert_eq!(repo_keys.targets_keys().len(), 1);
136    assert_eq!(repo_keys.snapshot_keys().len(), 1);
137    assert_eq!(repo_keys.timestamp_keys().len(), 1);
138
139    repo_keys
140}
141
142pub fn make_repo_keys_dir(root: &Utf8Path) {
143    let src = PathBuf::from(EMPTY_REPO_PATH).canonicalize().unwrap();
144    copy_dir(&src.join("keys"), root.as_std_path()).unwrap();
145}
146
147pub fn make_empty_pm_repo_dir(root: &Utf8Path) {
148    let src = PathBuf::from(EMPTY_REPO_PATH).canonicalize().unwrap();
149    copy_dir(&src, root.as_std_path()).unwrap();
150}
151
152#[cfg(not(target_os = "fuchsia"))]
153pub async fn make_readonly_empty_repository() -> anyhow::Result<RepoClient<Box<dyn RepoProvider>>> {
154    let backend = PmRepository::new(Utf8PathBuf::from(EMPTY_REPO_PATH));
155    let mut client = RepoClient::from_trusted_remote(Box::new(backend) as Box<_>)
156        .await
157        .map_err(|e| anyhow!(e))?;
158    client.update().await?;
159    Ok(client)
160}
161
162#[cfg(not(target_os = "fuchsia"))]
163pub async fn make_writable_empty_repository(
164    root: Utf8PathBuf,
165) -> anyhow::Result<RepoClient<Box<dyn RepoProvider>>> {
166    make_empty_pm_repo_dir(&root);
167    let backend = PmRepository::new(root);
168    let mut client = RepoClient::from_trusted_remote(Box::new(backend) as Box<_>).await?;
169    client.update().await?;
170    Ok(client)
171}
172
173pub fn make_package_manifest(
174    name: &str,
175    build_path: &Path,
176    subpackages: Vec<(RelativePackageUrl, Hash, PathBuf)>,
177) -> (PathBuf, PackageManifest) {
178    make_package_manifest_with_abi_revision(
179        name,
180        build_path,
181        subpackages,
182        // ABI revision for API level 7
183        0xECCEA2F70ACD6FC0.into(),
184    )
185}
186
187pub fn make_package_manifest_with_abi_revision(
188    name: &str,
189    build_path: &Path,
190    subpackages: Vec<(RelativePackageUrl, Hash, PathBuf)>,
191    abi_revision: version_history::AbiRevision,
192) -> (PathBuf, PackageManifest) {
193    let package_path = build_path.join(name);
194
195    let mut builder = PackageBuilder::new(name, abi_revision);
196
197    builder
198        .add_contents_as_blob(
199            format!("bin/{name}"),
200            format!("binary {name}").as_bytes(),
201            &package_path,
202        )
203        .unwrap();
204    builder
205        .add_contents_as_blob(
206            format!("lib/{name}"),
207            format!("lib {name}").as_bytes(),
208            &package_path,
209        )
210        .unwrap();
211    builder
212        .add_contents_to_far(
213            format!("meta/{name}.cm"),
214            format!("cm {name}").as_bytes(),
215            &package_path,
216        )
217        .unwrap();
218    builder
219        .add_contents_to_far(
220            format!("meta/{name}.cmx"),
221            format!("cmx {name}").as_bytes(),
222            &package_path,
223        )
224        .unwrap();
225
226    for (name, hash, manifest_path) in subpackages {
227        builder.add_subpackage(&name, hash, manifest_path).unwrap();
228    }
229
230    let meta_far_path = package_path.join("meta.far");
231    let manifest = builder.build(&package_path, &meta_far_path).unwrap();
232
233    (meta_far_path, manifest)
234}
235
236pub async fn make_package_archive(archive_name: &str, outdir: &Path) -> Utf8PathBuf {
237    let build_path = TempDir::new().unwrap();
238    let package_path = build_path.path().join(archive_name);
239    let (_, manifest) = make_package_manifest(archive_name, build_path.path(), Vec::new());
240    let far_file = format!("{archive_name}.far");
241
242    let archive_path = outdir.join(&far_file);
243    let archive_file = File::create(archive_path.clone()).unwrap();
244    manifest.archive(&package_path, &archive_file).await.unwrap();
245    Utf8PathBuf::from_path_buf(archive_path).expect("convert archive pathbuf to utf8pathbuf")
246}
247
248pub async fn make_pm_repo_dir(repo_dir: &Path) {
249    let keys_dir = repo_dir.join("keys");
250    create_dir_all(&keys_dir).unwrap();
251    copy_dir(Utf8PathBuf::from(EMPTY_REPO_PATH).join("keys").as_std_path(), &keys_dir).unwrap();
252
253    let metadata_dir = repo_dir.join("repository");
254    let blobs_dir = metadata_dir.join("blobs");
255    make_repo_dir(&metadata_dir, &blobs_dir, None).await;
256}
257
258pub async fn make_repo_dir(
259    metadata_dir: &Path,
260    blobs_dir: &Path,
261    current_time: Option<DateTime<Utc>>,
262) {
263    create_dir_all(metadata_dir).unwrap();
264    create_dir_all(blobs_dir).unwrap();
265
266    // Construct some packages for the repository.
267    let build_tmp = tempfile::tempdir().unwrap();
268    let build_path = build_tmp.path();
269
270    // Write TUF metadata
271    let repo =
272        FileSystemRepositoryBuilder::<Pouf1>::new(metadata_dir).targets_prefix("targets").build();
273
274    let repo_keys = make_repo_keys();
275    let root_keys = repo_keys.root_keys().iter().map(|k| &**k).collect::<Vec<_>>();
276    let targets_keys = repo_keys.targets_keys().iter().map(|k| &**k).collect::<Vec<_>>();
277    let snapshot_keys = repo_keys.snapshot_keys().iter().map(|k| &**k).collect::<Vec<_>>();
278    let timestamp_keys = repo_keys.timestamp_keys().iter().map(|k| &**k).collect::<Vec<_>>();
279
280    let current_time = if let Some(t) = current_time { t } else { Utc::now() };
281
282    let mut builder = RepoBuilder::create(repo)
283        .current_time(current_time)
284        .trusted_root_keys(&root_keys)
285        .trusted_targets_keys(&targets_keys)
286        .trusted_snapshot_keys(&snapshot_keys)
287        .trusted_timestamp_keys(&timestamp_keys)
288        .stage_root()
289        .unwrap();
290
291    // Add all the packages to the metadata.
292    for name in ["package1", "package2"] {
293        let (meta_far_path, manifest) = make_package_manifest(name, build_path, Vec::new());
294
295        // Copy the package blobs into the blobs directory.
296        let mut meta_far_merkle = None;
297        for blob in manifest.blobs() {
298            let merkle = blob.merkle.to_string();
299
300            if blob.path == "meta/" {
301                meta_far_merkle = Some(merkle.clone());
302            }
303
304            let blob_type = delivery_blob::DeliveryBlobType::Type1;
305            crate::repository::file_system::generate_delivery_blob(
306                blob.source_path.as_str().into(),
307                &Utf8PathBuf::from_path_buf(
308                    blobs_dir.join(format!("{}/{merkle}", u32::from(blob_type))),
309                )
310                .unwrap(),
311                blob_type,
312            )
313            .await
314            .unwrap();
315        }
316        builder = builder
317            .add_target_with_custom(
318                TargetPath::new(format!("{name}/0")).unwrap(),
319                AllowStdIo::new(File::open(meta_far_path).unwrap()),
320                hashmap! { "merkle".into() => meta_far_merkle.unwrap().into() },
321            )
322            .await
323            .unwrap();
324    }
325
326    // Even though we don't use delegations, add a simple one to make sure we at least preserve them
327    // when we modify repositories.
328    let delegations_keys = targets_keys.clone();
329    let delegations = Delegations::new(
330        delegations_keys
331            .iter()
332            .map(|k| (k.public().key_id().clone(), k.public().clone()))
333            .collect(),
334        vec![
335            Delegation::new(
336                MetadataPath::new("delegation").unwrap(),
337                false,
338                1,
339                delegations_keys.iter().map(|k| k.public().key_id().clone()).collect(),
340                HashSet::from([TargetPath::new("some-delegated-target").unwrap()]),
341            )
342            .unwrap(),
343        ],
344    )
345    .unwrap();
346
347    builder
348        .stage_targets_with_builder(|b| b.delegations(delegations))
349        .unwrap()
350        .stage_snapshot_with_builder(|b| {
351            b.insert_metadata_description(
352                MetadataPath::new("delegation").unwrap(),
353                MetadataDescription::from_slice(&[0u8], 1, &[HashAlgorithm::Sha256]).unwrap(),
354            )
355        })
356        .unwrap()
357        .commit()
358        .await
359        .unwrap();
360}
361
362pub async fn make_pm_repository(dir: impl Into<Utf8PathBuf>) -> PmRepository {
363    let dir = dir.into();
364    let metadata_dir = dir.join("repository");
365    let blobs_dir = metadata_dir.join("blobs");
366    make_repo_dir(metadata_dir.as_std_path(), blobs_dir.as_std_path(), None).await;
367
368    let keys_dir = dir.join("keys");
369    create_dir(&keys_dir).unwrap();
370
371    let empty_repo_dir = PathBuf::from(EMPTY_REPO_PATH).canonicalize().unwrap();
372    copy_dir(&empty_repo_dir.join("keys"), keys_dir.as_std_path()).unwrap();
373
374    PmRepository::new(dir)
375}
376
377#[cfg(not(target_os = "fuchsia"))]
378pub async fn make_file_system_repository(
379    metadata_dir: impl Into<Utf8PathBuf>,
380    blobs_dir: impl Into<Utf8PathBuf>,
381) -> RepoClient<Box<dyn RepoProvider>> {
382    let metadata_dir = metadata_dir.into();
383    let blobs_dir = blobs_dir.into();
384    make_repo_dir(metadata_dir.as_std_path(), blobs_dir.as_std_path(), None).await;
385
386    let backend = FileSystemRepository::new(metadata_dir, blobs_dir);
387    let mut client = RepoClient::from_trusted_remote(Box::new(backend) as Box<_>).await.unwrap();
388    client.update().await.unwrap();
389    client
390}
391
392#[cfg(not(target_os = "fuchsia"))]
393/// Given the path to the blobstore, and the hash of a blob within it, read the
394/// the delivery blob and return its decompressed contents.  This fn assumes
395/// Type1 delivery blobs as callers are not specifying the delivery blob type
396/// at this time, and using the default.
397pub fn read_blob_from_repo(blobs_dir: &Utf8Path, hash: &str) -> Vec<u8> {
398    let blob_path = blobs_dir.join(format!("1/{hash}"));
399    let delivery_blob = std::fs::read(&blob_path).unwrap();
400    delivery_blob::decompress(&delivery_blob).unwrap()
401}