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