| // Copyright 2022 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "shill/wifi/hotspot_device.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/memory/ref_counted.h> |
| #include <base/test/mock_callback.h> |
| #include <gmock/gmock.h> |
| #include <net-base/mac_address.h> |
| |
| #include "shill/mock_control.h" |
| #include "shill/mock_manager.h" |
| #include "shill/mock_metrics.h" |
| #include "shill/store/key_value_store.h" |
| #include "shill/supplicant/mock_supplicant_interface_proxy.h" |
| #include "shill/supplicant/mock_supplicant_process_proxy.h" |
| #include "shill/supplicant/wpa_supplicant.h" |
| #include "shill/test_event_dispatcher.h" |
| #include "shill/wifi/hotspot_service.h" |
| #include "shill/wifi/local_device.h" |
| #include "shill/wifi/mock_wifi_phy.h" |
| #include "shill/wifi/mock_wifi_provider.h" |
| #include "shill/wifi/wifi_security.h" |
| |
| using ::testing::_; |
| using ::testing::ByMove; |
| using ::testing::DoAll; |
| using ::testing::Eq; |
| using ::testing::Invoke; |
| using ::testing::Mock; |
| using ::testing::NiceMock; |
| using ::testing::Return; |
| using ::testing::SetArgPointee; |
| using ::testing::StrictMock; |
| using ::testing::Test; |
| using ::testing::WithArg; |
| |
| namespace shill { |
| |
| namespace { |
| constexpr char kPrimaryInterfaceName[] = "wlan0"; |
| constexpr char kInterfaceName[] = "ap0"; |
| constexpr net_base::MacAddress kDeviceAddress( |
| 0x00, 0x01, 0x02, 0x03, 0x04, 0x05); |
| constexpr char kHotspotSSID[] = "chromeOS-1234"; |
| constexpr char kHotspotPassphrase[] = "test0000"; |
| constexpr int kHotspotFrequency = 2437; |
| constexpr net_base::MacAddress kStationAddress1( |
| 0x00, 0x11, 0x22, 0x33, 0x44, 0x55); |
| constexpr net_base::MacAddress kStationAddress2( |
| 0x00, 0x11, 0x22, 0x33, 0x44, 0x66); |
| constexpr uint32_t kPhyIndex = 5678; |
| constexpr WiFiPhy::Priority kPriority = WiFiPhy::Priority(0); |
| const RpcIdentifier kPrimaryIfacePath = RpcIdentifier("/interface/wlan0"); |
| const RpcIdentifier kIfacePath = RpcIdentifier("/interface/ap0"); |
| const RpcIdentifier kNetworkPath = RpcIdentifier("/network/path"); |
| const RpcIdentifier kStationPath1 = RpcIdentifier("/station/path/1"); |
| const RpcIdentifier kStationPath2 = RpcIdentifier("/station/path/2"); |
| const RpcIdentifier kStationPath3 = RpcIdentifier("/station/path/3"); |
| } // namespace |
| |
| class HotspotDeviceTest : public testing::Test { |
| public: |
| HotspotDeviceTest() |
| : manager_(&control_interface_, &dispatcher_, &metrics_), |
| wifi_provider_(new NiceMock<MockWiFiProvider>(&manager_)), |
| device_(new HotspotDevice(&manager_, |
| kPrimaryInterfaceName, |
| kInterfaceName, |
| kDeviceAddress, |
| kPhyIndex, |
| kPriority, |
| cb.Get())), |
| wifi_phy_(kPhyIndex), |
| supplicant_process_proxy_(new NiceMock<MockSupplicantProcessProxy>()), |
| supplicant_interface_proxy_( |
| new NiceMock<MockSupplicantInterfaceProxy>()) { |
| // Replace the Manager's WiFi provider with a mock. |
| manager_.wifi_provider_.reset(wifi_provider_); |
| // Update the Manager's map from technology to provider. |
| manager_.UpdateProviderMapping(); |
| manager_.supplicant_manager()->set_proxy(supplicant_process_proxy_); |
| ON_CALL(*supplicant_process_proxy_, CreateInterface(_, _)) |
| .WillByDefault(DoAll(SetArgPointee<1>(kIfacePath), Return(true))); |
| ON_CALL(control_interface_, CreateSupplicantInterfaceProxy(_, kIfacePath)) |
| .WillByDefault(Return(ByMove(std::move(supplicant_interface_proxy_)))); |
| ON_CALL(*wifi_provider_, GetPhyAtIndex(kPhyIndex)) |
| .WillByDefault(Return(&wifi_phy_)); |
| } |
| |
| void DispatchPendingEvents() { dispatcher_.DispatchPendingEvents(); } |
| |
| protected: |
| MockSupplicantInterfaceProxy* GetSupplicantInterfaceProxy() { |
| return static_cast<MockSupplicantInterfaceProxy*>( |
| device_->supplicant_interface_proxy_.get()); |
| } |
| |
| StrictMock<base::MockRepeatingCallback<void(LocalDevice::DeviceEvent, |
| const LocalDevice*)>> |
| cb; |
| |
| NiceMock<MockControl> control_interface_; |
| EventDispatcherForTest dispatcher_; |
| NiceMock<MockMetrics> metrics_; |
| NiceMock<MockManager> manager_; |
| MockWiFiProvider* wifi_provider_; |
| |
| scoped_refptr<HotspotDevice> device_; |
| MockWiFiPhy wifi_phy_; |
| MockSupplicantProcessProxy* supplicant_process_proxy_; |
| std::unique_ptr<MockSupplicantInterfaceProxy> supplicant_interface_proxy_; |
| }; |
| |
| TEST_F(HotspotDeviceTest, DeviceCleanStartStopWiFiDisabled_NonSelfManaged) { |
| // wpa_supplicant does not control wlan0 if WiFi is disabled. |
| EXPECT_CALL(*supplicant_process_proxy_, |
| GetInterface(kPrimaryInterfaceName, _)) |
| .WillOnce(Return(false)); |
| // Expect to ask wpa_supplicant to control wlan0 first then ap0. |
| EXPECT_CALL(*supplicant_process_proxy_, CreateInterface(_, _)) |
| .WillOnce(DoAll(SetArgPointee<1>(kPrimaryIfacePath), Return(true))) |
| .WillOnce(DoAll(SetArgPointee<1>(kIfacePath), Return(true))); |
| ON_CALL(wifi_phy_, reg_self_managed()).WillByDefault(Return(false)); |
| EXPECT_CALL(*wifi_provider_, UpdateRegAndPhyInfo(_)) |
| .WillOnce(Invoke([&](auto callback) { std::move(callback).Run(); })); |
| EXPECT_TRUE(device_->Start()); |
| // Expect kInterfaceEnabled DeviceEvent sent. |
| EXPECT_CALL(cb, Run(LocalDevice::DeviceEvent::kInterfaceEnabled, _)).Times(1); |
| DispatchPendingEvents(); |
| Mock::VerifyAndClearExpectations(&cb); |
| |
| // Expect disconnect wpa_supplicant from ap0 and wlan0. |
| EXPECT_CALL(*supplicant_process_proxy_, RemoveInterface(kIfacePath)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(*supplicant_process_proxy_, RemoveInterface(kPrimaryIfacePath)) |
| .WillOnce(Return(true)); |
| // Expect no DeviceEvent::kInterfaceDisabled sent if the interface is |
| // destroyed by caller not Kernel. |
| EXPECT_CALL(cb, Run(_, _)).Times(0); |
| EXPECT_TRUE(device_->Stop()); |
| DispatchPendingEvents(); |
| } |
| |
| TEST_F(HotspotDeviceTest, DeviceCleanStartStopWiFiDisabled_SelfManaged) { |
| // wpa_supplicant does not control wlan0 if WiFi is disabled. |
| EXPECT_CALL(*supplicant_process_proxy_, |
| GetInterface(kPrimaryInterfaceName, _)) |
| .WillOnce(Return(false)); |
| // Expect to ask wpa_supplicant to control wlan0 first then ap0. |
| EXPECT_CALL(*supplicant_process_proxy_, CreateInterface(_, _)) |
| .WillOnce(DoAll(SetArgPointee<1>(kPrimaryIfacePath), Return(true))) |
| .WillOnce(DoAll(SetArgPointee<1>(kIfacePath), Return(true))); |
| ON_CALL(wifi_phy_, reg_self_managed()).WillByDefault(Return(true)); |
| EXPECT_TRUE(device_->Start()); |
| // Create a ScanDone event. |
| EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(_)).WillOnce(Return(true)); |
| DispatchPendingEvents(); |
| EXPECT_CALL(*wifi_provider_, UpdatePhyInfo(_)) |
| .WillOnce(Invoke([&](auto callback) { std::move(callback).Run(); })); |
| device_->ScanDone(true); |
| // Expect kInterfaceEnabled DeviceEvent sent. |
| EXPECT_CALL(cb, Run(LocalDevice::DeviceEvent::kInterfaceEnabled, _)).Times(1); |
| DispatchPendingEvents(); |
| Mock::VerifyAndClearExpectations(&cb); |
| |
| // Expect disconnect wpa_supplicant from ap0 and wlan0. |
| EXPECT_CALL(*supplicant_process_proxy_, RemoveInterface(kIfacePath)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(*supplicant_process_proxy_, RemoveInterface(kPrimaryIfacePath)) |
| .WillOnce(Return(true)); |
| // Expect no DeviceEvent::kInterfaceDisabled sent if the interface is |
| // destroyed by caller not Kernel. |
| EXPECT_CALL(cb, Run(_, _)).Times(0); |
| EXPECT_TRUE(device_->Stop()); |
| DispatchPendingEvents(); |
| } |
| |
| TEST_F(HotspotDeviceTest, DeviceCleanStartStopWiFiEnabled) { |
| // wpa_supplicant already controls wlan0 if WiFi is enabled. |
| EXPECT_CALL(*supplicant_process_proxy_, |
| GetInterface(kPrimaryInterfaceName, _)) |
| .WillOnce(DoAll(SetArgPointee<1>(kPrimaryIfacePath), Return(true))); |
| // wpa_supplicant only need to control ap0. |
| EXPECT_CALL(*supplicant_process_proxy_, CreateInterface(_, _)) |
| .WillOnce(DoAll(SetArgPointee<1>(kIfacePath), Return(true))); |
| ON_CALL(wifi_phy_, reg_self_managed()).WillByDefault(Return(false)); |
| EXPECT_CALL(*wifi_provider_, UpdateRegAndPhyInfo(_)) |
| .WillOnce(Invoke([&](auto callback) { std::move(callback).Run(); })); |
| EXPECT_TRUE(device_->Start()); |
| // Expect kInterfaceEnabled DeviceEvent sent. |
| EXPECT_CALL(cb, Run(LocalDevice::DeviceEvent::kInterfaceEnabled, _)).Times(1); |
| DispatchPendingEvents(); |
| Mock::VerifyAndClearExpectations(&cb); |
| |
| // Expect disconnect wpa_supplicant from ap0 only. |
| EXPECT_CALL(*supplicant_process_proxy_, RemoveInterface(kIfacePath)) |
| .WillOnce(Return(true)); |
| // Expect no DeviceEvent::kInterfaceDisabled sent if the interface is |
| // destroyed by caller not Kernel. |
| EXPECT_CALL(cb, Run(_, _)).Times(0); |
| EXPECT_TRUE(device_->Stop()); |
| DispatchPendingEvents(); |
| } |
| |
| TEST_F(HotspotDeviceTest, DeviceExistStart) { |
| EXPECT_CALL(*supplicant_process_proxy_, |
| GetInterface(kPrimaryInterfaceName, _)) |
| .WillOnce(DoAll(SetArgPointee<1>(kPrimaryIfacePath), Return(true))); |
| EXPECT_CALL(*supplicant_process_proxy_, CreateInterface(_, _)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(*supplicant_process_proxy_, GetInterface(kInterfaceName, _)) |
| .WillOnce(DoAll(SetArgPointee<1>(kIfacePath), Return(true))); |
| ON_CALL(wifi_phy_, reg_self_managed()).WillByDefault(Return(false)); |
| EXPECT_CALL(*wifi_provider_, UpdateRegAndPhyInfo(_)) |
| .WillOnce(Invoke([&](auto callback) { std::move(callback).Run(); })); |
| EXPECT_TRUE(device_->Start()); |
| // Expect kInterfaceEnabled DeviceEvent sent. |
| EXPECT_CALL(cb, Run(LocalDevice::DeviceEvent::kInterfaceEnabled, _)).Times(1); |
| DispatchPendingEvents(); |
| } |
| |
| TEST_F(HotspotDeviceTest, InterfaceDisabledEvent) { |
| KeyValueStore props; |
| props.Set<std::string>(WPASupplicant::kInterfacePropertyState, |
| WPASupplicant::kInterfaceStateInterfaceDisabled); |
| |
| // Expect supplicant_state_ change and kInterfaceDisabled DeviceEvent sent. |
| EXPECT_CALL(cb, Run(LocalDevice::DeviceEvent::kInterfaceDisabled, _)) |
| .Times(1); |
| device_->PropertiesChangedTask(props); |
| EXPECT_EQ(device_->supplicant_state_, |
| WPASupplicant::kInterfaceStateInterfaceDisabled); |
| DispatchPendingEvents(); |
| Mock::VerifyAndClearExpectations(&cb); |
| |
| // Expect no supplicant_state_ change and no DeviceEvent sent with same state. |
| EXPECT_CALL(cb, Run(_, _)).Times(0); |
| device_->PropertiesChangedTask(props); |
| EXPECT_EQ(device_->supplicant_state_, |
| WPASupplicant::kInterfaceStateInterfaceDisabled); |
| DispatchPendingEvents(); |
| } |
| |
| TEST_F(HotspotDeviceTest, ConfigureDeconfigureService) { |
| EXPECT_TRUE(device_->Start()); |
| |
| // Configure service for the first time. |
| auto service0 = std::make_unique<HotspotService>( |
| device_, kHotspotSSID, kHotspotPassphrase, WiFiSecurity::kWpa2, |
| kHotspotFrequency); |
| EXPECT_CALL(*GetSupplicantInterfaceProxy(), AddNetwork(_, _)) |
| .WillOnce(DoAll(SetArgPointee<1>(kNetworkPath), Return(true))); |
| EXPECT_CALL(*GetSupplicantInterfaceProxy(), SelectNetwork(Eq(kNetworkPath))) |
| .WillOnce(Return(true)); |
| EXPECT_TRUE(device_->ConfigureService(std::move(service0))); |
| |
| // Configure a second service should be a no-op and return false. |
| auto service1 = std::make_unique<HotspotService>( |
| device_, kHotspotSSID, kHotspotPassphrase, WiFiSecurity::kWpa2, |
| kHotspotFrequency); |
| EXPECT_CALL(*GetSupplicantInterfaceProxy(), AddNetwork(_, _)).Times(0); |
| EXPECT_CALL(*GetSupplicantInterfaceProxy(), SelectNetwork(Eq(kNetworkPath))) |
| .Times(0); |
| EXPECT_FALSE(device_->ConfigureService(std::move(service1))); |
| |
| // Deconfigure service. |
| EXPECT_CALL(*GetSupplicantInterfaceProxy(), RemoveNetwork(Eq(kNetworkPath))) |
| .WillOnce(Return(true)); |
| EXPECT_TRUE(device_->DeconfigureService()); |
| |
| // Deconfigure service for the second time should be a no-op. |
| EXPECT_CALL(*GetSupplicantInterfaceProxy(), RemoveNetwork(Eq(kNetworkPath))) |
| .Times(0); |
| EXPECT_TRUE(device_->DeconfigureService()); |
| } |
| |
| TEST_F(HotspotDeviceTest, ServiceEvent) { |
| auto service = std::make_unique<HotspotService>( |
| device_, kHotspotSSID, kHotspotPassphrase, WiFiSecurity::kWpa2, |
| kHotspotFrequency); |
| EXPECT_TRUE(device_->Start()); |
| // Expect kInterfaceEnabled DeviceEvent sent. |
| EXPECT_CALL(cb, Run(LocalDevice::DeviceEvent::kInterfaceEnabled, _)).Times(1); |
| DispatchPendingEvents(); |
| Mock::VerifyAndClearExpectations(&cb); |
| |
| ON_CALL(*GetSupplicantInterfaceProxy(), AddNetwork(_, _)) |
| .WillByDefault(DoAll(SetArgPointee<1>(kNetworkPath), Return(true))); |
| EXPECT_TRUE(device_->ConfigureService(std::move(service))); |
| |
| KeyValueStore props; |
| props.Set<std::string>(WPASupplicant::kInterfacePropertyState, |
| WPASupplicant::kInterfaceStateCompleted); |
| |
| // Expect supplicant_state_ change and kLinkUp DeviceEvent sent on |
| // wpa_supplicant state kInterfaceStateCompleted. |
| EXPECT_CALL(cb, Run(LocalDevice::DeviceEvent::kLinkUp, _)).Times(1); |
| device_->PropertiesChangedTask(props); |
| EXPECT_EQ(device_->supplicant_state_, |
| WPASupplicant::kInterfaceStateCompleted); |
| DispatchPendingEvents(); |
| Mock::VerifyAndClearExpectations(&cb); |
| |
| // Expect supplicant_state_ change and kLinkDown DeviceEvent sent on |
| // wpa_supplicant state kInterfaceStateDisconnected. |
| props.Set<std::string>(WPASupplicant::kInterfacePropertyState, |
| WPASupplicant::kInterfaceStateDisconnected); |
| EXPECT_CALL(cb, Run(LocalDevice::DeviceEvent::kLinkDown, _)).Times(1); |
| device_->PropertiesChangedTask(props); |
| EXPECT_EQ(device_->supplicant_state_, |
| WPASupplicant::kInterfaceStateDisconnected); |
| DispatchPendingEvents(); |
| Mock::VerifyAndClearExpectations(&cb); |
| |
| // Expect supplicant_state_ change but no kLinkDown DeviceEvent sent on |
| // further wpa_supplicant state change kInterfaceStateInactive. |
| props.Set<std::string>(WPASupplicant::kInterfacePropertyState, |
| WPASupplicant::kInterfaceStateInactive); |
| EXPECT_CALL(cb, Run(_, _)).Times(0); |
| device_->PropertiesChangedTask(props); |
| EXPECT_EQ(device_->supplicant_state_, WPASupplicant::kInterfaceStateInactive); |
| DispatchPendingEvents(); |
| } |
| |
| TEST_F(HotspotDeviceTest, StationAddedRemoved) { |
| // Station connects. |
| KeyValueStore props1; |
| props1.Set<std::vector<uint8_t>>(WPASupplicant::kStationPropertyAddress, |
| kStationAddress1.ToBytes()); |
| props1.Set<uint16_t>(WPASupplicant::kStationPropertyAID, 0); |
| EXPECT_CALL(cb, Run(LocalDevice::DeviceEvent::kPeerConnected, _)).Times(1); |
| device_->StationAdded(kStationPath1, props1); |
| DispatchPendingEvents(); |
| Mock::VerifyAndClearExpectations(&cb); |
| |
| // Same station connect event should not generate device event. |
| EXPECT_CALL(cb, Run(LocalDevice::DeviceEvent::kPeerConnected, _)).Times(0); |
| device_->StationAdded(kStationPath1, props1); |
| DispatchPendingEvents(); |
| Mock::VerifyAndClearExpectations(&cb); |
| |
| // Remove station |
| EXPECT_CALL(cb, Run(LocalDevice::DeviceEvent::kPeerDisconnected, _)).Times(1); |
| device_->StationRemoved(kStationPath1); |
| DispatchPendingEvents(); |
| Mock::VerifyAndClearExpectations(&cb); |
| |
| // Same station remove event should not generate device event. |
| EXPECT_CALL(cb, Run(LocalDevice::DeviceEvent::kPeerDisconnected, _)).Times(0); |
| device_->StationRemoved(kStationPath1); |
| DispatchPendingEvents(); |
| } |
| |
| TEST_F(HotspotDeviceTest, GetStations) { |
| std::vector<net_base::MacAddress> mac; |
| |
| // Station1 connects. |
| KeyValueStore props1; |
| props1.Set<std::vector<uint8_t>>(WPASupplicant::kStationPropertyAddress, |
| kStationAddress1.ToBytes()); |
| props1.Set<uint16_t>(WPASupplicant::kStationPropertyAID, 0); |
| mac.push_back(kStationAddress1); |
| device_->StationAdded(kStationPath1, props1); |
| EXPECT_EQ(device_->GetStations(), mac); |
| |
| // Station2 connects. |
| KeyValueStore props2; |
| props2.Set<std::vector<uint8_t>>(WPASupplicant::kStationPropertyAddress, |
| kStationAddress2.ToBytes()); |
| props2.Set<uint16_t>(WPASupplicant::kStationPropertyAID, 1); |
| mac.push_back(kStationAddress2); |
| device_->StationAdded(kStationPath2, props2); |
| EXPECT_EQ(device_->GetStations(), mac); |
| |
| // Remove station1 |
| mac.erase(mac.begin()); |
| device_->StationRemoved(kStationPath1); |
| EXPECT_EQ(device_->GetStations(), mac); |
| |
| // Remove station2 |
| mac.erase(mac.begin()); |
| device_->StationRemoved(kStationPath2); |
| EXPECT_EQ(device_->GetStations(), mac); |
| |
| // Station without properties connects. |
| KeyValueStore props3; |
| device_->StationAdded(kStationPath3, props3); |
| EXPECT_EQ(device_->GetStations().size(), 0); |
| } |
| |
| } // namespace shill |