| // 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 |