// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/usb/web_usb_service_impl.h"

#include <memory>
#include <set>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "base/barrier_closure.h"
#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/test_future.h"
#include "build/android_buildflags.h"
#include "content/browser/service_worker/embedded_worker_test_helper.h"
#include "content/browser/usb/usb_test_utils.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/mock_web_contents_observer.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/test_web_contents_factory.h"
#include "content/test/test_render_view_host.h"
#include "content/test/test_web_contents.h"
#include "mojo/public/cpp/bindings/associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/test_support/fake_message_dispatch_context.h"
#include "mojo/public/cpp/test_support/test_utils.h"
#include "services/device/public/cpp/test/fake_usb_device_info.h"
#include "services/device/public/cpp/test/fake_usb_device_manager.h"
#include "services/device/public/mojom/usb_device.mojom.h"
#include "services/device/public/mojom/usb_enumeration_options.mojom.h"
#include "services/device/public/mojom/usb_manager_client.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace content {

namespace {

using ::base::test::RunOnceCallback;
using ::base::test::RunOnceClosure;
using ::base::test::TestFuture;
using ::testing::_;
using ::testing::Combine;
using ::testing::ElementsAre;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::TestParamInfo;
using ::testing::Values;
using ::testing::WithParamInterface;

enum ServiceCreationType {
  kCreateForFrame,
  kCreateForServiceWorker,
};

constexpr std::string_view kDefaultTestUrl{"https://www.google.com/"};
constexpr std::string_view kCrossOriginTestUrl{"https://www.chromium.org"};

MATCHER_P(HasGuid, matcher, "") {
  return ExplainMatchResult(matcher, arg->guid, result_listener);
}

device::mojom::UsbOpenDeviceResultPtr NewUsbOpenDeviceSuccess() {
  return device::mojom::UsbOpenDeviceResult::NewSuccess(
      device::mojom::UsbOpenDeviceSuccess::OK);
}

std::string ServiceCreationTypeToString(ServiceCreationType type) {
  switch (type) {
    case kCreateForFrame:
      return "Frame";
    case kCreateForServiceWorker:
      return "ServiceWorker";
  }
}

std::string ClassCodeToString(uint8_t class_code) {
  switch (class_code) {
    case device::mojom::kUsbAudioClass:
      return "AudioClass";
    case device::mojom::kUsbHidClass:
      return "HidClass";
    case device::mojom::kUsbMassStorageClass:
      return "MassStorageClass";
    case device::mojom::kUsbSmartCardClass:
      return "SmartCardClass";
    case device::mojom::kUsbVideoClass:
      return "VideoClass";
    case device::mojom::kUsbAudioVideoClass:
      return "AudioVideoClass";
    case device::mojom::kUsbWirelessClass:
      return "WirelessClass";
    default:
      return "UnknownClass";
  }
}

}  // namespace

class WebUsbServiceImplBaseTest : public testing::Test {
 public:
  WebUsbServiceImplBaseTest() = default;
  WebUsbServiceImplBaseTest(WebUsbServiceImplBaseTest&) = delete;
  WebUsbServiceImplBaseTest& operator=(WebUsbServiceImplBaseTest&) = delete;
  ~WebUsbServiceImplBaseTest() override = default;

