Audio metadata: Refinements and clarifications

Increase bit-width for type index and container size.

For the parceling:
1) Add recursive container support for vector and map.
2) Add simple struct support through reflection
by structured binding.

Add documentation for byte string formats.

Test: atest metadata_tests
Change-Id: If0916a5431709c5edce63198b9f7b13d4b82cbbb
diff --git a/audio_utils/include/audio_utils/Metadata.h b/audio_utils/include/audio_utils/Metadata.h
index 97d6e31..7169a70 100644
--- a/audio_utils/include/audio_utils/Metadata.h
+++ b/audio_utils/include/audio_utils/Metadata.h
@@ -40,21 +40,82 @@
  * std::string                         (String)
  * Data (std::map<std::string, Datum>) (Map<String, Object>)
  *
- * TEST ONLY std::vector<Datum>        (Object[])
- * TEST ONLY std::pair<Datum, Datum>   (Pair<Object, Object>)
- *
- * As can be seen, std::map, std::vector, std::pair are
- * recursive types on Datum.
+ * Metadata code supports advanced automatic parceling.
+ * TEST ONLY:
+ * std::vector<Datum>                  (Object[])             --> vector of Objects
+ * std::pair<Datum, Datum>             (Pair<Object, Object>) --> pair of Objects
+ * std::vector<std::vector<std::pair<std::string, short>>>    --> recursive containers
+ * struct { int i0; std::vector<int> v1; std::pair<int, int> p2; } --> struct parceling
  *
  * The Data map accepts typed Keys, which designate the type T of the
  * value associated with the Key<T> in the template parameter.
  *
  * CKey<T> is the constexpr version suitable for fixed compile-time constants.
  * Key<T> is the non-constexpr version.
+ *
+ * Notes: for future extensibility:
+ *
+ * In order to add a new type in.
+ *
+ * 1) Add the new type to the END of the metadata_types lists below.
+ *
+ * 2) Update the copyFromByteString() code, since that isn't automatically
+ * generated (the switch is faster for speed).
+ *
+ * 3) The new type can be a primitive, or make use of containers std::map, std::vector,
+ * std::pair, or be simple structs (see below).
+ *
+ * 4) Simple structs contain no pointers and all public data. The members can be based
+ * on existing types.
+ *   a) If trivially copyable (packed) primitive data,
+ *      add to primitive_metadata_types.
+ *   b) If the struct requires member-wise parceling, add to structural_metadata_types
+ *      (current limit is 4 members).
+ *
+ * 5) The type system is recursive.
+ *
+ * Design notes:
+ * 1) Tuple is intentionally not implemented as it isn't that readable.  This can
+ * be revisited if the need comes up.  If you have more than a couple of elements,
+ * we suggest embedding in a Data typed map or a Simple struct.
+ *
+ * 2) Each custom type e.g. vector<int>, pair<short, char> takes one
+ * slot in the type index.  A full type description language is not implemented
+ * here for brevity and clarity.
  */
 
 namespace android::audio_utils::metadata {
 
+// Determine if a type is a specialization of a templated type
+// Example: is_specialization_v<T, std::vector>
+// https://stackoverflow.com/questions/16337610/how-to-know-if-a-type-is-a-specialization-of-stdvector
+
+template <typename Test, template <typename...> class Ref>
+struct is_specialization : std::false_type {};
+
+template <template <typename...> class Ref, typename... Args>
+struct is_specialization<Ref<Args...>, Ref>: std::true_type {};
+
+template <typename Test, template <typename...> class Ref>
+inline constexpr bool is_specialization_v = is_specialization<Test, Ref>::value;
+
+// Determine the number of arguments required for structured binding.
+// See the discussion here and follow the links:
+// https://isocpp.org/blog/2016/08/cpp17-structured-bindings-convert-struct-to-a-tuple-simple-reflection
+struct any_type {
+  template<class T>
+  constexpr operator T(); // non explicit
+};
+
+template <typename T, typename... TArgs>
+decltype(void(T{std::declval<TArgs>()...}), std::true_type{}) test_is_braces_constructible(int);
+
+template <typename, typename...>
+std::false_type test_is_braces_constructible(...);
+
+template <typename T, typename... TArgs>
+using is_braces_constructible = decltype(test_is_braces_constructible<T, TArgs...>(0));
+
 // Set up type comparison system
 // see std::variant for the how the type_index() may be used.
 
@@ -154,6 +215,15 @@
         return *this;
     }
 };
