Merge "[attestation] Rename the client VM attestation API" into main
diff --git a/libs/libfdt/Android.bp b/libs/libfdt/Android.bp
index 0a05471..b889ee5 100644
--- a/libs/libfdt/Android.bp
+++ b/libs/libfdt/Android.bp
@@ -57,6 +57,7 @@
         ":fdt_test_tree_multiple_memory_ranges_dtb",
         ":fdt_test_tree_empty_memory_range_dtb",
         ":fdt_test_tree_no_memory_node_dtb",
+        ":fdt_test_tree_phandle_dtb",
     ],
     prefer_rlib: true,
     rustlibs: [
@@ -91,3 +92,10 @@
     srcs: ["tests/data/test_tree_no_memory_node.dts"],
     out: ["data/test_tree_no_memory_node.dtb"],
 }
+
+genrule {
+    name: "fdt_test_tree_phandle_dtb",
+    defaults: ["dts_to_dtb"],
+    srcs: ["tests/data/test_tree_phandle.dts"],
+    out: ["data/test_tree_phandle.dtb"],
+}
diff --git a/libs/libfdt/src/iterators.rs b/libs/libfdt/src/iterators.rs
index 7bffd8c..000f723 100644
--- a/libs/libfdt/src/iterators.rs
+++ b/libs/libfdt/src/iterators.rs
@@ -17,6 +17,7 @@
 use crate::Fdt;
 use crate::FdtError;
 use crate::FdtNode;
+use crate::FdtProperty;
 use crate::{AddrCells, SizeCells};
 use core::ffi::CStr;
 use core::marker::PhantomData;
@@ -321,3 +322,29 @@
         res
     }
 }
+
+/// Iterator over properties
+#[derive(Debug)]
+pub struct PropertyIterator<'a> {
+    prop: Option<FdtProperty<'a>>,
+}
+
+impl<'a> PropertyIterator<'a> {
+    pub(crate) fn new(node: &'a FdtNode) -> Result<Self, FdtError> {
+        let prop = node.first_property()?;
+
+        Ok(Self { prop })
+    }
+}
+
+impl<'a> Iterator for PropertyIterator<'a> {
+    type Item = FdtProperty<'a>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let res = self.prop;
+
+        self.prop = res?.next_property().ok()?;
+
+        res
+    }
+}
diff --git a/libs/libfdt/src/lib.rs b/libs/libfdt/src/lib.rs
index a6d5739..d800c13 100644
--- a/libs/libfdt/src/lib.rs
+++ b/libs/libfdt/src/lib.rs
@@ -20,8 +20,8 @@
 mod iterators;
 
 pub use iterators::{
-    AddressRange, CellIterator, CompatibleIterator, MemRegIterator, RangesIterator, Reg,
-    RegIterator, SubnodeIterator,
+    AddressRange, CellIterator, CompatibleIterator, MemRegIterator, PropertyIterator,
+    RangesIterator, Reg, RegIterator, SubnodeIterator,
 };
 
 use core::cmp::max;
@@ -29,6 +29,7 @@
 use core::fmt;
 use core::mem;
 use core::ops::Range;
+use core::ptr;
 use core::result;
 use zerocopy::AsBytes as _;
 
@@ -194,6 +195,71 @@
     }
 }
 