  void SetUp() override {
    // Connect with the FakeUsbDeviceManager.
    mojo::PendingRemote<device::mojom::UsbDeviceManager> pending_device_manager;
    device_manager_.AddReceiver(
        pending_device_manager.InitWithNewPipeAndPassReceiver());

    original_client_ = SetBrowserClientForTesting(&test_client_);

    // For tests, all devices are permitted by default.
    ON_CALL(delegate(), HasDevicePermission).WillByDefault(Return(true));
    ON_CALL(delegate(), PageMayUseUsb).WillByDefault(Return(true));

    // Forward calls to the fake device manager.
    ON_CALL(delegate(), GetDevices)
        .WillByDefault(
            [this](
                auto* browser_context,
                device::mojom::UsbDeviceManager::GetDevicesCallback callback) {
              device_manager_.GetDevices(nullptr, std::move(callback));
            });
    ON_CALL(delegate(), GetDevice)
        .WillByDefault(
            [this](
                auto* browser_context, const std::string& guid,
                base::span<const uint8_t> blocked_interface_classes,
                mojo::PendingReceiver<device::mojom::UsbDevice> device_receiver,
                mojo::PendingRemote<device::mojom::UsbDeviceClient>
                    device_client) {
              device_manager_.GetDevice(
                  guid,
                  std::vector<uint8_t>(blocked_interface_classes.begin(),
                                       blocked_interface_classes.end()),
                  std::move(device_receiver), std::move(device_client));
            });
    ON_CALL(delegate(), GetDeviceInfo)
        .WillByDefault([this](auto* browser_context, const std::string& guid) {
          return device_manager_.GetDeviceInfo(guid);
        });
  }

  void TearDown() override { SetBrowserClientForTesting(original_client_); }

  const mojo::Remote<blink::mojom::WebUsbService>& GetService(
      ServiceCreationType type) {
    switch (type) {
      case kCreateForFrame:
        web_contents_ =
            web_contents_factory_.CreateWebContents(&browser_context_);
        contents()->NavigateAndCommit(GURL(kDefaultTestUrl));
        contents()->GetPrimaryMainFrame()->CreateWebUsbService(
            service_.BindNewPipeAndPassReceiver());
        break;
      case kCreateForServiceWorker:
        auto scope = GURL(kDefaultTestUrl);
        auto origin = url::Origin::Create(scope);
        auto worker_url = scope.Resolve("worker.js");
        embedded_worker_test_helper_ =
            std::make_unique<EmbeddedWorkerTestHelper>(base::FilePath());
        EXPECT_CALL(delegate(), IsServiceWorkerAllowedForOrigin)
            .Times(2)
            .WillRepeatedly(Return(true));
        EmbeddedWorkerTestHelper::RegistrationAndVersionPair pair =
            embedded_worker_test_helper_->PrepareRegistrationAndVersion(
                scope, worker_url);
        worker_version_ = pair.second;
        worker_version_->set_fetch_handler_type(
            ServiceWorkerVersion::FetchHandlerType::kNotSkippable);
        // Since this test fixture is used expecting device events being
        // handled, simulate the script having hid event handlers by setting
        // `has_hid_event_handlers_` of `worker_version_` before it is being
        // activated.
        worker_version_->set_has_usb_event_handlers(true);
        worker_version_->SetStatus(ServiceWorkerVersion::Status::ACTIVATED);
        pair.first->SetActiveVersion(worker_version_);
        auto* embedded_worker = worker_version_->embedded_worker();
        embedded_worker_test_helper_->StartWorker(
            embedded_worker,
            embedded_worker_test_helper_->CreateStartParams(pair.second));
        embedded_worker->BindUsbService(origin,
                                        service_.BindNewPipeAndPassReceiver());
        break;
    }
    return service_;
  }

  device::mojom::UsbDeviceInfoPtr ConnectDevice(
      scoped_refptr<device::FakeUsbDeviceInfo> device,
      device::MockUsbMojoDevice* mock_device) {
    auto device_info = device_manager_.AddDevice(std::move(device));
    if (mock_device)
      device_manager_.SetMockForDevice(device_info->guid, mock_device);
    delegate().OnDeviceAdded(*device_info);
    return device_info;
  }

  void DisconnectDevice(scoped_refptr<device::FakeUsbDeviceInfo> device) {
    auto device_info = device->GetDeviceInfo().Clone();
    device_manager_.RemoveDevice(std::move(device));
    delegate().OnDeviceRemoved(*device_info);
  }

  scoped_refptr<device::FakeUsbDeviceInfo> CreateFakeDevice() {
    return base::MakeRefCounted<device::FakeUsbDeviceInfo>(
        0x1234, 0x5678, "ACME", "Frobinator", "ABCDEF");
  }