+
+// We can automatically parcel this "Arbitrary" struct
+// since it has no pointers and all public members.
+struct Arbitrary {
+    int i0;
+    std::vector<int> v1;
+    std::pair<int, int> p2;
+};
+
 #endif
 
 class Data;
@@ -171,22 +241,35 @@
 #ifdef METADATA_TESTING
     , std::vector<Datum>      // another complex object for testing
     , std::pair<Datum, Datum> // another complex object for testing
+    , std::vector<std::vector<std::pair<std::string, short>>> // complex object
     , MoveCount
+    , Arbitrary
 #endif
     >;
 
-// A subset of the metadata types may be directly copied.
+// A subset of the metadata types may be directly copied as bytes
 using primitive_metadata_types = compound_type<int32_t, int64_t, float, double
 #ifdef METADATA_TESTING
     , MoveCount
 #endif
     >;
 
+// A subset of metadata types which are a struct-based.
+using structural_metadata_types = compound_type<
+#ifdef METADATA_TESTING
+    Arbitrary
+#endif
+    >;
+
 template <typename T>
 inline constexpr bool is_primitive_metadata_type_v =
     primitive_metadata_types::contains_v<T>;
 
 template <typename T>
+inline constexpr bool is_structural_metadata_type_v =
+    structural_metadata_types::contains_v<T>;
+
+template <typename T>
 inline constexpr bool is_metadata_type_v =
     metadata_types::contains_v<T>;
 
@@ -251,6 +334,7 @@
     template <typename T, typename = std::enable_if_t<is_metadata_type_v<T>>>
     Datum& operator=(T&& t) {
         static_cast<std::any *>(this)->operator=(std::forward<T>(t));
+        return *this;
     }
 
     Datum(const char *t) : std::any(std::string(t)) {}; // special string handling
@@ -370,15 +454,41 @@
 
 /**
  * Parceling of Datum by recursive descent to a ByteString
+ *
+ * Parceling Format:
+ * All values are native endian order.
+ *
+ * Datum = { (datum_size_t) Size (size of datum, including the size field)
+ *           (type_size_t)  Type (the type index from type_as_value<T>.)
+ *           (byte string)  Payload<Type>
+ *         }
+ *
+ * Primitive types:
+ * Payload<Type> = { bytes in native endian order }
+ *
+ * Vector, Map, Container types:
+ * Payload<Type> = { (index_size_t) number of elements
+ *                   (byte string)  Payload<Element_Type> * number
+ *                 }
+ *
+ * Pair container types:
+ * Payload<Type> = { (byte string) Payload<first>,
+ *                   (byte string) Payload<second>
+ *                 }
+ *
+ * Design notes:
+ *
+ * 1) The size of each datum allows skipping of unknown types for compatibility
+ * of older code with newer Datums.
  */
 
 // Platform Apex compatibility note:
 // type_size_t may not change.
-using type_size_t = uint16_t;
+using type_size_t = uint32_t;
 
 // Platform Apex compatibility note:
 // index_size_t must not change.
-using index_size_t = uint16_t;
+using index_size_t = uint32_t;
 
 // Platform Apex compatibility note:
 // datum_size_t must not change.
