1use 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 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 let build_tmp = tempfile::tempdir().unwrap();
268 let build_path = build_tmp.path();
269
270 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(×tamp_keys)
288 .stage_root()
289 .unwrap();
290
291 for name in ["package1", "package2"] {
293 let (meta_far_path, manifest) = make_package_manifest(name, build_path, Vec::new());
294
295 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 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"))]
393pub 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}