aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Pursell <dpursell@google.com>2023-11-20 16:21:46 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2023-11-20 16:21:46 +0000
commitd4384906c9a75e35afe8c5089b2cd6aaea2c5ca8 (patch)
tree25cdeab81e2ff48f0feb83d3bf5be07c39bf0251
parent7761592d44a53a17aca30fc60c6563b548ce9266 (diff)
parent5734c7eb63c6c9927747036552debfd3bf19804e (diff)
downloadavb-d4384906c9a75e35afe8c5089b2cd6aaea2c5ca8.tar.gz
Merge changes I26462d60,Ied33940b into main
* changes: libavb_rs: move TestOps fixture to external crate libavb_rs: fix default GUID callback behavior
-rw-r--r--rust/Android.bp24
-rw-r--r--rust/src/ops.rs1190
-rw-r--r--rust/tests/test_ops.rs325
-rw-r--r--rust/tests/tests.rs15
4 files changed, 402 insertions, 1152 deletions
diff --git a/rust/Android.bp b/rust/Android.bp
index 8f6bcdb..2d5730e 100644
--- a/rust/Android.bp
+++ b/rust/Android.bp
@@ -172,23 +172,25 @@ rust_library {
],
}
-// device test: no std, no features.
-// Note: we are testing the nostd version of the library but the tests
-// themselves do use std.
+rust_defaults {
+ name: "libavb_rs_test.defaults",
+ srcs: ["tests/tests.rs"],
+ test_suites: ["general-tests"],
+}
+
+// device test: no features.
rust_test {
name: "libavb_rs_test",
- defaults: [
- "libavb_rs_nostd.defaults",
- ],
- test_suites: ["general-tests"],
+ defaults: ["libavb_rs_test.defaults"],
+ rustlibs: ["libavb_rs"],
}
-// device test: no std, UUID feature.
+// device test: UUID feature.
rust_test {
name: "libavb_rs_uuid_test",
defaults: [
- "libavb_rs_nostd.defaults",
- "libavb_rs_nostd.uuid.defaults",
+ "libavb_rs_test.defaults",
+ "libavb_rs.uuid.defaults",
],
- test_suites: ["general-tests"],
+ rustlibs: ["libavb_rs_uuid"],
}
diff --git a/rust/src/ops.rs b/rust/src/ops.rs
index 8289816..74f12f5 100644
--- a/rust/src/ops.rs
+++ b/rust/src/ops.rs
@@ -706,7 +706,6 @@ unsafe fn try_read_is_device_unlocked(ops: *mut AvbOps, out_is_unlocked: *mut bo
/// Wraps a callback to convert the given `Result<>` to raw `AvbIOResult` for libavb.
///
/// See corresponding `try_*` function docs.
-#[cfg(feature = "uuid")]
unsafe extern "C" fn get_unique_guid_for_partition(
ops: *mut AvbOps,
partition: *const c_char,
@@ -721,24 +720,12 @@ unsafe extern "C" fn get_unique_guid_for_partition(
))
}
-/// When compiled without the `uuid` feature this callback is not used, just return error.
-#[cfg(not(feature = "uuid"))]
-unsafe extern "C" fn get_unique_guid_for_partition(
- ops: *mut AvbOps,
- partition: *const c_char,
- guid_buf: *mut c_char,
- guid_buf_size: usize,
-) -> AvbIOResult {
- AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION
-}
-
/// Bounces the C callback into the user-provided Rust implementation.
///
/// # Safety
/// * `ops` must have been created via `ScopedAvbOps`.
/// * `partition` must adhere to the requirements of `CStr::from_ptr()`.
/// * `guid_buf` must adhere to the requirements of `slice::from_raw_parts_mut()`.
-#[cfg(feature = "uuid")]
unsafe fn try_get_unique_guid_for_partition(
ops: *mut AvbOps,
partition: *const c_char,
@@ -749,40 +736,66 @@ unsafe fn try_get_unique_guid_for_partition(
check_nonnull(guid_buf)?;
// SAFETY:
- // * we only use `ops` objects created via `ScopedAvbOps` as required.
- // * `ops` is only extracted once and is dropped at the end of the callback.
- let ops = unsafe { as_ops(ops) }?;
- // SAFETY:
// * we've checked that the pointer is non-NULL.
// * libavb gives us a properly-allocated and nul-terminated `partition`.
// * the string contents are not modified while the returned `&CStr` exists.
// * the returned `&CStr` is not held past the scope of this callback.
let partition = unsafe { CStr::from_ptr(partition) };
- let guid = ops.get_unique_guid_for_partition(partition)?;
-
- // Write the UUID string to a uuid buffer which is guaranteed to be large enough, then use
- // `CString` to apply nul-termination.
- // This does allocate memory, but it's short-lived and discarded as soon as we copy the
- // properly-terminated string back to the buffer.
- let mut encode_buffer = uuid::Uuid::encode_buffer();
- let guid_str = guid.as_hyphenated().encode_lower(&mut encode_buffer);
- let guid_cstring = alloc::ffi::CString::new(guid_str.as_bytes()).or(Err(IoError::Io))?;
- let guid_bytes = guid_cstring.to_bytes_with_nul();
-
- if guid_buf_size < guid_bytes.len() {
- // This would indicate some internal error - the uuid library needs more
- // space to print the UUID string than libavb provided.
- return Err(IoError::Oom);
- }
-
// SAFETY:
// * we've checked that the pointer is non-NULL.
// * libavb gives us a properly-allocated `guid_buf` with size `guid_buf_size`.
// * we only access the contents via the returned slice.
// * the returned slice is not held past the scope of this callback.
let buffer = unsafe { slice::from_raw_parts_mut(guid_buf as *mut u8, guid_buf_size) };
- buffer[..guid_bytes.len()].copy_from_slice(guid_bytes);
- Ok(())
+
+ // Initialize the output buffer to the empty string.
+ if buffer.is_empty() {
+ return Err(IoError::Oom);
+ }
+ buffer[0] = b'\0';
+
+ #[cfg(feature = "uuid")]
+ {
+ // SAFETY:
+ // * we only use `ops` objects created via `ScopedAvbOps` as required.
+ // * `ops` is only extracted once and is dropped at the end of the callback.
+ let ops = unsafe { as_ops(ops) }?;
+ let guid = ops.get_unique_guid_for_partition(partition)?;
+
+ // Write the UUID string to a uuid buffer which is guaranteed to be large enough, then use
+ // `CString` to apply nul-termination.
+ // This does allocate memory, but it's short-lived and discarded as soon as we copy the
+ // properly-terminated string back to the buffer.
+ let mut encode_buffer = uuid::Uuid::encode_buffer();
+ let guid_str = guid.as_hyphenated().encode_lower(&mut encode_buffer);
+ let guid_cstring = alloc::ffi::CString::new(guid_str.as_bytes()).or(Err(IoError::Io))?;
+ let guid_bytes = guid_cstring.to_bytes_with_nul();
+
+ if buffer.len() < guid_bytes.len() {
+ // This would indicate some internal error - the uuid library needs more
+ // space to print the UUID string than libavb provided.
+ return Err(IoError::Oom);
+ }
+ buffer[..guid_bytes.len()].copy_from_slice(guid_bytes);
+ Ok(())
+ }
+
+ #[cfg(not(feature = "uuid"))]
+ {
+ // The user doesn't need this feature, but libavb may still attempt to inject the vbmeta
+ // partition GUID into the commandline, depending on the verification flags. In this case
+ // the function needs to return success or verification will fail, but leaving the buffer
+ // as the empty string is sufficient since nobody will be reading the value.
+ //
+ // We restrict this to only "vbmeta*" partitions because if we return success for all
+ // partitions, libavb will also try to inject a "system" partition GUID into the
+ // commandline, which the user doesn't need.
+ partition
+ .to_bytes()
+ .starts_with(b"vbmeta")
+ .then_some(())
+ .ok_or(IoError::NoSuchPartition)
+ }
}
/// Wraps a callback to convert the given `Result<>` to raw `AvbIOResult` for libavb.
@@ -1064,1108 +1077,3 @@ unsafe fn try_validate_public_key_for_partition(
}
Ok(())
}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- use std::collections::HashMap;
- use std::ffi::CString;
- #[cfg(feature = "uuid")]
- use uuid::uuid;
-
- /// Length of a UUID C-string representation including hyphens and a null-terminator.
- #[cfg(feature = "uuid")]
- const UUID_CSTRING_LENGTH: usize = uuid::fmt::Hyphenated::LENGTH + 1;
-
- /// Represents a single fake partition.
- #[derive(Default)]
- struct FakePartition {
- contents: Vec<u8>, // Partition contents
- preloaded: bool, // Whether it should report as preloaded or not
- #[cfg(feature = "uuid")]
- uuid: Uuid, // Partition UUID
- }
-
- /// Fake vbmeta key state.
- struct FakeVbmetaKeyState {
- /// Key trust & rollback index info.
- info: PublicKeyForPartitionInfo,
- /// If specified, indicates the specific partition this vbmeta is tied to (for
- /// `validate_public_key_for_partition()`).
- for_partition: Option<&'static str>,
- }
-
- /// Ops implementation for testing.
- ///
- /// In addition to being used to exercise individual callback wrappers, this will be used for
- /// full verification tests so behavior needs to be correct.
- struct TestOps {
- /// Partitions to provide to libavb callbacks.
- partitions: HashMap<&'static str, FakePartition>,
- /// Vbmeta public keys as a map of {(key, metadata): state}. Querying unknown keys will
- /// return `IoError::Io`.
- ///
- /// See `add_vbmeta_key*()` functions for simpler wrappers to inject these keys.
- vbmeta_keys: HashMap<(&'static [u8], Option<&'static [u8]>), FakeVbmetaKeyState>,
- /// Rollback indices. Accessing unknown locations will return `IoError::Io`.
- rollbacks: HashMap<usize, u64>,
- /// Unlock state. Set an error to simulate IoError during access.
- unlock_state: Result<bool>,
- /// Persistent named values. Set an error to simulate `IoError` during access. Writing
- /// a non-existent persistent value will create it; to simulate `NoSuchValue` instead,
- /// create an entry with `Err(IoError::NoSuchValue)` as the value.
- persistent_values: HashMap<String, Result<Vec<u8>>>,
- }
-
- impl TestOps {
- /// Adds a partition with the given contents.
- ///
- /// Reduces boilerplate a bit by taking in a raw array and returning a &mut so tests can
- /// do something like this:
- ///
- /// ```
- /// test_ops.add_partition("foo", [1, 2, 3, 4]);
- /// test_ops.add_partition("bar", [0, 0]).preloaded = true;
- /// ```
- fn add_partition<const N: usize>(
- &mut self,
- name: &'static str,
- contents: [u8; N],
- ) -> &mut FakePartition {
- self.partitions.insert(
- name,
- FakePartition {
- contents: contents.into(),
- ..Default::default()
- },
- );
- self.partitions.get_mut(name).unwrap()
- }
-
- /// Adds a persistent value with the given state.
- ///
- /// Reduces boilerplate by allowing array input:
- ///
- /// ```
- /// test_ops.add_persistent_value("foo", Ok(b"contents"));
- /// test_ops.add_persistent_value("bar", Err(IoError::NoSuchValue));
- /// ```
- fn add_persistent_value(&mut self, name: &str, contents: Result<&[u8]>) {
- self.persistent_values
- .insert(name.into(), contents.map(|b| b.into()));
- }
-
- /// Adds a fake vbmeta key not tied to any partition.
- fn add_vbmeta_key(
- &mut self,
- key: &'static [u8],
- metadata: Option<&'static [u8]>,
- trusted: bool,
- ) {
- self.vbmeta_keys.insert(
- (key, metadata),
- FakeVbmetaKeyState {
- // `rollback_index_location` doesn't matter in this case, it will be read from
- // the vbmeta blob.
- info: PublicKeyForPartitionInfo {
- trusted,
- rollback_index_location: 0,
- },
- for_partition: None,
- },
- );
- }
-
- /// Adds a fake vbmeta key tied to the given partition and rollback index location.
- fn add_vbmeta_key_for_partition(
- &mut self,
- key: &'static [u8],
- metadata: Option<&'static [u8]>,
- trusted: bool,
- partition: &'static str,
- rollback_index_location: u32,
- ) {
- self.vbmeta_keys.insert(
- (key, metadata),
- FakeVbmetaKeyState {
- info: PublicKeyForPartitionInfo {
- trusted,
- rollback_index_location,
- },
- for_partition: Some(partition),
- },
- );
- }
- }
-
- impl Default for TestOps {
- fn default() -> Self {
- Self {
- partitions: HashMap::new(),
- vbmeta_keys: HashMap::new(),
- rollbacks: HashMap::new(),
- unlock_state: Err(IoError::Io),
- persistent_values: HashMap::new(),
- }
- }
- }
-
- impl Ops for TestOps {
- fn read_from_partition(
- &mut self,
- partition: &CStr,
- offset: i64,
- buffer: &mut [u8],
- ) -> Result<usize> {
- let partition = self
- .partitions
- .get(partition.to_str()?)
- .ok_or(IoError::NoSuchPartition)?;
-
- // We should never be trying to read a preloaded partition from disk since we already
- // have it available in memory.
- assert!(!partition.preloaded);
-
- let contents = &partition.contents;
-
- // Negative offset means count backwards from the end.
- let offset = {
- if offset < 0 {
- offset
- .checked_add(i64::try_from(contents.len()).unwrap())
- .unwrap()
- } else {
- offset
- }
- };
- if offset < 0 {
- return Err(IoError::RangeOutsidePartition);
- }
- let offset = usize::try_from(offset).unwrap();
-
- if offset >= contents.len() {
- return Err(IoError::RangeOutsidePartition);
- }
-
- // Truncating is allowed for reads past the partition end.
- let end = min(offset.checked_add(buffer.len()).unwrap(), contents.len());
- let bytes_read = end - offset;
-
- buffer[..bytes_read].copy_from_slice(&contents[offset..end]);
- Ok(bytes_read)
- }
-
- fn get_preloaded_partition(&mut self, partition: &CStr) -> Result<&[u8]> {
- match self.partitions.get(partition.to_str()?) {
- Some(FakePartition {
- contents,
- preloaded: true,
- ..
- }) => Ok(&contents[..]),
- _ => Err(IoError::NotImplemented),
- }
- }
-
- fn validate_vbmeta_public_key(
- &mut self,
- public_key: &[u8],
- public_key_metadata: Option<&[u8]>,
- ) -> Result<bool> {
- self.vbmeta_keys
- .get(&(public_key, public_key_metadata))
- .ok_or(IoError::Io)
- .map(|k| k.info.trusted)
- }
-
- fn read_rollback_index(&mut self, location: usize) -> Result<u64> {
- self.rollbacks.get(&location).ok_or(IoError::Io).copied()
- }
-
- fn write_rollback_index(&mut self, location: usize, index: u64) -> Result<()> {
- *(self.rollbacks.get_mut(&location).ok_or(IoError::Io)?) = index;
- Ok(())
- }
-
- fn read_is_device_unlocked(&mut self) -> Result<bool> {
- self.unlock_state.clone()
- }
-
- #[cfg(feature = "uuid")]
- fn get_unique_guid_for_partition(&mut self, partition: &CStr) -> Result<Uuid> {
- self.partitions
- .get(partition.to_str()?)
- .map(|p| p.uuid)
- .ok_or(IoError::NoSuchPartition)
- }
-
- fn get_size_of_partition(&mut self, partition: &CStr) -> Result<u64> {
- self.partitions
- .get(partition.to_str()?)
- .map(|p| u64::try_from(p.contents.len()).unwrap())
- .ok_or(IoError::NoSuchPartition)
- }
-
- fn read_persistent_value(&mut self, name: &CStr, value: &mut [u8]) -> Result<usize> {
- match self
- .persistent_values
- .get(name.to_str()?)
- .ok_or(IoError::NoSuchValue)?
- {
- // If we were given enough space, write the value contents.
- Ok(contents) if contents.len() <= value.len() => {
- value[..contents.len()].clone_from_slice(contents);
- Ok(contents.len())
- }
- // Not enough space, tell the caller how much we need.
- Ok(contents) => Err(IoError::InsufficientSpace(contents.len())),
- // Simulated error, return it.
- Err(e) => Err(e.clone()),
- }
- }
-
- fn write_persistent_value(&mut self, name: &CStr, value: &[u8]) -> Result<()> {
- let name = name.to_str()?;
-
- // If the test requested a simulated error on this value, return it.
- if let Some(Err(e)) = self.persistent_values.get(name) {
- return Err(e.clone());
- }
-
- self.persistent_values
- .insert(name.to_string(), Ok(value.to_vec()));
- Ok(())
- }
-
- fn erase_persistent_value(&mut self, name: &CStr) -> Result<()> {
- let name = name.to_str()?;
-
- // If the test requested a simulated error on this value, return it.
- if let Some(Err(e)) = self.persistent_values.get(name) {
- return Err(e.clone());
- }
-
- self.persistent_values.remove(name);
- Ok(())
- }
-
- fn validate_public_key_for_partition(
- &mut self,
- partition: &CStr,
- public_key: &[u8],
- public_key_metadata: Option<&[u8]>,
- ) -> Result<PublicKeyForPartitionInfo> {
- let key = self
- .vbmeta_keys
- .get(&(public_key, public_key_metadata))
- .ok_or(IoError::Io)?;
-
- if let Some(for_partition) = key.for_partition {
- if (for_partition == partition.to_str()?) {
- // The key is registered for this partition; return its info.
- return Ok(key.info);
- }
- }
-
- // No match.
- Err(IoError::Io)
- }
- }
-
- /// Calls the `read_from_partition()` C callback the same way libavb would.
- fn call_read_from_partition(
- ops: &mut TestOps,
- partition: &str,
- offset: i64,
- num_bytes: usize,
- buffer: &mut [u8],
- out_num_read: &mut usize,
- ) -> AvbIOResult {
- let mut user_data = UserData(ops);
- let mut scoped_ops = ScopedAvbOps::new(&mut user_data);
- let avb_ops = scoped_ops.as_mut();
- let part_name = CString::new(partition).unwrap();
-
- // SAFETY: we've properly created and initialized all the raw pointers being passed into
- // this C function.
- unsafe {
- avb_ops.read_from_partition.unwrap()(
- avb_ops,
- part_name.as_ptr(),
- offset,
- num_bytes,
- buffer.as_mut_ptr() as *mut c_void,
- out_num_read,
- )
- }
- }
-
- /// Calls the `get_preloaded_partition()` C callback the same way libavb would.
- ///
- /// # Safety
- /// If `ops` provides preloaded data, `out_buffer` will become an alias to this data. The
- /// lifetime bounds will ensure `ops` outlives `out_buffer`, but the caller must ensure the
- /// preloaded data is not modified while `out_buffer` lives.
- unsafe fn call_get_preloaded_partition<'a, 'b>(
- ops: &'a mut TestOps,
- partition: &str,
- num_bytes: usize,
- out_buffer: &mut &'b mut [u8],
- out_num_bytes_preloaded: &mut usize,
- ) -> AvbIOResult
- where
- 'a: 'b,
- {
- let mut user_data = UserData(ops);
- let mut scoped_ops = ScopedAvbOps::new(&mut user_data);
- let avb_ops = scoped_ops.as_mut();
- let part_name = CString::new(partition).unwrap();
- let mut out_ptr: *mut u8 = ptr::null_mut();
-
- // SAFETY:
- // * We've properly created and initialized all the raw pointers being passed in
- // * We've set up lifetimes such that the `TestOps` which owns the data will outlive
- // `out_buffer` which wraps the data.
- let result = unsafe {
- avb_ops.get_preloaded_partition.unwrap()(
- avb_ops,
- part_name.as_ptr(),
- num_bytes,
- &mut out_ptr,
- out_num_bytes_preloaded,
- )
- };
-
- // If preload failed, libavb will see the null buffer and go to `read_from_partition()`.
- // For our purposes we return `NO_SUCH_PARTITION` so we can detect and test this case.
- if out_ptr.is_null() {
- return AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
- }
-
- // SAFETY: we've properly created the `out` variables.
- *out_buffer = unsafe { slice::from_raw_parts_mut(out_ptr, *out_num_bytes_preloaded) };
- result
- }
-
- /// Calls the `validate_vbmeta_public_key()` C callback the same way libavb would.
- fn call_validate_vbmeta_public_key(
- ops: &mut TestOps,
- public_key: &[u8],
- public_key_metadata: Option<&[u8]>,
- out_is_trusted: &mut bool,
- ) -> AvbIOResult {
- let mut user_data = UserData(ops);
- let mut scoped_ops = ScopedAvbOps::new(&mut user_data);
- let avb_ops = scoped_ops.as_mut();
- let (metadata_ptr, metadata_size) =
- public_key_metadata.map_or((ptr::null(), 0), |m| (m.as_ptr(), m.len()));
-
- // SAFETY: we've properly created and initialized all the raw pointers being passed in.
- unsafe {
- avb_ops.validate_vbmeta_public_key.unwrap()(
- avb_ops,
- public_key.as_ptr(),
- public_key.len(),
- metadata_ptr,
- metadata_size,
- out_is_trusted,
- )
- }
- }
-
- /// Calls the `read_rollback_index()` C callback the same way libavb would.
- fn call_read_rollback_index(
- ops: &mut impl Ops,
- rollback_index_location: usize,
- out_index: &mut u64,
- ) -> AvbIOResult {
- let mut user_data = UserData(ops);
- let mut scoped_ops = ScopedAvbOps::new(&mut user_data);
- let avb_ops = scoped_ops.as_mut();
-
- // SAFETY: we've properly created and initialized all the raw pointers being passed in.
- unsafe { avb_ops.read_rollback_index.unwrap()(avb_ops, rollback_index_location, out_index) }
- }
-
- /// Calls the `write_rollback_index()` C callback the same way libavb would.
- fn call_write_rollback_index(
- ops: &mut impl Ops,
- rollback_index_location: usize,
- index: u64,
- ) -> AvbIOResult {
- let mut user_data = UserData(ops);
- let mut scoped_ops = ScopedAvbOps::new(&mut user_data);
- let avb_ops = scoped_ops.as_mut();
-
- // SAFETY: we've properly created and initialized all the raw pointers being passed in.
- unsafe { avb_ops.write_rollback_index.unwrap()(avb_ops, rollback_index_location, index) }
- }
-
- /// Calls the `read_is_device_unlocked()` C callback the same way libavb would.
- fn call_read_is_device_unlocked(ops: &mut impl Ops, out_is_unlocked: &mut bool) -> AvbIOResult {
- let mut user_data = UserData(ops);
- let mut scoped_ops = ScopedAvbOps::new(&mut user_data);
- let avb_ops = scoped_ops.as_mut();
-
- // SAFETY: we've properly created and initialized all the raw pointers being passed in.
- unsafe { avb_ops.read_is_device_unlocked.unwrap()(avb_ops, out_is_unlocked) }
- }
-
- /// Calls the `get_unique_guid_for_partition()` C callback the same way libavb would.
- fn call_get_unique_guid_for_partition(
- ops: &mut impl Ops,
- partition: &str,
- out_guid_str: &mut [u8],
- ) -> AvbIOResult {
- let mut user_data = UserData(ops);
- let mut scoped_ops = ScopedAvbOps::new(&mut user_data);
- let avb_ops = scoped_ops.as_mut();
- let part_name = CString::new(partition).unwrap();
-
- // SAFETY: we've properly created and initialized all the raw pointers being passed in.
- unsafe {
- avb_ops.get_unique_guid_for_partition.unwrap()(
- avb_ops,
- part_name.as_ptr(),
- out_guid_str.as_mut_ptr() as *mut _,
- out_guid_str.len(),
- )
- }
- }
-
- /// Calls the `get_size_of_partition()` C callback the same way libavb would.
- fn call_get_size_of_partition(
- ops: &mut impl Ops,
- partition: &str,
- out_size: &mut u64,
- ) -> AvbIOResult {
- let mut user_data = UserData(ops);
- let mut scoped_ops = ScopedAvbOps::new(&mut user_data);
- let avb_ops = scoped_ops.as_mut();
- let part_name = CString::new(partition).unwrap();
-
- // SAFETY: we've properly created and initialized all the raw pointers being passed in.
- unsafe { avb_ops.get_size_of_partition.unwrap()(avb_ops, part_name.as_ptr(), out_size) }
- }
-
- /// Calls the `read_persistent_value()` C callback the same way libavb would.
- fn call_read_persistent_value(
- ops: &mut impl Ops,
- name: &str,
- out_buffer: Option<&mut [u8]>,
- out_num_bytes_read: &mut usize,
- ) -> AvbIOResult {
- let mut user_data = UserData(ops);
- let mut scoped_ops = ScopedAvbOps::new(&mut user_data);
- let avb_ops = scoped_ops.as_mut();
- let name = CString::new(name).unwrap();
- let (buffer_ptr, buffer_size) =
- out_buffer.map_or((ptr::null_mut(), 0), |b| (b.as_mut_ptr(), b.len()));
-
- // SAFETY: we've properly created and initialized all the raw pointers being passed in.
- unsafe {
- avb_ops.read_persistent_value.unwrap()(
- avb_ops,
- name.as_ptr(),
- buffer_size,
- buffer_ptr,
- out_num_bytes_read,
- )
- }
- }
-
- /// Calls the `write_persistent_value()` C callback the same way libavb would.
- fn call_write_persistent_value(
- ops: &mut impl Ops,
- name: &str,
- value: Option<&[u8]>,
- ) -> AvbIOResult {
- let mut user_data = UserData(ops);
- let mut scoped_ops = ScopedAvbOps::new(&mut user_data);
- let avb_ops = scoped_ops.as_mut();
- let name = CString::new(name).unwrap();
- let (value_ptr, value_size) = value.map_or((ptr::null(), 0), |v| (v.as_ptr(), v.len()));
-
- // SAFETY: we've properly created and initialized all the raw pointers being passed in.
- unsafe {
- avb_ops.write_persistent_value.unwrap()(avb_ops, name.as_ptr(), value_size, value_ptr)
- }
- }
-
- fn call_validate_public_key_for_partition(
- ops: &mut impl Ops,
- partition: &str,
- public_key: &[u8],
- public_key_metadata: Option<&[u8]>,
- out_is_trusted: &mut bool,
- out_rollback_index_location: &mut u32,
- ) -> AvbIOResult {
- let mut user_data = UserData(ops);
- let mut scoped_ops = ScopedAvbOps::new(&mut user_data);
- let avb_ops = scoped_ops.as_mut();
- let part_name = CString::new(partition).unwrap();
- let (metadata_ptr, metadata_size) =
- public_key_metadata.map_or((ptr::null(), 0), |m| (m.as_ptr(), m.len()));
-
- // SAFETY: we've properly created and initialized all the raw pointers being passed in.
- unsafe {
- avb_ops.validate_public_key_for_partition.unwrap()(
- avb_ops,
- part_name.as_ptr(),
- public_key.as_ptr(),
- public_key.len(),
- metadata_ptr,
- metadata_size,
- out_is_trusted,
- out_rollback_index_location,
- )
- }
- }
-
- #[test]
- fn test_read_from_partition() {
- let mut ops = TestOps::default();
- ops.add_partition("foo", [1, 2, 3, 4]);
-
- let mut buffer: [u8; 8] = [0; 8];
- let mut bytes_read: usize = 0;
- let result = call_read_from_partition(&mut ops, "foo", 0, 4, &mut buffer, &mut bytes_read);
-
- assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK);
- assert_eq!(bytes_read, 4);
- assert_eq!(buffer, [1, 2, 3, 4, 0, 0, 0, 0]);
- }
-
- #[test]
- fn test_read_from_partition_with_offset() {
- let mut ops = TestOps::default();
- ops.add_partition("foo", [1, 2, 3, 4]);
-
- let mut buffer: [u8; 8] = [0; 8];
- let mut bytes_read: usize = 0;
- let result = call_read_from_partition(&mut ops, "foo", 1, 2, &mut buffer, &mut bytes_read);
-
- assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK);
- assert_eq!(bytes_read, 2);
- assert_eq!(buffer, [2, 3, 0, 0, 0, 0, 0, 0]);
- }
-
- #[test]
- fn test_read_from_partition_negative_offset() {
- let mut ops = TestOps::default();
- ops.add_partition("foo", [1, 2, 3, 4]);
-
- let mut buffer: [u8; 8] = [0; 8];
- let mut bytes_read: usize = 0;
- let result = call_read_from_partition(&mut ops, "foo", -2, 2, &mut buffer, &mut bytes_read);
-
- assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK);
- assert_eq!(bytes_read, 2);
- assert_eq!(buffer, [3, 4, 0, 0, 0, 0, 0, 0]);
- }
-
- #[test]
- fn test_read_from_partition_truncate() {
- let mut ops = TestOps::default();
- ops.add_partition("foo", [1, 2, 3, 4]);
-
- let mut buffer: [u8; 8] = [0; 8];
- let mut bytes_read: usize = 0;
- let result = call_read_from_partition(&mut ops, "foo", 0, 8, &mut buffer, &mut bytes_read);
-
- assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK);
- assert_eq!(bytes_read, 4);
- assert_eq!(buffer, [1, 2, 3, 4, 0, 0, 0, 0]);
- }
-
- #[test]
- fn test_read_from_partition_unknown() {
- let mut ops = TestOps::default();
- ops.add_partition("foo", [1, 2, 3, 4]);
-
- let mut buffer: [u8; 8] = [0; 8];
- let mut bytes_read: usize = 10;
- let result = call_read_from_partition(&mut ops, "bar", 0, 8, &mut buffer, &mut bytes_read);
-
- assert_eq!(result, AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION);
- assert_eq!(bytes_read, 0);
- assert_eq!(buffer, [0; 8]);
- }
-
- #[test]
- fn test_get_preloaded_partition() {
- let mut ops = TestOps::default();
- ops.add_partition("foo_preload", [1, 2, 3, 4]).preloaded = true;
-
- let mut contents: &mut [u8] = &mut [];
- let mut size: usize = 0;
- // SAFETY: preloaded data remain valid and unmodified while `contents` exists.
- let result = unsafe {
- call_get_preloaded_partition(&mut ops, "foo_preload", 4, &mut contents, &mut size)
- };
-
- assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK);
- assert_eq!(size, 4);
- assert_eq!(contents, [1, 2, 3, 4]);
- }
-
- #[test]
- fn test_get_preloaded_partition_truncate() {
- let mut ops = TestOps::default();
- ops.add_partition("foo_preload", [1, 2, 3, 4]).preloaded = true;
-
- let mut contents: &mut [u8] = &mut [];
- let mut size: usize = 0;
- // SAFETY: preloaded data remain valid and unmodified while `contents` exists.
- let result = unsafe {
- call_get_preloaded_partition(&mut ops, "foo_preload", 2, &mut contents, &mut size)
- };
-
- assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK);
- assert_eq!(size, 2);
- assert_eq!(contents, [1, 2]);
- }
-
- #[test]
- fn test_get_preloaded_partition_unknown() {
- let mut ops = TestOps::default();
- ops.add_partition("foo_preload", [1, 2, 3, 4]).preloaded = true;
-
- let mut contents: &mut [u8] = &mut [];
- let mut size: usize = 10;
- // SAFETY: requested preloaded data does not exist, no data alias is created.
- let result =
- unsafe { call_get_preloaded_partition(&mut ops, "bar", 4, &mut contents, &mut size) };
-
- assert_eq!(result, AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION);
- assert_eq!(size, 0);
- assert_eq!(contents, []);
- }
-
- #[test]
- fn test_validate_vbmeta_public_key() {
- let mut ops = TestOps::default();
- ops.add_vbmeta_key(b"testkey", None, true);
-
- let mut is_trusted = false;
- let result = call_validate_vbmeta_public_key(&mut ops, b"testkey", None, &mut is_trusted);
-
- assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK);
- assert!(is_trusted);
- }
-
- #[test]
- fn test_validate_vbmeta_public_key_with_metadata() {
- let mut ops = TestOps::default();
- ops.add_vbmeta_key(b"testkey", Some(b"testmeta"), true);
-
- let mut is_trusted = false;
- let result = call_validate_vbmeta_public_key(
- &mut ops,
- b"testkey",
- Some(b"testmeta"),
- &mut is_trusted,
- );
-
- assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK);
- assert!(is_trusted);
- }
-
- #[test]
- fn test_validate_vbmeta_public_key_rejected() {
- let mut ops = TestOps::default();
- ops.add_vbmeta_key(b"testkey", None, false);
-
- let mut is_trusted = true;
- let result = call_validate_vbmeta_public_key(&mut ops, b"testkey", None, &mut is_trusted);
-
- assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK);
- assert!(!is_trusted);
- }
-
- #[test]
- fn test_validate_vbmeta_public_key_error() {
- let mut ops = TestOps::default();
-
- let mut is_trusted = true;
- let result = call_validate_vbmeta_public_key(&mut ops, b"testkey", None, &mut is_trusted);
-
- assert_eq!(result, AvbIOResult::AVB_IO_RESULT_ERROR_IO);
- assert!(!is_trusted);
- }
-
- #[test]
- fn test_read_rollback_index() {
- let mut ops = TestOps::default();
- ops.rollbacks.insert(10, 20);
-
- let mut index = 0u64;
- assert_eq!(
- call_read_rollback_index(&mut ops, 10, &mut index),
- AvbIOResult::AVB_IO_RESULT_OK
- );
- assert_eq!(index, 20);
- }
-
- #[test]
- fn test_read_rollback_index_not_found() {
- let mut ops = TestOps::default();
-
- let mut index = 30u64;
- assert_eq!(
- call_read_rollback_index(&mut ops, 10, &mut index),
- AvbIOResult::AVB_IO_RESULT_ERROR_IO
- );
- assert_eq!(index, 0);
- }
-
- #[test]
- fn test_write_rollback_index() {
- let mut ops = TestOps::default();
- ops.rollbacks.insert(10, 20);
-
- assert_eq!(
- call_write_rollback_index(&mut ops, 10, 30),
- AvbIOResult::AVB_IO_RESULT_OK
- );
- assert_eq!(*ops.rollbacks.get(&10).unwrap(), 30);
- }
-
- #[test]
- fn test_read_is_device_unlocked_yes() {
- let mut ops = TestOps::default();
- ops.unlock_state = Ok(true);
-
- let mut unlocked = false;
- assert_eq!(
- call_read_is_device_unlocked(&mut ops, &mut unlocked),
- AvbIOResult::AVB_IO_RESULT_OK
- );
- assert_eq!(unlocked, true);
- }
-
- #[test]
- fn test_read_is_device_unlocked_no() {
- let mut ops = TestOps::default();
- ops.unlock_state = Ok(false);
-
- let mut unlocked = true;
- assert_eq!(
- call_read_is_device_unlocked(&mut ops, &mut unlocked),
- AvbIOResult::AVB_IO_RESULT_OK
- );
- assert_eq!(unlocked, false);
- }
-
- #[test]
- fn test_read_is_device_unlocked_error() {
- let mut ops = TestOps::default();
- ops.unlock_state = Err(IoError::Io);
-
- let mut unlocked = true;
- assert_eq!(
- call_read_is_device_unlocked(&mut ops, &mut unlocked),
- AvbIOResult::AVB_IO_RESULT_ERROR_IO
- );
- assert_eq!(unlocked, false);
- }
-
- #[cfg(feature = "uuid")]
- #[test]
- fn test_get_unique_guid_for_partition() {
- let mut ops = TestOps::default();
- ops.add_partition("foo", []).uuid = uuid!("01234567-89ab-cdef-0123-456789abcdef");
-
- let mut uuid_str = [b'?'; UUID_CSTRING_LENGTH];
- assert_eq!(
- call_get_unique_guid_for_partition(&mut ops, "foo", &mut uuid_str[..]),
- AvbIOResult::AVB_IO_RESULT_OK
- );
- assert_eq!(
- String::from_utf8(uuid_str.into()).unwrap(),
- "01234567-89ab-cdef-0123-456789abcdef\0"
- )
- }
-
- #[cfg(feature = "uuid")]
- #[test]
- fn test_get_unique_guid_for_partition_unknown() {
- let mut ops = TestOps::default();
-
- let mut uuid_str = [b'?'; UUID_CSTRING_LENGTH];
- assert_eq!(
- call_get_unique_guid_for_partition(&mut ops, "foo", &mut uuid_str[..]),
- AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION
- );
- }
-
- #[cfg(feature = "uuid")]
- #[test]
- fn test_get_unique_guid_for_partition_undersize_buffer() {
- let mut ops = TestOps::default();
- ops.add_partition("foo", []).uuid = uuid!("01234567-89ab-cdef-0123-456789abcdef");
-
- let mut uuid_str = [b'?'; UUID_CSTRING_LENGTH - 1];
- assert_eq!(
- call_get_unique_guid_for_partition(&mut ops, "foo", &mut uuid_str[..]),
- AvbIOResult::AVB_IO_RESULT_ERROR_OOM
- );
- }
-
- #[cfg(not(feature = "uuid"))]
- #[test]
- fn test_get_unique_guid_for_partition_not_implemented() {
- let mut ops = TestOps::default();
- ops.add_partition("foo", []);
-
- // Without the `uuid` feature enabled, get_unique_guid_for_partition() should
- // unconditionally fail without trying to call any Ops functions.
- let mut uuid_str = [b'?'; 0];
- assert_eq!(
- call_get_unique_guid_for_partition(&mut ops, "foo", &mut uuid_str[..]),
- AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION
- );
- }
-
- #[test]
- fn test_get_size_of_partition() {
- let mut ops = TestOps::default();
- ops.add_partition("foo", [1, 2, 3, 4]);
-
- let mut size = 0;
- let result = call_get_size_of_partition(&mut ops, "foo", &mut size);
-
- assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK);
- assert_eq!(size, 4);
- }
-
- #[test]
- fn test_get_size_of_partition_unknown() {
- let mut ops = TestOps::default();
-
- let mut size = 10;
- let result = call_get_size_of_partition(&mut ops, "foo", &mut size);
-
- assert_eq!(result, AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION);
- assert_eq!(size, 0);
- }
-
- #[test]
- fn test_read_persistent_value() {
- let mut ops = TestOps::default();
- ops.add_persistent_value("foo", Ok(b"1234"));
-
- let mut size = 0;
- let mut buffer = [b'.'; 8];
- let result = call_read_persistent_value(&mut ops, "foo", Some(&mut buffer), &mut size);
-
- assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK);
- assert_eq!(size, 4);
- assert_eq!(&buffer[..], b"1234....");
- }
-
- #[test]
- fn test_read_persistent_value_buffer_too_small() {
- let mut ops = TestOps::default();
- ops.add_persistent_value("foo", Ok(b"1234"));
-
- let mut size = 0;
- let mut buffer = [b'.'; 2];
- let result = call_read_persistent_value(&mut ops, "foo", Some(&mut buffer), &mut size);
-
- assert_eq!(result, AvbIOResult::AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE);
- assert_eq!(size, 4);
- assert_eq!(&buffer[..], b"..");
- }
-
- #[test]
- fn test_read_persistent_value_buffer_null() {
- let mut ops = TestOps::default();
- ops.add_persistent_value("foo", Ok(b"1234"));
-
- let mut size = 0;
- let result = call_read_persistent_value(&mut ops, "foo", None, &mut size);
-
- assert_eq!(result, AvbIOResult::AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE);
- assert_eq!(size, 4);
- }
-
- #[test]
- fn test_read_persistent_value_error() {
- let mut ops = TestOps::default();
- ops.add_persistent_value("foo", Err(IoError::Io));
-
- let mut size = 10;
- let mut buffer = [b'.'; 8];
- let result = call_read_persistent_value(&mut ops, "foo", Some(&mut buffer), &mut size);
-
- assert_eq!(result, AvbIOResult::AVB_IO_RESULT_ERROR_IO);
- assert_eq!(size, 0);
- assert_eq!(&buffer[..], b"........");
- }
-
- #[test]
- fn test_read_persistent_value_unknown() {
- let mut ops = TestOps::default();
-
- let mut size = 10;
- let mut buffer = [b'.'; 8];
- let result = call_read_persistent_value(&mut ops, "foo", Some(&mut buffer), &mut size);
-
- assert_eq!(result, AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_VALUE);
- assert_eq!(size, 0);
- assert_eq!(&buffer[..], b"........");
- }
-
- #[test]
- fn test_write_persistent_value() {
- let mut ops = TestOps::default();
-
- let result = call_write_persistent_value(&mut ops, "foo", Some(b"1234"));
-
- assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK);
- assert_eq!(
- ops.persistent_values.get("foo").unwrap().as_ref().unwrap(),
- b"1234"
- );
- }
-
- #[test]
- fn test_write_persistent_value_overwrite_existing() {
- let mut ops = TestOps::default();
- ops.add_persistent_value("foo", Ok(b"1234"));
-
- let result = call_write_persistent_value(&mut ops, "foo", Some(b"5678"));
-
- assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK);
- assert_eq!(
- ops.persistent_values.get("foo").unwrap().as_ref().unwrap(),
- b"5678"
- );
- }
-
- #[test]
- fn test_write_persistent_value_erase_existing() {
- let mut ops = TestOps::default();
- ops.add_persistent_value("foo", Ok(b"1234"));
-
- let result = call_write_persistent_value(&mut ops, "foo", None);
-
- assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK);
- assert!(ops.persistent_values.is_empty());
- }
-
- #[test]
- fn test_write_persistent_value_error() {
- let mut ops = TestOps::default();
- ops.add_persistent_value("foo", Err(IoError::NoSuchValue));
-
- let result = call_write_persistent_value(&mut ops, "foo", Some(b"1234"));
-
- assert_eq!(result, AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_VALUE);
- }
-
- #[test]
- fn test_validate_public_key_for_partition() {
- let mut ops = TestOps::default();
- ops.add_vbmeta_key_for_partition(b"testkey", None, true, "foo", 1);
-
- let mut is_trusted = false;
- let mut rollback_location = 0;
- let result = call_validate_public_key_for_partition(
- &mut ops,
- "foo",
- b"testkey",
- None,
- &mut is_trusted,
- &mut rollback_location,
- );
-
- assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK);
- assert!(is_trusted);
- assert_eq!(rollback_location, 1);
- }
-
- #[test]
- fn test_validate_public_key_for_partition_with_metadata() {
- let mut ops = TestOps::default();
- ops.add_vbmeta_key_for_partition(b"testkey", Some(b"testmeta"), true, "foo", 1);
-
- let mut is_trusted = false;
- let mut rollback_location = 0;
- let result = call_validate_public_key_for_partition(
- &mut ops,
- "foo",
- b"testkey",
- Some(b"testmeta"),
- &mut is_trusted,
- &mut rollback_location,
- );
-
- assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK);
- assert!(is_trusted);
- assert_eq!(rollback_location, 1);
- }
-
- #[test]
- fn test_validate_public_key_for_partition_rejected() {
- let mut ops = TestOps::default();
- ops.add_vbmeta_key_for_partition(b"testkey", None, false, "foo", 1);
-
- let mut is_trusted = true;
- let mut rollback_location = 0;
- let result = call_validate_public_key_for_partition(
- &mut ops,
- "foo",
- b"testkey",
- None,
- &mut is_trusted,
- &mut rollback_location,
- );
-
- assert_eq!(result, AvbIOResult::AVB_IO_RESULT_OK);
- assert!(!is_trusted);
- assert_eq!(rollback_location, 1);
- }
-
- #[test]
- fn test_validate_public_key_for_partition_unknown_key() {
- let mut ops = TestOps::default();
-
- let mut is_trusted = true;
- let mut rollback_location = 10;
- let result = call_validate_public_key_for_partition(
- &mut ops,
- "foo",
- b"testkey",
- None,
- &mut is_trusted,
- &mut rollback_location,
- );
-
- assert_ne!(result, AvbIOResult::AVB_IO_RESULT_OK);
- assert!(!is_trusted);
- assert_eq!(rollback_location, 0);
- }
-
- #[test]
- fn test_validate_public_key_for_partition_unknown_partition() {
- let mut ops = TestOps::default();
- ops.add_vbmeta_key_for_partition(b"testkey", None, true, "foo", 1);
-
- let mut is_trusted = true;
- let mut rollback_location = 10;
- let result = call_validate_public_key_for_partition(
- &mut ops,
- "bar",
- b"testkey",
- None,
- &mut is_trusted,
- &mut rollback_location,
- );
-
- assert_ne!(result, AvbIOResult::AVB_IO_RESULT_OK);
- assert!(!is_trusted);
- assert_eq!(rollback_location, 0);
- }
-}
diff --git a/rust/tests/test_ops.rs b/rust/tests/test_ops.rs
new file mode 100644
index 0000000..1e1ad1b
--- /dev/null
+++ b/rust/tests/test_ops.rs
@@ -0,0 +1,325 @@
+// Copyright 2023, 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.
+
+//! Provides `avb::Ops` test fixtures.
+
+use avb::{IoError, Ops, PublicKeyForPartitionInfo};
+use std::{cmp::min, collections::HashMap, ffi::CStr};
+#[cfg(feature = "uuid")]
+use uuid::Uuid;
+
+/// Common `Result` type for `IoError` errors.
+type Result<T> = core::result::Result<T, IoError>;
+
+/// Represents a single fake partition.
+#[derive(Default)]
+pub struct FakePartition {
+ /// Partition contents.
+ pub contents: Vec<u8>,
+
+ /// Whether the partition should report as preloaded or not.
+ pub preloaded: bool,
+
+ /// Partition UUID.
+ #[cfg(feature = "uuid")]
+ pub uuid: Uuid,
+}
+
+/// Fake vbmeta key state.
+pub struct FakeVbmetaKeyState {
+ /// Key trust & rollback index info.
+ pub info: PublicKeyForPartitionInfo,
+
+ /// If specified, indicates the specific partition this vbmeta is tied to (for
+ /// `validate_public_key_for_partition()`).
+ pub for_partition: Option<&'static str>,
+}
+
+/// Fake `Ops` test fixture.
+///
+/// The user is expected to set up the internal values to the desired device state - disk contents,
+/// rollback indices, etc. This class then uses this state to implement the avb callback operations.
+pub struct TestOps {
+ /// Partitions to provide to libavb callbacks.
+ pub partitions: HashMap<&'static str, FakePartition>,
+
+ /// Vbmeta public keys as a map of {(key, metadata): state}. Querying unknown keys will
+ /// return `IoError::Io`.
+ ///
+ /// See `add_vbmeta_key*()` functions for simpler wrappers to inject these keys.
+ pub vbmeta_keys: HashMap<(Vec<u8>, Option<Vec<u8>>), FakeVbmetaKeyState>,
+
+ /// Rollback indices. Accessing unknown locations will return `IoError::Io`.
+ pub rollbacks: HashMap<usize, u64>,
+
+ /// Unlock state. Set an error to simulate IoError during access.
+ pub unlock_state: Result<bool>,
+
+ /// Persistent named values. Set an error to simulate `IoError` during access. Writing
+ /// a non-existent persistent value will create it; to simulate `NoSuchValue` instead,
+ /// create an entry with `Err(IoError::NoSuchValue)` as the value.
+ pub persistent_values: HashMap<String, Result<Vec<u8>>>,
+}
+
+impl TestOps {
+ /// Adds a partition with the given contents.
+ ///
+ /// Reduces boilerplate a bit by taking in a raw array and returning a &mut so tests can
+ /// do something like this:
+ ///
+ /// ```
+ /// test_ops.add_partition("foo", [1, 2, 3, 4]);
+ /// test_ops.add_partition("bar", [0, 0]).preloaded = true;
+ /// ```
+ pub fn add_partition<T: Into<Vec<u8>>>(
+ &mut self,
+ name: &'static str,
+ contents: T,
+ ) -> &mut FakePartition {
+ self.partitions.insert(
+ name,
+ FakePartition {
+ contents: contents.into(),
+ ..Default::default()
+ },
+ );
+ self.partitions.get_mut(name).unwrap()
+ }
+
+ /// Adds a persistent value with the given state.
+ ///
+ /// Reduces boilerplate by allowing array input:
+ ///
+ /// ```
+ /// test_ops.add_persistent_value("foo", Ok(b"contents"));
+ /// test_ops.add_persistent_value("bar", Err(IoError::NoSuchValue));
+ /// ```
+ pub fn add_persistent_value(&mut self, name: &str, contents: Result<&[u8]>) {
+ self.persistent_values
+ .insert(name.into(), contents.map(|b| b.into()));
+ }
+
+ /// Adds a fake vbmeta key not tied to any partition.
+ pub fn add_vbmeta_key(&mut self, key: Vec<u8>, metadata: Option<Vec<u8>>, trusted: bool) {
+ self.vbmeta_keys.insert(
+ (key, metadata),
+ FakeVbmetaKeyState {
+ // `rollback_index_location` doesn't matter in this case, it will be read from
+ // the vbmeta blob.
+ info: PublicKeyForPartitionInfo {
+ trusted,
+ rollback_index_location: 0,
+ },
+ for_partition: None,
+ },
+ );
+ }
+
+ /// Adds a fake vbmeta key tied to the given partition and rollback index location.
+ pub fn add_vbmeta_key_for_partition(
+ &mut self,
+ key: Vec<u8>,
+ metadata: Option<Vec<u8>>,
+ trusted: bool,
+ partition: &'static str,
+ rollback_index_location: u32,
+ ) {
+ self.vbmeta_keys.insert(
+ (key, metadata),
+ FakeVbmetaKeyState {
+ info: PublicKeyForPartitionInfo {
+ trusted,
+ rollback_index_location,
+ },
+ for_partition: Some(partition),
+ },
+ );
+ }
+}
+
+impl Default for TestOps {
+ fn default() -> Self {
+ Self {
+ partitions: HashMap::new(),
+ vbmeta_keys: HashMap::new(),
+ rollbacks: HashMap::new(),
+ unlock_state: Err(IoError::Io),
+ persistent_values: HashMap::new(),
+ }
+ }
+}
+
+impl Ops for TestOps {
+ fn read_from_partition(
+ &mut self,
+ partition: &CStr,
+ offset: i64,
+ buffer: &mut [u8],
+ ) -> Result<usize> {
+ let partition = self
+ .partitions
+ .get(partition.to_str()?)
+ .ok_or(IoError::NoSuchPartition)?;
+
+ // We should never be trying to read a preloaded partition from disk since we already
+ // have it available in memory.
+ assert!(!partition.preloaded);
+
+ let contents = &partition.contents;
+
+ // Negative offset means count backwards from the end.
+ let offset = {
+ if offset < 0 {
+ offset
+ .checked_add(i64::try_from(contents.len()).unwrap())
+ .unwrap()
+ } else {
+ offset
+ }
+ };
+ if offset < 0 {
+ return Err(IoError::RangeOutsidePartition);
+ }
+ let offset = usize::try_from(offset).unwrap();
+
+ if offset >= contents.len() {
+ return Err(IoError::RangeOutsidePartition);
+ }
+
+ // Truncating is allowed for reads past the partition end.
+ let end = min(offset.checked_add(buffer.len()).unwrap(), contents.len());
+ let bytes_read = end - offset;
+
+ buffer[..bytes_read].copy_from_slice(&contents[offset..end]);
+ Ok(bytes_read)
+ }
+
+ fn get_preloaded_partition(&mut self, partition: &CStr) -> Result<&[u8]> {
+ match self.partitions.get(partition.to_str()?) {
+ Some(FakePartition {
+ contents,
+ preloaded: true,
+ ..
+ }) => Ok(&contents[..]),
+ _ => Err(IoError::NotImplemented),
+ }
+ }
+
+ fn validate_vbmeta_public_key(
+ &mut self,
+ public_key: &[u8],
+ public_key_metadata: Option<&[u8]>,
+ ) -> Result<bool> {
+ self.vbmeta_keys
+ // The compiler can't match (&[u8], Option<&[u8]>) to keys of type
+ // (Vec<u8>, Option<Vec<u8>>) so we turn the &[u8] into vectors here. This is a bit
+ // inefficient, but it's simple which is more important for tests than efficiency.
+ .get(&(public_key.to_vec(), public_key_metadata.map(|m| m.to_vec())))
+ .ok_or(IoError::Io)
+ .map(|k| k.info.trusted)
+ }
+
+ fn read_rollback_index(&mut self, location: usize) -> Result<u64> {
+ self.rollbacks.get(&location).ok_or(IoError::Io).copied()
+ }
+
+ fn write_rollback_index(&mut self, location: usize, index: u64) -> Result<()> {
+ *(self.rollbacks.get_mut(&location).ok_or(IoError::Io)?) = index;
+ Ok(())
+ }
+
+ fn read_is_device_unlocked(&mut self) -> Result<bool> {
+ self.unlock_state.clone()
+ }
+
+ #[cfg(feature = "uuid")]
+ fn get_unique_guid_for_partition(&mut self, partition: &CStr) -> Result<Uuid> {
+ self.partitions
+ .get(partition.to_str()?)
+ .map(|p| p.uuid)
+ .ok_or(IoError::NoSuchPartition)
+ }
+
+ fn get_size_of_partition(&mut self, partition: &CStr) -> Result<u64> {
+ self.partitions
+ .get(partition.to_str()?)
+ .map(|p| u64::try_from(p.contents.len()).unwrap())
+ .ok_or(IoError::NoSuchPartition)
+ }
+
+ fn read_persistent_value(&mut self, name: &CStr, value: &mut [u8]) -> Result<usize> {
+ match self
+ .persistent_values
+ .get(name.to_str()?)
+ .ok_or(IoError::NoSuchValue)?
+ {
+ // If we were given enough space, write the value contents.
+ Ok(contents) if contents.len() <= value.len() => {
+ value[..contents.len()].clone_from_slice(contents);
+ Ok(contents.len())
+ }
+ // Not enough space, tell the caller how much we need.
+ Ok(contents) => Err(IoError::InsufficientSpace(contents.len())),
+ // Simulated error, return it.
+ Err(e) => Err(e.clone()),
+ }
+ }
+
+ fn write_persistent_value(&mut self, name: &CStr, value: &[u8]) -> Result<()> {
+ let name = name.to_str()?;
+
+ // If the test requested a simulated error on this value, return it.
+ if let Some(Err(e)) = self.persistent_values.get(name) {
+ return Err(e.clone());
+ }
+
+ self.persistent_values
+ .insert(name.to_string(), Ok(value.to_vec()));
+ Ok(())
+ }
+
+ fn erase_persistent_value(&mut self, name: &CStr) -> Result<()> {
+ let name = name.to_str()?;
+
+ // If the test requested a simulated error on this value, return it.
+ if let Some(Err(e)) = self.persistent_values.get(name) {
+ return Err(e.clone());
+ }
+
+ self.persistent_values.remove(name);
+ Ok(())
+ }
+
+ fn validate_public_key_for_partition(
+ &mut self,
+ partition: &CStr,
+ public_key: &[u8],
+ public_key_metadata: Option<&[u8]>,
+ ) -> Result<PublicKeyForPartitionInfo> {
+ let key = self
+ .vbmeta_keys
+ .get(&(public_key.to_vec(), public_key_metadata.map(|m| m.to_vec())))
+ .ok_or(IoError::Io)?;
+
+ if let Some(for_partition) = key.for_partition {
+ if (for_partition == partition.to_str()?) {
+ // The key is registered for this partition; return its info.
+ return Ok(key.info);
+ }
+ }
+
+ // No match.
+ Err(IoError::Io)
+ }
+}
diff --git a/rust/tests/tests.rs b/rust/tests/tests.rs
new file mode 100644
index 0000000..a9df1c4
--- /dev/null
+++ b/rust/tests/tests.rs
@@ -0,0 +1,15 @@
+// Copyright 2023, 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.
+
+mod test_ops;