Merge "Look for test files under our module" into main
diff --git a/Android.bp b/Android.bp
index f50007f..2091a90 100644
--- a/Android.bp
+++ b/Android.bp
@@ -68,6 +68,7 @@
     module_type: "cc_defaults",
     config_namespace: "ANDROID",
     bool_variables: [
+        "release_avf_enable_dice_changes",
         "release_avf_enable_virt_cpufreq",
     ],
     properties: [
@@ -78,6 +79,9 @@
 avf_flag_aware_cc_defaults {
     name: "avf_build_flags_cc",
     soong_config_variables: {
+        release_avf_enable_dice_changes: {
+            cflags: ["-DAVF_OPEN_DICE_CHANGES=1"],
+        },
         release_avf_enable_virt_cpufreq: {
             cflags: ["-DAVF_ENABLE_VIRT_CPUFREQ=1"],
         },
diff --git a/flags/cpp/Android.bp b/flags/cpp/Android.bp
new file mode 100644
index 0000000..da4158a
--- /dev/null
+++ b/flags/cpp/Android.bp
@@ -0,0 +1,13 @@
+cc_library_static {
+    name: "libavf_cc_flags",
+    defaults: ["avf_build_flags_cc"],
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.virt",
+    ],
+
+    export_include_dirs: ["include"],
+    local_include_dirs: ["include"],
+    ramdisk_available: true,
+    recovery_available: true,
+}
diff --git a/flags/cpp/include/android/avf_cc_flags.h b/flags/cpp/include/android/avf_cc_flags.h
new file mode 100644
index 0000000..536ea9f
--- /dev/null
+++ b/flags/cpp/include/android/avf_cc_flags.h
@@ -0,0 +1,31 @@
+// 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.
+
+#pragma once
+
+// TODO(b/309090563): remove this file once build flags are exposed to aconfig.
+
+namespace android {
+namespace virtualization {
+
+inline bool IsOpenDiceChangesFlagEnabled() {
+#ifdef AVF_OPEN_DICE_CHANGES
+    return AVF_OPEN_DICE_CHANGES;
+#else
+    return false;
+#endif
+}
+
+} // namespace virtualization
+} // namespace android
diff --git a/libs/dice/driver/Android.bp b/libs/dice/driver/Android.bp
new file mode 100644
index 0000000..c93bd7d
--- /dev/null
+++ b/libs/dice/driver/Android.bp
@@ -0,0 +1,50 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+    name: "libdice_driver_defaults",
+    crate_name: "dice_driver",
+    defaults: [
+        "avf_build_flags_rust",
+    ],
+    srcs: ["src/lib.rs"],
+    edition: "2021",
+    prefer_rlib: true,
+    rustlibs: [
+        "libanyhow",
+        "libbyteorder",
+        "libcoset",
+        "libdice_policy_builder",
+        "libdiced_open_dice",
+        "libdiced_sample_inputs",
+        "libkeystore2_crypto_rust",
+        "liblibc",
+        "liblog_rust",
+        "libnix",
+        "libonce_cell",
+        "libopenssl",
+        "libthiserror",
+        "libserde_cbor",
+    ],
+    multilib: {
+        lib32: {
+            enabled: false,
+        },
+    },
+}
+
+rust_library {
+    name: "libdice_driver",
+    defaults: ["libdice_driver_defaults"],
+}
+
+rust_test {
+    name: "libdice_driver_test",
+    defaults: ["libdice_driver_defaults"],
+    test_suites: ["general-tests"],
+    rustlibs: [
+        "libhex",
+        "libtempfile",
+    ],
+}
diff --git a/microdroid_manager/src/dice_driver.rs b/libs/dice/driver/src/lib.rs
similarity index 70%
rename from microdroid_manager/src/dice_driver.rs
rename to libs/dice/driver/src/lib.rs
index 229f3e0..79edb51 100644
--- a/microdroid_manager/src/dice_driver.rs
+++ b/libs/dice/driver/src/lib.rs
@@ -32,13 +32,26 @@
 
 /// Artifacts that are mapped into the process address space from the driver.
 pub enum DiceDriver<'a> {
+    /// Implementation that reads bcc handover from the dice driver.
     Real {
+        /// Path to the driver character device (e.g. /dev/open-dice0).
         driver_path: PathBuf,
+        /// Address of the memory to mmap driver to.
         mmap_addr: *mut c_void,
+        /// Size of the mmap.
         mmap_size: usize,
+        /// BCC handover.
         bcc_handover: BccHandover<'a>,
     },
+    /// Fake implementation used in tests and non-protected VMs.
     Fake(OwnedDiceArtifacts),
+    /// Implementation that reads bcc handover from the file.
+    FromFile {
+        /// Path to the file to read dice chain from,
+        file_path: PathBuf,
+        /// Dice artifacts read from file_path,
+        dice_artifacts: OwnedDiceArtifacts,
+    },
 }
 
 impl DiceDriver<'_> {
