// 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 "chrome/browser/usb/usb_chooser_context.h"

#include <utility>
#include <vector>

#include "base/metrics/histogram_macros.h"
#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/profiles/profile.h"
#include "device/core/device_client.h"
#include "device/usb/usb_device.h"

using device::UsbDevice;

namespace {

const char kDeviceNameKey[] = "name";
const char kGuidKey[] = "ephemeral-guid";
const char kProductIdKey[] = "product-id";
const char kSerialNumberKey[] = "serial-number";
const char kVendorIdKey[] = "vendor-id";

// Reasons a permission may be closed. These are used in histograms so do not
// remove/reorder entries. Only add at the end just before
// WEBUSB_PERMISSION_REVOKED_MAX. Also remember to update the enum listing in
// tools/metrics/histograms/histograms.xml.
enum WebUsbPermissionRevoked {
  // Permission to access a USB device was revoked by the user.
  WEBUSB_PERMISSION_REVOKED = 0,
  // Permission to access an ephemeral USB device was revoked by the user.
  WEBUSB_PERMISSION_REVOKED_EPHEMERAL,
  // Maximum value for the enum.
  WEBUSB_PERMISSION_REVOKED_MAX
};

void RecordPermissionRevocation(WebUsbPermissionRevoked kind) {
  UMA_HISTOGRAM_ENUMERATION("WebUsb.PermissionRevoked", kind,
                            WEBUSB_PERMISSION_REVOKED_MAX);
}

bool CanStorePersistentEntry(const scoped_refptr<const UsbDevice>& device) {
  return !device->serial_number().empty();
}

const base::DictionaryValue* FindForDevice(
    const std::vector<scoped_ptr<base::DictionaryValue>>& device_list,
    const scoped_refptr<const UsbDevice>& device) {
  const std::string utf8_serial_number =
      base::UTF16ToUTF8(device->serial_number());

  for (const scoped_ptr<base::DictionaryValue>& device_dict : device_list) {
    int vendor_id;
    int product_id;
    std::string serial_number;
    if (device_dict->GetInteger(kVendorIdKey, &vendor_id) &&
        device->vendor_id() == vendor_id &&
        device_dict->GetInteger(kProductIdKey, &product_id) &&
        device->product_id() == product_id &&
        device_dict->GetString(kSerialNumberKey, &serial_number) &&
        utf8_serial_number == serial_number) {
      return device_dict.get();
    }
  }
  return nullptr;
}

}  // namespace

UsbChooserContext::UsbChooserContext(Profile* profile)
    : ChooserContextBase(profile, CONTENT_SETTINGS_TYPE_USB_CHOOSER_DATA),
      is_off_the_record_(profile->IsOffTheRecord()),
      observer_(this) {
  usb_service_ = device::DeviceClient::Get()->GetUsbService();
  if (usb_service_)
    observer_.Add(usb_service_);
}

UsbChooserContext::~UsbChooserContext() {}

std::vector<scoped_ptr<base::DictionaryValue>>
UsbChooserContext::GetGrantedObjects(const GURL& requesting_origin,
                                     const GURL& embedding_origin) {
  std::vector<scoped_ptr<base::DictionaryValue>> objects =
      ChooserContextBase::GetGrantedObjects(requesting_origin,
                                            embedding_origin);

  auto it = ephemeral_devices_.find(
      std::make_pair(requesting_origin, embedding_origin));
  if (it != ephemeral_devices_.end()) {
    for (const std::string& guid : it->second) {
      scoped_refptr<UsbDevice> device = usb_service_->GetDevice(guid);
      DCHECK(device);
      scoped_ptr<base::DictionaryValue> object(new base::DictionaryValue());
      object->SetString(kDeviceNameKey, device->product_string());
      object->SetString(kGuidKey, device->guid());
      objects.push_back(std::move(object));
    }
  }

  return objects;
}

