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

#include "extensions/common/permissions/usb_device_permission_data.h"

#include <stdint.h>

#include <limits>
#include <memory>
#include <string>
#include <vector>

#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/values.h"
#include "extensions/common/permissions/api_permission.h"
#include "extensions/common/permissions/usb_device_permission.h"

namespace extensions {

namespace {

const char kProductIdKey[] = "productId";
const char kVendorIdKey[] = "vendorId";
const char kInterfaceIdKey[] = "interfaceId";
const char kInterfaceClassKey[] = "interfaceClass";

bool ExtractFromDict(const std::string& key,
                     const base::DictValue* dict_value,
                     int max,
                     int* value) {
  std::optional<int> temp = dict_value->FindInt(key);
  if (!temp) {
    *value = UsbDevicePermissionData::SPECIAL_VALUE_ANY;
    return true;
  }

  if (*temp < UsbDevicePermissionData::SPECIAL_VALUE_ANY || *temp > max)
    return false;

  *value = *temp;
  return true;
}

}  // namespace

UsbDevicePermissionData::UsbDevicePermissionData() = default;

UsbDevicePermissionData::UsbDevicePermissionData(int vendor_id,
                                                 int product_id,
                                                 int interface_id,
                                                 int interface_class)
    : vendor_id_(vendor_id),
      product_id_(product_id),
      interface_id_(interface_id),
      interface_class_(interface_class) {}

bool UsbDevicePermissionData::Check(
    const APIPermission::CheckParam* param) const {
  if (!param)
    return false;
  const UsbDevicePermission::CheckParam& specific_param =
      *static_cast<const UsbDevicePermission::CheckParam*>(param);

  // The permission should be ignored if it filters by interface class when
  // filtering by interface class is not allowed.
  if (!specific_param.interface_class_allowed &&
      interface_class_ != SPECIAL_VALUE_ANY) {
    return false;
  }
  DCHECK(specific_param.interface_class_allowed ||
         (vendor_id_ != SPECIAL_VALUE_ANY && product_id_ != SPECIAL_VALUE_ANY));

  return (vendor_id_ == SPECIAL_VALUE_ANY ||
          vendor_id_ == specific_param.vendor_id) &&
         (product_id_ == SPECIAL_VALUE_ANY ||
          product_id_ == specific_param.product_id) &&
         (specific_param.interface_id == SPECIAL_VALUE_UNSPECIFIED ||
          interface_id_ == specific_param.interface_id) &&
         (!specific_param.interface_classes ||
          interface_class_ == SPECIAL_VALUE_ANY ||
          specific_param.interface_classes->count(interface_class_) > 0);
}

std::unique_ptr<base::Value> UsbDevicePermissionData::ToValue() const {
  base::DictValue result;
  result.Set(kVendorIdKey, vendor_id_);
  result.Set(kProductIdKey, product_id_);
  result.Set(kInterfaceIdKey, interface_id_);
  result.Set(kInterfaceClassKey, interface_class_);
  return std::make_unique<base::Value>(std::move(result));
}

bool UsbDevicePermissionData::FromValue(const base::Value* value) {
  if (!value)
    return false;

  const base::DictValue* dict_value = value->GetIfDict();
  if (!dict_value)
    return false;

  const int kMaxId = std::numeric_limits<uint16_t>::max();
  if (!ExtractFromDict(kVendorIdKey, dict_value, kMaxId, &vendor_id_))
    return false;

  if (!ExtractFromDict(kProductIdKey, dict_value, kMaxId, &product_id_))
    return false;
  // If product ID is specified, so should be vendor ID.
  if (product_id_ != SPECIAL_VALUE_ANY && vendor_id_ == SPECIAL_VALUE_ANY)
    return false;

  const int kMaxInterfaceData = std::numeric_limits<uint8_t>::max();
  if (!ExtractFromDict(kInterfaceIdKey, dict_value, kMaxInterfaceData,
                       &interface_id_)) {
    return false;
  }
  // If interface ID is specified, so should be vendor ID and product ID (note
  // that product ID being set implies that vendor ID is set).
  if (interface_id_ != SPECIAL_VALUE_ANY && product_id_ == SPECIAL_VALUE_ANY)
    return false;

  if (!ExtractFromDict(kInterfaceClassKey, dict_value, kMaxInterfaceData,
                       &interface_class_)) {
    return false;
  }

  // Reject the permission if neither interface class nor vendor ID, product ID
  // pair is specified in the permission.
  // Note that set product ID implies that vendor ID is set as well, so only
  // product ID has to be checked.
  if (interface_class_ == SPECIAL_VALUE_ANY && product_id_ == SPECIAL_VALUE_ANY)
    return false;

  // Interface ID is ignored, but kept for backward compatibility - don't allow
  // it's usage with interface class, as interface class property was introduced
  // after interface ID support was dropped.
  if (interface_class_ != SPECIAL_VALUE_ANY &&
      interface_id_ != SPECIAL_VALUE_ANY) {
    return false;
  }

  return true;
}

}  // namespace extensions
