[ottool] Enums that can round-trip unkown values

This module adds C-like enums which can preserve un-enumerated values.
In rust code, these enums should behave as though in addition to
all enumerated values, there is also a NewType variant which is the
catch-all for unenumerated values.

The enums also have a custom Serialize/Deserialize implementations to
facilitate an optimal user experience in text serialization formats.
That is to say: for enumerated values, these enums serialize as the
named discriminant.  For un-enumerated values, these enums serialize
as the integer value.

See the doc-comments for more info.

Signed-off-by: Chris Frantz <cfrantz@google.com>
diff --git a/sw/host/opentitanlib/BUILD b/sw/host/opentitanlib/BUILD
index d05976d..b12fa57 100644
--- a/sw/host/opentitanlib/BUILD
+++ b/sw/host/opentitanlib/BUILD
@@ -117,6 +117,7 @@
         "src/util/parse_int.rs",
         "src/util/present.rs",
         "src/util/rom_detect.rs",
+        "src/util/unknown.rs",
         "src/util/usb.rs",
         "src/util/voltage.rs",
     ],
diff --git a/sw/host/opentitanlib/src/util/mod.rs b/sw/host/opentitanlib/src/util/mod.rs
index c55a416..292cf3a 100644
--- a/sw/host/opentitanlib/src/util/mod.rs
+++ b/sw/host/opentitanlib/src/util/mod.rs
@@ -9,6 +9,7 @@
 pub mod parse_int;
 pub mod present;
 pub mod rom_detect;
+pub mod unknown;
 pub mod usb;
 pub mod voltage;
 
