1use fidl_fuchsia_io as fio;
6use fuchsia_url::fuchsia_pkg::{AbsoluteComponentUrl, PinnedAbsolutePackageUrl};
7use serde_json::json;
8use sha2::Digest as _;
9use std::collections::BTreeMap;
10
11pub struct FakeUpdatePackage {
13 update_pkg: update_package::UpdatePackage,
14 temp_dir: tempfile::TempDir,
15 packages: Vec<String>,
16}
17
18impl FakeUpdatePackage {
19 #[allow(clippy::new_without_default)]
21 pub fn new() -> Self {
22 let temp_dir = tempfile::tempdir().expect("/tmp to exist");
23 let update_pkg_proxy = fuchsia_fs::directory::open_in_namespace(
24 temp_dir.path().to_str().unwrap(),
25 fio::PERM_READABLE,
26 )
27 .expect("temp dir to open");
28 Self {
29 temp_dir,
30 update_pkg: update_package::UpdatePackage::new(update_pkg_proxy),
31 packages: vec![],
32 }
33 }
34
35 pub async fn add_file(
37 self,
38 path: impl AsRef<std::path::Path>,
39 contents: impl AsRef<[u8]>,
40 ) -> Self {
41 let path = path.as_ref();
42 match path.parent() {
43 Some(empty) if empty == std::path::Path::new("") => {}
44 None => {}
45 Some(parent) => std::fs::create_dir_all(self.temp_dir.path().join(parent)).unwrap(),
46 }
47 fuchsia_fs::file::write_in_namespace(
48 self.temp_dir.path().join(path).to_str().unwrap(),
49 contents,
50 )
51 .await
52 .expect("create test update package file");
53 self
54 }
55
56 pub async fn add_package(mut self, package_url: impl Into<String>) -> Self {
58 self.packages.push(package_url.into());
59 let packages_json = json!({
60 "version": "1",
61 "content": self.packages,
62 })
63 .to_string();
64 self.add_file("packages.json", packages_json).await
65 }
66
67 pub async fn hash(self, hash: impl AsRef<[u8]>) -> Self {
69 self.add_file("meta", hash).await
70 }
71}
72
73impl std::ops::Deref for FakeUpdatePackage {
74 type Target = update_package::UpdatePackage;
75
76 fn deref(&self) -> &Self::Target {
77 &self.update_pkg
78 }
79}
80
81pub fn make_packages_json<'a>(urls: impl AsRef<[&'a str]>) -> String {
85 json!({
86 "version": "1",
87 "content": urls.as_ref(),
88 })
89 .to_string()
90}
91
92pub const SOURCE_EPOCH: u64 = 1;
100
101pub fn make_epoch_json(epoch: u64) -> String {
103 serde_json::to_string(&epoch::EpochFile::Version1 { epoch }).unwrap()
104}
105
106pub fn make_current_epoch_json() -> String {
108 make_epoch_json(SOURCE_EPOCH)
109}
110
111const IMAGES_PACKAGE_NAME: &str = "update-images";
112
113pub struct UpdatePackageBuilder {
115 images_package_repo: fuchsia_url::RepositoryUrl,
116 epoch: Option<u64>,
117 packages: Vec<PinnedAbsolutePackageUrl>,
118 fuchsia_image: Option<(Vec<u8>, Option<Vec<u8>>)>,
119 recovery_image: Option<(Vec<u8>, Option<Vec<u8>>)>,
120 images_package_name: Option<fuchsia_url::PackageName>,
121 firmware_images: BTreeMap<String, Vec<u8>>,
122}
123
124impl UpdatePackageBuilder {
125 pub fn new(images_package_repo: fuchsia_url::RepositoryUrl) -> Self {
131 Self {
132 images_package_repo,
133 epoch: None,
134 packages: vec![],
135 fuchsia_image: None,
136 recovery_image: None,
137 images_package_name: None,
138 firmware_images: BTreeMap::new(),
139 }
140 }
141
142 pub fn epoch(mut self, epoch: u64) -> Self {
146 assert_eq!(self.epoch, None);
147 self.epoch = Some(epoch);
148 self
149 }
150
151 pub fn packages(mut self, packages: Vec<PinnedAbsolutePackageUrl>) -> Self {
153 assert_eq!(self.packages, vec![]);
154 self.packages = packages;
155 self
156 }
157
158 pub fn fuchsia_image(mut self, zbi: Vec<u8>, vbmeta: Option<Vec<u8>>) -> Self {
160 assert_eq!(self.fuchsia_image, None);
161 self.fuchsia_image = Some((zbi, vbmeta));
162 self
163 }
164
165 pub fn recovery_image(mut self, zbi: Vec<u8>, vbmeta: Option<Vec<u8>>) -> Self {
167 assert_eq!(self.recovery_image, None);
168 self.recovery_image = Some((zbi, vbmeta));
169 self
170 }
171
172 pub fn images_package_name(mut self, name: fuchsia_url::PackageName) -> Self {
175 assert_eq!(self.images_package_name, None);
176 self.images_package_name = Some(name);
177 self
178 }
179
180 pub fn firmware_images(mut self, images: BTreeMap<String, Vec<u8>>) -> Self {
182 assert_eq!(self.firmware_images, BTreeMap::new());
183 self.firmware_images = images;
184 self
185 }
186
187 pub async fn build(self) -> (UpdatePackage, crate::Package) {
190 let Self {
191 images_package_repo,
192 epoch,
193 packages,
194 fuchsia_image,
195 recovery_image,
196 images_package_name,
197 firmware_images,
198 } = self;
199 let images_package_name =
200 images_package_name.unwrap_or_else(|| IMAGES_PACKAGE_NAME.parse().unwrap());
201
202 let mut update = crate::PackageBuilder::new("update")
203 .add_resource_at(
204 "packages.json",
205 update_package::serialize_packages_json(&packages).unwrap().as_slice(),
206 )
207 .add_resource_at(
208 "epoch.json",
209 make_epoch_json(epoch.unwrap_or(SOURCE_EPOCH)).as_bytes(),
210 );
211
212 let mut images = crate::PackageBuilder::new(images_package_name.clone());
213 if let Some((zbi, vbmeta)) = &fuchsia_image {
214 images = images.add_resource_at("zbi", zbi.as_slice());
215 if let Some(vbmeta) = vbmeta.as_ref() {
216 images = images.add_resource_at("vbmeta", vbmeta.as_slice());
217 }
218 }
219 if let Some((zbi, vbmeta)) = &recovery_image {
220 images = images.add_resource_at("recovery-zbi", zbi.as_slice());
221 if let Some(vbmeta) = vbmeta.as_ref() {
222 images = images.add_resource_at("recovery-vbmeta", vbmeta.as_slice());
223 }
224 }
225 for (type_, content) in &firmware_images {
226 images = images.add_resource_at(format!("firmware-{type_}"), content.as_slice());
227 }
228 let images = images.build().await.unwrap();
229
230 let mut images_manifest = update_package::ImagePackagesManifest::builder();
231 if let Some((zbi, vbmeta)) = &fuchsia_image {
232 images_manifest.fuchsia_package(
233 image_metadata(
234 zbi,
235 AbsoluteComponentUrl::new(
236 images_package_repo.clone(),
237 images_package_name.clone(),
238 None,
239 Some(*images.hash()),
240 "zbi".to_owned(),
241 )
242 .unwrap(),
243 ),
244 vbmeta.as_ref().map(|v| {
245 image_metadata(
246 v,
247 AbsoluteComponentUrl::new(
248 images_package_repo.clone(),
249 images_package_name.clone(),
250 None,
251 Some(*images.hash()),
252 "vbmeta".to_owned(),
253 )
254 .unwrap(),
255 )
256 }),
257 );
258 }
259 if let Some((zbi, vbmeta)) = &recovery_image {
260 images_manifest.recovery_package(
261 image_metadata(
262 zbi,
263 AbsoluteComponentUrl::new(
264 images_package_repo.clone(),
265 images_package_name.clone(),
266 None,
267 Some(*images.hash()),
268 "recovery-zbi".to_owned(),
269 )
270 .unwrap(),
271 ),
272 vbmeta.as_ref().map(|v| {
273 image_metadata(
274 v,
275 AbsoluteComponentUrl::new(
276 images_package_repo.clone(),
277 images_package_name.clone(),
278 None,
279 Some(*images.hash()),
280 "recovery-vbmeta".to_owned(),
281 )
282 .unwrap(),
283 )
284 }),
285 );
286 }
287 images_manifest.firmware_package(
288 firmware_images
289 .into_iter()
290 .map(|(type_, content)| {
291 (
292 type_.clone(),
293 image_metadata(
294 content.as_slice(),
295 AbsoluteComponentUrl::new(
296 images_package_repo.clone(),
297 images_package_name.clone(),
298 None,
299 Some(*images.hash()),
300 format!("firmware-{type_}"),
301 )
302 .unwrap(),
303 ),
304 )
305 })
306 .collect(),
307 );
308 update = update.add_resource_at(
309 "images.json",
310 serde_json::to_vec(&images_manifest.clone().build()).unwrap().as_slice(),
311 );
312 let update = update.build().await.unwrap();
313
314 (UpdatePackage { package: update }, images)
315 }
316}
317
318fn image_metadata(image: &[u8], url: AbsoluteComponentUrl) -> update_package::ImageMetadata {
319 let mut hasher = sha2::Sha256::new();
320 let () = hasher.update(image);
321 update_package::ImageMetadata::new(
322 image.len().try_into().unwrap(),
323 fuchsia_hash::Sha256::from(*AsRef::<[u8; 32]>::as_ref(&hasher.finalize())),
324 url,
325 )
326}
327
328pub struct UpdatePackage {
330 package: crate::Package,
331}
332
333impl UpdatePackage {
334 pub fn as_package(&self) -> &crate::Package {
336 &self.package
337 }
338
339 pub fn into_package(self) -> crate::Package {
341 self.package
342 }
343}