  scoped_refptr<device::FakeUsbDeviceInfo>
  CreateFakeDeviceWithProtectedInterface(uint8_t class_code) {
    auto blocked_interface_alt =
        device::mojom::UsbAlternateInterfaceInfo::New();
    blocked_interface_alt->alternate_setting = 0;
    blocked_interface_alt->class_code = class_code;

    auto blocked_interface = device::mojom::UsbInterfaceInfo::New();
    blocked_interface->interface_number = 0;
    blocked_interface->alternates.push_back(std::move(blocked_interface_alt));

    auto unblocked_interface_alt =
        device::mojom::UsbAlternateInterfaceInfo::New();
    unblocked_interface_alt->alternate_setting = 0;
    unblocked_interface_alt->class_code = 0xff;  // Vendor specific interface.

    auto unblocked_interface = device::mojom::UsbInterfaceInfo::New();
    unblocked_interface->interface_number = 1;
    unblocked_interface->alternates.push_back(
        std::move(unblocked_interface_alt));

    auto config = device::mojom::UsbConfigurationInfo::New();
    config->configuration_value = 1;
    config->interfaces.push_back(std::move(blocked_interface));
    config->interfaces.push_back(std::move(unblocked_interface));

    std::vector<device::mojom::UsbConfigurationInfoPtr> configs;
    configs.push_back(std::move(config));

    return base::MakeRefCounted<device::FakeUsbDeviceInfo>(
        0x1234, 0x5678, "ACME", "Frobinator", "ABCDEF", std::move(configs));
  }

  void SimulateDeviceServiceCrash() { device_manager_.CloseAllBindings(); }
  void CheckIsConnected(ServiceCreationType type, bool expected_state) {
    // Skip the check for service workers which do not have web contents.
    if (!web_contents_)
      return;

    if (type == kCreateForFrame) {
      ASSERT_EQ(
          web_contents_->IsCapabilityActive(WebContentsCapabilityType::kUSB),
          expected_state);
    } else if (type == kCreateForServiceWorker) {
      ASSERT_EQ(worker_version_->GetExternalRequestCountForTest(),
                expected_state ? 1u : 0u);
    }
  }

  void DestroyBrowserContext() { embedded_worker_test_helper_.reset(); }

  TestWebContents* contents() {
    return static_cast<TestWebContents*>(web_contents_);
  }

  MockUsbDelegate& delegate() { return test_client_.delegate(); }

  TestWebContentsFactory& web_contents_factory() {
    return web_contents_factory_;
  }

  TestBrowserContext& browser_context() { return browser_context_; }

 private:
  BrowserTaskEnvironment task_environment_;
  mojo::Remote<blink::mojom::WebUsbService> service_;
  device::FakeUsbDeviceManager device_manager_;
  UsbTestContentBrowserClient test_client_;
  raw_ptr<ContentBrowserClient> original_client_;
  TestBrowserContext browser_context_;
  TestWebContentsFactory web_contents_factory_;
  raw_ptr<WebContents> web_contents_ = nullptr;
  std::unique_ptr<EmbeddedWorkerTestHelper> embedded_worker_test_helper_;
  scoped_refptr<content::ServiceWorkerVersion> worker_version_;
};

class WebUsbServiceImplTest : public WebUsbServiceImplBaseTest,
                              public WithParamInterface<ServiceCreationType> {};