diff --git a/sw/host/opentitanlib/src/util/unknown.rs b/sw/host/opentitanlib/src/util/unknown.rs
new file mode 100644
index 0000000..ca7592a
--- /dev/null
+++ b/sw/host/opentitanlib/src/util/unknown.rs
@@ -0,0 +1,264 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+/// Creates C-like enums which preserve unknown (un-enumerated) values.
+///
+/// If you wanted an enum like:
+/// ```
+/// #[repr(u32)]
+/// pub enum HardenedBool {
+///     True = 0x739,
+///     False = 0x146,
+///     Unknown(u32),
+/// }
+/// ```
+///
+/// Where the `Unknown` discriminator would be the catch-all for any
+/// non-enumerated values, you can use `with_unknown!` as follows:
+///
+/// ```
+/// with_unknown! {
+///     pub enum HardenedBool: u32 {
+///         True = 0x739,
+///         False = 0x14d,
+///     }
+/// }
+/// ```
+///
+/// This "enum" can be used later in match statements:
+/// ```
+/// match foo {
+///     HardenedBool::True => do_the_thing(),
+///     HardenedBool::False => do_the_opposite_thing(),
+///     HardenedBool(x) => panic!("Oh noes! {} is neither True nor False!", x),
+/// }
+/// ```
+///
+/// Behind the scenes, `with_unknown!` implements a newtype struct and
+/// creates associated constants for each of the enumerated values.
+/// The struct also implements `Copy`, `Clone`, `PartialEq`, `Eq`,
+/// `PartialOrd`, `Ord`, `Hash`, `Debug` and `Display` (including the hex,
+/// octal and binary versions).
+///
+/// In addition, `serde::Serialize` and `serde::Deserialize` are
+/// implemented.  The serialized form is a string for known values and an
+/// integer for all unknown values.
+#[macro_export]
+macro_rules! with_unknown {
+    (
+        $(
+            $(#[$outer:meta])*
+            $vis:vis enum $Enum:ident: $type:ty {
+                $(
+                    $(#[$inner:meta])*
+                    $enumerator:ident = $value:expr,
+                )*
+            }
+        )*
+    ) => {$(
+        $(#[$outer])*
+        #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
+        #[repr(transparent)]
+        $vis struct $Enum(pub $type);
+
+        #[allow(non_upper_case_globals)]
+        impl $Enum {
+            $(
+                $(#[$inner])*
+                $vis const $enumerator: $Enum = $Enum($value);
+            )*
+        }
+
+        // Implement the various display traits.
+        $crate::__impl_fmt_unknown!(Display, "{}", "{}", $Enum { $($enumerator),* });
+        $crate::__impl_fmt_unknown!(LowerHex, "{:x}", "{:#x}", $Enum { $($enumerator),* });
+        $crate::__impl_fmt_unknown!(UpperHex, "{:X}", "{:#X}", $Enum { $($enumerator),* });
+        $crate::__impl_fmt_unknown!(Octal, "{:o}", "{:#o}", $Enum { $($enumerator),* });
+        $crate::__impl_fmt_unknown!(Binary, "{:b}", "{:#b}", $Enum { $($enumerator),* });
+
+        // Manually implement Serialize and Deserialize to have tight control over how
+        // the struct is serialized.
+        const _: () = {
+            use serde::ser::{Serialize, Serializer};
+            use serde::de::{Deserialize, Deserializer, Error, Visitor};
+            use std::convert::TryFrom;
+
+            impl Serialize for $Enum {
+                /// Serializes the enumerated values.  All named discriminants are
+                /// serialized to strings.  All unknown values are serialized as
+                /// integers.
+                fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+                where
+                    S: Serializer,
+                {
+                    match *self {
+                        $(
+                            $Enum::$enumerator => serializer.serialize_str(stringify!($enumerator)),
+                        )*
+                        $Enum(value) => value.serialize(serializer),
+                    }
+                }
+            }
+
+            // The `EnumVistor` assists in deserializing the value.
+            struct EnumVisitor;
+            impl<'de> Visitor<'de> for EnumVisitor {
+                type Value = $Enum;
+
+                fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+                    f.write_str(concat!("A valid enumerator of ", stringify!($Enum)))
+                }
+
+                fn visit_str<E: Error>(self, value: &str) -> Result<Self::Value, E> {
+                    match value {
+                        $(
+                            stringify!($enumerator) => Ok($Enum::$enumerator),
+                        )*
+                        _ => Err(E::custom(format!("unrecognized: {}", value))),
+                    }
+                }
+                $crate::__expand_visit_fn!(visit_i8, i8, $Enum, $type);
+                $crate::__expand_visit_fn!(visit_i16, i16, $Enum, $type);
+                $crate::__expand_visit_fn!(visit_i32, i32, $Enum, $type);
+                $crate::__expand_visit_fn!(visit_i64, i64, $Enum, $type);
+                $crate::__expand_visit_fn!(visit_u8, u8, $Enum, $type);
+                $crate::__expand_visit_fn!(visit_u16, u16, $Enum, $type);
+                $crate::__expand_visit_fn!(visit_u32, u32, $Enum, $type);
+                $crate::__expand_visit_fn!(visit_u64, u64, $Enum, $type);
+            }
+
+            impl<'de> Deserialize<'de> for $Enum {
+                /// Deserializes the value by forwarding to `deserialize_any`.
+                /// `deserialize_any` will forward strings to the string visitor
+                /// and forward integers to the appropriate integer visitor.
+                fn deserialize<D>(deserializer: D) -> Result<$Enum, D::Error>
+                where
+                    D: Deserializer<'de>,
+                {
+                    deserializer.deserialize_any(EnumVisitor)
+                }
+            }
+        };
+    )*};
+}
+
+#[macro_export]
+macro_rules! __expand_visit_fn {
+    ($visit_func:ident, $ser_type:ty, $Enum:ident, $enum_type:ty) => {
+        fn $visit_func<E>(self, value: $ser_type) -> Result<Self::Value, E>
+        where
+            E: Error,
+        {
+            match <$enum_type>::try_from(value) {
+                Ok(v) => Ok($Enum(v)),
+                Err(_) => Err(E::custom(format!(
+                    "cannot convert {:?} to {}({})",
+                    value,
+                    stringify!($Enum),
+                    stringify!($enum_type)
+                ))),
+            }
+        }
+    };
+}
+
+// Helper macro for implementing the various formatting traits.
+#[macro_export]
+macro_rules! __impl_fmt_unknown {
+    (
+        $Trait:ident, $Fmt:literal, $Alt:literal, $Enum:ident {
+            $($enumerator:ident),*
+        }
+    ) => {
+        impl std::fmt::$Trait for $Enum {
+            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+                match *self {
+                    $(
+                        $Enum::$enumerator => write!(f, "{}", stringify!($enumerator)),
+                    )*
+                    $Enum(value) => {
+                        if f.alternate() {
+                            write!(f, concat!(stringify!($Enum), "(", $Alt, ")"), value)
+                        } else {
+                            write!(f, concat!(stringify!($Enum), "(", $Fmt, ")"), value)
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use anyhow::Result;
+    use serde::{Deserialize, Serialize};
+
+    with_unknown! {
+        pub enum HardenedBool: u32 {
+            True = 0x739,
+            False = 0x14d,
+        }
+
+        // Check the top-level repeat in the macro.
+        enum Misc: u8 {
+            X = 0,
+            Y = 1,
+            Z = 2,
+        }
+    }
+
+    #[test]
+    fn test_display() -> Result<()> {
+        let t = HardenedBool::True;
+        assert_eq!(t.to_string(), "True");
+
+        let f = HardenedBool::False;
+        assert_eq!(f.to_string(), "False");
+
+        let j = HardenedBool(0x6A);
+        assert_eq!(j.to_string(), "HardenedBool(106)");
+        assert_eq!(format!("{:x}", j), "HardenedBool(6a)");
+        assert_eq!(format!("{:#x}", j), "HardenedBool(0x6a)");
+        assert_eq!(format!("{:X}", j), "HardenedBool(6A)");
+        assert_eq!(format!("{:o}", j), "HardenedBool(152)");
+        assert_eq!(format!("{:b}", j), "HardenedBool(1101010)");
+        assert_eq!(format!("{:#b}", j), "HardenedBool(0b1101010)");
+        Ok(())
+    }
+
+    #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
+    struct SomeBools {
+        a: HardenedBool,
+        b: HardenedBool,
+        c: HardenedBool,
+    }
+
+    #[test]
+    fn test_serde() -> Result<()> {
+        let b = SomeBools {
+            a: HardenedBool::True,
+            b: HardenedBool::False,
+            c: HardenedBool(0x6a),
+        };
+        let json = serde_json::to_string(&b)?;
+        assert_eq!(json, r#"{"a":"True","b":"False","c":106}"#);
+
+        let de = serde_json::from_str::<SomeBools>(&json)?;
+        assert_eq!(de, b);
+        Ok(())
+    }
+
+    #[test]
+    fn test_serde_error() -> Result<()> {
+        let json = r#"{"a":"True","b":"False","c":-1}"#;
+        let de = serde_json::from_str::<SomeBools>(&json);
+        let err = de.unwrap_err().to_string();
+        assert_eq!(
+            err,
+            "cannot convert -1 to HardenedBool(u32) at line 1 column 30"
+        );
+        Ok(())
+    }
+}