|  | // Copyright 2022 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/forensics/feedback/annotations/fidl_provider.h" | 
|  |  | 
|  | #include <fuchsia/feedback/cpp/fidl.h> | 
|  | #include <fuchsia/update/channelcontrol/cpp/fidl.h> | 
|  | #include <lib/zx/time.h> | 
|  | #include <zircon/errors.h> | 
|  |  | 
|  | #include <gmock/gmock.h> | 
|  | #include <gtest/gtest.h> | 
|  |  | 
|  | #include "src/developer/forensics/feedback/annotations/types.h" | 
|  | #include "src/developer/forensics/testing/stubs/channel_control.h" | 
|  | #include "src/developer/forensics/testing/stubs/device_id_provider.h" | 
|  | #include "src/developer/forensics/testing/unit_test_fixture.h" | 
|  | #include "src/lib/backoff/backoff.h" | 
|  |  | 
|  | namespace forensics::feedback { | 
|  | namespace { | 
|  |  | 
|  | using ::testing::IsEmpty; | 
|  | using ::testing::Pair; | 
|  | using ::testing::UnorderedElementsAreArray; | 
|  |  | 
|  | constexpr char kChannelKey[] = "current_channel"; | 
|  | constexpr char kChannelValue[] = "channel"; | 
|  |  | 
|  | class MonotonicBackoff : public backoff::Backoff { | 
|  | public: | 
|  | zx::duration GetNext() override { | 
|  | const auto backoff = backoff_; | 
|  | backoff_ = backoff + zx::sec(1); | 
|  | return backoff; | 
|  | } | 
|  | void Reset() override { backoff_ = zx::sec(1); } | 
|  |  | 
|  | private: | 
|  | zx::duration backoff_{zx::sec(1)}; | 
|  | }; | 
|  |  | 
|  | using StaticSingleFidlMethodAnnotationProviderTest = UnitTestFixture; | 
|  |  | 
|  | struct ConvertChannel { | 
|  | Annotations operator()(const std::string& channel) { | 
|  | return {{kChannelKey, ErrorOrString(channel)}}; | 
|  | } | 
|  |  | 
|  | Annotations operator()(const Error channel) { return {{kChannelKey, ErrorOrString(channel)}}; } | 
|  | }; | 
|  |  | 
|  | class StaticCurrentChannelProvider | 
|  | : public StaticSingleFidlMethodAnnotationProvider< | 
|  | fuchsia::update::channelcontrol::ChannelControl, | 
|  | &fuchsia::update::channelcontrol::ChannelControl::GetCurrent, ConvertChannel> { | 
|  | public: | 
|  | using StaticSingleFidlMethodAnnotationProvider::StaticSingleFidlMethodAnnotationProvider; | 
|  |  | 
|  | std::set<std::string> GetKeys() const override { return {kChannelKey}; } | 
|  | }; | 
|  |  | 
|  | TEST_F(StaticSingleFidlMethodAnnotationProviderTest, GetAll) { | 
|  | StaticCurrentChannelProvider provider(dispatcher(), services(), | 
|  | std::make_unique<MonotonicBackoff>()); | 
|  |  | 
|  | auto channel_server = std::make_unique<stubs::ChannelControl>(stubs::ChannelControlBase::Params{ | 
|  | .current = kChannelValue, | 
|  | }); | 
|  | InjectServiceProvider(channel_server.get()); | 
|  |  | 
|  | RunLoopUntilIdle(); | 
|  |  | 
|  | Annotations annotations; | 
|  | provider.GetOnce([&annotations](Annotations a) { annotations = std::move(a); }); | 
|  |  | 
|  | RunLoopUntilIdle(); | 
|  | EXPECT_THAT(annotations, | 
|  | UnorderedElementsAreArray({Pair(kChannelKey, ErrorOrString(kChannelValue))})); | 
|  | EXPECT_EQ(channel_server->NumConnections(), 0u); | 
|  | } | 
|  |  | 
|  | TEST_F(StaticSingleFidlMethodAnnotationProviderTest, Reconnects) { | 
|  | StaticCurrentChannelProvider provider(dispatcher(), services(), | 
|  | std::make_unique<MonotonicBackoff>()); | 
|  |  | 
|  | auto channel_server = std::make_unique<stubs::ChannelControlClosesFirstConnection>( | 
|  | stubs::ChannelControlBase::Params{ | 
|  | .current = kChannelValue, | 
|  | }); | 
|  | InjectServiceProvider(channel_server.get()); | 
|  |  | 
|  | RunLoopUntilIdle(); | 
|  | ASSERT_EQ(channel_server->NumConnections(), 0u); | 
|  |  | 
|  | Annotations annotations; | 
|  | provider.GetOnce([&annotations](Annotations a) { annotations = std::move(a); }); | 
|  |  | 
|  | RunLoopUntilIdle(); | 
|  | EXPECT_THAT(annotations, IsEmpty()); | 
|  |  | 
|  | RunLoopFor(zx::sec(1)); | 
|  | EXPECT_THAT(annotations, | 
|  | UnorderedElementsAreArray({Pair(kChannelKey, ErrorOrString(kChannelValue))})); | 
|  | EXPECT_EQ(channel_server->NumConnections(), 0u); | 
|  | } | 
|  |  | 
|  | using DynamicSingleFidlMethodAnnotationProviderTest = UnitTestFixture; | 
|  |  | 
|  | class DynamicCurrentChannelProvider | 
|  | : public DynamicSingleFidlMethodAnnotationProvider< | 
|  | fuchsia::update::channelcontrol::ChannelControl, | 
|  | &fuchsia::update::channelcontrol::ChannelControl::GetCurrent, ConvertChannel> { | 
|  | public: | 
|  | using DynamicSingleFidlMethodAnnotationProvider::DynamicSingleFidlMethodAnnotationProvider; | 
|  |  | 
|  | std::set<std::string> GetKeys() const override { return {kChannelKey}; } | 
|  | }; | 
|  |  | 
|  | TEST_F(DynamicSingleFidlMethodAnnotationProviderTest, Get) { | 
|  | DynamicCurrentChannelProvider provider(dispatcher(), services(), | 
|  | std::make_unique<MonotonicBackoff>()); | 
|  |  | 
|  | auto channel_server = std::make_unique<stubs::ChannelControl>(stubs::ChannelControlBase::Params{ | 
|  | .current = kChannelValue, | 
|  | }); | 
|  | InjectServiceProvider(channel_server.get()); | 
|  |  | 
|  | RunLoopUntilIdle(); | 
|  |  | 
|  | Annotations annotations; | 
|  | provider.Get([&annotations](Annotations a) { annotations = std::move(a); }); | 
|  |  | 
|  | RunLoopUntilIdle(); | 
|  | EXPECT_THAT(annotations, | 
|  | UnorderedElementsAreArray({Pair(kChannelKey, ErrorOrString(kChannelValue))})); | 
|  | EXPECT_EQ(channel_server->NumConnections(), 1u); | 
|  | } | 
|  |  | 
|  | TEST_F(DynamicSingleFidlMethodAnnotationProviderTest, Reconnects) { | 
|  | DynamicCurrentChannelProvider provider(dispatcher(), services(), | 
|  | std::make_unique<MonotonicBackoff>()); | 
|  |  | 
|  | auto channel_server = std::make_unique<stubs::ChannelControl>(stubs::ChannelControlBase::Params{ | 
|  | .current = kChannelValue, | 
|  | }); | 
|  | InjectServiceProvider(channel_server.get()); | 
|  |  | 
|  | RunLoopUntilIdle(); | 
|  | ASSERT_EQ(channel_server->NumConnections(), 1u); | 
|  |  | 
|  | channel_server->CloseAllConnections(); | 
|  |  | 
|  | RunLoopUntilIdle(); | 
|  | ASSERT_EQ(channel_server->NumConnections(), 0u); | 
|  |  | 
|  | Annotations annotations; | 
|  | provider.Get([&annotations](Annotations a) { annotations = std::move(a); }); | 
|  |  | 
|  | RunLoopUntilIdle(); | 
|  | EXPECT_THAT(annotations, UnorderedElementsAreArray( | 
|  | {Pair(kChannelKey, ErrorOrString(Error::kConnectionError))})); | 
|  |  | 
|  | RunLoopFor(zx::sec(1)); | 
|  | EXPECT_EQ(channel_server->NumConnections(), 1u); | 
|  |  | 
|  | provider.Get([&annotations](Annotations a) { annotations = std::move(a); }); | 
|  |  | 
|  | RunLoopUntilIdle(); | 
|  | EXPECT_THAT(annotations, | 
|  | UnorderedElementsAreArray({Pair(kChannelKey, ErrorOrString(kChannelValue))})); | 
|  |  | 
|  | channel_server->CloseAllConnections(); | 
|  |  | 
|  | RunLoopUntilIdle(); | 
|  | EXPECT_EQ(channel_server->NumConnections(), 0u); | 
|  | } | 
|  |  | 
|  | TEST_F(DynamicSingleFidlMethodAnnotationProviderTest, ReconnectsAfterErrorOnInitialConnection) { | 
|  | DynamicCurrentChannelProvider provider(dispatcher(), services(), | 
|  | std::make_unique<MonotonicBackoff>()); | 
|  | // Inject a service provider which rejects connections until we allow them. | 
|  | auto channel_server = std::make_unique<stubs::ChannelControl>(stubs::ChannelControlBase::Params{ | 
|  | .current = kChannelValue, | 
|  | }); | 
|  | bool allow_connection = false; | 
|  | InjectServiceProvider( | 
|  | std::make_unique<vfs::Service>([&](zx::channel channel, async_dispatcher_t* dispatcher) { | 
|  | if (!allow_connection) { | 
|  | channel.reset(); | 
|  | } else { | 
|  | auto handler = channel_server->GetHandler(); | 
|  | handler(fidl::InterfaceRequest<fuchsia::update::channelcontrol::ChannelControl>{ | 
|  | std::move(channel)}); | 
|  | } | 
|  | }), | 
|  | stubs::ChannelControl::Name_); | 
|  |  | 
|  | // Ensure we get a connection error on the first attempt. | 
|  | RunLoopUntilIdle(); | 
|  | Annotations annotations; | 
|  | provider.Get([&annotations](Annotations a) { annotations = std::move(a); }); | 
|  | RunLoopUntilIdle(); | 
|  | ASSERT_EQ(channel_server->NumConnections(), 0u); | 
|  | EXPECT_THAT(annotations, UnorderedElementsAreArray( | 
|  | {Pair(kChannelKey, ErrorOrString(Error::kConnectionError))})); | 
|  |  | 
|  | // Enable the service, and wait for enough time to pass for another attempt to be made. | 
|  | allow_connection = true; | 
|  | RunLoopFor(zx::sec(1)); | 
|  | ASSERT_EQ(channel_server->NumConnections(), 1u); | 
|  | provider.Get([&annotations](Annotations a) { annotations = std::move(a); }); | 
|  | RunLoopUntilIdle(); | 
|  | EXPECT_THAT(annotations, | 
|  | UnorderedElementsAreArray({Pair(kChannelKey, ErrorOrString(kChannelValue))})); | 
|  | } | 
|  |  | 
|  | TEST_F(DynamicSingleFidlMethodAnnotationProviderTest, DoesNotReconnectIfUnavailable) { | 
|  | DynamicCurrentChannelProvider provider(dispatcher(), services(), | 
|  | std::make_unique<MonotonicBackoff>()); | 
|  |  | 
|  | auto channel_server = std::make_unique<stubs::ChannelControl>(stubs::ChannelControlBase::Params{ | 
|  | .current = kChannelValue, | 
|  | }); | 
|  | InjectServiceProvider(channel_server.get()); | 
|  |  | 
|  | RunLoopUntilIdle(); | 
|  | ASSERT_EQ(channel_server->NumConnections(), 1u); | 
|  |  | 
|  | channel_server->CloseAllConnections(ZX_ERR_UNAVAILABLE); | 
|  |  | 
|  | RunLoopUntilIdle(); | 
|  | ASSERT_EQ(channel_server->NumConnections(), 0u); | 
|  |  | 
|  | Annotations annotations; | 
|  | provider.Get([&annotations](Annotations a) { annotations = std::move(a); }); | 
|  |  | 
|  | RunLoopUntilIdle(); | 
|  | EXPECT_THAT(annotations, UnorderedElementsAreArray( | 
|  | {Pair(kChannelKey, ErrorOrString(Error::kNotAvailableInProduct))})); | 
|  |  | 
|  | RunLoopFor(zx::sec(1)); | 
|  | EXPECT_EQ(channel_server->NumConnections(), 0u); | 
|  |  | 
|  | provider.Get([&annotations](Annotations a) { annotations = std::move(a); }); | 
|  |  | 
|  | RunLoopUntilIdle(); | 
|  | EXPECT_THAT(annotations, UnorderedElementsAreArray( | 
|  | {Pair(kChannelKey, ErrorOrString(Error::kNotAvailableInProduct))})); | 
|  | } | 
|  |  | 
|  | TEST_F(DynamicSingleFidlMethodAnnotationProviderTest, DoesNotReconnectIfNotFound) { | 
|  | DynamicCurrentChannelProvider provider(dispatcher(), services(), | 
|  | std::make_unique<MonotonicBackoff>()); | 
|  |  | 
|  | auto channel_server = std::make_unique<stubs::ChannelControl>(stubs::ChannelControlBase::Params{ | 
|  | .current = kChannelValue, | 
|  | }); | 
|  | InjectServiceProvider(channel_server.get()); | 
|  |  | 
|  | RunLoopUntilIdle(); | 
|  | ASSERT_EQ(channel_server->NumConnections(), 1u); | 
|  |  | 
|  | channel_server->CloseAllConnections(ZX_ERR_NOT_FOUND); | 
|  |  | 
|  | RunLoopUntilIdle(); | 
|  | ASSERT_EQ(channel_server->NumConnections(), 0u); | 
|  |  | 
|  | Annotations annotations; | 
|  | provider.Get([&annotations](Annotations a) { annotations = std::move(a); }); | 
|  |  | 
|  | RunLoopUntilIdle(); | 
|  | EXPECT_THAT(annotations, UnorderedElementsAreArray( | 
|  | {Pair(kChannelKey, ErrorOrString(Error::kNotAvailableInProduct))})); | 
|  |  | 
|  | RunLoopFor(zx::sec(1)); | 
|  | EXPECT_EQ(channel_server->NumConnections(), 0u); | 
|  |  | 
|  | provider.Get([&annotations](Annotations a) { annotations = std::move(a); }); | 
|  |  | 
|  | RunLoopUntilIdle(); | 
|  | EXPECT_THAT(annotations, UnorderedElementsAreArray( | 
|  | {Pair(kChannelKey, ErrorOrString(Error::kNotAvailableInProduct))})); | 
|  | } | 
|  |  | 
|  | constexpr char kDeviceIdKey[] = "current_device_id"; | 
|  | constexpr std::array<const char*, 2> kDeviceIdValues = { | 
|  | "device_id_1", | 
|  | "device_id_2", | 
|  | }; | 
|  |  | 
|  | struct ConvertDeviceId { | 
|  | Annotations operator()(const std::string& device_id) { | 
|  | return {{kDeviceIdKey, ErrorOrString(device_id)}}; | 
|  | } | 
|  | }; | 
|  |  | 
|  | class HangingGetDeviceIdProvider | 
|  | : public HangingGetSingleFidlMethodAnnotationProvider< | 
|  | fuchsia::feedback::DeviceIdProvider, &fuchsia::feedback::DeviceIdProvider::GetId, | 
|  | ConvertDeviceId> { | 
|  | public: | 
|  | using HangingGetSingleFidlMethodAnnotationProvider::HangingGetSingleFidlMethodAnnotationProvider; | 
|  |  | 
|  | std::set<std::string> GetKeys() const override { return {kDeviceIdKey}; } | 
|  | }; | 
|  |  | 
|  | class HangingGetSingleFidlMethodAnnotationProviderTest : public UnitTestFixture { | 
|  | protected: | 
|  | void SetUpDeviceIdProviderServer( | 
|  | std::unique_ptr<stubs::DeviceIdProviderBase> device_id_provider_server) { | 
|  | device_id_provider_server_ = std::move(device_id_provider_server); | 
|  | if (device_id_provider_server_) { | 
|  | InjectServiceProvider(device_id_provider_server_.get()); | 
|  | } | 
|  | } | 
|  |  | 
|  | std::unique_ptr<stubs::DeviceIdProviderBase> device_id_provider_server_; | 
|  | }; | 
|  |  | 
|  | TEST_F(HangingGetSingleFidlMethodAnnotationProviderTest, Get) { | 
|  | SetUpDeviceIdProviderServer(std::make_unique<stubs::DeviceIdProvider>(kDeviceIdValues[0])); | 
|  | HangingGetDeviceIdProvider device_id_provider(dispatcher(), services(), | 
|  | std::make_unique<MonotonicBackoff>()); | 
|  |  | 
|  | Annotations annotations; | 
|  | device_id_provider.GetOnUpdate( | 
|  | [&annotations](Annotations result) { annotations = std::move(result); }); | 
|  |  | 
|  | // |annotations| should be empty because the call hasn't completed. | 
|  | EXPECT_THAT(annotations, IsEmpty()); | 
|  |  | 
|  | RunLoopUntilIdle(); | 
|  | EXPECT_THAT(annotations, UnorderedElementsAreArray({ | 
|  | Pair(kDeviceIdKey, ErrorOrString(kDeviceIdValues[0])), | 
|  | })); | 
|  |  | 
|  | device_id_provider_server_->SetDeviceId(kDeviceIdValues[1]); | 
|  |  | 
|  | // |annotations| should the old value because the change hasn't propagated yet. | 
|  | EXPECT_THAT(annotations, UnorderedElementsAreArray({ | 
|  | Pair(kDeviceIdKey, ErrorOrString(kDeviceIdValues[0])), | 
|  | })); | 
|  |  | 
|  | RunLoopUntilIdle(); | 
|  | EXPECT_THAT(annotations, UnorderedElementsAreArray({ | 
|  | Pair(kDeviceIdKey, ErrorOrString(kDeviceIdValues[1])), | 
|  | })); | 
|  |  | 
|  | device_id_provider_server_->CloseConnection(); | 
|  |  | 
|  | // |annotations| should contain the old value because the disconnection hasn't propagated. | 
|  | EXPECT_THAT(annotations, UnorderedElementsAreArray({ | 
|  | Pair(kDeviceIdKey, ErrorOrString(kDeviceIdValues[1])), | 
|  | })); | 
|  | } | 
|  |  | 
|  | TEST_F(HangingGetSingleFidlMethodAnnotationProviderTest, Reconnects) { | 
|  | SetUpDeviceIdProviderServer(std::make_unique<stubs::DeviceIdProviderNeverReturns>()); | 
|  | HangingGetDeviceIdProvider device_id_provider(dispatcher(), services(), | 
|  | std::make_unique<MonotonicBackoff>()); | 
|  |  | 
|  | RunLoopUntilIdle(); | 
|  | ASSERT_TRUE(device_id_provider_server_->IsBound()); | 
|  |  | 
|  | Annotations annotations; | 
|  | device_id_provider.GetOnUpdate( | 
|  | [&annotations](Annotations result) { annotations = std::move(result); }); | 
|  |  | 
|  | device_id_provider_server_->CloseConnection(); | 
|  | ASSERT_FALSE(device_id_provider_server_->IsBound()); | 
|  |  | 
|  | RunLoopUntilIdle(); | 
|  |  | 
|  | // The outstanding request should complete with a connection error. | 
|  | EXPECT_THAT(annotations, IsEmpty()); | 
|  | RunLoopFor(zx::sec(1)); | 
|  | ASSERT_TRUE(device_id_provider_server_->IsBound()); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace forensics::feedback |