// 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.

#ifndef EXTENSIONS_DEVICE_PERMISSION_MANAGER_H_
#define EXTENSIONS_DEVICE_PERMISSION_MANAGER_H_

#include <stdint.h>

#include <map>
#include <memory>
#include <set>
#include <vector>

#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/scoped_observer.h"
#include "base/strings/string16.h"
#include "base/threading/thread_checker.h"
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
#include "components/keyed_service/core/keyed_service.h"
#include "device/hid/hid_service.h"
#include "device/usb/usb_service.h"

namespace base {
template <typename T>
struct DefaultSingletonTraits;
class Value;
}

namespace content {
class BrowserContext;
}

namespace extensions {

// Stores information about a device saved with access granted.
class DevicePermissionEntry : public base::RefCounted<DevicePermissionEntry> {
 public:
  enum class Type {
    USB,
    HID,
  };

  DevicePermissionEntry(scoped_refptr<device::UsbDevice> device);
  DevicePermissionEntry(scoped_refptr<device::HidDeviceInfo> device);
  DevicePermissionEntry(Type type,
                        uint16_t vendor_id,
                        uint16_t product_id,
                        const base::string16& serial_number,
                        const base::string16& manufacturer_string,
                        const base::string16& product_string,
                        const base::Time& last_used);

  // A persistent device is one that can be recognized when it is reconnected
  // and can therefore be remembered persistently by writing information about
  // it to ExtensionPrefs. Currently this means it has a serial number string.
  bool IsPersistent() const;

  // Convert the device to a serializable value, returns a null pointer if the
  // entry is not persistent.
  std::unique_ptr<base::Value> ToValue() const;

  base::string16 GetPermissionMessageString() const;

  Type type() const { return type_; }
  uint16_t vendor_id() const { return vendor_id_; }
  uint16_t product_id() const { return product_id_; }
  const base::string16& serial_number() const { return serial_number_; }
  const base::Time& last_used() const { return last_used_; }

  base::string16 GetManufacturer() const;
  base::string16 GetProduct() const;

 private:
  friend class base::RefCounted<DevicePermissionEntry>;
  friend class DevicePermissionsManager;

  ~DevicePermissionEntry();

  void set_last_used(const base::Time& last_used) { last_used_ = last_used; }

  // The USB device tracked by this entry. Will be nullptr if this entry was
  // restored from ExtensionPrefs or type_ is not Type::USB.
  scoped_refptr<device::UsbDevice> usb_device_;
  // The HID device tracked by this entry. Will be nullptr if this entry was
  // restored from ExtensionPrefs or type_ is not Type::HID.
  scoped_refptr<device::HidDeviceInfo> hid_device_;

  // The type of device this entry represents.
  Type type_;
  // The vendor ID of this device.
  uint16_t vendor_id_;
  // The product ID of this device.
  uint16_t product_id_;
  // The serial number (possibly alphanumeric) of this device.
  base::string16 serial_number_;
  // The manufacturer string read from the device (optional).
  base::string16 manufacturer_string_;
  // The product string read from the device (optional).
  base::string16 product_string_;
  // The last time this device was used by the extension.
  base::Time last_used_;
};

// Stores device permissions associated with a particular extension.
class DevicePermissions {
 public:
  virtual ~DevicePermissions();

  // Attempts to find a permission entry matching the given device.
  scoped_refptr<DevicePermissionEntry> FindUsbDeviceEntry(
      scoped_refptr<device::UsbDevice> device) const;
  scoped_refptr<DevicePermissionEntry> FindHidDeviceEntry(
      scoped_refptr<device::HidDeviceInfo> device) const;

  const std::set<scoped_refptr<DevicePermissionEntry>>& entries() const {
    return entries_;
  }

 private:
  friend class DevicePermissionsManager;

  // Reads permissions out of ExtensionPrefs.
  DevicePermissions(content::BrowserContext* context,
                    const std::string& extension_id);

