// Copyright 2015 The Chromium 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 "components/proximity_auth/proximity_monitor_impl.h" #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/run_loop.h" #include "base/test/histogram_tester.h" #include "base/test/simple_test_tick_clock.h" #include "base/test/test_simple_task_runner.h" #include "base/thread_task_runner_handle.h" #include "base/time/time.h" #include "components/proximity_auth/logging/logging.h" #include "components/proximity_auth/proximity_monitor_observer.h" #include "components/proximity_auth/remote_device.h" #include "device/bluetooth/bluetooth_adapter_factory.h" #include "device/bluetooth/test/mock_bluetooth_adapter.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" using device::BluetoothDevice; using testing::_; using testing::NiceMock; using testing::Return; using testing::SaveArg; namespace proximity_auth { namespace { const char kBluetoothAddress[] = "AA:BB:CC:DD:EE:FF"; const char kRemoteDeviceName[] = "LGE Nexus 5"; class TestProximityMonitorImpl : public ProximityMonitorImpl { public: explicit TestProximityMonitorImpl(const RemoteDevice& remote_device, scoped_ptr clock, ProximityMonitorObserver* observer) : ProximityMonitorImpl(remote_device, clock.Pass(), observer) {} ~TestProximityMonitorImpl() override {} using ProximityMonitorImpl::SetStrategy; private: DISALLOW_COPY_AND_ASSIGN(TestProximityMonitorImpl); }; class MockProximityMonitorObserver : public ProximityMonitorObserver { public: MockProximityMonitorObserver() {} ~MockProximityMonitorObserver() override {} MOCK_METHOD0(OnProximityStateChanged, void()); private: DISALLOW_COPY_AND_ASSIGN(MockProximityMonitorObserver); }; // Creates a mock Bluetooth adapter and sets it as the global adapter for // testing. scoped_refptr CreateAndRegisterMockBluetoothAdapter() { scoped_refptr adapter = new NiceMock(); device::BluetoothAdapterFactory::SetAdapterForTesting(adapter); return adapter; } } // namespace class ProximityAuthProximityMonitorImplTest : public testing::Test { public: ProximityAuthProximityMonitorImplTest() : clock_(new base::SimpleTestTickClock()), bluetooth_adapter_(CreateAndRegisterMockBluetoothAdapter()), remote_bluetooth_device_(&*bluetooth_adapter_, 0, kRemoteDeviceName, kBluetoothAddress, false /* paired */, true /* connected */), monitor_({kRemoteDeviceName, kBluetoothAddress}, make_scoped_ptr(clock_), &observer_), task_runner_(new base::TestSimpleTaskRunner()), thread_task_runner_handle_(task_runner_) { ON_CALL(*bluetooth_adapter_, GetDevice(kBluetoothAddress)) .WillByDefault(Return(&remote_bluetooth_device_)); ON_CALL(remote_bluetooth_device_, GetConnectionInfo(_)) .WillByDefault(SaveArg<0>(&connection_info_callback_)); } ~ProximityAuthProximityMonitorImplTest() override {} void RunPendingTasks() { task_runner_->RunPendingTasks(); } void ProvideConnectionInfo( const BluetoothDevice::ConnectionInfo& connection_info) { RunPendingTasks(); connection_info_callback_.Run(connection_info); // Reset the callback to ensure that tests correctly only respond at most // once per call to GetConnectionInfo(). connection_info_callback_ = BluetoothDevice::ConnectionInfoCallback(); } protected: // Mock for verifying interactions with the proximity monitor's observer. NiceMock observer_; // Clock used for verifying time calculations. Owned by the monitor_. base::SimpleTestTickClock* clock_; // Mocks used for verifying interactions with the Bluetooth subsystem. scoped_refptr bluetooth_adapter_; NiceMock remote_bluetooth_device_; // The proximity monitor under test. TestProximityMonitorImpl monitor_; private: scoped_refptr task_runner_; base::ThreadTaskRunnerHandle thread_task_runner_handle_; BluetoothDevice::ConnectionInfoCallback connection_info_callback_; ScopedDisableLoggingForTesting disable_logging_; }; TEST_F(ProximityAuthProximityMonitorImplTest, GetStrategy) { EXPECT_EQ(ProximityMonitor::Strategy::NONE, monitor_.GetStrategy()); monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_TRANSMIT_POWER); EXPECT_EQ(ProximityMonitor::Strategy::CHECK_TRANSMIT_POWER, monitor_.GetStrategy()); } TEST_F(ProximityAuthProximityMonitorImplTest, ProximityState_StrategyIsNone) { monitor_.SetStrategy(ProximityMonitor::Strategy::NONE); EXPECT_TRUE(monitor_.IsUnlockAllowed()); EXPECT_FALSE(monitor_.IsInRssiRange()); } TEST_F(ProximityAuthProximityMonitorImplTest, ProximityState_NeverStarted) { monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI); EXPECT_FALSE(monitor_.IsUnlockAllowed()); EXPECT_FALSE(monitor_.IsInRssiRange()); } TEST_F(ProximityAuthProximityMonitorImplTest, ProximityState_Started_NoConnectionInfoReceivedYet) { monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI); monitor_.Start(); EXPECT_FALSE(monitor_.IsUnlockAllowed()); EXPECT_FALSE(monitor_.IsInRssiRange()); } TEST_F(ProximityAuthProximityMonitorImplTest, ProximityState_InformsObserverOfChanges) { monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_TRANSMIT_POWER); // Initially, the device is not in proximity. monitor_.Start(); EXPECT_FALSE(monitor_.IsUnlockAllowed()); // Simulate a reading indicating proximity. EXPECT_CALL(observer_, OnProximityStateChanged()).Times(1); ProvideConnectionInfo({0, 0, 4}); EXPECT_TRUE(monitor_.IsUnlockAllowed()); // Simulate a reading indicating non-proximity. EXPECT_CALL(observer_, OnProximityStateChanged()).Times(1); ProvideConnectionInfo({0, 4, 4}); EXPECT_FALSE(monitor_.IsUnlockAllowed()); } TEST_F(ProximityAuthProximityMonitorImplTest, ProximityState_CheckRssi_RssiIndicatesProximity_TxPowerDoesNot) { monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI); monitor_.Start(); ProvideConnectionInfo({0, 4, 4}); EXPECT_TRUE(monitor_.IsUnlockAllowed()); EXPECT_TRUE(monitor_.IsInRssiRange()); } TEST_F(ProximityAuthProximityMonitorImplTest, ProximityState_CheckRssi_TxPowerIndicatesProximity_RssiDoesNot) { monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI); monitor_.Start(); ProvideConnectionInfo({-10, 0, 4}); EXPECT_FALSE(monitor_.IsUnlockAllowed()); EXPECT_FALSE(monitor_.IsInRssiRange()); } TEST_F(ProximityAuthProximityMonitorImplTest, ProximityState_CheckRssi_NeitherRssiNorTxPowerIndicatesProximity) { monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI); monitor_.Start(); ProvideConnectionInfo({-10, 4, 4}); EXPECT_FALSE(monitor_.IsUnlockAllowed()); EXPECT_FALSE(monitor_.IsInRssiRange()); } TEST_F(ProximityAuthProximityMonitorImplTest, ProximityState_CheckRssi_BothRssiAndTxPowerIndicateProximity) { monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI); monitor_.Start(); ProvideConnectionInfo({0, 0, 4}); EXPECT_TRUE(monitor_.IsUnlockAllowed()); EXPECT_TRUE(monitor_.IsInRssiRange()); } TEST_F(ProximityAuthProximityMonitorImplTest, ProximityState_CheckRssi_UnknownRssi) { monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI); monitor_.Start(); ProvideConnectionInfo({0, 0, 4}); ProvideConnectionInfo({BluetoothDevice::kUnknownPower, 0, 4}); EXPECT_FALSE(monitor_.IsUnlockAllowed()); EXPECT_FALSE(monitor_.IsInRssiRange()); } TEST_F(ProximityAuthProximityMonitorImplTest, ProximityState_CheckRssi_UnknownTxPower) { monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI); monitor_.Start(); ProvideConnectionInfo({0, 0, 4}); ProvideConnectionInfo({0, BluetoothDevice::kUnknownPower, 4}); EXPECT_FALSE(monitor_.IsUnlockAllowed()); EXPECT_FALSE(monitor_.IsInRssiRange()); } TEST_F(ProximityAuthProximityMonitorImplTest, ProximityState_CheckRssi_UnknownMaxTxPower) { monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI); monitor_.Start(); ProvideConnectionInfo({0, 0, 4}); ProvideConnectionInfo({0, 0, BluetoothDevice::kUnknownPower}); EXPECT_FALSE(monitor_.IsUnlockAllowed()); EXPECT_FALSE(monitor_.IsInRssiRange()); } TEST_F(ProximityAuthProximityMonitorImplTest, ProximityState_CheckTxPower_RssiIndicatesProximity_TxPowerDoesNot) { monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_TRANSMIT_POWER); monitor_.Start(); ProvideConnectionInfo({0, 4, 4}); EXPECT_FALSE(monitor_.IsUnlockAllowed()); EXPECT_TRUE(monitor_.IsInRssiRange()); } TEST_F(ProximityAuthProximityMonitorImplTest, ProximityState_CheckTxPower_TxPowerIndicatesProximity_RssiDoesNot) { monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_TRANSMIT_POWER); monitor_.Start(); ProvideConnectionInfo({-10, 0, 4}); EXPECT_TRUE(monitor_.IsUnlockAllowed()); EXPECT_FALSE(monitor_.IsInRssiRange()); } TEST_F(ProximityAuthProximityMonitorImplTest, ProximityState_CheckTxPower_NeitherRssiNorTxPowerIndicatesProximity) { monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_TRANSMIT_POWER); monitor_.Start(); ProvideConnectionInfo({-10, 4, 4}); EXPECT_FALSE(monitor_.IsUnlockAllowed()); EXPECT_FALSE(monitor_.IsInRssiRange()); } TEST_F(ProximityAuthProximityMonitorImplTest, ProximityState_CheckTxPower_BothRssiAndTxPowerIndicateProximity) { monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_TRANSMIT_POWER); monitor_.Start(); ProvideConnectionInfo({0, 0, 4}); EXPECT_TRUE(monitor_.IsUnlockAllowed()); EXPECT_TRUE(monitor_.IsInRssiRange()); } TEST_F(ProximityAuthProximityMonitorImplTest, ProximityState_CheckTxPower_UnknownRssi) { monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_TRANSMIT_POWER); monitor_.Start(); ProvideConnectionInfo({0, 0, 4}); ProvideConnectionInfo({BluetoothDevice::kUnknownPower, 0, 4}); EXPECT_FALSE(monitor_.IsUnlockAllowed()); EXPECT_FALSE(monitor_.IsInRssiRange()); } TEST_F(ProximityAuthProximityMonitorImplTest, ProximityState_CheckTxPower_UnknownTxPower) { monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_TRANSMIT_POWER); monitor_.Start(); ProvideConnectionInfo({0, 0, 4}); ProvideConnectionInfo({0, BluetoothDevice::kUnknownPower, 4}); EXPECT_FALSE(monitor_.IsUnlockAllowed()); EXPECT_FALSE(monitor_.IsInRssiRange()); } TEST_F(ProximityAuthProximityMonitorImplTest, ProximityState_CheckTxPower_UnknownMaxTxPower) { monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_TRANSMIT_POWER); monitor_.Start(); ProvideConnectionInfo({0, 0, 4}); ProvideConnectionInfo({0, 0, BluetoothDevice::kUnknownPower}); EXPECT_FALSE(monitor_.IsUnlockAllowed()); EXPECT_FALSE(monitor_.IsInRssiRange()); } TEST_F(ProximityAuthProximityMonitorImplTest, ProximityState_StartThenStop) { monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI); monitor_.Start(); ProvideConnectionInfo({0, 0, 4}); monitor_.Stop(); EXPECT_FALSE(monitor_.IsUnlockAllowed()); EXPECT_FALSE(monitor_.IsInRssiRange()); } TEST_F(ProximityAuthProximityMonitorImplTest, ProximityState_StartThenStopThenStartAgain) { monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI); monitor_.Start(); ProvideConnectionInfo({-10, 4, 4}); ProvideConnectionInfo({-10, 4, 4}); ProvideConnectionInfo({-10, 4, 4}); ProvideConnectionInfo({-10, 4, 4}); ProvideConnectionInfo({-10, 4, 4}); monitor_.Stop(); // Restarting the monitor should immediately reset the proximity state, rather // than building on the previous rolling average. monitor_.Start(); ProvideConnectionInfo({0, 4, 4}); EXPECT_TRUE(monitor_.IsUnlockAllowed()); EXPECT_TRUE(monitor_.IsInRssiRange()); } TEST_F(ProximityAuthProximityMonitorImplTest, ProximityState_RemoteDeviceRemainsInProximity) { monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI); monitor_.Start(); ProvideConnectionInfo({0, 4, 4}); ProvideConnectionInfo({-1, 4, 4}); ProvideConnectionInfo({0, 4, 4}); ProvideConnectionInfo({-2, 4, 4}); ProvideConnectionInfo({-1, 4, 4}); EXPECT_TRUE(monitor_.IsUnlockAllowed()); EXPECT_TRUE(monitor_.IsInRssiRange()); // Brief drops in RSSI should be handled by weighted averaging. ProvideConnectionInfo({-10, 4, 4}); EXPECT_TRUE(monitor_.IsUnlockAllowed()); EXPECT_TRUE(monitor_.IsInRssiRange()); } TEST_F(ProximityAuthProximityMonitorImplTest, ProximityState_RemoteDeviceLeavesProximity) { monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI); monitor_.Start(); // Start with a device in proximity. ProvideConnectionInfo({0, 4, 4}); EXPECT_TRUE(monitor_.IsUnlockAllowed()); EXPECT_TRUE(monitor_.IsInRssiRange()); // Simulate readings for the remote device leaving proximity. ProvideConnectionInfo({-1, 4, 4}); ProvideConnectionInfo({-4, 4, 4}); ProvideConnectionInfo({0, 4, 4}); ProvideConnectionInfo({-10, 4, 4}); ProvideConnectionInfo({-8, 4, 4}); ProvideConnectionInfo({-15, 4, 4}); ProvideConnectionInfo({-10, 4, 4}); ProvideConnectionInfo({-10, 4, 4}); ProvideConnectionInfo({-10, 4, 4}); ProvideConnectionInfo({-10, 4, 4}); EXPECT_FALSE(monitor_.IsUnlockAllowed()); EXPECT_FALSE(monitor_.IsInRssiRange()); } TEST_F(ProximityAuthProximityMonitorImplTest, ProximityState_RemoteDeviceEntersProximity) { monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI); monitor_.Start(); // Start with a device in proximity. ProvideConnectionInfo({-20, 4, 4}); EXPECT_FALSE(monitor_.IsUnlockAllowed()); EXPECT_FALSE(monitor_.IsInRssiRange()); // Simulate readings for the remote device entering proximity. ProvideConnectionInfo({-15, 4, 4}); ProvideConnectionInfo({-8, 4, 4}); ProvideConnectionInfo({-12, 4, 4}); ProvideConnectionInfo({-18, 4, 4}); ProvideConnectionInfo({-7, 4, 4}); ProvideConnectionInfo({-3, 4, 4}); ProvideConnectionInfo({-2, 4, 4}); ProvideConnectionInfo({0, 4, 4}); ProvideConnectionInfo({0, 4, 4}); EXPECT_TRUE(monitor_.IsUnlockAllowed()); EXPECT_TRUE(monitor_.IsInRssiRange()); } TEST_F(ProximityAuthProximityMonitorImplTest, ProximityState_DeviceNotKnownToAdapter) { monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI); monitor_.Start(); // Start with the device known to the adapter and in proximity. ProvideConnectionInfo({0, 4, 4}); EXPECT_TRUE(monitor_.IsUnlockAllowed()); EXPECT_TRUE(monitor_.IsInRssiRange()); // Simulate it being forgotten. ON_CALL(*bluetooth_adapter_, GetDevice(kBluetoothAddress)) .WillByDefault(Return(nullptr)); EXPECT_CALL(observer_, OnProximityStateChanged()); RunPendingTasks(); EXPECT_FALSE(monitor_.IsUnlockAllowed()); EXPECT_FALSE(monitor_.IsInRssiRange()); } TEST_F(ProximityAuthProximityMonitorImplTest, ProximityState_DeviceNotConnected) { monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI); monitor_.Start(); // Start with the device connected and in proximity. ProvideConnectionInfo({0, 4, 4}); EXPECT_TRUE(monitor_.IsUnlockAllowed()); EXPECT_TRUE(monitor_.IsInRssiRange()); // Simulate it disconnecting. ON_CALL(remote_bluetooth_device_, IsConnected()).WillByDefault(Return(false)); EXPECT_CALL(observer_, OnProximityStateChanged()); RunPendingTasks(); EXPECT_FALSE(monitor_.IsUnlockAllowed()); EXPECT_FALSE(monitor_.IsInRssiRange()); } TEST_F(ProximityAuthProximityMonitorImplTest, ProximityState_ConnectionInfoReceivedAfterStopping) { monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI); monitor_.Start(); monitor_.Stop(); ProvideConnectionInfo({0, 4, 4}); EXPECT_FALSE(monitor_.IsUnlockAllowed()); EXPECT_FALSE(monitor_.IsInRssiRange()); } TEST_F(ProximityAuthProximityMonitorImplTest, RecordProximityMetricsOnAuthSuccess_NormalValues) { monitor_.Start(); ProvideConnectionInfo({0, 0, 4}); clock_->Advance(base::TimeDelta::FromMilliseconds(101)); ProvideConnectionInfo({-20, 3, 4}); clock_->Advance(base::TimeDelta::FromMilliseconds(203)); base::HistogramTester histogram_tester; monitor_.RecordProximityMetricsOnAuthSuccess(); histogram_tester.ExpectUniqueSample("EasyUnlock.AuthProximity.RollingRssi", -6, 1); histogram_tester.ExpectUniqueSample( "EasyUnlock.AuthProximity.TransmitPowerDelta", -1, 1); histogram_tester.ExpectUniqueSample( "EasyUnlock.AuthProximity.TimeSinceLastZeroRssi", 304, 1); histogram_tester.ExpectUniqueSample( "EasyUnlock.AuthProximity.RemoteDeviceModelHash", 1881443083 /* hash of "LGE Nexus 5" */, 1); } TEST_F(ProximityAuthProximityMonitorImplTest, RecordProximityMetricsOnAuthSuccess_ClampedValues) { monitor_.Start(); ProvideConnectionInfo({-99999, 99999, 12345}); base::HistogramTester histogram_tester; monitor_.RecordProximityMetricsOnAuthSuccess(); histogram_tester.ExpectUniqueSample("EasyUnlock.AuthProximity.RollingRssi", -100, 1); histogram_tester.ExpectUniqueSample( "EasyUnlock.AuthProximity.TransmitPowerDelta", 50, 1); } TEST_F(ProximityAuthProximityMonitorImplTest, RecordProximityMetricsOnAuthSuccess_UnknownValues) { RemoteDevice unnamed_remote_device = {kBluetoothAddress, kBluetoothAddress}; scoped_ptr clock(new base::SimpleTestTickClock()); ProximityMonitorImpl monitor(unnamed_remote_device, clock.Pass(), &observer_); monitor.Start(); ProvideConnectionInfo({127, 127, 127}); base::HistogramTester histogram_tester; monitor.RecordProximityMetricsOnAuthSuccess(); histogram_tester.ExpectUniqueSample("EasyUnlock.AuthProximity.RollingRssi", 127, 1); histogram_tester.ExpectUniqueSample( "EasyUnlock.AuthProximity.TransmitPowerDelta", 127, 1); histogram_tester.ExpectUniqueSample( "EasyUnlock.AuthProximity.TimeSinceLastZeroRssi", base::TimeDelta::FromSeconds(10).InMilliseconds(), 1); histogram_tester.ExpectUniqueSample( "EasyUnlock.AuthProximity.RemoteDeviceModelHash", -1808066424 /* hash of "Unknown" */, 1); } } // namespace proximity_auth