@@ -391,6 +501,8 @@
 /*
     These should correspond to the Java AudioMetadata.java
 
+    Permitted type indexes:
+
     TYPE_NONE = 0,
     TYPE_INT32 = 1,
     TYPE_INT64 = 2,
@@ -408,71 +520,94 @@
 template <typename T>
 inline constexpr type_size_t type_as_value = get_type_as_value<T>();
 
-// forward decl for recursion.
+// forward decl for recursion - do not remove.
 bool copyToByteString(const Datum& datum, ByteString &bs);
 
-template <typename T,  typename = std::enable_if_t<
-    is_primitive_metadata_type_v<T> || std::is_arithmetic_v<std::decay_t<T>>
-    >>
-bool copyToByteString(const T *t, ByteString& bs) {
-    bs.append((uint8_t*)t, sizeof(*t));
+template <template <typename ...> class V, typename... Args>
+bool copyToByteString(const V<Args...>& v, ByteString&bs);
+// end forward decl
+
+// primitives handled here
+template <typename T>
+std::enable_if_t<
+    is_primitive_metadata_type_v<T> || std::is_arithmetic_v<std::decay_t<T>>,
+    bool
+    >
+copyToByteString(const T& t, ByteString& bs) {
+    bs.append((uint8_t*)&t, sizeof(t));
     return true;
 }
 
-bool copyToByteString(const std::string *s, ByteString&bs) {
-    if (s->size() > std::numeric_limits<index_size_t>::max()) return false;
-    index_size_t size = s->size();
-    if (!copyToByteString(&size, bs)) return false;
-    bs.append((uint8_t*)s->c_str());
-    return true;
+// pairs handled here
+template <typename A, typename B>
+bool copyToByteString(const std::pair<A, B>& p, ByteString& bs) {
+    return copyToByteString(p.first, bs) && copyToByteString(p.second, bs);
 }
 
-bool copyToByteString(const Data *d, ByteString& bs) {
-    if (d->size() > std::numeric_limits<index_size_t>::max()) return false;
-    index_size_t size = d->size();
-    if (!copyToByteString(&size, bs)) return false;
-    for (const auto &[name, datum2] : *d) {
-        if (!copyToByteString(&name, bs) || !copyToByteString(datum2, bs))
-            return false;
+// containers
+template <template <typename ...> class V, typename... Args>
+bool copyToByteString(const V<Args...>& v, ByteString& bs) {
+    if (v.size() > std::numeric_limits<index_size_t>::max()) return false;
+    index_size_t size = v.size();
+    if (!copyToByteString(size, bs)) return false;
+    if constexpr (std::is_same_v<std::decay_t<V<Args...>>, std::string>) {
+        bs.append((uint8_t*)v.c_str());
+    }  else /* constexpr */ {
+        for (const auto &d : v) { // handles std::vector and std::map
+            if (!copyToByteString(d, bs)) return false;
+        }
     }
     return true;
 }
 
