| // Copyright 2024, The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| use core::cmp::min; |
| use core::ffi::CStr; |
| use core::str::Split; |
| use fastboot::{ |
| next_arg, next_arg_u64, CommandError, FastbootImplementation, FastbootUtils, UploadBuilder, |
| }; |
| use gbl_storage::{AsBlockDevice, AsMultiBlockDevices, GPT_NAME_LEN_U16}; |
| |
| mod vars; |
| use vars::{BlockDevice, Partition, Variable}; |
| |
| pub(crate) const GPT_NAME_LEN_U8: usize = GPT_NAME_LEN_U16 * 2; |
| |
| /// `GblFbPartition` represents a GBL Fastboot partition, which is defined as any sub window of a |
| /// GPT partition or raw storage. |
| #[derive(Debug, Copy, Clone)] |
| pub(crate) struct GblFbPartition { |
| // GPT partition if it is a non-null string, raw block otherwise. |
| part: [u8; GPT_NAME_LEN_U8], |
| blk_id: u64, |
| // The offset where the window starts. |
| window_start: u64, |
| // The size of the window. |
| window_size: u64, |
| } |
| |
| impl GblFbPartition { |
| pub fn part(&self) -> &str { |
| // The construction is guaranteed to give a valid UTF8 string. |
| CStr::from_bytes_until_nul(&self.part[..]).unwrap().to_str().unwrap() |
| } |
| } |
| |
| /// `GblFbPartitionIo` provides read/write/size methods for a GBL Fastboot partition. |
| pub(crate) struct GblFbPartitionIo<'a> { |
| part: GblFbPartition, |
| devs: &'a mut dyn AsMultiBlockDevices, |
| } |
| |
| impl GblFbPartitionIo<'_> { |
| /// Checks read/write offset/size and returns the absolute offset. |
| fn check_range(&self, rw_off: u64, rw_size: usize) -> Result<u64, CommandError> { |
| if add(rw_off, u64::try_from(rw_size)?)? > self.part.window_size { |
| return Err("Read/Write range overflow".into()); |
| } |
| Ok(add(rw_off, self.part.window_start)?) |
| } |
| |
| /// Reads from the GBL Fastboot partition. |
| pub fn read(&mut self, offset: u64, out: &mut [u8]) -> Result<(), CommandError> { |
| let offset = self.check_range(offset, out.len())?; |
| let mut dev = (&mut self.devs).get(self.part.blk_id)?; |
| Ok(match self.part.part() { |
| "" => dev.read(offset, out), |
| part => dev.read_gpt_partition(part, offset, out), |
| }?) |
| } |
| |
| /// Writes to the GBL Fastboot partition. |
| pub fn write(&mut self, offset: u64, data: &mut [u8]) -> Result<(), CommandError> { |
| let offset = self.check_range(offset, data.len())?; |
| let mut dev = (&mut self.devs).get(self.part.blk_id)?; |
| Ok(match self.part.part() { |
| "" => dev.write(offset, data), |
| part => dev.write_gpt_partition_mut(part, offset, data), |
| }?) |
| } |
| |
| /// Returns the size of the GBL Fastboot partition. |
| pub fn size(&mut self) -> u64 { |
| self.part.window_size |
| } |
| } |
| |
| /// `GblFastboot` implements fastboot commands in the GBL context. |
| pub struct GblFastboot<'a> { |
| pub storage: &'a mut dyn AsMultiBlockDevices, |
| } |
| |
| impl<'a> GblFastboot<'a> { |
| /// Native GBL fastboot variables. |
| const NATIVE_VARS: &'static [&'static dyn Variable] = &[ |
| &("version-bootloader", "1.0"), // Placeholder for now. |
| // GBL Fastboot can internally handle uploading in batches, thus there is no limit on |
| // max-fetch-size. |
| &("max-fetch-size", "0xffffffffffffffff"), |
| &BlockDevice {}, |
| &Partition {}, |
| ]; |
| |
| /// Creates a new instance. |
| pub fn new(storage: &'a mut dyn AsMultiBlockDevices) -> Self { |
| Self { storage: storage } |
| } |
| |
| /// Returns the storage object. |
| /// |
| /// `AsMultiBlockDevices` has methods with `Self: Sized` constraint. Thus we return a |
| /// `&mut &mut dyn AsMultiBlockDevices` which also implements `AsMultiBlockDevices` but meets |
| /// the `Sized` bound. |
| pub fn storage(&mut self) -> &mut &'a mut dyn AsMultiBlockDevices { |
| &mut self.storage |
| } |
| |
| /// Parses and checks partition name, block device ID and offset from the arguments and |
| /// returns an instance of `GblFbPartition`. |
| pub(crate) fn parse_partition<'b>( |
| &mut self, |
| mut args: Split<'b, char>, |
| ) -> Result<GblFbPartition, CommandError> { |
| let devs = self.storage(); |
| // Copies over partition name string |
| let part = next_arg(&mut args, Ok(""))?; |
| let mut part_str = [0u8; GPT_NAME_LEN_U8]; |
| part_str |
| .get_mut(..part.len()) |
| .ok_or("Partition name too long")? |
| .clone_from_slice(part.as_bytes()); |
| // Parses block device ID. |
| let blk_id = next_arg_u64(&mut args, Err("".into())).ok(); |
| // Parses offset |
| let window_start = next_arg_u64(&mut args, Ok(0))?; |
| // Checks blk_id and computes maximum partition size. |
| let (blk_id, max_size) = match part { |
| "" => { |
| let blk_id = blk_id.ok_or("Must provide a block device ID")?; |
| (blk_id, devs.get(blk_id)?.total_size()?) |
| } |
| gpt => match blk_id { |
| Some(id) => (id, devs.get(id)?.find_partition(gpt)?.size()?), |
| _ => { |
| devs.check_part(gpt).map(|(id, p)| Ok::<_, CommandError>((id, p.size()?)))?? |
| } |
| }, |
| }; |
| let max_size = max_size.checked_sub(window_start).ok_or("Offset overflows")?; |
| // Parses size or uses `max_size` |
| let window_size = next_arg_u64(&mut args, Ok(max_size))?; |
| match window_size > max_size { |
| true => Err("Size overflows".into()), |
| _ => Ok(GblFbPartition { |
| part: part_str, |
| blk_id: blk_id, |
| window_start: window_start, |
| window_size: window_size, |
| }), |
| } |
| } |
| |
| /// Creates an instance of `GblFbPartitionIO` |
| pub(crate) fn partition_io(&mut self, part: GblFbPartition) -> GblFbPartitionIo { |
| GblFbPartitionIo { part: part, devs: self.storage() } |
| } |
| } |
| |
| impl FastbootImplementation for GblFastboot<'_> { |
| fn get_var( |
| &mut self, |
| var: &str, |
| args: Split<char>, |
| out: &mut [u8], |
| _utils: &mut FastbootUtils, |
| ) -> Result<usize, CommandError> { |
| Self::NATIVE_VARS |
| .iter() |
| .find_map(|v| v.get(self, var, args.clone(), out).transpose()) |
| .ok_or::<CommandError>("No such variable".into())? |
| } |
| |
| fn get_var_all( |
| &mut self, |
| f: &mut dyn FnMut(&str, &[&str], &str) -> Result<(), CommandError>, |
| _utils: &mut FastbootUtils, |
| ) -> Result<(), CommandError> { |
| Self::NATIVE_VARS.iter().find_map(|v| v.get_all(self, f).err()).map_or(Ok(()), |e| Err(e)) |
| } |
| |
| fn flash(&mut self, part: &str, utils: &mut FastbootUtils) -> Result<(), CommandError> { |
| let part = self.parse_partition(part.split(':'))?; |
| self.partition_io(part).write(0, utils.download_data()) |
| } |
| |
| fn upload( |
| &mut self, |
| _upload_builder: UploadBuilder, |
| _utils: &mut FastbootUtils, |
| ) -> Result<(), CommandError> { |
| Err("Unimplemented".into()) |
| } |
| |
| fn fetch( |
| &mut self, |
| part: &str, |
| offset: u64, |
| size: u64, |
| upload_builder: UploadBuilder, |
| utils: &mut FastbootUtils, |
| ) -> Result<(), CommandError> { |
| let part = self.parse_partition(part.split(':'))?; |
| let buffer = utils.take_download_buffer(); |
| let buffer_len = u64::try_from(buffer.len()) |
| .map_err::<CommandError, _>(|_| "buffer size overflow".into())?; |
| let end = add(offset, size)?; |
| let mut curr = offset; |
| let mut uploader = upload_builder.start(size)?; |
| while curr < end { |
| let to_send = min(end - curr, buffer_len); |
| self.partition_io(part).read(curr, &mut buffer[..to_usize(to_send)?])?; |
| uploader.upload(&mut buffer[..to_usize(to_send)?])?; |
| curr += to_send; |
| } |
| Ok(()) |
| } |
| |
| fn oem<'a>( |
| &mut self, |
| _cmd: &str, |
| utils: &mut FastbootUtils, |
| _res: &'a mut [u8], |
| ) -> Result<&'a [u8], CommandError> { |
| let _ = utils.info_send("GBL OEM not implemented yet")?; |
| Err("Unimplemented".into()) |
| } |
| } |
| |
| /// Check and convert u64 into usize |
| fn to_usize(val: u64) -> Result<usize, CommandError> { |
| val.try_into().map_err(|_| "Overflow".into()) |
| } |
| |
| /// Add two u64 integers and check overflow |
| fn add(lhs: u64, rhs: u64) -> Result<u64, CommandError> { |
| lhs.checked_add(rhs).ok_or("Overflow".into()) |
| } |
| |
| /// Subtracts two u64 integers and check overflow |
| fn sub(lhs: u64, rhs: u64) -> Result<u64, CommandError> { |
| lhs.checked_sub(rhs).ok_or("Overflow".into()) |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| use fastboot::test_utils::with_mock_upload_builder; |
| use gbl_storage::{required_scratch_size, AsBlockDevice, BlockIo}; |
| use std::string::String; |
| use Vec; |
| |
| const BLOCK_SIZE: u64 = 512; |
| const MAX_GPT_ENTRIES: u64 = 128; |
| |
| // TODO(b/329138620): Migrate to common storage test library once available. |
| struct TestBlockIo(Vec<u8>); |
| |
| impl BlockIo for TestBlockIo { |
| fn block_size(&mut self) -> u64 { |
| BLOCK_SIZE |
| } |
| |
| fn num_blocks(&mut self) -> u64 { |
| u64::try_from(self.0.len()).unwrap() / BLOCK_SIZE |
| } |
| |
| fn alignment(&mut self) -> u64 { |
| BLOCK_SIZE |
| } |
| |
| fn read_blocks(&mut self, blk_offset: u64, out: &mut [u8]) -> bool { |
| out.clone_from_slice( |
| &self.0[usize::try_from(blk_offset * BLOCK_SIZE).unwrap()..][..out.len()], |
| ); |
| true |
| } |
| |
| fn write_blocks(&mut self, blk_offset: u64, data: &[u8]) -> bool { |
| self.0[usize::try_from(blk_offset * BLOCK_SIZE).unwrap()..][..data.len()] |
| .clone_from_slice(data); |
| true |
| } |
| } |
| |
| struct TestBlockDevice((TestBlockIo, Vec<u8>)); |
| |
| impl AsBlockDevice for TestBlockDevice { |
| fn with(&mut self, f: &mut dyn FnMut(&mut dyn BlockIo, &mut [u8], u64)) { |
| f(&mut self.0 .0, &mut self.0 .1[..], MAX_GPT_ENTRIES) |
| } |
| } |
| |
| fn test_block_device(storage: &[u8]) -> TestBlockDevice { |
| let mut io = TestBlockIo(Vec::from(storage)); |
| let scratch_size = required_scratch_size(&mut io, MAX_GPT_ENTRIES).unwrap(); |
| TestBlockDevice((io, vec![0u8; scratch_size])) |
| } |
| |
| struct TestMultiBlockDevices(Vec<TestBlockDevice>); |
| |
| impl AsMultiBlockDevices for TestMultiBlockDevices { |
| fn for_each_until(&mut self, f: &mut dyn FnMut(&mut dyn AsBlockDevice, u64) -> bool) { |
| let _ = self |
| .0 |
| .iter_mut() |
| .enumerate() |
| .find_map(|(idx, ele)| f(ele, u64::try_from(idx).unwrap()).then_some(())); |
| } |
| } |
| |
| /// Helper to test fastboot variable value. |
| fn check_var(gbl_fb: &mut GblFastboot, var: &str, args: &str, expected: &str) { |
| let mut dl_size = 0; |
| let mut utils = FastbootUtils::new(&mut [], &mut dl_size, None); |
| let mut out = vec![0u8; fastboot::MAX_RESPONSE_SIZE]; |
| assert_eq!( |
| gbl_fb.get_var_as_str(var, args.split(':'), &mut out[..], &mut utils).unwrap(), |
| expected |
| ); |
| } |
| |
| #[test] |
| fn test_get_var_partition_info() { |
| let mut devs = TestMultiBlockDevices(vec![ |
| test_block_device(include_bytes!("../../../libstorage/test/gpt_test_1.bin")), |
| test_block_device(include_bytes!("../../../libstorage/test/gpt_test_2.bin")), |
| ]); |
| devs.sync_gpt_all(&mut |_, _, _| panic!("GPT sync failed")); |
| let mut gbl_fb = GblFastboot::new(&mut devs); |
| |
| // Check different semantics |
| check_var(&mut gbl_fb, "partition-size", "boot_a", "0x2000"); |
| check_var(&mut gbl_fb, "partition-size", "boot_a:", "0x2000"); |
| check_var(&mut gbl_fb, "partition-size", "boot_a::", "0x2000"); |
| check_var(&mut gbl_fb, "partition-size", "boot_a:::", "0x2000"); |
| check_var(&mut gbl_fb, "partition-size", "boot_a:0", "0x2000"); |
| check_var(&mut gbl_fb, "partition-size", "boot_a:0:", "0x2000"); |
| check_var(&mut gbl_fb, "partition-size", "boot_a::0", "0x2000"); |
| check_var(&mut gbl_fb, "partition-size", "boot_a:0:0", "0x2000"); |
| check_var(&mut gbl_fb, "partition-size", "boot_a::0x1000", "0x1000"); |
| |
| check_var(&mut gbl_fb, "partition-size", "boot_b:0", "0x3000"); |
| check_var(&mut gbl_fb, "partition-size", "vendor_boot_a:1", "0x1000"); |
| check_var(&mut gbl_fb, "partition-size", "vendor_boot_b:1", "0x1800"); |
| |
| let mut dl_size = 0; |
| let mut utils = FastbootUtils::new(&mut [], &mut dl_size, None); |
| let mut out = vec![0u8; fastboot::MAX_RESPONSE_SIZE]; |
| assert!(gbl_fb |
| .get_var_as_str("partition", "non-existent".split(':'), &mut out[..], &mut utils) |
| .is_err()); |
| } |
| |
| #[test] |
| fn test_get_var_all() { |
| let mut devs = TestMultiBlockDevices(vec![ |
| test_block_device(include_bytes!("../../../libstorage/test/gpt_test_1.bin")), |
| test_block_device(include_bytes!("../../../libstorage/test/gpt_test_2.bin")), |
| ]); |
| devs.sync_gpt_all(&mut |_, _, _| panic!("GPT sync failed")); |
| let mut gbl_fb = GblFastboot::new(&mut devs); |
| |
| let mut dl_size = 0; |
| let mut utils = FastbootUtils::new(&mut [], &mut dl_size, None); |
| let mut out: Vec<String> = Default::default(); |
| gbl_fb |
| .get_var_all( |
| &mut |name, args, val| { |
| out.push(format!("{}:{}: {}", name, args.join(":"), val)); |
| Ok(()) |
| }, |
| &mut utils, |
| ) |
| .unwrap(); |
| assert_eq!( |
| out, |
| [ |
| "version-bootloader:: 1.0", |
| "max-fetch-size:: 0xffffffffffffffff", |
| "block-device:0:total-blocks: 0x80", |
| "block-device:0:block-size: 0x200", |
| "block-device:1:total-blocks: 0x100", |
| "block-device:1:block-size: 0x200", |
| "partition-size:boot_a:0: 0x2000", |
| "partition-type:boot_a:0: raw", |
| "partition-size:boot_b:0: 0x3000", |
| "partition-type:boot_b:0: raw", |
| "partition-size:vendor_boot_a:1: 0x1000", |
| "partition-type:vendor_boot_a:1: raw", |
| "partition-size:vendor_boot_b:1: 0x1800", |
| "partition-type:vendor_boot_b:1: raw" |
| ] |
| ); |
| } |
| |
| /// A helper for fetching partition from a `GblFastboot` |
| fn fetch( |
| fb: &mut GblFastboot, |
| part: String, |
| off: u64, |
| size: u64, |
| ) -> Result<Vec<u8>, CommandError> { |
| let mut dl_size = 0; |
| // Forces upload in two batches for testing. |
| let mut download_buffer = |
| vec![0u8; core::cmp::max(1, usize::try_from(size).unwrap() / 2usize)]; |
| let mut utils = FastbootUtils::new(&mut download_buffer[..], &mut dl_size, None); |
| let mut upload_out = vec![0u8; usize::try_from(size).unwrap()]; |
| let mut res = Ok(()); |
| let (uploaded, _) = with_mock_upload_builder(&mut upload_out[..], |upload_builder| { |
| res = fb.fetch(part.as_str(), off, size, upload_builder, &mut utils) |
| }); |
| assert!(res.is_err() || uploaded == usize::try_from(size).unwrap()); |
| res.map(|_| upload_out) |
| } |
| |
| #[test] |
| fn test_fetch_invalid_partition_arg() { |
| let mut devs = TestMultiBlockDevices(vec![ |
| test_block_device(include_bytes!("../../../libstorage/test/gpt_test_1.bin")), |
| test_block_device(include_bytes!("../../../libstorage/test/gpt_test_2.bin")), |
| test_block_device(include_bytes!("../../../libstorage/test/gpt_test_2.bin")), |
| ]); |
| devs.sync_gpt_all(&mut |_, _, _| panic!("GPT sync failed")); |
| let mut fb = GblFastboot::new(&mut devs); |
| |
| // Missing mandatory block device ID for raw block partition. |
| assert!(fetch(&mut fb, "::0:0".into(), 0, 0).is_err()); |
| |
| // GPT partition does not exist. |
| assert!(fetch(&mut fb, "non:::".into(), 0, 0).is_err()); |
| |
| // GPT Partition is not unique. |
| assert!(fetch(&mut fb, "vendor_boot_a:::".into(), 0, 0).is_err()); |
| |
| // Offset overflows. |
| assert!(fetch(&mut fb, "boot_a::0x2001:".into(), 0, 1).is_err()); |
| assert!(fetch(&mut fb, "boot_a".into(), 0x2000, 1).is_err()); |
| |
| // Size overflows. |
| assert!(fetch(&mut fb, "boot_a:::0x2001".into(), 0, 0).is_err()); |
| assert!(fetch(&mut fb, "boot_a".into(), 0, 0x2001).is_err()); |
| } |
| |
| /// A helper for testing raw block upload. It verifies that data read from block device |
| /// `blk_id` in range [`off`, `off`+`size`) is the same as `disk[off..][..size]` |
| fn check_blk_upload(fb: &mut GblFastboot, blk_id: u64, off: u64, size: u64, disk: &[u8]) { |
| let expected = disk[off.try_into().unwrap()..][..size.try_into().unwrap()].to_vec(); |
| // offset/size as part of the partition string. |
| let part = format!(":{:#x}:{:#x}:{:#x}", blk_id, off, size); |
| assert_eq!(fetch(fb, part, 0, size).unwrap(), expected); |
| // offset/size as separate fetch arguments. |
| let part = format!(":{:#x}", blk_id); |
| assert_eq!(fetch(fb, part, off, size).unwrap(), expected); |
| } |
| |
| #[test] |
| fn test_fetch_raw_block() { |
| let disk_0 = include_bytes!("../../../libstorage/test/gpt_test_1.bin"); |
| let disk_1 = include_bytes!("../../../libstorage/test/gpt_test_2.bin"); |
| let mut devs = |
| TestMultiBlockDevices(vec![test_block_device(disk_0), test_block_device(disk_1)]); |
| devs.sync_gpt_all(&mut |_, _, _| panic!("GPT sync failed")); |
| let mut gbl_fb = GblFastboot::new(&mut devs); |
| |
| let off = 512; |
| let size = 512; |
| check_blk_upload(&mut gbl_fb, 0, off, size, disk_0); |
| check_blk_upload(&mut gbl_fb, 1, off, size, disk_1); |
| } |
| |
| /// A helper for testing uploading GPT partition. It verifies that data read from GPT partition |
| /// `part` at disk `blk_id` in range [`off`, `off`+`size`) is the same as |
| /// `partition_data[off..][..size]`. |
| fn check_gpt_upload( |
| fb: &mut GblFastboot, |
| part: &str, |
| off: u64, |
| size: u64, |
| blk_id: Option<u64>, |
| partition_data: &[u8], |
| ) { |
| let expected = |
| partition_data[off.try_into().unwrap()..][..size.try_into().unwrap()].to_vec(); |
| let blk_id = blk_id.map_or("".to_string(), |v| format!("{:#x}", v)); |
| // offset/size as part of the partition string. |
| let gpt_part = format!("{}:{}:{:#x}:{:#x}", part, blk_id, off, size); |
| assert_eq!(fetch(fb, gpt_part, 0, size).unwrap(), expected); |
| // offset/size as separate fetch arguments. |
| let gpt_part = format!("{}:{}", part, blk_id); |
| assert_eq!(fetch(fb, gpt_part, off, size).unwrap(), expected); |
| } |
| |
| #[test] |
| fn test_fetch_gpt_partition() { |
| let mut devs = TestMultiBlockDevices(vec![ |
| test_block_device(include_bytes!("../../../libstorage/test/gpt_test_1.bin")), |
| test_block_device(include_bytes!("../../../libstorage/test/gpt_test_2.bin")), |
| ]); |
| devs.sync_gpt_all(&mut |_, _, _| panic!("GPT sync failed")); |
| let mut gbl_fb = GblFastboot::new(&mut devs); |
| |
| let expect_boot_a = include_bytes!("../../../libstorage/test/boot_a.bin"); |
| let expect_boot_b = include_bytes!("../../../libstorage/test/boot_b.bin"); |
| let expect_vendor_boot_a = include_bytes!("../../../libstorage/test/vendor_boot_a.bin"); |
| let expect_vendor_boot_b = include_bytes!("../../../libstorage/test/vendor_boot_b.bin"); |
| |
| let size = 512; |
| let off = 512; |
| |
| check_gpt_upload(&mut gbl_fb, "boot_a", off, size, Some(0), expect_boot_a); |
| check_gpt_upload(&mut gbl_fb, "boot_b", off, size, Some(0), expect_boot_b); |
| check_gpt_upload(&mut gbl_fb, "vendor_boot_a", off, size, Some(1), expect_vendor_boot_a); |
| check_gpt_upload(&mut gbl_fb, "vendor_boot_b", off, size, Some(1), expect_vendor_boot_b); |
| |
| // No block device id |
| check_gpt_upload(&mut gbl_fb, "boot_a", off, size, None, expect_boot_a); |
| check_gpt_upload(&mut gbl_fb, "boot_b", off, size, None, expect_boot_b); |
| check_gpt_upload(&mut gbl_fb, "vendor_boot_a", off, size, None, expect_vendor_boot_a); |
| check_gpt_upload(&mut gbl_fb, "vendor_boot_b", off, size, None, expect_vendor_boot_b); |
| } |
| |
| /// A helper for testing GPT partition flashing. |
| fn check_flash_part(fb: &mut GblFastboot, part: &str, expected: &[u8]) { |
| // Prepare a download buffer. |
| let mut dl_size = expected.len(); |
| let mut download = expected.to_vec(); |
| let mut utils = FastbootUtils::new(&mut download[..], &mut dl_size, None); |
| fb.flash(part, &mut utils).unwrap(); |
| assert_eq!(fetch(fb, part.into(), 0, dl_size.try_into().unwrap()).unwrap(), download); |
| |
| // Also flashes bit-wise reversed version in case the initial content is the same. |
| let mut download = expected.iter().map(|v| !(*v)).collect::<Vec<_>>(); |
| let mut utils = FastbootUtils::new(&mut download[..], &mut dl_size, None); |
| fb.flash(part, &mut utils).unwrap(); |
| assert_eq!(fetch(fb, part.into(), 0, dl_size.try_into().unwrap()).unwrap(), download); |
| } |
| |
| #[test] |
| fn test_flash_partition() { |
| let mut disk_0 = include_bytes!("../../../libstorage/test/gpt_test_1.bin").to_vec(); |
| let mut disk_1 = include_bytes!("../../../libstorage/test/gpt_test_2.bin").to_vec(); |
| let mut devs = TestMultiBlockDevices(vec![ |
| test_block_device(&disk_0[..]), |
| test_block_device(&disk_1[..]), |
| ]); |
| devs.sync_gpt_all(&mut |_, _, _| panic!("GPT sync failed")); |
| |
| let mut gbl_fb = GblFastboot::new(&mut devs); |
| |
| let expect_boot_a = &mut include_bytes!("../../../libstorage/test/boot_a.bin").to_vec()[..]; |
| let expect_boot_b = &mut include_bytes!("../../../libstorage/test/boot_b.bin").to_vec()[..]; |
| check_flash_part(&mut gbl_fb, "boot_a", expect_boot_a); |
| check_flash_part(&mut gbl_fb, "boot_b", expect_boot_b); |
| check_flash_part(&mut gbl_fb, ":0", &mut disk_0[..]); |
| check_flash_part(&mut gbl_fb, ":1", &mut disk_1[..]); |
| |
| // Partital flash |
| let off = 0x200; |
| let size = 1024; |
| check_flash_part(&mut gbl_fb, "boot_a::200", &expect_boot_a[off..][..size]); |
| check_flash_part(&mut gbl_fb, "boot_b::200", &expect_boot_b[off..][..size]); |
| check_flash_part(&mut gbl_fb, ":0:200", &mut disk_0[off..][..size]); |
| check_flash_part(&mut gbl_fb, ":1:200", &mut disk_1[off..][..size]); |
| } |
| } |