blob: 2387e7d08aca36cf5a0712d460f0403445c3e4a2 [file] [log] [blame]
// Copyright 2019 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_LIB_FIDL_CODEC_LIBRARY_LOADER_H_
#define SRC_LIB_FIDL_CODEC_LIBRARY_LOADER_H_
#include <iostream>
#include <map>
#include <optional>
#include <vector>
#include <rapidjson/document.h>
#include "src/lib/fidl_codec/semantic.h"
// This file contains a programmatic representation of a FIDL schema. A
// LibraryLoader loads a set of Libraries. The libraries contain structs,
// enums, protocols, and so on. Each element has the logic necessary to take
// wire-encoded bits of that type, and transform it to a representation of that
// type.
// A LibraryLoader object can be used to fetch a particular library or protocol
// method, which can then be used for debug purposes.
// An example of building a LibraryLoader can be found in
// library_loader_test.cc:LoadSimple. Callers can then do something like the
// following, if they have a fidl::Message:
//
// fidl_message_header_t header = message.header();
// const std::vector<const ProtocolMethod*>* methods = loader_->GetByOrdinal(header.ordinal);
// rapidjson::Document actual;
// fidl_codec::RequestToJSON(methods->at(0), message, actual);
//
// |actual| will then contain the contents of the message in JSON
// (human-readable) format.
//
// These libraries are currently thread-unsafe.
namespace fidl_codec {
constexpr int kDecimalBase = 10;
using Ordinal32 = uint32_t;
using Ordinal64 = uint64_t;
enum class WireVersion { kWireV2 };
struct LibraryReadError {
enum ErrorValue {
kOk,
kIoError,
kParseError,
};
ErrorValue value;
rapidjson::ParseResult parse_result;
};
class Protocol;
class ProtocolMethod;
class Payload;
class Library;
class LibraryLoader;
class MessageDecoder;
class Struct;
class StructValue;
class Table;
class TypeVisitor;
class Union;
class EnumOrBitsMember {
friend class Enum;
friend class Bits;
public:
EnumOrBitsMember(const std::string_view& name, uint64_t absolute_value, bool negative)
: name_(name), absolute_value_(absolute_value), negative_(negative) {}
const std::string& name() const { return name_; }
uint64_t absolute_value() const { return absolute_value_; }
bool negative() const { return negative_; }
private:
const std::string name_;
const uint64_t absolute_value_;
const bool negative_;
};
class EnumOrBits {
public:
friend class Library;
~EnumOrBits();
const std::string& name() const { return name_; }
uint64_t size_v2() const { return size_v2_; }
const Type* type() const { return type_.get(); }
// Get a list of Enum members.
const std::vector<EnumOrBitsMember>& members() const { return members_; }
uint64_t Size(WireVersion version) const { return size_v2_; }
protected:
explicit EnumOrBits(const rapidjson::Value* json_definition);
// Decode all the values from the JSON definition.
void DecodeTypes(bool is_scalar, const std::string& supertype_name, Library* enclosing_library);
private:
const rapidjson::Value* json_definition_;
std::string name_;
uint64_t size_v2_;
std::unique_ptr<Type> type_;
std::vector<EnumOrBitsMember> members_;
};
class Enum : public EnumOrBits {
public:
friend class Library;
~Enum();
// Gets the name of the enum member corresponding to the value pointed to by
// |bytes| of length |length|. For example, if we had the following
// definition:
// enum i16_enum : int16 {
// x = -23;
// };
// and you pass |bytes| a 2-byte representation of -23, and |length| 2, this
// function will return "x". Returns "(Unknown enum member)" if it can't find
// the member.
std::string GetName(uint64_t absolute_value, bool negative) const;
private:
explicit Enum(const rapidjson::Value* json_definition) : EnumOrBits(json_definition) {}
void DecodeTypes(Library* enclosing_library) {
return EnumOrBits::DecodeTypes(true, "enum", enclosing_library);
}
};
class Bits : public EnumOrBits {
public:
friend class Library;
~Bits();
std::string GetName(uint64_t absolute_value, bool negative) const;
private:
explicit Bits(const rapidjson::Value* json_definition) : EnumOrBits(json_definition) {}
void DecodeTypes(Library* enclosing_library) {
return EnumOrBits::DecodeTypes(false, "bits", enclosing_library);
}
};
// An abstract representation of a method parameter. For structs, this enumerates every argument
// (ie, the "flattened" representation), while for tables and unions this is just a reference to the
// underlying payload's type (ie, the "unflattened" representation). In the unflattened case, the
// name of the returned Parameter will always be "payload."
class Parameter {
public:
Parameter(std::string name, Type* type);
~Parameter();
const std::string& name() const { return name_; }
Type* type() const { return type_; }
private:
const std::string name_;
Type* type_;
};
// A base class for "payloadable" type definitions (structs, tables, and unions). Data and
// capabilities that will be common to all payloadable types are stored on the base class, while
// type specific information is held by the derived classes.
class Payloadable {
public:
friend class Library;
friend class Payload;
Payloadable() = default;
Payloadable(Library* enclosing_library, const rapidjson::Value* json_definition,
std::string name);
virtual ~Payloadable();
Library* enclosing_library() const { return enclosing_library_; }
const std::string& name() const { return name_; }
// Decodes the |Payloadable|-derived class held by this instance. Note that decoding always starts
// with an offset of |kTransactionHeaderSize|, since a |Payloadable| always represents the
// entirety of the message body, meaning the head has already been skipped over by the decoder.
// The |payload_type| argument is passed in by the owning |Payload| wrapper class.
virtual std::unique_ptr<PayloadableValue> DecodeAsPayload(
const std::unique_ptr<Type>& payload_type, MessageDecoder& decoder) const = 0;
virtual std::string ToString(bool expand) const = 0;
protected:
// Decode all the values from the JSON definition.
virtual void DecodeTypes() = 0;
virtual std::unique_ptr<Parameter> FindParameter(std::string_view,
const std::unique_ptr<Type>&) = 0;
Library* enclosing_library_;
const rapidjson::Value* json_definition_;
std::string name_;
};
// A wrapper class to hold a |Payloadable|. It stores both the |Payloadable| and its associated
// type, so that at the MessageDecoder can decode the incoming message's body using a standard
// |DecodeValue()| call on that body, regardless of its actual type. The |type_| is resolved after
// the owning |Library| has been loaded in the first |DecodeTypes()| call on this instance.
class Payload {
public:
friend class Library;
friend class ProtocolMethod;
Payload(Library* enclosing_library, const ProtocolMethod* method,
const rapidjson::Value* json_type_definition, Payloadable* payloadable);
~Payload();
Library* enclosing_library() const { return enclosing_library_; }
const ProtocolMethod& enclosing_method() const { return *enclosing_method_; }
const std::unique_ptr<Type>& type() const { return type_; }
Struct* AsStruct();
const Struct* AsStruct() const;
Table* AsTable();
const Table* AsTable() const;
Union* AsUnion();
const Union* AsUnion() const;
// Decodes the |Payloadable|-derived class held by this instance. Note that decoding always starts
// with an offset of |kTransactionHeaderSize|, since a |Payload| always represents the entirety of
// the message body, meaning the head has already been skipped over by the decoder.
std::unique_ptr<PayloadableValue> Decode(MessageDecoder& decoder) const;
std::unique_ptr<Parameter> FindParameter(std::string_view name);
std::string ToString(bool expand = false) const;
private:
void DecodeTypes();
Library* enclosing_library_;
const ProtocolMethod* enclosing_method_;
const rapidjson::Value* type_definition_;
Payloadable* payloadable_;
std::unique_ptr<Type> type_;
};
class UnionMember {
public:
UnionMember(const Union& union_definition, Library* enclosing_library,
const rapidjson::Value* json_definition);
~UnionMember();
const Union& union_definition() const { return union_definition_; }
bool reserved() const { return reserved_; }
const std::string& name() const { return name_; }
Ordinal64 ordinal() const { return ordinal_; }
const Type* type() const { return type_.get(); }
private:
const Union& union_definition_;
const bool reserved_;
const std::string name_;
const Ordinal64 ordinal_;
std::unique_ptr<Type> type_;
};
class Union final : public Payloadable {
public:
friend class Library;
~Union() override;
const std::vector<std::unique_ptr<UnionMember>>& members() const { return members_; }
std::unique_ptr<PayloadableValue> DecodeAsPayload(const std::unique_ptr<Type>& payload_type,
MessageDecoder& decoder) const override;
const UnionMember* MemberFromOrdinal(Ordinal64 ordinal) const;
UnionMember* SearchMember(std::string_view name) const;
std::string ToString(bool expand) const override;
private:
Union(Library* enclosing_library, const rapidjson::Value* json_definition);
// Decode all the values from the JSON definition.
void DecodeTypes() override;
std::unique_ptr<Parameter> FindParameter(std::string_view, const std::unique_ptr<Type>&) override;
std::vector<std::unique_ptr<UnionMember>> members_;
};
class StructMember {
public:
StructMember(Library* enclosing_library, const rapidjson::Value* json_definition);
StructMember(std::string_view name, std::unique_ptr<Type> type);
StructMember(std::string_view name, std::unique_ptr<Type> type, uint8_t id);
~StructMember();
const std::string& name() const { return name_; }
Type* type() const { return type_.get(); }
void reset_type();
uint8_t id() const { return id_; }
uint64_t Offset(WireVersion version) const { return offset_v2_; }
private:
const std::string name_;
uint64_t offset_v1_ = 0;
uint64_t offset_v2_ = 0;
std::unique_ptr<Type> type_;
uint8_t id_ = 0;
};
class Struct final : public Payloadable {
public:
friend class Library;
static const Struct Empty;
Struct() = default;
explicit Struct(std::string_view name);
~Struct() override;
const std::vector<std::unique_ptr<StructMember>>& members() const { return members_; }
void AddMember(std::string_view name, std::unique_ptr<Type> type, uint32_t id = 0);
std::unique_ptr<PayloadableValue> DecodeAsPayload(const std::unique_ptr<Type>& payload_type,
MessageDecoder& decoder) const override;
StructMember* SearchMember(std::string_view name, uint32_t id = 0) const;
uint32_t Size(WireVersion version) const;
std::string ToString(bool expand) const override;
// Wrap this Struct in a non-nullable type and use the given visitor on it.
void VisitAsType(TypeVisitor* visitor) const;
private:
Struct(Library* enclosing_library, const rapidjson::Value* json_definition);
// Decode all the values from the JSON definition.
void DecodeTypes() override;
std::unique_ptr<Parameter> FindParameter(std::string_view, const std::unique_ptr<Type>&) override;
uint32_t size_v1_ = 0;
uint32_t size_v2_ = 0;
std::vector<std::unique_ptr<StructMember>> members_;
};
class TableMember {
public:
TableMember(Library* enclosing_library, const rapidjson::Value* json_definition);
~TableMember();
bool reserved() const { return reserved_; }
const std::string& name() const { return name_; }
Ordinal32 ordinal() const { return ordinal_; }
const Type* type() const { return type_.get(); }
private:
const bool reserved_;
const std::string name_;
const Ordinal32 ordinal_;
std::unique_ptr<Type> type_;
};
class Table final : public Payloadable {
public:
friend class Library;
~Table() override;
Library* enclosing_library() const { return enclosing_library_; }
const std::string& name() const { return name_; }
const std::vector<std::unique_ptr<TableMember>>& members() const { return members_; }
std::unique_ptr<PayloadableValue> DecodeAsPayload(const std::unique_ptr<Type>& payload_type,
MessageDecoder& decoder) const override;
const TableMember* MemberFromOrdinal(Ordinal64 ordinal) const;
const TableMember* SearchMember(std::string_view name) const;
std::string ToString(bool expand) const override;
private:
Table(Library* enclosing_library, const rapidjson::Value* json_definition);
// Decode all the values from the JSON definition.
void DecodeTypes() override;
std::unique_ptr<Parameter> FindParameter(std::string_view, const std::unique_ptr<Type>&) override;
std::vector<std::unique_ptr<TableMember>> members_;
};
class ProtocolMethod {
public:
friend class Protocol;
ProtocolMethod() = default;
~ProtocolMethod();
const Protocol& enclosing_protocol() const { return *enclosing_protocol_; }
const std::string& name() const { return name_; }
Ordinal64 ordinal() const { return ordinal_; }
bool is_composed() const { return is_composed_; }
bool has_request() const { return has_request_; }
Payload* request() const {
if (request_ != nullptr) {
request_->DecodeTypes();
}
return request_.get();
}
bool has_response() const { return has_response_; }
Payload* response() const {
if (response_ != nullptr) {
response_->DecodeTypes();
}
return response_.get();
}
semantic::MethodSemantic* semantic() { return semantic_.get(); }
const semantic::MethodSemantic* semantic() const { return semantic_.get(); }
void set_semantic(std::unique_ptr<semantic::MethodSemantic> semantic) {
semantic_ = std::move(semantic);
}
semantic::MethodDisplay* short_display() { return short_display_.get(); }
const semantic::MethodDisplay* short_display() const { return short_display_.get(); }
void set_short_display(std::unique_ptr<semantic::MethodDisplay> short_display) {
short_display_ = std::move(short_display);
}
std::string fully_qualified_name() const;
void DecodeTypes() {
if (request_ != nullptr) {
request_->DecodeTypes();
}
if (response_ != nullptr) {
response_->DecodeTypes();
}
}
std::unique_ptr<Parameter> FindParameter(std::string_view name) const;
ProtocolMethod(const ProtocolMethod& other) = delete;
ProtocolMethod& operator=(const ProtocolMethod&) = delete;
private:
ProtocolMethod(Library* enclosing_library, const Protocol& protocol,
const rapidjson::Value* json_definition);
Library* enclosing_library_;
const Protocol* const enclosing_protocol_ = nullptr;
const std::string name_;
const Ordinal64 ordinal_ = 0;
const bool is_composed_ = false;
bool has_request_ = false;
std::unique_ptr<Payload> request_ = nullptr;
bool has_response_ = false;
std::unique_ptr<Payload> response_ = nullptr;
std::unique_ptr<semantic::MethodSemantic> semantic_;
std::unique_ptr<semantic::MethodDisplay> short_display_;
};
class Protocol {
public:
friend class Library;
Protocol(const Protocol& other) = delete;
Protocol& operator=(const Protocol&) = delete;
Library* enclosing_library() const { return enclosing_library_; }
const std::string& name() const { return name_; }
void AddMethodsToIndex(LibraryLoader* library_loader);
// Sets *|method| to the fully qualified |name|'s ProtocolMethod (protocol.method).
bool GetMethodByFullName(const std::string& name, const ProtocolMethod** method) const;
ProtocolMethod* GetMethodByName(std::string_view name) const;
const std::vector<std::unique_ptr<ProtocolMethod>>& methods() const { return protocol_methods_; }
private:
Protocol(Library* enclosing_library, const rapidjson::Value& json_definition)
: enclosing_library_(enclosing_library), name_(json_definition["name"].GetString()) {
for (auto& method : json_definition["methods"].GetArray()) {
protocol_methods_.emplace_back(new ProtocolMethod(enclosing_library, *this, &method));
}
}
Library* enclosing_library_;
std::string name_;
std::vector<std::unique_ptr<ProtocolMethod>> protocol_methods_;
};
class Library {
public:
friend class LibraryLoader;
LibraryLoader* enclosing_loader() const { return enclosing_loader_; }
const std::string& name() const { return name_; }
const std::vector<std::unique_ptr<Protocol>>& protocols() const { return protocols_; }
// Decode all the values from the JSON definition.
void DecodeTypes();
// Decode all the content of this FIDL file.
bool DecodeAll();
std::unique_ptr<Type> TypeFromIdentifier(bool is_nullable, const std::string& identifier);
// The size of the type with name |identifier| when it is inline (e.g.,
// embedded in an array)
size_t InlineSizeFromIdentifier(std::string& identifier) const;
// Set *ptr to the Protocol called |name|
bool GetProtocolByName(std::string_view name, Protocol** ptr) const;
// Extract a boolean field from a JSON value.
bool ExtractBool(const rapidjson::Value* json_definition, std::string_view container_type,
std::string_view container_name, const char* field_name);
// Extract a string field from a JSON value.
std::string ExtractString(const rapidjson::Value* json_definition,
std::string_view container_type, std::string_view container_name,
const char* field_name);
// Extract a uint64_t field from a JSON value.
uint64_t ExtractUint64(const rapidjson::Value* json_definition, std::string_view container_type,
std::string_view container_name, const char* field_name);
// Extract a uint32_t field from a JSON value.
uint32_t ExtractUint32(const rapidjson::Value* json_definition, std::string_view container_type,
std::string_view container_name, const char* field_name);
// Extract a scalar type from a JSON value.
std::unique_ptr<Type> ExtractScalarType(const rapidjson::Value* json_definition,
std::string_view container_type,
std::string_view container_name, const char* field_name);
// Extract a type from a JSON value.
std::unique_ptr<Type> ExtractType(const rapidjson::Value* json_definition,
std::string_view container_type,
std::string_view container_name, const char* field_name);
// Extract field offset.
uint64_t ExtractFieldOffset(const rapidjson::Value* json_definition,
std::string_view container_type, std::string_view container_name,
const char* field_name);
// Display an error when a field is not found.
void FieldNotFound(std::string_view container_type, std::string_view container_name,
const char* field_name);
Payloadable* GetPayloadable(const std::string& payload_name) const {
auto result = payloadables_.find(payload_name);
if (result == payloadables_.end()) {
return nullptr;
}
return result->second.get();
}
const Table* GetTable(const std::string& table_name) const {
auto result = tables_.find(table_name);
if (result == tables_.end()) {
return nullptr;
}
return result->second.get();
}
Library& operator=(const Library&) = delete;
Library(const Library&) = delete;
~Library();
private:
Library(LibraryLoader* enclosing_loader, rapidjson::Document& json_definition);
LibraryLoader* enclosing_loader_;
rapidjson::Document json_definition_;
bool decoded_ = false;
bool has_errors_ = false;
std::string name_;
std::vector<std::unique_ptr<Protocol>> protocols_;
std::map<std::string, std::unique_ptr<Payloadable>> payloadables_;
std::map<std::string, std::unique_ptr<Enum>> enums_;
std::map<std::string, std::unique_ptr<Bits>> bits_;
std::map<std::string, std::unique_ptr<Union>> unions_;
std::map<std::string, std::unique_ptr<Struct>> structs_;
std::map<std::string, std::unique_ptr<Table>> tables_;
};
// An indexed collection of libraries.
// WARNING: All references on Enum, Struct, Table, ... and all references on
// types and fields must be destroyed before this class (LibraryLoader
// should be one of the last objects we destroy).
class LibraryLoader {
public:
friend class Library;
// Creates a LibraryLoader populated by the given library paths.
LibraryLoader(const std::vector<std::string>& library_paths, LibraryReadError* err);
// Creates a LibraryLoader with no libraries
LibraryLoader() = default;
LibraryLoader& operator=(const LibraryLoader&) = delete;
LibraryLoader(const LibraryLoader&) = delete;
// Add the libraries for all the paths.
bool AddAll(const std::vector<std::string>& library_paths, LibraryReadError* err);
// Decode all the FIDL files.
bool DecodeAll();
// Adds a single library to this Loader given its path. Sets err as appropriate.
void AddPath(const std::string& path, LibraryReadError* err);
// Adds a single library to this Loader given its content (the JSON text).
// Sets err as appropriate.
void AddContent(const std::string& content, LibraryReadError* err);
// Adds a method ordinal to the ordinal map.
void AddMethod(const ProtocolMethod* method);
void ParseBuiltinSemantic();
// Returns a pointer to a set of methods that have this ordinal. There may be
// more than one if the method was composed into multiple protocols. For
// convenience, the methods that are not composed are at the front of the
// vector. Returns |nullptr| if there is no such method. The returned
// pointer continues to be owned by the LibraryLoader, and should not be
// deleted.
const std::vector<const ProtocolMethod*>* GetByOrdinal(Ordinal64 ordinal) {
auto m = ordinal_map_.find(ordinal);
if (m != ordinal_map_.end()) {
return m->second.get();
}
return nullptr;
}
// If the library with name |name| is present in this loader, returns the
// library. Otherwise, returns null.
// |name| is of the format "a.b.c"
Library* GetLibraryFromName(const std::string& name) {
auto l = representations_.find(name);
if (l != representations_.end()) {
Library* library = l->second.get();
library->DecodeTypes();
return library;
}
return nullptr;
}
private:
void Delete(const Library* library) {
// The only way to delete a library is to remove it from representations_, so we don't need to
// do that explicitly. However...
for (const auto& iface : library->protocols()) {
for (const auto& method : iface->methods()) {
ordinal_map_.erase(method->ordinal());
}
}
}
// Because Delete() above is run whenever a Library is destructed, we want ordinal_map_ to be
// intact when a Library is destructed. Therefore, ordinal_map_ has to come first.
std::map<Ordinal64, std::unique_ptr<std::vector<const ProtocolMethod*>>> ordinal_map_;
std::map<std::string, std::unique_ptr<Library>> representations_;
};
} // namespace fidl_codec
#endif // SRC_LIB_FIDL_CODEC_LIBRARY_LOADER_H_