|  | // Copyright 2017 The Fuchsia Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #ifndef SRC_MODULAR_LIB_FIDL_JSON_XDR_H_ | 
|  | #define SRC_MODULAR_LIB_FIDL_JSON_XDR_H_ | 
|  |  | 
|  | #include <lib/fidl/cpp/string.h> | 
|  | #include <lib/syslog/cpp/macros.h> | 
|  |  | 
|  | #include <array> | 
|  | #include <map> | 
|  | #include <string> | 
|  | #include <type_traits> | 
|  | #include <vector> | 
|  |  | 
|  | #include <rapidjson/document.h> | 
|  | #include <rapidjson/rapidjson.h> | 
|  |  | 
|  | #include "src/lib/fxl/macros.h" | 
|  | #include "src/lib/json_parser/pretty_print.h" | 
|  |  | 
|  | namespace modular { | 
|  |  | 
|  | // This file provides a tool to serialize arbitrary data structures | 
|  | // into JSON, and back. It specifically supports serialization of FIDL | 
|  | // data (structs, arrays, maps, and combinations thereof), but FIDL is | 
|  | // not a requirement. For example, support for STL containers in | 
|  | // addition to FIDL containers is easy to add once we need it. | 
|  | // | 
|  | // We use JSON as the serialization format to store structured values | 
|  | // (and at times also structured keys) in the ledger. | 
|  | // | 
|  | // The design is inspired by Sun RPC's XDR, specifically the definiton | 
|  | // of "filters". A filter function takes an operation and a data | 
|  | // pointer, and depending on the operation parameter either serializes | 
|  | // or deserializes the data. There is one such filter function for | 
|  | // every data type. A filter for a simple data type does different | 
|  | // things for serialization and deserialization, so having a single | 
|  | // one for both operations instead of two separate functions barely | 
|  | // reduces code size. However, the efficiency of this design shows in | 
|  | // composition: A filter for a struct can be written by simply calling | 
|  | // the filters for each field of the struct and passing the operation | 
|  | // parameter down. Thus, a filter function for a struct is half the | 
|  | // code size of a pair of serialization/deserialization functions. | 
|  | // | 
|  | // NOTES: | 
|  | // | 
|  | // XDR is not sync: Although the XDR operation can be applied to an | 
|  | // existing instance of the output end (an existing FIDL struct, or an | 
|  | // existing JSON AST), full synchronization of the data structure is | 
|  | // not guaranteed. All data that exist in the input are added to the | 
|  | // output, but not necessarily all data that don't exist in the input | 
|  | // are removed from the output. Also, if an error occurs, the output | 
|  | // is left in some intermediate state. The most suitable use for | 
|  | // updates as of now is to always create a fresh output instance, and | 
|  | // if the transciption succeeds, replace the previous instance by the | 
|  | // fresh instance. | 
|  | // | 
|  | // XDR is not about resolving conflicts: If an existing output | 
|  | // instance is updated using XDR, we might improve accuracy of | 
|  | // removing data that no longer exist, but it is out of the scope of | 
|  | // XDR (at least for now) to note that input data conflict with | 
|  | // existing output data, and resolving the conflict. Conflict | 
|  | // resolution between different versions of data is most likely | 
|  | // handled outside XDR. | 
|  | // | 
|  | // It may be that we will use XDR to support conflict resolution in a | 
|  | // data type agnostic way: Instead of defining a conflict resolution | 
|  | // between e.g. STL or FIDL data structures, we might instead define | 
|  | // XDR filters for them, translate all values to JSON, apply conflict | 
|  | // resolution to JSON, and translate the result back. | 
|  | // | 
|  | // SCHEMA VERSION BACK COMPATIBILITY: | 
|  | // | 
|  | // The schema of the persistent data is defined in terms of filter functions. In | 
|  | // order to support new versions of the code reading versions of the data | 
|  | // written by old versions of the code, filter functions are always defined by | 
|  | // the client at the top level entry points as lists, never as single functions. | 
|  | // | 
|  | // The lists contain the filter for the current version of the schema at the | 
|  | // top, and filters for reading previous versions into the current version of | 
|  | // the code below. | 
|  | // | 
|  | // Whenever the storage schema changes, a new version of the filter is created | 
|  | // and add it to the version list. | 
|  | // | 
|  | // If the memory schema changes, filters of all versions are adjusted as | 
|  | // necessary. | 
|  | // | 
|  | // Filters that don't change can be reused between versions. If a filter does | 
|  | // not change, but the ones that it uses do change, templates can be used to | 
|  | // save on code duplication. | 
|  | // | 
|  | // TODO(mesch): Right now there is no way to ensure that old versions of the | 
|  | // code will never read new versions of the data. Support for this is expected | 
|  | // from the Ledger, and partially from an upcoming API for explicit version | 
|  | // numbers. | 
|  | // | 
|  | // See comments on XdrFilterType, XdrRead(), and XdrWrite() for details. | 
|  |  | 
|  | class XdrContext; | 
|  |  | 
|  | // The two operations: reading from JSON or writing to JSON. | 
|  | enum class XdrOp { | 
|  | TO_JSON = 0, | 
|  | FROM_JSON = 1, | 
|  | }; | 
|  |  | 
|  | // Custom types are serialized by passing a function of this type to a method on | 
|  | // XdrContext. Note this is a pointer type that points to a const (an existing | 
|  | // function). So we will never use a reference or a const of it. However, | 
|  | // argument values of such will still be defined const. | 
|  | // | 
|  | // The top level entry functions used by clients never pass a single filter | 
|  | // function alone, but always a list of filters for different versions of the | 
|  | // data, such that the reading code can fall back to functions reading | 
|  | // previously written versions. Such lists can (and should) be defined | 
|  | // statically as constexpr: | 
|  | // | 
|  | //   void XdrFoo_v1(XdrContext* const xdr, Foo* const data) { ... } | 
|  | //   void XdrFoo_v2(XdrContext* const xdr, Foo* const data) { ... } | 
|  | // | 
|  | //   constexpr XdrFilterType<Foo> XdrFoo[] = { | 
|  | //     XdrFoo_v1, | 
|  | //     XdrFoo_v2, | 
|  | //     nullptr, | 
|  | //   }; | 
|  | // | 
|  | //   Foo foo; | 
|  | //   XdrRead(json, &foo, XdrFoo); | 
|  | // | 
|  | // XdrRead and XdrWrite are defined to take an XdrFilterList<> argument, which | 
|  | // that constexpr can be passed to. See Definition of XdrRead(), below. | 
|  | template <typename T> | 
|  | using XdrFilterType = void (*)(XdrContext*, T*); | 
|  |  | 
|  | template <typename T> | 
|  | using XdrFilterList = void (*const*)(XdrContext*, T*); | 
|  |  | 
|  | // A generic implementation of such a filter, which works for simple | 
|  | // types. (The implementation uses XdrContext, so it's below.) | 
|  | template <typename V> | 
|  | void XdrFilter(XdrContext* xdr, V* value); | 
|  |  | 
|  | // XdrContext holds on to a JSON document as well as a specific | 
|  | // position inside the document on which its methods operate, as well | 
|  | // as the operation (writing to JSON, reading from JSON) that is | 
|  | // executed when methods are called. | 
|  | // | 
|  | // There are two kinds of methods: Value() and Field(). Value() | 
|  | // affects the current JSON value itself. Field() assumes the current | 
|  | // JSON value is an Object, accesses a property on it and affects the | 
|  | // value of the property. | 
|  | // | 
|  | // Clients usually call Value(); filters for custom types usually call | 
|  | // Field(). | 
|  | class XdrContext { | 
|  | public: | 
|  | XdrContext(XdrOp op, rapidjson::Document* doc, std::string* error); | 
|  |  | 
|  | ~XdrContext(); | 
|  |  | 
|  | // Returns the XdrOp that this XdrContext was created with. | 
|  | // | 
|  | // This is required by some XdrFilters that cannot use the same code to set or | 
|  | // get data from objects. However, in general, try to avoid special-casing | 
|  | // an XdrFilter to change behavior based on whether it's translating to or | 
|  | // from JSON. | 
|  | XdrOp op() const { return op_; } | 
|  |  | 
|  | // Below are methods to handle values on properties of objects for | 
|  | // handling standalone values. These methods are called by filter | 
|  | // code during a serialization/deserialization operation. | 
|  |  | 
|  | // The version of a struct. On Write, the version number is written and it | 
|  | // always returns true. On Read, raises an error and returns false if the | 
|  | // version number read doesn't match the version number passed in. Thus it | 
|  | // gives an explicit way to a filter function to force an error. | 
|  | // | 
|  | // The filter should also return early so as to not partially read data. | 
|  | // | 
|  | // This can be applied at any level, but only when it happens as the first | 
|  | // call in the top level filter will it fully prevent partial reads. | 
|  | // | 
|  | // How it should be used: | 
|  | // | 
|  | //   void XdrFoo_v1(XdrContext* const xdr, Foo* const data) { ... } | 
|  | //   void XdrFoo_v2(XdrContext* const xdr, Foo* const data) { | 
|  | //      if (!xdr->Version(2)) { | 
|  | //        return; | 
|  | //      } | 
|  | //      ... | 
|  | //   } | 
|  | // | 
|  | //   constexpr XdrFilterType<Foo> XdrFoo[] = { | 
|  | //     XdrFoo_v2, | 
|  | //     XdrFoo_v1, | 
|  | //     nullptr, | 
|  | //   }; | 
|  | // | 
|  | //   Foo foo; | 
|  | //   XdrRead(json, &foo, XdrFoo); | 
|  | // | 
|  | // Notice that _v1 doesn't need to have a Version() call. This is usual when | 
|  | // the first use of the data predates the introduction of the Version() | 
|  | // mechanism. | 
|  | // | 
|  | // This method cannot be used (and returns false and logs an error) in a | 
|  | // context that is not Object. | 
|  | // | 
|  | // It writes the reserved field name "@version" to the current Object context. | 
|  | // | 
|  | // The value passed to the call inside the Xdr filter function should never be | 
|  | // defined as a constant outside of the filter function, because then it | 
|  | // becomes tempting to change it to a new version number without creating a | 
|  | // copy of the filter function for the previous version number. | 
|  | bool Version(uint32_t version); | 
|  |  | 
|  | // For optional fields, such as in FIDL tables. Returns true if either: | 
|  | //   * op is TO_JSON and the given "data_has_value" boolean is true; or | 
|  | //   * op is FROM_JSON and the JSON field exists. | 
|  | // Returns false otherwise. | 
|  | // | 
|  | // |field| is the name of the JSON field to be read or written to | 
|  | // |data_has_value| used only if writing TO_JSON, this indicates the | 
|  | // source data has a value for the field. | 
|  | // | 
|  | // Returns: true if the source contains the field, false otherwise | 
|  | // | 
|  | // Example: | 
|  | //   if (xdr->HasField("field", data->has_field())) | 
|  | //     xdr->Field("field", data->mutable_field()); | 
|  | //   else | 
|  | //     data->clear_field(); | 
|  | bool HasField(const char field[], bool data_has_value) { | 
|  | if (op_ == XdrOp::TO_JSON) { | 
|  | return data_has_value; | 
|  | } | 
|  | return value_->HasMember(field); | 
|  | } | 
|  |  | 
|  | // A field of a struct. The value type V is assumed to be one of the | 
|  | // primitive JSON data types. If anything else is passed here and | 
|  | // not to the method below with a custom filter, the rapidjson code | 
|  | // will fail to compile. | 
|  | template <typename V> | 
|  | void Field(const char field[], V* const data) { | 
|  | Field(field).Value(data); | 
|  | } | 
|  |  | 
|  | // Same as Field(), but allows a default value to be specified. | 
|  | // | 
|  | // |field| is the name of the JSON field to be read or written to | 
|  | // |data| is a pointer to the fidl field to be read or written to | 
|  | // |use_data| when set to true, set the JSON field to the value of data | 
|  | // |default_value| the value data should be defaulted to if the field doesn't | 
|  | //   exist in JSON | 
|  | // | 
|  | // IMPORTANT: calling mutable_field() on a fidl table forces a default value | 
|  | // on the field resulting in has_field() to return true. The value of | 
|  | // has_field() will need to be stored in a separate variable to avoid this. | 
|  | // Example: | 
|  | // bool has_field = data->has_field(); | 
|  | // xdr->FieldWithDefault("field", data->mutable_field(), has_field, "value"); | 
|  | template <typename V> | 
|  | void FieldWithDefault(const char field[], V* const data, bool use_data, V default_value) { | 
|  | FieldWithDefault(field).ValueWithDefault(data, use_data, default_value); | 
|  | } | 
|  |  | 
|  | // If we supply a custom filter for the value of a field, the data | 
|  | // type of the field very often does not match directly the data | 
|  | // type for which we write a filter, therefore this template has two | 
|  | // type parameters. This happens in several situations: | 
|  | // | 
|  | // 1. Fields with fidl struct types. The field data type, which we pass the | 
|  | //    data for, is a std::unique_ptr<X>, but the filter supplied is for X (and | 
|  | //    thus takes X*). | 
|  | // | 
|  | // 2. Fields with fidl array types. The filter is for an element, | 
|  | //    but the field is the array type. | 
|  | // | 
|  | // 3. Fields with STL container types. The filter is for an element, | 
|  | //    but the field is the container type. | 
|  | // | 
|  | // We could handle this by specialization, it's much simpler to just cover all | 
|  | // possible combinations with a template of higher dimension, at the expense | 
|  | // of covering also a few impossible cases. | 
|  | template <typename D, typename V> | 
|  | void Field(const char field[], D* const data, XdrFilterType<V> const filter) { | 
|  | Field(field).Value(data, filter); | 
|  | } | 
|  |  | 
|  | // Same as Field(), but allows for a default value to be specified. If | 
|  | // |use_data| is false, the supplied |default_value| is used. Otherwise, | 
|  | // |data| is used. | 
|  | template <typename D, typename V> | 
|  | void FieldWithDefault(const char field[], D* const data, XdrFilterType<V> const filter, | 
|  | bool use_data, D default_value) { | 
|  | FieldWithDefault(field).ValueWithDefault(data, filter, use_data, std::move(default_value)); | 
|  | } | 
|  |  | 
|  | // Below are methods analog to those for values on properties of | 
|  | // objects for handling standalone values. These methods are called | 
|  | // by XdrContext client code such as XdrRead() and XdrWrite() to | 
|  | // start a serialization/deserialization operation. | 
|  |  | 
|  | // A simple value is mapped to the corresponding JSON type (int, | 
|  | // float, bool) directly. | 
|  | template <typename V> | 
|  | typename std::enable_if<!std::is_enum<V>::value>::type Value(V* data) { | 
|  | V default_value = V(); | 
|  | ValueWithDefault(data, true, std::move(default_value)); | 
|  | } | 
|  |  | 
|  | // An enum is mapped to a JSON int. | 
|  | template <typename V> | 
|  | typename std::enable_if<std::is_enum<V>::value>::type Value(V* const data) { | 
|  | V default_value = V(); | 
|  | ValueWithDefault(data, true, std::move(default_value)); | 
|  | } | 
|  |  | 
|  | // This function used when |data| is not an enum. It maps type V to its | 
|  | // corresponding JSON type (int, float, bool). If |use_data| is true, read | 
|  | // from |data|, otherwise, use |default_value|. | 
|  | template <typename V> | 
|  | typename std::enable_if<!std::is_enum<V>::value>::type ValueWithDefault(V* data, bool use_data, | 
|  | V default_value) { | 
|  | switch (op_) { | 
|  | case XdrOp::TO_JSON: | 
|  | use_data ? value_->Set(*data, allocator()) : value_->Set(default_value, allocator()); | 
|  | break; | 
|  |  | 
|  | case XdrOp::FROM_JSON: | 
|  | if (!value_->Is<V>()) { | 
|  | if (use_data) { | 
|  | AddError("Unexpected type."); | 
|  | return; | 
|  | } | 
|  | *data = std::move(default_value); | 
|  | return; | 
|  | } | 
|  | *data = value_->Get<V>(); | 
|  | } | 
|  | } | 
|  |  | 
|  | // This function is used when |data| is an enum. It maps enums to a JSON int. | 
|  | // If |use_data| is true, read from |data|, otherwise, use |default_value|. | 
|  | template <typename V> | 
|  | typename std::enable_if<std::is_enum<V>::value>::type ValueWithDefault(V* const data, | 
|  | bool use_data, | 
|  | V default_value) { | 
|  | switch (op_) { | 
|  | case XdrOp::TO_JSON: | 
|  | use_data ? value_->Set(static_cast<int>(*data), allocator()) | 
|  | : value_->Set(static_cast<int>(default_value), allocator()); | 
|  | break; | 
|  |  | 
|  | case XdrOp::FROM_JSON: | 
|  | if (!value_->Is<int>()) { | 
|  | if (use_data) { | 
|  | AddError("Unexpected type."); | 
|  | return; | 
|  | } | 
|  | *data = std::move(default_value); | 
|  | return; | 
|  | } | 
|  | *data = static_cast<V>(value_->Get<int>()); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Bytes and shorts, both signed and unsigned, are mapped to JSON int, since | 
|  | // they are not directly supported in the rapidjson API. | 
|  | void Value(unsigned char* data); | 
|  | void Value(int8_t* data); | 
|  | void Value(unsigned short* data); | 
|  | void Value(short* data); | 
|  |  | 
|  | // A fidl String is mapped to either (i.e., the union type of) JSON | 
|  | // null or JSON string. | 
|  | void Value(fidl::StringPtr* data); | 
|  |  | 
|  | // An STL string is mapped to a JSON string. | 
|  | void Value(std::string* data); | 
|  |  | 
|  | // Allows for default values to be specified for the following types. | 
|  | // These follow the mapping properties listed in Value(). | 
|  | void ValueWithDefault(unsigned char* data, bool use_data, unsigned char default_value); | 
|  | void ValueWithDefault(int8_t* data, bool use_data, int8_t default_value); | 
|  | void ValueWithDefault(unsigned short* data, bool use_data, unsigned short default_value); | 
|  | void ValueWithDefault(short* data, bool use_data, short default_value); | 
|  | void ValueWithDefault(fidl::StringPtr* data, bool use_data, fidl::StringPtr default_value); | 
|  | void ValueWithDefault(std::string* data, bool use_data, std::string default_value); | 
|  |  | 
|  | // A value of a custom type is mapped using the custom filter. See | 
|  | // the corresponding Field() method for why there are two type | 
|  | // parameters here. | 
|  | template <typename D, typename V> | 
|  | void Value(D* data, XdrFilterType<V> filter) { | 
|  | filter(this, data); | 
|  | } | 
|  |  | 
|  | // Same as Value() but allows a default value to be specified. | 
|  | template <typename D, typename V> | 
|  | void ValueWithDefault(D* data, XdrFilterType<V> filter, bool use_data, D default_value) { | 
|  | switch (op_) { | 
|  | case XdrOp::TO_JSON: { | 
|  | if (use_data) { | 
|  | filter(this, data); | 
|  | break; | 
|  | } | 
|  | filter(this, &default_value); | 
|  | break; | 
|  | } | 
|  |  | 
|  | case XdrOp::FROM_JSON: { | 
|  | if (value_->IsNull()) { | 
|  | *data = std::move(default_value); | 
|  | break; | 
|  | } | 
|  | filter(this, data); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Operator & may be overloaded to return a type that acts like a pointer, but | 
|  | // isn't one, and therefore is not matched by the Value<D,V>(data, filter) | 
|  | // method above. In that case, we need to exercise the operator * of the | 
|  | // pointer type explicitly. | 
|  | // | 
|  | // This is needed for example for std::vector<bool>, where &at(i) is a bit | 
|  | // iterator, not a bool*. | 
|  | template <typename Ptr, typename V> | 
|  | void Value(Ptr data, XdrFilterType<V> filter) { | 
|  | switch (op_) { | 
|  | case XdrOp::TO_JSON: { | 
|  | V value = *data; | 
|  | filter(this, &value); | 
|  | break; | 
|  | } | 
|  |  | 
|  | case XdrOp::FROM_JSON: { | 
|  | V value; | 
|  | filter(this, &value); | 
|  | *data = std::move(value); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | template <typename S> | 
|  | void Value(std::unique_ptr<S>* data, XdrFilterType<S> filter) { | 
|  | switch (op_) { | 
|  | case XdrOp::TO_JSON: | 
|  | if (!data->get()) { | 
|  | value_->SetNull(); | 
|  | } else { | 
|  | value_->SetObject(); | 
|  | filter(this, data->get()); | 
|  | } | 
|  | break; | 
|  |  | 
|  | case XdrOp::FROM_JSON: | 
|  | if (value_->IsNull()) { | 
|  | data->reset(); | 
|  | } else { | 
|  | if (!value_->IsObject()) { | 
|  | AddError("Object type expected."); | 
|  | return; | 
|  | } | 
|  |  | 
|  | *data = std::make_unique<S>(); | 
|  | filter(this, data->get()); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // A fidl vector is mapped to JSON null and JSON Array with a custom | 
|  | // filter for the elements. | 
|  | template <typename D, typename V> | 
|  | void Value(fidl::VectorPtr<D>* const data, const XdrFilterType<V> filter) { | 
|  | switch (op_) { | 
|  | case XdrOp::TO_JSON: | 
|  | if (!data->has_value()) { | 
|  | value_->SetNull(); | 
|  |  | 
|  | } else { | 
|  | value_->SetArray(); | 
|  | value_->Reserve((*data)->size(), allocator()); | 
|  |  | 
|  | for (size_t i = 0; i < (*data)->size(); ++i) { | 
|  | Element(i).Value(&(*data)->at(i), filter); | 
|  | } | 
|  | } | 
|  | break; | 
|  |  | 
|  | case XdrOp::FROM_JSON: | 
|  | if (value_->IsNull()) { | 
|  | data->reset(); | 
|  |  | 
|  | } else { | 
|  | if (!value_->IsArray()) { | 
|  | AddError("Array type expected."); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Set the VectorPtr to contain a value. | 
|  | data->emplace(); | 
|  | // Save on allocations for growing the underlying vector by one. | 
|  | (*data)->resize(value_->Size()); | 
|  |  | 
|  | for (size_t i = 0; i < value_->Size(); ++i) { | 
|  | Element(i).Value(&(*data)->at(i), filter); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // A fidl array with a simple element type can infer its element | 
|  | // value filter from the type parameters of the array. | 
|  | template <typename V> | 
|  | void Value(fidl::VectorPtr<V>* const data) { | 
|  | Value(data, XdrFilter<V>); | 
|  | } | 
|  |  | 
|  | // A fidl array is mapped to JSON null and JSON Array with a custom | 
|  | // filter for the elements. | 
|  | template <typename D, size_t N, typename V> | 
|  | void Value(std::array<D, N>* const data, const XdrFilterType<V> filter) { | 
|  | switch (op_) { | 
|  | case XdrOp::TO_JSON: { | 
|  | value_->SetArray(); | 
|  | value_->Reserve(N, allocator()); | 
|  |  | 
|  | for (size_t i = 0; i < N; ++i) { | 
|  | Element(i).Value(&data->at(i), filter); | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | case XdrOp::FROM_JSON: { | 
|  | if (!value_->IsArray()) { | 
|  | AddError("Array type expected."); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (value_->Size() != N) { | 
|  | AddError(std::string("Array size unexpected: found ") + std::to_string(value_->Size()) + | 
|  | " expected " + std::to_string(N)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | for (size_t i = 0; i < N; ++i) { | 
|  | Element(i).Value(&data->at(i), filter); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // A fidl array with a simple element type can infer its element | 
|  | // value filter from the type parameters of the array. | 
|  | template <typename V, size_t N> | 
|  | void Value(std::array<V, N>* const data) { | 
|  | Value(data, XdrFilter<V>); | 
|  | } | 
|  |  | 
|  | // An STL vector is mapped to JSON Array with a custom filter for the | 
|  | // elements. | 
|  | template <typename D, typename V> | 
|  | void Value(std::vector<D>* const data, const XdrFilterType<V> filter) { | 
|  | switch (op_) { | 
|  | case XdrOp::TO_JSON: | 
|  | value_->SetArray(); | 
|  | value_->Reserve(data->size(), allocator()); | 
|  |  | 
|  | for (size_t i = 0; i < data->size(); ++i) { | 
|  | Element(i).Value(&data->at(i), filter); | 
|  | } | 
|  | break; | 
|  |  | 
|  | case XdrOp::FROM_JSON: | 
|  | if (!value_->IsArray()) { | 
|  | AddError("Array type expected."); | 
|  | return; | 
|  | } | 
|  |  | 
|  | data->resize(value_->Size()); | 
|  |  | 
|  | for (size_t i = 0; i < value_->Size(); ++i) { | 
|  | Element(i).Value(&data->at(i), filter); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Allows for a default value to be specified for STL vectors mapped to JSON | 
|  | // arrays with a custom filter for the elements. This only supports vectors | 
|  | // with simple types. | 
|  | template <typename D, typename V> | 
|  | void ValueWithDefault(std::vector<D>* const data, const XdrFilterType<V> filter, bool use_data, | 
|  | std::vector<D> default_value) { | 
|  | switch (op_) { | 
|  | case XdrOp::TO_JSON: | 
|  | if (use_data) { | 
|  | value_->SetArray(); | 
|  | value_->Reserve(data->size(), allocator()); | 
|  |  | 
|  | for (size_t i = 0; i < data->size(); ++i) { | 
|  | Element(i).Value(&data->at(i), filter); | 
|  | } | 
|  | break; | 
|  | } | 
|  | data->resize(default_value.size()); | 
|  | for (size_t i = 0; i < default_value.size(); ++i) { | 
|  | ElementWithDefault(i).ValueWithDefault(&data->at(i), filter, use_data, | 
|  | std::move(default_value.at(i))); | 
|  | } | 
|  | break; | 
|  |  | 
|  | case XdrOp::FROM_JSON: | 
|  | if (!value_->IsArray()) { | 
|  | if (use_data) { | 
|  | AddError("Array type expected."); | 
|  | return; | 
|  | } | 
|  | data->resize(default_value.size()); | 
|  | for (size_t i = 0; i < default_value.size(); ++i) { | 
|  | ElementWithDefault(i).ValueWithDefault(&data->at(i), filter, use_data, | 
|  | std::move(default_value.at(i))); | 
|  | } | 
|  | return; | 
|  | } | 
|  | data->resize(value_->Size()); | 
|  | for (size_t i = 0; i < value_->Size(); ++i) { | 
|  | Element(i).Value(&data->at(i), filter); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // An STL vector with a simple element type can infer its element value filter | 
|  | // from the type parameters of the array. | 
|  | template <typename V> | 
|  | void Value(std::vector<V>* const data) { | 
|  | Value(data, XdrFilter<V>); | 
|  | } | 
|  |  | 
|  | // Allows for a default value to be specified for STL vector with a simple | 
|  | // element type | 
|  | template <typename V> | 
|  | void ValueWithDefault(std::vector<V>* const data, bool use_data, std::vector<V> default_value) { | 
|  | ValueWithDefault(data, XdrFilter<V>, use_data, default_value); | 
|  | } | 
|  |  | 
|  | // An STL map is mapped to an array of pairs of key and value, because maps | 
|  | // can have non-string keys. There are two filters, for the key type and the | 
|  | // value type. | 
|  | template <typename K, typename V> | 
|  | void Value(std::map<K, V>* const data, XdrFilterType<K> const key_filter, | 
|  | XdrFilterType<V> const value_filter) { | 
|  | switch (op_) { | 
|  | case XdrOp::TO_JSON: { | 
|  | value_->SetArray(); | 
|  | value_->Reserve(data->size(), allocator()); | 
|  |  | 
|  | size_t index = 0; | 
|  | for (auto i = data->begin(); i != data->end(); ++i) { | 
|  | XdrContext&& element = Element(index++); | 
|  | element.value_->SetObject(); | 
|  |  | 
|  | K k{i->first}; | 
|  | element.Field("@k").Value(&k, key_filter); | 
|  |  | 
|  | V v{i->second}; | 
|  | element.Field("@v").Value(&v, value_filter); | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | case XdrOp::FROM_JSON: { | 
|  | if (!value_->IsArray()) { | 
|  | AddError("Array type expected."); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Erase existing data in case there are some left. | 
|  | data->clear(); | 
|  |  | 
|  | size_t index = 0; | 
|  | for (auto i = value_->Begin(); i != value_->End(); ++i) { | 
|  | XdrContext&& element = Element(index++); | 
|  |  | 
|  | K k; | 
|  | element.Field("@k").Value(&k, key_filter); | 
|  |  | 
|  | V v; | 
|  | element.Field("@v").Value(&v, value_filter); | 
|  |  | 
|  | data->emplace(std::move(k), std::move(v)); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // An STL map with only simple values can infer its key value filters from the | 
|  | // type parameters of the map. | 
|  | template <typename K, typename V> | 
|  | void Value(std::map<K, V>* const data) { | 
|  | Value(data, XdrFilter<K>, XdrFilter<V>); | 
|  | } | 
|  |  | 
|  | private: | 
|  | XdrContext(XdrContext* parent, const char* name, XdrOp op, rapidjson::Document* doc, | 
|  | rapidjson::Value* value); | 
|  | rapidjson::Document::AllocatorType& allocator() const { return doc_->GetAllocator(); } | 
|  | XdrContext Field(const char field[]); | 
|  | XdrContext FieldWithDefault(const char field[]); | 
|  | XdrContext Element(size_t i); | 
|  | XdrContext ElementWithDefault(size_t i); | 
|  |  | 
|  | // Error reporting: Recursively requests the error string from the | 
|  | // parent, and on the way back appends a description of the current | 
|  | // JSON context hierarchy. | 
|  | void AddError(const std::string& message); | 
|  | std::string* AddError(); | 
|  |  | 
|  | // Return the root error string so that IgnoreError() can manipulate it. | 
|  | std::string* GetError(); | 
|  |  | 
|  | // The root of the context tree (where parent_ == nullptr) keeps a | 
|  | // string to write errors to. In an error situation the chain of | 
|  | // parent contexts is traversed up in order to (1) access the error | 
|  | // string to write to, (2) record the current context hierarchy in | 
|  | // an error message. Each level in the context hierarchy is | 
|  | // described using the type of value_ and, if present, name_. name_ | 
|  | // is the name of the field for contexts that are values of a field, | 
|  | // otherwise nullptr. | 
|  | XdrContext* const parent_; | 
|  | const char* const name_; | 
|  | std::string* const error_; | 
|  |  | 
|  | // These three fields represent the context itself: The operation to | 
|  | // perform (read or write), the value it will be performed on, and | 
|  | // the document the value is part of, in order to access the | 
|  | // allocator. | 
|  | const XdrOp op_; | 
|  | rapidjson::Document* const doc_; | 
|  | rapidjson::Value* const value_; | 
|  |  | 
|  | // A JSON value to continue processing on when the expected one is | 
|  | // not found in the JSON AST, to avoid value_ becoming null. It | 
|  | // needs to be thread local because it is a global that's modified | 
|  | // potentially by every ongoing XDR invocation. | 
|  | static thread_local rapidjson::Value null_; | 
|  |  | 
|  | // All Xdr* functions take a XdrContext* and pass it on. We might | 
|  | // want to change this once we support asynchronous input/output | 
|  | // operations, for example directly to/from a Ledger page rather | 
|  | // than just the JSON DOM. | 
|  | FXL_DISALLOW_COPY_AND_ASSIGN(XdrContext); | 
|  | }; | 
|  |  | 
|  | // This filter function works for all types for which XdrContext has a Value() | 
|  | // method defined. | 
|  | template <typename V> | 
|  | void XdrFilter(XdrContext* const xdr, V* const value) { | 
|  | xdr->Value(value); | 
|  | } | 
|  |  | 
|  | // Clients use the following functions as entry points. | 
|  |  | 
|  | // A wrapper function to read data from a JSON document. This may fail if the | 
|  | // JSON document doesn't match the structure required by any of the filter | 
|  | // versions. In that case it logs an error and returns false. Clients are | 
|  | // expected to either crash or recover e.g. by ignoring the value. | 
|  | // | 
|  | // The items in the filter versions list are tried in turn until one succeeds. | 
|  | // The filter versions list must end with a nullptr entry to mark the end. | 
|  | template <typename D, typename V> | 
|  | bool XdrRead(rapidjson::Document* const doc, D* const data, XdrFilterList<V> filter_versions) { | 
|  | std::vector<std::string> errors; | 
|  | for (XdrFilterList<V> filter = filter_versions; *filter; ++filter) { | 
|  | std::string error; | 
|  | XdrContext xdr(XdrOp::FROM_JSON, doc, &error); | 
|  | xdr.Value(data, *filter); | 
|  |  | 
|  | if (error.empty()) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | FX_LOGS(INFO) << "Filter failed, trying previous version."; | 
|  | errors.emplace_back(std::move(error)); | 
|  | } | 
|  |  | 
|  | FX_LOGS(ERROR) << "XdrRead: No filter version succeeded" | 
|  | << " to extract data from JSON: " << json_parser::JsonValueToPrettyString(*doc) | 
|  | << std::endl; | 
|  | for (const std::string& error : errors) { | 
|  | FX_LOGS(INFO) << "XdrRead error message: " << error; | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // A wrapper function to read data from a JSON string. This may fail if the JSON | 
|  | // doesn't parse or doesn't match the structure required by the filter version | 
|  | // list. In that case it logs an error and returns false. Clients are expected | 
|  | // to either crash or recover e.g. by ignoring the value. | 
|  | template <typename D, typename V> | 
|  | bool XdrRead(const std::string& json, D* const data, XdrFilterList<V> filter_versions) { | 
|  | rapidjson::Document doc; | 
|  | doc.Parse(json); | 
|  | if (doc.HasParseError()) { | 
|  | FX_LOGS(ERROR) << "Unable to parse data as JSON: " << json; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return XdrRead(&doc, data, filter_versions); | 
|  | } | 
|  |  | 
|  | // A wrapper function to write data as JSON doc. This never fails. It always | 
|  | // only uses the first version of the filter. It takes a filter version list | 
|  | // anyway for symmetry with XdrRead(), so that the same filter version list | 
|  | // constant can be passed to both XdrRead and XdrWrite. | 
|  | template <typename D, typename V> | 
|  | void XdrWrite(rapidjson::Document* const doc, D* const data, XdrFilterList<V> filter_versions) { | 
|  | std::string error; | 
|  | XdrContext xdr(XdrOp::TO_JSON, doc, &error); | 
|  | xdr.Value(data, filter_versions[0]); | 
|  | FX_DCHECK(error.empty()) << "There are no errors possible in XdrOp::TO_JSON: " << std::endl | 
|  | << error << std::endl | 
|  | << json_parser::JsonValueToPrettyString(*doc) << std::endl; | 
|  | } | 
|  |  | 
|  | // A wrapper function to write data as JSON to a string. This never fails. | 
|  | template <typename D, typename V> | 
|  | void XdrWrite(std::string* const json, D* const data, XdrFilterList<V> filter_versions) { | 
|  | rapidjson::Document doc; | 
|  | doc.SetObject();  // Allows empty objects (produces "{}"), such as an uninitialized FIDL table | 
|  | XdrWrite(&doc, data, filter_versions); | 
|  | *json = json_parser::JsonValueToString(doc); | 
|  | } | 
|  |  | 
|  | // A wrapper function to return data as a JSON string. This never fails. | 
|  | template <typename D, typename V> | 
|  | std::string XdrWrite(D* const data, XdrFilterList<V> filter_versions) { | 
|  | std::string json; | 
|  | XdrWrite(&json, data, filter_versions); | 
|  | return json; | 
|  | } | 
|  |  | 
|  | }  // namespace modular | 
|  |  | 
|  | #endif  // SRC_MODULAR_LIB_FIDL_JSON_XDR_H_ |