/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/** \file
  This file consists of implementation of helper routines used
  in the API.
*/

#include "stdafx.h"
#include "adb_api.h"
#include "adb_api_legacy.h"
#include "adb_helper_routines.h"
#include "adb_interface_enum.h"

bool GetSDKComplientParam(AdbOpenAccessType access_type,
                          AdbOpenSharingMode sharing_mode,
                          ULONG* desired_access,
                          ULONG* desired_sharing) {
  if (NULL != desired_access) {
    switch (access_type) {
      case AdbOpenAccessTypeReadWrite:
        *desired_access = GENERIC_READ | GENERIC_WRITE;
        break;

      case AdbOpenAccessTypeRead:
        *desired_access = GENERIC_READ;
        break;

      case AdbOpenAccessTypeWrite:
        *desired_access = GENERIC_WRITE;
        break;

      case AdbOpenAccessTypeQueryInfo:
        *desired_access = FILE_READ_ATTRIBUTES | FILE_READ_EA;
        break;

      default:
        SetLastError(ERROR_INVALID_ACCESS);
        return false;
    }
  }

  if (NULL != desired_sharing) {
    switch (sharing_mode) {
      case AdbOpenSharingModeReadWrite:
        *desired_sharing = FILE_SHARE_READ | FILE_SHARE_WRITE;
        break;

      case AdbOpenSharingModeRead:
        *desired_sharing = FILE_SHARE_READ;
        break;

      case AdbOpenSharingModeWrite:
        *desired_sharing = FILE_SHARE_WRITE;
        break;

      case AdbOpenSharingModeExclusive:
        *desired_sharing = 0;
        break;

      default:
        SetLastError(ERROR_INVALID_PARAMETER);
        return false;
    }
  }

  return true;
}

bool EnumerateDeviceInterfaces(HDEVINFO hardware_dev_info,
                               GUID class_id,
                               bool exclude_removed,
                               bool active_only,
                               AdbEnumInterfaceArray* interfaces) {
  AdbEnumInterfaceArray tmp;
  bool ret = false;

  // Enumerate interfaces on this device
  for (ULONG index = 0; ; index++) {
    SP_DEVICE_INTERFACE_DATA interface_data;
    interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);

    // SetupDiEnumDeviceInterfaces() returns information about device
    // interfaces exposed by one or more devices defined by our interface
    // class. Each call returns information about one interface. The routine
    // can be called repeatedly to get information about several interfaces
    // exposed by one or more devices.
    if (SetupDiEnumDeviceInterfaces(hardware_dev_info,
                                    0, 
                                    &class_id,
                                    index,
                                    &interface_data)) {
      // Satisfy "exclude removed" and "active only" filters.
      if ((!exclude_removed || (0 == (interface_data.Flags & SPINT_REMOVED))) &&
          (!active_only || (interface_data.Flags & SPINT_ACTIVE))) {
        std::wstring dev_name;

        if (GetUsbDeviceName(hardware_dev_info, &interface_data, &dev_name)) {
          try {
            // Add new entry to the array
            tmp.push_back(AdbInstanceEnumEntry(dev_name.c_str(),
                                               interface_data.InterfaceClassGuid,
                                               interface_data.Flags));
          } catch (... ) {
            SetLastError(ERROR_OUTOFMEMORY);
            break;
          }
        } else {
          // Something went wrong in getting device name
          break;
        }
      }
    } else {
      if (ERROR_NO_MORE_ITEMS == GetLastError()) {
        // There are no more items in the list. Enum is completed.
        ret = true;
        break;
      } else {
        // Something went wrong in SDK enum
        break;
      }
    }
  }

  // On success, swap temp array with the returning one
  if (ret)
    interfaces->swap(tmp);

  return ret;
}