std::vector<scoped_ptr<ChooserContextBase::Object>>
UsbChooserContext::GetAllGrantedObjects() {
  std::vector<scoped_ptr<ChooserContextBase::Object>> objects =
      ChooserContextBase::GetAllGrantedObjects();

  for (const auto& map_entry : ephemeral_devices_) {
    const GURL& requesting_origin = map_entry.first.first;
    const GURL& embedding_origin = map_entry.first.second;
    for (const std::string& guid : map_entry.second) {
      scoped_refptr<UsbDevice> device = usb_service_->GetDevice(guid);
      DCHECK(device);
      base::DictionaryValue object;
      object.SetString(kDeviceNameKey, device->product_string());
      object.SetString(kGuidKey, device->guid());
      objects.push_back(make_scoped_ptr(new ChooserContextBase::Object(
          requesting_origin, embedding_origin, &object, "preference",
          is_off_the_record_)));
    }
  }

  return objects;
}

void UsbChooserContext::RevokeObjectPermission(
    const GURL& requesting_origin,
    const GURL& embedding_origin,
    const base::DictionaryValue& object) {
  std::string guid;
  if (object.GetString(kGuidKey, &guid)) {
    RevokeDevicePermission(requesting_origin, embedding_origin, guid);
    RecordPermissionRevocation(WEBUSB_PERMISSION_REVOKED_EPHEMERAL);
  } else {
    ChooserContextBase::RevokeObjectPermission(requesting_origin,
                                               embedding_origin, object);
    RecordPermissionRevocation(WEBUSB_PERMISSION_REVOKED);
  }
}

void UsbChooserContext::GrantDevicePermission(const GURL& requesting_origin,
                                              const GURL& embedding_origin,
                                              const std::string& guid) {
  scoped_refptr<UsbDevice> device = usb_service_->GetDevice(guid);
  if (!device)
    return;

  if (CanStorePersistentEntry(device)) {
    scoped_ptr<base::DictionaryValue> device_dict(new base::DictionaryValue());
    device_dict->SetString(kDeviceNameKey, device->product_string());
    device_dict->SetInteger(kVendorIdKey, device->vendor_id());
    device_dict->SetInteger(kProductIdKey, device->product_id());
    device_dict->SetString(kSerialNumberKey, device->serial_number());
    GrantObjectPermission(requesting_origin, embedding_origin,
                          std::move(device_dict));
  } else {
    ephemeral_devices_[std::make_pair(requesting_origin, embedding_origin)]
        .insert(guid);
  }
}

void UsbChooserContext::RevokeDevicePermission(const GURL& requesting_origin,
                                               const GURL& embedding_origin,
                                               const std::string& guid) {
  auto it = ephemeral_devices_.find(
      std::make_pair(requesting_origin, embedding_origin));
  if (it != ephemeral_devices_.end()) {
    it->second.erase(guid);
    if (it->second.empty())
      ephemeral_devices_.erase(it);
  }

  scoped_refptr<UsbDevice> device = usb_service_->GetDevice(guid);
  if (!device)
    return;

  std::vector<scoped_ptr<base::DictionaryValue>> device_list =
      GetGrantedObjects(requesting_origin, embedding_origin);
  const base::DictionaryValue* entry = FindForDevice(device_list, device);
  if (entry)
    RevokeObjectPermission(requesting_origin, embedding_origin, *entry);
}

bool UsbChooserContext::HasDevicePermission(const GURL& requesting_origin,
                                            const GURL& embedding_origin,
                                            const std::string& guid) {
  auto it = ephemeral_devices_.find(
      std::make_pair(requesting_origin, embedding_origin));
  if (it != ephemeral_devices_.end())
    return ContainsValue(it->second, guid);

  scoped_refptr<UsbDevice> device = usb_service_->GetDevice(guid);
  if (!device)
    return false;

  std::vector<scoped_ptr<base::DictionaryValue>> device_list =
      GetGrantedObjects(requesting_origin, embedding_origin);
  return FindForDevice(device_list, device) != nullptr;
}

bool UsbChooserContext::IsValidObject(const base::DictionaryValue& object) {
  return object.size() == 4 && object.HasKey(kDeviceNameKey) &&
         object.HasKey(kVendorIdKey) && object.HasKey(kProductIdKey) &&
         object.HasKey(kSerialNumberKey);
}

void UsbChooserContext::OnDeviceRemoved(scoped_refptr<UsbDevice> device) {
  for (auto& map_entry : ephemeral_devices_)
    map_entry.second.erase(device->guid());
}