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