Merge "[test][refactoring] Use hex crate to decode hex string in test" into main
diff --git a/apkdmverity/Android.bp b/apkdmverity/Android.bp
index c4c90cd..0cb8ca1 100644
--- a/apkdmverity/Android.bp
+++ b/apkdmverity/Android.bp
@@ -15,6 +15,7 @@
         "libbitflags",
         "libclap",
         "libdm_rust",
+        "libhex",
         "libitertools",
         "liblibc",
         "libnix",
diff --git a/apkdmverity/src/main.rs b/apkdmverity/src/main.rs
index d9e9e2b..0ecb0ea 100644
--- a/apkdmverity/src/main.rs
+++ b/apkdmverity/src/main.rs
@@ -46,7 +46,7 @@
 
     for (apk, idsig, name, roothash) in apks.tuples() {
         let roothash = if roothash != "none" {
-            Some(util::parse_hexstring(roothash).expect("failed to parse roothash"))
+            Some(hex::decode(roothash).expect("failed to parse roothash"))
         } else {
             None
         };
@@ -108,8 +108,10 @@
             bail!("The size of {:?} is not multiple of {}.", &apk, BLOCK_SIZE)
         }
         (
-            loopdevice::attach(&apk, 0, apk_size, /*direct_io*/ true, /*writable*/ false)
-                .context("Failed to attach APK to a loop device")?,
+            loopdevice::attach(
+                &apk, 0, apk_size, /* direct_io */ true, /* writable */ false,
+            )
+            .context("Failed to attach APK to a loop device")?,
             apk_size,
         )
     };
@@ -123,9 +125,10 @@
     // Due to unknown reason(b/191344832), we can't enable "direct IO" for the IDSIG file (backing
     // the hash). For now we don't use "direct IO" but it seems OK since the IDSIG file is very
     // small and the benefit of direct-IO would be negliable.
-    let hash_device =
-        loopdevice::attach(&idsig, offset, size, /*direct_io*/ false, /*writable*/ false)
-            .context("Failed to attach idsig to a loop device")?;
+    let hash_device = loopdevice::attach(
+        &idsig, offset, size, /* direct_io */ false, /* writable */ false,
+    )
+    .context("Failed to attach idsig to a loop device")?;
 
     // Build a dm-verity target spec from the information from the idsig file. The apk and the
     // idsig files are used as the data device and the hash device, respectively.
@@ -338,7 +341,7 @@
         // of the data device is done in the scopeguard for the return value of `enable_verity`
         // below. Only the idsig_loop_device needs detatching.
         let apk_loop_device = loopdevice::attach(
-            &apk_path, 0, apk_size, /*direct_io*/ true, /*writable*/ false,
+            &apk_path, 0, apk_size, /* direct_io */ true, /* writable */ false,
         )
         .unwrap();
         let idsig_loop_device = scopeguard::guard(
@@ -346,8 +349,8 @@
                 &idsig_path,
                 0,
                 idsig_size,
-                /*direct_io*/ false,
-                /*writable*/ false,
+                /* direct_io */ false,
+                /* writable */ false,
             )
             .unwrap(),
             |dev| loopdevice::detach(dev).unwrap(),
diff --git a/libs/devicemapper/src/util.rs b/libs/devicemapper/src/util.rs
index e8df424..cc071e4 100644
--- a/libs/devicemapper/src/util.rs
+++ b/libs/devicemapper/src/util.rs
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-use anyhow::{anyhow, bail, Result};
+use anyhow::{bail, Result};
 use nix::sys::stat::FileStat;
 use std::fs::File;
 use std::os::unix::fs::FileTypeExt;
@@ -52,24 +52,6 @@
     Ok(())
 }
 
-/// Returns hexadecimal reprentation of a given byte array.
-pub fn hexstring_from(s: &[u8]) -> String {
-    s.iter().map(|byte| format!("{:02x}", byte)).reduce(|i, j| i + &j).unwrap_or_default()
-}
-
-/// Parses a hexadecimal string into a byte array
-pub fn parse_hexstring(s: &str) -> Result<Vec<u8>> {
-    let len = s.len();
-    if len % 2 != 0 {
-        bail!("length {} is not even", len)
-    } else {
-        (0..len)
-            .step_by(2)
-            .map(|i| u8::from_str_radix(&s[i..i + 2], 16).map_err(|e| anyhow!(e)))
-            .collect()
-    }
-}
-
 /// fstat that accepts a path rather than FD
 pub fn fstat(p: &Path) -> Result<FileStat> {
     let f = File::open(p)?;
diff --git a/libs/devicemapper/src/verity.rs b/libs/devicemapper/src/verity.rs
index 24584f8..bbd9d38 100644
--- a/libs/devicemapper/src/verity.rs
+++ b/libs/devicemapper/src/verity.rs
@@ -151,7 +151,7 @@
         };
 
         let root_digest = if let Some(root_digest) = self.root_digest {
-            hexstring_from(root_digest)
+            hex::encode(root_digest)
         } else {
             bail!("root digest is not set")
         };