TEST_P(WebUsbServiceImplTest, OpenAndCloseDevice) {
  const auto origin = url::Origin::Create(GURL(kDefaultTestUrl));

  const auto service_creation_type = GetParam();
  const auto& service = GetService(service_creation_type);
  NiceMock<MockWebContentsObserver> web_contents_observer(contents());

  device::MockUsbMojoDevice mock_device;
  auto device_info = ConnectDevice(CreateFakeDevice(), &mock_device);

  TestFuture<std::vector<device::mojom::UsbDeviceInfoPtr>> devices_future;
  service->GetDevices(devices_future.GetCallback());
  EXPECT_THAT(devices_future.Get(), ElementsAre(HasGuid(device_info->guid)));

  mojo::Remote<device::mojom::UsbDevice> device;
  service->GetDevice(device_info->guid, device.BindNewPipeAndPassReceiver());
  CheckIsConnected(service_creation_type, false);

  EXPECT_CALL(web_contents_observer,
              OnCapabilityTypesChanged(WebContentsCapabilityType::kUSB, true))
      .Times(service_creation_type == kCreateForFrame ? 1 : 0)
      .WillOnce([&]() {
        EXPECT_TRUE(
            contents()->IsCapabilityActive(WebContentsCapabilityType::kUSB));
      });
  EXPECT_CALL(mock_device, Open)
      .WillOnce(RunOnceCallback<0>(NewUsbOpenDeviceSuccess()));
  TestFuture<device::mojom::UsbOpenDeviceResultPtr> open_future;
  device->Open(open_future.GetCallback());
  EXPECT_TRUE(open_future.Get()->is_success());
  CheckIsConnected(service_creation_type, true);

  EXPECT_CALL(web_contents_observer,
              OnCapabilityTypesChanged(WebContentsCapabilityType::kUSB, false))
      .Times(service_creation_type == kCreateForFrame ? 1 : 0)
      .WillOnce([&]() {
        EXPECT_FALSE(
            contents()->IsCapabilityActive(WebContentsCapabilityType::kUSB));
      });
  EXPECT_CALL(mock_device, Close).WillOnce(RunOnceClosure<0>());
  base::RunLoop run_loop;
  device->Close(run_loop.QuitClosure());
  run_loop.Run();
  CheckIsConnected(service_creation_type, false);
}

TEST_P(WebUsbServiceImplTest, OpenAndDisconnectDevice) {
  const auto service_creation_type = GetParam();
  const auto origin = url::Origin::Create(GURL(kDefaultTestUrl));

  const auto& service = GetService(service_creation_type);
  NiceMock<MockWebContentsObserver> web_contents_observer(contents());

  device::MockUsbMojoDevice mock_device;
  auto fake_device_info = CreateFakeDevice();
  auto device_info = ConnectDevice(fake_device_info, &mock_device);

  TestFuture<std::vector<device::mojom::UsbDeviceInfoPtr>> devices_future;
  service->GetDevices(devices_future.GetCallback());
  EXPECT_THAT(devices_future.Get(), ElementsAre(HasGuid(device_info->guid)));

  mojo::Remote<device::mojom::UsbDevice> device;
  service->GetDevice(device_info->guid, device.BindNewPipeAndPassReceiver());
  CheckIsConnected(service_creation_type, false);

  EXPECT_CALL(web_contents_observer,
              OnCapabilityTypesChanged(WebContentsCapabilityType::kUSB, true))
      .Times(service_creation_type == kCreateForFrame ? 1 : 0)
      .WillOnce([&]() {
        EXPECT_TRUE(
            contents()->IsCapabilityActive(WebContentsCapabilityType::kUSB));
      });
  EXPECT_CALL(mock_device, Open)
      .WillOnce(RunOnceCallback<0>(NewUsbOpenDeviceSuccess()));
  TestFuture<device::mojom::UsbOpenDeviceResultPtr> open_future;
  device->Open(open_future.GetCallback());
  EXPECT_TRUE(open_future.Get()->is_success());
  CheckIsConnected(service_creation_type, true);

  base::RunLoop loop;
  EXPECT_CALL(mock_device, Close).WillOnce([&]() { loop.Quit(); });
  EXPECT_CALL(web_contents_observer,
              OnCapabilityTypesChanged(WebContentsCapabilityType::kUSB, false))
      .Times(service_creation_type == kCreateForFrame ? 1 : 0)
      .WillOnce([&]() {
        EXPECT_FALSE(
            contents()->IsCapabilityActive(WebContentsCapabilityType::kUSB));
      });
  DisconnectDevice(fake_device_info);
  loop.Run();
  CheckIsConnected(service_creation_type, false);
}