@@ -46,13 +59,15 @@
         match self {
             Self::Real { bcc_handover, .. } => bcc_handover,
             Self::Fake(owned_dice_artifacts) => owned_dice_artifacts,
+            Self::FromFile { dice_artifacts, .. } => dice_artifacts,
         }
     }
 
-    pub fn new(driver_path: &Path) -> Result<Self> {
+    /// Creates a new dice driver from the given driver_path.
+    pub fn new(driver_path: &Path, is_strict_boot: bool) -> Result<Self> {
         if driver_path.exists() {
             log::info!("Using DICE values from driver");
-        } else if super::is_strict_boot() {
+        } else if is_strict_boot {
             bail!("Strict boot requires DICE value from driver but none were found");
         } else {
             log::warn!("Using sample DICE values");
@@ -90,6 +105,15 @@
         })
     }
 
+    /// Create a new dice driver that reads dice_artifacts from the given file.
+    pub fn from_file(file_path: &Path) -> Result<Self> {
+        let file =
+            fs::File::open(file_path).map_err(|error| Error::new(error).context("open file"))?;
+        let dice_artifacts = serde_cbor::from_reader(file)
+            .map_err(|error| Error::new(error).context("read file"))?;
+        Ok(Self::FromFile { file_path: file_path.to_path_buf(), dice_artifacts })
+    }
+
     /// Derives a sealing key of `key_length` bytes from the DICE sealing CDI.
     pub fn get_sealing_key(&self, identifier: &[u8], key_length: usize) -> Result<ZVec> {
         // Deterministically derive a key to use for sealing data, rather than using the CDI
@@ -101,6 +125,7 @@
         Ok(key)
     }
 
+    /// Derives a new dice chain.
     pub fn derive(
         self,
         code_hash: Hash,
@@ -147,3 +172,36 @@
         }
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    fn assert_eq_bytes(expected: &[u8], actual: &[u8]) {
+        assert_eq!(
+            expected,
+            actual,
+            "Expected {}, got {}",
+            hex::encode(expected),
+            hex::encode(actual)
+        )
+    }
+
+    #[test]
+    fn test_write_bcc_to_file_read_from_file() -> Result<()> {
+        let dice_artifacts = diced_sample_inputs::make_sample_bcc_and_cdis()?;
+
+        let test_file = tempfile::NamedTempFile::new()?;
+        serde_cbor::to_writer(test_file.as_file(), &dice_artifacts)?;
+        test_file.as_file().sync_all()?;
+
+        let dice = DiceDriver::from_file(test_file.as_ref())?;
+
+        let dice_artifacts2 = dice.dice_artifacts();
+        assert_eq_bytes(dice_artifacts.cdi_attest(), dice_artifacts2.cdi_attest());
+        assert_eq_bytes(dice_artifacts.cdi_seal(), dice_artifacts2.cdi_seal());
+        assert_eq_bytes(dice_artifacts.bcc().expect("bcc"), dice_artifacts2.bcc().expect("bcc"));
+
+        Ok(())
+    }
+}
diff --git a/libs/dice/open_dice/Android.bp b/libs/dice/open_dice/Android.bp
index 79d0b96..ecc40f7 100644
--- a/libs/dice/open_dice/Android.bp
+++ b/libs/dice/open_dice/Android.bp
@@ -39,12 +39,15 @@
     rustlibs: [
         "libopen_dice_android_bindgen",
         "libopen_dice_cbor_bindgen",
+        "libserde",
         "libzeroize",
     ],
     features: [
         "alloc",
+        "serde_derive",
         "std",
     ],
+    proc_macros: ["libserde_derive"],
     shared_libs: [
         "libcrypto",
     ],
diff --git a/libs/dice/open_dice/src/dice.rs b/libs/dice/open_dice/src/dice.rs
index e42e373..e330e00 100644
--- a/libs/dice/open_dice/src/dice.rs
+++ b/libs/dice/open_dice/src/dice.rs
@@ -23,6 +23,8 @@
     DICE_INLINE_CONFIG_SIZE, DICE_PRIVATE_KEY_SEED_SIZE, DICE_PRIVATE_KEY_SIZE,
     DICE_PUBLIC_KEY_SIZE, DICE_SIGNATURE_SIZE,
 };
+#[cfg(feature = "serde_derive")]
+use serde_derive::{Deserialize, Serialize};
 use std::{marker::PhantomData, ptr};
 use zeroize::{Zeroize, ZeroizeOnDrop};
 