+/// DT property wrapper to abstract endianess changes
+#[repr(transparent)]
+#[derive(Debug)]
+struct FdtPropertyStruct(libfdt_bindgen::fdt_property);
+
+impl FdtPropertyStruct {
+    fn from_offset(fdt: &Fdt, offset: c_int) -> Result<&Self> {
+        let mut len = 0;
+        let prop =
+            // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
+            unsafe { libfdt_bindgen::fdt_get_property_by_offset(fdt.as_ptr(), offset, &mut len) };
+        if prop.is_null() {
+            fdt_err(len)?;
+            return Err(FdtError::Internal); // shouldn't happen.
+        }
+        // SAFETY: prop is only returned when it points to valid libfdt_bindgen.
+        Ok(unsafe { &*prop.cast::<FdtPropertyStruct>() })
+    }
+
+    fn name_offset(&self) -> c_int {
+        u32::from_be(self.0.nameoff).try_into().unwrap()
+    }
+
+    fn data_len(&self) -> usize {
+        u32::from_be(self.0.len).try_into().unwrap()
+    }
+
+    fn data_ptr(&self) -> *const c_void {
+        self.0.data.as_ptr().cast::<_>()
+    }
+}
+
+/// DT property.
+#[derive(Clone, Copy, Debug)]
+pub struct FdtProperty<'a> {
+    fdt: &'a Fdt,
+    offset: c_int,
+    property: &'a FdtPropertyStruct,
+}
+
+impl<'a> FdtProperty<'a> {
+    fn new(fdt: &'a Fdt, offset: c_int) -> Result<Self> {
+        let property = FdtPropertyStruct::from_offset(fdt, offset)?;
+        Ok(Self { fdt, offset, property })
+    }
+
+    /// Returns the property name
+    pub fn name(&self) -> Result<&'a CStr> {
+        self.fdt.string(self.property.name_offset())
+    }
+
+    /// Returns the property value
+    pub fn value(&self) -> Result<&'a [u8]> {
+        self.fdt.get_from_ptr(self.property.data_ptr(), self.property.data_len())
+    }
+
+    fn next_property(&self) -> Result<Option<Self>> {
+        let ret =
+            // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
+            unsafe { libfdt_bindgen::fdt_next_property_offset(self.fdt.as_ptr(), self.offset) };
+
+        fdt_err_or_option(ret)?.map(|offset| Self::new(self.fdt, offset)).transpose()
+    }
+}
+
 /// DT node.
 #[derive(Clone, Copy, Debug)]
 pub struct FdtNode<'a> {
@@ -214,6 +280,21 @@
         Ok(Self { fdt: self.fdt, offset: fdt_err(ret)? })
     }
 
+    /// Returns supernode with depth. Note that root is at depth 0.
+    pub fn supernode_at_depth(&self, depth: usize) -> Result<Self> {
+        // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
+        let ret = unsafe {
+            libfdt_bindgen::fdt_supernode_atdepth_offset(
+                self.fdt.as_ptr(),
+                self.offset,
+                depth.try_into().unwrap(),
+                ptr::null_mut(),
+            )
+        };
+
+        Ok(Self { fdt: self.fdt, offset: fdt_err(ret)? })
+    }
+
     /// Returns the standard (deprecated) device_type <string> property.
     pub fn device_type(&self) -> Result<Option<&CStr>> {
         self.getprop_str(CStr::from_bytes_with_nul(b"device_type\0").unwrap())
@@ -403,6 +484,40 @@
 
         Ok(fdt_err_or_option(ret)?.map(|offset| FdtNode { fdt: self.fdt, offset }))
     }
+
+    /// Returns an iterator of properties
+    pub fn properties(&'a self) -> Result<PropertyIterator<'a>> {
+        PropertyIterator::new(self)
+    }
+
+    fn first_property(&self) -> Result<Option<FdtProperty<'a>>> {
+        let ret =
+            // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
+            unsafe { libfdt_bindgen::fdt_first_property_offset(self.fdt.as_ptr(), self.offset) };
+
+        fdt_err_or_option(ret)?.map(|offset| FdtProperty::new(self.fdt, offset)).transpose()
+    }
+}
+
+/// Phandle of a FDT node
+#[repr(transparent)]
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub struct Phandle(u32);
+
+impl Phandle {
+    /// Creates a new Phandle
+    pub fn new(value: u32) -> Result<Self> {
+        if value == 0 || value > libfdt_bindgen::FDT_MAX_PHANDLE {
+            return Err(FdtError::BadPhandle);
+        }
+        Ok(Self(value))
+    }
+}
+
+impl From<Phandle> for u32 {
+    fn from(phandle: Phandle) -> u32 {
+        phandle.0
+    }
 }
 
 /// Mutable FDT node.
@@ -512,7 +627,7 @@
         fdt_err_expect_zero(ret)
     }
 
