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]);
+ }
+};