bool EnumerateDeviceInterfaces(GUID class_id,
                               ULONG flags,
                               bool exclude_removed,
                               bool active_only,
                               AdbEnumInterfaceArray* interfaces) {
  // Open a handle to the plug and play dev node.
  // SetupDiGetClassDevs() returns a device information set that
  // contains info on all installed devices of a specified class.
  HDEVINFO hardware_dev_info =
    SetupDiGetClassDevs(&class_id, NULL, NULL, flags);

  bool ret = false;

  if (INVALID_HANDLE_VALUE != hardware_dev_info) {
    // Do the enum
    ret = EnumerateDeviceInterfaces(hardware_dev_info,
                                    class_id,
                                    exclude_removed,
                                    active_only,
                                    interfaces);

    // Preserve last error accross hardware_dev_info destruction
    ULONG error_to_report = ret ? NO_ERROR : GetLastError();

    SetupDiDestroyDeviceInfoList(hardware_dev_info);

    if (NO_ERROR != error_to_report)
      SetLastError(error_to_report);
  }

  return ret;
}

bool GetUsbDeviceDetails(
    HDEVINFO hardware_dev_info,
    PSP_DEVICE_INTERFACE_DATA dev_info_data,
    PSP_DEVICE_INTERFACE_DETAIL_DATA* dev_info_detail_data) {
  ULONG required_len = 0;

  // First query for the structure size. At this point we expect this call
  // to fail with ERROR_INSUFFICIENT_BUFFER error code.
  if (SetupDiGetDeviceInterfaceDetail(hardware_dev_info,
                                      dev_info_data,
                                      NULL,
                                      0,
                                      &required_len,
                                      NULL)) {
    return false;
  }

  if (ERROR_INSUFFICIENT_BUFFER != GetLastError())
    return false;

  // Allocate buffer for the structure
  PSP_DEVICE_INTERFACE_DETAIL_DATA buffer =
    reinterpret_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA>(malloc(required_len));

  if (NULL == buffer) {
    SetLastError(ERROR_OUTOFMEMORY);
    return false;
  }

  buffer->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);

  // Retrieve the information from Plug and Play.
  if (SetupDiGetDeviceInterfaceDetail(hardware_dev_info,
                                      dev_info_data,
                                      buffer,
                                      required_len,
                                      &required_len,
                                      NULL)) {
    *dev_info_detail_data = buffer;
    return true;
  } else {
    // Free the buffer if this call failed
    free(buffer);

    return false;
  }
}

bool GetUsbDeviceName(HDEVINFO hardware_dev_info,
                      PSP_DEVICE_INTERFACE_DATA dev_info_data,
                      std::wstring* name) {
  PSP_DEVICE_INTERFACE_DETAIL_DATA func_class_dev_data = NULL;
  if (!GetUsbDeviceDetails(hardware_dev_info,
                           dev_info_data,
                           &func_class_dev_data)) {
    return false;
  }

  try {
    *name = func_class_dev_data->DevicePath;
  } catch (...) {
    SetLastError(ERROR_OUTOFMEMORY);
  }

  free(func_class_dev_data);

  return !name->empty();
}

bool IsLegacyInterface(const wchar_t* interface_name) {
  // Open USB device for this intefface
  HANDLE usb_device_handle = CreateFile(interface_name,
                                        GENERIC_READ | GENERIC_WRITE,
                                        FILE_SHARE_READ | FILE_SHARE_WRITE,
                                        NULL,
                                        OPEN_EXISTING,
                                        0,
                                        NULL);
  if (INVALID_HANDLE_VALUE == usb_device_handle)
    return NULL;

  // Try to issue ADB_IOCTL_GET_USB_DEVICE_DESCRIPTOR IOCTL that is supported
  // by the legacy driver, but is not implemented in the WinUsb driver.
  DWORD ret_bytes = 0;
  USB_DEVICE_DESCRIPTOR descriptor;
  BOOL ret = DeviceIoControl(usb_device_handle,
                             ADB_IOCTL_GET_USB_DEVICE_DESCRIPTOR,
                             NULL, 0,
                             &descriptor,
                             sizeof(descriptor),
                             &ret_bytes,
                             NULL);
  ::CloseHandle(usb_device_handle);

  // If IOCTL succeeded we've got legacy driver underneath.
  return ret ? true : false;
}
