1 // Copyright 2024, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 use core::cmp::min;
16 use core::ffi::CStr;
17 use core::str::Split;
18 use fastboot::{
19     next_arg, next_arg_u64, CommandError, FastbootImplementation, FastbootUtils, UploadBuilder,
20 };
21 use gbl_storage::{AsBlockDevice, AsMultiBlockDevices, GPT_NAME_LEN_U16};
22 
23 mod vars;
24 use vars::{BlockDevice, Partition, Variable};
25 
26 mod sparse;
27 use sparse::{is_sparse_image, write_sparse_image};
28 
29 pub(crate) const GPT_NAME_LEN_U8: usize = GPT_NAME_LEN_U16 * 2;
30 
31 /// `GblFbPartition` represents a GBL Fastboot partition, which is defined as any sub window of a
32 /// GPT partition or raw storage.
33 #[derive(Debug, Copy, Clone)]
34 pub(crate) struct GblFbPartition {
35     // GPT partition if it is a non-null string, raw block otherwise.
36     part: [u8; GPT_NAME_LEN_U8],
37     blk_id: u64,
38     // The offset where the window starts.
39     window_start: u64,
40     // The size of the window.
41     window_size: u64,
42 }
43 
44 impl GblFbPartition {
part(&self) -> &str45     pub fn part(&self) -> &str {
46         // The construction is guaranteed to give a valid UTF8 string.
47         CStr::from_bytes_until_nul(&self.part[..]).unwrap().to_str().unwrap()
48     }
49 }
50 
51 /// `GblFbPartitionIo` provides read/write/size methods for a GBL Fastboot partition.
52 pub(crate) struct GblFbPartitionIo<'a> {
53     part: GblFbPartition,
54     devs: &'a mut dyn AsMultiBlockDevices,
55 }
56 
57 impl GblFbPartitionIo<'_> {
58     /// Checks read/write offset/size and returns the absolute offset.
check_range(&self, rw_off: u64, rw_size: usize) -> Result<u64, CommandError>59     fn check_range(&self, rw_off: u64, rw_size: usize) -> Result<u64, CommandError> {
60         if add(rw_off, u64::try_from(rw_size)?)? > self.part.window_size {
61             return Err("Read/Write range overflow".into());
62         }
63         Ok(add(rw_off, self.part.window_start)?)
64     }
65 
66     /// Reads from the GBL Fastboot partition.
read(&mut self, offset: u64, out: &mut [u8]) -> Result<(), CommandError>67     pub fn read(&mut self, offset: u64, out: &mut [u8]) -> Result<(), CommandError> {
68         let offset = self.check_range(offset, out.len())?;
69         let mut dev = (&mut self.devs).get(self.part.blk_id)?;
70         Ok(match self.part.part() {
71             "" => dev.read(offset, out),
72             part => dev.read_gpt_partition(part, offset, out),
73         }?)
74     }
75 
76     /// Writes to the GBL Fastboot partition.
write(&mut self, offset: u64, data: &mut [u8]) -> Result<(), CommandError>77     pub fn write(&mut self, offset: u64, data: &mut [u8]) -> Result<(), CommandError> {
78         let offset = self.check_range(offset, data.len())?;
79         let mut dev = (&mut self.devs).get(self.part.blk_id)?;
80         Ok(match self.part.part() {
81             "" => dev.write(offset, data),
82             part => dev.write_gpt_partition(part, offset, data),
83         }?)
84     }
85 
86     /// Returns the size of the GBL Fastboot partition.
size(&mut self) -> u6487     pub fn size(&mut self) -> u64 {
88         self.part.window_size
89     }
90 }
91 
92 /// `GblFastboot` implements fastboot commands in the GBL context.
93 pub struct GblFastboot<'a> {
94     pub storage: &'a mut dyn AsMultiBlockDevices,
95 }
96 
97 impl<'a> GblFastboot<'a> {
98     /// Native GBL fastboot variables.
99     const NATIVE_VARS: &'static [&'static dyn Variable] = &[
100         &("version-bootloader", "1.0"), // Placeholder for now.
101         // GBL Fastboot can internally handle uploading in batches, thus there is no limit on
102         // max-fetch-size.
103         &("max-fetch-size", "0xffffffffffffffff"),
104         &BlockDevice {},
105         &Partition {},
106     ];
107 
108     /// Creates a new instance.
new(storage: &'a mut dyn AsMultiBlockDevices) -> Self109     pub fn new(storage: &'a mut dyn AsMultiBlockDevices) -> Self {
110         Self { storage: storage }
111     }
112 
113     /// Returns the storage object.
114     ///
115     /// `AsMultiBlockDevices` has methods with `Self: Sized` constraint. Thus we return a
116     /// `&mut &mut dyn AsMultiBlockDevices` which also implements `AsMultiBlockDevices` but meets
117     /// the `Sized` bound.
storage(&mut self) -> &mut &'a mut dyn AsMultiBlockDevices118     pub fn storage(&mut self) -> &mut &'a mut dyn AsMultiBlockDevices {
119         &mut self.storage
120     }
121 
122     /// Parses and checks partition name, block device ID and offset from the arguments and
123     /// returns an instance of `GblFbPartition`.
parse_partition<'b>( &mut self, mut args: Split<'b, char>, ) -> Result<GblFbPartition, CommandError>124     pub(crate) fn parse_partition<'b>(
125         &mut self,
126         mut args: Split<'b, char>,
127     ) -> Result<GblFbPartition, CommandError> {
128         let devs = self.storage();
129         // Copies over partition name string
130         let part = next_arg(&mut args, Ok(""))?;
131         let mut part_str = [0u8; GPT_NAME_LEN_U8];
132         part_str
133             .get_mut(..part.len())
134             .ok_or("Partition name too long")?
135             .clone_from_slice(part.as_bytes());
136         // Parses block device ID.
137         let blk_id = next_arg_u64(&mut args, Err("".into())).ok();
138         // Parses offset
139         let window_start = next_arg_u64(&mut args, Ok(0))?;
140         // Checks blk_id and computes maximum partition size.
141         let (blk_id, max_size) = match part {
142             "" => {
143                 let blk_id = blk_id.ok_or("Must provide a block device ID")?;
144                 (blk_id, devs.get(blk_id)?.total_size()?)
145             }
146             gpt => match blk_id {
147                 Some(id) => (id, devs.get(id)?.find_partition(gpt)?.size()?),
148                 _ => {
149                     devs.check_part(gpt).map(|(id, p)| Ok::<_, CommandError>((id, p.size()?)))??
150                 }
151             },
152         };
153         let max_size = max_size.checked_sub(window_start).ok_or("Offset overflows")?;
154         // Parses size or uses `max_size`
155         let window_size = next_arg_u64(&mut args, Ok(max_size))?;
156         match window_size > max_size {
157             true => Err("Size overflows".into()),
158             _ => Ok(GblFbPartition {
159                 part: part_str,
160                 blk_id: blk_id,
161                 window_start: window_start,
162                 window_size: window_size,
163             }),
164         }
165     }
166 
167     /// Creates an instance of `GblFbPartitionIO`
partition_io(&mut self, part: GblFbPartition) -> GblFbPartitionIo168     pub(crate) fn partition_io(&mut self, part: GblFbPartition) -> GblFbPartitionIo {
169         GblFbPartitionIo { part: part, devs: self.storage() }
170     }
171 }
172 
173 impl FastbootImplementation for GblFastboot<'_> {
get_var( &mut self, var: &str, args: Split<char>, out: &mut [u8], _utils: &mut FastbootUtils, ) -> Result<usize, CommandError>174     fn get_var(
175         &mut self,
176         var: &str,
177         args: Split<char>,
178         out: &mut [u8],
179         _utils: &mut FastbootUtils,
180     ) -> Result<usize, CommandError> {
181         Self::NATIVE_VARS
182             .iter()
183             .find_map(|v| v.get(self, var, args.clone(), out).transpose())
184             .ok_or::<CommandError>("No such variable".into())?
185     }
186 
get_var_all( &mut self, f: &mut dyn FnMut(&str, &[&str], &str) -> Result<(), CommandError>, _utils: &mut FastbootUtils, ) -> Result<(), CommandError>187     fn get_var_all(
188         &mut self,
189         f: &mut dyn FnMut(&str, &[&str], &str) -> Result<(), CommandError>,
190         _utils: &mut FastbootUtils,
191     ) -> Result<(), CommandError> {
192         Self::NATIVE_VARS.iter().find_map(|v| v.get_all(self, f).err()).map_or(Ok(()), |e| Err(e))
193     }
194 
flash(&mut self, part: &str, utils: &mut FastbootUtils) -> Result<(), CommandError>195     fn flash(&mut self, part: &str, utils: &mut FastbootUtils) -> Result<(), CommandError> {
196         let part = self.parse_partition(part.split(':'))?;
197         match is_sparse_image(utils.download_data()) {
198             // Passes the entire download buffer so that more can be used as fill buffer.
199             Ok(_) => write_sparse_image(utils.take_download_buffer().0, |off, data| {
200                 self.partition_io(part).write(off, data)
201             })
202             .map(|_| ()),
203             _ => self.partition_io(part).write(0, utils.download_data()),
204         }
205     }
206 
upload( &mut self, _upload_builder: UploadBuilder, _utils: &mut FastbootUtils, ) -> Result<(), CommandError>207     fn upload(
208         &mut self,
209         _upload_builder: UploadBuilder,
210         _utils: &mut FastbootUtils,
211     ) -> Result<(), CommandError> {
212         Err("Unimplemented".into())
213     }
214 
fetch( &mut self, part: &str, offset: u64, size: u64, upload_builder: UploadBuilder, utils: &mut FastbootUtils, ) -> Result<(), CommandError>215     fn fetch(
216         &mut self,
217         part: &str,
218         offset: u64,
219         size: u64,
220         upload_builder: UploadBuilder,
221         utils: &mut FastbootUtils,
222     ) -> Result<(), CommandError> {
223         let part = self.parse_partition(part.split(':'))?;
224         let (buffer, _) = utils.take_download_buffer();
225         let buffer_len = u64::try_from(buffer.len())
226             .map_err::<CommandError, _>(|_| "buffer size overflow".into())?;
227         let end = add(offset, size)?;
228         let mut curr = offset;
229         let mut uploader = upload_builder.start(size)?;
230         while curr < end {
231             let to_send = min(end - curr, buffer_len);
232             self.partition_io(part).read(curr, &mut buffer[..to_usize(to_send)?])?;
233             uploader.upload(&mut buffer[..to_usize(to_send)?])?;
234             curr += to_send;
235         }
236         Ok(())
237     }
238 
oem<'a>( &mut self, _cmd: &str, utils: &mut FastbootUtils, _res: &'a mut [u8], ) -> Result<&'a [u8], CommandError>239     fn oem<'a>(
240         &mut self,
241         _cmd: &str,
242         utils: &mut FastbootUtils,
243         _res: &'a mut [u8],
244     ) -> Result<&'a [u8], CommandError> {
245         let _ = utils.info_send("GBL OEM not implemented yet")?;
246         Err("Unimplemented".into())
247     }
248 }
249 
250 /// Check and convert u64 into usize
to_usize(val: u64) -> Result<usize, CommandError>251 fn to_usize(val: u64) -> Result<usize, CommandError> {
252     val.try_into().map_err(|_| "Overflow".into())
253 }
254 
255 /// Add two u64 integers and check overflow
add(lhs: u64, rhs: u64) -> Result<u64, CommandError>256 fn add(lhs: u64, rhs: u64) -> Result<u64, CommandError> {
257     lhs.checked_add(rhs).ok_or("Overflow".into())
258 }
259 
260 /// Subtracts two u64 integers and check overflow
sub(lhs: u64, rhs: u64) -> Result<u64, CommandError>261 fn sub(lhs: u64, rhs: u64) -> Result<u64, CommandError> {
262     lhs.checked_sub(rhs).ok_or("Overflow".into())
263 }
264 
265 #[cfg(test)]
266 mod test {
267     use super::*;
268     use fastboot::test_utils::with_mock_upload_builder;
269     use gbl_storage_testlib::{TestBlockDeviceBuilder, TestMultiBlockDevices};
270     use std::string::String;
271     use Vec;
272 
273     /// Helper to test fastboot variable value.
check_var(gbl_fb: &mut GblFastboot, var: &str, args: &str, expected: &str)274     fn check_var(gbl_fb: &mut GblFastboot, var: &str, args: &str, expected: &str) {
275         let mut dl_size = 0;
276         let mut utils = FastbootUtils::new(&mut [], &mut dl_size, None);
277         let mut out = vec![0u8; fastboot::MAX_RESPONSE_SIZE];
278         assert_eq!(
279             gbl_fb.get_var_as_str(var, args.split(':'), &mut out[..], &mut utils).unwrap(),
280             expected
281         );
282     }
283 
284     #[test]
test_get_var_partition_info()285     fn test_get_var_partition_info() {
286         let mut devs = TestMultiBlockDevices(vec![
287             include_bytes!("../../../libstorage/test/gpt_test_1.bin").as_slice().into(),
288             include_bytes!("../../../libstorage/test/gpt_test_2.bin").as_slice().into(),
289         ]);
290         devs.sync_gpt_all(&mut |_, _, _| panic!("GPT sync failed"));
291         let mut gbl_fb = GblFastboot::new(&mut devs);
292 
293         // Check different semantics
294         check_var(&mut gbl_fb, "partition-size", "boot_a", "0x2000");
295         check_var(&mut gbl_fb, "partition-size", "boot_a:", "0x2000");
296         check_var(&mut gbl_fb, "partition-size", "boot_a::", "0x2000");
297         check_var(&mut gbl_fb, "partition-size", "boot_a:::", "0x2000");
298         check_var(&mut gbl_fb, "partition-size", "boot_a:0", "0x2000");
299         check_var(&mut gbl_fb, "partition-size", "boot_a:0:", "0x2000");
300         check_var(&mut gbl_fb, "partition-size", "boot_a::0", "0x2000");
301         check_var(&mut gbl_fb, "partition-size", "boot_a:0:0", "0x2000");
302         check_var(&mut gbl_fb, "partition-size", "boot_a::0x1000", "0x1000");
303 
304         check_var(&mut gbl_fb, "partition-size", "boot_b:0", "0x3000");
305         check_var(&mut gbl_fb, "partition-size", "vendor_boot_a:1", "0x1000");
306         check_var(&mut gbl_fb, "partition-size", "vendor_boot_b:1", "0x1800");
307 
308         let mut dl_size = 0;
309         let mut utils = FastbootUtils::new(&mut [], &mut dl_size, None);
310         let mut out = vec![0u8; fastboot::MAX_RESPONSE_SIZE];
311         assert!(gbl_fb
312             .get_var_as_str("partition", "non-existent".split(':'), &mut out[..], &mut utils)
313             .is_err());
314     }
315 
316     #[test]
test_get_var_all()317     fn test_get_var_all() {
318         let mut devs = TestMultiBlockDevices(vec![
319             include_bytes!("../../../libstorage/test/gpt_test_1.bin").as_slice().into(),
320             include_bytes!("../../../libstorage/test/gpt_test_2.bin").as_slice().into(),
321         ]);
322         devs.sync_gpt_all(&mut |_, _, _| panic!("GPT sync failed"));
323         let mut gbl_fb = GblFastboot::new(&mut devs);
324 
325         let mut dl_size = 0;
326         let mut utils = FastbootUtils::new(&mut [], &mut dl_size, None);
327         let mut out: Vec<String> = Default::default();
328         gbl_fb
329             .get_var_all(
330                 &mut |name, args, val| {
331                     out.push(format!("{}:{}: {}", name, args.join(":"), val));
332                     Ok(())
333                 },
334                 &mut utils,
335             )
336             .unwrap();
337         assert_eq!(
338             out,
339             [
340                 "version-bootloader:: 1.0",
341                 "max-fetch-size:: 0xffffffffffffffff",
342                 "block-device:0:total-blocks: 0x80",
343                 "block-device:0:block-size: 0x200",
344                 "block-device:1:total-blocks: 0x100",
345                 "block-device:1:block-size: 0x200",
346                 "partition-size:boot_a:0: 0x2000",
347                 "partition-type:boot_a:0: raw",
348                 "partition-size:boot_b:0: 0x3000",
349                 "partition-type:boot_b:0: raw",
350                 "partition-size:vendor_boot_a:1: 0x1000",
351                 "partition-type:vendor_boot_a:1: raw",
352                 "partition-size:vendor_boot_b:1: 0x1800",
353                 "partition-type:vendor_boot_b:1: raw"
354             ]
355         );
356     }
357 
358     /// A helper for fetching partition from a `GblFastboot`
fetch( fb: &mut GblFastboot, part: String, off: u64, size: u64, ) -> Result<Vec<u8>, CommandError>359     fn fetch(
360         fb: &mut GblFastboot,
361         part: String,
362         off: u64,
363         size: u64,
364     ) -> Result<Vec<u8>, CommandError> {
365         let mut dl_size = 0;
366         // Forces upload in two batches for testing.
367         let mut download_buffer =
368             vec![0u8; core::cmp::max(1, usize::try_from(size).unwrap() / 2usize)];
369         let mut utils = FastbootUtils::new(&mut download_buffer[..], &mut dl_size, None);
370         let mut upload_out = vec![0u8; usize::try_from(size).unwrap()];
371         let mut res = Ok(());
372         let (uploaded, _) = with_mock_upload_builder(&mut upload_out[..], |upload_builder| {
373             res = fb.fetch(part.as_str(), off, size, upload_builder, &mut utils)
374         });
375         assert!(res.is_err() || uploaded == usize::try_from(size).unwrap());
376         res.map(|_| upload_out)
377     }
378 
379     #[test]
test_fetch_invalid_partition_arg()380     fn test_fetch_invalid_partition_arg() {
381         let mut devs = TestMultiBlockDevices(vec![
382             include_bytes!("../../../libstorage/test/gpt_test_1.bin").as_slice().into(),
383             include_bytes!("../../../libstorage/test/gpt_test_2.bin").as_slice().into(),
384             include_bytes!("../../../libstorage/test/gpt_test_2.bin").as_slice().into(),
385         ]);
386         devs.sync_gpt_all(&mut |_, _, _| panic!("GPT sync failed"));
387         let mut fb = GblFastboot::new(&mut devs);
388 
389         // Missing mandatory block device ID for raw block partition.
390         assert!(fetch(&mut fb, "::0:0".into(), 0, 0).is_err());
391 
392         // GPT partition does not exist.
393         assert!(fetch(&mut fb, "non:::".into(), 0, 0).is_err());
394 
395         // GPT Partition is not unique.
396         assert!(fetch(&mut fb, "vendor_boot_a:::".into(), 0, 0).is_err());
397 
398         // Offset overflows.
399         assert!(fetch(&mut fb, "boot_a::0x2001:".into(), 0, 1).is_err());
400         assert!(fetch(&mut fb, "boot_a".into(), 0x2000, 1).is_err());
401 
402         // Size overflows.
403         assert!(fetch(&mut fb, "boot_a:::0x2001".into(), 0, 0).is_err());
404         assert!(fetch(&mut fb, "boot_a".into(), 0, 0x2001).is_err());
405     }
406 
407     /// A helper for testing raw block upload. It verifies that data read from block device
408     /// `blk_id` in range [`off`, `off`+`size`) is the same as `disk[off..][..size]`
check_blk_upload(fb: &mut GblFastboot, blk_id: u64, off: u64, size: u64, disk: &[u8])409     fn check_blk_upload(fb: &mut GblFastboot, blk_id: u64, off: u64, size: u64, disk: &[u8]) {
410         let expected = disk[off.try_into().unwrap()..][..size.try_into().unwrap()].to_vec();
411         // offset/size as part of the partition string.
412         let part = format!(":{:#x}:{:#x}:{:#x}", blk_id, off, size);
413         assert_eq!(fetch(fb, part, 0, size).unwrap(), expected);
414         // offset/size as separate fetch arguments.
415         let part = format!(":{:#x}", blk_id);
416         assert_eq!(fetch(fb, part, off, size).unwrap(), expected);
417     }
418 
419     #[test]
test_fetch_raw_block()420     fn test_fetch_raw_block() {
421         let disk_0 = include_bytes!("../../../libstorage/test/gpt_test_1.bin");
422         let disk_1 = include_bytes!("../../../libstorage/test/gpt_test_2.bin");
423         let mut devs =
424             TestMultiBlockDevices(vec![disk_0.as_slice().into(), disk_1.as_slice().into()]);
425         devs.sync_gpt_all(&mut |_, _, _| panic!("GPT sync failed"));
426         let mut gbl_fb = GblFastboot::new(&mut devs);
427 
428         let off = 512;
429         let size = 512;
430         check_blk_upload(&mut gbl_fb, 0, off, size, disk_0);
431         check_blk_upload(&mut gbl_fb, 1, off, size, disk_1);
432     }
433 
434     /// A helper for testing uploading GPT partition. It verifies that data read from GPT partition
435     /// `part` at disk `blk_id` in range [`off`, `off`+`size`) is the same as
436     /// `partition_data[off..][..size]`.
check_gpt_upload( fb: &mut GblFastboot, part: &str, off: u64, size: u64, blk_id: Option<u64>, partition_data: &[u8], )437     fn check_gpt_upload(
438         fb: &mut GblFastboot,
439         part: &str,
440         off: u64,
441         size: u64,
442         blk_id: Option<u64>,
443         partition_data: &[u8],
444     ) {
445         let expected =
446             partition_data[off.try_into().unwrap()..][..size.try_into().unwrap()].to_vec();
447         let blk_id = blk_id.map_or("".to_string(), |v| format!("{:#x}", v));
448         // offset/size as part of the partition string.
449         let gpt_part = format!("{}:{}:{:#x}:{:#x}", part, blk_id, off, size);
450         assert_eq!(fetch(fb, gpt_part, 0, size).unwrap(), expected);
451         // offset/size as separate fetch arguments.
452         let gpt_part = format!("{}:{}", part, blk_id);
453         assert_eq!(fetch(fb, gpt_part, off, size).unwrap(), expected);
454     }
455 
456     #[test]
test_fetch_gpt_partition()457     fn test_fetch_gpt_partition() {
458         let mut devs = TestMultiBlockDevices(vec![
459             include_bytes!("../../../libstorage/test/gpt_test_1.bin").as_slice().into(),
460             include_bytes!("../../../libstorage/test/gpt_test_2.bin").as_slice().into(),
461         ]);
462         devs.sync_gpt_all(&mut |_, _, _| panic!("GPT sync failed"));
463         let mut gbl_fb = GblFastboot::new(&mut devs);
464 
465         let expect_boot_a = include_bytes!("../../../libstorage/test/boot_a.bin");
466         let expect_boot_b = include_bytes!("../../../libstorage/test/boot_b.bin");
467         let expect_vendor_boot_a = include_bytes!("../../../libstorage/test/vendor_boot_a.bin");
468         let expect_vendor_boot_b = include_bytes!("../../../libstorage/test/vendor_boot_b.bin");
469 
470         let size = 512;
471         let off = 512;
472 
473         check_gpt_upload(&mut gbl_fb, "boot_a", off, size, Some(0), expect_boot_a);
474         check_gpt_upload(&mut gbl_fb, "boot_b", off, size, Some(0), expect_boot_b);
475         check_gpt_upload(&mut gbl_fb, "vendor_boot_a", off, size, Some(1), expect_vendor_boot_a);
476         check_gpt_upload(&mut gbl_fb, "vendor_boot_b", off, size, Some(1), expect_vendor_boot_b);
477 
478         // No block device id
479         check_gpt_upload(&mut gbl_fb, "boot_a", off, size, None, expect_boot_a);
480         check_gpt_upload(&mut gbl_fb, "boot_b", off, size, None, expect_boot_b);
481         check_gpt_upload(&mut gbl_fb, "vendor_boot_a", off, size, None, expect_vendor_boot_a);
482         check_gpt_upload(&mut gbl_fb, "vendor_boot_b", off, size, None, expect_vendor_boot_b);
483     }
484 
485     /// A helper for testing GPT partition flashing.
check_flash_part(fb: &mut GblFastboot, part: &str, expected: &[u8])486     fn check_flash_part(fb: &mut GblFastboot, part: &str, expected: &[u8]) {
487         // Prepare a download buffer.
488         let mut dl_size = expected.len();
489         let mut download = expected.to_vec();
490         let mut utils = FastbootUtils::new(&mut download[..], &mut dl_size, None);
491         fb.flash(part, &mut utils).unwrap();
492         assert_eq!(fetch(fb, part.into(), 0, dl_size.try_into().unwrap()).unwrap(), download);
493 
494         // Also flashes bit-wise reversed version in case the initial content is the same.
495         let mut download = expected.iter().map(|v| !(*v)).collect::<Vec<_>>();
496         let mut utils = FastbootUtils::new(&mut download[..], &mut dl_size, None);
497         fb.flash(part, &mut utils).unwrap();
498         assert_eq!(fetch(fb, part.into(), 0, dl_size.try_into().unwrap()).unwrap(), download);
499     }
500 
501     #[test]
test_flash_partition()502     fn test_flash_partition() {
503         let disk_0 = include_bytes!("../../../libstorage/test/gpt_test_1.bin");
504         let disk_1 = include_bytes!("../../../libstorage/test/gpt_test_2.bin");
505         let mut devs =
506             TestMultiBlockDevices(vec![disk_0.as_slice().into(), disk_1.as_slice().into()]);
507         devs.sync_gpt_all(&mut |_, _, _| panic!("GPT sync failed"));
508 
509         let mut gbl_fb = GblFastboot::new(&mut devs);
510 
511         let expect_boot_a = include_bytes!("../../../libstorage/test/boot_a.bin");
512         let expect_boot_b = include_bytes!("../../../libstorage/test/boot_b.bin");
513         check_flash_part(&mut gbl_fb, "boot_a", expect_boot_a);
514         check_flash_part(&mut gbl_fb, "boot_b", expect_boot_b);
515         check_flash_part(&mut gbl_fb, ":0", disk_0);
516         check_flash_part(&mut gbl_fb, ":1", disk_1);
517 
518         // Partital flash
519         let off = 0x200;
520         let size = 1024;
521         check_flash_part(&mut gbl_fb, "boot_a::200", &expect_boot_a[off..size]);
522         check_flash_part(&mut gbl_fb, "boot_b::200", &expect_boot_b[off..size]);
523         check_flash_part(&mut gbl_fb, ":0:200", &disk_0[off..size]);
524         check_flash_part(&mut gbl_fb, ":1:200", &disk_1[off..size]);
525     }
526 
527     #[test]
test_flash_partition_sparse()528     fn test_flash_partition_sparse() {
529         let raw = include_bytes!("../../testdata/sparse_test_raw.bin");
530         let sparse = include_bytes!("../../testdata/sparse_test.bin");
531         let mut devs =
532             TestMultiBlockDevices(vec![TestBlockDeviceBuilder::new().set_size(raw.len()).build()]);
533         let mut fb = GblFastboot::new(&mut devs);
534 
535         let mut dl_size = sparse.len();
536         let mut download = sparse.to_vec();
537         let mut utils = FastbootUtils::new(&mut download[..], &mut dl_size, None);
538         fb.flash(":0", &mut utils).unwrap();
539         assert_eq!(fetch(&mut fb, ":0".into(), 0, raw.len().try_into().unwrap()).unwrap(), raw);
540     }
541 }
542