blob: a689c697ba4503643f17fd24ce0e2d928648a38c [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.
// clang-format off
#pragma GCC diagnostic push
#include <Weave/DeviceLayer/internal/WeaveDeviceLayerInternal.h>
#include <Weave/DeviceLayer/ConnectivityManager.h>
#include <Weave/DeviceLayer/internal/ServiceTunnelAgent.h>
#include <Weave/Profiles/WeaveProfiles.h>
#include <Warm/Warm.h>
#pragma GCC diagnostic pop
// clang-format on
#include <fuchsia/net/interfaces/cpp/fidl_test_base.h>
#include <lib/fidl/cpp/binding_set.h>
#include <lib/fit/defer.h>
#include <lib/sys/cpp/testing/component_context_provider.h>
#include <lib/syslog/cpp/macros.h>
#include <deque>
#include "src/connectivity/weave/adaptation/connectivity_manager_delegate_impl.h"
#include "src/connectivity/weave/adaptation/connectivity_manager_impl.h"
#include "test_configuration_manager.h"
#include "test_connectivity_manager.h"
#include "test_thread_stack_manager.h"
#include "weave_test_fixture.h"
namespace nl::Weave::DeviceLayer::Internal {
namespace testing {
namespace {
using weave::adaptation::testing::TestConfigurationManager;
using weave::adaptation::testing::TestConnectivityManager;
using weave::adaptation::testing::TestThreadStackManager;
using nl::Weave::DeviceLayer::ConnectivityManager;
using nl::Weave::DeviceLayer::ConnectivityManagerImpl;
using nl::Weave::Profiles::ServiceDirectory::WeaveServiceManager;
using nl::Weave::Profiles::WeaveTunnel::WeaveTunnelAgent;
using nl::Weave::Profiles::WeaveTunnel::WeaveTunnelConnectionMgr;
} // namespace
class FakeNetInterfaces : public fuchsia::net::interfaces::testing::State_TestBase,
public fuchsia::net::interfaces::testing::Watcher_TestBase {
public:
FakeNetInterfaces() {
// Inject an idle event to represent an empty interface list.
fuchsia::net::interfaces::Empty idle_event;
fuchsia::net::interfaces::Event event =
fuchsia::net::interfaces::Event::WithIdle(std::move(idle_event));
events_.push_back(std::move(event));
}
void NotImplemented_(const std::string& name) override { FAIL() << "Not implemented: " << name; }
fidl::InterfaceRequestHandler<fuchsia::net::interfaces::State> GetHandler(
async_dispatcher_t* dispatcher) {
dispatcher_ = dispatcher;
return [this](fidl::InterfaceRequest<fuchsia::net::interfaces::State> request) {
state_binding_.Bind(std::move(request), dispatcher_);
};
}
void GetWatcher(fuchsia::net::interfaces::WatcherOptions options,
fidl::InterfaceRequest<fuchsia::net::interfaces::Watcher> watcher) override {
watcher_binding_.Bind(std::move(watcher), dispatcher_);
}
void Watch(fuchsia::net::interfaces::Watcher::WatchCallback callback) override {
watch_callback_ = std::move(callback);
SendPendingEvent();
}
void SendPendingEvent() {
if (events_.empty() || !watch_callback_) {
return;
}
fuchsia::net::interfaces::Event event(std::move(events_.front()));
events_.pop_front();
watch_callback_(std::move(event));
watch_callback_ = nullptr;
}
void AddEvent(uint64_t id, bool enable_ipv4, bool enable_ipv6, bool enable_intf = false) {
uint64_t intf_id = id;
fuchsia::net::interfaces::Event event;
fuchsia::net::interfaces::Properties properties;
properties.set_id(intf_id);
properties.set_has_default_ipv4_route(enable_ipv4);
properties.set_has_default_ipv6_route(enable_ipv6);
if (enable_ipv4 || enable_ipv6 || enable_intf) {
event = fuchsia::net::interfaces::Event::WithChanged(std::move(properties));
} else {
event = fuchsia::net::interfaces::Event::WithRemoved(std::move(intf_id));
}
events_.push_back(std::move(event));
SendPendingEvent();
}
void AddOnlineChangeEvent(uint64_t id, bool online) {
fuchsia::net::interfaces::Properties properties;
properties.set_id(id);
properties.set_online(online);
events_.push_back(fuchsia::net::interfaces::Event::WithChanged(std::move(properties)));
SendPendingEvent();
}
void AddEmptyChangeEvent(uint64_t id) {
fuchsia::net::interfaces::Properties properties;
properties.set_id(id);
events_.push_back(fuchsia::net::interfaces::Event::WithChanged(std::move(properties)));
SendPendingEvent();
}
void Close(zx_status_t epitaph_value = ZX_OK) {
watcher_binding_.Close(epitaph_value);
state_binding_.Close(epitaph_value);
}
private:
async_dispatcher_t* dispatcher_;
fuchsia::net::interfaces::Watcher::WatchCallback watch_callback_;
std::deque<fuchsia::net::interfaces::Event> events_;
fidl::Binding<fuchsia::net::interfaces::State> state_binding_{this};
fidl::Binding<fuchsia::net::interfaces::Watcher> watcher_binding_{this};
};
class ConnectivityManagerTest : public WeaveTestFixture<> {
public:
ConnectivityManagerTest() {
context_provider_.service_directory_provider()->AddService(
fake_net_interfaces_.GetHandler(dispatcher()));
}
void SetUp() {
WeaveTestFixture<>::SetUp();
// In order to handle callbacks on the same thread, the delegate cannot be
// registered while using RunFixtureLoop, which runs the loop in a separate
// thread context.
PlatformMgrImpl().SetComponentContextForProcess(context_provider_.TakeContext());
PlatformMgrImpl().SetDispatcher(dispatcher());
// Use default ConfigurationManager and mock out tunnel invocation.
ConfigurationMgrImpl().SetDelegate(std::make_unique<TestConfigurationManager>());
ConnectivityMgrImpl().SetDelegate(std::make_unique<TestConnectivityManager>());
ThreadStackMgrImpl().SetDelegate(std::make_unique<TestThreadStackManager>());
// Perform initialization of delegate and run to complete FIDL connection.
EXPECT_EQ(delegate().Init(), WEAVE_NO_ERROR);
RunLoopUntilIdle();
}
void TearDown() {
Warm::Shutdown(FabricState);
WeaveTestFixture<>::TearDown();
ConfigurationMgrImpl().SetDelegate(nullptr);
ConnectivityMgrImpl().SetDelegate(nullptr);
ThreadStackMgrImpl().SetDelegate(nullptr);
}
protected:
FakeNetInterfaces fake_net_interfaces_;
std::vector<WeaveDeviceEvent> application_events_;
TestConnectivityManager& delegate() {
return *reinterpret_cast<TestConnectivityManager*>(ConnectivityMgrImpl().GetDelegate());
}
static void HandleApplicationEvent(const WeaveDeviceEvent* event, intptr_t arg) {
ConnectivityManagerTest* instance = (ConnectivityManagerTest*)arg;
instance->application_events_.push_back(*event);
}
void SetProvisionState(bool provisioned) {
constexpr uint64_t kFabricId = 1;
constexpr uint64_t kServiceId = 1;
constexpr uint8_t kServiceConfig[] = {1};
constexpr char kAccountId[] = "account-id";
if (provisioned) {
EXPECT_EQ(ConfigurationMgr().StoreFabricId(kFabricId), WEAVE_NO_ERROR);
EXPECT_EQ(
ConfigurationMgr().StoreServiceProvisioningData(
kServiceId, kServiceConfig, sizeof(kServiceConfig), kAccountId, sizeof(kAccountId)),
WEAVE_NO_ERROR);
EXPECT_TRUE(ConfigurationMgr().IsMemberOfFabric());
} else {
EXPECT_EQ(ConfigurationMgr().StoreFabricId(kFabricIdNotSpecified), WEAVE_NO_ERROR);
EXPECT_EQ(ConfigurationMgr().StoreServiceProvisioningData(0 /* service_id */,
nullptr, /* service_config */
0, /* service_config_len */
nullptr, /* account_id */
0 /* account_id_len */),
WEAVE_NO_ERROR);
}
}
private:
sys::testing::ComponentContextProvider context_provider_;
};
TEST_F(ConnectivityManagerTest, Init) {
EXPECT_FALSE(ConnectivityMgr().IsServiceTunnelConnected());
EXPECT_FALSE(ConnectivityMgr().HaveIPv4InternetConnectivity());
EXPECT_FALSE(ConnectivityMgr().HaveIPv6InternetConnectivity());
EXPECT_EQ(ConnectivityMgr().GetServiceTunnelMode(),
ConnectivityManager::kServiceTunnelMode_Enabled);
}
TEST_F(ConnectivityManagerTest, OnInterfaceEvent) {
constexpr uint64_t kPrimaryIntfId = 1;
constexpr uint64_t kSecondaryIntfId = kPrimaryIntfId + 1;
// Report interface with IPv4 connectivity.
fake_net_interfaces_.AddEvent(kPrimaryIntfId, true, false);
RunLoopUntilIdle();
EXPECT_TRUE(ConnectivityMgr().HaveIPv4InternetConnectivity());
EXPECT_FALSE(ConnectivityMgr().HaveIPv6InternetConnectivity());
// Report interface with IPv4 and IPv6 connectivity.
fake_net_interfaces_.AddEvent(kPrimaryIntfId, true, true);
RunLoopUntilIdle();
EXPECT_TRUE(ConnectivityMgr().HaveIPv4InternetConnectivity());
EXPECT_TRUE(ConnectivityMgr().HaveIPv6InternetConnectivity());
// Report new interface with IPv4 connectivity.
fake_net_interfaces_.AddEvent(kSecondaryIntfId, true, false);
RunLoopUntilIdle();
EXPECT_TRUE(ConnectivityMgr().HaveIPv4InternetConnectivity());
EXPECT_TRUE(ConnectivityMgr().HaveIPv6InternetConnectivity());
// Report IPv4 connectivity loss on both interfaces.
fake_net_interfaces_.AddEvent(kPrimaryIntfId, false, true);
fake_net_interfaces_.AddEvent(kSecondaryIntfId, false, false);
RunLoopUntilIdle();
EXPECT_FALSE(ConnectivityMgr().HaveIPv4InternetConnectivity());
EXPECT_TRUE(ConnectivityMgr().HaveIPv6InternetConnectivity());
// Report new interface with no connectivity.
fake_net_interfaces_.AddEvent(kSecondaryIntfId, false, false, true /* enable_intf */);
RunLoopUntilIdle();
EXPECT_FALSE(ConnectivityMgr().HaveIPv4InternetConnectivity());
EXPECT_TRUE(ConnectivityMgr().HaveIPv6InternetConnectivity());
}
TEST_F(ConnectivityManagerTest, InterfacePropertiesChange) {
constexpr uint64_t kPrimaryIntfId = 1;
fake_net_interfaces_.AddOnlineChangeEvent(kPrimaryIntfId, true);
RunLoopUntilIdle();
EXPECT_TRUE(delegate().get_endpoints_refreshed());
delegate().set_endpoints_refreshed(false);
fake_net_interfaces_.AddOnlineChangeEvent(kPrimaryIntfId, false);
RunLoopUntilIdle();
EXPECT_TRUE(delegate().get_endpoints_refreshed());
delegate().set_endpoints_refreshed(false);
fake_net_interfaces_.AddEmptyChangeEvent(kPrimaryIntfId);
EXPECT_FALSE(delegate().get_endpoints_refreshed());
}
TEST_F(ConnectivityManagerTest, HandleServiceTunnelNotification) {
PlatformMgr().AddEventHandler(HandleApplicationEvent, (intptr_t)this);
auto remove_event_handler =
fit::defer([&] { PlatformMgr().RemoveEventHandler(HandleApplicationEvent, (intptr_t)this); });
// Enable unrestricted tunnel.
ServiceTunnelAgent.OnServiceTunStatusNotify(WeaveTunnelConnectionMgr::kStatus_TunPrimaryUp,
WEAVE_NO_ERROR, &delegate());
RunLoopUntilIdle();
EXPECT_TRUE(delegate().get_service_tunnel_up());
EXPECT_EQ(application_events_.size(), 2UL);
EXPECT_EQ(application_events_[0].Type, DeviceEventType::kServiceTunnelStateChange);
EXPECT_EQ(application_events_[0].ServiceTunnelStateChange.Result, kConnectivity_Established);
EXPECT_EQ(application_events_[0].ServiceTunnelStateChange.IsRestricted, false);
EXPECT_EQ(application_events_[1].Type, DeviceEventType::kServiceConnectivityChange);
EXPECT_EQ(application_events_[1].ServiceConnectivityChange.ViaTunnel.Result,
kConnectivity_Established);
EXPECT_EQ(application_events_[1].ServiceConnectivityChange.ViaThread.Result,
kConnectivity_NoChange);
EXPECT_EQ(application_events_[1].ServiceConnectivityChange.Overall.Result,
kConnectivity_Established);
application_events_.clear();
// Bring the tunnel down.
ServiceTunnelAgent.OnServiceTunStatusNotify(WeaveTunnelConnectionMgr::kStatus_TunDown,
WEAVE_NO_ERROR, &delegate());
RunLoopUntilIdle();
EXPECT_FALSE(delegate().get_service_tunnel_up());
EXPECT_EQ(application_events_.size(), 2UL);
EXPECT_EQ(application_events_[0].Type, DeviceEventType::kServiceTunnelStateChange);
EXPECT_EQ(application_events_[0].ServiceTunnelStateChange.Result, kConnectivity_Lost);
EXPECT_EQ(application_events_[0].ServiceTunnelStateChange.IsRestricted, false);
EXPECT_EQ(application_events_[1].Type, DeviceEventType::kServiceConnectivityChange);
EXPECT_EQ(application_events_[1].ServiceConnectivityChange.ViaTunnel.Result, kConnectivity_Lost);
EXPECT_EQ(application_events_[1].ServiceConnectivityChange.ViaThread.Result,
kConnectivity_NoChange);
EXPECT_EQ(application_events_[1].ServiceConnectivityChange.Overall.Result, kConnectivity_Lost);
application_events_.clear();
// Enable restricted tunnel.
ServiceTunnelAgent.OnServiceTunStatusNotify(WeaveTunnelConnectionMgr::kStatus_TunPrimaryUp,
WEAVE_ERROR_TUNNEL_ROUTING_RESTRICTED, &delegate());
RunLoopUntilIdle();
EXPECT_TRUE(delegate().get_service_tunnel_up());
EXPECT_EQ(application_events_.size(), 1UL);
EXPECT_EQ(application_events_[0].Type, DeviceEventType::kServiceTunnelStateChange);
EXPECT_EQ(application_events_[0].ServiceTunnelStateChange.Result, kConnectivity_Established);
EXPECT_EQ(application_events_[0].ServiceTunnelStateChange.IsRestricted, true);
application_events_.clear();
// Simulate tunnel down due to error.
ServiceTunnelAgent.OnServiceTunStatusNotify(WeaveTunnelConnectionMgr::kStatus_TunPrimaryConnError,
WEAVE_ERROR_TIMEOUT, &delegate());
RunLoopUntilIdle();
EXPECT_FALSE(delegate().get_service_tunnel_up());
EXPECT_EQ(application_events_.size(), 2UL);
EXPECT_EQ(application_events_[0].Type, DeviceEventType::kServiceTunnelStateChange);
EXPECT_EQ(application_events_[0].ServiceTunnelStateChange.Result, kConnectivity_Lost);
EXPECT_EQ(application_events_[0].ServiceTunnelStateChange.IsRestricted, false);
EXPECT_EQ(application_events_[1].Type, DeviceEventType::kServiceConnectivityChange);
EXPECT_EQ(application_events_[1].ServiceConnectivityChange.ViaTunnel.Result, kConnectivity_Lost);
EXPECT_EQ(application_events_[1].ServiceConnectivityChange.ViaThread.Result,
kConnectivity_NoChange);
EXPECT_EQ(application_events_[1].ServiceConnectivityChange.Overall.Result, kConnectivity_Lost);
application_events_.clear();
}
TEST_F(ConnectivityManagerTest, OnPlatformEvent) {
WeaveDeviceEvent fabric_event{.Type = DeviceEventType::kFabricMembershipChange};
WeaveDeviceEvent provisioning_event{.Type = DeviceEventType::kServiceProvisioningChange};
WeaveDeviceEvent account_pairing_event{
.Type = DeviceEventType::kAccountPairingChange,
.AccountPairingChange =
{
.IsPairedToAccount = true,
},
};
// The tunnel should be down by default.
EXPECT_FALSE(delegate().get_service_tunnel_started());
// Enable IPv4 connectivity.
fake_net_interfaces_.AddEvent(0, true, false);
RunLoopUntilIdle();
// Add provisioning information.
SetProvisionState(true);
// Send fabric membership change event, which should trigger tunnel start.
delegate().OnPlatformEvent(&fabric_event);
EXPECT_TRUE(delegate().get_service_tunnel_started());
// Remove provisioning information.
SetProvisionState(false);
// Send provisioning change event, which should shut the tunnel down.
delegate().OnPlatformEvent(&provisioning_event);
EXPECT_FALSE(delegate().get_service_tunnel_started());
// Setting provision infromation and adding account pairing should restart the
// tunnel and leave it in the started state.
SetProvisionState(true);
delegate().OnPlatformEvent(&provisioning_event);
EXPECT_TRUE(delegate().get_service_tunnel_started());
delegate().OnPlatformEvent(&account_pairing_event);
EXPECT_TRUE(delegate().get_service_tunnel_started());
// Sending an event when connectivity is down should bring the tunnel down.
fake_net_interfaces_.AddEvent(0, false, false);
RunLoopUntilIdle();
EXPECT_FALSE(delegate().get_service_tunnel_started());
// Sending an event that should disable the tunnel should retain its state.
account_pairing_event.AccountPairingChange.IsPairedToAccount = false;
delegate().OnPlatformEvent(&account_pairing_event);
EXPECT_FALSE(delegate().get_service_tunnel_started());
}
TEST_F(ConnectivityManagerTest, RequestShutdownOnFidlError) {
PlatformMgr().AddEventHandler(HandleApplicationEvent, (intptr_t)this);
auto remove_event_handler =
fit::defer([&] { PlatformMgr().RemoveEventHandler(HandleApplicationEvent, (intptr_t)this); });
EXPECT_TRUE(application_events_.empty());
fake_net_interfaces_.Close(ZX_ERR_PEER_CLOSED);
RunLoopUntilIdle();
EXPECT_EQ(application_events_.size(), 1UL);
EXPECT_EQ(application_events_[0].Type, WeaveDevicePlatformEventType::kShutdownRequest);
}
} // namespace testing
} // namespace nl::Weave::DeviceLayer::Internal