-    /// Sets the given property with FDT_NOP, effectively removing it from the DT.
+    /// Deletes the given property effectively from DT, by setting it with FDT_NOP.
     pub fn nop_property(&mut self, name: &CStr) -> Result<()> {
         // SAFETY: Accesses are constrained to the DT totalsize (validated by ctor) when the
         // library locates the node's property.
@@ -555,12 +670,49 @@
 
     /// Adds a new subnode to the given node and return it as a FdtNodeMut on success.
     pub fn add_subnode(&'a mut self, name: &CStr) -> Result<Self> {
+        let offset = self.add_subnode_offset(name.to_bytes())?;
+        Ok(Self { fdt: self.fdt, offset })
+    }
+
+    /// Adds a new subnode to the given node with name and namelen, and returns it as a FdtNodeMut
+    /// on success.
+    pub fn add_subnode_with_namelen(&'a mut self, name: &CStr, namelen: usize) -> Result<Self> {
+        let offset = { self.add_subnode_offset(&name.to_bytes()[..namelen])? };
+        Ok(Self { fdt: self.fdt, offset })
+    }
+
+    fn add_subnode_offset(&mut self, name: &[u8]) -> Result<c_int> {
+        let namelen = name.len().try_into().unwrap();
         // SAFETY: Accesses are constrained to the DT totalsize (validated by ctor).
         let ret = unsafe {
-            libfdt_bindgen::fdt_add_subnode(self.fdt.as_mut_ptr(), self.offset, name.as_ptr())
+            libfdt_bindgen::fdt_add_subnode_namelen(
+                self.fdt.as_mut_ptr(),
+                self.offset,
+                name.as_ptr().cast::<_>(),
+                namelen,
+            )
         };
+        fdt_err(ret)
+    }
 
-        Ok(Self { fdt: self.fdt, offset: fdt_err(ret)? })
+    /// Returns the subnode of the given name with len.
+    pub fn subnode_with_namelen(&'a mut self, name: &CStr, namelen: usize) -> Result<Option<Self>> {
+        let offset = self.subnode_offset(&name.to_bytes()[..namelen])?;
+        Ok(offset.map(|offset| Self { fdt: self.fdt, offset }))
+    }
+
+    fn subnode_offset(&self, name: &[u8]) -> Result<Option<c_int>> {
+        let namelen = name.len().try_into().unwrap();
+        // SAFETY: Accesses are constrained to the DT totalsize (validated by ctor).
+        let ret = unsafe {
+            libfdt_bindgen::fdt_subnode_offset_namelen(
+                self.fdt.as_ptr(),
+                self.offset,
+                name.as_ptr().cast::<_>(),
+                namelen,
+            )
+        };
+        fdt_err_or_option(ret)
     }
 
     fn parent(&'a self) -> Result<FdtNode<'a>> {
@@ -596,7 +748,7 @@
     // node, and delete the current node, the Rust borrow checker kicks in. The next node has a
     // mutable reference to DT, so we can't use current node (which also has a mutable reference to
     // DT).
-    pub fn delete_and_next_compatible(self, compatible: &CStr) -> Result<Option<Self>> {
+    pub fn delete_and_next_compatible(mut self, compatible: &CStr) -> Result<Option<Self>> {
         // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
         let ret = unsafe {
             libfdt_bindgen::fdt_node_offset_by_compatible(
@@ -607,13 +759,33 @@
         };
         let next_offset = fdt_err_or_option(ret)?;
 
-        // SAFETY: fdt_nop_node alter only the bytes in the blob which contain the node and its
-        // properties and subnodes, and will not alter or move any other part of the tree.
-        let ret = unsafe { libfdt_bindgen::fdt_nop_node(self.fdt.as_mut_ptr(), self.offset) };
-        fdt_err_expect_zero(ret)?;
+        if Some(self.offset) == next_offset {
+            return Err(FdtError::Internal);
+        }
+
+        // SAFETY: nop_self() only touches bytes of the self and its properties and subnodes, and
+        // doesn't alter any other blob in the tree. self.fdt and next_offset would remain valid.
+        unsafe { self.nop_self()? };
 
         Ok(next_offset.map(|offset| Self { fdt: self.fdt, offset }))
     }
+
+    /// Deletes this node effectively from DT, by setting it with FDT_NOP
+    pub fn nop(mut self) -> Result<()> {
+        // SAFETY: This consumes self, so invalid node wouldn't be used any further
+        unsafe { self.nop_self() }
+    }
+
+    /// Deletes this node effectively from DT, by setting it with FDT_NOP.
+    /// This only changes bytes of the node and its properties and subnodes, and doesn't alter or
+    /// move any other part of the tree.
+    /// SAFETY: This node is no longer valid.
+    unsafe fn nop_self(&mut self) -> Result<()> {
+        // SAFETY: Accesses are constrained to the DT totalsize (validated by ctor).
+        let ret = unsafe { libfdt_bindgen::fdt_nop_node(self.fdt.as_mut_ptr(), self.offset) };
+
+        fdt_err_expect_zero(ret)
+    }
 }
 
 /// Wrapper around low-level libfdt functions.
@@ -777,7 +949,7 @@
 
     /// Returns a tree node by its full path.
     pub fn node(&self, path: &CStr) -> Result<Option<FdtNode>> {
-        Ok(self.path_offset(path)?.map(|offset| FdtNode { fdt: self, offset }))
+        Ok(self.path_offset(path.to_bytes())?.map(|offset| FdtNode { fdt: self, offset }))
     }
 
     /// Iterate over nodes with a given compatible string.
@@ -785,6 +957,23 @@
         CompatibleIterator::new(self, compatible)
     }
 
+    /// Returns max phandle in the tree.
+    pub fn max_phandle(&self) -> Result<Phandle> {
+        let mut phandle: u32 = 0;
+        // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
+        let ret = unsafe { libfdt_bindgen::fdt_find_max_phandle(self.as_ptr(), &mut phandle) };
+
+        fdt_err_expect_zero(ret)?;
+        Phandle::new(phandle)
+    }
+
+    /// Returns a node with the phandle
+    pub fn node_with_phandle(&self, phandle: Phandle) -> Result<Option<FdtNode>> {
+        // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
+        let ret = unsafe { libfdt_bindgen::fdt_node_offset_by_phandle(self.as_ptr(), phandle.0) };
+        Ok(fdt_err_or_option(ret)?.map(|offset| FdtNode { fdt: self, offset }))
+    }
+
     /// Returns the mutable root node of the tree.
     pub fn root_mut(&mut self) -> Result<FdtNodeMut> {
         self.node_mut(CStr::from_bytes_with_nul(b"/\0").unwrap())?.ok_or(FdtError::Internal)
@@ -792,7 +981,7 @@
 
     /// Returns a mutable tree node by its full path.
     pub fn node_mut(&mut self, path: &CStr) -> Result<Option<FdtNodeMut>> {
-        Ok(self.path_offset(path)?.map(|offset| FdtNodeMut { fdt: self, offset }))
+        Ok(self.path_offset(path.to_bytes())?.map(|offset| FdtNodeMut { fdt: self, offset }))
     }
 
     /// Returns the device tree as a slice (may be smaller than the containing buffer).
@@ -800,13 +989,13 @@
         &self.buffer[..self.totalsize()]
     }
 
-    fn path_offset(&self, path: &CStr) -> Result<Option<c_int>> {
-        let len = path.to_bytes().len().try_into().map_err(|_| FdtError::BadPath)?;
+    fn path_offset(&self, path: &[u8]) -> Result<Option<c_int>> {
+        let len = path.len().try_into().map_err(|_| FdtError::BadPath)?;
         // SAFETY: Accesses are constrained to the DT totalsize (validated by ctor) and the
         // function respects the passed number of characters.
         let ret = unsafe {
             // *_namelen functions don't include the trailing nul terminator in 'len'.
-            libfdt_bindgen::fdt_path_offset_namelen(self.as_ptr(), path.as_ptr(), len)
+            libfdt_bindgen::fdt_path_offset_namelen(self.as_ptr(), path.as_ptr().cast::<_>(), len)
         };
 
         fdt_err_or_option(ret)
@@ -828,6 +1017,17 @@
         self.buffer.get(offset..(offset + len)).ok_or(FdtError::Internal)
     }
 
+    fn string(&self, offset: c_int) -> Result<&CStr> {
+        // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
+        let res = unsafe { libfdt_bindgen::fdt_string(self.as_ptr(), offset) };
+        if res.is_null() {
+            return Err(FdtError::Internal);
+        }
+
+        // SAFETY: Non-null return from fdt_string() is valid null-terminating string within FDT.
+        Ok(unsafe { CStr::from_ptr(res) })
+    }
+
     /// Returns a shared pointer to the device tree.
     pub fn as_ptr(&self) -> *const c_void {
         self.buffer.as_ptr().cast::<_>()
diff --git a/libs/libfdt/tests/api_test.rs b/libs/libfdt/tests/api_test.rs
index 513ffd8..61503eb 100644
--- a/libs/libfdt/tests/api_test.rs
+++ b/libs/libfdt/tests/api_test.rs
@@ -16,16 +16,23 @@
 
 //! Integration tests of the library libfdt.
 
-use libfdt::{Fdt, FdtError};
-use std::ffi::CStr;
+use libfdt::{Fdt, FdtError, Phandle};
+use std::ffi::{CStr, CString};
 use std::fs;
 use std::ops::Range;
 
+macro_rules! cstr {
+    ($str:literal) => {{
+        CStr::from_bytes_with_nul(concat!($str, "\0").as_bytes()).unwrap()
+    }};
+}
+
 const TEST_TREE_WITH_ONE_MEMORY_RANGE_PATH: &str = "data/test_tree_one_memory_range.dtb";
 const TEST_TREE_WITH_MULTIPLE_MEMORY_RANGES_PATH: &str =
     "data/test_tree_multiple_memory_ranges.dtb";
 const TEST_TREE_WITH_EMPTY_MEMORY_RANGE_PATH: &str = "data/test_tree_empty_memory_range.dtb";
 const TEST_TREE_WITH_NO_MEMORY_NODE_PATH: &str = "data/test_tree_no_memory_node.dtb";
+const TEST_TREE_PHANDLE_PATH: &str = "data/test_tree_phandle.dtb";
 
 #[test]
 fn retrieving_memory_from_fdt_with_one_memory_range_succeeds() {
@@ -83,7 +90,7 @@
     let chosen = fdt.chosen().unwrap().unwrap();
     assert_eq!(chosen.name().unwrap().to_str().unwrap(), "chosen");
 
-    let nested_node_path = CStr::from_bytes_with_nul(b"/cpus/PowerPC,970@0\0").unwrap();
+    let nested_node_path = cstr!("/cpus/PowerPC,970@0");
     let nested_node = fdt.node(nested_node_path).unwrap().unwrap();
     assert_eq!(nested_node.name().unwrap().to_str().unwrap(), "PowerPC,970@0");
 }
@@ -99,3 +106,117 @@
         assert_eq!(node.name().unwrap().to_str().unwrap(), name);
     }
 }
+
+#[test]
+fn node_properties() {
+    let data = fs::read(TEST_TREE_WITH_NO_MEMORY_NODE_PATH).unwrap();
+    let fdt = Fdt::from_slice(&data).unwrap();
+    let root = fdt.root().unwrap();
+    let one_be = 0x1_u32.to_be_bytes();
+    let expected: Vec<(&str, &[u8])> = vec![
+        ("model", b"MyBoardName\0"),
+        ("compatible", b"MyBoardName\0MyBoardFamilyName\0"),
+        ("#address-cells", &one_be),
+        ("#size-cells", &one_be),
+        ("empty_prop", b""),
+    ];
+
+    for (prop, (name, value)) in root.properties().unwrap().zip(expected) {
+        assert_eq!(prop.name().unwrap().to_str().unwrap(), name);
+        assert_eq!(prop.value().unwrap(), value);
+    }
+}
+
+#[test]
+fn node_supernode_at_depth() {
+    let data = fs::read(TEST_TREE_WITH_NO_MEMORY_NODE_PATH).unwrap();
+    let fdt = Fdt::from_slice(&data).unwrap();
+    let node = fdt.node(cstr!("/cpus/PowerPC,970@1")).unwrap().unwrap();
+    let expected = &["", "cpus", "PowerPC,970@1"];
+
+    for (depth, expect) in expected.iter().enumerate() {
+        let supernode = node.supernode_at_depth(depth).unwrap();
+        assert_eq!(supernode.name().unwrap().to_str().unwrap(), *expect);
+    }
+}
+
+#[test]
+fn phandle_new() {
+    let phandle_u32 = 0x55;
+    let phandle = Phandle::new(phandle_u32).unwrap();
+
+    assert_eq!(u32::from(phandle), phandle_u32);
+}
+
+#[test]
+fn max_phandle() {
+    let data = fs::read(TEST_TREE_PHANDLE_PATH).unwrap();
+    let fdt = Fdt::from_slice(&data).unwrap();
+
+    assert_eq!(fdt.max_phandle().unwrap(), Phandle::new(0xFF).unwrap());
+}
+
+#[test]
+fn node_with_phandle() {
+    let data = fs::read(TEST_TREE_PHANDLE_PATH).unwrap();
+    let fdt = Fdt::from_slice(&data).unwrap();
+
+    // Test linux,phandle
+    let node = fdt.node_with_phandle(Phandle::new(0xFF).unwrap()).unwrap().unwrap();
+    assert_eq!(node.name().unwrap().to_str().unwrap(), "node_zz");
+
+    // Test phandle
+    let node = fdt.node_with_phandle(Phandle::new(0x22).unwrap()).unwrap().unwrap();
+    assert_eq!(node.name().unwrap().to_str().unwrap(), "node_abc");
+}
+
+#[test]
+fn node_nop() {
+    let mut data = fs::read(TEST_TREE_PHANDLE_PATH).unwrap();
+    let fdt = Fdt::from_mut_slice(&mut data).unwrap();
+
+    fdt.node_with_phandle(Phandle::new(0xFF).unwrap()).unwrap().unwrap();
+    let node = fdt.node_mut(cstr!("/node_z/node_zz")).unwrap().unwrap();
+
+    node.nop().unwrap();
+
+    assert!(fdt.node_with_phandle(Phandle::new(0xFF).unwrap()).unwrap().is_none());
+    assert!(fdt.node(cstr!("/node_z/node_zz")).unwrap().is_none());
+
+    fdt.unpack().unwrap();
+    fdt.pack().unwrap();
+
+    assert!(fdt.node_with_phandle(Phandle::new(0xFF).unwrap()).unwrap().is_none());
+    assert!(fdt.node(cstr!("/node_z/node_zz")).unwrap().is_none());
+}
+
+#[test]
+fn node_add_subnode_with_namelen() {
+    let mut data = fs::read(TEST_TREE_PHANDLE_PATH).unwrap();
+    data.resize(data.len() * 2, 0_u8);
+
+    let fdt = Fdt::from_mut_slice(&mut data).unwrap();
+    fdt.unpack().unwrap();
+
+    let node_path = cstr!("/node_z/node_zz");
+    let subnode_name = cstr!("123456789");
+
+    for len in 0..subnode_name.to_bytes().len() {
+        let mut node = fdt.node_mut(node_path).unwrap().unwrap();
+        assert!(node.subnode_with_namelen(subnode_name, len).unwrap().is_none());
+
+        let mut node = fdt.node_mut(node_path).unwrap().unwrap();
+        node.add_subnode_with_namelen(subnode_name, len).unwrap();
+
+        let mut node = fdt.node_mut(node_path).unwrap().unwrap();
+        assert!(node.subnode_with_namelen(subnode_name, len).unwrap().is_some());
+    }
+
+    let node_path = node_path.to_str().unwrap();
+    for len in 1..subnode_name.to_bytes().len() {
+        let name = String::from_utf8(subnode_name.to_bytes()[..len].to_vec()).unwrap();
+        let path = CString::new(format!("{node_path}/{name}")).unwrap();
+        let subnode = fdt.node(&path).unwrap().unwrap();
+        assert_eq!(subnode.name().unwrap().to_str().unwrap(), name);
+    }
+}
diff --git a/libs/libfdt/tests/data/test_tree_no_memory_node.dts b/libs/libfdt/tests/data/test_tree_no_memory_node.dts
index 35e02cd..73451d7 100644
--- a/libs/libfdt/tests/data/test_tree_no_memory_node.dts
+++ b/libs/libfdt/tests/data/test_tree_no_memory_node.dts
@@ -6,6 +6,7 @@
 	compatible = "MyBoardName", "MyBoardFamilyName";
 	#address-cells = <0x1>;
 	#size-cells = <0x1>;
+	empty_prop;
 
 	cpus {
 		linux,phandle = <0x1>;
diff --git a/libs/libfdt/tests/data/test_tree_phandle.dts b/libs/libfdt/tests/data/test_tree_phandle.dts
new file mode 100644
index 0000000..0438241
--- /dev/null
+++ b/libs/libfdt/tests/data/test_tree_phandle.dts
@@ -0,0 +1,35 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+    node_a {
+        phandle = <0x1>;
+        node_ab {
+            node_abc {
+                linux,phandle = <0x22>;
+            };
+        };
+    };
+
+    node_b {
+    };
+
+    node_c {
+    };
+
+    node_z {
+        node_za {
+        };
+
+        node_zb {
+
+        };
+
+        node_zz {
+            phandle = <0xFF>;
+
+            node_zzz {
+            };
+        };
+    };
+};
\ No newline at end of file
diff --git a/secretkeeper/dice_policy/src/lib.rs b/secretkeeper/dice_policy/src/lib.rs
index f5d117c..327b8a4 100644
--- a/secretkeeper/dice_policy/src/lib.rs
+++ b/secretkeeper/dice_policy/src/lib.rs
@@ -174,7 +174,7 @@
     payload: Value,
     constraint_spec: &[ConstraintSpec],
 ) -> Result<NodeConstraints> {
-    let mut node_constraints: Vec<Constraint> = Vec::new();
+    let mut node_constraints: Vec<Constraint> = Vec::with_capacity(constraint_spec.len());
     for constraint_item in constraint_spec {
         let constraint_path = constraint_item.path.to_vec();
         if constraint_path.is_empty() {
@@ -258,15 +258,98 @@
     const COMPONENT_NAME: i64 = -70002;
     const KEY_MODE: i64 = -4670551;
 
-    // This is the number of certs in compos bcc (including the first ROT)
-    // To analyze a bcc use hwtrust tool from /tools/security/remote_provisioning/hwtrust
-    // `hwtrust --verbose dice-chain [path]/composbcc`
-    const COMPOS_DICE_CHAIN_SIZE: usize = 5;
-    const EXAMPLE_STRING: &str = "testing_dice_policy";
-    const EXAMPLE_NUM: i64 = 59765;
+    // Helper struct to encapsulate artifacts that are useful for unit tests.
+    struct TestArtifacts {
+        // A dice chain.
+        input_dice: Vec<u8>,
+        // A list of ConstraintSpec that can be applied on the input_dice to get a dice policy.
+        constraint_spec: Vec<ConstraintSpec>,
+        // The expected dice policy if above constraint_spec is applied to input_dice.
+        expected_dice_policy: DicePolicy,
+    }
+
+    impl TestArtifacts {
+        // Get an example instance of TestArtifacts. This uses a hard coded, hypothetical
+        // chain of certificates & a list of constraint_spec on this.
+        fn get_example() -> Self {
+            const EXAMPLE_NUM: i64 = 59765;
+            const EXAMPLE_STRING: &str = "testing_dice_policy";
+
+            let rot_key = CoseKey::default().to_cbor_value().unwrap();
+            let nested_payload = cbor!({
+                100 => EXAMPLE_NUM
+            })
+            .unwrap();
+            let payload = cbor!({
+                1 => EXAMPLE_STRING,
+                2 => "some_other_example_string",
+                3 => Value::Bytes(value_to_bytes(&nested_payload).unwrap()),
+            })
+            .unwrap();
+            let payload = value_to_bytes(&payload).unwrap();
+            let dice_node = CoseSign1 {
+                protected: ProtectedHeader::default(),
+                unprotected: Header::default(),
+                payload: Some(payload),
+                signature: b"ddef".to_vec(),
+            }
+            .to_cbor_value()
+            .unwrap();
+            let input_dice = Value::Array([rot_key.clone(), dice_node].to_vec());
+
+            let input_dice = value_to_bytes(&input_dice).unwrap();
+
+            // Now construct constraint_spec on the input dice, note this will use the keys
+            // which are also hardcoded within the get_dice_chain_helper.
+
+            let constraint_spec = vec![
+                ConstraintSpec::new(ConstraintType::ExactMatch, vec![1]).unwrap(),
+                // Notice how key "2" is (deliberately) absent in ConstraintSpec
+                // so policy should not constraint it.
+                ConstraintSpec::new(ConstraintType::GreaterOrEqual, vec![3, 100]).unwrap(),
+            ];
+            let expected_dice_policy = DicePolicy {
+                version: 1,
+                node_constraints_list: Box::new([
+                    NodeConstraints(Box::new([Constraint(
+                        ConstraintType::ExactMatch as u16,
+                        vec![],
+                        rot_key.clone(),
+                    )])),
+                    NodeConstraints(Box::new([
+                        Constraint(
+                            ConstraintType::ExactMatch as u16,
+                            vec![1],
+                            Value::Text(EXAMPLE_STRING.to_string()),
+                        ),
+                        Constraint(
+                            ConstraintType::GreaterOrEqual as u16,
+                            vec![3, 100],
+                            Value::from(EXAMPLE_NUM),
+                        ),
+                    ])),
+                ]),
+            };
+            Self { input_dice, constraint_spec, expected_dice_policy }
+        }
+    }
+
+    test!(policy_structure_check);
+    fn policy_structure_check() {
+        let example = TestArtifacts::get_example();
+        let policy =
+            DicePolicy::from_dice_chain(&example.input_dice, &example.constraint_spec).unwrap();
+
+        // Assert policy is exactly as expected!
+        assert_eq!(policy, example.expected_dice_policy);
+    }
 
     test!(policy_dice_size_is_same);
     fn policy_dice_size_is_same() {
+        // This is the number of certs in compos bcc (including the first ROT)
+        // To analyze a bcc use hwtrust tool from /tools/security/remote_provisioning/hwtrust
+        // `hwtrust --verbose dice-chain [path]/composbcc`
+        let compos_dice_chain_size: usize = 5;
         let input_dice = include_bytes!("../testdata/composbcc");
         let constraint_spec = [
             ConstraintSpec::new(ConstraintType::ExactMatch, vec![AUTHORITY_HASH]).unwrap(),
@@ -275,66 +358,7 @@
                 .unwrap(),
         ];
         let policy = DicePolicy::from_dice_chain(input_dice, &constraint_spec).unwrap();
-        assert_eq!(policy.node_constraints_list.len(), COMPOS_DICE_CHAIN_SIZE);
-    }
-
-    test!(policy_structure_check);
-    fn policy_structure_check() {
-        let rot_key = CoseKey::default().to_cbor_value().unwrap();
-        let nested_payload = cbor!({
-            100 => EXAMPLE_NUM
-        })
-        .unwrap();
-        let payload = cbor!({
-            1 => EXAMPLE_STRING,
-            2 => "some_other_example_string",
-            3 => Value::Bytes(value_to_bytes(&nested_payload).unwrap()),
-        })
-        .unwrap();
-        let payload = value_to_bytes(&payload).unwrap();
-        let dice_node = CoseSign1 {
-            protected: ProtectedHeader::default(),
-            unprotected: Header::default(),
-            payload: Some(payload),
-            signature: b"ddef".to_vec(),
-        }
-        .to_cbor_value()
-        .unwrap();
-        let input_dice = Value::Array([rot_key.clone(), dice_node].to_vec());
-
-        let input_dice = value_to_bytes(&input_dice).unwrap();
-        let constraint_spec = [
-            ConstraintSpec::new(ConstraintType::ExactMatch, vec![1]).unwrap(),
-            ConstraintSpec::new(ConstraintType::GreaterOrEqual, vec![3, 100]).unwrap(),
-        ];
-        let policy = DicePolicy::from_dice_chain(&input_dice, &constraint_spec).unwrap();
-
-        // Assert policy is exactly as expected!
-        assert_eq!(
-            policy,
-            DicePolicy {
-                version: 1,
-                node_constraints_list: Box::new([
-                    NodeConstraints(Box::new([Constraint(
-                        ConstraintType::ExactMatch as u16,
-                        vec![],
-                        rot_key
-                    )])),
-                    NodeConstraints(Box::new([
-                        Constraint(
-                            ConstraintType::ExactMatch as u16,
-                            vec![1],
-                            Value::Text(EXAMPLE_STRING.to_string())
-                        ),
-                        Constraint(
-                            ConstraintType::GreaterOrEqual as u16,
-                            vec![3, 100],
-                            Value::from(EXAMPLE_NUM)
-                        )
-                    ])),
-                ])
-            }
-        );
+        assert_eq!(policy.node_constraints_list.len(), compos_dice_chain_size);
     }
 
     /// Encodes a ciborium::Value into bytes.