-#ifdef METADATA_TESTING
-
-bool copyToByteString(const std::vector<Datum> *v, ByteString& bs) {
-    if (v->size() > std::numeric_limits<index_size_t>::max()) return false;
-    index_size_t size = v->size();
-    if (!copyToByteString(&size, bs)) return false;
-    for (index_size_t i = 0; i < size; ++i) {
-        if (!copyToByteString((*v)[i], bs)) return false;
+// simple struct data (use structured binding to extract members)
+template <typename T>
+std::enable_if_t<
+    is_structural_metadata_type_v<T>,
+    bool
+    >
+copyToByteString(const T& t, ByteString& bs) {
+    using type = std::decay_t<T>;
+    if constexpr(is_braces_constructible<type, any_type, any_type, any_type, any_type>{}) {
+        const auto& [e1, e2, e3, e4] = t;
+        return copyToByteString(e1, bs)
+            && copyToByteString(e2, bs)
+            && copyToByteString(e3, bs)
+            && copyToByteString(e4, bs);
+    } else if constexpr(is_braces_constructible<type, any_type, any_type, any_type>{}) {
+        const auto& [e1, e2, e3] = t;
+        return copyToByteString(e1, bs)
+            && copyToByteString(e2, bs)
+            && copyToByteString(e3, bs);
+    } else if constexpr(is_braces_constructible<type, any_type, any_type>{}) {
+        const auto& [e1, e2] = t;
+        return copyToByteString(e1, bs)
+            && copyToByteString(e2, bs);
+    } else if constexpr(is_braces_constructible<type, any_type>{}) {
+        const auto& [e1] = t;
+        return copyToByteString(e1, bs);
+    } else {
+        return false;
     }
-    return true;
 }
 
-bool copyToByteString(const std::pair<Datum, Datum> *p, ByteString& bs) {
-    return copyToByteString(p->first, bs) && copyToByteString(p->second, bs);
-}
-
-#endif // METADATA_TESTING
-
-// Now the actual code to use.
+// Datum
 bool copyToByteString(const Datum& datum, ByteString &bs) {
     bool success = false;
     return metadata_types::apply([&bs, &success](auto ptr) {
              // save type
              const type_size_t type = type_as_value<decltype(*ptr)>;
-             if (!copyToByteString(&type, bs)) return;
+             if (!copyToByteString(type, bs)) return;
 
              // get current location
              const size_t idx = bs.size();
 
              // save size (replaced later)
              datum_size_t datum_size = 0;
-             if (!copyToByteString(&datum_size, bs)) return;
+             if (!copyToByteString(datum_size, bs)) return;
 
              // copy data
-             if (!copyToByteString(ptr, bs)) return;
+             if (!copyToByteString(*ptr, bs)) return;
 
              // save correct size
              const size_t diff = bs.size() - idx - sizeof(datum_size);
@@ -487,129 +622,239 @@
  * Obtaining the Datum back from ByteString
  */
 
-// forward declaration
-Datum datumFromByteString(const ByteString& bs, size_t& idx);
+// A container that lists all the unknown types found during parsing.
+using ByteStringUnknowns = std::vector<type_size_t>;
 
-template <typename T, typename = std::enable_if_t<
+// forward decl for recursion - do not remove.
+bool copyFromByteString(Datum *datum, const ByteString &bs, size_t& idx,
+        ByteStringUnknowns *unknowns);
+
+template <template <typename ...> class V, typename... Args>
+bool copyFromByteString(V<Args...> *v, const ByteString& bs, size_t& idx,
+        ByteStringUnknowns *unknowns);
+
+// primitive
+template <typename T>
+std::enable_if_t<
         is_primitive_metadata_type_v<T> ||
-        std::is_arithmetic_v<std::decay_t<T>>
-        >>
-bool copyFromByteString(T *dest, const ByteString& bs, size_t& idx) {
+        std::is_arithmetic_v<std::decay_t<T>>,
+        bool
+        >
+copyFromByteString(T *dest, const ByteString& bs, size_t& idx,
+        ByteStringUnknowns *unknowns __unused) {
     if (idx + sizeof(T) > bs.size()) return false;
     bs.copy((uint8_t*)dest, sizeof(T), idx);
     idx += sizeof(T);
     return true;
 }
 
-bool copyFromByteString(std::string *s, const ByteString& bs, size_t& idx) {
+// pairs
+template <typename A, typename B>
+bool copyFromByteString(std::pair<A, B>* p, const ByteString& bs, size_t& idx,
+        ByteStringUnknowns *unknowns) {
+    return copyFromByteString(&p->first, bs, idx, unknowns)
+            && copyFromByteString(&p->second, bs, idx, unknowns);
+}
+
+// containers
+template <template <typename ...> class V, typename... Args>
+bool copyFromByteString(V<Args...> *v, const ByteString& bs, size_t& idx,
+        ByteStringUnknowns *unknowns) {
     index_size_t size;
-    if (!copyFromByteString(&size, bs, idx)) return false;
-    if (idx + size > bs.size()) return false;
-    s->resize(size);
-    for (index_size_t i = 0; i < size; ++i) {
-        (*s)[i] = bs[idx++];
+    if (!copyFromByteString(&size, bs, idx, unknowns)) return false;
+
+    if constexpr (std::is_same_v<std::decay_t<V<Args...>>, std::string>) {
+        if (size > bs.size() - idx) return false;
+        v->resize(size);
+        for (index_size_t i = 0; i < size; ++i) {
+            (*v)[i] = bs[idx++];
+        }
+    } else if constexpr (is_specialization_v<std::decay_t<V<Args...>>, std::vector>) {
+        for (index_size_t i = 0; i < size; ++i) {
+            std::decay_t<decltype(*v->begin())> value{};
+            if (!copyFromByteString(&value, bs, idx, unknowns)) {
+                return false;
+            }
+            if constexpr (std::is_same_v<std::decay_t<decltype(value)>, Datum>) {
+                if (!value.has_value()) {
+                    continue;  // ignore empty datum values in a vector.
+                }
+            }
+            v->emplace_back(std::move(value));
+        }
+    } else if constexpr (is_specialization_v<std::decay_t<V<Args...>>, std::map>) {
+        for (index_size_t i = 0; i < size; ++i) {
+            // we can't directly use pair because there may be internal const decls.
+            std::decay_t<decltype(v->begin()->first)> key{};
+            std::decay_t<decltype(v->begin()->second)> value{};
+            if (!copyFromByteString(&key, bs, idx, unknowns) ||
+                    !copyFromByteString(&value, bs, idx, unknowns)) {
+                return false;
+            }
+            if constexpr (std::is_same_v<std::decay_t<decltype(value)>, Datum>) {
+                if (!value.has_value()) {
+                    continue;  // ignore empty datum values in a map.
+                }
+            }
+            v->emplace(std::move(key), std::move(value));
+        }
+    } else /* constexpr */ {
+        for (index_size_t i = 0; i < size; ++i) {
+            std::decay_t<decltype(*v->begin())> value{};
+            if (!copyFromByteString(&value, bs, idx, unknowns)) {
+                return false;
+            }
+            v->emplace(std::move(value));
+        }
     }
     return true;
 }
 
-bool copyFromByteString(Data *d, const ByteString& bs, size_t& idx) {
-    index_size_t size;
-    if (!copyFromByteString(&size, bs, idx)) return false;
-    for (index_size_t i = 0; i < size; ++i) {
-        std::string s;
-        if (!copyFromByteString(&s, bs, idx)) return false;
-        Datum value = datumFromByteString(bs, idx);
-        if (!value.has_value()) return false;
-        (*d)[s] = std::move(value);
+// simple structs (use structured binding to extract members)
+template <typename T>
+typename std::enable_if_t<is_structural_metadata_type_v<T>, bool>
+copyFromByteString(T *t, const ByteString& bs, size_t& idx,
+        ByteStringUnknowns *unknowns) {
+    using type = std::decay_t<T>;
+    if constexpr(is_braces_constructible<type, any_type, any_type, any_type, any_type>{}) {
+        auto& [e1, e2, e3, e4] =  *t;
+        return copyFromByteString(&e1, bs, idx, unknowns)
+            && copyFromByteString(&e2, bs, idx, unknowns)
+            && copyFromByteString(&e3, bs, idx, unknowns)
+            && copyFromByteString(&e4, bs, idx, unknowns);
+    } else if constexpr(is_braces_constructible<type, any_type, any_type, any_type>{}) {
+        auto& [e1, e2, e3] =  *t;
+        return copyFromByteString(&e1, bs, idx, unknowns)
+            && copyFromByteString(&e2, bs, idx, unknowns)
+            && copyFromByteString(&e3, bs, idx, unknowns);
+    } else if constexpr(is_braces_constructible<type, any_type, any_type>{}) {
+        auto& [e1, e2] =  *t;
+        return copyFromByteString(&e1, bs, idx, unknowns)
+            && copyFromByteString(&e2, bs, idx, unknowns);
+    } else if constexpr(is_braces_constructible<type, any_type>{}) {
+        auto& [e1] =  *t;
+        return copyFromByteString(&e1, bs, idx, unknowns);
+    } else {
+        return false;
     }
-    return true;
 }
 
 // TODO: consider a more elegant implementation, e.g. std::variant visit.
 // perhaps using integer sequences.
-Datum datumFromByteString(const ByteString &bs, size_t& idx) {
+bool copyFromByteString(Datum *datum, const ByteString &bs, size_t& idx,
+        ByteStringUnknowns *unknowns) {
     type_size_t type;
-    if (!copyFromByteString(&type, bs, idx)) return {};
+    if (!copyFromByteString(&type, bs, idx, unknowns)) return false;
 
     datum_size_t datum_size;
-    if (!copyFromByteString(&datum_size, bs, idx)) return {};
-    if (datum_size > bs.size() - idx) return {};
+    if (!copyFromByteString(&datum_size, bs, idx, unknowns)) return false;
+    if (datum_size > bs.size() - idx) return false;
     const size_t finalIdx = idx + datum_size; // TODO: always check this
 
     switch (type) {
     case type_as_value<int32_t>: {
         int32_t i32;
-        if (!copyFromByteString(&i32, bs, idx)) return {};
-        return i32;
+        if (!copyFromByteString(&i32, bs, idx, unknowns)) return false;
+        *datum = i32;
+        return true;
     }
     case type_as_value<int64_t>: {
         int64_t i64;
-        if (!copyFromByteString(&i64, bs, idx)) return {};
-        return i64;
+        if (!copyFromByteString(&i64, bs, idx, unknowns)) return false;
+        *datum = i64;
+        return true;
     }
     case type_as_value<float>: {
         float f;
-        if (!copyFromByteString(&f, bs, idx)) return {};
-        return f;
+        if (!copyFromByteString(&f, bs, idx, unknowns)) return false;
+        *datum = f;
+        return true;
     }
     case type_as_value<double>: {
         double d;
-        if (!copyFromByteString(&d, bs, idx)) return {};
-        return d;
+        if (!copyFromByteString(&d, bs, idx, unknowns)) return false;
+        *datum = d;
+        return true;
     }
     case type_as_value<std::string>: {
         std::string s;
-        if (!copyFromByteString(&s, bs, idx)) return {};
-        return s;
+        if (!copyFromByteString(&s, bs, idx, unknowns)) return false;
+        *datum = std::move(s);
+        return true;
     }
     case type_as_value<Data>: {
         Data d;
-        if (!copyFromByteString(&d, bs, idx)) return {};
-        return d;
+        if (!copyFromByteString(&d, bs, idx, unknowns)) return false;
+        *datum = std::move(d);
+        return true;
     }
 #ifdef METADATA_TESTING
     case type_as_value<std::vector<Datum>>: {
-        index_size_t size;
-        if (!copyFromByteString(&size, bs, idx)) return {};
-        std::vector<Datum> v(size);
-        for (index_size_t i = 0; i < size; ++i) {
-            Datum d = datumFromByteString(bs, idx);
-            if (!d.has_value()) return {};
-            v[i] = std::move(d);
-        }
-        return v;
+        std::vector<Datum> v;
+        if (!copyFromByteString(&v, bs, idx, unknowns)) return false;
+        *datum =std::move(v);
+        return true;
     }
     case type_as_value<std::pair<Datum, Datum>>: {
-        Datum d1 = datumFromByteString(bs, idx);
-        if (!d1.has_value()) return {};
-        Datum d2 = datumFromByteString(bs, idx);
-        if (!d2.has_value()) return {};
-        return std::make_pair(std::move(d1), std::move(d2));
+        std::pair<Datum, Datum> p;
+        if (!copyFromByteString(&p, bs, idx, unknowns)) return false;
+        *datum = std::move(p);
+        return true;
+    }
+    case type_as_value<std::vector<std::vector<std::pair<std::string, short>>>>: {
+        std::vector<std::vector<std::pair<std::string, short>>> vvp;
+        if (!copyFromByteString(&vvp, bs, idx, unknowns)) return false;
+        *datum = std::move(vvp);
+        return true;
+    }
+    case type_as_value<Arbitrary>: {
+        Arbitrary a{};
+        if (!copyFromByteString(&a, bs, idx, unknowns)) return false;
+        *datum = std::move(a);
+        return true;
     }
     case type_as_value<MoveCount>: {
         MoveCount mc;
-        if (!copyFromByteString(&mc, bs, idx)) return {};
-        return mc;
+        if (!copyFromByteString(&mc, bs, idx, unknowns)) return false;
+        *datum = std::move(mc);
+        return true;
     }
 #endif
     case 0: // This is excluded to have compile failure. We do not allow undefined types.
     default:
         idx = finalIdx; // skip unknown type.
-        return {};
+        if (unknowns != nullptr) {
+            unknowns->push_back(type);
+            return true;  // allow further recursion.
+        }
+        return false;
     }
 }
 
 // Handy helpers - these are the most efficient ways to parcel Data.
-Data dataFromByteString(const ByteString &bs) {
+/**
+ * Returns the Data map from a byte string.
+ *
+ * If unknowns is nullptr, then any unknown entries during parsing will cause
+ * an empty map to be returned.
+ *
+ * If unknowns is non-null, then it contains all of the unknown types
+ * encountered during parsing, and a partial map will be returned excluding all
+ * unknown types encountered.
+ */
+Data dataFromByteString(const ByteString &bs,
+        ByteStringUnknowns *unknowns = nullptr) {
     Data d;
     size_t idx = 0;
-    copyFromByteString(&d, bs, idx);
+    if (!copyFromByteString(&d, bs, idx, unknowns)) {
+        return {};
+    }
     return d; // copy elision
 }
 
 ByteString byteStringFromData(const Data &data) {
     ByteString bs;
-    copyToByteString(&data, bs);
+    copyToByteString(data, bs);
     return bs; // copy elision
 }
 
diff --git a/audio_utils/tests/metadata_tests.cpp b/audio_utils/tests/metadata_tests.cpp
index 0062018..adc0209 100644
--- a/audio_utils/tests/metadata_tests.cpp
+++ b/audio_utils/tests/metadata_tests.cpp
@@ -36,11 +36,43 @@
 inline constexpr CKey<Data> TABLE("table");
 
 #ifdef METADATA_TESTING
+
+// Validate recursive typing on "Datum".
 inline constexpr CKey<std::vector<Datum>> VECTOR("vector");
 inline constexpr CKey<std::pair<Datum, Datum>> PAIR("pair");
+
+// Validate that we move instead of copy.
 inline constexpr CKey<MoveCount> MOVE_COUNT("MoveCount");
+
+// Validate recursive container support.
+inline constexpr CKey<std::vector<std::vector<std::pair<std::string, short>>>> FUNKY("funky");
+
+// Validate structured binding parceling.
+inline constexpr CKey<Arbitrary> ARBITRARY("arbitrary");
 #endif
 
+std::string toString(const ByteString &bs) {
+    std::stringstream ss;
+    ss << "{\n" << std::hex;
+    if (bs.size() > 0) {
+        for (size_t i = 0; ; ++i) {
+            if ((i & 7) == 0) {
+                ss << "  ";
+            }
+            ss << "0x" <<  std::setfill('0') << std::setw(2) << (unsigned)bs[i];
+            if (i == bs.size() - 1) {
+                break;
+            } else if ((i & 7) == 7) {
+                ss << ",\n";
+            } else {
+                ss << ", ";
+            }
+        }
+    }
+    ss << "\n}\n";
+    return ss.str();
+}
+
 TEST(metadata_tests, basic_datum) {
     Datum d;
     d = "abc";
@@ -120,7 +152,8 @@
     ASSERT_TRUE(copyToByteString(mc, bs));
     // deserialize
     size_t idx = 0;
-    Datum parceled = datumFromByteString(bs, idx);
+    Datum parceled;
+    ASSERT_TRUE(copyFromByteString(&parceled, bs, idx, nullptr /* unknowns */));
 
     // everything OK with the received data?
     ASSERT_EQ(bs.size(), idx);          // no data left over.
@@ -159,16 +192,8 @@
     ASSERT_EQ("neo", d[MY_NAME_IS]);
     ASSERT_EQ("spot", d[ITS_NAME_IS]);
 
-    // Try round-trip conversion to a ByteString.
-    ByteString bs;
-    copyToByteString(d, bs);
-    size_t index = 0;
-    Datum datum = datumFromByteString(bs, index);
-
-    ASSERT_TRUE(datum.has_value());
-
-    // Check that we got a valid Data table back.
-    Data data = *std::any_cast<Data>(&datum);
+    ByteString bs = byteStringFromData(d);
+    Data data = dataFromByteString(bs);
     ASSERT_EQ((size_t)8, data.size());
 
     ASSERT_EQ(1, std::any_cast<int32_t>(data["int32"]));
@@ -219,14 +244,22 @@
 #ifdef METADATA_TESTING
     big[VECTOR] = std::vector<Datum>{small, small};
     big[PAIR] = std::make_pair<Datum, Datum>(small, small);
-    ASSERT_EQ(1, big[TABLE][MOVE_COUNT].mCopyCount); // one copy done.
+    ASSERT_EQ(1, big[TABLE][MOVE_COUNT].mCopyCount); // one copy done for small.
+
+    big[FUNKY] = std::vector<std::vector<std::pair<std::string, short>>>{
+        {{"a", 1}, {"b", 2}},
+        {{"c", 3}, {"d", 4}},
+    };
+
+    // struct Arbitrary { int i0; std::vector<int> v1; std::pair<int, int> p2; };
+    big[ARBITRARY] = Arbitrary{0, {1, 2, 3}, {4, 5}};
 #endif
 
     // Try round-trip conversion to a ByteString.
     ByteString bs = byteStringFromData(big);
     Data data = dataFromByteString(bs);
 #ifdef METADATA_TESTING
-    ASSERT_EQ((size_t)3, data.size());
+    ASSERT_EQ((size_t)5, data.size());
 #else
     ASSERT_EQ((size_t)1, data.size());
 #endif
@@ -239,5 +272,93 @@
     ASSERT_EQ("abc", std::any_cast<Data>(data[VECTOR][1])[MY_NAME_IS]);
     ASSERT_EQ("abc", std::any_cast<Data>(data[PAIR].first)[MY_NAME_IS]);
     ASSERT_EQ(1, data[TABLE][MOVE_COUNT].mCopyCount); // no additional copies.
+
+    auto funky = data[FUNKY];
+    ASSERT_EQ("a", funky[0][0].first);
+    ASSERT_EQ(4, funky[1][1].second);
+
+    auto arbitrary = data[ARBITRARY];
+    ASSERT_EQ(0, arbitrary.i0);
+    ASSERT_EQ(2, arbitrary.v1[1]);
+    ASSERT_EQ(4, arbitrary.p2.first);
 #endif
 }
+
+// DO NOT CHANGE THIS after R, but add a new test.
+TEST(metadata_tests, compatibility_R) {
+    Data d;
+    d.emplace("i32", (int32_t)1);
+    d.emplace("i64", (int64_t)2);
+    d.emplace("float", (float)3.1f);
+    d.emplace("double", (double)4.11);
+    Data s;
+    s.emplace("string", "hello");
+    d.emplace("data", s);
+
+    ByteString bs = byteStringFromData(d);
+    ALOGD("%s", toString(bs).c_str());
+
+    // Since we use a map instead of a hashmap
+    // layout order of elements is precisely defined.
+    ByteString reference = {
+        0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+        0x64, 0x61, 0x74, 0x61, 0x06, 0x00, 0x00, 0x00,
+        0x1f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+        0x06, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x69,
+        0x6e, 0x67, 0x05, 0x00, 0x00, 0x00, 0x09, 0x00,
+        0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x68, 0x65,
+        0x6c, 0x6c, 0x6f, 0x06, 0x00, 0x00, 0x00, 0x64,
+        0x6f, 0x75, 0x62, 0x6c, 0x65, 0x04, 0x00, 0x00,
+        0x00, 0x08, 0x00, 0x00, 0x00, 0x71, 0x3d, 0x0a,
+        0xd7, 0xa3, 0x70, 0x10, 0x40, 0x05, 0x00, 0x00,
+        0x00, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x03, 0x00,
+        0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x66, 0x66,
+        0x46, 0x40, 0x03, 0x00, 0x00, 0x00, 0x69, 0x33,
+        0x32, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00,
+        0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
+        0x00, 0x69, 0x36, 0x34, 0x02, 0x00, 0x00, 0x00,
+        0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00
+    };
+    ASSERT_EQ(reference, bs);
+
+    Data decoded = dataFromByteString(bs);
+
+    // TODO: data equality.
+    // ASSERT_EQ(decoded, d);
+
+    ASSERT_EQ(1, std::any_cast<int32_t>(decoded["i32"]));
+    ASSERT_EQ(2, std::any_cast<int64_t>(decoded["i64"]));
+    ASSERT_EQ(3.1f, std::any_cast<float>(decoded["float"]));
+    ASSERT_EQ(4.11, std::any_cast<double>(decoded["double"]));
+    Data decoded_s = std::any_cast<Data>(decoded["data"]);
+
+    ASSERT_EQ("hello", std::any_cast<std::string>(s["string"]));
+
+    {
+        ByteString unknownData = reference;
+        unknownData[12] = 0xff;
+        Data decoded2 = dataFromByteString(unknownData);
+        ASSERT_EQ((size_t)0, decoded2.size());
+
+        ByteStringUnknowns unknowns;
+        Data decoded3 = dataFromByteString(unknownData, &unknowns);
+        ASSERT_EQ((size_t)4, decoded3.size());
+        ASSERT_EQ((size_t)1, unknowns.size());
+        ASSERT_EQ((unsigned)0xff, unknowns[0]);
+    }
+
+    {
+        ByteString unknownDouble = reference;
+        ASSERT_EQ(0x4, unknownDouble[0x3d]);
+        unknownDouble[0x3d] = 0xfe;
+        Data decoded2 = dataFromByteString(unknownDouble);
+        ASSERT_EQ((size_t)0, decoded2.size());
+
+        ByteStringUnknowns unknowns;
+        Data decoded3 = dataFromByteString(unknownDouble, &unknowns);
+        ASSERT_EQ((size_t)4, decoded3.size());
+        ASSERT_EQ((size_t)1, unknowns.size());
+        ASSERT_EQ((unsigned)0xfe, unknowns[0]);
+    }
+};