  std::set<scoped_refptr<DevicePermissionEntry>> entries_;
  std::map<device::UsbDevice*, scoped_refptr<DevicePermissionEntry>>
      ephemeral_usb_devices_;
  std::map<device::HidDeviceInfo*, scoped_refptr<DevicePermissionEntry>>
      ephemeral_hid_devices_;

  DISALLOW_COPY_AND_ASSIGN(DevicePermissions);
};

// Manages saved device permissions for all extensions.
class DevicePermissionsManager : public KeyedService,
                                 public device::UsbService::Observer,
                                 public device::HidService::Observer {
 public:
  static DevicePermissionsManager* Get(content::BrowserContext* context);

  static base::string16 GetPermissionMessage(
      uint16_t vendor_id,
      uint16_t product_id,
      const base::string16& manufacturer_string,
      const base::string16& product_string,
      const base::string16& serial_number,
      bool always_include_manufacturer);

  // The DevicePermissions object for a given extension.
  DevicePermissions* GetForExtension(const std::string& extension_id);

  // Equivalent to calling GetForExtension and extracting the permission string
  // for each entry.
  std::vector<base::string16> GetPermissionMessageStrings(
      const std::string& extension_id) const;

  void AllowUsbDevice(const std::string& extension_id,
                      scoped_refptr<device::UsbDevice> device);
  void AllowHidDevice(const std::string& extension_id,
                      scoped_refptr<device::HidDeviceInfo> device);

  // Updates the "last used" timestamp on the given device entry and writes it
  // out to ExtensionPrefs.
  void UpdateLastUsed(const std::string& extension_id,
                      scoped_refptr<DevicePermissionEntry> entry);

  // Revokes permission for the extension to access the given device.
  void RemoveEntry(const std::string& extension_id,
                   scoped_refptr<DevicePermissionEntry> entry);

  // Revokes permission for the extension to access all allowed devices.
  void Clear(const std::string& extension_id);

 private:

  friend class DevicePermissionsManagerFactory;
  FRIEND_TEST_ALL_PREFIXES(DevicePermissionsManagerTest, SuspendExtension);

  DevicePermissionsManager(content::BrowserContext* context);
  ~DevicePermissionsManager() override;

  DevicePermissions* GetInternal(const std::string& extension_id) const;

  // UsbService::Observer implementation
  void OnDeviceRemovedCleanup(scoped_refptr<device::UsbDevice> device) override;

  // HidService::Observer implementation
  void OnDeviceRemovedCleanup(
      scoped_refptr<device::HidDeviceInfo> device) override;

  base::ThreadChecker thread_checker_;
  content::BrowserContext* context_;
  std::map<std::string, DevicePermissions*> extension_id_to_device_permissions_;
  ScopedObserver<device::UsbService, device::UsbService::Observer>
      usb_service_observer_;
  ScopedObserver<device::HidService, device::HidService::Observer>
      hid_service_observer_;

  DISALLOW_COPY_AND_ASSIGN(DevicePermissionsManager);
};

class DevicePermissionsManagerFactory
    : public BrowserContextKeyedServiceFactory {
 public:
  static DevicePermissionsManager* GetForBrowserContext(
      content::BrowserContext* context);
  static DevicePermissionsManagerFactory* GetInstance();

 private:
  friend struct base::DefaultSingletonTraits<DevicePermissionsManagerFactory>;

  DevicePermissionsManagerFactory();
  ~DevicePermissionsManagerFactory() override;

  // BrowserContextKeyedServiceFactory implementation
  KeyedService* BuildServiceInstanceFor(
      content::BrowserContext* context) const override;
  content::BrowserContext* GetBrowserContextToUse(
      content::BrowserContext* context) const override;

  DISALLOW_COPY_AND_ASSIGN(DevicePermissionsManagerFactory);
};

}  // namespace extensions

#endif  // EXTENSIONS_DEVICE_PERMISSION_MANAGER_H_