@@ -82,6 +84,7 @@
 /// for sensitive data like CDI values and private key.
 /// CDI Values.
 #[derive(Debug, Zeroize, ZeroizeOnDrop, Default)]
+#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
 pub struct CdiValues {
     /// Attestation CDI.
     pub cdi_attest: [u8; CDI_SIZE],
diff --git a/libs/dice/open_dice/src/retry.rs b/libs/dice/open_dice/src/retry.rs
index a6303bd..d9551f3 100644
--- a/libs/dice/open_dice/src/retry.rs
+++ b/libs/dice/open_dice/src/retry.rs
@@ -25,12 +25,15 @@
 use crate::ops::generate_certificate;
 #[cfg(feature = "alloc")]
 use alloc::vec::Vec;
+#[cfg(feature = "serde_derive")]
+use serde_derive::{Deserialize, Serialize};
 
 /// Artifacts stores a set of dice artifacts comprising CDI_ATTEST, CDI_SEAL,
 /// and the BCC formatted attestation certificate chain.
 /// As we align with the DICE standards today, this is the certificate chain
 /// is also called DICE certificate chain.
 #[derive(Debug)]
+#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
 pub struct OwnedDiceArtifacts {
     /// CDI Values.
     cdi_values: CdiValues,
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 999dc52..e19a343 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -49,10 +49,12 @@
     module_type: "android_system_image",
     config_namespace: "ANDROID",
     bool_variables: [
+        "release_avf_enable_dice_changes",
         "release_avf_enable_multi_tenant_microdroid_vm",
     ],
     properties: [
         "deps",
+        "dirs",
     ],
 }
 
@@ -154,6 +156,14 @@
 
     // Below are dependencies that are conditionally enabled depending on value of build flags.
     soong_config_variables: {
+        release_avf_enable_dice_changes: {
+            deps: [
+                "derive_microdroid_vendor_dice_node",
+            ],
+            dirs: [
+                "microdroid_resources",
+            ],
+        },
         release_avf_enable_multi_tenant_microdroid_vm: {
             deps: [
                 "microdroid_etc_passwd",
@@ -295,7 +305,19 @@
     },
 }
 
-android_filesystem {
+soong_config_module_type {
+    name: "flag_aware_microdroid_filesystem",
+    module_type: "android_filesystem",
+    config_namespace: "ANDROID",
+    bool_variables: [
+        "release_avf_enable_dice_changes",
+    ],
+    properties: [
+        "dirs",
+    ],
+}
+
+flag_aware_microdroid_filesystem {
     name: "microdroid_ramdisk",
     deps: [
         "init_first_stage.microdroid",
@@ -305,14 +327,23 @@
         "proc",
         "sys",
 
-        // TODO(jiyong): remove these
         "mnt",
         "debug_ramdisk",
         "second_stage_resources",
     ],
     type: "compressed_cpio",
+
+    // Below are dependencies that are conditionally enabled depending on value of build flags.
+    soong_config_variables: {
+        release_avf_enable_dice_changes: {
+            dirs: [
+                "microdroid_resources",
+            ],
+        },
+    },
 }
 
+// TODO(ioffe): rename to microdroid_first_stage_ramdisk
 android_filesystem {
     name: "microdroid_fstab_ramdisk",
     deps: [
diff --git a/microdroid/derive_microdroid_vendor_dice_node/Android.bp b/microdroid/derive_microdroid_vendor_dice_node/Android.bp
new file mode 100644
index 0000000..de1bef7
--- /dev/null
+++ b/microdroid/derive_microdroid_vendor_dice_node/Android.bp
@@ -0,0 +1,22 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+    name: "derive_microdroid_vendor_dice_node_defaults",
+    crate_name: "derive_microdroid_vendor_dice_node",
+    defaults: ["avf_build_flags_rust"],
+    srcs: ["src/main.rs"],
+    rustlibs: [
+        "libanyhow",
+        "libclap",
+    ],
+    bootstrap: true,
+    prefer_rlib: true,
+}
+
+rust_binary {
+    name: "derive_microdroid_vendor_dice_node",
+    defaults: ["derive_microdroid_vendor_dice_node_defaults"],
+    stem: "derive_microdroid_vendor_dice_node",
+}
diff --git a/microdroid/derive_microdroid_vendor_dice_node/src/main.rs b/microdroid/derive_microdroid_vendor_dice_node/src/main.rs
new file mode 100644
index 0000000..1d5db0d
--- /dev/null
+++ b/microdroid/derive_microdroid_vendor_dice_node/src/main.rs
@@ -0,0 +1,38 @@
+// 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.
+
+//! Derives microdroid vendor dice node.
+
+use anyhow::Error;
+use clap::Parser;
+use std::path::PathBuf;
+
+#[derive(Parser)]
+struct Args {
+    /// Path to the dice driver (e.g. /dev/open-dice0)
+    #[arg(long)]
+    dice_driver: PathBuf,
+    /// Path to the microdroid-vendor.img disk image.
+    #[arg(long)]
+    microdroid_vendor_disk_image: PathBuf,
+    /// File to save resulting dice chain to.
+    #[arg(long)]
+    output: PathBuf,
+}
+
+fn main() -> Result<(), Error> {
+    let args = Args::parse();
+    eprintln!("{:?} {:?} {:?}", args.dice_driver, args.microdroid_vendor_disk_image, args.output);
+    Ok(())
+}
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 81bb409..9c9a3d0 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -29,6 +29,7 @@
         "libclient_vm_csr",
         "libciborium",
         "libcoset",
+        "libdice_driver",
         "libdice_policy_builder",
         "libdiced_open_dice",
         "libdiced_sample_inputs",
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
index a8b88aa..7f65159 100644
--- a/microdroid_manager/src/dice.rs
+++ b/microdroid_manager/src/dice.rs
@@ -12,12 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use crate::dice_driver::DiceDriver;
 use crate::instance::{ApexData, ApkData};
 use crate::{is_debuggable, MicrodroidData};
 use anyhow::{bail, Context, Result};
 use ciborium::{cbor, Value};
 use coset::CborSerializable;
+use dice_driver::DiceDriver;
 use diced_open_dice::OwnedDiceArtifacts;
 use microdroid_metadata::PayloadMetadata;
 use openssl::sha::{sha512, Sha512};
diff --git a/microdroid_manager/src/instance.rs b/microdroid_manager/src/instance.rs
index 7a9f0e0..888c451 100644
--- a/microdroid_manager/src/instance.rs
+++ b/microdroid_manager/src/instance.rs
@@ -33,11 +33,11 @@
 //! The payload of a partition is encrypted/signed by a key that is unique to the loader and to the
 //! VM as well. Failing to decrypt/authenticate a partition by a loader stops the boot process.
 
-use crate::dice_driver::DiceDriver;
 use crate::ioutil;
 
 use anyhow::{anyhow, bail, Context, Result};
 use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
+use dice_driver::DiceDriver;
 use openssl::symm::{decrypt_aead, encrypt_aead, Cipher};
 use serde::{Deserialize, Serialize};
 use std::fs::{File, OpenOptions};
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 0d67632..8d2c629 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -15,7 +15,6 @@
 //! Microdroid Manager
 
 mod dice;
-mod dice_driver;
 mod instance;
 mod ioutil;
 mod payload;
@@ -33,12 +32,12 @@
 };
 
 use crate::dice::dice_derivation;
-use crate::dice_driver::DiceDriver;
 use crate::instance::{InstanceDisk, MicrodroidData};
 use crate::verify::verify_payload;
 use crate::vm_payload_service::register_vm_payload_service;
 use anyhow::{anyhow, bail, ensure, Context, Error, Result};
 use binder::Strong;
+use dice_driver::DiceDriver;
 use keystore2_crypto::ZVec;
 use libc::VMADDR_CID_HOST;
 use log::{error, info};
@@ -241,7 +240,8 @@
     vm_payload_service_fd: OwnedFd,
 ) -> Result<i32> {
     let metadata = load_metadata().context("Failed to load payload metadata")?;
-    let dice = DiceDriver::new(Path::new("/dev/open-dice0")).context("Failed to load DICE")?;
+    let dice = DiceDriver::new(Path::new("/dev/open-dice0"), is_strict_boot())
+        .context("Failed to load DICE")?;
 
     let mut instance = InstanceDisk::new().context("Failed to load instance.img")?;
     let saved_data =
diff --git a/pvmfw/README.md b/pvmfw/README.md
index 2758a5d..053e4f7 100644
--- a/pvmfw/README.md
+++ b/pvmfw/README.md
@@ -30,11 +30,11 @@
 hypervisor, although trusted, is also validated.
 
 Once it has been determined that the platform can be trusted, pvmfw derives
-unique secrets for the guest through the [_Boot Certificate Chain_][BCC]
-("BCC", see [Open Profile for DICE][open-dice]) that can be used to prove the
-identity of the pVM to local and remote actors. If any operation or check fails,
-or in case of a missing prerequisite, pvmfw will abort the boot process of the
-pVM, effectively preventing non-compliant pVMs and/or guests from running.
+unique secrets for the guest through the [_DICE Chain_][android-dice] (see
+[Open Profile for DICE][open-dice]) that can be used to prove the identity of
+the pVM to local and remote actors. If any operation or check fails, or in case
+of a missing prerequisite, pvmfw will abort the boot process of the pVM,
+effectively preventing non-compliant pVMs and/or guests from running.
 Otherwise, it hands over the pVM to the guest kernel by jumping to its first
 instruction, similarly to a bootloader.
 
@@ -42,7 +42,7 @@
 
 [AVF]: https://source.android.com/docs/core/virtualization
 [why-avf]: https://source.android.com/docs/core/virtualization/whyavf
-[BCC]: https://pigweed.googlesource.com/open-dice/+/refs/heads/main/docs/android.md
+[android-dice]: https://pigweed.googlesource.com/open-dice/+/refs/heads/main/docs/android.md
 [pKVM]: https://source.android.com/docs/core/virtualization/architecture#hypervisor
 [open-dice]: https://pigweed.googlesource.com/open-dice/+/refs/heads/main/docs/specification.md
 
@@ -153,7 +153,7 @@
 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
 | (Padding to 8-byte alignment) |
 +===============================+ <-- FIRST
-|       {First blob: BCC}       |
+|   {First blob: DICE chain}    |
 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ <-- FIRST_END
 | (Padding to 8-byte alignment) |
 +===============================+ <-- SECOND
@@ -189,7 +189,7 @@
 The header format itself is agnostic of the internal format of the individual
 blos it refers to. In version 1.0, it describes two blobs:
 
-- entry 0 must point to a valid BCC Handover (see below)
+- entry 0 must point to a valid DICE chain handover (see below)
 - entry 1 may point to a [DTBO] to be applied to the pVM device tree. See
   [debug policy][debug_policy] for an example.
 
@@ -230,39 +230,39 @@
 [secretkeeper_key]: https://android.googlesource.com/platform/system/secretkeeper/+/refs/heads/main/README.md#secretkeeper-public-key
 [vendor_hashtree_digest]: ../microdroid/README.md#verification-of-vendor-image
 
-#### Virtual Platform Boot Certificate Chain Handover
+#### Virtual Platform DICE Chain Handover
 
-The format of the BCC entry mentioned above, compatible with the
-[`BccHandover`][BccHandover] defined by the Open Profile for DICE reference
-implementation, is described by the following [CDDL][CDDL]:
+The format of the DICE chain entry mentioned above, compatible with the
+[`AndroidDiceHandover`][AndroidDiceHandover] defined by the Open Profile for
+DICE reference implementation, is described by the following [CDDL][CDDL]:
 ```
-PvmfwBccHandover = {
+PvmfwDiceHandover = {
   1 : bstr .size 32,     ; CDI_Attest
   2 : bstr .size 32,     ; CDI_Seal
-  3 : Bcc,               ; Certificate chain
+  3 : DiceCertChain,     ; Android DICE chain
 }
 ```
 
 and contains the _Compound Device Identifiers_ ("CDIs"), used to derive the
 next-stage secret, and a certificate chain, intended for pVM attestation. Note
-that it differs from the `BccHandover` defined by the specification in that its
-`Bcc` field is mandatory (while optional in the original).
+that it differs from the `AndroidDiceHandover` defined by the specification in
+that its `DiceCertChain` field is mandatory (while optional in the original).
 
 Devices that fully implement DICE should provide a certificate rooted at the
 Unique Device Secret (UDS) in a boot stage preceding the pvmfw loader (typically
-ABL), in such a way that it would receive a valid `BccHandover`, that can be
-passed to [`BccHandoverMainFlow`][BccHandoverMainFlow] along with the inputs
-described below.
+ABL), in such a way that it would receive a valid `AndroidDiceHandover`, that
+can be passed to [`DiceAndroidHandoverMainFlow`][DiceAndroidHandoverMainFlow] along with
+the inputs described below.
 
 Otherwise, as an intermediate step towards supporting DICE throughout the
-software stack of the device, incomplete implementations may root the BCC at the
-pvmfw loader, using an arbitrary constant as initial CDI. The pvmfw loader can
-easily do so by:
+software stack of the device, incomplete implementations may root the DICE chain
+at the pvmfw loader, using an arbitrary constant as initial CDI. The pvmfw
+loader can easily do so by:
 
-1. Building a BCC-less `BccHandover` using CBOR operations
-   ([example][Trusty-BCC]) and containing the constant CDIs
-1. Passing the resulting `BccHandover` to `BccHandoverMainFlow` as described
-   above
+1. Building an "empty" `AndroidDiceHandover` using CBOR operations only
+   containing constant CDIs ([example][Trusty-BCC])
+1. Passing the resulting `AndroidDiceHandover` to `DiceAndroidHandoverMainFlow`
+   as described above
 
 The recommended DICE inputs at this stage are:
 
@@ -278,13 +278,14 @@
   storage and changes during every factory reset) or similar that changes as
   part of the device lifecycle (_e.g._ reset)
 
-The resulting `BccHandover` is then used by pvmfw in a similar way to derive
-another [DICE layer][Layering], passed to the guest through a `/reserved-memory`
-device tree node marked as [`compatible=”google,open-dice”`][dice-dt].
+The resulting `AndroidDiceHandover` is then used by pvmfw in a similar way to
+derive another [DICE layer][Layering], passed to the guest through a
+`/reserved-memory` device tree node marked as
+[`compatible=”google,open-dice”`][dice-dt].
 
 [AVB]: https://source.android.com/docs/security/features/verifiedboot/boot-flow
-[BccHandover]: https://pigweed.googlesource.com/open-dice/+/825e3beb6c/src/android/bcc.c#260
-[BccHandoverMainFlow]: https://pigweed.googlesource.com/open-dice/+/825e3beb6c/src/android/bcc.c#199
+[AndroidDiceHandover]: https://pigweed.googlesource.com/open-dice/+/42ae7760023/src/android.c#212
+[DiceAndroidHandoverMainFlow]: https://pigweed.googlesource.com/open-dice/+/42ae7760023/src/android.c#221
 [CDDL]: https://datatracker.ietf.org/doc/rfc8610
 [dice-mode]: https://pigweed.googlesource.com/open-dice/+/refs/heads/main/docs/specification.md#Mode-Value-Details
 [dice-dt]: https://www.kernel.org/doc/Documentation/devicetree/bindings/reserved-memory/google%2Copen-dice.yaml
@@ -383,7 +384,7 @@
 After verifying the guest kernel, pvmfw boots it using the Linux ABI described
 above. It uses the device tree to pass the following:
 
-- a reserved memory node containing the produced BCC:
+- a reserved memory node containing the produced DICE chain:
 
     ```
     / {
@@ -462,15 +463,15 @@
 above must be replicated to produce a single file containing the pvmfw binary
 and its configuration data.
 
-As a quick prototyping solution, a valid BCC (such as the [bcc.dat] test file)
-can be appended to the `pvmfw.bin` image with `pvmfw-tool`.
+As a quick prototyping solution, a valid DICE chain (such as this [test
+file][bcc.dat]) can be appended to the `pvmfw.bin` image with `pvmfw-tool`.
 
 ```shell
 m pvmfw-tool pvmfw_bin
 PVMFW_BIN=${ANDROID_PRODUCT_OUT}/system/etc/pvmfw.bin
-BCC_DAT=${ANDROID_BUILD_TOP}/packages/modules/Virtualization/tests/pvmfw/assets/bcc.dat
+DICE=${ANDROID_BUILD_TOP}/packages/modules/Virtualization/tests/pvmfw/assets/bcc.dat
 
-pvmfw-tool custom_pvmfw ${PVMFW_BIN} ${BCC_DAT}
+pvmfw-tool custom_pvmfw ${PVMFW_BIN} ${DICE}
 ```
 
 The result can then be pushed to the device. Pointing the system property
diff --git a/pvmfw/src/device_assignment.rs b/pvmfw/src/device_assignment.rs
index c732822..edfe000 100644
--- a/pvmfw/src/device_assignment.rs
+++ b/pvmfw/src/device_assignment.rs
@@ -55,8 +55,10 @@
     InvalidSymbols,
     /// Malformed <reg>. Can't parse.
     MalformedReg,
-    /// Invalid <reg>. Failed to validate with HVC.
-    InvalidReg,
+    /// Invalid physical <reg> of assigned device.
+    InvalidPhysReg(u64, u64),
+    /// Invalid virtual <reg> of assigned device.
+    InvalidReg(u64, u64),
     /// Invalid <interrupts>
     InvalidInterrupts,
     /// Malformed <iommus>
@@ -104,7 +106,12 @@
                 "Invalid property in /__symbols__. Must point to valid assignable device node."
             ),
             Self::MalformedReg => write!(f, "Malformed <reg>. Can't parse"),
-            Self::InvalidReg => write!(f, "Invalid <reg>. Failed to validate with hypervisor"),
+            Self::InvalidReg(addr, size) => {
+                write!(f, "Invalid guest MMIO region (addr: {addr:#x}, size: {size:#x})")
+            }
+            Self::InvalidPhysReg(addr, size) => {
+                write!(f, "Invalid physical MMIO region (addr: {addr:#x}, size: {size:#x})")
+            }
             Self::InvalidInterrupts => write!(f, "Invalid <interrupts>"),
             Self::MalformedIommus => write!(f, "Malformed <iommus>. Can't parse."),
             Self::InvalidIommus => {
@@ -521,21 +528,29 @@
         physical_device_reg: &[DeviceReg],
         hypervisor: &dyn DeviceAssigningHypervisor,
     ) -> Result<()> {
-        if device_reg.len() != physical_device_reg.len() {
-            return Err(DeviceAssignmentError::InvalidReg);
-        }
+        let mut virt_regs = device_reg.iter();
+        let mut phys_regs = physical_device_reg.iter();
         // PV reg and physical reg should have 1:1 match in order.
-        for (reg, phys_reg) in device_reg.iter().zip(physical_device_reg.iter()) {
+        for (reg, phys_reg) in virt_regs.by_ref().zip(phys_regs.by_ref()) {
             let addr = hypervisor.get_phys_mmio_token(reg.addr, reg.size).map_err(|e| {
-                error!("Failed to validate device <reg>, error={e:?}, reg={reg:x?}");
-                DeviceAssignmentError::InvalidReg
+                error!("Hypervisor error while requesting MMIO token: {e}");
+                DeviceAssignmentError::InvalidReg(reg.addr, reg.size)
             })?;
             // Only check address because hypervisor guaranatees size match when success.
             if phys_reg.addr != addr {
-                error!("Failed to validate device <reg>. No matching phys reg for reg={reg:x?}");
-                return Err(DeviceAssignmentError::InvalidReg);
+                error!("Assigned device {reg:x?} has unexpected physical address");
+                return Err(DeviceAssignmentError::InvalidPhysReg(addr, reg.size));
             }
         }
+
+        if let Some(DeviceReg { addr, size }) = virt_regs.next() {
+            return Err(DeviceAssignmentError::InvalidReg(*addr, *size));
+        }
+
+        if let Some(DeviceReg { addr, size }) = phys_regs.next() {
+            return Err(DeviceAssignmentError::InvalidPhysReg(*addr, *size));
+        }
+
         Ok(())
     }
 
@@ -592,11 +607,11 @@
         // So we need to mark what's matched or not.
         let mut physical_device_iommu = physical_device_iommu.to_vec();
         for (pviommu, vsid) in iommus {
-            let (id, sid) = hypervisor.get_phys_iommu_token(pviommu.id.into(), vsid.0.into())
-            .map_err(|e| {
-                error!("Failed to validate device <iommus>, error={e:?}, pviommu={pviommu:?}, vsid={vsid:?}");
-                DeviceAssignmentError::InvalidIommus
-            })?;
+            let (id, sid) =
+                hypervisor.get_phys_iommu_token(pviommu.id.into(), vsid.0.into()).map_err(|e| {
+                    error!("Hypervisor error while requesting IOMMU token ({pviommu:?}, {vsid:?}): {e}");
+                    DeviceAssignmentError::InvalidIommus
+                })?;
 
             let pos = physical_device_iommu
                 .iter()
@@ -1308,7 +1323,7 @@
         };
         let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
 
-        assert_eq!(device_info, Err(DeviceAssignmentError::InvalidReg));
+        assert_eq!(device_info, Err(DeviceAssignmentError::InvalidReg(0x9, 0xFF)));
     }
 
     #[test]
@@ -1324,7 +1339,7 @@
         };
         let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor);
 
-        assert_eq!(device_info, Err(DeviceAssignmentError::InvalidReg));
+        assert_eq!(device_info, Err(DeviceAssignmentError::InvalidPhysReg(0xF10000, 0x1000)));
     }
 
     #[test]
diff --git a/pvmfw/testdata/test_crosvm_dt_base.dtsi b/pvmfw/testdata/test_crosvm_dt_base.dtsi
index 0c1a311..10d7e6d 100644
--- a/pvmfw/testdata/test_crosvm_dt_base.dtsi
+++ b/pvmfw/testdata/test_crosvm_dt_base.dtsi
@@ -1,5 +1,4 @@
 /dts-v1/;
-/plugin/;
 
 // This is generated manually by removing unassigned pvIOMMU nodes
 // from patched platform.dts.
diff --git a/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_without_symbols.dts b/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_without_symbols.dts
index 20a8c1b..495a0eb 100644
--- a/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_without_symbols.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_without_symbols.dts
@@ -1,4 +1,5 @@
 /dts-v1/;
+// /plugin/ omitted as this DTBO has been written by hand as a DTB in this DTS.
 
 / {
     host {
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_duplicated_pviommus.dts b/pvmfw/testdata/test_pvmfw_devices_with_duplicated_pviommus.dts
index 4ebf034..5646c7f 100644
--- a/pvmfw/testdata/test_pvmfw_devices_with_duplicated_pviommus.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_with_duplicated_pviommus.dts
@@ -1,5 +1,4 @@
 /dts-v1/;
-/plugin/;
 
 /include/ "test_crosvm_dt_base.dtsi"
 
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts b/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts
index a9e30be..04052fa 100644
--- a/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts
@@ -1,5 +1,4 @@
 /dts-v1/;
-/plugin/;
 
 /include/ "test_crosvm_dt_base.dtsi"
 
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts b/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts
index 2470725..32e5610 100644
--- a/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts
@@ -1,5 +1,4 @@
 /dts-v1/;
-/plugin/;
 
 /include/ "test_crosvm_dt_base.dtsi"
 
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts b/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts
index 3aaafdd..3698c1d 100644
--- a/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts
@@ -1,5 +1,4 @@
 /dts-v1/;
-/plugin/;
 
 /include/ "test_crosvm_dt_base.dtsi"
 / {
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_multiple_reg_iommus.dts b/pvmfw/testdata/test_pvmfw_devices_with_multiple_reg_iommus.dts
index 0676aa3..94fe18e 100644
--- a/pvmfw/testdata/test_pvmfw_devices_with_multiple_reg_iommus.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_with_multiple_reg_iommus.dts
@@ -1,5 +1,4 @@
 /dts-v1/;
-/plugin/;
 
 /include/ "test_crosvm_dt_base.dtsi"
 
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_rng.dts b/pvmfw/testdata/test_pvmfw_devices_with_rng.dts
index a987098..429771c 100644
--- a/pvmfw/testdata/test_pvmfw_devices_with_rng.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_with_rng.dts
@@ -1,5 +1,4 @@
 /dts-v1/;
-/plugin/;
 
 /include/ "test_crosvm_dt_base.dtsi"
 
diff --git a/pvmfw/testdata/test_pvmfw_devices_without_device.dts b/pvmfw/testdata/test_pvmfw_devices_without_device.dts
index ee0be3a..05daaa7 100644
--- a/pvmfw/testdata/test_pvmfw_devices_without_device.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_without_device.dts
@@ -1,5 +1,4 @@
 /dts-v1/;
-/plugin/;
 
 /include/ "test_crosvm_dt_base.dtsi"
 
diff --git a/pvmfw/testdata/test_pvmfw_devices_without_iommus.dts b/pvmfw/testdata/test_pvmfw_devices_without_iommus.dts
index 1a12c87..96fb073 100644
--- a/pvmfw/testdata/test_pvmfw_devices_without_iommus.dts
+++ b/pvmfw/testdata/test_pvmfw_devices_without_iommus.dts
@@ -1,5 +1,4 @@
 /dts-v1/;
-/plugin/;
 
 /include/ "test_crosvm_dt_base.dtsi"
 
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidCapabilitiesTest.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidCapabilitiesTest.java
index eb23e21..61f4cba 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidCapabilitiesTest.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidCapabilitiesTest.java
@@ -15,11 +15,16 @@
  */
 package com.android.microdroid.test;
 
-import static com.google.common.truth.Truth.assertWithMessage;
+import static android.content.pm.PackageManager.FEATURE_VIRTUALIZATION_FRAMEWORK;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.common.truth.TruthJUnit.assume;
+
+import android.os.SystemProperties;
 import android.system.virtualmachine.VirtualMachineManager;
 
 import com.android.compatibility.common.util.CddTest;
+import com.android.compatibility.common.util.VsrTest;
 import com.android.microdroid.test.device.MicrodroidDeviceTestBase;
 
 import org.junit.Ignore;
@@ -28,16 +33,16 @@
 import org.junit.runners.JUnit4;
 
 /**
- * Test the advertised AVF capabilities include the ability to start some type of VM.
+ * Test the device's AVF capabilities.
  *
  * <p>Tests in MicrodroidTests run on either protected or non-protected VMs, provided they are
  * supported. If neither is they are all skipped. So we need a separate test (that doesn't call
- * {@link #prepareTestSetup}) to make sure that at least one of these is available.
+ * {@link #prepareTestSetup}) when we need to run on such devices.
  */
 @RunWith(JUnit4.class)
 public class MicrodroidCapabilitiesTest extends MicrodroidDeviceTestBase {
     @Test
-    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+    @CddTest(requirements = "9.17/C-1-6")
     @Ignore("b/326092480")
     public void supportForProtectedOrNonProtectedVms() {
         assumeSupportedDevice();
@@ -57,4 +62,16 @@
                 .that(vmCapabilities)
                 .isNotEqualTo(0);
     }
+
+    @Test
+    @VsrTest(requirements = "VSR-7.1-001.005")
+    public void avfIsRequired() {
+        int vendorApiLevel = SystemProperties.getInt("ro.vendor.api_level", 0);
+        assume().withMessage("Requirement doesn't apply due to vendor API level")
+                .that(vendorApiLevel)
+                .isAtLeast(202404);
+        boolean avfSupported =
+                getContext().getPackageManager().hasSystemFeature(FEATURE_VIRTUALIZATION_FRAMEWORK);
+        assertWithMessage("Device doesn't support AVF").that(avfSupported).isTrue();
+    }
 }