INSTANTIATE_TEST_SUITE_P(WebUsbServiceImplTests,
                         WebUsbServiceImplTest,
                          #if !BUILDFLAG(IS_ANDROID)
                            Values(kCreateForFrame, kCreateForServiceWorker),
                          #else
                            Values(kCreateForFrame),
                          #endif
                         [](const auto& info) {
                           return ServiceCreationTypeToString(info.param);
                         });

using WebUsbServiceImplFrameTest = WebUsbServiceImplBaseTest;

TEST_F(WebUsbServiceImplFrameTest, GetPermission) {
  const auto& service = GetService(kCreateForFrame);

  contents()->GetPrimaryMainFrame()->SimulateUserActivation();

  auto device_info = ConnectDevice(CreateFakeDevice(), nullptr);
  EXPECT_CALL(delegate(), CanRequestDevicePermission).WillOnce(Return(true));
  EXPECT_CALL(delegate(), RunChooserInternal)
      .WillOnce(Return(testing::ByMove(device_info->Clone())));

  TestFuture<device::mojom::UsbDeviceInfoPtr> future;
  service->GetPermission(blink::mojom::WebUsbRequestDeviceOptions::New(),
                         future.GetCallback());
  EXPECT_FALSE(future.Get().is_null());
}

TEST_F(WebUsbServiceImplFrameTest, GetPermissionWithoutUserActivation) {
  const auto& service = GetService(kCreateForFrame);

  ON_CALL(delegate(), CanRequestDevicePermission).WillByDefault(Return(true));
  EXPECT_CALL(delegate(), RunChooserInternal).Times(0);

  TestFuture<device::mojom::UsbDeviceInfoPtr> future;
  service->GetPermission(blink::mojom::WebUsbRequestDeviceOptions::New(),
                         future.GetCallback());
  EXPECT_TRUE(future.Get().is_null());
}

TEST_F(WebUsbServiceImplFrameTest, OpenAndNavigateCrossOrigin) {
  const auto origin = url::Origin::Create(GURL(kDefaultTestUrl));

  auto service_creation_type = kCreateForFrame;
  const auto& service = GetService(service_creation_type);
  NiceMock<MockWebContentsObserver> web_contents_observer(contents());

  device::MockUsbMojoDevice mock_device;
  auto device_info = ConnectDevice(CreateFakeDevice(), &mock_device);

  TestFuture<std::vector<device::mojom::UsbDeviceInfoPtr>> devices_future;
  service->GetDevices(devices_future.GetCallback());
  EXPECT_THAT(devices_future.Get(), ElementsAre(HasGuid(device_info->guid)));

  mojo::Remote<device::mojom::UsbDevice> device;
  service->GetDevice(device_info->guid, device.BindNewPipeAndPassReceiver());
  CheckIsConnected(service_creation_type, false);

  EXPECT_CALL(web_contents_observer,
              OnCapabilityTypesChanged(WebContentsCapabilityType::kUSB, true))
      .WillOnce([&]() {
        EXPECT_TRUE(
            contents()->IsCapabilityActive(WebContentsCapabilityType::kUSB));
      });
  EXPECT_CALL(mock_device, Open)
      .WillOnce(RunOnceCallback<0>(NewUsbOpenDeviceSuccess()));
  TestFuture<device::mojom::UsbOpenDeviceResultPtr> open_future;
  device->Open(open_future.GetCallback());
  EXPECT_TRUE(open_future.Get()->is_success());
  CheckIsConnected(service_creation_type, true);

  base::RunLoop loop;
  EXPECT_CALL(mock_device, Close).WillOnce([&]() { loop.Quit(); });
  EXPECT_CALL(web_contents_observer,
              OnCapabilityTypesChanged(WebContentsCapabilityType::kUSB, false))
      .WillOnce([&]() {
        EXPECT_FALSE(
            contents()->IsCapabilityActive(WebContentsCapabilityType::kUSB));
      });
  contents()->NavigateAndCommit(GURL(kCrossOriginTestUrl));
  loop.Run();
  CheckIsConnected(service_creation_type, false);
}

