blob: 2c73cc8df0eba4bf71a7d6702e2079705041cb73 [file] [log] [blame]
// Copyright 2023 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/developer/debug/debug_agent/test_realm.h"
#include <fidl/fuchsia.component/cpp/fidl.h>
#include <fidl/fuchsia.io/cpp/fidl.h>
#include <fidl/fuchsia.sys2/cpp/fidl.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/syslog/cpp/macros.h>
#include <unistd.h>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include "src/developer/debug/shared/status.h"
namespace debug_agent {
namespace {
const std::string kCapabilityRequested = "capability_requested";
// Helper to simplify request pipelining.
template <typename Protocol>
fidl::ServerEnd<Protocol> CreateEndpointsAndBind(fidl::SyncClient<Protocol>& client) {
auto [client_end, server_end] = fidl::Endpoints<Protocol>::Create();
client.Bind(std::move(client_end));
return std::move(server_end);
}
// Helper to convert method-specific errors into |debug::Status|.
template <typename Method>
debug::Status ErrorToStatus(fidl::ErrorsIn<Method> error) {
if (error.is_framework_error()) {
return debug::ZxStatus(error.framework_error().status());
}
return debug::Status(error.FormatDescription());
}
// Reads a manifest from a |ManifestBytesIterator| producing a vector of bytes.
fit::result<debug::Status, std::vector<uint8_t>> DrainManifestBytesIterator(
fidl::ClientEnd<fuchsia_sys2::ManifestBytesIterator> iterator_client_end) {
fidl::SyncClient iterator(std::move(iterator_client_end));
std::vector<uint8_t> result;
while (true) {
auto next_res = iterator->Next();
if (next_res.is_error()) {
return fit::error(debug::ZxStatus(next_res.error_value().status(),
next_res.error_value().FormatDescription()));
}
if (next_res->infos().empty()) {
break;
}
result.insert(result.end(), next_res->infos().begin(), next_res->infos().end());
}
return fit::ok(std::move(result));
}
// Gets the component declaration for a realm specified by |realm_query|.
fit::result<debug::Status, fidl::Box<fuchsia_component_decl::Component>> GetResolvedDeclaration(
std::string realm_moniker, fidl::SyncClient<fuchsia_sys2::RealmQuery>& realm_query) {
auto get_manifest_res = realm_query->GetManifest({std::move(realm_moniker)});
if (get_manifest_res.is_error()) {
return fit::error(ErrorToStatus(get_manifest_res.error_value()));
}
auto drain_res = DrainManifestBytesIterator(std::move(get_manifest_res->iterator()));
if (drain_res.is_error()) {
return fit::error(drain_res.error_value());
}
auto unpersist_res = fidl::InplaceUnpersist<fuchsia_component_decl::wire::Component>(
cpp20::span<uint8_t>(drain_res->begin(), drain_res->size()));
if (unpersist_res.is_error()) {
return fit::error(debug::ZxStatus(unpersist_res.error_value().status(),
unpersist_res.error_value().FormatDescription()));
}
return fit::ok(fidl::ToNatural(*unpersist_res));
}
// Returns the target of an |Offer|.
std::optional<fuchsia_component_decl::Ref> TargetOf(const fuchsia_component_decl::Offer& offer) {
switch (offer.Which()) {
case fuchsia_component_decl::Offer::Tag::kService:
return offer.service()->target();
case fuchsia_component_decl::Offer::Tag::kProtocol:
return offer.protocol()->target();
case fuchsia_component_decl::Offer::Tag::kDirectory:
return offer.directory()->target();
case fuchsia_component_decl::Offer::Tag::kStorage:
return offer.storage()->target();
case fuchsia_component_decl::Offer::Tag::kRunner:
return offer.runner()->target();
case fuchsia_component_decl::Offer::Tag::kResolver:
return offer.resolver()->target();
case fuchsia_component_decl::Offer::Tag::kEventStream:
return offer.event_stream()->target();
default:
return std::nullopt;
}
}
// Gets all offers in the manifest targeting |test_collection|.
fit::result<debug::Status, std::vector<fuchsia_component_decl::Offer>> ValidateAndGetOffers(
fuchsia_component_decl::Component& manifest, const std::string& test_collection) {
if (!manifest.collections().has_value() ||
!std::any_of(
manifest.collections()->begin(), manifest.collections()->end(),
[&test_collection](auto& collection) { return collection.name() == test_collection; })) {
return fit::error(debug::Status("Manifest contains no offers matching test collection"));
}
if (!manifest.exposes().has_value() ||
!std::any_of(manifest.exposes()->begin(), manifest.exposes()->end(), [](auto& expose) {
return expose.protocol().has_value() &&
expose.protocol()->target_name() ==
fidl::DiscoverableProtocolName<fuchsia_component::Realm>;
})) {
return fit::error(debug::Status("Manifest does not offer Realm procotol"));
}
bool capability_requested = false;
std::vector<fuchsia_component_decl::Offer> offers;
for (auto& offer : *manifest.offers()) {
// Ignore offers that aren't targeted at |test_collection|.
auto target = TargetOf(offer);
if (!target.has_value() || !target->collection().has_value() ||
target->collection()->name() != test_collection) {
continue;
}
if (offer.event_stream().has_value()) {
// Look for 'capability_requested' event stream offers whose source is
// parent and whose scope includes |test_collection|.
auto& event_stream = offer.event_stream().value();
if (event_stream.target_name() == kCapabilityRequested && event_stream.source().has_value() &&
event_stream.source()->parent().has_value() && event_stream.scope().has_value() &&
std::any_of(event_stream.scope()->begin(), event_stream.scope()->end(),
[&test_collection](auto& ref) {
return ref.collection().has_value() &&
ref.collection()->name() == test_collection;
})) {
// Note 'capability_requested' event.
capability_requested |= event_stream.target_name() == kCapabilityRequested;
}
}
offers.push_back(std::move(offer));
}
if (!capability_requested) {
return fit::error(debug::Status("Capability not requested"));
}
return fit::ok(offers);
}
} // namespace
fit::result<debug::Status, TestRealmAndOffers> GetTestRealmAndOffers(
const std::string& realm_arg,
fidl::SyncClient<fuchsia_sys2::LifecycleController> lifecycle_controller,
fidl::SyncClient<fuchsia_sys2::RealmQuery> realm_query) {
size_t separator_position = realm_arg.rfind(':');
if (separator_position == std::string::npos) {
return fit::error(
debug::Status("Realm parameter must take the form <realm>:<test collection>"));
}
std::string realm_moniker = realm_arg.substr(0, separator_position);
std::string test_collection = realm_arg.substr(separator_position + 1);
fidl::Result resolve_instance_res = lifecycle_controller->ResolveInstance({realm_moniker});
if (resolve_instance_res.is_error()) {
return fit::error(ErrorToStatus(resolve_instance_res.error_value()));
}
auto get_resolved_decl_res = GetResolvedDeclaration(realm_moniker, realm_query);
if (get_resolved_decl_res.is_error()) {
return fit::error(get_resolved_decl_res.error_value());
}
auto& component_decl = *get_resolved_decl_res;
auto get_offers_res = ValidateAndGetOffers(*component_decl, test_collection);
if (get_offers_res.is_error()) {
return fit::error(get_offers_res.error_value());
}
auto& offers = get_offers_res.value();
fidl::SyncClient<fuchsia_io::Directory> directory;
fidl::ServerEnd directory_server_end = CreateEndpointsAndBind(directory);
auto exposed_dir_open_res =
realm_query->Open({std::move(realm_moniker), fuchsia_sys2::OpenDirType::kExposedDir,
fuchsia_io::OpenFlags::kRightReadable, fuchsia_io::ModeType(0), ".",
fidl::ServerEnd<fuchsia_io::Node>(directory_server_end.TakeChannel())});
if (exposed_dir_open_res.is_error()) {
return fit::error(ErrorToStatus(exposed_dir_open_res.error_value()));
}
auto [realm_client_end, realm_server_end] = fidl::Endpoints<fuchsia_component::Realm>::Create();
auto realm_open_res =
directory->Open({fuchsia_io::OpenFlags(0), fuchsia_io::ModeType(0),
fidl::DiscoverableProtocolName<fuchsia_component::Realm>,
fidl::ServerEnd<fuchsia_io::Node>(realm_server_end.TakeChannel())});
if (realm_open_res.is_error()) {
return fit::error(debug::ZxStatus(realm_open_res.error_value().status()));
}
return fit::ok(TestRealmAndOffers{std::move(realm_client_end), std::move(offers),
std::move(test_collection)});
}
fit::result<debug::Status, TestRealmAndOffers> GetTestRealmAndOffers(const std::string& realm_arg) {
auto connect_res = component::Connect<fuchsia_sys2::LifecycleController>(
"/svc/fuchsia.sys2.LifecycleController.root");
if (!connect_res.is_ok())
return fit::error(debug::ZxStatus(connect_res.error_value()));
fidl::SyncClient lifecycle_controller(std::move(*connect_res));
auto realm_query_res =
component::Connect<fuchsia_sys2::RealmQuery>("/svc/fuchsia.sys2.RealmQuery.root");
if (realm_query_res.is_error()) {
return fit::error(debug::ZxStatus(realm_query_res.error_value()));
}
fidl::SyncClient realm_query(std::move(*realm_query_res));
return GetTestRealmAndOffers(realm_arg, std::move(lifecycle_controller), std::move(realm_query));
}
} // namespace debug_agent