[opentitanlib] Add serialization for num_de types

A few wrapper types exist for deserialization of some string wrapped
numeric types in HJSON config files. This adds the ability to serialize
those types in serde.

Signed-off-by: Jon Flatley <jflat@google.com>
diff --git a/sw/host/opentitanlib/src/image/image.rs b/sw/host/opentitanlib/src/image/image.rs
index 79e9f35..443728a 100644
--- a/sw/host/opentitanlib/src/image/image.rs
+++ b/sw/host/opentitanlib/src/image/image.rs
@@ -16,7 +16,6 @@
 
 use crate::image::manifest::Manifest;
 use crate::image::manifest_def::ManifestDef;
-use crate::util::bigint;
 use crate::util::parse_int::ParseInt;
 
 #[derive(Debug, Error)]
@@ -92,16 +91,29 @@
 
     /// Overwrites all fields in the image's manifest that are defined in `other`.
     pub fn overwrite_manifest(&mut self, other: ManifestDef) -> Result<()> {
-        let manifest_slice = &mut self.data.bytes[0..size_of::<Manifest>()];
-        let manifest_layout: LayoutVerified<&mut [u8], Manifest> =
-            LayoutVerified::new(&mut *manifest_slice).ok_or(ImageError::Parse)?;
-        let manifest: &mut Manifest = manifest_layout.into_mut();
+        let manifest = self.borrow_manifest_mut()?;
         let mut manifest_def: ManifestDef = (&*manifest).try_into()?;
         manifest_def.overwrite_fields(other);
         *manifest = manifest_def.try_into()?;
         Ok(())
     }
 
+    pub fn borrow_manifest(&self) -> Result<&Manifest> {
+        let manifest_slice = &self.data.bytes[0..size_of::<Manifest>()];
+        let manifest_layout: LayoutVerified<&[u8], Manifest> =
+            LayoutVerified::new(manifest_slice).ok_or(ImageError::Parse)?;
+        let manifest: &Manifest = manifest_layout.into_ref();
+        Ok(manifest)
+    }
+
+    pub fn borrow_manifest_mut(&mut self) -> Result<&mut Manifest> {
+        let manifest_slice = &mut self.data.bytes[0..size_of::<Manifest>()];
+        let manifest_layout: LayoutVerified<&mut [u8], Manifest> =
+            LayoutVerified::new(&mut *manifest_slice).ok_or(ImageError::Parse)?;
+        let manifest: &mut Manifest = manifest_layout.into_mut();
+        Ok(manifest)
+    }
+
     /// Compute the SHA256 digest for the signed portion of the `Image`.
     pub fn compute_digest(&self) -> Vec<u8> {
         let mut hasher = Sha256::new();
diff --git a/sw/host/opentitanlib/src/image/manifest_def.rs b/sw/host/opentitanlib/src/image/manifest_def.rs
index 42af264..e369226 100644
--- a/sw/host/opentitanlib/src/image/manifest_def.rs
+++ b/sw/host/opentitanlib/src/image/manifest_def.rs
@@ -8,8 +8,9 @@
 use crate::util::parse_int::ParseInt;
 
 use anyhow::{bail, Result};
-use serde::Deserialize;
+use serde::{Deserialize, Serialize};
 use std::convert::{TryFrom, TryInto};
+use std::fmt;
 use std::iter::IntoIterator;
 use std::path::Path;
 use thiserror::Error;
@@ -24,11 +25,17 @@
 
 fixed_size_bigint!(ManifestRsa, at_most 3072);
 
-#[derive(Clone, Default, Debug, Deserialize)]
+#[derive(Clone, Default, Debug, Deserialize, Serialize)]
 struct ManifestBigInt(Option<HexEncoded<ManifestRsa>>);
 
-#[derive(Clone, Default, Debug, Deserialize)]
-struct ManifestSmallInt<T: ParseInt>(Option<HexEncoded<T>>);
+#[derive(Clone, Default, Debug, Deserialize, Serialize)]
+struct ManifestSmallInt<T: ParseInt + fmt::UpperHex>(Option<HexEncoded<T>>);
+
+impl fmt::UpperHex for ManifestRsa {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+        fmt::UpperHex::fmt(&self.as_biguint(), f)
+    }
+}
 
 /// A macro for wrapping manifest struct definitions that parse from HJSON.
 ///
