blob: c7c387a6cf7e7eba35fc3f18b6266b0edf035782 [file] [log] [blame] [edit]
// Copyright 2020 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.
#include "src/sys/appmgr/component_id_index.h"
#include <lib/syslog/cpp/macros.h>
#include <zircon/assert.h>
#include <unordered_set>
#include <rapidjson/document.h>
#include "src/lib/files/file.h"
namespace component {
namespace {
const char kIndexFilePath[] = "component_id_index";
bool IsValidInstanceId(const std::string& instance_id) {
// * 256-bits encoded in base16 = 64 characters
// - 1 char to represent 4 bits.
if (instance_id.length() != 64) {
return false;
}
for (size_t i = 0; i < 64; i++) {
const auto& c = instance_id[i];
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))) {
return false;
}
}
return true;
}
struct ComponentIdEntry {
ComponentIdIndex::InstanceId id;
std::vector<Moniker> monikers;
};
// Parses |json| to append realm path entries to |realm_path_out|.
// Returns false if |json| does not contain a valid realm path.
bool ParseRealmPath(const rapidjson::Value& json, std::vector<std::string>* realm_path_out) {
realm_path_out->clear();
if (!json.IsArray() || json.GetArray().Size() < 1)
return false;
const auto& json_array = json.GetArray();
for (const auto& realm_name : json_array) {
if (!realm_name.IsString())
return false;
realm_path_out->push_back(realm_name.GetString());
}
return true;
}
fit::result<ComponentIdEntry, ComponentIdIndex::Error> ParseEntry(const rapidjson::Value& entry) {
// Entry must be an object.
if (!entry.IsObject()) {
FX_LOGS(ERROR) << "Entry must be an object.";
return fit::error(ComponentIdIndex::Error::INVALID_SCHEMA);
}
// `instance_id` is a required string.
if (!entry.HasMember("instance_id") || !entry["instance_id"].IsString()) {
FX_LOGS(ERROR) << "instance_id is a required string.";
return fit::error(ComponentIdIndex::Error::INVALID_SCHEMA);
}
// `instance_id` must be a valid format.
if (!IsValidInstanceId(entry["instance_id"].GetString())) {
FX_LOGS(ERROR) << "instance_id must be valid format.";
return fit::error(ComponentIdIndex::Error::INVALID_INSTANCE_ID);
}
// `appmgr_moniker` is a required object.
if (!entry.HasMember("appmgr_moniker") || !entry["appmgr_moniker"].IsObject()) {
FX_LOGS(ERROR) << "appmgr_moniker must be valid object.";
return fit::error(ComponentIdIndex::Error::INVALID_MONIKER);
}
const auto& appmgr_moniker = entry["appmgr_moniker"];
// `url` is a required string.
if (!appmgr_moniker.HasMember("url") || !appmgr_moniker["url"].IsString()) {
FX_LOGS(ERROR) << "appmgr_moniker.url is a required string.";
return fit::error(ComponentIdIndex::Error::INVALID_MONIKER);
}
// `realm_path` is a required vector of size >= 1.
std::vector<std::string> realm_path;
if (!appmgr_moniker.HasMember("realm_path") ||
!ParseRealmPath(appmgr_moniker["realm_path"], &realm_path)) {
FX_LOGS(ERROR) << "appmgr_moniker.realm_path is a required, non-empty list.";
return fit::error(ComponentIdIndex::Error::INVALID_MONIKER);
}
const std::string& component_url = appmgr_moniker["url"].GetString();
ComponentIdEntry component_id_entry{
.id = ComponentIdIndex::InstanceId(entry["instance_id"].GetString()),
.monikers = {Moniker{.url = component_url, .realm_path = std::move(realm_path)}}};
// 'transitional_realm_paths' is an optional vector of realm paths.
// Note that optional fields are serialized as |null| by some tooling.
constexpr char kTransitionalRealmPaths[] = "transitional_realm_paths";
if (appmgr_moniker.HasMember(kTransitionalRealmPaths) &&
!appmgr_moniker[kTransitionalRealmPaths].IsNull()) {
const auto& transitional_paths = appmgr_moniker[kTransitionalRealmPaths];
if (!transitional_paths.IsArray() || transitional_paths.GetArray().Size() < 1) {
FX_LOGS(ERROR) << "appmgr_moniker.transitional_realm_paths is an optional, non-empty list.";
return fit::error(ComponentIdIndex::Error::INVALID_MONIKER);
}
for (auto& json : transitional_paths.GetArray()) {
if (!ParseRealmPath(json, &realm_path)) {
FX_LOGS(ERROR)
<< "appmgr_moniker.transitional_realm_paths entries must be non-empty string lists.";
return fit::error(ComponentIdIndex::Error::INVALID_MONIKER);
}
component_id_entry.monikers.emplace_back(
Moniker{.url = component_url, .realm_path = std::move(realm_path)});
}
}
return fit::ok(std::move(component_id_entry));
}
} // namespace
ComponentIdIndex::ComponentIdIndex(ComponentIdIndex::MonikerToInstanceId moniker_to_id,
bool restrict_isolated_persistent_storage)
: moniker_to_id_(std::move(moniker_to_id)),
restrict_isolated_persistent_storage_(restrict_isolated_persistent_storage) {}
// static
fit::result<fbl::RefPtr<ComponentIdIndex>, ComponentIdIndex::Error>
ComponentIdIndex::CreateFromAppmgrConfigDir(const fbl::unique_fd& appmgr_config_dir) {
if (!files::IsFileAt(appmgr_config_dir.get(), kIndexFilePath)) {
FX_LOGS(WARNING) << "Could not find component instance ID index file = " << kIndexFilePath;
return fit::ok(fbl::AdoptRef(new ComponentIdIndex({}, false)));
}
std::string file_contents;
if (!files::ReadFileToStringAt(appmgr_config_dir.get(), kIndexFilePath, &file_contents)) {
FX_LOGS(ERROR) << "Could not read component instance ID index file.";
return fit::error(Error::INVALID_JSON);
}
return CreateFromIndexContents(file_contents);
}
// static
fit::result<fbl::RefPtr<ComponentIdIndex>, ComponentIdIndex::Error>
ComponentIdIndex::CreateFromIndexContents(const std::string& index_contents) {
rapidjson::Document doc;
doc.Parse(index_contents.c_str());
if (doc.HasParseError()) {
FX_LOGS(ERROR) << "Could not json-parse component instance ID index file.";
return fit::error(Error::INVALID_JSON);
}
if (!doc.IsObject()) {
FX_LOGS(ERROR) << "Index must be a valid object.";
return fit::error(ComponentIdIndex::Error::INVALID_SCHEMA);
}
constexpr char kRestrictIsolatedPersistentStorage[] =
"appmgr_restrict_isolated_persistent_storage";
// `appmgr_restrict_isolated_persistent_storage` is an optional bool.
// By default, it is `true`.
bool restrict_isolated_persistent_storage = true;
if (doc.HasMember(kRestrictIsolatedPersistentStorage) &&
// We check that the field is not null, because the compile-time `component_id_index` tool
// outputs a 'null' valued field when it serializes JSON when it is absent.
!doc[kRestrictIsolatedPersistentStorage].IsNull()) {
if (!doc[kRestrictIsolatedPersistentStorage].IsBool()) {
FX_LOGS(ERROR) << "appmgr_restrict_isolated_persistent_storage must be bool";
return fit::error(ComponentIdIndex::Error::INVALID_SCHEMA);
}
restrict_isolated_persistent_storage = doc[kRestrictIsolatedPersistentStorage].GetBool();
}
// `instances` must be an array.
if (!doc.HasMember("instances") || !doc["instances"].IsArray()) {
FX_LOGS(ERROR) << "instances is a required list.";
return fit::error(ComponentIdIndex::Error::INVALID_SCHEMA);
}
const auto& instances = doc["instances"].GetArray();
ComponentIdIndex::MonikerToInstanceId moniker_to_id;
std::unordered_set<ComponentIdIndex::InstanceId> instance_id_set;
for (const auto& entry : instances) {
auto parsed_entry = ParseEntry(entry);
if (parsed_entry.is_error()) {
return fit::error(parsed_entry.error());
}
auto id_result = instance_id_set.insert(parsed_entry.value().id);
if (!id_result.second) {
FX_LOGS(ERROR) << "The set of instance IDs must be unique.";
return fit::error(ComponentIdIndex::Error::DUPLICATE_INSTANCE_ID);
}
for (Moniker& moniker : parsed_entry.value().monikers) {
auto result =
moniker_to_id.insert(std::make_pair(std::move(moniker), parsed_entry.value().id));
if (!result.second) {
FX_LOGS(ERROR) << "The set of appmgr_monikers must be unique.";
return fit::error(ComponentIdIndex::Error::DUPLICATE_MONIKER);
}
}
}
FX_LOGS(INFO) << "Found " << instances.Size() << " entries in component instance ID index.";
return fit::ok(
fbl::AdoptRef(new ComponentIdIndex(moniker_to_id, restrict_isolated_persistent_storage)));
}
std::optional<ComponentIdIndex::InstanceId> ComponentIdIndex::LookupMoniker(
const Moniker& moniker) const {
const auto& it = moniker_to_id_.find(moniker);
if (it != moniker_to_id_.end()) {
return it->second;
}
return std::nullopt;
}
} // namespace component