blob: 2560fa55c1135d564c4bfe349ee58d1166d29f65 [file] [log] [blame]
// 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;
}
fit::result<std::pair<Moniker, ComponentIdIndex::InstanceId>, 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.
if (!appmgr_moniker.HasMember("realm_path") || !appmgr_moniker["realm_path"].IsArray() ||
appmgr_moniker["realm_path"].GetArray().Size() < 1) {
FX_LOGS(ERROR) << "appmgr_moniker.realm_path is a required, non-empty list.";
return fit::error(ComponentIdIndex::Error::INVALID_MONIKER);
}
// `realm_path` elements must be strings.
const auto& realm_path_json = appmgr_moniker["realm_path"].GetArray();
std::vector<std::string> realm_path;
for (const auto& realm_name : realm_path_json) {
if (!realm_name.IsString()) {
FX_LOGS(ERROR) << "appmgr_moniker.realm_path must be a list of strings.";
return fit::error(ComponentIdIndex::Error::INVALID_MONIKER);
}
realm_path.push_back(realm_name.GetString());
}
return fit::ok(std::pair{
Moniker{.url = appmgr_moniker["url"].GetString(), .realm_path = std::move(realm_path)},
ComponentIdIndex::InstanceId(entry["instance_id"].GetString())});
}
} // namespace
ComponentIdIndex::ComponentIdIndex(ComponentIdIndex::MonikerToInstanceId moniker_to_id)
: moniker_to_id_(std::move(moniker_to_id)) {}
fit::result<ComponentIdIndex::MonikerToInstanceId, ComponentIdIndex::Error> Parse(
const rapidjson::Document& doc) {
if (!doc.IsObject()) {
FX_LOGS(ERROR) << "Index must be a valid object.";
return fit::error(ComponentIdIndex::Error::INVALID_SCHEMA);
}
// `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().second);
if (!id_result.second) {
FX_LOGS(ERROR) << "The set of instance IDs must be unique.";
// Instance ID already exists.
return fit::error(ComponentIdIndex::Error::DUPLICATE_INSTANCE_ID);
}
auto result = moniker_to_id.insert(parsed_entry.take_value());
if (!result.second) {
FX_LOGS(ERROR) << "The set of appmgr_monikers must be unique.";
// Moniker already exists in the map.
return fit::error(ComponentIdIndex::Error::DUPLICATE_MONIKER);
}
}
return fit::ok(moniker_to_id);
}
// static
fit::result<fbl::RefPtr<ComponentIdIndex>, ComponentIdIndex::Error>
ComponentIdIndex::CreateFromAppmgrConfigDir(const fxl::UniqueFD& appmgr_config_dir) {
if (!files::IsFileAt(appmgr_config_dir.get(), kIndexFilePath)) {
return fit::ok(fbl::AdoptRef(new ComponentIdIndex({})));
}
std::string file_contents;
if (!files::ReadFileToStringAt(appmgr_config_dir.get(), kIndexFilePath, &file_contents)) {
FX_LOGS(ERROR) << "Could not read 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 instance ID index file.";
return fit::error(Error::INVALID_JSON);
}
auto parse_retval = Parse(doc);
if (parse_retval.is_error()) {
return fit::error(parse_retval.take_error());
}
return fit::ok(fbl::AdoptRef(new ComponentIdIndex(parse_retval.take_value())));
}
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