@@ -42,7 +49,7 @@
             $field_name:ident: $field_type:ty,
         )*
     }, $out_type:ident) => {
-        #[derive(Clone, Default, Deserialize, Debug)]
+        #[derive(Clone, Default, Deserialize, Serialize, Debug)]
         $access struct $name {
             $(
                 $(#[$doc])?
@@ -123,7 +130,7 @@
     }
 }
 
-impl<T: ParseInt> ManifestPacked<T> for ManifestSmallInt<T> {
+impl<T: ParseInt + fmt::UpperHex> ManifestPacked<T> for ManifestSmallInt<T> {
     fn unpack(self, name: &'static str) -> Result<T> {
         match self.0 {
             Some(v) => Ok(v.0),
@@ -138,7 +145,9 @@
     }
 }
 
-impl<T: ParseInt, const N: usize> ManifestPacked<[T; N]> for [ManifestSmallInt<T>; N] {
+impl<T: ParseInt + fmt::UpperHex, const N: usize> ManifestPacked<[T; N]>
+    for [ManifestSmallInt<T>; N]
+{
     fn unpack(self, name: &'static str) -> Result<[T; N]> {
         let results = self.map(|e| e.unpack(name));
         if let Some(err_idx) = results.iter().position(Result::is_err) {
@@ -254,7 +263,7 @@
 
 impl<T> From<&T> for ManifestSmallInt<T>
 where
-    T: ParseInt + Copy,
+    T: ParseInt + fmt::UpperHex + Copy,
 {
     fn from(o: &T) -> ManifestSmallInt<T> {
         ManifestSmallInt(Some(HexEncoded(*o)))
diff --git a/sw/host/opentitanlib/src/otp/otp_mmap.rs b/sw/host/opentitanlib/src/otp/otp_mmap.rs
index 516fdf8..f676ab0 100644
--- a/sw/host/opentitanlib/src/otp/otp_mmap.rs
+++ b/sw/host/opentitanlib/src/otp/otp_mmap.rs
@@ -18,9 +18,9 @@
 
 #[derive(Deserialize, Debug)]
 struct OtpMapConfig {
-    #[serde(with = "num_de")]
+    #[serde(deserialize_with = "num_de::deserialize")]
     width: usize,
-    #[serde(with = "num_de")]
+    #[serde(deserialize_with = "num_de::deserialize")]
     depth: usize,
 }
 
@@ -39,11 +39,11 @@
 
 #[derive(Deserialize, Debug)]
 struct OtpMapScrambling {
-    #[serde(with = "num_de")]
+    #[serde(deserialize_with = "num_de::deserialize")]
     key_size: usize,
-    #[serde(with = "num_de")]
+    #[serde(deserialize_with = "num_de::deserialize")]
     iv_size: usize,
-    #[serde(with = "num_de")]
+    #[serde(deserialize_with = "num_de::deserialize")]
     cnst_size: usize,
     keys: Vec<OtpMapKey>,
     digests: Vec<OtpMapDigest>,
@@ -52,7 +52,7 @@
 #[derive(Deserialize, Debug)]
 struct OtpMapItem {
     name: String,
-    #[serde(with = "num_de")]
+    #[serde(deserialize_with = "num_de::deserialize")]
     size: usize,
     #[serde(default)]
     #[allow(dead_code)]
@@ -66,7 +66,7 @@
     name: String,
     #[allow(dead_code)]
     secret: bool,
-    #[serde(default, with = "num_de")]
+    #[serde(default, deserialize_with = "num_de::deserialize")]
     size: usize,
     sw_digest: bool,
     hw_digest: bool,
diff --git a/sw/host/opentitanlib/src/util/num_de.rs b/sw/host/opentitanlib/src/util/num_de.rs
index a3122d3..8dc6983 100644
--- a/sw/host/opentitanlib/src/util/num_de.rs
+++ b/sw/host/opentitanlib/src/util/num_de.rs
@@ -27,14 +27,6 @@
 
 use crate::util::parse_int::{ParseInt, ParseIntError};
 
-pub fn _serialize<S, T>(_r: T, _ser: S) -> Result<S::Ok, S::Error>
-where
-    S: Serializer,
-    T: Serialize + Copy,
-{
-    unimplemented!();
-}
-
 /// Deserialize numeric types from HJSON config files.
 pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
 where
@@ -105,7 +97,7 @@
 }
 
 #[derive(Debug, Clone, Deserialize)]
-pub struct DeferredValue(#[serde(with = "self")] DeferredInit);
+pub struct DeferredValue(#[serde(deserialize_with = "deserialize")] DeferredInit);
 
 impl DeferredValue {
     pub fn resolve(&self, size: usize, rng: &mut dyn RngCore) -> Vec<u8> {
@@ -158,19 +150,25 @@
 
 /// Wrapper type to force deserialization assuming octal encoding.
 #[derive(Clone, Deserialize, Debug)]
-pub struct OctEncoded<T: ParseInt>(#[serde(with = "self")] pub T);
+pub struct OctEncoded<T>(#[serde(deserialize_with = "deserialize")] pub T)
+where
+    T: ParseInt + fmt::Octal;
 
 /// Wrapper type to force deserialization assuming decimal encoding.
 #[derive(Clone, Deserialize, Debug)]
-pub struct DecEncoded<T: ParseInt>(#[serde(with = "self")] pub T);
+pub struct DecEncoded<T>(#[serde(deserialize_with = "deserialize")] pub T)
+where
+    T: ParseInt + fmt::Display;
 
 /// Wrapper type to force deserialization assuming hexadecimal encoding.
 #[derive(Clone, Deserialize, Debug)]
-pub struct HexEncoded<T: ParseInt>(#[serde(with = "self")] pub T);
+pub struct HexEncoded<T>(#[serde(deserialize_with = "deserialize")] pub T)
+where
+    T: ParseInt + fmt::UpperHex;
 
 macro_rules! impl_parse_int_enc {
-    ($ty:ident, $radix:expr) => {
-        impl<T: ParseInt> std::ops::Deref for $ty<T> {
+    ($ty:ident, $radix:expr, $fmt:path) => {
+        impl<T: ParseInt + $fmt> std::ops::Deref for $ty<T> {
             type Target = T;
 
             fn deref(&self) -> &Self::Target {
@@ -178,7 +176,7 @@
             }
         }
 
-        impl<T: ParseInt> ParseInt for $ty<T> {
+        impl<T: ParseInt + $fmt> ParseInt for $ty<T> {
             type FromStrRadixErr = T::FromStrRadixErr;
 
             fn from_str_radix(src: &str, radix: u32) -> Result<Self, T::FromStrRadixErr> {
@@ -189,12 +187,27 @@
                 Self::from_str_radix(src, $radix).map_err(|e| e.into())
             }
         }
+
+        impl<T: ParseInt + $fmt> fmt::Display for $ty<T> {
+            fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+                <_ as $fmt>::fmt(&self.0, f)
+            }
+        }
+
+        impl<T: ParseInt + $fmt> Serialize for $ty<T> {
+            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+            where
+                S: Serializer,
+            {
+                serializer.serialize_str(&self.to_string())
+            }
+        }
     };
 }
 
-impl_parse_int_enc!(OctEncoded, 8);
-impl_parse_int_enc!(DecEncoded, 10);
-impl_parse_int_enc!(HexEncoded, 16);
+impl_parse_int_enc!(OctEncoded, 8, fmt::Octal);
+impl_parse_int_enc!(DecEncoded, 10, fmt::Display);
+impl_parse_int_enc!(HexEncoded, 16, fmt::UpperHex);
 
 #[cfg(test)]
 mod test {
@@ -205,11 +218,11 @@
     fn de_u8() -> Result<()> {
         #[derive(Debug, Deserialize)]
         struct TestData {
-            #[serde(with = "super")]
+            #[serde(deserialize_with = "deserialize")]
             oct: OctEncoded<u8>,
-            #[serde(with = "super")]
+            #[serde(deserialize_with = "deserialize")]
             dec: DecEncoded<u8>,
-            #[serde(with = "super")]
+            #[serde(deserialize_with = "deserialize")]
             hex: HexEncoded<u8>,
         }
 
diff --git a/sw/host/opentitantool/src/command/image.rs b/sw/host/opentitantool/src/command/image.rs
index 26b6333..a38d9cb 100644
--- a/sw/host/opentitantool/src/command/image.rs
+++ b/sw/host/opentitantool/src/command/image.rs
@@ -5,6 +5,7 @@
 use anyhow::{ensure, Result};
 use erased_serde::Serialize;
 use std::any::Any;
+use std::convert::TryInto;
 use std::fs::File;
 use std::io::Write;
 use std::path::PathBuf;
@@ -78,7 +79,9 @@
         _context: &dyn Any,
         _transport: &TransportWrapper,
     ) -> Result<Option<Box<dyn Serialize>>> {
-        Ok(None)
+        let image = image::Image::read_from_file(&self.image)?;
+        let manifest_def: ManifestDef = image.borrow_manifest()?.try_into()?;
+        Ok(Some(Box::new(manifest_def)))
     }
 }