TEST_F(WebUsbServiceImplFrameTest, RejectOpaqueOrigin) {
  // Create a fake dispatch context to trigger a bad message in.
  mojo::FakeMessageDispatchContext fake_dispatch_context;
  mojo::test::BadMessageObserver bad_message_observer;

  auto response_headers =
      base::MakeRefCounted<net::HttpResponseHeaders>(std::string());
  response_headers->SetHeader("Content-Security-Policy",
                              "sandbox allow-scripts");
  auto* web_contents = static_cast<TestWebContents*>(
      web_contents_factory().CreateWebContents(&browser_context()));
  auto navigation_simulator = NavigationSimulator::CreateRendererInitiated(
      GURL("https://opaque.com"), web_contents->GetPrimaryMainFrame());
  navigation_simulator->SetResponseHeaders(response_headers);
  navigation_simulator->Start();
  navigation_simulator->Commit();
  EXPECT_TRUE(
      web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin().opaque());

  mojo::Remote<blink::mojom::WebUsbService> service;
  web_contents->GetPrimaryMainFrame()->CreateWebUsbService(
      service.BindNewPipeAndPassReceiver());
  EXPECT_EQ(bad_message_observer.WaitForBadMessage(),
            "WebUSB is not allowed when the top-level document has an "
            "opaque origin.");
}

TEST_F(WebUsbServiceImplFrameTest, RejectOpaqueOriginEmbeddedFrame) {
  // Create a fake dispatch context to trigger a bad message in.
  mojo::FakeMessageDispatchContext fake_dispatch_context;
  mojo::test::BadMessageObserver bad_message_observer;
  auto* web_contents = static_cast<TestWebContents*>(
      web_contents_factory().CreateWebContents(&browser_context()));

  auto response_headers =
      base::MakeRefCounted<net::HttpResponseHeaders>(std::string());
  response_headers->SetHeader("Content-Security-Policy",
                              "sandbox allow-scripts");
  auto navigation_simulator = NavigationSimulator::CreateRendererInitiated(
      GURL("https://opaque.com"), web_contents->GetPrimaryMainFrame());
  navigation_simulator->SetResponseHeaders(response_headers);
  navigation_simulator->Start();
  navigation_simulator->Commit();
  EXPECT_TRUE(
      web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin().opaque());

  const GURL kEmbeddedUrl("https://non-opaque");
  RenderFrameHost* embedded_rfh =
      RenderFrameHostTester::For(web_contents->GetPrimaryMainFrame())
          ->AppendChildWithPolicy(
              "embedded_frame",
              {{network::mojom::PermissionsPolicyFeature::kUsb,
                /*allowed_origins=*/{},
                /*self_if_matches=*/url::Origin::Create(kEmbeddedUrl),
                /*matches_all_origins=*/false, /*matches_opaque_src=*/true}});
  embedded_rfh = NavigationSimulator::NavigateAndCommitFromDocument(
      kEmbeddedUrl, embedded_rfh);

  mojo::Remote<blink::mojom::WebUsbService> service;
  static_cast<TestRenderFrameHost*>(embedded_rfh)
      ->CreateWebUsbService(service.BindNewPipeAndPassReceiver());
  EXPECT_EQ(bad_message_observer.WaitForBadMessage(),
            "WebUSB is not allowed when the top-level document has an "
            "opaque origin.");
}

using WebUsbServiceImplServiceWorkerTest = WebUsbServiceImplBaseTest;