@@ -159,7 +159,7 @@
         let salt = if self.salt.is_none() || self.salt.unwrap().is_empty() {
             "-".to_string() // Note. It's not an empty string!
         } else {
-            hexstring_from(self.salt.unwrap())
+            hex::encode(self.salt.unwrap())
         };
 
         // Step2: serialize the information according to the spec, which is ...
diff --git a/libs/libfdt/src/iterators.rs b/libs/libfdt/src/iterators.rs
index 000f723..a524655 100644
--- a/libs/libfdt/src/iterators.rs
+++ b/libs/libfdt/src/iterators.rs
@@ -323,6 +323,29 @@
     }
 }
 
+/// Iterator over descendants
+#[derive(Debug)]
+pub struct DescendantsIterator<'a> {
+    node: Option<(FdtNode<'a>, usize)>,
+}
+
+impl<'a> DescendantsIterator<'a> {
+    pub(crate) fn new(node: &'a FdtNode) -> Self {
+        Self { node: Some((*node, 0)) }
+    }
+}
+
+impl<'a> Iterator for DescendantsIterator<'a> {
+    type Item = (FdtNode<'a>, usize);
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let (node, depth) = self.node?;
+        self.node = node.next_node(depth).ok().flatten().filter(|(_, depth)| *depth > 0);
+
+        self.node
+    }
+}
+
 /// Iterator over properties
 #[derive(Debug)]
 pub struct PropertyIterator<'a> {
diff --git a/libs/libfdt/src/lib.rs b/libs/libfdt/src/lib.rs
index b513649..aae75f7 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, PropertyIterator,
-    RangesIterator, Reg, RegIterator, SubnodeIterator,
+    AddressRange, CellIterator, CompatibleIterator, DescendantsIterator, MemRegIterator,
+    PropertyIterator, RangesIterator, Reg, RegIterator, SubnodeIterator,
 };
 
 use core::cmp::max;
@@ -486,6 +486,23 @@
         Ok(fdt_err_or_option(ret)?.map(|offset| FdtNode { fdt: self.fdt, offset }))
     }
 
+    /// Returns an iterator of descendants
+    pub fn descendants(&'a self) -> DescendantsIterator<'a> {
+        DescendantsIterator::new(self)
+    }
+
+    fn next_node(&self, depth: usize) -> Result<Option<(Self, usize)>> {
+        let mut next_depth: c_int = depth.try_into().unwrap();
+        // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
+        let ret = unsafe {
+            libfdt_bindgen::fdt_next_node(self.fdt.as_ptr(), self.offset, &mut next_depth)
+        };
+        let Ok(next_depth) = usize::try_from(next_depth) else {
+            return Ok(None);
+        };
+        Ok(fdt_err_or_option(ret)?.map(|offset| (FdtNode { fdt: self.fdt, offset }, next_depth)))
+    }
+
     /// Returns an iterator of properties
     pub fn properties(&'a self) -> Result<PropertyIterator<'a>> {
         PropertyIterator::new(self)
diff --git a/libs/libfdt/tests/api_test.rs b/libs/libfdt/tests/api_test.rs
index 63cbdee..d5d6ece 100644
--- a/libs/libfdt/tests/api_test.rs
+++ b/libs/libfdt/tests/api_test.rs
@@ -308,3 +308,23 @@
     // Just check whether borrow checker doesn't complain this.
     memory.setprop_inplace(cstr!("device_type"), b"MEMORY\0").unwrap();
 }
+
+#[test]
+fn node_descendants() {
+    let mut data = fs::read(TEST_TREE_PHANDLE_PATH).unwrap();
+    let fdt = Fdt::from_mut_slice(&mut data).unwrap();
+
+    let node_z = fdt.node(cstr!("/node_z")).unwrap().unwrap();
+    let descendants: Vec<_> =
+        node_z.descendants().map(|(node, depth)| (node.name().unwrap(), depth)).collect();
+
+    assert_eq!(
+        descendants,
+        vec![
+            (cstr!("node_za"), 1),
+            (cstr!("node_zb"), 1),
+            (cstr!("node_zz"), 1),
+            (cstr!("node_zzz"), 2)
+        ]
+    );
+}