// Copyright 2014 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 #include #include #include #include #include #include #include "base/memory/free_deleter.h" #include "base/memory/raw_ptr.h" #include "base/memory/ref_counted.h" #include "base/run_loop.h" #include "base/strings/utf_string_conversions.h" #include "base/synchronization/waitable_event.h" #include "components/storage_monitor/mock_removable_storage_observer.h" #include "components/storage_monitor/portable_device_watcher_win.h" #include "components/storage_monitor/removable_device_constants.h" #include "components/storage_monitor/storage_info.h" #include "components/storage_monitor/storage_monitor_win.h" #include "components/storage_monitor/test_portable_device_watcher_win.h" #include "components/storage_monitor/test_storage_monitor.h" #include "components/storage_monitor/test_storage_monitor_win.h" #include "components/storage_monitor/test_volume_mount_watcher_win.h" #include "components/storage_monitor/volume_mount_watcher_win.h" #include "content/public/test/browser_task_environment.h" #include "content/public/test/test_utils.h" #include "testing/gtest/include/gtest/gtest.h" using base::ASCIIToUTF16; typedef std::vector DeviceIndices; // StorageMonitorWinTest ------------------------------------------------------- namespace storage_monitor { class StorageMonitorWinTest : public testing::Test { public: StorageMonitorWinTest(); StorageMonitorWinTest(const StorageMonitorWinTest&) = delete; StorageMonitorWinTest& operator=(const StorageMonitorWinTest&) = delete; ~StorageMonitorWinTest() override; protected: // testing::Test: void SetUp() override; void TearDown() override; void PreAttachDevices(); void DoMassStorageDeviceAttachedTest(const DeviceIndices& device_indices); void DoMassStorageDevicesDetachedTest(const DeviceIndices& device_indices); // Injects a device attach or detach change (depending on the value of // |test_attach|) and tests that the appropriate handler is called. void DoMTPDeviceTest(const std::wstring& pnp_device_id, bool test_attach); // Gets the MTP details of the storage specified by the |storage_device_id|. // On success, returns true and fills in |pnp_device_id| and // |storage_object_id|. bool GetMTPStorageInfo(const std::string& storage_device_id, std::wstring* pnp_device_id, std::wstring* storage_object_id); std::unique_ptr monitor_; // Weak pointer; owned by the device notifications class. raw_ptr volume_mount_watcher_; MockRemovableStorageObserver observer_; private: content::BrowserTaskEnvironment task_environment_; }; StorageMonitorWinTest::StorageMonitorWinTest() { } StorageMonitorWinTest::~StorageMonitorWinTest() { } void StorageMonitorWinTest::SetUp() { auto volume_mount_watcher = std::make_unique(); volume_mount_watcher_ = volume_mount_watcher.get(); monitor_ = std::make_unique( std::move(volume_mount_watcher), std::make_unique()); monitor_->Init(); content::RunAllTasksUntilIdle(); monitor_->AddObserver(&observer_); } void StorageMonitorWinTest::TearDown() { content::RunAllTasksUntilIdle(); monitor_->RemoveObserver(&observer_); // Windows storage monitor must be destroyed on the same thread // as construction. monitor_.reset(); } void StorageMonitorWinTest::PreAttachDevices() { monitor_.reset(); auto volume_mount_watcher = std::make_unique(); volume_mount_watcher_ = volume_mount_watcher.get(); volume_mount_watcher_->SetAttachedDevicesFake(); int expect_attach_calls = 0; std::vector initial_devices = volume_mount_watcher_->GetAttachedDevicesCallback().Run(); for (std::vector::const_iterator it = initial_devices.begin(); it != initial_devices.end(); ++it) { bool removable; ASSERT_TRUE(volume_mount_watcher_->GetDeviceRemovable(*it, &removable)); if (removable) expect_attach_calls++; } monitor_ = std::make_unique( std::move(volume_mount_watcher), std::make_unique()); monitor_->AddObserver(&observer_); monitor_->Init(); EXPECT_EQ(0u, volume_mount_watcher_->devices_checked().size()); content::RunAllTasksUntilIdle(); std::vector checked_devices = volume_mount_watcher_->devices_checked(); sort(checked_devices.begin(), checked_devices.end()); EXPECT_EQ(initial_devices, checked_devices); EXPECT_EQ(expect_attach_calls, observer_.attach_calls()); EXPECT_EQ(0, observer_.detach_calls()); } void StorageMonitorWinTest::DoMassStorageDeviceAttachedTest( const DeviceIndices& device_indices) { DEV_BROADCAST_VOLUME volume_broadcast; volume_broadcast.dbcv_size = sizeof(volume_broadcast); volume_broadcast.dbcv_devicetype = DBT_DEVTYP_VOLUME; volume_broadcast.dbcv_unitmask = 0x0; volume_broadcast.dbcv_flags = 0x0; int expect_attach_calls = observer_.attach_calls(); for (DeviceIndices::const_iterator it = device_indices.begin(); it != device_indices.end(); ++it) { volume_broadcast.dbcv_unitmask |= 0x1 << *it; bool removable; ASSERT_TRUE(volume_mount_watcher_->GetDeviceRemovable( VolumeMountWatcherWin::DriveNumberToFilePath(*it), &removable)); if (removable) expect_attach_calls++; } monitor_->InjectDeviceChange(DBT_DEVICEARRIVAL, reinterpret_cast(&volume_broadcast)); content::RunAllTasksUntilIdle(); EXPECT_EQ(expect_attach_calls, observer_.attach_calls()); EXPECT_EQ(0, observer_.detach_calls()); } void StorageMonitorWinTest::DoMassStorageDevicesDetachedTest( const DeviceIndices& device_indices) { DEV_BROADCAST_VOLUME volume_broadcast; volume_broadcast.dbcv_size = sizeof(volume_broadcast); volume_broadcast.dbcv_devicetype = DBT_DEVTYP_VOLUME; volume_broadcast.dbcv_unitmask = 0x0; volume_broadcast.dbcv_flags = 0x0; int pre_attach_calls = observer_.attach_calls(); int expect_detach_calls = 0; for (DeviceIndices::const_iterator it = device_indices.begin(); it != device_indices.end(); ++it) { volume_broadcast.dbcv_unitmask |= 0x1 << *it; StorageInfo info; ASSERT_TRUE(volume_mount_watcher_->GetDeviceInfo( VolumeMountWatcherWin::DriveNumberToFilePath(*it), &info)); if (StorageInfo::IsRemovableDevice(info.device_id())) ++expect_detach_calls; } monitor_->InjectDeviceChange(DBT_DEVICEREMOVECOMPLETE, reinterpret_cast(&volume_broadcast)); content::RunAllTasksUntilIdle(); EXPECT_EQ(pre_attach_calls, observer_.attach_calls()); EXPECT_EQ(expect_detach_calls, observer_.detach_calls()); } void StorageMonitorWinTest::DoMTPDeviceTest(const std::wstring& pnp_device_id, bool test_attach) { GUID guidDevInterface = GUID_NULL; HRESULT hr = CLSIDFromString(kWPDDevInterfaceGUID, &guidDevInterface); if (FAILED(hr)) return; size_t device_id_size = pnp_device_id.size() * sizeof(wchar_t); size_t size = sizeof(DEV_BROADCAST_DEVICEINTERFACE) + device_id_size; std::unique_ptr dev_interface_broadcast( static_cast(malloc(size))); DCHECK(dev_interface_broadcast); ZeroMemory(dev_interface_broadcast.get(), size); dev_interface_broadcast->dbcc_size = size; dev_interface_broadcast->dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; dev_interface_broadcast->dbcc_classguid = guidDevInterface; memcpy(dev_interface_broadcast->dbcc_name, pnp_device_id.data(), device_id_size); int expect_attach_calls = observer_.attach_calls(); int expect_detach_calls = observer_.detach_calls(); PortableDeviceWatcherWin::StorageObjectIDs storage_object_ids = TestPortableDeviceWatcherWin::GetMTPStorageObjectIds(pnp_device_id); for (PortableDeviceWatcherWin::StorageObjectIDs::const_iterator it = storage_object_ids.begin(); it != storage_object_ids.end(); ++it) { std::string unique_id; std::wstring name; std::wstring location; TestPortableDeviceWatcherWin::GetMTPStorageDetails(pnp_device_id, *it, &location, &unique_id, &name); if (test_attach && !name.empty() && !unique_id.empty()) expect_attach_calls++; else if (!name.empty() && !unique_id.empty()) expect_detach_calls++; } monitor_->InjectDeviceChange( test_attach ? DBT_DEVICEARRIVAL : DBT_DEVICEREMOVECOMPLETE, reinterpret_cast(dev_interface_broadcast.get())); content::RunAllTasksUntilIdle(); EXPECT_EQ(expect_attach_calls, observer_.attach_calls()); EXPECT_EQ(expect_detach_calls, observer_.detach_calls()); } bool StorageMonitorWinTest::GetMTPStorageInfo( const std::string& storage_device_id, std::wstring* pnp_device_id, std::wstring* storage_object_id) { return monitor_->GetMTPStorageInfoFromDeviceId( storage_device_id, pnp_device_id, storage_object_id); } TEST_F(StorageMonitorWinTest, RandomMessage) { monitor_->InjectDeviceChange(DBT_DEVICEQUERYREMOVE, NULL); content::RunAllTasksUntilIdle(); } TEST_F(StorageMonitorWinTest, DevicesAttached) { DeviceIndices device_indices; device_indices.push_back(1); // B device_indices.push_back(5); // F device_indices.push_back(7); // H device_indices.push_back(13); // N DoMassStorageDeviceAttachedTest(device_indices); StorageInfo info; EXPECT_TRUE(monitor_->volume_mount_watcher()->GetDeviceInfo( base::FilePath(FILE_PATH_LITERAL("F:\\")), &info)); EXPECT_EQ(L"F:\\", info.location()); EXPECT_EQ("dcim:\\\\?\\Volume{F0000000-0000-0000-0000-000000000000}\\", info.device_id()); EXPECT_EQ(u"F:\\ Drive", info.storage_label()); EXPECT_FALSE(monitor_->GetStorageInfoForPath( base::FilePath(FILE_PATH_LITERAL("G:\\")), &info)); EXPECT_TRUE(monitor_->GetStorageInfoForPath( base::FilePath(FILE_PATH_LITERAL("F:\\")), &info)); StorageInfo info1; EXPECT_TRUE(monitor_->GetStorageInfoForPath( base::FilePath(FILE_PATH_LITERAL("F:\\subdir")), &info1)); StorageInfo info2; EXPECT_TRUE(monitor_->GetStorageInfoForPath( base::FilePath(FILE_PATH_LITERAL("F:\\subdir\\sub")), &info2)); EXPECT_EQ(u"F:\\ Drive", info.storage_label()); EXPECT_EQ(u"F:\\ Drive", info1.storage_label()); EXPECT_EQ(u"F:\\ Drive", info2.storage_label()); } TEST_F(StorageMonitorWinTest, PathMountDevices) { PreAttachDevices(); size_t init_storages = monitor_->GetAllAvailableStorages().size(); volume_mount_watcher_->AddDeviceForTesting( base::FilePath(FILE_PATH_LITERAL("F:\\mount1")), "dcim:mount1", u"mount1", 100); volume_mount_watcher_->AddDeviceForTesting( base::FilePath(FILE_PATH_LITERAL("F:\\mount1\\subdir")), "dcim:mount1subdir", u"mount1subdir", 100); volume_mount_watcher_->AddDeviceForTesting( base::FilePath(FILE_PATH_LITERAL("F:\\mount2")), "dcim:mount2", u"mount2", 100); content::RunAllTasksUntilIdle(); EXPECT_EQ(init_storages + 3, monitor_->GetAllAvailableStorages().size()); StorageInfo info; EXPECT_TRUE(monitor_->GetStorageInfoForPath( base::FilePath(FILE_PATH_LITERAL("F:\\dir")), &info)); EXPECT_EQ(u"F:\\ Drive", info.GetDisplayName(false)); EXPECT_TRUE(monitor_->GetStorageInfoForPath( base::FilePath(FILE_PATH_LITERAL("F:\\mount1")), &info)); EXPECT_EQ(u"mount1", info.GetDisplayName(false)); EXPECT_TRUE(monitor_->GetStorageInfoForPath( base::FilePath(FILE_PATH_LITERAL("F:\\mount1\\dir")), &info)); EXPECT_EQ(u"mount1", info.GetDisplayName(false)); EXPECT_TRUE(monitor_->GetStorageInfoForPath( base::FilePath(FILE_PATH_LITERAL("F:\\mount2\\dir")), &info)); EXPECT_EQ(u"mount2", info.GetDisplayName(false)); EXPECT_TRUE(monitor_->GetStorageInfoForPath( base::FilePath(FILE_PATH_LITERAL("F:\\mount1\\subdir")), &info)); EXPECT_EQ(u"mount1subdir", info.GetDisplayName(false)); EXPECT_TRUE(monitor_->GetStorageInfoForPath( base::FilePath(FILE_PATH_LITERAL("F:\\mount1\\subdir\\dir")), &info)); EXPECT_EQ(u"mount1subdir", info.GetDisplayName(false)); EXPECT_TRUE(monitor_->GetStorageInfoForPath( base::FilePath(FILE_PATH_LITERAL("F:\\mount1\\subdir\\dir\\dir")), &info)); EXPECT_EQ(u"mount1subdir", info.GetDisplayName(false)); } TEST_F(StorageMonitorWinTest, DevicesAttachedHighBoundary) { DeviceIndices device_indices; device_indices.push_back(25); DoMassStorageDeviceAttachedTest(device_indices); } TEST_F(StorageMonitorWinTest, DevicesAttachedLowBoundary) { DeviceIndices device_indices; device_indices.push_back(0); DoMassStorageDeviceAttachedTest(device_indices); } TEST_F(StorageMonitorWinTest, DevicesAttachedAdjacentBits) { DeviceIndices device_indices; device_indices.push_back(0); device_indices.push_back(1); device_indices.push_back(2); device_indices.push_back(3); DoMassStorageDeviceAttachedTest(device_indices); } TEST_F(StorageMonitorWinTest, DevicesDetached) { PreAttachDevices(); DeviceIndices device_indices; device_indices.push_back(1); device_indices.push_back(5); device_indices.push_back(7); device_indices.push_back(13); DoMassStorageDevicesDetachedTest(device_indices); } TEST_F(StorageMonitorWinTest, DevicesDetachedHighBoundary) { PreAttachDevices(); DeviceIndices device_indices; device_indices.push_back(25); DoMassStorageDevicesDetachedTest(device_indices); } TEST_F(StorageMonitorWinTest, DevicesDetachedLowBoundary) { PreAttachDevices(); DeviceIndices device_indices; device_indices.push_back(0); DoMassStorageDevicesDetachedTest(device_indices); } TEST_F(StorageMonitorWinTest, DevicesDetachedAdjacentBits) { PreAttachDevices(); DeviceIndices device_indices; device_indices.push_back(0); device_indices.push_back(1); device_indices.push_back(2); device_indices.push_back(3); DoMassStorageDevicesDetachedTest(device_indices); } TEST_F(StorageMonitorWinTest, DuplicateAttachCheckSuppressed) { // Make sure the original C: mount notification makes it all the // way through. content::RunAllTasksUntilIdle(); volume_mount_watcher_->BlockDeviceCheckForTesting(); base::FilePath kAttachedDevicePath = VolumeMountWatcherWin::DriveNumberToFilePath(8); // I: DEV_BROADCAST_VOLUME volume_broadcast; volume_broadcast.dbcv_size = sizeof(volume_broadcast); volume_broadcast.dbcv_devicetype = DBT_DEVTYP_VOLUME; volume_broadcast.dbcv_flags = 0x0; volume_broadcast.dbcv_unitmask = 0x100; // I: drive monitor_->InjectDeviceChange(DBT_DEVICEARRIVAL, reinterpret_cast(&volume_broadcast)); EXPECT_EQ(0u, volume_mount_watcher_->devices_checked().size()); // Re-attach the same volume. We haven't released the mock device check // event, so there'll be pending calls in the UI thread to finish the // device check notification, blocking the duplicate device injection. monitor_->InjectDeviceChange(DBT_DEVICEARRIVAL, reinterpret_cast(&volume_broadcast)); EXPECT_EQ(0u, volume_mount_watcher_->devices_checked().size()); volume_mount_watcher_->ReleaseDeviceCheck(); content::RunAllTasksUntilIdle(); volume_mount_watcher_->ReleaseDeviceCheck(); // Now let all attach notifications finish running. We'll only get one // finish-attach call. content::RunAllTasksUntilIdle(); const std::vector& checked_devices = volume_mount_watcher_->devices_checked(); ASSERT_EQ(1u, checked_devices.size()); EXPECT_EQ(kAttachedDevicePath, checked_devices[0]); // We'll receive a duplicate check now that the first check has fully cleared. monitor_->InjectDeviceChange(DBT_DEVICEARRIVAL, reinterpret_cast(&volume_broadcast)); content::RunAllTasksUntilIdle(); volume_mount_watcher_->ReleaseDeviceCheck(); content::RunAllTasksUntilIdle(); ASSERT_EQ(2u, checked_devices.size()); EXPECT_EQ(kAttachedDevicePath, checked_devices[0]); EXPECT_EQ(kAttachedDevicePath, checked_devices[1]); } TEST_F(StorageMonitorWinTest, DeviceInfoForPath) { PreAttachDevices(); StorageInfo device_info; // An invalid path. EXPECT_FALSE(monitor_->GetStorageInfoForPath(base::FilePath(L"COM1:\\"), &device_info)); // An unconnected removable device. EXPECT_FALSE(monitor_->GetStorageInfoForPath(base::FilePath(L"E:\\"), &device_info)); // A connected removable device. base::FilePath removable_device(L"F:\\"); EXPECT_TRUE(monitor_->GetStorageInfoForPath(removable_device, &device_info)); StorageInfo info; ASSERT_TRUE(volume_mount_watcher_->GetDeviceInfo(removable_device, &info)); EXPECT_TRUE(StorageInfo::IsRemovableDevice(info.device_id())); EXPECT_EQ(info.device_id(), device_info.device_id()); EXPECT_EQ(info.GetDisplayName(false), device_info.GetDisplayName(false)); EXPECT_EQ(info.location(), device_info.location()); EXPECT_EQ(1000000u, info.total_size_in_bytes()); // A fixed device. base::FilePath fixed_device(L"N:\\"); EXPECT_TRUE(monitor_->GetStorageInfoForPath(fixed_device, &device_info)); ASSERT_TRUE(volume_mount_watcher_->GetDeviceInfo( fixed_device, &info)); EXPECT_FALSE(StorageInfo::IsRemovableDevice(info.device_id())); EXPECT_EQ(info.device_id(), device_info.device_id()); EXPECT_EQ(info.GetDisplayName(false), device_info.GetDisplayName(false)); EXPECT_EQ(info.location(), device_info.location()); } // Test to verify basic MTP storage attach and detach notifications. TEST_F(StorageMonitorWinTest, MTPDeviceBasicAttachDetach) { DoMTPDeviceTest(TestPortableDeviceWatcherWin::kMTPDeviceWithValidInfo, true); DoMTPDeviceTest(TestPortableDeviceWatcherWin::kMTPDeviceWithValidInfo, false); } // When a MTP storage device with invalid storage label and id is // attached/detached, there should not be any device attach/detach // notifications. TEST_F(StorageMonitorWinTest, MTPDeviceWithInvalidInfo) { DoMTPDeviceTest(TestPortableDeviceWatcherWin::kMTPDeviceWithInvalidInfo, true); DoMTPDeviceTest(TestPortableDeviceWatcherWin::kMTPDeviceWithInvalidInfo, false); } // Attach a device with two data partitions. Verify that attach/detach // notifications are sent out for each removable storage. TEST_F(StorageMonitorWinTest, MTPDeviceWithMultipleStorageObjects) { DoMTPDeviceTest(TestPortableDeviceWatcherWin::kMTPDeviceWithMultipleStorages, true); DoMTPDeviceTest(TestPortableDeviceWatcherWin::kMTPDeviceWithMultipleStorages, false); } TEST_F(StorageMonitorWinTest, DriveNumberToFilePath) { EXPECT_EQ(L"A:\\", VolumeMountWatcherWin::DriveNumberToFilePath(0).value()); EXPECT_EQ(L"Y:\\", VolumeMountWatcherWin::DriveNumberToFilePath(24).value()); EXPECT_EQ(L"", VolumeMountWatcherWin::DriveNumberToFilePath(-1).value()); EXPECT_EQ(L"", VolumeMountWatcherWin::DriveNumberToFilePath(199).value()); } // Given a MTP storage persistent id, GetMTPStorageInfo() should fetch the // device interface path and local storage object identifier. TEST_F(StorageMonitorWinTest, GetMTPStorageInfoFromDeviceId) { DoMTPDeviceTest(TestPortableDeviceWatcherWin::kMTPDeviceWithValidInfo, true); PortableDeviceWatcherWin::StorageObjects storage_objects = TestPortableDeviceWatcherWin::GetDeviceStorageObjects( TestPortableDeviceWatcherWin::kMTPDeviceWithValidInfo); for (PortableDeviceWatcherWin::StorageObjects::const_iterator it = storage_objects.begin(); it != storage_objects.end(); ++it) { std::wstring pnp_device_id; std::wstring storage_object_id; ASSERT_TRUE(GetMTPStorageInfo(it->object_persistent_id, &pnp_device_id, &storage_object_id)); std::wstring expected( TestPortableDeviceWatcherWin::kMTPDeviceWithValidInfo); EXPECT_EQ(expected, pnp_device_id); EXPECT_EQ(it->object_persistent_id, TestPortableDeviceWatcherWin::GetMTPStorageUniqueId( pnp_device_id, storage_object_id)); } DoMTPDeviceTest(TestPortableDeviceWatcherWin::kMTPDeviceWithValidInfo, false); } } // namespace storage_monitor