#if !BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_DESKTOP_ANDROID)
TEST_F(WebUsbServiceImplServiceWorkerTest,
       GetPermissionFromServiceWorkerReportsBadMessage) {
  // Create the service via the service worker path, which sets
  // render_frame_host_ to nullptr.
  const auto& service = GetService(kCreateForServiceWorker);

  // Set up the delegate to allow the permission request to pass through
  // the initial checks so execution reaches the null dereference.
  EXPECT_CALL(delegate(), CanRequestDevicePermission).WillOnce(Return(true));

  // Set up the bad message observer to catch the expected ReportBadMessage.
  mojo::FakeMessageDispatchContext fake_dispatch_context;
  mojo::test::BadMessageObserver bad_message_observer;

  // Call GetPermission with empty options. Without the fix, this crashes
  // the browser process due to dereferencing a null render_frame_host_.
  auto options = blink::mojom::WebUsbRequestDeviceOptions::New();
  TestFuture<device::mojom::UsbDeviceInfoPtr> future;
  service->GetPermission(std::move(options), future.GetCallback());

  EXPECT_EQ(bad_message_observer.WaitForBadMessage(),
            "GetPermission is not allowed from a service worker.");
  EXPECT_FALSE(future.Get());
}
#endif  // !BUILDFLAG(IS_ANDROID)

class WebUsbServiceImplProtectedInterfaceTest
    : public WebUsbServiceImplBaseTest,
      public WithParamInterface<std::tuple<ServiceCreationType, uint8_t>> {};

TEST_P(WebUsbServiceImplProtectedInterfaceTest, BlockProtectedInterface) {
  const auto service_creation_type = std::get<0>(GetParam());
  const auto class_code = std::get<1>(GetParam());

  auto device_info =
      ConnectDevice(CreateFakeDeviceWithProtectedInterface(class_code),
                    /*mock_device=*/nullptr);

  const auto& service = GetService(service_creation_type);
  TestFuture<std::vector<device::mojom::UsbDeviceInfoPtr>> devices_future;
  service->GetDevices(devices_future.GetCallback());
  EXPECT_THAT(devices_future.Get(), ElementsAre(HasGuid(device_info->guid)));

  mojo::Remote<device::mojom::UsbDevice> device;
  service->GetDevice(device_info->guid, device.BindNewPipeAndPassReceiver());

  TestFuture<device::mojom::UsbOpenDeviceResultPtr> open_future;
  device->Open(open_future.GetCallback());
  EXPECT_TRUE(open_future.Get()->is_success());

  TestFuture<bool> set_configuration_future;
  device->SetConfiguration(1, set_configuration_future.GetCallback());
  EXPECT_TRUE(set_configuration_future.Get());

  TestFuture<device::mojom::UsbClaimInterfaceResult> claim_interface0_future;
  device->ClaimInterface(0, claim_interface0_future.GetCallback());
  EXPECT_EQ(claim_interface0_future.Get(),
            device::mojom::UsbClaimInterfaceResult::kProtectedClass);

  TestFuture<device::mojom::UsbClaimInterfaceResult> claim_interface1_future;
  device->ClaimInterface(1, claim_interface1_future.GetCallback());
  EXPECT_EQ(claim_interface1_future.Get(),
            device::mojom::UsbClaimInterfaceResult::kSuccess);
}

INSTANTIATE_TEST_SUITE_P(
    WebUsbServiceImplProtectedInterfaceTests,
    WebUsbServiceImplProtectedInterfaceTest,
#if !BUILDFLAG(IS_ANDROID)
    Combine(Values(kCreateForFrame, kCreateForServiceWorker),
#else
    Combine(Values(kCreateForFrame),
#endif
            Values(device::mojom::kUsbAudioClass,
                   device::mojom::kUsbHidClass,
                   device::mojom::kUsbMassStorageClass,
                   device::mojom::kUsbSmartCardClass,
                   device::mojom::kUsbVideoClass,
                   device::mojom::kUsbAudioVideoClass,
                   device::mojom::kUsbWirelessClass)),
    [](const TestParamInfo<std::tuple<ServiceCreationType, uint8_t>>& info) {
      return base::StringPrintf(
          "%s_%s", ServiceCreationTypeToString(std::get<0>(info.param)).c_str(),
          ClassCodeToString(std::get<1>(info.param)).c_str());
    });

}  // namespace content
